Skip to content

Commit 0110b3c

Browse files
committed
Introduce the Windows lcow diff/snaphotter
Implements the Windows lcow differ/snapshotter responsible for managing the creation and lifetime of lcow containers on Windows. Signed-off-by: Justin Terry (VM) <[email protected]>
1 parent ce24328 commit 0110b3c

14 files changed

Lines changed: 751 additions & 13 deletions

File tree

cmd/containerd/builtins_windows.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@
1919
package main
2020

2121
import (
22+
_ "github.com/containerd/containerd/diff/lcow"
2223
_ "github.com/containerd/containerd/diff/windows"
24+
_ "github.com/containerd/containerd/snapshots/lcow"
2325
_ "github.com/containerd/containerd/snapshots/windows"
2426
_ "github.com/containerd/containerd/windows"
2527
)

cmd/containerd/builtins_windows_v2.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@
1919
package main
2020

2121
import (
22+
_ "github.com/containerd/containerd/diff/lcow"
2223
_ "github.com/containerd/containerd/diff/windows"
2324
_ "github.com/containerd/containerd/runtime/v2"
25+
_ "github.com/containerd/containerd/snapshots/lcow"
2426
_ "github.com/containerd/containerd/snapshots/windows"
2527
)

diff/lcow/lcow.go

Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
// +build windows
2+
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 lcow
20+
21+
import (
22+
"context"
23+
"io"
24+
"os/exec"
25+
"path"
26+
"time"
27+
28+
"github.com/containerd/containerd/archive/compression"
29+
"github.com/containerd/containerd/content"
30+
"github.com/containerd/containerd/diff"
31+
"github.com/containerd/containerd/errdefs"
32+
"github.com/containerd/containerd/images"
33+
"github.com/containerd/containerd/log"
34+
"github.com/containerd/containerd/metadata"
35+
"github.com/containerd/containerd/mount"
36+
"github.com/containerd/containerd/plugin"
37+
digest "github.com/opencontainers/go-digest"
38+
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
39+
"github.com/pkg/errors"
40+
"github.com/sirupsen/logrus"
41+
)
42+
43+
func init() {
44+
plugin.Register(&plugin.Registration{
45+
Type: plugin.DiffPlugin,
46+
ID: "windows-lcow",
47+
Requires: []plugin.Type{
48+
plugin.MetadataPlugin,
49+
},
50+
InitFn: func(ic *plugin.InitContext) (interface{}, error) {
51+
md, err := ic.Get(plugin.MetadataPlugin)
52+
if err != nil {
53+
return nil, err
54+
}
55+
56+
ic.Meta.Platforms = append(ic.Meta.Platforms, ocispec.Platform{
57+
OS: "linux",
58+
Architecture: "amd64",
59+
})
60+
return NewWindowsLcowDiff(md.(*metadata.DB).ContentStore())
61+
},
62+
})
63+
}
64+
65+
// CompareApplier handles both comparison and
66+
// application of layer diffs.
67+
type CompareApplier interface {
68+
diff.Applier
69+
diff.Comparer
70+
}
71+
72+
// windowsLcowDiff does filesystem comparison and application
73+
// for Windows specific Linux layer diffs.
74+
type windowsLcowDiff struct {
75+
store content.Store
76+
}
77+
78+
var emptyDesc = ocispec.Descriptor{}
79+
80+
// NewWindowsLcowDiff is the Windows LCOW container layer implementation
81+
// for comparing and applying Linux filesystem layers on Windows
82+
func NewWindowsLcowDiff(store content.Store) (CompareApplier, error) {
83+
return windowsLcowDiff{
84+
store: store,
85+
}, nil
86+
}
87+
88+
// Apply applies the content associated with the provided digests onto the
89+
// provided mounts. Archive content will be extracted and decompressed if
90+
// necessary.
91+
func (s windowsLcowDiff) Apply(ctx context.Context, desc ocispec.Descriptor, mounts []mount.Mount) (d ocispec.Descriptor, err error) {
92+
t1 := time.Now()
93+
defer func() {
94+
if err == nil {
95+
log.G(ctx).WithFields(logrus.Fields{
96+
"d": time.Now().Sub(t1),
97+
"dgst": desc.Digest,
98+
"size": desc.Size,
99+
"media": desc.MediaType,
100+
}).Debugf("diff applied")
101+
}
102+
}()
103+
104+
layer, _, err := mountsToLayerAndParents(mounts)
105+
if err != nil {
106+
return emptyDesc, err
107+
}
108+
109+
isCompressed, err := images.IsCompressedDiff(ctx, desc.MediaType)
110+
if err != nil {
111+
return emptyDesc, errors.Wrapf(errdefs.ErrNotImplemented, "unsupported diff media type: %v", desc.MediaType)
112+
}
113+
114+
ra, err := s.store.ReaderAt(ctx, desc)
115+
if err != nil {
116+
return emptyDesc, errors.Wrap(err, "failed to get reader from content store")
117+
}
118+
defer ra.Close()
119+
rdr := content.NewReader(ra)
120+
if isCompressed {
121+
ds, err := compression.DecompressStream(rdr)
122+
if err != nil {
123+
return emptyDesc, err
124+
}
125+
defer ds.Close()
126+
rdr = ds
127+
}
128+
// Calculate the Digest as we go
129+
digester := digest.Canonical.Digester()
130+
rc := &readCounter{
131+
r: io.TeeReader(rdr, digester.Hash()),
132+
}
133+
134+
cmd := exec.Command(
135+
"runhcs.exe",
136+
"tar2vhd",
137+
"--scratchpath", path.Join(layer, "sandbox.vhdx"), // TODO: JTERRY75 when the snapshotter changes this to be scratch.vhdx update it here too.
138+
"--destpath", path.Join(layer, "layer.vhd"))
139+
140+
cmd.Stdin = rc
141+
if bytes, err := cmd.CombinedOutput(); err != nil {
142+
return emptyDesc, errors.Wrapf(err, "failed to exec runhcs.exe tar2vhd: %s", string(bytes))
143+
}
144+
145+
return ocispec.Descriptor{
146+
MediaType: ocispec.MediaTypeImageLayer,
147+
Size: rc.c,
148+
Digest: digester.Digest(),
149+
}, nil
150+
}
151+
152+
// Compare creates a diff between the given mounts and uploads the result
153+
// to the content store.
154+
func (s windowsLcowDiff) Compare(ctx context.Context, lower, upper []mount.Mount, opts ...diff.Opt) (d ocispec.Descriptor, err error) {
155+
return emptyDesc, errdefs.ErrNotImplemented
156+
}
157+
158+
type readCounter struct {
159+
r io.Reader
160+
c int64
161+
}
162+
163+
func (rc *readCounter) Read(p []byte) (n int, err error) {
164+
n, err = rc.r.Read(p)
165+
rc.c += int64(n)
166+
return
167+
}
168+
169+
func mountsToLayerAndParents(mounts []mount.Mount) (string, []string, error) {
170+
if len(mounts) != 1 {
171+
return "", nil, errors.Wrap(errdefs.ErrInvalidArgument, "number of mounts should always be 1 for Windows lcow-layers")
172+
}
173+
mnt := mounts[0]
174+
if mnt.Type != "lcow-layer" {
175+
return "", nil, errors.Wrap(errdefs.ErrInvalidArgument, "mount layer type must be lcow-layer")
176+
}
177+
178+
parentLayerPaths, err := mnt.GetParentPaths()
179+
if err != nil {
180+
return "", nil, err
181+
}
182+
183+
return mnt.Source, parentLayerPaths, nil
184+
}

