nri: add experimental NRI plugin.
Add a common NRI 'service' plugin. It takes care of relaying requests and respones to and from NRI (external NRI plugins) and the high-level containerd namespace-independent logic of applying NRI container adjustments and updates to actual CRI and other containers. The namespace-dependent details of the necessary container manipulation operations are to be implemented by namespace- specific adaptations. This NRI plugin defines the API which such adaptations need to implement. Signed-off-by: Krisztian Litkey <krisztian.litkey@intel.com>
This commit is contained in:
58
pkg/nri/config.go
Normal file
58
pkg/nri/config.go
Normal file
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
Copyright The containerd Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package nri
|
||||
|
||||
import (
|
||||
nri "github.com/containerd/nri/pkg/adaptation"
|
||||
)
|
||||
|
||||
// Config data for NRI.
|
||||
type Config struct {
|
||||
// Disable this NRI plugin and containerd NRI functionality altogether.
|
||||
Disable bool `toml:"disable" json:"disable"`
|
||||
// ConfigPath is the path to the NRI configuration file to use.
|
||||
ConfigPath string `toml:"config_file" json:"configFile"`
|
||||
// SocketPath is the path to the NRI socket to create for NRI plugins to connect to.
|
||||
SocketPath string `toml:"socket_path" json:"socketPath"`
|
||||
// PluginPath is the path to search for NRI plugins to launch on startup.
|
||||
PluginPath string `toml:"plugin_path" json:"pluginPath"`
|
||||
}
|
||||
|
||||
// DefaultConfig returns the default configuration.
|
||||
func DefaultConfig() *Config {
|
||||
return &Config{
|
||||
Disable: true,
|
||||
ConfigPath: nri.DefaultConfigPath,
|
||||
SocketPath: nri.DefaultSocketPath,
|
||||
PluginPath: nri.DefaultPluginPath,
|
||||
}
|
||||
}
|
||||
|
||||
// toOptions returns NRI options for this configuration.
|
||||
func (c *Config) toOptions() []nri.Option {
|
||||
opts := []nri.Option{}
|
||||
if c.ConfigPath != "" {
|
||||
opts = append(opts, nri.WithConfigPath(c.ConfigPath))
|
||||
}
|
||||
if c.SocketPath != "" {
|
||||
opts = append(opts, nri.WithSocketPath(c.SocketPath))
|
||||
}
|
||||
if c.PluginPath != "" {
|
||||
opts = append(opts, nri.WithPluginPath(c.PluginPath))
|
||||
}
|
||||
return opts
|
||||
}
|
||||
72
pkg/nri/container.go
Normal file
72
pkg/nri/container.go
Normal file
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
Copyright The containerd Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package nri
|
||||
|
||||
import (
|
||||
nri "github.com/containerd/nri/pkg/adaptation"
|
||||
)
|
||||
|
||||
// Container interface for interacting with NRI.
|
||||
type Container interface {
|
||||
GetDomain() string
|
||||
|
||||
GetPodSandboxID() string
|
||||
GetID() string
|
||||
GetName() string
|
||||
GetState() nri.ContainerState
|
||||
GetLabels() map[string]string
|
||||
GetAnnotations() map[string]string
|
||||
GetArgs() []string
|
||||
GetEnv() []string
|
||||
GetMounts() []*nri.Mount
|
||||
GetHooks() *nri.Hooks
|
||||
GetLinuxContainer() LinuxContainer
|
||||
|
||||
GetPid() uint32
|
||||
}
|
||||
|
||||
type LinuxContainer interface {
|
||||
GetLinuxNamespaces() []*nri.LinuxNamespace
|
||||
GetLinuxDevices() []*nri.LinuxDevice
|
||||
GetLinuxResources() *nri.LinuxResources
|
||||
GetOOMScoreAdj() *int
|
||||
GetCgroupsPath() string
|
||||
}
|
||||
|
||||
func commonContainerToNRI(ctr Container) *nri.Container {
|
||||
return &nri.Container{
|
||||
Id: ctr.GetID(),
|
||||
PodSandboxId: ctr.GetPodSandboxID(),
|
||||
Name: ctr.GetName(),
|
||||
State: ctr.GetState(),
|
||||
Labels: ctr.GetLabels(),
|
||||
Annotations: ctr.GetAnnotations(),
|
||||
Args: ctr.GetArgs(),
|
||||
Env: ctr.GetEnv(),
|
||||
Mounts: ctr.GetMounts(),
|
||||
Hooks: ctr.GetHooks(),
|
||||
Pid: ctr.GetPid(),
|
||||
}
|
||||
}
|
||||
|
||||
func containersToNRI(ctrList []Container) []*nri.Container {
|
||||
ctrs := []*nri.Container{}
|
||||
for _, ctr := range ctrList {
|
||||
ctrs = append(ctrs, containerToNRI(ctr))
|
||||
}
|
||||
return ctrs
|
||||
}
|
||||
37
pkg/nri/container_linux.go
Normal file
37
pkg/nri/container_linux.go
Normal file
@@ -0,0 +1,37 @@
|
||||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
/*
|
||||
Copyright The containerd Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package nri
|
||||
|
||||
import (
|
||||
nri "github.com/containerd/nri/pkg/adaptation"
|
||||
)
|
||||
|
||||
func containerToNRI(ctr Container) *nri.Container {
|
||||
nriCtr := commonContainerToNRI(ctr)
|
||||
lnxCtr := ctr.GetLinuxContainer()
|
||||
nriCtr.Linux = &nri.LinuxContainer{
|
||||
Namespaces: lnxCtr.GetLinuxNamespaces(),
|
||||
Devices: lnxCtr.GetLinuxDevices(),
|
||||
Resources: lnxCtr.GetLinuxResources(),
|
||||
OomScoreAdj: nri.Int(lnxCtr.GetOOMScoreAdj()),
|
||||
CgroupsPath: lnxCtr.GetCgroupsPath(),
|
||||
}
|
||||
return nriCtr
|
||||
}
|
||||
28
pkg/nri/container_other.go
Normal file
28
pkg/nri/container_other.go
Normal file
@@ -0,0 +1,28 @@
|
||||
//go:build !linux
|
||||
// +build !linux
|
||||
|
||||
/*
|
||||
Copyright The containerd Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package nri
|
||||
|
||||
import (
|
||||
nri "github.com/containerd/nri/pkg/adaptation"
|
||||
)
|
||||
|
||||
func containerToNRI(ctr Container) *nri.Container {
|
||||
return commonContainerToNRI(ctr)
|
||||
}
|
||||
183
pkg/nri/domain.go
Normal file
183
pkg/nri/domain.go
Normal file
@@ -0,0 +1,183 @@
|
||||
/*
|
||||
Copyright The containerd Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package nri
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/containerd/containerd/errdefs"
|
||||
"github.com/containerd/containerd/log"
|
||||
"github.com/containerd/containerd/namespaces"
|
||||
nri "github.com/containerd/nri/pkg/adaptation"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// Domain implements the functions the generic NRI interface needs to
|
||||
// deal with pods and containers from a particular containerd namespace.
|
||||
type Domain interface {
|
||||
// GetName() returns the containerd namespace for this domain.
|
||||
GetName() string
|
||||
|
||||
// ListPodSandboxes list all pods in this namespace.
|
||||
ListPodSandboxes() []PodSandbox
|
||||
|
||||
// ListContainer list all containers in this namespace.
|
||||
ListContainers() []Container
|
||||
|
||||
// GetPodSandbox returns the pod for the given ID.
|
||||
GetPodSandbox(string) (PodSandbox, bool)
|
||||
|
||||
// GetContainer returns the container for the given ID.
|
||||
GetContainer(string) (Container, bool)
|
||||
|
||||
// UpdateContainer applies an NRI container update request in the namespace.
|
||||
UpdateContainer(context.Context, *nri.ContainerUpdate) error
|
||||
|
||||
// EvictContainer evicts the requested container in the namespace.
|
||||
EvictContainer(context.Context, *nri.ContainerEviction) error
|
||||
}
|
||||
|
||||
// RegisterDomain registers an NRI domain for a containerd namespace.
|
||||
func RegisterDomain(d Domain) {
|
||||
err := domains.add(d)
|
||||
if err != nil {
|
||||
logrus.WithError(err).Fatalf("Failed to register namespace %q with NRI", d.GetName())
|
||||
}
|
||||
|
||||
logrus.Infof("Registered namespace %q with NRI", d.GetName())
|
||||
}
|
||||
|
||||
type domainTable struct {
|
||||
sync.Mutex
|
||||
domains map[string]Domain
|
||||
}
|
||||
|
||||
func (t *domainTable) add(d Domain) error {
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
namespace := d.GetName()
|
||||
|
||||
if _, ok := t.domains[namespace]; ok {
|
||||
return errdefs.ErrAlreadyExists
|
||||
}
|
||||
|
||||
t.domains[namespace] = d
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *domainTable) listPodSandboxes() []PodSandbox {
|
||||
var pods []PodSandbox
|
||||
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
for _, d := range t.domains {
|
||||
pods = append(pods, d.ListPodSandboxes()...)
|
||||
}
|
||||
return pods
|
||||
}
|
||||
|
||||
func (t *domainTable) listContainers() []Container {
|
||||
var ctrs []Container
|
||||
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
for _, d := range t.domains {
|
||||
ctrs = append(ctrs, d.ListContainers()...)
|
||||
}
|
||||
return ctrs
|
||||
}
|
||||
|
||||
func (t *domainTable) getContainer(id string) (Container, Domain) {
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
// TODO(klihub): Are ID conflicts across namespaces possible ? Probably...
|
||||
|
||||
for _, d := range t.domains {
|
||||
if ctr, ok := d.GetContainer(id); ok {
|
||||
return ctr, d
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (t *domainTable) updateContainers(ctx context.Context, updates []*nri.ContainerUpdate) ([]*nri.ContainerUpdate, error) {
|
||||
var failed []*nri.ContainerUpdate
|
||||
|
||||
for _, u := range updates {
|
||||
_, d := t.getContainer(u.ContainerId)
|
||||
if d == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
domain := d.GetName()
|
||||
err := d.UpdateContainer(namespaces.WithNamespace(ctx, domain), u)
|
||||
if err != nil {
|
||||
log.G(ctx).WithError(err).Errorf("NRI update of %s container %s failed",
|
||||
domain, u.ContainerId)
|
||||
if !u.IgnoreFailure {
|
||||
failed = append(failed, u)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
log.G(ctx).Tracef("NRI update of %s container %s successful", domain, u.ContainerId)
|
||||
}
|
||||
|
||||
if len(failed) != 0 {
|
||||
return failed, fmt.Errorf("NRI update of some containers failed")
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (t *domainTable) evictContainers(ctx context.Context, evict []*nri.ContainerEviction) ([]*nri.ContainerEviction, error) {
|
||||
var failed []*nri.ContainerEviction
|
||||
|
||||
for _, e := range evict {
|
||||
_, d := t.getContainer(e.ContainerId)
|
||||
if d == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
domain := d.GetName()
|
||||
err := d.EvictContainer(namespaces.WithNamespace(ctx, domain), e)
|
||||
if err != nil {
|
||||
log.G(ctx).WithError(err).Errorf("NRI eviction of %s container %s failed",
|
||||
domain, e.ContainerId)
|
||||
failed = append(failed, e)
|
||||
continue
|
||||
}
|
||||
|
||||
log.G(ctx).Tracef("NRI eviction of %s container %s successful", domain, e.ContainerId)
|
||||
}
|
||||
|
||||
if len(failed) != 0 {
|
||||
return failed, fmt.Errorf("NRI eviction of some containers failed")
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var domains = &domainTable{
|
||||
domains: make(map[string]Domain),
|
||||
}
|
||||
525
pkg/nri/nri.go
Normal file
525
pkg/nri/nri.go
Normal file
@@ -0,0 +1,525 @@
|
||||
/*
|
||||
Copyright The containerd Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package nri
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"path"
|
||||
"sync"
|
||||
|
||||
"github.com/containerd/containerd/log"
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/containerd/containerd/version"
|
||||
nri "github.com/containerd/nri/pkg/adaptation"
|
||||
)
|
||||
|
||||
// API implements a common API for interfacing NRI from containerd. It is
|
||||
// agnostic to any internal containerd implementation details of pods and
|
||||
// containers. It needs corresponding Domain interfaces for each containerd
|
||||
// namespace it needs to handle. These domains take care of the namespace-
|
||||
// specific details of providing pod and container metadata to NRI and of
|
||||
// applying NRI-requested adjustments to the state of containers.
|
||||
type API interface {
|
||||
// IsEnabled returns true if the NRI interface is enabled and initialized.
|
||||
IsEnabled() bool
|
||||
|
||||
// Start start the NRI interface, allowing external NRI plugins to
|
||||
// connect, register, and hook themselves into the lifecycle events
|
||||
// of pods and containers.
|
||||
Start() error
|
||||
|
||||
// Stop stops the NRI interface.
|
||||
Stop()
|
||||
|
||||
// RunPodSandbox relays pod creation events to NRI.
|
||||
RunPodSandbox(context.Context, PodSandbox) error
|
||||
|
||||
// StopPodSandbox relays pod shutdown events to NRI.
|
||||
StopPodSandbox(context.Context, PodSandbox) error
|
||||
|
||||
// RemovePodSandbox relays pod removal events to NRI.
|
||||
RemovePodSandbox(context.Context, PodSandbox) error
|
||||
|
||||
// CreateContainer relays container creation requests to NRI.
|
||||
CreateContainer(context.Context, PodSandbox, Container) (*nri.ContainerAdjustment, error)
|
||||
|
||||
// PostCreateContainer relays successful container creation events to NRI.
|
||||
PostCreateContainer(context.Context, PodSandbox, Container) error
|
||||
|
||||
// StartContainer relays container start request notifications to NRI.
|
||||
StartContainer(context.Context, PodSandbox, Container) error
|
||||
|
||||
// PostStartContainer relays successful container startup events to NRI.
|
||||
PostStartContainer(context.Context, PodSandbox, Container) error
|
||||
|
||||
// UpdateContainer relays container update requests to NRI.
|
||||
UpdateContainer(context.Context, PodSandbox, Container, *nri.LinuxResources) (*nri.LinuxResources, error)
|
||||
|
||||
// PostUpdateContainer relays successful container update events to NRI.
|
||||
PostUpdateContainer(context.Context, PodSandbox, Container) error
|
||||
|
||||
// StopContainer relays container stop requests to NRI.
|
||||
StopContainer(context.Context, PodSandbox, Container) error
|
||||
|
||||
// NotifyContainerExit handles the exit event of a container.
|
||||
NotifyContainerExit(context.Context, PodSandbox, Container)
|
||||
|
||||
// StopContainer relays container removal events to NRI.
|
||||
RemoveContainer(context.Context, PodSandbox, Container) error
|
||||
}
|
||||
|
||||
type State int
|
||||
|
||||
const (
|
||||
Created State = iota + 1
|
||||
Running
|
||||
Stopped
|
||||
Removed
|
||||
)
|
||||
|
||||
type local struct {
|
||||
sync.Mutex
|
||||
cfg *Config
|
||||
nri *nri.Adaptation
|
||||
|
||||
state map[string]State
|
||||
}
|
||||
|
||||
var _ API = &local{}
|
||||
|
||||
// New creates an instance of the NRI interface with the given configuration.
|
||||
func New(cfg *Config) (API, error) {
|
||||
l := &local{
|
||||
cfg: cfg,
|
||||
}
|
||||
|
||||
if cfg.Disable {
|
||||
logrus.Info("NRI interface is disabled by configuration.")
|
||||
return l, nil
|
||||
}
|
||||
|
||||
var (
|
||||
name = path.Base(version.Package)
|
||||
version = version.Version
|
||||
opts = cfg.toOptions()
|
||||
syncFn = l.syncPlugin
|
||||
updateFn = l.updateFromPlugin
|
||||
err error
|
||||
)
|
||||
|
||||
l.nri, err = nri.New(name, version, syncFn, updateFn, opts...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to initialize NRI interface: %w", err)
|
||||
}
|
||||
|
||||
l.state = make(map[string]State)
|
||||
|
||||
logrus.Info("created NRI interface")
|
||||
|
||||
return l, nil
|
||||
}
|
||||
|
||||
func (l *local) IsEnabled() bool {
|
||||
return l != nil && !l.cfg.Disable
|
||||
}
|
||||
|
||||
func (l *local) Start() error {
|
||||
if !l.IsEnabled() {
|
||||
return nil
|
||||
}
|
||||
|
||||
err := l.nri.Start()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to start NRI interface: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *local) Stop() {
|
||||
if !l.IsEnabled() {
|
||||
return
|
||||
}
|
||||
|
||||
l.Lock()
|
||||
defer l.Unlock()
|
||||
|
||||
l.nri.Stop()
|
||||
l.nri = nil
|
||||
}
|
||||
|
||||
func (l *local) RunPodSandbox(ctx context.Context, pod PodSandbox) error {
|
||||
if !l.IsEnabled() {
|
||||
return nil
|
||||
}
|
||||
|
||||
l.Lock()
|
||||
defer l.Unlock()
|
||||
|
||||
request := &nri.RunPodSandboxRequest{
|
||||
Pod: podSandboxToNRI(pod),
|
||||
}
|
||||
|
||||
err := l.nri.RunPodSandbox(ctx, request)
|
||||
l.setState(pod.GetID(), Running)
|
||||
return err
|
||||
}
|
||||
|
||||
func (l *local) StopPodSandbox(ctx context.Context, pod PodSandbox) error {
|
||||
if !l.IsEnabled() {
|
||||
return nil
|
||||
}
|
||||
|
||||
l.Lock()
|
||||
defer l.Unlock()
|
||||
|
||||
if !l.needsStopping(pod.GetID()) {
|
||||
return nil
|
||||
}
|
||||
|
||||
request := &nri.StopPodSandboxRequest{
|
||||
Pod: podSandboxToNRI(pod),
|
||||
}
|
||||
|
||||
err := l.nri.StopPodSandbox(ctx, request)
|
||||
l.setState(pod.GetID(), Stopped)
|
||||
return err
|
||||
}
|
||||
|
||||
func (l *local) RemovePodSandbox(ctx context.Context, pod PodSandbox) error {
|
||||
if !l.IsEnabled() {
|
||||
return nil
|
||||
}
|
||||
|
||||
l.Lock()
|
||||
defer l.Unlock()
|
||||
|
||||
if !l.needsRemoval(pod.GetID()) {
|
||||
return nil
|
||||
}
|
||||
|
||||
request := &nri.RemovePodSandboxRequest{
|
||||
Pod: podSandboxToNRI(pod),
|
||||
}
|
||||
|
||||
err := l.nri.RemovePodSandbox(ctx, request)
|
||||
l.setState(pod.GetID(), Removed)
|
||||
return err
|
||||
}
|
||||
|
||||
func (l *local) CreateContainer(ctx context.Context, pod PodSandbox, ctr Container) (*nri.ContainerAdjustment, error) {
|
||||
if !l.IsEnabled() {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
l.Lock()
|
||||
defer l.Unlock()
|
||||
|
||||
request := &nri.CreateContainerRequest{
|
||||
Pod: podSandboxToNRI(pod),
|
||||
Container: containerToNRI(ctr),
|
||||
}
|
||||
|
||||
response, err := l.nri.CreateContainer(ctx, request)
|
||||
l.setState(request.Container.Id, Created)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, err = l.evictContainers(ctx, response.Evict)
|
||||
if err != nil {
|
||||
// TODO(klihub): we ignore pre-create eviction failures for now
|
||||
log.G(ctx).WithError(err).Warnf("pre-create eviction failed")
|
||||
}
|
||||
|
||||
if _, err := l.applyUpdates(ctx, response.Update); err != nil {
|
||||
// TODO(klihub): we ignore pre-create update failures for now
|
||||
log.G(ctx).WithError(err).Warnf("pre-create update failed")
|
||||
}
|
||||
|
||||
return response.Adjust, nil
|
||||
}
|
||||
|
||||
func (l *local) PostCreateContainer(ctx context.Context, pod PodSandbox, ctr Container) error {
|
||||
if !l.IsEnabled() {
|
||||
return nil
|
||||
}
|
||||
|
||||
l.Lock()
|
||||
defer l.Unlock()
|
||||
|
||||
request := &nri.PostCreateContainerRequest{
|
||||
Pod: podSandboxToNRI(pod),
|
||||
Container: containerToNRI(ctr),
|
||||
}
|
||||
|
||||
return l.nri.PostCreateContainer(ctx, request)
|
||||
}
|
||||
|
||||
func (l *local) StartContainer(ctx context.Context, pod PodSandbox, ctr Container) error {
|
||||
if !l.IsEnabled() {
|
||||
return nil
|
||||
}
|
||||
|
||||
l.Lock()
|
||||
defer l.Unlock()
|
||||
|
||||
request := &nri.StartContainerRequest{
|
||||
Pod: podSandboxToNRI(pod),
|
||||
Container: containerToNRI(ctr),
|
||||
}
|
||||
|
||||
err := l.nri.StartContainer(ctx, request)
|
||||
l.setState(request.Container.Id, Running)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (l *local) PostStartContainer(ctx context.Context, pod PodSandbox, ctr Container) error {
|
||||
if !l.IsEnabled() {
|
||||
return nil
|
||||
}
|
||||
|
||||
l.Lock()
|
||||
defer l.Unlock()
|
||||
|
||||
request := &nri.PostStartContainerRequest{
|
||||
Pod: podSandboxToNRI(pod),
|
||||
Container: containerToNRI(ctr),
|
||||
}
|
||||
|
||||
return l.nri.PostStartContainer(ctx, request)
|
||||
}
|
||||
|
||||
func (l *local) UpdateContainer(ctx context.Context, pod PodSandbox, ctr Container, req *nri.LinuxResources) (*nri.LinuxResources, error) {
|
||||
if !l.IsEnabled() {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
l.Lock()
|
||||
defer l.Unlock()
|
||||
|
||||
request := &nri.UpdateContainerRequest{
|
||||
Pod: podSandboxToNRI(pod),
|
||||
Container: containerToNRI(ctr),
|
||||
LinuxResources: req,
|
||||
}
|
||||
|
||||
response, err := l.nri.UpdateContainer(ctx, request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, err = l.evictContainers(ctx, response.Evict)
|
||||
if err != nil {
|
||||
// TODO(klihub): we ignore pre-update eviction failures for now
|
||||
log.G(ctx).WithError(err).Warnf("pre-update eviction failed")
|
||||
}
|
||||
|
||||
cnt := len(response.Update)
|
||||
if cnt == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if cnt > 1 {
|
||||
_, err = l.applyUpdates(ctx, response.Update[0:cnt-1])
|
||||
if err != nil {
|
||||
// TODO(klihub): we ignore pre-update update failures for now
|
||||
log.G(ctx).WithError(err).Warnf("pre-update update failed")
|
||||
}
|
||||
}
|
||||
|
||||
return response.Update[cnt-1].GetLinux().GetResources(), nil
|
||||
}
|
||||
|
||||
func (l *local) PostUpdateContainer(ctx context.Context, pod PodSandbox, ctr Container) error {
|
||||
if !l.IsEnabled() {
|
||||
return nil
|
||||
}
|
||||
|
||||
l.Lock()
|
||||
defer l.Unlock()
|
||||
|
||||
request := &nri.PostUpdateContainerRequest{
|
||||
Pod: podSandboxToNRI(pod),
|
||||
Container: containerToNRI(ctr),
|
||||
}
|
||||
|
||||
return l.nri.PostUpdateContainer(ctx, request)
|
||||
}
|
||||
|
||||
func (l *local) StopContainer(ctx context.Context, pod PodSandbox, ctr Container) error {
|
||||
if !l.IsEnabled() {
|
||||
return nil
|
||||
}
|
||||
|
||||
l.Lock()
|
||||
defer l.Unlock()
|
||||
|
||||
return l.stopContainer(ctx, pod, ctr)
|
||||
}
|
||||
|
||||
func (l *local) NotifyContainerExit(ctx context.Context, pod PodSandbox, ctr Container) {
|
||||
go func() {
|
||||
l.Lock()
|
||||
defer l.Unlock()
|
||||
l.stopContainer(ctx, pod, ctr)
|
||||
}()
|
||||
}
|
||||
|
||||
func (l *local) stopContainer(ctx context.Context, pod PodSandbox, ctr Container) error {
|
||||
if !l.needsStopping(ctr.GetID()) {
|
||||
log.G(ctx).Tracef("NRI stopContainer: container %s does not need stopping",
|
||||
ctr.GetID())
|
||||
return nil
|
||||
}
|
||||
|
||||
request := &nri.StopContainerRequest{
|
||||
Pod: podSandboxToNRI(pod),
|
||||
Container: containerToNRI(ctr),
|
||||
}
|
||||
|
||||
response, err := l.nri.StopContainer(ctx, request)
|
||||
l.setState(request.Container.Id, Stopped)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = l.applyUpdates(ctx, response.Update)
|
||||
if err != nil {
|
||||
// TODO(klihub): we ignore post-stop update failures for now
|
||||
log.G(ctx).WithError(err).Warnf("post-stop update failed")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *local) RemoveContainer(ctx context.Context, pod PodSandbox, ctr Container) error {
|
||||
if !l.IsEnabled() {
|
||||
return nil
|
||||
}
|
||||
|
||||
l.Lock()
|
||||
defer l.Unlock()
|
||||
|
||||
if !l.needsRemoval(ctr.GetID()) {
|
||||
return nil
|
||||
}
|
||||
|
||||
l.stopContainer(ctx, pod, ctr)
|
||||
|
||||
request := &nri.RemoveContainerRequest{
|
||||
Pod: podSandboxToNRI(pod),
|
||||
Container: containerToNRI(ctr),
|
||||
}
|
||||
err := l.nri.RemoveContainer(ctx, request)
|
||||
l.setState(request.Container.Id, Removed)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (l *local) syncPlugin(ctx context.Context, syncFn nri.SyncCB) error {
|
||||
l.Lock()
|
||||
defer l.Unlock()
|
||||
|
||||
log.G(ctx).Info("Synchronizing NRI (plugin) with current runtime state")
|
||||
|
||||
pods := podSandboxesToNRI(domains.listPodSandboxes())
|
||||
containers := containersToNRI(domains.listContainers())
|
||||
|
||||
for _, ctr := range containers {
|
||||
switch ctr.GetState() {
|
||||
case nri.ContainerState_CONTAINER_CREATED:
|
||||
l.setState(ctr.GetId(), Created)
|
||||
case nri.ContainerState_CONTAINER_RUNNING, nri.ContainerState_CONTAINER_PAUSED:
|
||||
l.setState(ctr.GetId(), Running)
|
||||
case nri.ContainerState_CONTAINER_STOPPED:
|
||||
l.setState(ctr.GetId(), Stopped)
|
||||
default:
|
||||
l.setState(ctr.GetId(), Removed)
|
||||
}
|
||||
}
|
||||
|
||||
updates, err := syncFn(ctx, pods, containers)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = l.applyUpdates(ctx, updates)
|
||||
if err != nil {
|
||||
// TODO(klihub): we ignore post-sync update failures for now
|
||||
log.G(ctx).WithError(err).Warnf("post-sync update failed")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *local) updateFromPlugin(ctx context.Context, req []*nri.ContainerUpdate) ([]*nri.ContainerUpdate, error) {
|
||||
l.Lock()
|
||||
defer l.Unlock()
|
||||
|
||||
log.G(ctx).Trace("Unsolicited NRI container updates")
|
||||
|
||||
failed, err := l.applyUpdates(ctx, req)
|
||||
return failed, err
|
||||
}
|
||||
|
||||
func (l *local) applyUpdates(ctx context.Context, updates []*nri.ContainerUpdate) ([]*nri.ContainerUpdate, error) {
|
||||
// TODO(klihub): should we pre-save state and attempt a rollback on failure ?
|
||||
failed, err := domains.updateContainers(ctx, updates)
|
||||
return failed, err
|
||||
}
|
||||
|
||||
func (l *local) evictContainers(ctx context.Context, evict []*nri.ContainerEviction) ([]*nri.ContainerEviction, error) {
|
||||
failed, err := domains.evictContainers(ctx, evict)
|
||||
return failed, err
|
||||
}
|
||||
|
||||
func (l *local) setState(id string, state State) {
|
||||
if state != Removed {
|
||||
l.state[id] = state
|
||||
return
|
||||
}
|
||||
|
||||
delete(l.state, id)
|
||||
}
|
||||
|
||||
func (l *local) getState(id string) State {
|
||||
if state, ok := l.state[id]; ok {
|
||||
return state
|
||||
}
|
||||
|
||||
return Removed
|
||||
}
|
||||
|
||||
func (l *local) needsStopping(id string) bool {
|
||||
s := l.getState(id)
|
||||
if s == Created || s == Running {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (l *local) needsRemoval(id string) bool {
|
||||
s := l.getState(id)
|
||||
if s == Created || s == Running || s == Stopped {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
36
pkg/nri/plugin/plugin.go
Normal file
36
pkg/nri/plugin/plugin.go
Normal file
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
Copyright The containerd Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package plugin
|
||||
|
||||
import (
|
||||
"github.com/containerd/containerd/pkg/nri"
|
||||
"github.com/containerd/containerd/plugin"
|
||||
)
|
||||
|
||||
func init() {
|
||||
plugin.Register(&plugin.Registration{
|
||||
Type: plugin.NRIApiPlugin,
|
||||
ID: "nri",
|
||||
Config: nri.DefaultConfig(),
|
||||
InitFn: initFunc,
|
||||
})
|
||||
}
|
||||
|
||||
func initFunc(ic *plugin.InitContext) (interface{}, error) {
|
||||
l, err := nri.New(ic.Config.(*nri.Config))
|
||||
return l, err
|
||||
}
|
||||
67
pkg/nri/sandbox.go
Normal file
67
pkg/nri/sandbox.go
Normal file
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
Copyright The containerd Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package nri
|
||||
|
||||
import (
|
||||
nri "github.com/containerd/nri/pkg/adaptation"
|
||||
)
|
||||
|
||||
// PodSandbox interface for interacting with NRI.
|
||||
type PodSandbox interface {
|
||||
GetDomain() string
|
||||
|
||||
GetID() string
|
||||
GetName() string
|
||||
GetUID() string
|
||||
GetNamespace() string
|
||||
GetLabels() map[string]string
|
||||
GetAnnotations() map[string]string
|
||||
GetRuntimeHandler() string
|
||||
GetLinuxPodSandbox() LinuxPodSandbox
|
||||
|
||||
GetPid() uint32
|
||||
}
|
||||
|
||||
type LinuxPodSandbox interface {
|
||||
GetLinuxNamespaces() []*nri.LinuxNamespace
|
||||
GetPodLinuxOverhead() *nri.LinuxResources
|
||||
GetPodLinuxResources() *nri.LinuxResources
|
||||
GetCgroupParent() string
|
||||
GetCgroupsPath() string
|
||||
GetLinuxResources() *nri.LinuxResources
|
||||
}
|
||||
|
||||
func commonPodSandboxToNRI(pod PodSandbox) *nri.PodSandbox {
|
||||
return &nri.PodSandbox{
|
||||
Id: pod.GetID(),
|
||||
Name: pod.GetName(),
|
||||
Uid: pod.GetUID(),
|
||||
Namespace: pod.GetNamespace(),
|
||||
Labels: pod.GetLabels(),
|
||||
Annotations: pod.GetAnnotations(),
|
||||
RuntimeHandler: pod.GetRuntimeHandler(),
|
||||
Pid: pod.GetPid(),
|
||||
}
|
||||
}
|
||||
|
||||
func podSandboxesToNRI(podList []PodSandbox) []*nri.PodSandbox {
|
||||
pods := []*nri.PodSandbox{}
|
||||
for _, pod := range podList {
|
||||
pods = append(pods, podSandboxToNRI(pod))
|
||||
}
|
||||
return pods
|
||||
}
|
||||
38
pkg/nri/sandbox_linux.go
Normal file
38
pkg/nri/sandbox_linux.go
Normal file
@@ -0,0 +1,38 @@
|
||||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
/*
|
||||
Copyright The containerd Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package nri
|
||||
|
||||
import (
|
||||
nri "github.com/containerd/nri/pkg/adaptation"
|
||||
)
|
||||
|
||||
func podSandboxToNRI(pod PodSandbox) *nri.PodSandbox {
|
||||
nriPod := commonPodSandboxToNRI(pod)
|
||||
lnxPod := pod.GetLinuxPodSandbox()
|
||||
nriPod.Linux = &nri.LinuxPodSandbox{
|
||||
Namespaces: lnxPod.GetLinuxNamespaces(),
|
||||
PodOverhead: lnxPod.GetPodLinuxOverhead(),
|
||||
PodResources: lnxPod.GetPodLinuxResources(),
|
||||
CgroupParent: lnxPod.GetCgroupParent(),
|
||||
CgroupsPath: lnxPod.GetCgroupsPath(),
|
||||
Resources: lnxPod.GetLinuxResources(),
|
||||
}
|
||||
return nriPod
|
||||
}
|
||||
28
pkg/nri/sandbox_other.go
Normal file
28
pkg/nri/sandbox_other.go
Normal file
@@ -0,0 +1,28 @@
|
||||
//go:build !linux
|
||||
// +build !linux
|
||||
|
||||
/*
|
||||
Copyright The containerd Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package nri
|
||||
|
||||
import (
|
||||
nri "github.com/containerd/nri/pkg/adaptation"
|
||||
)
|
||||
|
||||
func podSandboxToNRI(pod PodSandbox) *nri.PodSandbox {
|
||||
return commonPodSandboxToNRI(pod)
|
||||
}
|
||||
Reference in New Issue
Block a user