Skip to content

Commit e9932f6

Browse files
authored
added wait for service external Ip assignment (#11325)
* service wait for ip * updated task.json * code refactoring * addressed review comments * addressing review comments * some more review comments * added test for service IP
1 parent 98a2e11 commit e9932f6

File tree

8 files changed

+103
-5
lines changed

8 files changed

+103
-5
lines changed

Tasks/KubernetesManifestV0/Strings/resources.resjson/en-US/resources.resjson

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,5 +74,9 @@
7474
"loc.messages.InvalidPromotetActionDeploymentStrategy": "Promote action works only with strategy: canary",
7575
"loc.messages.AllContainersNotInReadyState": "All the containers are not in a ready state.",
7676
"loc.messages.CouldNotDeterminePodStatus": "Could not determine the pod's status due to the error: %s",
77-
"loc.messages.KubectlShouldBeUpgraded": "kubectl client version equal to v1.14 or higher is required to use kustomize features."
77+
"loc.messages.KubectlShouldBeUpgraded": "kubectl client version equal to v1.14 or higher is required to use kustomize features.",
78+
"loc.messages.CouldNotDetermineServiceStatus": "Could not determine the service %s status due to the error: %s",
79+
"loc.messages.waitForServiceIpAssignment": "Waiting for service %s external IP assignment",
80+
"loc.messages.waitForServiceIpAssignmentTimedOut": "Wait for service %s external IP assignment timed out",
81+
"loc.messages.ServiceExternalIP": "service %s external IP is %s"
7882
}

Tasks/KubernetesManifestV0/Tests/L0.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ describe('Kubernetes Manifests Suite', function () {
4545
process.env[shared.TestEnvVars.imagePullSecrets] = 'test-key1\ntest-key2';
4646
tr.run();
4747
assert(tr.succeeded, 'task should have succeeded');
48+
assert(tr.stdout.indexOf('nginx-service 104.211.243.77') != -1, 'nginx-service external IP is 104.211.243.77')
4849
done();
4950
});
5051