diff/windows/windows.go

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -175,12 +175,18 @@ func mountsToLayerAndParents(mounts []mount.Mount) (string, []string, error) {
175175
if len(mounts) != 1 {
176176
return "", nil, errors.Wrap(errdefs.ErrInvalidArgument, "number of mounts should always be 1 for Windows layers")
177177
}
178-
layer := mounts[0].Source
178+
mnt := mounts[0]
179+
if mnt.Type != "windows-layer" {
180+
// This is a special case error. When this is received the diff service
181+
// will attempt the next differ in the chain which for Windows is the
182+
// lcow differ that we want.
183+
return "", nil, errdefs.ErrNotImplemented
184+
}
179185

180-
parentLayerPaths, err := mounts[0].GetParentPaths()
186+
parentLayerPaths, err := mnt.GetParentPaths()
181187
if err != nil {
182188
return "", nil, err
183189
}
184190

185-
return layer, parentLayerPaths, nil
191+
return mnt.Source, parentLayerPaths, nil
186192
}

mount/mount_windows.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ var (
3232

3333
// Mount to the provided target
3434
func (m *Mount) Mount(target string) error {
35+
if m.Type != "windows-layer" {
36+
return errors.Errorf("invalid windows mount type: '%s'", m.Type)
37+
}
38+
3539
home, layerID := filepath.Split(m.Source)
3640

3741
parentLayerPaths, err := m.GetParentPaths()

platforms/defaults.go

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,6 @@ import (
2222
specs "github.com/opencontainers/image-spec/specs-go/v1"
2323
)
2424

25-
// Default returns the default matcher for the platform.
26-
func Default() MatchComparer {
27-
return Only(DefaultSpec())
28-
}
29-
3025
// DefaultString returns the default string specifier for the platform.
3126
func DefaultString() string {
3227
return Format(DefaultSpec())

platforms/defaults_unix.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// +build !windows
2+
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 platforms
20+
21+
// Default returns the default matcher for the platform.
22+
func Default() MatchComparer {
23+
return Only(DefaultSpec())
24+
}

platforms/defaults_windows.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// +build windows
2+
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 platforms
20+
21+
import (
22+
specs "github.com/opencontainers/image-spec/specs-go/v1"
23+
)
24+
25+
// Default returns the default matcher for the platform.
26+
func Default() MatchComparer {
27+
return Ordered(DefaultSpec(), specs.Platform{
28+
OS: "linux",
29+
Architecture: "amd64",
30+
})
31+
}

runtime/v2/manager.go

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,8 @@ import (
3030
"github.com/containerd/containerd/metadata"
3131
"github.com/containerd/containerd/mount"
3232
"github.com/containerd/containerd/namespaces"
33-
"github.com/containerd/containerd/platforms"
3433
"github.com/containerd/containerd/plugin"
3534
"github.com/containerd/containerd/runtime"
36-
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
3735
)
3836

3937
func init() {
@@ -44,7 +42,7 @@ func init() {
4442
plugin.MetadataPlugin,
4543
},
4644
InitFn: func(ic *plugin.InitContext) (interface{}, error) {
47-
ic.Meta.Platforms = []ocispec.Platform{platforms.DefaultSpec()}
45+
ic.Meta.Platforms = supportedPlatforms()
4846
if err := os.MkdirAll(ic.Root, 0711); err != nil {
4947
return nil, err
5048
}

runtime/v2/manager_unix.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// +build !windows
2+
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 v2
20+
21+
import (
22+
"github.com/containerd/containerd/platforms"
23+
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
24+
)
25+
26+
func supportedPlatforms() []ocispec.Platform {
27+
return []ocispec.Platform{platforms.DefaultSpec()}
28+
}

0 commit comments

Comments
 (0)