Fix the dependency issue (#231)
This commit is contained in:
788
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/archive.go
generated
vendored
788
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/archive.go
generated
vendored
@@ -1,788 +0,0 @@
|
||||
// Copyright 2017 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package backends
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/tchap/go-patricia/patricia"
|
||||
|
||||
"github.com/vmware/vic/lib/apiservers/engine/backends/cache"
|
||||
viccontainer "github.com/vmware/vic/lib/apiservers/engine/backends/container"
|
||||
"github.com/vmware/vic/lib/apiservers/engine/errors"
|
||||
"github.com/vmware/vic/lib/apiservers/engine/proxy"
|
||||
"github.com/vmware/vic/lib/apiservers/portlayer/client/storage"
|
||||
vicarchive "github.com/vmware/vic/lib/archive"
|
||||
"github.com/vmware/vic/lib/constants"
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/pkg/archive"
|
||||
)
|
||||
|
||||
// ContainerArchivePath creates an archive of the filesystem resource at the
|
||||
// specified path in the container identified by the given name. Returns a
|
||||
// tar archive of the resource and whether it was a directory or a single file.
|
||||
func (c *ContainerBackend) ContainerArchivePath(name string, path string) (io.ReadCloser, *types.ContainerPathStat, error) {
|
||||
defer trace.End(trace.Begin(name))
|
||||
op := trace.NewOperation(context.Background(), "ContainerArchivePath: %s", name)
|
||||
|
||||
path = "/" + strings.TrimPrefix(path, "/")
|
||||
vc := cache.ContainerCache().GetContainer(name)
|
||||
if vc == nil {
|
||||
return nil, nil, errors.NotFoundError(name)
|
||||
}
|
||||
|
||||
stat, err := c.ContainerStatPath(name, path)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
reader, err := c.exportFromContainer(op, vc, path)
|
||||
if err != nil {
|
||||
if errors.IsResourceInUse(err) {
|
||||
err = fmt.Errorf("ContainerArchivePath failed, resource in use: %s", err.Error())
|
||||
}
|
||||
return nil, nil, errors.InternalServerError(err.Error())
|
||||
}
|
||||
|
||||
return reader, stat, nil
|
||||
}
|
||||
|
||||
func (c *ContainerBackend) exportFromContainer(op trace.Operation, vc *viccontainer.VicContainer, path string) (io.ReadCloser, error) {
|
||||
mounts := proxy.MountsFromContainer(vc)
|
||||
mounts = append(mounts, types.MountPoint{Destination: "/"})
|
||||
readerMap := NewArchiveStreamReaderMap(op, mounts, path)
|
||||
|
||||
readers, err := readerMap.ReadersForSourcePath(archiveProxy, vc.ContainerID, path)
|
||||
if err != nil {
|
||||
op.Errorf("Errors getting readers for export: %s", err.Error())
|
||||
return nil, err
|
||||
}
|
||||
count := len(readers)
|
||||
op.Infof("Got %d archive readers", count)
|
||||
|
||||
// We want to combine the streams, so need to strip the end-of-archive elements for all but the last
|
||||
strippersWithCloser := make([]io.Reader, len(readers))
|
||||
i := 0
|
||||
for ; i < count-1; i++ {
|
||||
stripper := vicarchive.NewStripper(op, tar.NewReader(readers[i]), readers[i].Close)
|
||||
strippersWithCloser[i] = stripper
|
||||
op.Debugf("Added stripping reader: %p", stripper)
|
||||
}
|
||||
op.Debugf("Adding closing reader: %p", readers[i])
|
||||
strippersWithCloser[i] = readers[i]
|
||||
|
||||
return vicarchive.MultiReader(strippersWithCloser...), nil
|
||||
}
|
||||
|
||||
// ContainerCopy performs a deprecated operation of archiving the resource at
|
||||
// the specified path in the container identified by the given name.
|
||||
func (c *ContainerBackend) ContainerCopy(name string, res string) (io.ReadCloser, error) {
|
||||
return nil, errors.APINotSupportedMsg(ProductName(), "ContainerCopy")
|
||||
}
|
||||
|
||||
// ContainerExport writes the contents of the container to the given
|
||||
// writer. An error is returned if the container cannot be found.
|
||||
func (c *ContainerBackend) ContainerExport(name string, out io.Writer) error {
|
||||
return errors.APINotSupportedMsg(ProductName(), "ContainerExport")
|
||||
}
|
||||
|
||||
// ContainerExtractToDir extracts the given archive to the specified location
|
||||
// in the filesystem of the container identified by the given name. The given
|
||||
// path must be of a directory in the container. If it is not, the error will
|
||||
// be ErrExtractPointNotDirectory. If noOverwriteDirNonDir is true then it will
|
||||
// be an error if unpacking the given content would cause an existing directory
|
||||
// to be replaced with a non-directory and vice versa.
|
||||
func (c *ContainerBackend) ContainerExtractToDir(name, path string, noOverwriteDirNonDir bool, content io.Reader) error {
|
||||
defer trace.End(trace.Begin(name))
|
||||
op := trace.NewOperation(context.Background(), "ContainerExtractToDir: %s", name)
|
||||
|
||||
path = "/" + strings.TrimPrefix(path, "/")
|
||||
|
||||
vc := cache.ContainerCache().GetContainer(name)
|
||||
if vc == nil {
|
||||
return errors.NotFoundError(name)
|
||||
}
|
||||
|
||||
err := c.importToContainer(op, vc, path, content)
|
||||
if err != nil && errors.IsResourceInUse(err) {
|
||||
op.Errorf("ContainerExtractToDir failed, resource in use: %s", err.Error())
|
||||
|
||||
err = fmt.Errorf("Resource in use")
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *ContainerBackend) importToContainer(op trace.Operation, vc *viccontainer.VicContainer, target string, content io.Reader) (err error) {
|
||||
rawReader, err := archive.DecompressStream(content)
|
||||
if err != nil {
|
||||
op.Errorf("Input tar stream to ContainerExtractToDir not recognized: %s", err.Error())
|
||||
return errors.StreamFormatNotRecognized()
|
||||
}
|
||||
|
||||
tarReader := tar.NewReader(rawReader)
|
||||
mounts := proxy.MountsFromContainer(vc)
|
||||
mounts = append(mounts, types.MountPoint{Destination: "/"})
|
||||
writerMap := NewArchiveStreamWriterMap(op, mounts, target)
|
||||
defer func() {
|
||||
// This should shutdown all the stream connections to the portlayer.
|
||||
e1 := writerMap.Close(op)
|
||||
if e1 != nil {
|
||||
err = e1
|
||||
op.Debugf("import to container: assigned err as %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
for {
|
||||
header, err := tarReader.Next()
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
op.Errorf("Error reading tar header from client archive: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
// Lookup the writer for that mount prefix
|
||||
tarWriter, err := writerMap.WriterForAsset(archiveProxy, vc.ContainerID, target, *header)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = tarWriter.WriteHeader(header); err != nil {
|
||||
op.Errorf("Error while copying tar header %#v: %s", *header, err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err = io.Copy(tarWriter, tarReader); err != nil {
|
||||
op.Errorf("Error while copying tar data for %s: %s", header.Name, err.Error())
|
||||
return err
|
||||
}
|
||||
// TODO: change this to log level 3
|
||||
if vchConfig.Cfg.Diagnostics.DebugLevel >= 1 {
|
||||
op.Debugf("Wrote entry: %s", header.Name)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ContainerStatPath stats the filesystem resource at the specified path in the
|
||||
// container identified by the given name.
|
||||
func (c *ContainerBackend) ContainerStatPath(name string, path string) (stat *types.ContainerPathStat, err error) {
|
||||
defer trace.End(trace.Begin(name))
|
||||
op := trace.NewOperation(context.Background(), "ContainerStatPath: %s", name)
|
||||
|
||||
op.Debugf("path received by statpath %s", path)
|
||||
|
||||
vc := cache.ContainerCache().GetContainer(name)
|
||||
if vc == nil {
|
||||
return nil, errors.NotFoundError(name)
|
||||
}
|
||||
|
||||
// trim / and . off from path and then append / to ensure the format is correct
|
||||
path = filepath.Clean(path)
|
||||
if !strings.HasPrefix(path, "/") {
|
||||
path = "/" + path
|
||||
}
|
||||
|
||||
mounts := proxy.MountsFromContainer(vc)
|
||||
mounts = append(mounts, types.MountPoint{Destination: "/"})
|
||||
|
||||
// handle the special case of targeting a volume mount point before it exists.
|
||||
// this will be important for non started container cp, will also be important
|
||||
// to certain behaviors for diff on a non started container.
|
||||
if stat, succeed := tryFakeStatPath(mounts, path); succeed {
|
||||
op.Debugf("faking container stat path %#v", stat)
|
||||
return stat, nil
|
||||
}
|
||||
|
||||
primaryTarget := resolvePathWithMountPoints(op, mounts, path)
|
||||
fs := primaryTarget.filterSpec
|
||||
|
||||
var deviceID string
|
||||
var store string
|
||||
if primaryTarget.mountPoint.Destination == "/" {
|
||||
// Special case. / refers to container VMDK and not a volume vmdk.
|
||||
deviceID = vc.ContainerID
|
||||
store = constants.ContainerStoreName
|
||||
} else {
|
||||
deviceID = primaryTarget.mountPoint.Name
|
||||
store = constants.VolumeStoreName
|
||||
}
|
||||
|
||||
stat, err = archiveProxy.StatPath(op, store, deviceID, fs)
|
||||
if err != nil {
|
||||
op.Errorf("error getting statpath: %s", err.Error())
|
||||
switch err := err.(type) {
|
||||
case *storage.StatPathNotFound:
|
||||
return nil, errors.ContainerResourceNotFoundError(vc.Name, "file or directory")
|
||||
case *storage.StatPathUnprocessableEntity:
|
||||
return nil, errors.InternalServerError("failed to process given path")
|
||||
default:
|
||||
return nil, errors.InternalServerError(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
op.Debugf("container stat path %#v", stat)
|
||||
return stat, nil
|
||||
}
|
||||
|
||||
//----------------------------------
|
||||
// Docker cp utility
|
||||
//----------------------------------
|
||||
|
||||
type ArchiveReader struct {
|
||||
mountPoint types.MountPoint
|
||||
filterSpec vicarchive.FilterSpec
|
||||
reader io.ReadCloser
|
||||
}
|
||||
type ArchiveStreamReaderMap struct {
|
||||
prefixTrie *patricia.Trie
|
||||
op trace.Operation
|
||||
}
|
||||
|
||||
type ArchiveWriter struct {
|
||||
mountPoint types.MountPoint
|
||||
filterSpec vicarchive.FilterSpec
|
||||
writer io.WriteCloser
|
||||
tarWriter *tar.Writer
|
||||
}
|
||||
|
||||
// ArchiveStreamWriterMap maps mount prefix to io.WriteCloser
|
||||
type ArchiveStreamWriterMap struct {
|
||||
prefixTrie *patricia.Trie
|
||||
op trace.Operation
|
||||
wg *sync.WaitGroup
|
||||
errchan chan error
|
||||
}
|
||||
|
||||
// NewArchiveStreamWriterMap creates a new ArchiveStreamWriterMap. The map contains all information
|
||||
// needed to create a writers for every volume mounts for the container. This includes the root
|
||||
// volume of the container.
|
||||
//
|
||||
// mounts is the mount data from inspect
|
||||
// containerDestPath is the destination path in the container
|
||||
func NewArchiveStreamWriterMap(op trace.Operation, mounts []types.MountPoint, dest string) *ArchiveStreamWriterMap {
|
||||
writerMap := &ArchiveStreamWriterMap{}
|
||||
writerMap.prefixTrie = patricia.NewTrie()
|
||||
writerMap.op = op
|
||||
writerMap.errchan = make(chan error, 1)
|
||||
writerMap.wg = &sync.WaitGroup{}
|
||||
|
||||
for _, m := range mounts {
|
||||
aw := ArchiveWriter{
|
||||
mountPoint: m,
|
||||
writer: nil,
|
||||
}
|
||||
|
||||
// If container destination path is part of this mount point's prefix, we must remove it and
|
||||
// add to the filterspec. If the container destination path is "/" we do no stripping.
|
||||
//
|
||||
// e.g. mount A at /mnt/A
|
||||
//
|
||||
// cp /mnt cid:/mnt
|
||||
//
|
||||
// file data.txt from local /mnt/A/data.txt will come to the persona as A/data.txt. We must
|
||||
// tell the storage portlayer to remove "A".
|
||||
//
|
||||
// e.g. mount A at /mnt/A
|
||||
//
|
||||
// cp / cid:/
|
||||
//
|
||||
// file data.txt from local /mnt/A/data.txt will come to the persona as mnt/A/data.txt.
|
||||
// Here, we must tell the portlayer to remove "mnt/A". The key to determining whether to
|
||||
// strip "A" or "mnt/A" is based on the container destination path.
|
||||
isPrimary := !strings.Contains(aw.mountPoint.Destination, dest) || aw.mountPoint.Destination == dest
|
||||
aw.filterSpec = vicarchive.GenerateFilterSpec(dest, aw.mountPoint.Destination, isPrimary, vicarchive.CopyTo)
|
||||
|
||||
writerMap.prefixTrie.Insert(patricia.Prefix(m.Destination), &aw)
|
||||
}
|
||||
|
||||
return writerMap
|
||||
}
|
||||
|
||||
// NewArchiveStreamReaderMap creates a new ArchiveStreamReaderMap. After the call, it contains
|
||||
// information to create readers for every volume mounts for the container
|
||||
//
|
||||
// mounts is the mount data from inspect
|
||||
func NewArchiveStreamReaderMap(op trace.Operation, mounts []types.MountPoint, dest string) *ArchiveStreamReaderMap {
|
||||
readerMap := &ArchiveStreamReaderMap{}
|
||||
readerMap.prefixTrie = patricia.NewTrie()
|
||||
readerMap.op = op
|
||||
|
||||
for _, m := range mounts {
|
||||
ar := ArchiveReader{
|
||||
mountPoint: m,
|
||||
reader: nil,
|
||||
}
|
||||
|
||||
// If the mount point is not the root file system, we must tell the portlayer to rebase the
|
||||
// files in the return tar stream with the mount point path since the volume does not know
|
||||
// the path it is mounted to. It only knows it's root file system.
|
||||
//
|
||||
// e.g. mount A at /mnt/A with a file data.txt in A
|
||||
//
|
||||
// /mnt/A/data.txt <-- from container point of view
|
||||
// /data.txt <-- from volume point of view
|
||||
//
|
||||
// Neither the volume nor the storage portlayer knows about /mnt/A. The persona must tell
|
||||
// the portlayer to rebase all files from this volume to the /mnt/A/ in the final tar stream.
|
||||
cleanDest := vicarchive.Clean(dest, false)
|
||||
isPrimary := !strings.Contains(ar.mountPoint.Destination, cleanDest) || ar.mountPoint.Destination == cleanDest
|
||||
ar.filterSpec = vicarchive.GenerateFilterSpec(dest, ar.mountPoint.Destination, isPrimary, vicarchive.CopyFrom)
|
||||
|
||||
readerMap.prefixTrie.Insert(patricia.Prefix(m.Destination), &ar)
|
||||
}
|
||||
|
||||
return readerMap
|
||||
}
|
||||
|
||||
// FindArchiveWriter finds the one writer that matches the asset name. There should only be one
|
||||
// stream this asset needs to be written to.
|
||||
func (wm *ArchiveStreamWriterMap) FindArchiveWriter(containerDestPath, assetName string) (*ArchiveWriter, error) {
|
||||
defer trace.End(trace.Begin(""))
|
||||
|
||||
var aw *ArchiveWriter
|
||||
var err error
|
||||
|
||||
// go function used later for searching
|
||||
findPrefix := func(prefix patricia.Prefix, item patricia.Item) error {
|
||||
if _, ok := item.(*ArchiveWriter); !ok {
|
||||
return fmt.Errorf("item not ArchiveWriter")
|
||||
}
|
||||
|
||||
aw, _ = item.(*ArchiveWriter)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Find the prefix for the final destination. Final destination is the combination of container destination path
|
||||
// and the asset's name. For example,
|
||||
//
|
||||
// container destination path = /
|
||||
// asset name = mnt/A/file.txt
|
||||
// mount 1 = /mnt/A
|
||||
// mount prefix = /mnt/A
|
||||
//
|
||||
// In the above example, mount prefxi can only be determined by combining both the container destination path and
|
||||
// the asset name, as the final destination includes a mounted volume.
|
||||
|
||||
combinedPath := path.Join(containerDestPath, assetName)
|
||||
prefix := patricia.Prefix(combinedPath)
|
||||
err = wm.prefixTrie.VisitPrefixes(prefix, findPrefix)
|
||||
if err != nil {
|
||||
wm.op.Errorf(err.Error())
|
||||
return nil, fmt.Errorf("Failed to find a node for prefix %s: %s", containerDestPath, err.Error())
|
||||
}
|
||||
|
||||
if aw == nil {
|
||||
return nil, fmt.Errorf("No archive writer found for container destination %s and asset name %s", containerDestPath, assetName)
|
||||
}
|
||||
|
||||
return aw, nil
|
||||
}
|
||||
|
||||
// WriterForAsset takes a destination path and subpath of the archive data and returns the
|
||||
// appropriate writer for the two. It's intention is to solve the case where there exist
|
||||
// a mount point and another mount point within the first mount point. For instance, the
|
||||
// prefix map can have,
|
||||
//
|
||||
// R/W - /
|
||||
// mount 1 - /mnt/a
|
||||
// mount 2 - /mnt/a/b
|
||||
//
|
||||
// case 1:
|
||||
// containerDestPath - /mnt/a
|
||||
// archive header source - b/file.txt
|
||||
//
|
||||
// The correct writer would be the one corresponding to mount 2.
|
||||
//
|
||||
// case 2:
|
||||
// containerDestPath - /mnt/a
|
||||
// archive header source - file.txt
|
||||
//
|
||||
// The correct writer would be the one corresponding to mount 1.
|
||||
//
|
||||
// case 3:
|
||||
// containerDestPath - /
|
||||
// archive header source - mnt/a/file.txt
|
||||
//
|
||||
// The correct writer would be the one corresponding to mount 1
|
||||
//
|
||||
// As demonstrated above, the mount prefix and writer cannot be determined with just the
|
||||
// container destination path. It must be combined with the actual asset's name.
|
||||
func (wm *ArchiveStreamWriterMap) WriterForAsset(proxy proxy.VicArchiveProxy, cid, containerDestPath string, assetHeader tar.Header) (*tar.Writer, error) {
|
||||
defer trace.End(trace.Begin(assetHeader.Name))
|
||||
|
||||
var err error
|
||||
|
||||
aw, err := wm.FindArchiveWriter(containerDestPath, assetHeader.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Perform the lazy initialization here.
|
||||
if aw.writer == nil || aw.tarWriter == nil {
|
||||
// lazy initialize.
|
||||
wm.op.Debugf("Lazily initializing import stream for %s", aw.mountPoint.Destination)
|
||||
var deviceID string
|
||||
var store string
|
||||
if aw.mountPoint.Destination == "/" {
|
||||
// Special case. / refers to container VMDK and not a volume vmdk.
|
||||
deviceID = cid
|
||||
store = constants.ContainerStoreName
|
||||
} else {
|
||||
deviceID = aw.mountPoint.Name
|
||||
store = constants.VolumeStoreName
|
||||
}
|
||||
rawWriter, err := proxy.ArchiveImportWriter(wm.op, store, deviceID, aw.filterSpec, wm.wg, wm.errchan)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Unable to initialize import stream writer for mount prefix %s", aw.mountPoint.Destination)
|
||||
wm.op.Errorf(err.Error())
|
||||
return nil, err
|
||||
}
|
||||
aw.writer = rawWriter
|
||||
aw.tarWriter = tar.NewWriter(rawWriter)
|
||||
}
|
||||
|
||||
return aw.tarWriter, nil
|
||||
}
|
||||
|
||||
// Close visits all the archive writer in the trie and closes the actual io.WritCloser
|
||||
func (wm *ArchiveStreamWriterMap) Close(op trace.Operation) error {
|
||||
defer trace.End(trace.Begin(""))
|
||||
|
||||
closeStream := func(prefix patricia.Prefix, item patricia.Item) error {
|
||||
if aw, ok := item.(*ArchiveWriter); ok && aw.writer != nil {
|
||||
aw.tarWriter.Close()
|
||||
aw.writer.Close()
|
||||
aw.tarWriter = nil
|
||||
aw.writer = nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
wm.prefixTrie.Visit(closeStream)
|
||||
|
||||
// wait for all pl calls to return and close the channel
|
||||
go func() {
|
||||
wm.wg.Wait()
|
||||
close(wm.errchan)
|
||||
}()
|
||||
|
||||
var err error
|
||||
// wait for all the streams to finish
|
||||
for result := range wm.errchan {
|
||||
if result != nil {
|
||||
err = result
|
||||
op.Errorf("Error received from portlayer for import streams: %s", result.Error())
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// FindArchiveReaders finds all archive readers that are within the container source path. For example,
|
||||
//
|
||||
// mount A - /mnt/A
|
||||
// mount B - /mnt/B
|
||||
// mount AB - /mnt/A/AB
|
||||
// base container - /
|
||||
//
|
||||
// container source path - /mnt/A
|
||||
//
|
||||
// For the above example, this function returns the readers for mount A and mount AB but not the
|
||||
// readers for / or mount B.
|
||||
func (rm *ArchiveStreamReaderMap) FindArchiveReaders(containerSourcePath string) ([]*ArchiveReader, error) {
|
||||
defer trace.End(trace.Begin(containerSourcePath))
|
||||
|
||||
var nodes []*ArchiveReader
|
||||
var startingNode *ArchiveReader
|
||||
var err error
|
||||
var isMountPoint bool
|
||||
|
||||
findStartingPrefix := func(prefix patricia.Prefix, item patricia.Item) error {
|
||||
if _, ok := item.(*ArchiveReader); !ok {
|
||||
return fmt.Errorf("item not ArchiveReader")
|
||||
}
|
||||
|
||||
startingNode = item.(*ArchiveReader)
|
||||
return nil
|
||||
}
|
||||
|
||||
walkPrefixSubtree := func(prefix patricia.Prefix, item patricia.Item) error {
|
||||
if _, ok := item.(*ArchiveReader); !ok {
|
||||
return fmt.Errorf("item not ArchiveReader")
|
||||
}
|
||||
|
||||
ar, _ := item.(*ArchiveReader)
|
||||
nodes = append(nodes, ar)
|
||||
isMountPoint = ar.mountPoint.Destination != "/" &&
|
||||
(isMountPoint || strings.HasPrefix(containerSourcePath, ar.mountPoint.Destination))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Clean off any trailing periods from the path, such as `cp cid:/mnt/. -`
|
||||
// Including the periods in the prefix walk would not match with subvolume
|
||||
// mounts like /mnt/vol1 or /mnt/vol2.
|
||||
// Find all mounts for the sourcepath
|
||||
cleanPath := vicarchive.Clean(containerSourcePath, false)
|
||||
prefix := patricia.Prefix(cleanPath)
|
||||
err = rm.prefixTrie.VisitSubtree(prefix, walkPrefixSubtree)
|
||||
if err != nil {
|
||||
msg := fmt.Sprintf("Failed to find a node for prefix %s: %s", containerSourcePath, err.Error())
|
||||
rm.op.Errorf(msg)
|
||||
return nil, fmt.Errorf(msg)
|
||||
}
|
||||
|
||||
// The above subtree walking MAY NOT find the starting prefix. For example /etc will not find /.
|
||||
// Subtree only finds prefix that starts with /etc. VisitPrefixes will find the starting prefix.
|
||||
// If the search was for /, then it will not find the starting node. In that case, we grab the
|
||||
// first node in the slice.
|
||||
err = rm.prefixTrie.VisitPrefixes(prefix, findStartingPrefix)
|
||||
if err != nil {
|
||||
msg := fmt.Sprintf("Failed to find starting node for prefix %s: %s", containerSourcePath, err.Error())
|
||||
rm.op.Errorf(msg)
|
||||
return nil, fmt.Errorf(msg)
|
||||
}
|
||||
|
||||
if startingNode != nil {
|
||||
found := false
|
||||
for _, node := range nodes {
|
||||
if node.mountPoint.Destination == startingNode.mountPoint.Destination {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
// prepend the starting node at the beginning
|
||||
nodes = append([]*ArchiveReader{startingNode}, nodes...)
|
||||
}
|
||||
} else if len(nodes) > 0 {
|
||||
startingNode = nodes[0]
|
||||
} else {
|
||||
msg := fmt.Sprintf("Failed to find starting node for prefix %s: %s", containerSourcePath, err.Error())
|
||||
rm.op.Errorf(msg)
|
||||
return nil, fmt.Errorf(msg)
|
||||
}
|
||||
|
||||
// if the path is a mount path, we need to include the directory header of the actual mountpoint
|
||||
// to ensure the corrent permissions of the directory, eg docker cp cid:/mnt/vol1/ needs to include
|
||||
// header from /mnt/vol1 located on containerfs
|
||||
// data from /mnt/vol1/ located on deviceId vol1
|
||||
if isMountPoint && path.Base(containerSourcePath) != "." {
|
||||
rm.op.Debugf("%s is a mountpoint, getting dir permissions from parent", cleanPath)
|
||||
// find the parent node using VisitPrefixes
|
||||
parent := path.Dir(cleanPath)
|
||||
prefix = patricia.Prefix(parent)
|
||||
startingNode = nil
|
||||
err = rm.prefixTrie.VisitPrefixes(prefix, findStartingPrefix)
|
||||
if err != nil {
|
||||
msg := fmt.Sprintf("Failed to generate parent node for mountpoint %s: %s", parent, err.Error())
|
||||
rm.op.Errorf(msg)
|
||||
return nil, fmt.Errorf(msg)
|
||||
}
|
||||
|
||||
var found bool
|
||||
if startingNode != nil {
|
||||
for _, node := range nodes {
|
||||
found = found || node.mountPoint.Destination == startingNode.mountPoint.Destination
|
||||
}
|
||||
if !found {
|
||||
nodes = append([]*ArchiveReader{startingNode}, nodes...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
err = rm.buildFilterSpec(containerSourcePath, nodes, startingNode)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return nodes, nil
|
||||
}
|
||||
|
||||
// ReadersForSourcePath returns all an array of io.Reader for all the readers within a container source path.
|
||||
// Example:
|
||||
// Reader 1 - /mnt/A
|
||||
// Reader 2 - /mnt/A/B
|
||||
//
|
||||
// containerSourcePath - /mnt/A
|
||||
// In the above, both readers are within the the container source path.
|
||||
func (rm *ArchiveStreamReaderMap) ReadersForSourcePath(proxy proxy.VicArchiveProxy, cid, containerSourcePath string) ([]io.ReadCloser, error) {
|
||||
defer trace.End(trace.Begin(containerSourcePath))
|
||||
|
||||
var streamReaders []io.ReadCloser
|
||||
|
||||
nodes, err := rm.FindArchiveReaders(containerSourcePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
mounts := []string{}
|
||||
for _, node := range nodes {
|
||||
mounts = append(mounts, node.mountPoint.Destination)
|
||||
}
|
||||
|
||||
// Create the io.Reader for those mounts if they haven't already been initialized
|
||||
for _, node := range nodes {
|
||||
if node.reader == nil {
|
||||
var store, deviceID string
|
||||
if node.mountPoint.Destination == "/" {
|
||||
// Special case. / refers to container VMDK and not a volume vmdk.
|
||||
store = constants.ContainerStoreName
|
||||
deviceID = cid
|
||||
} else {
|
||||
store = constants.VolumeStoreName
|
||||
deviceID = node.mountPoint.Name
|
||||
}
|
||||
|
||||
rm.op.Infof("Lazily initializing export stream for %s [%s]", node.mountPoint.Name, node.mountPoint.Destination)
|
||||
reader, err := proxy.ArchiveExportReader(rm.op, store, "", deviceID, "", true, node.filterSpec)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Unable to initialize export stream reader for prefix %s", node.mountPoint.Destination)
|
||||
rm.op.Errorf(err.Error())
|
||||
return nil, err
|
||||
}
|
||||
rm.op.Infof("Lazy initialization created reader %#v", reader)
|
||||
streamReaders = append(streamReaders, reader)
|
||||
} else {
|
||||
streamReaders = append(streamReaders, node.reader)
|
||||
}
|
||||
}
|
||||
|
||||
if len(nodes) == 0 {
|
||||
rm.op.Infof("Found no archive readers for %s", containerSourcePath)
|
||||
}
|
||||
|
||||
return streamReaders, nil
|
||||
}
|
||||
|
||||
// Close visits all the archive readers in the trie and closes the actual io.ReadCloser
|
||||
func (rm *ArchiveStreamReaderMap) Close() {
|
||||
defer trace.End(trace.Begin(""))
|
||||
|
||||
closeStream := func(prefix patricia.Prefix, item patricia.Item) error {
|
||||
if aw, ok := item.(*ArchiveReader); ok && aw.reader != nil {
|
||||
aw.reader.Close()
|
||||
aw.reader = nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
rm.prefixTrie.Visit(closeStream)
|
||||
}
|
||||
|
||||
// tryFakeStatPath tries to fake the statpath for path that targets the mountpoint or along the mountpoint
|
||||
func tryFakeStatPath(mounts []types.MountPoint, target string) (*types.ContainerPathStat, bool) {
|
||||
isMountPathTarget := false
|
||||
|
||||
for _, mount := range mounts {
|
||||
if strings.HasPrefix(mount.Destination, target) {
|
||||
isMountPathTarget = true
|
||||
}
|
||||
}
|
||||
|
||||
// check to see if the path is a mount point, if so, return fake path
|
||||
if isMountPathTarget {
|
||||
return &types.ContainerPathStat{
|
||||
Name: filepath.Base(target),
|
||||
Size: int64(4096),
|
||||
Mode: os.ModeDir,
|
||||
Mtime: time.Now(),
|
||||
LinkTarget: "",
|
||||
}, true
|
||||
}
|
||||
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// resolvePathWithMountPoints use mounts to generate a filter spec for the given path
|
||||
func resolvePathWithMountPoints(op trace.Operation, mounts []types.MountPoint, path string) *ArchiveReader {
|
||||
var primaryTarget *ArchiveReader
|
||||
|
||||
readerMap := NewArchiveStreamReaderMap(op, mounts, path)
|
||||
// #nosec: Errors unhandled.
|
||||
nodes, _ := readerMap.FindArchiveReaders(path)
|
||||
|
||||
for _, node := range nodes {
|
||||
if strings.HasPrefix(path, node.mountPoint.Destination) &&
|
||||
(primaryTarget == nil || len(node.mountPoint.Destination) > len(primaryTarget.mountPoint.Destination)) {
|
||||
primaryTarget = node
|
||||
}
|
||||
}
|
||||
|
||||
return primaryTarget
|
||||
}
|
||||
|
||||
func (rm *ArchiveStreamReaderMap) buildFilterSpec(containerSourcePath string, nodes []*ArchiveReader, startingNode *ArchiveReader) error {
|
||||
|
||||
mounts, foundNodes, err := rm.buildMountsAndNodes(startingNode.mountPoint.Destination, startingNode)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, node := range foundNodes {
|
||||
vicarchive.AddMountInclusionsExclusions(node.mountPoint.Destination, &node.filterSpec, mounts, containerSourcePath)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// buildMountsAndNodes returns the node pointers from the prefix tree as well as all mounts involved in the operation
|
||||
func (rm *ArchiveStreamReaderMap) buildMountsAndNodes(path string, node *ArchiveReader) ([]string, []*ArchiveReader, error) {
|
||||
|
||||
// NOTE(sflxn): We can modify this to make proper exclusions in the future. For now,
|
||||
// we assemble the list of mounts which are involved in the operation
|
||||
// and use the util.go function for generating all the needed information
|
||||
|
||||
mounts := []string{}
|
||||
nodes := []*ArchiveReader{}
|
||||
childWalker := func(prefix patricia.Prefix, item patricia.Item) error {
|
||||
if _, ok := item.(*ArchiveReader); !ok {
|
||||
return fmt.Errorf("item not ArchiveReader")
|
||||
}
|
||||
|
||||
ar, _ := item.(*ArchiveReader)
|
||||
mounts = append(mounts, ar.mountPoint.Destination)
|
||||
nodes = append(nodes, ar)
|
||||
return nil
|
||||
}
|
||||
|
||||
// prefix = current node's mount path
|
||||
nodePrefix := patricia.Prefix(path)
|
||||
|
||||
err := rm.prefixTrie.VisitSubtree(nodePrefix, childWalker)
|
||||
if err != nil {
|
||||
msg := fmt.Sprintf("Failed to build exclusion filter for %s: %s", path, err.Error())
|
||||
log.Error(msg)
|
||||
return nil, nil, fmt.Errorf(msg)
|
||||
}
|
||||
|
||||
return mounts, nodes, nil
|
||||
}
|
||||
341
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/archive_test.go
generated
vendored
341
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/archive_test.go
generated
vendored
@@ -1,341 +0,0 @@
|
||||
// Copyright 2017 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package backends
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
)
|
||||
|
||||
type MockCopyToData struct {
|
||||
containerDestPath string
|
||||
tarAssetName string
|
||||
expectedPrefix string
|
||||
}
|
||||
|
||||
type ReaderFilters struct {
|
||||
rebase string
|
||||
strip string
|
||||
exclude []string
|
||||
include string
|
||||
}
|
||||
type MockCopyFromData struct {
|
||||
containerSourcePath string
|
||||
expectedPrefices []string
|
||||
expectedFilterSpecs map[string]ReaderFilters
|
||||
}
|
||||
|
||||
func TestFindArchiveWriter(t *testing.T) {
|
||||
mounts := []types.MountPoint{
|
||||
{Name: "volA", Destination: "/mnt/A"},
|
||||
{Name: "volAB", Destination: "/mnt/A/AB"},
|
||||
{Name: "volB", Destination: "/mnt/B"},
|
||||
{Name: "R/W", Destination: "/"},
|
||||
}
|
||||
|
||||
mockData := []MockCopyToData{
|
||||
// mock data for tar asset as a file and container dest path including a mount point
|
||||
{
|
||||
containerDestPath: "/mnt/A/",
|
||||
tarAssetName: "file.txt",
|
||||
expectedPrefix: "/mnt/A",
|
||||
},
|
||||
{
|
||||
containerDestPath: "/mnt/A/AB",
|
||||
tarAssetName: "file.txt",
|
||||
expectedPrefix: "/mnt/A/AB",
|
||||
},
|
||||
// mock data for tar asset containing a mount point and the container dest path as /
|
||||
{
|
||||
containerDestPath: "/",
|
||||
tarAssetName: "mnt/A/file.txt",
|
||||
expectedPrefix: "/mnt/A",
|
||||
},
|
||||
{
|
||||
containerDestPath: "/",
|
||||
tarAssetName: "mnt/A/AB/file.txt",
|
||||
expectedPrefix: "/mnt/A/AB",
|
||||
},
|
||||
// mock data for cases that do not involve mount points
|
||||
{
|
||||
containerDestPath: "/",
|
||||
tarAssetName: "test/file.txt",
|
||||
expectedPrefix: "/",
|
||||
},
|
||||
}
|
||||
|
||||
for _, data := range mockData {
|
||||
op := trace.NewOperation(context.Background(), "")
|
||||
writerMap := NewArchiveStreamWriterMap(op, mounts, data.containerDestPath)
|
||||
aw, err := writerMap.FindArchiveWriter(data.containerDestPath, data.tarAssetName)
|
||||
assert.Nil(t, err, "Expected success from finding archive writer for container dest %s and tar asset path %s", data.containerDestPath, data.tarAssetName)
|
||||
assert.NotNil(t, aw, "Expected non-nil archive writer")
|
||||
if aw != nil {
|
||||
assert.Contains(t, aw.mountPoint.Destination, data.expectedPrefix,
|
||||
"Expected to find prefix %s for container dest %s and tar asset path %s",
|
||||
data.expectedPrefix, data.containerDestPath, data.tarAssetName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindArchiveReaders(t *testing.T) {
|
||||
mounts := []types.MountPoint{
|
||||
{Name: "volA", Destination: "/mnt/A"}, //mount point
|
||||
{Name: "volAB", Destination: "/mnt/A/AB"}, //mount point
|
||||
{Name: "volB", Destination: "/mnt/B"}, //mount point
|
||||
{Name: "R/W", Destination: "/"}, //container base volume
|
||||
}
|
||||
|
||||
mockData := []MockCopyFromData{
|
||||
// case 1: Get all mount prefix
|
||||
{
|
||||
containerSourcePath: "/",
|
||||
expectedPrefices: []string{"/", "/mnt/A", "/mnt/B", "/mnt/A/AB"},
|
||||
expectedFilterSpecs: map[string]ReaderFilters{
|
||||
"/": {
|
||||
rebase: "",
|
||||
strip: "",
|
||||
exclude: []string{"mnt/A/", "mnt/B/", "mnt/A/AB/"},
|
||||
include: "",
|
||||
},
|
||||
"/mnt/A": {
|
||||
rebase: "mnt/A",
|
||||
strip: "",
|
||||
exclude: []string{"AB/"},
|
||||
include: "",
|
||||
},
|
||||
"/mnt/B": {
|
||||
rebase: "mnt/B",
|
||||
strip: "",
|
||||
exclude: []string{},
|
||||
include: "",
|
||||
},
|
||||
"/mnt/A/AB": {
|
||||
rebase: "mnt/A/AB",
|
||||
strip: "",
|
||||
exclude: []string{},
|
||||
include: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
containerSourcePath: "/mnt",
|
||||
expectedPrefices: []string{"/", "/mnt/A", "/mnt/B", "/mnt/A/AB"},
|
||||
expectedFilterSpecs: map[string]ReaderFilters{
|
||||
"/": {
|
||||
rebase: "mnt",
|
||||
strip: "mnt",
|
||||
exclude: []string{"mnt/A/", "mnt/B/", "mnt/A/AB/"},
|
||||
include: "",
|
||||
},
|
||||
"/mnt/A": {
|
||||
rebase: "mnt/A",
|
||||
strip: "",
|
||||
exclude: []string{"AB/"},
|
||||
include: "",
|
||||
},
|
||||
"/mnt/B": {
|
||||
rebase: "mnt/B",
|
||||
strip: "",
|
||||
exclude: []string{},
|
||||
include: "",
|
||||
},
|
||||
"/mnt/A/AB": {
|
||||
rebase: "mnt/A/AB",
|
||||
strip: "",
|
||||
exclude: []string{},
|
||||
include: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
containerSourcePath: "/mnt/",
|
||||
expectedPrefices: []string{"/", "/mnt/A", "/mnt/B", "/mnt/A/AB"},
|
||||
expectedFilterSpecs: map[string]ReaderFilters{
|
||||
"/": {
|
||||
rebase: "mnt",
|
||||
strip: "mnt",
|
||||
exclude: []string{"mnt/A/", "mnt/B/", "mnt/A/AB/"},
|
||||
include: "",
|
||||
},
|
||||
"/mnt/A": {
|
||||
rebase: "mnt/A",
|
||||
strip: "",
|
||||
exclude: []string{"AB/"},
|
||||
include: "",
|
||||
},
|
||||
"/mnt/B": {
|
||||
rebase: "mnt/B",
|
||||
strip: "",
|
||||
exclude: []string{},
|
||||
include: "",
|
||||
},
|
||||
"/mnt/A/AB": {
|
||||
rebase: "mnt/A/AB",
|
||||
strip: "",
|
||||
exclude: []string{},
|
||||
include: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
// case 2: Do not include /mnt/B
|
||||
{
|
||||
containerSourcePath: "/mnt/A",
|
||||
expectedPrefices: []string{"/", "/mnt/A", "/mnt/A/AB"},
|
||||
expectedFilterSpecs: map[string]ReaderFilters{
|
||||
"/": {
|
||||
rebase: "A",
|
||||
strip: "mnt/A",
|
||||
exclude: []string{"mnt/A/", "mnt/A/AB/"},
|
||||
include: "/mnt/A",
|
||||
},
|
||||
"/mnt/A": {
|
||||
rebase: "A",
|
||||
strip: "",
|
||||
exclude: []string{"AB/"},
|
||||
},
|
||||
"/mnt/A/AB": {
|
||||
rebase: "A/AB",
|
||||
exclude: []string{},
|
||||
include: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
// case 3: Return only the container base "/"
|
||||
{
|
||||
containerSourcePath: "/mnt/not-a-mount",
|
||||
expectedPrefices: []string{"/"},
|
||||
expectedFilterSpecs: map[string]ReaderFilters{
|
||||
"/": {
|
||||
rebase: "not-a-mount",
|
||||
strip: "mnt/not-a-mount",
|
||||
exclude: []string{""},
|
||||
include: "mnt/not-a-mount",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
containerSourcePath: "/etc/",
|
||||
expectedPrefices: []string{"/"},
|
||||
expectedFilterSpecs: map[string]ReaderFilters{
|
||||
"/": {
|
||||
rebase: "etc",
|
||||
strip: "etc",
|
||||
exclude: []string{""},
|
||||
include: "etc",
|
||||
},
|
||||
},
|
||||
},
|
||||
// case 4: Check inclusion filter
|
||||
{
|
||||
containerSourcePath: "/mnt/A/a/file.txt",
|
||||
expectedPrefices: []string{"/mnt/A"},
|
||||
expectedFilterSpecs: map[string]ReaderFilters{
|
||||
"/mnt/A": {
|
||||
rebase: "file.txt",
|
||||
strip: "a/file.txt",
|
||||
exclude: []string{""},
|
||||
include: "a/file.txt",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for i, data := range mockData {
|
||||
op := trace.NewOperation(context.Background(), "")
|
||||
readerMap := NewArchiveStreamReaderMap(op, mounts, data.containerSourcePath)
|
||||
archiveReaders, err := readerMap.FindArchiveReaders(data.containerSourcePath)
|
||||
assert.Nil(t, err, "Expected success from finding archive readers for container source %s", data.containerSourcePath)
|
||||
assert.NotNil(t, archiveReaders, "Expected an array of archive readers but got nil for container source path %s", data.containerSourcePath)
|
||||
assert.NotEmpty(t, archiveReaders, "Expected an array of archive readers %s with more than one items", data.containerSourcePath)
|
||||
|
||||
log.Debugf("Data = %#v", data)
|
||||
pa := PrefixArray(archiveReaders)
|
||||
nonOverlap := UnionMinusIntersection(pa, data.expectedPrefices)
|
||||
assert.Empty(t, nonOverlap, "Found mismatch in the prefix array and expected array for source path %s. Non-overlapped result = %#v", data.containerSourcePath, nonOverlap)
|
||||
|
||||
// Check filter spec
|
||||
for _, ar := range archiveReaders {
|
||||
currPath := ar.mountPoint.Destination
|
||||
assert.Equal(t, data.expectedFilterSpecs[currPath].rebase, ar.filterSpec.RebasePath, "rebase filterspec not correct")
|
||||
assert.Equal(t, data.expectedFilterSpecs[currPath].strip, ar.filterSpec.StripPath, "strip filterspec not correct")
|
||||
for _, ex := range data.expectedFilterSpecs[currPath].exclude {
|
||||
_, ok := ar.filterSpec.Exclusions[ex]
|
||||
assert.True(t, ok, "Did not find %s in exclusion map for reader %s in mock #%d", ex, currPath, i)
|
||||
}
|
||||
}
|
||||
|
||||
// Check inclusion filter
|
||||
if len(archiveReaders) == 1 {
|
||||
ar := archiveReaders[0]
|
||||
currPath := ar.mountPoint.Destination
|
||||
|
||||
expectedInclusion := data.expectedFilterSpecs[currPath].include
|
||||
|
||||
assert.Len(t, ar.filterSpec.Inclusions, 1, "Expected only 1 inclusion filter for %s but got %d in mock #%d", data.containerSourcePath, len(ar.filterSpec.Inclusions), i)
|
||||
|
||||
if len(ar.filterSpec.Inclusions) == 1 {
|
||||
_, ok := ar.filterSpec.Inclusions[expectedInclusion]
|
||||
assert.True(t, ok, "Expected inclusion filter to contain %s in mock #%d", expectedInclusion, i)
|
||||
|
||||
// Sanity check to make sure include isn't in exclusion. This should never happen.
|
||||
for _, ex := range data.expectedFilterSpecs[currPath].exclude {
|
||||
assert.NotEqual(t, expectedInclusion, ex, "Expected inclusion %s not to be in exclusion list %#v in mock #%d", expectedInclusion, ex, i)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func PrefixArray(readers []*ArchiveReader) (pa []string) {
|
||||
for _, reader := range readers {
|
||||
pa = append(pa, reader.mountPoint.Destination)
|
||||
}
|
||||
|
||||
log.Debugf("prefix array - %#v", pa)
|
||||
return
|
||||
}
|
||||
|
||||
func UnionMinusIntersection(A, B []string) (res []string) {
|
||||
test := make(map[string]bool)
|
||||
|
||||
log.Debugf("Looking for non overlapping in array A-%#v and array B-%#v", A, B)
|
||||
|
||||
for _, data := range A {
|
||||
test[data] = true
|
||||
}
|
||||
|
||||
for _, data := range B {
|
||||
if _, ok := test[data]; ok {
|
||||
delete(test, data)
|
||||
} else {
|
||||
res = append(res, data)
|
||||
}
|
||||
}
|
||||
|
||||
for key := range test {
|
||||
res = append(res, key)
|
||||
}
|
||||
|
||||
log.Debugf("Resulting non overlapped array - %#v", res)
|
||||
|
||||
return
|
||||
}
|
||||
543
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/backends.go
generated
vendored
543
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/backends.go
generated
vendored
@@ -1,543 +0,0 @@
|
||||
// Copyright 2016 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package backends
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/url"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/docker/docker/daemon/events"
|
||||
"github.com/go-openapi/runtime"
|
||||
rc "github.com/go-openapi/runtime/client"
|
||||
"github.com/go-openapi/swag"
|
||||
"golang.org/x/sync/singleflight"
|
||||
|
||||
"github.com/vmware/vic/lib/apiservers/engine/backends/cache"
|
||||
"github.com/vmware/vic/lib/apiservers/engine/backends/container"
|
||||
"github.com/vmware/vic/lib/apiservers/engine/network"
|
||||
"github.com/vmware/vic/lib/apiservers/engine/proxy"
|
||||
apiclient "github.com/vmware/vic/lib/apiservers/portlayer/client"
|
||||
"github.com/vmware/vic/lib/apiservers/portlayer/client/containers"
|
||||
"github.com/vmware/vic/lib/apiservers/portlayer/client/misc"
|
||||
"github.com/vmware/vic/lib/apiservers/portlayer/client/scopes"
|
||||
"github.com/vmware/vic/lib/apiservers/portlayer/client/storage"
|
||||
"github.com/vmware/vic/lib/apiservers/portlayer/models"
|
||||
"github.com/vmware/vic/lib/config"
|
||||
"github.com/vmware/vic/lib/config/dynamic"
|
||||
"github.com/vmware/vic/lib/config/dynamic/admiral"
|
||||
"github.com/vmware/vic/lib/constants"
|
||||
"github.com/vmware/vic/lib/imagec"
|
||||
"github.com/vmware/vic/pkg/errors"
|
||||
"github.com/vmware/vic/pkg/registry"
|
||||
"github.com/vmware/vic/pkg/vsphere/session"
|
||||
"github.com/vmware/vic/pkg/vsphere/sys"
|
||||
)
|
||||
|
||||
const (
|
||||
PortlayerName = "Backend Engine"
|
||||
|
||||
// RetryTimeSeconds defines how many seconds to wait between retries
|
||||
RetryTimeSeconds = 2
|
||||
defaultSessionKeepAlive = 20 * time.Second
|
||||
APITimeout = constants.PropertyCollectorTimeout + 3*time.Second
|
||||
)
|
||||
|
||||
var (
|
||||
portLayerClient *apiclient.PortLayer
|
||||
portLayerServerAddr string
|
||||
portLayerName string
|
||||
productName string
|
||||
productVersion string
|
||||
|
||||
vchConfig *dynConfig
|
||||
RegistryCertPool *x509.CertPool
|
||||
archiveProxy proxy.VicArchiveProxy
|
||||
|
||||
eventService *events.Events
|
||||
|
||||
servicePort uint
|
||||
)
|
||||
|
||||
type dynConfig struct {
|
||||
sync.Mutex
|
||||
|
||||
Cfg *config.VirtualContainerHostConfigSpec
|
||||
src dynamic.Source
|
||||
merger dynamic.Merger
|
||||
sess *session.Session
|
||||
|
||||
Whitelist, Blacklist, Insecure registry.Set
|
||||
remoteWl bool
|
||||
|
||||
group singleflight.Group
|
||||
lastCfg *dynConfig
|
||||
}
|
||||
|
||||
func Init(portLayerAddr, product string, port uint, config *config.VirtualContainerHostConfigSpec) error {
|
||||
servicePort = port
|
||||
_, _, err := net.SplitHostPort(portLayerAddr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if config == nil {
|
||||
return fmt.Errorf("docker API server requires VCH config")
|
||||
}
|
||||
|
||||
productName = product
|
||||
|
||||
if config.Version != nil {
|
||||
productVersion = config.Version.ShortVersion()
|
||||
}
|
||||
if productVersion == "" {
|
||||
portLayerName = product + " Backend Engine"
|
||||
} else {
|
||||
portLayerName = product + " " + productVersion + " Backend Engine"
|
||||
}
|
||||
|
||||
if vchConfig, err = newDynConfig(ctx, config); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
loadRegistryCACerts()
|
||||
|
||||
t := rc.New(portLayerAddr, "/", []string{"http"})
|
||||
t.Consumers["application/x-tar"] = runtime.ByteStreamConsumer()
|
||||
t.Consumers["application/octet-stream"] = runtime.ByteStreamConsumer()
|
||||
t.Producers["application/x-tar"] = runtime.ByteStreamProducer()
|
||||
t.Producers["application/octet-stream"] = runtime.ByteStreamProducer()
|
||||
|
||||
portLayerClient = apiclient.New(t, nil)
|
||||
portLayerServerAddr = portLayerAddr
|
||||
|
||||
log.Infof("*** Portlayer Address = %s", portLayerAddr)
|
||||
|
||||
// block indefinitely while waiting on the portlayer to respond to pings
|
||||
// the vic-machine installer timeout will intervene if this blocks for too long
|
||||
pingPortLayer()
|
||||
|
||||
if err := hydrateCaches(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Info("Creating image store")
|
||||
if err := createImageStore(); err != nil {
|
||||
log.Errorf("Failed to create image store")
|
||||
return err
|
||||
}
|
||||
|
||||
archiveProxy = proxy.NewArchiveProxy(portLayerClient)
|
||||
|
||||
eventService = events.New()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func hydrateCaches() error {
|
||||
const waiters = 3
|
||||
|
||||
wg := sync.WaitGroup{}
|
||||
wg.Add(waiters)
|
||||
errChan := make(chan error, waiters)
|
||||
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
if err := imagec.InitializeLayerCache(portLayerClient); err != nil {
|
||||
errChan <- fmt.Errorf("Failed to initialize layer cache: %s", err)
|
||||
return
|
||||
}
|
||||
log.Info("Layer cache initialized successfully")
|
||||
errChan <- nil
|
||||
}()
|
||||
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
if err := cache.InitializeImageCache(portLayerClient); err != nil {
|
||||
errChan <- fmt.Errorf("Failed to initialize image cache: %s", err)
|
||||
return
|
||||
}
|
||||
log.Info("Image cache initialized successfully")
|
||||
|
||||
// container cache relies on image cache so we share a goroutine to update
|
||||
// them serially
|
||||
if err := syncContainerCache(); err != nil {
|
||||
errChan <- fmt.Errorf("Failed to update container cache: %s", err)
|
||||
return
|
||||
}
|
||||
log.Info("Container cache updated successfully")
|
||||
errChan <- nil
|
||||
}()
|
||||
|
||||
go func() {
|
||||
log.Info("Refreshing repository cache")
|
||||
defer wg.Done()
|
||||
if err := cache.NewRepositoryCache(portLayerClient); err != nil {
|
||||
errChan <- fmt.Errorf("Failed to create repository cache: %s", err.Error())
|
||||
return
|
||||
}
|
||||
errChan <- nil
|
||||
log.Info("Repository cache updated successfully")
|
||||
}()
|
||||
|
||||
wg.Wait()
|
||||
close(errChan)
|
||||
|
||||
var errs []string
|
||||
for err := range errChan {
|
||||
if err != nil {
|
||||
// accumulate all errors into one
|
||||
errs = append(errs, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
var e error
|
||||
if len(errs) > 0 {
|
||||
e = fmt.Errorf(strings.Join(errs, ", "))
|
||||
}
|
||||
|
||||
if e != nil {
|
||||
log.Errorf("Errors occurred during cache hydration at VCH start: %s", e)
|
||||
}
|
||||
|
||||
return e
|
||||
}
|
||||
|
||||
func PortLayerClient() *apiclient.PortLayer {
|
||||
return portLayerClient
|
||||
}
|
||||
|
||||
func PortLayerServer() string {
|
||||
return portLayerServerAddr
|
||||
}
|
||||
|
||||
func PortLayerName() string {
|
||||
return portLayerName
|
||||
}
|
||||
|
||||
func ProductName() string {
|
||||
return productName
|
||||
}
|
||||
|
||||
func ProductVersion() string {
|
||||
return productVersion
|
||||
}
|
||||
|
||||
func pingPortLayer() {
|
||||
ticker := time.NewTicker(RetryTimeSeconds * time.Second)
|
||||
defer ticker.Stop()
|
||||
params := misc.NewPingParamsWithContext(context.TODO())
|
||||
|
||||
log.Infof("Waiting for portlayer to come up")
|
||||
|
||||
for range ticker.C {
|
||||
if _, err := portLayerClient.Misc.Ping(params); err == nil {
|
||||
log.Info("Portlayer is up and responding to pings")
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func createImageStore() error {
|
||||
// TODO(jzt): we should move this to a utility package or something
|
||||
host, err := sys.UUID()
|
||||
if err != nil {
|
||||
log.Errorf("Failed to determine host UUID")
|
||||
return err
|
||||
}
|
||||
|
||||
log.Infof("*** UUID = %s", host)
|
||||
|
||||
// attempt to create the image store if it doesn't exist
|
||||
store := &models.ImageStore{Name: host}
|
||||
_, err = portLayerClient.Storage.CreateImageStore(
|
||||
storage.NewCreateImageStoreParamsWithContext(ctx).WithBody(store),
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
if _, ok := err.(*storage.CreateImageStoreConflict); ok {
|
||||
log.Debugf("Store already exists")
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
log.Infof("Image store created successfully")
|
||||
return nil
|
||||
}
|
||||
|
||||
// syncContainerCache runs once at startup to populate the container cache
|
||||
func syncContainerCache() error {
|
||||
log.Debugf("Updating container cache")
|
||||
|
||||
backend := NewContainerBackend()
|
||||
client := PortLayerClient()
|
||||
|
||||
reqParams := containers.NewGetContainerListParamsWithContext(ctx).WithAll(swag.Bool(true))
|
||||
containme, err := client.Containers.GetContainerList(reqParams)
|
||||
if err != nil {
|
||||
return errors.Errorf("Failed to retrieve container list from portlayer: %s", err)
|
||||
}
|
||||
|
||||
log.Debugf("Found %d containers", len(containme.Payload))
|
||||
cc := cache.ContainerCache()
|
||||
var errs []string
|
||||
for _, info := range containme.Payload {
|
||||
container := proxy.ContainerInfoToVicContainer(*info, portLayerName)
|
||||
cc.AddContainer(container)
|
||||
if err = setPortMapping(info, backend, container); err != nil {
|
||||
errs = append(errs, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
if len(errs) > 0 {
|
||||
return errors.Errorf("Failed to set port mapping: %s", strings.Join(errs, "\n"))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func setPortMapping(info *models.ContainerInfo, backend *ContainerBackend, container *container.VicContainer) error {
|
||||
if info.ContainerConfig.State == "" {
|
||||
log.Infof("container state is nil")
|
||||
return nil
|
||||
}
|
||||
|
||||
if info.ContainerConfig.State != "Running" || len(container.HostConfig.PortBindings) == 0 {
|
||||
log.Infof("No need to restore port bindings, state: %s, portbinding: %+v", info.ContainerConfig.State, container.HostConfig.PortBindings)
|
||||
return nil
|
||||
}
|
||||
|
||||
log.Debugf("Set port mapping for container %q, portmapping %+v", container.Name, container.HostConfig.PortBindings)
|
||||
client := PortLayerClient()
|
||||
endpointsOK, err := client.Scopes.GetContainerEndpoints(
|
||||
scopes.NewGetContainerEndpointsParamsWithContext(ctx).WithHandleOrID(container.ContainerID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, e := range endpointsOK.Payload {
|
||||
if len(e.Ports) > 0 && e.Scope == constants.BridgeScopeType {
|
||||
if err = network.MapPorts(container, e, container.ContainerID); err != nil {
|
||||
log.Errorf(err.Error())
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func loadRegistryCACerts() {
|
||||
var err error
|
||||
|
||||
RegistryCertPool, err = x509.SystemCertPool()
|
||||
log.Debugf("Loaded %d CAs for registries from system CA bundle", len(RegistryCertPool.Subjects()))
|
||||
if err != nil {
|
||||
log.Errorf("Unable to load system CAs")
|
||||
return
|
||||
}
|
||||
|
||||
vchConfig.Lock()
|
||||
defer vchConfig.Unlock()
|
||||
if !RegistryCertPool.AppendCertsFromPEM(vchConfig.Cfg.RegistryCertificateAuthorities) {
|
||||
log.Errorf("Unable to load CAs for registry access in config")
|
||||
return
|
||||
}
|
||||
|
||||
log.Debugf("Loaded %d CAs for registries from config", len(RegistryCertPool.Subjects()))
|
||||
}
|
||||
|
||||
func EventService() *events.Events {
|
||||
return eventService
|
||||
}
|
||||
|
||||
// RegistryCheck checkes the given url against the registry whitelist, blacklist, and insecure
|
||||
// registries lists. It returns true for each list where u matches that list.
|
||||
func (d *dynConfig) RegistryCheck(ctx context.Context, u *url.URL) (wl bool, bl bool, insecure bool) {
|
||||
m := d.update(ctx)
|
||||
|
||||
us := u.String()
|
||||
wl = len(m.Whitelist) == 0 || m.Whitelist.Match(us)
|
||||
bl = len(m.Blacklist) == 0 || !m.Blacklist.Match(us)
|
||||
insecure = m.Insecure.Match(us)
|
||||
return
|
||||
}
|
||||
|
||||
func (d *dynConfig) update(ctx context.Context) *dynConfig {
|
||||
const key = "RegistryCheck"
|
||||
resCh := d.group.DoChan(key, func() (interface{}, error) {
|
||||
d.Lock()
|
||||
src := d.src
|
||||
d.Unlock()
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
|
||||
defer cancel()
|
||||
|
||||
c, err := src.Get(ctx)
|
||||
if err != nil {
|
||||
log.Warnf("error getting config from source: %s", err)
|
||||
}
|
||||
|
||||
d.Lock()
|
||||
defer d.Unlock()
|
||||
|
||||
m := d
|
||||
if c != nil {
|
||||
// update config
|
||||
if m, err = d.merged(c); err != nil {
|
||||
log.Errorf("error updating config: %s", err)
|
||||
m = d
|
||||
} else {
|
||||
if len(c.RegistryWhitelist) > 0 {
|
||||
m.remoteWl = true
|
||||
}
|
||||
}
|
||||
} else if err == nil && src == d.src {
|
||||
// err == nil and c == nil, which
|
||||
// indicates no remote sources
|
||||
// were found, try resetting the
|
||||
// source for next time
|
||||
if err := d.resetSrc(); err != nil {
|
||||
log.Warnf("could not reset config source: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
d.lastCfg = m
|
||||
return m, nil
|
||||
})
|
||||
|
||||
select {
|
||||
case res := <-resCh:
|
||||
return res.Val.(*dynConfig)
|
||||
case <-ctx.Done():
|
||||
return func() *dynConfig {
|
||||
d.Lock()
|
||||
defer d.Unlock()
|
||||
|
||||
if d.lastCfg == nil {
|
||||
return d
|
||||
}
|
||||
|
||||
return d.lastCfg
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
func (d *dynConfig) resetSrc() error {
|
||||
ep, err := d.clientEndpoint()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
d.src = admiral.NewSource(d.sess, ep.String())
|
||||
return nil
|
||||
}
|
||||
|
||||
func newDynConfig(ctx context.Context, c *config.VirtualContainerHostConfigSpec) (*dynConfig, error) {
|
||||
d := &dynConfig{
|
||||
Cfg: c,
|
||||
}
|
||||
var err error
|
||||
if d.Insecure, err = dynamic.ParseRegistries(c.InsecureRegistries); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if d.Whitelist, err = dynamic.ParseRegistries(c.RegistryWhitelist); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if d.sess, err = newSession(ctx, c); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
d.merger = dynamic.NewMerger()
|
||||
if err := d.resetSrc(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return d, nil
|
||||
}
|
||||
|
||||
// update merges another config into this config. d should be locked before
|
||||
// calling this.
|
||||
func (d *dynConfig) merged(c *config.VirtualContainerHostConfigSpec) (*dynConfig, error) {
|
||||
if c == nil {
|
||||
return d, nil
|
||||
}
|
||||
|
||||
newcfg, err := d.merger.Merge(d.Cfg, c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var wl, bl, insecure registry.Set
|
||||
if wl, err = dynamic.ParseRegistries(newcfg.RegistryWhitelist); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if bl, err = dynamic.ParseRegistries(newcfg.RegistryBlacklist); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if insecure, err = dynamic.ParseRegistries(newcfg.InsecureRegistries); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &dynConfig{
|
||||
Whitelist: wl,
|
||||
Blacklist: bl,
|
||||
Insecure: insecure,
|
||||
Cfg: newcfg,
|
||||
src: d.src,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (d *dynConfig) clientEndpoint() (*url.URL, error) {
|
||||
ips, err := net.LookupIP("client.localhost")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
scheme := "https"
|
||||
if d.Cfg.HostCertificate.IsNil() {
|
||||
scheme = "http"
|
||||
}
|
||||
|
||||
return url.Parse(fmt.Sprintf("%s://%s:%d", scheme, ips[0], servicePort))
|
||||
}
|
||||
|
||||
func newSession(ctx context.Context, config *config.VirtualContainerHostConfigSpec) (*session.Session, error) {
|
||||
// strip the path off of the target url since it may contain the
|
||||
// datacenter
|
||||
u, err := url.Parse(config.Target)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
u.Path = ""
|
||||
sessCfg := &session.Config{
|
||||
Service: u.String(),
|
||||
User: url.UserPassword(config.Username, config.Token),
|
||||
Thumbprint: config.TargetThumbprint,
|
||||
Keepalive: defaultSessionKeepAlive,
|
||||
}
|
||||
|
||||
sess := session.NewSession(sessCfg)
|
||||
if sess.Connect(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return sess, nil
|
||||
}
|
||||
32
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/build.go
generated
vendored
32
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/build.go
generated
vendored
@@ -1,32 +0,0 @@
|
||||
// Copyright 2017 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package backends
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/backend"
|
||||
"golang.org/x/net/context"
|
||||
|
||||
"github.com/vmware/vic/lib/apiservers/engine/errors"
|
||||
)
|
||||
|
||||
type Builder struct {
|
||||
}
|
||||
|
||||
func (b *Builder) BuildFromContext(ctx context.Context, src io.ReadCloser, remote string, buildOptions *types.ImageBuildOptions, pg backend.ProgressWriter) (string, error) {
|
||||
return "", errors.APINotSupportedMsg(ProductName(), "BuildFromContext")
|
||||
}
|
||||
104
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/cache/repo_cache_test.go
generated
vendored
104
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/cache/repo_cache_test.go
generated
vendored
@@ -1,104 +0,0 @@
|
||||
// Copyright 2016 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package cache
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/vmware/vic/lib/apiservers/portlayer/client"
|
||||
"github.com/vmware/vic/pkg/uid"
|
||||
|
||||
"github.com/docker/docker/reference"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func repoSetup() {
|
||||
rCache = &repoCache{
|
||||
client: &client.PortLayer{},
|
||||
Repositories: make(map[string]repository),
|
||||
Layers: make(map[string]string),
|
||||
images: make(map[string]string),
|
||||
referencesByIDCache: make(map[string]map[string]reference.Named),
|
||||
}
|
||||
}
|
||||
|
||||
func TestRepo(t *testing.T) {
|
||||
repoSetup()
|
||||
|
||||
notInRepo, _ := reference.ParseNamed("alpine")
|
||||
noImageID := uid.New()
|
||||
ref, _ := reference.ParseNamed("busybox:1.25.1")
|
||||
imageID := uid.New()
|
||||
layerID := uid.New()
|
||||
|
||||
// add busybox:1.25.1
|
||||
err := RepositoryCache().AddReference(ref, imageID.String(), false, layerID.String(), false)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Get will return the imageID for the named object
|
||||
n, err := RepositoryCache().Get(ref)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, imageID.String(), n)
|
||||
|
||||
// Get all references
|
||||
refs := RepositoryCache().References(imageID.String())
|
||||
assert.Equal(t, 1, len(refs))
|
||||
|
||||
// Get reference by Named
|
||||
associated := RepositoryCache().ReferencesByName(ref)
|
||||
assert.Equal(t, 1, len(associated))
|
||||
|
||||
// Get tags for image
|
||||
tags := RepositoryCache().Tags(imageID.String())
|
||||
assert.Equal(t, 1, len(tags))
|
||||
|
||||
// Get references for non-existent image
|
||||
refs = RepositoryCache().References(noImageID.String())
|
||||
assert.Equal(t, 0, len(refs))
|
||||
|
||||
// Get reference by Named
|
||||
associated = RepositoryCache().ReferencesByName(notInRepo)
|
||||
assert.Equal(t, 0, len(associated))
|
||||
|
||||
// get image id via layer id
|
||||
ig := RepositoryCache().GetImageID(layerID.String())
|
||||
assert.Equal(t, imageID.String(), ig)
|
||||
|
||||
// remove busybox from the cache
|
||||
r, err := RepositoryCache().Remove(ref.String(), false)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, ref.String(), r)
|
||||
|
||||
// busybox is removed, so this should fail
|
||||
x, err := RepositoryCache().Remove(ref.String(), false)
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, "", x)
|
||||
|
||||
// add reference by digest
|
||||
ng, _ := reference.ParseNamed("nginx@sha256:7281cf7c854b0dfc7c68a6a4de9a785a973a14f1481bc028e2022bcd6a8d9f64")
|
||||
err = RepositoryCache().AddReference(ng, imageID.String(), true, layerID.String(), false)
|
||||
assert.NoError(t, err)
|
||||
|
||||
dd := RepositoryCache().Digests(imageID.String())
|
||||
assert.Equal(t, 1, len(dd))
|
||||
// remove the digest
|
||||
ngx, err := RepositoryCache().Remove(ng.String(), false)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, ng.String(), ngx)
|
||||
// nada
|
||||
nada := RepositoryCache().Digests(imageID.String())
|
||||
assert.Equal(t, 0, len(nada))
|
||||
|
||||
}
|
||||
40
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/checkpoint.go
generated
vendored
40
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/checkpoint.go
generated
vendored
@@ -1,40 +0,0 @@
|
||||
// Copyright 2017 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package backends
|
||||
|
||||
import (
|
||||
"github.com/docker/docker/api/types"
|
||||
|
||||
"github.com/vmware/vic/lib/apiservers/engine/errors"
|
||||
)
|
||||
|
||||
type CheckpointBackend struct {
|
||||
}
|
||||
|
||||
func NewCheckpointBackend() *CheckpointBackend {
|
||||
return &CheckpointBackend{}
|
||||
}
|
||||
|
||||
func (c *CheckpointBackend) CheckpointCreate(container string, config types.CheckpointCreateOptions) error {
|
||||
return errors.APINotSupportedMsg(ProductName(), "checkpointing")
|
||||
}
|
||||
|
||||
func (c *CheckpointBackend) CheckpointDelete(container string, config types.CheckpointDeleteOptions) error {
|
||||
return errors.APINotSupportedMsg(ProductName(), "checkpointing")
|
||||
}
|
||||
|
||||
func (c *CheckpointBackend) CheckpointList(container string, config types.CheckpointListOptions) ([]types.Checkpoint, error) {
|
||||
return nil, errors.APINotSupportedMsg(ProductName(), "checkpointing")
|
||||
}
|
||||
479
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/commit.go
generated
vendored
479
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/commit.go
generated
vendored
@@ -1,479 +0,0 @@
|
||||
// Copyright 2016-2017 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package backends
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"compress/gzip"
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
|
||||
"github.com/docker/distribution/digest"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/backend"
|
||||
containertypes "github.com/docker/docker/api/types/container"
|
||||
eventtypes "github.com/docker/docker/api/types/events"
|
||||
"github.com/docker/docker/builder/dockerfile"
|
||||
dockerimage "github.com/docker/docker/image"
|
||||
dockerLayer "github.com/docker/docker/layer"
|
||||
"github.com/docker/docker/pkg/progress"
|
||||
"github.com/docker/docker/pkg/streamformatter"
|
||||
"github.com/docker/docker/pkg/stringid"
|
||||
"github.com/docker/docker/reference"
|
||||
|
||||
"github.com/vmware/vic/lib/apiservers/engine/backends/cache"
|
||||
"github.com/vmware/vic/lib/apiservers/engine/errors"
|
||||
"github.com/vmware/vic/lib/apiservers/portlayer/models"
|
||||
"github.com/vmware/vic/lib/constants"
|
||||
"github.com/vmware/vic/lib/imagec"
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
"github.com/vmware/vic/pkg/version"
|
||||
"github.com/vmware/vic/pkg/vsphere/sys"
|
||||
)
|
||||
|
||||
// Commit creates a new filesystem image from the current state of a container.
|
||||
// The image can optionally be tagged into a repository.
|
||||
func (i *ImageBackend) Commit(name string, config *backend.ContainerCommitConfig) (imageID string, err error) {
|
||||
defer trace.End(trace.Begin(name))
|
||||
op := trace.NewOperation(context.Background(), "Commit")
|
||||
// Look up the container name in the metadata cache to get long ID
|
||||
vc := cache.ContainerCache().GetContainer(name)
|
||||
if vc == nil {
|
||||
return "", errors.NotFoundError(name)
|
||||
}
|
||||
|
||||
// get container info
|
||||
c, err := containerEngine.ContainerInspect(name, false, "")
|
||||
if err != nil {
|
||||
return "", errors.InternalServerError(err.Error())
|
||||
}
|
||||
container, ok := c.(*types.ContainerJSON)
|
||||
if !ok {
|
||||
return "", errors.InternalServerError(fmt.Sprintf("Container type assertion failed"))
|
||||
}
|
||||
if container.State.Running || container.State.Restarting {
|
||||
return "", errors.ConflictError(fmt.Sprintf("%s does not yet support commit of a running container", ProductName()))
|
||||
}
|
||||
// TODO: pause container after container.Pause is implemented
|
||||
newConfig, err := dockerfile.BuildFromConfig(config.Config, config.Changes)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if config.MergeConfigs {
|
||||
if err := merge(newConfig, container.Config); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
ic, err := getImagec(config)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
rc, err := containerEngine.GetContainerChanges(op, vc, true)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Unable to initialize export stream reader for container %s", name)
|
||||
}
|
||||
|
||||
layer, err := downloadDiff(rc, container.ID, ic.Options)
|
||||
if err != nil {
|
||||
rc.Close()
|
||||
return "", fmt.Errorf("Unable to export stream reader for container %s: %s", name, err)
|
||||
}
|
||||
// close reader before write image to avoid resource conflict
|
||||
rc.Close()
|
||||
if err = setLayerConfig(layer, container, config, newConfig); err != nil {
|
||||
return "", err
|
||||
}
|
||||
// Dump metadata next to diff file
|
||||
destination := path.Join(imagec.DestinationDirectory(ic.Options), layer.ID)
|
||||
err = ioutil.WriteFile(path.Join(destination, layer.ID+".json"), []byte(layer.Meta), 0644)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
imagec.LayerCache().Add(layer)
|
||||
|
||||
var layers []*imagec.ImageWithMeta
|
||||
|
||||
layers = append(layers, layer)
|
||||
lm := layer
|
||||
for pl := lm.Parent; pl != constants.ScratchLayerID; pl = lm.Parent {
|
||||
// populate manifest layer with existing cached data
|
||||
if lm, err = imagec.LayerCache().Get(pl); err != nil {
|
||||
return "", errors.InternalServerError(fmt.Sprintf("Failed to get parent image layer %s: %s", pl, err))
|
||||
}
|
||||
layers = append(layers, lm)
|
||||
}
|
||||
|
||||
ic.ImageLayers = layers
|
||||
imageConfig, err := ic.CreateImageConfig(layers)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
imageConfig.Name = config.Repo
|
||||
// place calculated ImageID in struct
|
||||
ic.ImageID = imageConfig.ImageID
|
||||
|
||||
// cache and persist the image
|
||||
if err = cache.ImageCache().Add(&imageConfig); err != nil {
|
||||
return "", fmt.Errorf("error adding image %s to image cache: %s", ic.ImageID, err)
|
||||
}
|
||||
if err = cache.ImageCache().Save(); err != nil {
|
||||
return "", fmt.Errorf("error saving image cache: %s", err)
|
||||
}
|
||||
// if repo:tag is specified, update image to repo cache, otherwise, this image will be updated to repo cache while it's tagged
|
||||
if ic.Reference != nil {
|
||||
imagec.UpdateRepoCache(ic)
|
||||
}
|
||||
|
||||
ic.Storename = layer.Image.Store
|
||||
// Write blob to the storage layer
|
||||
if err = ic.WriteImageBlob(layer, progress.DiscardOutput(), true); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
imagec.LayerCache().Commit(layer)
|
||||
|
||||
refName := ""
|
||||
if ic.Reference != nil {
|
||||
refName = ic.Reference.String()
|
||||
}
|
||||
actor := CreateImageEventActorWithAttributes(imageConfig.ImageID, refName, map[string]string{})
|
||||
EventService().Log("commit", eventtypes.ImageEventType, actor)
|
||||
return imageConfig.ImageID, nil
|
||||
}
|
||||
|
||||
func getImagec(config *backend.ContainerCommitConfig) (*imagec.ImageC, error) {
|
||||
var imageRef reference.Named
|
||||
var err error
|
||||
|
||||
if config.Repo != "" {
|
||||
imageRef, err = reference.WithName(config.Repo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if config.Tag != "" {
|
||||
if imageRef, err = reference.WithTag(imageRef, config.Tag); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
options := imagec.Options{
|
||||
Destination: os.TempDir(),
|
||||
Reference: imageRef,
|
||||
Tag: config.Tag,
|
||||
}
|
||||
portLayerServer := PortLayerServer()
|
||||
|
||||
if portLayerServer != "" {
|
||||
options.Host = portLayerServer
|
||||
}
|
||||
|
||||
ic := imagec.NewImageC(options, streamformatter.NewJSONStreamFormatter())
|
||||
if imageRef != nil {
|
||||
ic.ParseReference()
|
||||
}
|
||||
|
||||
return ic, nil
|
||||
}
|
||||
|
||||
func setLayerConfig(lm *imagec.ImageWithMeta, container *types.ContainerJSON, config *backend.ContainerCommitConfig, newConfig *containertypes.Config) error {
|
||||
defer trace.End(trace.Begin(lm.ID))
|
||||
|
||||
// Host is either the host's UUID (if run on vsphere) or the hostname of
|
||||
// the system (if run standalone)
|
||||
host, err := sys.UUID()
|
||||
if err != nil {
|
||||
return errors.InternalServerError(fmt.Sprintf("Failed to get host name: %s", err))
|
||||
}
|
||||
|
||||
if host != "" {
|
||||
log.Infof("Using UUID (%s) for imagestore name", host)
|
||||
}
|
||||
|
||||
vc := cache.ContainerCache().GetContainer(container.ID)
|
||||
meta := dockerimage.V1Image{
|
||||
ID: lm.ID,
|
||||
Parent: vc.LayerID,
|
||||
Author: config.Author,
|
||||
Comment: config.Comment,
|
||||
Created: time.Now().UTC(),
|
||||
Container: container.ID,
|
||||
ContainerConfig: *container.Config,
|
||||
Architecture: runtime.GOARCH,
|
||||
OS: runtime.GOOS,
|
||||
DockerVersion: version.DockerServerVersion,
|
||||
Config: newConfig,
|
||||
Size: lm.Size,
|
||||
}
|
||||
|
||||
m, err := json.Marshal(meta)
|
||||
if err != nil {
|
||||
return errors.InternalServerError(fmt.Sprintf("Failed to marshal image layer config: %s", err))
|
||||
}
|
||||
// layer metadata
|
||||
lm.Meta = string(m)
|
||||
lm.Image.Parent = vc.LayerID
|
||||
lm.Image.Store = host
|
||||
return nil
|
||||
}
|
||||
|
||||
func downloadDiff(rc io.ReadCloser, containerID string, options imagec.Options) (*imagec.ImageWithMeta, error) {
|
||||
defer trace.End(trace.Begin(containerID))
|
||||
|
||||
// generate random string as layer ID
|
||||
layerID := stringid.GenerateRandomID()
|
||||
|
||||
tmpLayerFileName, diffIDSum, gzSum, err := compressDiffToTmpFile(rc, containerID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Cleanup function for the error case
|
||||
defer func() {
|
||||
if err != nil {
|
||||
os.Remove(tmpLayerFileName)
|
||||
}
|
||||
}()
|
||||
|
||||
blobSum := digest.NewDigestFromBytes(digest.SHA256, gzSum)
|
||||
log.Debugf("container %s blob sum: %s", containerID, blobSum.String())
|
||||
|
||||
layerFile, err := os.Open(string(tmpLayerFileName))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer layerFile.Close()
|
||||
|
||||
decompressed, err := gzip.NewReader(layerFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer decompressed.Close()
|
||||
|
||||
// get a tar reader
|
||||
tr := tar.NewReader(decompressed)
|
||||
|
||||
// iterate through tar headers to get file sizes
|
||||
var size int64
|
||||
for {
|
||||
tarHeader, terr := tr.Next()
|
||||
if terr == io.EOF {
|
||||
break
|
||||
}
|
||||
if terr != nil {
|
||||
err = terr
|
||||
return nil, err
|
||||
}
|
||||
size += tarHeader.Size
|
||||
}
|
||||
|
||||
diffID := digest.NewDigestFromBytes(digest.SHA256, diffIDSum)
|
||||
if size == 0 {
|
||||
diffID = digest.Digest(dockerLayer.DigestSHA256EmptyTar)
|
||||
}
|
||||
log.Debugf("container %s diff id: %s, size: %d", containerID, diffID.String(), size)
|
||||
|
||||
// Ensure the parent directory exists
|
||||
destination := path.Join(imagec.DestinationDirectory(options), layerID)
|
||||
err = os.MkdirAll(destination, 0755) /* #nosec */
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Move(rename) the temporary file to its final destination
|
||||
err = os.Rename(string(tmpLayerFileName), path.Join(destination, layerID+".tar"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// layer metadata
|
||||
lm := &imagec.ImageWithMeta{
|
||||
Image: &models.Image{
|
||||
ID: layerID,
|
||||
},
|
||||
DiffID: diffID.String(),
|
||||
Layer: imagec.FSLayer{
|
||||
BlobSum: blobSum.String(),
|
||||
},
|
||||
Size: size,
|
||||
}
|
||||
return lm, nil
|
||||
}
|
||||
|
||||
// compressDiffToTmpFile will write stream to temp file, and return temp file name and tar file checksum, compressed file checksum
|
||||
func compressDiffToTmpFile(rc io.ReadCloser, containerID string) (string, []byte, []byte, error) {
|
||||
defer trace.End(trace.Begin(containerID))
|
||||
// Create a temporary file and stream the res.Body into it
|
||||
var out *os.File
|
||||
var gzWriter *gzip.Writer
|
||||
var err error
|
||||
|
||||
cleanup := func() {
|
||||
if gzWriter != nil {
|
||||
gzWriter.Close()
|
||||
gzWriter = nil
|
||||
}
|
||||
if out != nil {
|
||||
out.Close()
|
||||
if err != nil {
|
||||
os.Remove(out.Name())
|
||||
}
|
||||
out = nil
|
||||
}
|
||||
}
|
||||
defer cleanup()
|
||||
|
||||
out, err = ioutil.TempFile("", containerID)
|
||||
if err != nil {
|
||||
return "", nil, nil, err
|
||||
}
|
||||
|
||||
// compress tar file using gzip and calculate blobsum and diffID all together using multi writer
|
||||
gzSum := sha256.New()
|
||||
tarSum := sha256.New()
|
||||
compressedMW := io.MultiWriter(out, gzSum)
|
||||
|
||||
gzWriter = gzip.NewWriter(compressedMW)
|
||||
tarMW := io.MultiWriter(gzWriter, tarSum)
|
||||
_, err = io.Copy(tarMW, rc)
|
||||
if err != nil {
|
||||
log.Errorf("failed to stream to file: %s", err)
|
||||
return "", nil, nil, err
|
||||
}
|
||||
|
||||
// close writer before calculate checksum
|
||||
fileName := out.Name()
|
||||
err = gzWriter.Flush()
|
||||
if err != nil {
|
||||
log.Errorf("failed to flush writer: %s", err)
|
||||
}
|
||||
cleanup()
|
||||
// Return the temporary file name and checksum
|
||||
return fileName, tarSum.Sum(nil), gzSum.Sum(nil), nil
|
||||
}
|
||||
|
||||
// ***** Code from Docker v17.03.2-ce PullImage to merge two Configs
|
||||
|
||||
// merge merges two Config, the image container configuration (defaults values),
|
||||
// and the user container configuration, either passed by the API or generated
|
||||
// by the cli.
|
||||
// It will mutate the specified user configuration (userConf) with the image
|
||||
// configuration where the user configuration is incomplete.
|
||||
func merge(userConf, imageConf *containertypes.Config) error {
|
||||
if userConf.User == "" {
|
||||
userConf.User = imageConf.User
|
||||
}
|
||||
if len(userConf.ExposedPorts) == 0 {
|
||||
userConf.ExposedPorts = imageConf.ExposedPorts
|
||||
} else if imageConf.ExposedPorts != nil {
|
||||
for port := range imageConf.ExposedPorts {
|
||||
if _, exists := userConf.ExposedPorts[port]; !exists {
|
||||
userConf.ExposedPorts[port] = struct{}{}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(userConf.Env) == 0 {
|
||||
userConf.Env = imageConf.Env
|
||||
} else {
|
||||
for _, imageEnv := range imageConf.Env {
|
||||
found := false
|
||||
imageEnvKey := strings.Split(imageEnv, "=")[0]
|
||||
for _, userEnv := range userConf.Env {
|
||||
userEnvKey := strings.Split(userEnv, "=")[0]
|
||||
if runtime.GOOS == "windows" {
|
||||
// Case insensitive environment variables on Windows
|
||||
imageEnvKey = strings.ToUpper(imageEnvKey)
|
||||
userEnvKey = strings.ToUpper(userEnvKey)
|
||||
}
|
||||
if imageEnvKey == userEnvKey {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
userConf.Env = append(userConf.Env, imageEnv)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if userConf.Labels == nil {
|
||||
userConf.Labels = map[string]string{}
|
||||
}
|
||||
for l, v := range imageConf.Labels {
|
||||
if _, ok := userConf.Labels[l]; !ok {
|
||||
userConf.Labels[l] = v
|
||||
}
|
||||
}
|
||||
|
||||
if len(userConf.Entrypoint) == 0 {
|
||||
if len(userConf.Cmd) == 0 {
|
||||
userConf.Cmd = imageConf.Cmd
|
||||
userConf.ArgsEscaped = imageConf.ArgsEscaped
|
||||
}
|
||||
|
||||
if userConf.Entrypoint == nil {
|
||||
userConf.Entrypoint = imageConf.Entrypoint
|
||||
}
|
||||
}
|
||||
if imageConf.Healthcheck != nil {
|
||||
if userConf.Healthcheck == nil {
|
||||
userConf.Healthcheck = imageConf.Healthcheck
|
||||
} else {
|
||||
if len(userConf.Healthcheck.Test) == 0 {
|
||||
userConf.Healthcheck.Test = imageConf.Healthcheck.Test
|
||||
}
|
||||
if userConf.Healthcheck.Interval == 0 {
|
||||
userConf.Healthcheck.Interval = imageConf.Healthcheck.Interval
|
||||
}
|
||||
if userConf.Healthcheck.Timeout == 0 {
|
||||
userConf.Healthcheck.Timeout = imageConf.Healthcheck.Timeout
|
||||
}
|
||||
if userConf.Healthcheck.Retries == 0 {
|
||||
userConf.Healthcheck.Retries = imageConf.Healthcheck.Retries
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if userConf.WorkingDir == "" {
|
||||
userConf.WorkingDir = imageConf.WorkingDir
|
||||
}
|
||||
if len(userConf.Volumes) == 0 {
|
||||
userConf.Volumes = imageConf.Volumes
|
||||
} else {
|
||||
for k, v := range imageConf.Volumes {
|
||||
userConf.Volumes[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
if userConf.StopSignal == "" {
|
||||
userConf.StopSignal = imageConf.StopSignal
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// *****
|
||||
143
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/commit_test.go
generated
vendored
143
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/commit_test.go
generated
vendored
@@ -1,143 +0,0 @@
|
||||
// Copyright 2017 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package backends
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"testing"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/docker/docker/api/types/backend"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/vmware/vic/lib/imagec"
|
||||
)
|
||||
|
||||
func getMockReader(t *testing.T) (io.ReadCloser, error) {
|
||||
// Create a buffer to write our archive to.
|
||||
buf := new(bytes.Buffer)
|
||||
|
||||
// Create a new tar archive.
|
||||
tw := tar.NewWriter(buf)
|
||||
|
||||
// Add some files to the archive.
|
||||
var files = []struct {
|
||||
Name, Body string
|
||||
}{
|
||||
{"readme.txt", "This archive contains some text files."},
|
||||
{"gopher.txt", "Gopher names:\nGeorge\nGeoffrey\nGonzo"},
|
||||
{"todo.txt", "Get animal handling license."},
|
||||
}
|
||||
for _, file := range files {
|
||||
hdr := &tar.Header{
|
||||
Name: file.Name,
|
||||
Mode: 0600,
|
||||
Size: int64(len(file.Body)),
|
||||
}
|
||||
if err := tw.WriteHeader(hdr); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := tw.Write([]byte(file.Body)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
// Make sure to check the error on Close.
|
||||
if err := tw.Close(); err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
// Open the tar archive for reading.
|
||||
r := bytes.NewReader(buf.Bytes())
|
||||
return ioutil.NopCloser(r), nil
|
||||
}
|
||||
|
||||
func TestDownload(t *testing.T) {
|
||||
log.SetLevel(log.DebugLevel)
|
||||
|
||||
tests := []struct {
|
||||
repo string
|
||||
tag string
|
||||
}{
|
||||
{repo: "registry-1.docker.io", tag: ""},
|
||||
{repo: "registry-1.docker.io", tag: "mycommit"},
|
||||
{repo: "myrepo.io", tag: ""},
|
||||
{repo: "myrepo.io", tag: "mycommit"},
|
||||
{repo: "", tag: ""},
|
||||
}
|
||||
for _, test := range tests {
|
||||
config := &backend.ContainerCommitConfig{}
|
||||
config.Tag = test.tag
|
||||
config.Repo = test.repo
|
||||
ic, err := getImagec(config)
|
||||
if err != nil {
|
||||
t.Errorf("Failed to get imagec: %s", err)
|
||||
return
|
||||
}
|
||||
rc, err := getMockReader(t)
|
||||
if err != nil {
|
||||
t.Errorf("Failed to get mocked reader: %s", err)
|
||||
}
|
||||
|
||||
layer, err := downloadDiff(rc, "abcd", ic.Options)
|
||||
if err != nil {
|
||||
t.Errorf("Failed to download layer: %s", err)
|
||||
return
|
||||
}
|
||||
t.Logf("layer id: %#v", layer)
|
||||
destDir := path.Join(imagec.DestinationDirectory(ic.Options), layer.ID)
|
||||
destination := path.Join(destDir, layer.ID+".tar")
|
||||
if _, err := os.Stat(destination); err != nil {
|
||||
t.Errorf("diff file %s is not created", destination)
|
||||
}
|
||||
assert.Equal(t, int64(101), layer.Size, "layer size is wrong")
|
||||
|
||||
layerFile, err := os.Open(string(destination))
|
||||
if err != nil {
|
||||
t.Errorf("Layer file %s is not created: %s", destination, err)
|
||||
}
|
||||
defer layerFile.Close()
|
||||
|
||||
decompressed, err := gzip.NewReader(layerFile)
|
||||
if err != nil {
|
||||
t.Errorf("Failed to create gzip reader: %s", err)
|
||||
}
|
||||
defer decompressed.Close()
|
||||
|
||||
// get a tar reader
|
||||
tr := tar.NewReader(decompressed)
|
||||
|
||||
// iterate through tar headers to get file sizes
|
||||
var layerSize int64
|
||||
for {
|
||||
tarHeader, terr := tr.Next()
|
||||
if terr == io.EOF {
|
||||
break
|
||||
}
|
||||
if terr != nil {
|
||||
t.Errorf("Failed to read layer file: %s", terr)
|
||||
}
|
||||
t.Logf("Read file: %s", tarHeader.Name)
|
||||
layerSize += tarHeader.Size
|
||||
}
|
||||
assert.Equal(t, int64(101), layerSize, "tar file size is wrong")
|
||||
os.RemoveAll(destDir)
|
||||
}
|
||||
}
|
||||
1953
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/container.go
generated
vendored
1953
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/container.go
generated
vendored
File diff suppressed because it is too large
Load Diff
839
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/container_test.go
generated
vendored
839
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/container_test.go
generated
vendored
@@ -1,839 +0,0 @@
|
||||
// Copyright 2016-2017 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package backends
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
derr "github.com/docker/docker/api/errors"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/backend"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
dnetwork "github.com/docker/docker/api/types/network"
|
||||
"github.com/docker/docker/reference"
|
||||
"github.com/docker/go-connections/nat"
|
||||
"github.com/go-openapi/runtime"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/vmware/vic/lib/apiservers/engine/backends/cache"
|
||||
viccontainer "github.com/vmware/vic/lib/apiservers/engine/backends/container"
|
||||
"github.com/vmware/vic/lib/apiservers/engine/backends/convert"
|
||||
"github.com/vmware/vic/lib/apiservers/engine/network"
|
||||
"github.com/vmware/vic/lib/apiservers/engine/proxy"
|
||||
plclient "github.com/vmware/vic/lib/apiservers/portlayer/client"
|
||||
plscopes "github.com/vmware/vic/lib/apiservers/portlayer/client/scopes"
|
||||
plmodels "github.com/vmware/vic/lib/apiservers/portlayer/models"
|
||||
"github.com/vmware/vic/lib/config/executor"
|
||||
"github.com/vmware/vic/lib/metadata"
|
||||
)
|
||||
|
||||
//***********
|
||||
// Mock proxy
|
||||
//***********
|
||||
|
||||
type CreateHandleMockData struct {
|
||||
createInputID string
|
||||
retID string
|
||||
retHandle string
|
||||
retErr error
|
||||
createErrSubstr string
|
||||
}
|
||||
|
||||
type AddToScopeMockData struct {
|
||||
createInputID string
|
||||
retHandle string
|
||||
retErr error
|
||||
createErrSubstr string
|
||||
}
|
||||
|
||||
type AddVolumesMockData struct {
|
||||
retHandle string
|
||||
retErr error
|
||||
createErrSubstr string
|
||||
}
|
||||
|
||||
type AddInteractionMockData struct {
|
||||
retHandle string
|
||||
retErr error
|
||||
createErrSubstr string
|
||||
}
|
||||
|
||||
type AddLoggingMockData struct {
|
||||
retHandle string
|
||||
retErr error
|
||||
createErrSubstr string
|
||||
}
|
||||
|
||||
type CommitHandleMockData struct {
|
||||
createInputID string
|
||||
createErrSubstr string
|
||||
|
||||
retErr error
|
||||
}
|
||||
|
||||
type LogMockData struct {
|
||||
continaerID string
|
||||
running bool
|
||||
}
|
||||
|
||||
type MockContainerProxy struct {
|
||||
mockRespIndices []int
|
||||
mockCreateHandleData []CreateHandleMockData
|
||||
mockAddToScopeData []AddToScopeMockData
|
||||
mockAddVolumesData []AddVolumesMockData
|
||||
mockAddInteractionData []AddInteractionMockData
|
||||
mockAddLoggingData []AddLoggingMockData
|
||||
mockCommitData []CommitHandleMockData
|
||||
}
|
||||
|
||||
type MockStorageProxy struct {
|
||||
}
|
||||
|
||||
type MockStreamProxy struct {
|
||||
}
|
||||
|
||||
const (
|
||||
SUCCESS = 0
|
||||
dummyContainerID = "abc123"
|
||||
dummyContainerIDTTY = "tty123"
|
||||
fakeContainerID = ""
|
||||
)
|
||||
|
||||
var randomNames = []string{
|
||||
"hello_world",
|
||||
"hello_world",
|
||||
"goodbye_world",
|
||||
"goodbye_world",
|
||||
"cruel_world",
|
||||
}
|
||||
|
||||
func mockRandomName(retry int) string {
|
||||
return randomNames[retry%len(randomNames)]
|
||||
}
|
||||
|
||||
var dummyContainers = []string{dummyContainerID, dummyContainerIDTTY}
|
||||
|
||||
func NewMockContainerProxy() *MockContainerProxy {
|
||||
return &MockContainerProxy{
|
||||
mockRespIndices: make([]int, 6),
|
||||
mockCreateHandleData: MockCreateHandleData(),
|
||||
mockAddToScopeData: MockAddToScopeData(),
|
||||
mockAddVolumesData: MockAddVolumesData(),
|
||||
mockAddInteractionData: MockAddInteractionData(),
|
||||
mockAddLoggingData: MockAddLoggingData(),
|
||||
mockCommitData: MockCommitData(),
|
||||
}
|
||||
}
|
||||
|
||||
func NewMockStorageProxy() *MockStorageProxy {
|
||||
return &MockStorageProxy{}
|
||||
}
|
||||
|
||||
func NewMockStreamProxy() *MockStreamProxy {
|
||||
return &MockStreamProxy{}
|
||||
}
|
||||
|
||||
func MockCreateHandleData() []CreateHandleMockData {
|
||||
|
||||
createHandleTimeoutErr := runtime.NewAPIError("unknown error", "context deadline exceeded", http.StatusServiceUnavailable)
|
||||
|
||||
mockCreateHandleData := []CreateHandleMockData{
|
||||
{"busybox", "321cba", "handle", nil, ""},
|
||||
{"busybox", "", "", derr.NewRequestNotFoundError(fmt.Errorf("No such image: abc123")), "No such image"},
|
||||
{"busybox", "", "", derr.NewErrorWithStatusCode(createHandleTimeoutErr, http.StatusInternalServerError), "context deadline exceeded"},
|
||||
}
|
||||
|
||||
return mockCreateHandleData
|
||||
}
|
||||
|
||||
func MockAddToScopeData() []AddToScopeMockData {
|
||||
addToScopeNotFound := plscopes.AddContainerNotFound{
|
||||
Payload: &plmodels.Error{
|
||||
Message: "Scope not found",
|
||||
},
|
||||
}
|
||||
|
||||
addToScopeNotFoundErr := fmt.Errorf("ContainerProxy.AddContainerToScope: Scopes error: %s", addToScopeNotFound.Error())
|
||||
|
||||
addToScopeTimeout := plscopes.AddContainerInternalServerError{
|
||||
Payload: &plmodels.Error{
|
||||
Message: "context deadline exceeded",
|
||||
},
|
||||
}
|
||||
|
||||
addToScopeTimeoutErr := fmt.Errorf("ContainerProxy.AddContainerToScope: Scopes error: %s", addToScopeTimeout.Error())
|
||||
|
||||
mockAddToScopeData := []AddToScopeMockData{
|
||||
{"busybox", "handle", nil, ""},
|
||||
{"busybox", "handle", derr.NewErrorWithStatusCode(fmt.Errorf("container.ContainerCreate failed to create a portlayer client"), http.StatusInternalServerError), "failed to create a portlayer"},
|
||||
{"busybox", "handle", derr.NewErrorWithStatusCode(addToScopeNotFoundErr, http.StatusInternalServerError), "Scope not found"},
|
||||
{"busybox", "handle", derr.NewErrorWithStatusCode(addToScopeTimeoutErr, http.StatusInternalServerError), "context deadline exceeded"},
|
||||
}
|
||||
|
||||
return mockAddToScopeData
|
||||
}
|
||||
|
||||
func MockAddVolumesData() []AddVolumesMockData {
|
||||
return nil
|
||||
}
|
||||
|
||||
func MockAddInteractionData() []AddInteractionMockData {
|
||||
return nil
|
||||
}
|
||||
|
||||
func MockAddLoggingData() []AddLoggingMockData {
|
||||
return nil
|
||||
}
|
||||
|
||||
func MockCommitData() []CommitHandleMockData {
|
||||
noSuchImageErr := fmt.Errorf("No such image: busybox")
|
||||
|
||||
mockCommitData := []CommitHandleMockData{
|
||||
{"buxybox", "", nil},
|
||||
{"busybox", "failed to create a portlayer", derr.NewErrorWithStatusCode(fmt.Errorf("container.ContainerCreate failed to create a portlayer client"), http.StatusInternalServerError)},
|
||||
{"busybox", "No such image", derr.NewRequestNotFoundError(noSuchImageErr)},
|
||||
}
|
||||
|
||||
return mockCommitData
|
||||
}
|
||||
|
||||
func (m *MockContainerProxy) GetMockDataCount() (int, int, int, int) {
|
||||
return len(m.mockCreateHandleData), len(m.mockAddToScopeData), len(m.mockAddVolumesData), len(m.mockCommitData)
|
||||
}
|
||||
|
||||
func (m *MockContainerProxy) SetMockDataResponse(createHandleResp int, addToScopeResp int, addVolumeResp int, addInteractionResp int, addLoggingResp int, commitContainerResp int) {
|
||||
m.mockRespIndices[0] = createHandleResp
|
||||
m.mockRespIndices[1] = addToScopeResp
|
||||
m.mockRespIndices[2] = addVolumeResp
|
||||
m.mockRespIndices[3] = addInteractionResp
|
||||
m.mockRespIndices[4] = addLoggingResp
|
||||
m.mockRespIndices[5] = commitContainerResp
|
||||
}
|
||||
|
||||
func (m *MockContainerProxy) Handle(ctx context.Context, id, name string) (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func (m *MockContainerProxy) CreateContainerHandle(ctx context.Context, vc *viccontainer.VicContainer, config types.ContainerCreateConfig) (string, string, error) {
|
||||
respIdx := m.mockRespIndices[0]
|
||||
|
||||
if respIdx >= len(m.mockCreateHandleData) {
|
||||
return "", "", nil
|
||||
}
|
||||
return m.mockCreateHandleData[respIdx].retID, m.mockCreateHandleData[respIdx].retHandle, m.mockCreateHandleData[respIdx].retErr
|
||||
}
|
||||
|
||||
func (m *MockContainerProxy) CreateContainerTask(ctx context.Context, handle string, id string, config types.ContainerCreateConfig) (string, error) {
|
||||
respIdx := m.mockRespIndices[0]
|
||||
|
||||
if respIdx >= len(m.mockCreateHandleData) {
|
||||
return "", nil
|
||||
}
|
||||
return m.mockCreateHandleData[respIdx].retHandle, m.mockCreateHandleData[respIdx].retErr
|
||||
}
|
||||
|
||||
func (m *MockContainerProxy) AddContainerToScope(ctx context.Context, handle string, config types.ContainerCreateConfig) (string, error) {
|
||||
respIdx := m.mockRespIndices[1]
|
||||
|
||||
if respIdx >= len(m.mockAddToScopeData) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
return m.mockAddToScopeData[respIdx].retHandle, m.mockAddToScopeData[respIdx].retErr
|
||||
}
|
||||
|
||||
func (m *MockContainerProxy) AddVolumesToContainer(ctx context.Context, handle string, config types.ContainerCreateConfig) (string, error) {
|
||||
respIdx := m.mockRespIndices[2]
|
||||
|
||||
if respIdx >= len(m.mockAddVolumesData) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
return m.mockAddVolumesData[respIdx].retHandle, m.mockAddVolumesData[respIdx].retErr
|
||||
}
|
||||
|
||||
func (m *MockContainerProxy) AddInteractionToContainer(ctx context.Context, handle string, config types.ContainerCreateConfig) (string, error) {
|
||||
respIdx := m.mockRespIndices[3]
|
||||
|
||||
if respIdx >= len(m.mockAddInteractionData) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
return m.mockAddInteractionData[respIdx].retHandle, m.mockAddInteractionData[respIdx].retErr
|
||||
}
|
||||
|
||||
func (m *MockContainerProxy) AddLoggingToContainer(ctx context.Context, handle string, config types.ContainerCreateConfig) (string, error) {
|
||||
respIdx := m.mockRespIndices[4]
|
||||
|
||||
if respIdx >= len(m.mockAddLoggingData) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
return m.mockAddLoggingData[respIdx].retHandle, m.mockAddLoggingData[respIdx].retErr
|
||||
}
|
||||
|
||||
func (m *MockContainerProxy) BindInteraction(ctx context.Context, handle string, name string, id string) (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func (m *MockContainerProxy) CreateExecTask(ctx context.Context, handle string, config *types.ExecConfig) (string, string, error) {
|
||||
return "", "", nil
|
||||
}
|
||||
|
||||
func (m *MockContainerProxy) UnbindInteraction(ctx context.Context, handle string, name string, id string) (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func (m *MockContainerProxy) CommitContainerHandle(ctx context.Context, handle, containerID string, waitTime int32) error {
|
||||
respIdx := m.mockRespIndices[5]
|
||||
|
||||
if respIdx >= len(m.mockCommitData) {
|
||||
return nil
|
||||
}
|
||||
|
||||
return m.mockCommitData[respIdx].retErr
|
||||
}
|
||||
|
||||
func (m *MockContainerProxy) Client() *plclient.PortLayer {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MockContainerProxy) Stop(ctx context.Context, vc *viccontainer.VicContainer, name string, seconds *int, unbound bool) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MockContainerProxy) State(ctx context.Context, vc *viccontainer.VicContainer) (*types.ContainerState, error) {
|
||||
// Assume container is running if container in cache. If we need other conditions
|
||||
// in the future, we can add it, but for now, just assume running.
|
||||
c := cache.ContainerCache().GetContainer(vc.ContainerID)
|
||||
|
||||
if c == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
state := &types.ContainerState{
|
||||
Running: true,
|
||||
}
|
||||
return state, nil
|
||||
}
|
||||
|
||||
func (m *MockContainerProxy) Wait(ctx context.Context, vc *viccontainer.VicContainer, timeout time.Duration) (*types.ContainerState, error) {
|
||||
dockerState := &types.ContainerState{ExitCode: 0}
|
||||
return dockerState, nil
|
||||
}
|
||||
|
||||
func (m *MockContainerProxy) Signal(ctx context.Context, vc *viccontainer.VicContainer, sig uint64) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MockContainerProxy) Resize(ctx context.Context, id string, height, width int32) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MockContainerProxy) Rename(ctx context.Context, vc *viccontainer.VicContainer, newName string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MockContainerProxy) Remove(ctx context.Context, vc *viccontainer.VicContainer, config *types.ContainerRmConfig) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MockContainerProxy) StreamContainerStats(ctx context.Context, config *convert.ContainerStatsConfig) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MockContainerProxy) UnbindContainerFromNetwork(ctx context.Context, vc *viccontainer.VicContainer, handle string) (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func (m *MockContainerProxy) ExitCode(ctx context.Context, vc *viccontainer.VicContainer) (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func AddMockImageToCache() {
|
||||
mockImage := &metadata.ImageConfig{
|
||||
ImageID: "e732471cb81a564575aad46b9510161c5945deaf18e9be3db344333d72f0b4b2",
|
||||
Name: "busybox",
|
||||
Tags: []string{"latest"},
|
||||
Reference: "busybox:latest",
|
||||
}
|
||||
mockImage.Config = &container.Config{
|
||||
Hostname: "55cd1f8f6e5b",
|
||||
Domainname: "",
|
||||
User: "",
|
||||
AttachStdin: false,
|
||||
AttachStdout: false,
|
||||
AttachStderr: false,
|
||||
Tty: false,
|
||||
OpenStdin: false,
|
||||
StdinOnce: false,
|
||||
Env: []string{"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"},
|
||||
Cmd: []string{"sh"},
|
||||
Image: "sha256:e732471cb81a564575aad46b9510161c5945deaf18e9be3db344333d72f0b4b2",
|
||||
Volumes: nil,
|
||||
WorkingDir: "",
|
||||
Entrypoint: nil,
|
||||
OnBuild: nil,
|
||||
}
|
||||
|
||||
cache.ImageCache().Add(mockImage)
|
||||
|
||||
ref, _ := reference.ParseNamed(mockImage.Reference)
|
||||
cache.RepositoryCache().AddReference(ref, mockImage.ImageID, false, mockImage.ImageID, false)
|
||||
}
|
||||
|
||||
func AddMockContainerToCache() {
|
||||
AddMockImageToCache()
|
||||
|
||||
image, err := cache.ImageCache().Get("e732471cb81a564575aad46b9510161c5945deaf18e9be3db344333d72f0b4b2")
|
||||
if err == nil {
|
||||
vc := viccontainer.NewVicContainer()
|
||||
vc.ImageID = image.ID
|
||||
vc.Config = image.Config //Set defaults. Overrides will get copied below.
|
||||
vc.Config.Tty = false
|
||||
vc.ContainerID = dummyContainerID
|
||||
cache.ContainerCache().AddContainer(vc)
|
||||
|
||||
vc = viccontainer.NewVicContainer()
|
||||
vc.ImageID = image.ID
|
||||
vc.Config = image.Config
|
||||
vc.Config.Tty = true
|
||||
vc.ContainerID = dummyContainerIDTTY
|
||||
cache.ContainerCache().AddContainer(vc)
|
||||
|
||||
vc = viccontainer.NewVicContainer()
|
||||
vc.ImageID = image.ID
|
||||
vc.Config = image.Config
|
||||
vc.Config.Tty = false
|
||||
vc.ContainerID = fakeContainerID
|
||||
cache.ContainerCache().AddContainer(vc)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *MockStorageProxy) Create(ctx context.Context, name, driverName string, volumeData, labels map[string]string) (*types.Volume, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (s *MockStorageProxy) VolumeList(ctx context.Context, filter string) ([]*plmodels.VolumeResponse, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (s *MockStorageProxy) VolumeInfo(ctx context.Context, name string) (*plmodels.VolumeResponse, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (s *MockStorageProxy) Remove(ctx context.Context, name string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *MockStorageProxy) AddVolumesToContainer(ctx context.Context, handle string, config types.ContainerCreateConfig) (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func (sp *MockStreamProxy) AttachStreams(ctx context.Context, ac *proxy.AttachConfig, stdin io.ReadCloser, stdout, stderr io.Writer) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (sp *MockStreamProxy) StreamContainerLogs(_ context.Context, name string, out io.Writer, started chan struct{}, showTimestamps bool, followLogs bool, since int64, tailLines int64) error {
|
||||
if name == "" {
|
||||
return fmt.Errorf("sample error message")
|
||||
}
|
||||
|
||||
var lineCount int64 = 10
|
||||
|
||||
close(started)
|
||||
|
||||
for i := int64(0); i < lineCount; i++ {
|
||||
if !followLogs && i > tailLines {
|
||||
break
|
||||
}
|
||||
if followLogs && i > tailLines {
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
}
|
||||
|
||||
fmt.Fprintf(out, "line %d\n", i)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (sp *MockStreamProxy) StreamContainerStats(ctx context.Context, config *convert.ContainerStatsConfig) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
//***********
|
||||
// Tests
|
||||
//***********
|
||||
|
||||
// TestContainerCreateEmptyImageCache() attempts a ContainerCreate() with an empty image
|
||||
// cache
|
||||
func TestContainerCreateEmptyImageCache(t *testing.T) {
|
||||
mockContainerProxy := NewMockContainerProxy()
|
||||
|
||||
// Create our personality Container backend
|
||||
cb := &ContainerBackend{
|
||||
containerProxy: mockContainerProxy,
|
||||
}
|
||||
|
||||
// mock a container create config
|
||||
var config types.ContainerCreateConfig
|
||||
|
||||
config.HostConfig = &container.HostConfig{}
|
||||
config.Config = &container.Config{}
|
||||
config.NetworkingConfig = &dnetwork.NetworkingConfig{}
|
||||
config.Config.Image = "busybox"
|
||||
|
||||
_, err := cb.ContainerCreate(config)
|
||||
|
||||
assert.Contains(t, err.Error(), "No such image", "Error (%s) should have 'No such image' for an empty image cache", err.Error())
|
||||
}
|
||||
|
||||
// TestCreateHandle() cycles through all possible input/outputs for creating a handle
|
||||
// and calls vicbackends.ContainerCreate(). The idea is that if creating handle fails
|
||||
// then vicbackends.ContainerCreate() should return errors from that.
|
||||
func TestCreateHandle(t *testing.T) {
|
||||
mockContainerProxy := NewMockContainerProxy()
|
||||
|
||||
// Create our personality Container backend
|
||||
cb := &ContainerBackend{
|
||||
containerProxy: mockContainerProxy,
|
||||
}
|
||||
|
||||
AddMockImageToCache()
|
||||
|
||||
// configure mock naming for just this test
|
||||
defer func(fn func(int) string) {
|
||||
randomName = fn
|
||||
}(randomName)
|
||||
randomName = mockRandomName
|
||||
|
||||
// mock a container create config
|
||||
var config types.ContainerCreateConfig
|
||||
|
||||
config.HostConfig = &container.HostConfig{}
|
||||
config.Config = &container.Config{}
|
||||
config.NetworkingConfig = &dnetwork.NetworkingConfig{}
|
||||
|
||||
mockCreateHandleData := MockCreateHandleData()
|
||||
|
||||
// Iterate over create handler responses and see what the composite ContainerCreate()
|
||||
// returns. Since the handle is the first operation, we expect to receive a create handle
|
||||
// error.
|
||||
count, _, _, _ := mockContainerProxy.GetMockDataCount()
|
||||
|
||||
for i := 0; i < count; i++ {
|
||||
if i == SUCCESS { //skip success case
|
||||
continue
|
||||
}
|
||||
|
||||
mockContainerProxy.SetMockDataResponse(i, 0, 0, 0, 0, 0)
|
||||
config.Config.Image = mockCreateHandleData[i].createInputID
|
||||
_, err := cb.ContainerCreate(config)
|
||||
|
||||
assert.Contains(t, err.Error(), mockCreateHandleData[i].createErrSubstr)
|
||||
}
|
||||
}
|
||||
|
||||
// TestContainerAddToScope() assumes container handle create succeeded and cycles through all
|
||||
// possible input/outputs for adding container to scope and calls vicbackends.ContainerCreate()
|
||||
func TestContainerAddToScope(t *testing.T) {
|
||||
mockContainerProxy := NewMockContainerProxy()
|
||||
|
||||
// Create our personality Container backend
|
||||
cb := &ContainerBackend{
|
||||
containerProxy: mockContainerProxy,
|
||||
}
|
||||
|
||||
AddMockImageToCache()
|
||||
|
||||
// mock a container create config
|
||||
var config types.ContainerCreateConfig
|
||||
|
||||
config.HostConfig = &container.HostConfig{}
|
||||
config.Config = &container.Config{}
|
||||
config.NetworkingConfig = &dnetwork.NetworkingConfig{}
|
||||
|
||||
mockAddToScopeData := MockAddToScopeData()
|
||||
|
||||
// Iterate over create handler responses and see what the composite ContainerCreate()
|
||||
// returns. Since the handle is the first operation, we expect to receive a create handle
|
||||
// error.
|
||||
_, count, _, _ := mockContainerProxy.GetMockDataCount()
|
||||
|
||||
for i := 0; i < count; i++ {
|
||||
if i == SUCCESS { //skip success case
|
||||
continue
|
||||
}
|
||||
|
||||
mockContainerProxy.SetMockDataResponse(0, i, 0, 0, 0, 0)
|
||||
config.Config.Image = mockAddToScopeData[i].createInputID
|
||||
_, err := cb.ContainerCreate(config)
|
||||
|
||||
assert.Contains(t, err.Error(), mockAddToScopeData[i].createErrSubstr)
|
||||
}
|
||||
}
|
||||
|
||||
// TestContainerAddVolumes() assumes container handle create succeeded and cycles through all
|
||||
// possible input/outputs for committing the handle and calls vicbackends.ContainerCreate()
|
||||
func TestCommitHandle(t *testing.T) {
|
||||
mockContainerProxy := NewMockContainerProxy()
|
||||
mockStorageProxy := NewMockStorageProxy()
|
||||
|
||||
// Create our personality Container backend
|
||||
cb := &ContainerBackend{
|
||||
containerProxy: mockContainerProxy,
|
||||
storageProxy: mockStorageProxy,
|
||||
}
|
||||
|
||||
AddMockImageToCache()
|
||||
|
||||
// mock a container create config
|
||||
var config types.ContainerCreateConfig
|
||||
|
||||
config.HostConfig = &container.HostConfig{}
|
||||
config.Config = &container.Config{}
|
||||
config.NetworkingConfig = &dnetwork.NetworkingConfig{}
|
||||
|
||||
mockCommitHandleData := MockCommitData()
|
||||
|
||||
// Iterate over create handler responses and see what the composite ContainerCreate()
|
||||
// returns. Since the handle is the first operation, we expect to receive a create handle
|
||||
// error.
|
||||
_, _, _, count := mockContainerProxy.GetMockDataCount()
|
||||
|
||||
for i := 0; i < count; i++ {
|
||||
if i == SUCCESS { //skip success case
|
||||
continue
|
||||
}
|
||||
|
||||
mockContainerProxy.SetMockDataResponse(0, 0, 0, 0, 0, i)
|
||||
config.Config.Image = mockCommitHandleData[i].createInputID
|
||||
_, err := cb.ContainerCreate(config)
|
||||
|
||||
assert.Contains(t, err.Error(), mockCommitHandleData[i].createErrSubstr)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// TestContainerLogs() tests the docker logs api when user asks for entire log
|
||||
func TestContainerLogs(t *testing.T) {
|
||||
// Create our personality Container backend
|
||||
cb := &ContainerBackend{
|
||||
containerProxy: NewMockContainerProxy(),
|
||||
streamProxy: NewMockStreamProxy(),
|
||||
}
|
||||
|
||||
// Prepopulate our image and container cache with dummy data
|
||||
AddMockContainerToCache()
|
||||
|
||||
// Create a buffer io.writer
|
||||
var writer bytes.Buffer
|
||||
|
||||
successDuration := 1 * time.Second
|
||||
|
||||
// Create our mock table
|
||||
mockData := []struct {
|
||||
Config backend.ContainerLogsConfig
|
||||
ExpectedSuccess bool
|
||||
ExpectedFollow bool
|
||||
}{
|
||||
{
|
||||
Config: backend.ContainerLogsConfig{
|
||||
ContainerLogsOptions: types.ContainerLogsOptions{
|
||||
ShowStdout: true,
|
||||
ShowStderr: true,
|
||||
Tail: "all",
|
||||
},
|
||||
OutStream: &writer,
|
||||
},
|
||||
ExpectedSuccess: true,
|
||||
ExpectedFollow: false,
|
||||
},
|
||||
{
|
||||
Config: backend.ContainerLogsConfig{
|
||||
ContainerLogsOptions: types.ContainerLogsOptions{
|
||||
ShowStdout: false,
|
||||
ShowStderr: false,
|
||||
},
|
||||
OutStream: &writer,
|
||||
},
|
||||
ExpectedSuccess: false,
|
||||
ExpectedFollow: false,
|
||||
},
|
||||
{
|
||||
Config: backend.ContainerLogsConfig{
|
||||
ContainerLogsOptions: types.ContainerLogsOptions{
|
||||
ShowStdout: true,
|
||||
ShowStderr: true,
|
||||
Follow: true,
|
||||
},
|
||||
OutStream: &writer,
|
||||
},
|
||||
ExpectedSuccess: true,
|
||||
ExpectedFollow: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, containerID := range dummyContainers {
|
||||
for _, data := range mockData {
|
||||
started := make(chan struct{})
|
||||
|
||||
start := time.Now()
|
||||
err := cb.ContainerLogs(context.TODO(), containerID, &data.Config, started)
|
||||
end := time.Now()
|
||||
|
||||
select {
|
||||
case <-started:
|
||||
default:
|
||||
close(started)
|
||||
}
|
||||
|
||||
if data.ExpectedSuccess {
|
||||
assert.Nil(t, err, "Expected success, but got error, config: %#v", data.Config)
|
||||
} else {
|
||||
assert.NotEqual(t, err, nil, "Expected error but received nil, config: %#v", data.Config)
|
||||
}
|
||||
|
||||
immediate := start.Add(successDuration)
|
||||
|
||||
didFollow := immediate.Before(end) //determines if logs continued to stream
|
||||
|
||||
if data.ExpectedFollow {
|
||||
assert.True(t, didFollow, "Expected logs to follow but didn't (%s, %s), config: %#v", start.String(), end.String(), data.Config)
|
||||
} else {
|
||||
assert.False(t, didFollow, "Expected logs to NOT follow but it did, config: %#v", data.Config)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check that ContainerLogs *does not* return an error if StreamContainerLogs
|
||||
// returns an error. Here, the config is valid and the container is in the
|
||||
// cache, so the only error will come from StreamContainerLogs. Since the
|
||||
// containerID = "", StreamContainerLogs will return an error.
|
||||
started := make(chan struct{})
|
||||
err := cb.ContainerLogs(context.TODO(), fakeContainerID, &mockData[0].Config, started)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestPortInformation(t *testing.T) {
|
||||
mockContainerInfo := &plmodels.ContainerInfo{}
|
||||
mockContainerConfig := &plmodels.ContainerConfig{}
|
||||
containerID := "foo"
|
||||
mockContainerConfig.ContainerID = containerID
|
||||
|
||||
mockHostConfig := &container.HostConfig{}
|
||||
|
||||
portMap := nat.PortMap{}
|
||||
port, _ := nat.NewPort("tcp", "80")
|
||||
portBinding := nat.PortBinding{
|
||||
HostIP: "127.0.0.1",
|
||||
HostPort: "8000",
|
||||
}
|
||||
portBindings := []nat.PortBinding{portBinding}
|
||||
portMap[port] = portBindings
|
||||
mockHostConfig.PortBindings = portMap
|
||||
|
||||
mockContainerInfo.ContainerConfig = mockContainerConfig
|
||||
mockContainerInfo.Endpoints = []*plmodels.EndpointConfig{
|
||||
{
|
||||
Direct: true,
|
||||
Trust: executor.Published.String(),
|
||||
Ports: []string{"8000/tcp"},
|
||||
},
|
||||
}
|
||||
|
||||
ips := []string{"192.168.1.1"}
|
||||
|
||||
co := viccontainer.NewVicContainer()
|
||||
co.HostConfig = mockHostConfig
|
||||
co.NATMap = portMap
|
||||
co.ContainerID = containerID
|
||||
co.Name = "bar"
|
||||
cache.ContainerCache().AddContainer(co)
|
||||
|
||||
// unless there are entries in vicnetwork.ContainerByPort we won't report them as bound
|
||||
ports := network.PortForwardingInformation(co, ips)
|
||||
assert.Empty(t, ports, "There should be no bound IPs at this point for forwarding")
|
||||
|
||||
// the current port binding should show up as a direct port
|
||||
ports = network.DirectPortInformation(mockContainerInfo)
|
||||
assert.NotEmpty(t, ports, "There should be a direct port")
|
||||
|
||||
network.ContainerByPort["8000"] = containerID
|
||||
ports = network.PortForwardingInformation(co, ips)
|
||||
assert.NotEmpty(t, ports, "There should be bound IPs")
|
||||
assert.Equal(t, 1, len(ports), "Expected 1 port binding, found %d", len(ports))
|
||||
// now that this port presents as a forwarded port it should NOT present as a direct port
|
||||
ports = network.DirectPortInformation(mockContainerInfo)
|
||||
assert.Empty(t, ports, "There should not be a direct port")
|
||||
|
||||
port, _ = nat.NewPort("tcp", "80")
|
||||
portBinding = nat.PortBinding{
|
||||
HostIP: "127.0.0.1",
|
||||
HostPort: "00",
|
||||
}
|
||||
portMap[port] = portBindings
|
||||
|
||||
// forwarding of 00 should never happen, but this is allowing us to confirm that
|
||||
// it's kicked out by the function even if present in the map
|
||||
network.ContainerByPort["00"] = containerID
|
||||
ports = network.PortForwardingInformation(co, ips)
|
||||
assert.NotEmpty(t, ports, "There should be 1 bound IP")
|
||||
assert.Equal(t, 1, len(ports), "Expected 1 port binding, found %d", len(ports))
|
||||
|
||||
port, _ = nat.NewPort("tcp", "800")
|
||||
portBinding = nat.PortBinding{
|
||||
HostIP: "127.0.0.1",
|
||||
HostPort: "800",
|
||||
}
|
||||
portMap[port] = portBindings
|
||||
network.ContainerByPort["800"] = containerID
|
||||
ports = network.PortForwardingInformation(co, ips)
|
||||
assert.Equal(t, 2, len(ports), "Expected 2 port binding, found %d", len(ports))
|
||||
}
|
||||
|
||||
// TestCreateConfigNetowrkMode() whether the HostConfig.NetworkMode is set correctly in ValidateCreateConfig()
|
||||
func TestCreateConfigNetworkMode(t *testing.T) {
|
||||
|
||||
// mock a container create config
|
||||
mockConfig := types.ContainerCreateConfig{
|
||||
HostConfig: &container.HostConfig{},
|
||||
Config: &container.Config{
|
||||
Image: "busybox",
|
||||
},
|
||||
NetworkingConfig: &dnetwork.NetworkingConfig{
|
||||
EndpointsConfig: map[string]*dnetwork.EndpointSettings{
|
||||
"net1": {},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
validateCreateConfig(&mockConfig)
|
||||
|
||||
assert.Equal(t, mockConfig.HostConfig.NetworkMode.NetworkName(), "net1", "expected NetworkMode is net1, found %s", mockConfig.HostConfig.NetworkMode)
|
||||
|
||||
// container connects to two vicnetwork endpoints; check for NetworkMode error
|
||||
mockConfig.NetworkingConfig.EndpointsConfig["net2"] = &dnetwork.EndpointSettings{}
|
||||
|
||||
err := validateCreateConfig(&mockConfig)
|
||||
|
||||
assert.Contains(t, err.Error(), "NetworkMode error", "error (%s) should have 'NetworkMode error'", err.Error())
|
||||
}
|
||||
48
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/convert/annotation_test.go
generated
vendored
48
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/convert/annotation_test.go
generated
vendored
@@ -1,48 +0,0 @@
|
||||
// Copyright 2017 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package convert
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/vmware/vic/lib/apiservers/portlayer/models"
|
||||
)
|
||||
|
||||
func TestSetContainerAnnotation(t *testing.T) {
|
||||
|
||||
config := &models.ContainerCreateConfig{}
|
||||
labels := make(map[string]string)
|
||||
labels["environment"] = "dev"
|
||||
|
||||
err := SetContainerAnnotation(nil, AnnotationKeyLabels, labels)
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = SetContainerAnnotation(config, AnnotationKeyLabels, &labels)
|
||||
assert.NoError(t, err)
|
||||
|
||||
var myLabels map[string]string
|
||||
|
||||
err = ContainerAnnotation(myLabels, AnnotationKeyLabels, &myLabels)
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = ContainerAnnotation(config.Annotations, AnnotationKeyLabels, &myLabels)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 1, len(myLabels))
|
||||
|
||||
err = ContainerAnnotation(config.Annotations, AnnotationKeyLabels, myLabels)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
495
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/convert/stats_test.go
generated
vendored
495
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/convert/stats_test.go
generated
vendored
@@ -1,495 +0,0 @@
|
||||
// Copyright 2017 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package convert
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/vmware/vic/pkg/retry"
|
||||
"github.com/vmware/vic/pkg/vsphere/performance"
|
||||
)
|
||||
|
||||
const (
|
||||
vcpuMhz = 3300
|
||||
vcpuCount = 1
|
||||
vchMhzTotal = 3300
|
||||
memConsumed = 1024 * 1024 * 500
|
||||
memProvisioned = 1024 * 1024 * 1024
|
||||
)
|
||||
|
||||
func TestContainerConverter(t *testing.T) {
|
||||
plumb := setup()
|
||||
defer teardown(plumb)
|
||||
|
||||
// grab a config object
|
||||
config := ccConfig(plumb)
|
||||
|
||||
cStats := NewContainerStats(config)
|
||||
assert.NotNil(t, cStats)
|
||||
|
||||
// returned writer is given to PL
|
||||
writer := cStats.Listen()
|
||||
assert.NotNil(t, writer)
|
||||
// second call should result in nil writer as
|
||||
// we are already listening
|
||||
w2 := cStats.Listen()
|
||||
assert.Nil(t, w2)
|
||||
|
||||
// // ensure stop closes reader / writer
|
||||
cStats.Stop()
|
||||
// verify we stopped listening
|
||||
assert.False(t, cStats.IsListening())
|
||||
}
|
||||
|
||||
func TestToContainerStats(t *testing.T) {
|
||||
plumb := setup()
|
||||
defer teardown(plumb)
|
||||
// grab a config object
|
||||
config := ccConfig(plumb)
|
||||
|
||||
cStats := NewContainerStats(config)
|
||||
assert.NotNil(t, cStats)
|
||||
|
||||
initCPU := 1000
|
||||
vmBefore := vmMetrics(vcpuCount, initCPU)
|
||||
vmm := vmMetrics(vcpuCount, initCPU)
|
||||
// ensure we are after the initial metric
|
||||
vmm.SampleTime.Add(time.Second * 1)
|
||||
|
||||
// first metric sent, should return nil
|
||||
js, err := cStats.ToContainerStats(vmm)
|
||||
assert.NoError(t, err)
|
||||
assert.Nil(t, js)
|
||||
|
||||
// send the same stat should return nil
|
||||
js, err = cStats.ToContainerStats(vmm)
|
||||
assert.Nil(t, err)
|
||||
assert.Nil(t, js)
|
||||
|
||||
// send out of order stat
|
||||
js, err = cStats.ToContainerStats(vmBefore)
|
||||
assert.NotNil(t, err)
|
||||
assert.Nil(t, js)
|
||||
|
||||
secondCPU := 250
|
||||
// create a new metric
|
||||
vmmm := vmMetrics(vcpuCount, secondCPU)
|
||||
// sample will be 20 seconds apart..
|
||||
vmmm.SampleTime = vmm.SampleTime.Add(time.Second * 20)
|
||||
|
||||
js, err = cStats.ToContainerStats(vmmm)
|
||||
assert.NoError(t, err)
|
||||
assert.NotZero(t, js.Read, js.PreRead)
|
||||
assert.Equal(t, uint64(vchMhzTotal*2), js.CPUStats.SystemUsage)
|
||||
assert.Equal(t, uint64(secondCPU+initCPU), js.CPUStats.CPUUsage.TotalUsage)
|
||||
assert.Equal(t, uint64(initCPU), js.PreCPUStats.CPUUsage.TotalUsage)
|
||||
assert.Equal(t, uint64(vchMhzTotal), js.PreCPUStats.SystemUsage)
|
||||
|
||||
// this reading should show 250mhz of 3300mhz used -- 7.58%
|
||||
cpuPercent := fmt.Sprintf("%2.2f", calculateCPUPercentUnix(js.PreCPUStats.CPUUsage.TotalUsage, js.PreCPUStats.SystemUsage, js))
|
||||
assert.Equal(t, "7.58", cpuPercent)
|
||||
|
||||
config.Cancel()
|
||||
<-config.Ctx.Done()
|
||||
// verify we stopped listening
|
||||
assert.True(t, success(cStats))
|
||||
}
|
||||
|
||||
func TestContainerStatsListener(t *testing.T) {
|
||||
plumb := setup()
|
||||
defer teardown(plumb)
|
||||
// grab a config object
|
||||
config := ccConfig(plumb)
|
||||
cStats := NewContainerStats(config)
|
||||
assert.NotNil(t, cStats)
|
||||
|
||||
// start the listener
|
||||
writer := cStats.Listen()
|
||||
assert.NotNil(t, writer)
|
||||
|
||||
// create an initial metric
|
||||
initCPU := 1000
|
||||
vm := vmMetrics(vcpuCount, initCPU)
|
||||
err := plumb.mockPLMetrics(vm, writer)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// send second metric
|
||||
vmm := vmMetrics(vcpuCount, initCPU+100)
|
||||
vmm.SampleTime = vm.SampleTime.Add(time.Second * 20)
|
||||
err = plumb.mockPLMetrics(vmm, writer)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// did client receive metric??
|
||||
ds, err := plumb.mockDockerClient()
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, ds)
|
||||
assert.Equal(t, uint64((initCPU*2+100)/vcpuCount), ds.CPUStats.CPUUsage.TotalUsage)
|
||||
|
||||
// docker expects data quicker than vSphere can produce -- sleep for just over 1 sec
|
||||
// and ensure the previous docker stat is returned to client
|
||||
time.Sleep(time.Millisecond * 1100)
|
||||
same, err := plumb.mockDockerClient()
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, same)
|
||||
assert.Equal(t, ds.CPUStats.CPUUsage.TotalUsage, same.CPUStats.CPUUsage.TotalUsage)
|
||||
|
||||
config.Cancel()
|
||||
<-config.Ctx.Done()
|
||||
|
||||
// verify we stopped listening
|
||||
assert.True(t, success(cStats))
|
||||
}
|
||||
|
||||
func TestContainerConvertCtxCancel(t *testing.T) {
|
||||
plumb := setup()
|
||||
defer teardown(plumb)
|
||||
// grab a config object
|
||||
config := ccConfig(plumb)
|
||||
cStats := NewContainerStats(config)
|
||||
assert.NotNil(t, cStats)
|
||||
|
||||
// start the listener
|
||||
writer := cStats.Listen()
|
||||
assert.NotNil(t, writer)
|
||||
|
||||
// cancel the context
|
||||
config.Cancel()
|
||||
<-config.Ctx.Done()
|
||||
// verify we stopped listening
|
||||
assert.True(t, success(cStats))
|
||||
}
|
||||
|
||||
func TestContainerConvertNoStream(t *testing.T) {
|
||||
plumb := setup()
|
||||
defer teardown(plumb)
|
||||
// grab a config object
|
||||
config := ccConfig(plumb)
|
||||
config.Stream = false
|
||||
cStats := NewContainerStats(config)
|
||||
assert.NotNil(t, cStats)
|
||||
|
||||
// start the listener
|
||||
writer := cStats.Listen()
|
||||
assert.NotNil(t, writer)
|
||||
|
||||
// create an initial metric
|
||||
initCPU := 1000
|
||||
vm := vmMetrics(vcpuCount, initCPU)
|
||||
err := plumb.mockPLMetrics(vm, writer)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// send second metric
|
||||
vmm := vmMetrics(vcpuCount, initCPU+100)
|
||||
vmm.SampleTime = vm.SampleTime.Add(time.Second * 20)
|
||||
err = plumb.mockPLMetrics(vmm, writer)
|
||||
assert.NoError(t, err)
|
||||
|
||||
ds, err := plumb.mockDockerClient()
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, ds)
|
||||
|
||||
// converter canceled the context
|
||||
<-config.Ctx.Done()
|
||||
// verify we stopped listening
|
||||
assert.True(t, success(cStats))
|
||||
}
|
||||
|
||||
func TestContainerNotRunningNoStream(t *testing.T) {
|
||||
plumb := setup()
|
||||
defer teardown(plumb)
|
||||
// grab a config object
|
||||
config := ccConfig(plumb)
|
||||
config.Stream = false
|
||||
config.ContainerState.Running = false
|
||||
cStats := NewContainerStats(config)
|
||||
assert.NotNil(t, cStats)
|
||||
|
||||
// start the listener
|
||||
writer := cStats.Listen()
|
||||
assert.NotNil(t, writer)
|
||||
|
||||
ds, err := plumb.mockDockerClient()
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, ds)
|
||||
|
||||
// converter canceled the context
|
||||
<-config.Ctx.Done()
|
||||
// verify we stopped listening
|
||||
assert.True(t, success(cStats))
|
||||
}
|
||||
|
||||
func TestDiskMinor(t *testing.T) {
|
||||
containerID := "12345"
|
||||
for i := 0; i <= 15; i++ {
|
||||
name := fmt.Sprintf("scsi0:%d", i)
|
||||
assert.Equal(t, uint64(i*16), diskMinor(containerID, name))
|
||||
}
|
||||
|
||||
minor := uint64(0)
|
||||
// test with invalid disk names to ensure no panic, etc
|
||||
assert.Equal(t, minor, diskMinor(containerID, "foo:bar:0"))
|
||||
assert.Equal(t, minor, diskMinor(containerID, "foo"))
|
||||
assert.Equal(t, minor, diskMinor(containerID, "foo:"))
|
||||
}
|
||||
|
||||
func TestCreateBlkioStatsEntry(t *testing.T) {
|
||||
minor := uint64(0)
|
||||
val := uint64(12)
|
||||
maj := uint64(8)
|
||||
entry := createBlkioStatsEntry(minor, "Read", val)
|
||||
assert.Equal(t, "Read", entry.Op)
|
||||
assert.Equal(t, val, entry.Value)
|
||||
assert.Equal(t, minor, entry.Minor)
|
||||
assert.Equal(t, maj, entry.Major)
|
||||
}
|
||||
|
||||
func TestDiskStats(t *testing.T) {
|
||||
plumb := setup()
|
||||
defer teardown(plumb)
|
||||
// grab a config object
|
||||
config := ccConfig(plumb)
|
||||
cStats := NewContainerStats(config)
|
||||
assert.NotNil(t, cStats)
|
||||
// create metric
|
||||
initCPU := 1000
|
||||
vm := vmMetrics(vcpuCount, initCPU)
|
||||
cStats.currentMetric = vm
|
||||
|
||||
// update disk
|
||||
cStats.disk()
|
||||
|
||||
assert.Equal(t, 3, len(cStats.curDockerStat.BlkioStats.IoServiceBytesRecursive))
|
||||
assert.Equal(t, 1, len(cStats.diskStats))
|
||||
|
||||
// update again -- this should accumulate the totals
|
||||
cStats.disk()
|
||||
assert.Equal(t, 3, len(cStats.curDockerStat.BlkioStats.IoServiceBytesRecursive))
|
||||
assert.Equal(t, 1, len(cStats.diskStats))
|
||||
|
||||
for _, disk := range cStats.curDockerStat.BlkioStats.IoServiceBytesRecursive {
|
||||
switch disk.Op {
|
||||
case "Write":
|
||||
assert.Equal(t, uint64(vm.Disks[0].Write.Bytes*2), disk.Value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNetworkStats(t *testing.T) {
|
||||
plumb := setup()
|
||||
defer teardown(plumb)
|
||||
// grab a config object
|
||||
config := ccConfig(plumb)
|
||||
cStats := NewContainerStats(config)
|
||||
assert.NotNil(t, cStats)
|
||||
// create metric
|
||||
initCPU := 1000
|
||||
vm := vmMetrics(vcpuCount, initCPU)
|
||||
cStats.currentMetric = vm
|
||||
|
||||
// update network
|
||||
cStats.network()
|
||||
|
||||
assert.Equal(t, 1, len(cStats.curDockerStat.Networks))
|
||||
assert.Equal(t, 1, len(cStats.netStats))
|
||||
|
||||
// update again -- this should accumulate the totals
|
||||
cStats.network()
|
||||
assert.Equal(t, 1, len(cStats.curDockerStat.Networks))
|
||||
assert.Equal(t, 1, len(cStats.netStats))
|
||||
|
||||
for network, usage := range cStats.curDockerStat.Networks {
|
||||
switch network {
|
||||
case "eth0":
|
||||
assert.Equal(t, uint64(200), usage.RxBytes)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Test Helpers
|
||||
|
||||
type plumbing struct {
|
||||
r *io.PipeReader
|
||||
w *io.PipeWriter
|
||||
out io.Writer
|
||||
// mock portlayer
|
||||
mockPL *json.Encoder
|
||||
// mock docker client decoder
|
||||
mockDoc *json.Decoder
|
||||
}
|
||||
|
||||
func setup() *plumbing {
|
||||
r, o := io.Pipe()
|
||||
out := io.Writer(o)
|
||||
|
||||
return &plumbing{
|
||||
r: r,
|
||||
w: o,
|
||||
out: out,
|
||||
mockDoc: json.NewDecoder(r),
|
||||
}
|
||||
}
|
||||
|
||||
// success is a helper to check the listening status of the
|
||||
// converter
|
||||
func success(converter *ContainerStats) bool {
|
||||
op := func() error {
|
||||
listen := converter.IsListening()
|
||||
if listen {
|
||||
return fmt.Errorf("still listening: %t", listen)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
wait := func(err error) bool {
|
||||
if err != nil {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
// use the retry package and keep retrying until we've hit the limit
|
||||
if err := retry.Do(op, wait); err != nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func teardown(p *plumbing) {
|
||||
// close the reader / writer
|
||||
p.r.Close()
|
||||
p.w.Close()
|
||||
}
|
||||
|
||||
func (p *plumbing) mockPLMetrics(metric *performance.VMMetrics, writer io.Writer) error {
|
||||
if p.mockPL == nil {
|
||||
p.mockPL = json.NewEncoder(writer)
|
||||
}
|
||||
return p.mockPL.Encode(metric)
|
||||
}
|
||||
|
||||
func (p *plumbing) mockDockerClient() (*types.StatsJSON, error) {
|
||||
docStats := &types.StatsJSON{}
|
||||
|
||||
err := p.mockDoc.Decode(docStats)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return docStats, nil
|
||||
}
|
||||
|
||||
func ccConfig(p *plumbing) *ContainerStatsConfig {
|
||||
// test config
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
config := &ContainerStatsConfig{
|
||||
VchMhz: int64(vchMhzTotal),
|
||||
Ctx: ctx,
|
||||
Cancel: cancel,
|
||||
ContainerID: "1234",
|
||||
Out: p.out,
|
||||
Stream: true,
|
||||
Memory: 2048,
|
||||
ContainerState: &types.ContainerState{
|
||||
Running: true,
|
||||
},
|
||||
}
|
||||
return config
|
||||
}
|
||||
|
||||
func vmMetrics(count int, vcpuMhz int) *performance.VMMetrics {
|
||||
vmm := &performance.VMMetrics{}
|
||||
vmm.SampleTime = time.Now()
|
||||
vmm.CPU = cpuUsageMetrics(count, vcpuMhz)
|
||||
vmm.Memory = performance.MemoryMetrics{
|
||||
Consumed: int64(memConsumed),
|
||||
Provisioned: int64(memProvisioned),
|
||||
}
|
||||
disk := performance.VirtualDisk{
|
||||
Name: "scsi0:0",
|
||||
Write: performance.DiskUsage{
|
||||
Bytes: uint64(100),
|
||||
Kbps: 5,
|
||||
Op: uint64(5),
|
||||
Ops: 5,
|
||||
},
|
||||
Read: performance.DiskUsage{
|
||||
Bytes: uint64(10),
|
||||
Kbps: 5,
|
||||
Op: uint64(5),
|
||||
Ops: 5,
|
||||
},
|
||||
}
|
||||
vmm.Disks = append(vmm.Disks, disk)
|
||||
network := performance.Network{
|
||||
Name: "eth0",
|
||||
Rx: performance.NetworkUsage{
|
||||
Bytes: uint64(100),
|
||||
Kbps: 5,
|
||||
Packets: 1,
|
||||
},
|
||||
Tx: performance.NetworkUsage{
|
||||
Bytes: uint64(10),
|
||||
Packets: 1,
|
||||
},
|
||||
}
|
||||
vmm.Networks = append(vmm.Networks, network)
|
||||
return vmm
|
||||
}
|
||||
|
||||
// cpuUsageMetrics will return a populated CPUMetrics struct
|
||||
func cpuUsageMetrics(count int, cpuMhz int) performance.CPUMetrics {
|
||||
vmCPUs := make([]performance.CPUUsage, count, count)
|
||||
total := count * cpuMhz
|
||||
for i := range vmCPUs {
|
||||
vmCPUs[i] = performance.CPUUsage{
|
||||
ID: i,
|
||||
MhzUsage: int64(cpuMhz),
|
||||
}
|
||||
}
|
||||
|
||||
return performance.CPUMetrics{
|
||||
CPUs: vmCPUs,
|
||||
Usage: calcVCPUUsage(total),
|
||||
}
|
||||
}
|
||||
|
||||
// calcUsage is a helper function that will take the total provdied usage
|
||||
// and convert to percentage of total vCPU usage
|
||||
func calcVCPUUsage(total int) float32 {
|
||||
return float32(total) / (vcpuMhz * vcpuCount)
|
||||
}
|
||||
|
||||
// calculateCPUPercentUnix is a copy from docker to test the percentage calculations
|
||||
func calculateCPUPercentUnix(previousCPU, previousSystem uint64, v *types.StatsJSON) float64 {
|
||||
var (
|
||||
cpuPercent = 0.0
|
||||
// calculate the change for the cpu usage of the container in between readings
|
||||
cpuDelta = float64(v.CPUStats.CPUUsage.TotalUsage) - float64(previousCPU)
|
||||
// calculate the change for the entire system between readings
|
||||
systemDelta = float64(v.CPUStats.SystemUsage) - float64(previousSystem)
|
||||
)
|
||||
|
||||
if systemDelta > 0.0 && cpuDelta > 0.0 {
|
||||
cpuPercent = (cpuDelta / systemDelta) * float64(len(v.CPUStats.CPUUsage.PercpuUsage)) * 100.0
|
||||
}
|
||||
return cpuPercent
|
||||
}
|
||||
135
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/endpoint.go
generated
vendored
135
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/endpoint.go
generated
vendored
@@ -1,135 +0,0 @@
|
||||
// Copyright 2016 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package backends
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
|
||||
derr "github.com/docker/docker/api/errors"
|
||||
"github.com/docker/libnetwork"
|
||||
"github.com/docker/libnetwork/types"
|
||||
|
||||
"github.com/vmware/vic/lib/apiservers/portlayer/models"
|
||||
)
|
||||
|
||||
var notImplementedError = derr.NewErrorWithStatusCode(fmt.Errorf("not implemented"), http.StatusInternalServerError)
|
||||
|
||||
type endpoint struct {
|
||||
ep *models.EndpointConfig
|
||||
sc *models.ScopeConfig
|
||||
}
|
||||
|
||||
// A system generated id for this endpoint.
|
||||
func (e *endpoint) ID() string {
|
||||
return e.ep.ID
|
||||
}
|
||||
|
||||
// Name returns the name of this endpoint.
|
||||
func (e *endpoint) Name() string {
|
||||
return e.ep.Name
|
||||
}
|
||||
|
||||
// Network returns the name of the vicnetwork to which this endpoint is attached.
|
||||
func (e *endpoint) Network() string {
|
||||
return e.ep.Scope
|
||||
}
|
||||
|
||||
// Join joins the sandbox to the endpoint and populates into the sandbox
|
||||
// the vicnetwork resources allocated for the endpoint.
|
||||
func (e *endpoint) Join(sandbox libnetwork.Sandbox, options ...libnetwork.EndpointOption) error {
|
||||
return notImplementedError
|
||||
}
|
||||
|
||||
// Leave detaches the vicnetwork resources populated in the sandbox.
|
||||
func (e *endpoint) Leave(sandbox libnetwork.Sandbox, options ...libnetwork.EndpointOption) error {
|
||||
return notImplementedError
|
||||
}
|
||||
|
||||
// Return certain operational data belonging to this endpoint
|
||||
func (e *endpoint) Info() libnetwork.EndpointInfo {
|
||||
return e
|
||||
}
|
||||
|
||||
// DriverInfo returns a collection of driver operational data related to this endpoint retrieved from the driver
|
||||
func (e *endpoint) DriverInfo() (map[string]interface{}, error) {
|
||||
return nil, notImplementedError
|
||||
}
|
||||
|
||||
// Delete and detaches this endpoint from the vicnetwork.
|
||||
func (e *endpoint) Delete(force bool) error {
|
||||
return notImplementedError
|
||||
}
|
||||
|
||||
// Iface returns InterfaceInfo, go interface that can be used
|
||||
// to get more information on the interface which was assigned to
|
||||
// the endpoint by the driver. This can be used after the
|
||||
// endpoint has been created.
|
||||
func (e *endpoint) Iface() libnetwork.InterfaceInfo {
|
||||
return e
|
||||
}
|
||||
|
||||
// Gateway returns the IPv4 gateway assigned by the driver.
|
||||
// This will only return a valid value if a container has joined the endpoint.
|
||||
func (e *endpoint) Gateway() net.IP {
|
||||
return net.ParseIP(e.sc.Gateway)
|
||||
}
|
||||
|
||||
// GatewayIPv6 returns the IPv6 gateway assigned by the driver.
|
||||
// This will only return a valid value if a container has joined the endpoint.
|
||||
func (e *endpoint) GatewayIPv6() net.IP {
|
||||
return nil
|
||||
}
|
||||
|
||||
// StaticRoutes returns the list of static routes configured by the vicnetwork
|
||||
// driver when the container joins a vicnetwork
|
||||
func (e *endpoint) StaticRoutes() []*types.StaticRoute {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Sandbox returns the attached sandbox if there, nil otherwise.
|
||||
func (e *endpoint) Sandbox() libnetwork.Sandbox {
|
||||
return newSandbox(e.ep.Container)
|
||||
}
|
||||
|
||||
// MacAddress returns the MAC address assigned to the endpoint.
|
||||
func (e *endpoint) MacAddress() net.HardwareAddr {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Address returns the IPv4 address assigned to the endpoint.
|
||||
func (e *endpoint) Address() *net.IPNet {
|
||||
ip := net.ParseIP(e.ep.Address)
|
||||
if ip == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
_, snet, err := net.ParseCIDR(e.sc.Subnet)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &net.IPNet{IP: ip, Mask: snet.Mask}
|
||||
}
|
||||
|
||||
// AddressIPv6 returns the IPv6 address assigned to the endpoint.
|
||||
func (e *endpoint) AddressIPv6() *net.IPNet {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *endpoint) LinkLocalAddresses() []*net.IPNet {
|
||||
return nil
|
||||
}
|
||||
291
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/eventmonitor.go
generated
vendored
291
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/eventmonitor.go
generated
vendored
@@ -1,291 +0,0 @@
|
||||
// Copyright 2017-2018 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package backends
|
||||
|
||||
//**** eventmonitor.go
|
||||
//
|
||||
// Handles monitoring of events from the portlayer. Events that are applicable to
|
||||
// Docker events are then translated and published to the Docker event subscribers.
|
||||
// NOTE: This does not handle all Docker events. In fact, most docker events are
|
||||
// passively handled by API calls in the backend routers, with no feedback from
|
||||
// the portlayer.
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"golang.org/x/net/context"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
eventtypes "github.com/docker/docker/api/types/events"
|
||||
|
||||
"github.com/vmware/vic/lib/apiservers/engine/backends/cache"
|
||||
"github.com/vmware/vic/lib/apiservers/engine/errors"
|
||||
"github.com/vmware/vic/lib/apiservers/engine/network"
|
||||
"github.com/vmware/vic/lib/apiservers/engine/proxy"
|
||||
"github.com/vmware/vic/lib/apiservers/portlayer/client/events"
|
||||
plevents "github.com/vmware/vic/lib/portlayer/event/events"
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
"github.com/vmware/vic/pkg/uid"
|
||||
)
|
||||
|
||||
const (
|
||||
containerDieEvent = "die"
|
||||
containerDestroyEvent = "destroy"
|
||||
containerStopEvent = "stop"
|
||||
containerStartEvent = "start"
|
||||
containerCreateEvent = "create"
|
||||
containerRestartEvent = "restart"
|
||||
containerAttachEvent = "attach"
|
||||
containerDetachEvent = "detach"
|
||||
containerKillEvent = "kill"
|
||||
containerResizeEvent = "resize"
|
||||
)
|
||||
|
||||
// for unit testing purposes
|
||||
type eventproxy interface {
|
||||
StreamEvents(ctx context.Context, out io.Writer) error
|
||||
}
|
||||
|
||||
type eventpublisher interface {
|
||||
PublishEvent(event plevents.BaseEvent)
|
||||
}
|
||||
|
||||
type PlEventProxy struct {
|
||||
}
|
||||
|
||||
type DockerEventPublisher struct {
|
||||
}
|
||||
|
||||
type PortlayerEventMonitor struct {
|
||||
stop chan struct{}
|
||||
proxy eventproxy
|
||||
publisher eventpublisher
|
||||
}
|
||||
|
||||
// StreamEvents() handles all swagger interaction to the Portlayer's event manager
|
||||
//
|
||||
// Input:
|
||||
// context and a io.Writer
|
||||
func (ep PlEventProxy) StreamEvents(ctx context.Context, out io.Writer) error {
|
||||
defer trace.End(trace.Begin(""))
|
||||
|
||||
plClient := PortLayerClient()
|
||||
if plClient == nil {
|
||||
return errors.InternalServerError("eventproxy.StreamEvents failed to get a portlayer client")
|
||||
}
|
||||
|
||||
params := events.NewGetEventsParamsWithContext(ctx)
|
||||
if _, err := plClient.Events.GetEvents(params, out); err != nil {
|
||||
switch err := err.(type) {
|
||||
case *events.GetEventsInternalServerError:
|
||||
return errors.InternalServerError("Server error from the events port layer")
|
||||
default:
|
||||
//Check for EOF. Since the connection, transport, and data handling are
|
||||
//encapsulated inside of Swagger, we can only detect EOF by checking the
|
||||
//error string
|
||||
if strings.Contains(err.Error(), proxy.SwaggerSubstringEOF) {
|
||||
return nil
|
||||
}
|
||||
return errors.InternalServerError(fmt.Sprintf("Unknown error from the interaction port layer: %s", err))
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewPortlayerEventMonitor(proxy eventproxy, publisher eventpublisher) *PortlayerEventMonitor {
|
||||
return &PortlayerEventMonitor{proxy: proxy, publisher: publisher}
|
||||
}
|
||||
|
||||
// Start() starts the portlayer event monitoring
|
||||
func (m *PortlayerEventMonitor) Start() error {
|
||||
defer trace.End(trace.Begin(""))
|
||||
|
||||
if m.stop != nil {
|
||||
return fmt.Errorf("Portlayer event monitor: Already started")
|
||||
}
|
||||
|
||||
m.stop = make(chan struct{})
|
||||
go m.monitor()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stop() stops the portlayer event monitoring
|
||||
func (m *PortlayerEventMonitor) Stop() {
|
||||
defer trace.End(trace.Begin(""))
|
||||
|
||||
if m.stop != nil {
|
||||
close(m.stop)
|
||||
}
|
||||
}
|
||||
|
||||
// monitor() establishes a streaming connection to the portlayer's event
|
||||
// endpoint, decodes the results, translate it to Docker events if needed,
|
||||
// and publishes the event to Docker event subscribers.
|
||||
func (m *PortlayerEventMonitor) monitor() error {
|
||||
defer trace.End(trace.Begin(""))
|
||||
|
||||
var wg sync.WaitGroup
|
||||
errors := make(chan error, 2)
|
||||
|
||||
reader, writer := io.Pipe()
|
||||
ctx, cancel := context.WithCancel(context.TODO())
|
||||
// Start streaming events
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
var err error
|
||||
|
||||
defer wg.Done()
|
||||
|
||||
if err = m.proxy.StreamEvents(ctx, writer); err != nil {
|
||||
if ctx.Err() != context.Canceled {
|
||||
log.Warnf("Event streaming from portlayer returned: %#v", err)
|
||||
}
|
||||
}
|
||||
if ctx.Err() == context.Canceled {
|
||||
log.Infof("Event streaming from portlayer was cancelled")
|
||||
return
|
||||
}
|
||||
errors <- err
|
||||
|
||||
writer.Close()
|
||||
reader.Close()
|
||||
}()
|
||||
|
||||
// Start decoding event stream json
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
var err error
|
||||
var event plevents.BaseEvent
|
||||
|
||||
defer wg.Done()
|
||||
|
||||
decoder := json.NewDecoder(reader)
|
||||
for decoder.More() {
|
||||
if err = decoder.Decode(&event); err == nil {
|
||||
m.publisher.PublishEvent(event)
|
||||
}
|
||||
}
|
||||
errors <- err
|
||||
|
||||
reader.Close()
|
||||
writer.Close()
|
||||
}()
|
||||
|
||||
// Create a channel signaling when the waitgroup finishes
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
wg.Wait()
|
||||
close(errors)
|
||||
close(done)
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-done:
|
||||
for err := range errors {
|
||||
if err != nil {
|
||||
log.Warnf("Exiting Events Monitor: %#v", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
case <-m.stop:
|
||||
cancel()
|
||||
writer.Close()
|
||||
reader.Close()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// PublishEvent translates select portlayer container events into Docker events
|
||||
// and publishes to subscribers
|
||||
func (p DockerEventPublisher) PublishEvent(event plevents.BaseEvent) {
|
||||
// create a shortID for the container for logging purposes
|
||||
containerShortID := uid.Parse(event.Ref).Truncate()
|
||||
defer trace.End(trace.Begin(fmt.Sprintf("Event Monitor received eventID(%s) for container(%s) - %s", event.ID, containerShortID, event.Event)))
|
||||
|
||||
vc := cache.ContainerCache().GetContainer(event.Ref)
|
||||
if vc == nil && event.Event != plevents.ContainerCreated {
|
||||
log.Warnf("Event Monitor received eventID(%s) but container(%s) not in cache", event.ID, containerShortID)
|
||||
return
|
||||
}
|
||||
|
||||
// docker event attributes
|
||||
var attrs map[string]string
|
||||
|
||||
switch event.Event {
|
||||
case plevents.ContainerCreated:
|
||||
syncContainerCache()
|
||||
case plevents.ContainerStarted:
|
||||
attrs = make(map[string]string)
|
||||
|
||||
actor := CreateContainerEventActorWithAttributes(vc, attrs)
|
||||
EventService().Log(containerStartEvent, eventtypes.ContainerEventType, actor)
|
||||
|
||||
case plevents.ContainerStopped,
|
||||
plevents.ContainerPoweredOff,
|
||||
plevents.ContainerFailed:
|
||||
// since we are going to make a call to the portLayer lets execute this in a go routine
|
||||
go func() {
|
||||
attrs = make(map[string]string)
|
||||
// get the containerEngine
|
||||
code, _ := NewContainerBackend().containerProxy.ExitCode(context.Background(), vc)
|
||||
|
||||
log.Infof("Sending die event for container(%s) with exitCode[%s] - eventID(%s)", containerShortID, code, event.ID)
|
||||
// if the docker client is unable to convert the code to an int the client will return 125
|
||||
attrs["exitCode"] = code
|
||||
actor := CreateContainerEventActorWithAttributes(vc, attrs)
|
||||
EventService().Log(containerDieEvent, eventtypes.ContainerEventType, actor)
|
||||
// TODO: this really, really shouldn't be in the event publishing code - it's fine to have multiple consumers of events
|
||||
// and this should be registered as a callback by the logic responsible for the MapPorts portion.
|
||||
if err := network.UnmapPorts(vc.ContainerID, vc); err != nil {
|
||||
log.Errorf("Event Monitor failed to unmap ports for container(%s): %s - eventID(%s)", containerShortID, err, event.ID)
|
||||
}
|
||||
|
||||
// auto-remove if required
|
||||
// TODO: this should be a separate event hook registered by logic outside of the publish events loop.
|
||||
if vc.HostConfig.AutoRemove {
|
||||
config := &types.ContainerRmConfig{
|
||||
ForceRemove: true,
|
||||
RemoveVolume: true,
|
||||
}
|
||||
|
||||
err := NewContainerBackend().ContainerRm(vc.Name, config)
|
||||
if err != nil {
|
||||
log.Errorf("Event Monitor failed to remove container(%s) - eventID(%s): %s", containerShortID, event.ID, err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
case plevents.ContainerRemoved:
|
||||
attrs = make(map[string]string)
|
||||
// pop the destroy event...
|
||||
actor := CreateContainerEventActorWithAttributes(vc, attrs)
|
||||
EventService().Log(containerDestroyEvent, eventtypes.ContainerEventType, actor)
|
||||
if err := network.UnmapPorts(vc.ContainerID, vc); err != nil {
|
||||
log.Errorf("Event Monitor failed to unmap ports for container(%s): %s - eventID(%s)", containerShortID, err, event.ID)
|
||||
}
|
||||
// remove from the container cache...
|
||||
cache.ContainerCache().DeleteContainer(vc.ContainerID)
|
||||
default:
|
||||
// let everything else slide on by...
|
||||
}
|
||||
|
||||
}
|
||||
137
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/eventmonitor_test.go
generated
vendored
137
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/eventmonitor_test.go
generated
vendored
@@ -1,137 +0,0 @@
|
||||
// Copyright 2017 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package backends
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
plevents "github.com/vmware/vic/lib/portlayer/event/events"
|
||||
)
|
||||
|
||||
type MockEventProxy struct {
|
||||
MockEvents []plevents.BaseEvent
|
||||
Delay time.Duration
|
||||
}
|
||||
|
||||
type MockEventPublisher struct {
|
||||
MockEventChan chan plevents.BaseEvent
|
||||
}
|
||||
|
||||
func (ep *MockEventProxy) StreamEvents(ctx context.Context, out io.Writer) error {
|
||||
encoder := json.NewEncoder(out)
|
||||
if encoder == nil {
|
||||
return fmt.Errorf("Failed to create a json encoder")
|
||||
}
|
||||
|
||||
for _, event := range ep.MockEvents {
|
||||
if err := encoder.Encode(event); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
time.Sleep(ep.Delay)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *MockEventPublisher) PublishEvent(event plevents.BaseEvent) {
|
||||
if p.MockEventChan != nil {
|
||||
p.MockEventChan <- event
|
||||
}
|
||||
}
|
||||
|
||||
func TestStartStopMonitor(t *testing.T) {
|
||||
proxy := MockEventProxy{
|
||||
MockEvents: []plevents.BaseEvent{
|
||||
{
|
||||
Event: plevents.ContainerCreated,
|
||||
Ref: "abc",
|
||||
},
|
||||
{
|
||||
Event: plevents.ContainerStarted,
|
||||
Ref: "abc",
|
||||
},
|
||||
{
|
||||
Event: plevents.ContainerStopped,
|
||||
Ref: "abc",
|
||||
},
|
||||
},
|
||||
Delay: 1 * time.Second,
|
||||
}
|
||||
publisher := MockEventPublisher{
|
||||
MockEventChan: make(chan plevents.BaseEvent, 3),
|
||||
}
|
||||
monitor := NewPortlayerEventMonitor(&proxy, &publisher)
|
||||
|
||||
var err error
|
||||
|
||||
// The actual tests
|
||||
err = monitor.Start()
|
||||
assert.Nil(t, err, "Expected monitor start to succeed, but received: %#v", err)
|
||||
|
||||
err = monitor.Start()
|
||||
assert.NotEqual(t, err, nil, "Expected error but received nil on double start")
|
||||
if err != nil {
|
||||
assert.Contains(t, err.Error(), "Already started", "Expected already started error but received: %s", err)
|
||||
}
|
||||
|
||||
monitor.Stop()
|
||||
}
|
||||
|
||||
func TestEventMonitor(t *testing.T) {
|
||||
proxy := MockEventProxy{
|
||||
MockEvents: []plevents.BaseEvent{
|
||||
{
|
||||
Event: plevents.ContainerCreated,
|
||||
Ref: "abc",
|
||||
},
|
||||
{
|
||||
Event: plevents.ContainerStarted,
|
||||
Ref: "abc",
|
||||
},
|
||||
{
|
||||
Event: plevents.ContainerStopped,
|
||||
Ref: "abc",
|
||||
},
|
||||
},
|
||||
Delay: 0,
|
||||
}
|
||||
publisher := MockEventPublisher{
|
||||
MockEventChan: make(chan plevents.BaseEvent, 3),
|
||||
}
|
||||
monitor := NewPortlayerEventMonitor(&proxy, &publisher)
|
||||
|
||||
var err error
|
||||
|
||||
// The actual tests
|
||||
err = monitor.Start()
|
||||
assert.Nil(t, err, "Expected monitor start to succeed, but received: %#v", err)
|
||||
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
count := len(publisher.MockEventChan)
|
||||
for i := 0; i < count; i++ {
|
||||
event := <-publisher.MockEventChan
|
||||
assert.Equal(t, event.Event, proxy.MockEvents[i].Event, "Expected to find event %s but found %s", proxy.MockEvents[i].Event, event.Event)
|
||||
}
|
||||
}
|
||||
171
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/executor/SwarmBackend.go
generated
vendored
171
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/executor/SwarmBackend.go
generated
vendored
@@ -1,171 +0,0 @@
|
||||
// Copyright 2017 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package executor
|
||||
|
||||
import (
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
|
||||
"github.com/docker/distribution"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/backend"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/api/types/events"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"github.com/docker/docker/api/types/network"
|
||||
swarmtypes "github.com/docker/docker/api/types/swarm"
|
||||
clustertypes "github.com/docker/docker/daemon/cluster/provider"
|
||||
"github.com/docker/docker/plugin"
|
||||
"github.com/docker/docker/reference"
|
||||
"github.com/docker/libnetwork"
|
||||
"github.com/docker/libnetwork/cluster"
|
||||
networktypes "github.com/docker/libnetwork/types"
|
||||
"github.com/docker/swarmkit/agent/exec"
|
||||
|
||||
"github.com/vmware/vic/lib/apiservers/engine/errors"
|
||||
)
|
||||
|
||||
type SwarmBackend struct {
|
||||
}
|
||||
|
||||
func (b SwarmBackend) CreateManagedNetwork(clustertypes.NetworkCreateRequest) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b SwarmBackend) DeleteManagedNetwork(name string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b SwarmBackend) FindNetwork(idName string) (libnetwork.Network, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (b SwarmBackend) SetupIngress(req clustertypes.NetworkCreateRequest, nodeIP string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b SwarmBackend) PullImage(ctx context.Context, image, tag string, metaHeaders map[string][]string, authConfig *types.AuthConfig, outStream io.Writer) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b SwarmBackend) CreateManagedContainer(config types.ContainerCreateConfig) (container.ContainerCreateCreatedBody, error) {
|
||||
return container.ContainerCreateCreatedBody{}, nil
|
||||
}
|
||||
|
||||
func (b SwarmBackend) ContainerStart(name string, hostConfig *container.HostConfig, checkpoint string, checkpointDir string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b SwarmBackend) ContainerStop(name string, seconds *int) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// ContainerLogs hooks up a container's stdout and stderr streams
|
||||
// configured with the given struct.
|
||||
func (b SwarmBackend) ContainerLogs(ctx context.Context, containerName string, config *backend.ContainerLogsConfig, started chan struct{}) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b SwarmBackend) ConnectContainerToNetwork(containerName, networkName string, endpointConfig *network.EndpointSettings) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b SwarmBackend) ActivateContainerServiceBinding(containerName string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b SwarmBackend) DeactivateContainerServiceBinding(containerName string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b SwarmBackend) UpdateContainerServiceConfig(containerName string, serviceConfig *clustertypes.ServiceConfig) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b SwarmBackend) ContainerInspectCurrent(name string, size bool) (*types.ContainerJSON, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (b SwarmBackend) ContainerWaitWithContext(ctx context.Context, name string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b SwarmBackend) ContainerRm(name string, config *types.ContainerRmConfig) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b SwarmBackend) ContainerKill(name string, sig uint64) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b SwarmBackend) SetContainerSecretStore(name string, store exec.SecretGetter) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b SwarmBackend) SetContainerSecretReferences(name string, refs []*swarmtypes.SecretReference) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b SwarmBackend) SystemInfo() (*types.Info, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (b SwarmBackend) VolumeCreate(name, driverName string, opts, labels map[string]string) (*types.Volume, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (b SwarmBackend) Containers(config *types.ContainerListOptions) ([]*types.Container, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (b SwarmBackend) SetNetworkBootstrapKeys([]*networktypes.EncryptionKey) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b SwarmBackend) SetClusterProvider(provider cluster.Provider) {
|
||||
}
|
||||
|
||||
func (b SwarmBackend) IsSwarmCompatible() error {
|
||||
return errors.SwarmNotSupportedError()
|
||||
}
|
||||
|
||||
func (b SwarmBackend) SubscribeToEvents(since, until time.Time, filter filters.Args) ([]events.Message, chan interface{}) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (b SwarmBackend) UnsubscribeFromEvents(listener chan interface{}) {
|
||||
}
|
||||
|
||||
func (b SwarmBackend) UpdateAttachment(string, string, string, *network.NetworkingConfig) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b SwarmBackend) WaitForDetachment(context.Context, string, string, string, string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b SwarmBackend) GetRepository(context.Context, reference.NamedTagged, *types.AuthConfig) (distribution.Repository, bool, error) {
|
||||
return nil, false, nil
|
||||
}
|
||||
|
||||
func (b SwarmBackend) LookupImage(name string) (*types.ImageInspect, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (b SwarmBackend) PluginManager() *plugin.Manager {
|
||||
return nil
|
||||
}
|
||||
213
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/filter/container_test.go
generated
vendored
213
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/filter/container_test.go
generated
vendored
@@ -1,213 +0,0 @@
|
||||
// Copyright 2017 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package filter
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/vmware/vic/lib/apiservers/engine/backends/cache"
|
||||
viccontainer "github.com/vmware/vic/lib/apiservers/engine/backends/container"
|
||||
"github.com/vmware/vic/lib/apiservers/portlayer/models"
|
||||
)
|
||||
|
||||
func TestValidateContainerFilters(t *testing.T) {
|
||||
|
||||
options := &types.ContainerListOptions{
|
||||
Filters: filters.NewArgs(),
|
||||
}
|
||||
options.Filters.Add("id", "12345")
|
||||
options.Filters.Add("status", "running")
|
||||
options.Filters.Add("exited", "143")
|
||||
options.Filters.Add("exited", "127")
|
||||
// valid status & exit
|
||||
listContext, err := ValidateContainerFilters(options, acceptedPsFilterTags, unSupportedPsFilters)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// we should have two exit codes added to the list
|
||||
// context
|
||||
assert.Equal(t, 2, len(listContext.exitAllowed))
|
||||
assert.Equal(t, options.Filters, listContext.Filters)
|
||||
|
||||
// remove valid status and replace w/invalid
|
||||
options.Filters.Del("status", "running")
|
||||
options.Filters.Add("status", "jackedup")
|
||||
|
||||
// invalid status
|
||||
_, err = ValidateContainerFilters(options, acceptedPsFilterTags, unSupportedPsFilters)
|
||||
assert.Error(t, err)
|
||||
|
||||
// remove valid exit code and replace w/invalid
|
||||
options.Filters.Del("exited", "143")
|
||||
options.Filters.Add("exited", "abc")
|
||||
|
||||
// invalid exit code
|
||||
_, err = ValidateContainerFilters(options, acceptedPsFilterTags, unSupportedPsFilters)
|
||||
assert.Error(t, err)
|
||||
|
||||
// add an invalid container option
|
||||
options.Filters.Add("jojo", "jojo")
|
||||
|
||||
// invalid container filter option
|
||||
_, err = ValidateContainerFilters(options, acceptedPsFilterTags, unSupportedPsFilters)
|
||||
assert.Error(t, err)
|
||||
|
||||
options.Filters.Del("jojo", "jojo")
|
||||
|
||||
// add before filter
|
||||
options.Filters = filters.NewArgs()
|
||||
options.Filters.Add("before", "1234")
|
||||
// fail because the before container isn't present
|
||||
_, err = ValidateContainerFilters(options, acceptedPsFilterTags, unSupportedPsFilters)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "No such container:")
|
||||
|
||||
// add the container to the cache
|
||||
containerBefore := &viccontainer.VicContainer{
|
||||
ContainerID: "12345",
|
||||
Name: "fuzzy",
|
||||
}
|
||||
cache.ContainerCache().AddContainer(containerBefore)
|
||||
|
||||
// successful before validation
|
||||
_, err = ValidateContainerFilters(options, acceptedPsFilterTags, unSupportedPsFilters)
|
||||
assert.NoError(t, err)
|
||||
|
||||
options.Filters.Add("since", "8888")
|
||||
|
||||
// fail because the since container isn't present
|
||||
_, err = ValidateContainerFilters(options, acceptedPsFilterTags, unSupportedPsFilters)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "No such container:")
|
||||
|
||||
}
|
||||
|
||||
func TestDockerState(t *testing.T) {
|
||||
vicState := make([]string, 4, 4)
|
||||
vicState[0] = "Running"
|
||||
vicState[1] = "Stopped"
|
||||
vicState[2] = "Created"
|
||||
vicState[3] = "sammy"
|
||||
|
||||
docker := make(map[string]bool)
|
||||
docker["created"] = true
|
||||
docker["running"] = true
|
||||
docker["exited"] = true
|
||||
|
||||
// This is not a docker state, but is used to validate the
|
||||
// default switch in the tested function
|
||||
docker["sammy"] = false
|
||||
|
||||
for i := range vicState {
|
||||
if _, ok := docker[DockerState(vicState[i])]; !ok {
|
||||
t.Errorf("vicState doesn't map to docker state: %s", vicState[i])
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestIncludeContainer(t *testing.T) {
|
||||
|
||||
ep := &models.EndpointConfig{
|
||||
Scope: "bridge",
|
||||
}
|
||||
eps := make([]*models.EndpointConfig, 0)
|
||||
|
||||
vol := &models.VolumeConfig{
|
||||
Name: "fooVol",
|
||||
}
|
||||
vols := make([]*models.VolumeConfig, 0)
|
||||
|
||||
contain := &models.ContainerInfo{
|
||||
ContainerConfig: &models.ContainerConfig{
|
||||
Names: []string{"jojo"},
|
||||
},
|
||||
ProcessConfig: &models.ProcessConfig{},
|
||||
VolumeConfig: append(vols, vol),
|
||||
Endpoints: append(eps, ep),
|
||||
}
|
||||
|
||||
listCtx := &ContainerListContext{
|
||||
ContainerListOptions: &types.ContainerListOptions{
|
||||
Filters: filters.NewArgs()},
|
||||
}
|
||||
|
||||
listCtx.Filters.Add("name", "jojo")
|
||||
assert.Equal(t, IncludeAction, IncludeContainer(listCtx, contain))
|
||||
|
||||
listCtx.Limit = 1
|
||||
listCtx.Counter = listCtx.Limit
|
||||
assert.Equal(t, StopAction, IncludeContainer(listCtx, contain))
|
||||
|
||||
// reset counter
|
||||
listCtx.Counter = 0
|
||||
|
||||
// create exited map
|
||||
var s struct{}
|
||||
listCtx.exitAllowed = make(map[int]struct{})
|
||||
listCtx.exitAllowed[137] = s
|
||||
|
||||
// exclude since no container exit code
|
||||
assert.Equal(t, ExcludeAction, IncludeContainer(listCtx, contain))
|
||||
|
||||
startTime := int64(4444)
|
||||
contain.ProcessConfig.StartTime = startTime
|
||||
listCtx.ExitCode = 137
|
||||
assert.Equal(t, IncludeAction, IncludeContainer(listCtx, contain))
|
||||
|
||||
// test network name
|
||||
listCtx.Filters = filters.NewArgs()
|
||||
listCtx.Filters.Add("network", "bridge")
|
||||
assert.Equal(t, IncludeAction, IncludeContainer(listCtx, contain))
|
||||
listCtx.Filters.Add("network", "fooNet")
|
||||
assert.Equal(t, IncludeAction, IncludeContainer(listCtx, contain))
|
||||
listCtx.Filters.Del("network", "bridge")
|
||||
listCtx.Filters.Del("network", "fooNet")
|
||||
listCtx.Filters.Add("network", "barNet")
|
||||
assert.Equal(t, ExcludeAction, IncludeContainer(listCtx, contain))
|
||||
|
||||
listCtx.Filters = filters.NewArgs()
|
||||
listCtx.Filters.Add("network", "missed")
|
||||
assert.Equal(t, ExcludeAction, IncludeContainer(listCtx, contain))
|
||||
|
||||
// test volume name
|
||||
listCtx.Filters = filters.NewArgs()
|
||||
listCtx.Filters.Add("volume", "fooVol")
|
||||
assert.Equal(t, IncludeAction, IncludeContainer(listCtx, contain))
|
||||
listCtx.Filters.Add("volume", "barVol")
|
||||
assert.Equal(t, IncludeAction, IncludeContainer(listCtx, contain))
|
||||
listCtx.Filters.Del("volume", "fooVol")
|
||||
listCtx.Filters.Del("volume", "barVol")
|
||||
listCtx.Filters.Add("volume", "quxVol")
|
||||
assert.Equal(t, ExcludeAction, IncludeContainer(listCtx, contain))
|
||||
|
||||
// test volume and network filters together
|
||||
listCtx.Filters = filters.NewArgs()
|
||||
listCtx.Filters.Add("volume", "fooVol")
|
||||
listCtx.Filters.Add("network", "bridge")
|
||||
assert.Equal(t, IncludeAction, IncludeContainer(listCtx, contain))
|
||||
listCtx.Filters.Add("volume", "barVol")
|
||||
listCtx.Filters.Add("network", "fooNet")
|
||||
assert.Equal(t, IncludeAction, IncludeContainer(listCtx, contain))
|
||||
listCtx.Filters.Del("volume", "fooVol")
|
||||
assert.Equal(t, ExcludeAction, IncludeContainer(listCtx, contain))
|
||||
|
||||
listCtx.Filters = filters.NewArgs()
|
||||
listCtx.Filters.Add("status", "stopped")
|
||||
assert.Equal(t, ExcludeAction, IncludeContainer(listCtx, contain))
|
||||
}
|
||||
69
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/filter/filter_test.go
generated
vendored
69
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/filter/filter_test.go
generated
vendored
@@ -1,69 +0,0 @@
|
||||
// Copyright 2017 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package filter
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestFilterCommon(t *testing.T) {
|
||||
cmdFilters := filters.NewArgs()
|
||||
id := "123"
|
||||
before := "456"
|
||||
|
||||
fContext := &FilterContext{
|
||||
ID: id,
|
||||
}
|
||||
|
||||
// no common filter
|
||||
assert.Equal(t, IncludeAction, filterCommon(fContext, cmdFilters))
|
||||
|
||||
// exclude on Name
|
||||
cmdFilters.Add("name", "jojo")
|
||||
assert.Equal(t, ExcludeAction, filterCommon(fContext, cmdFilters))
|
||||
|
||||
// exclude on ID
|
||||
cmdFilters.Add("id", before)
|
||||
assert.Equal(t, ExcludeAction, filterCommon(fContext, cmdFilters))
|
||||
|
||||
// we've hit the before id exclude object
|
||||
fContext.ID = before
|
||||
fContext.BeforeID = &before
|
||||
cmdFilters.Add("before", before)
|
||||
assert.Equal(t, ExcludeAction, filterCommon(fContext, cmdFilters))
|
||||
|
||||
// stop due to since
|
||||
since := "859"
|
||||
fContext.SinceID = &since
|
||||
fContext.ID = since
|
||||
cmdFilters.Add("since", since)
|
||||
assert.Equal(t, StopAction, filterCommon(fContext, cmdFilters))
|
||||
|
||||
// exclude based on label mismatch
|
||||
fContext.Labels = createLabels()
|
||||
fContext.ID = id
|
||||
cmdFilters.Add("label", "joe")
|
||||
assert.Equal(t, ExcludeAction, filterCommon(fContext, cmdFilters))
|
||||
}
|
||||
|
||||
func createLabels() map[string]string {
|
||||
labels := make(map[string]string)
|
||||
labels["prod"] = "ATX"
|
||||
labels["brown"] = "fox"
|
||||
return labels
|
||||
}
|
||||
120
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/filter/image_test.go
generated
vendored
120
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/filter/image_test.go
generated
vendored
@@ -1,120 +0,0 @@
|
||||
// Copyright 2017 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package filter
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"github.com/docker/docker/reference"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/vmware/vic/lib/apiservers/engine/backends/cache"
|
||||
"github.com/vmware/vic/lib/metadata"
|
||||
)
|
||||
|
||||
func loadImageCache(repo string, imageCount int, t *testing.T) {
|
||||
|
||||
for i := 0; i < imageCount; i++ {
|
||||
id := fmt.Sprintf("120%d", i)
|
||||
tag := fmt.Sprintf("1.0%d", i+1)
|
||||
ref := fmt.Sprintf("%s:%s", repo, tag)
|
||||
img := &metadata.ImageConfig{
|
||||
ImageID: id,
|
||||
Tags: []string{tag},
|
||||
Name: repo,
|
||||
Reference: ref,
|
||||
}
|
||||
img.Created = time.Now().UTC()
|
||||
img.ID = id
|
||||
img.Config = &container.Config{}
|
||||
cache.ImageCache().Add(img)
|
||||
|
||||
named, err := reference.ParseNamed(ref)
|
||||
if err != nil {
|
||||
t.Fatalf("Error while parsing reference %s: %#v", ref, err)
|
||||
}
|
||||
cache.RepositoryCache().AddReference(named, id, true, id, false)
|
||||
}
|
||||
|
||||
assert.Equal(t, imageCount, len(cache.ImageCache().GetImages()))
|
||||
}
|
||||
|
||||
func TestValidateImageFilters(t *testing.T) {
|
||||
loadImageCache("busyboxy", 5, t)
|
||||
|
||||
cmdFilters := filters.NewArgs()
|
||||
cmdFilters.Add("dangling", "true")
|
||||
_, err := ValidateImageFilters(cmdFilters, acceptedImageFilterTags, unSupportedImageFilters)
|
||||
assert.Error(t, err)
|
||||
|
||||
cmdFilters.Del("dangling", "true")
|
||||
cmdFilters.Add("before", "1200")
|
||||
_, err = ValidateImageFilters(cmdFilters, acceptedImageFilterTags, unSupportedImageFilters)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "No such image")
|
||||
|
||||
cmdFilters.Del("before", "1200")
|
||||
cmdFilters.Add("since", "1200")
|
||||
_, err = ValidateImageFilters(cmdFilters, acceptedImageFilterTags, unSupportedImageFilters)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "No such image")
|
||||
}
|
||||
|
||||
func TestIncludeImage(t *testing.T) {
|
||||
cmdFilters := filters.NewArgs()
|
||||
|
||||
cmdFilters.Add("before", "busyboxy:1.03")
|
||||
imageContext, err := ValidateImageFilters(cmdFilters, acceptedImageFilterTags, unSupportedImageFilters)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "1202", *imageContext.BeforeID)
|
||||
|
||||
imageContext.ID = "1202"
|
||||
action := IncludeImage(cmdFilters, imageContext)
|
||||
assert.Equal(t, ExcludeAction, action)
|
||||
|
||||
imageContext.ID = "1200"
|
||||
action = IncludeImage(cmdFilters, imageContext)
|
||||
assert.Equal(t, IncludeAction, action)
|
||||
|
||||
cmdFilters.Del("before", "busyboxy:1.03")
|
||||
|
||||
cmdFilters.Add("since", "busyboxy:1.01")
|
||||
imageContext, err = ValidateImageFilters(cmdFilters, acceptedImageFilterTags, unSupportedImageFilters)
|
||||
assert.NoError(t, err)
|
||||
|
||||
imageContext.ID = "1200"
|
||||
action = IncludeImage(cmdFilters, imageContext)
|
||||
assert.Equal(t, StopAction, action)
|
||||
|
||||
cmdFilters.Del("since", "busyboxy:1.01")
|
||||
cmdFilters.Add("reference", "busy*")
|
||||
imageContext.SinceID = nil
|
||||
action = IncludeImage(cmdFilters, imageContext)
|
||||
assert.Equal(t, IncludeAction, action)
|
||||
|
||||
// remove previous filter and reset tags / digests
|
||||
cmdFilters.Del("reference", "busy*")
|
||||
imageContext.Tags = []string{}
|
||||
imageContext.Digests = []string{}
|
||||
cmdFilters.Add("reference", "busyboxy:1.01")
|
||||
|
||||
action = IncludeImage(cmdFilters, imageContext)
|
||||
assert.Equal(t, action, IncludeAction)
|
||||
assert.EqualValues(t, 1, len(imageContext.Tags))
|
||||
}
|
||||
111
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/filter/validation_test.go
generated
vendored
111
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/filter/validation_test.go
generated
vendored
@@ -1,111 +0,0 @@
|
||||
// Copyright 2017 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package filter
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// TODO: what can we do here...don't like this, but can't
|
||||
// reference upper package due to import constraints
|
||||
// valid filters as of docker commit 49bf474
|
||||
var acceptedImageFilterTags = map[string]bool{
|
||||
"dangling": true,
|
||||
"label": true,
|
||||
"before": true,
|
||||
"since": true,
|
||||
"reference": true,
|
||||
}
|
||||
|
||||
// currently not supported by vic
|
||||
var unSupportedImageFilters = map[string]bool{
|
||||
"dangling": false,
|
||||
}
|
||||
|
||||
// valid filters as of docker commit 49bf474
|
||||
var acceptedPsFilterTags = map[string]bool{
|
||||
"ancestor": true,
|
||||
"before": true,
|
||||
"exited": true,
|
||||
"id": true,
|
||||
"isolation": true,
|
||||
"label": true,
|
||||
"name": true,
|
||||
"status": true,
|
||||
"health": true,
|
||||
"since": true,
|
||||
"volume": true,
|
||||
"network": true,
|
||||
"is-task": true,
|
||||
}
|
||||
|
||||
// currently not supported by vic
|
||||
var unSupportedPsFilters = map[string]bool{
|
||||
"ancestor": false,
|
||||
"health": false,
|
||||
"isolation": false,
|
||||
"is-task": false,
|
||||
}
|
||||
|
||||
// valid volume filters as of Docker v1.13
|
||||
var acceptedVolumeFilterTags = map[string]bool{
|
||||
"dangling": true,
|
||||
"name": true,
|
||||
"driver": true,
|
||||
"label": true,
|
||||
}
|
||||
|
||||
func TestValidateFilters(t *testing.T) {
|
||||
args := filters.NewArgs()
|
||||
args.Add("id", "12345")
|
||||
args.Add("name", "jojo")
|
||||
|
||||
// valid container filter
|
||||
assert.NoError(t, ValidateFilters(args, acceptedPsFilterTags, unSupportedPsFilters))
|
||||
|
||||
// unsupported container filter
|
||||
args.Add("isolation", "windows")
|
||||
assert.Error(t, ValidateFilters(args, acceptedPsFilterTags, unSupportedPsFilters))
|
||||
|
||||
// invalid container filter
|
||||
args.Add("failure", "yoyo")
|
||||
assert.Error(t, ValidateFilters(args, acceptedPsFilterTags, unSupportedPsFilters))
|
||||
|
||||
// unsupported image filter
|
||||
args = filters.NewArgs()
|
||||
args.Add("dangling", "true")
|
||||
assert.Error(t, ValidateFilters(args, acceptedImageFilterTags, unSupportedImageFilters))
|
||||
|
||||
// invalid image filter
|
||||
args.Add("failure", "yoyo")
|
||||
assert.Error(t, ValidateFilters(args, acceptedImageFilterTags, unSupportedImageFilters))
|
||||
|
||||
// valid image filter
|
||||
args = filters.NewArgs()
|
||||
args.Add("label", "124")
|
||||
assert.NoError(t, ValidateFilters(args, acceptedImageFilterTags, unSupportedImageFilters))
|
||||
|
||||
// valid volume filter
|
||||
args = filters.NewArgs()
|
||||
args.Add("name", "vol")
|
||||
assert.NoError(t, ValidateFilters(args, acceptedVolumeFilterTags, nil))
|
||||
|
||||
// invalid volume filter
|
||||
args.Add("mountpoint", "/volumes")
|
||||
assert.Error(t, ValidateFilters(args, acceptedVolumeFilterTags, nil))
|
||||
}
|
||||
114
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/filter/volume_test.go
generated
vendored
114
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/filter/volume_test.go
generated
vendored
@@ -1,114 +0,0 @@
|
||||
// Copyright 2017 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package filter
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestValidateVolumeFilters(t *testing.T) {
|
||||
|
||||
// Valid filters
|
||||
volFilters := filters.NewArgs()
|
||||
volFilters.Add("dangling", "true")
|
||||
volFilters.Add("name", "mulder")
|
||||
volFilters.Add("driver", "aliens")
|
||||
volFilterContext, err := ValidateVolumeFilters(volFilters, acceptedVolumeFilterTags, nil)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, volFilterContext.Dangling, true)
|
||||
|
||||
// Change a filter's value
|
||||
volFilters.Del("dangling", "true")
|
||||
volFilters.Add("dangling", "false")
|
||||
volFilterContext, err = ValidateVolumeFilters(volFilters, acceptedVolumeFilterTags, nil)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, volFilterContext.Dangling, false)
|
||||
|
||||
// Valid filter with invalid value
|
||||
volFilters.Del("dangling", "false")
|
||||
volFilters.Add("dangling", "no")
|
||||
volFilterContext, err = ValidateVolumeFilters(volFilters, acceptedVolumeFilterTags, nil)
|
||||
assert.Error(t, err)
|
||||
|
||||
// Invalid filter
|
||||
volFilters.Add("mountpoint", "/volumes")
|
||||
_, err = ValidateVolumeFilters(volFilters, acceptedVolumeFilterTags, nil)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestIncludeVolume(t *testing.T) {
|
||||
|
||||
// Filter by dangling=true
|
||||
volFilters := filters.NewArgs()
|
||||
volFilters.Add("dangling", "true")
|
||||
volFilterContext := &VolumeFilterContext{
|
||||
FilterContext: FilterContext{
|
||||
Name: "scully",
|
||||
Labels: map[string]string{"samplelabel": ""},
|
||||
},
|
||||
Driver: "science",
|
||||
Joined: false,
|
||||
Dangling: true,
|
||||
}
|
||||
action := IncludeVolume(volFilters, volFilterContext)
|
||||
assert.Equal(t, action, IncludeAction)
|
||||
|
||||
// Filter by dangling=false
|
||||
volFilters.Del("dangling", "true")
|
||||
volFilters.Add("dangling", "false")
|
||||
volFilterContext.Dangling = false
|
||||
action = IncludeVolume(volFilters, volFilterContext)
|
||||
assert.Equal(t, action, ExcludeAction)
|
||||
|
||||
// Filter by name and dangling=false
|
||||
volFilters.Add("name", "scul")
|
||||
volFilterContext.Joined = true
|
||||
action = IncludeVolume(volFilters, volFilterContext)
|
||||
assert.Equal(t, action, IncludeAction)
|
||||
|
||||
// Filter by name, dangling=false and driver=science
|
||||
volFilters.Add("driver", "science")
|
||||
action = IncludeVolume(volFilters, volFilterContext)
|
||||
assert.Equal(t, action, IncludeAction)
|
||||
|
||||
// Filter by incorrect name, dangling=false and incorrect driver
|
||||
volFilterContext.Name = "mulder"
|
||||
volFilterContext.Driver = "aliens"
|
||||
action = IncludeVolume(volFilters, volFilterContext)
|
||||
assert.Equal(t, action, ExcludeAction)
|
||||
|
||||
// Filter by name, dangling=false and incorrect driver
|
||||
volFilterContext.Name = "scully"
|
||||
volFilterContext.Driver = "science"
|
||||
volFilters.Del("driver", "science")
|
||||
volFilters.Add("driver", "sci")
|
||||
action = IncludeVolume(volFilters, volFilterContext)
|
||||
assert.Equal(t, action, ExcludeAction)
|
||||
|
||||
// Filter by correct label
|
||||
volFilters = filters.NewArgs()
|
||||
volFilters.Add("label", "samplelabel")
|
||||
action = IncludeVolume(volFilters, volFilterContext)
|
||||
assert.Equal(t, action, IncludeAction)
|
||||
|
||||
// Filter by incorrect label
|
||||
volFilters.Del("label", "samplelabel")
|
||||
volFilters.Add("label", "wronglabel")
|
||||
action = IncludeVolume(volFilters, volFilterContext)
|
||||
assert.Equal(t, action, ExcludeAction)
|
||||
}
|
||||
501
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/image.go
generated
vendored
501
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/image.go
generated
vendored
@@ -1,501 +0,0 @@
|
||||
// Copyright 2016-2018 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package backends
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
|
||||
"github.com/docker/distribution/digest"
|
||||
"github.com/docker/docker/api/types"
|
||||
eventtypes "github.com/docker/docker/api/types/events"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"github.com/docker/docker/api/types/registry"
|
||||
"github.com/docker/docker/pkg/streamformatter"
|
||||
"github.com/docker/docker/reference"
|
||||
|
||||
"github.com/vmware/vic/lib/apiservers/engine/backends/cache"
|
||||
vicfilter "github.com/vmware/vic/lib/apiservers/engine/backends/filter"
|
||||
"github.com/vmware/vic/lib/apiservers/engine/errors"
|
||||
"github.com/vmware/vic/lib/apiservers/portlayer/client/storage"
|
||||
"github.com/vmware/vic/lib/imagec"
|
||||
"github.com/vmware/vic/lib/metadata"
|
||||
"github.com/vmware/vic/lib/portlayer/util"
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
"github.com/vmware/vic/pkg/uid"
|
||||
"github.com/vmware/vic/pkg/vsphere/sys"
|
||||
)
|
||||
|
||||
// valid filters as of docker commit 49bf474
|
||||
var acceptedImageFilterTags = map[string]bool{
|
||||
"dangling": true,
|
||||
"label": true,
|
||||
"before": true,
|
||||
"since": true,
|
||||
"reference": true,
|
||||
}
|
||||
|
||||
// currently not supported by vic
|
||||
var unSupportedImageFilters = map[string]bool{
|
||||
"dangling": false,
|
||||
}
|
||||
|
||||
type ImageBackend struct {
|
||||
}
|
||||
|
||||
func NewImageBackend() *ImageBackend {
|
||||
return &ImageBackend{}
|
||||
}
|
||||
|
||||
func (i *ImageBackend) Exists(containerName string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// TODO fix the errors so the client doesnt print the generic POST or DELETE message
|
||||
func (i *ImageBackend) ImageDelete(imageRef string, force, prune bool) ([]types.ImageDelete, error) {
|
||||
defer trace.End(trace.Begin(imageRef))
|
||||
|
||||
var (
|
||||
deletedRes []types.ImageDelete
|
||||
userRefIsID bool
|
||||
)
|
||||
|
||||
// Use the image cache to go from the reference to the ID we use in the image store
|
||||
img, err := cache.ImageCache().Get(imageRef)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tags := img.Tags
|
||||
digests := img.Digests
|
||||
|
||||
// did the user pass an id or partial id
|
||||
userRefIsID = cache.ImageCache().IsImageID(imageRef)
|
||||
// do we have any reference conflicts
|
||||
if len(tags) > 1 && userRefIsID && !force {
|
||||
t := uid.Parse(img.ImageID).Truncate()
|
||||
return nil,
|
||||
fmt.Errorf("conflict: unable to delete %s (must be forced) - image is referenced in one or more repositories", t)
|
||||
}
|
||||
|
||||
// if we have an ID or only 1 tag/digest lets delete the vmdk(s) via the PL
|
||||
if userRefIsID || len(tags) == 1 || len(digests) == 1 {
|
||||
log.Infof("Deleting image via PL %s (%s)", img.ImageID, img.ID)
|
||||
|
||||
// storeName is the uuid of the host this service is running on.
|
||||
storeName, err := sys.UUID()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// We're going to delete all of the images in the layer branch starting
|
||||
// at the given leaf. BUT! we need to keep the images which may be
|
||||
// referenced by tags. Therefore, we need to assemble a list of images
|
||||
// (by URI) which are referred to by tags.
|
||||
allImages := cache.ImageCache().GetImages()
|
||||
keepNodes := make([]string, len(allImages))
|
||||
for idx, node := range allImages {
|
||||
imgURL, err := util.ImageURL(storeName, node.ImageID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
keepNodes[idx] = imgURL.String()
|
||||
}
|
||||
|
||||
params := storage.NewDeleteImageParamsWithContext(ctx).WithStoreName(storeName).WithID(img.ID).WithKeepNodes(keepNodes)
|
||||
// TODO: This will fail if any containerVMs are referencing the vmdk - vanilla docker
|
||||
// allows the removal of an image (via force flag) even if a container is referencing it
|
||||
// should vic?
|
||||
res, err := PortLayerClient().Storage.DeleteImage(params)
|
||||
|
||||
// We may have deleted images despite error. Account for that in the cache.
|
||||
if res != nil {
|
||||
for _, deletedImage := range res.Payload {
|
||||
|
||||
// map the layer id to the blob sum so the ids map to what we
|
||||
// present to the user on pull
|
||||
id := deletedImage.ID
|
||||
i, err := imagec.LayerCache().Get(deletedImage.ID)
|
||||
if err == nil {
|
||||
id = i.Layer.BlobSum
|
||||
}
|
||||
|
||||
// remove the layer from the layer cache (used by imagec)
|
||||
imagec.LayerCache().Remove(deletedImage.ID)
|
||||
|
||||
// form the response
|
||||
imageDeleted := types.ImageDelete{Deleted: strings.TrimPrefix(id, "sha256:")}
|
||||
deletedRes = append(deletedRes, imageDeleted)
|
||||
}
|
||||
|
||||
if err := imagec.LayerCache().Save(); err != nil {
|
||||
return nil, fmt.Errorf("failed to save layer cache: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
switch err := err.(type) {
|
||||
case *storage.DeleteImageLocked:
|
||||
return nil, fmt.Errorf("Failed to remove image %q: %s", imageRef, err.Payload.Message)
|
||||
default:
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// we've deleted the image so remove from cache
|
||||
cache.ImageCache().RemoveImageByConfig(img)
|
||||
if err := cache.ImageCache().Save(); err != nil {
|
||||
return nil, fmt.Errorf("failed to save image cache: %s", err)
|
||||
}
|
||||
|
||||
actor := CreateImageEventActorWithAttributes(imageRef, imageRef, map[string]string{})
|
||||
EventService().Log("delete", eventtypes.ImageEventType, actor)
|
||||
} else {
|
||||
|
||||
// only untag the ref supplied
|
||||
n, err := reference.ParseNamed(imageRef)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to parse reference(%s): %s", imageRef, err.Error())
|
||||
}
|
||||
tag := reference.WithDefaultTag(n)
|
||||
tags = []string{tag.String()}
|
||||
|
||||
actor := CreateImageEventActorWithAttributes(imageRef, imageRef, map[string]string{})
|
||||
EventService().Log("untag", eventtypes.ImageEventType, actor)
|
||||
}
|
||||
// loop thru and remove from repoCache
|
||||
for i := range tags {
|
||||
// remove from cache, but don't save -- we'll do that afer all
|
||||
// updates
|
||||
// #nosec: Errors unhandled.
|
||||
refNamed, _ := cache.RepositoryCache().Remove(tags[i], false)
|
||||
deletedRes = append(deletedRes, types.ImageDelete{Untagged: refNamed})
|
||||
}
|
||||
|
||||
for i := range digests {
|
||||
// #nosec: Errors unhandled.
|
||||
refNamed, _ := cache.RepositoryCache().Remove(digests[i], false)
|
||||
deletedRes = append(deletedRes, types.ImageDelete{Untagged: refNamed})
|
||||
}
|
||||
|
||||
// save repo now -- this will limit the number of PL
|
||||
// calls to one per rmi call
|
||||
err = cache.RepositoryCache().Save()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Untag error: %s", err.Error())
|
||||
}
|
||||
|
||||
return deletedRes, err
|
||||
}
|
||||
|
||||
func (i *ImageBackend) ImageHistory(imageName string) ([]*types.ImageHistory, error) {
|
||||
return nil, errors.APINotSupportedMsg(ProductName(), "ImageHistory")
|
||||
}
|
||||
|
||||
func (i *ImageBackend) Images(imageFilters filters.Args, all bool, withExtraAttrs bool) ([]*types.ImageSummary, error) {
|
||||
defer trace.End(trace.Begin(fmt.Sprintf("imageFilters: %#v", imageFilters)))
|
||||
|
||||
// validate filters for accuracy and support
|
||||
filterContext, err := vicfilter.ValidateImageFilters(imageFilters, acceptedImageFilterTags, unSupportedImageFilters)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// get all images
|
||||
images := cache.ImageCache().GetImages()
|
||||
|
||||
result := make([]*types.ImageSummary, 0, len(images))
|
||||
|
||||
imageLoop:
|
||||
for i := range images {
|
||||
|
||||
// provide filter with current ImageID
|
||||
filterContext.ID = images[i].ImageID
|
||||
|
||||
// provide image labels
|
||||
if images[i].Config != nil {
|
||||
filterContext.Labels = images[i].Config.Labels
|
||||
}
|
||||
|
||||
// determine if image should be part of list
|
||||
action := vicfilter.IncludeImage(imageFilters, filterContext)
|
||||
|
||||
switch action {
|
||||
case vicfilter.ExcludeAction:
|
||||
continue imageLoop
|
||||
case vicfilter.StopAction:
|
||||
break imageLoop
|
||||
}
|
||||
// if we are here then add image
|
||||
dockerImage := convertV1ImageToDockerImage(images[i])
|
||||
// reference is a filter, so we must add the tags / digests
|
||||
// identified by the filter
|
||||
if imageFilters.Include("reference") {
|
||||
dockerImage.RepoTags = filterContext.Tags
|
||||
dockerImage.RepoDigests = filterContext.Digests
|
||||
|
||||
}
|
||||
result = append(result, dockerImage)
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// Docker Inspect. LookupImage looks up an image by name and returns it as an
|
||||
// ImageInspect structure.
|
||||
func (i *ImageBackend) LookupImage(name string) (*types.ImageInspect, error) {
|
||||
defer trace.End(trace.Begin("LookupImage (docker inspect)"))
|
||||
|
||||
imageConfig, err := cache.ImageCache().Get(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return imageConfigToDockerImageInspect(imageConfig, ProductName()), nil
|
||||
}
|
||||
|
||||
func (i *ImageBackend) TagImage(imageName, repository, tag string) error {
|
||||
img, err := cache.ImageCache().Get(imageName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
newTag, err := reference.WithName(repository)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if tag != "" {
|
||||
if newTag, err = reference.WithTag(newTag, tag); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// place tag in repo and save to portLayer k/v store
|
||||
err = cache.RepositoryCache().AddReference(newTag, img.ImageID, true, "", true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
actor := CreateImageEventActorWithAttributes(imageName, newTag.String(), map[string]string{})
|
||||
EventService().Log("tag", eventtypes.ImageEventType, actor)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *ImageBackend) ImagesPrune(pruneFilters filters.Args) (*types.ImagesPruneReport, error) {
|
||||
return nil, errors.APINotSupportedMsg(ProductName(), "ImagesPrune")
|
||||
}
|
||||
|
||||
func (i *ImageBackend) LoadImage(inTar io.ReadCloser, outStream io.Writer, quiet bool) error {
|
||||
return errors.APINotSupportedMsg(ProductName(), "LoadImage")
|
||||
}
|
||||
|
||||
func (i *ImageBackend) ImportImage(src string, repository, tag string, msg string, inConfig io.ReadCloser, outStream io.Writer, changes []string) error {
|
||||
return errors.APINotSupportedMsg(ProductName(), "ImportImage")
|
||||
}
|
||||
|
||||
func (i *ImageBackend) ExportImage(names []string, outStream io.Writer) error {
|
||||
return errors.APINotSupportedMsg(ProductName(), "ExportImage")
|
||||
}
|
||||
|
||||
func (i *ImageBackend) PullImage(ctx context.Context, image, tag string, metaHeaders map[string][]string, authConfig *types.AuthConfig, outStream io.Writer) error {
|
||||
defer trace.End(trace.Begin(""))
|
||||
|
||||
log.Debugf("PullImage: image = %s, tag = %s, metaheaders = %+v\n", image, tag, metaHeaders)
|
||||
|
||||
//***** Code from Docker 1.13 PullImage to convert image and tag to a ref
|
||||
image = strings.TrimSuffix(image, ":")
|
||||
|
||||
ref, err := reference.ParseNamed(image)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if tag != "" {
|
||||
// The "tag" could actually be a digest.
|
||||
var dgst digest.Digest
|
||||
dgst, err = digest.ParseDigest(tag)
|
||||
if err == nil {
|
||||
ref, err = reference.WithDigest(reference.TrimNamed(ref), dgst)
|
||||
} else {
|
||||
ref, err = reference.WithTag(ref, tag)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
//*****
|
||||
|
||||
options := imagec.Options{
|
||||
Destination: os.TempDir(),
|
||||
Reference: ref,
|
||||
Timeout: imagec.DefaultHTTPTimeout,
|
||||
Outstream: outStream,
|
||||
}
|
||||
|
||||
portLayerServer := PortLayerServer()
|
||||
if portLayerServer != "" {
|
||||
options.Host = portLayerServer
|
||||
}
|
||||
|
||||
ic := imagec.NewImageC(options, streamformatter.NewJSONStreamFormatter())
|
||||
ic.ParseReference()
|
||||
// create url from hostname
|
||||
hostnameURL, err := url.Parse(ic.Registry)
|
||||
if err != nil || hostnameURL.Hostname() == "" {
|
||||
hostnameURL, err = url.Parse("//" + ic.Registry)
|
||||
if err != nil {
|
||||
log.Infof("Error parsing hostname %s during registry access: %s", ic.Registry, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// Check if url is contained within set of whitelisted or insecure registries
|
||||
regctx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
||||
defer cancel()
|
||||
whitelistOk, _, insecureOk := vchConfig.RegistryCheck(regctx, hostnameURL)
|
||||
if !whitelistOk {
|
||||
err = fmt.Errorf("Access denied to unauthorized registry (%s) while VCH is in whitelist mode", hostnameURL.Host)
|
||||
log.Errorf(err.Error())
|
||||
sf := streamformatter.NewJSONStreamFormatter()
|
||||
outStream.Write(sf.FormatError(err))
|
||||
return nil
|
||||
}
|
||||
|
||||
ic.InsecureAllowHTTP = insecureOk
|
||||
ic.RegistryCAs = RegistryCertPool
|
||||
|
||||
if authConfig != nil {
|
||||
if len(authConfig.Username) > 0 {
|
||||
ic.Username = authConfig.Username
|
||||
}
|
||||
if len(authConfig.Password) > 0 {
|
||||
ic.Password = authConfig.Password
|
||||
}
|
||||
}
|
||||
|
||||
log.Infof("PullImage: reference: %s, %s, portlayer: %#v",
|
||||
ic.Reference,
|
||||
ic.Host,
|
||||
portLayerServer)
|
||||
|
||||
err = ic.PullImage()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
//TODO: Need repo name as second parameter. Leave blank for now
|
||||
actor := CreateImageEventActorWithAttributes(image, "", map[string]string{})
|
||||
EventService().Log("pull", eventtypes.ImageEventType, actor)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *ImageBackend) PushImage(ctx context.Context, image, tag string, metaHeaders map[string][]string, authConfig *types.AuthConfig, outStream io.Writer) error {
|
||||
return errors.APINotSupportedMsg(ProductName(), "PushImage")
|
||||
}
|
||||
|
||||
func (i *ImageBackend) SearchRegistryForImages(ctx context.Context, filtersArgs string, term string, limit int, authConfig *types.AuthConfig, metaHeaders map[string][]string) (*registry.SearchResults, error) {
|
||||
return nil, errors.APINotSupportedMsg(ProductName(), "SearchRegistryForImages")
|
||||
}
|
||||
|
||||
// Utility functions
|
||||
|
||||
func convertV1ImageToDockerImage(image *metadata.ImageConfig) *types.ImageSummary {
|
||||
var labels map[string]string
|
||||
if image.Config != nil {
|
||||
labels = image.Config.Labels
|
||||
}
|
||||
|
||||
return &types.ImageSummary{
|
||||
ID: image.ImageID,
|
||||
ParentID: image.Parent,
|
||||
RepoTags: image.Tags,
|
||||
RepoDigests: image.Digests,
|
||||
Created: image.Created.Unix(),
|
||||
Size: image.Size,
|
||||
VirtualSize: image.Size,
|
||||
Labels: labels,
|
||||
}
|
||||
}
|
||||
|
||||
// Converts the data structure retrieved from the portlayer. This src datastructure
|
||||
// represents the unmarshalled data saved in the storage port layer. The return
|
||||
// data is what the Docker CLI understand and returns to user.
|
||||
func imageConfigToDockerImageInspect(imageConfig *metadata.ImageConfig, productName string) *types.ImageInspect {
|
||||
if imageConfig == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
rootfs := types.RootFS{
|
||||
Type: "layers",
|
||||
Layers: make([]string, 0, len(imageConfig.History)),
|
||||
BaseLayer: "",
|
||||
}
|
||||
|
||||
for k := range imageConfig.DiffIDs {
|
||||
rootfs.Layers = append(rootfs.Layers, k)
|
||||
}
|
||||
|
||||
inspectData := &types.ImageInspect{
|
||||
RepoTags: imageConfig.Tags,
|
||||
RepoDigests: imageConfig.Digests,
|
||||
Parent: imageConfig.Parent,
|
||||
Comment: imageConfig.Comment,
|
||||
Created: imageConfig.Created.Format(time.RFC3339Nano),
|
||||
Container: imageConfig.Container,
|
||||
ContainerConfig: &imageConfig.ContainerConfig,
|
||||
DockerVersion: imageConfig.DockerVersion,
|
||||
Author: imageConfig.Author,
|
||||
Config: imageConfig.Config,
|
||||
Architecture: imageConfig.Architecture,
|
||||
Os: imageConfig.OS,
|
||||
Size: imageConfig.Size,
|
||||
VirtualSize: imageConfig.Size,
|
||||
RootFS: rootfs,
|
||||
}
|
||||
|
||||
inspectData.GraphDriver.Name = productName + " " + PortlayerName
|
||||
|
||||
// ImageID is currently stored within VIC without the "sha256:" prefix
|
||||
// so we add it here to match Docker output.
|
||||
inspectData.ID = digest.Canonical.String() + ":" + imageConfig.ImageID
|
||||
|
||||
return inspectData
|
||||
}
|
||||
|
||||
func CreateImageEventActorWithAttributes(imageID, refName string, attributes map[string]string) eventtypes.Actor {
|
||||
if imageConfig, err := cache.ImageCache().Get(imageID); err == nil && imageConfig != nil {
|
||||
for k, v := range imageConfig.Config.Labels {
|
||||
attributes[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
if refName != "" {
|
||||
attributes["name"] = refName
|
||||
}
|
||||
|
||||
return eventtypes.Actor{
|
||||
ID: imageID,
|
||||
Attributes: attributes,
|
||||
}
|
||||
}
|
||||
62
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/image_test.go
generated
vendored
62
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/image_test.go
generated
vendored
@@ -1,62 +0,0 @@
|
||||
// Copyright 2016 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package backends
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/docker/docker/api/types/container"
|
||||
v1 "github.com/docker/docker/image"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/vmware/vic/lib/metadata"
|
||||
)
|
||||
|
||||
func TestConvertV1ImageToDockerImage(t *testing.T) {
|
||||
now := time.Now()
|
||||
|
||||
image := &metadata.ImageConfig{
|
||||
V1Image: v1.V1Image{
|
||||
ID: "deadbeef",
|
||||
Size: 1024,
|
||||
Created: now,
|
||||
Parent: "",
|
||||
Config: &container.Config{
|
||||
Labels: map[string]string{},
|
||||
},
|
||||
},
|
||||
ImageID: "test_id",
|
||||
Digests: []string{fmt.Sprintf("%s@sha:%s", "test_name", "12345")},
|
||||
Tags: []string{fmt.Sprintf("%s:%s", "test_name", "test_tag")},
|
||||
Name: "test_name",
|
||||
DiffIDs: map[string]string{"test_diffid": "test_layerid"},
|
||||
History: []v1.History{},
|
||||
Reference: "test_name:test_tag",
|
||||
}
|
||||
|
||||
dockerImage := convertV1ImageToDockerImage(image)
|
||||
|
||||
assert.Equal(t, image.ImageID, dockerImage.ID, "Error: expected id %s, got %s", image.ImageID, dockerImage.ID)
|
||||
assert.Equal(t, image.Size, dockerImage.VirtualSize, "Error: expected size %s, got %s", image.Size, dockerImage.VirtualSize)
|
||||
assert.Equal(t, image.Size, dockerImage.Size, "Error: expected size %s, got %s", image.Size, dockerImage.Size)
|
||||
assert.Equal(t, image.Created.Unix(), dockerImage.Created, "Error: expected created %s, got %s", image.Created, dockerImage.Created)
|
||||
assert.Equal(t, image.Parent, dockerImage.ParentID, "Error: expected parent %s, got %s", image.Parent, dockerImage.ParentID)
|
||||
assert.Equal(t, image.Config.Labels, dockerImage.Labels, "Error: expected labels %s, got %s", image.Config.Labels, dockerImage.Labels)
|
||||
assert.Equal(t, image.Digests[0], dockerImage.RepoDigests[0], "Error: expected digest %s, got %s", image.Digests[0], dockerImage.RepoDigests[0])
|
||||
assert.Equal(t, image.Tags[0], dockerImage.RepoTags[0], "Error: expected tag %s, got %s", image.Tags[0], dockerImage.RepoTags[0])
|
||||
}
|
||||
551
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/network.go
generated
vendored
551
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/network.go
generated
vendored
@@ -1,551 +0,0 @@
|
||||
// Copyright 2016-2017 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package backends
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"net/http"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
derr "github.com/docker/docker/api/errors"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
apinet "github.com/docker/docker/api/types/network"
|
||||
"github.com/docker/libnetwork"
|
||||
"github.com/docker/libnetwork/networkdb"
|
||||
|
||||
"github.com/vmware/vic/lib/apiservers/engine/backends/cache"
|
||||
"github.com/vmware/vic/lib/apiservers/engine/backends/convert"
|
||||
vicendpoint "github.com/vmware/vic/lib/apiservers/engine/backends/endpoint"
|
||||
"github.com/vmware/vic/lib/apiservers/engine/errors"
|
||||
"github.com/vmware/vic/lib/apiservers/portlayer/client/containers"
|
||||
"github.com/vmware/vic/lib/apiservers/portlayer/client/scopes"
|
||||
"github.com/vmware/vic/lib/apiservers/portlayer/models"
|
||||
"github.com/vmware/vic/pkg/retry"
|
||||
)
|
||||
|
||||
type NetworkBackend struct {
|
||||
}
|
||||
|
||||
func NewNetworkBackend() *NetworkBackend {
|
||||
return &NetworkBackend{}
|
||||
}
|
||||
|
||||
func (n *NetworkBackend) NetworkControllerEnabled() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (n *NetworkBackend) FindNetwork(idName string) (libnetwork.Network, error) {
|
||||
ok, err := PortLayerClient().Scopes.List(scopes.NewListParamsWithContext(ctx).WithIDName(idName))
|
||||
if err != nil {
|
||||
switch err := err.(type) {
|
||||
case *scopes.ListNotFound:
|
||||
return nil, derr.NewRequestNotFoundError(fmt.Errorf("network %s not found", idName))
|
||||
|
||||
case *scopes.ListDefault:
|
||||
return nil, derr.NewErrorWithStatusCode(fmt.Errorf(err.Payload.Message), http.StatusInternalServerError)
|
||||
|
||||
default:
|
||||
return nil, derr.NewErrorWithStatusCode(err, http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
return &vicnetwork{cfg: ok.Payload[0]}, nil
|
||||
}
|
||||
|
||||
func (n *NetworkBackend) GetNetworkByName(idName string) (libnetwork.Network, error) {
|
||||
ok, err := PortLayerClient().Scopes.List(scopes.NewListParamsWithContext(ctx).WithIDName(idName))
|
||||
if err != nil {
|
||||
switch err := err.(type) {
|
||||
case *scopes.ListNotFound:
|
||||
return nil, nil
|
||||
|
||||
case *scopes.ListDefault:
|
||||
return nil, derr.NewErrorWithStatusCode(fmt.Errorf(err.Payload.Message), http.StatusInternalServerError)
|
||||
|
||||
default:
|
||||
return nil, derr.NewErrorWithStatusCode(err, http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
return &vicnetwork{cfg: ok.Payload[0]}, nil
|
||||
}
|
||||
|
||||
func (n *NetworkBackend) GetNetworksByID(partialID string) []libnetwork.Network {
|
||||
ok, err := PortLayerClient().Scopes.List(scopes.NewListParamsWithContext(ctx).WithIDName(partialID))
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
nets := make([]libnetwork.Network, len(ok.Payload))
|
||||
for i, cfg := range ok.Payload {
|
||||
nets[i] = &vicnetwork{cfg: cfg}
|
||||
}
|
||||
|
||||
return nets
|
||||
}
|
||||
|
||||
func (n *NetworkBackend) GetNetworks() []libnetwork.Network {
|
||||
ok, err := PortLayerClient().Scopes.ListAll(scopes.NewListAllParamsWithContext(ctx))
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
nets := make([]libnetwork.Network, len(ok.Payload))
|
||||
for i, cfg := range ok.Payload {
|
||||
nets[i] = &vicnetwork{cfg: cfg}
|
||||
i++
|
||||
}
|
||||
|
||||
return nets
|
||||
}
|
||||
|
||||
func (n *NetworkBackend) CreateNetwork(nc types.NetworkCreateRequest) (*types.NetworkCreateResponse, error) {
|
||||
if nc.IPAM != nil && len(nc.IPAM.Config) > 1 {
|
||||
return nil, fmt.Errorf("at most one ipam config supported")
|
||||
}
|
||||
|
||||
var gateway, subnet string
|
||||
var pools []string
|
||||
if nc.IPAM != nil && len(nc.IPAM.Config) > 0 {
|
||||
if nc.IPAM.Config[0].Gateway != "" {
|
||||
gateway = nc.IPAM.Config[0].Gateway
|
||||
}
|
||||
|
||||
if nc.IPAM.Config[0].Subnet != "" {
|
||||
subnet = nc.IPAM.Config[0].Subnet
|
||||
}
|
||||
|
||||
if nc.IPAM.Config[0].IPRange != "" {
|
||||
pools = append(pools, nc.IPAM.Config[0].IPRange)
|
||||
}
|
||||
}
|
||||
|
||||
if nc.Driver == "" {
|
||||
nc.Driver = "bridge"
|
||||
}
|
||||
|
||||
cfg := &models.ScopeConfig{
|
||||
Gateway: gateway,
|
||||
Name: nc.Name,
|
||||
ScopeType: nc.Driver,
|
||||
Subnet: subnet,
|
||||
IPAM: pools,
|
||||
Annotations: make(map[string]string),
|
||||
Internal: nc.Internal,
|
||||
}
|
||||
|
||||
// Marshal and encode the labels for transport and storage in the portlayer
|
||||
if labelsBytes, err := json.Marshal(nc.Labels); err == nil {
|
||||
encodedLabels := base64.StdEncoding.EncodeToString(labelsBytes)
|
||||
cfg.Annotations[convert.AnnotationKeyLabels] = encodedLabels
|
||||
} else {
|
||||
log.Errorf("error marshaling labels: %s", err)
|
||||
return nil, derr.NewErrorWithStatusCode(fmt.Errorf("unable to marshal labels: %s", err), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
created, err := PortLayerClient().Scopes.CreateScope(scopes.NewCreateScopeParamsWithContext(ctx).WithConfig(cfg))
|
||||
if err != nil {
|
||||
switch err := err.(type) {
|
||||
case *scopes.CreateScopeConflict:
|
||||
return nil, derr.NewErrorWithStatusCode(fmt.Errorf("vicnetwork %s already exists", nc.Name), http.StatusConflict)
|
||||
|
||||
case *scopes.CreateScopeDefault:
|
||||
return nil, derr.NewErrorWithStatusCode(fmt.Errorf(err.Payload.Message), http.StatusInternalServerError)
|
||||
|
||||
default:
|
||||
return nil, derr.NewErrorWithStatusCode(err, http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
ncResponse := &types.NetworkCreateResponse{
|
||||
ID: created.Payload.ID,
|
||||
Warning: "",
|
||||
}
|
||||
|
||||
return ncResponse, nil
|
||||
}
|
||||
|
||||
// isCommitConflictError returns true if err is a conflict error from the portlayer's
|
||||
// handle commit operation, and false otherwise.
|
||||
func isCommitConflictError(err error) bool {
|
||||
_, isConflictErr := err.(*containers.CommitConflict)
|
||||
return isConflictErr
|
||||
}
|
||||
|
||||
// connectContainerToNetwork performs portlayer operations to connect a container to a container vicnetwork.
|
||||
func connectContainerToNetwork(containerName, networkName string, endpointConfig *apinet.EndpointSettings) error {
|
||||
client := PortLayerClient()
|
||||
getRes, err := client.Containers.Get(containers.NewGetParamsWithContext(ctx).WithID(containerName))
|
||||
if err != nil {
|
||||
switch err := err.(type) {
|
||||
case *containers.GetNotFound:
|
||||
return derr.NewRequestNotFoundError(fmt.Errorf(err.Payload.Message))
|
||||
|
||||
case *containers.GetDefault:
|
||||
return derr.NewErrorWithStatusCode(fmt.Errorf(err.Payload.Message), http.StatusInternalServerError)
|
||||
|
||||
default:
|
||||
return derr.NewErrorWithStatusCode(err, http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
h := getRes.Payload
|
||||
nc := &models.NetworkConfig{NetworkName: networkName}
|
||||
if endpointConfig != nil {
|
||||
if endpointConfig.IPAMConfig != nil && endpointConfig.IPAMConfig.IPv4Address != "" {
|
||||
nc.Address = endpointConfig.IPAMConfig.IPv4Address
|
||||
|
||||
}
|
||||
|
||||
// Pass Links and Aliases to PL.
|
||||
nc.Aliases = vicendpoint.Alias(endpointConfig)
|
||||
}
|
||||
|
||||
addConRes, err := client.Scopes.AddContainer(scopes.NewAddContainerParamsWithContext(ctx).
|
||||
WithScope(nc.NetworkName).
|
||||
WithConfig(&models.ScopesAddContainerConfig{
|
||||
Handle: h,
|
||||
NetworkConfig: nc,
|
||||
}))
|
||||
if err != nil {
|
||||
switch err := err.(type) {
|
||||
case *scopes.AddContainerNotFound:
|
||||
return derr.NewRequestNotFoundError(fmt.Errorf(err.Payload.Message))
|
||||
|
||||
case *scopes.AddContainerInternalServerError:
|
||||
return derr.NewErrorWithStatusCode(fmt.Errorf(err.Payload.Message), http.StatusInternalServerError)
|
||||
|
||||
default:
|
||||
return derr.NewErrorWithStatusCode(err, http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
h = addConRes.Payload
|
||||
|
||||
// Get the power state of the container.
|
||||
getStateRes, err := client.Containers.GetState(containers.NewGetStateParamsWithContext(ctx).WithHandle(h))
|
||||
if err != nil {
|
||||
switch err := err.(type) {
|
||||
case *containers.GetStateNotFound:
|
||||
return derr.NewRequestNotFoundError(fmt.Errorf(err.Payload.Message))
|
||||
|
||||
case *containers.GetStateDefault:
|
||||
return derr.NewErrorWithStatusCode(fmt.Errorf(err.Payload.Message), http.StatusInternalServerError)
|
||||
|
||||
default:
|
||||
return derr.NewErrorWithStatusCode(err, http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
h = getStateRes.Payload.Handle
|
||||
// Only bind if the container is running.
|
||||
if getStateRes.Payload.State == "RUNNING" {
|
||||
bindRes, err := client.Scopes.BindContainer(scopes.NewBindContainerParamsWithContext(ctx).WithHandle(h))
|
||||
if err != nil {
|
||||
switch err := err.(type) {
|
||||
case *scopes.BindContainerNotFound:
|
||||
return derr.NewRequestNotFoundError(fmt.Errorf(err.Payload.Message))
|
||||
|
||||
case *scopes.BindContainerInternalServerError:
|
||||
return derr.NewErrorWithStatusCode(fmt.Errorf(err.Payload.Message), http.StatusInternalServerError)
|
||||
|
||||
default:
|
||||
return derr.NewErrorWithStatusCode(err, http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
if _, err2 := client.Scopes.UnbindContainer(scopes.NewUnbindContainerParamsWithContext(ctx).WithHandle(h)); err2 != nil {
|
||||
log.Warnf("failed bind container rollback: %s", err2)
|
||||
}
|
||||
}()
|
||||
|
||||
h = bindRes.Payload.Handle
|
||||
}
|
||||
|
||||
// Commit the handle.
|
||||
_, err = client.Containers.Commit(containers.NewCommitParamsWithContext(ctx).WithHandle(h))
|
||||
return err
|
||||
}
|
||||
|
||||
// ConnectContainerToNetwork connects a container to a container vicnetwork. It wraps the portlayer operations
|
||||
// in a retry for when there's a conflict error received, such as one during a similar concurrent operation.
|
||||
func (n *NetworkBackend) ConnectContainerToNetwork(containerName, networkName string, endpointConfig *apinet.EndpointSettings) error {
|
||||
vc := cache.ContainerCache().GetContainer(containerName)
|
||||
if vc != nil {
|
||||
containerName = vc.ContainerID
|
||||
}
|
||||
|
||||
operation := func() error {
|
||||
return connectContainerToNetwork(containerName, networkName, endpointConfig)
|
||||
}
|
||||
|
||||
config := retry.NewBackoffConfig()
|
||||
config.MaxElapsedTime = maxElapsedTime
|
||||
err := retry.DoWithConfig(operation, isCommitConflictError, config)
|
||||
if err != nil {
|
||||
switch err := err.(type) {
|
||||
case *containers.CommitNotFound:
|
||||
return derr.NewRequestNotFoundError(fmt.Errorf(err.Payload.Message))
|
||||
|
||||
case *containers.CommitDefault:
|
||||
return derr.NewErrorWithStatusCode(fmt.Errorf(err.Payload.Message), http.StatusInternalServerError)
|
||||
|
||||
default:
|
||||
return derr.NewErrorWithStatusCode(err, http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *NetworkBackend) DisconnectContainerFromNetwork(containerName string, networkName string, force bool) error {
|
||||
vc := cache.ContainerCache().GetContainer(containerName)
|
||||
if vc != nil {
|
||||
containerName = vc.ContainerID
|
||||
}
|
||||
return errors.APINotSupportedMsg(ProductName(), "DisconnectContainerFromNetwork")
|
||||
}
|
||||
|
||||
func (n *NetworkBackend) DeleteNetwork(name string) error {
|
||||
client := PortLayerClient()
|
||||
|
||||
if _, err := client.Scopes.DeleteScope(scopes.NewDeleteScopeParamsWithContext(ctx).WithIDName(name)); err != nil {
|
||||
switch err := err.(type) {
|
||||
case *scopes.DeleteScopeNotFound:
|
||||
return derr.NewRequestNotFoundError(fmt.Errorf("network %s not found", name))
|
||||
|
||||
case *scopes.DeleteScopeInternalServerError:
|
||||
return derr.NewErrorWithStatusCode(fmt.Errorf(err.Payload.Message), http.StatusInternalServerError)
|
||||
|
||||
default:
|
||||
return derr.NewErrorWithStatusCode(err, http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *NetworkBackend) NetworksPrune(pruneFilters filters.Args) (*types.NetworksPruneReport, error) {
|
||||
return nil, errors.APINotSupportedMsg(ProductName(), "NetworksPrune")
|
||||
}
|
||||
|
||||
// vicnetwork implements the libnetwork.Network and libnetwork.NetworkInfo interfaces
|
||||
type vicnetwork struct {
|
||||
sync.Mutex
|
||||
|
||||
cfg *models.ScopeConfig
|
||||
}
|
||||
|
||||
// A user chosen name for this vicnetwork.
|
||||
func (n *vicnetwork) Name() string {
|
||||
return n.cfg.Name
|
||||
}
|
||||
|
||||
// A system generated id for this vicnetwork.
|
||||
func (n *vicnetwork) ID() string {
|
||||
return n.cfg.ID
|
||||
}
|
||||
|
||||
// The type of vicnetwork, which corresponds to its managing driver.
|
||||
func (n *vicnetwork) Type() string {
|
||||
return n.cfg.ScopeType
|
||||
}
|
||||
|
||||
// Create a new endpoint to this vicnetwork symbolically identified by the
|
||||
// specified unique name. The options parameter carry driver specific options.
|
||||
func (n *vicnetwork) CreateEndpoint(name string, options ...libnetwork.EndpointOption) (libnetwork.Endpoint, error) {
|
||||
return nil, fmt.Errorf("not implemented")
|
||||
}
|
||||
|
||||
// Delete the vicnetwork.
|
||||
func (n *vicnetwork) Delete() error {
|
||||
return fmt.Errorf("not implemented")
|
||||
}
|
||||
|
||||
// Endpoints returns the list of Endpoint(s) in this vicnetwork.
|
||||
func (n *vicnetwork) Endpoints() []libnetwork.Endpoint {
|
||||
eps := make([]libnetwork.Endpoint, len(n.cfg.Endpoints))
|
||||
for i, e := range n.cfg.Endpoints {
|
||||
eps[i] = &endpoint{ep: e, sc: n.cfg}
|
||||
}
|
||||
|
||||
return eps
|
||||
}
|
||||
|
||||
// WalkEndpoints uses the provided function to walk the Endpoints
|
||||
func (n *vicnetwork) WalkEndpoints(walker libnetwork.EndpointWalker) {
|
||||
for _, e := range n.cfg.Endpoints {
|
||||
if walker(&endpoint{ep: e, sc: n.cfg}) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// EndpointByName returns the Endpoint which has the passed name. If not found, the error ErrNoSuchEndpoint is returned.
|
||||
func (n *vicnetwork) EndpointByName(name string) (libnetwork.Endpoint, error) {
|
||||
for _, e := range n.cfg.Endpoints {
|
||||
if e.Name == name {
|
||||
return &endpoint{ep: e, sc: n.cfg}, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("not found")
|
||||
}
|
||||
|
||||
// EndpointByID returns the Endpoint which has the passed id. If not found, the error ErrNoSuchEndpoint is returned.
|
||||
func (n *vicnetwork) EndpointByID(id string) (libnetwork.Endpoint, error) {
|
||||
for _, e := range n.cfg.Endpoints {
|
||||
if e.ID == id {
|
||||
return &endpoint{ep: e, sc: n.cfg}, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("not found")
|
||||
}
|
||||
|
||||
// Return certain operational data belonging to this vicnetwork
|
||||
func (n *vicnetwork) Info() libnetwork.NetworkInfo {
|
||||
return n
|
||||
}
|
||||
|
||||
func (n *vicnetwork) IpamConfig() (string, map[string]string, []*libnetwork.IpamConf, []*libnetwork.IpamConf) {
|
||||
n.Lock()
|
||||
defer n.Unlock()
|
||||
|
||||
confs := make([]*libnetwork.IpamConf, len(n.cfg.IPAM))
|
||||
for j, i := range n.cfg.IPAM {
|
||||
conf := &libnetwork.IpamConf{
|
||||
PreferredPool: n.cfg.Subnet,
|
||||
Gateway: "",
|
||||
}
|
||||
|
||||
if i != n.cfg.Subnet {
|
||||
conf.SubPool = i
|
||||
}
|
||||
|
||||
if n.cfg.Gateway != "" {
|
||||
conf.Gateway = n.cfg.Gateway
|
||||
}
|
||||
|
||||
confs[j] = conf
|
||||
}
|
||||
|
||||
return "", make(map[string]string), confs, nil
|
||||
}
|
||||
|
||||
func (n *vicnetwork) IpamInfo() ([]*libnetwork.IpamInfo, []*libnetwork.IpamInfo) {
|
||||
n.Lock()
|
||||
defer n.Unlock()
|
||||
|
||||
var infos []*libnetwork.IpamInfo
|
||||
for _, i := range n.cfg.IPAM {
|
||||
_, pool, err := net.ParseCIDR(i)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
info := &libnetwork.IpamInfo{
|
||||
Meta: make(map[string]string),
|
||||
}
|
||||
|
||||
info.Pool = pool
|
||||
if n.cfg.Gateway != "" {
|
||||
info.Gateway = &net.IPNet{
|
||||
IP: net.ParseIP(n.cfg.Gateway),
|
||||
Mask: net.CIDRMask(32, 32),
|
||||
}
|
||||
}
|
||||
|
||||
info.AuxAddresses = make(map[string]*net.IPNet)
|
||||
infos = append(infos, info)
|
||||
}
|
||||
|
||||
return infos, nil
|
||||
}
|
||||
|
||||
func (n *vicnetwork) DriverOptions() map[string]string {
|
||||
return make(map[string]string)
|
||||
}
|
||||
|
||||
func (n *vicnetwork) Scope() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (n *vicnetwork) IPv6Enabled() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (n *vicnetwork) Internal() bool {
|
||||
n.Lock()
|
||||
defer n.Unlock()
|
||||
|
||||
return n.cfg.Internal
|
||||
}
|
||||
|
||||
// Labels decodes and unmarshals the stored blob of vicnetwork labels.
|
||||
func (n *vicnetwork) Labels() map[string]string {
|
||||
n.Lock()
|
||||
defer n.Unlock()
|
||||
|
||||
labels := make(map[string]string)
|
||||
if n.cfg.Annotations == nil {
|
||||
return labels
|
||||
}
|
||||
|
||||
// Look for the Docker-specific annotation (label) blob and process it for the output
|
||||
if encodedLabels, ok := n.cfg.Annotations[convert.AnnotationKeyLabels]; ok {
|
||||
|
||||
if labelsBytes, decodeErr := base64.StdEncoding.DecodeString(encodedLabels); decodeErr == nil {
|
||||
if unmarshalErr := json.Unmarshal(labelsBytes, &labels); unmarshalErr != nil {
|
||||
log.Errorf("error unmarshaling labels: %s", unmarshalErr)
|
||||
}
|
||||
} else {
|
||||
log.Errorf("error decoding label blob: %s", decodeErr)
|
||||
}
|
||||
}
|
||||
|
||||
return labels
|
||||
}
|
||||
|
||||
func (n *vicnetwork) Attachable() bool {
|
||||
return false //?
|
||||
}
|
||||
|
||||
func (n *vicnetwork) Dynamic() bool {
|
||||
return false //?
|
||||
}
|
||||
|
||||
func (n *vicnetwork) Created() time.Time {
|
||||
return time.Now()
|
||||
}
|
||||
|
||||
// Peers returns a slice of PeerInfo structures which has the information about the peer
|
||||
// nodes participating in the same overlay vicnetwork. This is currently the per-vicnetwork
|
||||
// gossip cluster. For non-dynamic overlay networks and bridge networks it returns an
|
||||
// empty slice
|
||||
func (n *vicnetwork) Peers() []networkdb.PeerInfo {
|
||||
return nil
|
||||
}
|
||||
73
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/plugins.go
generated
vendored
73
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/plugins.go
generated
vendored
@@ -1,73 +0,0 @@
|
||||
// Copyright 2017 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package backends
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
enginetypes "github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/reference"
|
||||
"golang.org/x/net/context"
|
||||
|
||||
"github.com/vmware/vic/lib/apiservers/engine/errors"
|
||||
)
|
||||
|
||||
type PluginBackend struct {
|
||||
}
|
||||
|
||||
func NewPluginBackend() *PluginBackend {
|
||||
return &PluginBackend{}
|
||||
}
|
||||
|
||||
func (p *PluginBackend) Disable(name string, config *enginetypes.PluginDisableConfig) error {
|
||||
return errors.APINotSupportedMsg(ProductName(), "plugins")
|
||||
}
|
||||
|
||||
func (p *PluginBackend) Enable(name string, config *enginetypes.PluginEnableConfig) error {
|
||||
return errors.APINotSupportedMsg(ProductName(), "plugins")
|
||||
}
|
||||
|
||||
func (p *PluginBackend) List() ([]enginetypes.Plugin, error) {
|
||||
return nil, errors.APINotSupportedMsg(ProductName(), "plugins")
|
||||
}
|
||||
|
||||
func (p *PluginBackend) Inspect(name string) (*enginetypes.Plugin, error) {
|
||||
return nil, errors.PluginNotFoundError(name)
|
||||
}
|
||||
|
||||
func (p *PluginBackend) Remove(name string, config *enginetypes.PluginRmConfig) error {
|
||||
return errors.APINotSupportedMsg(ProductName(), "plugins")
|
||||
}
|
||||
|
||||
func (p *PluginBackend) Set(name string, args []string) error {
|
||||
return errors.APINotSupportedMsg(ProductName(), "plugins")
|
||||
}
|
||||
|
||||
func (p *PluginBackend) Privileges(ctx context.Context, ref reference.Named, metaHeaders http.Header, authConfig *enginetypes.AuthConfig) (enginetypes.PluginPrivileges, error) {
|
||||
return nil, errors.APINotSupportedMsg(ProductName(), "plugins")
|
||||
}
|
||||
|
||||
func (p *PluginBackend) Pull(ctx context.Context, ref reference.Named, name string, metaHeaders http.Header, authConfig *enginetypes.AuthConfig, privileges enginetypes.PluginPrivileges, outStream io.Writer) error {
|
||||
return errors.APINotSupportedMsg(ProductName(), "plugins")
|
||||
}
|
||||
|
||||
func (p *PluginBackend) Push(ctx context.Context, name string, metaHeaders http.Header, authConfig *enginetypes.AuthConfig, outStream io.Writer) error {
|
||||
return errors.APINotSupportedMsg(ProductName(), "plugins")
|
||||
}
|
||||
|
||||
func (p *PluginBackend) CreateFromContext(ctx context.Context, tarCtx io.ReadCloser, options *enginetypes.PluginCreateOptions) error {
|
||||
return errors.APINotSupportedMsg(ProductName(), "plugins")
|
||||
}
|
||||
1
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/portmap/portmap.go
generated
vendored
1
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/portmap/portmap.go
generated
vendored
@@ -22,6 +22,7 @@ import (
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/docker/libnetwork/iptables"
|
||||
//viccontainer "github.com/vmware/vic/lib/apiservers/engine/backends/container"
|
||||
)
|
||||
|
||||
type Operation int
|
||||
|
||||
122
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/sandbox.go
generated
vendored
122
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/sandbox.go
generated
vendored
@@ -1,122 +0,0 @@
|
||||
// Copyright 2016 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package backends
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
"github.com/docker/docker/pkg/stringid"
|
||||
"github.com/docker/libnetwork"
|
||||
"github.com/docker/libnetwork/types"
|
||||
)
|
||||
|
||||
type sandbox struct {
|
||||
id string
|
||||
containerID string
|
||||
}
|
||||
|
||||
func newSandbox(containerID string) *sandbox {
|
||||
return &sandbox{
|
||||
id: stringid.GenerateRandomID(),
|
||||
containerID: containerID,
|
||||
}
|
||||
}
|
||||
|
||||
// ID returns the ID of the sandbox
|
||||
func (s *sandbox) ID() string {
|
||||
return s.id
|
||||
}
|
||||
|
||||
// Key returns the sandbox's key
|
||||
func (s *sandbox) Key() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// ContainerID returns the container id associated to this sandbox
|
||||
func (s *sandbox) ContainerID() string {
|
||||
return s.containerID
|
||||
}
|
||||
|
||||
// Labels returns the sandbox's labels
|
||||
func (s *sandbox) Labels() map[string]interface{} {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Statistics retrieves the interfaces' statistics for the sandbox
|
||||
func (s *sandbox) Statistics() (map[string]*types.InterfaceStatistics, error) {
|
||||
return nil, notImplementedError
|
||||
}
|
||||
|
||||
// Refresh leaves all the endpoints, resets and re-apply the options,
|
||||
// re-joins all the endpoints without destroying the osl sandbox
|
||||
func (s *sandbox) Refresh(options ...libnetwork.SandboxOption) error {
|
||||
return notImplementedError
|
||||
}
|
||||
|
||||
// SetKey updates the Sandbox Key
|
||||
func (s *sandbox) SetKey(key string) error {
|
||||
return notImplementedError
|
||||
}
|
||||
|
||||
// Rename changes the name of all attached Endpoints
|
||||
func (s *sandbox) Rename(name string) error {
|
||||
return notImplementedError
|
||||
|
||||
}
|
||||
|
||||
// Delete destroys this container after detaching it from all connected endpoints.
|
||||
func (s *sandbox) Delete() error {
|
||||
return notImplementedError
|
||||
|
||||
}
|
||||
|
||||
// ResolveName resolves a service name to an IPv4 or IPv6 address by searching
|
||||
// the networks the sandbox is connected to. For IPv6 queries, second return
|
||||
// value will be true if the name exists in docker domain but doesn't have an
|
||||
// IPv6 address. Such queries shouldn't be forwarded to external nameservers.
|
||||
func (s *sandbox) ResolveName(name string, iplen int) ([]net.IP, bool) {
|
||||
return nil, false
|
||||
|
||||
}
|
||||
|
||||
// ResolveIP returns the service name for the passed in IP. IP is in reverse dotted
|
||||
// notation; the format used for DNS PTR records
|
||||
func (s *sandbox) ResolveIP(name string) string {
|
||||
return ""
|
||||
|
||||
}
|
||||
|
||||
// Endpoints returns all the endpoints connected to the sandbox
|
||||
func (s *sandbox) Endpoints() []libnetwork.Endpoint {
|
||||
return nil
|
||||
}
|
||||
|
||||
// ResolveService returns all the backend details about the containers or hosts
|
||||
// backing a service. Its purpose is to satisfy an SRV query
|
||||
func (s *sandbox) ResolveService(name string) ([]*net.SRV, []net.IP) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// EnableService makes a managed container's service available by adding the
|
||||
// endpoint to the service load balancer and service discovery
|
||||
func (s *sandbox) EnableService() error {
|
||||
return notImplementedError
|
||||
}
|
||||
|
||||
// DisableService removes a managed contianer's endpoints from the load balancer
|
||||
// and service discovery
|
||||
func (s *sandbox) DisableService() error {
|
||||
return notImplementedError
|
||||
}
|
||||
128
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/swarm.go
generated
vendored
128
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/swarm.go
generated
vendored
@@ -1,128 +0,0 @@
|
||||
// Copyright 2017 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package backends
|
||||
|
||||
import (
|
||||
"golang.org/x/net/context"
|
||||
|
||||
basictypes "github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/backend"
|
||||
types "github.com/docker/docker/api/types/swarm"
|
||||
|
||||
"github.com/vmware/vic/lib/apiservers/engine/errors"
|
||||
)
|
||||
|
||||
type SwarmBackend struct {
|
||||
}
|
||||
|
||||
func NewSwarmBackend() *SwarmBackend {
|
||||
return &SwarmBackend{}
|
||||
}
|
||||
|
||||
func (s *SwarmBackend) Init(req types.InitRequest) (string, error) {
|
||||
return "", errors.SwarmNotSupportedError()
|
||||
}
|
||||
|
||||
func (s *SwarmBackend) Join(req types.JoinRequest) error {
|
||||
return errors.SwarmNotSupportedError()
|
||||
}
|
||||
|
||||
func (s *SwarmBackend) Leave(force bool) error {
|
||||
return errors.SwarmNotSupportedError()
|
||||
}
|
||||
|
||||
func (s *SwarmBackend) Inspect() (types.Swarm, error) {
|
||||
return types.Swarm{}, errors.SwarmNotSupportedError()
|
||||
}
|
||||
|
||||
func (s *SwarmBackend) Update(uint64, types.Spec, types.UpdateFlags) error {
|
||||
return errors.SwarmNotSupportedError()
|
||||
}
|
||||
|
||||
func (s *SwarmBackend) GetUnlockKey() (string, error) {
|
||||
return "", errors.SwarmNotSupportedError()
|
||||
}
|
||||
|
||||
func (s *SwarmBackend) UnlockSwarm(req types.UnlockRequest) error {
|
||||
return errors.SwarmNotSupportedError()
|
||||
}
|
||||
|
||||
func (s *SwarmBackend) GetServices(basictypes.ServiceListOptions) ([]types.Service, error) {
|
||||
return nil, errors.SwarmNotSupportedError()
|
||||
}
|
||||
|
||||
func (s *SwarmBackend) GetService(string) (types.Service, error) {
|
||||
return types.Service{}, errors.SwarmNotSupportedError()
|
||||
}
|
||||
|
||||
func (s *SwarmBackend) CreateService(types.ServiceSpec, string) (*basictypes.ServiceCreateResponse, error) {
|
||||
return nil, errors.SwarmNotSupportedError()
|
||||
}
|
||||
|
||||
func (s *SwarmBackend) UpdateService(string, uint64, types.ServiceSpec, string, string) (*basictypes.ServiceUpdateResponse, error) {
|
||||
return nil, errors.SwarmNotSupportedError()
|
||||
}
|
||||
|
||||
func (s *SwarmBackend) RemoveService(string) error {
|
||||
return errors.SwarmNotSupportedError()
|
||||
}
|
||||
|
||||
func (s *SwarmBackend) ServiceLogs(context.Context, string, *backend.ContainerLogsConfig, chan struct{}) error {
|
||||
return errors.SwarmNotSupportedError()
|
||||
}
|
||||
|
||||
func (s *SwarmBackend) GetNodes(basictypes.NodeListOptions) ([]types.Node, error) {
|
||||
return nil, errors.SwarmNotSupportedError()
|
||||
}
|
||||
|
||||
func (s *SwarmBackend) GetNode(string) (types.Node, error) {
|
||||
return types.Node{}, errors.SwarmNotSupportedError()
|
||||
}
|
||||
|
||||
func (s *SwarmBackend) UpdateNode(string, uint64, types.NodeSpec) error {
|
||||
return errors.SwarmNotSupportedError()
|
||||
}
|
||||
|
||||
func (s *SwarmBackend) RemoveNode(string, bool) error {
|
||||
return errors.SwarmNotSupportedError()
|
||||
}
|
||||
|
||||
func (s *SwarmBackend) GetTasks(basictypes.TaskListOptions) ([]types.Task, error) {
|
||||
return nil, errors.SwarmNotSupportedError()
|
||||
}
|
||||
|
||||
func (s *SwarmBackend) GetTask(string) (types.Task, error) {
|
||||
return types.Task{}, errors.SwarmNotSupportedError()
|
||||
}
|
||||
|
||||
func (s *SwarmBackend) GetSecrets(opts basictypes.SecretListOptions) ([]types.Secret, error) {
|
||||
return nil, errors.SwarmNotSupportedError()
|
||||
}
|
||||
|
||||
func (s *SwarmBackend) CreateSecret(sp types.SecretSpec) (string, error) {
|
||||
return "", errors.SwarmNotSupportedError()
|
||||
}
|
||||
|
||||
func (s *SwarmBackend) RemoveSecret(id string) error {
|
||||
return errors.SwarmNotSupportedError()
|
||||
}
|
||||
|
||||
func (s *SwarmBackend) GetSecret(id string) (types.Secret, error) {
|
||||
return types.Secret{}, errors.SwarmNotSupportedError()
|
||||
}
|
||||
|
||||
func (s *SwarmBackend) UpdateSecret(id string, version uint64, spec types.SecretSpec) error {
|
||||
return errors.SwarmNotSupportedError()
|
||||
}
|
||||
454
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/system.go
generated
vendored
454
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/system.go
generated
vendored
@@ -1,454 +0,0 @@
|
||||
// Copyright 2016-2017 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package backends
|
||||
|
||||
//****
|
||||
// system.go
|
||||
//
|
||||
// Rules for code to be in here:
|
||||
// 1. No remote or swagger calls. Move those code to ../proxy/system_proxy.go
|
||||
// 2. Always return docker engine-api compatible errors.
|
||||
// - Do NOT return fmt.Errorf()
|
||||
// - Do NOT return errors.New()
|
||||
// - DO USE the aliased docker error package 'derr'
|
||||
// - It is OK to return errors returned from functions in system_proxy.go
|
||||
|
||||
import (
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
|
||||
"github.com/vmware/vic/lib/apiservers/engine/backends/cache"
|
||||
"github.com/vmware/vic/lib/apiservers/engine/errors"
|
||||
"github.com/vmware/vic/lib/apiservers/engine/proxy"
|
||||
"github.com/vmware/vic/lib/apiservers/portlayer/client"
|
||||
"github.com/vmware/vic/lib/apiservers/portlayer/client/storage"
|
||||
"github.com/vmware/vic/lib/imagec"
|
||||
urlfetcher "github.com/vmware/vic/pkg/fetcher"
|
||||
"github.com/vmware/vic/pkg/registry"
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
"github.com/vmware/vic/pkg/version"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
eventtypes "github.com/docker/docker/api/types/events"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"github.com/docker/docker/daemon/events"
|
||||
"github.com/docker/docker/pkg/platform"
|
||||
"github.com/docker/go-units"
|
||||
)
|
||||
|
||||
type SystemBackend struct {
|
||||
systemProxy proxy.VicSystemProxy
|
||||
}
|
||||
|
||||
const (
|
||||
systemStatusMhz = " VCH CPU limit"
|
||||
systemStatusMemory = " VCH memory limit"
|
||||
systemStatusCPUUsageMhz = " VCH CPU usage"
|
||||
systemStatusMemUsage = " VCH memory usage"
|
||||
systemOS = " VMware OS"
|
||||
systemOSVersion = " VMware OS version"
|
||||
systemProductName = " VMware Product"
|
||||
volumeStoresID = "VolumeStores"
|
||||
loginTimeout = 20 * time.Second
|
||||
infoTimeout = 5 * time.Second
|
||||
vchWhitelistMode = " Registry Whitelist Mode"
|
||||
whitelistRegistriesLabel = " Whitelisted Registries"
|
||||
insecureRegistriesLabel = " Insecure Registries"
|
||||
)
|
||||
|
||||
// var for use by other engine components
|
||||
var systemBackend *SystemBackend
|
||||
var sysOnce sync.Once
|
||||
|
||||
func NewSystemBackend() *SystemBackend {
|
||||
sysOnce.Do(func() {
|
||||
systemBackend = &SystemBackend{
|
||||
systemProxy: proxy.NewSystemProxy(PortLayerClient()),
|
||||
}
|
||||
})
|
||||
return systemBackend
|
||||
}
|
||||
|
||||
func (s *SystemBackend) SystemInfo() (*types.Info, error) {
|
||||
defer trace.End(trace.Begin("SystemInfo"))
|
||||
client := PortLayerClient()
|
||||
|
||||
// Retrieve container status from port layer
|
||||
running, paused, stopped, err := s.systemProxy.ContainerCount(context.Background())
|
||||
if err != nil {
|
||||
log.Infof("System.SytemInfo unable to get global status on containers: %s", err.Error())
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), infoTimeout)
|
||||
defer cancel()
|
||||
|
||||
vchConfig := vchConfig.update(ctx)
|
||||
|
||||
vchConfig.Lock()
|
||||
defer vchConfig.Unlock()
|
||||
|
||||
cfg := vchConfig.Cfg
|
||||
|
||||
// Build up the struct that the Remote API and CLI wants
|
||||
info := &types.Info{
|
||||
Driver: PortLayerName(),
|
||||
IndexServerAddress: imagec.DefaultDockerURL,
|
||||
ServerVersion: ProductVersion(),
|
||||
ID: ProductName(),
|
||||
Containers: running + paused + stopped,
|
||||
ContainersRunning: running,
|
||||
ContainersPaused: paused,
|
||||
ContainersStopped: stopped,
|
||||
Images: getImageCount(),
|
||||
Debug: cfg.Diagnostics.DebugLevel > 0,
|
||||
NGoroutines: runtime.NumGoroutine(),
|
||||
SystemTime: time.Now().Format(time.RFC3339Nano),
|
||||
LoggingDriver: "",
|
||||
CgroupDriver: "",
|
||||
DockerRootDir: "",
|
||||
ClusterStore: "",
|
||||
ClusterAdvertise: "",
|
||||
|
||||
// FIXME: Get this info once we have event listening service
|
||||
// NEventsListener int
|
||||
|
||||
// These are system related. Some refer to cgroup info. Others are
|
||||
// retrieved from the port layer and are information about the resource
|
||||
// pool.
|
||||
Name: cfg.Name,
|
||||
KernelVersion: "",
|
||||
Architecture: platform.Architecture, //stubbed
|
||||
|
||||
// NOTE: These values have no meaning for VIC. We default them to true to
|
||||
// prevent the CLI from displaying warning messages.
|
||||
CPUCfsPeriod: true,
|
||||
CPUCfsQuota: true,
|
||||
CPUShares: true,
|
||||
CPUSet: true,
|
||||
OomKillDisable: true,
|
||||
MemoryLimit: true,
|
||||
SwapLimit: true,
|
||||
KernelMemory: true,
|
||||
IPv4Forwarding: true,
|
||||
BridgeNfIptables: true,
|
||||
BridgeNfIP6tables: true,
|
||||
HTTPProxy: "",
|
||||
HTTPSProxy: "",
|
||||
NoProxy: "",
|
||||
}
|
||||
|
||||
// Add in vicnetwork info from the VCH via guestinfo
|
||||
for _, network := range cfg.ContainerNetworks {
|
||||
info.Plugins.Network = append(info.Plugins.Network, network.Name)
|
||||
}
|
||||
|
||||
info.SystemStatus = make([][2]string, 0)
|
||||
|
||||
// Add in volume label from the VCH via guestinfo
|
||||
volumeStoreString, err := FetchVolumeStores(client)
|
||||
if err != nil {
|
||||
log.Infof("Unable to get the volume store list from the portlayer : %s", err.Error())
|
||||
} else {
|
||||
customInfo := [2]string{volumeStoresID, volumeStoreString}
|
||||
info.SystemStatus = append(info.SystemStatus, customInfo)
|
||||
|
||||
// Show a list of supported volume drivers if there's at least one volume
|
||||
// store configured for the VCH. "local" is excluded because it's the default
|
||||
// driver supplied by the Docker client and is equivalent to "vsphere" in
|
||||
// our implementation.
|
||||
if len(volumeStoreString) > 0 {
|
||||
for driver := range proxy.SupportedVolDrivers {
|
||||
if driver != "local" {
|
||||
info.Plugins.Volume = append(info.Plugins.Volume, driver)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if s.systemProxy.PingPortlayer(context.Background()) {
|
||||
status := [2]string{PortLayerName(), "RUNNING"}
|
||||
info.SystemStatus = append(info.SystemStatus, status)
|
||||
} else {
|
||||
status := [2]string{PortLayerName(), "STOPPED"}
|
||||
info.SystemStatus = append(info.SystemStatus, status)
|
||||
}
|
||||
|
||||
// Add in vch information
|
||||
vchInfo, err := s.systemProxy.VCHInfo(context.Background())
|
||||
if err != nil || vchInfo == nil {
|
||||
log.Infof("System.SystemInfo unable to get vch info from port layer: %s", err.Error())
|
||||
} else {
|
||||
if vchInfo.CPUMhz > 0 {
|
||||
info.NCPU = int(vchInfo.CPUMhz)
|
||||
|
||||
customInfo := [2]string{systemStatusMhz, fmt.Sprintf("%d MHz", info.NCPU)}
|
||||
info.SystemStatus = append(info.SystemStatus, customInfo)
|
||||
}
|
||||
if vchInfo.Memory > 0 {
|
||||
info.MemTotal = vchInfo.Memory * 1024 * 1024 // Get Mebibytes
|
||||
|
||||
customInfo := [2]string{systemStatusMemory, units.BytesSize(float64(info.MemTotal))}
|
||||
info.SystemStatus = append(info.SystemStatus, customInfo)
|
||||
}
|
||||
if vchInfo.CPUUsage >= 0 {
|
||||
customInfo := [2]string{systemStatusCPUUsageMhz, fmt.Sprintf("%d MHz", int(vchInfo.CPUUsage))}
|
||||
info.SystemStatus = append(info.SystemStatus, customInfo)
|
||||
}
|
||||
if vchInfo.MemUsage >= 0 {
|
||||
customInfo := [2]string{systemStatusMemUsage, units.BytesSize(float64(vchInfo.MemUsage))}
|
||||
info.SystemStatus = append(info.SystemStatus, customInfo)
|
||||
}
|
||||
if vchInfo.HostProductName != "" {
|
||||
customInfo := [2]string{systemProductName, vchInfo.HostProductName}
|
||||
info.SystemStatus = append(info.SystemStatus, customInfo)
|
||||
}
|
||||
if vchInfo.HostOS != "" {
|
||||
info.OperatingSystem = vchInfo.HostOS
|
||||
info.OSType = vchInfo.HostOS //Value for OS and OS Type the same from vmomi
|
||||
|
||||
customInfo := [2]string{systemOS, vchInfo.HostOS}
|
||||
info.SystemStatus = append(info.SystemStatus, customInfo)
|
||||
}
|
||||
if vchInfo.HostOSVersion != "" {
|
||||
customInfo := [2]string{systemOSVersion, vchInfo.HostOSVersion}
|
||||
info.SystemStatus = append(info.SystemStatus, customInfo)
|
||||
}
|
||||
if len(vchConfig.Insecure) > 0 {
|
||||
customInfo := [2]string{insecureRegistriesLabel, strings.Join(vchConfig.Insecure.Strings(), ",")}
|
||||
info.SystemStatus = append(info.SystemStatus, customInfo)
|
||||
}
|
||||
if len(vchConfig.Whitelist) > 0 {
|
||||
s := "enabled"
|
||||
if vchConfig.remoteWl {
|
||||
s += "; remote source"
|
||||
}
|
||||
customInfo := [2]string{vchWhitelistMode, s}
|
||||
info.SystemStatus = append(info.SystemStatus, customInfo)
|
||||
customInfo = [2]string{whitelistRegistriesLabel, strings.Join(vchConfig.Whitelist.Strings(), ",")}
|
||||
info.SystemStatus = append(info.SystemStatus, customInfo)
|
||||
} else {
|
||||
customInfo := [2]string{vchWhitelistMode, "disabled. All registry access allowed."}
|
||||
info.SystemStatus = append(info.SystemStatus, customInfo)
|
||||
}
|
||||
}
|
||||
|
||||
return info, nil
|
||||
}
|
||||
|
||||
// layout for build time as per constants defined in https://golang.org/src/time/format.go
|
||||
const buildTimeLayout = "2006/01/02@15:04:05"
|
||||
|
||||
func (s *SystemBackend) SystemVersion() types.Version {
|
||||
Arch := runtime.GOARCH
|
||||
|
||||
BuildTime := version.BuildDate
|
||||
if t, err := time.Parse(buildTimeLayout, BuildTime); err == nil {
|
||||
// match time format from docker version's output
|
||||
BuildTime = t.Format(time.ANSIC)
|
||||
}
|
||||
|
||||
Experimental := true
|
||||
GitCommit := version.GitCommit
|
||||
GoVersion := runtime.Version()
|
||||
// FIXME: fill with real kernel version
|
||||
KernelVersion := "-"
|
||||
Os := runtime.GOOS
|
||||
Version := version.Version
|
||||
if Version != "" && Version[0] == 'v' {
|
||||
// match version format from docker version's output
|
||||
Version = Version[1:]
|
||||
}
|
||||
|
||||
// go runtime panics without this so keep this here
|
||||
// until we find a repro case and report it to upstream
|
||||
_ = Arch
|
||||
|
||||
version := types.Version{
|
||||
APIVersion: version.DockerAPIVersion,
|
||||
MinAPIVersion: version.DockerMinimumVersion,
|
||||
Arch: Arch,
|
||||
BuildTime: BuildTime,
|
||||
Experimental: Experimental,
|
||||
GitCommit: GitCommit,
|
||||
GoVersion: GoVersion,
|
||||
KernelVersion: KernelVersion,
|
||||
Os: Os,
|
||||
Version: Version,
|
||||
}
|
||||
|
||||
log.Infof("***** version = %#v", version)
|
||||
|
||||
return version
|
||||
}
|
||||
|
||||
// SystemCPUMhzLimit will return the VCH configured Mhz limit
|
||||
func (s *SystemBackend) SystemCPUMhzLimit() (int64, error) {
|
||||
vchInfo, err := s.systemProxy.VCHInfo(context.Background())
|
||||
if err != nil || vchInfo == nil {
|
||||
return 0, err
|
||||
}
|
||||
return vchInfo.CPUMhz, nil
|
||||
}
|
||||
|
||||
func (s *SystemBackend) SystemDiskUsage() (*types.DiskUsage, error) {
|
||||
return nil, errors.APINotSupportedMsg(ProductName(), "SystemDiskUsage")
|
||||
}
|
||||
|
||||
func (s *SystemBackend) SubscribeToEvents(since, until time.Time, filter filters.Args) ([]eventtypes.Message, chan interface{}) {
|
||||
defer trace.End(trace.Begin(""))
|
||||
|
||||
ef := events.NewFilter(filter)
|
||||
return EventService().SubscribeTopic(since, until, ef)
|
||||
}
|
||||
|
||||
func (s *SystemBackend) UnsubscribeFromEvents(listener chan interface{}) {
|
||||
defer trace.End(trace.Begin(""))
|
||||
EventService().Evict(listener)
|
||||
}
|
||||
|
||||
// AuthenticateToRegistry handles docker logins
|
||||
func (s *SystemBackend) AuthenticateToRegistry(ctx context.Context, authConfig *types.AuthConfig) (string, string, error) {
|
||||
defer trace.End(trace.Begin(""))
|
||||
|
||||
// Only look at V2 registries
|
||||
registryAddress := authConfig.ServerAddress
|
||||
if !strings.Contains(authConfig.ServerAddress, "/v2") {
|
||||
registryAddress = registryAddress + "/v2/"
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(registryAddress, "http") {
|
||||
registryAddress = "//" + registryAddress
|
||||
}
|
||||
|
||||
loginURL, err := url.Parse(registryAddress)
|
||||
if err != nil {
|
||||
msg := fmt.Sprintf("Bad login address: %s", registryAddress)
|
||||
log.Errorf(msg)
|
||||
return msg, "", err
|
||||
}
|
||||
|
||||
// Check if registry is contained within whitelisted or insecure registries
|
||||
regctx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
||||
defer cancel()
|
||||
whitelistOk, _, insecureOk := vchConfig.RegistryCheck(regctx, loginURL)
|
||||
if !whitelistOk {
|
||||
msg := fmt.Sprintf("Access denied to unauthorized registry (%s) while VCH is in whitelist mode", loginURL.Host)
|
||||
return msg, "", fmt.Errorf(msg)
|
||||
}
|
||||
|
||||
var certPool *x509.CertPool
|
||||
if insecureOk {
|
||||
log.Infof("Attempting to log into %s insecurely", loginURL.Host)
|
||||
certPool = nil
|
||||
} else {
|
||||
certPool = RegistryCertPool
|
||||
}
|
||||
|
||||
dologin := func(scheme string, skipVerify bool) (string, error) {
|
||||
loginURL.Scheme = scheme
|
||||
|
||||
var authURL *url.URL
|
||||
|
||||
fetcher := urlfetcher.NewURLFetcher(urlfetcher.Options{
|
||||
Timeout: loginTimeout,
|
||||
Username: authConfig.Username,
|
||||
Password: authConfig.Password,
|
||||
RootCAs: certPool,
|
||||
InsecureSkipVerify: skipVerify,
|
||||
})
|
||||
|
||||
// Attempt to get the Auth URL from a simple ping operation (GET) to the registry
|
||||
hdr, err := fetcher.Ping(loginURL)
|
||||
if err == nil {
|
||||
if fetcher.IsStatusUnauthorized() {
|
||||
log.Debugf("Looking up OAuth URL from server %s", loginURL)
|
||||
authURL, err = fetcher.ExtractOAuthURL(hdr.Get("www-authenticate"), nil)
|
||||
} else {
|
||||
// We're not suppose to be here, but if we do end up here, use the login
|
||||
// URL for the auth URL.
|
||||
authURL = loginURL
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
log.Errorf("Looking up OAuth URL failed: %s", err)
|
||||
return "", err
|
||||
}
|
||||
|
||||
log.Debugf("logging onto %s", authURL.String())
|
||||
|
||||
// Just check if we get a token back.
|
||||
token, err := fetcher.FetchAuthToken(authURL)
|
||||
if err != nil || token.Token == "" {
|
||||
// At this point, if a request cannot be solved by a retry, it is an authentication error.
|
||||
log.Errorf("Fetch auth token failed: %s", err)
|
||||
if _, ok := err.(urlfetcher.DoNotRetry); ok {
|
||||
err = fmt.Errorf("Get %s: unauthorized: incorrect username or password", loginURL)
|
||||
} else {
|
||||
err = urlfetcher.AuthTokenError{TokenServer: *authURL}
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
|
||||
return token.Token, nil
|
||||
}
|
||||
|
||||
_, err = dologin("https", insecureOk)
|
||||
if err != nil && insecureOk {
|
||||
_, err = dologin("http", insecureOk)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
// We don't return the token. The config.json will store token if we return
|
||||
// it, but the regular docker daemon doesn't seem to return it either.
|
||||
return "Login Succeeded", "", nil
|
||||
}
|
||||
|
||||
// Utility functions
|
||||
|
||||
func getImageCount() int {
|
||||
images := cache.ImageCache().GetImages()
|
||||
return len(images)
|
||||
}
|
||||
|
||||
func FetchVolumeStores(client *client.PortLayer) (string, error) {
|
||||
|
||||
res, err := client.Storage.VolumeStoresList(storage.NewVolumeStoresListParamsWithContext(ctx))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return strings.Join(res.Payload.Stores, " "), nil
|
||||
}
|
||||
|
||||
func entryStrJoin(entries registry.Set, sep string) string {
|
||||
var s string
|
||||
for _, e := range entries {
|
||||
s += e.String() + sep
|
||||
}
|
||||
|
||||
return s[:len(s)-len(sep)]
|
||||
}
|
||||
232
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/volume.go
generated
vendored
232
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/volume.go
generated
vendored
@@ -1,232 +0,0 @@
|
||||
// Copyright 2016-2017 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package backends
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
//"regexp"
|
||||
//"strconv"
|
||||
//"strings"
|
||||
"sync"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
//derr "github.com/docker/docker/api/errors"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
//"github.com/docker/go-units"
|
||||
//"github.com/google/uuid"
|
||||
|
||||
vicfilter "github.com/vmware/vic/lib/apiservers/engine/backends/filter"
|
||||
"github.com/vmware/vic/lib/apiservers/engine/errors"
|
||||
"github.com/vmware/vic/lib/apiservers/engine/proxy"
|
||||
"github.com/vmware/vic/lib/apiservers/portlayer/client/containers"
|
||||
//"github.com/vmware/vic/lib/apiservers/portlayer/client/storage"
|
||||
"github.com/vmware/vic/lib/apiservers/portlayer/models"
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
)
|
||||
|
||||
// Volume which defines the docker personalities view of a Volume
|
||||
type VolumeBackend struct {
|
||||
storageProxy proxy.VicStorageProxy
|
||||
}
|
||||
|
||||
// acceptedVolumeFilters are volume filters that are supported by VIC
|
||||
var acceptedVolumeFilters = map[string]bool{
|
||||
"dangling": true,
|
||||
"name": true,
|
||||
"driver": true,
|
||||
"label": true,
|
||||
}
|
||||
|
||||
var volumeBackend *VolumeBackend
|
||||
var volOnce sync.Once
|
||||
|
||||
func NewVolumeBackend() *VolumeBackend {
|
||||
volOnce.Do(func() {
|
||||
volumeBackend = &VolumeBackend{
|
||||
storageProxy: proxy.NewStorageProxy(PortLayerClient()),
|
||||
}
|
||||
})
|
||||
return volumeBackend
|
||||
}
|
||||
|
||||
// Volumes docker personality implementation for VIC
|
||||
func (v *VolumeBackend) Volumes(filter string) ([]*types.Volume, []string, error) {
|
||||
defer trace.End(trace.Begin(filter))
|
||||
|
||||
var volumes []*types.Volume
|
||||
|
||||
// Get volume list from the portlayer
|
||||
volumeResponses, err := v.storageProxy.VolumeList(context.Background(), filter)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// Parse and validate filters
|
||||
volumeFilters, err := filters.FromParam(filter)
|
||||
if err != nil {
|
||||
return nil, nil, errors.VolumeInternalServerError(err)
|
||||
}
|
||||
volFilterContext, err := vicfilter.ValidateVolumeFilters(volumeFilters, acceptedVolumeFilters, nil)
|
||||
if err != nil {
|
||||
return nil, nil, errors.VolumeInternalServerError(err)
|
||||
}
|
||||
|
||||
// joinedVolumes stores names of volumes that are joined to a container
|
||||
// and is used while filtering the output by dangling (dangling=true should
|
||||
// return volumes that are not attached to a container)
|
||||
joinedVolumes := make(map[string]struct{})
|
||||
if volumeFilters.Include("dangling") {
|
||||
// If the dangling filter is specified, gather required items beforehand
|
||||
joinedVolumes, err = fetchJoinedVolumes()
|
||||
if err != nil {
|
||||
return nil, nil, errors.VolumeInternalServerError(err)
|
||||
}
|
||||
}
|
||||
|
||||
log.Infoln("volumes found:")
|
||||
for _, vol := range volumeResponses {
|
||||
log.Infof("%s", vol.Name)
|
||||
|
||||
volumeMetadata, err := extractDockerMetadata(vol.Metadata)
|
||||
if err != nil {
|
||||
return nil, nil, errors.VolumeInternalServerError(fmt.Errorf("error unmarshalling docker metadata: %s", err))
|
||||
}
|
||||
|
||||
// Set fields needed for filtering the output
|
||||
volFilterContext.Name = vol.Name
|
||||
volFilterContext.Driver = vol.Driver
|
||||
_, volFilterContext.Joined = joinedVolumes[vol.Name]
|
||||
volFilterContext.Labels = volumeMetadata.Labels
|
||||
|
||||
// Include the volume in the output if it meets the filtering criteria
|
||||
filterAction := vicfilter.IncludeVolume(volumeFilters, volFilterContext)
|
||||
if filterAction == vicfilter.IncludeAction {
|
||||
volume := NewVolumeModel(vol, volumeMetadata.Labels)
|
||||
volumes = append(volumes, volume)
|
||||
}
|
||||
}
|
||||
|
||||
return volumes, nil, nil
|
||||
}
|
||||
|
||||
// VolumeInspect : docker personality implementation for VIC
|
||||
func (v *VolumeBackend) VolumeInspect(name string) (*types.Volume, error) {
|
||||
defer trace.End(trace.Begin(name))
|
||||
|
||||
volInfo, err := v.storageProxy.VolumeInfo(context.Background(), name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
volumeMetadata, err := extractDockerMetadata(volInfo.Metadata)
|
||||
if err != nil {
|
||||
return nil, errors.VolumeInternalServerError(fmt.Errorf("error unmarshalling docker metadata: %s", err))
|
||||
}
|
||||
volume := NewVolumeModel(volInfo, volumeMetadata.Labels)
|
||||
|
||||
return volume, nil
|
||||
}
|
||||
|
||||
// VolumeCreate : docker personality implementation for VIC
|
||||
func (v *VolumeBackend) VolumeCreate(name, driverName string, volumeData, labels map[string]string) (*types.Volume, error) {
|
||||
defer trace.End(trace.Begin(name))
|
||||
|
||||
result, err := v.storageProxy.Create(context.Background(), name, driverName, volumeData, labels)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// VolumeRm : docker personality for VIC
|
||||
func (v *VolumeBackend) VolumeRm(name string, force bool) error {
|
||||
defer trace.End(trace.Begin(name))
|
||||
|
||||
err := v.storageProxy.Remove(context.Background(), name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v *VolumeBackend) VolumesPrune(pruneFilters filters.Args) (*types.VolumesPruneReport, error) {
|
||||
return nil, errors.APINotSupportedMsg(ProductName(), "VolumesPrune")
|
||||
}
|
||||
|
||||
//------------------------------------
|
||||
// Utility Functions
|
||||
//------------------------------------
|
||||
|
||||
func NewVolumeModel(volume *models.VolumeResponse, labels map[string]string) *types.Volume {
|
||||
return &types.Volume{
|
||||
Driver: volume.Driver,
|
||||
Name: volume.Name,
|
||||
Labels: labels,
|
||||
Mountpoint: volume.Label,
|
||||
}
|
||||
}
|
||||
|
||||
// fetchJoinedVolumes obtains all containers from the portlayer and returns a map with all
|
||||
// volumes that are joined to at least one container.
|
||||
func fetchJoinedVolumes() (map[string]struct{}, error) {
|
||||
conts, err := allContainers()
|
||||
if err != nil {
|
||||
return nil, errors.VolumeInternalServerError(err)
|
||||
}
|
||||
|
||||
joinedVolumes := make(map[string]struct{})
|
||||
var s struct{}
|
||||
for i := range conts {
|
||||
for _, vol := range conts[i].VolumeConfig {
|
||||
joinedVolumes[vol.Name] = s
|
||||
}
|
||||
}
|
||||
|
||||
return joinedVolumes, nil
|
||||
}
|
||||
|
||||
// allContainers obtains all containers from the portlayer, akin to `docker ps -a`.
|
||||
func allContainers() ([]*models.ContainerInfo, error) {
|
||||
client := PortLayerClient()
|
||||
if client == nil {
|
||||
return nil, errors.NillPortlayerClientError("Volume Backend")
|
||||
}
|
||||
|
||||
all := true
|
||||
cons, err := client.Containers.GetContainerList(containers.NewGetContainerListParamsWithContext(ctx).WithAll(&all))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return cons.Payload, nil
|
||||
}
|
||||
|
||||
// Unmarshal the docker metadata using the docker metadata key. The docker
|
||||
// metadatakey. We stash the vals we know about in that map with that key.
|
||||
func extractDockerMetadata(metadataMap map[string]string) (*proxy.VolumeMetadata, error) {
|
||||
v, ok := metadataMap[proxy.DockerMetadataModelKey]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("metadata %s missing", proxy.DockerMetadataModelKey)
|
||||
}
|
||||
|
||||
result := &proxy.VolumeMetadata{}
|
||||
err := json.Unmarshal([]byte(v), result)
|
||||
return result, err
|
||||
}
|
||||
63
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/volume_test.go
generated
vendored
63
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/volume_test.go
generated
vendored
@@ -1,63 +0,0 @@
|
||||
// Copyright 2016 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package backends
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/vmware/vic/lib/apiservers/engine/proxy"
|
||||
)
|
||||
|
||||
func TestExtractDockerMetadata(t *testing.T) {
|
||||
driver := "vsphere"
|
||||
volumeName := "testVolume"
|
||||
store := "storeName"
|
||||
testCap := "512"
|
||||
|
||||
testOptMap := make(map[string]string)
|
||||
testOptMap[proxy.OptsVolumeStoreKey] = store
|
||||
testOptMap[proxy.OptsCapacityKey] = testCap
|
||||
|
||||
testLabelMap := make(map[string]string)
|
||||
testLabelMap["someLabel"] = "this is a label"
|
||||
|
||||
metaDataBefore := proxy.VolumeMetadata{
|
||||
Driver: driver,
|
||||
Name: volumeName,
|
||||
DriverOpts: testOptMap,
|
||||
Labels: testLabelMap,
|
||||
}
|
||||
|
||||
buf, err := json.Marshal(metaDataBefore)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
metadataMap := make(map[string]string)
|
||||
metadataMap[proxy.DockerMetadataModelKey] = string(buf)
|
||||
metadataAfter, err := extractDockerMetadata(metadataMap)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
assert.Equal(t, metaDataBefore.DriverOpts[proxy.OptsCapacityKey], metadataAfter.DriverOpts[proxy.OptsCapacityKey])
|
||||
assert.Equal(t, metaDataBefore.DriverOpts[proxy.OptsVolumeStoreKey], metadataAfter.DriverOpts[proxy.OptsVolumeStoreKey])
|
||||
assert.Equal(t, metaDataBefore.Labels["someLabel"], metadataAfter.Labels["someLabel"])
|
||||
assert.Equal(t, metaDataBefore.Name, metadataAfter.Name)
|
||||
assert.Equal(t, metaDataBefore.Driver, metadataAfter.Driver)
|
||||
}
|
||||
Reference in New Issue
Block a user