github.com/Mirantis/virtlet@v1.5.2-0.20191204181327-1659b8a48e9b/pkg/manager/runtime.go (about) 1 /* 2 Copyright 2016-2018 Mirantis 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package manager 18 19 import ( 20 "errors" 21 "fmt" 22 "time" 23 24 cnitypes "github.com/containernetworking/cni/pkg/types" 25 cnicurrent "github.com/containernetworking/cni/pkg/types/current" 26 "github.com/golang/glog" 27 "github.com/jonboulle/clockwork" 28 "golang.org/x/net/context" 29 kubeapi "k8s.io/kubernetes/pkg/kubelet/apis/cri/runtime/v1alpha2" 30 31 "github.com/Mirantis/virtlet/pkg/cni" 32 "github.com/Mirantis/virtlet/pkg/libvirttools" 33 "github.com/Mirantis/virtlet/pkg/metadata" 34 "github.com/Mirantis/virtlet/pkg/metadata/types" 35 "github.com/Mirantis/virtlet/pkg/tapmanager" 36 ) 37 38 const ( 39 runtimeAPIVersion = "0.1.0" 40 runtimeName = "virtlet" 41 runtimeVersion = "0.1.0" 42 ) 43 44 // StreamServer denotes a server that handles Attach and PortForward requests. 45 type StreamServer interface { 46 GetAttach(req *kubeapi.AttachRequest) (*kubeapi.AttachResponse, error) 47 GetPortForward(req *kubeapi.PortForwardRequest) (*kubeapi.PortForwardResponse, error) 48 } 49 50 // GCHandler performs GC when a container is deleted. 51 type GCHandler interface { 52 GC() error 53 } 54 55 // VirtletRuntimeService handles CRI runtime service calls. 56 type VirtletRuntimeService struct { 57 virtTool *libvirttools.VirtualizationTool 58 metadataStore metadata.Store 59 fdManager tapmanager.FDManager 60 streamServer StreamServer 61 gcHandler GCHandler 62 clock clockwork.Clock 63 } 64 65 // NewVirtletRuntimeService returns a new instance of VirtletRuntimeService. 66 func NewVirtletRuntimeService( 67 virtTool *libvirttools.VirtualizationTool, 68 metadataStore metadata.Store, 69 fdManager tapmanager.FDManager, 70 streamServer StreamServer, 71 gcHandler GCHandler, 72 clock clockwork.Clock) *VirtletRuntimeService { 73 if clock == nil { 74 clock = clockwork.NewRealClock() 75 } 76 return &VirtletRuntimeService{ 77 virtTool: virtTool, 78 metadataStore: metadataStore, 79 fdManager: fdManager, 80 streamServer: streamServer, 81 gcHandler: gcHandler, 82 clock: clock, 83 } 84 } 85 86 // Version implements Version method of CRI. 87 func (v *VirtletRuntimeService) Version(ctx context.Context, in *kubeapi.VersionRequest) (*kubeapi.VersionResponse, error) { 88 vRuntimeAPIVersion := runtimeAPIVersion 89 vRuntimeName := runtimeName 90 vRuntimeVersion := runtimeVersion 91 return &kubeapi.VersionResponse{ 92 Version: vRuntimeAPIVersion, 93 RuntimeName: vRuntimeName, 94 RuntimeVersion: vRuntimeVersion, 95 RuntimeApiVersion: vRuntimeVersion, 96 }, nil 97 } 98 99 // 100 // Sandboxes 101 // 102 103 // RunPodSandbox implements RunPodSandbox method of CRI. 104 func (v *VirtletRuntimeService) RunPodSandbox(ctx context.Context, in *kubeapi.RunPodSandboxRequest) (response *kubeapi.RunPodSandboxResponse, retErr error) { 105 config := in.GetConfig() 106 if config == nil { 107 return nil, errors.New("no pod sandbox config passed to RunPodSandbox") 108 } 109 podName := "<no metadata>" 110 if config.Metadata != nil { 111 podName = config.Metadata.Name 112 } 113 if err := validatePodSandboxConfig(config); err != nil { 114 return nil, err 115 } 116 podID := config.Metadata.Uid 117 podNs := config.Metadata.Namespace 118 119 // Check if sandbox already exists, it may happen when virtlet restarts and kubelet "thinks" that sandbox disappered 120 sandbox := v.metadataStore.PodSandbox(podID) 121 sandboxInfo, err := sandbox.Retrieve() 122 if err == nil && sandboxInfo != nil { 123 if sandboxInfo.State == types.PodSandboxState_SANDBOX_READY { 124 return &kubeapi.RunPodSandboxResponse{ 125 PodSandboxId: podID, 126 }, nil 127 } 128 } 129 130 state := kubeapi.PodSandboxState_SANDBOX_READY 131 pnd := &tapmanager.PodNetworkDesc{ 132 PodID: podID, 133 PodNs: podNs, 134 PodName: podName, 135 } 136 // Mimic kubelet's method of handling nameservers. 137 // As of k8s 1.5.2, kubelet doesn't use any nameserver information from CNI. 138 // (TODO: recheck this for 1.6) 139 // CNI is used just to configure the network namespace and CNI DNS 140 // info is ignored. Instead of this, DnsConfig from PodSandboxConfig 141 // is used to configure container's resolv.conf. 142 if config.DnsConfig != nil { 143 pnd.DNS = &cnitypes.DNS{ 144 Nameservers: config.DnsConfig.Servers, 145 Search: config.DnsConfig.Searches, 146 Options: config.DnsConfig.Options, 147 } 148 } 149 150 fdPayload := &tapmanager.GetFDPayload{Description: pnd} 151 csnBytes, err := v.fdManager.AddFDs(podID, fdPayload) 152 // The reason for defer here is that it is also necessary to ReleaseFDs if AddFDs fail 153 // Try to clean up CNI netns (this may be necessary e.g. in case of multiple CNI plugins with CNI Genie) 154 defer func() { 155 if retErr != nil { 156 // Try to clean up CNI netns if we couldn't add the pod to the metadata store or if AddFDs call wasn't 157 // successful to avoid leaking resources 158 if fdErr := v.fdManager.ReleaseFDs(podID); fdErr != nil { 159 glog.Errorf("Error removing pod %s (%s) from CNI network: %v", podName, podID, fdErr) 160 } 161 } 162 }() 163 if err != nil { 164 return nil, fmt.Errorf("Error adding pod %s (%s) to CNI network: %v", podName, podID, err) 165 } 166 167 psi, err := metadata.NewPodSandboxInfo( 168 CRIPodSandboxConfigToPodSandboxConfig(config), 169 csnBytes, types.PodSandboxState(state), v.clock) 170 if err != nil { 171 return nil, err 172 } 173 174 sandbox = v.metadataStore.PodSandbox(config.Metadata.Uid) 175 if err := sandbox.Save( 176 func(c *types.PodSandboxInfo) (*types.PodSandboxInfo, error) { 177 return psi, nil 178 }, 179 ); err != nil { 180 return nil, err 181 } 182 183 return &kubeapi.RunPodSandboxResponse{ 184 PodSandboxId: podID, 185 }, nil 186 } 187 188 // StopPodSandbox implements StopPodSandbox method of CRI. 189 func (v *VirtletRuntimeService) StopPodSandbox(ctx context.Context, in *kubeapi.StopPodSandboxRequest) (*kubeapi.StopPodSandboxResponse, error) { 190 sandbox := v.metadataStore.PodSandbox(in.PodSandboxId) 191 switch sandboxInfo, err := sandbox.Retrieve(); { 192 case err != nil: 193 return nil, err 194 case sandboxInfo == nil: 195 return nil, fmt.Errorf("sandbox %q not found in Virtlet metadata store", in.PodSandboxId) 196 // check if the sandbox is already stopped 197 case sandboxInfo.State != types.PodSandboxState_SANDBOX_NOTREADY: 198 if err := sandbox.Save( 199 func(c *types.PodSandboxInfo) (*types.PodSandboxInfo, error) { 200 // make sure the pod is not removed during the call 201 if c != nil { 202 c.State = types.PodSandboxState_SANDBOX_NOTREADY 203 } 204 return c, nil 205 }, 206 ); err != nil { 207 return nil, err 208 } 209 210 if err := v.fdManager.ReleaseFDs(in.PodSandboxId); err != nil { 211 glog.Errorf("Error releasing tap fd for the pod %q: %v", in.PodSandboxId, err) 212 } 213 } 214 215 response := &kubeapi.StopPodSandboxResponse{} 216 return response, nil 217 } 218 219 // RemovePodSandbox method implements RemovePodSandbox from CRI. 220 func (v *VirtletRuntimeService) RemovePodSandbox(ctx context.Context, in *kubeapi.RemovePodSandboxRequest) (*kubeapi.RemovePodSandboxResponse, error) { 221 podSandboxID := in.PodSandboxId 222 223 if err := v.metadataStore.PodSandbox(podSandboxID).Save( 224 func(c *types.PodSandboxInfo) (*types.PodSandboxInfo, error) { 225 return nil, nil 226 }, 227 ); err != nil { 228 return nil, err 229 } 230 231 response := &kubeapi.RemovePodSandboxResponse{} 232 return response, nil 233 } 234 235 // PodSandboxStatus method implements PodSandboxStatus from CRI. 236 func (v *VirtletRuntimeService) PodSandboxStatus(ctx context.Context, in *kubeapi.PodSandboxStatusRequest) (*kubeapi.PodSandboxStatusResponse, error) { 237 podSandboxID := in.PodSandboxId 238 239 sandbox := v.metadataStore.PodSandbox(podSandboxID) 240 sandboxInfo, err := sandbox.Retrieve() 241 if err != nil { 242 return nil, err 243 } 244 if sandboxInfo == nil { 245 return nil, fmt.Errorf("sandbox %q not found in Virtlet metadata store", podSandboxID) 246 } 247 status := PodSandboxInfoToCRIPodSandboxStatus(sandboxInfo) 248 249 var cniResult *cnicurrent.Result 250 if sandboxInfo.ContainerSideNetwork != nil { 251 cniResult = sandboxInfo.ContainerSideNetwork.Result 252 } 253 254 ip := cni.GetPodIP(cniResult) 255 if ip != "" { 256 status.Network = &kubeapi.PodSandboxNetworkStatus{Ip: ip} 257 } 258 259 response := &kubeapi.PodSandboxStatusResponse{Status: status} 260 return response, nil 261 } 262 263 // ListPodSandbox method implements ListPodSandbox from CRI. 264 func (v *VirtletRuntimeService) ListPodSandbox(ctx context.Context, in *kubeapi.ListPodSandboxRequest) (*kubeapi.ListPodSandboxResponse, error) { 265 filter := CRIPodSandboxFilterToPodSandboxFilter(in.GetFilter()) 266 sandboxes, err := v.metadataStore.ListPodSandboxes(filter) 267 if err != nil { 268 return nil, err 269 } 270 var podSandboxList []*kubeapi.PodSandbox 271 for _, sandbox := range sandboxes { 272 sandboxInfo, err := sandbox.Retrieve() 273 if err != nil { 274 glog.Errorf("Error retrieving pod sandbox %q", sandbox.GetID()) 275 } 276 if sandboxInfo != nil { 277 podSandboxList = append(podSandboxList, PodSandboxInfoToCRIPodSandbox(sandboxInfo)) 278 } 279 } 280 response := &kubeapi.ListPodSandboxResponse{Items: podSandboxList} 281 return response, nil 282 } 283 284 // 285 // Containers 286 // 287 288 // CreateContainer method implements CreateContainer from CRI. 289 func (v *VirtletRuntimeService) CreateContainer(ctx context.Context, in *kubeapi.CreateContainerRequest) (*kubeapi.CreateContainerResponse, error) { 290 config := in.GetConfig() 291 podSandboxID := in.PodSandboxId 292 name := config.GetMetadata().Name 293 294 sandboxInfo, err := v.metadataStore.PodSandbox(podSandboxID).Retrieve() 295 if err != nil { 296 return nil, err 297 } 298 if sandboxInfo == nil { 299 return nil, fmt.Errorf("sandbox %q not in Virtlet metadata store", podSandboxID) 300 } 301 302 // Was a container already started in this sandbox? 303 // NOTE: there is no distinction between lack of key and other types of 304 // errors when accessing boltdb. This will be changed when we switch to 305 // storing whole marshaled sandbox metadata as json. 306 curContainers, err := v.metadataStore.ListPodContainers(podSandboxID) 307 if err != nil { 308 glog.V(3).Infof("Error retrieving pod %q containers", podSandboxID) 309 } else { 310 for _, container := range curContainers { 311 // TODO: check container name; if it's the same, update the network config 312 glog.V(3).Infof("CreateContainer: there's already a container in the sandbox (id: %s)", container.GetID()) 313 //err := v.updateContainer(sandboxInfo, container.GetID()) 314 err := v.virtTool.UpdateContainerNetwork(container.GetID(), sandboxInfo.ContainerSideNetwork) 315 if err != nil { 316 return nil, err 317 } 318 response := &kubeapi.CreateContainerResponse{ContainerId: container.GetID()} 319 return response, nil 320 } 321 } 322 323 fdKey := podSandboxID 324 vmConfig, err := GetVMConfig(in, sandboxInfo.ContainerSideNetwork) 325 if err != nil { 326 return nil, err 327 } 328 if sandboxInfo.ContainerSideNetwork == nil || sandboxInfo.ContainerSideNetwork.Result == nil { 329 fdKey = "" 330 } 331 332 uuid, err := v.virtTool.CreateContainer(vmConfig, fdKey) 333 if err != nil { 334 glog.Errorf("Error creating container %s: %v", name, err) 335 return nil, err 336 } 337 338 response := &kubeapi.CreateContainerResponse{ContainerId: uuid} 339 return response, nil 340 } 341 342 // StartContainer method implements StartContainer from CRI. 343 func (v *VirtletRuntimeService) StartContainer(ctx context.Context, in *kubeapi.StartContainerRequest) (*kubeapi.StartContainerResponse, error) { 344 info, err := v.virtTool.ContainerInfo(in.ContainerId) 345 if err == nil && info != nil && info.State == types.ContainerState_CONTAINER_RUNNING { 346 glog.V(2).Infof("StartContainer: Container %s is already running", in.ContainerId) 347 response := &kubeapi.StartContainerResponse{} 348 return response, nil 349 } 350 351 if err := v.virtTool.StartContainer(in.ContainerId); err != nil { 352 return nil, err 353 } 354 response := &kubeapi.StartContainerResponse{} 355 return response, nil 356 } 357 358 // StopContainer method implements StopContainer from CRI. 359 func (v *VirtletRuntimeService) StopContainer(ctx context.Context, in *kubeapi.StopContainerRequest) (*kubeapi.StopContainerResponse, error) { 360 if err := v.virtTool.StopContainer(in.ContainerId, time.Duration(in.Timeout)*time.Second); err != nil { 361 return nil, err 362 } 363 response := &kubeapi.StopContainerResponse{} 364 return response, nil 365 } 366 367 // RemoveContainer method implements RemoveContainer from CRI. 368 func (v *VirtletRuntimeService) RemoveContainer(ctx context.Context, in *kubeapi.RemoveContainerRequest) (*kubeapi.RemoveContainerResponse, error) { 369 if err := v.virtTool.RemoveContainer(in.ContainerId); err != nil { 370 return nil, err 371 } 372 373 if err := v.gcHandler.GC(); err != nil { 374 return nil, fmt.Errorf("GC error: %v", err) 375 } 376 377 response := &kubeapi.RemoveContainerResponse{} 378 return response, nil 379 } 380 381 // ListContainers method implements ListContainers from CRI. 382 func (v *VirtletRuntimeService) ListContainers(ctx context.Context, in *kubeapi.ListContainersRequest) (*kubeapi.ListContainersResponse, error) { 383 filter := CRIContainerFilterToContainerFilter(in.GetFilter()) 384 containers, err := v.virtTool.ListContainers(filter) 385 if err != nil { 386 return nil, err 387 } 388 var r []*kubeapi.Container 389 for _, c := range containers { 390 r = append(r, ContainerInfoToCRIContainer(c)) 391 } 392 response := &kubeapi.ListContainersResponse{Containers: r} 393 return response, nil 394 } 395 396 // ContainerStatus method implements ContainerStatus from CRI. 397 func (v *VirtletRuntimeService) ContainerStatus(ctx context.Context, in *kubeapi.ContainerStatusRequest) (*kubeapi.ContainerStatusResponse, error) { 398 info, err := v.virtTool.ContainerInfo(in.ContainerId) 399 if err != nil { 400 return nil, err 401 } 402 403 response := &kubeapi.ContainerStatusResponse{Status: ContainerInfoToCRIContainerStatus(info)} 404 return response, nil 405 } 406 407 // ExecSync is a placeholder for an unimplemented CRI method. 408 func (v *VirtletRuntimeService) ExecSync(context.Context, *kubeapi.ExecSyncRequest) (*kubeapi.ExecSyncResponse, error) { 409 return nil, errors.New("not implemented") 410 } 411 412 // Exec is a placeholder for an unimplemented CRI method. 413 func (v *VirtletRuntimeService) Exec(context.Context, *kubeapi.ExecRequest) (*kubeapi.ExecResponse, error) { 414 return nil, errors.New("not implemented") 415 } 416 417 // Attach calls streamer server to implement Attach functionality from CRI. 418 func (v *VirtletRuntimeService) Attach(ctx context.Context, req *kubeapi.AttachRequest) (*kubeapi.AttachResponse, error) { 419 if !req.Stdout && !req.Stderr { 420 // Support k8s 1.8 or earlier. 421 // We don't care about Stderr because it's not used 422 // by the Virtlet stream server. 423 req.Stdout = true 424 } 425 return v.streamServer.GetAttach(req) 426 } 427 428 // PortForward calls streamer server to implement PortForward functionality from CRI. 429 func (v *VirtletRuntimeService) PortForward(ctx context.Context, req *kubeapi.PortForwardRequest) (*kubeapi.PortForwardResponse, error) { 430 return v.streamServer.GetPortForward(req) 431 } 432 433 // UpdateRuntimeConfig is a placeholder for an unimplemented CRI method. 434 func (v *VirtletRuntimeService) UpdateRuntimeConfig(context.Context, *kubeapi.UpdateRuntimeConfigRequest) (*kubeapi.UpdateRuntimeConfigResponse, error) { 435 // we don't need to do anything here for now 436 return &kubeapi.UpdateRuntimeConfigResponse{}, nil 437 } 438 439 // UpdateContainerResources stores in domain on libvirt info about Cpuset 440 // for container then looks for running emulator and tries to adjust its 441 // current settings through cgroups 442 func (v *VirtletRuntimeService) UpdateContainerResources(ctx context.Context, req *kubeapi.UpdateContainerResourcesRequest) (*kubeapi.UpdateContainerResourcesResponse, error) { 443 setByCgroup, err := v.virtTool.UpdateCpusetsForEmulatorProcess(req.GetContainerId(), req.GetLinux().CpusetCpus) 444 if err != nil { 445 return nil, err 446 } 447 if !setByCgroup { 448 if err = v.virtTool.UpdateCpusetsInContainerDefinition(req.GetContainerId(), req.GetLinux().CpusetCpus); err != nil { 449 return nil, err 450 } 451 } 452 return &kubeapi.UpdateContainerResourcesResponse{}, nil 453 } 454 455 // Status method implements Status from CRI for both types of service, Image and Runtime. 456 func (v *VirtletRuntimeService) Status(context.Context, *kubeapi.StatusRequest) (*kubeapi.StatusResponse, error) { 457 ready := true 458 runtimeReadyStr := kubeapi.RuntimeReady 459 networkReadyStr := kubeapi.NetworkReady 460 return &kubeapi.StatusResponse{ 461 Status: &kubeapi.RuntimeStatus{ 462 Conditions: []*kubeapi.RuntimeCondition{ 463 { 464 Type: runtimeReadyStr, 465 Status: ready, 466 }, 467 { 468 Type: networkReadyStr, 469 Status: ready, 470 }, 471 }, 472 }, 473 }, nil 474 } 475 476 // ContainerStats returns cpu/memory/disk usage for particular container id 477 func (v *VirtletRuntimeService) ContainerStats(ctx context.Context, in *kubeapi.ContainerStatsRequest) (*kubeapi.ContainerStatsResponse, error) { 478 info, err := v.virtTool.ContainerInfo(in.ContainerId) 479 if err != nil { 480 return nil, err 481 } 482 vs, err := v.virtTool.VMStats(info.Id, info.Name) 483 if err != nil { 484 return nil, err 485 } 486 fsstats, err := v.virtTool.ImageManager().FilesystemStats() 487 if err != nil { 488 return nil, err 489 } 490 return &kubeapi.ContainerStatsResponse{ 491 Stats: VMStatsToCRIContainerStats(*vs, fsstats.Mountpoint), 492 }, nil 493 } 494 495 // ListContainerStats returns stats (same as ContainerStats) for containers 496 // selected by filter 497 func (v *VirtletRuntimeService) ListContainerStats(ctx context.Context, in *kubeapi.ListContainerStatsRequest) (*kubeapi.ListContainerStatsResponse, error) { 498 filter := CRIContainerStatsFilterToVMStatsFilter(in.GetFilter()) 499 vmstatsList, err := v.virtTool.ListVMStats(filter) 500 if err != nil { 501 return nil, err 502 } 503 fsstats, err := v.virtTool.ImageManager().FilesystemStats() 504 if err != nil { 505 return nil, err 506 } 507 var stats []*kubeapi.ContainerStats 508 for _, vs := range vmstatsList { 509 stats = append(stats, VMStatsToCRIContainerStats(vs, fsstats.Mountpoint)) 510 } 511 512 return &kubeapi.ListContainerStatsResponse{ 513 Stats: stats, 514 }, nil 515 } 516 517 // VMStatsToCRIContainerStats converts internal representation of vm/container stats 518 // to corresponding kubeapi type object 519 func VMStatsToCRIContainerStats(vs types.VMStats, mountpoint string) *kubeapi.ContainerStats { 520 return &kubeapi.ContainerStats{ 521 Attributes: &kubeapi.ContainerAttributes{ 522 Id: vs.ContainerID, 523 Metadata: &kubeapi.ContainerMetadata{ 524 Name: vs.ContainerID, 525 }, 526 }, 527 Cpu: &kubeapi.CpuUsage{ 528 Timestamp: vs.Timestamp, 529 UsageCoreNanoSeconds: &kubeapi.UInt64Value{Value: vs.CpuUsage}, 530 }, 531 Memory: &kubeapi.MemoryUsage{ 532 Timestamp: vs.Timestamp, 533 WorkingSetBytes: &kubeapi.UInt64Value{Value: vs.MemoryUsage}, 534 }, 535 WritableLayer: &kubeapi.FilesystemUsage{ 536 Timestamp: vs.Timestamp, 537 FsId: &kubeapi.FilesystemIdentifier{ 538 Mountpoint: mountpoint, 539 }, 540 UsedBytes: &kubeapi.UInt64Value{Value: vs.FsBytes}, 541 InodesUsed: &kubeapi.UInt64Value{Value: 1}, 542 }, 543 } 544 } 545 546 // ReopenContainerLog is a placeholder for an unimplemented CRI method. 547 func (v *VirtletRuntimeService) ReopenContainerLog(ctx context.Context, in *kubeapi.ReopenContainerLogRequest) (*kubeapi.ReopenContainerLogResponse, error) { 548 return &kubeapi.ReopenContainerLogResponse{}, nil 549 } 550 551 func validatePodSandboxConfig(config *kubeapi.PodSandboxConfig) error { 552 if config.GetMetadata() == nil { 553 return errors.New("sandbox config is missing Metadata attribute") 554 } 555 556 return nil 557 }