Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion .github/workflows/job-test-in-container.yml
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,17 @@ jobs:
sudo sysctl -w net.ipv4.ip_forward=1
# Enable IPv6 for Docker, and configure docker to use containerd for gha
sudo mkdir -p /etc/docker
echo '{"ipv6": true, "fixed-cidr-v6": "2001:db8:1::/64", "experimental": true, "ip6tables": true}' | sudo tee /etc/docker/daemon.json
echo '{"ipv6": true, "fixed-cidr-v6": "2001:db8:1::/64", "ip6tables": true}' | sudo tee /etc/docker/daemon.json
- name: "Init: enable Docker experimental features"
run: |
sudo mkdir -p /etc/docker
if [ -f /etc/docker/daemon.json ]; then
tmpfile="$(sudo mktemp)"
sudo jq '.experimental = true' /etc/docker/daemon.json | sudo tee "$tmpfile" >/dev/null
sudo mv "$tmpfile" /etc/docker/daemon.json
else
echo '{"experimental": true}' | sudo tee /etc/docker/daemon.json >/dev/null
fi
sudo systemctl restart docker
- name: "Run: integration tests"
run: |
Expand Down
8 changes: 5 additions & 3 deletions .github/workflows/job-test-in-host.yml
Original file line number Diff line number Diff line change
Expand Up @@ -107,9 +107,9 @@ jobs:
name: "Init (linux): prepare host"
run: |
if [ "${{ contains(inputs.binary, 'docker') }}" == true ]; then
echo "::group:: configure cdi for docker"
echo "::group:: configure cdi and experimental for docker"
sudo mkdir -p /etc/docker
sudo jq '.features.cdi = true' /etc/docker/daemon.json | sudo tee /etc/docker/daemon.json.tmp && sudo mv /etc/docker/daemon.json.tmp /etc/docker/daemon.json
sudo jq -n '.features.cdi = true | .experimental = true' | sudo tee /etc/docker/daemon.json
echo "::endgroup::"
echo "::group:: downgrade docker to the specific version we want to test (${{ inputs.docker-version }})"
sudo apt-get update -qq
Expand All @@ -122,6 +122,7 @@ jobs:
| sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update -qq
sudo apt-get install -qq --allow-downgrades docker-ce=${{ inputs.docker-version }} docker-ce-cli=${{ inputs.docker-version }}
sudo systemctl restart docker
echo "::endgroup::"
else
# FIXME: this is missing runc (see top level workflow note about the state of this)
Expand Down Expand Up @@ -153,7 +154,8 @@ jobs:

# FIXME: remove expect when we are done removing unbuffer from tests
echo "::group:: installing test dependencies"
sudo apt-get install -qq expect
sudo add-apt-repository ppa:criu/ppa -y
sudo apt-get install -qq expect criu
echo "::endgroup::"

# This ensures that bridged traffic goes through netfilter
Expand Down
7 changes: 5 additions & 2 deletions .github/workflows/job-test-unit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -68,14 +68,17 @@ jobs:
go-version: ${{ env.GO_VERSION }}
check-latest: true

# Install CNI
# Install CNI and CRIU
- if: ${{ env.GO_VERSION != '' }}
name: "Init: set up CNI"
name: "Init: set up CNI and CRIU"
run: |
if [ "$RUNNER_OS" == "Windows" ]; then
GOPATH=$(go env GOPATH) WINCNI_VERSION=${{ inputs.windows-cni-version }} ./hack/provisioning/windows/cni.sh
elif [ "$RUNNER_OS" == "Linux" ]; then
./hack/provisioning/linux/cni.sh install "${{ inputs.linux-cni-version }}" "amd64" "${{ inputs.linux-cni-sha }}"
sudo apt-get update -qq
sudo add-apt-repository ppa:criu/ppa -y
sudo apt-get install -qq criu
fi

