forked from kata-containers/kata-containers
-
Notifications
You must be signed in to change notification settings - Fork 4
/
sandbox.go
2793 lines (2340 loc) · 81.1 KB
/
sandbox.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// Copyright (c) 2016 Intel Corporation
// Copyright (c) 2020 Adobe Inc.
//
// SPDX-License-Identifier: Apache-2.0
//
package virtcontainers
import (
"bufio"
"bytes"
"context"
"fmt"
"io"
"math"
"net"
"os"
"os/exec"
"path/filepath"
"strings"
"sync"
"syscall"
v1 "github.com/containerd/cgroups/stats/v1"
v2 "github.com/containerd/cgroups/v2/stats"
specs "github.com/opencontainers/runtime-spec/specs-go"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/vishvananda/netlink"
cri "github.com/containerd/containerd/pkg/cri/annotations"
crio "github.com/containers/podman/v4/pkg/annotations"
"github.com/kata-containers/kata-containers/src/runtime/pkg/device/api"
"github.com/kata-containers/kata-containers/src/runtime/pkg/device/config"
"github.com/kata-containers/kata-containers/src/runtime/pkg/device/drivers"
deviceManager "github.com/kata-containers/kata-containers/src/runtime/pkg/device/manager"
"github.com/kata-containers/kata-containers/src/runtime/pkg/katautils/katatrace"
resCtrl "github.com/kata-containers/kata-containers/src/runtime/pkg/resourcecontrol"
exp "github.com/kata-containers/kata-containers/src/runtime/virtcontainers/experimental"
"github.com/kata-containers/kata-containers/src/runtime/virtcontainers/persist"
persistapi "github.com/kata-containers/kata-containers/src/runtime/virtcontainers/persist/api"
pbTypes "github.com/kata-containers/kata-containers/src/runtime/virtcontainers/pkg/agent/protocols"
"github.com/kata-containers/kata-containers/src/runtime/virtcontainers/pkg/agent/protocols/grpc"
"github.com/kata-containers/kata-containers/src/runtime/virtcontainers/pkg/annotations"
"github.com/kata-containers/kata-containers/src/runtime/virtcontainers/pkg/compatoci"
"github.com/kata-containers/kata-containers/src/runtime/virtcontainers/pkg/cpuset"
"github.com/kata-containers/kata-containers/src/runtime/virtcontainers/pkg/rootless"
"github.com/kata-containers/kata-containers/src/runtime/virtcontainers/types"
"github.com/kata-containers/kata-containers/src/runtime/virtcontainers/utils"
"google.golang.org/grpc/codes"
grpcStatus "google.golang.org/grpc/status"
)
// sandboxTracingTags defines tags for the trace span
var sandboxTracingTags = map[string]string{
"source": "runtime",
"package": "virtcontainers",
"subsystem": "sandbox",
}
const (
// VmStartTimeout represents the time in seconds a sandbox can wait before
// to consider the VM starting operation failed.
VmStartTimeout = 10
// DirMode is the permission bits used for creating a directory
DirMode = os.FileMode(0750) | os.ModeDir
mkswapPath = "/sbin/mkswap"
rwm = "rwm"
// When the Kata overhead threads (I/O, VMM, etc) are not
// placed in the sandbox resource controller (A cgroup on Linux),
// they are moved to a specific, unconstrained resource controller.
// On Linux, assuming the cgroup mount point is at /sys/fs/cgroup/,
// on a cgroup v1 system, the Kata overhead memory cgroup will be at
// /sys/fs/cgroup/memory/kata_overhead/$CGPATH where $CGPATH is
// defined by the orchestrator.
resCtrlKataOverheadID = "/kata_overhead/"
sandboxMountsDir = "sandbox-mounts"
// Restricted permission for shared directory managed by virtiofs
sharedDirMode = os.FileMode(0700) | os.ModeDir
// hotplug factor indicates how much memory can be hotplugged relative to the amount of
// RAM provided to the guest. This is a conservative heuristic based on needing 64 bytes per
// 4KiB page of hotplugged memory.
//
// As an example: 12 GiB hotplugged -> 3 Mi pages -> 192 MiBytes overhead (3Mi x 64B).
// This is approximately what should be free in a relatively unloaded 256 MiB guest (75% of available memory). So, 256 Mi x 48 => 12 Gi
acpiMemoryHotplugFactor = 48
)
var (
errSandboxNotRunning = errors.New("Sandbox not running")
)
// HypervisorPidKey is the context key for hypervisor pid
type HypervisorPidKey struct{}
// SandboxStatus describes a sandbox status.
type SandboxStatus struct {
Annotations map[string]string
ID string
Hypervisor HypervisorType
ContainersStatus []ContainerStatus
State types.SandboxState
HypervisorConfig HypervisorConfig
}
// SandboxStats describes a sandbox's stats
type SandboxStats struct {
CgroupStats CgroupStats
Cpus int
}
type SandboxResourceSizing struct {
// The number of CPUs required for the sandbox workload(s)
WorkloadCPUs float32
// The base number of CPUs for the VM that are assigned as overhead
BaseCPUs float32
// The amount of memory required for the sandbox workload(s)
WorkloadMemMB uint32
// The base amount of memory required for that VM that is assigned as overhead
BaseMemMB uint32
}
// SandboxConfig is a Sandbox configuration.
type SandboxConfig struct {
// Annotations keys must be unique strings and must be name-spaced
Annotations map[string]string
// Custom SELinux security policy to the container process inside the VM
GuestSeLinuxLabel string
HypervisorType HypervisorType
ID string
Hostname string
// SandboxBindMounts - list of paths to mount into guest
SandboxBindMounts []string
// Experimental features enabled
Experimental []exp.Feature
// Containers describe the list of containers within a Sandbox.
// This list can be empty and populated by adding containers
// to the Sandbox a posteriori.
// TODO: this should be a map to avoid duplicated containers
Containers []ContainerConfig
Volumes []types.Volume
NetworkConfig NetworkConfig
AgentConfig KataAgentConfig
HypervisorConfig HypervisorConfig
ShmSize uint64
SandboxResources SandboxResourceSizing
VfioMode config.VFIOModeType
// StaticResourceMgmt indicates if the shim should rely on statically sizing the sandbox (VM)
StaticResourceMgmt bool
// SharePidNs sets all containers to share the same sandbox level pid namespace.
SharePidNs bool
// SystemdCgroup enables systemd cgroup support
SystemdCgroup bool
// SandboxCgroupOnly enables cgroup only at podlevel in the host
SandboxCgroupOnly bool
// DisableGuestSeccomp disable seccomp within the guest
DisableGuestSeccomp bool
// EnableVCPUsPinning controls whether each vCPU thread should be scheduled to a fixed CPU
EnableVCPUsPinning bool
}
// valid checks that the sandbox configuration is valid.
func (sandboxConfig *SandboxConfig) valid() bool {
if sandboxConfig.ID == "" {
return false
}
if _, err := NewHypervisor(sandboxConfig.HypervisorType); err != nil {
sandboxConfig.HypervisorType = QemuHypervisor
}
// validate experimental features
for _, f := range sandboxConfig.Experimental {
if exp.Get(f.Name) == nil {
return false
}
}
return true
}
// Sandbox is composed of a set of containers and a runtime environment.
// A Sandbox can be created, deleted, started, paused, stopped, listed, entered, and restored.
type Sandbox struct {
ctx context.Context
devManager api.DeviceManager
factory Factory
hypervisor Hypervisor
agent agent
store persistapi.PersistDriver
fsShare FilesystemSharer
swapDevices []*config.BlockDrive
volumes []types.Volume
monitor *monitor
config *SandboxConfig
annotationsLock *sync.RWMutex
wg *sync.WaitGroup
cw *consoleWatcher
sandboxController resCtrl.ResourceController
overheadController resCtrl.ResourceController
containers map[string]*Container
id string
network Network
state types.SandboxState
sync.Mutex
swapSizeBytes int64
shmSize uint64
swapDeviceNum uint
sharePidNs bool
seccompSupported bool
disableVMShutdown bool
isVCPUsPinningOn bool
}
// ID returns the sandbox identifier string.
func (s *Sandbox) ID() string {
return s.id
}
// Logger returns a logrus logger appropriate for logging Sandbox messages
func (s *Sandbox) Logger() *logrus.Entry {
return virtLog.WithFields(logrus.Fields{
"subsystem": "sandbox",
"sandbox": s.id,
})
}
// Annotations returns any annotation that a user could have stored through the sandbox.
func (s *Sandbox) Annotations(key string) (string, error) {
s.annotationsLock.RLock()
defer s.annotationsLock.RUnlock()
value, exist := s.config.Annotations[key]
if !exist {
return "", fmt.Errorf("Annotations key %s does not exist", key)
}
return value, nil
}
// SetAnnotations sets or adds an annotations
func (s *Sandbox) SetAnnotations(annotations map[string]string) error {
s.annotationsLock.Lock()
defer s.annotationsLock.Unlock()
for k, v := range annotations {
s.config.Annotations[k] = v
}
return nil
}
// GetAnnotations returns sandbox's annotations
func (s *Sandbox) GetAnnotations() map[string]string {
s.annotationsLock.RLock()
defer s.annotationsLock.RUnlock()
return s.config.Annotations
}
// GetNetNs returns the network namespace of the current sandbox.
func (s *Sandbox) GetNetNs() string {
return s.network.NetworkID()
}
// GetHypervisorPid returns the hypervisor's pid.
func (s *Sandbox) GetHypervisorPid() (int, error) {
pids := s.hypervisor.GetPids()
if len(pids) == 0 || pids[0] == 0 {
return -1, fmt.Errorf("Invalid hypervisor PID: %+v", pids)
}
return pids[0], nil
}
// GetAllContainers returns all containers.
func (s *Sandbox) GetAllContainers() []VCContainer {
ifa := make([]VCContainer, len(s.containers))
i := 0
for _, v := range s.containers {
ifa[i] = v
i++
}
return ifa
}
// GetContainer returns the container named by the containerID.
func (s *Sandbox) GetContainer(containerID string) VCContainer {
if c, ok := s.containers[containerID]; ok {
return c
}
return nil
}
// Release closes the agent connection.
func (s *Sandbox) Release(ctx context.Context) error {
s.Logger().Info("release sandbox")
if s.monitor != nil {
s.monitor.stop()
}
s.fsShare.StopFileEventWatcher(ctx)
s.hypervisor.Disconnect(ctx)
return s.agent.disconnect(ctx)
}
// Status gets the status of the sandbox
func (s *Sandbox) Status() SandboxStatus {
var contStatusList []ContainerStatus
for _, c := range s.containers {
rootfs := c.config.RootFs.Source
if c.config.RootFs.Mounted {
rootfs = c.config.RootFs.Target
}
contStatusList = append(contStatusList, ContainerStatus{
ID: c.id,
State: c.state,
PID: c.process.Pid,
StartTime: c.process.StartTime,
RootFs: rootfs,
Annotations: c.config.Annotations,
})
}
return SandboxStatus{
ID: s.id,
State: s.state,
Hypervisor: s.config.HypervisorType,
HypervisorConfig: s.config.HypervisorConfig,
ContainersStatus: contStatusList,
Annotations: s.config.Annotations,
}
}
// Monitor returns a error channel for watcher to watch at
func (s *Sandbox) Monitor(ctx context.Context) (chan error, error) {
if s.state.State != types.StateRunning {
return nil, errSandboxNotRunning
}
s.Lock()
if s.monitor == nil {
s.monitor = newMonitor(s)
}
s.Unlock()
return s.monitor.newWatcher(ctx)
}
// WaitProcess waits on a container process and return its exit code
func (s *Sandbox) WaitProcess(ctx context.Context, containerID, processID string) (int32, error) {
if s.state.State != types.StateRunning {
return 0, errSandboxNotRunning
}
c, err := s.findContainer(containerID)
if err != nil {
return 0, err
}
return c.wait(ctx, processID)
}
// SignalProcess sends a signal to a process of a container when all is false.
// When all is true, it sends the signal to all processes of a container.
func (s *Sandbox) SignalProcess(ctx context.Context, containerID, processID string, signal syscall.Signal, all bool) error {
if s.state.State != types.StateRunning {
return errSandboxNotRunning
}
c, err := s.findContainer(containerID)
if err != nil {
return err
}
return c.signalProcess(ctx, processID, signal, all)
}
// WinsizeProcess resizes the tty window of a process
func (s *Sandbox) WinsizeProcess(ctx context.Context, containerID, processID string, height, width uint32) error {
if s.state.State != types.StateRunning {
return errSandboxNotRunning
}
c, err := s.findContainer(containerID)
if err != nil {
return err
}
return c.winsizeProcess(ctx, processID, height, width)
}
// IOStream returns stdin writer, stdout reader and stderr reader of a process
func (s *Sandbox) IOStream(containerID, processID string) (io.WriteCloser, io.Reader, io.Reader, error) {
if s.state.State != types.StateRunning {
return nil, nil, nil, errSandboxNotRunning
}
c, err := s.findContainer(containerID)
if err != nil {
return nil, nil, nil, err
}
return c.ioStream(processID)
}
func createAssets(ctx context.Context, sandboxConfig *SandboxConfig) error {
span, _ := katatrace.Trace(ctx, nil, "createAssets", sandboxTracingTags, map[string]string{"sandbox_id": sandboxConfig.ID})
defer span.End()
for _, name := range types.AssetTypes() {
a, err := types.NewAsset(sandboxConfig.Annotations, name)
if err != nil {
return err
}
if err := sandboxConfig.HypervisorConfig.AddCustomAsset(a); err != nil {
return err
}
}
_, imageErr := sandboxConfig.HypervisorConfig.assetPath(types.ImageAsset)
_, initrdErr := sandboxConfig.HypervisorConfig.assetPath(types.InitrdAsset)
if imageErr != nil && initrdErr != nil {
return fmt.Errorf("%s and %s cannot be both set", types.ImageAsset, types.InitrdAsset)
}
return nil
}
func (s *Sandbox) getAndStoreGuestDetails(ctx context.Context) error {
guestDetailRes, err := s.agent.getGuestDetails(ctx, &grpc.GuestDetailsRequest{
MemBlockSize: true,
MemHotplugProbe: true,
})
if err != nil {
return err
}
if guestDetailRes != nil {
s.state.GuestMemoryBlockSizeMB = uint32(guestDetailRes.MemBlockSizeBytes >> 20)
if guestDetailRes.AgentDetails != nil {
s.seccompSupported = guestDetailRes.AgentDetails.SupportsSeccomp
}
s.state.GuestMemoryHotplugProbe = guestDetailRes.SupportMemHotplugProbe
}
return nil
}
// createSandbox creates a sandbox from a sandbox description, the containers list, the hypervisor
// and the agent passed through the Config structure.
// It will create and store the sandbox structure, and then ask the hypervisor
// to physically create that sandbox i.e. starts a VM for that sandbox to eventually
// be started.
func createSandbox(ctx context.Context, sandboxConfig SandboxConfig, factory Factory) (*Sandbox, error) {
span, ctx := katatrace.Trace(ctx, nil, "createSandbox", sandboxTracingTags, map[string]string{"sandbox_id": sandboxConfig.ID})
defer span.End()
if err := createAssets(ctx, &sandboxConfig); err != nil {
return nil, err
}
s, err := newSandbox(ctx, sandboxConfig, factory)
if err != nil {
return nil, err
}
if len(s.config.Experimental) != 0 {
s.Logger().WithField("features", s.config.Experimental).Infof("Enable experimental features")
}
// Sandbox state has been loaded from storage.
// If the Stae is not empty, this is a re-creation, i.e.
// we don't need to talk to the guest's agent, but only
// want to create the sandbox and its containers in memory.
if s.state.State != "" {
return s, nil
}
// The code below only gets called when initially creating a sandbox, not when restoring or
// re-creating it. The above check for the sandbox state enforces that.
if err := s.fsShare.Prepare(ctx); err != nil {
return nil, err
}
if err := s.agent.createSandbox(ctx, s); err != nil {
return nil, err
}
// Set sandbox state
if err := s.setSandboxState(types.StateReady); err != nil {
return nil, err
}
return s, nil
}
func newSandbox(ctx context.Context, sandboxConfig SandboxConfig, factory Factory) (sb *Sandbox, retErr error) {
span, ctx := katatrace.Trace(ctx, nil, "newSandbox", sandboxTracingTags, map[string]string{"sandbox_id": sandboxConfig.ID})
defer span.End()
if !sandboxConfig.valid() {
return nil, fmt.Errorf("Invalid sandbox configuration")
}
// create agent instance
agent := getNewAgentFunc(ctx)()
hypervisor, err := NewHypervisor(sandboxConfig.HypervisorType)
if err != nil {
return nil, err
}
network, err := NewNetwork(&sandboxConfig.NetworkConfig)
if err != nil {
return nil, err
}
s := &Sandbox{
id: sandboxConfig.ID,
factory: factory,
hypervisor: hypervisor,
agent: agent,
config: &sandboxConfig,
volumes: sandboxConfig.Volumes,
containers: map[string]*Container{},
state: types.SandboxState{BlockIndexMap: make(map[int]struct{})},
annotationsLock: &sync.RWMutex{},
wg: &sync.WaitGroup{},
shmSize: sandboxConfig.ShmSize,
sharePidNs: sandboxConfig.SharePidNs,
network: network,
ctx: ctx,
swapDeviceNum: 0,
swapSizeBytes: 0,
swapDevices: []*config.BlockDrive{},
}
fsShare, err := NewFilesystemShare(s)
if err != nil {
return nil, err
}
s.fsShare = fsShare
if s.store, err = persist.GetDriver(); err != nil || s.store == nil {
return nil, fmt.Errorf("failed to get fs persist driver: %v", err)
}
defer func() {
if retErr != nil {
s.Logger().WithError(retErr).Error("Create new sandbox failed")
s.store.Destroy(s.id)
}
}()
sandboxConfig.HypervisorConfig.VMStorePath = s.store.RunVMStoragePath()
sandboxConfig.HypervisorConfig.RunStorePath = s.store.RunStoragePath()
spec := s.GetPatchedOCISpec()
if spec != nil && spec.Process.SelinuxLabel != "" {
sandboxConfig.HypervisorConfig.SELinuxProcessLabel = spec.Process.SelinuxLabel
}
s.devManager = deviceManager.NewDeviceManager(sandboxConfig.HypervisorConfig.BlockDeviceDriver,
sandboxConfig.HypervisorConfig.EnableVhostUserStore,
sandboxConfig.HypervisorConfig.VhostUserStorePath, sandboxConfig.HypervisorConfig.VhostUserDeviceReconnect, nil)
// Create the sandbox resource controllers.
if err := s.createResourceController(); err != nil {
return nil, err
}
// Ignore the error. Restore can fail for a new sandbox
if err := s.Restore(); err != nil {
s.Logger().WithError(err).Debug("restore sandbox failed")
}
if err := validateHypervisorConfig(&sandboxConfig.HypervisorConfig); err != nil {
return nil, err
}
// Start the event loop if not already started when fs sharing is not used
if sandboxConfig.HypervisorConfig.SharedFS == config.NoSharedFS {
// Start the StartFileEventWatcher method as a goroutine
// to monitor the file events.
go func() {
if err := s.fsShare.StartFileEventWatcher(ctx); err != nil {
s.Logger().WithError(err).Error("Failed to start file event watcher")
return
}
}()
// Stop the file event watcher on error
defer func() {
if retErr != nil {
s.Logger().WithError(retErr).Error("Stopping File Event Watcher")
s.fsShare.StopFileEventWatcher(ctx)
}
}()
}
setHypervisorConfigAnnotations(&sandboxConfig)
coldPlugVFIO, err := s.coldOrHotPlugVFIO(&sandboxConfig)
if err != nil {
return nil, err
}
// store doesn't require hypervisor to be stored immediately
if err = s.hypervisor.CreateVM(ctx, s.id, s.network, &sandboxConfig.HypervisorConfig); err != nil {
return nil, err
}
if s.disableVMShutdown, err = s.agent.init(ctx, s, sandboxConfig.AgentConfig); err != nil {
return nil, err
}
if !coldPlugVFIO {
return s, nil
}
for _, dev := range sandboxConfig.HypervisorConfig.VFIODevices {
s.Logger().Info("cold-plug device: ", dev)
_, err := s.AddDevice(ctx, dev)
if err != nil {
s.Logger().WithError(err).Debug("Cannot cold-plug add device")
return nil, err
}
}
return s, nil
}
func (s *Sandbox) coldOrHotPlugVFIO(sandboxConfig *SandboxConfig) (bool, error) {
// If we have a confidential guest we need to cold-plug the PCIe VFIO devices
// until we have TDISP/IDE PCIe support.
coldPlugVFIO := (sandboxConfig.HypervisorConfig.ColdPlugVFIO != config.NoPort)
// Aggregate all the containner devices for hot-plug and use them to dedcue
// the correct amount of ports to reserve for the hypervisor.
hotPlugVFIO := (sandboxConfig.HypervisorConfig.HotPlugVFIO != config.NoPort)
modeIsGK := (sandboxConfig.VfioMode == config.VFIOModeGuestKernel)
// modeIsVFIO is needed at the container level not the sandbox level.
// modeIsVFIO := (sandboxConfig.VfioMode == config.VFIOModeVFIO)
var vfioDevices []config.DeviceInfo
// vhost-user-block device is a PCIe device in Virt, keep track of it
// for correct number of PCIe root ports.
var vhostUserBlkDevices []config.DeviceInfo
for cnt, containers := range sandboxConfig.Containers {
for dev, device := range containers.DeviceInfos {
if deviceManager.IsVhostUserBlk(device) {
vhostUserBlkDevices = append(vhostUserBlkDevices, device)
continue
}
isVFIODevice := deviceManager.IsVFIODevice(device.ContainerPath)
if hotPlugVFIO && isVFIODevice {
device.ColdPlug = false
device.Port = sandboxConfig.HypervisorConfig.HotPlugVFIO
vfioDevices = append(vfioDevices, device)
sandboxConfig.Containers[cnt].DeviceInfos[dev].Port = sandboxConfig.HypervisorConfig.HotPlugVFIO
}
if coldPlugVFIO && isVFIODevice {
device.ColdPlug = true
device.Port = sandboxConfig.HypervisorConfig.ColdPlugVFIO
vfioDevices = append(vfioDevices, device)
// We need to remove the devices marked for cold-plug
// otherwise at the container level the kata-agent
// will try to hot-plug them.
if modeIsGK {
sandboxConfig.Containers[cnt].DeviceInfos[dev].ID = "remove-we-are-cold-plugging"
}
}
}
var filteredDevices []config.DeviceInfo
for _, device := range containers.DeviceInfos {
if device.ID != "remove-we-are-cold-plugging" {
filteredDevices = append(filteredDevices, device)
}
}
sandboxConfig.Containers[cnt].DeviceInfos = filteredDevices
}
sandboxConfig.HypervisorConfig.VFIODevices = vfioDevices
sandboxConfig.HypervisorConfig.VhostUserBlkDevices = vhostUserBlkDevices
return coldPlugVFIO, nil
}
func setHypervisorConfigAnnotations(sandboxConfig *SandboxConfig) {
if len(sandboxConfig.Containers) > 0 {
// These values are required by remote hypervisor
for _, a := range []string{cri.SandboxName, crio.SandboxName} {
if value, ok := sandboxConfig.Containers[0].Annotations[a]; ok {
sandboxConfig.HypervisorConfig.SandboxName = value
}
}
for _, a := range []string{cri.SandboxNamespace, crio.Namespace} {
if value, ok := sandboxConfig.Containers[0].Annotations[a]; ok {
sandboxConfig.HypervisorConfig.SandboxNamespace = value
}
}
}
}
func (s *Sandbox) createResourceController() error {
var err error
cgroupPath := ""
// Do not change current cgroup configuration.
// Create a spec without constraints
resources := specs.LinuxResources{}
if s.config == nil {
return fmt.Errorf("Could not create %s resource controller manager: empty sandbox configuration", s.sandboxController)
}
spec := s.GetPatchedOCISpec()
if spec != nil && spec.Linux != nil {
cgroupPath = spec.Linux.CgroupsPath
// Kata relies on the resource controller (cgroups on Linux) parent created and configured by the
// container engine by default. The exception is for devices whitelist as well as sandbox-level CPUSet.
// For the sandbox controllers we create and manage, rename the base of the controller ID to
// include "kata_"
if !resCtrl.IsSystemdCgroup(cgroupPath) { // don't add prefix when cgroups are managed by systemd
cgroupPath, err = resCtrl.RenameCgroupPath(cgroupPath)
if err != nil {
return err
}
}
if spec.Linux.Resources != nil {
resources.Devices = spec.Linux.Resources.Devices
intptr := func(i int64) *int64 { return &i }
// Determine if device /dev/null and /dev/urandom exist, and add if they don't
nullDeviceExist := false
urandomDeviceExist := false
ptmxDeviceExist := false
for _, device := range resources.Devices {
if device.Type == "c" && device.Major == intptr(1) && device.Minor == intptr(3) {
nullDeviceExist = true
}
if device.Type == "c" && device.Major == intptr(1) && device.Minor == intptr(9) {
urandomDeviceExist = true
}
if device.Type == "c" && device.Major == intptr(5) && device.Minor == intptr(2) {
ptmxDeviceExist = true
}
}
if !nullDeviceExist {
// "/dev/null"
resources.Devices = append(resources.Devices, []specs.LinuxDeviceCgroup{
{Type: "c", Major: intptr(1), Minor: intptr(3), Access: rwm, Allow: true},
}...)
}
if !urandomDeviceExist {
// "/dev/urandom"
resources.Devices = append(resources.Devices, []specs.LinuxDeviceCgroup{
{Type: "c", Major: intptr(1), Minor: intptr(9), Access: rwm, Allow: true},
}...)
}
// If the hypervisor debug console is enabled and
// sandbox_cgroup_only are configured, then the vmm needs access to
// /dev/ptmx. Add this to the device allowlist if it is not
// already present in the config.
if s.config.HypervisorConfig.Debug && s.config.SandboxCgroupOnly && !ptmxDeviceExist {
// "/dev/ptmx"
resources.Devices = append(resources.Devices, []specs.LinuxDeviceCgroup{
{Type: "c", Major: intptr(5), Minor: intptr(2), Access: rwm, Allow: true},
}...)
}
if spec.Linux.Resources.CPU != nil {
resources.CPU = &specs.LinuxCPU{
Cpus: spec.Linux.Resources.CPU.Cpus,
}
}
}
//TODO: in Docker or Podman use case, it is reasonable to set a constraint. Need to add a flag
// to allow users to configure Kata to constrain CPUs and Memory in this alternative
// scenario. See https://github.com/kata-containers/runtime/issues/2811
}
if s.devManager != nil {
for _, d := range s.devManager.GetAllDevices() {
dev, err := resCtrl.DeviceToLinuxDevice(d.GetHostPath())
if err != nil {
s.Logger().WithError(err).WithField("device", d.GetHostPath()).Warn("Could not add device to sandbox resources")
continue
}
resources.Devices = append(resources.Devices, dev)
}
}
// Create the sandbox resource controller (cgroups on Linux).
// Depending on the SandboxCgroupOnly value, this cgroup
// will either hold all the pod threads (SandboxCgroupOnly is true)
// or only the virtual CPU ones (SandboxCgroupOnly is false).
s.sandboxController, err = resCtrl.NewSandboxResourceController(cgroupPath, &resources, s.config.SandboxCgroupOnly)
if err != nil {
return fmt.Errorf("Could not create the sandbox resource controller %v", err)
}
// Now that the sandbox resource controller is created, we can set the state controller paths.
s.state.SandboxCgroupPath = s.sandboxController.ID()
s.state.OverheadCgroupPath = ""
if s.config.SandboxCgroupOnly {
s.overheadController = nil
} else {
// The shim configuration is requesting that we do not put all threads
// into the sandbox resource controller.
// We're creating an overhead controller, with no constraints. Everything but
// the vCPU threads will eventually make it there.
overheadController, err := resCtrl.NewResourceController(fmt.Sprintf("%s%s", resCtrlKataOverheadID, s.id), &specs.LinuxResources{})
// TODO: support systemd cgroups overhead cgroup
// https://github.com/kata-containers/kata-containers/issues/2963
if err != nil {
return err
}
s.overheadController = overheadController
s.state.OverheadCgroupPath = s.overheadController.ID()
}
return nil
}
// storeSandbox stores a sandbox config.
func (s *Sandbox) storeSandbox(ctx context.Context) error {
span, _ := katatrace.Trace(ctx, s.Logger(), "storeSandbox", sandboxTracingTags, map[string]string{"sandbox_id": s.id})
defer span.End()
// flush data to storage
if err := s.Save(); err != nil {
return err
}
return nil
}
func rwLockSandbox(sandboxID string) (func() error, error) {
store, err := persist.GetDriver()
if err != nil {
return nil, fmt.Errorf("failed to get fs persist driver: %v", err)
}
return store.Lock(sandboxID, true)
}
// findContainer returns a container from the containers list held by the
// sandbox structure, based on a container ID.
func (s *Sandbox) findContainer(containerID string) (*Container, error) {
if s == nil {
return nil, types.ErrNeedSandbox
}
if containerID == "" {
return nil, types.ErrNeedContainerID
}
if c, ok := s.containers[containerID]; ok {
return c, nil
}
return nil, errors.Wrapf(types.ErrNoSuchContainer, "Could not find the container %q from the sandbox %q containers list",
containerID, s.id)
}
// removeContainer removes a container from the containers list held by the
// sandbox structure, based on a container ID.
func (s *Sandbox) removeContainer(containerID string) error {
if s == nil {
return types.ErrNeedSandbox
}
if containerID == "" {
return types.ErrNeedContainerID
}
if _, ok := s.containers[containerID]; !ok {
return errors.Wrapf(types.ErrNoSuchContainer, "Could not remove the container %q from the sandbox %q containers list",
containerID, s.id)
}
delete(s.containers, containerID)
return nil
}
// Delete deletes an already created sandbox.
// The VM in which the sandbox is running will be shut down.
func (s *Sandbox) Delete(ctx context.Context) error {
if s.state.State != types.StateReady &&
s.state.State != types.StatePaused &&
s.state.State != types.StateStopped {
return fmt.Errorf("Sandbox not ready, paused or stopped, impossible to delete")
}
for _, c := range s.containers {
if err := c.delete(ctx); err != nil {
s.Logger().WithError(err).WithField("container`", c.id).Debug("failed to delete container")
}
}
if !rootless.IsRootless() {
if err := s.resourceControllerDelete(); err != nil {
s.Logger().WithError(err).Errorf("failed to cleanup the %s resource controllers", s.sandboxController)
}
}
if s.monitor != nil {
s.monitor.stop()
}
if err := s.hypervisor.Cleanup(ctx); err != nil {
s.Logger().WithError(err).Error("failed to Cleanup hypervisor")
}
if err := s.fsShare.Cleanup(ctx); err != nil {
s.Logger().WithError(err).Error("failed to cleanup share files")
}
return s.store.Destroy(s.id)
}
func (s *Sandbox) createNetwork(ctx context.Context) error {
if s.config.NetworkConfig.DisableNewNetwork ||
s.config.NetworkConfig.NetworkID == "" {
return nil
}
// docker container needs the hypervisor process ID to find out the container netns,
// which means that the hypervisor has to support network device hotplug so that docker
// can use the prestart hooks to set up container netns.
caps := s.hypervisor.Capabilities(ctx)
if !caps.IsNetworkDeviceHotplugSupported() {
spec := s.GetPatchedOCISpec()
if utils.IsDockerContainer(spec) {
return errors.New("docker container needs network device hotplug but the configured hypervisor does not support it")
}
}
span, ctx := katatrace.Trace(ctx, s.Logger(), "createNetwork", sandboxTracingTags, map[string]string{"sandbox_id": s.id})
defer span.End()
katatrace.AddTags(span, "network", s.network, "NetworkConfig", s.config.NetworkConfig)
// In case there is a factory, network interfaces are hotplugged
// after the vm is started.
if s.factory != nil {
return nil
}
// Add all the networking endpoints.