Skip to content

Commit 1adb016

Browse files
authored
Merge pull request #44 from pablochacin/check-pod-condition
Add wait option to Pod Create
2 parents 18ac4e2 + 0501cd4 commit 1adb016

File tree

5 files changed

+336
-22
lines changed

5 files changed

+336
-22
lines changed

examples/create-pod-wait.js

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
2+
import { sleep } from 'k6';
3+
import { Kubernetes } from 'k6/x/kubernetes';
4+
5+
export default function () {
6+
const kubernetes = new Kubernetes({
7+
})
8+
const namespace = "default"
9+
const podName = "new-pod"
10+
const image = "busybox"
11+
const command = ["sh", "-c", "/bin/false"]
12+
13+
try {
14+
kubernetes.pods.create({
15+
namespace: namespace,
16+
name: podName,
17+
image: image,
18+
command: command,
19+
wait: "5s"
20+
})
21+
console.log(podName + " has been created")
22+
} catch (err) {
23+
console.log("error creating pod " + podName + ": " + err)
24+
}
25+
}

examples/wait-pod.js

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
2+
import { sleep } from 'k6';
3+
import { Kubernetes } from 'k6/x/kubernetes';
4+
5+
export default function () {
6+
const kubernetes = new Kubernetes({
7+
})
8+
const namespace = "default"
9+
const podName = "new-pod"
10+
const image = "busybox"
11+
const command = ["sh", "-c", "sleep 5"]
12+
13+
kubernetes.pods.create({
14+
namespace: namespace,
15+
name: podName,
16+
image: image,
17+
command: command
18+
})
19+
20+
21+
const options = {
22+
namespace: namespace,
23+
name: podName,
24+
status: "Succeeded",
25+
timeout: "10s"
26+
}
27+
if (kubernetes.pods.wait(options)) {
28+
console.log(podName + " pod completed successfully")
29+
} else {
30+
throw podName + " is not completed"
31+
}
32+
}

internal/testutils/kube_resources.go

+10
Original file line numberDiff line numberDiff line change
@@ -226,9 +226,19 @@ func NewPod(name string, namespace string) *coreV1.Pod {
226226
},
227227
EphemeralContainers: nil,
228228
},
229+
Status: coreV1.PodStatus{
230+
Phase: coreV1.PodRunning,
231+
},
229232
}
230233
}
231234

235+
// NewPodWithStatus is a helper for building Pods with a given Status
236+
func NewPodWithStatus(name string, namespace string, phase string) *coreV1.Pod {
237+
pod := NewPod(name, namespace)
238+
pod.Status.Phase = coreV1.PodPhase(phase)
239+
return pod
240+
}
241+
232242
// NewSecret is a helper to build a new Secret instance
233243
func NewSecret(name string, namespace string) *coreV1.Secret {
234244
return &coreV1.Secret{

pkg/pods/pods.go

+86-1
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,27 @@ import (
55
"context"
66
"encoding/json"
77
"errors"
8+
"fmt"
9+
"time"
810

911
k8sTypes "k8s.io/api/core/v1"
1012
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
13+
"k8s.io/apimachinery/pkg/fields"
1114
"k8s.io/apimachinery/pkg/types"
1215
"k8s.io/apimachinery/pkg/util/strategicpatch"
16+
"k8s.io/apimachinery/pkg/watch"
1317
"k8s.io/client-go/kubernetes"
1418
"k8s.io/client-go/kubernetes/scheme"
1519
"k8s.io/client-go/rest"
1620
"k8s.io/client-go/tools/remotecommand"
1721
)
1822

23+
// ValidPod status
24+
const (
25+
Running string = "Running"
26+
Succeeded string = "Succeeded"
27+
)
28+
1929
func New(client kubernetes.Interface, config *rest.Config, metaOptions metav1.ListOptions, ctx context.Context) *Pods {
2030
return &Pods{
2131
client,
@@ -62,6 +72,7 @@ type PodOptions struct {
6272
Image string // image to be executed by the pod's container
6373
Command []string // command to be executed by the pod's container and its arguments
6474
RestartPolicy k8sTypes.RestartPolicy // policy for restarting containers in the pod. One of One of Always, OnFailure, Never
75+
Wait string // timeout for waiting until the pod is running
6576
}
6677

6778
func (obj *Pods) List(namespace string) ([]k8sTypes.Pod, error) {
@@ -133,7 +144,81 @@ func (obj *Pods) Create(options PodOptions) (k8sTypes.Pod, error) {
133144
if err != nil {
134145
return k8sTypes.Pod{}, err
135146
}
136-
return *pod, nil
147+
148+
if options.Wait == "" {
149+
return *pod, nil
150+
}
151+
waitOpts := WaitOptions{
152+
Name: options.Name,
153+
Namespace: options.Namespace,
154+
Status: Running,
155+
Timeout: options.Wait,
156+
}
157+
status, err := obj.Wait(waitOpts)
158+
if err != nil {
159+
return k8sTypes.Pod{}, err
160+
}
161+
if !status {
162+
return k8sTypes.Pod{}, errors.New("timeout exceeded waiting for pod to be running")
163+
}
164+
165+
return obj.Get(options.Name, options.Namespace)
166+
}
167+
168+
// Options for waiting for a Pod status
169+
type WaitOptions struct {
170+
Name string // Pod name
171+
Namespace string // Namespace where the pod is running
172+
Status string // Wait until pod reaches the specified status. Must be one of "Running" or "Succeeded".
173+
Timeout string // Timeout for waiting condition to be true
174+
}
175+
176+
// Wait for the Pod to be in a given status up to given timeout and returns a boolean indicating if the staus was reached. If the pod is Failed returns error.
177+
func (obj *Pods) Wait(options WaitOptions) (bool, error) {
178+
if options.Status != Running && options.Status != Succeeded {
179+
return false, errors.New("wait condition must be 'Running' or 'Succeeded'")
180+
}
181+
timeout, err := time.ParseDuration(options.Timeout)
182+
if err != nil {
183+
return false, err
184+
}
185+
selector := fields.Set{
186+
"metadata.name": options.Name,
187+
}.AsSelector()
188+
watcher, err := obj.client.CoreV1().Pods(options.Namespace).Watch(
189+
obj.ctx,
190+
metav1.ListOptions{
191+
FieldSelector: selector.String(),
192+
},
193+
)
194+
if err != nil {
195+
return false, err
196+
}
197+
defer watcher.Stop()
198+
199+
for {
200+
select {
201+
case <-time.After(timeout):
202+
return false, nil
203+
case event := <-watcher.ResultChan():
204+
if event.Type == watch.Error {
205+
return false, fmt.Errorf("error watching for pod: %v", event.Object)
206+
}
207+
if event.Type == watch.Modified {
208+
pod, isPod := event.Object.(*k8sTypes.Pod)
209+
if !isPod {
210+
return false, errors.New("received unknown object while watching for pods")
211+
}
212+
if pod.Status.Phase == k8sTypes.PodFailed {
213+
return false, errors.New("Pod has failed")
214+
}
215+
if string(pod.Status.Phase) == options.Status {
216+
return true, nil
217+
}
218+
}
219+
}
220+
}
221+
return false, nil
137222
}
138223

139224
// Exec executes a non-interactive command described in options and returns the stdout and stderr outputs

0 commit comments

Comments
 (0)