- if: ${{ env.GO_VERSION != '' }}
Expand Down
15 changes: 11 additions & 4 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -309,10 +309,17 @@ ARG DEBIAN_FRONTEND=noninteractive
# `expect` package contains `unbuffer(1)`, which is used for emulating TTY for testing
# `jq` is required to generate test summaries
RUN apt-get update -qq && apt-get install -qq --no-install-recommends \
expect \
jq \
git \
make
software-properties-common \
gnupg \
gpg-agent \
ca-certificates && \
add-apt-repository ppa:criu/ppa && \
apt-get update -qq && apt-get install -qq --no-install-recommends \
expect \
jq \
git \
make \
criu
# We wouldn't need this if Docker Hub could have "golang:${GO_VERSION}-ubuntu"
COPY --from=build-base /usr/local/go /usr/local/go
ARG TARGETARCH
Expand Down
40 changes: 40 additions & 0 deletions cmd/nerdctl/checkpoint/checkpoint.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
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 checkpoint

import (
"github.com/spf13/cobra"

"github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers"
)

func Command() *cobra.Command {
cmd := &cobra.Command{
Annotations: map[string]string{helpers.Category: helpers.Management},
Use: "checkpoint",
Short: "Manage checkpoints.",
RunE: helpers.UnknownSubcommandAction,
SilenceUsage: true,
SilenceErrors: true,
}

cmd.AddCommand(
CreateCommand(),
)

return cmd
}
93 changes: 93 additions & 0 deletions cmd/nerdctl/checkpoint/checkpoint_create.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/*
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 checkpoint

import (
"path/filepath"

"github.com/spf13/cobra"

"github.com/containerd/nerdctl/v2/cmd/nerdctl/completion"
"github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers"
"github.com/containerd/nerdctl/v2/pkg/api/types"
"github.com/containerd/nerdctl/v2/pkg/clientutil"
"github.com/containerd/nerdctl/v2/pkg/cmd/checkpoint"
)

func CreateCommand() *cobra.Command {
var cmd = &cobra.Command{
Use: "create [OPTIONS] CONTAINER CHECKPOINT",
Short: "Create a checkpoint from a running container",
Args: cobra.ExactArgs(2),
RunE: createAction,
ValidArgsFunction: createShellComplete,
SilenceUsage: true,
SilenceErrors: true,
}
cmd.Flags().Bool("leave-running", false, "Leave the container running after checkpointing")
cmd.Flags().String("checkpoint-dir", "", "Checkpoint directory")
return cmd
}

func processCreateFlags(cmd *cobra.Command) (types.CheckpointCreateOptions, error) {
globalOptions, err := helpers.ProcessRootCmdFlags(cmd)
if err != nil {
return types.CheckpointCreateOptions{}, err
}

leaveRunning, err := cmd.Flags().GetBool("leave-running")
if err != nil {
return types.CheckpointCreateOptions{}, err
}
checkpointDir, err := cmd.Flags().GetString("checkpoint-dir")
if err != nil {
return types.CheckpointCreateOptions{}, err
}
if checkpointDir == "" {
checkpointDir = filepath.Join(globalOptions.DataRoot, "checkpoints")
}

return types.CheckpointCreateOptions{
Stdout: cmd.OutOrStdout(),
GOptions: globalOptions,
LeaveRunning: leaveRunning,
CheckpointDir: checkpointDir,
}, nil
}

func createAction(cmd *cobra.Command, args []string) error {
createOptions, err := processCreateFlags(cmd)
if err != nil {
return err
}
client, ctx, cancel, err := clientutil.NewClient(cmd.Context(), createOptions.GOptions.Namespace, createOptions.GOptions.Address)
if err != nil {
return err
}
defer cancel()

err = checkpoint.Create(ctx, client, args[0], args[1], createOptions)
if err != nil {
return err
}

return nil
}

func createShellComplete(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return completion.ImageNames(cmd)
}
126 changes: 126 additions & 0 deletions cmd/nerdctl/checkpoint/checkpoint_create_linux_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
/*
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 checkpoint

import (
"errors"
"testing"

"github.com/containerd/nerdctl/mod/tigron/expect"
"github.com/containerd/nerdctl/mod/tigron/require"
"github.com/containerd/nerdctl/mod/tigron/test"

"github.com/containerd/nerdctl/v2/pkg/testutil"
"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest"
)

func TestCheckpointCreateErrors(t *testing.T) {
testCase := nerdtest.Setup()

testCase.Require = require.All(
require.Not(nerdtest.Rootless),
// Docker version 28.x has a known regression that breaks Checkpoint/Restore functionality.
// The issue is tracked in the moby/moby project as https://github.com/moby/moby/issues/50750.
require.Not(nerdtest.Docker),
)
testCase.SubTests = []*test.Case{
{
Description: "too-few-arguments",
Command: test.Command("checkpoint", "create", "too-few-arguments"),
Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
return &test.Expected{
ExitCode: 1,
}
},
},
{
Description: "too-many-arguments",
Command: test.Command("checkpoint", "create", "too", "many", "arguments"),
Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
return &test.Expected{
ExitCode: 1,
}
},
},
{
Description: "invalid-container-id",
Command: test.Command("checkpoint", "create", "foo", "bar"),
Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
return &test.Expected{
ExitCode: 1,
Errors: []error{errors.New("error creating checkpoint for container: foo")},
}
},
},
}

testCase.Run(t)
}

func TestCheckpointCreate(t *testing.T) {
const (
checkpointName = "checkpoint-bar"
checkpointDir = "/dir/foo"
)
testCase := nerdtest.Setup()
testCase.Require = require.All(
require.Not(nerdtest.Rootless),
// Docker version 28.x has a known regression that breaks Checkpoint/Restore functionality.
// The issue is tracked in the moby/moby project as https://github.com/moby/moby/issues/50750.
require.Not(nerdtest.Docker),
)
testCase.SubTests = []*test.Case{
{
Description: "leave-running=true",
Setup: func(data test.Data, helpers test.Helpers) {
helpers.Ensure("run", "-d", "--name", data.Identifier("container-running"), testutil.CommonImage, "sleep", "infinity")
},
Cleanup: func(data test.Data, helpers test.Helpers) {
helpers.Anyhow("rm", "-f", data.Identifier("container-running"))
},
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
return helpers.Command("checkpoint", "create", "--leave-running", "--checkpoint-dir", checkpointDir, data.Identifier("container-running"), checkpointName+"running")
},
Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
return &test.Expected{
ExitCode: 0,
Output: expect.Equals(checkpointName + "running\n"),
}
},
},
{
Description: "leave-running=false",
Setup: func(data test.Data, helpers test.Helpers) {
helpers.Ensure("run", "-d", "--name", data.Identifier("container-exit"), testutil.CommonImage, "sleep", "infinity")
},
Cleanup: func(data test.Data, helpers test.Helpers) {
helpers.Anyhow("rm", "-f", data.Identifier("container-exit"))
},
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
return helpers.Command("checkpoint", "create", "--checkpoint-dir", checkpointDir, data.Identifier("container-exit"), checkpointName+"exit")
},
Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
return &test.Expected{
ExitCode: 0,
Output: expect.Equals(checkpointName + "exit\n"),
}
},
},
}

testCase.Run(t)
}
27 changes: 27 additions & 0 deletions cmd/nerdctl/checkpoint/checkpoint_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
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 checkpoint

import (
"testing"

"github.com/containerd/nerdctl/v2/pkg/testutil"
)

func TestMain(m *testing.M) {
testutil.M(m)
}
2 changes: 1 addition & 1 deletion cmd/nerdctl/compose/compose_start.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ func startContainers(ctx context.Context, client *containerd.Client, containers
}

// in compose, always disable attach
if err := containerutil.Start(ctx, c, false, false, client, "", (*config.Config)(globalOptions)); err != nil {
if err := containerutil.Start(ctx, c, false, false, client, "", "", (*config.Config)(globalOptions)); err != nil {
return err
}
info, err := c.Info(ctx, containerd.WithoutRefreshedMetadata)
Expand Down
Loading
Loading