Fix the dependency issue (#231)

This commit is contained in:
Robbie Zhang
2018-06-21 12:09:42 -07:00
committed by GitHub
parent 027b76651d
commit 6ec1098bb8
16629 changed files with 74837 additions and 4975021 deletions

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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")
}

View File

@@ -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))
}

View File

@@ -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")
}

View File

@@ -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
}
// *****

View File

@@ -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)
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -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())
}

View File

@@ -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)
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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...
}
}

View File

@@ -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)
}
}

View File

@@ -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
}

View File

@@ -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))
}

View File

@@ -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
}

View File

@@ -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))
}

View File

@@ -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))
}

View File

@@ -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)
}

View File

@@ -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,
}
}

View File

@@ -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])
}

View File

@@ -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
}

View File

@@ -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")
}

View File

@@ -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

View File

@@ -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
}

View File

@@ -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()
}

View File

@@ -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)]
}

View File

@@ -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
}

View File

@@ -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)
}