Skip to content

Commit 322dda3

Browse files
committed
NRI: add TestNRIContainerCreateEnvVarMod
Signed-off-by: Rob Murray <rob.murray@docker.com>
1 parent b67f0c0 commit 322dda3

File tree

5 files changed

+1222
-0
lines changed

5 files changed

+1222
-0
lines changed
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package nri
2+
3+
import (
4+
"context"
5+
"os"
6+
"testing"
7+
8+
"github.com/moby/moby/v2/internal/testutil"
9+
"github.com/moby/moby/v2/internal/testutil/environment"
10+
"go.opentelemetry.io/otel"
11+
"go.opentelemetry.io/otel/codes"
12+
)
13+
14+
var (
15+
testEnv *environment.Execution
16+
baseContext context.Context
17+
)
18+
19+
func TestMain(m *testing.M) {
20+
shutdown := testutil.ConfigureTracing()
21+
ctx, span := otel.Tracer("").Start(context.Background(), "integration/plugin/volume.TestMain")
22+
baseContext = ctx
23+
24+
var err error
25+
testEnv, err = environment.New(ctx)
26+
if err != nil {
27+
span.SetStatus(codes.Error, err.Error())
28+
span.End()
29+
shutdown(ctx)
30+
panic(err)
31+
}
32+
err = environment.EnsureFrozenImagesLinux(ctx, testEnv)
33+
if err != nil {
34+
span.SetStatus(codes.Error, err.Error())
35+
span.End()
36+
shutdown(ctx)
37+
panic(err)
38+
}
39+
testEnv.Print()
40+
code := m.Run()
41+
if code != 0 {
42+
span.SetStatus(codes.Error, "m.Run() exited with non-zero code")
43+
}
44+
shutdown(ctx)
45+
os.Exit(code)
46+
}

integration/daemon/nri/nri_test.go

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
package nri
2+
3+
import (
4+
"path/filepath"
5+
"testing"
6+
7+
"github.com/containerd/nri/pkg/api"
8+
"github.com/moby/moby/client"
9+
"github.com/moby/moby/v2/integration/internal/container"
10+
"github.com/moby/moby/v2/internal/testutil"
11+
"github.com/moby/moby/v2/internal/testutil/daemon"
12+
"gotest.tools/v3/assert"
13+
is "gotest.tools/v3/assert/cmp"
14+
"gotest.tools/v3/skip"
15+
)
16+
17+
func TestNRIContainerCreateEnvVarMod(t *testing.T) {
18+
skip.If(t, testEnv.IsRemoteDaemon, "cannot run daemon when remote daemon")
19+
skip.If(t, testEnv.DaemonInfo.OSType == "windows")
20+
skip.If(t, testEnv.IsRootless)
21+
22+
ctx := testutil.StartSpan(baseContext, t)
23+
24+
tmp := t.TempDir()
25+
sockPath := filepath.Join(tmp, "nri.sock")
26+
27+
d := daemon.New(t)
28+
d.StartWithBusybox(ctx, t,
29+
"--nri-opts=enable=true,socket-path="+sockPath,
30+
"--iptables=false", "--ip6tables=false",
31+
)
32+
defer d.Stop(t)
33+
c := d.NewClientT(t)
34+
35+
testcases := []struct {
36+
name string
37+
ctrCreateAdj *api.ContainerAdjustment
38+
expEnv string
39+
}{
40+
{
41+
name: "env/set",
42+
ctrCreateAdj: &api.ContainerAdjustment{Env: []*api.KeyValue{{Key: "NRI_SAYS", Value: "hello"}}},
43+
expEnv: "NRI_SAYS=hello",
44+
},
45+
{
46+
name: "env/modify",
47+
ctrCreateAdj: &api.ContainerAdjustment{Env: []*api.KeyValue{{Key: "HOSTNAME", Value: "nrivictim"}}},
48+
expEnv: "HOSTNAME=nrivictim",
49+
},
50+
}
51+
52+
for _, tc := range testcases {
53+
t.Run(tc.name, func(t *testing.T) {
54+
stopPlugin := startBuiltinPlugin(ctx, t, builtinPluginConfig{
55+
pluginName: "nri-test-plugin",
56+
pluginIdx: "00",
57+
sockPath: sockPath,
58+
ctrCreateAdj: tc.ctrCreateAdj,
59+
})
60+
defer stopPlugin()
61+
62+
ctrId := container.Run(ctx, t, c)
63+
defer func() { _, _ = c.ContainerRemove(ctx, ctrId, client.ContainerRemoveOptions{Force: true}) }()
64+
65+
inspect, err := c.ContainerInspect(ctx, ctrId, client.ContainerInspectOptions{})
66+
if assert.Check(t, err) {
67+
assert.Check(t, is.Contains(inspect.Container.Config.Env, tc.expEnv))
68+
}
69+
})
70+
}
71+
}