Tasks/KubernetesManifestV0/Tests/TestSetup.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,11 @@ a.exec[`${kubectlPath} scale ${process.env[shared.TestEnvVars.kind]}/${process.e
247247
stdout: 'created secret'
248248
}
249249

250+
a.exec[`${kubectlPath} get service/nginx-service -o json --namespace testnamespace`] = {
251+
'code': 0,
252+
'stdout': '{\r\n "apiVersion": "v1",\r\n "kind": "Service",\r\n "metadata": {\r\n "annotations": {\r\n "azure-pipelines/jobName": "Agent phase",\r\n "azure-pipelines/org": "https://codedev.ms/anchauh/",\r\n "azure-pipelines/pipeline": "aksCd-153 - 64 - CD",\r\n "azure-pipelines/pipelineId": "40",\r\n "azure-pipelines/project": "nginx",\r\n "azure-pipelines/run": "41",\r\n "azure-pipelines/runuri": "https://codedev.ms/anchauh/nginx/_releaseProgress?releaseId=41",\r\n "kubectl.kubernetes.io/last-applied-configuration": "{\\"apiVersion\\":\\"v1\\",\\"kind\\":\\"Service\\",\\"metadata\\":{\\"annotations\\":{},\\"labels\\":{\\"app\\":\\"nginx\\"},\\"name\\":\\"nginx-service\\",\\"namespace\\":\\"testnamespace\\"},\\"spec\\":{\\"ports\\":[{\\"name\\":\\"http\\",\\"port\\":80,\\"protocol\\":\\"TCP\\",\\"targetPort\\":\\"http\\"}],\\"selector\\":{\\"app\\":\\"nginx\\"},\\"type\\":\\"LoadBalancer\\"}}\\n"\r\n },\r\n "creationTimestamp": "2019-09-11T10:09:09Z",\r\n "labels": {\r\n "app": "nginx"\r\n },\r\n "name": "nginx-service",\r\n "namespace": "testnamespace",\r\n "resourceVersion": "8754335",\r\n "selfLink": "/api/v1/namespaces/testnamespace/services/nginx-service",\r\n "uid": "31f02713-d47c-11e9-9448-16b93c17a2b4"\r\n },\r\n "spec": {\r\n "clusterIP": "10.0.157.189",\r\n "externalTrafficPolicy": "Cluster",\r\n "ports": [\r\n {\r\n "name": "http",\r\n "nodePort": 32112,\r\n "port": 80,\r\n "protocol": "TCP",\r\n "targetPort": "http"\r\n }\r\n ],\r\n "selector": {\r\n "app": "nginx"\r\n },\r\n "sessionAffinity": "***",\r\n "type": "LoadBalancer"\r\n },\r\n "status": {\r\n "loadBalancer": {\r\n "ingress": [\r\n {\r\n "ip": "104.211.243.77"\r\n }\r\n ]\r\n }\r\n }\r\n }'
253+
}
254+
250255
const pipelineAnnotations: string = [
251256
`azure-pipelines/run=${buildNumber}`,
252257
`azure-pipelines/pipeline="${definitionName}"`,
@@ -282,6 +287,7 @@ if (process.env[shared.TestEnvVars.arguments]) {
282287
};
283288
}
284289

290+
285291
tr.setAnswers(<any>a);
286292
tr.registerMock('azure-pipelines-task-lib/toolrunner', require('azure-pipelines-task-lib/mock-toolrunner'));
287293

@@ -324,6 +330,7 @@ tr.registerMock('../utils/FileHelper', {
324330
});
325331

326332
if (newFilePaths.length === 0) {
333+
console.log(shared.ManifestFilesPath);
327334
newFilePaths.push(shared.ManifestFilesPath);
328335
}
329336
return newFilePaths;

Tasks/KubernetesManifestV0/Tests/manifests/deployment.yaml

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,22 @@ spec:
2020
ports:
2121
- containerPort: 80
2222
imagePullSecrets:
23-
- name: key1
23+
- name: key1
24+
25+
---
26+
27+
apiVersion: v1
28+
kind: Service
29+
metadata:
30+
name: nginx-service
31+
labels:
32+
app: nginx
33+
spec:
34+
type: LoadBalancer
35+
ports:
36+
- port: 80
37+
targetPort: http
38+
protocol: TCP
39+
name: http
40+
selector:
41+
app: nginx

Tasks/KubernetesManifestV0/src/models/constants.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,17 @@ export class KubernetesWorkload {
1313
public static cronjob: string = 'cronjob';
1414
}
1515

16+
export class DiscoveryAndLoadBalancerResource {
17+
public static service: string = 'service';
18+
public static ingress: string = 'ingress';
19+
}
20+
21+
export class ServiceTypes {
22+
public static loadBalancer: string = 'LoadBalancer';
23+
public static nodePort: string = 'NodePort';
24+
public static clusterIP: string = 'ClusterIP'
25+
}
26+
1627
export const deploymentTypes: string[] = ['deployment', 'replicaset', 'daemonset', 'pod', 'statefulset'];
1728
export const workloadTypes: string[] = ['deployment', 'replicaset', 'daemonset', 'pod', 'statefulset', 'job', 'cronjob'];
1829
export const workloadTypesWithRolloutStatus: string[] = ['deployment', 'daemonset', 'statefulset'];

Tasks/KubernetesManifestV0/src/utils/DeploymentHelper.ts

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,15 @@ export async function deploy(kubectl: Kubectl, manifestFilePaths: string[], depl
3434
const deployedManifestFiles = deployManifests(inputManifestFiles, kubectl, isCanaryDeploymentStrategy(deploymentStrategy));
3535

3636
// check manifest stability
37-
const resourceTypes: Resource[] = KubernetesObjectUtility.getResources(deployedManifestFiles, models.deploymentTypes);
37+
const resourceTypes: Resource[] = KubernetesObjectUtility.getResources(deployedManifestFiles, models.deploymentTypes.concat([constants.DiscoveryAndLoadBalancerResource.service]));
3838
await checkManifestStability(kubectl, resourceTypes);
3939

40+
// print ingress resources
41+
const ingressResources: Resource[] = KubernetesObjectUtility.getResources(deployedManifestFiles, [constants.DiscoveryAndLoadBalancerResource.ingress]);
42+
ingressResources.forEach(ingressResource => {
43+
kubectl.getResource(constants.DiscoveryAndLoadBalancerResource.ingress, ingressResource.name);
44+
});
45+
4046
// annotate resources
4147
const allPods = JSON.parse((kubectl.getAllPods()).stdout);
4248
annotateResources(deployedManifestFiles, kubectl, resourceTypes, allPods);
@@ -92,6 +98,21 @@ async function checkManifestStability(kubectl: Kubectl, resources: Resource[]):
9298
tl.warning(tl.loc('CouldNotDeterminePodStatus', JSON.stringify(ex)));
9399
}
94100
}
101+
if (isEqual(resource.type, constants.DiscoveryAndLoadBalancerResource.service, StringComparer.OrdinalIgnoreCase)) {
102+
try {
103+
const service = getService(kubectl, resource.name);
104+
const spec = service.spec;
105+
const status = service.status;
106+
if (isEqual(spec.type, constants.ServiceTypes.loadBalancer, StringComparer.OrdinalIgnoreCase)) {
107+
if(!isLoadBalancerIPAssigned(status)) {
108+
await waitForServiceExternalIPAssignment(kubectl, resource.name);
109+
}
110+
console.log(tl.loc('ServiceExternalIP', resource.name, status.loadBalancer.ingress[0].ip));
111+
}
112+
} catch (ex) {
113+
tl.warning(tl.loc('CouldNotDetermineServiceStatus', resource.name, JSON.stringify(ex)));
114+
}
115+
}
95116
}
96117
utils.checkForErrors(rolloutStatusResults);
97118
}
@@ -263,6 +284,34 @@ function isPodReady(podStatus: any): boolean {
263284
return allContainersAreReady;
264285
}
265286

287+
function getService(kubectl: Kubectl, serviceName) {
288+
const serviceResult = kubectl.getResource(constants.DiscoveryAndLoadBalancerResource.service, serviceName);
289+
utils.checkForErrors([serviceResult]);
290+
return JSON.parse(serviceResult.stdout);
291+
}
292+
293+
async function waitForServiceExternalIPAssignment(kubectl: Kubectl, serviceName: string): Promise<void> {
294+
const sleepTimeout = 10 * 1000; // 10 seconds
295+
const iterations = 18; // 18 * 10 seconds timeout = 3 minutes max timeout
296+
297+
for (let i = 0; i < iterations; i++) {
298+
console.log(tl.loc('waitForServiceIpAssignment', serviceName));
299+
await sleep(sleepTimeout);
300+
let status = getService(kubectl, serviceName).status;
301+
if (isLoadBalancerIPAssigned(status)) {
302+
return;
303+
}
304+
}
305+
tl.warning(tl.loc('waitForServiceIpAssignmentTimedOut', serviceName));
306+
}
307+
308+
function isLoadBalancerIPAssigned(status: any) {
309+
if (status && status.loadBalancer && status.loadBalancer.ingress && status.loadBalancer.ingress.length > 0) {
310+
return true;
311+
}
312+
return false;
313+
}
314+
266315
function sleep(timeout: number) {
267316
return new Promise(resolve => setTimeout(resolve, timeout));
268317
}

Tasks/KubernetesManifestV0/task.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,10 @@
333333
"InvalidPromotetActionDeploymentStrategy": "Promote action works only with strategy: canary",
334334
"AllContainersNotInReadyState": "All the containers are not in a ready state.",
335335
"CouldNotDeterminePodStatus": "Could not determine the pod's status due to the error: %s",
336-
"KubectlShouldBeUpgraded": "kubectl client version equal to v1.14 or higher is required to use kustomize features."
336+
"KubectlShouldBeUpgraded": "kubectl client version equal to v1.14 or higher is required to use kustomize features.",
337+
"CouldNotDetermineServiceStatus": "Could not determine the service %s status due to the error: %s",
338+
"waitForServiceIpAssignment": "Waiting for service %s external IP assignment",
339+
"waitForServiceIpAssignmentTimedOut": "Wait for service %s external IP assignment timed out",
340+
"ServiceExternalIP": "service %s external IP is %s"
337341
}
338342
}

Tasks/KubernetesManifestV0/task.loc.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,10 @@
333333
"InvalidPromotetActionDeploymentStrategy": "ms-resource:loc.messages.InvalidPromotetActionDeploymentStrategy",
334334
"AllContainersNotInReadyState": "ms-resource:loc.messages.AllContainersNotInReadyState",
335335
"CouldNotDeterminePodStatus": "ms-resource:loc.messages.CouldNotDeterminePodStatus",
336-
"KubectlShouldBeUpgraded": "ms-resource:loc.messages.KubectlShouldBeUpgraded"
336+
"KubectlShouldBeUpgraded": "ms-resource:loc.messages.KubectlShouldBeUpgraded",
337+
"CouldNotDetermineServiceStatus": "ms-resource:loc.messages.CouldNotDetermineServiceStatus",
338+
"waitForServiceIpAssignment": "ms-resource:loc.messages.waitForServiceIpAssignment",
339+
"waitForServiceIpAssignmentTimedOut": "ms-resource:loc.messages.waitForServiceIpAssignmentTimedOut",
340+
"ServiceExternalIP": "ms-resource:loc.messages.ServiceExternalIP"
337341
}
338342
}

0 commit comments

Comments
 (0)