Windows: Updates Windows Vendoring

Updates windows dependent libraries for vendoing.
This commit is contained in:
Nathan Gieseker 2019-01-23 18:43:18 -08:00
parent a686cc4bd8
commit 9a429d8d25
839 changed files with 282895 additions and 774 deletions

17
vendor/github.com/Microsoft/hcsshim/.gometalinter.json generated vendored Normal file
View File

@ -0,0 +1,17 @@
{
"Vendor": true,
"Deadline": "2m",
"Sort": [
"linter",
"severity",
"path",
"line"
],
"Skip": [
"internal\\schema2"
],
"EnableGC": true,
"Enable": [
"gofmt"
]
}

View File

@ -6,14 +6,24 @@ clone_folder: c:\gopath\src\github.com\Microsoft\hcsshim
environment:
GOPATH: c:\gopath
PATH: C:\mingw-w64\x86_64-7.2.0-posix-seh-rt_v5-rev1\mingw64\bin;%PATH%
PATH: C:\mingw-w64\x86_64-7.2.0-posix-seh-rt_v5-rev1\mingw64\bin;%GOPATH%\bin;C:\gometalinter-2.0.12-windows-amd64;%PATH%
stack: go 1.11
build_script:
- go get -v -t ./...
- appveyor DownloadFile https://github.com/alecthomas/gometalinter/releases/download/v2.0.12/gometalinter-2.0.12-windows-amd64.zip
- 7z x gometalinter-2.0.12-windows-amd64.zip -y -oC:\ > NUL
- gometalinter.exe --config .gometalinter.json ./...
- go build ./cmd/wclayer
- go build ./cmd/runhcs
- go build ./cmd/tar2ext4
- go test -v ./... -tags admin
- go test -c ./test/functional/ -tags functional
- go test -c ./test/runhcs/ -tags integration
artifacts:
- path: 'wclayer.exe'
- path: 'runhcs.exe'
- path: 'tar2ext4.exe'
- path: 'functional.test.exe'
- path: 'runhcs.test.exe'

191
vendor/github.com/Microsoft/hcsshim/cmd/runhcs/LICENSE generated vendored Normal file
View File

@ -0,0 +1,191 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
Copyright 2014 Docker, Inc.
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.

22
vendor/github.com/Microsoft/hcsshim/cmd/runhcs/NOTICE generated vendored Normal file
View File

@ -0,0 +1,22 @@
runhcs is a fork of runc.
The following is runc's legal notice.
---
runc
Copyright 2012-2015 Docker, Inc.
This product includes software developed at Docker, Inc. (http://www.docker.com).
The following is courtesy of our legal counsel:
Use and transfer of Docker may be subject to certain restrictions by the
United States and other governments.
It is your responsibility to ensure that your use and/or transfer does not
violate applicable laws.
For more information, please see http://www.bis.doc.gov
See also http://www.apache.org/dev/crypto.html and/or seek legal counsel.

View File

@ -0,0 +1,848 @@
package main
import (
"encoding/json"
"errors"
"fmt"
"os"
"path/filepath"
"strconv"
"strings"
"time"
winio "github.com/Microsoft/go-winio"
"github.com/Microsoft/hcsshim/internal/cni"
"github.com/Microsoft/hcsshim/internal/guid"
"github.com/Microsoft/hcsshim/internal/hcs"
"github.com/Microsoft/hcsshim/internal/hcsoci"
"github.com/Microsoft/hcsshim/internal/logfields"
"github.com/Microsoft/hcsshim/internal/regstate"
"github.com/Microsoft/hcsshim/internal/runhcs"
"github.com/Microsoft/hcsshim/internal/uvm"
"github.com/Microsoft/hcsshim/osversion"
specs "github.com/opencontainers/runtime-spec/specs-go"
"github.com/sirupsen/logrus"
"golang.org/x/sys/windows"
)
var errContainerStopped = errors.New("container is stopped")
type persistedState struct {
// ID is the id of this container/UVM.
ID string `json:",omitempty"`
// Owner is the owner value passed into the runhcs command and may be `""`.
Owner string `json:",omitempty"`
// SandboxID is the sandbox identifer passed in via OCI specifications. This
// can either be the sandbox itself or the sandbox this container should run
// in. See `parseSandboxAnnotations`.
SandboxID string `json:",omitempty"`
// HostID will be VM ID hosting this container. If a sandbox is used it will
// match the `SandboxID`.
HostID string `json:",omitempty"`
// Bundle is the folder path on disk where the container state and spec files
// reside.
Bundle string `json:",omitempty"`
Created time.Time `json:",omitempty"`
Rootfs string `json:",omitempty"`
// Spec is the in memory deserialized values found on `Bundle\config.json`.
Spec *specs.Spec `json:",omitempty"`
RequestedNetNS string `json:",omitempty"`
// IsHost is `true` when this is a VM isolated config.
IsHost bool `json:",omitempty"`
// UniqueID is a unique ID generated per container config.
UniqueID guid.GUID `json:",omitempty"`
// HostUniqueID is the unique ID of the hosting VM if this container is
// hosted.
HostUniqueID guid.GUID `json:",omitempty"`
}
type containerStatus string
const (
containerRunning containerStatus = "running"
containerStopped containerStatus = "stopped"
containerCreated containerStatus = "created"
containerPaused containerStatus = "paused"
containerUnknown containerStatus = "unknown"
keyState = "state"
keyResources = "resources"
keyShimPid = "shim"
keyInitPid = "pid"
keyNetNS = "netns"
// keyPidMapFmt is the format to use when mapping a host OS pid to a guest
// pid.
keyPidMapFmt = "pid-%d"
)
type container struct {
persistedState
ShimPid int
hc *hcs.System
resources *hcsoci.Resources
}
func startProcessShim(id, pidFile, logFile string, spec *specs.Process) (_ *os.Process, err error) {
// Ensure the stdio handles inherit to the child process. This isn't undone
// after the StartProcess call because the caller never launches another
// process before exiting.
for _, f := range []*os.File{os.Stdin, os.Stdout, os.Stderr} {
err = windows.SetHandleInformation(windows.Handle(f.Fd()), windows.HANDLE_FLAG_INHERIT, windows.HANDLE_FLAG_INHERIT)
if err != nil {
return nil, err
}
}
args := []string{
"--stdin", strconv.Itoa(int(os.Stdin.Fd())),
"--stdout", strconv.Itoa(int(os.Stdout.Fd())),
"--stderr", strconv.Itoa(int(os.Stderr.Fd())),
}
if spec != nil {
args = append(args, "--exec")
}
if strings.HasPrefix(logFile, runhcs.SafePipePrefix) {
args = append(args, "--log-pipe", logFile)
}
args = append(args, id)
return launchShim("shim", pidFile, logFile, args, spec)
}
func launchShim(cmd, pidFile, logFile string, args []string, data interface{}) (_ *os.Process, err error) {
executable, err := os.Executable()
if err != nil {
return nil, err
}
// Create a pipe to use as stderr for the shim process. This is used to
// retrieve early error information, up to the point that the shim is ready
// to launch a process in the container.
rp, wp, err := os.Pipe()
if err != nil {
return nil, err
}
defer rp.Close()
defer wp.Close()
// Create a pipe to send the data, if one is provided.
var rdatap, wdatap *os.File
if data != nil {
rdatap, wdatap, err = os.Pipe()
if err != nil {
return nil, err
}
defer rdatap.Close()
defer wdatap.Close()
}
var log *os.File
fullargs := []string{os.Args[0]}
if logFile != "" {
if !strings.HasPrefix(logFile, runhcs.SafePipePrefix) {
log, err = os.OpenFile(logFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND|os.O_SYNC, 0666)
if err != nil {
return nil, err
}
defer log.Close()
}
fullargs = append(fullargs, "--log-format", logFormat)
if logrus.GetLevel() == logrus.DebugLevel {
fullargs = append(fullargs, "--debug")
}
}
fullargs = append(fullargs, cmd)
fullargs = append(fullargs, args...)
attr := &os.ProcAttr{
Files: []*os.File{rdatap, wp, log},
}
p, err := os.StartProcess(executable, fullargs, attr)
if err != nil {
return nil, err
}
defer func() {
if err != nil {
p.Kill()
}
}()
wp.Close()
// Write the data if provided.
if data != nil {
rdatap.Close()
dataj, err := json.Marshal(data)
if err != nil {
return nil, err
}
_, err = wdatap.Write(dataj)
if err != nil {
return nil, err
}
wdatap.Close()
}
err = runhcs.GetErrorFromPipe(rp, p)
if err != nil {
return nil, err
}
if pidFile != "" {
if err = createPidFile(pidFile, p.Pid); err != nil {
return nil, err
}
}
return p, nil
}
// parseSandboxAnnotations searches `a` for various annotations used by
// different runtimes to represent a sandbox ID, and sandbox type.
//
// If found returns the tuple `(sandboxID, isSandbox)` where `isSandbox == true`
// indicates the identifer is the sandbox itself; `isSandbox == false` indicates
// the identifer is the sandbox in which to place this container. Otherwise
// returns `("", false)`.
func parseSandboxAnnotations(a map[string]string) (string, bool) {
var t, id string
if t = a["io.kubernetes.cri.container-type"]; t != "" {
id = a["io.kubernetes.cri.sandbox-id"]
} else if t = a["io.kubernetes.cri-o.ContainerType"]; t != "" {
id = a["io.kubernetes.cri-o.SandboxID"]
} else if t = a["io.kubernetes.docker.type"]; t != "" {
id = a["io.kubernetes.sandbox.id"]
if t == "podsandbox" {
t = "sandbox"
}
}
if t == "container" {
return id, false
}
if t == "sandbox" {
return id, true
}
return "", false
}
// parseAnnotationsBool searches `a` for `key` and if found verifies that the
// value is `true` or `false` in any case. If `key` is not found returns `def`.
func parseAnnotationsBool(a map[string]string, key string, def bool) bool {
if v, ok := a[key]; ok {
switch strings.ToLower(v) {
case "true":
return true
case "false":
return false
default:
logrus.WithFields(logrus.Fields{
logfields.OCIAnnotation: key,
logfields.Value: v,
logfields.ExpectedType: logfields.Bool,
}).Warning("annotation could not be parsed")
}
}
return def
}
// parseAnnotationsCPU searches `s.Annotations` for the CPU annotation. If
// not found searches `s` for the Windows CPU section. If neither are found
// returns `def`.
func parseAnnotationsCPU(s *specs.Spec, annotation string, def int32) int32 {
if m := parseAnnotationsUint64(s.Annotations, annotation, 0); m != 0 {
return int32(m)
}
if s.Windows != nil &&
s.Windows.Resources != nil &&
s.Windows.Resources.CPU != nil &&
s.Windows.Resources.CPU.Count != nil &&
*s.Windows.Resources.CPU.Count > 0 {
return int32(*s.Windows.Resources.CPU.Count)
}
return def
}
// parseAnnotationsMemory searches `s.Annotations` for the memory annotation. If
// not found searches `s` for the Windows memory section. If neither are found
// returns `def`.
func parseAnnotationsMemory(s *specs.Spec, annotation string, def int32) int32 {
if m := parseAnnotationsUint64(s.Annotations, annotation, 0); m != 0 {
return int32(m)
}
if s.Windows != nil &&
s.Windows.Resources != nil &&
s.Windows.Resources.Memory != nil &&
s.Windows.Resources.Memory.Limit != nil &&
*s.Windows.Resources.Memory.Limit > 0 {
return int32(*s.Windows.Resources.Memory.Limit)
}
return def
}
// parseAnnotationsPreferredRootFSType searches `a` for `key` and verifies that the
// value is in the set of allowed values. If `key` is not found returns `def`.
func parseAnnotationsPreferredRootFSType(a map[string]string, key string, def uvm.PreferredRootFSType) uvm.PreferredRootFSType {
if v, ok := a[key]; ok {
switch v {
case "initrd":
return uvm.PreferredRootFSTypeInitRd
case "vhd":
return uvm.PreferredRootFSTypeVHD
default:
logrus.Warningf("annotation: '%s', with value: '%s' must be 'initrd' or 'vhd'", key, v)
}
}
return def
}
// parseAnnotationsUint32 searches `a` for `key` and if found verifies that the
// value is a 32 bit unsigned integer. If `key` is not found returns `def`.
func parseAnnotationsUint32(a map[string]string, key string, def uint32) uint32 {
if v, ok := a[key]; ok {
countu, err := strconv.ParseUint(v, 10, 32)
if err == nil {
v := uint32(countu)
return v
}
logrus.WithFields(logrus.Fields{
logfields.OCIAnnotation: key,
logfields.Value: v,
logfields.ExpectedType: logfields.Uint32,
logrus.ErrorKey: err,
}).Warning("annotation could not be parsed")
}
return def
}
// parseAnnotationsUint64 searches `a` for `key` and if found verifies that the
// value is a 64 bit unsigned integer. If `key` is not found returns `def`.
func parseAnnotationsUint64(a map[string]string, key string, def uint64) uint64 {
if v, ok := a[key]; ok {
countu, err := strconv.ParseUint(v, 10, 64)
if err == nil {
return countu
}
logrus.WithFields(logrus.Fields{
logfields.OCIAnnotation: key,
logfields.Value: v,
logfields.ExpectedType: logfields.Uint64,
logrus.ErrorKey: err,
}).Warning("annotation could not be parsed")
}
return def
}
// startVMShim starts a vm-shim command with the specified `opts`. `opts` can be `uvm.OptionsWCOW` or `uvm.OptionsLCOW`
func (c *container) startVMShim(logFile string, opts interface{}) (*os.Process, error) {
var os string
if _, ok := opts.(*uvm.OptionsLCOW); ok {
os = "linux"
} else {
os = "windows"
}
args := []string{"--os", os}
if strings.HasPrefix(logFile, runhcs.SafePipePrefix) {
args = append(args, "--log-pipe", logFile)
}
args = append(args, c.VMPipePath())
return launchShim("vmshim", "", logFile, args, opts)
}
type containerConfig struct {
ID string
Owner string
HostID string
PidFile string
ShimLogFile, VMLogFile string
Spec *specs.Spec
VMConsolePipe string
}
func createContainer(cfg *containerConfig) (_ *container, err error) {
// Store the container information in a volatile registry key.
cwd, err := os.Getwd()
if err != nil {
return nil, err
}
vmisolated := cfg.Spec.Linux != nil || (cfg.Spec.Windows != nil && cfg.Spec.Windows.HyperV != nil)
sandboxID, isSandbox := parseSandboxAnnotations(cfg.Spec.Annotations)
hostID := cfg.HostID
if isSandbox {
if sandboxID != cfg.ID {
return nil, errors.New("sandbox ID must match ID")
}
} else if sandboxID != "" {
// Validate that the sandbox container exists.
sandbox, err := getContainer(sandboxID, false)
if err != nil {
return nil, err
}
defer sandbox.Close()
if sandbox.SandboxID != sandboxID {
return nil, fmt.Errorf("container %s is not a sandbox", sandboxID)
}
if hostID == "" {
// Use the sandbox's host.
hostID = sandbox.HostID
} else if sandbox.HostID == "" {
return nil, fmt.Errorf("sandbox container %s is not running in a VM host, but host %s was specified", sandboxID, hostID)
} else if hostID != sandbox.HostID {
return nil, fmt.Errorf("sandbox container %s has a different host %s from the requested host %s", sandboxID, sandbox.HostID, hostID)
}
if vmisolated && hostID == "" {
return nil, fmt.Errorf("container %s is not a VM isolated sandbox", sandboxID)
}
}
uniqueID := guid.New()
newvm := false
var hostUniqueID guid.GUID
if hostID != "" {
host, err := getContainer(hostID, false)
if err != nil {
return nil, err
}
defer host.Close()
if !host.IsHost {
return nil, fmt.Errorf("host container %s is not a VM host", hostID)
}
hostUniqueID = host.UniqueID
} else if vmisolated && (isSandbox || cfg.Spec.Linux != nil || osversion.Get().Build >= osversion.RS5) {
// This handles all LCOW, Pod Sandbox, and (Windows Xenon V2 for RS5+)
hostID = cfg.ID
newvm = true
hostUniqueID = uniqueID
}
// Make absolute the paths in Root.Path and Windows.LayerFolders.
rootfs := ""
if cfg.Spec.Root != nil {
rootfs = cfg.Spec.Root.Path
if rootfs != "" && !filepath.IsAbs(rootfs) && !strings.HasPrefix(rootfs, `\\?\`) {
rootfs = filepath.Join(cwd, rootfs)
cfg.Spec.Root.Path = rootfs
}
}
netNS := ""
if cfg.Spec.Windows != nil {
for i, f := range cfg.Spec.Windows.LayerFolders {
if !filepath.IsAbs(f) && !strings.HasPrefix(rootfs, `\\?\`) {
cfg.Spec.Windows.LayerFolders[i] = filepath.Join(cwd, f)
}
}
// Determine the network namespace to use.
if cfg.Spec.Windows.Network != nil {
if cfg.Spec.Windows.Network.NetworkSharedContainerName != "" {
// RS4 case
err = stateKey.Get(cfg.Spec.Windows.Network.NetworkSharedContainerName, keyNetNS, &netNS)
if err != nil {
if _, ok := err.(*regstate.NoStateError); !ok {
return nil, err
}
}
} else if cfg.Spec.Windows.Network.NetworkNamespace != "" {
// RS5 case
netNS = cfg.Spec.Windows.Network.NetworkNamespace
}
}
}
// Store the initial container state in the registry so that the delete
// command can clean everything up if something goes wrong.
c := &container{
persistedState: persistedState{
ID: cfg.ID,
Owner: cfg.Owner,
Bundle: cwd,
Rootfs: rootfs,
Created: time.Now(),
Spec: cfg.Spec,
SandboxID: sandboxID,
HostID: hostID,
IsHost: newvm,
RequestedNetNS: netNS,
UniqueID: uniqueID,
HostUniqueID: hostUniqueID,
},
}
err = stateKey.Create(cfg.ID, keyState, &c.persistedState)
if err != nil {
return nil, err
}
defer func() {
if err != nil {
c.Remove()
}
}()
if isSandbox && vmisolated {
cnicfg := cni.NewPersistedNamespaceConfig(netNS, cfg.ID, hostUniqueID)
err = cnicfg.Store()
if err != nil {
return nil, err
}
defer func() {
if err != nil {
cnicfg.Remove()
}
}()
}
// Start a VM if necessary.
if newvm {
var opts interface{}
const (
annotationAllowOvercommit = "io.microsoft.virtualmachine.computetopology.memory.allowovercommit"
annotationEnableDeferredCommit = "io.microsoft.virtualmachine.computetopology.memory.enabledeferredcommit"
annotationMemorySizeInMB = "io.microsoft.virtualmachine.computetopology.memory.sizeinmb"
annotationProcessorCount = "io.microsoft.virtualmachine.computetopology.processor.count"
annotationVPMemCount = "io.microsoft.virtualmachine.devices.virtualpmem.maximumcount"
annotationVPMemSize = "io.microsoft.virtualmachine.devices.virtualpmem.maximumsizebytes"
annotationPreferredRootFSType = "io.microsoft.virtualmachine.lcow.preferredrootfstype"
)
if cfg.Spec.Linux != nil {
lopts := uvm.NewDefaultOptionsLCOW(vmID(c.ID), cfg.Owner)
lopts.MemorySizeInMB = parseAnnotationsMemory(cfg.Spec, annotationMemorySizeInMB, lopts.MemorySizeInMB)
lopts.AllowOvercommit = parseAnnotationsBool(cfg.Spec.Annotations, annotationAllowOvercommit, lopts.AllowOvercommit)
lopts.EnableDeferredCommit = parseAnnotationsBool(cfg.Spec.Annotations, annotationEnableDeferredCommit, lopts.EnableDeferredCommit)
lopts.ProcessorCount = parseAnnotationsCPU(cfg.Spec, annotationProcessorCount, lopts.ProcessorCount)
lopts.ConsolePipe = cfg.VMConsolePipe
lopts.VPMemDeviceCount = parseAnnotationsUint32(cfg.Spec.Annotations, annotationVPMemCount, lopts.VPMemDeviceCount)
lopts.VPMemSizeBytes = parseAnnotationsUint64(cfg.Spec.Annotations, annotationVPMemSize, lopts.VPMemSizeBytes)
lopts.PreferredRootFSType = parseAnnotationsPreferredRootFSType(cfg.Spec.Annotations, annotationPreferredRootFSType, lopts.PreferredRootFSType)
switch lopts.PreferredRootFSType {
case uvm.PreferredRootFSTypeInitRd:
lopts.RootFSFile = uvm.InitrdFile
case uvm.PreferredRootFSTypeVHD:
lopts.RootFSFile = uvm.VhdFile
}
opts = lopts
} else {
wopts := uvm.NewDefaultOptionsWCOW(vmID(c.ID), cfg.Owner)
wopts.MemorySizeInMB = parseAnnotationsMemory(cfg.Spec, annotationMemorySizeInMB, wopts.MemorySizeInMB)
wopts.AllowOvercommit = parseAnnotationsBool(cfg.Spec.Annotations, annotationAllowOvercommit, wopts.AllowOvercommit)
wopts.EnableDeferredCommit = parseAnnotationsBool(cfg.Spec.Annotations, annotationEnableDeferredCommit, wopts.EnableDeferredCommit)
wopts.ProcessorCount = parseAnnotationsCPU(cfg.Spec, annotationProcessorCount, wopts.ProcessorCount)
// In order for the UVM sandbox.vhdx not to collide with the actual
// nested Argon sandbox.vhdx we append the \vm folder to the last entry
// in the list.
layersLen := len(cfg.Spec.Windows.LayerFolders)
layers := make([]string, layersLen)
copy(layers, cfg.Spec.Windows.LayerFolders)
vmPath := filepath.Join(layers[layersLen-1], "vm")
err := os.MkdirAll(vmPath, 0)
if err != nil {
return nil, err
}
layers[layersLen-1] = vmPath
wopts.LayerFolders = layers
opts = wopts
}
shim, err := c.startVMShim(cfg.VMLogFile, opts)
if err != nil {
return nil, err
}
shim.Release()
}
if c.HostID != "" {
// Call to the VM shim process to create the container. This is done so
// that the VM process can keep track of the VM's virtual hardware
// resource use.
err = c.issueVMRequest(runhcs.OpCreateContainer)
if err != nil {
return nil, err
}
c.hc, err = hcs.OpenComputeSystem(cfg.ID)
if err != nil {
return nil, err
}
} else {
// Create the container directly from this process.
err = createContainerInHost(c, nil)
if err != nil {
return nil, err
}
}
// Create the shim process for the container.
err = startContainerShim(c, cfg.PidFile, cfg.ShimLogFile)
if err != nil {
if e := c.Kill(); e == nil {
c.Remove()
}
return nil, err
}
return c, nil
}
func (c *container) ShimPipePath() string {
return runhcs.SafePipePath("runhcs-shim-" + c.UniqueID.String())
}
func (c *container) VMPipePath() string {
return runhcs.VMPipePath(c.HostUniqueID)
}
func (c *container) VMIsolated() bool {
return c.HostID != ""
}
func (c *container) unmountInHost(vm *uvm.UtilityVM, all bool) error {
resources := &hcsoci.Resources{}
err := stateKey.Get(c.ID, keyResources, resources)
if _, ok := err.(*regstate.NoStateError); ok {
return nil
}
if err != nil {
return err
}
err = hcsoci.ReleaseResources(resources, vm, all)
if err != nil {
stateKey.Set(c.ID, keyResources, resources)
return err
}
err = stateKey.Clear(c.ID, keyResources)
if err != nil {
return err
}
return nil
}
func (c *container) Unmount(all bool) error {
if c.VMIsolated() {
op := runhcs.OpUnmountContainerDiskOnly
if all {
op = runhcs.OpUnmountContainer
}
err := c.issueVMRequest(op)
if err != nil {
if _, ok := err.(*noVMError); ok {
logrus.WithFields(logrus.Fields{
logfields.ContainerID: c.ID,
logfields.UVMID: c.HostID,
logrus.ErrorKey: errors.New("failed to unmount container resources"),
}).Warning("VM shim could not be contacted")
} else {
return err
}
}
} else {
c.unmountInHost(nil, false)
}
return nil
}
func createContainerInHost(c *container, vm *uvm.UtilityVM) (err error) {
if c.hc != nil {
return errors.New("container already created")
}
// Create the container without starting it.
opts := &hcsoci.CreateOptions{
ID: c.ID,
Owner: c.Owner,
Spec: c.Spec,
HostingSystem: vm,
NetworkNamespace: c.RequestedNetNS,
}
vmid := ""
if vm != nil {
vmid = vm.ID()
}
logrus.WithFields(logrus.Fields{
logfields.ContainerID: c.ID,
logfields.UVMID: vmid,
}).Info("creating container in UVM")
hc, resources, err := hcsoci.CreateContainer(opts)
if err != nil {
return err
}
defer func() {
if err != nil {
hc.Terminate()
hc.Wait()
hcsoci.ReleaseResources(resources, vm, true)
}
}()
// Record the network namespace to support namespace sharing by container ID.
if resources.NetNS() != "" {
err = stateKey.Set(c.ID, keyNetNS, resources.NetNS())
if err != nil {
return err
}
}
err = stateKey.Set(c.ID, keyResources, resources)
if err != nil {
return err
}
c.hc = hc
return nil
}
func startContainerShim(c *container, pidFile, logFile string) error {
// Launch a shim process to later execute a process in the container.
shim, err := startProcessShim(c.ID, pidFile, logFile, nil)
if err != nil {
return err
}
defer shim.Release()
defer func() {
if err != nil {
shim.Kill()
}
}()
c.ShimPid = shim.Pid
err = stateKey.Set(c.ID, keyShimPid, shim.Pid)
if err != nil {
return err
}
if pidFile != "" {
if err = createPidFile(pidFile, shim.Pid); err != nil {
return err
}
}
return nil
}
func (c *container) Close() error {
if c.hc == nil {
return nil
}
return c.hc.Close()
}
func (c *container) Exec() error {
err := c.hc.Start()
if err != nil {
return err
}
if c.Spec.Process == nil {
return nil
}
// Alert the shim that the container is ready.
pipe, err := winio.DialPipe(c.ShimPipePath(), nil)
if err != nil {
return err
}
defer pipe.Close()
shim, err := os.FindProcess(c.ShimPid)
if err != nil {
return err
}
defer shim.Release()
err = runhcs.GetErrorFromPipe(pipe, shim)
if err != nil {
return err
}
return nil
}
func getContainer(id string, notStopped bool) (*container, error) {
var c container
err := stateKey.Get(id, keyState, &c.persistedState)
if err != nil {
return nil, err
}
err = stateKey.Get(id, keyShimPid, &c.ShimPid)
if err != nil {
if _, ok := err.(*regstate.NoStateError); !ok {
return nil, err
}
c.ShimPid = -1
}
if notStopped && c.ShimPid == 0 {
return nil, errContainerStopped
}
hc, err := hcs.OpenComputeSystem(c.ID)
if err == nil {
c.hc = hc
} else if !hcs.IsNotExist(err) {
return nil, err
} else if notStopped {
return nil, errContainerStopped
}
return &c, nil
}
func (c *container) Remove() error {
// Unmount any layers or mapped volumes.
err := c.Unmount(!c.IsHost)
if err != nil {
return err
}
// Follow kata's example and delay tearing down the VM until the owning
// container is removed.
if c.IsHost {
vm, err := hcs.OpenComputeSystem(vmID(c.ID))
if err == nil {
if err := vm.Terminate(); hcs.IsPending(err) {
vm.Wait()
}
}
}
return stateKey.Remove(c.ID)
}
func (c *container) Kill() error {
if c.hc == nil {
return nil
}
err := c.hc.Terminate()
if hcs.IsPending(err) {
err = c.hc.Wait()
}
if hcs.IsAlreadyStopped(err) {
err = nil
}
return err
}
func (c *container) Status() (containerStatus, error) {
if c.hc == nil || c.ShimPid == 0 {
return containerStopped, nil
}
props, err := c.hc.Properties()
if err != nil {
if !strings.Contains(err.Error(), "operation is not valid in the current state") {
return "", err
}
return containerUnknown, nil
}
state := containerUnknown
switch props.State {
case "", "Created":
state = containerCreated
case "Running":
state = containerRunning
case "Paused":
state = containerPaused
case "Stopped":
state = containerStopped
}
return state, nil
}

View File

@ -0,0 +1,71 @@
package main
import (
"os"
"path/filepath"
"github.com/Microsoft/hcsshim/internal/appargs"
"github.com/Microsoft/hcsshim/internal/lcow"
"github.com/Microsoft/hcsshim/internal/uvm"
"github.com/Microsoft/hcsshim/osversion"
gcsclient "github.com/Microsoft/opengcs/client"
"github.com/pkg/errors"
"github.com/urfave/cli"
)
var createScratchCommand = cli.Command{
Name: "create-scratch",
Usage: "creates a scratch vhdx at 'destpath' that is ext4 formatted",
Description: "Creates a scratch vhdx at 'destpath' that is ext4 formatted",
Flags: []cli.Flag{
cli.StringFlag{
Name: "destpath",
Usage: "Required: describes the destination vhd path",
},
},
Before: appargs.Validate(),
Action: func(context *cli.Context) error {
dest := context.String("destpath")
if dest == "" {
return errors.New("'destpath' is required")
}
// If we only have v1 lcow support do it the old way.
if osversion.Get().Build < osversion.RS5 {
cfg := gcsclient.Config{
Options: gcsclient.Options{
KirdPath: filepath.Join(os.Getenv("ProgramFiles"), "Linux Containers"),
KernelFile: "kernel",
InitrdFile: uvm.InitrdFile,
},
Name: "createscratch-uvm",
UvmTimeoutSeconds: 5 * 60, // 5 Min
}
if err := cfg.StartUtilityVM(); err != nil {
return errors.Wrapf(err, "failed to start '%s'", cfg.Name)
}
defer cfg.Uvm.Terminate()
if err := cfg.CreateExt4Vhdx(dest, lcow.DefaultScratchSizeGB, ""); err != nil {
return errors.Wrapf(err, "failed to create ext4vhdx for '%s'", cfg.Name)
}
} else {
opts := uvm.NewDefaultOptionsLCOW("createscratch-uvm", context.GlobalString("owner"))
convertUVM, err := uvm.CreateLCOW(opts)
if err != nil {
return errors.Wrapf(err, "failed to create '%s'", opts.ID)
}
defer convertUVM.Close()
if err := convertUVM.Start(); err != nil {
return errors.Wrapf(err, "failed to start '%s'", opts.ID)
}
if err := lcow.CreateScratch(convertUVM, dest, lcow.DefaultScratchSizeGB, "", ""); err != nil {
return errors.Wrapf(err, "failed to create ext4vhdx for '%s'", opts.ID)
}
}
return nil
},
}

View File

@ -0,0 +1,100 @@
package main
import (
"github.com/Microsoft/hcsshim/internal/appargs"
"github.com/urfave/cli"
)
var createRunFlags = []cli.Flag{
cli.StringFlag{
Name: "bundle, b",
Value: "",
Usage: `path to the root of the bundle directory, defaults to the current directory`,
},
cli.StringFlag{
Name: "pid-file",
Value: "",
Usage: "specify the file to write the process id to",
},
cli.StringFlag{
Name: "shim-log",
Value: "",
Usage: `path to the log file or named pipe (e.g. \\.\pipe\ProtectedPrefix\Administrators\runhcs-<container-id>-shim-log) for the launched shim process`,
},
cli.StringFlag{
Name: "vm-log",
Value: "",
Usage: `path to the log file or named pipe (e.g. \\.\pipe\ProtectedPrefix\Administrators\runhcs-<container-id>-vm-log) for the launched VM shim process`,
},
cli.StringFlag{
Name: "vm-console",
Value: "",
Usage: `path to the pipe for the VM's console (e.g. \\.\pipe\debugpipe)`,
},
cli.StringFlag{
Name: "host",
Value: "",
Usage: "host container whose VM this container should run in",
},
}
var createCommand = cli.Command{
Name: "create",
Usage: "create a container",
ArgsUsage: `<container-id>
Where "<container-id>" is your name for the instance of the container that you
are starting. The name you provide for the container instance must be unique on
your host.`,
Description: `The create command creates an instance of a container for a bundle. The bundle
is a directory with a specification file named "` + specConfig + `" and a root
filesystem.
The specification file includes an args parameter. The args parameter is used
to specify command(s) that get run when the container is started. To change the
command(s) that get executed on start, edit the args parameter of the spec. See
"runc spec --help" for more explanation.`,
Flags: append(createRunFlags),
Before: appargs.Validate(argID),
Action: func(context *cli.Context) error {
cfg, err := containerConfigFromContext(context)
if err != nil {
return err
}
_, err = createContainer(cfg)
if err != nil {
return err
}
return nil
},
}
func containerConfigFromContext(context *cli.Context) (*containerConfig, error) {
id := context.Args().First()
pidFile, err := absPathOrEmpty(context.String("pid-file"))
if err != nil {
return nil, err
}
shimLog, err := absPathOrEmpty(context.String("shim-log"))
if err != nil {
return nil, err
}
vmLog, err := absPathOrEmpty(context.String("vm-log"))
if err != nil {
return nil, err
}
spec, err := setupSpec(context)
if err != nil {
return nil, err
}
return &containerConfig{
ID: id,
Owner: context.GlobalString("owner"),
PidFile: pidFile,
ShimLogFile: shimLog,
VMLogFile: vmLog,
VMConsolePipe: context.String("vm-console"),
Spec: spec,
HostID: context.String("host"),
}, nil
}

View File

@ -0,0 +1,73 @@
package main
import (
"fmt"
"os"
"github.com/Microsoft/hcsshim/internal/appargs"
"github.com/Microsoft/hcsshim/internal/regstate"
"github.com/urfave/cli"
)
var deleteCommand = cli.Command{
Name: "delete",
Usage: "delete any resources held by the container often used with detached container",
ArgsUsage: `<container-id>
Where "<container-id>" is the name for the instance of the container.
EXAMPLE:
For example, if the container id is "ubuntu01" and runhcs list currently shows the
status of "ubuntu01" as "stopped" the following will delete resources held for
"ubuntu01" removing "ubuntu01" from the runhcs list of containers:
# runhcs delete ubuntu01`,
Flags: []cli.Flag{
cli.BoolFlag{
Name: "force, f",
Usage: "Forcibly deletes the container if it is still running (uses SIGKILL)",
},
},
Before: appargs.Validate(argID),
Action: func(context *cli.Context) error {
id := context.Args().First()
force := context.Bool("force")
container, err := getContainer(id, false)
if err != nil {
if _, ok := err.(*regstate.NoStateError); ok {
if e := stateKey.Remove(id); e != nil {
fmt.Fprintf(os.Stderr, "remove %s: %v\n", id, e)
}
if force {
return nil
}
}
return err
}
defer container.Close()
s, err := container.Status()
if err != nil {
return err
}
kill := false
switch s {
case containerStopped:
case containerCreated:
kill = true
default:
if !force {
return fmt.Errorf("cannot delete container %s that is not stopped: %s\n", id, s)
}
kill = true
}
if kill {
err = container.Kill()
if err != nil {
return err
}
}
return container.Remove()
},
}

160
vendor/github.com/Microsoft/hcsshim/cmd/runhcs/exec.go generated vendored Normal file
View File

@ -0,0 +1,160 @@
package main
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"strings"
"syscall"
"github.com/Microsoft/hcsshim/internal/appargs"
"github.com/opencontainers/runtime-spec/specs-go"
"github.com/urfave/cli"
)
var execCommand = cli.Command{
Name: "exec",
Usage: "execute new process inside the container",
ArgsUsage: `<container-id> <command> [command options] || -p process.json <container-id>
Where "<container-id>" is the name for the instance of the container and
"<command>" is the command to be executed in the container.
"<command>" can't be empty unless a "-p" flag provided.
EXAMPLE:
For example, if the container is configured to run the linux ps command the
following will output a list of processes running in the container:
# runhcs exec <container-id> ps`,
Flags: []cli.Flag{
cli.StringFlag{
Name: "cwd",
Usage: "current working directory in the container",
},
cli.StringSliceFlag{
Name: "env, e",
Usage: "set environment variables",
},
cli.BoolFlag{
Name: "tty, t",
Usage: "allocate a pseudo-TTY",
},
cli.StringFlag{
Name: "user, u",
},
cli.StringFlag{
Name: "process, p",
Usage: "path to the process.json",
},
cli.BoolFlag{
Name: "detach,d",
Usage: "detach from the container's process",
},
cli.StringFlag{
Name: "pid-file",
Value: "",
Usage: "specify the file to write the process id to",
},
cli.StringFlag{
Name: "shim-log",
Value: "",
Usage: `path to the log file or named pipe (e.g. \\.\pipe\ProtectedPrefix\Administrators\runhcs-<container-id>-<exec-id>-log) for the launched shim process`,
},
},
Before: appargs.Validate(argID, appargs.Rest(appargs.String)),
Action: func(context *cli.Context) error {
id := context.Args().First()
pidFile, err := absPathOrEmpty(context.String("pid-file"))
if err != nil {
return err
}
shimLog, err := absPathOrEmpty(context.String("shim-log"))
if err != nil {
return err
}
c, err := getContainer(id, false)
if err != nil {
return err
}
defer c.Close()
status, err := c.Status()
if err != nil {
return err
}
if status != containerRunning {
return errContainerStopped
}
spec, err := getProcessSpec(context, c)
if err != nil {
return err
}
p, err := startProcessShim(id, pidFile, shimLog, spec)
if err != nil {
return err
}
if !context.Bool("detach") {
state, err := p.Wait()
if err != nil {
return err
}
os.Exit(int(state.Sys().(syscall.WaitStatus).ExitCode))
}
return nil
},
SkipArgReorder: true,
}
func getProcessSpec(context *cli.Context, c *container) (*specs.Process, error) {
if path := context.String("process"); path != "" {
f, err := os.Open(path)
if err != nil {
return nil, err
}
defer f.Close()
var p specs.Process
if err := json.NewDecoder(f).Decode(&p); err != nil {
return nil, err
}
return &p, validateProcessSpec(&p)
}
// process via cli flags
p := c.Spec.Process
if len(context.Args()) == 1 {
return nil, fmt.Errorf("process args cannot be empty")
}
p.Args = context.Args()[1:]
// override the cwd, if passed
if context.String("cwd") != "" {
p.Cwd = context.String("cwd")
}
// append the passed env variables
p.Env = append(p.Env, context.StringSlice("env")...)
// set the tty
if context.IsSet("tty") {
p.Terminal = context.Bool("tty")
}
// override the user, if passed
if context.String("user") != "" {
p.User.Username = context.String("user")
}
return p, nil
}
func validateProcessSpec(spec *specs.Process) error {
if spec.Cwd == "" {
return fmt.Errorf("Cwd property must not be empty")
}
// IsAbs doesnt recognize Unix paths on Windows builds so handle that case
// here.
if !filepath.IsAbs(spec.Cwd) && !strings.HasPrefix(spec.Cwd, "/") {
return fmt.Errorf("Cwd must be an absolute path")
}
if len(spec.Args) == 0 {
return fmt.Errorf("args must not be empty")
}
return nil
}

178
vendor/github.com/Microsoft/hcsshim/cmd/runhcs/kill.go generated vendored Normal file
View File

@ -0,0 +1,178 @@
package main
import (
"strconv"
"strings"
"github.com/Microsoft/hcsshim/internal/appargs"
"github.com/Microsoft/hcsshim/internal/guestrequest"
"github.com/Microsoft/hcsshim/internal/hcs"
"github.com/Microsoft/hcsshim/internal/schema1"
"github.com/Microsoft/hcsshim/osversion"
"github.com/pkg/errors"
"github.com/urfave/cli"
)
var killCommand = cli.Command{
Name: "kill",
Usage: "kill sends the specified signal (default: SIGTERM) to the container's init process",
ArgsUsage: `<container-id> [signal]
Where "<container-id>" is the name for the instance of the container and
"[signal]" is the signal to be sent to the init process.
EXAMPLE:
For example, if the container id is "ubuntu01" the following will send a "KILL"
signal to the init process of the "ubuntu01" container:
# runhcs kill ubuntu01 KILL`,
Flags: []cli.Flag{},
Before: appargs.Validate(argID, appargs.Optional(appargs.String)),
Action: func(context *cli.Context) error {
id := context.Args().First()
c, err := getContainer(id, true)
if err != nil {
return err
}
defer c.Close()
status, err := c.Status()
if err != nil {
return err
}
if status != containerRunning {
return errContainerStopped
}
signalsSupported := false
// The Signal feature was added in RS5
if osversion.Get().Build >= osversion.RS5 {
if c.IsHost || c.HostID != "" {
var hostID string
if c.IsHost {
// This is the LCOW, Pod Sandbox, or Windows Xenon V2 for RS5+
hostID = vmID(c.ID)
} else {
// This is the Nth container in a Pod
hostID = c.HostID
}
uvm, err := hcs.OpenComputeSystem(hostID)
if err != nil {
return err
}
defer uvm.Close()
if props, err := uvm.Properties(schema1.PropertyTypeGuestConnection); err == nil &&
props.GuestConnectionInfo.GuestDefinedCapabilities.SignalProcessSupported {
signalsSupported = true
}
} else if c.Spec.Linux == nil && c.Spec.Windows.HyperV == nil {
// RS5+ Windows Argon
signalsSupported = true
}
}
signal := 0
if signalsSupported {
signal, err = validateSigstr(context.Args().Get(1), signalsSupported, c.Spec.Linux != nil)
if err != nil {
return err
}
}
var pid int
if err := stateKey.Get(id, keyInitPid, &pid); err != nil {
return err
}
p, err := c.hc.OpenProcess(pid)
if err != nil {
return err
}
defer p.Close()
if signalsSupported && (c.Spec.Linux != nil || !c.Spec.Process.Terminal) {
opts := guestrequest.SignalProcessOptions{
Signal: signal,
}
return p.Signal(opts)
}
// Legacy signal issue a kill
return p.Kill()
},
}
func validateSigstr(sigstr string, signalsSupported bool, isLcow bool) (int, error) {
errInvalidSignal := errors.Errorf("invalid signal '%s'", sigstr)
// All flavors including legacy default to SIGTERM on LCOW CtrlC on Windows
if sigstr == "" {
if isLcow {
return 0xf, nil
}
return 0, nil
}
sigstr = strings.ToUpper(sigstr)
if !signalsSupported {
// If signals arent supported we just validate that its a known signal.
// We already return 0 since we only supported a platform Kill() at that
// time.
if isLcow {
switch sigstr {
case "15":
fallthrough
case "TERM":
fallthrough
case "SIGTERM":
return 0, nil
default:
return 0, errInvalidSignal
}
}
switch sigstr {
// Docker sends a UNIX term in the supported Windows Signal map.
case "15":
fallthrough
case "TERM":
fallthrough
case "0":
fallthrough
case "CTRLC":
return 0, nil
case "9":
fallthrough
case "KILL":
return 0, nil
default:
return 0, errInvalidSignal
}
}
var sigmap map[string]int
if isLcow {
sigmap = signalMapLcow
} else {
sigmap = signalMapWindows
}
signal, err := strconv.Atoi(sigstr)
if err != nil {
// Signal might still match the string value
for k, v := range sigmap {
if k == sigstr {
return v, nil
}
}
return 0, errInvalidSignal
}
// Match signal by value
for _, v := range sigmap {
if signal == v {
return signal, nil
}
}
return 0, errInvalidSignal
}

View File

@ -0,0 +1,95 @@
package main
import (
"fmt"
"strconv"
"strings"
"testing"
)
func runValidateSigstrTest(sigstr string, signalsSupported, isLcow bool,
expectedSignal int, expectedError bool, t *testing.T) {
signal, err := validateSigstr(sigstr, signalsSupported, isLcow)
if expectedError {
if err == nil {
t.Fatalf("Expected err: %v, got: nil", expectedError)
} else if err.Error() != fmt.Sprintf("invalid signal '%s'", sigstr) {
t.Fatalf("Expected err: %v, got: %v", expectedError, err)
}
}
if signal != expectedSignal {
t.Fatalf("Test - Signal: %s, Support: %v, LCOW: %v\nExpected signal: %v, got: %v",
sigstr, signalsSupported, isLcow,
expectedSignal, signal)
}
}
func TestValidateSigstrEmpty(t *testing.T) {
runValidateSigstrTest("", false, false, 0, false, t)
runValidateSigstrTest("", false, true, 0xf, false, t)
runValidateSigstrTest("", true, false, 0, false, t)
runValidateSigstrTest("", true, true, 0xf, false, t)
}
func TestValidateSigstrDefaultLCOW(t *testing.T) {
runValidateSigstrTest("15", false, true, 0, false, t)
runValidateSigstrTest("TERM", false, true, 0, false, t)
runValidateSigstrTest("SIGTERM", false, true, 0, false, t)
}
func TestValidateSigstrDefaultLCOWInvalid(t *testing.T) {
runValidateSigstrTest("2", false, true, 0, true, t)
runValidateSigstrTest("test", false, true, 0, true, t)
}
func TestValidateSigstrDefaultWCOW(t *testing.T) {
runValidateSigstrTest("15", false, false, 0, false, t)
runValidateSigstrTest("TERM", false, false, 0, false, t)
runValidateSigstrTest("0", false, false, 0, false, t)
runValidateSigstrTest("CTRLC", false, false, 0, false, t)
runValidateSigstrTest("9", false, false, 0, false, t)
runValidateSigstrTest("KILL", false, false, 0, false, t)
}
func TestValidateSigstrDefaultWCOWInvalid(t *testing.T) {
runValidateSigstrTest("2", false, false, 0, true, t)
runValidateSigstrTest("test", false, false, 0, true, t)
}
func TestValidateSignalStringLCOW(t *testing.T) {
for k, v := range signalMapLcow {
runValidateSigstrTest(k, true, true, v, false, t)
// run it again with a case not in the map
lc := strings.ToLower(k)
if k == lc {
t.Fatalf("Expected lower casing - map: %v, got: %v", k, lc)
}
runValidateSigstrTest(lc, true, true, v, false, t)
}
}
func TestValidateSignalStringWCOW(t *testing.T) {
for k, v := range signalMapWindows {
runValidateSigstrTest(k, true, false, v, false, t)
// run it again with a case not in the map
lc := strings.ToLower(k)
if k == lc {
t.Fatalf("Expected lower casing - map: %v, got: %v", k, lc)
}
runValidateSigstrTest(lc, true, false, v, false, t)
}
}
func TestValidateSignalValueLCOW(t *testing.T) {
for _, v := range signalMapLcow {
str := strconv.Itoa(v)
runValidateSigstrTest(str, true, true, v, false, t)
}
}
func TestValidateSignalValueWCOW(t *testing.T) {
for _, v := range signalMapWindows {
str := strconv.Itoa(v)
runValidateSigstrTest(str, true, false, v, false, t)
}
}

116
vendor/github.com/Microsoft/hcsshim/cmd/runhcs/list.go generated vendored Normal file
View File

@ -0,0 +1,116 @@
package main
import (
"fmt"
"os"
"text/tabwriter"
"time"
"encoding/json"
"github.com/Microsoft/hcsshim/internal/appargs"
"github.com/Microsoft/hcsshim/internal/runhcs"
"github.com/urfave/cli"
)
const formatOptions = `table or json`
var listCommand = cli.Command{
Name: "list",
Usage: "lists containers started by runhcs with the given root",
ArgsUsage: `
Where the given root is specified via the global option "--root"
(default: "/run/runhcs").
EXAMPLE 1:
To list containers created via the default "--root":
# runhcs list
EXAMPLE 2:
To list containers created using a non-default value for "--root":
# runhcs --root value list`,
Flags: []cli.Flag{
cli.StringFlag{
Name: "format, f",
Value: "table",
Usage: `select one of: ` + formatOptions,
},
cli.BoolFlag{
Name: "quiet, q",
Usage: "display only container IDs",
},
},
Before: appargs.Validate(),
Action: func(context *cli.Context) error {
s, err := getContainers(context)
if err != nil {
return err
}
if context.Bool("quiet") {
for _, item := range s {
fmt.Println(item.ID)
}
return nil
}
switch context.String("format") {
case "table":
w := tabwriter.NewWriter(os.Stdout, 12, 1, 3, ' ', 0)
fmt.Fprint(w, "ID\tPID\tSTATUS\tBUNDLE\tCREATED\tOWNER\n")
for _, item := range s {
fmt.Fprintf(w, "%s\t%d\t%s\t%s\t%s\t%s\n",
item.ID,
item.InitProcessPid,
item.Status,
item.Bundle,
item.Created.Format(time.RFC3339Nano),
item.Owner)
}
if err := w.Flush(); err != nil {
return err
}
case "json":
if err := json.NewEncoder(os.Stdout).Encode(s); err != nil {
return err
}
default:
return fmt.Errorf("invalid format option")
}
return nil
},
}
func getContainers(context *cli.Context) ([]runhcs.ContainerState, error) {
ids, err := stateKey.Enumerate()
if err != nil {
return nil, err
}
var s []runhcs.ContainerState
for _, id := range ids {
c, err := getContainer(id, false)
if err != nil {
fmt.Fprintf(os.Stderr, "reading state for %s: %v\n", id, err)
continue
}
status, err := c.Status()
if err != nil {
fmt.Fprintf(os.Stderr, "reading status for %s: %v\n", id, err)
}
s = append(s, runhcs.ContainerState{
ID: id,
Version: c.Spec.Version,
InitProcessPid: c.ShimPid,
Status: string(status),
Bundle: c.Bundle,
Rootfs: c.Rootfs,
Created: c.Created,
Annotations: c.Spec.Annotations,
})
c.Close()
}
return s, nil
}

174
vendor/github.com/Microsoft/hcsshim/cmd/runhcs/main.go generated vendored Normal file
View File

@ -0,0 +1,174 @@
package main
import (
"fmt"
"io"
"os"
"strings"
"github.com/Microsoft/go-winio"
"github.com/Microsoft/go-winio/pkg/etwlogrus"
"github.com/Microsoft/hcsshim/internal/regstate"
"github.com/Microsoft/hcsshim/internal/runhcs"
"github.com/opencontainers/runtime-spec/specs-go"
"github.com/sirupsen/logrus"
"github.com/urfave/cli"
)
// Add a manifest to get proper Windows version detection.
//
// goversioninfo can be installed with "go get github.com/josephspurrier/goversioninfo/cmd/goversioninfo"
//go:generate goversioninfo -platform-specific
// version will be populated by the Makefile, read from
// VERSION file of the source code.
var version = ""
// gitCommit will be the hash that the binary was built from
// and will be populated by the Makefile
var gitCommit = ""
var stateKey *regstate.Key
var logFormat string
const (
specConfig = "config.json"
usage = `Open Container Initiative runtime for Windows
runhcs is a fork of runc, modified to run containers on Windows with or without Hyper-V isolation. Like runc, it is a command line client for running applications packaged according to the Open Container Initiative (OCI) format.
runhcs integrates with existing process supervisors to provide a production container runtime environment for applications. It can be used with your existing process monitoring tools and the container will be spawned as a direct child of the process supervisor.
Containers are configured using bundles. A bundle for a container is a directory that includes a specification file named "` + specConfig + `". Bundle contents will depend on the container type.
To start a new instance of a container:
# runhcs run [ -b bundle ] <container-id>
Where "<container-id>" is your name for the instance of the container that you are starting. The name you provide for the container instance must be unique on your host. Providing the bundle directory using "-b" is optional. The default value for "bundle" is the current directory.`
)
func main() {
// Provider ID: 0b52781f-b24d-5685-ddf6-69830ed40ec3
// Hook isn't closed explicitly, as it will exist until process exit.
if hook, err := etwlogrus.NewHook("Microsoft.Virtualization.RunHCS"); err == nil {
logrus.AddHook(hook)
} else {
logrus.Error(err)
}
app := cli.NewApp()
app.Name = "runhcs"
app.Usage = usage
var v []string
if version != "" {
v = append(v, version)
}
if gitCommit != "" {
v = append(v, fmt.Sprintf("commit: %s", gitCommit))
}
v = append(v, fmt.Sprintf("spec: %s", specs.Version))
app.Version = strings.Join(v, "\n")
app.Flags = []cli.Flag{
cli.BoolFlag{
Name: "debug",
Usage: "enable debug output for logging",
},
cli.StringFlag{
Name: "log",
Value: "nul",
Usage: `set the log file path or named pipe (e.g. \\.\pipe\ProtectedPrefix\Administrators\runhcs-log) where internal debug information is written`,
},
cli.StringFlag{
Name: "log-format",
Value: "text",
Usage: "set the format used by logs ('text' (default), or 'json')",
},
cli.StringFlag{
Name: "owner",
Value: "runhcs",
Usage: "compute system owner",
},
cli.StringFlag{
Name: "root",
Value: "default",
Usage: "registry key for storage of container state",
},
}
app.Commands = []cli.Command{
createCommand,
createScratchCommand,
deleteCommand,
// eventsCommand,
execCommand,
killCommand,
listCommand,
pauseCommand,
psCommand,
resizeTtyCommand,
resumeCommand,
runCommand,
shimCommand,
startCommand,
stateCommand,
// updateCommand,
vmshimCommand,
}
app.Before = func(context *cli.Context) error {
if context.GlobalBool("debug") {
logrus.SetLevel(logrus.DebugLevel)
}
if path := context.GlobalString("log"); path != "" {
var f io.Writer
var err error
if strings.HasPrefix(path, runhcs.SafePipePrefix) {
f, err = winio.DialPipe(path, nil)
} else {
f, err = os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_APPEND|os.O_SYNC, 0666)
}
if err != nil {
return err
}
logrus.SetOutput(f)
}
switch logFormat = context.GlobalString("log-format"); logFormat {
case "text":
// retain logrus's default.
case "json":
logrus.SetFormatter(new(logrus.JSONFormatter))
default:
return fmt.Errorf("unknown log-format %q", logFormat)
}
var err error
stateKey, err = regstate.Open(context.GlobalString("root"), false)
if err != nil {
return err
}
return nil
}
// If the command returns an error, cli takes upon itself to print
// the error on cli.ErrWriter and exit.
// Use our own writer here to ensure the log gets sent to the right location.
fatalWriter.Writer = cli.ErrWriter
cli.ErrWriter = &fatalWriter
if err := app.Run(os.Args); err != nil {
fmt.Fprintln(cli.ErrWriter, err)
os.Exit(1)
}
}
type logErrorWriter struct {
Writer io.Writer
}
var fatalWriter logErrorWriter
func (f *logErrorWriter) Write(p []byte) (n int, err error) {
logrus.Error(string(p))
return f.Writer.Write(p)
}

View File

@ -0,0 +1,58 @@
package main
import (
"github.com/Microsoft/hcsshim/internal/appargs"
"github.com/urfave/cli"
)
var pauseCommand = cli.Command{
Name: "pause",
Usage: "pause suspends all processes inside the container",
ArgsUsage: `<container-id>
Where "<container-id>" is the name for the instance of the container to be
paused. `,
Description: `The pause command suspends all processes in the instance of the container.
Use runhcs list to identify instances of containers and their current status.`,
Before: appargs.Validate(argID),
Action: func(context *cli.Context) error {
id := context.Args().First()
container, err := getContainer(id, true)
if err != nil {
return err
}
defer container.Close()
if err := container.hc.Pause(); err != nil {
return err
}
return nil
},
}
var resumeCommand = cli.Command{
Name: "resume",
Usage: "resumes all processes that have been previously paused",
ArgsUsage: `<container-id>
Where "<container-id>" is the name for the instance of the container to be
resumed.`,
Description: `The resume command resumes all processes in the instance of the container.
Use runhcs list to identify instances of containers and their current status.`,
Before: appargs.Validate(argID),
Action: func(context *cli.Context) error {
id := context.Args().First()
container, err := getContainer(id, true)
if err != nil {
return err
}
defer container.Close()
if err := container.hc.Resume(); err != nil {
return err
}
return nil
},
}

51
vendor/github.com/Microsoft/hcsshim/cmd/runhcs/ps.go generated vendored Normal file
View File

@ -0,0 +1,51 @@
package main
import (
"encoding/json"
"fmt"
"os"
"github.com/Microsoft/hcsshim/internal/appargs"
"github.com/Microsoft/hcsshim/internal/schema1"
"github.com/urfave/cli"
)
var psCommand = cli.Command{
Name: "ps",
Usage: "ps displays the processes running inside a container",
ArgsUsage: `<container-id> [ps options]`,
Flags: []cli.Flag{
cli.StringFlag{
Name: "format, f",
Value: "json",
Usage: `select one of: ` + formatOptions,
},
},
Before: appargs.Validate(argID),
Action: func(context *cli.Context) error {
id := context.Args().First()
container, err := getContainer(id, true)
if err != nil {
return err
}
defer container.Close()
props, err := container.hc.Properties(schema1.PropertyTypeProcessList)
if err != nil {
return err
}
var pids []int
for _, p := range props.ProcessList {
pids = append(pids, int(p.ProcessId))
}
switch context.String("format") {
case "json":
return json.NewEncoder(os.Stdout).Encode(pids)
default:
return fmt.Errorf("invalid format option")
}
},
SkipArgReorder: true,
}

Binary file not shown.

Binary file not shown.

64
vendor/github.com/Microsoft/hcsshim/cmd/runhcs/run.go generated vendored Normal file
View File

@ -0,0 +1,64 @@
package main
import (
"os"
"syscall"
"github.com/Microsoft/hcsshim/internal/appargs"
"github.com/urfave/cli"
)
// default action is to start a container
var runCommand = cli.Command{
Name: "run",
Usage: "create and run a container",
ArgsUsage: `<container-id>
Where "<container-id>" is your name for the instance of the container that you
are starting. The name you provide for the container instance must be unique on
your host.`,
Description: `The run command creates an instance of a container for a bundle. The bundle
is a directory with a specification file named "` + specConfig + `" and a root
filesystem.
The specification file includes an args parameter. The args parameter is used
to specify command(s) that get run when the container is started. To change the
command(s) that get executed on start, edit the args parameter of the spec.`,
Flags: append(createRunFlags,
cli.BoolFlag{
Name: "detach, d",
Usage: "detach from the container's process",
},
),
Before: appargs.Validate(argID),
Action: func(context *cli.Context) error {
cfg, err := containerConfigFromContext(context)
if err != nil {
return err
}
c, err := createContainer(cfg)
if err != nil {
return err
}
if err != nil {
return err
}
p, err := os.FindProcess(c.ShimPid)
if err != nil {
return err
}
err = c.Exec()
if err != nil {
return err
}
if !context.Bool("detach") {
state, err := p.Wait()
if err != nil {
return err
}
c.Remove()
os.Exit(int(state.Sys().(syscall.WaitStatus).ExitCode))
}
return nil
},
}

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<description>runhcs</description>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- Windows 10 -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
</application>
</compatibility>
</assembly>

323
vendor/github.com/Microsoft/hcsshim/cmd/runhcs/shim.go generated vendored Normal file
View File

@ -0,0 +1,323 @@
package main
import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net"
"os"
"strings"
"sync"
"time"
winio "github.com/Microsoft/go-winio"
"github.com/Microsoft/hcsshim/internal/appargs"
"github.com/Microsoft/hcsshim/internal/hcs"
"github.com/Microsoft/hcsshim/internal/lcow"
"github.com/Microsoft/hcsshim/internal/runhcs"
"github.com/Microsoft/hcsshim/internal/schema2"
specs "github.com/opencontainers/runtime-spec/specs-go"
"github.com/sirupsen/logrus"
"github.com/urfave/cli"
"golang.org/x/sys/windows"
)
func containerPipePath(id string) string {
return runhcs.SafePipePath("runhcs-shim-" + id)
}
func newFile(context *cli.Context, param string) *os.File {
fd := uintptr(context.Int(param))
if fd == 0 {
return nil
}
return os.NewFile(fd, "")
}
var shimCommand = cli.Command{
Name: "shim",
Usage: `launch the process and proxy stdio (do not call it outside of runhcs)`,
Hidden: true,
Flags: []cli.Flag{
&cli.IntFlag{Name: "stdin", Hidden: true},
&cli.IntFlag{Name: "stdout", Hidden: true},
&cli.IntFlag{Name: "stderr", Hidden: true},
&cli.BoolFlag{Name: "exec", Hidden: true},
cli.StringFlag{Name: "log-pipe", Hidden: true},
},
Before: appargs.Validate(argID),
Action: func(context *cli.Context) error {
logPipe := context.String("log-pipe")
if logPipe != "" {
lpc, err := winio.DialPipe(logPipe, nil)
if err != nil {
return err
}
defer lpc.Close()
logrus.SetOutput(lpc)
} else {
logrus.SetOutput(os.Stderr)
}
fatalWriter.Writer = os.Stdout
id := context.Args().First()
c, err := getContainer(id, true)
if err != nil {
return err
}
defer c.Close()
// Asynchronously wait for the container to exit.
containerExitCh := make(chan error)
go func() {
containerExitCh <- c.hc.Wait()
}()
// Get File objects for the open stdio files passed in as arguments.
stdin := newFile(context, "stdin")
stdout := newFile(context, "stdout")
stderr := newFile(context, "stderr")
exec := context.Bool("exec")
terminateOnFailure := false
errorOut := io.WriteCloser(os.Stdout)
var spec *specs.Process
if exec {
// Read the process spec from stdin.
specj, err := ioutil.ReadAll(os.Stdin)
if err != nil {
return err
}
os.Stdin.Close()
spec = new(specs.Process)
err = json.Unmarshal(specj, spec)
if err != nil {
return err
}
} else {
// Stdin is not used.
os.Stdin.Close()
// Listen on the named pipe associated with this container.
l, err := winio.ListenPipe(c.ShimPipePath(), nil)
if err != nil {
return err
}
// Alert the parent process that initialization has completed
// successfully.
errorOut.Write(runhcs.ShimSuccess)
errorOut.Close()
fatalWriter.Writer = ioutil.Discard
// When this process exits, clear this process's pid in the registry.
defer func() {
stateKey.Set(id, keyShimPid, 0)
}()
defer func() {
if terminateOnFailure {
if err = c.hc.Terminate(); hcs.IsPending(err) {
<-containerExitCh
}
}
}()
terminateOnFailure = true
// Wait for a connection to the named pipe, exiting if the container
// exits before this happens.
var pipe net.Conn
pipeCh := make(chan error)
go func() {
var err error
pipe, err = l.Accept()
pipeCh <- err
}()
select {
case err = <-pipeCh:
if err != nil {
return err
}
case err = <-containerExitCh:
if err != nil {
return err
}
return cli.NewExitError("", 1)
}
// The next set of errors goes to the open pipe connection.
errorOut = pipe
fatalWriter.Writer = pipe
// The process spec comes from the original container spec.
spec = c.Spec.Process
}
// Create the process in the container.
var wpp *hcsschema.ProcessParameters // Windows Process Parameters
var lpp *lcow.ProcessParameters // Linux Process Parameters
var p *hcs.Process
if c.Spec.Linux == nil {
environment := make(map[string]string)
for _, v := range spec.Env {
s := strings.SplitN(v, "=", 2)
if len(s) == 2 && len(s[1]) > 0 {
environment[s[0]] = s[1]
}
}
wpp = &hcsschema.ProcessParameters{
WorkingDirectory: spec.Cwd,
EmulateConsole: spec.Terminal,
Environment: environment,
User: spec.User.Username,
}
for i, arg := range spec.Args {
e := windows.EscapeArg(arg)
if i == 0 {
wpp.CommandLine = e
} else {
wpp.CommandLine += " " + e
}
}
if spec.ConsoleSize != nil {
wpp.ConsoleSize = []int32{
int32(spec.ConsoleSize.Height),
int32(spec.ConsoleSize.Width),
}
}
wpp.CreateStdInPipe = stdin != nil
wpp.CreateStdOutPipe = stdout != nil
wpp.CreateStdErrPipe = stderr != nil
p, err = c.hc.CreateProcess(wpp)
} else {
lpp = &lcow.ProcessParameters{}
if exec {
lpp.OCIProcess = spec
}
lpp.CreateStdInPipe = stdin != nil
lpp.CreateStdOutPipe = stdout != nil
lpp.CreateStdErrPipe = stderr != nil
p, err = c.hc.CreateProcess(lpp)
}
if err != nil {
return err
}
cstdin, cstdout, cstderr, err := p.Stdio()
if err != nil {
return err
}
if !exec {
err = stateKey.Set(c.ID, keyInitPid, p.Pid())
if err != nil {
return err
}
}
// Store the Guest pid map
err = stateKey.Set(c.ID, fmt.Sprintf(keyPidMapFmt, os.Getpid()), p.Pid())
if err != nil {
return err
}
defer func() {
// Remove the Guest pid map when this process is cleaned up
stateKey.Clear(c.ID, fmt.Sprintf(keyPidMapFmt, os.Getpid()))
}()
terminateOnFailure = false
// Alert the connected process that the process was launched
// successfully.
errorOut.Write(runhcs.ShimSuccess)
errorOut.Close()
fatalWriter.Writer = ioutil.Discard
// Relay stdio.
var wg sync.WaitGroup
if cstdin != nil {
go func() {
io.Copy(cstdin, stdin)
cstdin.Close()
p.CloseStdin()
}()
}
if cstdout != nil {
wg.Add(1)
go func() {
io.Copy(stdout, cstdout)
stdout.Close()
cstdout.Close()
wg.Done()
}()
}
if cstderr != nil {
wg.Add(1)
go func() {
io.Copy(stderr, cstderr)
stderr.Close()
cstderr.Close()
wg.Done()
}()
}
err = p.Wait()
wg.Wait()
// Attempt to get the exit code from the process.
code := 1
if err == nil {
code, err = p.ExitCode()
if err != nil {
code = 1
}
}
if !exec {
// Shutdown the container, waiting 5 minutes before terminating is
// forcefully.
const shutdownTimeout = time.Minute * 5
waited := false
err = c.hc.Shutdown()
if hcs.IsPending(err) {
select {
case err = <-containerExitCh:
waited = true
case <-time.After(shutdownTimeout):
err = hcs.ErrTimeout
}
}
if hcs.IsAlreadyStopped(err) {
err = nil
}
if err != nil {
err = c.hc.Terminate()
if waited {
err = c.hc.Wait()
} else {
err = <-containerExitCh
}
}
}
return cli.NewExitError("", code)
},
}

View File

@ -0,0 +1,48 @@
package main
var signalMapLcow = map[string]int{
"ABRT": 0x6,
"ALRM": 0xe,
"BUS": 0x7,
"CHLD": 0x11,
"CLD": 0x11,
"CONT": 0x12,
"FPE": 0x8,
"HUP": 0x1,
"ILL": 0x4,
"INT": 0x2,
"IO": 0x1d,
"IOT": 0x6,
"KILL": 0x9,
"PIPE": 0xd,
"POLL": 0x1d,
"PROF": 0x1b,
"PWR": 0x1e,
"QUIT": 0x3,
"SEGV": 0xb,
"STKFLT": 0x10,
"STOP": 0x13,
"SYS": 0x1f,
"TERM": 0xf,
"TRAP": 0x5,
"TSTP": 0x14,
"TTIN": 0x15,
"TTOU": 0x16,
"URG": 0x17,
"USR1": 0xa,
"USR2": 0xc,
"VTALRM": 0x1a,
"WINCH": 0x1c,
"XCPU": 0x18,
"XFSZ": 0x19,
}
var signalMapWindows = map[string]int{
"CTRLC": 0x0,
"CTRLBREAK": 0x1,
"CTRLCLOSE": 0x2,
"CTRLLOGOFF": 0x5,
"CTRLSHUTDOWN": 0x6,
"TERM": 0x0, // Docker sends the UNIX signal. Convert to CTRLC
"KILL": 0x6, // Docker sends the UNIX signal. Convert to CTRLSHUTDOWN
}

42
vendor/github.com/Microsoft/hcsshim/cmd/runhcs/spec.go generated vendored Normal file
View File

@ -0,0 +1,42 @@
package main
import (
"encoding/json"
"fmt"
"os"
"github.com/opencontainers/runtime-spec/specs-go"
"github.com/urfave/cli"
)
// loadSpec loads the specification from the provided path.
func loadSpec(cPath string) (spec *specs.Spec, err error) {
cf, err := os.Open(cPath)
if err != nil {
if os.IsNotExist(err) {
return nil, fmt.Errorf("JSON specification file %s not found", cPath)
}
return nil, err
}
defer cf.Close()
if err = json.NewDecoder(cf).Decode(&spec); err != nil {
return nil, err
}
return spec, nil
}
// setupSpec performs initial setup based on the cli.Context for the container
func setupSpec(context *cli.Context) (*specs.Spec, error) {
bundle := context.String("bundle")
if bundle != "" {
if err := os.Chdir(bundle); err != nil {
return nil, err
}
}
spec, err := loadSpec(specConfig)
if err != nil {
return nil, err
}
return spec, nil
}

View File

@ -0,0 +1,43 @@
package main
import (
"errors"
"fmt"
"github.com/Microsoft/hcsshim/internal/appargs"
"github.com/urfave/cli"
)
var startCommand = cli.Command{
Name: "start",
Usage: "executes the user defined process in a created container",
ArgsUsage: `<container-id>
Where "<container-id>" is your name for the instance of the container that you
are starting. The name you provide for the container instance must be unique on
your host.`,
Description: `The start command executes the user defined process in a created container.`,
Before: appargs.Validate(argID),
Action: func(context *cli.Context) error {
id := context.Args().First()
container, err := getContainer(id, false)
if err != nil {
return err
}
defer container.Close()
status, err := container.Status()
if err != nil {
return err
}
switch status {
case containerCreated:
return container.Exec()
case containerStopped:
return errors.New("cannot start a container that has stopped")
case containerRunning:
return errors.New("cannot start an already running container")
default:
return fmt.Errorf("cannot start a container in the '%s' state", status)
}
},
}

View File

@ -0,0 +1,49 @@
package main
import (
"encoding/json"
"os"
"github.com/Microsoft/hcsshim/internal/appargs"
"github.com/Microsoft/hcsshim/internal/runhcs"
"github.com/urfave/cli"
)
var stateCommand = cli.Command{
Name: "state",
Usage: "output the state of a container",
ArgsUsage: `<container-id>
Where "<container-id>" is your name for the instance of the container.`,
Description: `The state command outputs current state information for the
instance of a container.`,
Before: appargs.Validate(argID),
Action: func(context *cli.Context) error {
id := context.Args().First()
c, err := getContainer(id, false)
if err != nil {
return err
}
defer c.Close()
status, err := c.Status()
if err != nil {
return err
}
cs := runhcs.ContainerState{
Version: c.Spec.Version,
ID: c.ID,
InitProcessPid: c.ShimPid,
Status: string(status),
Bundle: c.Bundle,
Rootfs: c.Rootfs,
Created: c.Created,
Annotations: c.Spec.Annotations,
}
data, err := json.MarshalIndent(cs, "", " ")
if err != nil {
return err
}
os.Stdout.Write(data)
return nil
},
}

56
vendor/github.com/Microsoft/hcsshim/cmd/runhcs/tty.go generated vendored Normal file
View File

@ -0,0 +1,56 @@
package main
import (
"fmt"
"strconv"
"github.com/Microsoft/hcsshim/internal/appargs"
"github.com/urfave/cli"
)
var resizeTtyCommand = cli.Command{
Name: "resize-tty",
Usage: "resize-tty updates the terminal size for a container process",
ArgsUsage: `<container-id> <width> <height>`,
Flags: []cli.Flag{
&cli.IntFlag{
Name: "pid, p",
Usage: "the process pid (defaults to init pid)",
},
},
Before: appargs.Validate(
argID,
appargs.Int(10, 1, 65535),
appargs.Int(10, 1, 65535),
),
Action: func(context *cli.Context) error {
id := context.Args()[0]
width, _ := strconv.ParseUint(context.Args()[1], 10, 16)
height, _ := strconv.ParseUint(context.Args()[2], 10, 16)
c, err := getContainer(id, true)
if err != nil {
return err
}
defer c.Close()
pid := context.Int("pid")
if pid == 0 {
if err := stateKey.Get(id, keyInitPid, &pid); err != nil {
return err
}
} else {
// If a pid was provided map it to its hcs pid.
if err := stateKey.Get(id, fmt.Sprintf(keyPidMapFmt, pid), &pid); err != nil {
return err
}
}
p, err := c.hc.OpenProcess(pid)
if err != nil {
return err
}
defer p.Close()
return p.ResizeConsole(uint16(width), uint16(height))
},
}

View File

@ -0,0 +1,52 @@
package main
import (
"fmt"
"net"
"os"
"path/filepath"
"strings"
"github.com/Microsoft/hcsshim/internal/appargs"
"github.com/Microsoft/hcsshim/internal/runhcs"
)
var argID = appargs.NonEmptyString
func absPathOrEmpty(path string) (string, error) {
if path == "" {
return "", nil
}
if strings.HasPrefix(path, runhcs.SafePipePrefix) {
if len(path) > len(runhcs.SafePipePrefix) {
return runhcs.SafePipePath(path[len(runhcs.SafePipePrefix):]), nil
}
}
return filepath.Abs(path)
}
// createPidFile creates a file with the processes pid inside it atomically
// it creates a temp file with the paths filename + '.' infront of it
// then renames the file
func createPidFile(path string, pid int) error {
var (
tmpDir = filepath.Dir(path)
tmpName = filepath.Join(tmpDir, fmt.Sprintf(".%s", filepath.Base(path)))
)
f, err := os.OpenFile(tmpName, os.O_RDWR|os.O_CREATE|os.O_EXCL|os.O_SYNC, 0666)
if err != nil {
return err
}
_, err = fmt.Fprintf(f, "%d", pid)
f.Close()
if err != nil {
return err
}
return os.Rename(tmpName, path)
}
func closeWritePipe(pipe net.Conn) error {
return pipe.(interface {
CloseWrite() error
}).CloseWrite()
}

View File

@ -0,0 +1,39 @@
package main
import (
"os"
"testing"
"github.com/Microsoft/hcsshim/internal/runhcs"
)
func Test_AbsPathOrEmpty(t *testing.T) {
wd, err := os.Getwd()
if err != nil {
t.Fatalf("failed to get test wd: %v", err)
}
tests := []string{
"",
runhcs.SafePipePrefix + "test",
runhcs.SafePipePrefix + "test with spaces",
"test",
"C:\\test..\\test",
}
expected := []string{
"",
runhcs.SafePipePrefix + "test",
runhcs.SafePipePrefix + "test%20with%20spaces",
wd + "\\test",
"C:\\test..\\test",
}
for i, test := range tests {
actual, err := absPathOrEmpty(test)
if err != nil {
t.Fatalf("absPathOrEmpty: error '%v'", err)
}
if actual != expected[i] {
t.Fatalf("absPathOrEmpty: actual '%s' != '%s'", actual, expected[i])
}
}
}

View File

@ -0,0 +1,43 @@
{
"FixedFileInfo": {
"FileVersion": {
"Major": 1,
"Minor": 0,
"Patch": 0,
"Build": 0
},
"ProductVersion": {
"Major": 1,
"Minor": 0,
"Patch": 0,
"Build": 0
},
"FileFlagsMask": "3f",
"FileFlags ": "00",
"FileOS": "040004",
"FileType": "01",
"FileSubType": "00"
},
"StringFileInfo": {
"Comments": "",
"CompanyName": "",
"FileDescription": "",
"FileVersion": "",
"InternalName": "",
"LegalCopyright": "",
"LegalTrademarks": "",
"OriginalFilename": "",
"PrivateBuild": "",
"ProductName": "",
"ProductVersion": "v1.0.0.0",
"SpecialBuild": ""
},
"VarFileInfo": {
"Translation": {
"LangID": "0409",
"CharsetID": "04B0"
}
},
"IconPath": "",
"ManifestPath": "runhcs.exe.manifest"
}

209
vendor/github.com/Microsoft/hcsshim/cmd/runhcs/vm.go generated vendored Normal file
View File

@ -0,0 +1,209 @@
package main
import (
"encoding/json"
"fmt"
"io/ioutil"
"net"
"os"
"syscall"
winio "github.com/Microsoft/go-winio"
"github.com/Microsoft/hcsshim/internal/appargs"
"github.com/Microsoft/hcsshim/internal/logfields"
"github.com/Microsoft/hcsshim/internal/runhcs"
"github.com/Microsoft/hcsshim/internal/uvm"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/urfave/cli"
)
func vmID(id string) string {
return id + "@vm"
}
var vmshimCommand = cli.Command{
Name: "vmshim",
Usage: `launch a VM and containers inside it (do not call it outside of runhcs)`,
Hidden: true,
Flags: []cli.Flag{
cli.StringFlag{Name: "log-pipe", Hidden: true},
cli.StringFlag{Name: "os", Hidden: true},
},
Before: appargs.Validate(argID),
Action: func(context *cli.Context) error {
logPipe := context.String("log-pipe")
if logPipe != "" {
lpc, err := winio.DialPipe(logPipe, nil)
if err != nil {
return err
}
defer lpc.Close()
logrus.SetOutput(lpc)
} else {
logrus.SetOutput(os.Stderr)
}
fatalWriter.Writer = os.Stdout
pipePath := context.Args().First()
optsj, err := ioutil.ReadAll(os.Stdin)
if err != nil {
return err
}
os.Stdin.Close()
var opts interface{}
isLCOW := context.String("os") == "linux"
if isLCOW {
opts = &uvm.OptionsLCOW{}
} else {
opts = &uvm.OptionsWCOW{}
}
err = json.Unmarshal(optsj, opts)
if err != nil {
return err
}
// Listen on the named pipe associated with this VM.
l, err := winio.ListenPipe(pipePath, &winio.PipeConfig{MessageMode: true})
if err != nil {
return err
}
var vm *uvm.UtilityVM
if isLCOW {
vm, err = uvm.CreateLCOW(opts.(*uvm.OptionsLCOW))
} else {
vm, err = uvm.CreateWCOW(opts.(*uvm.OptionsWCOW))
}
if err != nil {
return err
}
defer vm.Close()
if err = vm.Start(); err != nil {
return err
}
// Asynchronously wait for the VM to exit.
exitCh := make(chan error)
go func() {
exitCh <- vm.Wait()
}()
defer vm.Terminate()
// Alert the parent process that initialization has completed
// successfully.
os.Stdout.Write(runhcs.ShimSuccess)
os.Stdout.Close()
fatalWriter.Writer = ioutil.Discard
pipeCh := make(chan net.Conn)
go func() {
for {
conn, err := l.Accept()
if err != nil {
logrus.Error(err)
continue
}
pipeCh <- conn
}
}()
for {
select {
case <-exitCh:
return nil
case pipe := <-pipeCh:
err = processRequest(vm, pipe)
if err == nil {
_, err = pipe.Write(runhcs.ShimSuccess)
// Wait until the pipe is closed before closing the
// container so that it is properly handed off to the other
// process.
if err == nil {
err = closeWritePipe(pipe)
}
if err == nil {
ioutil.ReadAll(pipe)
}
} else {
logrus.WithError(err).
Error("failed creating container in VM")
fmt.Fprintf(pipe, "%v", err)
}
pipe.Close()
}
}
},
}
func processRequest(vm *uvm.UtilityVM, pipe net.Conn) error {
var req runhcs.VMRequest
err := json.NewDecoder(pipe).Decode(&req)
if err != nil {
return err
}
logrus.WithFields(logrus.Fields{
logfields.ContainerID: req.ID,
logfields.VMShimOperation: req.Op,
}).Debug("process request")
c, err := getContainer(req.ID, false)
if err != nil {
return err
}
defer func() {
if c != nil {
c.Close()
}
}()
switch req.Op {
case runhcs.OpCreateContainer:
err = createContainerInHost(c, vm)
if err != nil {
return err
}
c2 := c
c = nil
go func() {
c2.hc.Wait()
c2.Close()
}()
case runhcs.OpUnmountContainer, runhcs.OpUnmountContainerDiskOnly:
err = c.unmountInHost(vm, req.Op == runhcs.OpUnmountContainer)
if err != nil {
return err
}
case runhcs.OpSyncNamespace:
return errors.New("Not implemented")
default:
panic("unknown operation")
}
return nil
}
type noVMError struct {
ID string
}
func (err *noVMError) Error() string {
return "VM " + err.ID + " cannot be contacted"
}
func (c *container) issueVMRequest(op runhcs.VMRequestOp) error {
req := runhcs.VMRequest{
ID: c.ID,
Op: op,
}
if err := runhcs.IssueVMRequest(c.VMPipePath(), &req); err != nil {
if perr, ok := err.(*os.PathError); ok && perr.Err == syscall.ERROR_FILE_NOT_FOUND {
return &noVMError{c.HostID}
}
return err
}
return nil
}

View File

@ -0,0 +1,64 @@
package main
import (
"flag"
"fmt"
"io"
"io/ioutil"
"os"
"github.com/Microsoft/hcsshim/ext4/tar2ext4"
)
var (
input = flag.String("i", "", "input file")
output = flag.String("o", "", "output file")
overlay = flag.Bool("overlay", false, "produce overlayfs-compatible layer image")
vhd = flag.Bool("vhd", false, "add a VHD footer to the end of the image")
inlineData = flag.Bool("inline", false, "write small file data into the inode; not compatible with DAX")
)
func main() {
flag.Parse()
if flag.NArg() != 0 || len(*output) == 0 {
flag.Usage()
os.Exit(1)
}
err := func() (err error) {
in := os.Stdin
if *input != "" {
in, err = os.Open(*input)
if err != nil {
return err
}
}
out, err := os.Create(*output)
if err != nil {
return err
}
var opts []tar2ext4.Option
if *overlay {
opts = append(opts, tar2ext4.ConvertWhiteout)
}
if *vhd {
opts = append(opts, tar2ext4.AppendVhdFooter)
}
if *inlineData {
opts = append(opts, tar2ext4.InlineData)
}
err = tar2ext4.Convert(in, out, opts...)
if err != nil {
return err
}
// Exhaust the tar stream.
io.Copy(ioutil.Discard, in)
return nil
}()
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}

View File

@ -0,0 +1,36 @@
package main
import (
"path/filepath"
"github.com/Microsoft/hcsshim"
"github.com/Microsoft/hcsshim/internal/appargs"
"github.com/urfave/cli"
)
var createCommand = cli.Command{
Name: "create",
Usage: "creates a new writable container layer",
Flags: []cli.Flag{
cli.StringSliceFlag{
Name: "layer, l",
Usage: "paths to the read-only parent layers",
},
},
ArgsUsage: "<layer path>",
Before: appargs.Validate(appargs.NonEmptyString),
Action: func(context *cli.Context) error {
path, err := filepath.Abs(context.Args().First())
if err != nil {
return err
}
layers, err := normalizeLayers(context.StringSlice("layer"), true)
if err != nil {
return err
}
di := driverInfo
return hcsshim.CreateScratchLayer(di, path, layers[len(layers)-1], layers)
},
}

View File

@ -0,0 +1,66 @@
package main
import (
"compress/gzip"
"io"
"os"
"path/filepath"
winio "github.com/Microsoft/go-winio"
"github.com/Microsoft/hcsshim/internal/appargs"
"github.com/Microsoft/hcsshim/internal/ociwclayer"
"github.com/urfave/cli"
)
var exportCommand = cli.Command{
Name: "export",
Usage: "exports a layer to a tar file",
Flags: []cli.Flag{
cli.StringSliceFlag{
Name: "layer, l",
Usage: "paths to the read-only parent layers",
},
cli.StringFlag{
Name: "output, o",
Usage: "output layer tar (defaults to stdout)",
},
cli.BoolFlag{
Name: "gzip, z",
Usage: "compress output with gzip compression",
},
},
ArgsUsage: "<layer path>",
Before: appargs.Validate(appargs.NonEmptyString),
Action: func(context *cli.Context) (err error) {
path, err := filepath.Abs(context.Args().First())
if err != nil {
return err
}
layers, err := normalizeLayers(context.StringSlice("layer"), true)
if err != nil {
return err
}
err = winio.EnableProcessPrivileges([]string{winio.SeBackupPrivilege})
if err != nil {
return err
}
fp := context.String("output")
f := os.Stdout
if fp != "" {
f, err = os.Create(fp)
if err != nil {
return err
}
defer f.Close()
}
w := io.Writer(f)
if context.Bool("gzip") {
w = gzip.NewWriter(w)
}
return ociwclayer.ExportLayer(w, path, layers)
},
}

View File

@ -0,0 +1,74 @@
package main
import (
"bufio"
"compress/gzip"
"io"
"os"
"path/filepath"
"github.com/Microsoft/go-winio"
"github.com/Microsoft/hcsshim/internal/appargs"
"github.com/Microsoft/hcsshim/internal/ociwclayer"
"github.com/urfave/cli"
)
var importCommand = cli.Command{
Name: "import",
Usage: "imports a layer from a tar file",
Flags: []cli.Flag{
cli.StringSliceFlag{
Name: "layer, l",
Usage: "paths to the read-only parent layers",
},
cli.StringFlag{
Name: "input, i",
Usage: "input layer tar (defaults to stdin)",
},
},
ArgsUsage: "<layer path>",
Before: appargs.Validate(appargs.NonEmptyString),
Action: func(context *cli.Context) (err error) {
path, err := filepath.Abs(context.Args().First())
if err != nil {
return err
}
layers, err := normalizeLayers(context.StringSlice("layer"), false)
if err != nil {
return err
}
fp := context.String("input")
f := os.Stdin
if fp != "" {
f, err = os.Open(fp)
if err != nil {
return err
}
defer f.Close()
}
r, err := addDecompressor(f)
if err != nil {
return err
}
err = winio.EnableProcessPrivileges([]string{winio.SeBackupPrivilege, winio.SeRestorePrivilege})
if err != nil {
return err
}
_, err = ociwclayer.ImportLayer(r, path, layers)
return err
},
}
func addDecompressor(r io.Reader) (io.Reader, error) {
b := bufio.NewReader(r)
hdr, err := b.Peek(3)
if err != nil {
return nil, err
}
if hdr[0] == 0x1f && hdr[1] == 0x8b && hdr[2] == 8 {
return gzip.NewReader(b)
}
return b, nil
}

View File

@ -0,0 +1,88 @@
package main
import (
"errors"
"fmt"
"os"
"path/filepath"
"github.com/Microsoft/hcsshim"
"github.com/Microsoft/hcsshim/internal/appargs"
"github.com/urfave/cli"
)
var mountCommand = cli.Command{
Name: "mount",
Usage: "mounts a scratch",
ArgsUsage: "<scratch path>",
Flags: []cli.Flag{
cli.StringSliceFlag{
Name: "layer, l",
Usage: "paths to the parent layers for this layer",
},
},
Action: func(context *cli.Context) (err error) {
if context.NArg() != 1 {
return errors.New("invalid usage")
}
path, err := filepath.Abs(context.Args().First())
if err != nil {
return err
}
layers, err := normalizeLayers(context.StringSlice("layer"), true)
if err != nil {
return err
}
err = hcsshim.ActivateLayer(driverInfo, path)
if err != nil {
return err
}
defer func() {
if err != nil {
hcsshim.DeactivateLayer(driverInfo, path)
}
}()
err = hcsshim.PrepareLayer(driverInfo, path, layers)
if err != nil {
return err
}
defer func() {
if err != nil {
hcsshim.UnprepareLayer(driverInfo, path)
}
}()
mountPath, err := hcsshim.GetLayerMountPath(driverInfo, path)
if err != nil {
return err
}
_, err = fmt.Println(mountPath)
return err
},
}
var unmountCommand = cli.Command{
Name: "unmount",
Usage: "unmounts a scratch",
ArgsUsage: "<layer path>",
Before: appargs.Validate(appargs.NonEmptyString),
Action: func(context *cli.Context) (err error) {
path, err := filepath.Abs(context.Args().First())
if err != nil {
return err
}
err = hcsshim.UnprepareLayer(driverInfo, path)
if err != nil {
fmt.Fprintln(os.Stderr, err)
}
err = hcsshim.DeactivateLayer(driverInfo, path)
if err != nil {
return err
}
return nil
},
}

View File

@ -0,0 +1,31 @@
package main
import (
"path/filepath"
winio "github.com/Microsoft/go-winio"
"github.com/Microsoft/hcsshim"
"github.com/Microsoft/hcsshim/internal/appargs"
"github.com/urfave/cli"
)
var removeCommand = cli.Command{
Name: "remove",
Usage: "permanently removes a layer directory in its entirety",
ArgsUsage: "<layer path>",
Before: appargs.Validate(appargs.NonEmptyString),
Action: func(context *cli.Context) (err error) {
path, err := filepath.Abs(context.Args().First())
if err != nil {
return err
}
err = winio.EnableProcessPrivileges([]string{winio.SeBackupPrivilege, winio.SeRestorePrivilege})
if err != nil {
return err
}
return hcsshim.DestroyLayer(driverInfo, path)
},
}

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,43 @@
{
"FixedFileInfo": {
"FileVersion": {
"Major": 1,
"Minor": 0,
"Patch": 0,
"Build": 0
},
"ProductVersion": {
"Major": 1,
"Minor": 0,
"Patch": 0,
"Build": 0
},
"FileFlagsMask": "3f",
"FileFlags ": "00",
"FileOS": "040004",
"FileType": "01",
"FileSubType": "00"
},
"StringFileInfo": {
"Comments": "",
"CompanyName": "",
"FileDescription": "",
"FileVersion": "",
"InternalName": "",
"LegalCopyright": "",
"LegalTrademarks": "",
"OriginalFilename": "",
"PrivateBuild": "",
"ProductName": "",
"ProductVersion": "v1.0.0.0",
"SpecialBuild": ""
},
"VarFileInfo": {
"Translation": {
"LangID": "0409",
"CharsetID": "04B0"
}
},
"IconPath": "",
"ManifestPath": "wclayer.exe.manifest"
}

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<description>wclayer</description>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- Windows 10 -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
</application>
</compatibility>
</assembly>

View File

@ -0,0 +1,60 @@
package main
import (
"errors"
"fmt"
"os"
"path/filepath"
"github.com/Microsoft/hcsshim"
"github.com/urfave/cli"
)
// Add a manifest to get proper Windows version detection.
//
// goversioninfo can be installed with "go get github.com/josephspurrier/goversioninfo/cmd/goversioninfo"
//go:generate goversioninfo -platform-specific
var usage = `Windows Container layer utility
wclayer is a command line tool for manipulating Windows Container
storage layers. It can import and export layers from and to OCI format
layer tar files, create new writable layers, and mount and unmount
container images.`
var driverInfo = hcsshim.DriverInfo{}
func main() {
app := cli.NewApp()
app.Name = "wclayer"
app.Commands = []cli.Command{
createCommand,
exportCommand,
importCommand,
mountCommand,
removeCommand,
unmountCommand,
}
app.Usage = usage
if err := app.Run(os.Args); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
func normalizeLayers(il []string, needOne bool) ([]string, error) {
if needOne && len(il) == 0 {
return nil, errors.New("at least one read-only layer must be specified")
}
ol := make([]string, len(il))
for i := range il {
var err error
ol[i], err = filepath.Abs(il[i])
if err != nil {
return nil, err
}
}
return ol, nil
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,355 @@
package compactext4
import (
"bytes"
"encoding/binary"
"fmt"
"io"
"os"
"strings"
"testing"
"time"
"github.com/Microsoft/hcsshim/ext4/internal/format"
)
type testFile struct {
Path string
File *File
Data []byte
DataSize int64
Link string
ExpectError bool
}
var (
data []byte
name string
)
func init() {
data = make([]byte, blockSize*2)
for i := range data {
data[i] = uint8(i)
}
nameb := make([]byte, 300)
for i := range nameb {
nameb[i] = byte('0' + i%10)
}
name = string(nameb)
}
type largeData struct {
pos int64
}
func (d *largeData) Read(b []byte) (int, error) {
p := d.pos
var pb [8]byte
for i := range b {
binary.LittleEndian.PutUint64(pb[:], uint64(p+int64(i)))
b[i] = pb[i%8]
}
p += int64(len(b))
return len(b), nil
}
func (tf *testFile) Reader() io.Reader {
if tf.DataSize != 0 {
return io.LimitReader(&largeData{}, tf.DataSize)
}
return bytes.NewReader(tf.Data)
}
func createTestFile(t *testing.T, w *Writer, tf testFile) {
var err error
if tf.File != nil {
tf.File.Size = int64(len(tf.Data))
if tf.File.Size == 0 {
tf.File.Size = tf.DataSize
}
err = w.Create(tf.Path, tf.File)
} else {
err = w.Link(tf.Link, tf.Path)
}
if tf.ExpectError && err == nil {
t.Errorf("%s: expected error", tf.Path)
} else if !tf.ExpectError && err != nil {
t.Error(err)
} else {
_, err := io.Copy(w, tf.Reader())
if err != nil {
t.Error(err)
}
}
}
func expectedMode(f *File) uint16 {
switch f.Mode & format.TypeMask {
case 0:
return f.Mode | S_IFREG
case S_IFLNK:
return f.Mode | 0777
default:
return f.Mode
}
}
func expectedSize(f *File) int64 {
switch f.Mode & format.TypeMask {
case 0, S_IFREG:
return f.Size
case S_IFLNK:
return int64(len(f.Linkname))
default:
return 0
}
}
func xattrsEqual(x1, x2 map[string][]byte) bool {
if len(x1) != len(x2) {
return false
}
for name, value := range x1 {
if !bytes.Equal(x2[name], value) {
return false
}
}
return true
}
func fileEqual(f1, f2 *File) bool {
return f1.Linkname == f2.Linkname &&
expectedSize(f1) == expectedSize(f2) &&
expectedMode(f1) == expectedMode(f2) &&
f1.Uid == f2.Uid &&
f1.Gid == f2.Gid &&
f1.Atime.Equal(f2.Atime) &&
f1.Ctime.Equal(f2.Ctime) &&
f1.Mtime.Equal(f2.Mtime) &&
f1.Crtime.Equal(f2.Crtime) &&
f1.Devmajor == f2.Devmajor &&
f1.Devminor == f2.Devminor &&
xattrsEqual(f1.Xattrs, f2.Xattrs)
}
func runTestsOnFiles(t *testing.T, testFiles []testFile, opts ...Option) {
image := "testfs.img"
imagef, err := os.Create(image)
if err != nil {
t.Fatal(err)
}
defer os.Remove(image)
defer imagef.Close()
w := NewWriter(imagef, opts...)
for _, tf := range testFiles {
createTestFile(t, w, tf)
if !tf.ExpectError && tf.File != nil {
f, err := w.Stat(tf.Path)
if err != nil {
if !strings.Contains(err.Error(), "cannot retrieve") {
t.Error(err)
}
} else if !fileEqual(f, tf.File) {
t.Errorf("%s: stat mismatch: %#v %#v", tf.Path, tf.File, f)
}
}
}
if t.Failed() {
return
}
if err := w.Close(); err != nil {
t.Fatal(err)
}
fsck(t, image)
mountPath := "testmnt"
if mountImage(t, image, mountPath) {
defer unmountImage(t, mountPath)
validated := make(map[string]*testFile)
for i := range testFiles {
tf := testFiles[len(testFiles)-i-1]
if validated[tf.Link] != nil {
// The link target was subsequently replaced. Find the
// earlier instance.
for j := range testFiles[:len(testFiles)-i-1] {
otf := testFiles[j]
if otf.Path == tf.Link && !otf.ExpectError {
tf = otf
break
}
}
}
if !tf.ExpectError && validated[tf.Path] == nil {
verifyTestFile(t, mountPath, tf)
validated[tf.Path] = &tf
}
}
}
}
func TestBasic(t *testing.T) {
now := time.Now()
testFiles := []testFile{
{Path: "empty", File: &File{Mode: 0644}},
{Path: "small", File: &File{Mode: 0644}, Data: data[:40]},
{Path: "time", File: &File{Atime: now, Ctime: now.Add(time.Second), Mtime: now.Add(time.Hour)}},
{Path: "block_1", File: &File{Mode: 0644}, Data: data[:blockSize]},
{Path: "block_2", File: &File{Mode: 0644}, Data: data[:blockSize*2]},
{Path: "symlink", File: &File{Linkname: "block_1", Mode: format.S_IFLNK}},
{Path: "symlink_59", File: &File{Linkname: name[:59], Mode: format.S_IFLNK}},
{Path: "symlink_60", File: &File{Linkname: name[:60], Mode: format.S_IFLNK}},
{Path: "symlink_120", File: &File{Linkname: name[:120], Mode: format.S_IFLNK}},
{Path: "symlink_300", File: &File{Linkname: name[:300], Mode: format.S_IFLNK}},
{Path: "dir", File: &File{Mode: format.S_IFDIR | 0755}},
{Path: "dir/fifo", File: &File{Mode: format.S_IFIFO}},
{Path: "dir/sock", File: &File{Mode: format.S_IFSOCK}},
{Path: "dir/blk", File: &File{Mode: format.S_IFBLK, Devmajor: 0x5678, Devminor: 0x1234}},
{Path: "dir/chr", File: &File{Mode: format.S_IFCHR, Devmajor: 0x5678, Devminor: 0x1234}},
{Path: "dir/hard_link", Link: "small"},
}
runTestsOnFiles(t, testFiles)
}
func TestLargeDirectory(t *testing.T) {
testFiles := []testFile{
{Path: "bigdir", File: &File{Mode: format.S_IFDIR | 0755}},
}
for i := 0; i < 50000; i++ {
testFiles = append(testFiles, testFile{
Path: fmt.Sprintf("bigdir/%d", i), File: &File{Mode: 0644},
})
}
runTestsOnFiles(t, testFiles)
}
func TestInlineData(t *testing.T) {
testFiles := []testFile{
{Path: "inline_30", File: &File{Mode: 0644}, Data: data[:30]},
{Path: "inline_60", File: &File{Mode: 0644}, Data: data[:60]},
{Path: "inline_120", File: &File{Mode: 0644}, Data: data[:120]},
{Path: "inline_full", File: &File{Mode: 0644}, Data: data[:inlineDataSize]},
{Path: "block_min", File: &File{Mode: 0644}, Data: data[:inlineDataSize+1]},
}
runTestsOnFiles(t, testFiles, InlineData)
}
func TestXattrs(t *testing.T) {
testFiles := []testFile{
{Path: "withsmallxattrs",
File: &File{
Mode: format.S_IFREG | 0644,
Xattrs: map[string][]byte{
"user.foo": []byte("test"),
"user.bar": []byte("test2"),
},
},
},
{Path: "withlargexattrs",
File: &File{
Mode: format.S_IFREG | 0644,
Xattrs: map[string][]byte{
"user.foo": data[:100],
"user.bar": data[:50],
},
},
},
}
runTestsOnFiles(t, testFiles)
}
func TestReplace(t *testing.T) {
testFiles := []testFile{
{Path: "lost+found", ExpectError: true, File: &File{}}, // can't change type
{Path: "lost+found", File: &File{Mode: format.S_IFDIR | 0777}},
{Path: "dir", File: &File{Mode: format.S_IFDIR | 0777}},
{Path: "dir/file", File: &File{}},
{Path: "dir", File: &File{Mode: format.S_IFDIR | 0700}},
{Path: "file", File: &File{}},
{Path: "file", File: &File{Mode: 0600}},
{Path: "file2", File: &File{}},
{Path: "link", Link: "file2"},
{Path: "file2", File: &File{Mode: 0600}},
{Path: "nolinks", File: &File{}},
{Path: "nolinks", ExpectError: true, Link: "file"}, // would orphan nolinks
{Path: "onelink", File: &File{}},
{Path: "onelink2", Link: "onelink"},
{Path: "onelink", Link: "file"},
{Path: "", ExpectError: true, File: &File{}},
{Path: "", ExpectError: true, Link: "file"},
{Path: "", File: &File{Mode: format.S_IFDIR | 0777}},
{Path: "smallxattr", File: &File{Xattrs: map[string][]byte{"user.foo": data[:4]}}},
{Path: "smallxattr", File: &File{Xattrs: map[string][]byte{"user.foo": data[:8]}}},
{Path: "smallxattr_delete", File: &File{Xattrs: map[string][]byte{"user.foo": data[:4]}}},
{Path: "smallxattr_delete", File: &File{}},
{Path: "largexattr", File: &File{Xattrs: map[string][]byte{"user.small": data[:8], "user.foo": data[:200]}}},
{Path: "largexattr", File: &File{Xattrs: map[string][]byte{"user.small": data[:12], "user.foo": data[:400]}}},
{Path: "largexattr", File: &File{Xattrs: map[string][]byte{"user.foo": data[:200]}}},
{Path: "largexattr_delete", File: &File{}},
}
runTestsOnFiles(t, testFiles)
}
func TestTime(t *testing.T) {
now := time.Now()
now2 := fsTimeToTime(timeToFsTime(now))
if now.UnixNano() != now2.UnixNano() {
t.Fatalf("%s != %s", now, now2)
}
}
func TestLargeFile(t *testing.T) {
testFiles := []testFile{
{Path: "small", File: &File{}, DataSize: 1024 * 1024}, // can't change type
{Path: "medium", File: &File{}, DataSize: 200 * 1024 * 1024}, // can't change type
{Path: "large", File: &File{}, DataSize: 600 * 1024 * 1024}, // can't change type
}
runTestsOnFiles(t, testFiles)
}
func TestFileLinkLimit(t *testing.T) {
testFiles := []testFile{
{Path: "file", File: &File{}},
}
for i := 0; i < format.MaxLinks; i++ {
testFiles = append(testFiles, testFile{Path: fmt.Sprintf("link%d", i), Link: "file"})
}
testFiles[len(testFiles)-1].ExpectError = true
runTestsOnFiles(t, testFiles)
}
func TestDirLinkLimit(t *testing.T) {
testFiles := []testFile{
{Path: "dir", File: &File{Mode: S_IFDIR}},
}
for i := 0; i < format.MaxLinks-1; i++ {
testFiles = append(testFiles, testFile{Path: fmt.Sprintf("dir/%d", i), File: &File{Mode: S_IFDIR}})
}
testFiles[len(testFiles)-1].ExpectError = true
runTestsOnFiles(t, testFiles)
}
func TestLargeDisk(t *testing.T) {
testFiles := []testFile{
{Path: "file", File: &File{}},
}
runTestsOnFiles(t, testFiles, MaximumDiskSize(maxMaxDiskSize))
}

View File

@ -0,0 +1,248 @@
package compactext4
import (
"bytes"
"fmt"
"io"
"os"
"os/exec"
"path"
"syscall"
"testing"
"time"
"unsafe"
"github.com/Microsoft/hcsshim/ext4/internal/format"
)
func timeEqual(ts syscall.Timespec, t time.Time) bool {
sec, nsec := t.Unix(), t.Nanosecond()
if t.IsZero() {
sec, nsec = 0, 0
}
return ts.Sec == sec && int(ts.Nsec) == nsec
}
func expectedDevice(f *File) uint64 {
return uint64(f.Devminor&0xff | f.Devmajor<<8 | (f.Devminor&0xffffff00)<<12)
}
func llistxattr(path string, b []byte) (int, error) {
pathp := syscall.StringBytePtr(path)
var p unsafe.Pointer
if len(b) > 0 {
p = unsafe.Pointer(&b[0])
}
r, _, e := syscall.Syscall(syscall.SYS_LLISTXATTR, uintptr(unsafe.Pointer(pathp)), uintptr(p), uintptr(len(b)))
if e != 0 {
return 0, &os.PathError{Path: path, Op: "llistxattr", Err: syscall.Errno(e)}
}
return int(r), nil
}
func lgetxattr(path string, name string, b []byte) (int, error) {
pathp := syscall.StringBytePtr(path)
namep := syscall.StringBytePtr(name)
var p unsafe.Pointer
if len(b) > 0 {
p = unsafe.Pointer(&b[0])
}
r, _, e := syscall.Syscall6(syscall.SYS_LGETXATTR, uintptr(unsafe.Pointer(pathp)), uintptr(unsafe.Pointer(namep)), uintptr(p), uintptr(len(b)), 0, 0)
if e != 0 {
return 0, &os.PathError{Path: path, Op: "lgetxattr", Err: syscall.Errno(e)}
}
return int(r), nil
}
func readXattrs(path string) (map[string][]byte, error) {
xattrs := make(map[string][]byte)
var buf [4096]byte
var buf2 [4096]byte
b := buf[:]
n, err := llistxattr(path, b)
if err != nil {
return nil, err
}
b = b[:n]
for len(b) != 0 {
nn := bytes.IndexByte(b, 0)
name := string(b[:nn])
b = b[nn+1:]
vn, err := lgetxattr(path, name, buf2[:])
if err != nil {
return nil, err
}
value := buf2[:vn]
xattrs[name] = value
}
return xattrs, nil
}
func streamEqual(r1, r2 io.Reader) (bool, error) {
var b [4096]byte
var b2 [4096]byte
for {
n, err := r1.Read(b[:])
if n == 0 {
if err == io.EOF {
break
}
if err == nil {
continue
}
return false, err
}
_, err = io.ReadFull(r2, b2[:n])
if err == io.EOF || err == io.ErrUnexpectedEOF {
return false, nil
}
if err != nil {
return false, err
}
if !bytes.Equal(b[n:], b2[n:]) {
return false, nil
}
}
// Check the tail of r2
_, err := r2.Read(b[:1])
if err == nil {
return false, nil
}
if err != io.EOF {
return false, err
}
return true, nil
}
func verifyTestFile(t *testing.T, mountPath string, tf testFile) {
name := path.Join(mountPath, tf.Path)
fi, err := os.Lstat(name)
if err != nil {
t.Error(err)
return
}
st := fi.Sys().(*syscall.Stat_t)
if tf.File != nil {
if st.Mode != uint32(expectedMode(tf.File)) ||
st.Uid != tf.File.Uid ||
st.Gid != tf.File.Gid ||
(!fi.IsDir() && st.Size != expectedSize(tf.File)) ||
st.Rdev != expectedDevice(tf.File) ||
!timeEqual(st.Atim, tf.File.Atime) ||
!timeEqual(st.Mtim, tf.File.Mtime) ||
!timeEqual(st.Ctim, tf.File.Ctime) {
t.Errorf("%s: stat mismatch, expected: %#v got: %#v", tf.Path, tf.File, st)
}
xattrs, err := readXattrs(name)
if err != nil {
t.Error(err)
} else if !xattrsEqual(xattrs, tf.File.Xattrs) {
t.Errorf("%s: xattr mismatch, expected: %#v got: %#v", tf.Path, tf.File.Xattrs, xattrs)
}
switch tf.File.Mode & format.TypeMask {
case S_IFREG:
if f, err := os.Open(name); err != nil {
t.Error(err)
} else {
same, err := streamEqual(f, tf.Reader())
if err != nil {
t.Error(err)
} else if !same {
t.Errorf("%s: data mismatch", tf.Path)
}
f.Close()
}
case S_IFLNK:
if link, err := os.Readlink(name); err != nil {
t.Error(err)
} else if link != tf.File.Linkname {
t.Errorf("%s: link mismatch, expected: %s got: %s", tf.Path, tf.File.Linkname, link)
}
}
} else {
lfi, err := os.Lstat(path.Join(mountPath, tf.Link))
if err != nil {
t.Error(err)
return
}
lst := lfi.Sys().(*syscall.Stat_t)
if lst.Ino != st.Ino {
t.Errorf("%s: hard link mismatch with %s, expected inode: %d got inode: %d", tf.Path, tf.Link, lst.Ino, st.Ino)
}
}
}
type capHeader struct {
version uint32
pid int
}
type capData struct {
effective uint32
permitted uint32
inheritable uint32
}
const CAP_SYS_ADMIN = 21
type caps struct {
hdr capHeader
data [2]capData
}
func getCaps() (caps, error) {
var c caps
// Get capability version
if _, _, errno := syscall.Syscall(syscall.SYS_CAPGET, uintptr(unsafe.Pointer(&c.hdr)), uintptr(unsafe.Pointer(nil)), 0); errno != 0 {
return c, fmt.Errorf("SYS_CAPGET: %v", errno)
}
// Get current capabilities
if _, _, errno := syscall.Syscall(syscall.SYS_CAPGET, uintptr(unsafe.Pointer(&c.hdr)), uintptr(unsafe.Pointer(&c.data[0])), 0); errno != 0 {
return c, fmt.Errorf("SYS_CAPGET: %v", errno)
}
return c, nil
}
func mountImage(t *testing.T, image string, mountPath string) bool {
caps, err := getCaps()
if err != nil || caps.data[0].effective&(1<<uint(CAP_SYS_ADMIN)) == 0 {
t.Log("cannot mount to run verification tests without CAP_SYS_ADMIN")
return false
}
err = os.MkdirAll(mountPath, 0777)
if err != nil {
t.Fatal(err)
}
out, err := exec.Command("mount", "-o", "loop,ro", "-t", "ext4", image, mountPath).CombinedOutput()
t.Logf("%s", out)
if err != nil {
t.Fatal(err)
}
return true
}
func unmountImage(t *testing.T, mountPath string) {
out, err := exec.Command("umount", mountPath).CombinedOutput()
t.Logf("%s", out)
if err != nil {
t.Log(err)
}
}
func fsck(t *testing.T, image string) {
cmd := exec.Command("e2fsck", "-v", "-f", "-n", image)
out, err := cmd.CombinedOutput()
t.Logf("%s", out)
if err != nil {
t.Fatal(err)
}
}

View File

@ -0,0 +1,18 @@
// +build !linux
package compactext4
import "testing"
func verifyTestFile(t *testing.T, mountPath string, tf testFile) {
}
func mountImage(t *testing.T, image string, mountPath string) bool {
return false
}
func unmountImage(t *testing.T, mountPath string) {
}
func fsck(t *testing.T, image string) {
}

View File

@ -0,0 +1,411 @@
package format
type SuperBlock struct {
InodesCount uint32
BlocksCountLow uint32
RootBlocksCountLow uint32
FreeBlocksCountLow uint32
FreeInodesCount uint32
FirstDataBlock uint32
LogBlockSize uint32
LogClusterSize uint32
BlocksPerGroup uint32
ClustersPerGroup uint32
InodesPerGroup uint32
Mtime uint32
Wtime uint32
MountCount uint16
MaxMountCount uint16
Magic uint16
State uint16
Errors uint16
MinorRevisionLevel uint16
LastCheck uint32
CheckInterval uint32
CreatorOS uint32
RevisionLevel uint32
DefaultReservedUid uint16
DefaultReservedGid uint16
FirstInode uint32
InodeSize uint16
BlockGroupNr uint16
FeatureCompat CompatFeature
FeatureIncompat IncompatFeature
FeatureRoCompat RoCompatFeature
UUID [16]uint8
VolumeName [16]byte
LastMounted [64]byte
AlgorithmUsageBitmap uint32
PreallocBlocks uint8
PreallocDirBlocks uint8
ReservedGdtBlocks uint16
JournalUUID [16]uint8
JournalInum uint32
JournalDev uint32
LastOrphan uint32
HashSeed [4]uint32
DefHashVersion uint8
JournalBackupType uint8
DescSize uint16
DefaultMountOpts uint32
FirstMetaBg uint32
MkfsTime uint32
JournalBlocks [17]uint32
BlocksCountHigh uint32
RBlocksCountHigh uint32
FreeBlocksCountHigh uint32
MinExtraIsize uint16
WantExtraIsize uint16
Flags uint32
RaidStride uint16
MmpInterval uint16
MmpBlock uint64
RaidStripeWidth uint32
LogGroupsPerFlex uint8
ChecksumType uint8
ReservedPad uint16
KbytesWritten uint64
SnapshotInum uint32
SnapshotID uint32
SnapshotRBlocksCount uint64
SnapshotList uint32
ErrorCount uint32
FirstErrorTime uint32
FirstErrorInode uint32
FirstErrorBlock uint64
FirstErrorFunc [32]uint8
FirstErrorLine uint32
LastErrorTime uint32
LastErrorInode uint32
LastErrorLine uint32
LastErrorBlock uint64
LastErrorFunc [32]uint8
MountOpts [64]uint8
UserQuotaInum uint32
GroupQuotaInum uint32
OverheadBlocks uint32
BackupBgs [2]uint32
EncryptAlgos [4]uint8
EncryptPwSalt [16]uint8
LpfInode uint32
ProjectQuotaInum uint32
ChecksumSeed uint32
WtimeHigh uint8
MtimeHigh uint8
MkfsTimeHigh uint8
LastcheckHigh uint8
FirstErrorTimeHigh uint8
LastErrorTimeHigh uint8
Pad [2]uint8
Reserved [96]uint32
Checksum uint32
}
const SuperBlockMagic uint16 = 0xef53
type CompatFeature uint32
type IncompatFeature uint32
type RoCompatFeature uint32
const (
CompatDirPrealloc CompatFeature = 0x1
CompatImagicInodes CompatFeature = 0x2
CompatHasJournal CompatFeature = 0x4
CompatExtAttr CompatFeature = 0x8
CompatResizeInode CompatFeature = 0x10
CompatDirIndex CompatFeature = 0x20
CompatLazyBg CompatFeature = 0x40
CompatExcludeInode CompatFeature = 0x80
CompatExcludeBitmap CompatFeature = 0x100
CompatSparseSuper2 CompatFeature = 0x200
IncompatCompression IncompatFeature = 0x1
IncompatFiletype IncompatFeature = 0x2
IncompatRecover IncompatFeature = 0x4
IncompatJournalDev IncompatFeature = 0x8
IncompatMetaBg IncompatFeature = 0x10
IncompatExtents IncompatFeature = 0x40
Incompat_64Bit IncompatFeature = 0x80
IncompatMmp IncompatFeature = 0x100
IncompatFlexBg IncompatFeature = 0x200
IncompatEaInode IncompatFeature = 0x400
IncompatDirdata IncompatFeature = 0x1000
IncompatCsumSeed IncompatFeature = 0x2000
IncompatLargedir IncompatFeature = 0x4000
IncompatInlineData IncompatFeature = 0x8000
IncompatEncrypt IncompatFeature = 0x10000
RoCompatSparseSuper RoCompatFeature = 0x1
RoCompatLargeFile RoCompatFeature = 0x2
RoCompatBtreeDir RoCompatFeature = 0x4
RoCompatHugeFile RoCompatFeature = 0x8
RoCompatGdtCsum RoCompatFeature = 0x10
RoCompatDirNlink RoCompatFeature = 0x20
RoCompatExtraIsize RoCompatFeature = 0x40
RoCompatHasSnapshot RoCompatFeature = 0x80
RoCompatQuota RoCompatFeature = 0x100
RoCompatBigalloc RoCompatFeature = 0x200
RoCompatMetadataCsum RoCompatFeature = 0x400
RoCompatReplica RoCompatFeature = 0x800
RoCompatReadonly RoCompatFeature = 0x1000
RoCompatProject RoCompatFeature = 0x2000
)
type BlockGroupFlag uint16
const (
BlockGroupInodeUninit BlockGroupFlag = 0x1
BlockGroupBlockUninit BlockGroupFlag = 0x2
BlockGroupInodeZeroed BlockGroupFlag = 0x4
)
type GroupDescriptor struct {
BlockBitmapLow uint32
InodeBitmapLow uint32
InodeTableLow uint32
FreeBlocksCountLow uint16
FreeInodesCountLow uint16
UsedDirsCountLow uint16
Flags BlockGroupFlag
ExcludeBitmapLow uint32
BlockBitmapCsumLow uint16
InodeBitmapCsumLow uint16
ItableUnusedLow uint16
Checksum uint16
}
type GroupDescriptor64 struct {
GroupDescriptor
BlockBitmapHigh uint32
InodeBitmapHigh uint32
InodeTableHigh uint32
FreeBlocksCountHigh uint16
FreeInodesCountHigh uint16
UsedDirsCountHigh uint16
ItableUnusedHigh uint16
ExcludeBitmapHigh uint32
BlockBitmapCsumHigh uint16
InodeBitmapCsumHigh uint16
Reserved uint32
}
const (
S_IXOTH = 0x1
S_IWOTH = 0x2
S_IROTH = 0x4
S_IXGRP = 0x8
S_IWGRP = 0x10
S_IRGRP = 0x20
S_IXUSR = 0x40
S_IWUSR = 0x80
S_IRUSR = 0x100
S_ISVTX = 0x200
S_ISGID = 0x400
S_ISUID = 0x800
S_IFIFO = 0x1000
S_IFCHR = 0x2000
S_IFDIR = 0x4000
S_IFBLK = 0x6000
S_IFREG = 0x8000
S_IFLNK = 0xA000
S_IFSOCK = 0xC000
TypeMask uint16 = 0xF000
)
type InodeNumber uint32
const (
InodeRoot = 2
)
type Inode struct {
Mode uint16
Uid uint16
SizeLow uint32
Atime uint32
Ctime uint32
Mtime uint32
Dtime uint32
Gid uint16
LinksCount uint16
BlocksLow uint32
Flags InodeFlag
Version uint32
Block [60]byte
Generation uint32
XattrBlockLow uint32
SizeHigh uint32
ObsoleteFragmentAddr uint32
BlocksHigh uint16
XattrBlockHigh uint16
UidHigh uint16
GidHigh uint16
ChecksumLow uint16
Reserved uint16
ExtraIsize uint16
ChecksumHigh uint16
CtimeExtra uint32
MtimeExtra uint32
AtimeExtra uint32
Crtime uint32
CrtimeExtra uint32
VersionHigh uint32
Projid uint32
}
type InodeFlag uint32
const (
InodeFlagSecRm InodeFlag = 0x1
InodeFlagUnRm InodeFlag = 0x2
InodeFlagCompressed InodeFlag = 0x4
InodeFlagSync InodeFlag = 0x8
InodeFlagImmutable InodeFlag = 0x10
InodeFlagAppend InodeFlag = 0x20
InodeFlagNoDump InodeFlag = 0x40
InodeFlagNoAtime InodeFlag = 0x80
InodeFlagDirtyCompressed InodeFlag = 0x100
InodeFlagCompressedClusters InodeFlag = 0x200
InodeFlagNoCompress InodeFlag = 0x400
InodeFlagEncrypted InodeFlag = 0x800
InodeFlagHashedIndex InodeFlag = 0x1000
InodeFlagMagic InodeFlag = 0x2000
InodeFlagJournalData InodeFlag = 0x4000
InodeFlagNoTail InodeFlag = 0x8000
InodeFlagDirSync InodeFlag = 0x10000
InodeFlagTopDir InodeFlag = 0x20000
InodeFlagHugeFile InodeFlag = 0x40000
InodeFlagExtents InodeFlag = 0x80000
InodeFlagEaInode InodeFlag = 0x200000
InodeFlagEOFBlocks InodeFlag = 0x400000
InodeFlagSnapfile InodeFlag = 0x01000000
InodeFlagSnapfileDeleted InodeFlag = 0x04000000
InodeFlagSnapfileShrunk InodeFlag = 0x08000000
InodeFlagInlineData InodeFlag = 0x10000000
InodeFlagProjectIDInherit InodeFlag = 0x20000000
InodeFlagReserved InodeFlag = 0x80000000
)
const (
MaxLinks = 65000
)
type ExtentHeader struct {
Magic uint16
Entries uint16
Max uint16
Depth uint16
Generation uint32
}
const ExtentHeaderMagic uint16 = 0xf30a
type ExtentIndexNode struct {
Block uint32
LeafLow uint32
LeafHigh uint16
Unused uint16
}
type ExtentLeafNode struct {
Block uint32
Length uint16
StartHigh uint16
StartLow uint32
}
type ExtentTail struct {
Checksum uint32
}
type DirectoryEntry struct {
Inode InodeNumber
RecordLength uint16
NameLength uint8
FileType FileType
//Name []byte
}
type FileType uint8
const (
FileTypeUnknown FileType = 0x0
FileTypeRegular FileType = 0x1
FileTypeDirectory FileType = 0x2
FileTypeCharacter FileType = 0x3
FileTypeBlock FileType = 0x4
FileTypeFIFO FileType = 0x5
FileTypeSocket FileType = 0x6
FileTypeSymbolicLink FileType = 0x7
)
type DirectoryEntryTail struct {
ReservedZero1 uint32
RecordLength uint16
ReservedZero2 uint8
FileType uint8
Checksum uint32
}
type DirectoryTreeRoot struct {
Dot DirectoryEntry
DotName [4]byte
DotDot DirectoryEntry
DotDotName [4]byte
ReservedZero uint32
HashVersion uint8
InfoLength uint8
IndirectLevels uint8
UnusedFlags uint8
Limit uint16
Count uint16
Block uint32
//Entries []DirectoryTreeEntry
}
type DirectoryTreeNode struct {
FakeInode uint32
FakeRecordLength uint16
NameLength uint8
FileType uint8
Limit uint16
Count uint16
Block uint32
//Entries []DirectoryTreeEntry
}
type DirectoryTreeEntry struct {
Hash uint32
Block uint32
}
type DirectoryTreeTail struct {
Reserved uint32
Checksum uint32
}
type XAttrInodeBodyHeader struct {
Magic uint32
}
type XAttrHeader struct {
Magic uint32
ReferenceCount uint32
Blocks uint32
Hash uint32
Checksum uint32
Reserved [3]uint32
}
const XAttrHeaderMagic uint32 = 0xea020000
type XAttrEntry struct {
NameLength uint8
NameIndex uint8
ValueOffset uint16
ValueInum uint32
ValueSize uint32
Hash uint32
//Name []byte
}

View File

@ -0,0 +1,174 @@
package tar2ext4
import (
"archive/tar"
"bufio"
"encoding/binary"
"io"
"path"
"strings"
"github.com/Microsoft/hcsshim/ext4/internal/compactext4"
)
type params struct {
convertWhiteout bool
appendVhdFooter bool
ext4opts []compactext4.Option
}
// Option is the type for optional parameters to Convert.
type Option func(*params)
// ConvertWhiteout instructs the converter to convert OCI-style whiteouts
// (beginning with .wh.) to overlay-style whiteouts.
func ConvertWhiteout(p *params) {
p.convertWhiteout = true
}
// AppendVhdFooter instructs the converter to add a fixed VHD footer to the
// file.
func AppendVhdFooter(p *params) {
p.appendVhdFooter = true
}
// InlineData instructs the converter to write small files into the inode
// structures directly. This creates smaller images but currently is not
// compatible with DAX.
func InlineData(p *params) {
p.ext4opts = append(p.ext4opts, compactext4.InlineData)
}
// MaximumDiskSize instructs the writer to limit the disk size to the specified
// value. This also reserves enough metadata space for the specified disk size.
// If not provided, then 16GB is the default.
func MaximumDiskSize(size int64) Option {
return func(p *params) {
p.ext4opts = append(p.ext4opts, compactext4.MaximumDiskSize(size))
}
}
const (
whiteoutPrefix = ".wh."
opaqueWhiteout = ".wh..wh..opq"
)
// Convert writes a compact ext4 file system image that contains the files in the
// input tar stream.
func Convert(r io.Reader, w io.ReadWriteSeeker, options ...Option) error {
var p params
for _, opt := range options {
opt(&p)
}
t := tar.NewReader(bufio.NewReader(r))
fs := compactext4.NewWriter(w, p.ext4opts...)
for {
hdr, err := t.Next()
if err == io.EOF {
break
}
if err != nil {
return err
}
if p.convertWhiteout {
dir, name := path.Split(hdr.Name)
if strings.HasPrefix(name, whiteoutPrefix) {
if name == opaqueWhiteout {
// Update the directory with the appropriate xattr.
f, err := fs.Stat(dir)
if err != nil {
return err
}
f.Xattrs["trusted.overlay.opaque"] = []byte("y")
err = fs.Create(dir, f)
if err != nil {
return err
}
} else {
// Create an overlay-style whiteout.
f := &compactext4.File{
Mode: compactext4.S_IFCHR,
Devmajor: 0,
Devminor: 0,
}
err = fs.Create(path.Join(dir, name[len(whiteoutPrefix):]), f)
if err != nil {
return err
}
}
continue
}
}
if hdr.Typeflag == tar.TypeLink {
err = fs.Link(hdr.Linkname, hdr.Name)
if err != nil {
return err
}
} else {
f := &compactext4.File{
Mode: uint16(hdr.Mode),
Atime: hdr.AccessTime,
Mtime: hdr.ModTime,
Ctime: hdr.ChangeTime,
Crtime: hdr.ModTime,
Size: hdr.Size,
Uid: uint32(hdr.Uid),
Gid: uint32(hdr.Gid),
Linkname: hdr.Linkname,
Devmajor: uint32(hdr.Devmajor),
Devminor: uint32(hdr.Devminor),
Xattrs: make(map[string][]byte),
}
for key, value := range hdr.PAXRecords {
const xattrPrefix = "SCHILY.xattr."
if strings.HasPrefix(key, xattrPrefix) {
f.Xattrs[key[len(xattrPrefix):]] = []byte(value)
}
}
var typ uint16
switch hdr.Typeflag {
case tar.TypeReg, tar.TypeRegA:
typ = compactext4.S_IFREG
case tar.TypeSymlink:
typ = compactext4.S_IFLNK
case tar.TypeChar:
typ = compactext4.S_IFCHR
case tar.TypeBlock:
typ = compactext4.S_IFBLK
case tar.TypeDir:
typ = compactext4.S_IFDIR
case tar.TypeFifo:
typ = compactext4.S_IFIFO
}
f.Mode &= ^compactext4.TypeMask
f.Mode |= typ
err = fs.Create(hdr.Name, f)
if err != nil {
return err
}
_, err = io.Copy(fs, t)
if err != nil {
return err
}
}
}
err := fs.Close()
if err != nil {
return err
}
if p.appendVhdFooter {
size, err := w.Seek(0, io.SeekEnd)
if err != nil {
return err
}
err = binary.Write(w, binary.BigEndian, makeFixedVHDFooter(size))
if err != nil {
return err
}
}
return nil
}

View File

@ -0,0 +1,76 @@
package tar2ext4
import (
"bytes"
"crypto/rand"
"encoding/binary"
)
// Constants for the VHD footer
const (
cookieMagic = "conectix"
featureMask = 0x2
fileFormatVersionMagic = 0x00010000
fixedDataOffset = -1
creatorVersionMagic = 0x000a0000
diskTypeFixed = 2
)
type vhdFooter struct {
Cookie [8]byte
Features uint32
FileFormatVersion uint32
DataOffset int64
TimeStamp uint32
CreatorApplication [4]byte
CreatorVersion uint32
CreatorHostOS [4]byte
OriginalSize int64
CurrentSize int64
DiskGeometry uint32
DiskType uint32
Checksum uint32
UniqueID [16]uint8
SavedState uint8
Reserved [427]uint8
}
func makeFixedVHDFooter(size int64) *vhdFooter {
footer := &vhdFooter{
Features: featureMask,
FileFormatVersion: fileFormatVersionMagic,
DataOffset: fixedDataOffset,
CreatorVersion: creatorVersionMagic,
OriginalSize: size,
CurrentSize: size,
DiskType: diskTypeFixed,
UniqueID: generateUUID(),
}
copy(footer.Cookie[:], cookieMagic)
footer.Checksum = calculateCheckSum(footer)
return footer
}
func calculateCheckSum(footer *vhdFooter) uint32 {
oldchk := footer.Checksum
footer.Checksum = 0
buf := &bytes.Buffer{}
binary.Write(buf, binary.BigEndian, footer)
var chk uint32
bufBytes := buf.Bytes()
for i := 0; i < len(bufBytes); i++ {
chk += uint32(bufBytes[i])
}
footer.Checksum = oldchk
return uint32(^chk)
}
func generateUUID() [16]byte {
res := [16]byte{}
if _, err := rand.Read(res[:]); err != nil {
panic(err)
}
return res
}

177
vendor/github.com/Microsoft/hcsshim/hcn/hcn.go generated vendored Normal file
View File

@ -0,0 +1,177 @@
// Package hcn is a shim for the Host Compute Networking (HCN) service, which manages networking for Windows Server
// containers and Hyper-V containers. Previous to RS5, HCN was referred to as Host Networking Service (HNS).
package hcn
import (
"encoding/json"
"fmt"
"syscall"
"github.com/Microsoft/hcsshim/internal/guid"
)
//go:generate go run ../mksyscall_windows.go -output zsyscall_windows.go hcn.go
/// HNS V1 API
//sys SetCurrentThreadCompartmentId(compartmentId uint32) (hr error) = iphlpapi.SetCurrentThreadCompartmentId
//sys _hnsCall(method string, path string, object string, response **uint16) (hr error) = vmcompute.HNSCall?
/// HCN V2 API
// Network
//sys hcnEnumerateNetworks(query string, networks **uint16, result **uint16) (hr error) = computenetwork.HcnEnumerateNetworks?
//sys hcnCreateNetwork(id *_guid, settings string, network *hcnNetwork, result **uint16) (hr error) = computenetwork.HcnCreateNetwork?
//sys hcnOpenNetwork(id *_guid, network *hcnNetwork, result **uint16) (hr error) = computenetwork.HcnOpenNetwork?
//sys hcnModifyNetwork(network hcnNetwork, settings string, result **uint16) (hr error) = computenetwork.HcnModifyNetwork?
//sys hcnQueryNetworkProperties(network hcnNetwork, query string, properties **uint16, result **uint16) (hr error) = computenetwork.HcnQueryNetworkProperties?
//sys hcnDeleteNetwork(id *_guid, result **uint16) (hr error) = computenetwork.HcnDeleteNetwork?
//sys hcnCloseNetwork(network hcnNetwork) (hr error) = computenetwork.HcnCloseNetwork?
// Endpoint
//sys hcnEnumerateEndpoints(query string, endpoints **uint16, result **uint16) (hr error) = computenetwork.HcnEnumerateEndpoints?
//sys hcnCreateEndpoint(network hcnNetwork, id *_guid, settings string, endpoint *hcnEndpoint, result **uint16) (hr error) = computenetwork.HcnCreateEndpoint?
//sys hcnOpenEndpoint(id *_guid, endpoint *hcnEndpoint, result **uint16) (hr error) = computenetwork.HcnOpenEndpoint?
//sys hcnModifyEndpoint(endpoint hcnEndpoint, settings string, result **uint16) (hr error) = computenetwork.HcnModifyEndpoint?
//sys hcnQueryEndpointProperties(endpoint hcnEndpoint, query string, properties **uint16, result **uint16) (hr error) = computenetwork.HcnQueryEndpointProperties?
//sys hcnDeleteEndpoint(id *_guid, result **uint16) (hr error) = computenetwork.HcnDeleteEndpoint?
//sys hcnCloseEndpoint(endpoint hcnEndpoint) (hr error) = computenetwork.HcnCloseEndpoint?
// Namespace
//sys hcnEnumerateNamespaces(query string, namespaces **uint16, result **uint16) (hr error) = computenetwork.HcnEnumerateNamespaces?
//sys hcnCreateNamespace(id *_guid, settings string, namespace *hcnNamespace, result **uint16) (hr error) = computenetwork.HcnCreateNamespace?
//sys hcnOpenNamespace(id *_guid, namespace *hcnNamespace, result **uint16) (hr error) = computenetwork.HcnOpenNamespace?
//sys hcnModifyNamespace(namespace hcnNamespace, settings string, result **uint16) (hr error) = computenetwork.HcnModifyNamespace?
//sys hcnQueryNamespaceProperties(namespace hcnNamespace, query string, properties **uint16, result **uint16) (hr error) = computenetwork.HcnQueryNamespaceProperties?
//sys hcnDeleteNamespace(id *_guid, result **uint16) (hr error) = computenetwork.HcnDeleteNamespace?
//sys hcnCloseNamespace(namespace hcnNamespace) (hr error) = computenetwork.HcnCloseNamespace?
// LoadBalancer
//sys hcnEnumerateLoadBalancers(query string, loadBalancers **uint16, result **uint16) (hr error) = computenetwork.HcnEnumerateLoadBalancers?
//sys hcnCreateLoadBalancer(id *_guid, settings string, loadBalancer *hcnLoadBalancer, result **uint16) (hr error) = computenetwork.HcnCreateLoadBalancer?
//sys hcnOpenLoadBalancer(id *_guid, loadBalancer *hcnLoadBalancer, result **uint16) (hr error) = computenetwork.HcnOpenLoadBalancer?
//sys hcnModifyLoadBalancer(loadBalancer hcnLoadBalancer, settings string, result **uint16) (hr error) = computenetwork.HcnModifyLoadBalancer?
//sys hcnQueryLoadBalancerProperties(loadBalancer hcnLoadBalancer, query string, properties **uint16, result **uint16) (hr error) = computenetwork.HcnQueryLoadBalancerProperties?
//sys hcnDeleteLoadBalancer(id *_guid, result **uint16) (hr error) = computenetwork.HcnDeleteLoadBalancer?
//sys hcnCloseLoadBalancer(loadBalancer hcnLoadBalancer) (hr error) = computenetwork.HcnCloseLoadBalancer?
// Service
//sys hcnOpenService(service *hcnService, result **uint16) (hr error) = computenetwork.HcnOpenService?
//sys hcnRegisterServiceCallback(service hcnService, callback int32, context int32, callbackHandle *hcnCallbackHandle) (hr error) = computenetwork.HcnRegisterServiceCallback?
//sys hcnUnregisterServiceCallback(callbackHandle hcnCallbackHandle) (hr error) = computenetwork.HcnUnregisterServiceCallback?
//sys hcnCloseService(service hcnService) (hr error) = computenetwork.HcnCloseService?
type _guid = guid.GUID
type hcnNetwork syscall.Handle
type hcnEndpoint syscall.Handle
type hcnNamespace syscall.Handle
type hcnLoadBalancer syscall.Handle
type hcnService syscall.Handle
type hcnCallbackHandle syscall.Handle
// SchemaVersion for HCN Objects/Queries.
type SchemaVersion = Version // hcnglobals.go
// HostComputeQueryFlags are passed in to a HostComputeQuery to determine which
// properties of an object are returned.
type HostComputeQueryFlags uint32
var (
// HostComputeQueryFlagsNone returns an object with the standard properties.
HostComputeQueryFlagsNone HostComputeQueryFlags
// HostComputeQueryFlagsDetailed returns an object with all properties.
HostComputeQueryFlagsDetailed HostComputeQueryFlags = 1
)
// HostComputeQuery is the format for HCN queries.
type HostComputeQuery struct {
SchemaVersion SchemaVersion `json:""`
Flags HostComputeQueryFlags `json:",omitempty"`
Filter string `json:",omitempty"`
}
// defaultQuery generates HCN Query.
// Passed into get/enumerate calls to filter results.
func defaultQuery() HostComputeQuery {
query := HostComputeQuery{
SchemaVersion: SchemaVersion{
Major: 2,
Minor: 0,
},
Flags: HostComputeQueryFlagsNone,
}
return query
}
func defaultQueryJson() string {
query := defaultQuery()
queryJson, err := json.Marshal(query)
if err != nil {
return ""
}
return string(queryJson)
}
// PlatformDoesNotSupportError happens when users are attempting to use a newer shim on an older OS
func platformDoesNotSupportError(featureName string) error {
return fmt.Errorf("Platform does not support feature %s", featureName)
}
// V2ApiSupported returns an error if the HCN version does not support the V2 Apis.
func V2ApiSupported() error {
supported := GetSupportedFeatures()
if supported.Api.V2 {
return nil
}
return platformDoesNotSupportError("V2 Api/Schema")
}
func V2SchemaVersion() SchemaVersion {
return SchemaVersion{
Major: 2,
Minor: 0,
}
}
// RemoteSubnetSupported returns an error if the HCN version does not support Remote Subnet policies.
func RemoteSubnetSupported() error {
supported := GetSupportedFeatures()
if supported.RemoteSubnet {
return nil
}
return platformDoesNotSupportError("Remote Subnet")
}
// HostRouteSupported returns an error if the HCN version does not support Host Route policies.
func HostRouteSupported() error {
supported := GetSupportedFeatures()
if supported.HostRoute {
return nil
}
return platformDoesNotSupportError("Host Route")
}
// DSRSupported returns an error if the HCN version does not support Direct Server Return.
func DSRSupported() error {
supported := GetSupportedFeatures()
if supported.DSR {
return nil
}
return platformDoesNotSupportError("Direct Server Return (DSR)")
}
// RequestType are the different operations performed to settings.
// Used to update the settings of Endpoint/Namespace objects.
type RequestType string
var (
// RequestTypeAdd adds the provided settings object.
RequestTypeAdd RequestType = "Add"
// RequestTypeRemove removes the provided settings object.
RequestTypeRemove RequestType = "Remove"
// RequestTypeUpdate replaces settings with the ones provided.
RequestTypeUpdate RequestType = "Update"
// RequestTypeRefresh refreshes the settings provided.
RequestTypeRefresh RequestType = "Refresh"
)

366
vendor/github.com/Microsoft/hcsshim/hcn/hcnendpoint.go generated vendored Normal file
View File

@ -0,0 +1,366 @@
package hcn
import (
"encoding/json"
"github.com/Microsoft/hcsshim/internal/guid"
"github.com/Microsoft/hcsshim/internal/interop"
"github.com/sirupsen/logrus"
)
// IpConfig is assoicated with an endpoint
type IpConfig struct {
IpAddress string `json:",omitempty"`
PrefixLength uint8 `json:",omitempty"`
}
// EndpointFlags are special settings on an endpoint.
type EndpointFlags uint32
var (
// EndpointFlagsNone is the default.
EndpointFlagsNone EndpointFlags
// EndpointFlagsRemoteEndpoint means that an endpoint is on another host.
EndpointFlagsRemoteEndpoint EndpointFlags = 1
)
// HostComputeEndpoint represents a network endpoint
type HostComputeEndpoint struct {
Id string `json:"ID,omitempty"`
Name string `json:",omitempty"`
HostComputeNetwork string `json:",omitempty"` // GUID
HostComputeNamespace string `json:",omitempty"` // GUID
Policies []EndpointPolicy `json:",omitempty"`
IpConfigurations []IpConfig `json:",omitempty"`
Dns Dns `json:",omitempty"`
Routes []Route `json:",omitempty"`
MacAddress string `json:",omitempty"`
Flags EndpointFlags `json:",omitempty"`
SchemaVersion SchemaVersion `json:",omitempty"`
}
// EndpointResourceType are the two different Endpoint settings resources.
type EndpointResourceType string
var (
// EndpointResourceTypePolicy is for Endpoint Policies. Ex: ACL, NAT
EndpointResourceTypePolicy EndpointResourceType = "Policy"
// EndpointResourceTypePort is for Endpoint Port settings.
EndpointResourceTypePort EndpointResourceType = "Port"
)
// ModifyEndpointSettingRequest is the structure used to send request to modify an endpoint.
// Used to update policy/port on an endpoint.
type ModifyEndpointSettingRequest struct {
ResourceType EndpointResourceType `json:",omitempty"` // Policy, Port
RequestType RequestType `json:",omitempty"` // Add, Remove, Update, Refresh
Settings json.RawMessage `json:",omitempty"`
}
type PolicyEndpointRequest struct {
Policies []EndpointPolicy `json:",omitempty"`
}
func getEndpoint(endpointGuid guid.GUID, query string) (*HostComputeEndpoint, error) {
// Open endpoint.
var (
endpointHandle hcnEndpoint
resultBuffer *uint16
propertiesBuffer *uint16
)
hr := hcnOpenEndpoint(&endpointGuid, &endpointHandle, &resultBuffer)
if err := checkForErrors("hcnOpenEndpoint", hr, resultBuffer); err != nil {
return nil, err
}
// Query endpoint.
hr = hcnQueryEndpointProperties(endpointHandle, query, &propertiesBuffer, &resultBuffer)
if err := checkForErrors("hcnQueryEndpointProperties", hr, resultBuffer); err != nil {
return nil, err
}
properties := interop.ConvertAndFreeCoTaskMemString(propertiesBuffer)
// Close endpoint.
hr = hcnCloseEndpoint(endpointHandle)
if err := checkForErrors("hcnCloseEndpoint", hr, nil); err != nil {
return nil, err
}
// Convert output to HostComputeEndpoint
var outputEndpoint HostComputeEndpoint
if err := json.Unmarshal([]byte(properties), &outputEndpoint); err != nil {
return nil, err
}
return &outputEndpoint, nil
}
func enumerateEndpoints(query string) ([]HostComputeEndpoint, error) {
// Enumerate all Endpoint Guids
var (
resultBuffer *uint16
endpointBuffer *uint16
)
hr := hcnEnumerateEndpoints(query, &endpointBuffer, &resultBuffer)
if err := checkForErrors("hcnEnumerateEndpoints", hr, resultBuffer); err != nil {
return nil, err
}
endpoints := interop.ConvertAndFreeCoTaskMemString(endpointBuffer)
var endpointIds []guid.GUID
err := json.Unmarshal([]byte(endpoints), &endpointIds)
if err != nil {
return nil, err
}
var outputEndpoints []HostComputeEndpoint
for _, endpointGuid := range endpointIds {
endpoint, err := getEndpoint(endpointGuid, query)
if err != nil {
return nil, err
}
outputEndpoints = append(outputEndpoints, *endpoint)
}
return outputEndpoints, nil
}
func createEndpoint(networkId string, endpointSettings string) (*HostComputeEndpoint, error) {
networkGuid := guid.FromString(networkId)
// Open network.
var networkHandle hcnNetwork
var resultBuffer *uint16
hr := hcnOpenNetwork(&networkGuid, &networkHandle, &resultBuffer)
if err := checkForErrors("hcnOpenNetwork", hr, resultBuffer); err != nil {
return nil, err
}
// Create endpoint.
endpointId := guid.GUID{}
var endpointHandle hcnEndpoint
hr = hcnCreateEndpoint(networkHandle, &endpointId, endpointSettings, &endpointHandle, &resultBuffer)
if err := checkForErrors("hcnCreateEndpoint", hr, resultBuffer); err != nil {
return nil, err
}
// Query endpoint.
hcnQuery := defaultQuery()
query, err := json.Marshal(hcnQuery)
if err != nil {
return nil, err
}
var propertiesBuffer *uint16
hr = hcnQueryEndpointProperties(endpointHandle, string(query), &propertiesBuffer, &resultBuffer)
if err := checkForErrors("hcnQueryEndpointProperties", hr, resultBuffer); err != nil {
return nil, err
}
properties := interop.ConvertAndFreeCoTaskMemString(propertiesBuffer)
// Close endpoint.
hr = hcnCloseEndpoint(endpointHandle)
if err := checkForErrors("hcnCloseEndpoint", hr, nil); err != nil {
return nil, err
}
// Close network.
hr = hcnCloseNetwork(networkHandle)
if err := checkForErrors("hcnCloseNetwork", hr, nil); err != nil {
return nil, err
}
// Convert output to HostComputeEndpoint
var outputEndpoint HostComputeEndpoint
if err := json.Unmarshal([]byte(properties), &outputEndpoint); err != nil {
return nil, err
}
return &outputEndpoint, nil
}
func modifyEndpoint(endpointId string, settings string) (*HostComputeEndpoint, error) {
endpointGuid := guid.FromString(endpointId)
// Open endpoint
var (
endpointHandle hcnEndpoint
resultBuffer *uint16
propertiesBuffer *uint16
)
hr := hcnOpenEndpoint(&endpointGuid, &endpointHandle, &resultBuffer)
if err := checkForErrors("hcnOpenEndpoint", hr, resultBuffer); err != nil {
return nil, err
}
// Modify endpoint
hr = hcnModifyEndpoint(endpointHandle, settings, &resultBuffer)
if err := checkForErrors("hcnModifyEndpoint", hr, resultBuffer); err != nil {
return nil, err
}
// Query endpoint.
hcnQuery := defaultQuery()
query, err := json.Marshal(hcnQuery)
if err != nil {
return nil, err
}
hr = hcnQueryEndpointProperties(endpointHandle, string(query), &propertiesBuffer, &resultBuffer)
if err := checkForErrors("hcnQueryEndpointProperties", hr, resultBuffer); err != nil {
return nil, err
}
properties := interop.ConvertAndFreeCoTaskMemString(propertiesBuffer)
// Close endpoint.
hr = hcnCloseEndpoint(endpointHandle)
if err := checkForErrors("hcnCloseEndpoint", hr, nil); err != nil {
return nil, err
}
// Convert output to HostComputeEndpoint
var outputEndpoint HostComputeEndpoint
if err := json.Unmarshal([]byte(properties), &outputEndpoint); err != nil {
return nil, err
}
return &outputEndpoint, nil
}
func deleteEndpoint(endpointId string) error {
endpointGuid := guid.FromString(endpointId)
var resultBuffer *uint16
hr := hcnDeleteEndpoint(&endpointGuid, &resultBuffer)
if err := checkForErrors("hcnDeleteEndpoint", hr, resultBuffer); err != nil {
return err
}
return nil
}
// ListEndpoints makes a call to list all available endpoints.
func ListEndpoints() ([]HostComputeEndpoint, error) {
hcnQuery := defaultQuery()
endpoints, err := ListEndpointsQuery(hcnQuery)
if err != nil {
return nil, err
}
return endpoints, nil
}
// ListEndpointsQuery makes a call to query the list of available endpoints.
func ListEndpointsQuery(query HostComputeQuery) ([]HostComputeEndpoint, error) {
queryJson, err := json.Marshal(query)
if err != nil {
return nil, err
}
endpoints, err := enumerateEndpoints(string(queryJson))
if err != nil {
return nil, err
}
return endpoints, nil
}
// ListEndpointsOfNetwork queries the list of endpoints on a network.
func ListEndpointsOfNetwork(networkId string) ([]HostComputeEndpoint, error) {
hcnQuery := defaultQuery()
// TODO: Once query can convert schema, change to {HostComputeNetwork:networkId}
mapA := map[string]string{"VirtualNetwork": networkId}
filter, err := json.Marshal(mapA)
if err != nil {
return nil, err
}
hcnQuery.Filter = string(filter)
return ListEndpointsQuery(hcnQuery)
}
// GetEndpointByID returns an endpoint specified by Id
func GetEndpointByID(endpointId string) (*HostComputeEndpoint, error) {
hcnQuery := defaultQuery()
mapA := map[string]string{"ID": endpointId}
filter, err := json.Marshal(mapA)
if err != nil {
return nil, err
}
hcnQuery.Filter = string(filter)
endpoints, err := ListEndpointsQuery(hcnQuery)
if err != nil {
return nil, err
}
if len(endpoints) == 0 {
return nil, EndpointNotFoundError{EndpointID: endpointId}
}
return &endpoints[0], err
}
// GetEndpointByName returns an endpoint specified by Name
func GetEndpointByName(endpointName string) (*HostComputeEndpoint, error) {
hcnQuery := defaultQuery()
mapA := map[string]string{"Name": endpointName}
filter, err := json.Marshal(mapA)
if err != nil {
return nil, err
}
hcnQuery.Filter = string(filter)
endpoints, err := ListEndpointsQuery(hcnQuery)
if err != nil {
return nil, err
}
if len(endpoints) == 0 {
return nil, EndpointNotFoundError{EndpointName: endpointName}
}
return &endpoints[0], err
}
// Create Endpoint.
func (endpoint *HostComputeEndpoint) Create() (*HostComputeEndpoint, error) {
logrus.Debugf("hcn::HostComputeEndpoint::Create id=%s", endpoint.Id)
jsonString, err := json.Marshal(endpoint)
if err != nil {
return nil, err
}
logrus.Debugf("hcn::HostComputeEndpoint::Create JSON: %s", jsonString)
endpoint, hcnErr := createEndpoint(endpoint.HostComputeNetwork, string(jsonString))
if hcnErr != nil {
return nil, hcnErr
}
return endpoint, nil
}
// Delete Endpoint.
func (endpoint *HostComputeEndpoint) Delete() error {
logrus.Debugf("hcn::HostComputeEndpoint::Delete id=%s", endpoint.Id)
if err := deleteEndpoint(endpoint.Id); err != nil {
return err
}
return nil
}
// ModifyEndpointSettings updates the Port/Policy of an Endpoint.
func ModifyEndpointSettings(endpointId string, request *ModifyEndpointSettingRequest) error {
logrus.Debugf("hcn::HostComputeEndpoint::ModifyEndpointSettings id=%s", endpointId)
endpointSettingsRequest, err := json.Marshal(request)
if err != nil {
return err
}
_, err = modifyEndpoint(endpointId, string(endpointSettingsRequest))
if err != nil {
return err
}
return nil
}
// ApplyPolicy applies a Policy (ex: ACL) on the Endpoint.
func (endpoint *HostComputeEndpoint) ApplyPolicy(endpointPolicy PolicyEndpointRequest) error {
logrus.Debugf("hcn::HostComputeEndpoint::ApplyPolicy id=%s", endpoint.Id)
settingsJson, err := json.Marshal(endpointPolicy)
if err != nil {
return err
}
requestMessage := &ModifyEndpointSettingRequest{
ResourceType: EndpointResourceTypePolicy,
RequestType: RequestTypeUpdate,
Settings: settingsJson,
}
return ModifyEndpointSettings(endpoint.Id, requestMessage)
}
// NamespaceAttach modifies a Namespace to add an endpoint.
func (endpoint *HostComputeEndpoint) NamespaceAttach(namespaceId string) error {
return AddNamespaceEndpoint(namespaceId, endpoint.Id)
}
// NamespaceDetach modifies a Namespace to remove an endpoint.
func (endpoint *HostComputeEndpoint) NamespaceDetach(namespaceId string) error {
return RemoveNamespaceEndpoint(namespaceId, endpoint.Id)
}

View File

@ -0,0 +1,298 @@
// +build integration
package hcn
import (
"encoding/json"
"fmt"
"testing"
)
func TestCreateDeleteEndpoint(t *testing.T) {
network, err := HcnCreateTestNATNetwork()
if err != nil {
t.Fatal(err)
}
Endpoint, err := HcnCreateTestEndpoint(network)
if err != nil {
t.Fatal(err)
}
jsonString, err := json.Marshal(Endpoint)
if err != nil {
t.Fatal(err)
}
fmt.Printf("Endpoint JSON:\n%s \n", jsonString)
err = Endpoint.Delete()
if err != nil {
t.Fatal(err)
}
err = network.Delete()
if err != nil {
t.Fatal(err)
}
}
func TestGetEndpointById(t *testing.T) {
network, err := HcnCreateTestNATNetwork()
if err != nil {
t.Fatal(err)
}
Endpoint, err := HcnCreateTestEndpoint(network)
if err != nil {
t.Fatal(err)
}
foundEndpoint, err := GetEndpointByID(Endpoint.Id)
if err != nil {
t.Fatal(err)
}
if foundEndpoint == nil {
t.Fatal("No Endpoint found")
}
err = foundEndpoint.Delete()
if err != nil {
t.Fatal(err)
}
err = network.Delete()
if err != nil {
t.Fatal(err)
}
}
func TestGetEndpointByName(t *testing.T) {
network, err := HcnCreateTestNATNetwork()
if err != nil {
t.Fatal(err)
}
Endpoint, err := HcnCreateTestEndpoint(network)
if err != nil {
t.Fatal(err)
}
foundEndpoint, err := GetEndpointByName(Endpoint.Name)
if err != nil {
t.Fatal(err)
}
if foundEndpoint == nil {
t.Fatal("No Endpoint found")
}
err = foundEndpoint.Delete()
if err != nil {
t.Fatal(err)
}
err = network.Delete()
if err != nil {
t.Fatal(err)
}
}
func TestListEndpoints(t *testing.T) {
network, err := HcnCreateTestNATNetwork()
if err != nil {
t.Fatal(err)
}
Endpoint, err := HcnCreateTestEndpoint(network)
if err != nil {
t.Fatal(err)
}
foundEndpoints, err := ListEndpoints()
if err != nil {
t.Fatal(err)
}
if len(foundEndpoints) == 0 {
t.Fatal("No Endpoint found")
}
err = Endpoint.Delete()
if err != nil {
t.Fatal(err)
}
err = network.Delete()
if err != nil {
t.Fatal(err)
}
}
func TestListEndpointsOfNetwork(t *testing.T) {
network, err := HcnCreateTestNATNetwork()
if err != nil {
t.Fatal(err)
}
Endpoint, err := HcnCreateTestEndpoint(network)
if err != nil {
t.Fatal(err)
}
foundEndpoints, err := ListEndpointsOfNetwork(network.Id)
if err != nil {
t.Fatal(err)
}
if len(foundEndpoints) == 0 {
t.Fatal("No Endpoint found")
}
err = Endpoint.Delete()
if err != nil {
t.Fatal(err)
}
err = network.Delete()
if err != nil {
t.Fatal(err)
}
}
func TestEndpointNamespaceAttachDetach(t *testing.T) {
network, err := HcnCreateTestNATNetwork()
if err != nil {
t.Fatal(err)
}
endpoint, err := HcnCreateTestEndpoint(network)
if err != nil {
t.Fatal(err)
}
namespace, err := HcnCreateTestNamespace()
if err != nil {
t.Fatal(err)
}
err = endpoint.NamespaceAttach(namespace.Id)
if err != nil {
t.Fatal(err)
}
err = endpoint.NamespaceDetach(namespace.Id)
if err != nil {
t.Fatal(err)
}
err = namespace.Delete()
if err != nil {
t.Fatal(err)
}
err = endpoint.Delete()
if err != nil {
t.Fatal(err)
}
err = network.Delete()
if err != nil {
t.Fatal(err)
}
}
func TestCreateEndpointWithNamespace(t *testing.T) {
network, err := HcnCreateTestNATNetwork()
if err != nil {
t.Fatal(err)
}
namespace, err := HcnCreateTestNamespace()
if err != nil {
t.Fatal(err)
}
Endpoint, err := HcnCreateTestEndpointWithNamespace(network, namespace)
if err != nil {
t.Fatal(err)
}
if Endpoint.HostComputeNamespace == "" {
t.Fatal("No Namespace detected.")
}
err = Endpoint.Delete()
if err != nil {
t.Fatal(err)
}
err = network.Delete()
if err != nil {
t.Fatal(err)
}
}
func TestApplyPolicyOnEndpoint(t *testing.T) {
network, err := HcnCreateTestNATNetwork()
if err != nil {
t.Fatal(err)
}
Endpoint, err := HcnCreateTestEndpoint(network)
if err != nil {
t.Fatal(err)
}
endpointPolicyList, err := HcnCreateAcls()
if err != nil {
t.Fatal(err)
}
jsonString, err := json.Marshal(*endpointPolicyList)
if err != nil {
t.Fatal(err)
}
fmt.Printf("ACLS JSON:\n%s \n", jsonString)
err = Endpoint.ApplyPolicy(*endpointPolicyList)
if err != nil {
t.Fatal(err)
}
foundEndpoint, err := GetEndpointByName(Endpoint.Name)
if err != nil {
t.Fatal(err)
}
if len(foundEndpoint.Policies) == 0 {
t.Fatal("No Endpoint Policies found")
}
err = Endpoint.Delete()
if err != nil {
t.Fatal(err)
}
err = network.Delete()
if err != nil {
t.Fatal(err)
}
}
func TestModifyEndpointSettings(t *testing.T) {
network, err := HcnCreateTestNATNetwork()
if err != nil {
t.Fatal(err)
}
endpoint, err := HcnCreateTestEndpoint(network)
if err != nil {
t.Fatal(err)
}
endpointPolicy, err := HcnCreateAcls()
if err != nil {
t.Fatal(err)
}
settingsJson, err := json.Marshal(endpointPolicy)
if err != nil {
t.Fatal(err)
}
requestMessage := &ModifyEndpointSettingRequest{
ResourceType: EndpointResourceTypePolicy,
RequestType: RequestTypeUpdate,
Settings: settingsJson,
}
err = ModifyEndpointSettings(endpoint.Id, requestMessage)
if err != nil {
t.Fatal(err)
}
foundEndpoint, err := GetEndpointByName(endpoint.Name)
if err != nil {
t.Fatal(err)
}
if len(foundEndpoint.Policies) == 0 {
t.Fatal("No Endpoint Policies found")
}
err = endpoint.Delete()
if err != nil {
t.Fatal(err)
}
err = network.Delete()
if err != nil {
t.Fatal(err)
}
}

95
vendor/github.com/Microsoft/hcsshim/hcn/hcnerrors.go generated vendored Normal file
View File

@ -0,0 +1,95 @@
// Package hcn is a shim for the Host Compute Networking (HCN) service, which manages networking for Windows Server
// containers and Hyper-V containers. Previous to RS5, HCN was referred to as Host Networking Service (HNS).
package hcn
import (
"fmt"
"github.com/Microsoft/hcsshim/internal/hcserror"
"github.com/Microsoft/hcsshim/internal/interop"
"github.com/sirupsen/logrus"
)
func checkForErrors(methodName string, hr error, resultBuffer *uint16) error {
errorFound := false
if hr != nil {
errorFound = true
}
result := ""
if resultBuffer != nil {
result = interop.ConvertAndFreeCoTaskMemString(resultBuffer)
if result != "" {
errorFound = true
}
}
if errorFound {
returnError := hcserror.New(hr, methodName, result)
logrus.Debugf(returnError.Error()) // HCN errors logged for debugging.
return returnError
}
return nil
}
// NetworkNotFoundError results from a failed seach for a network by Id or Name
type NetworkNotFoundError struct {
NetworkName string
NetworkID string
}
func (e NetworkNotFoundError) Error() string {
if e.NetworkName == "" {
return fmt.Sprintf("Network Name %s not found", e.NetworkName)
}
return fmt.Sprintf("Network Id %s not found", e.NetworkID)
}
// EndpointNotFoundError results from a failed seach for an endpoint by Id or Name
type EndpointNotFoundError struct {
EndpointName string
EndpointID string
}
func (e EndpointNotFoundError) Error() string {
if e.EndpointName == "" {
return fmt.Sprintf("Endpoint Name %s not found", e.EndpointName)
}
return fmt.Sprintf("Endpoint Id %s not found", e.EndpointID)
}
// NamespaceNotFoundError results from a failed seach for a namsepace by Id
type NamespaceNotFoundError struct {
NamespaceID string
}
func (e NamespaceNotFoundError) Error() string {
return fmt.Sprintf("Namespace %s not found", e.NamespaceID)
}
// LoadBalancerNotFoundError results from a failed seach for a loadbalancer by Id
type LoadBalancerNotFoundError struct {
LoadBalancerId string
}
func (e LoadBalancerNotFoundError) Error() string {
return fmt.Sprintf("LoadBalancer %s not found", e.LoadBalancerId)
}
// IsNotFoundError returns a boolean indicating whether the error was caused by
// a resource not being found.
func IsNotFoundError(err error) bool {
switch err.(type) {
case NetworkNotFoundError:
return true
case EndpointNotFoundError:
return true
case NamespaceNotFoundError:
return true
case LoadBalancerNotFoundError:
return true
}
return false
}

View File

@ -0,0 +1,34 @@
// +build integration
package hcn
import (
"testing"
)
func TestMissingNetworkByName(t *testing.T) {
_, err := GetNetworkByName("Not found name")
if err == nil {
t.Fatal("Error was not thrown.")
}
if !IsNotFoundError(err) {
t.Fatal("Unrelated error was thrown.")
}
if _, ok := err.(NetworkNotFoundError); !ok {
t.Fatal("Wrong error type was thrown.")
}
}
func TestMissingNetworkById(t *testing.T) {
// Random guid
_, err := GetNetworkByID("5f0b1190-63be-4e0c-b974-bd0f55675a42")
if err == nil {
t.Fatal("Unrelated error was thrown.")
}
if !IsNotFoundError(err) {
t.Fatal("Unrelated error was thrown.")
}
if _, ok := err.(NetworkNotFoundError); !ok {
t.Fatal("Wrong error type was thrown.")
}
}

87
vendor/github.com/Microsoft/hcsshim/hcn/hcnglobals.go generated vendored Normal file
View File

@ -0,0 +1,87 @@
package hcn
import (
"encoding/json"
"fmt"
"github.com/Microsoft/hcsshim/internal/hcserror"
"github.com/Microsoft/hcsshim/internal/interop"
"github.com/sirupsen/logrus"
)
// Globals are all global properties of the HCN Service.
type Globals struct {
Version Version `json:"Version"`
}
// Version is the HCN Service version.
type Version struct {
Major int `json:"Major"`
Minor int `json:"Minor"`
}
var (
// HNSVersion1803 added ACL functionality.
HNSVersion1803 = Version{Major: 7, Minor: 2}
// V2ApiSupport allows the use of V2 Api calls and V2 Schema.
V2ApiSupport = Version{Major: 9, Minor: 1}
// Remote Subnet allows for Remote Subnet policies on Overlay networks
RemoteSubnetVersion = Version{Major: 9, Minor: 2}
// A Host Route policy allows for local container to local host communication Overlay networks
HostRouteVersion = Version{Major: 9, Minor: 2}
// HNS 10.2 allows for Direct Server Return for loadbalancing
DSRVersion = Version{Major: 10, Minor: 2}
)
// GetGlobals returns the global properties of the HCN Service.
func GetGlobals() (*Globals, error) {
var version Version
err := hnsCall("GET", "/globals/version", "", &version)
if err != nil {
return nil, err
}
globals := &Globals{
Version: version,
}
return globals, nil
}
type hnsResponse struct {
Success bool
Error string
Output json.RawMessage
}
func hnsCall(method, path, request string, returnResponse interface{}) error {
var responseBuffer *uint16
logrus.Debugf("[%s]=>[%s] Request : %s", method, path, request)
err := _hnsCall(method, path, request, &responseBuffer)
if err != nil {
return hcserror.New(err, "hnsCall ", "")
}
response := interop.ConvertAndFreeCoTaskMemString(responseBuffer)
hnsresponse := &hnsResponse{}
if err = json.Unmarshal([]byte(response), &hnsresponse); err != nil {
return err
}
if !hnsresponse.Success {
return fmt.Errorf("HNS failed with error : %s", hnsresponse.Error)
}
if len(hnsresponse.Output) == 0 {
return nil
}
logrus.Debugf("Network Response : %s", hnsresponse.Output)
err = json.Unmarshal(hnsresponse.Output, returnResponse)
if err != nil {
return err
}
return nil
}

View File

@ -0,0 +1,321 @@
package hcn
import (
"encoding/json"
"github.com/Microsoft/hcsshim/internal/guid"
"github.com/Microsoft/hcsshim/internal/interop"
"github.com/sirupsen/logrus"
)
// LoadBalancerPortMapping is associated with HostComputeLoadBalancer
type LoadBalancerPortMapping struct {
Protocol uint32 `json:",omitempty"` // EX: TCP = 6, UDP = 17
InternalPort uint16 `json:",omitempty"`
ExternalPort uint16 `json:",omitempty"`
Flags uint32 `json:",omitempty"` // 0: None, 1: EnableILB, 2: LocalRoutedVip
}
// HostComputeLoadBalancer represents software load balancer.
type HostComputeLoadBalancer struct {
Id string `json:"ID,omitempty"`
HostComputeEndpoints []string `json:",omitempty"`
SourceVIP string `json:",omitempty"`
FrontendVIPs []string `json:",omitempty"`
PortMappings []LoadBalancerPortMapping `json:",omitempty"`
SchemaVersion SchemaVersion `json:",omitempty"`
Flags uint32 `json:",omitempty"` // 0: None, 1: EnableDirectServerReturn
}
func getLoadBalancer(loadBalancerGuid guid.GUID, query string) (*HostComputeLoadBalancer, error) {
// Open loadBalancer.
var (
loadBalancerHandle hcnLoadBalancer
resultBuffer *uint16
propertiesBuffer *uint16
)
hr := hcnOpenLoadBalancer(&loadBalancerGuid, &loadBalancerHandle, &resultBuffer)
if err := checkForErrors("hcnOpenLoadBalancer", hr, resultBuffer); err != nil {
return nil, err
}
// Query loadBalancer.
hr = hcnQueryLoadBalancerProperties(loadBalancerHandle, query, &propertiesBuffer, &resultBuffer)
if err := checkForErrors("hcnQueryLoadBalancerProperties", hr, resultBuffer); err != nil {
return nil, err
}
properties := interop.ConvertAndFreeCoTaskMemString(propertiesBuffer)
// Close loadBalancer.
hr = hcnCloseLoadBalancer(loadBalancerHandle)
if err := checkForErrors("hcnCloseLoadBalancer", hr, nil); err != nil {
return nil, err
}
// Convert output to HostComputeLoadBalancer
var outputLoadBalancer HostComputeLoadBalancer
if err := json.Unmarshal([]byte(properties), &outputLoadBalancer); err != nil {
return nil, err
}
return &outputLoadBalancer, nil
}
func enumerateLoadBalancers(query string) ([]HostComputeLoadBalancer, error) {
// Enumerate all LoadBalancer Guids
var (
resultBuffer *uint16
loadBalancerBuffer *uint16
)
hr := hcnEnumerateLoadBalancers(query, &loadBalancerBuffer, &resultBuffer)
if err := checkForErrors("hcnEnumerateLoadBalancers", hr, resultBuffer); err != nil {
return nil, err
}
loadBalancers := interop.ConvertAndFreeCoTaskMemString(loadBalancerBuffer)
var loadBalancerIds []guid.GUID
if err := json.Unmarshal([]byte(loadBalancers), &loadBalancerIds); err != nil {
return nil, err
}
var outputLoadBalancers []HostComputeLoadBalancer
for _, loadBalancerGuid := range loadBalancerIds {
loadBalancer, err := getLoadBalancer(loadBalancerGuid, query)
if err != nil {
return nil, err
}
outputLoadBalancers = append(outputLoadBalancers, *loadBalancer)
}
return outputLoadBalancers, nil
}
func createLoadBalancer(settings string) (*HostComputeLoadBalancer, error) {
// Create new loadBalancer.
var (
loadBalancerHandle hcnLoadBalancer
resultBuffer *uint16
propertiesBuffer *uint16
)
loadBalancerGuid := guid.GUID{}
hr := hcnCreateLoadBalancer(&loadBalancerGuid, settings, &loadBalancerHandle, &resultBuffer)
if err := checkForErrors("hcnCreateLoadBalancer", hr, resultBuffer); err != nil {
return nil, err
}
// Query loadBalancer.
hcnQuery := defaultQuery()
query, err := json.Marshal(hcnQuery)
if err != nil {
return nil, err
}
hr = hcnQueryLoadBalancerProperties(loadBalancerHandle, string(query), &propertiesBuffer, &resultBuffer)
if err := checkForErrors("hcnQueryLoadBalancerProperties", hr, resultBuffer); err != nil {
return nil, err
}
properties := interop.ConvertAndFreeCoTaskMemString(propertiesBuffer)
// Close loadBalancer.
hr = hcnCloseLoadBalancer(loadBalancerHandle)
if err := checkForErrors("hcnCloseLoadBalancer", hr, nil); err != nil {
return nil, err
}
// Convert output to HostComputeLoadBalancer
var outputLoadBalancer HostComputeLoadBalancer
if err := json.Unmarshal([]byte(properties), &outputLoadBalancer); err != nil {
return nil, err
}
return &outputLoadBalancer, nil
}
func modifyLoadBalancer(loadBalancerId string, settings string) (*HostComputeLoadBalancer, error) {
loadBalancerGuid := guid.FromString(loadBalancerId)
// Open loadBalancer.
var (
loadBalancerHandle hcnLoadBalancer
resultBuffer *uint16
propertiesBuffer *uint16
)
hr := hcnOpenLoadBalancer(&loadBalancerGuid, &loadBalancerHandle, &resultBuffer)
if err := checkForErrors("hcnOpenLoadBalancer", hr, resultBuffer); err != nil {
return nil, err
}
// Modify loadBalancer.
hr = hcnModifyLoadBalancer(loadBalancerHandle, settings, &resultBuffer)
if err := checkForErrors("hcnModifyLoadBalancer", hr, resultBuffer); err != nil {
return nil, err
}
// Query loadBalancer.
hcnQuery := defaultQuery()
query, err := json.Marshal(hcnQuery)
if err != nil {
return nil, err
}
hr = hcnQueryLoadBalancerProperties(loadBalancerHandle, string(query), &propertiesBuffer, &resultBuffer)
if err := checkForErrors("hcnQueryLoadBalancerProperties", hr, resultBuffer); err != nil {
return nil, err
}
properties := interop.ConvertAndFreeCoTaskMemString(propertiesBuffer)
// Close loadBalancer.
hr = hcnCloseLoadBalancer(loadBalancerHandle)
if err := checkForErrors("hcnCloseLoadBalancer", hr, nil); err != nil {
return nil, err
}
// Convert output to LoadBalancer
var outputLoadBalancer HostComputeLoadBalancer
if err := json.Unmarshal([]byte(properties), &outputLoadBalancer); err != nil {
return nil, err
}
return &outputLoadBalancer, nil
}
func deleteLoadBalancer(loadBalancerId string) error {
loadBalancerGuid := guid.FromString(loadBalancerId)
var resultBuffer *uint16
hr := hcnDeleteLoadBalancer(&loadBalancerGuid, &resultBuffer)
if err := checkForErrors("hcnDeleteLoadBalancer", hr, resultBuffer); err != nil {
return err
}
return nil
}
// ListLoadBalancers makes a call to list all available loadBalancers.
func ListLoadBalancers() ([]HostComputeLoadBalancer, error) {
hcnQuery := defaultQuery()
loadBalancers, err := ListLoadBalancersQuery(hcnQuery)
if err != nil {
return nil, err
}
return loadBalancers, nil
}
// ListLoadBalancersQuery makes a call to query the list of available loadBalancers.
func ListLoadBalancersQuery(query HostComputeQuery) ([]HostComputeLoadBalancer, error) {
queryJson, err := json.Marshal(query)
if err != nil {
return nil, err
}
loadBalancers, err := enumerateLoadBalancers(string(queryJson))
if err != nil {
return nil, err
}
return loadBalancers, nil
}
// GetLoadBalancerByID returns the LoadBalancer specified by Id.
func GetLoadBalancerByID(loadBalancerId string) (*HostComputeLoadBalancer, error) {
hcnQuery := defaultQuery()
mapA := map[string]string{"ID": loadBalancerId}
filter, err := json.Marshal(mapA)
if err != nil {
return nil, err
}
hcnQuery.Filter = string(filter)
loadBalancers, err := ListLoadBalancersQuery(hcnQuery)
if err != nil {
return nil, err
}
if len(loadBalancers) == 0 {
return nil, LoadBalancerNotFoundError{LoadBalancerId: loadBalancerId}
}
return &loadBalancers[0], err
}
// Create LoadBalancer.
func (loadBalancer *HostComputeLoadBalancer) Create() (*HostComputeLoadBalancer, error) {
logrus.Debugf("hcn::HostComputeLoadBalancer::Create id=%s", loadBalancer.Id)
jsonString, err := json.Marshal(loadBalancer)
if err != nil {
return nil, err
}
logrus.Debugf("hcn::HostComputeLoadBalancer::Create JSON: %s", jsonString)
loadBalancer, hcnErr := createLoadBalancer(string(jsonString))
if hcnErr != nil {
return nil, hcnErr
}
return loadBalancer, nil
}
// Delete LoadBalancer.
func (loadBalancer *HostComputeLoadBalancer) Delete() error {
logrus.Debugf("hcn::HostComputeLoadBalancer::Delete id=%s", loadBalancer.Id)
if err := deleteLoadBalancer(loadBalancer.Id); err != nil {
return err
}
return nil
}
// AddEndpoint add an endpoint to a LoadBalancer
func (loadBalancer *HostComputeLoadBalancer) AddEndpoint(endpoint *HostComputeEndpoint) (*HostComputeLoadBalancer, error) {
logrus.Debugf("hcn::HostComputeLoadBalancer::AddEndpoint loadBalancer=%s endpoint=%s", loadBalancer.Id, endpoint.Id)
err := loadBalancer.Delete()
if err != nil {
return nil, err
}
// Add Endpoint to the Existing List
loadBalancer.HostComputeEndpoints = append(loadBalancer.HostComputeEndpoints, endpoint.Id)
return loadBalancer.Create()
}
// RemoveEndpoint removes an endpoint from a LoadBalancer
func (loadBalancer *HostComputeLoadBalancer) RemoveEndpoint(endpoint *HostComputeEndpoint) (*HostComputeLoadBalancer, error) {
logrus.Debugf("hcn::HostComputeLoadBalancer::RemoveEndpoint loadBalancer=%s endpoint=%s", loadBalancer.Id, endpoint.Id)
err := loadBalancer.Delete()
if err != nil {
return nil, err
}
// Create a list of all the endpoints besides the one being removed
var endpoints []string
for _, endpointReference := range loadBalancer.HostComputeEndpoints {
if endpointReference == endpoint.Id {
continue
}
endpoints = append(endpoints, endpointReference)
}
loadBalancer.HostComputeEndpoints = endpoints
return loadBalancer.Create()
}
// AddLoadBalancer for the specified endpoints
func AddLoadBalancer(endpoints []HostComputeEndpoint, isILB bool, isDSR bool, sourceVIP string, frontendVIPs []string, protocol uint16, internalPort uint16, externalPort uint16) (*HostComputeLoadBalancer, error) {
logrus.Debugf("hcn::HostComputeLoadBalancer::AddLoadBalancer endpointId=%v, isILB=%v, sourceVIP=%s, frontendVIPs=%v, protocol=%v, internalPort=%v, externalPort=%v", endpoints, isILB, sourceVIP, frontendVIPs, protocol, internalPort, externalPort)
var portMappingFlags uint32
portMappingFlags = 0
if isILB {
portMappingFlags = 1
}
var lbFlags uint32
lbFlags = 0
if isDSR {
lbFlags = 1 // EnableDirectServerReturn
}
loadBalancer := &HostComputeLoadBalancer{
SourceVIP: sourceVIP,
PortMappings: []LoadBalancerPortMapping{
{
Protocol: uint32(protocol),
InternalPort: internalPort,
ExternalPort: externalPort,
Flags: portMappingFlags,
},
},
FrontendVIPs: frontendVIPs,
SchemaVersion: SchemaVersion{
Major: 2,
Minor: 0,
},
Flags: lbFlags,
}
for _, endpoint := range endpoints {
loadBalancer.HostComputeEndpoints = append(loadBalancer.HostComputeEndpoints, endpoint.Id)
}
return loadBalancer.Create()
}

View File

@ -0,0 +1,243 @@
// +build integration
package hcn
import (
"encoding/json"
"fmt"
"testing"
)
func TestCreateDeleteLoadBalancer(t *testing.T) {
network, err := HcnCreateTestNATNetwork()
if err != nil {
t.Fatal(err)
}
endpoint, err := HcnCreateTestEndpoint(network)
if err != nil {
t.Fatal(err)
}
loadBalancer, err := HcnCreateTestLoadBalancer(endpoint)
if err != nil {
t.Fatal(err)
}
jsonString, err := json.Marshal(loadBalancer)
if err != nil {
t.Fatal(err)
}
fmt.Printf("LoadBalancer JSON:\n%s \n", jsonString)
err = loadBalancer.Delete()
if err != nil {
t.Fatal(err)
}
err = endpoint.Delete()
if err != nil {
t.Fatal(err)
}
err = network.Delete()
if err != nil {
t.Fatal(err)
}
}
func TestGetLoadBalancerById(t *testing.T) {
network, err := HcnCreateTestNATNetwork()
if err != nil {
t.Fatal(err)
}
endpoint, err := HcnCreateTestEndpoint(network)
if err != nil {
t.Fatal(err)
}
loadBalancer, err := HcnCreateTestLoadBalancer(endpoint)
if err != nil {
t.Fatal(err)
}
foundLB, err := GetLoadBalancerByID(loadBalancer.Id)
if err != nil {
t.Fatal(err)
}
if foundLB == nil {
t.Fatalf("No loadBalancer found")
}
err = loadBalancer.Delete()
if err != nil {
t.Fatal(err)
}
err = endpoint.Delete()
if err != nil {
t.Fatal(err)
}
err = network.Delete()
if err != nil {
t.Fatal(err)
}
}
func TestListLoadBalancer(t *testing.T) {
_, err := ListLoadBalancers()
if err != nil {
t.Fatal(err)
}
}
func TestLoadBalancerAddRemoveEndpoint(t *testing.T) {
network, err := HcnCreateTestNATNetwork()
if err != nil {
t.Fatal(err)
}
endpoint, err := HcnCreateTestEndpoint(network)
if err != nil {
t.Fatal(err)
}
loadBalancer, err := HcnCreateTestLoadBalancer(endpoint)
if err != nil {
t.Fatal(err)
}
secondEndpoint, err := HcnCreateTestEndpoint(network)
if err != nil {
t.Fatal(err)
}
updatedLB, err := loadBalancer.AddEndpoint(secondEndpoint)
if err != nil {
t.Fatal(err)
}
if len(updatedLB.HostComputeEndpoints) != 2 {
t.Fatalf("Endpoint not added to loadBalancer")
}
updatedLB, err = loadBalancer.RemoveEndpoint(secondEndpoint)
if err != nil {
t.Fatal(err)
}
if len(updatedLB.HostComputeEndpoints) != 1 {
t.Fatalf("Endpoint not removed from loadBalancer")
}
err = loadBalancer.Delete()
if err != nil {
t.Fatal(err)
}
err = secondEndpoint.Delete()
if err != nil {
t.Fatal(err)
}
err = endpoint.Delete()
if err != nil {
t.Fatal(err)
}
err = network.Delete()
if err != nil {
t.Fatal(err)
}
}
func TestAddLoadBalancer(t *testing.T) {
network, err := HcnCreateTestNATNetwork()
if err != nil {
t.Fatal(err)
}
endpoint, err := HcnCreateTestEndpoint(network)
if err != nil {
t.Fatal(err)
}
loadBalancer, err := AddLoadBalancer([]HostComputeEndpoint{*endpoint}, false, false, "10.0.0.1", []string{"1.1.1.2", "1.1.1.3"}, 6, 8080, 80)
if err != nil {
t.Fatal(err)
}
foundLB, err := GetLoadBalancerByID(loadBalancer.Id)
if err != nil {
t.Fatal(err)
}
if foundLB == nil {
t.Fatal(fmt.Errorf("No loadBalancer found"))
}
err = loadBalancer.Delete()
if err != nil {
t.Fatal(err)
}
err = endpoint.Delete()
if err != nil {
t.Fatal(err)
}
err = network.Delete()
if err != nil {
t.Fatal(err)
}
}
func TestAddDSRLoadBalancer(t *testing.T) {
network, err := CreateTestOverlayNetwork()
if err != nil {
t.Fatal(err)
}
endpoint, err := HcnCreateTestEndpoint(network)
if err != nil {
t.Fatal(err)
}
loadBalancer, err := AddLoadBalancer([]HostComputeEndpoint{*endpoint}, false, true, "10.0.0.1", []string{"1.1.1.2", "1.1.1.3"}, 6, 8080, 80)
if err != nil {
t.Fatal(err)
}
foundLB, err := GetLoadBalancerByID(loadBalancer.Id)
if err != nil {
t.Fatal(err)
}
if foundLB == nil {
t.Fatal(fmt.Errorf("No loadBalancer found"))
}
err = loadBalancer.Delete()
if err != nil {
t.Fatal(err)
}
err = endpoint.Delete()
if err != nil {
t.Fatal(err)
}
err = network.Delete()
if err != nil {
t.Fatal(err)
}
}
func TestAddILBLoadBalancer(t *testing.T) {
network, err := CreateTestOverlayNetwork()
if err != nil {
t.Fatal(err)
}
endpoint, err := HcnCreateTestEndpoint(network)
if err != nil {
t.Fatal(err)
}
loadBalancer, err := AddLoadBalancer([]HostComputeEndpoint{*endpoint}, true, false, "10.0.0.1", []string{"1.1.1.2", "1.1.1.3"}, 6, 8080, 80)
if err != nil {
t.Fatal(err)
}
foundLB, err := GetLoadBalancerByID(loadBalancer.Id)
if err != nil {
t.Fatal(err)
}
if foundLB == nil {
t.Fatal(fmt.Errorf("No loadBalancer found"))
}
err = loadBalancer.Delete()
if err != nil {
t.Fatal(err)
}
err = endpoint.Delete()
if err != nil {
t.Fatal(err)
}
err = network.Delete()
if err != nil {
t.Fatal(err)
}
}

424
vendor/github.com/Microsoft/hcsshim/hcn/hcnnamespace.go generated vendored Normal file
View File

@ -0,0 +1,424 @@
package hcn
import (
"encoding/json"
"os"
"syscall"
icni "github.com/Microsoft/hcsshim/internal/cni"
"github.com/Microsoft/hcsshim/internal/guid"
"github.com/Microsoft/hcsshim/internal/interop"
"github.com/Microsoft/hcsshim/internal/regstate"
"github.com/Microsoft/hcsshim/internal/runhcs"
"github.com/sirupsen/logrus"
)
// NamespaceResourceEndpoint represents an Endpoint attached to a Namespace.
type NamespaceResourceEndpoint struct {
Id string `json:"ID,"`
}
// NamespaceResourceContainer represents a Container attached to a Namespace.
type NamespaceResourceContainer struct {
Id string `json:"ID,"`
}
// NamespaceResourceType determines whether the Namespace resource is a Container or Endpoint.
type NamespaceResourceType string
var (
// NamespaceResourceTypeContainer are contianers associated with a Namespace.
NamespaceResourceTypeContainer NamespaceResourceType = "Container"
// NamespaceResourceTypeEndpoint are endpoints associated with a Namespace.
NamespaceResourceTypeEndpoint NamespaceResourceType = "Endpoint"
)
// NamespaceResource is associated with a namespace
type NamespaceResource struct {
Type NamespaceResourceType `json:","` // Container, Endpoint
Data json.RawMessage `json:","`
}
// NamespaceType determines whether the Namespace is for a Host or Guest
type NamespaceType string
var (
// NamespaceTypeHost are host namespaces.
NamespaceTypeHost NamespaceType = "Host"
// NamespaceTypeHostDefault are host namespaces in the default compartment.
NamespaceTypeHostDefault NamespaceType = "HostDefault"
// NamespaceTypeGuest are guest namespaces.
NamespaceTypeGuest NamespaceType = "Guest"
// NamespaceTypeGuestDefault are guest namespaces in the default compartment.
NamespaceTypeGuestDefault NamespaceType = "GuestDefault"
)
// HostComputeNamespace represents a namespace (AKA compartment) in
type HostComputeNamespace struct {
Id string `json:"ID,omitempty"`
NamespaceId uint32 `json:",omitempty"`
Type NamespaceType `json:",omitempty"` // Host, HostDefault, Guest, GuestDefault
Resources []NamespaceResource `json:",omitempty"`
SchemaVersion SchemaVersion `json:",omitempty"`
}
// ModifyNamespaceSettingRequest is the structure used to send request to modify a namespace.
// Used to Add/Remove an endpoints and containers to/from a namespace.
type ModifyNamespaceSettingRequest struct {
ResourceType NamespaceResourceType `json:",omitempty"` // Container, Endpoint
RequestType RequestType `json:",omitempty"` // Add, Remove, Update, Refresh
Settings json.RawMessage `json:",omitempty"`
}
func getNamespace(namespaceGuid guid.GUID, query string) (*HostComputeNamespace, error) {
// Open namespace.
var (
namespaceHandle hcnNamespace
resultBuffer *uint16
propertiesBuffer *uint16
)
hr := hcnOpenNamespace(&namespaceGuid, &namespaceHandle, &resultBuffer)
if err := checkForErrors("hcnOpenNamespace", hr, resultBuffer); err != nil {
return nil, err
}
// Query namespace.
hr = hcnQueryNamespaceProperties(namespaceHandle, query, &propertiesBuffer, &resultBuffer)
if err := checkForErrors("hcnQueryNamespaceProperties", hr, resultBuffer); err != nil {
return nil, err
}
properties := interop.ConvertAndFreeCoTaskMemString(propertiesBuffer)
// Close namespace.
hr = hcnCloseNamespace(namespaceHandle)
if err := checkForErrors("hcnCloseNamespace", hr, nil); err != nil {
return nil, err
}
// Convert output to HostComputeNamespace
var outputNamespace HostComputeNamespace
if err := json.Unmarshal([]byte(properties), &outputNamespace); err != nil {
return nil, err
}
return &outputNamespace, nil
}
func enumerateNamespaces(query string) ([]HostComputeNamespace, error) {
// Enumerate all Namespace Guids
var (
resultBuffer *uint16
namespaceBuffer *uint16
)
hr := hcnEnumerateNamespaces(query, &namespaceBuffer, &resultBuffer)
if err := checkForErrors("hcnEnumerateNamespaces", hr, resultBuffer); err != nil {
return nil, err
}
namespaces := interop.ConvertAndFreeCoTaskMemString(namespaceBuffer)
var namespaceIds []guid.GUID
if err := json.Unmarshal([]byte(namespaces), &namespaceIds); err != nil {
return nil, err
}
var outputNamespaces []HostComputeNamespace
for _, namespaceGuid := range namespaceIds {
namespace, err := getNamespace(namespaceGuid, query)
if err != nil {
return nil, err
}
outputNamespaces = append(outputNamespaces, *namespace)
}
return outputNamespaces, nil
}
func createNamespace(settings string) (*HostComputeNamespace, error) {
// Create new namespace.
var (
namespaceHandle hcnNamespace
resultBuffer *uint16
propertiesBuffer *uint16
)
namespaceGuid := guid.GUID{}
hr := hcnCreateNamespace(&namespaceGuid, settings, &namespaceHandle, &resultBuffer)
if err := checkForErrors("hcnCreateNamespace", hr, resultBuffer); err != nil {
return nil, err
}
// Query namespace.
hcnQuery := defaultQuery()
query, err := json.Marshal(hcnQuery)
if err != nil {
return nil, err
}
hr = hcnQueryNamespaceProperties(namespaceHandle, string(query), &propertiesBuffer, &resultBuffer)
if err := checkForErrors("hcnQueryNamespaceProperties", hr, resultBuffer); err != nil {
return nil, err
}
properties := interop.ConvertAndFreeCoTaskMemString(propertiesBuffer)
// Close namespace.
hr = hcnCloseNamespace(namespaceHandle)
if err := checkForErrors("hcnCloseNamespace", hr, nil); err != nil {
return nil, err
}
// Convert output to HostComputeNamespace
var outputNamespace HostComputeNamespace
if err := json.Unmarshal([]byte(properties), &outputNamespace); err != nil {
return nil, err
}
return &outputNamespace, nil
}
func modifyNamespace(namespaceId string, settings string) (*HostComputeNamespace, error) {
namespaceGuid := guid.FromString(namespaceId)
// Open namespace.
var (
namespaceHandle hcnNamespace
resultBuffer *uint16
propertiesBuffer *uint16
)
hr := hcnOpenNamespace(&namespaceGuid, &namespaceHandle, &resultBuffer)
if err := checkForErrors("hcnOpenNamespace", hr, resultBuffer); err != nil {
return nil, err
}
// Modify namespace.
hr = hcnModifyNamespace(namespaceHandle, settings, &resultBuffer)
if err := checkForErrors("hcnModifyNamespace", hr, resultBuffer); err != nil {
return nil, err
}
// Query namespace.
hcnQuery := defaultQuery()
query, err := json.Marshal(hcnQuery)
if err != nil {
return nil, err
}
hr = hcnQueryNamespaceProperties(namespaceHandle, string(query), &propertiesBuffer, &resultBuffer)
if err := checkForErrors("hcnQueryNamespaceProperties", hr, resultBuffer); err != nil {
return nil, err
}
properties := interop.ConvertAndFreeCoTaskMemString(propertiesBuffer)
// Close namespace.
hr = hcnCloseNamespace(namespaceHandle)
if err := checkForErrors("hcnCloseNamespace", hr, nil); err != nil {
return nil, err
}
// Convert output to Namespace
var outputNamespace HostComputeNamespace
if err := json.Unmarshal([]byte(properties), &outputNamespace); err != nil {
return nil, err
}
return &outputNamespace, nil
}
func deleteNamespace(namespaceId string) error {
namespaceGuid := guid.FromString(namespaceId)
var resultBuffer *uint16
hr := hcnDeleteNamespace(&namespaceGuid, &resultBuffer)
if err := checkForErrors("hcnDeleteNamespace", hr, resultBuffer); err != nil {
return err
}
return nil
}
// ListNamespaces makes a call to list all available namespaces.
func ListNamespaces() ([]HostComputeNamespace, error) {
hcnQuery := defaultQuery()
namespaces, err := ListNamespacesQuery(hcnQuery)
if err != nil {
return nil, err
}
return namespaces, nil
}
// ListNamespacesQuery makes a call to query the list of available namespaces.
func ListNamespacesQuery(query HostComputeQuery) ([]HostComputeNamespace, error) {
queryJson, err := json.Marshal(query)
if err != nil {
return nil, err
}
namespaces, err := enumerateNamespaces(string(queryJson))
if err != nil {
return nil, err
}
return namespaces, nil
}
// GetNamespaceByID returns the Namespace specified by Id.
func GetNamespaceByID(namespaceId string) (*HostComputeNamespace, error) {
return getNamespace(guid.FromString(namespaceId), defaultQueryJson())
}
// GetNamespaceEndpointIds returns the endpoints of the Namespace specified by Id.
func GetNamespaceEndpointIds(namespaceId string) ([]string, error) {
namespace, err := GetNamespaceByID(namespaceId)
if err != nil {
return nil, err
}
var endpointsIds []string
for _, resource := range namespace.Resources {
if resource.Type == "Endpoint" {
var endpointResource NamespaceResourceEndpoint
if err := json.Unmarshal([]byte(resource.Data), &endpointResource); err != nil {
return nil, err
}
endpointsIds = append(endpointsIds, endpointResource.Id)
}
}
return endpointsIds, nil
}
// GetNamespaceContainerIds returns the containers of the Namespace specified by Id.
func GetNamespaceContainerIds(namespaceId string) ([]string, error) {
namespace, err := GetNamespaceByID(namespaceId)
if err != nil {
return nil, err
}
var containerIds []string
for _, resource := range namespace.Resources {
if resource.Type == "Container" {
var contaienrResource NamespaceResourceContainer
if err := json.Unmarshal([]byte(resource.Data), &contaienrResource); err != nil {
return nil, err
}
containerIds = append(containerIds, contaienrResource.Id)
}
}
return containerIds, nil
}
// NewNamespace creates a new Namespace object
func NewNamespace(nsType NamespaceType) *HostComputeNamespace {
return &HostComputeNamespace{
Type: nsType,
SchemaVersion: V2SchemaVersion(),
}
}
// Create Namespace.
func (namespace *HostComputeNamespace) Create() (*HostComputeNamespace, error) {
logrus.Debugf("hcn::HostComputeNamespace::Create id=%s", namespace.Id)
jsonString, err := json.Marshal(namespace)
if err != nil {
return nil, err
}
logrus.Debugf("hcn::HostComputeNamespace::Create JSON: %s", jsonString)
namespace, hcnErr := createNamespace(string(jsonString))
if hcnErr != nil {
return nil, hcnErr
}
return namespace, nil
}
// Delete Namespace.
func (namespace *HostComputeNamespace) Delete() error {
logrus.Debugf("hcn::HostComputeNamespace::Delete id=%s", namespace.Id)
if err := deleteNamespace(namespace.Id); err != nil {
return err
}
return nil
}
// Sync Namespace endpoints with the appropriate sandbox container holding the
// network namespace open. If no sandbox container is found for this namespace
// this method is determined to be a success and will not return an error in
// this case. If the sandbox container is found and a sync is initiated any
// failures will be returned via this method.
//
// This call initiates a sync between endpoints and the matching UtilityVM
// hosting those endpoints. It is safe to call for any `NamespaceType` but
// `NamespaceTypeGuest` is the only case when a sync will actually occur. For
// `NamespaceTypeHost` the process container will be automatically synchronized
// when the the endpoint is added via `AddNamespaceEndpoint`.
//
// Note: This method sync's both additions and removals of endpoints from a
// `NamespaceTypeGuest` namespace.
func (namespace *HostComputeNamespace) Sync() error {
logrus.WithField("id", namespace.Id).Debugf("hcs::HostComputeNamespace::Sync")
// We only attempt a sync for namespace guest.
if namespace.Type != NamespaceTypeGuest {
return nil
}
// Look in the registry for the key to map from namespace id to pod-id
cfg, err := icni.LoadPersistedNamespaceConfig(namespace.Id)
if err != nil {
if regstate.IsNotFoundError(err) {
return nil
}
return err
}
req := runhcs.VMRequest{
ID: cfg.ContainerID,
Op: runhcs.OpSyncNamespace,
}
shimPath := runhcs.VMPipePath(cfg.HostUniqueID)
if err := runhcs.IssueVMRequest(shimPath, &req); err != nil {
// The shim is likey gone. Simply ignore the sync as if it didn't exist.
if perr, ok := err.(*os.PathError); ok && perr.Err == syscall.ERROR_FILE_NOT_FOUND {
// Remove the reg key there is no point to try again
cfg.Remove()
return nil
}
f := map[string]interface{}{
"id": namespace.Id,
"container-id": cfg.ContainerID,
}
logrus.WithFields(f).
WithError(err).
Debugf("hcs::HostComputeNamespace::Sync failed to connect to shim pipe: '%s'", shimPath)
return err
}
return nil
}
// ModifyNamespaceSettings updates the Endpoints/Containers of a Namespace.
func ModifyNamespaceSettings(namespaceId string, request *ModifyNamespaceSettingRequest) error {
logrus.Debugf("hcn::HostComputeNamespace::ModifyNamespaceSettings id=%s", namespaceId)
namespaceSettings, err := json.Marshal(request)
if err != nil {
return err
}
_, err = modifyNamespace(namespaceId, string(namespaceSettings))
if err != nil {
return err
}
return nil
}
// AddNamespaceEndpoint adds an endpoint to a Namespace.
func AddNamespaceEndpoint(namespaceId string, endpointId string) error {
logrus.Debugf("hcn::HostComputeEndpoint::AddNamespaceEndpoint id=%s", endpointId)
mapA := map[string]string{"EndpointId": endpointId}
settingsJson, err := json.Marshal(mapA)
if err != nil {
return err
}
requestMessage := &ModifyNamespaceSettingRequest{
ResourceType: NamespaceResourceTypeEndpoint,
RequestType: RequestTypeAdd,
Settings: settingsJson,
}
return ModifyNamespaceSettings(namespaceId, requestMessage)
}
// RemoveNamespaceEndpoint removes an endpoint from a Namespace.
func RemoveNamespaceEndpoint(namespaceId string, endpointId string) error {
logrus.Debugf("hcn::HostComputeNamespace::RemoveNamespaceEndpoint id=%s", endpointId)
mapA := map[string]string{"EndpointId": endpointId}
settingsJson, err := json.Marshal(mapA)
if err != nil {
return err
}
requestMessage := &ModifyNamespaceSettingRequest{
ResourceType: NamespaceResourceTypeEndpoint,
RequestType: RequestTypeRemove,
Settings: settingsJson,
}
return ModifyNamespaceSettings(namespaceId, requestMessage)
}

View File

@ -0,0 +1,451 @@
// +build integration
package hcn
import (
"encoding/json"
"fmt"
"testing"
"github.com/Microsoft/hcsshim/internal/cni"
"github.com/Microsoft/hcsshim/internal/guid"
)
func TestNewNamespace(t *testing.T) {
_ = NewNamespace(NamespaceTypeHost)
_ = NewNamespace(NamespaceTypeHostDefault)
_ = NewNamespace(NamespaceTypeGuest)
_ = NewNamespace(NamespaceTypeGuestDefault)
}
func TestCreateDeleteNamespace(t *testing.T) {
namespace, err := HcnCreateTestNamespace()
if err != nil {
t.Fatal(err)
}
jsonString, err := json.Marshal(namespace)
if err != nil {
t.Fatal(err)
}
fmt.Printf("Namespace JSON:\n%s \n", jsonString)
err = namespace.Delete()
if err != nil {
t.Fatal(err)
}
}
func TestCreateDeleteNamespaceGuest(t *testing.T) {
namespace := &HostComputeNamespace{
Type: NamespaceTypeGuestDefault,
SchemaVersion: SchemaVersion{
Major: 2,
Minor: 0,
},
}
hnsNamespace, err := namespace.Create()
if err != nil {
t.Fatal(err)
}
err = hnsNamespace.Delete()
if err != nil {
t.Fatal(err)
}
}
func TestGetNamespaceById(t *testing.T) {
namespace, err := HcnCreateTestNamespace()
if err != nil {
t.Fatal(err)
}
foundNamespace, err := GetNamespaceByID(namespace.Id)
if err != nil {
t.Fatal(err)
}
if foundNamespace == nil {
t.Fatal("No namespace found")
}
err = namespace.Delete()
if err != nil {
t.Fatal(err)
}
}
func TestListNamespaces(t *testing.T) {
namespace, err := HcnCreateTestNamespace()
if err != nil {
t.Fatal(err)
}
foundNamespaces, err := ListNamespaces()
if err != nil {
t.Fatal(err)
}
if len(foundNamespaces) == 0 {
t.Fatal("No Namespaces found")
}
err = namespace.Delete()
if err != nil {
t.Fatal(err)
}
}
func TestGetNamespaceEndpointIds(t *testing.T) {
network, err := HcnCreateTestNATNetwork()
if err != nil {
t.Fatal(err)
}
endpoint, err := HcnCreateTestEndpoint(network)
if err != nil {
t.Fatal(err)
}
namespace, err := HcnCreateTestNamespace()
if err != nil {
t.Fatal(err)
}
err = endpoint.NamespaceAttach(namespace.Id)
if err != nil {
t.Fatal(err)
}
foundEndpoints, err := GetNamespaceEndpointIds(namespace.Id)
if err != nil {
t.Fatal(err)
}
if len(foundEndpoints) == 0 {
t.Fatal("No Endpoint found")
}
err = endpoint.NamespaceDetach(namespace.Id)
if err != nil {
t.Fatal(err)
}
err = namespace.Delete()
if err != nil {
t.Fatal(err)
}
err = endpoint.Delete()
if err != nil {
t.Fatal(err)
}
err = network.Delete()
if err != nil {
t.Fatal(err)
}
}
func TestGetNamespaceContainers(t *testing.T) {
namespace, err := HcnCreateTestNamespace()
if err != nil {
t.Fatal(err)
}
foundEndpoints, err := GetNamespaceContainerIds(namespace.Id)
if err != nil {
t.Fatal(err)
}
if len(foundEndpoints) != 0 {
t.Fatal("Found containers when none should exist")
}
err = namespace.Delete()
if err != nil {
t.Fatal(err)
}
}
func TestAddRemoveNamespaceEndpoint(t *testing.T) {
network, err := HcnCreateTestNATNetwork()
if err != nil {
t.Fatal(err)
}
endpoint, err := HcnCreateTestEndpoint(network)
if err != nil {
t.Fatal(err)
}
namespace, err := HcnCreateTestNamespace()
if err != nil {
t.Fatal(err)
}
err = AddNamespaceEndpoint(namespace.Id, endpoint.Id)
if err != nil {
t.Fatal(err)
}
foundEndpoints, err := GetNamespaceEndpointIds(namespace.Id)
if err != nil {
t.Fatal(err)
}
if len(foundEndpoints) == 0 {
t.Fatal("No Endpoint found")
}
err = RemoveNamespaceEndpoint(namespace.Id, endpoint.Id)
if err != nil {
t.Fatal(err)
}
err = namespace.Delete()
if err != nil {
t.Fatal(err)
}
err = endpoint.Delete()
if err != nil {
t.Fatal(err)
}
err = network.Delete()
if err != nil {
t.Fatal(err)
}
}
func TestModifyNamespaceSettings(t *testing.T) {
network, err := HcnCreateTestNATNetwork()
if err != nil {
t.Fatal(err)
}
endpoint, err := HcnCreateTestEndpoint(network)
if err != nil {
t.Fatal(err)
}
namespace, err := HcnCreateTestNamespace()
if err != nil {
t.Fatal(err)
}
mapA := map[string]string{"EndpointId": endpoint.Id}
settingsJson, err := json.Marshal(mapA)
if err != nil {
t.Fatal(err)
}
requestMessage := &ModifyNamespaceSettingRequest{
ResourceType: NamespaceResourceTypeEndpoint,
RequestType: RequestTypeAdd,
Settings: settingsJson,
}
err = ModifyNamespaceSettings(namespace.Id, requestMessage)
if err != nil {
t.Fatal(err)
}
foundEndpoints, err := GetNamespaceEndpointIds(namespace.Id)
if err != nil {
t.Fatal(err)
}
if len(foundEndpoints) == 0 {
t.Fatal("No Endpoint found")
}
err = RemoveNamespaceEndpoint(namespace.Id, endpoint.Id)
if err != nil {
t.Fatal(err)
}
err = namespace.Delete()
if err != nil {
t.Fatal(err)
}
err = endpoint.Delete()
if err != nil {
t.Fatal(err)
}
err = network.Delete()
if err != nil {
t.Fatal(err)
}
}
// Sync Tests
func TestSyncNamespaceHostDefault(t *testing.T) {
namespace := &HostComputeNamespace{
Type: NamespaceTypeHostDefault,
NamespaceId: 5,
SchemaVersion: SchemaVersion{
Major: 2,
Minor: 0,
},
}
hnsNamespace, err := namespace.Create()
if err != nil {
t.Fatal(err)
}
// Host namespace types should be no-op success
err = hnsNamespace.Sync()
if err != nil {
t.Fatal(err)
}
err = hnsNamespace.Delete()
if err != nil {
t.Fatal(err)
}
}
func TestSyncNamespaceHost(t *testing.T) {
namespace := &HostComputeNamespace{
Type: NamespaceTypeHost,
NamespaceId: 5,
SchemaVersion: SchemaVersion{
Major: 2,
Minor: 0,
},
}
hnsNamespace, err := namespace.Create()
if err != nil {
t.Fatal(err)
}
// Host namespace types should be no-op success
err = hnsNamespace.Sync()
if err != nil {
t.Fatal(err)
}
err = hnsNamespace.Delete()
if err != nil {
t.Fatal(err)
}
}
func TestSyncNamespaceGuestNoReg(t *testing.T) {
namespace := &HostComputeNamespace{
Type: NamespaceTypeGuest,
NamespaceId: 5,
SchemaVersion: SchemaVersion{
Major: 2,
Minor: 0,
},
}
hnsNamespace, err := namespace.Create()
if err != nil {
t.Fatal(err)
}
// Guest namespace type with out reg state should be no-op success
err = hnsNamespace.Sync()
if err != nil {
t.Fatal(err)
}
err = hnsNamespace.Delete()
if err != nil {
t.Fatal(err)
}
}
func TestSyncNamespaceGuestDefaultNoReg(t *testing.T) {
namespace := &HostComputeNamespace{
Type: NamespaceTypeGuestDefault,
NamespaceId: 5,
SchemaVersion: SchemaVersion{
Major: 2,
Minor: 0,
},
}
hnsNamespace, err := namespace.Create()
if err != nil {
t.Fatal(err)
}
// Guest namespace type with out reg state should be no-op success
err = hnsNamespace.Sync()
if err != nil {
t.Fatal(err)
}
err = hnsNamespace.Delete()
if err != nil {
t.Fatal(err)
}
}
func TestSyncNamespaceGuest(t *testing.T) {
namespace := &HostComputeNamespace{
Type: NamespaceTypeGuest,
NamespaceId: 5,
SchemaVersion: SchemaVersion{
Major: 2,
Minor: 0,
},
}
hnsNamespace, err := namespace.Create()
if err != nil {
t.Fatal(err)
}
// Create registry state
pnc := cni.NewPersistedNamespaceConfig(t.Name(), "test-container", guid.New())
err = pnc.Store()
if err != nil {
pnc.Remove()
t.Fatal(err)
}
// Guest namespace type with reg state but not Vm shim should pass...
// after trying to connect to VM shim that it doesn't find and remove the Key so it doesn't look again.
err = hnsNamespace.Sync()
if err != nil {
t.Fatal(err)
}
err = pnc.Remove()
if err != nil {
t.Fatal(err)
}
err = hnsNamespace.Delete()
if err != nil {
t.Fatal(err)
}
}
func TestSyncNamespaceGuestDefault(t *testing.T) {
namespace := &HostComputeNamespace{
Type: NamespaceTypeGuestDefault,
NamespaceId: 5,
SchemaVersion: SchemaVersion{
Major: 2,
Minor: 0,
},
}
hnsNamespace, err := namespace.Create()
if err != nil {
t.Fatal(err)
}
// Create registry state
pnc := cni.NewPersistedNamespaceConfig(t.Name(), "test-container", guid.New())
err = pnc.Store()
if err != nil {
pnc.Remove()
t.Fatal(err)
}
// Guest namespace type with reg state but not Vm shim should pass...
// after trying to connect to VM shim that it doesn't find and remove the Key so it doesn't look again.
err = hnsNamespace.Sync()
if err != nil {
t.Fatal(err)
}
err = pnc.Remove()
if err != nil {
t.Fatal(err)
}
err = hnsNamespace.Delete()
if err != nil {
t.Fatal(err)
}
}

418
vendor/github.com/Microsoft/hcsshim/hcn/hcnnetwork.go generated vendored Normal file
View File

@ -0,0 +1,418 @@
package hcn
import (
"encoding/json"
"github.com/Microsoft/hcsshim/internal/guid"
"github.com/Microsoft/hcsshim/internal/interop"
"github.com/sirupsen/logrus"
)
// Route is assoicated with a subnet.
type Route struct {
NextHop string `json:",omitempty"`
DestinationPrefix string `json:",omitempty"`
Metric uint16 `json:",omitempty"`
}
// Subnet is assoicated with a Ipam.
type Subnet struct {
IpAddressPrefix string `json:",omitempty"`
Policies []json.RawMessage `json:",omitempty"`
Routes []Route `json:",omitempty"`
}
// Ipam (Internet Protocol Addres Management) is assoicated with a network
// and represents the address space(s) of a network.
type Ipam struct {
Type string `json:",omitempty"` // Ex: Static, DHCP
Subnets []Subnet `json:",omitempty"`
}
// MacRange is associated with MacPool and respresents the start and end addresses.
type MacRange struct {
StartMacAddress string `json:",omitempty"`
EndMacAddress string `json:",omitempty"`
}
// MacPool is assoicated with a network and represents pool of MacRanges.
type MacPool struct {
Ranges []MacRange `json:",omitempty"`
}
// Dns (Domain Name System is associated with a network.
type Dns struct {
Domain string `json:",omitempty"`
Search []string `json:",omitempty"`
ServerList []string `json:",omitempty"`
Options []string `json:",omitempty"`
}
// NetworkType are various networks.
type NetworkType string
// NetworkType const
const (
NAT NetworkType = "NAT"
Transparent NetworkType = "Transparent"
L2Bridge NetworkType = "L2Bridge"
L2Tunnel NetworkType = "L2Tunnel"
ICS NetworkType = "ICS"
Private NetworkType = "Private"
Overlay NetworkType = "Overlay"
)
// NetworkFlags are various network flags.
type NetworkFlags uint32
// NetworkFlags const
const (
None NetworkFlags = 0
EnableNonPersistent NetworkFlags = 8
)
// HostComputeNetwork represents a network
type HostComputeNetwork struct {
Id string `json:"ID,omitempty"`
Name string `json:",omitempty"`
Type NetworkType `json:",omitempty"`
Policies []NetworkPolicy `json:",omitempty"`
MacPool MacPool `json:",omitempty"`
Dns Dns `json:",omitempty"`
Ipams []Ipam `json:",omitempty"`
Flags NetworkFlags `json:",omitempty"` // 0: None
SchemaVersion SchemaVersion `json:",omitempty"`
}
// NetworkResourceType are the 3 different Network settings resources.
type NetworkResourceType string
var (
// NetworkResourceTypePolicy is for Network's policies. Ex: RemoteSubnet
NetworkResourceTypePolicy NetworkResourceType = "Policy"
// NetworkResourceTypeDNS is for Network's DNS settings.
NetworkResourceTypeDNS NetworkResourceType = "DNS"
// NetworkResourceTypeExtension is for Network's extension settings.
NetworkResourceTypeExtension NetworkResourceType = "Extension"
)
// ModifyNetworkSettingRequest is the structure used to send request to modify an network.
// Used to update DNS/extension/policy on an network.
type ModifyNetworkSettingRequest struct {
ResourceType NetworkResourceType `json:",omitempty"` // Policy, DNS, Extension
RequestType RequestType `json:",omitempty"` // Add, Remove, Update, Refresh
Settings json.RawMessage `json:",omitempty"`
}
type PolicyNetworkRequest struct {
Policies []NetworkPolicy `json:",omitempty"`
}
func getNetwork(networkGuid guid.GUID, query string) (*HostComputeNetwork, error) {
// Open network.
var (
networkHandle hcnNetwork
resultBuffer *uint16
propertiesBuffer *uint16
)
hr := hcnOpenNetwork(&networkGuid, &networkHandle, &resultBuffer)
if err := checkForErrors("hcnOpenNetwork", hr, resultBuffer); err != nil {
return nil, err
}
// Query network.
hr = hcnQueryNetworkProperties(networkHandle, query, &propertiesBuffer, &resultBuffer)
if err := checkForErrors("hcnQueryNetworkProperties", hr, resultBuffer); err != nil {
return nil, err
}
properties := interop.ConvertAndFreeCoTaskMemString(propertiesBuffer)
// Close network.
hr = hcnCloseNetwork(networkHandle)
if err := checkForErrors("hcnCloseNetwork", hr, nil); err != nil {
return nil, err
}
// Convert output to HostComputeNetwork
var outputNetwork HostComputeNetwork
if err := json.Unmarshal([]byte(properties), &outputNetwork); err != nil {
return nil, err
}
return &outputNetwork, nil
}
func enumerateNetworks(query string) ([]HostComputeNetwork, error) {
// Enumerate all Network Guids
var (
resultBuffer *uint16
networkBuffer *uint16
)
hr := hcnEnumerateNetworks(query, &networkBuffer, &resultBuffer)
if err := checkForErrors("hcnEnumerateNetworks", hr, resultBuffer); err != nil {
return nil, err
}
networks := interop.ConvertAndFreeCoTaskMemString(networkBuffer)
var networkIds []guid.GUID
if err := json.Unmarshal([]byte(networks), &networkIds); err != nil {
return nil, err
}
var outputNetworks []HostComputeNetwork
for _, networkGuid := range networkIds {
network, err := getNetwork(networkGuid, query)
if err != nil {
return nil, err
}
outputNetworks = append(outputNetworks, *network)
}
return outputNetworks, nil
}
func createNetwork(settings string) (*HostComputeNetwork, error) {
// Create new network.
var (
networkHandle hcnNetwork
resultBuffer *uint16
propertiesBuffer *uint16
)
networkGuid := guid.GUID{}
hr := hcnCreateNetwork(&networkGuid, settings, &networkHandle, &resultBuffer)
if err := checkForErrors("hcnCreateNetwork", hr, resultBuffer); err != nil {
return nil, err
}
// Query network.
hcnQuery := defaultQuery()
query, err := json.Marshal(hcnQuery)
if err != nil {
return nil, err
}
hr = hcnQueryNetworkProperties(networkHandle, string(query), &propertiesBuffer, &resultBuffer)
if err := checkForErrors("hcnQueryNetworkProperties", hr, resultBuffer); err != nil {
return nil, err
}
properties := interop.ConvertAndFreeCoTaskMemString(propertiesBuffer)
// Close network.
hr = hcnCloseNetwork(networkHandle)
if err := checkForErrors("hcnCloseNetwork", hr, nil); err != nil {
return nil, err
}
// Convert output to HostComputeNetwork
var outputNetwork HostComputeNetwork
if err := json.Unmarshal([]byte(properties), &outputNetwork); err != nil {
return nil, err
}
return &outputNetwork, nil
}
func modifyNetwork(networkId string, settings string) (*HostComputeNetwork, error) {
networkGuid := guid.FromString(networkId)
// Open Network
var (
networkHandle hcnNetwork
resultBuffer *uint16
propertiesBuffer *uint16
)
hr := hcnOpenNetwork(&networkGuid, &networkHandle, &resultBuffer)
if err := checkForErrors("hcnOpenNetwork", hr, resultBuffer); err != nil {
return nil, err
}
// Modify Network
hr = hcnModifyNetwork(networkHandle, settings, &resultBuffer)
if err := checkForErrors("hcnModifyNetwork", hr, resultBuffer); err != nil {
return nil, err
}
// Query network.
hcnQuery := defaultQuery()
query, err := json.Marshal(hcnQuery)
if err != nil {
return nil, err
}
hr = hcnQueryNetworkProperties(networkHandle, string(query), &propertiesBuffer, &resultBuffer)
if err := checkForErrors("hcnQueryNetworkProperties", hr, resultBuffer); err != nil {
return nil, err
}
properties := interop.ConvertAndFreeCoTaskMemString(propertiesBuffer)
// Close network.
hr = hcnCloseNetwork(networkHandle)
if err := checkForErrors("hcnCloseNetwork", hr, nil); err != nil {
return nil, err
}
// Convert output to HostComputeNetwork
var outputNetwork HostComputeNetwork
if err := json.Unmarshal([]byte(properties), &outputNetwork); err != nil {
return nil, err
}
return &outputNetwork, nil
}
func deleteNetwork(networkId string) error {
networkGuid := guid.FromString(networkId)
var resultBuffer *uint16
hr := hcnDeleteNetwork(&networkGuid, &resultBuffer)
if err := checkForErrors("hcnDeleteNetwork", hr, resultBuffer); err != nil {
return err
}
return nil
}
// ListNetworks makes a call to list all available networks.
func ListNetworks() ([]HostComputeNetwork, error) {
hcnQuery := defaultQuery()
networks, err := ListNetworksQuery(hcnQuery)
if err != nil {
return nil, err
}
return networks, nil
}
// ListNetworksQuery makes a call to query the list of available networks.
func ListNetworksQuery(query HostComputeQuery) ([]HostComputeNetwork, error) {
queryJson, err := json.Marshal(query)
if err != nil {
return nil, err
}
networks, err := enumerateNetworks(string(queryJson))
if err != nil {
return nil, err
}
return networks, nil
}
// GetNetworkByID returns the network specified by Id.
func GetNetworkByID(networkID string) (*HostComputeNetwork, error) {
hcnQuery := defaultQuery()
mapA := map[string]string{"ID": networkID}
filter, err := json.Marshal(mapA)
if err != nil {
return nil, err
}
hcnQuery.Filter = string(filter)
networks, err := ListNetworksQuery(hcnQuery)
if err != nil {
return nil, err
}
if len(networks) == 0 {
return nil, NetworkNotFoundError{NetworkID: networkID}
}
return &networks[0], err
}
// GetNetworkByName returns the network specified by Name.
func GetNetworkByName(networkName string) (*HostComputeNetwork, error) {
hcnQuery := defaultQuery()
mapA := map[string]string{"Name": networkName}
filter, err := json.Marshal(mapA)
if err != nil {
return nil, err
}
hcnQuery.Filter = string(filter)
networks, err := ListNetworksQuery(hcnQuery)
if err != nil {
return nil, err
}
if len(networks) == 0 {
return nil, NetworkNotFoundError{NetworkName: networkName}
}
return &networks[0], err
}
// Create Network.
func (network *HostComputeNetwork) Create() (*HostComputeNetwork, error) {
logrus.Debugf("hcn::HostComputeNetwork::Create id=%s", network.Id)
jsonString, err := json.Marshal(network)
if err != nil {
return nil, err
}
logrus.Debugf("hcn::HostComputeNetwork::Create JSON: %s", jsonString)
network, hcnErr := createNetwork(string(jsonString))
if hcnErr != nil {
return nil, hcnErr
}
return network, nil
}
// Delete Network.
func (network *HostComputeNetwork) Delete() error {
logrus.Debugf("hcn::HostComputeNetwork::Delete id=%s", network.Id)
if err := deleteNetwork(network.Id); err != nil {
return err
}
return nil
}
// ModifyNetworkSettings updates the Policy for a network.
func (network *HostComputeNetwork) ModifyNetworkSettings(request *ModifyNetworkSettingRequest) error {
logrus.Debugf("hcn::HostComputeNetwork::ModifyNetworkSettings id=%s", network.Id)
networkSettingsRequest, err := json.Marshal(request)
if err != nil {
return err
}
_, err = modifyNetwork(network.Id, string(networkSettingsRequest))
if err != nil {
return err
}
return nil
}
// AddPolicy applies a Policy (ex: RemoteSubnet) on the Network.
func (network *HostComputeNetwork) AddPolicy(networkPolicy PolicyNetworkRequest) error {
logrus.Debugf("hcn::HostComputeNetwork::AddPolicy id=%s", network.Id)
settingsJson, err := json.Marshal(networkPolicy)
if err != nil {
return err
}
requestMessage := &ModifyNetworkSettingRequest{
ResourceType: NetworkResourceTypePolicy,
RequestType: RequestTypeAdd,
Settings: settingsJson,
}
return network.ModifyNetworkSettings(requestMessage)
}
// RemovePolicy removes a Policy (ex: RemoteSubnet) from the Network.
func (network *HostComputeNetwork) RemovePolicy(networkPolicy PolicyNetworkRequest) error {
logrus.Debugf("hcn::HostComputeNetwork::RemovePolicy id=%s", network.Id)
settingsJson, err := json.Marshal(networkPolicy)
if err != nil {
return err
}
requestMessage := &ModifyNetworkSettingRequest{
ResourceType: NetworkResourceTypePolicy,
RequestType: RequestTypeRemove,
Settings: settingsJson,
}
return network.ModifyNetworkSettings(requestMessage)
}
// CreateEndpoint creates an endpoint on the Network.
func (network *HostComputeNetwork) CreateEndpoint(endpoint *HostComputeEndpoint) (*HostComputeEndpoint, error) {
isRemote := endpoint.Flags&EndpointFlagsRemoteEndpoint != 0
logrus.Debugf("hcn::HostComputeNetwork::CreatEndpoint, networkId=%s remote=%t", network.Id, isRemote)
endpoint.HostComputeNetwork = network.Id
endpointSettings, err := json.Marshal(endpoint)
if err != nil {
return nil, err
}
newEndpoint, err := createEndpoint(network.Id, string(endpointSettings))
if err != nil {
return nil, err
}
return newEndpoint, nil
}
// CreateRemoteEndpoint creates a remote endpoint on the Network.
func (network *HostComputeNetwork) CreateRemoteEndpoint(endpoint *HostComputeEndpoint) (*HostComputeEndpoint, error) {
endpoint.Flags = EndpointFlagsRemoteEndpoint | endpoint.Flags
return network.CreateEndpoint(endpoint)
}

View File

@ -0,0 +1,165 @@
// +build integration
package hcn
import (
"encoding/json"
"fmt"
"testing"
)
func TestCreateDeleteNetwork(t *testing.T) {
network, err := HcnCreateTestNATNetwork()
if err != nil {
t.Fatal(err)
}
jsonString, err := json.Marshal(network)
if err != nil {
t.Fatal(err)
}
fmt.Printf("Network JSON:\n%s \n", jsonString)
err = network.Delete()
if err != nil {
t.Fatal(err)
}
}
func TestGetNetworkByName(t *testing.T) {
network, err := HcnCreateTestNATNetwork()
if err != nil {
t.Fatal(err)
}
network, err = GetNetworkByName(network.Name)
if err != nil {
t.Fatal(err)
}
if network == nil {
t.Fatal("No Network found")
}
err = network.Delete()
if err != nil {
t.Fatal(err)
}
}
func TestGetNetworkById(t *testing.T) {
network, err := HcnCreateTestNATNetwork()
if err != nil {
t.Fatal(err)
}
network, err = GetNetworkByID(network.Id)
if err != nil {
t.Fatal(err)
}
if network == nil {
t.Fatal("No Network found")
}
err = network.Delete()
if err != nil {
t.Fatal(err)
}
}
func TestListNetwork(t *testing.T) {
_, err := ListNetworks()
if err != nil {
t.Fatal(err)
}
}
func testNetworkPolicy(t *testing.T, policiesToTest *PolicyNetworkRequest) {
network, err := CreateTestOverlayNetwork()
if err != nil {
t.Fatal(err)
}
network.AddPolicy(*policiesToTest)
//Reload the network object from HNS.
network, err = GetNetworkByID(network.Id)
if err != nil {
t.Fatal(err)
}
for _, policyToTest := range policiesToTest.Policies {
foundPolicy := false
for _, policy := range network.Policies {
if policy.Type == policyToTest.Type {
foundPolicy = true
break
}
}
if !foundPolicy {
t.Fatalf("Could not find %s policy on network.", policyToTest.Type)
}
}
network.RemovePolicy(*policiesToTest)
//Reload the network object from HNS.
network, err = GetNetworkByID(network.Id)
if err != nil {
t.Fatal(err)
}
for _, policyToTest := range policiesToTest.Policies {
foundPolicy := false
for _, policy := range network.Policies {
if policy.Type == policyToTest.Type {
foundPolicy = true
break
}
}
if foundPolicy {
t.Fatalf("Found %s policy on network when it should have been deleted.", policyToTest.Type)
}
}
err = network.Delete()
if err != nil {
t.Fatal(err)
}
}
func TestAddRemoveRemoteSubnetRoutePolicy(t *testing.T) {
remoteSubnetRoutePolicy, err := HcnCreateTestRemoteSubnetRoute()
if err != nil {
t.Fatal(err)
}
testNetworkPolicy(t, remoteSubnetRoutePolicy)
}
func TestAddRemoveHostRoutePolicy(t *testing.T) {
hostRoutePolicy, err := HcnCreateTestHostRoute()
if err != nil {
t.Fatal(err)
}
testNetworkPolicy(t, hostRoutePolicy)
}
func TestNetworkFlags(t *testing.T) {
network, err := CreateTestOverlayNetwork()
if err != nil {
t.Fatal(err)
}
//Reload the network object from HNS.
network, err = GetNetworkByID(network.Id)
if err != nil {
t.Fatal(err)
}
if network.Flags != EnableNonPersistent {
t.Errorf("EnableNonPersistent flag (%d) is not set on network", EnableNonPersistent)
}
err = network.Delete()
if err != nil {
t.Fatal(err)
}
}

217
vendor/github.com/Microsoft/hcsshim/hcn/hcnpolicy.go generated vendored Normal file
View File

@ -0,0 +1,217 @@
package hcn
import "encoding/json"
// EndpointPolicyType are the potential Policies that apply to Endpoints.
type EndpointPolicyType string
// EndpointPolicyType const
const (
PortMapping EndpointPolicyType = "PortMapping"
ACL EndpointPolicyType = "ACL"
QOS EndpointPolicyType = "QOS"
L2Driver EndpointPolicyType = "L2Driver"
OutBoundNAT EndpointPolicyType = "OutBoundNAT"
SDNRoute EndpointPolicyType = "SDNRoute"
L4Proxy EndpointPolicyType = "L4Proxy"
PortName EndpointPolicyType = "PortName"
EncapOverhead EndpointPolicyType = "EncapOverhead"
// Endpoint and Network have InterfaceConstraint and ProviderAddress
NetworkProviderAddress EndpointPolicyType = "ProviderAddress"
NetworkInterfaceConstraint EndpointPolicyType = "InterfaceConstraint"
)
// EndpointPolicy is a collection of Policy settings for an Endpoint.
type EndpointPolicy struct {
Type EndpointPolicyType `json:""`
Settings json.RawMessage `json:",omitempty"`
}
// NetworkPolicyType are the potential Policies that apply to Networks.
type NetworkPolicyType string
// NetworkPolicyType const
const (
SourceMacAddress NetworkPolicyType = "SourceMacAddress"
NetAdapterName NetworkPolicyType = "NetAdapterName"
VSwitchExtension NetworkPolicyType = "VSwitchExtension"
DrMacAddress NetworkPolicyType = "DrMacAddress"
AutomaticDNS NetworkPolicyType = "AutomaticDNS"
InterfaceConstraint NetworkPolicyType = "InterfaceConstraint"
ProviderAddress NetworkPolicyType = "ProviderAddress"
RemoteSubnetRoute NetworkPolicyType = "RemoteSubnetRoute"
HostRoute NetworkPolicyType = "HostRoute"
)
// NetworkPolicy is a collection of Policy settings for a Network.
type NetworkPolicy struct {
Type NetworkPolicyType `json:""`
Settings json.RawMessage `json:",omitempty"`
}
// SubnetPolicyType are the potential Policies that apply to Subnets.
type SubnetPolicyType string
// SubnetPolicyType const
const (
VLAN SubnetPolicyType = "VLAN"
VSID SubnetPolicyType = "VSID"
)
// SubnetPolicy is a collection of Policy settings for a Subnet.
type SubnetPolicy struct {
Type SubnetPolicyType `json:""`
Settings json.RawMessage `json:",omitempty"`
}
/// Endpoint Policy objects
// PortMappingPolicySetting defines Port Mapping (NAT)
type PortMappingPolicySetting struct {
Protocol uint32 `json:",omitempty"` // EX: TCP = 6, UDP = 17
InternalPort uint16 `json:",omitempty"`
ExternalPort uint16 `json:",omitempty"`
VIP string `json:",omitempty"`
}
// ActionType associated with ACLs. Value is either Allow or Block.
type ActionType string
// DirectionType associated with ACLs. Value is either In or Out.
type DirectionType string
// RuleType associated with ACLs. Value is either Host (WFP) or Switch (VFP).
type RuleType string
const (
// Allow traffic
ActionTypeAllow ActionType = "Allow"
// Block traffic
ActionTypeBlock ActionType = "Block"
// In is traffic coming to the Endpoint
DirectionTypeIn DirectionType = "In"
// Out is traffic leaving the Endpoint
DirectionTypeOut DirectionType = "Out"
// Host creates WFP (Windows Firewall) rules
RuleTypeHost RuleType = "Host"
// Switch creates VFP (Virtual Filter Platform) rules
RuleTypeSwitch RuleType = "Switch"
)
// AclPolicySetting creates firewall rules on an endpoint
type AclPolicySetting struct {
Protocols string `json:",omitempty"` // EX: 6 (TCP), 17 (UDP), 1 (ICMPv4), 58 (ICMPv6), 2 (IGMP)
Action ActionType `json:","`
Direction DirectionType `json:","`
LocalAddresses string `json:",omitempty"`
RemoteAddresses string `json:",omitempty"`
LocalPorts string `json:",omitempty"`
RemotePorts string `json:",omitempty"`
RuleType RuleType `json:",omitempty"`
Priority uint16 `json:",omitempty"`
}
// QosPolicySetting sets Quality of Service bandwidth caps on an Endpoint.
type QosPolicySetting struct {
MaximumOutgoingBandwidthInBytes uint64
}
// OutboundNatPolicySetting sets outbound Network Address Translation on an Endpoint.
type OutboundNatPolicySetting struct {
VirtualIP string `json:",omitempty"`
Exceptions []string `json:",omitempty"`
}
// SDNRoutePolicySetting sets SDN Route on an Endpoint.
type SDNRoutePolicySetting struct {
DestinationPrefix string `json:",omitempty"`
NextHop string `json:",omitempty"`
NeedEncap bool `json:",omitempty"`
}
// L4ProxyPolicySetting sets Layer-4 Proxy on an endpoint.
type L4ProxyPolicySetting struct {
IP string `json:",omitempty"`
Port string `json:",omitempty"`
Protocol uint32 `json:",omitempty"` // EX: TCP = 6, UDP = 17
ExceptionList []string `json:",omitempty"`
Destination string `json:","`
OutboundNat bool `json:",omitempty"`
}
// PortnameEndpointPolicySetting sets the port name for an endpoint.
type PortnameEndpointPolicySetting struct {
Name string `json:",omitempty"`
}
// EncapOverheadEndpointPolicySetting sets the encap overhead for an endpoint.
type EncapOverheadEndpointPolicySetting struct {
Overhead uint16 `json:",omitempty"`
}
/// Endpoint and Network Policy objects
// ProviderAddressEndpointPolicySetting sets the PA for an endpoint.
type ProviderAddressEndpointPolicySetting struct {
ProviderAddress string `json:",omitempty"`
}
// InterfaceConstraintPolicySetting limits an Endpoint or Network to a specific Nic.
type InterfaceConstraintPolicySetting struct {
InterfaceGuid string `json:",omitempty"`
InterfaceLuid uint64 `json:",omitempty"`
InterfaceIndex uint32 `json:",omitempty"`
InterfaceMediaType uint32 `json:",omitempty"`
InterfaceAlias string `json:",omitempty"`
InterfaceDescription string `json:",omitempty"`
}
/// Network Policy objects
// SourceMacAddressNetworkPolicySetting sets source MAC for a network.
type SourceMacAddressNetworkPolicySetting struct {
SourceMacAddress string `json:",omitempty"`
}
// NetAdapterNameNetworkPolicySetting sets network adapter of a network.
type NetAdapterNameNetworkPolicySetting struct {
NetworkAdapterName string `json:",omitempty"`
}
// VSwitchExtensionNetworkPolicySetting enables/disabled VSwitch extensions for a network.
type VSwitchExtensionNetworkPolicySetting struct {
ExtensionID string `json:",omitempty"`
Enable bool `json:",omitempty"`
}
// DrMacAddressNetworkPolicySetting sets the DR MAC for a network.
type DrMacAddressNetworkPolicySetting struct {
Address string `json:",omitempty"`
}
// AutomaticDNSNetworkPolicySetting enables/disables automatic DNS on a network.
type AutomaticDNSNetworkPolicySetting struct {
Enable bool `json:",omitempty"`
}
/// Subnet Policy objects
// VlanPolicySetting isolates a subnet with VLAN tagging.
type VlanPolicySetting struct {
IsolationId uint32 `json:","`
}
// VsidPolicySetting isolates a subnet with VSID tagging.
type VsidPolicySetting struct {
IsolationId uint32 `json:","`
}
// RemoteSubnetRoutePolicySetting creates remote subnet route rules on a network
type RemoteSubnetRoutePolicySetting struct {
DestinationPrefix string
IsolationId uint16
ProviderAddress string
DistributedRouterMacAddress string
}

71
vendor/github.com/Microsoft/hcsshim/hcn/hcnsupport.go generated vendored Normal file
View File

@ -0,0 +1,71 @@
package hcn
import (
"github.com/sirupsen/logrus"
)
// SupportedFeatures are the features provided by the Service.
type SupportedFeatures struct {
Acl AclFeatures `json:"ACL"`
Api ApiSupport `json:"API"`
RemoteSubnet bool `json:"RemoteSubnet"`
HostRoute bool `json:"HostRoute"`
DSR bool `json:"DSR"`
}
// AclFeatures are the supported ACL possibilities.
type AclFeatures struct {
AclAddressLists bool `json:"AclAddressLists"`
AclNoHostRulePriority bool `json:"AclHostRulePriority"`
AclPortRanges bool `json:"AclPortRanges"`
AclRuleId bool `json:"AclRuleId"`
}
// ApiSupport lists the supported API versions.
type ApiSupport struct {
V1 bool `json:"V1"`
V2 bool `json:"V2"`
}
// GetSupportedFeatures returns the features supported by the Service.
func GetSupportedFeatures() SupportedFeatures {
var features SupportedFeatures
globals, err := GetGlobals()
if err != nil {
// Expected on pre-1803 builds, all features will be false/unsupported
logrus.Debugf("Unable to obtain globals: %s", err)
return features
}
features.Acl = AclFeatures{
AclAddressLists: isFeatureSupported(globals.Version, HNSVersion1803),
AclNoHostRulePriority: isFeatureSupported(globals.Version, HNSVersion1803),
AclPortRanges: isFeatureSupported(globals.Version, HNSVersion1803),
AclRuleId: isFeatureSupported(globals.Version, HNSVersion1803),
}
features.Api = ApiSupport{
V2: isFeatureSupported(globals.Version, V2ApiSupport),
V1: true, // HNSCall is still available.
}
features.RemoteSubnet = isFeatureSupported(globals.Version, RemoteSubnetVersion)
features.HostRoute = isFeatureSupported(globals.Version, HostRouteVersion)
features.DSR = isFeatureSupported(globals.Version, DSRVersion)
return features
}
func isFeatureSupported(currentVersion Version, minVersionSupported Version) bool {
if currentVersion.Major < minVersionSupported.Major {
return false
}
if currentVersion.Major > minVersionSupported.Major {
return true
}
if currentVersion.Minor < minVersionSupported.Minor {
return false
}
return true
}

View File

@ -0,0 +1,62 @@
// +build integration
package hcn
import (
"encoding/json"
"fmt"
"testing"
)
func TestSupportedFeatures(t *testing.T) {
supportedFeatures := GetSupportedFeatures()
jsonString, err := json.Marshal(supportedFeatures)
if err != nil {
t.Fatal(err)
}
fmt.Printf("Supported Features:\n%s \n", jsonString)
}
func TestV2ApiSupport(t *testing.T) {
supportedFeatures := GetSupportedFeatures()
err := V2ApiSupported()
if supportedFeatures.Api.V2 && err != nil {
t.Fatal(err)
}
if !supportedFeatures.Api.V2 && err == nil {
t.Fatal(err)
}
}
func TestRemoteSubnetSupport(t *testing.T) {
supportedFeatures := GetSupportedFeatures()
err := RemoteSubnetSupported()
if supportedFeatures.RemoteSubnet && err != nil {
t.Fatal(err)
}
if !supportedFeatures.RemoteSubnet && err == nil {
t.Fatal(err)
}
}
func TestHostRouteSupport(t *testing.T) {
supportedFeatures := GetSupportedFeatures()
err := HostRouteSupported()
if supportedFeatures.HostRoute && err != nil {
t.Fatal(err)
}
if !supportedFeatures.HostRoute && err == nil {
t.Fatal(err)
}
}
func TestDSRSupport(t *testing.T) {
supportedFeatures := GetSupportedFeatures()
err := DSRSupported()
if supportedFeatures.DSR && err != nil {
t.Fatal(err)
}
if !supportedFeatures.DSR && err == nil {
t.Fatal(err)
}
}

View File

@ -0,0 +1,267 @@
// +build integration
package hcn
import (
"encoding/json"
)
func cleanup(networkName string) {
// Delete test network (if exists)
testNetwork, err := GetNetworkByName(networkName)
if err != nil {
return
}
if testNetwork != nil {
err := testNetwork.Delete()
if err != nil {
return
}
}
}
func HcnCreateTestNATNetwork() (*HostComputeNetwork, error) {
cleanup(NatTestNetworkName)
network := &HostComputeNetwork{
Type: "NAT",
Name: NatTestNetworkName,
MacPool: MacPool{
Ranges: []MacRange{
{
StartMacAddress: "00-15-5D-52-C0-00",
EndMacAddress: "00-15-5D-52-CF-FF",
},
},
},
Ipams: []Ipam{
{
Type: "Static",
Subnets: []Subnet{
{
IpAddressPrefix: "192.168.100.0/24",
Routes: []Route{
{
NextHop: "192.168.100.1",
DestinationPrefix: "0.0.0.0",
},
},
},
},
},
},
SchemaVersion: SchemaVersion{
Major: 2,
Minor: 0,
},
}
return network.Create()
}
func CreateTestOverlayNetwork() (*HostComputeNetwork, error) {
cleanup(OverlayTestNetworkName)
network := &HostComputeNetwork{
Type: "Overlay",
Name: OverlayTestNetworkName,
MacPool: MacPool{
Ranges: []MacRange{
{
StartMacAddress: "00-15-5D-52-C0-00",
EndMacAddress: "00-15-5D-52-CF-FF",
},
},
},
Ipams: []Ipam{
{
Type: "Static",
Subnets: []Subnet{
{
IpAddressPrefix: "192.168.100.0/24",
Routes: []Route{
{
NextHop: "192.168.100.1",
DestinationPrefix: "0.0.0.0/0",
},
},
},
},
},
},
Flags: EnableNonPersistent,
SchemaVersion: SchemaVersion{
Major: 2,
Minor: 0,
},
}
vsid := &VsidPolicySetting{
IsolationId: 5000,
}
vsidJson, err := json.Marshal(vsid)
if err != nil {
return nil, err
}
sp := &SubnetPolicy{
Type: VSID,
}
sp.Settings = vsidJson
spJson, err := json.Marshal(sp)
if err != nil {
return nil, err
}
network.Ipams[0].Subnets[0].Policies = append(network.Ipams[0].Subnets[0].Policies, spJson)
return network.Create()
}
func HcnCreateTestEndpoint(network *HostComputeNetwork) (*HostComputeEndpoint, error) {
if network == nil {
}
Endpoint := &HostComputeEndpoint{
Name: NatTestEndpointName,
SchemaVersion: SchemaVersion{
Major: 2,
Minor: 0,
},
}
return network.CreateEndpoint(Endpoint)
}
func HcnCreateTestEndpointWithNamespace(network *HostComputeNetwork, namespace *HostComputeNamespace) (*HostComputeEndpoint, error) {
Endpoint := &HostComputeEndpoint{
Name: NatTestEndpointName,
HostComputeNamespace: namespace.Id,
SchemaVersion: SchemaVersion{
Major: 2,
Minor: 0,
},
}
return network.CreateEndpoint(Endpoint)
}
func HcnCreateTestNamespace() (*HostComputeNamespace, error) {
namespace := &HostComputeNamespace{
Type: NamespaceTypeHostDefault,
NamespaceId: 5,
SchemaVersion: SchemaVersion{
Major: 2,
Minor: 0,
},
}
return namespace.Create()
}
func HcnCreateAcls() (*PolicyEndpointRequest, error) {
in := AclPolicySetting{
Protocols: "6",
Action: ActionTypeAllow,
Direction: DirectionTypeIn,
LocalAddresses: "192.168.100.0/24,10.0.0.21",
RemoteAddresses: "192.168.100.0/24,10.0.0.21",
LocalPorts: "80,8080",
RemotePorts: "80,8080",
RuleType: RuleTypeSwitch,
Priority: 200,
}
rawJSON, err := json.Marshal(in)
if err != nil {
return nil, err
}
inPolicy := EndpointPolicy{
Type: ACL,
Settings: rawJSON,
}
out := AclPolicySetting{
Protocols: "6",
Action: ActionTypeAllow,
Direction: DirectionTypeOut,
LocalAddresses: "192.168.100.0/24,10.0.0.21",
RemoteAddresses: "192.168.100.0/24,10.0.0.21",
LocalPorts: "80,8080",
RemotePorts: "80,8080",
RuleType: RuleTypeSwitch,
Priority: 200,
}
rawJSON, err = json.Marshal(out)
if err != nil {
return nil, err
}
outPolicy := EndpointPolicy{
Type: ACL,
Settings: rawJSON,
}
endpointRequest := PolicyEndpointRequest{
Policies: []EndpointPolicy{inPolicy, outPolicy},
}
return &endpointRequest, nil
}
func HcnCreateTestLoadBalancer(endpoint *HostComputeEndpoint) (*HostComputeLoadBalancer, error) {
loadBalancer := &HostComputeLoadBalancer{
HostComputeEndpoints: []string{endpoint.Id},
SourceVIP: "10.0.0.1",
PortMappings: []LoadBalancerPortMapping{
{
Protocol: 6, // TCP
InternalPort: 8080,
ExternalPort: 8090,
},
},
FrontendVIPs: []string{"1.1.1.2", "1.1.1.3"},
SchemaVersion: SchemaVersion{
Major: 2,
Minor: 0,
},
}
return loadBalancer.Create()
}
func HcnCreateTestRemoteSubnetRoute() (*PolicyNetworkRequest, error) {
rsr := RemoteSubnetRoutePolicySetting{
DestinationPrefix: "192.168.2.0/24",
IsolationId: 5000,
ProviderAddress: "1.1.1.1",
DistributedRouterMacAddress: "00-12-34-56-78-9a",
}
rawJSON, err := json.Marshal(rsr)
if err != nil {
return nil, err
}
rsrPolicy := NetworkPolicy{
Type: RemoteSubnetRoute,
Settings: rawJSON,
}
networkRequest := PolicyNetworkRequest{
Policies: []NetworkPolicy{rsrPolicy},
}
return &networkRequest, nil
}
func HcnCreateTestHostRoute() (*PolicyNetworkRequest, error) {
hostRoutePolicy := NetworkPolicy{
Type: HostRoute,
Settings: []byte("{}"),
}
networkRequest := PolicyNetworkRequest{
Policies: []NetworkPolicy{hostRoutePolicy},
}
return &networkRequest, nil
}

View File

@ -0,0 +1,111 @@
// +build integration
package hcn
import (
"encoding/json"
"testing"
"github.com/Microsoft/hcsshim"
)
func TestV1Network(t *testing.T) {
cleanup(NatTestNetworkName)
v1network := hcsshim.HNSNetwork{
Type: "NAT",
Name: NatTestNetworkName,
MacPools: []hcsshim.MacPool{
{
StartMacAddress: "00-15-5D-52-C0-00",
EndMacAddress: "00-15-5D-52-CF-FF",
},
},
Subnets: []hcsshim.Subnet{
{
AddressPrefix: "192.168.100.0/24",
GatewayAddress: "192.168.100.1",
},
},
}
jsonString, err := json.Marshal(v1network)
if err != nil {
t.Fatal(err)
t.Fail()
}
network, err := createNetwork(string(jsonString))
if err != nil {
t.Fatal(err)
t.Fail()
}
err = network.Delete()
if err != nil {
t.Fatal(err)
t.Fail()
}
}
func TestV1Endpoint(t *testing.T) {
cleanup(NatTestNetworkName)
v1network := hcsshim.HNSNetwork{
Type: "NAT",
Name: NatTestNetworkName,
MacPools: []hcsshim.MacPool{
{
StartMacAddress: "00-15-5D-52-C0-00",
EndMacAddress: "00-15-5D-52-CF-FF",
},
},
Subnets: []hcsshim.Subnet{
{
AddressPrefix: "192.168.100.0/24",
GatewayAddress: "192.168.100.1",
},
},
}
jsonString, err := json.Marshal(v1network)
if err != nil {
t.Fatal(err)
t.Fail()
}
network, err := createNetwork(string(jsonString))
if err != nil {
t.Fatal(err)
t.Fail()
}
v1endpoint := hcsshim.HNSEndpoint{
Name: NatTestEndpointName,
VirtualNetwork: network.Id,
}
jsonString, err = json.Marshal(v1endpoint)
if err != nil {
t.Fatal(err)
t.Fail()
}
endpoint, err := createEndpoint(network.Id, string(jsonString))
if err != nil {
t.Fatal(err)
t.Fail()
}
err = endpoint.Delete()
if err != nil {
t.Fatal(err)
t.Fail()
}
err = network.Delete()
if err != nil {
t.Fatal(err)
t.Fail()
}
}

97
vendor/github.com/Microsoft/hcsshim/hcn/hnsv1_test.go generated vendored Normal file
View File

@ -0,0 +1,97 @@
// +build integration
package hcn
import (
"os"
"testing"
"github.com/Microsoft/hcsshim"
)
const (
NatTestNetworkName string = "GoTestNat"
NatTestEndpointName string = "GoTestNatEndpoint"
OverlayTestNetworkName string = "GoTestOverlay"
)
func TestMain(m *testing.M) {
os.Exit(m.Run())
}
func CreateTestNetwork() (*hcsshim.HNSNetwork, error) {
network := &hcsshim.HNSNetwork{
Type: "NAT",
Name: NatTestNetworkName,
Subnets: []hcsshim.Subnet{
{
AddressPrefix: "192.168.100.0/24",
GatewayAddress: "192.168.100.1",
},
},
}
return network.Create()
}
func TestEndpoint(t *testing.T) {
network, err := CreateTestNetwork()
if err != nil {
t.Fatal(err)
}
Endpoint := &hcsshim.HNSEndpoint{
Name: NatTestEndpointName,
}
Endpoint, err = network.CreateEndpoint(Endpoint)
if err != nil {
t.Fatal(err)
}
err = Endpoint.HostAttach(1)
if err != nil {
t.Fatal(err)
}
err = Endpoint.HostDetach()
if err != nil {
t.Fatal(err)
}
_, err = Endpoint.Delete()
if err != nil {
t.Fatal(err)
}
_, err = network.Delete()
if err != nil {
t.Fatal(err)
}
}
func TestEndpointGetAll(t *testing.T) {
_, err := hcsshim.HNSListEndpointRequest()
if err != nil {
t.Fatal(err)
}
}
func TestNetworkGetAll(t *testing.T) {
_, err := hcsshim.HNSListNetworkRequest("GET", "", "")
if err != nil {
t.Fatal(err)
}
}
func TestNetwork(t *testing.T) {
network, err := CreateTestNetwork()
if err != nil {
t.Fatal(err)
}
_, err = network.Delete()
if err != nil {
t.Fatal(err)
}
}

View File

@ -0,0 +1,714 @@
// Code generated mksyscall_windows.exe DO NOT EDIT
package hcn
import (
"syscall"
"unsafe"
"golang.org/x/sys/windows"
)
var _ unsafe.Pointer
// Do the interface allocations only once for common
// Errno values.
const (
errnoERROR_IO_PENDING = 997
)
var (
errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING)
)
// errnoErr returns common boxed Errno values, to prevent
// allocations at runtime.
func errnoErr(e syscall.Errno) error {
switch e {
case 0:
return nil
case errnoERROR_IO_PENDING:
return errERROR_IO_PENDING
}
// TODO: add more here, after collecting data on the common
// error values see on Windows. (perhaps when running
// all.bat?)
return e
}
var (
modiphlpapi = windows.NewLazySystemDLL("iphlpapi.dll")
modvmcompute = windows.NewLazySystemDLL("vmcompute.dll")
modcomputenetwork = windows.NewLazySystemDLL("computenetwork.dll")
procSetCurrentThreadCompartmentId = modiphlpapi.NewProc("SetCurrentThreadCompartmentId")
procHNSCall = modvmcompute.NewProc("HNSCall")
procHcnEnumerateNetworks = modcomputenetwork.NewProc("HcnEnumerateNetworks")
procHcnCreateNetwork = modcomputenetwork.NewProc("HcnCreateNetwork")
procHcnOpenNetwork = modcomputenetwork.NewProc("HcnOpenNetwork")
procHcnModifyNetwork = modcomputenetwork.NewProc("HcnModifyNetwork")
procHcnQueryNetworkProperties = modcomputenetwork.NewProc("HcnQueryNetworkProperties")
procHcnDeleteNetwork = modcomputenetwork.NewProc("HcnDeleteNetwork")
procHcnCloseNetwork = modcomputenetwork.NewProc("HcnCloseNetwork")
procHcnEnumerateEndpoints = modcomputenetwork.NewProc("HcnEnumerateEndpoints")
procHcnCreateEndpoint = modcomputenetwork.NewProc("HcnCreateEndpoint")
procHcnOpenEndpoint = modcomputenetwork.NewProc("HcnOpenEndpoint")
procHcnModifyEndpoint = modcomputenetwork.NewProc("HcnModifyEndpoint")
procHcnQueryEndpointProperties = modcomputenetwork.NewProc("HcnQueryEndpointProperties")
procHcnDeleteEndpoint = modcomputenetwork.NewProc("HcnDeleteEndpoint")
procHcnCloseEndpoint = modcomputenetwork.NewProc("HcnCloseEndpoint")
procHcnEnumerateNamespaces = modcomputenetwork.NewProc("HcnEnumerateNamespaces")
procHcnCreateNamespace = modcomputenetwork.NewProc("HcnCreateNamespace")
procHcnOpenNamespace = modcomputenetwork.NewProc("HcnOpenNamespace")
procHcnModifyNamespace = modcomputenetwork.NewProc("HcnModifyNamespace")
procHcnQueryNamespaceProperties = modcomputenetwork.NewProc("HcnQueryNamespaceProperties")
procHcnDeleteNamespace = modcomputenetwork.NewProc("HcnDeleteNamespace")
procHcnCloseNamespace = modcomputenetwork.NewProc("HcnCloseNamespace")
procHcnEnumerateLoadBalancers = modcomputenetwork.NewProc("HcnEnumerateLoadBalancers")
procHcnCreateLoadBalancer = modcomputenetwork.NewProc("HcnCreateLoadBalancer")
procHcnOpenLoadBalancer = modcomputenetwork.NewProc("HcnOpenLoadBalancer")
procHcnModifyLoadBalancer = modcomputenetwork.NewProc("HcnModifyLoadBalancer")
procHcnQueryLoadBalancerProperties = modcomputenetwork.NewProc("HcnQueryLoadBalancerProperties")
procHcnDeleteLoadBalancer = modcomputenetwork.NewProc("HcnDeleteLoadBalancer")
procHcnCloseLoadBalancer = modcomputenetwork.NewProc("HcnCloseLoadBalancer")
procHcnOpenService = modcomputenetwork.NewProc("HcnOpenService")
procHcnRegisterServiceCallback = modcomputenetwork.NewProc("HcnRegisterServiceCallback")
procHcnUnregisterServiceCallback = modcomputenetwork.NewProc("HcnUnregisterServiceCallback")
procHcnCloseService = modcomputenetwork.NewProc("HcnCloseService")
)
func SetCurrentThreadCompartmentId(compartmentId uint32) (hr error) {
r0, _, _ := syscall.Syscall(procSetCurrentThreadCompartmentId.Addr(), 1, uintptr(compartmentId), 0, 0)
if int32(r0) < 0 {
if r0&0x1fff0000 == 0x00070000 {
r0 &= 0xffff
}
hr = syscall.Errno(r0)
}
return
}
func _hnsCall(method string, path string, object string, response **uint16) (hr error) {
var _p0 *uint16
_p0, hr = syscall.UTF16PtrFromString(method)
if hr != nil {
return
}
var _p1 *uint16
_p1, hr = syscall.UTF16PtrFromString(path)
if hr != nil {
return
}
var _p2 *uint16
_p2, hr = syscall.UTF16PtrFromString(object)
if hr != nil {
return
}
return __hnsCall(_p0, _p1, _p2, response)
}
func __hnsCall(method *uint16, path *uint16, object *uint16, response **uint16) (hr error) {
if hr = procHNSCall.Find(); hr != nil {
return
}
r0, _, _ := syscall.Syscall6(procHNSCall.Addr(), 4, uintptr(unsafe.Pointer(method)), uintptr(unsafe.Pointer(path)), uintptr(unsafe.Pointer(object)), uintptr(unsafe.Pointer(response)), 0, 0)
if int32(r0) < 0 {
if r0&0x1fff0000 == 0x00070000 {
r0 &= 0xffff
}
hr = syscall.Errno(r0)
}
return
}
func hcnEnumerateNetworks(query string, networks **uint16, result **uint16) (hr error) {
var _p0 *uint16
_p0, hr = syscall.UTF16PtrFromString(query)
if hr != nil {
return
}
return _hcnEnumerateNetworks(_p0, networks, result)
}
func _hcnEnumerateNetworks(query *uint16, networks **uint16, result **uint16) (hr error) {
if hr = procHcnEnumerateNetworks.Find(); hr != nil {
return
}
r0, _, _ := syscall.Syscall(procHcnEnumerateNetworks.Addr(), 3, uintptr(unsafe.Pointer(query)), uintptr(unsafe.Pointer(networks)), uintptr(unsafe.Pointer(result)))
if int32(r0) < 0 {
if r0&0x1fff0000 == 0x00070000 {
r0 &= 0xffff
}
hr = syscall.Errno(r0)
}
return
}
func hcnCreateNetwork(id *_guid, settings string, network *hcnNetwork, result **uint16) (hr error) {
var _p0 *uint16
_p0, hr = syscall.UTF16PtrFromString(settings)
if hr != nil {
return
}
return _hcnCreateNetwork(id, _p0, network, result)
}
func _hcnCreateNetwork(id *_guid, settings *uint16, network *hcnNetwork, result **uint16) (hr error) {
if hr = procHcnCreateNetwork.Find(); hr != nil {
return
}
r0, _, _ := syscall.Syscall6(procHcnCreateNetwork.Addr(), 4, uintptr(unsafe.Pointer(id)), uintptr(unsafe.Pointer(settings)), uintptr(unsafe.Pointer(network)), uintptr(unsafe.Pointer(result)), 0, 0)
if int32(r0) < 0 {
if r0&0x1fff0000 == 0x00070000 {
r0 &= 0xffff
}
hr = syscall.Errno(r0)
}
return
}
func hcnOpenNetwork(id *_guid, network *hcnNetwork, result **uint16) (hr error) {
if hr = procHcnOpenNetwork.Find(); hr != nil {
return
}
r0, _, _ := syscall.Syscall(procHcnOpenNetwork.Addr(), 3, uintptr(unsafe.Pointer(id)), uintptr(unsafe.Pointer(network)), uintptr(unsafe.Pointer(result)))
if int32(r0) < 0 {
if r0&0x1fff0000 == 0x00070000 {
r0 &= 0xffff
}
hr = syscall.Errno(r0)
}
return
}
func hcnModifyNetwork(network hcnNetwork, settings string, result **uint16) (hr error) {
var _p0 *uint16
_p0, hr = syscall.UTF16PtrFromString(settings)
if hr != nil {
return
}
return _hcnModifyNetwork(network, _p0, result)
}
func _hcnModifyNetwork(network hcnNetwork, settings *uint16, result **uint16) (hr error) {
if hr = procHcnModifyNetwork.Find(); hr != nil {
return
}
r0, _, _ := syscall.Syscall(procHcnModifyNetwork.Addr(), 3, uintptr(network), uintptr(unsafe.Pointer(settings)), uintptr(unsafe.Pointer(result)))
if int32(r0) < 0 {
if r0&0x1fff0000 == 0x00070000 {
r0 &= 0xffff
}
hr = syscall.Errno(r0)
}
return
}
func hcnQueryNetworkProperties(network hcnNetwork, query string, properties **uint16, result **uint16) (hr error) {
var _p0 *uint16
_p0, hr = syscall.UTF16PtrFromString(query)
if hr != nil {
return
}
return _hcnQueryNetworkProperties(network, _p0, properties, result)
}
func _hcnQueryNetworkProperties(network hcnNetwork, query *uint16, properties **uint16, result **uint16) (hr error) {
if hr = procHcnQueryNetworkProperties.Find(); hr != nil {
return
}
r0, _, _ := syscall.Syscall6(procHcnQueryNetworkProperties.Addr(), 4, uintptr(network), uintptr(unsafe.Pointer(query)), uintptr(unsafe.Pointer(properties)), uintptr(unsafe.Pointer(result)), 0, 0)
if int32(r0) < 0 {
if r0&0x1fff0000 == 0x00070000 {
r0 &= 0xffff
}
hr = syscall.Errno(r0)
}
return
}
func hcnDeleteNetwork(id *_guid, result **uint16) (hr error) {
if hr = procHcnDeleteNetwork.Find(); hr != nil {
return
}
r0, _, _ := syscall.Syscall(procHcnDeleteNetwork.Addr(), 2, uintptr(unsafe.Pointer(id)), uintptr(unsafe.Pointer(result)), 0)
if int32(r0) < 0 {
if r0&0x1fff0000 == 0x00070000 {
r0 &= 0xffff
}
hr = syscall.Errno(r0)
}
return
}
func hcnCloseNetwork(network hcnNetwork) (hr error) {
if hr = procHcnCloseNetwork.Find(); hr != nil {
return
}
r0, _, _ := syscall.Syscall(procHcnCloseNetwork.Addr(), 1, uintptr(network), 0, 0)
if int32(r0) < 0 {
if r0&0x1fff0000 == 0x00070000 {
r0 &= 0xffff
}
hr = syscall.Errno(r0)
}
return
}
func hcnEnumerateEndpoints(query string, endpoints **uint16, result **uint16) (hr error) {
var _p0 *uint16
_p0, hr = syscall.UTF16PtrFromString(query)
if hr != nil {
return
}
return _hcnEnumerateEndpoints(_p0, endpoints, result)
}
func _hcnEnumerateEndpoints(query *uint16, endpoints **uint16, result **uint16) (hr error) {
if hr = procHcnEnumerateEndpoints.Find(); hr != nil {
return
}
r0, _, _ := syscall.Syscall(procHcnEnumerateEndpoints.Addr(), 3, uintptr(unsafe.Pointer(query)), uintptr(unsafe.Pointer(endpoints)), uintptr(unsafe.Pointer(result)))
if int32(r0) < 0 {
if r0&0x1fff0000 == 0x00070000 {
r0 &= 0xffff
}
hr = syscall.Errno(r0)
}
return
}
func hcnCreateEndpoint(network hcnNetwork, id *_guid, settings string, endpoint *hcnEndpoint, result **uint16) (hr error) {
var _p0 *uint16
_p0, hr = syscall.UTF16PtrFromString(settings)
if hr != nil {
return
}
return _hcnCreateEndpoint(network, id, _p0, endpoint, result)
}
func _hcnCreateEndpoint(network hcnNetwork, id *_guid, settings *uint16, endpoint *hcnEndpoint, result **uint16) (hr error) {
if hr = procHcnCreateEndpoint.Find(); hr != nil {
return
}
r0, _, _ := syscall.Syscall6(procHcnCreateEndpoint.Addr(), 5, uintptr(network), uintptr(unsafe.Pointer(id)), uintptr(unsafe.Pointer(settings)), uintptr(unsafe.Pointer(endpoint)), uintptr(unsafe.Pointer(result)), 0)
if int32(r0) < 0 {
if r0&0x1fff0000 == 0x00070000 {
r0 &= 0xffff
}
hr = syscall.Errno(r0)
}
return
}
func hcnOpenEndpoint(id *_guid, endpoint *hcnEndpoint, result **uint16) (hr error) {
if hr = procHcnOpenEndpoint.Find(); hr != nil {
return
}
r0, _, _ := syscall.Syscall(procHcnOpenEndpoint.Addr(), 3, uintptr(unsafe.Pointer(id)), uintptr(unsafe.Pointer(endpoint)), uintptr(unsafe.Pointer(result)))
if int32(r0) < 0 {
if r0&0x1fff0000 == 0x00070000 {
r0 &= 0xffff
}
hr = syscall.Errno(r0)
}
return
}
func hcnModifyEndpoint(endpoint hcnEndpoint, settings string, result **uint16) (hr error) {
var _p0 *uint16
_p0, hr = syscall.UTF16PtrFromString(settings)
if hr != nil {
return
}
return _hcnModifyEndpoint(endpoint, _p0, result)
}
func _hcnModifyEndpoint(endpoint hcnEndpoint, settings *uint16, result **uint16) (hr error) {
if hr = procHcnModifyEndpoint.Find(); hr != nil {
return
}
r0, _, _ := syscall.Syscall(procHcnModifyEndpoint.Addr(), 3, uintptr(endpoint), uintptr(unsafe.Pointer(settings)), uintptr(unsafe.Pointer(result)))
if int32(r0) < 0 {
if r0&0x1fff0000 == 0x00070000 {
r0 &= 0xffff
}
hr = syscall.Errno(r0)
}
return
}
func hcnQueryEndpointProperties(endpoint hcnEndpoint, query string, properties **uint16, result **uint16) (hr error) {
var _p0 *uint16
_p0, hr = syscall.UTF16PtrFromString(query)
if hr != nil {
return
}
return _hcnQueryEndpointProperties(endpoint, _p0, properties, result)
}
func _hcnQueryEndpointProperties(endpoint hcnEndpoint, query *uint16, properties **uint16, result **uint16) (hr error) {
if hr = procHcnQueryEndpointProperties.Find(); hr != nil {
return
}
r0, _, _ := syscall.Syscall6(procHcnQueryEndpointProperties.Addr(), 4, uintptr(endpoint), uintptr(unsafe.Pointer(query)), uintptr(unsafe.Pointer(properties)), uintptr(unsafe.Pointer(result)), 0, 0)
if int32(r0) < 0 {
if r0&0x1fff0000 == 0x00070000 {
r0 &= 0xffff
}
hr = syscall.Errno(r0)
}
return
}
func hcnDeleteEndpoint(id *_guid, result **uint16) (hr error) {
if hr = procHcnDeleteEndpoint.Find(); hr != nil {
return
}
r0, _, _ := syscall.Syscall(procHcnDeleteEndpoint.Addr(), 2, uintptr(unsafe.Pointer(id)), uintptr(unsafe.Pointer(result)), 0)
if int32(r0) < 0 {
if r0&0x1fff0000 == 0x00070000 {
r0 &= 0xffff
}
hr = syscall.Errno(r0)
}
return
}
func hcnCloseEndpoint(endpoint hcnEndpoint) (hr error) {
if hr = procHcnCloseEndpoint.Find(); hr != nil {
return
}
r0, _, _ := syscall.Syscall(procHcnCloseEndpoint.Addr(), 1, uintptr(endpoint), 0, 0)
if int32(r0) < 0 {
if r0&0x1fff0000 == 0x00070000 {
r0 &= 0xffff
}
hr = syscall.Errno(r0)
}
return
}
func hcnEnumerateNamespaces(query string, namespaces **uint16, result **uint16) (hr error) {
var _p0 *uint16
_p0, hr = syscall.UTF16PtrFromString(query)
if hr != nil {
return
}
return _hcnEnumerateNamespaces(_p0, namespaces, result)
}
func _hcnEnumerateNamespaces(query *uint16, namespaces **uint16, result **uint16) (hr error) {
if hr = procHcnEnumerateNamespaces.Find(); hr != nil {
return
}
r0, _, _ := syscall.Syscall(procHcnEnumerateNamespaces.Addr(), 3, uintptr(unsafe.Pointer(query)), uintptr(unsafe.Pointer(namespaces)), uintptr(unsafe.Pointer(result)))
if int32(r0) < 0 {
if r0&0x1fff0000 == 0x00070000 {
r0 &= 0xffff
}
hr = syscall.Errno(r0)
}
return
}
func hcnCreateNamespace(id *_guid, settings string, namespace *hcnNamespace, result **uint16) (hr error) {
var _p0 *uint16
_p0, hr = syscall.UTF16PtrFromString(settings)
if hr != nil {
return
}
return _hcnCreateNamespace(id, _p0, namespace, result)
}
func _hcnCreateNamespace(id *_guid, settings *uint16, namespace *hcnNamespace, result **uint16) (hr error) {
if hr = procHcnCreateNamespace.Find(); hr != nil {
return
}
r0, _, _ := syscall.Syscall6(procHcnCreateNamespace.Addr(), 4, uintptr(unsafe.Pointer(id)), uintptr(unsafe.Pointer(settings)), uintptr(unsafe.Pointer(namespace)), uintptr(unsafe.Pointer(result)), 0, 0)
if int32(r0) < 0 {
if r0&0x1fff0000 == 0x00070000 {
r0 &= 0xffff
}
hr = syscall.Errno(r0)
}
return
}
func hcnOpenNamespace(id *_guid, namespace *hcnNamespace, result **uint16) (hr error) {
if hr = procHcnOpenNamespace.Find(); hr != nil {
return
}
r0, _, _ := syscall.Syscall(procHcnOpenNamespace.Addr(), 3, uintptr(unsafe.Pointer(id)), uintptr(unsafe.Pointer(namespace)), uintptr(unsafe.Pointer(result)))
if int32(r0) < 0 {
if r0&0x1fff0000 == 0x00070000 {
r0 &= 0xffff
}
hr = syscall.Errno(r0)
}
return
}
func hcnModifyNamespace(namespace hcnNamespace, settings string, result **uint16) (hr error) {
var _p0 *uint16
_p0, hr = syscall.UTF16PtrFromString(settings)
if hr != nil {
return
}
return _hcnModifyNamespace(namespace, _p0, result)
}
func _hcnModifyNamespace(namespace hcnNamespace, settings *uint16, result **uint16) (hr error) {
if hr = procHcnModifyNamespace.Find(); hr != nil {
return
}
r0, _, _ := syscall.Syscall(procHcnModifyNamespace.Addr(), 3, uintptr(namespace), uintptr(unsafe.Pointer(settings)), uintptr(unsafe.Pointer(result)))
if int32(r0) < 0 {
if r0&0x1fff0000 == 0x00070000 {
r0 &= 0xffff
}
hr = syscall.Errno(r0)
}
return
}
func hcnQueryNamespaceProperties(namespace hcnNamespace, query string, properties **uint16, result **uint16) (hr error) {
var _p0 *uint16
_p0, hr = syscall.UTF16PtrFromString(query)
if hr != nil {
return
}
return _hcnQueryNamespaceProperties(namespace, _p0, properties, result)
}
func _hcnQueryNamespaceProperties(namespace hcnNamespace, query *uint16, properties **uint16, result **uint16) (hr error) {
if hr = procHcnQueryNamespaceProperties.Find(); hr != nil {
return
}
r0, _, _ := syscall.Syscall6(procHcnQueryNamespaceProperties.Addr(), 4, uintptr(namespace), uintptr(unsafe.Pointer(query)), uintptr(unsafe.Pointer(properties)), uintptr(unsafe.Pointer(result)), 0, 0)
if int32(r0) < 0 {
if r0&0x1fff0000 == 0x00070000 {
r0 &= 0xffff
}
hr = syscall.Errno(r0)
}
return
}
func hcnDeleteNamespace(id *_guid, result **uint16) (hr error) {
if hr = procHcnDeleteNamespace.Find(); hr != nil {
return
}
r0, _, _ := syscall.Syscall(procHcnDeleteNamespace.Addr(), 2, uintptr(unsafe.Pointer(id)), uintptr(unsafe.Pointer(result)), 0)
if int32(r0) < 0 {
if r0&0x1fff0000 == 0x00070000 {
r0 &= 0xffff
}
hr = syscall.Errno(r0)
}
return
}
func hcnCloseNamespace(namespace hcnNamespace) (hr error) {
if hr = procHcnCloseNamespace.Find(); hr != nil {
return
}
r0, _, _ := syscall.Syscall(procHcnCloseNamespace.Addr(), 1, uintptr(namespace), 0, 0)
if int32(r0) < 0 {
if r0&0x1fff0000 == 0x00070000 {
r0 &= 0xffff
}
hr = syscall.Errno(r0)
}
return
}
func hcnEnumerateLoadBalancers(query string, loadBalancers **uint16, result **uint16) (hr error) {
var _p0 *uint16
_p0, hr = syscall.UTF16PtrFromString(query)
if hr != nil {
return
}
return _hcnEnumerateLoadBalancers(_p0, loadBalancers, result)
}
func _hcnEnumerateLoadBalancers(query *uint16, loadBalancers **uint16, result **uint16) (hr error) {
if hr = procHcnEnumerateLoadBalancers.Find(); hr != nil {
return
}
r0, _, _ := syscall.Syscall(procHcnEnumerateLoadBalancers.Addr(), 3, uintptr(unsafe.Pointer(query)), uintptr(unsafe.Pointer(loadBalancers)), uintptr(unsafe.Pointer(result)))
if int32(r0) < 0 {
if r0&0x1fff0000 == 0x00070000 {
r0 &= 0xffff
}
hr = syscall.Errno(r0)
}
return
}
func hcnCreateLoadBalancer(id *_guid, settings string, loadBalancer *hcnLoadBalancer, result **uint16) (hr error) {
var _p0 *uint16
_p0, hr = syscall.UTF16PtrFromString(settings)
if hr != nil {
return
}
return _hcnCreateLoadBalancer(id, _p0, loadBalancer, result)
}
func _hcnCreateLoadBalancer(id *_guid, settings *uint16, loadBalancer *hcnLoadBalancer, result **uint16) (hr error) {
if hr = procHcnCreateLoadBalancer.Find(); hr != nil {
return
}
r0, _, _ := syscall.Syscall6(procHcnCreateLoadBalancer.Addr(), 4, uintptr(unsafe.Pointer(id)), uintptr(unsafe.Pointer(settings)), uintptr(unsafe.Pointer(loadBalancer)), uintptr(unsafe.Pointer(result)), 0, 0)
if int32(r0) < 0 {
if r0&0x1fff0000 == 0x00070000 {
r0 &= 0xffff
}
hr = syscall.Errno(r0)
}
return
}
func hcnOpenLoadBalancer(id *_guid, loadBalancer *hcnLoadBalancer, result **uint16) (hr error) {
if hr = procHcnOpenLoadBalancer.Find(); hr != nil {
return
}
r0, _, _ := syscall.Syscall(procHcnOpenLoadBalancer.Addr(), 3, uintptr(unsafe.Pointer(id)), uintptr(unsafe.Pointer(loadBalancer)), uintptr(unsafe.Pointer(result)))
if int32(r0) < 0 {
if r0&0x1fff0000 == 0x00070000 {
r0 &= 0xffff
}
hr = syscall.Errno(r0)
}
return
}
func hcnModifyLoadBalancer(loadBalancer hcnLoadBalancer, settings string, result **uint16) (hr error) {
var _p0 *uint16
_p0, hr = syscall.UTF16PtrFromString(settings)
if hr != nil {
return
}
return _hcnModifyLoadBalancer(loadBalancer, _p0, result)
}
func _hcnModifyLoadBalancer(loadBalancer hcnLoadBalancer, settings *uint16, result **uint16) (hr error) {
if hr = procHcnModifyLoadBalancer.Find(); hr != nil {
return
}
r0, _, _ := syscall.Syscall(procHcnModifyLoadBalancer.Addr(), 3, uintptr(loadBalancer), uintptr(unsafe.Pointer(settings)), uintptr(unsafe.Pointer(result)))
if int32(r0) < 0 {
if r0&0x1fff0000 == 0x00070000 {
r0 &= 0xffff
}
hr = syscall.Errno(r0)
}
return
}
func hcnQueryLoadBalancerProperties(loadBalancer hcnLoadBalancer, query string, properties **uint16, result **uint16) (hr error) {
var _p0 *uint16
_p0, hr = syscall.UTF16PtrFromString(query)
if hr != nil {
return
}
return _hcnQueryLoadBalancerProperties(loadBalancer, _p0, properties, result)
}
func _hcnQueryLoadBalancerProperties(loadBalancer hcnLoadBalancer, query *uint16, properties **uint16, result **uint16) (hr error) {
if hr = procHcnQueryLoadBalancerProperties.Find(); hr != nil {
return
}
r0, _, _ := syscall.Syscall6(procHcnQueryLoadBalancerProperties.Addr(), 4, uintptr(loadBalancer), uintptr(unsafe.Pointer(query)), uintptr(unsafe.Pointer(properties)), uintptr(unsafe.Pointer(result)), 0, 0)
if int32(r0) < 0 {
if r0&0x1fff0000 == 0x00070000 {
r0 &= 0xffff
}
hr = syscall.Errno(r0)
}
return
}
func hcnDeleteLoadBalancer(id *_guid, result **uint16) (hr error) {
if hr = procHcnDeleteLoadBalancer.Find(); hr != nil {
return
}
r0, _, _ := syscall.Syscall(procHcnDeleteLoadBalancer.Addr(), 2, uintptr(unsafe.Pointer(id)), uintptr(unsafe.Pointer(result)), 0)
if int32(r0) < 0 {
if r0&0x1fff0000 == 0x00070000 {
r0 &= 0xffff
}
hr = syscall.Errno(r0)
}
return
}
func hcnCloseLoadBalancer(loadBalancer hcnLoadBalancer) (hr error) {
if hr = procHcnCloseLoadBalancer.Find(); hr != nil {
return
}
r0, _, _ := syscall.Syscall(procHcnCloseLoadBalancer.Addr(), 1, uintptr(loadBalancer), 0, 0)
if int32(r0) < 0 {
if r0&0x1fff0000 == 0x00070000 {
r0 &= 0xffff
}
hr = syscall.Errno(r0)
}
return
}
func hcnOpenService(service *hcnService, result **uint16) (hr error) {
if hr = procHcnOpenService.Find(); hr != nil {
return
}
r0, _, _ := syscall.Syscall(procHcnOpenService.Addr(), 2, uintptr(unsafe.Pointer(service)), uintptr(unsafe.Pointer(result)), 0)
if int32(r0) < 0 {
if r0&0x1fff0000 == 0x00070000 {
r0 &= 0xffff
}
hr = syscall.Errno(r0)
}
return
}
func hcnRegisterServiceCallback(service hcnService, callback int32, context int32, callbackHandle *hcnCallbackHandle) (hr error) {
if hr = procHcnRegisterServiceCallback.Find(); hr != nil {
return
}
r0, _, _ := syscall.Syscall6(procHcnRegisterServiceCallback.Addr(), 4, uintptr(service), uintptr(callback), uintptr(context), uintptr(unsafe.Pointer(callbackHandle)), 0, 0)
if int32(r0) < 0 {
if r0&0x1fff0000 == 0x00070000 {
r0 &= 0xffff
}
hr = syscall.Errno(r0)
}
return
}
func hcnUnregisterServiceCallback(callbackHandle hcnCallbackHandle) (hr error) {
if hr = procHcnUnregisterServiceCallback.Find(); hr != nil {
return
}
r0, _, _ := syscall.Syscall(procHcnUnregisterServiceCallback.Addr(), 1, uintptr(callbackHandle), 0, 0)
if int32(r0) < 0 {
if r0&0x1fff0000 == 0x00070000 {
r0 &= 0xffff
}
hr = syscall.Errno(r0)
}
return
}
func hcnCloseService(service hcnService) (hr error) {
if hr = procHcnCloseService.Find(); hr != nil {
return
}
r0, _, _ := syscall.Syscall(procHcnCloseService.Addr(), 1, uintptr(service), 0, 0)
if int32(r0) < 0 {
if r0&0x1fff0000 == 0x00070000 {
r0 &= 0xffff
}
hr = syscall.Errno(r0)
}
return
}

View File

@ -7,6 +7,9 @@ import (
// HNSEndpoint represents a network endpoint in HNS
type HNSEndpoint = hns.HNSEndpoint
// Namespace represents a Compartment.
type Namespace = hns.Namespace
//SystemType represents the type of the system on which actions are done
type SystemType string

View File

@ -17,6 +17,11 @@ type MappedPipe = schema1.MappedPipe
type HvRuntime = schema1.HvRuntime
type MappedVirtualDisk = schema1.MappedVirtualDisk
// AssignedDevice represents a device that has been directly assigned to a container
//
// NOTE: Support added in RS5
type AssignedDevice = schema1.AssignedDevice
// ContainerConfig is used as both the input of CreateContainer
// and to convert the parameters to JSON for passing onto the HCS
type ContainerConfig = schema1.ContainerConfig

View File

@ -0,0 +1,93 @@
// Package appargs provides argument validation routines for use with
// github.com/urfave/cli.
package appargs
import (
"errors"
"strconv"
"github.com/urfave/cli"
)
// Validator is an argument validator function. It returns the number of
// arguments consumed or -1 on error.
type Validator = func([]string) int
// String is a validator for strings.
func String(args []string) int {
if len(args) == 0 {
return -1
}
return 1
}
// NonEmptyString is a validator for non-empty strings.
func NonEmptyString(args []string) int {
if len(args) == 0 || args[0] == "" {
return -1
}
return 1
}
// Int returns a validator for integers.
func Int(base int, min int, max int) Validator {
return func(args []string) int {
if len(args) == 0 {
return -1
}
i, err := strconv.ParseInt(args[0], base, 0)
if err != nil || int(i) < min || int(i) > max {
return -1
}
return 1
}
}
// Optional returns a validator that treats an argument as optional.
func Optional(v Validator) Validator {
return func(args []string) int {
if len(args) == 0 {
return 0
}
return v(args)
}
}
// Rest returns a validator that validates each of the remaining arguments.
func Rest(v Validator) Validator {
return func(args []string) int {
count := len(args)
for len(args) != 0 {
n := v(args)
if n < 0 {
return n
}
args = args[n:]
}
return count
}
}
// ErrInvalidUsage is returned when there is a validation error.
var ErrInvalidUsage = errors.New("invalid command usage")
// Validate can be used as a command's Before function to validate the arguments
// to the command.
func Validate(vs ...Validator) cli.BeforeFunc {
return func(context *cli.Context) error {
remaining := context.Args()
for _, v := range vs {
consumed := v(remaining)
if consumed < 0 {
return ErrInvalidUsage
}
remaining = remaining[consumed:]
}
if len(remaining) > 0 {
return ErrInvalidUsage
}
return nil
}
}

View File

@ -0,0 +1,110 @@
package cni
import (
"errors"
"github.com/Microsoft/hcsshim/internal/guid"
"github.com/Microsoft/hcsshim/internal/regstate"
)
const (
cniRoot = "cni"
cniKey = "cfg"
)
// PersistedNamespaceConfig is the registry version of the `NamespaceID` to UVM
// map.
type PersistedNamespaceConfig struct {
namespaceID string
stored bool
ContainerID string
HostUniqueID guid.GUID
}
// NewPersistedNamespaceConfig creates an in-memory namespace config that can be
// persisted to the registry.
func NewPersistedNamespaceConfig(namespaceID, containerID string, containerHostUniqueID guid.GUID) *PersistedNamespaceConfig {
return &PersistedNamespaceConfig{
namespaceID: namespaceID,
ContainerID: containerID,
HostUniqueID: containerHostUniqueID,
}
}
// LoadPersistedNamespaceConfig loads a persisted config from the registry that matches
// `namespaceID`. If not found returns `regstate.NotFoundError`
func LoadPersistedNamespaceConfig(namespaceID string) (*PersistedNamespaceConfig, error) {
sk, err := regstate.Open(cniRoot, false)
if err != nil {
return nil, err
}
defer sk.Close()
pnc := PersistedNamespaceConfig{
namespaceID: namespaceID,
stored: true,
}
if err := sk.Get(namespaceID, cniKey, &pnc); err != nil {
return nil, err
}
return &pnc, nil
}
// Store stores or updates the in-memory config to its registry state. If the
// store failes returns the store error.
func (pnc *PersistedNamespaceConfig) Store() error {
if pnc.namespaceID == "" {
return errors.New("invalid namespaceID ''")
}
if pnc.ContainerID == "" {
return errors.New("invalid containerID ''")
}
empty := guid.GUID{}
if pnc.HostUniqueID == empty {
return errors.New("invalid containerHostUniqueID 'empy'")
}
sk, err := regstate.Open(cniRoot, false)
if err != nil {
return err
}
defer sk.Close()
if pnc.stored {
if err := sk.Set(pnc.namespaceID, cniKey, pnc); err != nil {
return err
}
} else {
if err := sk.Create(pnc.namespaceID, cniKey, pnc); err != nil {
return err
}
}
pnc.stored = true
return nil
}
// Remove removes any persisted state associated with this config. If the config
// is not found in the registery `Remove` returns no error.
func (pnc *PersistedNamespaceConfig) Remove() error {
if pnc.stored {
sk, err := regstate.Open(cniRoot, false)
if err != nil {
if regstate.IsNotFoundError(err) {
pnc.stored = false
return nil
}
return err
}
defer sk.Close()
if err := sk.Remove(pnc.namespaceID); err != nil {
if regstate.IsNotFoundError(err) {
pnc.stored = false
return nil
}
return err
}
}
pnc.stored = false
return nil
}

View File

@ -0,0 +1,137 @@
package cni
import (
"testing"
"github.com/Microsoft/hcsshim/internal/guid"
"github.com/Microsoft/hcsshim/internal/regstate"
)
func Test_LoadPersistedNamespaceConfig_NoConfig(t *testing.T) {
pnc, err := LoadPersistedNamespaceConfig(t.Name())
if pnc != nil {
t.Fatal("config should be nil")
}
if err == nil {
t.Fatal("err should be set")
} else {
if !regstate.IsNotFoundError(err) {
t.Fatal("err should be NotFoundError")
}
}
}
func Test_LoadPersistedNamespaceConfig_WithConfig(t *testing.T) {
pnc := NewPersistedNamespaceConfig(t.Name(), "test-container", guid.New())
err := pnc.Store()
if err != nil {
pnc.Remove()
t.Fatalf("store failed with: %v", err)
}
defer pnc.Remove()
pnc2, err := LoadPersistedNamespaceConfig(t.Name())
if err != nil {
t.Fatal("should have no error on stored config")
}
if pnc2 == nil {
t.Fatal("stored config should have been returned")
} else {
if pnc.namespaceID != pnc2.namespaceID {
t.Fatal("actual/stored namespaceID not equal")
}
if pnc.ContainerID != pnc2.ContainerID {
t.Fatal("actual/stored ContainerID not equal")
}
if pnc.HostUniqueID != pnc2.HostUniqueID {
t.Fatal("actual/stored HostUniqueID not equal")
}
if !pnc2.stored {
t.Fatal("stored should be true for registry load")
}
}
}
func Test_PersistedNamespaceConfig_StoreNew(t *testing.T) {
pnc := NewPersistedNamespaceConfig(t.Name(), "test-container", guid.New())
err := pnc.Store()
if err != nil {
pnc.Remove()
t.Fatalf("store failed with: %v", err)
}
defer pnc.Remove()
}
func Test_PersistedNamespaceConfig_StoreUpdate(t *testing.T) {
pnc := NewPersistedNamespaceConfig(t.Name(), "test-container", guid.New())
err := pnc.Store()
if err != nil {
pnc.Remove()
t.Fatalf("store failed with: %v", err)
}
defer pnc.Remove()
pnc.ContainerID = "test-container2"
pnc.HostUniqueID = guid.New()
err = pnc.Store()
if err != nil {
pnc.Remove()
t.Fatalf("store update failed with: %v", err)
}
// Verify the update
pnc2, err := LoadPersistedNamespaceConfig(t.Name())
if err != nil {
t.Fatal("stored config should have been returned")
}
if pnc.ContainerID != pnc2.ContainerID {
t.Fatal("actual/stored ContainerID not equal")
}
if pnc.HostUniqueID != pnc2.HostUniqueID {
t.Fatal("actual/stored HostUniqueID not equal")
}
}
func Test_PersistedNamespaceConfig_RemoveNotStored(t *testing.T) {
pnc := NewPersistedNamespaceConfig(t.Name(), "test-container", guid.New())
err := pnc.Remove()
if err != nil {
t.Fatalf("remove on not stored should not fail: %v", err)
}
}
func Test_PersistedNamespaceConfig_RemoveStoredKey(t *testing.T) {
pnc := NewPersistedNamespaceConfig(t.Name(), "test-container", guid.New())
err := pnc.Store()
if err != nil {
t.Fatalf("store failed with: %v", err)
}
err = pnc.Remove()
if err != nil {
t.Fatalf("remove on stored key should not fail: %v", err)
}
}
func Test_PersistedNamespaceConfig_RemovedOtherKey(t *testing.T) {
pnc := NewPersistedNamespaceConfig(t.Name(), "test-container", guid.New())
err := pnc.Store()
if err != nil {
t.Fatalf("store failed with: %v", err)
}
pnc2, err := LoadPersistedNamespaceConfig(t.Name())
if err != nil {
t.Fatal("should of found stored config")
}
err = pnc.Remove()
if err != nil {
t.Fatalf("remove on stored key should not fail: %v", err)
}
// Now remove the other key that has the invalid memory state
err = pnc2.Remove()
if err != nil {
t.Fatalf("remove on in-memory already removed should not fail: %v", err)
}
}

View File

@ -0,0 +1,40 @@
package copyfile
import (
"fmt"
"syscall"
"unsafe"
)
var (
modkernel32 = syscall.NewLazyDLL("kernel32.dll")
procCopyFileW = modkernel32.NewProc("CopyFileW")
)
// CopyFile is a utility for copying a file - used for the LCOW scratch cache.
// Uses CopyFileW win32 API for performance.
func CopyFile(srcFile, destFile string, overwrite bool) error {
var bFailIfExists uint32 = 1
if overwrite {
bFailIfExists = 0
}
lpExistingFileName, err := syscall.UTF16PtrFromString(srcFile)
if err != nil {
return err
}
lpNewFileName, err := syscall.UTF16PtrFromString(destFile)
if err != nil {
return err
}
r1, _, err := syscall.Syscall(
procCopyFileW.Addr(),
3,
uintptr(unsafe.Pointer(lpExistingFileName)),
uintptr(unsafe.Pointer(lpNewFileName)),
uintptr(bFailIfExists))
if r1 == 0 {
return fmt.Errorf("failed CopyFileW Win32 call from '%s' to '%s': %s", srcFile, destFile, err)
}
return nil
}

View File

@ -0,0 +1,103 @@
package copywithtimeout
import (
"bytes"
"encoding/hex"
"fmt"
"io"
"os"
"strconv"
"syscall"
"time"
"github.com/sirupsen/logrus"
)
// logDataByteCount is for an advanced debugging technique to allow
// data read/written to a processes stdio channels hex-dumped to the
// log when running at debug level or higher. It is controlled through
// the environment variable HCSSHIM_LOG_DATA_BYTE_COUNT
var logDataByteCount int64
func init() {
bytes := os.Getenv("HCSSHIM_LOG_DATA_BYTE_COUNT")
if len(bytes) > 0 {
u, err := strconv.ParseUint(bytes, 10, 32)
if err == nil {
logDataByteCount = int64(u)
}
}
}
// Copy is a wrapper for io.Copy using a timeout duration
func Copy(dst io.Writer, src io.Reader, size int64, context string, timeout time.Duration) (int64, error) {
logrus.WithFields(logrus.Fields{
"stdval": context,
"size": size,
"timeout": timeout,
}).Debug("hcsshim::copywithtimeout - Begin")
type resultType struct {
err error
bytes int64
}
done := make(chan resultType, 1)
go func() {
result := resultType{}
if logrus.GetLevel() < logrus.DebugLevel || logDataByteCount == 0 {
result.bytes, result.err = io.Copy(dst, src)
} else {
// In advanced debug mode where we log (hexdump format) what is copied
// up to the number of bytes defined by environment variable
// HCSSHIM_LOG_DATA_BYTE_COUNT
var buf bytes.Buffer
tee := io.TeeReader(src, &buf)
result.bytes, result.err = io.Copy(dst, tee)
if result.err == nil {
size := result.bytes
if size > logDataByteCount {
size = logDataByteCount
}
if size > 0 {
bytes := make([]byte, size)
if _, err := buf.Read(bytes); err == nil {
logrus.Debugf("hcsshim::copyWithTimeout - Read bytes\n%s", hex.Dump(bytes))
}
}
}
}
done <- result
}()
var result resultType
timedout := time.After(timeout)
select {
case <-timedout:
return 0, fmt.Errorf("hcsshim::copyWithTimeout: timed out (%s)", context)
case result = <-done:
if result.err != nil && result.err != io.EOF {
// See https://github.com/golang/go/blob/f3f29d1dea525f48995c1693c609f5e67c046893/src/os/exec/exec_windows.go for a clue as to why we are doing this :)
if se, ok := result.err.(syscall.Errno); ok {
const (
errNoData = syscall.Errno(232)
errBrokenPipe = syscall.Errno(109)
)
if se == errNoData || se == errBrokenPipe {
logrus.WithFields(logrus.Fields{
"stdval": context,
logrus.ErrorKey: se,
}).Debug("hcsshim::copywithtimeout - End")
return result.bytes, nil
}
}
return 0, fmt.Errorf("hcsshim::copyWithTimeout: error reading: '%s' after %d bytes (%s)", result.err, result.bytes, context)
}
}
logrus.WithFields(logrus.Fields{
"stdval": context,
"copied-bytes": result.bytes,
}).Debug("hcsshim::copywithtimeout - Completed Successfully")
return result.bytes, nil
}

View File

@ -0,0 +1,100 @@
package guestrequest
import (
"github.com/Microsoft/hcsshim/internal/schema2"
)
// Arguably, many of these (at least CombinedLayers) should have been generated
// by swagger.
//
// This will also change package name due to an inbound breaking change.
// This class is used by a modify request to add or remove a combined layers
// structure in the guest. For windows, the GCS applies a filter in ContainerRootPath
// using the specified layers as the parent content. Ignores property ScratchPath
// since the container path is already the scratch path. For linux, the GCS unions
// the specified layers and ScratchPath together, placing the resulting union
// filesystem at ContainerRootPath.
type CombinedLayers struct {
ContainerRootPath string `json:"ContainerRootPath,omitempty"`
Layers []hcsschema.Layer `json:"Layers,omitempty"`
ScratchPath string `json:"ScratchPath,omitempty"`
}
// Defines the schema for hosted settings passed to GCS and/or OpenGCS
// SCSI. Scratch space for remote file-system commands, or R/W layer for containers
type LCOWMappedVirtualDisk struct {
MountPath string `json:"MountPath,omitempty"` // /tmp/scratch for an LCOW utility VM being used as a service VM
Lun uint8 `json:"Lun,omitempty"`
Controller uint8 `json:"Controller,omitempty"`
ReadOnly bool `json:"ReadOnly,omitempty"`
}
type WCOWMappedVirtualDisk struct {
ContainerPath string `json:"ContainerPath,omitempty"`
Lun int32 `json:"Lun,omitempty"`
}
type LCOWMappedDirectory struct {
MountPath string `json:"MountPath,omitempty"`
Port int32 `json:"Port,omitempty"`
ShareName string `json:"ShareName,omitempty"` // If empty not using ANames (not currently supported)
ReadOnly bool `json:"ReadOnly,omitempty"`
}
// Read-only layers over VPMem
type LCOWMappedVPMemDevice struct {
DeviceNumber uint32 `json:"DeviceNumber,omitempty"`
MountPath string `json:"MountPath,omitempty"` // /tmp/pN
}
type LCOWNetworkAdapter struct {
NamespaceID string `json:",omitempty"`
ID string `json:",omitempty"`
MacAddress string `json:",omitempty"`
IPAddress string `json:",omitempty"`
PrefixLength uint8 `json:",omitempty"`
GatewayAddress string `json:",omitempty"`
DNSSuffix string `json:",omitempty"`
DNSServerList string `json:",omitempty"`
EnableLowMetric bool `json:",omitempty"`
EncapOverhead uint16 `json:",omitempty"`
}
type ResourceType string
const (
// These are constants for v2 schema modify guest requests.
ResourceTypeMappedDirectory ResourceType = "MappedDirectory"
ResourceTypeMappedVirtualDisk ResourceType = "MappedVirtualDisk"
ResourceTypeNetwork ResourceType = "Network"
ResourceTypeNetworkNamespace ResourceType = "NetworkNamespace"
ResourceTypeCombinedLayers ResourceType = "CombinedLayers"
ResourceTypeVPMemDevice ResourceType = "VPMemDevice"
)
// GuestRequest is for modify commands passed to the guest.
type GuestRequest struct {
RequestType string `json:"RequestType,omitempty"`
ResourceType ResourceType `json:"ResourceType,omitempty"`
Settings interface{} `json:"Settings,omitempty"`
}
type NetworkModifyRequest struct {
AdapterId string `json:"AdapterId,omitempty"`
RequestType string `json:"RequestType,omitempty"`
Settings interface{} `json:"Settings,omitempty"`
}
type RS4NetworkModifyRequest struct {
AdapterInstanceId string `json:"AdapterInstanceId,omitempty"`
RequestType string `json:"RequestType,omitempty"`
Settings interface{} `json:"Settings,omitempty"`
}
// SignalProcessOptions is the options passed to either WCOW or LCOW
// to signal a given process.
type SignalProcessOptions struct {
Signal int `json:,omitempty`
}

View File

@ -0,0 +1,136 @@
package guid
import (
"encoding/json"
"fmt"
"testing"
)
func Test_New(t *testing.T) {
g := New()
g2 := New()
if g == g2 {
t.Fatal("GUID's should not be equal when generated")
}
}
func Test_FromString(t *testing.T) {
g := New()
g2 := FromString(g.String())
if g != g2 {
t.Fatalf("GUID's not equal %v, %v", g, g2)
}
}
func Test_MarshalJSON(t *testing.T) {
g := New()
gs := g.String()
js, err := json.Marshal(g)
if err != nil {
t.Fatalf("failed to marshal with %v", err)
}
gsJSON := fmt.Sprintf("\"%s\"", gs)
if gsJSON != string(js) {
t.Fatalf("failed to marshal %s != %s", gsJSON, string(js))
}
}
func Test_MarshalJSON_Ptr(t *testing.T) {
g := New()
gs := g.String()
js, err := json.Marshal(&g)
if err != nil {
t.Fatalf("failed to marshal with %v", err)
}
gsJSON := fmt.Sprintf("\"%s\"", gs)
if gsJSON != string(js) {
t.Fatalf("failed to marshal %s != %s", gsJSON, string(js))
}
}
func Test_MarshalJSON_Nested(t *testing.T) {
type test struct {
G GUID
}
t1 := test{
G: New(),
}
gs := t1.G.String()
js, err := json.Marshal(t1)
if err != nil {
t.Fatalf("failed to marshal with %v", err)
}
gsJSON := fmt.Sprintf("{\"G\":\"%s\"}", gs)
if gsJSON != string(js) {
t.Fatalf("failed to marshal %s != %s", gsJSON, string(js))
}
}
func Test_MarshalJSON_Nested_Ptr(t *testing.T) {
type test struct {
G *GUID
}
v := New()
t1 := test{
G: &v,
}
gs := t1.G.String()
js, err := json.Marshal(t1)
if err != nil {
t.Fatalf("failed to marshal with %v", err)
}
gsJSON := fmt.Sprintf("{\"G\":\"%s\"}", gs)
if gsJSON != string(js) {
t.Fatalf("failed to marshal %s != %s", gsJSON, string(js))
}
}
func Test_UnmarshalJSON(t *testing.T) {
g := New()
js, _ := json.Marshal(g)
var g2 GUID
err := json.Unmarshal(js, &g2)
if err != nil {
t.Fatalf("failed to unmarshal with: %v", err)
}
if g != g2 {
t.Fatalf("failed to unmarshal %s != %s", g, g2)
}
}
func Test_UnmarshalJSON_Nested(t *testing.T) {
type test struct {
G GUID
}
t1 := test{
G: New(),
}
js, _ := json.Marshal(t1)
var t2 test
err := json.Unmarshal(js, &t2)
if err != nil {
t.Fatalf("failed to unmarshal with: %v", err)
}
if t1.G != t2.G {
t.Fatalf("failed to unmarshal %v != %v", t1.G, t2.G)
}
}
func Test_UnmarshalJSON_Nested_Ptr(t *testing.T) {
type test struct {
G *GUID
}
v := New()
t1 := test{
G: &v,
}
js, _ := json.Marshal(t1)
var t2 test
err := json.Unmarshal(js, &t2)
if err != nil {
t.Fatalf("failed to unmarshal with: %v", err)
}
if *t1.G != *t2.G {
t.Fatalf("failed to unmarshal %v != %v", t1.G, t2.G)
}
}

View File

@ -5,6 +5,7 @@ import (
"syscall"
"github.com/Microsoft/hcsshim/internal/interop"
"github.com/sirupsen/logrus"
)
var (
@ -20,6 +21,15 @@ var (
hcsNotificationSystemStartCompleted hcsNotification = 0x00000003
hcsNotificationSystemPauseCompleted hcsNotification = 0x00000004
hcsNotificationSystemResumeCompleted hcsNotification = 0x00000005
hcsNotificationSystemCrashReport hcsNotification = 0x00000006
hcsNotificationSystemSiloJobCreated hcsNotification = 0x00000007
hcsNotificationSystemSaveCompleted hcsNotification = 0x00000008
hcsNotificationSystemRdpEnhancedModeStateChanged hcsNotification = 0x00000009
hcsNotificationSystemShutdownFailed hcsNotification = 0x0000000A
hcsNotificationSystemGetPropertiesCompleted hcsNotification = 0x0000000B
hcsNotificationSystemModifyCompleted hcsNotification = 0x0000000C
hcsNotificationSystemCrashInitiated hcsNotification = 0x0000000D
hcsNotificationSystemGuestConnectionClosed hcsNotification = 0x0000000E
// Notifications for HCS_PROCESS handles
hcsNotificationProcessExited hcsNotification = 0x00010000
@ -49,16 +59,23 @@ func newChannels() notificationChannels {
channels[hcsNotificationSystemResumeCompleted] = make(notificationChannel, 1)
channels[hcsNotificationProcessExited] = make(notificationChannel, 1)
channels[hcsNotificationServiceDisconnect] = make(notificationChannel, 1)
channels[hcsNotificationSystemCrashReport] = make(notificationChannel, 1)
channels[hcsNotificationSystemSiloJobCreated] = make(notificationChannel, 1)
channels[hcsNotificationSystemSaveCompleted] = make(notificationChannel, 1)
channels[hcsNotificationSystemRdpEnhancedModeStateChanged] = make(notificationChannel, 1)
channels[hcsNotificationSystemShutdownFailed] = make(notificationChannel, 1)
channels[hcsNotificationSystemGetPropertiesCompleted] = make(notificationChannel, 1)
channels[hcsNotificationSystemModifyCompleted] = make(notificationChannel, 1)
channels[hcsNotificationSystemCrashInitiated] = make(notificationChannel, 1)
channels[hcsNotificationSystemGuestConnectionClosed] = make(notificationChannel, 1)
return channels
}
func closeChannels(channels notificationChannels) {
close(channels[hcsNotificationSystemExited])
close(channels[hcsNotificationSystemCreateCompleted])
close(channels[hcsNotificationSystemStartCompleted])
close(channels[hcsNotificationSystemPauseCompleted])
close(channels[hcsNotificationSystemResumeCompleted])
close(channels[hcsNotificationProcessExited])
close(channels[hcsNotificationServiceDisconnect])
for _, c := range channels {
close(c)
}
}
func notificationWatcher(notificationType hcsNotification, callbackNumber uintptr, notificationStatus uintptr, notificationData *uint16) uintptr {
@ -75,7 +92,13 @@ func notificationWatcher(notificationType hcsNotification, callbackNumber uintpt
return 0
}
context.channels[notificationType] <- result
if channel, ok := context.channels[notificationType]; ok {
channel <- result
} else {
logrus.WithFields(logrus.Fields{
"notification-type": notificationType,
}).Warn("Received a callback of an unsupported type")
}
return 0
}

View File

@ -7,6 +7,7 @@ import (
"syscall"
"github.com/Microsoft/hcsshim/internal/interop"
"github.com/Microsoft/hcsshim/internal/logfields"
"github.com/sirupsen/logrus"
)
@ -72,6 +73,9 @@ var (
// ErrVmcomputeUnknownMessage is an error encountered guest compute system doesn't support the message
ErrVmcomputeUnknownMessage = syscall.Errno(0xc037010b)
// ErrVmcomputeUnexpectedExit is an error encountered when the compute system terminates unexpectedly
ErrVmcomputeUnexpectedExit = syscall.Errno(0xC0370106)
// ErrNotSupported is an error encountered when hcs doesn't support the request
ErrPlatformNotSupported = errors.New("unsupported platform request")
)
@ -116,10 +120,14 @@ func (ev *ErrorEvent) String() string {
func processHcsResult(resultp *uint16) []ErrorEvent {
if resultp != nil {
resultj := interop.ConvertAndFreeCoTaskMemString(resultp)
logrus.Debugf("Result: %s", resultj)
logrus.WithField(logfields.JSON, resultj).
Debug("HCS Result")
result := &hcsResult{}
if err := json.Unmarshal([]byte(resultj), result); err != nil {
logrus.Warnf("Could not unmarshal HCS result %s: %s", resultj, err)
logrus.WithFields(logrus.Fields{
logfields.JSON: resultj,
logrus.ErrorKey: err,
}).Warning("Could not unmarshal HCS result")
return nil
}
return result.ErrorEvents

View File

@ -27,6 +27,7 @@ import (
//sys hcsOpenProcess(computeSystem hcsSystem, pid uint32, process *hcsProcess, result **uint16) (hr error) = vmcompute.HcsOpenProcess?
//sys hcsCloseProcess(process hcsProcess) (hr error) = vmcompute.HcsCloseProcess?
//sys hcsTerminateProcess(process hcsProcess, result **uint16) (hr error) = vmcompute.HcsTerminateProcess?
//sys hcsSignalProcess(process hcsProcess, options string, result **uint16) (hr error) = vmcompute.HcsTerminateProcess?
//sys hcsGetProcessInfo(process hcsProcess, processInformation *hcsProcessInformation, result **uint16) (hr error) = vmcompute.HcsGetProcessInfo?
//sys hcsGetProcessProperties(process hcsProcess, processProperties **uint16, result **uint16) (hr error) = vmcompute.HcsGetProcessProperties?
//sys hcsModifyProcess(process hcsProcess, settings string, result **uint16) (hr error) = vmcompute.HcsModifyProcess?

View File

@ -0,0 +1,15 @@
package hcs
import "github.com/sirupsen/logrus"
func logOperationBegin(ctx logrus.Fields, msg string) {
logrus.WithFields(ctx).Debug(msg)
}
func logOperationEnd(ctx logrus.Fields, msg string, err error) {
if err == nil {
logrus.WithFields(ctx).Debug(msg)
} else {
logrus.WithFields(ctx).WithError(err).Error(msg)
}
}

View File

@ -2,13 +2,14 @@ package hcs
import (
"encoding/json"
"fmt"
"io"
"sync"
"syscall"
"time"
"github.com/Microsoft/hcsshim/internal/guestrequest"
"github.com/Microsoft/hcsshim/internal/interop"
"github.com/Microsoft/hcsshim/internal/logfields"
"github.com/sirupsen/logrus"
)
@ -20,6 +21,21 @@ type Process struct {
system *System
cachedPipes *cachedPipes
callbackNumber uintptr
logctx logrus.Fields
}
func newProcess(process hcsProcess, processID int, computeSystem *System) *Process {
return &Process{
handle: process,
processID: processID,
system: computeSystem,
logctx: logrus.Fields{
logfields.HCSOperation: "",
logfields.ContainerID: computeSystem.ID(),
logfields.ProcessID: processID,
},
}
}
type cachedPipes struct {
@ -71,70 +87,122 @@ func (process *Process) SystemID() string {
return process.system.ID()
}
// Kill signals the process to terminate but does not wait for it to finish terminating.
func (process *Process) Kill() error {
func (process *Process) logOperationBegin(operation string) {
process.logctx[logfields.HCSOperation] = operation
logOperationBegin(
process.logctx,
"hcsshim::Process - Begin Operation")
}
func (process *Process) logOperationEnd(err error) {
var result string
if err == nil {
result = "Success"
} else {
result = "Error"
}
logOperationEnd(
process.logctx,
"hcsshim::Process - End Operation - "+result,
err)
process.logctx[logfields.HCSOperation] = ""
}
// Signal signals the process with `options`.
func (process *Process) Signal(options guestrequest.SignalProcessOptions) (err error) {
process.handleLock.RLock()
defer process.handleLock.RUnlock()
operation := "Kill"
title := "hcsshim::Process::" + operation
logrus.Debugf(title+" processid=%d", process.processID)
operation := "hcsshim::Process::Signal"
process.logOperationBegin(operation)
defer func() { process.logOperationEnd(err) }()
if process.handle == 0 {
return makeProcessError(process, operation, ErrAlreadyClosed, nil)
}
optionsb, err := json.Marshal(options)
if err != nil {
return err
}
optionsStr := string(optionsb)
var resultp *uint16
syscallWatcher(process.logctx, func() {
err = hcsSignalProcess(process.handle, optionsStr, &resultp)
})
events := processHcsResult(resultp)
if err != nil {
return makeProcessError(process, operation, err, events)
}
return nil
}
// Kill signals the process to terminate but does not wait for it to finish terminating.
func (process *Process) Kill() (err error) {
process.handleLock.RLock()
defer process.handleLock.RUnlock()
operation := "hcsshim::Process::Kill"
process.logOperationBegin(operation)
defer func() { process.logOperationEnd(err) }()
if process.handle == 0 {
return makeProcessError(process, operation, ErrAlreadyClosed, nil)
}
var resultp *uint16
completed := false
go syscallWatcher(fmt.Sprintf("TerminateProcess %s: %d", process.SystemID(), process.Pid()), &completed)
err := hcsTerminateProcess(process.handle, &resultp)
completed = true
syscallWatcher(process.logctx, func() {
err = hcsTerminateProcess(process.handle, &resultp)
})
events := processHcsResult(resultp)
if err != nil {
return makeProcessError(process, operation, err, events)
}
logrus.Debugf(title+" succeeded processid=%d", process.processID)
return nil
}
// Wait waits for the process to exit.
func (process *Process) Wait() error {
operation := "Wait"
title := "hcsshim::Process::" + operation
logrus.Debugf(title+" processid=%d", process.processID)
func (process *Process) Wait() (err error) {
operation := "hcsshim::Process::Wait"
process.logOperationBegin(operation)
defer func() { process.logOperationEnd(err) }()
err := waitForNotification(process.callbackNumber, hcsNotificationProcessExited, nil)
err = waitForNotification(process.callbackNumber, hcsNotificationProcessExited, nil)
if err != nil {
return makeProcessError(process, operation, err, nil)
}
logrus.Debugf(title+" succeeded processid=%d", process.processID)
return nil
}
// WaitTimeout waits for the process to exit or the duration to elapse. It returns
// false if timeout occurs.
func (process *Process) WaitTimeout(timeout time.Duration) error {
operation := "WaitTimeout"
title := "hcsshim::Process::" + operation
logrus.Debugf(title+" processid=%d", process.processID)
func (process *Process) WaitTimeout(timeout time.Duration) (err error) {
operation := "hcssshim::Process::WaitTimeout"
process.logOperationBegin(operation)
defer func() { process.logOperationEnd(err) }()
err := waitForNotification(process.callbackNumber, hcsNotificationProcessExited, &timeout)
err = waitForNotification(process.callbackNumber, hcsNotificationProcessExited, &timeout)
if err != nil {
return makeProcessError(process, operation, err, nil)
}
logrus.Debugf(title+" succeeded processid=%d", process.processID)
return nil
}
// ResizeConsole resizes the console of the process.
func (process *Process) ResizeConsole(width, height uint16) error {
func (process *Process) ResizeConsole(width, height uint16) (err error) {
process.handleLock.RLock()
defer process.handleLock.RUnlock()
operation := "ResizeConsole"
title := "hcsshim::Process::" + operation
logrus.Debugf(title+" processid=%d", process.processID)
operation := "hcsshim::Process::ResizeConsole"
process.logOperationBegin(operation)
defer func() { process.logOperationEnd(err) }()
if process.handle == 0 {
return makeProcessError(process, operation, ErrAlreadyClosed, nil)
@ -162,16 +230,16 @@ func (process *Process) ResizeConsole(width, height uint16) error {
return makeProcessError(process, operation, err, events)
}
logrus.Debugf(title+" succeeded processid=%d", process.processID)
return nil
}
func (process *Process) Properties() (*ProcessStatus, error) {
func (process *Process) Properties() (_ *ProcessStatus, err error) {
process.handleLock.RLock()
defer process.handleLock.RUnlock()
operation := "Properties"
title := "hcsshim::Process::" + operation
logrus.Debugf(title+" processid=%d", process.processID)
operation := "hcsshim::Process::Properties"
process.logOperationBegin(operation)
defer func() { process.logOperationEnd(err) }()
if process.handle == 0 {
return nil, makeProcessError(process, operation, ErrAlreadyClosed, nil)
@ -181,10 +249,9 @@ func (process *Process) Properties() (*ProcessStatus, error) {
resultp *uint16
propertiesp *uint16
)
completed := false
go syscallWatcher(fmt.Sprintf("GetProcessProperties %s: %d", process.SystemID(), process.Pid()), &completed)
err := hcsGetProcessProperties(process.handle, &propertiesp, &resultp)
completed = true
syscallWatcher(process.logctx, func() {
err = hcsGetProcessProperties(process.handle, &propertiesp, &resultp)
})
events := processHcsResult(resultp)
if err != nil {
return nil, makeProcessError(process, operation, err, events)
@ -200,14 +267,16 @@ func (process *Process) Properties() (*ProcessStatus, error) {
return nil, makeProcessError(process, operation, err, nil)
}
logrus.Debugf(title+" succeeded processid=%d, properties=%s", process.processID, propertiesRaw)
return properties, nil
}
// ExitCode returns the exit code of the process. The process must have
// already terminated.
func (process *Process) ExitCode() (int, error) {
operation := "ExitCode"
func (process *Process) ExitCode() (_ int, err error) {
operation := "hcsshim::Process::ExitCode"
process.logOperationBegin(operation)
defer func() { process.logOperationEnd(err) }()
properties, err := process.Properties()
if err != nil {
return 0, makeProcessError(process, operation, err, nil)
@ -227,12 +296,13 @@ func (process *Process) ExitCode() (int, error) {
// Stdio returns the stdin, stdout, and stderr pipes, respectively. Closing
// these pipes does not close the underlying pipes; it should be possible to
// call this multiple times to get multiple interfaces.
func (process *Process) Stdio() (io.WriteCloser, io.ReadCloser, io.ReadCloser, error) {
func (process *Process) Stdio() (_ io.WriteCloser, _ io.ReadCloser, _ io.ReadCloser, err error) {
process.handleLock.RLock()
defer process.handleLock.RUnlock()
operation := "Stdio"
title := "hcsshim::Process::" + operation
logrus.Debugf(title+" processid=%d", process.processID)
operation := "hcsshim::Process::Stdio"
process.logOperationBegin(operation)
defer func() { process.logOperationEnd(err) }()
if process.handle == 0 {
return nil, nil, nil, makeProcessError(process, operation, ErrAlreadyClosed, nil)
@ -245,7 +315,7 @@ func (process *Process) Stdio() (io.WriteCloser, io.ReadCloser, io.ReadCloser, e
processInfo hcsProcessInformation
resultp *uint16
)
err := hcsGetProcessInfo(process.handle, &processInfo, &resultp)
err = hcsGetProcessInfo(process.handle, &processInfo, &resultp)
events := processHcsResult(resultp)
if err != nil {
return nil, nil, nil, makeProcessError(process, operation, err, events)
@ -265,18 +335,18 @@ func (process *Process) Stdio() (io.WriteCloser, io.ReadCloser, io.ReadCloser, e
return nil, nil, nil, makeProcessError(process, operation, err, nil)
}
logrus.Debugf(title+" succeeded processid=%d", process.processID)
return pipes[0], pipes[1], pipes[2], nil
}
// CloseStdin closes the write side of the stdin pipe so that the process is
// notified on the read side that there is no more data in stdin.
func (process *Process) CloseStdin() error {
func (process *Process) CloseStdin() (err error) {
process.handleLock.RLock()
defer process.handleLock.RUnlock()
operation := "CloseStdin"
title := "hcsshim::Process::" + operation
logrus.Debugf(title+" processid=%d", process.processID)
operation := "hcsshim::Process::CloseStdin"
process.logOperationBegin(operation)
defer func() { process.logOperationEnd(err) }()
if process.handle == 0 {
return makeProcessError(process, operation, ErrAlreadyClosed, nil)
@ -303,35 +373,34 @@ func (process *Process) CloseStdin() error {
return makeProcessError(process, operation, err, events)
}
logrus.Debugf(title+" succeeded processid=%d", process.processID)
return nil
}
// Close cleans up any state associated with the process but does not kill
// or wait on it.
func (process *Process) Close() error {
func (process *Process) Close() (err error) {
process.handleLock.Lock()
defer process.handleLock.Unlock()
operation := "Close"
title := "hcsshim::Process::" + operation
logrus.Debugf(title+" processid=%d", process.processID)
operation := "hcsshim::Process::Close"
process.logOperationBegin(operation)
defer func() { process.logOperationEnd(err) }()
// Don't double free this
if process.handle == 0 {
return nil
}
if err := process.unregisterCallback(); err != nil {
if err = process.unregisterCallback(); err != nil {
return makeProcessError(process, operation, err, nil)
}
if err := hcsCloseProcess(process.handle); err != nil {
if err = hcsCloseProcess(process.handle); err != nil {
return makeProcessError(process, operation, err, nil)
}
process.handle = 0
logrus.Debugf(title+" succeeded processid=%d", process.processID)
return nil
}

View File

@ -2,7 +2,6 @@ package hcs
import (
"encoding/json"
"fmt"
"os"
"strconv"
"sync"
@ -10,6 +9,7 @@ import (
"time"
"github.com/Microsoft/hcsshim/internal/interop"
"github.com/Microsoft/hcsshim/internal/logfields"
"github.com/Microsoft/hcsshim/internal/schema1"
"github.com/Microsoft/hcsshim/internal/timeout"
"github.com/sirupsen/logrus"
@ -41,16 +41,49 @@ type System struct {
handle hcsSystem
id string
callbackNumber uintptr
logctx logrus.Fields
}
func newSystem(id string) *System {
return &System{
id: id,
logctx: logrus.Fields{
logfields.HCSOperation: "",
logfields.ContainerID: id,
},
}
}
func (computeSystem *System) logOperationBegin(operation string) {
computeSystem.logctx[logfields.HCSOperation] = operation
logOperationBegin(
computeSystem.logctx,
"hcsshim::ComputeSystem - Begin Operation")
}
func (computeSystem *System) logOperationEnd(err error) {
var result string
if err == nil {
result = "Success"
} else {
result = "Error"
}
logOperationEnd(
computeSystem.logctx,
"hcsshim::ComputeSystem - End Operation - "+result,
err)
computeSystem.logctx[logfields.HCSOperation] = ""
}
// CreateComputeSystem creates a new compute system with the given configuration but does not start it.
func CreateComputeSystem(id string, hcsDocumentInterface interface{}) (*System, error) {
operation := "CreateComputeSystem"
title := "hcsshim::" + operation
func CreateComputeSystem(id string, hcsDocumentInterface interface{}) (_ *System, err error) {
operation := "hcsshim::CreateComputeSystem"
computeSystem := &System{
id: id,
}
computeSystem := newSystem(id)
computeSystem.logOperationBegin(operation)
defer func() { computeSystem.logOperationEnd(err) }()
hcsDocumentB, err := json.Marshal(hcsDocumentInterface)
if err != nil {
@ -58,19 +91,22 @@ func CreateComputeSystem(id string, hcsDocumentInterface interface{}) (*System,
}
hcsDocument := string(hcsDocumentB)
logrus.Debugf(title+" ID=%s config=%s", id, hcsDocument)
logrus.WithFields(computeSystem.logctx).
WithField(logfields.JSON, hcsDocument).
Debug("HCS ComputeSystem Document")
var (
resultp *uint16
identity syscall.Handle
createError error
)
completed := false
go syscallWatcher(fmt.Sprintf("CreateCompleteSystem %s: %s", id, hcsDocument), &completed)
createError := hcsCreateComputeSystem(id, hcsDocument, identity, &computeSystem.handle, &resultp)
completed = true
syscallWatcher(computeSystem.logctx, func() {
createError = hcsCreateComputeSystem(id, hcsDocument, identity, &computeSystem.handle, &resultp)
})
if createError == nil || IsPending(createError) {
if err := computeSystem.registerCallback(); err != nil {
if err = computeSystem.registerCallback(); err != nil {
// Terminate the compute system if it still exists. We're okay to
// ignore a failure here.
computeSystem.Terminate()
@ -88,25 +124,28 @@ func CreateComputeSystem(id string, hcsDocumentInterface interface{}) (*System,
return nil, makeSystemError(computeSystem, operation, hcsDocument, err, events)
}
logrus.Debugf(title+" succeeded id=%s handle=%d", id, computeSystem.handle)
return computeSystem, nil
}
// OpenComputeSystem opens an existing compute system by ID.
func OpenComputeSystem(id string) (*System, error) {
operation := "OpenComputeSystem"
title := "hcsshim::" + operation
logrus.Debugf(title+" ID=%s", id)
func OpenComputeSystem(id string) (_ *System, err error) {
operation := "hcsshim::OpenComputeSystem"
computeSystem := &System{
id: id,
computeSystem := newSystem(id)
computeSystem.logOperationBegin(operation)
defer func() {
if IsNotExist(err) {
computeSystem.logOperationEnd(nil)
} else {
computeSystem.logOperationEnd(err)
}
}()
var (
handle hcsSystem
resultp *uint16
)
err := hcsOpenComputeSystem(id, &handle, &resultp)
err = hcsOpenComputeSystem(id, &handle, &resultp)
events := processHcsResult(resultp)
if err != nil {
return nil, makeSystemError(computeSystem, operation, "", err, events)
@ -114,18 +153,36 @@ func OpenComputeSystem(id string) (*System, error) {
computeSystem.handle = handle
if err := computeSystem.registerCallback(); err != nil {
if err = computeSystem.registerCallback(); err != nil {
return nil, makeSystemError(computeSystem, operation, "", err, nil)
}
logrus.Debugf(title+" succeeded id=%s handle=%d", id, handle)
return computeSystem, nil
}
// GetComputeSystems gets a list of the compute systems on the system that match the query
func GetComputeSystems(q schema1.ComputeSystemQuery) ([]schema1.ContainerProperties, error) {
operation := "GetComputeSystems"
title := "hcsshim::" + operation
func GetComputeSystems(q schema1.ComputeSystemQuery) (_ []schema1.ContainerProperties, err error) {
operation := "hcsshim::GetComputeSystems"
fields := logrus.Fields{
logfields.HCSOperation: operation,
}
logOperationBegin(
fields,
"hcsshim::ComputeSystem - Begin Operation")
defer func() {
var result string
if err == nil {
result = "Success"
} else {
result = "Error"
}
logOperationEnd(
fields,
"hcsshim::ComputeSystem - End Operation - "+result,
err)
}()
queryb, err := json.Marshal(q)
if err != nil {
@ -133,16 +190,19 @@ func GetComputeSystems(q schema1.ComputeSystemQuery) ([]schema1.ContainerPropert
}
query := string(queryb)
logrus.Debugf(title+" query=%s", query)
logrus.WithFields(fields).
WithField(logfields.JSON, query).
Debug("HCS ComputeSystem Query")
var (
resultp *uint16
computeSystemsp *uint16
)
completed := false
go syscallWatcher(fmt.Sprintf("GetComputeSystems %s:", query), &completed)
syscallWatcher(fields, func() {
err = hcsEnumerateComputeSystems(query, &computeSystemsp, &resultp)
completed = true
})
events := processHcsResult(resultp)
if err != nil {
return nil, &HcsError{Op: operation, Err: err, Events: events}
@ -153,20 +213,21 @@ func GetComputeSystems(q schema1.ComputeSystemQuery) ([]schema1.ContainerPropert
}
computeSystemsRaw := interop.ConvertAndFreeCoTaskMemBytes(computeSystemsp)
computeSystems := []schema1.ContainerProperties{}
if err := json.Unmarshal(computeSystemsRaw, &computeSystems); err != nil {
if err = json.Unmarshal(computeSystemsRaw, &computeSystems); err != nil {
return nil, err
}
logrus.Debugf(title + " succeeded")
return computeSystems, nil
}
// Start synchronously starts the computeSystem.
func (computeSystem *System) Start() error {
func (computeSystem *System) Start() (err error) {
computeSystem.handleLock.RLock()
defer computeSystem.handleLock.RUnlock()
title := "hcsshim::ComputeSystem::Start ID=" + computeSystem.ID()
logrus.Debugf(title)
operation := "hcsshim::ComputeSystem::Start"
computeSystem.logOperationBegin(operation)
defer func() { computeSystem.logOperationEnd(err) }()
if computeSystem.handle == 0 {
return makeSystemError(computeSystem, "Start", "", ErrAlreadyClosed, nil)
@ -199,16 +260,14 @@ func (computeSystem *System) Start() error {
}
var resultp *uint16
completed := false
go syscallWatcher(fmt.Sprintf("StartComputeSystem %s:", computeSystem.ID()), &completed)
err := hcsStartComputeSystem(computeSystem.handle, "", &resultp)
completed = true
syscallWatcher(computeSystem.logctx, func() {
err = hcsStartComputeSystem(computeSystem.handle, "", &resultp)
})
events, err := processAsyncHcsResult(err, resultp, computeSystem.callbackNumber, hcsNotificationSystemStartCompleted, &timeout.SystemStart)
if err != nil {
return makeSystemError(computeSystem, "Start", "", err, events)
}
logrus.Debugf(title + " succeeded")
return nil
}
@ -219,98 +278,133 @@ func (computeSystem *System) ID() string {
// Shutdown requests a compute system shutdown, if IsPending() on the error returned is true,
// it may not actually be shut down until Wait() succeeds.
func (computeSystem *System) Shutdown() error {
func (computeSystem *System) Shutdown() (err error) {
computeSystem.handleLock.RLock()
defer computeSystem.handleLock.RUnlock()
title := "hcsshim::ComputeSystem::Shutdown"
logrus.Debugf(title)
operation := "hcsshim::ComputeSystem::Shutdown"
computeSystem.logOperationBegin(operation)
defer func() {
if IsAlreadyStopped(err) {
computeSystem.logOperationEnd(nil)
} else {
computeSystem.logOperationEnd(err)
}
}()
if computeSystem.handle == 0 {
return makeSystemError(computeSystem, "Shutdown", "", ErrAlreadyClosed, nil)
}
var resultp *uint16
completed := false
go syscallWatcher(fmt.Sprintf("ShutdownComputeSystem %s:", computeSystem.ID()), &completed)
err := hcsShutdownComputeSystem(computeSystem.handle, "", &resultp)
completed = true
syscallWatcher(computeSystem.logctx, func() {
err = hcsShutdownComputeSystem(computeSystem.handle, "", &resultp)
})
events := processHcsResult(resultp)
if err != nil {
return makeSystemError(computeSystem, "Shutdown", "", err, events)
}
logrus.Debugf(title + " succeeded")
return nil
}
// Terminate requests a compute system terminate, if IsPending() on the error returned is true,
// it may not actually be shut down until Wait() succeeds.
func (computeSystem *System) Terminate() error {
func (computeSystem *System) Terminate() (err error) {
computeSystem.handleLock.RLock()
defer computeSystem.handleLock.RUnlock()
title := "hcsshim::ComputeSystem::Terminate ID=" + computeSystem.ID()
logrus.Debugf(title)
operation := "hcsshim::ComputeSystem::Terminate"
computeSystem.logOperationBegin(operation)
defer func() {
if IsPending(err) {
computeSystem.logOperationEnd(nil)
} else {
computeSystem.logOperationEnd(err)
}
}()
if computeSystem.handle == 0 {
return makeSystemError(computeSystem, "Terminate", "", ErrAlreadyClosed, nil)
}
var resultp *uint16
completed := false
go syscallWatcher(fmt.Sprintf("TerminateComputeSystem %s:", computeSystem.ID()), &completed)
err := hcsTerminateComputeSystem(computeSystem.handle, "", &resultp)
completed = true
syscallWatcher(computeSystem.logctx, func() {
err = hcsTerminateComputeSystem(computeSystem.handle, "", &resultp)
})
events := processHcsResult(resultp)
if err != nil {
if err != nil && err != ErrVmcomputeAlreadyStopped {
return makeSystemError(computeSystem, "Terminate", "", err, events)
}
logrus.Debugf(title + " succeeded")
return nil
}
// Wait synchronously waits for the compute system to shutdown or terminate.
func (computeSystem *System) Wait() error {
title := "hcsshim::ComputeSystem::Wait ID=" + computeSystem.ID()
logrus.Debugf(title)
func (computeSystem *System) Wait() (err error) {
operation := "hcsshim::ComputeSystem::Wait"
computeSystem.logOperationBegin(operation)
defer func() { computeSystem.logOperationEnd(err) }()
err := waitForNotification(computeSystem.callbackNumber, hcsNotificationSystemExited, nil)
err = waitForNotification(computeSystem.callbackNumber, hcsNotificationSystemExited, nil)
if err != nil {
return makeSystemError(computeSystem, "Wait", "", err, nil)
}
logrus.Debugf(title + " succeeded")
return nil
}
// WaitExpectedError synchronously waits for the compute system to shutdown or
// terminate, and ignores the passed error if it occurs.
func (computeSystem *System) WaitExpectedError(expected error) (err error) {
operation := "hcsshim::ComputeSystem::WaitExpectedError"
computeSystem.logOperationBegin(operation)
defer func() { computeSystem.logOperationEnd(err) }()
err = waitForNotification(computeSystem.callbackNumber, hcsNotificationSystemExited, nil)
if err != nil && err != expected {
return makeSystemError(computeSystem, "WaitExpectedError", "", err, nil)
}
return nil
}
// WaitTimeout synchronously waits for the compute system to terminate or the duration to elapse.
// If the timeout expires, IsTimeout(err) == true
func (computeSystem *System) WaitTimeout(timeout time.Duration) error {
title := "hcsshim::ComputeSystem::WaitTimeout ID=" + computeSystem.ID()
logrus.Debugf(title)
func (computeSystem *System) WaitTimeout(timeout time.Duration) (err error) {
operation := "hcsshim::ComputeSystem::WaitTimeout"
computeSystem.logOperationBegin(operation)
defer func() { computeSystem.logOperationEnd(err) }()
err := waitForNotification(computeSystem.callbackNumber, hcsNotificationSystemExited, &timeout)
err = waitForNotification(computeSystem.callbackNumber, hcsNotificationSystemExited, &timeout)
if err != nil {
return makeSystemError(computeSystem, "WaitTimeout", "", err, nil)
}
logrus.Debugf(title + " succeeded")
return nil
}
func (computeSystem *System) Properties(types ...schema1.PropertyType) (*schema1.ContainerProperties, error) {
func (computeSystem *System) Properties(types ...schema1.PropertyType) (_ *schema1.ContainerProperties, err error) {
computeSystem.handleLock.RLock()
defer computeSystem.handleLock.RUnlock()
operation := "hcsshim::ComputeSystem::Properties"
computeSystem.logOperationBegin(operation)
defer func() { computeSystem.logOperationEnd(err) }()
queryj, err := json.Marshal(schema1.PropertyQuery{types})
if err != nil {
return nil, makeSystemError(computeSystem, "Properties", "", err, nil)
}
logrus.WithFields(computeSystem.logctx).
WithField(logfields.JSON, queryj).
Debug("HCS ComputeSystem Properties Query")
var resultp, propertiesp *uint16
completed := false
go syscallWatcher(fmt.Sprintf("GetComputeSystemProperties %s:", computeSystem.ID()), &completed)
syscallWatcher(computeSystem.logctx, func() {
err = hcsGetComputeSystemProperties(computeSystem.handle, string(queryj), &propertiesp, &resultp)
completed = true
})
events := processHcsResult(resultp)
if err != nil {
return nil, makeSystemError(computeSystem, "Properties", "", err, events)
@ -324,64 +418,69 @@ func (computeSystem *System) Properties(types ...schema1.PropertyType) (*schema1
if err := json.Unmarshal(propertiesRaw, properties); err != nil {
return nil, makeSystemError(computeSystem, "Properties", "", err, nil)
}
return properties, nil
}
// Pause pauses the execution of the computeSystem. This feature is not enabled in TP5.
func (computeSystem *System) Pause() error {
func (computeSystem *System) Pause() (err error) {
computeSystem.handleLock.RLock()
defer computeSystem.handleLock.RUnlock()
title := "hcsshim::ComputeSystem::Pause ID=" + computeSystem.ID()
logrus.Debugf(title)
operation := "hcsshim::ComputeSystem::Pause"
computeSystem.logOperationBegin(operation)
defer func() { computeSystem.logOperationEnd(err) }()
if computeSystem.handle == 0 {
return makeSystemError(computeSystem, "Pause", "", ErrAlreadyClosed, nil)
}
var resultp *uint16
completed := false
go syscallWatcher(fmt.Sprintf("PauseComputeSystem %s:", computeSystem.ID()), &completed)
err := hcsPauseComputeSystem(computeSystem.handle, "", &resultp)
completed = true
syscallWatcher(computeSystem.logctx, func() {
err = hcsPauseComputeSystem(computeSystem.handle, "", &resultp)
})
events, err := processAsyncHcsResult(err, resultp, computeSystem.callbackNumber, hcsNotificationSystemPauseCompleted, &timeout.SystemPause)
if err != nil {
return makeSystemError(computeSystem, "Pause", "", err, events)
}
logrus.Debugf(title + " succeeded")
return nil
}
// Resume resumes the execution of the computeSystem. This feature is not enabled in TP5.
func (computeSystem *System) Resume() error {
func (computeSystem *System) Resume() (err error) {
computeSystem.handleLock.RLock()
defer computeSystem.handleLock.RUnlock()
title := "hcsshim::ComputeSystem::Resume ID=" + computeSystem.ID()
logrus.Debugf(title)
operation := "hcsshim::ComputeSystem::Resume"
computeSystem.logOperationBegin(operation)
defer func() { computeSystem.logOperationEnd(err) }()
if computeSystem.handle == 0 {
return makeSystemError(computeSystem, "Resume", "", ErrAlreadyClosed, nil)
}
var resultp *uint16
completed := false
go syscallWatcher(fmt.Sprintf("ResumeComputeSystem %s:", computeSystem.ID()), &completed)
err := hcsResumeComputeSystem(computeSystem.handle, "", &resultp)
completed = true
syscallWatcher(computeSystem.logctx, func() {
err = hcsResumeComputeSystem(computeSystem.handle, "", &resultp)
})
events, err := processAsyncHcsResult(err, resultp, computeSystem.callbackNumber, hcsNotificationSystemResumeCompleted, &timeout.SystemResume)
if err != nil {
return makeSystemError(computeSystem, "Resume", "", err, events)
}
logrus.Debugf(title + " succeeded")
return nil
}
// CreateProcess launches a new process within the computeSystem.
func (computeSystem *System) CreateProcess(c interface{}) (*Process, error) {
func (computeSystem *System) CreateProcess(c interface{}) (_ *Process, err error) {
computeSystem.handleLock.RLock()
defer computeSystem.handleLock.RUnlock()
title := "hcsshim::ComputeSystem::CreateProcess ID=" + computeSystem.ID()
operation := "hcsshim::ComputeSystem::CreateProcess"
computeSystem.logOperationBegin(operation)
defer func() { computeSystem.logOperationEnd(err) }()
var (
processInfo hcsProcessInformation
processHandle hcsProcess
@ -398,42 +497,50 @@ func (computeSystem *System) CreateProcess(c interface{}) (*Process, error) {
}
configuration := string(configurationb)
logrus.Debugf(title+" config=%s", configuration)
completed := false
go syscallWatcher(fmt.Sprintf("CreateProcess %s: %s", computeSystem.ID(), configuration), &completed)
logrus.WithFields(computeSystem.logctx).
WithField(logfields.JSON, configuration).
Debug("HCS ComputeSystem Process Document")
syscallWatcher(computeSystem.logctx, func() {
err = hcsCreateProcess(computeSystem.handle, configuration, &processInfo, &processHandle, &resultp)
completed = true
})
events := processHcsResult(resultp)
if err != nil {
return nil, makeSystemError(computeSystem, "CreateProcess", configuration, err, events)
}
process := &Process{
handle: processHandle,
processID: int(processInfo.ProcessId),
system: computeSystem,
cachedPipes: &cachedPipes{
logrus.WithFields(computeSystem.logctx).
WithField(logfields.ProcessID, processInfo.ProcessId).
Debug("HCS ComputeSystem CreateProcess PID")
process := newProcess(processHandle, int(processInfo.ProcessId), computeSystem)
process.cachedPipes = &cachedPipes{
stdIn: processInfo.StdInput,
stdOut: processInfo.StdOutput,
stdErr: processInfo.StdError,
},
}
if err := process.registerCallback(); err != nil {
if err = process.registerCallback(); err != nil {
return nil, makeSystemError(computeSystem, "CreateProcess", "", err, nil)
}
logrus.Debugf(title+" succeeded processid=%d", process.processID)
return process, nil
}
// OpenProcess gets an interface to an existing process within the computeSystem.
func (computeSystem *System) OpenProcess(pid int) (*Process, error) {
func (computeSystem *System) OpenProcess(pid int) (_ *Process, err error) {
computeSystem.handleLock.RLock()
defer computeSystem.handleLock.RUnlock()
title := "hcsshim::ComputeSystem::OpenProcess ID=" + computeSystem.ID()
logrus.Debugf(title+" processid=%d", pid)
// Add PID for the context of this operation
computeSystem.logctx[logfields.ProcessID] = pid
defer delete(computeSystem.logctx, logfields.ProcessID)
operation := "hcsshim::ComputeSystem::OpenProcess"
computeSystem.logOperationBegin(operation)
defer func() { computeSystem.logOperationEnd(err) }()
var (
processHandle hcsProcess
resultp *uint16
@ -443,56 +550,49 @@ func (computeSystem *System) OpenProcess(pid int) (*Process, error) {
return nil, makeSystemError(computeSystem, "OpenProcess", "", ErrAlreadyClosed, nil)
}
completed := false
go syscallWatcher(fmt.Sprintf("OpenProcess %s: %d", computeSystem.ID(), pid), &completed)
err := hcsOpenProcess(computeSystem.handle, uint32(pid), &processHandle, &resultp)
completed = true
syscallWatcher(computeSystem.logctx, func() {
err = hcsOpenProcess(computeSystem.handle, uint32(pid), &processHandle, &resultp)
})
events := processHcsResult(resultp)
if err != nil {
return nil, makeSystemError(computeSystem, "OpenProcess", "", err, events)
}
process := &Process{
handle: processHandle,
processID: pid,
system: computeSystem,
}
if err := process.registerCallback(); err != nil {
process := newProcess(processHandle, pid, computeSystem)
if err = process.registerCallback(); err != nil {
return nil, makeSystemError(computeSystem, "OpenProcess", "", err, nil)
}
logrus.Debugf(title+" succeeded processid=%s", process.processID)
return process, nil
}
// Close cleans up any state associated with the compute system but does not terminate or wait for it.
func (computeSystem *System) Close() error {
func (computeSystem *System) Close() (err error) {
computeSystem.handleLock.Lock()
defer computeSystem.handleLock.Unlock()
title := "hcsshim::ComputeSystem::Close ID=" + computeSystem.ID()
logrus.Debugf(title)
operation := "hcsshim::ComputeSystem::Close"
computeSystem.logOperationBegin(operation)
defer func() { computeSystem.logOperationEnd(err) }()
// Don't double free this
if computeSystem.handle == 0 {
return nil
}
if err := computeSystem.unregisterCallback(); err != nil {
if err = computeSystem.unregisterCallback(); err != nil {
return makeSystemError(computeSystem, "Close", "", err, nil)
}
completed := false
go syscallWatcher(fmt.Sprintf("CloseComputeSystem %s:", computeSystem.ID()), &completed)
err := hcsCloseComputeSystem(computeSystem.handle)
completed = true
syscallWatcher(computeSystem.logctx, func() {
err = hcsCloseComputeSystem(computeSystem.handle)
})
if err != nil {
return makeSystemError(computeSystem, "Close", "", err, nil)
}
computeSystem.handle = 0
logrus.Debugf(title + " succeeded")
return nil
}
@ -553,11 +653,14 @@ func (computeSystem *System) unregisterCallback() error {
return nil
}
// Modifies the System by sending a request to HCS
func (computeSystem *System) Modify(config interface{}) error {
// Modify the System by sending a request to HCS
func (computeSystem *System) Modify(config interface{}) (err error) {
computeSystem.handleLock.RLock()
defer computeSystem.handleLock.RUnlock()
title := "hcsshim::Modify ID=" + computeSystem.id
operation := "hcsshim::ComputeSystem::Modify"
computeSystem.logOperationBegin(operation)
defer func() { computeSystem.logOperationEnd(err) }()
if computeSystem.handle == 0 {
return makeSystemError(computeSystem, "Modify", "", ErrAlreadyClosed, nil)
@ -569,17 +672,19 @@ func (computeSystem *System) Modify(config interface{}) error {
}
requestString := string(requestJSON)
logrus.Debugf(title + " " + requestString)
logrus.WithFields(computeSystem.logctx).
WithField(logfields.JSON, requestString).
Debug("HCS ComputeSystem Modify Document")
var resultp *uint16
completed := false
go syscallWatcher(fmt.Sprintf("ModifyComputeSystem %s: %s", computeSystem.ID(), requestString), &completed)
syscallWatcher(computeSystem.logctx, func() {
err = hcsModifyComputeSystem(computeSystem.handle, requestString, &resultp)
completed = true
})
events := processHcsResult(resultp)
if err != nil {
return makeSystemError(computeSystem, "Modify", requestString, err, events)
}
logrus.Debugf(title + " succeeded ")
return nil
}

View File

@ -1,8 +1,9 @@
package hcs
import (
"time"
"context"
"github.com/Microsoft/hcsshim/internal/logfields"
"github.com/Microsoft/hcsshim/internal/timeout"
"github.com/sirupsen/logrus"
)
@ -16,15 +17,25 @@ import (
//
// Usage is:
//
// completed := false
// go syscallWatcher("some description", &completed)
// <syscall>
// completed = true
// syscallWatcher(logContext, func() {
// err = <syscall>(args...)
// })
//
func syscallWatcher(description string, syscallCompleted *bool) {
time.Sleep(timeout.SyscallWatcher)
if *syscallCompleted {
return
}
logrus.Warnf("%s: Did not complete within %s. This may indicate a platform issue. If it appears to be making no forward progress, obtain the stacks and see is there is a syscall stuck in the platform API for a significant length of time.", description, timeout.SyscallWatcher)
func syscallWatcher(logContext logrus.Fields, syscallLambda func()) {
ctx, cancel := context.WithTimeout(context.Background(), timeout.SyscallWatcher)
defer cancel()
go watchFunc(ctx, logContext)
syscallLambda()
}
func watchFunc(ctx context.Context, logContext logrus.Fields) {
select {
case <-ctx.Done():
if ctx.Err() != context.Canceled {
logrus.WithFields(logContext).
WithField(logfields.Timeout, timeout.SyscallWatcher).
Warning("Syscall did not complete within operation timeout. This may indicate a platform issue. If it appears to be making no forward progress, obtain the stacks and see if there is a syscall stuck in the platform API for a significant length of time.")
}
}
}

View File

@ -1,4 +1,4 @@
// MACHINE GENERATED BY 'go generate' COMMAND; DO NOT EDIT
// Code generated mksyscall_windows.exe DO NOT EDIT
package hcs
@ -6,7 +6,6 @@ import (
"syscall"
"unsafe"
"github.com/Microsoft/hcsshim/internal/interop"
"golang.org/x/sys/windows"
)
@ -57,6 +56,7 @@ var (
procHcsOpenProcess = modvmcompute.NewProc("HcsOpenProcess")
procHcsCloseProcess = modvmcompute.NewProc("HcsCloseProcess")
procHcsTerminateProcess = modvmcompute.NewProc("HcsTerminateProcess")
procHcsGetProcessInfo = modvmcompute.NewProc("HcsGetProcessInfo")
procHcsGetProcessProperties = modvmcompute.NewProc("HcsGetProcessProperties")
procHcsModifyProcess = modvmcompute.NewProc("HcsModifyProcess")
@ -80,7 +80,10 @@ func _hcsEnumerateComputeSystems(query *uint16, computeSystems **uint16, result
}
r0, _, _ := syscall.Syscall(procHcsEnumerateComputeSystems.Addr(), 3, uintptr(unsafe.Pointer(query)), uintptr(unsafe.Pointer(computeSystems)), uintptr(unsafe.Pointer(result)))
if int32(r0) < 0 {
hr = interop.Win32FromHresult(r0)
if r0&0x1fff0000 == 0x00070000 {
r0 &= 0xffff
}
hr = syscall.Errno(r0)
}
return
}
@ -105,7 +108,10 @@ func _hcsCreateComputeSystem(id *uint16, configuration *uint16, identity syscall
}
r0, _, _ := syscall.Syscall6(procHcsCreateComputeSystem.Addr(), 5, uintptr(unsafe.Pointer(id)), uintptr(unsafe.Pointer(configuration)), uintptr(identity), uintptr(unsafe.Pointer(computeSystem)), uintptr(unsafe.Pointer(result)), 0)
if int32(r0) < 0 {
hr = interop.Win32FromHresult(r0)
if r0&0x1fff0000 == 0x00070000 {
r0 &= 0xffff
}
hr = syscall.Errno(r0)
}
return
}
@ -125,7 +131,10 @@ func _hcsOpenComputeSystem(id *uint16, computeSystem *hcsSystem, result **uint16
}
r0, _, _ := syscall.Syscall(procHcsOpenComputeSystem.Addr(), 3, uintptr(unsafe.Pointer(id)), uintptr(unsafe.Pointer(computeSystem)), uintptr(unsafe.Pointer(result)))
if int32(r0) < 0 {
hr = interop.Win32FromHresult(r0)
if r0&0x1fff0000 == 0x00070000 {
r0 &= 0xffff
}
hr = syscall.Errno(r0)
}
return
}
@ -136,7 +145,10 @@ func hcsCloseComputeSystem(computeSystem hcsSystem) (hr error) {
}
r0, _, _ := syscall.Syscall(procHcsCloseComputeSystem.Addr(), 1, uintptr(computeSystem), 0, 0)
if int32(r0) < 0 {
hr = interop.Win32FromHresult(r0)
if r0&0x1fff0000 == 0x00070000 {
r0 &= 0xffff
}
hr = syscall.Errno(r0)
}
return
}
@ -156,7 +168,10 @@ func _hcsStartComputeSystem(computeSystem hcsSystem, options *uint16, result **u
}
r0, _, _ := syscall.Syscall(procHcsStartComputeSystem.Addr(), 3, uintptr(computeSystem), uintptr(unsafe.Pointer(options)), uintptr(unsafe.Pointer(result)))
if int32(r0) < 0 {
hr = interop.Win32FromHresult(r0)
if r0&0x1fff0000 == 0x00070000 {
r0 &= 0xffff
}
hr = syscall.Errno(r0)
}
return
}
@ -176,7 +191,10 @@ func _hcsShutdownComputeSystem(computeSystem hcsSystem, options *uint16, result
}
r0, _, _ := syscall.Syscall(procHcsShutdownComputeSystem.Addr(), 3, uintptr(computeSystem), uintptr(unsafe.Pointer(options)), uintptr(unsafe.Pointer(result)))
if int32(r0) < 0 {
hr = interop.Win32FromHresult(r0)
if r0&0x1fff0000 == 0x00070000 {
r0 &= 0xffff
}
hr = syscall.Errno(r0)
}
return
}
@ -196,7 +214,10 @@ func _hcsTerminateComputeSystem(computeSystem hcsSystem, options *uint16, result
}
r0, _, _ := syscall.Syscall(procHcsTerminateComputeSystem.Addr(), 3, uintptr(computeSystem), uintptr(unsafe.Pointer(options)), uintptr(unsafe.Pointer(result)))
if int32(r0) < 0 {
hr = interop.Win32FromHresult(r0)
if r0&0x1fff0000 == 0x00070000 {
r0 &= 0xffff
}
hr = syscall.Errno(r0)
}
return
}
@ -216,7 +237,10 @@ func _hcsPauseComputeSystem(computeSystem hcsSystem, options *uint16, result **u
}
r0, _, _ := syscall.Syscall(procHcsPauseComputeSystem.Addr(), 3, uintptr(computeSystem), uintptr(unsafe.Pointer(options)), uintptr(unsafe.Pointer(result)))
if int32(r0) < 0 {
hr = interop.Win32FromHresult(r0)
if r0&0x1fff0000 == 0x00070000 {
r0 &= 0xffff
}
hr = syscall.Errno(r0)
}
return
}
@ -236,7 +260,10 @@ func _hcsResumeComputeSystem(computeSystem hcsSystem, options *uint16, result **
}
r0, _, _ := syscall.Syscall(procHcsResumeComputeSystem.Addr(), 3, uintptr(computeSystem), uintptr(unsafe.Pointer(options)), uintptr(unsafe.Pointer(result)))
if int32(r0) < 0 {
hr = interop.Win32FromHresult(r0)
if r0&0x1fff0000 == 0x00070000 {
r0 &= 0xffff
}
hr = syscall.Errno(r0)
}
return
}
@ -256,7 +283,10 @@ func _hcsGetComputeSystemProperties(computeSystem hcsSystem, propertyQuery *uint
}
r0, _, _ := syscall.Syscall6(procHcsGetComputeSystemProperties.Addr(), 4, uintptr(computeSystem), uintptr(unsafe.Pointer(propertyQuery)), uintptr(unsafe.Pointer(properties)), uintptr(unsafe.Pointer(result)), 0, 0)
if int32(r0) < 0 {
hr = interop.Win32FromHresult(r0)
if r0&0x1fff0000 == 0x00070000 {
r0 &= 0xffff
}
hr = syscall.Errno(r0)
}
return
}
@ -276,7 +306,10 @@ func _hcsModifyComputeSystem(computeSystem hcsSystem, configuration *uint16, res
}
r0, _, _ := syscall.Syscall(procHcsModifyComputeSystem.Addr(), 3, uintptr(computeSystem), uintptr(unsafe.Pointer(configuration)), uintptr(unsafe.Pointer(result)))
if int32(r0) < 0 {
hr = interop.Win32FromHresult(r0)
if r0&0x1fff0000 == 0x00070000 {
r0 &= 0xffff
}
hr = syscall.Errno(r0)
}
return
}
@ -287,7 +320,10 @@ func hcsRegisterComputeSystemCallback(computeSystem hcsSystem, callback uintptr,
}
r0, _, _ := syscall.Syscall6(procHcsRegisterComputeSystemCallback.Addr(), 4, uintptr(computeSystem), uintptr(callback), uintptr(context), uintptr(unsafe.Pointer(callbackHandle)), 0, 0)
if int32(r0) < 0 {
hr = interop.Win32FromHresult(r0)
if r0&0x1fff0000 == 0x00070000 {
r0 &= 0xffff
}
hr = syscall.Errno(r0)
}
return
}
@ -298,7 +334,10 @@ func hcsUnregisterComputeSystemCallback(callbackHandle hcsCallback) (hr error) {
}
r0, _, _ := syscall.Syscall(procHcsUnregisterComputeSystemCallback.Addr(), 1, uintptr(callbackHandle), 0, 0)
if int32(r0) < 0 {
hr = interop.Win32FromHresult(r0)
if r0&0x1fff0000 == 0x00070000 {
r0 &= 0xffff
}
hr = syscall.Errno(r0)
}
return
}
@ -318,7 +357,10 @@ func _hcsCreateProcess(computeSystem hcsSystem, processParameters *uint16, proce
}
r0, _, _ := syscall.Syscall6(procHcsCreateProcess.Addr(), 5, uintptr(computeSystem), uintptr(unsafe.Pointer(processParameters)), uintptr(unsafe.Pointer(processInformation)), uintptr(unsafe.Pointer(process)), uintptr(unsafe.Pointer(result)), 0)
if int32(r0) < 0 {
hr = interop.Win32FromHresult(r0)
if r0&0x1fff0000 == 0x00070000 {
r0 &= 0xffff
}
hr = syscall.Errno(r0)
}
return
}
@ -329,7 +371,10 @@ func hcsOpenProcess(computeSystem hcsSystem, pid uint32, process *hcsProcess, re
}
r0, _, _ := syscall.Syscall6(procHcsOpenProcess.Addr(), 4, uintptr(computeSystem), uintptr(pid), uintptr(unsafe.Pointer(process)), uintptr(unsafe.Pointer(result)), 0, 0)
if int32(r0) < 0 {
hr = interop.Win32FromHresult(r0)
if r0&0x1fff0000 == 0x00070000 {
r0 &= 0xffff
}
hr = syscall.Errno(r0)
}
return
}
@ -340,7 +385,10 @@ func hcsCloseProcess(process hcsProcess) (hr error) {
}
r0, _, _ := syscall.Syscall(procHcsCloseProcess.Addr(), 1, uintptr(process), 0, 0)
if int32(r0) < 0 {
hr = interop.Win32FromHresult(r0)
if r0&0x1fff0000 == 0x00070000 {
r0 &= 0xffff
}
hr = syscall.Errno(r0)
}
return
}
@ -351,7 +399,33 @@ func hcsTerminateProcess(process hcsProcess, result **uint16) (hr error) {
}
r0, _, _ := syscall.Syscall(procHcsTerminateProcess.Addr(), 2, uintptr(process), uintptr(unsafe.Pointer(result)), 0)
if int32(r0) < 0 {
hr = interop.Win32FromHresult(r0)
if r0&0x1fff0000 == 0x00070000 {
r0 &= 0xffff
}
hr = syscall.Errno(r0)
}
return
}
func hcsSignalProcess(process hcsProcess, options string, result **uint16) (hr error) {
var _p0 *uint16
_p0, hr = syscall.UTF16PtrFromString(options)
if hr != nil {
return
}
return _hcsSignalProcess(process, _p0, result)
}
func _hcsSignalProcess(process hcsProcess, options *uint16, result **uint16) (hr error) {
if hr = procHcsTerminateProcess.Find(); hr != nil {
return
}
r0, _, _ := syscall.Syscall(procHcsTerminateProcess.Addr(), 3, uintptr(process), uintptr(unsafe.Pointer(options)), uintptr(unsafe.Pointer(result)))
if int32(r0) < 0 {
if r0&0x1fff0000 == 0x00070000 {
r0 &= 0xffff
}
hr = syscall.Errno(r0)
}
return
}
@ -362,7 +436,10 @@ func hcsGetProcessInfo(process hcsProcess, processInformation *hcsProcessInforma
}
r0, _, _ := syscall.Syscall(procHcsGetProcessInfo.Addr(), 3, uintptr(process), uintptr(unsafe.Pointer(processInformation)), uintptr(unsafe.Pointer(result)))
if int32(r0) < 0 {
hr = interop.Win32FromHresult(r0)
if r0&0x1fff0000 == 0x00070000 {
r0 &= 0xffff
}
hr = syscall.Errno(r0)
}
return
}
@ -373,7 +450,10 @@ func hcsGetProcessProperties(process hcsProcess, processProperties **uint16, res
}
r0, _, _ := syscall.Syscall(procHcsGetProcessProperties.Addr(), 3, uintptr(process), uintptr(unsafe.Pointer(processProperties)), uintptr(unsafe.Pointer(result)))
if int32(r0) < 0 {
hr = interop.Win32FromHresult(r0)
if r0&0x1fff0000 == 0x00070000 {
r0 &= 0xffff
}
hr = syscall.Errno(r0)
}
return
}
@ -393,7 +473,10 @@ func _hcsModifyProcess(process hcsProcess, settings *uint16, result **uint16) (h
}
r0, _, _ := syscall.Syscall(procHcsModifyProcess.Addr(), 3, uintptr(process), uintptr(unsafe.Pointer(settings)), uintptr(unsafe.Pointer(result)))
if int32(r0) < 0 {
hr = interop.Win32FromHresult(r0)
if r0&0x1fff0000 == 0x00070000 {
r0 &= 0xffff
}
hr = syscall.Errno(r0)
}
return
}
@ -413,7 +496,10 @@ func _hcsGetServiceProperties(propertyQuery *uint16, properties **uint16, result
}
r0, _, _ := syscall.Syscall(procHcsGetServiceProperties.Addr(), 3, uintptr(unsafe.Pointer(propertyQuery)), uintptr(unsafe.Pointer(properties)), uintptr(unsafe.Pointer(result)))
if int32(r0) < 0 {
hr = interop.Win32FromHresult(r0)
if r0&0x1fff0000 == 0x00070000 {
r0 &= 0xffff
}
hr = syscall.Errno(r0)
}
return
}
@ -424,7 +510,10 @@ func hcsRegisterProcessCallback(process hcsProcess, callback uintptr, context ui
}
r0, _, _ := syscall.Syscall6(procHcsRegisterProcessCallback.Addr(), 4, uintptr(process), uintptr(callback), uintptr(context), uintptr(unsafe.Pointer(callbackHandle)), 0, 0)
if int32(r0) < 0 {
hr = interop.Win32FromHresult(r0)
if r0&0x1fff0000 == 0x00070000 {
r0 &= 0xffff
}
hr = syscall.Errno(r0)
}
return
}
@ -435,7 +524,10 @@ func hcsUnregisterProcessCallback(callbackHandle hcsCallback) (hr error) {
}
r0, _, _ := syscall.Syscall(procHcsUnregisterProcessCallback.Addr(), 1, uintptr(callbackHandle), 0, 0)
if int32(r0) < 0 {
hr = interop.Win32FromHresult(r0)
if r0&0x1fff0000 == 0x00070000 {
r0 &= 0xffff
}
hr = syscall.Errno(r0)
}
return
}

View File

@ -36,10 +36,6 @@ func New(err error, title, rest string) error {
return &HcsError{title, rest, err}
}
func Errorf(err error, title, format string, a ...interface{}) error {
return New(err, title, fmt.Sprintf(format, a...))
}
func Win32FromError(err error) uint32 {
if herr, ok := err.(*HcsError); ok {
return Win32FromError(herr.Err)

View File

@ -0,0 +1,173 @@
// +build windows
package hcsoci
import (
"errors"
"fmt"
"os"
"path/filepath"
"strconv"
"github.com/Microsoft/hcsshim/internal/guid"
"github.com/Microsoft/hcsshim/internal/hcs"
"github.com/Microsoft/hcsshim/internal/schema2"
"github.com/Microsoft/hcsshim/internal/schemaversion"
"github.com/Microsoft/hcsshim/internal/uvm"
specs "github.com/opencontainers/runtime-spec/specs-go"
"github.com/sirupsen/logrus"
)
// CreateOptions are the set of fields used to call CreateContainer().
// Note: In the spec, the LayerFolders must be arranged in the same way in which
// moby configures them: layern, layern-1,...,layer2,layer1,scratch
// where layer1 is the base read-only layer, layern is the top-most read-only
// layer, and scratch is the RW layer. This is for historical reasons only.
type CreateOptions struct {
// Common parameters
ID string // Identifier for the container
Owner string // Specifies the owner. Defaults to executable name.
Spec *specs.Spec // Definition of the container or utility VM being created
SchemaVersion *hcsschema.Version // Requested Schema Version. Defaults to v2 for RS5, v1 for RS1..RS4
HostingSystem *uvm.UtilityVM // Utility or service VM in which the container is to be created.
NetworkNamespace string // Host network namespace to use (overrides anything in the spec)
// This is an advanced debugging parameter. It allows for diagnosibility by leaving a containers
// resources allocated in case of a failure. Thus you would be able to use tools such as hcsdiag
// to look at the state of a utility VM to see what resources were allocated. Obviously the caller
// must a) not tear down the utility VM on failure (or pause in some way) and b) is responsible for
// performing the ReleaseResources() call themselves.
DoNotReleaseResourcesOnFailure bool
}
// createOptionsInternal is the set of user-supplied create options, but includes internal
// fields for processing the request once user-supplied stuff has been validated.
type createOptionsInternal struct {
*CreateOptions
actualSchemaVersion *hcsschema.Version // Calculated based on Windows build and optional caller-supplied override
actualID string // Identifier for the container
actualOwner string // Owner for the container
actualNetworkNamespace string
}
// CreateContainer creates a container. It can cope with a wide variety of
// scenarios, including v1 HCS schema calls, as well as more complex v2 HCS schema
// calls. Note we always return the resources that have been allocated, even in the
// case of an error. This provides support for the debugging option not to
// release the resources on failure, so that the client can make the necessary
// call to release resources that have been allocated as part of calling this function.
func CreateContainer(createOptions *CreateOptions) (_ *hcs.System, _ *Resources, err error) {
logrus.Debugf("hcsshim::CreateContainer options: %+v", createOptions)
coi := &createOptionsInternal{
CreateOptions: createOptions,
actualID: createOptions.ID,
actualOwner: createOptions.Owner,
}
// Defaults if omitted by caller.
if coi.actualID == "" {
coi.actualID = guid.New().String()
}
if coi.actualOwner == "" {
coi.actualOwner = filepath.Base(os.Args[0])
}
if coi.Spec == nil {
return nil, nil, fmt.Errorf("Spec must be supplied")
}
if coi.HostingSystem != nil {
// By definition, a hosting system can only be supplied for a v2 Xenon.
coi.actualSchemaVersion = schemaversion.SchemaV21()
} else {
coi.actualSchemaVersion = schemaversion.DetermineSchemaVersion(coi.SchemaVersion)
logrus.Debugf("hcsshim::CreateContainer using schema %s", schemaversion.String(coi.actualSchemaVersion))
}
resources := &Resources{}
defer func() {
if err != nil {
if !coi.DoNotReleaseResourcesOnFailure {
ReleaseResources(resources, coi.HostingSystem, true)
}
}
}()
if coi.HostingSystem != nil {
n := coi.HostingSystem.ContainerCounter()
if coi.Spec.Linux != nil {
resources.containerRootInUVM = "/run/gcs/c/" + strconv.FormatUint(n, 16)
} else {
resources.containerRootInUVM = `C:\c\` + strconv.FormatUint(n, 16)
}
}
// Create a network namespace if necessary.
if coi.Spec.Windows != nil &&
coi.Spec.Windows.Network != nil &&
schemaversion.IsV21(coi.actualSchemaVersion) {
if coi.NetworkNamespace != "" {
resources.netNS = coi.NetworkNamespace
} else {
err := createNetworkNamespace(coi, resources)
if err != nil {
return nil, resources, err
}
}
coi.actualNetworkNamespace = resources.netNS
if coi.HostingSystem != nil {
endpoints, err := getNamespaceEndpoints(coi.actualNetworkNamespace)
if err != nil {
return nil, resources, err
}
err = coi.HostingSystem.AddNetNS(coi.actualNetworkNamespace, endpoints)
if err != nil {
return nil, resources, err
}
resources.addedNetNSToVM = true
}
}
var hcsDocument interface{}
logrus.Debugf("hcsshim::CreateContainer allocating resources")
if coi.Spec.Linux != nil {
if schemaversion.IsV10(coi.actualSchemaVersion) {
return nil, resources, errors.New("LCOW v1 not supported")
}
logrus.Debugf("hcsshim::CreateContainer allocateLinuxResources")
err = allocateLinuxResources(coi, resources)
if err != nil {
logrus.Debugf("failed to allocateLinuxResources %s", err)
return nil, resources, err
}
hcsDocument, err = createLinuxContainerDocument(coi, resources.containerRootInUVM)
if err != nil {
logrus.Debugf("failed createHCSContainerDocument %s", err)
return nil, resources, err
}
} else {
err = allocateWindowsResources(coi, resources)
if err != nil {
logrus.Debugf("failed to allocateWindowsResources %s", err)
return nil, resources, err
}
logrus.Debugf("hcsshim::CreateContainer creating container document")
hcsDocument, err = createWindowsContainerDocument(coi)
if err != nil {
logrus.Debugf("failed createHCSContainerDocument %s", err)
return nil, resources, err
}
}
logrus.Debugf("hcsshim::CreateContainer creating compute system")
system, err := hcs.CreateComputeSystem(coi.actualID, hcsDocument)
if err != nil {
logrus.Debugf("failed to CreateComputeSystem %s", err)
return nil, resources, err
}
return system, resources, err
}

View File

@ -0,0 +1,78 @@
// +build windows,functional
//
// These unit tests must run on a system setup to run both Argons and Xenons,
// have docker installed, and have the nanoserver (WCOW) and alpine (LCOW)
// base images installed. The nanoserver image MUST match the build of the
// host.
//
// We rely on docker as the tools to extract a container image aren't
// open source. We use it to find the location of the base image on disk.
//
package hcsoci
//import (
// "bytes"
// "encoding/json"
// "io/ioutil"
// "os"
// "os/exec"
// "path/filepath"
// "strings"
// "testing"
// "github.com/Microsoft/hcsshim/internal/schemaversion"
// _ "github.com/Microsoft/hcsshim/test/assets"
// specs "github.com/opencontainers/runtime-spec/specs-go"
// "github.com/sirupsen/logrus"
//)
//func startUVM(t *testing.T, uvm *UtilityVM) {
// if err := uvm.Start(); err != nil {
// t.Fatalf("UVM %s Failed start: %s", uvm.Id, err)
// }
//}
//// Helper to shoot a utility VM
//func terminateUtilityVM(t *testing.T, uvm *UtilityVM) {
// if err := uvm.Terminate(); err != nil {
// t.Fatalf("Failed terminate utility VM %s", err)
// }
//}
//// TODO: Test UVMResourcesFromContainerSpec
//func TestUVMSizing(t *testing.T) {
// t.Skip("for now - not implemented at all")
//}
//// TestID validates that the requested ID is retrieved
//func TestID(t *testing.T) {
// t.Skip("fornow")
// tempDir := createWCOWTempDirWithSandbox(t)
// defer os.RemoveAll(tempDir)
// layers := append(layersNanoserver, tempDir)
// mountPath, err := mountContainerLayers(layers, nil)
// if err != nil {
// t.Fatalf("failed to mount container storage: %s", err)
// }
// defer unmountContainerLayers(layers, nil, unmountOperationAll)
// c, err := CreateContainer(&CreateOptions{
// Id: "gruntbuggly",
// SchemaVersion: schemaversion.SchemaV21(),
// Spec: &specs.Spec{
// Windows: &specs.Windows{LayerFolders: layers},
// Root: &specs.Root{Path: mountPath.(string)},
// },
// })
// if err != nil {
// t.Fatalf("Failed create: %s", err)
// }
// if c.ID() != "gruntbuggly" {
// t.Fatalf("id not set correctly: %s", c.ID())
// }
// c.Terminate()
//}

View File

@ -0,0 +1,115 @@
// +build windows
package hcsoci
import (
"encoding/json"
"github.com/Microsoft/hcsshim/internal/schema2"
"github.com/Microsoft/hcsshim/internal/schemaversion"
specs "github.com/opencontainers/runtime-spec/specs-go"
"github.com/sirupsen/logrus"
)
func createLCOWSpec(coi *createOptionsInternal) (*specs.Spec, error) {
// Remarshal the spec to perform a deep copy.
j, err := json.Marshal(coi.Spec)
if err != nil {
return nil, err
}
spec := &specs.Spec{}
err = json.Unmarshal(j, spec)
if err != nil {
return nil, err
}
// TODO
// Translate the mounts. The root has already been translated in
// allocateLinuxResources.
/*
for i := range spec.Mounts {
spec.Mounts[i].Source = "???"
spec.Mounts[i].Destination = "???"
}
*/
// Linux containers don't care about Windows aspects of the spec except the
// network namespace
spec.Windows = nil
if coi.Spec.Windows != nil &&
coi.Spec.Windows.Network != nil &&
coi.Spec.Windows.Network.NetworkNamespace != "" {
spec.Windows = &specs.Windows{
Network: &specs.WindowsNetwork{
NetworkNamespace: coi.Spec.Windows.Network.NetworkNamespace,
},
}
}
// Hooks are not supported (they should be run in the host)
spec.Hooks = nil
// Clear unsupported features
if spec.Linux.Resources != nil {
spec.Linux.Resources.Devices = nil
spec.Linux.Resources.Memory = nil
spec.Linux.Resources.Pids = nil
spec.Linux.Resources.BlockIO = nil
spec.Linux.Resources.HugepageLimits = nil
spec.Linux.Resources.Network = nil
}
spec.Linux.Seccomp = nil
// Clear any specified namespaces
var namespaces []specs.LinuxNamespace
for _, ns := range spec.Linux.Namespaces {
switch ns.Type {
case specs.NetworkNamespace:
default:
ns.Path = ""
namespaces = append(namespaces, ns)
}
}
spec.Linux.Namespaces = namespaces
return spec, nil
}
// This is identical to hcsschema.ComputeSystem but HostedSystem is an LCOW specific type - the schema docs only include WCOW.
type linuxComputeSystem struct {
Owner string `json:"Owner,omitempty"`
SchemaVersion *hcsschema.Version `json:"SchemaVersion,omitempty"`
HostingSystemId string `json:"HostingSystemId,omitempty"`
HostedSystem *linuxHostedSystem `json:"HostedSystem,omitempty"`
Container *hcsschema.Container `json:"Container,omitempty"`
VirtualMachine *hcsschema.VirtualMachine `json:"VirtualMachine,omitempty"`
ShouldTerminateOnLastHandleClosed bool `json:"ShouldTerminateOnLastHandleClosed,omitempty"`
}
type linuxHostedSystem struct {
SchemaVersion *hcsschema.Version
OciBundlePath string
OciSpecification *specs.Spec
}
func createLinuxContainerDocument(coi *createOptionsInternal, guestRoot string) (interface{}, error) {
spec, err := createLCOWSpec(coi)
if err != nil {
return nil, err
}
logrus.Debugf("hcsshim::createLinuxContainerDoc: guestRoot:%s", guestRoot)
v2 := &linuxComputeSystem{
Owner: coi.actualOwner,
SchemaVersion: schemaversion.SchemaV21(),
ShouldTerminateOnLastHandleClosed: true,
HostingSystemId: coi.HostingSystem.ID(),
HostedSystem: &linuxHostedSystem{
SchemaVersion: schemaversion.SchemaV21(),
OciBundlePath: guestRoot,
OciSpecification: spec,
},
}
return v2, nil
}

View File

@ -0,0 +1,273 @@
// +build windows
package hcsoci
import (
"fmt"
"path/filepath"
"regexp"
"runtime"
"strings"
"github.com/Microsoft/hcsshim/internal/schema1"
"github.com/Microsoft/hcsshim/internal/schema2"
"github.com/Microsoft/hcsshim/internal/schemaversion"
"github.com/Microsoft/hcsshim/internal/uvm"
"github.com/Microsoft/hcsshim/internal/uvmfolder"
"github.com/Microsoft/hcsshim/internal/wclayer"
"github.com/Microsoft/hcsshim/osversion"
"github.com/sirupsen/logrus"
)
// createWindowsContainerDocument creates a document suitable for calling HCS to create
// a container, both hosted and process isolated. It can create both v1 and v2
// schema, WCOW only. The containers storage should have been mounted already.
func createWindowsContainerDocument(coi *createOptionsInternal) (interface{}, error) {
logrus.Debugf("hcsshim: CreateHCSContainerDocument")
// TODO: Make this safe if exported so no null pointer dereferences.
if coi.Spec == nil {
return nil, fmt.Errorf("cannot create HCS container document - OCI spec is missing")
}
if coi.Spec.Windows == nil {
return nil, fmt.Errorf("cannot create HCS container document - OCI spec Windows section is missing ")
}
v1 := &schema1.ContainerConfig{
SystemType: "Container",
Name: coi.actualID,
Owner: coi.actualOwner,
HvPartition: false,
IgnoreFlushesDuringBoot: coi.Spec.Windows.IgnoreFlushesDuringBoot,
}
// IgnoreFlushesDuringBoot is a property of the SCSI attachment for the scratch. Set when it's hot-added to the utility VM
// ID is a property on the create call in V2 rather than part of the schema.
v2 := &hcsschema.ComputeSystem{
Owner: coi.actualOwner,
SchemaVersion: schemaversion.SchemaV21(),
ShouldTerminateOnLastHandleClosed: true,
}
v2Container := &hcsschema.Container{Storage: &hcsschema.Storage{}}
// TODO: Still want to revisit this.
if coi.Spec.Windows.LayerFolders == nil || len(coi.Spec.Windows.LayerFolders) < 2 {
return nil, fmt.Errorf("invalid spec - not enough layer folders supplied")
}
if coi.Spec.Hostname != "" {
v1.HostName = coi.Spec.Hostname
v2Container.GuestOs = &hcsschema.GuestOs{HostName: coi.Spec.Hostname}
}
if coi.Spec.Windows.Resources != nil {
if coi.Spec.Windows.Resources.CPU != nil {
if coi.Spec.Windows.Resources.CPU.Count != nil ||
coi.Spec.Windows.Resources.CPU.Shares != nil ||
coi.Spec.Windows.Resources.CPU.Maximum != nil {
v2Container.Processor = &hcsschema.Processor{}
}
if coi.Spec.Windows.Resources.CPU.Count != nil {
cpuCount := *coi.Spec.Windows.Resources.CPU.Count
hostCPUCount := uint64(runtime.NumCPU())
if cpuCount > hostCPUCount {
logrus.Warnf("Changing requested CPUCount of %d to current number of processors, %d", cpuCount, hostCPUCount)
cpuCount = hostCPUCount
}
v1.ProcessorCount = uint32(cpuCount)
v2Container.Processor.Count = int32(cpuCount)
}
if coi.Spec.Windows.Resources.CPU.Shares != nil {
v1.ProcessorWeight = uint64(*coi.Spec.Windows.Resources.CPU.Shares)
v2Container.Processor.Weight = int32(v1.ProcessorWeight)
}
if coi.Spec.Windows.Resources.CPU.Maximum != nil {
v1.ProcessorMaximum = int64(*coi.Spec.Windows.Resources.CPU.Maximum)
v2Container.Processor.Maximum = int32(v1.ProcessorMaximum)
}
}
if coi.Spec.Windows.Resources.Memory != nil {
if coi.Spec.Windows.Resources.Memory.Limit != nil {
v1.MemoryMaximumInMB = int64(*coi.Spec.Windows.Resources.Memory.Limit) / 1024 / 1024
v2Container.Memory = &hcsschema.Memory{SizeInMB: int32(v1.MemoryMaximumInMB)}
}
}
if coi.Spec.Windows.Resources.Storage != nil {
if coi.Spec.Windows.Resources.Storage.Bps != nil || coi.Spec.Windows.Resources.Storage.Iops != nil {
v2Container.Storage.QoS = &hcsschema.StorageQoS{}
}
if coi.Spec.Windows.Resources.Storage.Bps != nil {
v1.StorageBandwidthMaximum = *coi.Spec.Windows.Resources.Storage.Bps
v2Container.Storage.QoS.BandwidthMaximum = int32(v1.StorageBandwidthMaximum)
}
if coi.Spec.Windows.Resources.Storage.Iops != nil {
v1.StorageIOPSMaximum = *coi.Spec.Windows.Resources.Storage.Iops
v2Container.Storage.QoS.IopsMaximum = int32(*coi.Spec.Windows.Resources.Storage.Iops)
}
}
}
// TODO V2 networking. Only partial at the moment. v2.Container.Networking.Namespace specifically
if coi.Spec.Windows.Network != nil {
v2Container.Networking = &hcsschema.Networking{}
v1.EndpointList = coi.Spec.Windows.Network.EndpointList
v2Container.Networking.Namespace = coi.actualNetworkNamespace
v1.AllowUnqualifiedDNSQuery = coi.Spec.Windows.Network.AllowUnqualifiedDNSQuery
v2Container.Networking.AllowUnqualifiedDnsQuery = v1.AllowUnqualifiedDNSQuery
if coi.Spec.Windows.Network.DNSSearchList != nil {
v1.DNSSearchList = strings.Join(coi.Spec.Windows.Network.DNSSearchList, ",")
v2Container.Networking.DnsSearchList = v1.DNSSearchList
}
v1.NetworkSharedContainerName = coi.Spec.Windows.Network.NetworkSharedContainerName
v2Container.Networking.NetworkSharedContainerName = v1.NetworkSharedContainerName
}
// // TODO V2 Credentials not in the schema yet.
if cs, ok := coi.Spec.Windows.CredentialSpec.(string); ok {
v1.Credentials = cs
}
if coi.Spec.Root == nil {
return nil, fmt.Errorf("spec is invalid - root isn't populated")
}
if coi.Spec.Root.Readonly {
return nil, fmt.Errorf(`invalid container spec - readonly is not supported for Windows containers`)
}
// Strip off the top-most RW/scratch layer as that's passed in separately to HCS for v1
v1.LayerFolderPath = coi.Spec.Windows.LayerFolders[len(coi.Spec.Windows.LayerFolders)-1]
if (schemaversion.IsV21(coi.actualSchemaVersion) && coi.HostingSystem == nil) ||
(schemaversion.IsV10(coi.actualSchemaVersion) && coi.Spec.Windows.HyperV == nil) {
// Argon v1 or v2.
const volumeGUIDRegex = `^\\\\\?\\(Volume)\{{0,1}[0-9a-fA-F]{8}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{12}(\}){0,1}\}(|\\)$`
if matched, err := regexp.MatchString(volumeGUIDRegex, coi.Spec.Root.Path); !matched || err != nil {
return nil, fmt.Errorf(`invalid container spec - Root.Path '%s' must be a volume GUID path in the format '\\?\Volume{GUID}\'`, coi.Spec.Root.Path)
}
if coi.Spec.Root.Path[len(coi.Spec.Root.Path)-1] != '\\' {
coi.Spec.Root.Path += `\` // Be nice to clients and make sure well-formed for back-compat
}
v1.VolumePath = coi.Spec.Root.Path[:len(coi.Spec.Root.Path)-1] // Strip the trailing backslash. Required for v1.
v2Container.Storage.Path = coi.Spec.Root.Path
} else {
// A hosting system was supplied, implying v2 Xenon; OR a v1 Xenon.
if schemaversion.IsV10(coi.actualSchemaVersion) {
// V1 Xenon
v1.HvPartition = true
if coi.Spec == nil || coi.Spec.Windows == nil || coi.Spec.Windows.HyperV == nil { // Be resilient to nil de-reference
return nil, fmt.Errorf(`invalid container spec - Spec.Windows.HyperV is nil`)
}
if coi.Spec.Windows.HyperV.UtilityVMPath != "" {
// Client-supplied utility VM path
v1.HvRuntime = &schema1.HvRuntime{ImagePath: coi.Spec.Windows.HyperV.UtilityVMPath}
} else {
// Client was lazy. Let's locate it from the layer folders instead.
uvmImagePath, err := uvmfolder.LocateUVMFolder(coi.Spec.Windows.LayerFolders)
if err != nil {
return nil, err
}
v1.HvRuntime = &schema1.HvRuntime{ImagePath: filepath.Join(uvmImagePath, `UtilityVM`)}
}
} else {
// Hosting system was supplied, so is v2 Xenon.
v2Container.Storage.Path = coi.Spec.Root.Path
if coi.HostingSystem.OS() == "windows" {
layers, err := computeV2Layers(coi.HostingSystem, coi.Spec.Windows.LayerFolders[:len(coi.Spec.Windows.LayerFolders)-1])
if err != nil {
return nil, err
}
v2Container.Storage.Layers = layers
}
}
}
if coi.HostingSystem == nil { // Argon v1 or v2
for _, layerPath := range coi.Spec.Windows.LayerFolders[:len(coi.Spec.Windows.LayerFolders)-1] {
layerID, err := wclayer.LayerID(layerPath)
if err != nil {
return nil, err
}
v1.Layers = append(v1.Layers, schema1.Layer{ID: layerID.String(), Path: layerPath})
v2Container.Storage.Layers = append(v2Container.Storage.Layers, hcsschema.Layer{Id: layerID.String(), Path: layerPath})
}
}
// Add the mounts as mapped directories or mapped pipes
// TODO: Mapped pipes to add in v2 schema.
var (
mdsv1 []schema1.MappedDir
mpsv1 []schema1.MappedPipe
mdsv2 []hcsschema.MappedDirectory
mpsv2 []hcsschema.MappedPipe
)
for _, mount := range coi.Spec.Mounts {
const pipePrefix = `\\.\pipe\`
if mount.Type != "" {
return nil, fmt.Errorf("invalid container spec - Mount.Type '%s' must not be set", mount.Type)
}
if strings.HasPrefix(strings.ToLower(mount.Destination), pipePrefix) {
mpsv1 = append(mpsv1, schema1.MappedPipe{HostPath: mount.Source, ContainerPipeName: mount.Destination[len(pipePrefix):]})
mpsv2 = append(mpsv2, hcsschema.MappedPipe{HostPath: mount.Source, ContainerPipeName: mount.Destination[len(pipePrefix):]})
} else {
readOnly := false
for _, o := range mount.Options {
if strings.ToLower(o) == "ro" {
readOnly = true
}
}
mdv1 := schema1.MappedDir{HostPath: mount.Source, ContainerPath: mount.Destination, ReadOnly: readOnly}
mdv2 := hcsschema.MappedDirectory{ContainerPath: mount.Destination, ReadOnly: readOnly}
if coi.HostingSystem == nil {
mdv2.HostPath = mount.Source
} else {
uvmPath, err := coi.HostingSystem.GetVSMBUvmPath(mount.Source)
if err != nil {
if err == uvm.ErrNotAttached {
// It could also be a scsi mount.
uvmPath, err = coi.HostingSystem.GetScsiUvmPath(mount.Source)
if err != nil {
return nil, err
}
} else {
return nil, err
}
}
mdv2.HostPath = uvmPath
}
mdsv1 = append(mdsv1, mdv1)
mdsv2 = append(mdsv2, mdv2)
}
}
v1.MappedDirectories = mdsv1
v2Container.MappedDirectories = mdsv2
if len(mpsv1) > 0 && osversion.Get().Build < osversion.RS3 {
return nil, fmt.Errorf("named pipe mounts are not supported on this version of Windows")
}
v1.MappedPipes = mpsv1
v2Container.MappedPipes = mpsv2
// Put the v2Container object as a HostedSystem for a Xenon, or directly in the schema for an Argon.
if coi.HostingSystem == nil {
v2.Container = v2Container
} else {
v2.HostingSystemId = coi.HostingSystem.ID()
v2.HostedSystem = &hcsschema.HostedSystem{
SchemaVersion: schemaversion.SchemaV21(),
Container: v2Container,
}
}
if schemaversion.IsV10(coi.actualSchemaVersion) {
return v1, nil
}
return v2, nil
}

View File

@ -0,0 +1,373 @@
// +build windows
package hcsoci
import (
"fmt"
"os"
"path"
"path/filepath"
"github.com/Microsoft/hcsshim/internal/guestrequest"
"github.com/Microsoft/hcsshim/internal/ospath"
"github.com/Microsoft/hcsshim/internal/requesttype"
"github.com/Microsoft/hcsshim/internal/schema2"
"github.com/Microsoft/hcsshim/internal/uvm"
"github.com/Microsoft/hcsshim/internal/wclayer"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
type lcowLayerEntry struct {
hostPath string
uvmPath string
scsi bool
}
const scratchPath = "scratch"
// mountContainerLayers is a helper for clients to hide all the complexity of layer mounting
// Layer folder are in order: base, [rolayer1..rolayern,] scratch
//
// v1/v2: Argon WCOW: Returns the mount path on the host as a volume GUID.
// v1: Xenon WCOW: Done internally in HCS, so no point calling doing anything here.
// v2: Xenon WCOW: Returns a CombinedLayersV2 structure where ContainerRootPath is a folder
// inside the utility VM which is a GUID mapping of the scratch folder. Each
// of the layers are the VSMB locations where the read-only layers are mounted.
//
func MountContainerLayers(layerFolders []string, guestRoot string, uvm *uvm.UtilityVM) (interface{}, error) {
logrus.Debugln("hcsshim::mountContainerLayers", layerFolders)
if uvm == nil {
if len(layerFolders) < 2 {
return nil, fmt.Errorf("need at least two layers - base and scratch")
}
path := layerFolders[len(layerFolders)-1]
rest := layerFolders[:len(layerFolders)-1]
logrus.Debugln("hcsshim::mountContainerLayers ActivateLayer", path)
if err := wclayer.ActivateLayer(path); err != nil {
return nil, err
}
logrus.Debugln("hcsshim::mountContainerLayers Preparelayer", path, rest)
if err := wclayer.PrepareLayer(path, rest); err != nil {
if err2 := wclayer.DeactivateLayer(path); err2 != nil {
logrus.Warnf("Failed to Deactivate %s: %s", path, err)
}
return nil, err
}
mountPath, err := wclayer.GetLayerMountPath(path)
if err != nil {
if err := wclayer.UnprepareLayer(path); err != nil {
logrus.Warnf("Failed to Unprepare %s: %s", path, err)
}
if err2 := wclayer.DeactivateLayer(path); err2 != nil {
logrus.Warnf("Failed to Deactivate %s: %s", path, err)
}
return nil, err
}
return mountPath, nil
}
// V2 UVM
logrus.Debugf("hcsshim::mountContainerLayers Is a %s V2 UVM", uvm.OS())
// Add each read-only layers. For Windows, this is a VSMB share with the ResourceUri ending in
// a GUID based on the folder path. For Linux, this is a VPMEM device, except where is over the
// max size supported, where we put it on SCSI instead.
//
// Each layer is ref-counted so that multiple containers in the same utility VM can share them.
var wcowLayersAdded []string
var lcowlayersAdded []lcowLayerEntry
attachedSCSIHostPath := ""
for _, layerPath := range layerFolders[:len(layerFolders)-1] {
var err error
if uvm.OS() == "windows" {
options := &hcsschema.VirtualSmbShareOptions{
ReadOnly: true,
PseudoOplocks: true,
TakeBackupPrivilege: true,
CacheIo: true,
ShareRead: true,
}
err = uvm.AddVSMB(layerPath, "", options)
if err == nil {
wcowLayersAdded = append(wcowLayersAdded, layerPath)
}
} else {
uvmPath := ""
hostPath := filepath.Join(layerPath, "layer.vhd")
var fi os.FileInfo
fi, err = os.Stat(hostPath)
if err == nil && uint64(fi.Size()) > uvm.PMemMaxSizeBytes() {
// Too big for PMEM. Add on SCSI instead (at /tmp/S<C>/<L>).
var (
controller int
lun int32
)
controller, lun, err = uvm.AddSCSILayer(hostPath)
if err == nil {
lcowlayersAdded = append(lcowlayersAdded,
lcowLayerEntry{
hostPath: hostPath,
uvmPath: fmt.Sprintf("/tmp/S%d/%d", controller, lun),
scsi: true,
})
}
} else {
_, uvmPath, err = uvm.AddVPMEM(hostPath, true) // UVM path is calculated. Will be /tmp/vN/
if err == nil {
lcowlayersAdded = append(lcowlayersAdded,
lcowLayerEntry{
hostPath: hostPath,
uvmPath: uvmPath,
})
}
}
}
if err != nil {
cleanupOnMountFailure(uvm, wcowLayersAdded, lcowlayersAdded, attachedSCSIHostPath)
return nil, err
}
}
// Add the scratch at an unused SCSI location. The container path inside the
// utility VM will be C:\<ID>.
hostPath := filepath.Join(layerFolders[len(layerFolders)-1], "sandbox.vhdx")
// BUGBUG Rename guestRoot better.
containerScratchPathInUVM := ospath.Join(uvm.OS(), guestRoot, scratchPath)
_, _, err := uvm.AddSCSI(hostPath, containerScratchPathInUVM, false)
if err != nil {
cleanupOnMountFailure(uvm, wcowLayersAdded, lcowlayersAdded, attachedSCSIHostPath)
return nil, err
}
attachedSCSIHostPath = hostPath
if uvm.OS() == "windows" {
// Load the filter at the C:\s<ID> location calculated above. We pass into this request each of the
// read-only layer folders.
layers, err := computeV2Layers(uvm, wcowLayersAdded)
if err != nil {
cleanupOnMountFailure(uvm, wcowLayersAdded, lcowlayersAdded, attachedSCSIHostPath)
return nil, err
}
guestRequest := guestrequest.CombinedLayers{
ContainerRootPath: containerScratchPathInUVM,
Layers: layers,
}
combinedLayersModification := &hcsschema.ModifySettingRequest{
GuestRequest: guestrequest.GuestRequest{
Settings: guestRequest,
ResourceType: guestrequest.ResourceTypeCombinedLayers,
RequestType: requesttype.Add,
},
}
if err := uvm.Modify(combinedLayersModification); err != nil {
cleanupOnMountFailure(uvm, wcowLayersAdded, lcowlayersAdded, attachedSCSIHostPath)
return nil, err
}
logrus.Debugln("hcsshim::mountContainerLayers Succeeded")
return guestRequest, nil
}
// This is the LCOW layout inside the utilityVM. NNN is the container "number"
// which increments for each container created in a utility VM.
//
// /run/gcs/c/NNN/config.json
// /run/gcs/c/NNN/rootfs
// /run/gcs/c/NNN/scratch/upper
// /run/gcs/c/NNN/scratch/work
//
// /dev/sda on /tmp/scratch type ext4 (rw,relatime,block_validity,delalloc,barrier,user_xattr,acl)
// /dev/pmem0 on /tmp/v0 type ext4 (ro,relatime,block_validity,delalloc,norecovery,barrier,dax,user_xattr,acl)
// /dev/sdb on /run/gcs/c/NNN/scratch type ext4 (rw,relatime,block_validity,delalloc,barrier,user_xattr,acl)
// overlay on /run/gcs/c/NNN/rootfs type overlay (rw,relatime,lowerdir=/tmp/v0,upperdir=/run/gcs/c/NNN/scratch/upper,workdir=/run/gcs/c/NNN/scratch/work)
//
// Where /dev/sda is the scratch for utility VM itself
// /dev/pmemX are read-only layers for containers
// /dev/sd(b...) are scratch spaces for each container
layers := []hcsschema.Layer{}
for _, l := range lcowlayersAdded {
layers = append(layers, hcsschema.Layer{Path: l.uvmPath})
}
guestRequest := guestrequest.CombinedLayers{
ContainerRootPath: path.Join(guestRoot, rootfsPath),
Layers: layers,
ScratchPath: containerScratchPathInUVM,
}
combinedLayersModification := &hcsschema.ModifySettingRequest{
GuestRequest: guestrequest.GuestRequest{
ResourceType: guestrequest.ResourceTypeCombinedLayers,
RequestType: requesttype.Add,
Settings: guestRequest,
},
}
if err := uvm.Modify(combinedLayersModification); err != nil {
cleanupOnMountFailure(uvm, wcowLayersAdded, lcowlayersAdded, attachedSCSIHostPath)
return nil, err
}
logrus.Debugln("hcsshim::mountContainerLayers Succeeded")
return guestRequest, nil
}
// UnmountOperation is used when calling Unmount() to determine what type of unmount is
// required. In V1 schema, this must be unmountOperationAll. In V2, client can
// be more optimal and only unmount what they need which can be a minor performance
// improvement (eg if you know only one container is running in a utility VM, and
// the UVM is about to be torn down, there's no need to unmount the VSMB shares,
// just SCSI to have a consistent file system).
type UnmountOperation uint
const (
UnmountOperationSCSI UnmountOperation = 0x01
UnmountOperationVSMB = 0x02
UnmountOperationVPMEM = 0x04
UnmountOperationAll = UnmountOperationSCSI | UnmountOperationVSMB | UnmountOperationVPMEM
)
// UnmountContainerLayers is a helper for clients to hide all the complexity of layer unmounting
func UnmountContainerLayers(layerFolders []string, guestRoot string, uvm *uvm.UtilityVM, op UnmountOperation) error {
logrus.Debugln("hcsshim::unmountContainerLayers", layerFolders)
if uvm == nil {
// Must be an argon - folders are mounted on the host
if op != UnmountOperationAll {
return fmt.Errorf("only operation supported for host-mounted folders is unmountOperationAll")
}
if len(layerFolders) < 1 {
return fmt.Errorf("need at least one layer for Unmount")
}
path := layerFolders[len(layerFolders)-1]
logrus.Debugln("hcsshim::Unmount UnprepareLayer", path)
if err := wclayer.UnprepareLayer(path); err != nil {
return err
}
// TODO Should we try this anyway?
logrus.Debugln("hcsshim::unmountContainerLayers DeactivateLayer", path)
return wclayer.DeactivateLayer(path)
}
// V2 Xenon
// Base+Scratch as a minimum. This is different to v1 which only requires the scratch
if len(layerFolders) < 2 {
return fmt.Errorf("at least two layers are required for unmount")
}
var retError error
// Unload the storage filter followed by the SCSI scratch
if (op & UnmountOperationSCSI) == UnmountOperationSCSI {
containerScratchPathInUVM := ospath.Join(uvm.OS(), guestRoot, scratchPath)
logrus.Debugf("hcsshim::unmountContainerLayers CombinedLayers %s", containerScratchPathInUVM)
combinedLayersModification := &hcsschema.ModifySettingRequest{
GuestRequest: guestrequest.GuestRequest{
ResourceType: guestrequest.ResourceTypeCombinedLayers,
RequestType: requesttype.Remove,
Settings: guestrequest.CombinedLayers{ContainerRootPath: containerScratchPathInUVM},
},
}
if err := uvm.Modify(combinedLayersModification); err != nil {
logrus.Errorf(err.Error())
}
// Hot remove the scratch from the SCSI controller
hostScratchFile := filepath.Join(layerFolders[len(layerFolders)-1], "sandbox.vhdx")
logrus.Debugf("hcsshim::unmountContainerLayers SCSI %s %s", containerScratchPathInUVM, hostScratchFile)
if err := uvm.RemoveSCSI(hostScratchFile); err != nil {
e := fmt.Errorf("failed to remove SCSI %s: %s", hostScratchFile, err)
logrus.Debugln(e)
if retError == nil {
retError = e
} else {
retError = errors.Wrapf(retError, e.Error())
}
}
}
// Remove each of the read-only layers from VSMB. These's are ref-counted and
// only removed once the count drops to zero. This allows multiple containers
// to share layers.
if uvm.OS() == "windows" && len(layerFolders) > 1 && (op&UnmountOperationVSMB) == UnmountOperationVSMB {
for _, layerPath := range layerFolders[:len(layerFolders)-1] {
if e := uvm.RemoveVSMB(layerPath); e != nil {
logrus.Debugln(e)
if retError == nil {
retError = e
} else {
retError = errors.Wrapf(retError, e.Error())
}
}
}
}
// Remove each of the read-only layers from VPMEM (or SCSI). These's are ref-counted
// and only removed once the count drops to zero. This allows multiple containers to
// share layers. Note that SCSI is used on large layers.
if uvm.OS() == "linux" && len(layerFolders) > 1 && (op&UnmountOperationVPMEM) == UnmountOperationVPMEM {
for _, layerPath := range layerFolders[:len(layerFolders)-1] {
hostPath := filepath.Join(layerPath, "layer.vhd")
if fi, err := os.Stat(hostPath); err != nil {
var e error
if uint64(fi.Size()) > uvm.PMemMaxSizeBytes() {
e = uvm.RemoveSCSI(hostPath)
} else {
e = uvm.RemoveVPMEM(hostPath)
}
if e != nil {
logrus.Debugln(e)
if retError == nil {
retError = e
} else {
retError = errors.Wrapf(retError, e.Error())
}
}
}
}
}
// TODO (possibly) Consider deleting the container directory in the utility VM
return retError
}
func cleanupOnMountFailure(uvm *uvm.UtilityVM, wcowLayers []string, lcowLayers []lcowLayerEntry, scratchHostPath string) {
for _, wl := range wcowLayers {
if err := uvm.RemoveVSMB(wl); err != nil {
logrus.Warnf("Possibly leaked vsmbshare on error removal path: %s", err)
}
}
for _, ll := range lcowLayers {
if ll.scsi {
if err := uvm.RemoveSCSI(ll.hostPath); err != nil {
logrus.Warnf("Possibly leaked SCSI on error removal path: %s", err)
}
} else if err := uvm.RemoveVPMEM(ll.hostPath); err != nil {
logrus.Warnf("Possibly leaked vpmemdevice on error removal path: %s", err)
}
}
if scratchHostPath != "" {
if err := uvm.RemoveSCSI(scratchHostPath); err != nil {
logrus.Warnf("Possibly leaked SCSI disk on error removal path: %s", err)
}
}
}
func computeV2Layers(vm *uvm.UtilityVM, paths []string) (layers []hcsschema.Layer, err error) {
for _, path := range paths {
uvmPath, err := vm.GetVSMBUvmPath(path)
if err != nil {
return nil, err
}
layerID, err := wclayer.LayerID(path)
if err != nil {
return nil, err
}
layers = append(layers, hcsschema.Layer{Id: layerID.String(), Path: uvmPath})
}
return layers, nil
}

View File

@ -0,0 +1,41 @@
package hcsoci
import (
"github.com/Microsoft/hcsshim/internal/hns"
"github.com/sirupsen/logrus"
)
func createNetworkNamespace(coi *createOptionsInternal, resources *Resources) error {
netID, err := hns.CreateNamespace()
if err != nil {
return err
}
logrus.Infof("created network namespace %s for %s", netID, coi.ID)
resources.netNS = netID
resources.createdNetNS = true
for _, endpointID := range coi.Spec.Windows.Network.EndpointList {
err = hns.AddNamespaceEndpoint(netID, endpointID)
if err != nil {
return err
}
logrus.Infof("added network endpoint %s to namespace %s", endpointID, netID)
resources.networkEndpoints = append(resources.networkEndpoints, endpointID)
}
return nil
}
func getNamespaceEndpoints(netNS string) ([]*hns.HNSEndpoint, error) {
ids, err := hns.GetNamespaceEndpoints(netNS)
if err != nil {
return nil, err
}
var endpoints []*hns.HNSEndpoint
for _, id := range ids {
endpoint, err := hns.GetHNSEndpointByID(id)
if err != nil {
return nil, err
}
endpoints = append(endpoints, endpoint)
}
return endpoints, nil
}

View File

@ -0,0 +1,127 @@
package hcsoci
import (
"os"
"github.com/Microsoft/hcsshim/internal/hns"
"github.com/Microsoft/hcsshim/internal/uvm"
"github.com/sirupsen/logrus"
)
// NetNS returns the network namespace for the container
func (r *Resources) NetNS() string {
return r.netNS
}
// Resources is the structure returned as part of creating a container. It holds
// nothing useful to clients, hence everything is lowercased. A client would use
// it in a call to ReleaseResource to ensure everything is cleaned up when a
// container exits.
type Resources struct {
// containerRootInUVM is the base path in a utility VM where elements relating
// to a container are exposed. For example, the mounted filesystem; the runtime
// spec (in the case of LCOW); overlay and scratch (in the case of LCOW).
//
// For WCOW, this will be under C:\c\N, and for LCOW this will
// be under /run/gcs/c/N. N is an atomic counter for each container created
// in that utility VM. For LCOW this is also the "OCI Bundle Path".
containerRootInUVM string
// layers is an array of the layer folder paths which have been mounted either on
// the host in the case or a WCOW Argon, or in a utility VM for WCOW Xenon and LCOW.
layers []string
// vsmbMounts is an array of the host-paths mounted into a utility VM to support
// (bind-)mounts into a WCOW v2 Xenon.
vsmbMounts []string
// plan9Mounts is an array of all the host paths which have been added to
// an LCOW utility VM
plan9Mounts []string
// netNS is the network namespace
netNS string
// networkEndpoints is the list of network endpoints used by the container
networkEndpoints []string
// createNetNS indicates if the network namespace has been created
createdNetNS bool
// addedNetNSToVM indicates if the network namespace has been added to the containers utility VM
addedNetNSToVM bool
// scsiMounts is an array of the host-paths mounted into a utility VM to
// support scsi device passthrough.
scsiMounts []string
}
// TODO: Method on the resources?
func ReleaseResources(r *Resources, vm *uvm.UtilityVM, all bool) error {
if vm != nil && r.addedNetNSToVM {
err := vm.RemoveNetNS(r.netNS)
if err != nil {
logrus.Warn(err)
}
r.addedNetNSToVM = false
}
if r.createdNetNS {
for len(r.networkEndpoints) != 0 {
endpoint := r.networkEndpoints[len(r.networkEndpoints)-1]
err := hns.RemoveNamespaceEndpoint(r.netNS, endpoint)
if err != nil {
if !os.IsNotExist(err) {
return err
}
logrus.Warnf("removing endpoint %s from namespace %s: does not exist", endpoint, r.NetNS())
}
r.networkEndpoints = r.networkEndpoints[:len(r.networkEndpoints)-1]
}
r.networkEndpoints = nil
err := hns.RemoveNamespace(r.netNS)
if err != nil && !os.IsNotExist(err) {
return err
}
r.createdNetNS = false
}
if len(r.layers) != 0 {
op := UnmountOperationSCSI
if vm == nil || all {
op = UnmountOperationAll
}
err := UnmountContainerLayers(r.layers, r.containerRootInUVM, vm, op)
if err != nil {
return err
}
r.layers = nil
}
if all {
for len(r.vsmbMounts) != 0 {
mount := r.vsmbMounts[len(r.vsmbMounts)-1]
if err := vm.RemoveVSMB(mount); err != nil {
return err
}
r.vsmbMounts = r.vsmbMounts[:len(r.vsmbMounts)-1]
}
for len(r.plan9Mounts) != 0 {
mount := r.plan9Mounts[len(r.plan9Mounts)-1]
if err := vm.RemovePlan9(mount); err != nil {
return err
}
r.plan9Mounts = r.plan9Mounts[:len(r.plan9Mounts)-1]
}
for _, path := range r.scsiMounts {
if err := vm.RemoveSCSI(path); err != nil {
return err
}
r.scsiMounts = nil
}
}
return nil
}

View File

@ -0,0 +1,104 @@
// +build windows
package hcsoci
// Contains functions relating to a LCOW container, as opposed to a utility VM
import (
"fmt"
"path"
"strconv"
"strings"
"github.com/Microsoft/hcsshim/internal/guestrequest"
specs "github.com/opencontainers/runtime-spec/specs-go"
"github.com/sirupsen/logrus"
)
const rootfsPath = "rootfs"
const mountPathPrefix = "m"
func allocateLinuxResources(coi *createOptionsInternal, resources *Resources) error {
if coi.Spec.Root == nil {
coi.Spec.Root = &specs.Root{}
}
if coi.Spec.Root.Path == "" {
logrus.Debugln("hcsshim::allocateLinuxResources mounting storage")
mcl, err := MountContainerLayers(coi.Spec.Windows.LayerFolders, resources.containerRootInUVM, coi.HostingSystem)
if err != nil {
return fmt.Errorf("failed to mount container storage: %s", err)
}
if coi.HostingSystem == nil {
coi.Spec.Root.Path = mcl.(string) // Argon v1 or v2
} else {
coi.Spec.Root.Path = mcl.(guestrequest.CombinedLayers).ContainerRootPath // v2 Xenon LCOW
}
resources.layers = coi.Spec.Windows.LayerFolders
} else {
// This is the "Plan 9" root filesystem.
// TODO: We need a test for this. Ask @jstarks how you can even lay this out on Windows.
hostPath := coi.Spec.Root.Path
uvmPathForContainersFileSystem := path.Join(resources.containerRootInUVM, rootfsPath)
err := coi.HostingSystem.AddPlan9(hostPath, uvmPathForContainersFileSystem, coi.Spec.Root.Readonly)
if err != nil {
return fmt.Errorf("adding plan9 root: %s", err)
}
coi.Spec.Root.Path = uvmPathForContainersFileSystem
resources.plan9Mounts = append(resources.plan9Mounts, hostPath)
}
for i, mount := range coi.Spec.Mounts {
switch mount.Type {
case "bind":
case "physical-disk":
case "virtual-disk":
default:
// Unknown mount type
continue
}
if mount.Destination == "" || mount.Source == "" {
return fmt.Errorf("invalid OCI spec - a mount must have both source and a destination: %+v", mount)
}
if coi.HostingSystem != nil {
hostPath := mount.Source
uvmPathForShare := path.Join(resources.containerRootInUVM, mountPathPrefix+strconv.Itoa(i))
readOnly := false
for _, o := range mount.Options {
if strings.ToLower(o) == "ro" {
readOnly = true
break
}
}
if mount.Type == "physical-disk" {
logrus.Debugf("hcsshim::allocateLinuxResources Hot-adding SCSI physical disk for OCI mount %+v", mount)
_, _, err := coi.HostingSystem.AddSCSIPhysicalDisk(hostPath, uvmPathForShare, readOnly)
if err != nil {
return fmt.Errorf("adding SCSI physical disk mount %+v: %s", mount, err)
}
resources.scsiMounts = append(resources.scsiMounts, hostPath)
coi.Spec.Mounts[i].Type = "none"
} else if mount.Type == "virtual-disk" {
logrus.Debugf("hcsshim::allocateLinuxResources Hot-adding SCSI virtual disk for OCI mount %+v", mount)
_, _, err := coi.HostingSystem.AddSCSI(hostPath, uvmPathForShare, readOnly)
if err != nil {
return fmt.Errorf("adding SCSI virtual disk mount %+v: %s", mount, err)
}
resources.scsiMounts = append(resources.scsiMounts, hostPath)
coi.Spec.Mounts[i].Type = "none"
} else {
logrus.Debugf("hcsshim::allocateLinuxResources Hot-adding Plan9 for OCI mount %+v", mount)
err := coi.HostingSystem.AddPlan9(hostPath, uvmPathForShare, readOnly)
if err != nil {
return fmt.Errorf("adding plan9 mount %+v: %s", mount, err)
}
resources.plan9Mounts = append(resources.plan9Mounts, hostPath)
}
coi.Spec.Mounts[i].Source = uvmPathForShare
}
}
return nil
}

View File

@ -0,0 +1,127 @@
// +build windows
package hcsoci
// Contains functions relating to a WCOW container, as opposed to a utility VM
import (
"fmt"
"os"
"path/filepath"
"strings"
"github.com/Microsoft/hcsshim/internal/guestrequest"
"github.com/Microsoft/hcsshim/internal/schema2"
"github.com/Microsoft/hcsshim/internal/schemaversion"
"github.com/Microsoft/hcsshim/internal/wclayer"
specs "github.com/opencontainers/runtime-spec/specs-go"
"github.com/sirupsen/logrus"
)
func allocateWindowsResources(coi *createOptionsInternal, resources *Resources) error {
if coi.Spec == nil || coi.Spec.Windows == nil || coi.Spec.Windows.LayerFolders == nil {
return fmt.Errorf("field 'Spec.Windows.Layerfolders' is not populated")
}
scratchFolder := coi.Spec.Windows.LayerFolders[len(coi.Spec.Windows.LayerFolders)-1]
logrus.Debugf("hcsshim::allocateWindowsResources scratch folder: %s", scratchFolder)
// TODO: Remove this code for auto-creation. Make the caller responsible.
// Create the directory for the RW scratch layer if it doesn't exist
if _, err := os.Stat(scratchFolder); os.IsNotExist(err) {
logrus.Debugf("hcsshim::allocateWindowsResources container scratch folder does not exist so creating: %s ", scratchFolder)
if err := os.MkdirAll(scratchFolder, 0777); err != nil {
return fmt.Errorf("failed to auto-create container scratch folder %s: %s", scratchFolder, err)
}
}
// Create sandbox.vhdx if it doesn't exist in the scratch folder. It's called sandbox.vhdx
// rather than scratch.vhdx as in the v1 schema, it's hard-coded in HCS.
if _, err := os.Stat(filepath.Join(scratchFolder, "sandbox.vhdx")); os.IsNotExist(err) {
logrus.Debugf("hcsshim::allocateWindowsResources container sandbox.vhdx does not exist so creating in %s ", scratchFolder)
if err := wclayer.CreateScratchLayer(scratchFolder, coi.Spec.Windows.LayerFolders[:len(coi.Spec.Windows.LayerFolders)-1]); err != nil {
return fmt.Errorf("failed to CreateSandboxLayer %s", err)
}
}
if coi.Spec.Root == nil {
coi.Spec.Root = &specs.Root{}
}
if coi.Spec.Root.Path == "" && (coi.HostingSystem != nil || coi.Spec.Windows.HyperV == nil) {
logrus.Debugln("hcsshim::allocateWindowsResources mounting storage")
mcl, err := MountContainerLayers(coi.Spec.Windows.LayerFolders, resources.containerRootInUVM, coi.HostingSystem)
if err != nil {
return fmt.Errorf("failed to mount container storage: %s", err)
}
if coi.HostingSystem == nil {
coi.Spec.Root.Path = mcl.(string) // Argon v1 or v2
} else {
coi.Spec.Root.Path = mcl.(guestrequest.CombinedLayers).ContainerRootPath // v2 Xenon WCOW
}
resources.layers = coi.Spec.Windows.LayerFolders
}
// Validate each of the mounts. If this is a V2 Xenon, we have to add them as
// VSMB shares to the utility VM. For V1 Xenon and Argons, there's nothing for
// us to do as it's done by HCS.
for i, mount := range coi.Spec.Mounts {
if mount.Destination == "" || mount.Source == "" {
return fmt.Errorf("invalid OCI spec - a mount must have both source and a destination: %+v", mount)
}
switch mount.Type {
case "":
case "physical-disk":
case "virtual-disk":
default:
return fmt.Errorf("invalid OCI spec - Type '%s' not supported", mount.Type)
}
if coi.HostingSystem != nil && schemaversion.IsV21(coi.actualSchemaVersion) {
uvmPath := fmt.Sprintf("C:\\%s\\%d", coi.actualID, i)
readOnly := false
for _, o := range mount.Options {
if strings.ToLower(o) == "ro" {
readOnly = true
break
}
}
if mount.Type == "physical-disk" {
logrus.Debugf("hcsshim::allocateWindowsResources Hot-adding SCSI physical disk for OCI mount %+v", mount)
_, _, err := coi.HostingSystem.AddSCSIPhysicalDisk(mount.Source, uvmPath, readOnly)
if err != nil {
return fmt.Errorf("adding SCSI physical disk mount %+v: %s", mount, err)
}
coi.Spec.Mounts[i].Type = ""
resources.scsiMounts = append(resources.scsiMounts, mount.Source)
} else if mount.Type == "virtual-disk" {
logrus.Debugf("hcsshim::allocateWindowsResources Hot-adding SCSI virtual disk for OCI mount %+v", mount)
_, _, err := coi.HostingSystem.AddSCSI(mount.Source, uvmPath, readOnly)
if err != nil {
return fmt.Errorf("adding SCSI virtual disk mount %+v: %s", mount, err)
}
coi.Spec.Mounts[i].Type = ""
resources.scsiMounts = append(resources.scsiMounts, mount.Source)
} else {
logrus.Debugf("hcsshim::allocateWindowsResources Hot-adding VSMB share for OCI mount %+v", mount)
options := &hcsschema.VirtualSmbShareOptions{}
if readOnly {
options.ReadOnly = true
options.CacheIo = true
options.ShareRead = true
options.ForceLevelIIOplocks = true
break
}
err := coi.HostingSystem.AddVSMB(mount.Source, "", options)
if err != nil {
return fmt.Errorf("failed to add VSMB share to utility VM for mount %+v: %s", mount, err)
}
resources.vsmbMounts = append(resources.vsmbMounts, mount.Source)
}
}
}
return nil
}

View File

@ -0,0 +1,260 @@
// +build windows,functional
package hcsoci
//import (
// "os"
// "path/filepath"
// "testing"
// "github.com/Microsoft/hcsshim/internal/schemaversion"
// specs "github.com/opencontainers/runtime-spec/specs-go"
//)
//// --------------------------------
//// W C O W A R G O N V 1
//// --------------------------------
//// A v1 Argon with a single base layer. It also validates hostname functionality is propagated.
//func TestV1Argon(t *testing.T) {
// t.Skip("fornow")
// tempDir := createWCOWTempDirWithSandbox(t)
// defer os.RemoveAll(tempDir)
// layers := append(layersNanoserver, tempDir)
// mountPath, err := mountContainerLayers(layers, nil)
// if err != nil {
// t.Fatalf("failed to mount container storage: %s", err)
// }
// defer unmountContainerLayers(layers, nil, unmountOperationAll)
// c, err := CreateContainer(&CreateOptions{
// SchemaVersion: schemaversion.SchemaV10(),
// Id: "TestV1Argon",
// Owner: "unit-test",
// Spec: &specs.Spec{
// Hostname: "goofy",
// Windows: &specs.Windows{LayerFolders: layers},
// Root: &specs.Root{Path: mountPath.(string)},
// },
// })
// if err != nil {
// t.Fatalf("Failed create: %s", err)
// }
// startContainer(t, c)
// runCommand(t, c, "cmd /s /c echo Hello", `c:\`, "Hello")
// runCommand(t, c, "cmd /s /c hostname", `c:\`, "goofy")
// stopContainer(t, c)
// c.Terminate()
//}
//// A v1 Argon with a single base layer which uses the auto-mount capability
//func TestV1ArgonAutoMount(t *testing.T) {
// t.Skip("fornow")
// tempDir := createWCOWTempDirWithSandbox(t)
// defer os.RemoveAll(tempDir)
// layers := append(layersBusybox, tempDir)
// c, err := CreateContainer(&CreateOptions{
// Id: "TestV1ArgonAutoMount",
// SchemaVersion: schemaversion.SchemaV10(),
// Spec: &specs.Spec{Windows: &specs.Windows{LayerFolders: layers}},
// })
// if err != nil {
// t.Fatalf("Failed create: %s", err)
// }
// defer unmountContainerLayers(layers, nil, unmountOperationAll)
// startContainer(t, c)
// runCommand(t, c, "cmd /s /c echo Hello", `c:\`, "Hello")
// stopContainer(t, c)
// c.Terminate()
//}
//// A v1 Argon with multiple layers which uses the auto-mount capability
//func TestV1ArgonMultipleBaseLayersAutoMount(t *testing.T) {
// t.Skip("fornow")
// // This is the important bit for this test. It's deleted here. We call the helper only to allocate a temporary directory
// containerScratchDir := createTempDir(t)
// os.RemoveAll(containerScratchDir)
// defer os.RemoveAll(containerScratchDir) // As auto-created
// layers := append(layersBusybox, containerScratchDir)
// c, err := CreateContainer(&CreateOptions{
// Id: "TestV1ArgonMultipleBaseLayersAutoMount",
// SchemaVersion: schemaversion.SchemaV10(),
// Spec: &specs.Spec{Windows: &specs.Windows{LayerFolders: layers}},
// })
// if err != nil {
// t.Fatalf("Failed create: %s", err)
// }
// defer unmountContainerLayers(layers, nil, unmountOperationAll)
// startContainer(t, c)
// runCommand(t, c, "cmd /s /c echo Hello", `c:\`, "Hello")
// stopContainer(t, c)
// c.Terminate()
//}
//// A v1 Argon with a single mapped directory.
//func TestV1ArgonSingleMappedDirectory(t *testing.T) {
// t.Skip("fornow")
// tempDir := createWCOWTempDirWithSandbox(t)
// defer os.RemoveAll(tempDir)
// layers := append(layersNanoserver, tempDir)
// // Create a temp folder containing foo.txt which will be used for the bind-mount test.
// source := createTempDir(t)
// defer os.RemoveAll(source)
// mount := specs.Mount{
// Source: source,
// Destination: `c:\foo`,
// }
// f, err := os.OpenFile(filepath.Join(source, "foo.txt"), os.O_RDWR|os.O_CREATE, 0755)
// f.Close()
// c, err := CreateContainer(&CreateOptions{
// SchemaVersion: schemaversion.SchemaV10(),
// Spec: &specs.Spec{
// Windows: &specs.Windows{LayerFolders: layers},
// Mounts: []specs.Mount{mount},
// },
// })
// if err != nil {
// t.Fatalf("Failed create: %s", err)
// }
// defer unmountContainerLayers(layers, nil, unmountOperationAll)
// startContainer(t, c)
// runCommand(t, c, `cmd /s /c dir /b c:\foo`, `c:\`, "foo.txt")
// stopContainer(t, c)
// c.Terminate()
//}
//// --------------------------------
//// W C O W A R G O N V 2
//// --------------------------------
//// A v2 Argon with a single base layer. It also validates hostname functionality is propagated.
//// It also uses an auto-generated ID.
//func TestV2Argon(t *testing.T) {
// t.Skip("fornow")
// tempDir := createWCOWTempDirWithSandbox(t)
// defer os.RemoveAll(tempDir)
// layers := append(layersNanoserver, tempDir)
// mountPath, err := mountContainerLayers(layers, nil)
// if err != nil {
// t.Fatalf("failed to mount container storage: %s", err)
// }
// defer unmountContainerLayers(layers, nil, unmountOperationAll)
// c, err := CreateContainer(&CreateOptions{
// SchemaVersion: schemaversion.SchemaV21(),
// Spec: &specs.Spec{
// Hostname: "mickey",
// Windows: &specs.Windows{LayerFolders: layers},
// Root: &specs.Root{Path: mountPath.(string)},
// },
// })
// if err != nil {
// t.Fatalf("Failed create: %s", err)
// }
// startContainer(t, c)
// runCommand(t, c, "cmd /s /c echo Hello", `c:\`, "Hello")
// runCommand(t, c, "cmd /s /c hostname", `c:\`, "mickey")
// stopContainer(t, c)
// c.Terminate()
//}
//// A v2 Argon with multiple layers
//func TestV2ArgonMultipleBaseLayers(t *testing.T) {
// t.Skip("fornow")
// tempDir := createWCOWTempDirWithSandbox(t)
// defer os.RemoveAll(tempDir)
// layers := append(layersBusybox, tempDir)
// mountPath, err := mountContainerLayers(layers, nil)
// if err != nil {
// t.Fatalf("failed to mount container storage: %s", err)
// }
// defer unmountContainerLayers(layers, nil, unmountOperationAll)
// c, err := CreateContainer(&CreateOptions{
// SchemaVersion: schemaversion.SchemaV21(),
// Id: "TestV2ArgonMultipleBaseLayers",
// Spec: &specs.Spec{
// Windows: &specs.Windows{LayerFolders: layers},
// Root: &specs.Root{Path: mountPath.(string)},
// },
// })
// if err != nil {
// t.Fatalf("Failed create: %s", err)
// }
// startContainer(t, c)
// runCommand(t, c, "cmd /s /c echo Hello", `c:\`, "Hello")
// stopContainer(t, c)
// c.Terminate()
//}
//// A v2 Argon with multiple layers which uses the auto-mount capability and auto-create
//func TestV2ArgonAutoMountMultipleBaseLayers(t *testing.T) {
// t.Skip("fornow")
// // This is the important bit for this test. It's deleted here. We call the helper only to allocate a temporary directory
// containerScratchDir := createTempDir(t)
// os.RemoveAll(containerScratchDir)
// defer os.RemoveAll(containerScratchDir) // As auto-created
// layers := append(layersBusybox, containerScratchDir)
// c, err := CreateContainer(&CreateOptions{
// SchemaVersion: schemaversion.SchemaV21(),
// Id: "TestV2ArgonAutoMountMultipleBaseLayers",
// Spec: &specs.Spec{Windows: &specs.Windows{LayerFolders: layers}},
// })
// if err != nil {
// t.Fatalf("Failed create: %s", err)
// }
// defer unmountContainerLayers(layers, nil, unmountOperationAll)
// startContainer(t, c)
// runCommand(t, c, "cmd /s /c echo Hello", `c:\`, "Hello")
// stopContainer(t, c)
// c.Terminate()
//}
//// A v2 Argon with a single mapped directory.
//func TestV2ArgonSingleMappedDirectory(t *testing.T) {
// t.Skip("fornow")
// tempDir := createWCOWTempDirWithSandbox(t)
// defer os.RemoveAll(tempDir)
// layers := append(layersNanoserver, tempDir)
// // Create a temp folder containing foo.txt which will be used for the bind-mount test.
// source := createTempDir(t)
// defer os.RemoveAll(source)
// mount := specs.Mount{
// Source: source,
// Destination: `c:\foo`,
// }
// f, err := os.OpenFile(filepath.Join(source, "foo.txt"), os.O_RDWR|os.O_CREATE, 0755)
// f.Close()
// c, err := CreateContainer(&CreateOptions{
// SchemaVersion: schemaversion.SchemaV21(),
// Spec: &specs.Spec{
// Windows: &specs.Windows{LayerFolders: layers},
// Mounts: []specs.Mount{mount},
// },
// })
// if err != nil {
// t.Fatalf("Failed create: %s", err)
// }
// defer unmountContainerLayers(layers, nil, unmountOperationAll)
// startContainer(t, c)
// runCommand(t, c, `cmd /s /c dir /b c:\foo`, `c:\`, "foo.txt")
// stopContainer(t, c)
// c.Terminate()
//}

View File

@ -0,0 +1,365 @@
// +build windows,functional
package hcsoci
//import (
// "fmt"
// "os"
// "path/filepath"
// "testing"
// "github.com/Microsoft/hcsshim/internal/schemaversion"
// specs "github.com/opencontainers/runtime-spec/specs-go"
//)
//// --------------------------------
//// W C O W X E N O N V 2
//// --------------------------------
//// A single WCOW xenon. Note in this test, neither the UVM or the
//// containers are supplied IDs - they will be autogenerated for us.
//// This is the minimum set of parameters needed to create a V2 WCOW xenon.
//func TestV2XenonWCOW(t *testing.T) {
// t.Skip("Skipping for now")
// uvm, uvmScratchDir := createv2WCOWUVM(t, layersNanoserver, "", nil)
// defer os.RemoveAll(uvmScratchDir)
// defer uvm.Terminate()
// // Create the container hosted inside the utility VM
// containerScratchDir := createWCOWTempDirWithSandbox(t)
// defer os.RemoveAll(containerScratchDir)
// layerFolders := append(layersNanoserver, containerScratchDir)
// hostedContainer, err := CreateContainer(&CreateOptions{
// HostingSystem: uvm,
// Spec: &specs.Spec{Windows: &specs.Windows{LayerFolders: layerFolders}},
// })
// if err != nil {
// t.Fatalf("CreateContainer failed: %s", err)
// }
// defer unmountContainerLayers(layerFolders, uvm, unmountOperationAll)
// // Start/stop the container
// startContainer(t, hostedContainer)
// runCommand(t, hostedContainer, "cmd /s /c echo TestV2XenonWCOW", `c:\`, "TestV2XenonWCOW")
// stopContainer(t, hostedContainer)
// hostedContainer.Terminate()
//}
//// TODO: Have a similar test where the UVM scratch folder does not exist.
//// A single WCOW xenon but where the container sandbox folder is not pre-created by the client
//func TestV2XenonWCOWContainerSandboxFolderDoesNotExist(t *testing.T) {
// t.Skip("Skipping for now")
// uvm, uvmScratchDir := createv2WCOWUVM(t, layersNanoserver, "TestV2XenonWCOWContainerSandboxFolderDoesNotExist_UVM", nil)
// defer os.RemoveAll(uvmScratchDir)
// defer uvm.Terminate()
// // This is the important bit for this test. It's deleted here. We call the helper only to allocate a temporary directory
// containerScratchDir := createTempDir(t)
// os.RemoveAll(containerScratchDir)
// defer os.RemoveAll(containerScratchDir) // As auto-created
// layerFolders := append(layersBusybox, containerScratchDir)
// hostedContainer, err := CreateContainer(&CreateOptions{
// Id: "container",
// HostingSystem: uvm,
// Spec: &specs.Spec{Windows: &specs.Windows{LayerFolders: layerFolders}},
// })
// if err != nil {
// t.Fatalf("CreateContainer failed: %s", err)
// }
// defer unmountContainerLayers(layerFolders, uvm, unmountOperationAll)
// // Start/stop the container
// startContainer(t, hostedContainer)
// runCommand(t, hostedContainer, "cmd /s /c echo TestV2XenonWCOW", `c:\`, "TestV2XenonWCOW")
// stopContainer(t, hostedContainer)
// hostedContainer.Terminate()
//}
//// TODO What about mount. Test with the client doing the mount.
//// TODO Test as above, but where sandbox for UVM is entirely created by a client to show how it's done.
//// Two v2 WCOW containers in the same UVM, each with a single base layer
//func TestV2XenonWCOWTwoContainers(t *testing.T) {
// t.Skip("Skipping for now")
// uvm, uvmScratchDir := createv2WCOWUVM(t, layersNanoserver, "TestV2XenonWCOWTwoContainers_UVM", nil)
// defer os.RemoveAll(uvmScratchDir)
// defer uvm.Terminate()
// // First hosted container
// firstContainerScratchDir := createWCOWTempDirWithSandbox(t)
// defer os.RemoveAll(firstContainerScratchDir)
// firstLayerFolders := append(layersNanoserver, firstContainerScratchDir)
// firstHostedContainer, err := CreateContainer(&CreateOptions{
// Id: "FirstContainer",
// HostingSystem: uvm,
// SchemaVersion: schemaversion.SchemaV21(),
// Spec: &specs.Spec{Windows: &specs.Windows{LayerFolders: firstLayerFolders}},
// })
// if err != nil {
// t.Fatalf("CreateContainer failed: %s", err)
// }
// defer unmountContainerLayers(firstLayerFolders, uvm, unmountOperationAll)
// // Second hosted container
// secondContainerScratchDir := createWCOWTempDirWithSandbox(t)
// defer os.RemoveAll(firstContainerScratchDir)
// secondLayerFolders := append(layersNanoserver, secondContainerScratchDir)
// secondHostedContainer, err := CreateContainer(&CreateOptions{
// Id: "SecondContainer",
// HostingSystem: uvm,
// SchemaVersion: schemaversion.SchemaV21(),
// Spec: &specs.Spec{Windows: &specs.Windows{LayerFolders: secondLayerFolders}},
// })
// if err != nil {
// t.Fatalf("CreateContainer failed: %s", err)
// }
// defer unmountContainerLayers(secondLayerFolders, uvm, unmountOperationAll)
// startContainer(t, firstHostedContainer)
// runCommand(t, firstHostedContainer, "cmd /s /c echo FirstContainer", `c:\`, "FirstContainer")
// startContainer(t, secondHostedContainer)
// runCommand(t, secondHostedContainer, "cmd /s /c echo SecondContainer", `c:\`, "SecondContainer")
// stopContainer(t, firstHostedContainer)
// stopContainer(t, secondHostedContainer)
// firstHostedContainer.Terminate()
// secondHostedContainer.Terminate()
//}
////// This verifies the container storage is unmounted correctly so that a second
////// container can be started from the same storage.
////func TestV2XenonWCOWWithRemount(t *testing.T) {
////// //t.Skip("Skipping for now")
//// uvmID := "Testv2XenonWCOWWithRestart_UVM"
//// uvmScratchDir, err := ioutil.TempDir("", "uvmScratch")
//// if err != nil {
//// t.Fatalf("Failed create temporary directory: %s", err)
//// }
//// if err := CreateWCOWSandbox(layersNanoserver[0], uvmScratchDir, uvmID); err != nil {
//// t.Fatalf("Failed create Windows UVM Sandbox: %s", err)
//// }
//// defer os.RemoveAll(uvmScratchDir)
//// uvm, err := CreateContainer(&CreateOptions{
//// Id: uvmID,
//// Owner: "unit-test",
//// SchemaVersion: SchemaV21(),
//// IsHostingSystem: true,
//// Spec: &specs.Spec{
//// Windows: &specs.Windows{
//// LayerFolders: []string{uvmScratchDir},
//// HyperV: &specs.WindowsHyperV{UtilityVMPath: filepath.Join(layersNanoserver[0], `UtilityVM\Files`)},
//// },
//// },
//// })
//// if err != nil {
//// t.Fatalf("Failed create UVM: %s", err)
//// }
//// defer uvm.Terminate()
//// if err := uvm.Start(); err != nil {
//// t.Fatalf("Failed start utility VM: %s", err)
//// }
//// // Mount the containers storage in the utility VM
//// containerScratchDir := createWCOWTempDirWithSandbox(t)
//// layerFolders := append(layersNanoserver, containerScratchDir)
//// cls, err := Mount(layerFolders, uvm, SchemaV21())
//// if err != nil {
//// t.Fatalf("failed to mount container storage: %s", err)
//// }
//// combinedLayers := cls.(CombinedLayersV2)
//// mountedLayers := &ContainersResourcesStorageV2{
//// Layers: combinedLayers.Layers,
//// Path: combinedLayers.ContainerRootPath,
//// }
//// defer func() {
//// if err := Unmount(layerFolders, uvm, SchemaV21(), unmountOperationAll); err != nil {
//// t.Fatalf("failed to unmount container storage: %s", err)
//// }
//// }()
//// // Create the first container
//// defer os.RemoveAll(containerScratchDir)
//// xenon, err := CreateContainer(&CreateOptions{
//// Id: "container",
//// Owner: "unit-test",
//// HostingSystem: uvm,
//// SchemaVersion: SchemaV21(),
//// Spec: &specs.Spec{Windows: &specs.Windows{}}, // No layerfolders as we mounted them ourself.
//// })
//// if err != nil {
//// t.Fatalf("CreateContainer failed: %s", err)
//// }
//// // Start/stop the first container
//// startContainer(t, xenon)
//// runCommand(t, xenon, "cmd /s /c echo TestV2XenonWCOWFirstStart", `c:\`, "TestV2XenonWCOWFirstStart")
//// stopContainer(t, xenon)
//// xenon.Terminate()
//// // Now unmount and remount to exactly the same places
//// if err := Unmount(layerFolders, uvm, SchemaV21(), unmountOperationAll); err != nil {
//// t.Fatalf("failed to unmount container storage: %s", err)
//// }
//// if _, err = Mount(layerFolders, uvm, SchemaV21()); err != nil {
//// t.Fatalf("failed to mount container storage: %s", err)
//// }
//// // Create an identical second container and verify it works too.
//// xenon2, err := CreateContainer(&CreateOptions{
//// Id: "container",
//// Owner: "unit-test",
//// HostingSystem: uvm,
//// SchemaVersion: SchemaV21(),
//// Spec: &specs.Spec{Windows: &specs.Windows{LayerFolders: layerFolders}},
//// MountedLayers: mountedLayers,
//// })
//// if err != nil {
//// t.Fatalf("CreateContainer failed: %s", err)
//// }
//// startContainer(t, xenon2)
//// runCommand(t, xenon2, "cmd /s /c echo TestV2XenonWCOWAfterRemount", `c:\`, "TestV2XenonWCOWAfterRemount")
//// stopContainer(t, xenon2)
//// xenon2.Terminate()
////}
//// Lots of v2 WCOW containers in the same UVM, each with a single base layer. Containers aren't
//// actually started, but it stresses the SCSI controller hot-add logic.
//func TestV2XenonWCOWCreateLots(t *testing.T) {
// t.Skip("Skipping for now")
// uvm, uvmScratchDir := createv2WCOWUVM(t, layersNanoserver, "TestV2XenonWCOWCreateLots", nil)
// defer os.RemoveAll(uvmScratchDir)
// defer uvm.Terminate()
// // 63 as 0:0 is already taken as the UVMs scratch. So that leaves us with 64-1 left for container scratches on SCSI
// for i := 0; i < 63; i++ {
// containerScratchDir := createWCOWTempDirWithSandbox(t)
// defer os.RemoveAll(containerScratchDir)
// layerFolders := append(layersNanoserver, containerScratchDir)
// hostedContainer, err := CreateContainer(&CreateOptions{
// Id: fmt.Sprintf("container%d", i),
// HostingSystem: uvm,
// SchemaVersion: schemaversion.SchemaV21(),
// Spec: &specs.Spec{Windows: &specs.Windows{LayerFolders: layerFolders}},
// })
// if err != nil {
// t.Fatalf("CreateContainer failed: %s", err)
// }
// defer hostedContainer.Terminate()
// defer unmountContainerLayers(layerFolders, uvm, unmountOperationAll)
// }
// // TODO: Should check the internal structures here for VSMB and SCSI
// // TODO: Push it over 63 now and will get a failure.
//}
//// Helper for the v2 Xenon tests to create a utility VM. Returns the UtilityVM
//// object; folder used as its scratch
//func createv2WCOWUVM(t *testing.T, uvmLayers []string, uvmId string, resources *specs.WindowsResources) (*UtilityVM, string) {
// scratchDir := createTempDir(t)
// uvm := UtilityVM{
// OperatingSystem: "windows",
// LayerFolders: append(uvmLayers, scratchDir),
// Resources: resources,
// }
// if uvmId != "" {
// uvm.Id = uvmId
// }
// if err := uvm.Create(); err != nil {
// t.Fatalf("Failed create WCOW v2 UVM: %s", err)
// }
// if err := uvm.Start(); err != nil {
// t.Fatalf("Failed start WCOW v2UVM: %s", err)
// }
// return &uvm, scratchDir
//}
//// TestV2XenonWCOWMultiLayer creates a V2 Xenon having multiple image layers
//func TestV2XenonWCOWMultiLayer(t *testing.T) {
// t.Skip("for now")
// uvmMemory := uint64(1 * 1024 * 1024 * 1024)
// uvmCPUCount := uint64(2)
// resources := &specs.WindowsResources{
// Memory: &specs.WindowsMemoryResources{
// Limit: &uvmMemory,
// },
// CPU: &specs.WindowsCPUResources{
// Count: &uvmCPUCount,
// },
// }
// uvm, uvmScratchDir := createv2WCOWUVM(t, layersNanoserver, "TestV2XenonWCOWMultiLayer_UVM", resources)
// defer os.RemoveAll(uvmScratchDir)
// defer uvm.Terminate()
// // Create a sandbox for the hosted container
// containerScratchDir := createWCOWTempDirWithSandbox(t)
// defer os.RemoveAll(containerScratchDir)
// // Create the container. Note that this will auto-mount for us.
// containerLayers := append(layersBusybox, containerScratchDir)
// xenon, err := CreateContainer(&CreateOptions{
// Id: "container",
// HostingSystem: uvm,
// Spec: &specs.Spec{Windows: &specs.Windows{LayerFolders: containerLayers}},
// })
// if err != nil {
// t.Fatalf("CreateContainer failed: %s", err)
// }
// // Start/stop the container
// startContainer(t, xenon)
// runCommand(t, xenon, "echo Container", `c:\`, "Container")
// stopContainer(t, xenon)
// xenon.Terminate()
// // TODO Move this to a defer function to fail if it fails.
// if err := unmountContainerLayers(containerLayers, uvm, unmountOperationAll); err != nil {
// t.Fatalf("unmount failed: %s", err)
// }
//}
//// TestV2XenonWCOWSingleMappedDirectory tests a V2 Xenon WCOW with a single mapped directory
//func TestV2XenonWCOWSingleMappedDirectory(t *testing.T) {
// t.Skip("Skipping for now")
// uvm, uvmScratchDir := createv2WCOWUVM(t, layersNanoserver, "", nil)
// defer os.RemoveAll(uvmScratchDir)
// defer uvm.Terminate()
// // Create the container hosted inside the utility VM
// containerScratchDir := createWCOWTempDirWithSandbox(t)
// defer os.RemoveAll(containerScratchDir)
// layerFolders := append(layersNanoserver, containerScratchDir)
// // Create a temp folder containing foo.txt which will be used for the bind-mount test.
// source := createTempDir(t)
// defer os.RemoveAll(source)
// mount := specs.Mount{
// Source: source,
// Destination: `c:\foo`,
// }
// f, err := os.OpenFile(filepath.Join(source, "foo.txt"), os.O_RDWR|os.O_CREATE, 0755)
// f.Close()
// hostedContainer, err := CreateContainer(&CreateOptions{
// HostingSystem: uvm,
// Spec: &specs.Spec{
// Windows: &specs.Windows{LayerFolders: layerFolders},
// Mounts: []specs.Mount{mount},
// },
// })
// if err != nil {
// t.Fatalf("CreateContainer failed: %s", err)
// }
// defer unmountContainerLayers(layerFolders, uvm, unmountOperationAll)
// // TODO BUGBUG NEED TO UNMOUNT TO VSMB SHARE FOR THE CONTAINER
// // Start/stop the container
// startContainer(t, hostedContainer)
// runCommand(t, hostedContainer, `cmd /s /c dir /b c:\foo`, `c:\`, "foo.txt")
// stopContainer(t, hostedContainer)
// hostedContainer.Terminate()
//}

View File

@ -23,7 +23,9 @@ type HNSEndpoint struct {
DisableICC bool `json:",omitempty"`
PrefixLength uint8 `json:",omitempty"`
IsRemoteEndpoint bool `json:",omitempty"`
EnableLowMetric bool `json:",omitempty"`
Namespace *Namespace `json:",omitempty"`
EncapOverhead uint16 `json:",omitempty"`
}
//SystemType represents the type of the system on which actions are done

View File

@ -20,6 +20,7 @@ type ELBPolicy struct {
SourceVIP string `json:"SourceVIP,omitempty"`
VIPs []string `json:"VIPs,omitempty"`
ILB bool `json:"ILB,omitempty"`
DSR bool `json:"IsDSR,omitempty"`
}
// LBPolicy is a structure defining schema for LoadBalancing based Policy

View File

@ -1,4 +1,4 @@
// MACHINE GENERATED BY 'go generate' COMMAND; DO NOT EDIT
// Code generated mksyscall_windows.exe DO NOT EDIT
package hns
@ -6,7 +6,6 @@ import (
"syscall"
"unsafe"
"github.com/Microsoft/hcsshim/internal/interop"
"golang.org/x/sys/windows"
)
@ -68,7 +67,10 @@ func __hnsCall(method *uint16, path *uint16, object *uint16, response **uint16)
}
r0, _, _ := syscall.Syscall6(procHNSCall.Addr(), 4, uintptr(unsafe.Pointer(method)), uintptr(unsafe.Pointer(path)), uintptr(unsafe.Pointer(object)), uintptr(unsafe.Pointer(response)), 0, 0)
if int32(r0) < 0 {
hr = interop.Win32FromHresult(r0)
if r0&0x1fff0000 == 0x00070000 {
r0 &= 0xffff
}
hr = syscall.Errno(r0)
}
return
}

View File

@ -5,9 +5,9 @@ import (
"unsafe"
)
//go:generate go run $GOROOT/src/syscall/mksyscall_windows.go -output zsyscall_windows.go interop.go
//go:generate go run ../../mksyscall_windows.go -output zsyscall_windows.go interop.go
//sys coTaskMemFree(buffer unsafe.Pointer) = ole32.CoTaskMemFree
//sys coTaskMemFree(buffer unsafe.Pointer) = api_ms_win_core_com_l1_1_0.CoTaskMemFree
func ConvertAndFreeCoTaskMemString(buffer *uint16) string {
str := syscall.UTF16ToString((*[1 << 29]uint16)(unsafe.Pointer(buffer))[:])

View File

@ -1,4 +1,4 @@
// Code generated by 'go generate'; DO NOT EDIT.
// Code generated mksyscall_windows.exe DO NOT EDIT
package interop
@ -37,9 +37,9 @@ func errnoErr(e syscall.Errno) error {
}
var (
modole32 = windows.NewLazySystemDLL("ole32.dll")
modapi_ms_win_core_com_l1_1_0 = windows.NewLazySystemDLL("api-ms-win-core-com-l1-1-0.dll")
procCoTaskMemFree = modole32.NewProc("CoTaskMemFree")
procCoTaskMemFree = modapi_ms_win_core_com_l1_1_0.NewProc("CoTaskMemFree")
)
func coTaskMemFree(buffer unsafe.Pointer) {

Some files were not shown because too many files have changed in this diff Show More