1/*
2Copyright 2016 The Kubernetes Authors.
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8    http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16
17package testing
18
19import (
20	"fmt"
21	"reflect"
22	"sync"
23	"time"
24
25	runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1alpha2"
26)
27
28var (
29	// FakeVersion is a version of a fake runtime.
30	FakeVersion = "0.1.0"
31
32	// FakeRuntimeName is the name of the fake runtime.
33	FakeRuntimeName = "fakeRuntime"
34
35	// FakePodSandboxIPs is an IP address of the fake runtime.
36	FakePodSandboxIPs = []string{"192.168.192.168"}
37)
38
39// FakePodSandbox is the fake implementation of runtimeapi.PodSandboxStatus.
40type FakePodSandbox struct {
41	// PodSandboxStatus contains the runtime information for a sandbox.
42	runtimeapi.PodSandboxStatus
43	// RuntimeHandler is the runtime handler that was issued with the RunPodSandbox request.
44	RuntimeHandler string
45}
46
47// FakeContainer is a fake container.
48type FakeContainer struct {
49	// ContainerStatus contains the runtime information for a container.
50	runtimeapi.ContainerStatus
51
52	// LinuxResources contains the resources specific to linux containers.
53	LinuxResources *runtimeapi.LinuxContainerResources
54
55	// the sandbox id of this container
56	SandboxID string
57}
58
59// FakeRuntimeService is a fake runetime service.
60type FakeRuntimeService struct {
61	sync.Mutex
62
63	Called []string
64	Errors map[string][]error
65
66	FakeStatus         *runtimeapi.RuntimeStatus
67	Containers         map[string]*FakeContainer
68	Sandboxes          map[string]*FakePodSandbox
69	FakeContainerStats map[string]*runtimeapi.ContainerStats
70
71	ErrorOnSandboxCreate bool
72}
73
74// GetContainerID returns the unique container ID from the FakeRuntimeService.
75func (r *FakeRuntimeService) GetContainerID(sandboxID, name string, attempt uint32) (string, error) {
76	r.Lock()
77	defer r.Unlock()
78
79	for id, c := range r.Containers {
80		if c.SandboxID == sandboxID && c.Metadata.Name == name && c.Metadata.Attempt == attempt {
81			return id, nil
82		}
83	}
84	return "", fmt.Errorf("container (name, attempt, sandboxID)=(%q, %d, %q) not found", name, attempt, sandboxID)
85}
86
87// SetFakeSandboxes sets the fake sandboxes for the FakeRuntimeService.
88func (r *FakeRuntimeService) SetFakeSandboxes(sandboxes []*FakePodSandbox) {
89	r.Lock()
90	defer r.Unlock()
91
92	r.Sandboxes = make(map[string]*FakePodSandbox)
93	for _, sandbox := range sandboxes {
94		sandboxID := sandbox.Id
95		r.Sandboxes[sandboxID] = sandbox
96	}
97}
98
99// SetFakeContainers sets fake containers for the FakeRuntimeService.
100func (r *FakeRuntimeService) SetFakeContainers(containers []*FakeContainer) {
101	r.Lock()
102	defer r.Unlock()
103
104	r.Containers = make(map[string]*FakeContainer)
105	for _, c := range containers {
106		containerID := c.Id
107		r.Containers[containerID] = c
108	}
109
110}
111
112// AssertCalls validates whether specified calls were made to the FakeRuntimeService.
113func (r *FakeRuntimeService) AssertCalls(calls []string) error {
114	r.Lock()
115	defer r.Unlock()
116
117	if !reflect.DeepEqual(calls, r.Called) {
118		return fmt.Errorf("expected %#v, got %#v", calls, r.Called)
119	}
120	return nil
121}
122
123// GetCalls returns the list of calls made to the FakeRuntimeService.
124func (r *FakeRuntimeService) GetCalls() []string {
125	r.Lock()
126	defer r.Unlock()
127	return append([]string{}, r.Called...)
128}
129
130// InjectError inject the error to the next call to the FakeRuntimeService.
131func (r *FakeRuntimeService) InjectError(f string, err error) {
132	r.Lock()
133	defer r.Unlock()
134	r.Errors[f] = append(r.Errors[f], err)
135}
136
137// caller of popError must grab a lock.
138func (r *FakeRuntimeService) popError(f string) error {
139	if r.Errors == nil {
140		return nil
141	}
142	errs := r.Errors[f]
143	if len(errs) == 0 {
144		return nil
145	}
146	err, errs := errs[0], errs[1:]
147	r.Errors[f] = errs
148	return err
149}
150
151// NewFakeRuntimeService creates a new FakeRuntimeService.
152func NewFakeRuntimeService() *FakeRuntimeService {
153	return &FakeRuntimeService{
154		Called:             make([]string, 0),
155		Errors:             make(map[string][]error),
156		Containers:         make(map[string]*FakeContainer),
157		Sandboxes:          make(map[string]*FakePodSandbox),
158		FakeContainerStats: make(map[string]*runtimeapi.ContainerStats),
159	}
160}
161
162// Version returns version information from the FakeRuntimeService.
163func (r *FakeRuntimeService) Version(apiVersion string) (*runtimeapi.VersionResponse, error) {
164	r.Lock()
165	defer r.Unlock()
166
167	r.Called = append(r.Called, "Version")
168	if err := r.popError("Version"); err != nil {
169		return nil, err
170	}
171
172	return &runtimeapi.VersionResponse{
173		Version:           FakeVersion,
174		RuntimeName:       FakeRuntimeName,
175		RuntimeVersion:    FakeVersion,
176		RuntimeApiVersion: FakeVersion,
177	}, nil
178}
179
180// Status returns runtime status of the FakeRuntimeService.
181func (r *FakeRuntimeService) Status() (*runtimeapi.RuntimeStatus, error) {
182	r.Lock()
183	defer r.Unlock()
184
185	r.Called = append(r.Called, "Status")
186	if err := r.popError("Status"); err != nil {
187		return nil, err
188	}
189
190	return r.FakeStatus, nil
191}
192
193// RunPodSandbox emulates the run of the pod sandbox in the FakeRuntimeService.
194func (r *FakeRuntimeService) RunPodSandbox(config *runtimeapi.PodSandboxConfig, runtimeHandler string) (string, error) {
195	r.Lock()
196	defer r.Unlock()
197
198	r.Called = append(r.Called, "RunPodSandbox")
199	if err := r.popError("RunPodSandbox"); err != nil {
200		return "", err
201	}
202
203	if r.ErrorOnSandboxCreate {
204		return "", fmt.Errorf("error on sandbox create")
205	}
206
207	// PodSandboxID should be randomized for real container runtime, but here just use
208	// fixed name from BuildSandboxName() for easily making fake sandboxes.
209	podSandboxID := BuildSandboxName(config.Metadata)
210	createdAt := time.Now().UnixNano()
211	r.Sandboxes[podSandboxID] = &FakePodSandbox{
212		PodSandboxStatus: runtimeapi.PodSandboxStatus{
213			Id:        podSandboxID,
214			Metadata:  config.Metadata,
215			State:     runtimeapi.PodSandboxState_SANDBOX_READY,
216			CreatedAt: createdAt,
217			Network: &runtimeapi.PodSandboxNetworkStatus{
218				Ip: FakePodSandboxIPs[0],
219			},
220			// Without setting sandboxStatus's Linux.Namespaces.Options, kubeGenericRuntimeManager's podSandboxChanged will consider it as network
221			// namespace changed and always recreate sandbox which causes pod creation failed.
222			// Ref `sandboxStatus.GetLinux().GetNamespaces().GetOptions().GetNetwork() != networkNamespaceForPod(pod)` in podSandboxChanged function.
223			Linux: &runtimeapi.LinuxPodSandboxStatus{
224				Namespaces: &runtimeapi.Namespace{
225					Options: config.GetLinux().GetSecurityContext().GetNamespaceOptions(),
226				},
227			},
228			Labels:         config.Labels,
229			Annotations:    config.Annotations,
230			RuntimeHandler: runtimeHandler,
231		},
232		RuntimeHandler: runtimeHandler,
233	}
234	// assign additional IPs
235	additionalIPs := FakePodSandboxIPs[1:]
236	additionalPodIPs := make([]*runtimeapi.PodIP, 0, len(additionalIPs))
237	for _, ip := range additionalIPs {
238		additionalPodIPs = append(additionalPodIPs, &runtimeapi.PodIP{
239			Ip: ip,
240		})
241	}
242	r.Sandboxes[podSandboxID].PodSandboxStatus.Network.AdditionalIps = additionalPodIPs
243	return podSandboxID, nil
244}
245
246// StopPodSandbox emulates the stop of pod sandbox in the FakeRuntimeService.
247func (r *FakeRuntimeService) StopPodSandbox(podSandboxID string) error {
248	r.Lock()
249	defer r.Unlock()
250
251	r.Called = append(r.Called, "StopPodSandbox")
252	if err := r.popError("StopPodSandbox"); err != nil {
253		return err
254	}
255
256	if s, ok := r.Sandboxes[podSandboxID]; ok {
257		s.State = runtimeapi.PodSandboxState_SANDBOX_NOTREADY
258	} else {
259		return fmt.Errorf("pod sandbox %s not found", podSandboxID)
260	}
261
262	return nil
263}
264
265// RemovePodSandbox emulates removal of the pod sadbox in the FakeRuntimeService.
266func (r *FakeRuntimeService) RemovePodSandbox(podSandboxID string) error {
267	r.Lock()
268	defer r.Unlock()
269
270	r.Called = append(r.Called, "RemovePodSandbox")
271	if err := r.popError("RemovePodSandbox"); err != nil {
272		return err
273	}
274
275	// Remove the pod sandbox
276	delete(r.Sandboxes, podSandboxID)
277
278	return nil
279}
280
281// PodSandboxStatus returns pod sandbox status from the FakeRuntimeService.
282func (r *FakeRuntimeService) PodSandboxStatus(podSandboxID string) (*runtimeapi.PodSandboxStatus, error) {
283	r.Lock()
284	defer r.Unlock()
285
286	r.Called = append(r.Called, "PodSandboxStatus")
287	if err := r.popError("PodSandboxStatus"); err != nil {
288		return nil, err
289	}
290
291	s, ok := r.Sandboxes[podSandboxID]
292	if !ok {
293		return nil, fmt.Errorf("pod sandbox %q not found", podSandboxID)
294	}
295
296	status := s.PodSandboxStatus
297	return &status, nil
298}
299
300// ListPodSandbox returns the list of pod sandboxes in the FakeRuntimeService.
301func (r *FakeRuntimeService) ListPodSandbox(filter *runtimeapi.PodSandboxFilter) ([]*runtimeapi.PodSandbox, error) {
302	r.Lock()
303	defer r.Unlock()
304
305	r.Called = append(r.Called, "ListPodSandbox")
306	if err := r.popError("ListPodSandbox"); err != nil {
307		return nil, err
308	}
309
310	result := make([]*runtimeapi.PodSandbox, 0)
311	for id, s := range r.Sandboxes {
312		if filter != nil {
313			if filter.Id != "" && filter.Id != id {
314				continue
315			}
316			if filter.State != nil && filter.GetState().State != s.State {
317				continue
318			}
319			if filter.LabelSelector != nil && !filterInLabels(filter.LabelSelector, s.GetLabels()) {
320				continue
321			}
322		}
323
324		result = append(result, &runtimeapi.PodSandbox{
325			Id:             s.Id,
326			Metadata:       s.Metadata,
327			State:          s.State,
328			CreatedAt:      s.CreatedAt,
329			Labels:         s.Labels,
330			Annotations:    s.Annotations,
331			RuntimeHandler: s.RuntimeHandler,
332		})
333	}
334
335	return result, nil
336}
337
338// PortForward emulates the set up of port forward in the FakeRuntimeService.
339func (r *FakeRuntimeService) PortForward(*runtimeapi.PortForwardRequest) (*runtimeapi.PortForwardResponse, error) {
340	r.Lock()
341	defer r.Unlock()
342
343	r.Called = append(r.Called, "PortForward")
344	if err := r.popError("PortForward"); err != nil {
345		return nil, err
346	}
347
348	return &runtimeapi.PortForwardResponse{}, nil
349}
350
351// CreateContainer emulates container creation in the FakeRuntimeService.
352func (r *FakeRuntimeService) CreateContainer(podSandboxID string, config *runtimeapi.ContainerConfig, sandboxConfig *runtimeapi.PodSandboxConfig) (string, error) {
353	r.Lock()
354	defer r.Unlock()
355
356	r.Called = append(r.Called, "CreateContainer")
357	if err := r.popError("CreateContainer"); err != nil {
358		return "", err
359	}
360
361	// ContainerID should be randomized for real container runtime, but here just use
362	// fixed BuildContainerName() for easily making fake containers.
363	containerID := BuildContainerName(config.Metadata, podSandboxID)
364	createdAt := time.Now().UnixNano()
365	createdState := runtimeapi.ContainerState_CONTAINER_CREATED
366	imageRef := config.Image.Image
367	r.Containers[containerID] = &FakeContainer{
368		ContainerStatus: runtimeapi.ContainerStatus{
369			Id:          containerID,
370			Metadata:    config.Metadata,
371			Image:       config.Image,
372			ImageRef:    imageRef,
373			CreatedAt:   createdAt,
374			State:       createdState,
375			Labels:      config.Labels,
376			Annotations: config.Annotations,
377		},
378		SandboxID:      podSandboxID,
379		LinuxResources: config.GetLinux().GetResources(),
380	}
381
382	return containerID, nil
383}
384
385// StartContainer emulates start of a container in the FakeRuntimeService.
386func (r *FakeRuntimeService) StartContainer(containerID string) error {
387	r.Lock()
388	defer r.Unlock()
389
390	r.Called = append(r.Called, "StartContainer")
391	if err := r.popError("StartContainer"); err != nil {
392		return err
393	}
394
395	c, ok := r.Containers[containerID]
396	if !ok {
397		return fmt.Errorf("container %s not found", containerID)
398	}
399
400	// Set container to running.
401	c.State = runtimeapi.ContainerState_CONTAINER_RUNNING
402	c.StartedAt = time.Now().UnixNano()
403
404	return nil
405}
406
407// StopContainer emulates stop of a container in the FakeRuntimeService.
408func (r *FakeRuntimeService) StopContainer(containerID string, timeout int64) error {
409	r.Lock()
410	defer r.Unlock()
411
412	r.Called = append(r.Called, "StopContainer")
413	if err := r.popError("StopContainer"); err != nil {
414		return err
415	}
416
417	c, ok := r.Containers[containerID]
418	if !ok {
419		return fmt.Errorf("container %q not found", containerID)
420	}
421
422	// Set container to exited state.
423	finishedAt := time.Now().UnixNano()
424	exitedState := runtimeapi.ContainerState_CONTAINER_EXITED
425	c.State = exitedState
426	c.FinishedAt = finishedAt
427
428	return nil
429}
430
431// RemoveContainer emulates remove of a container in the FakeRuntimeService.
432func (r *FakeRuntimeService) RemoveContainer(containerID string) error {
433	r.Lock()
434	defer r.Unlock()
435
436	r.Called = append(r.Called, "RemoveContainer")
437	if err := r.popError("RemoveContainer"); err != nil {
438		return err
439	}
440
441	// Remove the container
442	delete(r.Containers, containerID)
443
444	return nil
445}
446
447// ListContainers returns the list of containers in the FakeRuntimeService.
448func (r *FakeRuntimeService) ListContainers(filter *runtimeapi.ContainerFilter) ([]*runtimeapi.Container, error) {
449	r.Lock()
450	defer r.Unlock()
451
452	r.Called = append(r.Called, "ListContainers")
453	if err := r.popError("ListContainers"); err != nil {
454		return nil, err
455	}
456
457	result := make([]*runtimeapi.Container, 0)
458	for _, s := range r.Containers {
459		if filter != nil {
460			if filter.Id != "" && filter.Id != s.Id {
461				continue
462			}
463			if filter.PodSandboxId != "" && filter.PodSandboxId != s.SandboxID {
464				continue
465			}
466			if filter.State != nil && filter.GetState().State != s.State {
467				continue
468			}
469			if filter.LabelSelector != nil && !filterInLabels(filter.LabelSelector, s.GetLabels()) {
470				continue
471			}
472		}
473
474		result = append(result, &runtimeapi.Container{
475			Id:           s.Id,
476			CreatedAt:    s.CreatedAt,
477			PodSandboxId: s.SandboxID,
478			Metadata:     s.Metadata,
479			State:        s.State,
480			Image:        s.Image,
481			ImageRef:     s.ImageRef,
482			Labels:       s.Labels,
483			Annotations:  s.Annotations,
484		})
485	}
486
487	return result, nil
488}
489
490// ContainerStatus returns the container status given the container ID in FakeRuntimeService.
491func (r *FakeRuntimeService) ContainerStatus(containerID string) (*runtimeapi.ContainerStatus, error) {
492	r.Lock()
493	defer r.Unlock()
494
495	r.Called = append(r.Called, "ContainerStatus")
496	if err := r.popError("ContainerStatus"); err != nil {
497		return nil, err
498	}
499
500	c, ok := r.Containers[containerID]
501	if !ok {
502		return nil, fmt.Errorf("container %q not found", containerID)
503	}
504
505	status := c.ContainerStatus
506	return &status, nil
507}
508
509// UpdateContainerResources returns the container resource in the FakeRuntimeService.
510func (r *FakeRuntimeService) UpdateContainerResources(string, *runtimeapi.LinuxContainerResources) error {
511	r.Lock()
512	defer r.Unlock()
513
514	r.Called = append(r.Called, "UpdateContainerResources")
515	return r.popError("UpdateContainerResources")
516}
517
518// ExecSync emulates the sync execution of a command in a container in the FakeRuntimeService.
519func (r *FakeRuntimeService) ExecSync(containerID string, cmd []string, timeout time.Duration) (stdout []byte, stderr []byte, err error) {
520	r.Lock()
521	defer r.Unlock()
522
523	r.Called = append(r.Called, "ExecSync")
524	err = r.popError("ExecSync")
525	return
526}
527
528// Exec emulates the execution of a command in a container in the FakeRuntimeService.
529func (r *FakeRuntimeService) Exec(*runtimeapi.ExecRequest) (*runtimeapi.ExecResponse, error) {
530	r.Lock()
531	defer r.Unlock()
532
533	r.Called = append(r.Called, "Exec")
534	if err := r.popError("Exec"); err != nil {
535		return nil, err
536	}
537
538	return &runtimeapi.ExecResponse{}, nil
539}
540
541// Attach emulates the attach request in the FakeRuntimeService.
542func (r *FakeRuntimeService) Attach(req *runtimeapi.AttachRequest) (*runtimeapi.AttachResponse, error) {
543	r.Lock()
544	defer r.Unlock()
545
546	r.Called = append(r.Called, "Attach")
547	if err := r.popError("Attach"); err != nil {
548		return nil, err
549	}
550
551	return &runtimeapi.AttachResponse{}, nil
552}
553
554// UpdateRuntimeConfig emulates the update of a runtime config for the FakeRuntimeService.
555func (r *FakeRuntimeService) UpdateRuntimeConfig(runtimeCOnfig *runtimeapi.RuntimeConfig) error {
556	r.Lock()
557	defer r.Unlock()
558
559	r.Called = append(r.Called, "UpdateRuntimeConfig")
560	return r.popError("UpdateRuntimeConfig")
561}
562
563// SetFakeContainerStats sets the fake container stats in the FakeRuntimeService.
564func (r *FakeRuntimeService) SetFakeContainerStats(containerStats []*runtimeapi.ContainerStats) {
565	r.Lock()
566	defer r.Unlock()
567
568	r.FakeContainerStats = make(map[string]*runtimeapi.ContainerStats)
569	for _, s := range containerStats {
570		r.FakeContainerStats[s.Attributes.Id] = s
571	}
572}
573
574// ContainerStats returns the container stats in the FakeRuntimeService.
575func (r *FakeRuntimeService) ContainerStats(containerID string) (*runtimeapi.ContainerStats, error) {
576	r.Lock()
577	defer r.Unlock()
578
579	r.Called = append(r.Called, "ContainerStats")
580	if err := r.popError("ContainerStats"); err != nil {
581		return nil, err
582	}
583
584	s, found := r.FakeContainerStats[containerID]
585	if !found {
586		return nil, fmt.Errorf("no stats for container %q", containerID)
587	}
588	return s, nil
589}
590
591// ListContainerStats returns the list of all container stats given the filter in the FakeRuntimeService.
592func (r *FakeRuntimeService) ListContainerStats(filter *runtimeapi.ContainerStatsFilter) ([]*runtimeapi.ContainerStats, error) {
593	r.Lock()
594	defer r.Unlock()
595
596	r.Called = append(r.Called, "ListContainerStats")
597	if err := r.popError("ListContainerStats"); err != nil {
598		return nil, err
599	}
600
601	var result []*runtimeapi.ContainerStats
602	for _, c := range r.Containers {
603		if filter != nil {
604			if filter.Id != "" && filter.Id != c.Id {
605				continue
606			}
607			if filter.PodSandboxId != "" && filter.PodSandboxId != c.SandboxID {
608				continue
609			}
610			if filter.LabelSelector != nil && !filterInLabels(filter.LabelSelector, c.GetLabels()) {
611				continue
612			}
613		}
614		s, found := r.FakeContainerStats[c.Id]
615		if !found {
616			continue
617		}
618		result = append(result, s)
619	}
620
621	return result, nil
622}
623
624// ReopenContainerLog emulates call to the reopen container log in the FakeRuntimeService.
625func (r *FakeRuntimeService) ReopenContainerLog(containerID string) error {
626	r.Lock()
627	defer r.Unlock()
628
629	r.Called = append(r.Called, "ReopenContainerLog")
630
631	if err := r.popError("ReopenContainerLog"); err != nil {
632		return err
633	}
634
635	return nil
636}
637