integration/daemon/nri/plugin.go

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
/*
2+
Based on https://github.com/containerd/nri/blob/main/plugins/template/ - which is ...
3+
4+
Copyright The containerd Authors.
5+
6+
Licensed under the Apache License, Version 2.0 (the "License");
7+
you may not use this file except in compliance with the License.
8+
You may obtain a copy of the License at
9+
10+
http://www.apache.org/licenses/LICENSE-2.0
11+
12+
Unless required by applicable law or agreed to in writing, software
13+
distributed under the License is distributed on an "AS IS" BASIS,
14+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
See the License for the specific language governing permissions and
16+
limitations under the License.
17+
*/
18+
19+
package nri
20+
21+
import (
22+
"context"
23+
"errors"
24+
"testing"
25+
26+
"github.com/containerd/log"
27+
"github.com/containerd/nri/pkg/api"
28+
"github.com/containerd/nri/pkg/stub"
29+
"gotest.tools/v3/assert"
30+
)
31+
32+
type builtinPluginConfig struct {
33+
pluginName string
34+
pluginIdx string
35+
sockPath string
36+
ctrCreateAdj *api.ContainerAdjustment
37+
}
38+
39+
type builtinPlugin struct {
40+
stub stub.Stub
41+
logG func(context.Context) *log.Entry
42+
config builtinPluginConfig
43+
}
44+
45+
// startBuiltinPlugin turns the test binary into an NRI plugin, or fails the test.
46+
// It is the caller's responsibility to call the returned function to stop the plugin.
47+
func startBuiltinPlugin(ctx context.Context, t *testing.T, cfg builtinPluginConfig) func() {
48+
p := &builtinPlugin{
49+
logG: func(ctx context.Context) *log.Entry {
50+
return log.G(ctx).WithField("nri-plugin", cfg.pluginIdx+"-"+cfg.pluginName)
51+
},
52+
config: cfg,
53+
}
54+
stub, err := stub.New(p,
55+
stub.WithOnClose(p.onClose),
56+
stub.WithPluginName(cfg.pluginName),
57+
stub.WithPluginIdx(cfg.pluginIdx),
58+
stub.WithSocketPath(cfg.sockPath),
59+
)
60+
assert.Assert(t, err)
61+
p.stub = stub
62+
err = p.stub.Start(ctx)
63+
assert.Assert(t, err)
64+
return p.stub.Stop
65+
}
66+
67+
func (p *builtinPlugin) Configure(ctx context.Context, config, runtime, version string) (stub.EventMask, error) {
68+
p.logG(ctx).Infof("Connected to %s/%s...", runtime, version)
69+
70+
if config != "" {
71+
return 0, errors.New("plugin config from yaml is not implemented")
72+
}
73+
return 0, nil
74+
}
75+
76+
func (p *builtinPlugin) Synchronize(ctx context.Context, pods []*api.PodSandbox, containers []*api.Container) ([]*api.ContainerUpdate, error) {
77+
p.logG(ctx).Infof("Synchronized state with the runtime (%d pods, %d containers)...",
78+
len(pods), len(containers))
79+
return nil, nil
80+
}
81+
82+
func (p *builtinPlugin) Shutdown(ctx context.Context) {
83+
p.logG(ctx).Info("Runtime shutting down...")
84+
}
85+
86+
func (p *builtinPlugin) RunPodSandbox(ctx context.Context, pod *api.PodSandbox) error {
87+
p.logG(ctx).Infof("Started pod %s/%s...", pod.GetNamespace(), pod.GetName())
88+
return nil
89+
}
90+
91+
func (p *builtinPlugin) StopPodSandbox(ctx context.Context, pod *api.PodSandbox) error {
92+
p.logG(ctx).Infof("Stopped pod %s/%s...", pod.GetNamespace(), pod.GetName())
93+
return nil
94+
}
95+
96+
func (p *builtinPlugin) RemovePodSandbox(ctx context.Context, pod *api.PodSandbox) error {
97+
p.logG(ctx).Infof("Removed pod %s/%s...", pod.GetNamespace(), pod.GetName())
98+
return nil
99+
}
100+
101+
func (p *builtinPlugin) CreateContainer(ctx context.Context, pod *api.PodSandbox, ctr *api.Container) (*api.ContainerAdjustment, []*api.ContainerUpdate, error) {
102+
p.logG(ctx).Infof("Creating container %s/%s/%s...", pod.GetNamespace(), pod.GetName(), ctr.GetName())
103+
104+
//
105+
// This is the container creation request handler. Because the container
106+
// has not been created yet, this is the lifecycle event which allows you
107+
// the largest set of changes to the container's configuration, including
108+
// some of the later immutable parameters. Take a look at the adjustment
109+
// functions in pkg/api/adjustment.go to see the available controls.
110+
//
111+
// In addition to reconfiguring the container being created, you are also
112+
// allowed to update other existing containers. Take a look at the update
113+
// functions in pkg/api/update.go to see the available controls.
114+
//
115+
116+
return p.config.ctrCreateAdj, []*api.ContainerUpdate{}, nil
117+
}
118+
119+
func (p *builtinPlugin) PostCreateContainer(ctx context.Context, pod *api.PodSandbox, ctr *api.Container) error {
120+
p.logG(ctx).Infof("Created container %s/%s/%s...", pod.GetNamespace(), pod.GetName(), ctr.GetName())
121+
return nil
122+
}
123+
124+
func (p *builtinPlugin) StartContainer(ctx context.Context, pod *api.PodSandbox, ctr *api.Container) error {
125+
p.logG(ctx).Infof("Starting container %s/%s/%s...", pod.GetNamespace(), pod.GetName(), ctr.GetName())
126+
return nil
127+
}
128+
129+
func (p *builtinPlugin) PostStartContainer(ctx context.Context, pod *api.PodSandbox, ctr *api.Container) error {
130+
p.logG(ctx).Infof("Started container %s/%s/%s...", pod.GetNamespace(), pod.GetName(), ctr.GetName())
131+
return nil
132+
}
133+
134+
func (p *builtinPlugin) UpdateContainer(ctx context.Context, pod *api.PodSandbox, ctr *api.Container, r *api.LinuxResources) ([]*api.ContainerUpdate, error) {
135+
p.logG(ctx).Infof("Updating container %s/%s/%s...", pod.GetNamespace(), pod.GetName(), ctr.GetName())
136+
137+
//
138+
// This is the container update request handler. You can make changes to
139+
// the container update before it is applied. Take a look at the functions
140+
// in pkg/api/update.go to see the available controls.
141+
//
142+
// In addition to altering the pending update itself, you are also allowed
143+
// to update other existing containers.
144+
//
145+
146+
updates := []*api.ContainerUpdate{}
147+
148+
return updates, nil
149+
}
150+
151+
func (p *builtinPlugin) PostUpdateContainer(ctx context.Context, pod *api.PodSandbox, ctr *api.Container) error {
152+
p.logG(ctx).Infof("Updated container %s/%s/%s...", pod.GetNamespace(), pod.GetName(), ctr.GetName())
153+
return nil
154+
}
155+
156+
func (p *builtinPlugin) StopContainer(ctx context.Context, pod *api.PodSandbox, ctr *api.Container) ([]*api.ContainerUpdate, error) {
157+
p.logG(ctx).Infof("Stopped container %s/%s/%s...", pod.GetNamespace(), pod.GetName(), ctr.GetName())
158+
159+
//
160+
// This is the container (post-)stop request handler. You can update any
161+
// of the remaining running containers. Take a look at the functions in
162+
// pkg/api/update.go to see the available controls.
163+
//
164+
165+
return []*api.ContainerUpdate{}, nil
166+
}
167+
168+
func (p *builtinPlugin) RemoveContainer(ctx context.Context, pod *api.PodSandbox, ctr *api.Container) error {
169+
p.logG(ctx).Infof("Removed container %s/%s/%s...", pod.GetNamespace(), pod.GetName(), ctr.GetName())
170+
return nil
171+
}
172+
173+
func (p *builtinPlugin) onClose() {
174+
p.logG(context.Background()).Infof("Connection to the runtime lost.")
175+
}

0 commit comments

Comments
 (0)