VMware vSphere Integrated Containers provider (#206)
* Add Virtual Kubelet provider for VIC Initial virtual kubelet provider for VMware VIC. This provider currently handles creating and starting of a pod VM via the VIC portlayer and persona server. Image store handling via the VIC persona server. This provider currently requires the feature/wolfpack branch of VIC. * Added pod stop and delete. Also added node capacity. Added the ability to stop and delete pod VMs via VIC. Also retrieve node capacity information from the VCH. * Cleanup and readme file Some file clean up and added a Readme.md markdown file for the VIC provider. * Cleaned up errors, added function comments, moved operation code 1. Cleaned up error handling. Set standard for creating errors. 2. Added method prototype comments for all interface functions. 3. Moved PodCreator, PodStarter, PodStopper, and PodDeleter to a new folder. * Add mocking code and unit tests for podcache, podcreator, and podstarter Used the unit test framework used in VIC to handle assertions in the provider's unit test. Mocking code generated using OSS project mockery, which is compatible with the testify assertion framework. * Vendored packages for the VIC provider Requires feature/wolfpack branch of VIC and a few specific commit sha of projects used within VIC. * Implementation of POD Stopper and Deleter unit tests (#4) * Updated files for initial PR
This commit is contained in:
788
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/archive.go
generated
vendored
Normal file
788
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/archive.go
generated
vendored
Normal file
@@ -0,0 +1,788 @@
|
||||
// Copyright 2017 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package backends
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/tchap/go-patricia/patricia"
|
||||
|
||||
"github.com/vmware/vic/lib/apiservers/engine/backends/cache"
|
||||
viccontainer "github.com/vmware/vic/lib/apiservers/engine/backends/container"
|
||||
"github.com/vmware/vic/lib/apiservers/engine/errors"
|
||||
"github.com/vmware/vic/lib/apiservers/engine/proxy"
|
||||
"github.com/vmware/vic/lib/apiservers/portlayer/client/storage"
|
||||
vicarchive "github.com/vmware/vic/lib/archive"
|
||||
"github.com/vmware/vic/lib/constants"
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/pkg/archive"
|
||||
)
|
||||
|
||||
// ContainerArchivePath creates an archive of the filesystem resource at the
|
||||
// specified path in the container identified by the given name. Returns a
|
||||
// tar archive of the resource and whether it was a directory or a single file.
|
||||
func (c *ContainerBackend) ContainerArchivePath(name string, path string) (io.ReadCloser, *types.ContainerPathStat, error) {
|
||||
defer trace.End(trace.Begin(name))
|
||||
op := trace.NewOperation(context.Background(), "ContainerArchivePath: %s", name)
|
||||
|
||||
path = "/" + strings.TrimPrefix(path, "/")
|
||||
vc := cache.ContainerCache().GetContainer(name)
|
||||
if vc == nil {
|
||||
return nil, nil, errors.NotFoundError(name)
|
||||
}
|
||||
|
||||
stat, err := c.ContainerStatPath(name, path)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
reader, err := c.exportFromContainer(op, vc, path)
|
||||
if err != nil {
|
||||
if errors.IsResourceInUse(err) {
|
||||
err = fmt.Errorf("ContainerArchivePath failed, resource in use: %s", err.Error())
|
||||
}
|
||||
return nil, nil, errors.InternalServerError(err.Error())
|
||||
}
|
||||
|
||||
return reader, stat, nil
|
||||
}
|
||||
|
||||
func (c *ContainerBackend) exportFromContainer(op trace.Operation, vc *viccontainer.VicContainer, path string) (io.ReadCloser, error) {
|
||||
mounts := proxy.MountsFromContainer(vc)
|
||||
mounts = append(mounts, types.MountPoint{Destination: "/"})
|
||||
readerMap := NewArchiveStreamReaderMap(op, mounts, path)
|
||||
|
||||
readers, err := readerMap.ReadersForSourcePath(archiveProxy, vc.ContainerID, path)
|
||||
if err != nil {
|
||||
op.Errorf("Errors getting readers for export: %s", err.Error())
|
||||
return nil, err
|
||||
}
|
||||
count := len(readers)
|
||||
op.Infof("Got %d archive readers", count)
|
||||
|
||||
// We want to combine the streams, so need to strip the end-of-archive elements for all but the last
|
||||
strippersWithCloser := make([]io.Reader, len(readers))
|
||||
i := 0
|
||||
for ; i < count-1; i++ {
|
||||
stripper := vicarchive.NewStripper(op, tar.NewReader(readers[i]), readers[i].Close)
|
||||
strippersWithCloser[i] = stripper
|
||||
op.Debugf("Added stripping reader: %p", stripper)
|
||||
}
|
||||
op.Debugf("Adding closing reader: %p", readers[i])
|
||||
strippersWithCloser[i] = readers[i]
|
||||
|
||||
return vicarchive.MultiReader(strippersWithCloser...), nil
|
||||
}
|
||||
|
||||
// ContainerCopy performs a deprecated operation of archiving the resource at
|
||||
// the specified path in the container identified by the given name.
|
||||
func (c *ContainerBackend) ContainerCopy(name string, res string) (io.ReadCloser, error) {
|
||||
return nil, errors.APINotSupportedMsg(ProductName(), "ContainerCopy")
|
||||
}
|
||||
|
||||
// ContainerExport writes the contents of the container to the given
|
||||
// writer. An error is returned if the container cannot be found.
|
||||
func (c *ContainerBackend) ContainerExport(name string, out io.Writer) error {
|
||||
return errors.APINotSupportedMsg(ProductName(), "ContainerExport")
|
||||
}
|
||||
|
||||
// ContainerExtractToDir extracts the given archive to the specified location
|
||||
// in the filesystem of the container identified by the given name. The given
|
||||
// path must be of a directory in the container. If it is not, the error will
|
||||
// be ErrExtractPointNotDirectory. If noOverwriteDirNonDir is true then it will
|
||||
// be an error if unpacking the given content would cause an existing directory
|
||||
// to be replaced with a non-directory and vice versa.
|
||||
func (c *ContainerBackend) ContainerExtractToDir(name, path string, noOverwriteDirNonDir bool, content io.Reader) error {
|
||||
defer trace.End(trace.Begin(name))
|
||||
op := trace.NewOperation(context.Background(), "ContainerExtractToDir: %s", name)
|
||||
|
||||
path = "/" + strings.TrimPrefix(path, "/")
|
||||
|
||||
vc := cache.ContainerCache().GetContainer(name)
|
||||
if vc == nil {
|
||||
return errors.NotFoundError(name)
|
||||
}
|
||||
|
||||
err := c.importToContainer(op, vc, path, content)
|
||||
if err != nil && errors.IsResourceInUse(err) {
|
||||
op.Errorf("ContainerExtractToDir failed, resource in use: %s", err.Error())
|
||||
|
||||
err = fmt.Errorf("Resource in use")
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *ContainerBackend) importToContainer(op trace.Operation, vc *viccontainer.VicContainer, target string, content io.Reader) (err error) {
|
||||
rawReader, err := archive.DecompressStream(content)
|
||||
if err != nil {
|
||||
op.Errorf("Input tar stream to ContainerExtractToDir not recognized: %s", err.Error())
|
||||
return errors.StreamFormatNotRecognized()
|
||||
}
|
||||
|
||||
tarReader := tar.NewReader(rawReader)
|
||||
mounts := proxy.MountsFromContainer(vc)
|
||||
mounts = append(mounts, types.MountPoint{Destination: "/"})
|
||||
writerMap := NewArchiveStreamWriterMap(op, mounts, target)
|
||||
defer func() {
|
||||
// This should shutdown all the stream connections to the portlayer.
|
||||
e1 := writerMap.Close(op)
|
||||
if e1 != nil {
|
||||
err = e1
|
||||
op.Debugf("import to container: assigned err as %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
for {
|
||||
header, err := tarReader.Next()
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
op.Errorf("Error reading tar header from client archive: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
// Lookup the writer for that mount prefix
|
||||
tarWriter, err := writerMap.WriterForAsset(archiveProxy, vc.ContainerID, target, *header)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = tarWriter.WriteHeader(header); err != nil {
|
||||
op.Errorf("Error while copying tar header %#v: %s", *header, err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err = io.Copy(tarWriter, tarReader); err != nil {
|
||||
op.Errorf("Error while copying tar data for %s: %s", header.Name, err.Error())
|
||||
return err
|
||||
}
|
||||
// TODO: change this to log level 3
|
||||
if vchConfig.Cfg.Diagnostics.DebugLevel >= 1 {
|
||||
op.Debugf("Wrote entry: %s", header.Name)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ContainerStatPath stats the filesystem resource at the specified path in the
|
||||
// container identified by the given name.
|
||||
func (c *ContainerBackend) ContainerStatPath(name string, path string) (stat *types.ContainerPathStat, err error) {
|
||||
defer trace.End(trace.Begin(name))
|
||||
op := trace.NewOperation(context.Background(), "ContainerStatPath: %s", name)
|
||||
|
||||
op.Debugf("path received by statpath %s", path)
|
||||
|
||||
vc := cache.ContainerCache().GetContainer(name)
|
||||
if vc == nil {
|
||||
return nil, errors.NotFoundError(name)
|
||||
}
|
||||
|
||||
// trim / and . off from path and then append / to ensure the format is correct
|
||||
path = filepath.Clean(path)
|
||||
if !strings.HasPrefix(path, "/") {
|
||||
path = "/" + path
|
||||
}
|
||||
|
||||
mounts := proxy.MountsFromContainer(vc)
|
||||
mounts = append(mounts, types.MountPoint{Destination: "/"})
|
||||
|
||||
// handle the special case of targeting a volume mount point before it exists.
|
||||
// this will be important for non started container cp, will also be important
|
||||
// to certain behaviors for diff on a non started container.
|
||||
if stat, succeed := tryFakeStatPath(mounts, path); succeed {
|
||||
op.Debugf("faking container stat path %#v", stat)
|
||||
return stat, nil
|
||||
}
|
||||
|
||||
primaryTarget := resolvePathWithMountPoints(op, mounts, path)
|
||||
fs := primaryTarget.filterSpec
|
||||
|
||||
var deviceID string
|
||||
var store string
|
||||
if primaryTarget.mountPoint.Destination == "/" {
|
||||
// Special case. / refers to container VMDK and not a volume vmdk.
|
||||
deviceID = vc.ContainerID
|
||||
store = constants.ContainerStoreName
|
||||
} else {
|
||||
deviceID = primaryTarget.mountPoint.Name
|
||||
store = constants.VolumeStoreName
|
||||
}
|
||||
|
||||
stat, err = archiveProxy.StatPath(op, store, deviceID, fs)
|
||||
if err != nil {
|
||||
op.Errorf("error getting statpath: %s", err.Error())
|
||||
switch err := err.(type) {
|
||||
case *storage.StatPathNotFound:
|
||||
return nil, errors.ContainerResourceNotFoundError(vc.Name, "file or directory")
|
||||
case *storage.StatPathUnprocessableEntity:
|
||||
return nil, errors.InternalServerError("failed to process given path")
|
||||
default:
|
||||
return nil, errors.InternalServerError(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
op.Debugf("container stat path %#v", stat)
|
||||
return stat, nil
|
||||
}
|
||||
|
||||
//----------------------------------
|
||||
// Docker cp utility
|
||||
//----------------------------------
|
||||
|
||||
type ArchiveReader struct {
|
||||
mountPoint types.MountPoint
|
||||
filterSpec vicarchive.FilterSpec
|
||||
reader io.ReadCloser
|
||||
}
|
||||
type ArchiveStreamReaderMap struct {
|
||||
prefixTrie *patricia.Trie
|
||||
op trace.Operation
|
||||
}
|
||||
|
||||
type ArchiveWriter struct {
|
||||
mountPoint types.MountPoint
|
||||
filterSpec vicarchive.FilterSpec
|
||||
writer io.WriteCloser
|
||||
tarWriter *tar.Writer
|
||||
}
|
||||
|
||||
// ArchiveStreamWriterMap maps mount prefix to io.WriteCloser
|
||||
type ArchiveStreamWriterMap struct {
|
||||
prefixTrie *patricia.Trie
|
||||
op trace.Operation
|
||||
wg *sync.WaitGroup
|
||||
errchan chan error
|
||||
}
|
||||
|
||||
// NewArchiveStreamWriterMap creates a new ArchiveStreamWriterMap. The map contains all information
|
||||
// needed to create a writers for every volume mounts for the container. This includes the root
|
||||
// volume of the container.
|
||||
//
|
||||
// mounts is the mount data from inspect
|
||||
// containerDestPath is the destination path in the container
|
||||
func NewArchiveStreamWriterMap(op trace.Operation, mounts []types.MountPoint, dest string) *ArchiveStreamWriterMap {
|
||||
writerMap := &ArchiveStreamWriterMap{}
|
||||
writerMap.prefixTrie = patricia.NewTrie()
|
||||
writerMap.op = op
|
||||
writerMap.errchan = make(chan error, 1)
|
||||
writerMap.wg = &sync.WaitGroup{}
|
||||
|
||||
for _, m := range mounts {
|
||||
aw := ArchiveWriter{
|
||||
mountPoint: m,
|
||||
writer: nil,
|
||||
}
|
||||
|
||||
// If container destination path is part of this mount point's prefix, we must remove it and
|
||||
// add to the filterspec. If the container destination path is "/" we do no stripping.
|
||||
//
|
||||
// e.g. mount A at /mnt/A
|
||||
//
|
||||
// cp /mnt cid:/mnt
|
||||
//
|
||||
// file data.txt from local /mnt/A/data.txt will come to the persona as A/data.txt. We must
|
||||
// tell the storage portlayer to remove "A".
|
||||
//
|
||||
// e.g. mount A at /mnt/A
|
||||
//
|
||||
// cp / cid:/
|
||||
//
|
||||
// file data.txt from local /mnt/A/data.txt will come to the persona as mnt/A/data.txt.
|
||||
// Here, we must tell the portlayer to remove "mnt/A". The key to determining whether to
|
||||
// strip "A" or "mnt/A" is based on the container destination path.
|
||||
isPrimary := !strings.Contains(aw.mountPoint.Destination, dest) || aw.mountPoint.Destination == dest
|
||||
aw.filterSpec = vicarchive.GenerateFilterSpec(dest, aw.mountPoint.Destination, isPrimary, vicarchive.CopyTo)
|
||||
|
||||
writerMap.prefixTrie.Insert(patricia.Prefix(m.Destination), &aw)
|
||||
}
|
||||
|
||||
return writerMap
|
||||
}
|
||||
|
||||
// NewArchiveStreamReaderMap creates a new ArchiveStreamReaderMap. After the call, it contains
|
||||
// information to create readers for every volume mounts for the container
|
||||
//
|
||||
// mounts is the mount data from inspect
|
||||
func NewArchiveStreamReaderMap(op trace.Operation, mounts []types.MountPoint, dest string) *ArchiveStreamReaderMap {
|
||||
readerMap := &ArchiveStreamReaderMap{}
|
||||
readerMap.prefixTrie = patricia.NewTrie()
|
||||
readerMap.op = op
|
||||
|
||||
for _, m := range mounts {
|
||||
ar := ArchiveReader{
|
||||
mountPoint: m,
|
||||
reader: nil,
|
||||
}
|
||||
|
||||
// If the mount point is not the root file system, we must tell the portlayer to rebase the
|
||||
// files in the return tar stream with the mount point path since the volume does not know
|
||||
// the path it is mounted to. It only knows it's root file system.
|
||||
//
|
||||
// e.g. mount A at /mnt/A with a file data.txt in A
|
||||
//
|
||||
// /mnt/A/data.txt <-- from container point of view
|
||||
// /data.txt <-- from volume point of view
|
||||
//
|
||||
// Neither the volume nor the storage portlayer knows about /mnt/A. The persona must tell
|
||||
// the portlayer to rebase all files from this volume to the /mnt/A/ in the final tar stream.
|
||||
cleanDest := vicarchive.Clean(dest, false)
|
||||
isPrimary := !strings.Contains(ar.mountPoint.Destination, cleanDest) || ar.mountPoint.Destination == cleanDest
|
||||
ar.filterSpec = vicarchive.GenerateFilterSpec(dest, ar.mountPoint.Destination, isPrimary, vicarchive.CopyFrom)
|
||||
|
||||
readerMap.prefixTrie.Insert(patricia.Prefix(m.Destination), &ar)
|
||||
}
|
||||
|
||||
return readerMap
|
||||
}
|
||||
|
||||
// FindArchiveWriter finds the one writer that matches the asset name. There should only be one
|
||||
// stream this asset needs to be written to.
|
||||
func (wm *ArchiveStreamWriterMap) FindArchiveWriter(containerDestPath, assetName string) (*ArchiveWriter, error) {
|
||||
defer trace.End(trace.Begin(""))
|
||||
|
||||
var aw *ArchiveWriter
|
||||
var err error
|
||||
|
||||
// go function used later for searching
|
||||
findPrefix := func(prefix patricia.Prefix, item patricia.Item) error {
|
||||
if _, ok := item.(*ArchiveWriter); !ok {
|
||||
return fmt.Errorf("item not ArchiveWriter")
|
||||
}
|
||||
|
||||
aw, _ = item.(*ArchiveWriter)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Find the prefix for the final destination. Final destination is the combination of container destination path
|
||||
// and the asset's name. For example,
|
||||
//
|
||||
// container destination path = /
|
||||
// asset name = mnt/A/file.txt
|
||||
// mount 1 = /mnt/A
|
||||
// mount prefix = /mnt/A
|
||||
//
|
||||
// In the above example, mount prefxi can only be determined by combining both the container destination path and
|
||||
// the asset name, as the final destination includes a mounted volume.
|
||||
|
||||
combinedPath := path.Join(containerDestPath, assetName)
|
||||
prefix := patricia.Prefix(combinedPath)
|
||||
err = wm.prefixTrie.VisitPrefixes(prefix, findPrefix)
|
||||
if err != nil {
|
||||
wm.op.Errorf(err.Error())
|
||||
return nil, fmt.Errorf("Failed to find a node for prefix %s: %s", containerDestPath, err.Error())
|
||||
}
|
||||
|
||||
if aw == nil {
|
||||
return nil, fmt.Errorf("No archive writer found for container destination %s and asset name %s", containerDestPath, assetName)
|
||||
}
|
||||
|
||||
return aw, nil
|
||||
}
|
||||
|
||||
// WriterForAsset takes a destination path and subpath of the archive data and returns the
|
||||
// appropriate writer for the two. It's intention is to solve the case where there exist
|
||||
// a mount point and another mount point within the first mount point. For instance, the
|
||||
// prefix map can have,
|
||||
//
|
||||
// R/W - /
|
||||
// mount 1 - /mnt/a
|
||||
// mount 2 - /mnt/a/b
|
||||
//
|
||||
// case 1:
|
||||
// containerDestPath - /mnt/a
|
||||
// archive header source - b/file.txt
|
||||
//
|
||||
// The correct writer would be the one corresponding to mount 2.
|
||||
//
|
||||
// case 2:
|
||||
// containerDestPath - /mnt/a
|
||||
// archive header source - file.txt
|
||||
//
|
||||
// The correct writer would be the one corresponding to mount 1.
|
||||
//
|
||||
// case 3:
|
||||
// containerDestPath - /
|
||||
// archive header source - mnt/a/file.txt
|
||||
//
|
||||
// The correct writer would be the one corresponding to mount 1
|
||||
//
|
||||
// As demonstrated above, the mount prefix and writer cannot be determined with just the
|
||||
// container destination path. It must be combined with the actual asset's name.
|
||||
func (wm *ArchiveStreamWriterMap) WriterForAsset(proxy proxy.VicArchiveProxy, cid, containerDestPath string, assetHeader tar.Header) (*tar.Writer, error) {
|
||||
defer trace.End(trace.Begin(assetHeader.Name))
|
||||
|
||||
var err error
|
||||
|
||||
aw, err := wm.FindArchiveWriter(containerDestPath, assetHeader.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Perform the lazy initialization here.
|
||||
if aw.writer == nil || aw.tarWriter == nil {
|
||||
// lazy initialize.
|
||||
wm.op.Debugf("Lazily initializing import stream for %s", aw.mountPoint.Destination)
|
||||
var deviceID string
|
||||
var store string
|
||||
if aw.mountPoint.Destination == "/" {
|
||||
// Special case. / refers to container VMDK and not a volume vmdk.
|
||||
deviceID = cid
|
||||
store = constants.ContainerStoreName
|
||||
} else {
|
||||
deviceID = aw.mountPoint.Name
|
||||
store = constants.VolumeStoreName
|
||||
}
|
||||
rawWriter, err := proxy.ArchiveImportWriter(wm.op, store, deviceID, aw.filterSpec, wm.wg, wm.errchan)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Unable to initialize import stream writer for mount prefix %s", aw.mountPoint.Destination)
|
||||
wm.op.Errorf(err.Error())
|
||||
return nil, err
|
||||
}
|
||||
aw.writer = rawWriter
|
||||
aw.tarWriter = tar.NewWriter(rawWriter)
|
||||
}
|
||||
|
||||
return aw.tarWriter, nil
|
||||
}
|
||||
|
||||
// Close visits all the archive writer in the trie and closes the actual io.WritCloser
|
||||
func (wm *ArchiveStreamWriterMap) Close(op trace.Operation) error {
|
||||
defer trace.End(trace.Begin(""))
|
||||
|
||||
closeStream := func(prefix patricia.Prefix, item patricia.Item) error {
|
||||
if aw, ok := item.(*ArchiveWriter); ok && aw.writer != nil {
|
||||
aw.tarWriter.Close()
|
||||
aw.writer.Close()
|
||||
aw.tarWriter = nil
|
||||
aw.writer = nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
wm.prefixTrie.Visit(closeStream)
|
||||
|
||||
// wait for all pl calls to return and close the channel
|
||||
go func() {
|
||||
wm.wg.Wait()
|
||||
close(wm.errchan)
|
||||
}()
|
||||
|
||||
var err error
|
||||
// wait for all the streams to finish
|
||||
for result := range wm.errchan {
|
||||
if result != nil {
|
||||
err = result
|
||||
op.Errorf("Error received from portlayer for import streams: %s", result.Error())
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// FindArchiveReaders finds all archive readers that are within the container source path. For example,
|
||||
//
|
||||
// mount A - /mnt/A
|
||||
// mount B - /mnt/B
|
||||
// mount AB - /mnt/A/AB
|
||||
// base container - /
|
||||
//
|
||||
// container source path - /mnt/A
|
||||
//
|
||||
// For the above example, this function returns the readers for mount A and mount AB but not the
|
||||
// readers for / or mount B.
|
||||
func (rm *ArchiveStreamReaderMap) FindArchiveReaders(containerSourcePath string) ([]*ArchiveReader, error) {
|
||||
defer trace.End(trace.Begin(containerSourcePath))
|
||||
|
||||
var nodes []*ArchiveReader
|
||||
var startingNode *ArchiveReader
|
||||
var err error
|
||||
var isMountPoint bool
|
||||
|
||||
findStartingPrefix := func(prefix patricia.Prefix, item patricia.Item) error {
|
||||
if _, ok := item.(*ArchiveReader); !ok {
|
||||
return fmt.Errorf("item not ArchiveReader")
|
||||
}
|
||||
|
||||
startingNode = item.(*ArchiveReader)
|
||||
return nil
|
||||
}
|
||||
|
||||
walkPrefixSubtree := func(prefix patricia.Prefix, item patricia.Item) error {
|
||||
if _, ok := item.(*ArchiveReader); !ok {
|
||||
return fmt.Errorf("item not ArchiveReader")
|
||||
}
|
||||
|
||||
ar, _ := item.(*ArchiveReader)
|
||||
nodes = append(nodes, ar)
|
||||
isMountPoint = ar.mountPoint.Destination != "/" &&
|
||||
(isMountPoint || strings.HasPrefix(containerSourcePath, ar.mountPoint.Destination))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Clean off any trailing periods from the path, such as `cp cid:/mnt/. -`
|
||||
// Including the periods in the prefix walk would not match with subvolume
|
||||
// mounts like /mnt/vol1 or /mnt/vol2.
|
||||
// Find all mounts for the sourcepath
|
||||
cleanPath := vicarchive.Clean(containerSourcePath, false)
|
||||
prefix := patricia.Prefix(cleanPath)
|
||||
err = rm.prefixTrie.VisitSubtree(prefix, walkPrefixSubtree)
|
||||
if err != nil {
|
||||
msg := fmt.Sprintf("Failed to find a node for prefix %s: %s", containerSourcePath, err.Error())
|
||||
rm.op.Errorf(msg)
|
||||
return nil, fmt.Errorf(msg)
|
||||
}
|
||||
|
||||
// The above subtree walking MAY NOT find the starting prefix. For example /etc will not find /.
|
||||
// Subtree only finds prefix that starts with /etc. VisitPrefixes will find the starting prefix.
|
||||
// If the search was for /, then it will not find the starting node. In that case, we grab the
|
||||
// first node in the slice.
|
||||
err = rm.prefixTrie.VisitPrefixes(prefix, findStartingPrefix)
|
||||
if err != nil {
|
||||
msg := fmt.Sprintf("Failed to find starting node for prefix %s: %s", containerSourcePath, err.Error())
|
||||
rm.op.Errorf(msg)
|
||||
return nil, fmt.Errorf(msg)
|
||||
}
|
||||
|
||||
if startingNode != nil {
|
||||
found := false
|
||||
for _, node := range nodes {
|
||||
if node.mountPoint.Destination == startingNode.mountPoint.Destination {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
// prepend the starting node at the beginning
|
||||
nodes = append([]*ArchiveReader{startingNode}, nodes...)
|
||||
}
|
||||
} else if len(nodes) > 0 {
|
||||
startingNode = nodes[0]
|
||||
} else {
|
||||
msg := fmt.Sprintf("Failed to find starting node for prefix %s: %s", containerSourcePath, err.Error())
|
||||
rm.op.Errorf(msg)
|
||||
return nil, fmt.Errorf(msg)
|
||||
}
|
||||
|
||||
// if the path is a mount path, we need to include the directory header of the actual mountpoint
|
||||
// to ensure the corrent permissions of the directory, eg docker cp cid:/mnt/vol1/ needs to include
|
||||
// header from /mnt/vol1 located on containerfs
|
||||
// data from /mnt/vol1/ located on deviceId vol1
|
||||
if isMountPoint && path.Base(containerSourcePath) != "." {
|
||||
rm.op.Debugf("%s is a mountpoint, getting dir permissions from parent", cleanPath)
|
||||
// find the parent node using VisitPrefixes
|
||||
parent := path.Dir(cleanPath)
|
||||
prefix = patricia.Prefix(parent)
|
||||
startingNode = nil
|
||||
err = rm.prefixTrie.VisitPrefixes(prefix, findStartingPrefix)
|
||||
if err != nil {
|
||||
msg := fmt.Sprintf("Failed to generate parent node for mountpoint %s: %s", parent, err.Error())
|
||||
rm.op.Errorf(msg)
|
||||
return nil, fmt.Errorf(msg)
|
||||
}
|
||||
|
||||
var found bool
|
||||
if startingNode != nil {
|
||||
for _, node := range nodes {
|
||||
found = found || node.mountPoint.Destination == startingNode.mountPoint.Destination
|
||||
}
|
||||
if !found {
|
||||
nodes = append([]*ArchiveReader{startingNode}, nodes...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
err = rm.buildFilterSpec(containerSourcePath, nodes, startingNode)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return nodes, nil
|
||||
}
|
||||
|
||||
// ReadersForSourcePath returns all an array of io.Reader for all the readers within a container source path.
|
||||
// Example:
|
||||
// Reader 1 - /mnt/A
|
||||
// Reader 2 - /mnt/A/B
|
||||
//
|
||||
// containerSourcePath - /mnt/A
|
||||
// In the above, both readers are within the the container source path.
|
||||
func (rm *ArchiveStreamReaderMap) ReadersForSourcePath(proxy proxy.VicArchiveProxy, cid, containerSourcePath string) ([]io.ReadCloser, error) {
|
||||
defer trace.End(trace.Begin(containerSourcePath))
|
||||
|
||||
var streamReaders []io.ReadCloser
|
||||
|
||||
nodes, err := rm.FindArchiveReaders(containerSourcePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
mounts := []string{}
|
||||
for _, node := range nodes {
|
||||
mounts = append(mounts, node.mountPoint.Destination)
|
||||
}
|
||||
|
||||
// Create the io.Reader for those mounts if they haven't already been initialized
|
||||
for _, node := range nodes {
|
||||
if node.reader == nil {
|
||||
var store, deviceID string
|
||||
if node.mountPoint.Destination == "/" {
|
||||
// Special case. / refers to container VMDK and not a volume vmdk.
|
||||
store = constants.ContainerStoreName
|
||||
deviceID = cid
|
||||
} else {
|
||||
store = constants.VolumeStoreName
|
||||
deviceID = node.mountPoint.Name
|
||||
}
|
||||
|
||||
rm.op.Infof("Lazily initializing export stream for %s [%s]", node.mountPoint.Name, node.mountPoint.Destination)
|
||||
reader, err := proxy.ArchiveExportReader(rm.op, store, "", deviceID, "", true, node.filterSpec)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Unable to initialize export stream reader for prefix %s", node.mountPoint.Destination)
|
||||
rm.op.Errorf(err.Error())
|
||||
return nil, err
|
||||
}
|
||||
rm.op.Infof("Lazy initialization created reader %#v", reader)
|
||||
streamReaders = append(streamReaders, reader)
|
||||
} else {
|
||||
streamReaders = append(streamReaders, node.reader)
|
||||
}
|
||||
}
|
||||
|
||||
if len(nodes) == 0 {
|
||||
rm.op.Infof("Found no archive readers for %s", containerSourcePath)
|
||||
}
|
||||
|
||||
return streamReaders, nil
|
||||
}
|
||||
|
||||
// Close visits all the archive readers in the trie and closes the actual io.ReadCloser
|
||||
func (rm *ArchiveStreamReaderMap) Close() {
|
||||
defer trace.End(trace.Begin(""))
|
||||
|
||||
closeStream := func(prefix patricia.Prefix, item patricia.Item) error {
|
||||
if aw, ok := item.(*ArchiveReader); ok && aw.reader != nil {
|
||||
aw.reader.Close()
|
||||
aw.reader = nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
rm.prefixTrie.Visit(closeStream)
|
||||
}
|
||||
|
||||
// tryFakeStatPath tries to fake the statpath for path that targets the mountpoint or along the mountpoint
|
||||
func tryFakeStatPath(mounts []types.MountPoint, target string) (*types.ContainerPathStat, bool) {
|
||||
isMountPathTarget := false
|
||||
|
||||
for _, mount := range mounts {
|
||||
if strings.HasPrefix(mount.Destination, target) {
|
||||
isMountPathTarget = true
|
||||
}
|
||||
}
|
||||
|
||||
// check to see if the path is a mount point, if so, return fake path
|
||||
if isMountPathTarget {
|
||||
return &types.ContainerPathStat{
|
||||
Name: filepath.Base(target),
|
||||
Size: int64(4096),
|
||||
Mode: os.ModeDir,
|
||||
Mtime: time.Now(),
|
||||
LinkTarget: "",
|
||||
}, true
|
||||
}
|
||||
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// resolvePathWithMountPoints use mounts to generate a filter spec for the given path
|
||||
func resolvePathWithMountPoints(op trace.Operation, mounts []types.MountPoint, path string) *ArchiveReader {
|
||||
var primaryTarget *ArchiveReader
|
||||
|
||||
readerMap := NewArchiveStreamReaderMap(op, mounts, path)
|
||||
// #nosec: Errors unhandled.
|
||||
nodes, _ := readerMap.FindArchiveReaders(path)
|
||||
|
||||
for _, node := range nodes {
|
||||
if strings.HasPrefix(path, node.mountPoint.Destination) &&
|
||||
(primaryTarget == nil || len(node.mountPoint.Destination) > len(primaryTarget.mountPoint.Destination)) {
|
||||
primaryTarget = node
|
||||
}
|
||||
}
|
||||
|
||||
return primaryTarget
|
||||
}
|
||||
|
||||
func (rm *ArchiveStreamReaderMap) buildFilterSpec(containerSourcePath string, nodes []*ArchiveReader, startingNode *ArchiveReader) error {
|
||||
|
||||
mounts, foundNodes, err := rm.buildMountsAndNodes(startingNode.mountPoint.Destination, startingNode)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, node := range foundNodes {
|
||||
vicarchive.AddMountInclusionsExclusions(node.mountPoint.Destination, &node.filterSpec, mounts, containerSourcePath)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// buildMountsAndNodes returns the node pointers from the prefix tree as well as all mounts involved in the operation
|
||||
func (rm *ArchiveStreamReaderMap) buildMountsAndNodes(path string, node *ArchiveReader) ([]string, []*ArchiveReader, error) {
|
||||
|
||||
// NOTE(sflxn): We can modify this to make proper exclusions in the future. For now,
|
||||
// we assemble the list of mounts which are involved in the operation
|
||||
// and use the util.go function for generating all the needed information
|
||||
|
||||
mounts := []string{}
|
||||
nodes := []*ArchiveReader{}
|
||||
childWalker := func(prefix patricia.Prefix, item patricia.Item) error {
|
||||
if _, ok := item.(*ArchiveReader); !ok {
|
||||
return fmt.Errorf("item not ArchiveReader")
|
||||
}
|
||||
|
||||
ar, _ := item.(*ArchiveReader)
|
||||
mounts = append(mounts, ar.mountPoint.Destination)
|
||||
nodes = append(nodes, ar)
|
||||
return nil
|
||||
}
|
||||
|
||||
// prefix = current node's mount path
|
||||
nodePrefix := patricia.Prefix(path)
|
||||
|
||||
err := rm.prefixTrie.VisitSubtree(nodePrefix, childWalker)
|
||||
if err != nil {
|
||||
msg := fmt.Sprintf("Failed to build exclusion filter for %s: %s", path, err.Error())
|
||||
log.Error(msg)
|
||||
return nil, nil, fmt.Errorf(msg)
|
||||
}
|
||||
|
||||
return mounts, nodes, nil
|
||||
}
|
||||
341
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/archive_test.go
generated
vendored
Normal file
341
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/archive_test.go
generated
vendored
Normal file
@@ -0,0 +1,341 @@
|
||||
// Copyright 2017 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package backends
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
)
|
||||
|
||||
type MockCopyToData struct {
|
||||
containerDestPath string
|
||||
tarAssetName string
|
||||
expectedPrefix string
|
||||
}
|
||||
|
||||
type ReaderFilters struct {
|
||||
rebase string
|
||||
strip string
|
||||
exclude []string
|
||||
include string
|
||||
}
|
||||
type MockCopyFromData struct {
|
||||
containerSourcePath string
|
||||
expectedPrefices []string
|
||||
expectedFilterSpecs map[string]ReaderFilters
|
||||
}
|
||||
|
||||
func TestFindArchiveWriter(t *testing.T) {
|
||||
mounts := []types.MountPoint{
|
||||
{Name: "volA", Destination: "/mnt/A"},
|
||||
{Name: "volAB", Destination: "/mnt/A/AB"},
|
||||
{Name: "volB", Destination: "/mnt/B"},
|
||||
{Name: "R/W", Destination: "/"},
|
||||
}
|
||||
|
||||
mockData := []MockCopyToData{
|
||||
// mock data for tar asset as a file and container dest path including a mount point
|
||||
{
|
||||
containerDestPath: "/mnt/A/",
|
||||
tarAssetName: "file.txt",
|
||||
expectedPrefix: "/mnt/A",
|
||||
},
|
||||
{
|
||||
containerDestPath: "/mnt/A/AB",
|
||||
tarAssetName: "file.txt",
|
||||
expectedPrefix: "/mnt/A/AB",
|
||||
},
|
||||
// mock data for tar asset containing a mount point and the container dest path as /
|
||||
{
|
||||
containerDestPath: "/",
|
||||
tarAssetName: "mnt/A/file.txt",
|
||||
expectedPrefix: "/mnt/A",
|
||||
},
|
||||
{
|
||||
containerDestPath: "/",
|
||||
tarAssetName: "mnt/A/AB/file.txt",
|
||||
expectedPrefix: "/mnt/A/AB",
|
||||
},
|
||||
// mock data for cases that do not involve mount points
|
||||
{
|
||||
containerDestPath: "/",
|
||||
tarAssetName: "test/file.txt",
|
||||
expectedPrefix: "/",
|
||||
},
|
||||
}
|
||||
|
||||
for _, data := range mockData {
|
||||
op := trace.NewOperation(context.Background(), "")
|
||||
writerMap := NewArchiveStreamWriterMap(op, mounts, data.containerDestPath)
|
||||
aw, err := writerMap.FindArchiveWriter(data.containerDestPath, data.tarAssetName)
|
||||
assert.Nil(t, err, "Expected success from finding archive writer for container dest %s and tar asset path %s", data.containerDestPath, data.tarAssetName)
|
||||
assert.NotNil(t, aw, "Expected non-nil archive writer")
|
||||
if aw != nil {
|
||||
assert.Contains(t, aw.mountPoint.Destination, data.expectedPrefix,
|
||||
"Expected to find prefix %s for container dest %s and tar asset path %s",
|
||||
data.expectedPrefix, data.containerDestPath, data.tarAssetName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindArchiveReaders(t *testing.T) {
|
||||
mounts := []types.MountPoint{
|
||||
{Name: "volA", Destination: "/mnt/A"}, //mount point
|
||||
{Name: "volAB", Destination: "/mnt/A/AB"}, //mount point
|
||||
{Name: "volB", Destination: "/mnt/B"}, //mount point
|
||||
{Name: "R/W", Destination: "/"}, //container base volume
|
||||
}
|
||||
|
||||
mockData := []MockCopyFromData{
|
||||
// case 1: Get all mount prefix
|
||||
{
|
||||
containerSourcePath: "/",
|
||||
expectedPrefices: []string{"/", "/mnt/A", "/mnt/B", "/mnt/A/AB"},
|
||||
expectedFilterSpecs: map[string]ReaderFilters{
|
||||
"/": {
|
||||
rebase: "",
|
||||
strip: "",
|
||||
exclude: []string{"mnt/A/", "mnt/B/", "mnt/A/AB/"},
|
||||
include: "",
|
||||
},
|
||||
"/mnt/A": {
|
||||
rebase: "mnt/A",
|
||||
strip: "",
|
||||
exclude: []string{"AB/"},
|
||||
include: "",
|
||||
},
|
||||
"/mnt/B": {
|
||||
rebase: "mnt/B",
|
||||
strip: "",
|
||||
exclude: []string{},
|
||||
include: "",
|
||||
},
|
||||
"/mnt/A/AB": {
|
||||
rebase: "mnt/A/AB",
|
||||
strip: "",
|
||||
exclude: []string{},
|
||||
include: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
containerSourcePath: "/mnt",
|
||||
expectedPrefices: []string{"/", "/mnt/A", "/mnt/B", "/mnt/A/AB"},
|
||||
expectedFilterSpecs: map[string]ReaderFilters{
|
||||
"/": {
|
||||
rebase: "mnt",
|
||||
strip: "mnt",
|
||||
exclude: []string{"mnt/A/", "mnt/B/", "mnt/A/AB/"},
|
||||
include: "",
|
||||
},
|
||||
"/mnt/A": {
|
||||
rebase: "mnt/A",
|
||||
strip: "",
|
||||
exclude: []string{"AB/"},
|
||||
include: "",
|
||||
},
|
||||
"/mnt/B": {
|
||||
rebase: "mnt/B",
|
||||
strip: "",
|
||||
exclude: []string{},
|
||||
include: "",
|
||||
},
|
||||
"/mnt/A/AB": {
|
||||
rebase: "mnt/A/AB",
|
||||
strip: "",
|
||||
exclude: []string{},
|
||||
include: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
containerSourcePath: "/mnt/",
|
||||
expectedPrefices: []string{"/", "/mnt/A", "/mnt/B", "/mnt/A/AB"},
|
||||
expectedFilterSpecs: map[string]ReaderFilters{
|
||||
"/": {
|
||||
rebase: "mnt",
|
||||
strip: "mnt",
|
||||
exclude: []string{"mnt/A/", "mnt/B/", "mnt/A/AB/"},
|
||||
include: "",
|
||||
},
|
||||
"/mnt/A": {
|
||||
rebase: "mnt/A",
|
||||
strip: "",
|
||||
exclude: []string{"AB/"},
|
||||
include: "",
|
||||
},
|
||||
"/mnt/B": {
|
||||
rebase: "mnt/B",
|
||||
strip: "",
|
||||
exclude: []string{},
|
||||
include: "",
|
||||
},
|
||||
"/mnt/A/AB": {
|
||||
rebase: "mnt/A/AB",
|
||||
strip: "",
|
||||
exclude: []string{},
|
||||
include: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
// case 2: Do not include /mnt/B
|
||||
{
|
||||
containerSourcePath: "/mnt/A",
|
||||
expectedPrefices: []string{"/", "/mnt/A", "/mnt/A/AB"},
|
||||
expectedFilterSpecs: map[string]ReaderFilters{
|
||||
"/": {
|
||||
rebase: "A",
|
||||
strip: "mnt/A",
|
||||
exclude: []string{"mnt/A/", "mnt/A/AB/"},
|
||||
include: "/mnt/A",
|
||||
},
|
||||
"/mnt/A": {
|
||||
rebase: "A",
|
||||
strip: "",
|
||||
exclude: []string{"AB/"},
|
||||
},
|
||||
"/mnt/A/AB": {
|
||||
rebase: "A/AB",
|
||||
exclude: []string{},
|
||||
include: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
// case 3: Return only the container base "/"
|
||||
{
|
||||
containerSourcePath: "/mnt/not-a-mount",
|
||||
expectedPrefices: []string{"/"},
|
||||
expectedFilterSpecs: map[string]ReaderFilters{
|
||||
"/": {
|
||||
rebase: "not-a-mount",
|
||||
strip: "mnt/not-a-mount",
|
||||
exclude: []string{""},
|
||||
include: "mnt/not-a-mount",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
containerSourcePath: "/etc/",
|
||||
expectedPrefices: []string{"/"},
|
||||
expectedFilterSpecs: map[string]ReaderFilters{
|
||||
"/": {
|
||||
rebase: "etc",
|
||||
strip: "etc",
|
||||
exclude: []string{""},
|
||||
include: "etc",
|
||||
},
|
||||
},
|
||||
},
|
||||
// case 4: Check inclusion filter
|
||||
{
|
||||
containerSourcePath: "/mnt/A/a/file.txt",
|
||||
expectedPrefices: []string{"/mnt/A"},
|
||||
expectedFilterSpecs: map[string]ReaderFilters{
|
||||
"/mnt/A": {
|
||||
rebase: "file.txt",
|
||||
strip: "a/file.txt",
|
||||
exclude: []string{""},
|
||||
include: "a/file.txt",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for i, data := range mockData {
|
||||
op := trace.NewOperation(context.Background(), "")
|
||||
readerMap := NewArchiveStreamReaderMap(op, mounts, data.containerSourcePath)
|
||||
archiveReaders, err := readerMap.FindArchiveReaders(data.containerSourcePath)
|
||||
assert.Nil(t, err, "Expected success from finding archive readers for container source %s", data.containerSourcePath)
|
||||
assert.NotNil(t, archiveReaders, "Expected an array of archive readers but got nil for container source path %s", data.containerSourcePath)
|
||||
assert.NotEmpty(t, archiveReaders, "Expected an array of archive readers %s with more than one items", data.containerSourcePath)
|
||||
|
||||
log.Debugf("Data = %#v", data)
|
||||
pa := PrefixArray(archiveReaders)
|
||||
nonOverlap := UnionMinusIntersection(pa, data.expectedPrefices)
|
||||
assert.Empty(t, nonOverlap, "Found mismatch in the prefix array and expected array for source path %s. Non-overlapped result = %#v", data.containerSourcePath, nonOverlap)
|
||||
|
||||
// Check filter spec
|
||||
for _, ar := range archiveReaders {
|
||||
currPath := ar.mountPoint.Destination
|
||||
assert.Equal(t, data.expectedFilterSpecs[currPath].rebase, ar.filterSpec.RebasePath, "rebase filterspec not correct")
|
||||
assert.Equal(t, data.expectedFilterSpecs[currPath].strip, ar.filterSpec.StripPath, "strip filterspec not correct")
|
||||
for _, ex := range data.expectedFilterSpecs[currPath].exclude {
|
||||
_, ok := ar.filterSpec.Exclusions[ex]
|
||||
assert.True(t, ok, "Did not find %s in exclusion map for reader %s in mock #%d", ex, currPath, i)
|
||||
}
|
||||
}
|
||||
|
||||
// Check inclusion filter
|
||||
if len(archiveReaders) == 1 {
|
||||
ar := archiveReaders[0]
|
||||
currPath := ar.mountPoint.Destination
|
||||
|
||||
expectedInclusion := data.expectedFilterSpecs[currPath].include
|
||||
|
||||
assert.Len(t, ar.filterSpec.Inclusions, 1, "Expected only 1 inclusion filter for %s but got %d in mock #%d", data.containerSourcePath, len(ar.filterSpec.Inclusions), i)
|
||||
|
||||
if len(ar.filterSpec.Inclusions) == 1 {
|
||||
_, ok := ar.filterSpec.Inclusions[expectedInclusion]
|
||||
assert.True(t, ok, "Expected inclusion filter to contain %s in mock #%d", expectedInclusion, i)
|
||||
|
||||
// Sanity check to make sure include isn't in exclusion. This should never happen.
|
||||
for _, ex := range data.expectedFilterSpecs[currPath].exclude {
|
||||
assert.NotEqual(t, expectedInclusion, ex, "Expected inclusion %s not to be in exclusion list %#v in mock #%d", expectedInclusion, ex, i)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func PrefixArray(readers []*ArchiveReader) (pa []string) {
|
||||
for _, reader := range readers {
|
||||
pa = append(pa, reader.mountPoint.Destination)
|
||||
}
|
||||
|
||||
log.Debugf("prefix array - %#v", pa)
|
||||
return
|
||||
}
|
||||
|
||||
func UnionMinusIntersection(A, B []string) (res []string) {
|
||||
test := make(map[string]bool)
|
||||
|
||||
log.Debugf("Looking for non overlapping in array A-%#v and array B-%#v", A, B)
|
||||
|
||||
for _, data := range A {
|
||||
test[data] = true
|
||||
}
|
||||
|
||||
for _, data := range B {
|
||||
if _, ok := test[data]; ok {
|
||||
delete(test, data)
|
||||
} else {
|
||||
res = append(res, data)
|
||||
}
|
||||
}
|
||||
|
||||
for key := range test {
|
||||
res = append(res, key)
|
||||
}
|
||||
|
||||
log.Debugf("Resulting non overlapped array - %#v", res)
|
||||
|
||||
return
|
||||
}
|
||||
543
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/backends.go
generated
vendored
Normal file
543
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/backends.go
generated
vendored
Normal file
@@ -0,0 +1,543 @@
|
||||
// Copyright 2016 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package backends
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/url"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/docker/docker/daemon/events"
|
||||
"github.com/go-openapi/runtime"
|
||||
rc "github.com/go-openapi/runtime/client"
|
||||
"github.com/go-openapi/swag"
|
||||
"golang.org/x/sync/singleflight"
|
||||
|
||||
"github.com/vmware/vic/lib/apiservers/engine/backends/cache"
|
||||
"github.com/vmware/vic/lib/apiservers/engine/backends/container"
|
||||
"github.com/vmware/vic/lib/apiservers/engine/network"
|
||||
"github.com/vmware/vic/lib/apiservers/engine/proxy"
|
||||
apiclient "github.com/vmware/vic/lib/apiservers/portlayer/client"
|
||||
"github.com/vmware/vic/lib/apiservers/portlayer/client/containers"
|
||||
"github.com/vmware/vic/lib/apiservers/portlayer/client/misc"
|
||||
"github.com/vmware/vic/lib/apiservers/portlayer/client/scopes"
|
||||
"github.com/vmware/vic/lib/apiservers/portlayer/client/storage"
|
||||
"github.com/vmware/vic/lib/apiservers/portlayer/models"
|
||||
"github.com/vmware/vic/lib/config"
|
||||
"github.com/vmware/vic/lib/config/dynamic"
|
||||
"github.com/vmware/vic/lib/config/dynamic/admiral"
|
||||
"github.com/vmware/vic/lib/constants"
|
||||
"github.com/vmware/vic/lib/imagec"
|
||||
"github.com/vmware/vic/pkg/errors"
|
||||
"github.com/vmware/vic/pkg/registry"
|
||||
"github.com/vmware/vic/pkg/vsphere/session"
|
||||
"github.com/vmware/vic/pkg/vsphere/sys"
|
||||
)
|
||||
|
||||
const (
|
||||
PortlayerName = "Backend Engine"
|
||||
|
||||
// RetryTimeSeconds defines how many seconds to wait between retries
|
||||
RetryTimeSeconds = 2
|
||||
defaultSessionKeepAlive = 20 * time.Second
|
||||
APITimeout = constants.PropertyCollectorTimeout + 3*time.Second
|
||||
)
|
||||
|
||||
var (
|
||||
portLayerClient *apiclient.PortLayer
|
||||
portLayerServerAddr string
|
||||
portLayerName string
|
||||
productName string
|
||||
productVersion string
|
||||
|
||||
vchConfig *dynConfig
|
||||
RegistryCertPool *x509.CertPool
|
||||
archiveProxy proxy.VicArchiveProxy
|
||||
|
||||
eventService *events.Events
|
||||
|
||||
servicePort uint
|
||||
)
|
||||
|
||||
type dynConfig struct {
|
||||
sync.Mutex
|
||||
|
||||
Cfg *config.VirtualContainerHostConfigSpec
|
||||
src dynamic.Source
|
||||
merger dynamic.Merger
|
||||
sess *session.Session
|
||||
|
||||
Whitelist, Blacklist, Insecure registry.Set
|
||||
remoteWl bool
|
||||
|
||||
group singleflight.Group
|
||||
lastCfg *dynConfig
|
||||
}
|
||||
|
||||
func Init(portLayerAddr, product string, port uint, config *config.VirtualContainerHostConfigSpec) error {
|
||||
servicePort = port
|
||||
_, _, err := net.SplitHostPort(portLayerAddr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if config == nil {
|
||||
return fmt.Errorf("docker API server requires VCH config")
|
||||
}
|
||||
|
||||
productName = product
|
||||
|
||||
if config.Version != nil {
|
||||
productVersion = config.Version.ShortVersion()
|
||||
}
|
||||
if productVersion == "" {
|
||||
portLayerName = product + " Backend Engine"
|
||||
} else {
|
||||
portLayerName = product + " " + productVersion + " Backend Engine"
|
||||
}
|
||||
|
||||
if vchConfig, err = newDynConfig(ctx, config); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
loadRegistryCACerts()
|
||||
|
||||
t := rc.New(portLayerAddr, "/", []string{"http"})
|
||||
t.Consumers["application/x-tar"] = runtime.ByteStreamConsumer()
|
||||
t.Consumers["application/octet-stream"] = runtime.ByteStreamConsumer()
|
||||
t.Producers["application/x-tar"] = runtime.ByteStreamProducer()
|
||||
t.Producers["application/octet-stream"] = runtime.ByteStreamProducer()
|
||||
|
||||
portLayerClient = apiclient.New(t, nil)
|
||||
portLayerServerAddr = portLayerAddr
|
||||
|
||||
log.Infof("*** Portlayer Address = %s", portLayerAddr)
|
||||
|
||||
// block indefinitely while waiting on the portlayer to respond to pings
|
||||
// the vic-machine installer timeout will intervene if this blocks for too long
|
||||
pingPortLayer()
|
||||
|
||||
if err := hydrateCaches(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Info("Creating image store")
|
||||
if err := createImageStore(); err != nil {
|
||||
log.Errorf("Failed to create image store")
|
||||
return err
|
||||
}
|
||||
|
||||
archiveProxy = proxy.NewArchiveProxy(portLayerClient)
|
||||
|
||||
eventService = events.New()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func hydrateCaches() error {
|
||||
const waiters = 3
|
||||
|
||||
wg := sync.WaitGroup{}
|
||||
wg.Add(waiters)
|
||||
errChan := make(chan error, waiters)
|
||||
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
if err := imagec.InitializeLayerCache(portLayerClient); err != nil {
|
||||
errChan <- fmt.Errorf("Failed to initialize layer cache: %s", err)
|
||||
return
|
||||
}
|
||||
log.Info("Layer cache initialized successfully")
|
||||
errChan <- nil
|
||||
}()
|
||||
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
if err := cache.InitializeImageCache(portLayerClient); err != nil {
|
||||
errChan <- fmt.Errorf("Failed to initialize image cache: %s", err)
|
||||
return
|
||||
}
|
||||
log.Info("Image cache initialized successfully")
|
||||
|
||||
// container cache relies on image cache so we share a goroutine to update
|
||||
// them serially
|
||||
if err := syncContainerCache(); err != nil {
|
||||
errChan <- fmt.Errorf("Failed to update container cache: %s", err)
|
||||
return
|
||||
}
|
||||
log.Info("Container cache updated successfully")
|
||||
errChan <- nil
|
||||
}()
|
||||
|
||||
go func() {
|
||||
log.Info("Refreshing repository cache")
|
||||
defer wg.Done()
|
||||
if err := cache.NewRepositoryCache(portLayerClient); err != nil {
|
||||
errChan <- fmt.Errorf("Failed to create repository cache: %s", err.Error())
|
||||
return
|
||||
}
|
||||
errChan <- nil
|
||||
log.Info("Repository cache updated successfully")
|
||||
}()
|
||||
|
||||
wg.Wait()
|
||||
close(errChan)
|
||||
|
||||
var errs []string
|
||||
for err := range errChan {
|
||||
if err != nil {
|
||||
// accumulate all errors into one
|
||||
errs = append(errs, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
var e error
|
||||
if len(errs) > 0 {
|
||||
e = fmt.Errorf(strings.Join(errs, ", "))
|
||||
}
|
||||
|
||||
if e != nil {
|
||||
log.Errorf("Errors occurred during cache hydration at VCH start: %s", e)
|
||||
}
|
||||
|
||||
return e
|
||||
}
|
||||
|
||||
func PortLayerClient() *apiclient.PortLayer {
|
||||
return portLayerClient
|
||||
}
|
||||
|
||||
func PortLayerServer() string {
|
||||
return portLayerServerAddr
|
||||
}
|
||||
|
||||
func PortLayerName() string {
|
||||
return portLayerName
|
||||
}
|
||||
|
||||
func ProductName() string {
|
||||
return productName
|
||||
}
|
||||
|
||||
func ProductVersion() string {
|
||||
return productVersion
|
||||
}
|
||||
|
||||
func pingPortLayer() {
|
||||
ticker := time.NewTicker(RetryTimeSeconds * time.Second)
|
||||
defer ticker.Stop()
|
||||
params := misc.NewPingParamsWithContext(context.TODO())
|
||||
|
||||
log.Infof("Waiting for portlayer to come up")
|
||||
|
||||
for range ticker.C {
|
||||
if _, err := portLayerClient.Misc.Ping(params); err == nil {
|
||||
log.Info("Portlayer is up and responding to pings")
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func createImageStore() error {
|
||||
// TODO(jzt): we should move this to a utility package or something
|
||||
host, err := sys.UUID()
|
||||
if err != nil {
|
||||
log.Errorf("Failed to determine host UUID")
|
||||
return err
|
||||
}
|
||||
|
||||
log.Infof("*** UUID = %s", host)
|
||||
|
||||
// attempt to create the image store if it doesn't exist
|
||||
store := &models.ImageStore{Name: host}
|
||||
_, err = portLayerClient.Storage.CreateImageStore(
|
||||
storage.NewCreateImageStoreParamsWithContext(ctx).WithBody(store),
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
if _, ok := err.(*storage.CreateImageStoreConflict); ok {
|
||||
log.Debugf("Store already exists")
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
log.Infof("Image store created successfully")
|
||||
return nil
|
||||
}
|
||||
|
||||
// syncContainerCache runs once at startup to populate the container cache
|
||||
func syncContainerCache() error {
|
||||
log.Debugf("Updating container cache")
|
||||
|
||||
backend := NewContainerBackend()
|
||||
client := PortLayerClient()
|
||||
|
||||
reqParams := containers.NewGetContainerListParamsWithContext(ctx).WithAll(swag.Bool(true))
|
||||
containme, err := client.Containers.GetContainerList(reqParams)
|
||||
if err != nil {
|
||||
return errors.Errorf("Failed to retrieve container list from portlayer: %s", err)
|
||||
}
|
||||
|
||||
log.Debugf("Found %d containers", len(containme.Payload))
|
||||
cc := cache.ContainerCache()
|
||||
var errs []string
|
||||
for _, info := range containme.Payload {
|
||||
container := proxy.ContainerInfoToVicContainer(*info, portLayerName)
|
||||
cc.AddContainer(container)
|
||||
if err = setPortMapping(info, backend, container); err != nil {
|
||||
errs = append(errs, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
if len(errs) > 0 {
|
||||
return errors.Errorf("Failed to set port mapping: %s", strings.Join(errs, "\n"))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func setPortMapping(info *models.ContainerInfo, backend *ContainerBackend, container *container.VicContainer) error {
|
||||
if info.ContainerConfig.State == "" {
|
||||
log.Infof("container state is nil")
|
||||
return nil
|
||||
}
|
||||
|
||||
if info.ContainerConfig.State != "Running" || len(container.HostConfig.PortBindings) == 0 {
|
||||
log.Infof("No need to restore port bindings, state: %s, portbinding: %+v", info.ContainerConfig.State, container.HostConfig.PortBindings)
|
||||
return nil
|
||||
}
|
||||
|
||||
log.Debugf("Set port mapping for container %q, portmapping %+v", container.Name, container.HostConfig.PortBindings)
|
||||
client := PortLayerClient()
|
||||
endpointsOK, err := client.Scopes.GetContainerEndpoints(
|
||||
scopes.NewGetContainerEndpointsParamsWithContext(ctx).WithHandleOrID(container.ContainerID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, e := range endpointsOK.Payload {
|
||||
if len(e.Ports) > 0 && e.Scope == constants.BridgeScopeType {
|
||||
if err = network.MapPorts(container, e, container.ContainerID); err != nil {
|
||||
log.Errorf(err.Error())
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func loadRegistryCACerts() {
|
||||
var err error
|
||||
|
||||
RegistryCertPool, err = x509.SystemCertPool()
|
||||
log.Debugf("Loaded %d CAs for registries from system CA bundle", len(RegistryCertPool.Subjects()))
|
||||
if err != nil {
|
||||
log.Errorf("Unable to load system CAs")
|
||||
return
|
||||
}
|
||||
|
||||
vchConfig.Lock()
|
||||
defer vchConfig.Unlock()
|
||||
if !RegistryCertPool.AppendCertsFromPEM(vchConfig.Cfg.RegistryCertificateAuthorities) {
|
||||
log.Errorf("Unable to load CAs for registry access in config")
|
||||
return
|
||||
}
|
||||
|
||||
log.Debugf("Loaded %d CAs for registries from config", len(RegistryCertPool.Subjects()))
|
||||
}
|
||||
|
||||
func EventService() *events.Events {
|
||||
return eventService
|
||||
}
|
||||
|
||||
// RegistryCheck checkes the given url against the registry whitelist, blacklist, and insecure
|
||||
// registries lists. It returns true for each list where u matches that list.
|
||||
func (d *dynConfig) RegistryCheck(ctx context.Context, u *url.URL) (wl bool, bl bool, insecure bool) {
|
||||
m := d.update(ctx)
|
||||
|
||||
us := u.String()
|
||||
wl = len(m.Whitelist) == 0 || m.Whitelist.Match(us)
|
||||
bl = len(m.Blacklist) == 0 || !m.Blacklist.Match(us)
|
||||
insecure = m.Insecure.Match(us)
|
||||
return
|
||||
}
|
||||
|
||||
func (d *dynConfig) update(ctx context.Context) *dynConfig {
|
||||
const key = "RegistryCheck"
|
||||
resCh := d.group.DoChan(key, func() (interface{}, error) {
|
||||
d.Lock()
|
||||
src := d.src
|
||||
d.Unlock()
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
|
||||
defer cancel()
|
||||
|
||||
c, err := src.Get(ctx)
|
||||
if err != nil {
|
||||
log.Warnf("error getting config from source: %s", err)
|
||||
}
|
||||
|
||||
d.Lock()
|
||||
defer d.Unlock()
|
||||
|
||||
m := d
|
||||
if c != nil {
|
||||
// update config
|
||||
if m, err = d.merged(c); err != nil {
|
||||
log.Errorf("error updating config: %s", err)
|
||||
m = d
|
||||
} else {
|
||||
if len(c.RegistryWhitelist) > 0 {
|
||||
m.remoteWl = true
|
||||
}
|
||||
}
|
||||
} else if err == nil && src == d.src {
|
||||
// err == nil and c == nil, which
|
||||
// indicates no remote sources
|
||||
// were found, try resetting the
|
||||
// source for next time
|
||||
if err := d.resetSrc(); err != nil {
|
||||
log.Warnf("could not reset config source: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
d.lastCfg = m
|
||||
return m, nil
|
||||
})
|
||||
|
||||
select {
|
||||
case res := <-resCh:
|
||||
return res.Val.(*dynConfig)
|
||||
case <-ctx.Done():
|
||||
return func() *dynConfig {
|
||||
d.Lock()
|
||||
defer d.Unlock()
|
||||
|
||||
if d.lastCfg == nil {
|
||||
return d
|
||||
}
|
||||
|
||||
return d.lastCfg
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
func (d *dynConfig) resetSrc() error {
|
||||
ep, err := d.clientEndpoint()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
d.src = admiral.NewSource(d.sess, ep.String())
|
||||
return nil
|
||||
}
|
||||
|
||||
func newDynConfig(ctx context.Context, c *config.VirtualContainerHostConfigSpec) (*dynConfig, error) {
|
||||
d := &dynConfig{
|
||||
Cfg: c,
|
||||
}
|
||||
var err error
|
||||
if d.Insecure, err = dynamic.ParseRegistries(c.InsecureRegistries); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if d.Whitelist, err = dynamic.ParseRegistries(c.RegistryWhitelist); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if d.sess, err = newSession(ctx, c); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
d.merger = dynamic.NewMerger()
|
||||
if err := d.resetSrc(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return d, nil
|
||||
}
|
||||
|
||||
// update merges another config into this config. d should be locked before
|
||||
// calling this.
|
||||
func (d *dynConfig) merged(c *config.VirtualContainerHostConfigSpec) (*dynConfig, error) {
|
||||
if c == nil {
|
||||
return d, nil
|
||||
}
|
||||
|
||||
newcfg, err := d.merger.Merge(d.Cfg, c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var wl, bl, insecure registry.Set
|
||||
if wl, err = dynamic.ParseRegistries(newcfg.RegistryWhitelist); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if bl, err = dynamic.ParseRegistries(newcfg.RegistryBlacklist); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if insecure, err = dynamic.ParseRegistries(newcfg.InsecureRegistries); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &dynConfig{
|
||||
Whitelist: wl,
|
||||
Blacklist: bl,
|
||||
Insecure: insecure,
|
||||
Cfg: newcfg,
|
||||
src: d.src,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (d *dynConfig) clientEndpoint() (*url.URL, error) {
|
||||
ips, err := net.LookupIP("client.localhost")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
scheme := "https"
|
||||
if d.Cfg.HostCertificate.IsNil() {
|
||||
scheme = "http"
|
||||
}
|
||||
|
||||
return url.Parse(fmt.Sprintf("%s://%s:%d", scheme, ips[0], servicePort))
|
||||
}
|
||||
|
||||
func newSession(ctx context.Context, config *config.VirtualContainerHostConfigSpec) (*session.Session, error) {
|
||||
// strip the path off of the target url since it may contain the
|
||||
// datacenter
|
||||
u, err := url.Parse(config.Target)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
u.Path = ""
|
||||
sessCfg := &session.Config{
|
||||
Service: u.String(),
|
||||
User: url.UserPassword(config.Username, config.Token),
|
||||
Thumbprint: config.TargetThumbprint,
|
||||
Keepalive: defaultSessionKeepAlive,
|
||||
}
|
||||
|
||||
sess := session.NewSession(sessCfg)
|
||||
if sess.Connect(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return sess, nil
|
||||
}
|
||||
32
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/build.go
generated
vendored
Normal file
32
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/build.go
generated
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
// 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")
|
||||
}
|
||||
199
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/cache/container_cache.go
generated
vendored
Normal file
199
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/cache/container_cache.go
generated
vendored
Normal file
@@ -0,0 +1,199 @@
|
||||
// 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 cache
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
|
||||
derr "github.com/docker/docker/api/errors"
|
||||
"github.com/docker/docker/pkg/truncindex"
|
||||
|
||||
"github.com/vmware/vic/lib/apiservers/engine/backends/container"
|
||||
)
|
||||
|
||||
// Tracks our container info from calls
|
||||
type CCache struct {
|
||||
m sync.RWMutex
|
||||
|
||||
idIndex *truncindex.TruncIndex
|
||||
containersByID map[string]*container.VicContainer
|
||||
containersByName map[string]*container.VicContainer
|
||||
containersByExecID map[string]*container.VicContainer
|
||||
}
|
||||
|
||||
var containerCache *CCache
|
||||
|
||||
func init() {
|
||||
containerCache = &CCache{
|
||||
idIndex: truncindex.NewTruncIndex([]string{}),
|
||||
containersByID: make(map[string]*container.VicContainer),
|
||||
containersByName: make(map[string]*container.VicContainer),
|
||||
containersByExecID: make(map[string]*container.VicContainer),
|
||||
}
|
||||
}
|
||||
|
||||
// ContainerCache returns a reference to the container cache
|
||||
func ContainerCache() *CCache {
|
||||
return containerCache
|
||||
}
|
||||
|
||||
func (cc *CCache) getContainerByName(nameOnly string) *container.VicContainer {
|
||||
if container, exist := cc.containersByName[nameOnly]; exist {
|
||||
return container
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cc *CCache) getContainer(nameOrID string) *container.VicContainer {
|
||||
// full name matching should take precedence over id prefix matching
|
||||
if container, exist := cc.containersByName[nameOrID]; exist {
|
||||
return container
|
||||
}
|
||||
|
||||
// get the full ID if we only have a prefix
|
||||
if cid, err := cc.idIndex.Get(nameOrID); err == nil {
|
||||
nameOrID = cid
|
||||
}
|
||||
|
||||
if container, exist := cc.containersByID[nameOrID]; exist {
|
||||
return container
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetContainerByName returns a container whose name "exactly" matches nameOnly
|
||||
func (cc *CCache) GetContainerByName(nameOnly string) *container.VicContainer {
|
||||
cc.m.RLock()
|
||||
defer cc.m.RUnlock()
|
||||
|
||||
return cc.getContainerByName(nameOnly)
|
||||
}
|
||||
|
||||
func (cc *CCache) GetContainer(nameOrID string) *container.VicContainer {
|
||||
cc.m.RLock()
|
||||
defer cc.m.RUnlock()
|
||||
|
||||
return cc.getContainer(nameOrID)
|
||||
}
|
||||
|
||||
func (cc *CCache) AddContainer(container *container.VicContainer) {
|
||||
cc.m.Lock()
|
||||
defer cc.m.Unlock()
|
||||
|
||||
// TODO(jzt): this probably shouldn't assume a valid container ID
|
||||
if err := cc.idIndex.Add(container.ContainerID); err != nil {
|
||||
log.Warnf("Error adding ID into index: %s", err)
|
||||
}
|
||||
cc.containersByID[container.ContainerID] = container
|
||||
cc.containersByName[container.Name] = container
|
||||
}
|
||||
|
||||
func (cc *CCache) DeleteContainer(nameOrID string) {
|
||||
cc.m.Lock()
|
||||
defer cc.m.Unlock()
|
||||
|
||||
container := cc.getContainer(nameOrID)
|
||||
if container == nil {
|
||||
return
|
||||
}
|
||||
|
||||
delete(cc.containersByID, container.ContainerID)
|
||||
delete(cc.containersByName, container.Name)
|
||||
|
||||
if err := cc.idIndex.Delete(container.ContainerID); err != nil {
|
||||
log.Warnf("Error deleting ID from index: %s", err)
|
||||
}
|
||||
|
||||
// remove exec references
|
||||
for _, id := range container.List() {
|
||||
container.Delete(id)
|
||||
}
|
||||
}
|
||||
|
||||
func (cc *CCache) AddExecToContainer(container *container.VicContainer, eid string) {
|
||||
cc.m.Lock()
|
||||
defer cc.m.Unlock()
|
||||
|
||||
// ignore if we already have it
|
||||
if _, ok := cc.containersByExecID[eid]; ok {
|
||||
return
|
||||
}
|
||||
|
||||
container.Add(eid)
|
||||
cc.containersByExecID[eid] = container
|
||||
}
|
||||
|
||||
func (cc *CCache) GetContainerFromExec(eid string) *container.VicContainer {
|
||||
cc.m.RLock()
|
||||
defer cc.m.RUnlock()
|
||||
|
||||
if container, exist := cc.containersByExecID[eid]; exist {
|
||||
return container
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateContainerName assumes that the newName is already reserved by ReserveName
|
||||
// so no need to check the existence of a container with the new name.
|
||||
func (cc *CCache) UpdateContainerName(oldName, newName string) error {
|
||||
cc.m.Lock()
|
||||
defer cc.m.Unlock()
|
||||
|
||||
container := cc.getContainer(oldName)
|
||||
if container == nil {
|
||||
return derr.NewRequestNotFoundError(fmt.Errorf("no such container: %s", oldName))
|
||||
}
|
||||
|
||||
delete(cc.containersByName, container.Name)
|
||||
|
||||
container.Name = newName
|
||||
cc.containersByName[newName] = container
|
||||
cc.containersByID[container.ContainerID] = container
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ReserveName is used during a container create/rename operation to prevent concurrent
|
||||
// container create/rename operations from grabbing the new name.
|
||||
func (cc *CCache) ReserveName(container *container.VicContainer, name string) error {
|
||||
cc.m.Lock()
|
||||
defer cc.m.Unlock()
|
||||
|
||||
if cont, exist := cc.containersByName[name]; exist {
|
||||
return fmt.Errorf("conflict. The name %q is already in use by container %s. You have to remove (or rename) that container to be able to re use that name.", name, cont.ContainerID)
|
||||
}
|
||||
|
||||
cc.containersByName[name] = container
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ReleaseName is used during a container rename operation to allow concurrent container
|
||||
// create/rename operations to use the name. It is also used during a failed create
|
||||
// operation to allow subsequent create operations to use that name.
|
||||
func (cc *CCache) ReleaseName(name string) {
|
||||
cc.m.Lock()
|
||||
defer cc.m.Unlock()
|
||||
|
||||
if _, exist := cc.containersByName[name]; !exist {
|
||||
log.Errorf("ReleaseName error: Name %s not found", name)
|
||||
return
|
||||
}
|
||||
|
||||
delete(cc.containersByName, name)
|
||||
}
|
||||
353
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/cache/image_cache.go
generated
vendored
Normal file
353
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/cache/image_cache.go
generated
vendored
Normal file
@@ -0,0 +1,353 @@
|
||||
// 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 cache
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
|
||||
"github.com/docker/distribution/digest"
|
||||
derr "github.com/docker/docker/api/errors"
|
||||
"github.com/docker/docker/pkg/truncindex"
|
||||
"github.com/docker/docker/reference"
|
||||
|
||||
"github.com/vmware/vic/lib/apiservers/engine/backends/kv"
|
||||
"github.com/vmware/vic/lib/apiservers/portlayer/client"
|
||||
"github.com/vmware/vic/lib/metadata"
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
)
|
||||
|
||||
// ICache is an in-memory cache of image metadata. It is refreshed at startup
|
||||
// by a call to the portlayer. It is updated when new images are pulled or
|
||||
// images are deleted.
|
||||
type ICache struct {
|
||||
m sync.RWMutex
|
||||
iDIndex *truncindex.TruncIndex
|
||||
cacheByID map[string]*metadata.ImageConfig
|
||||
cacheByName map[string]*metadata.ImageConfig
|
||||
dirty bool
|
||||
|
||||
client *client.PortLayer
|
||||
}
|
||||
|
||||
const (
|
||||
imageCacheKey = "images"
|
||||
)
|
||||
|
||||
var (
|
||||
imageCache *ICache
|
||||
)
|
||||
|
||||
// byCreated is a temporary type used to sort a list of images by creation
|
||||
// time.
|
||||
type byCreated []*metadata.ImageConfig
|
||||
|
||||
func (r byCreated) Len() int { return len(r) }
|
||||
func (r byCreated) Swap(i, j int) { r[i], r[j] = r[j], r[i] }
|
||||
func (r byCreated) Less(i, j int) bool { return r[i].Created.Unix() < r[j].Created.Unix() }
|
||||
|
||||
func init() {
|
||||
imageCache = &ICache{
|
||||
iDIndex: truncindex.NewTruncIndex(nil),
|
||||
cacheByID: make(map[string]*metadata.ImageConfig),
|
||||
cacheByName: make(map[string]*metadata.ImageConfig),
|
||||
}
|
||||
}
|
||||
|
||||
// ImageCache returns a reference to the image cache
|
||||
func ImageCache() *ICache {
|
||||
return imageCache
|
||||
}
|
||||
|
||||
// InitializeImageCache will create a new image cache or rehydrate an
|
||||
// existing image cache from the portlayer k/v store
|
||||
func InitializeImageCache(client *client.PortLayer) error {
|
||||
defer trace.End(trace.Begin(""))
|
||||
|
||||
imageCache.client = client
|
||||
|
||||
log.Debugf("Initializing image cache")
|
||||
|
||||
val, err := kv.Get(client, imageCacheKey)
|
||||
if err != nil && err != kv.ErrKeyNotFound {
|
||||
return err
|
||||
}
|
||||
|
||||
i := struct {
|
||||
IDIndex *truncindex.TruncIndex
|
||||
CacheByID map[string]*metadata.ImageConfig
|
||||
CacheByName map[string]*metadata.ImageConfig
|
||||
}{}
|
||||
|
||||
if val != "" {
|
||||
|
||||
if err = json.Unmarshal([]byte(val), &i); err != nil {
|
||||
return fmt.Errorf("Failed to unmarshal image cache: %s", err)
|
||||
}
|
||||
|
||||
// populate the trie with IDs
|
||||
for k := range i.CacheByID {
|
||||
// Separate out the hash prefix from the CacheByID key before indexing iDIndex
|
||||
// as it is keyed by the full image ID without the hash prefix.
|
||||
fields := strings.SplitN(k, ":", 2)
|
||||
if len(fields) == 2 {
|
||||
k = fields[1]
|
||||
}
|
||||
|
||||
imageCache.iDIndex.Add(k)
|
||||
}
|
||||
|
||||
imageCache.cacheByID = i.CacheByID
|
||||
imageCache.cacheByName = i.CacheByName
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetImages returns a slice containing metadata for all cached images
|
||||
func (ic *ICache) GetImages() []*metadata.ImageConfig {
|
||||
defer trace.End(trace.Begin(""))
|
||||
ic.m.RLock()
|
||||
defer ic.m.RUnlock()
|
||||
|
||||
result := make([]*metadata.ImageConfig, 0, len(ic.cacheByID))
|
||||
for _, image := range ic.cacheByID {
|
||||
result = append(result, copyImageConfig(image))
|
||||
}
|
||||
|
||||
sort.Sort(sort.Reverse(byCreated(result)))
|
||||
return result
|
||||
}
|
||||
|
||||
// IsImageID will check that a full or partial imageID
|
||||
// exists in the cache
|
||||
func (ic *ICache) IsImageID(id string) bool {
|
||||
ic.m.RLock()
|
||||
defer ic.m.RUnlock()
|
||||
if _, err := ic.iDIndex.Get(id); err == nil {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Get parses input to retrieve a cached image
|
||||
func (ic *ICache) Get(idOrRef string) (*metadata.ImageConfig, error) {
|
||||
defer trace.End(trace.Begin(idOrRef))
|
||||
ic.m.RLock()
|
||||
defer ic.m.RUnlock()
|
||||
|
||||
// cover the case of creating by a full reference
|
||||
if config, ok := ic.cacheByName[idOrRef]; ok {
|
||||
return copyImageConfig(config), nil
|
||||
}
|
||||
|
||||
// get the full image ID if supplied a prefix
|
||||
if id, err := ic.iDIndex.Get(idOrRef); err == nil {
|
||||
idOrRef = id
|
||||
}
|
||||
|
||||
imgDigest, named, err := reference.ParseIDOrReference(idOrRef)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var config *metadata.ImageConfig
|
||||
if imgDigest != "" {
|
||||
config = ic.getImageByDigest(imgDigest)
|
||||
} else {
|
||||
config = ic.getImageByNamed(named)
|
||||
}
|
||||
|
||||
if config == nil {
|
||||
// docker automatically prints out ":latest" tag if not specified in case if image is not found.
|
||||
postfixLatest := ""
|
||||
if !strings.Contains(idOrRef, ":") {
|
||||
postfixLatest += ":" + reference.DefaultTag
|
||||
}
|
||||
return nil, derr.NewRequestNotFoundError(fmt.Errorf(
|
||||
"No such image: %s%s", idOrRef, postfixLatest))
|
||||
}
|
||||
|
||||
return copyImageConfig(config), nil
|
||||
}
|
||||
|
||||
func (ic *ICache) getImageByDigest(digest digest.Digest) *metadata.ImageConfig {
|
||||
defer trace.End(trace.Begin(digest.String()))
|
||||
var config *metadata.ImageConfig
|
||||
config, ok := ic.cacheByID[string(digest)]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return copyImageConfig(config)
|
||||
}
|
||||
|
||||
// Looks up image by reference.Named
|
||||
func (ic *ICache) getImageByNamed(named reference.Named) *metadata.ImageConfig {
|
||||
defer trace.End(trace.Begin(""))
|
||||
// get the imageID from the repoCache
|
||||
// #nosec: Errors unhandled.
|
||||
id, _ := RepositoryCache().Get(named)
|
||||
return copyImageConfig(ic.cacheByID[prefixImageID(id)])
|
||||
}
|
||||
|
||||
// Add the default "sha256:" prefix to the image ID if it doesn't include a hash
|
||||
// prefix. Don't assume the image ID has "<hash>:<id> as format (e.g. "sha256:<id>").
|
||||
// We store it in this format to make it easier to lookup by digest.
|
||||
func prefixImageID(imageID string) string {
|
||||
if strings.Contains(imageID, ":") {
|
||||
return imageID
|
||||
}
|
||||
return digest.Canonical.String() + ":" + imageID
|
||||
}
|
||||
|
||||
// Add adds an image to the image cache
|
||||
func (ic *ICache) Add(imageConfig *metadata.ImageConfig) error {
|
||||
defer trace.End(trace.Begin(""))
|
||||
|
||||
ic.m.Lock()
|
||||
defer ic.m.Unlock()
|
||||
|
||||
imageID := prefixImageID(imageConfig.ImageID)
|
||||
err := ic.iDIndex.Add(imageConfig.ImageID)
|
||||
if err != nil && !os.IsExist(err) {
|
||||
return fmt.Errorf("error adding image %s to index: %s", imageID, err)
|
||||
}
|
||||
|
||||
err = nil
|
||||
|
||||
ic.cacheByID[imageID] = imageConfig
|
||||
ic.dirty = true
|
||||
|
||||
if imageConfig.Name == "" {
|
||||
log.Debugf("Image %s has no name", imageID)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Construct a reference after the image is added into cacheByID so that an image
|
||||
// without a name can at least be added to cacheByID.
|
||||
|
||||
// Normalize the name stored in imageConfig using Docker's reference code
|
||||
ref, err := reference.WithName(imageConfig.Name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error trying to create reference from %s: %s", imageConfig.Name, err)
|
||||
}
|
||||
|
||||
for _, tag := range imageConfig.Tags {
|
||||
ref, err = reference.WithTag(ref, tag)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error trying to create tagged reference from %s and tag %s: %s", imageConfig.Name, tag, err)
|
||||
}
|
||||
|
||||
ic.cacheByName[imageConfig.Reference] = imageConfig
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemoveImageByConfig removes image from the cache.
|
||||
func (ic *ICache) RemoveImageByConfig(imageConfig *metadata.ImageConfig) {
|
||||
defer trace.End(trace.Begin(""))
|
||||
|
||||
ic.m.Lock()
|
||||
defer ic.m.Unlock()
|
||||
|
||||
// If we get here we definitely want to remove image config from any data structure
|
||||
// where it can be present. So that, if there is something is wrong
|
||||
// it could be tracked on debug level.
|
||||
if err := ic.iDIndex.Delete(imageConfig.ImageID); err != nil {
|
||||
log.Debugf("Not found in image cache index: %v", err)
|
||||
}
|
||||
|
||||
prefixedID := prefixImageID(imageConfig.ImageID)
|
||||
delete(ic.cacheByID, prefixedID)
|
||||
delete(ic.cacheByName, imageConfig.Reference)
|
||||
|
||||
ic.dirty = true
|
||||
}
|
||||
|
||||
// Save will persist the image cache to the portlayer k/v store
|
||||
func (ic *ICache) Save() error {
|
||||
defer trace.End(trace.Begin(""))
|
||||
ic.m.Lock()
|
||||
defer ic.m.Unlock()
|
||||
|
||||
if !ic.dirty {
|
||||
return nil
|
||||
}
|
||||
|
||||
m := struct {
|
||||
IDIndex *truncindex.TruncIndex
|
||||
CacheByID map[string]*metadata.ImageConfig
|
||||
CacheByName map[string]*metadata.ImageConfig
|
||||
}{
|
||||
ic.iDIndex,
|
||||
ic.cacheByID,
|
||||
ic.cacheByName,
|
||||
}
|
||||
|
||||
bytes, err := json.Marshal(m)
|
||||
if err != nil {
|
||||
log.Errorf("Unable to marshal image cache: %s", err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
err = kv.Put(ic.client, imageCacheKey, string(bytes))
|
||||
if err != nil {
|
||||
log.Errorf("Unable to save image cache: %s", err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
ic.dirty = false
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// copyImageConfig performs and returns deep copy of an ImageConfig struct
|
||||
func copyImageConfig(image *metadata.ImageConfig) *metadata.ImageConfig {
|
||||
|
||||
if image == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// copy everything
|
||||
newImage := *image
|
||||
|
||||
// replace the pointer to metadata.ImageConfig.Config and copy the contents
|
||||
newConfig := *image.Config
|
||||
newImage.Config = &newConfig
|
||||
|
||||
// get tags and digests from repo
|
||||
tags := RepositoryCache().Tags(newImage.ImageID)
|
||||
digests := RepositoryCache().Digests(newImage.ImageID)
|
||||
|
||||
// if image has neither then set <none> vals
|
||||
if len(tags) == 0 && len(digests) == 0 {
|
||||
tags = append(tags, "<none>:<none>")
|
||||
digests = append(digests, "<none>@<none>")
|
||||
}
|
||||
|
||||
newImage.Tags = tags
|
||||
if digests != nil {
|
||||
newImage.Digests = digests
|
||||
}
|
||||
|
||||
return &newImage
|
||||
}
|
||||
413
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/cache/repo_cache.go
generated
vendored
Normal file
413
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/cache/repo_cache.go
generated
vendored
Normal file
@@ -0,0 +1,413 @@
|
||||
// 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 (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sort"
|
||||
"sync"
|
||||
|
||||
"github.com/vmware/vic/lib/apiservers/engine/backends/kv"
|
||||
"github.com/vmware/vic/lib/apiservers/portlayer/client"
|
||||
|
||||
"github.com/docker/distribution/digest"
|
||||
"github.com/docker/docker/reference"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
)
|
||||
|
||||
// repoCache is a cache of the docker repository information.
|
||||
// This info will help to provide proper tag and digest support
|
||||
//
|
||||
// The cache will be persisted to disk via the portlayer k/v
|
||||
// store and will be restored at system start
|
||||
//
|
||||
// This code is a heavy leverage of docker's reference store:
|
||||
// github.com/docker/docker/reference/store.go
|
||||
|
||||
var (
|
||||
rCache *repoCache
|
||||
repoKey = "repositories"
|
||||
)
|
||||
|
||||
// Repo provides the set of methods which can operate on a tag store.
|
||||
type Repo interface {
|
||||
References(imageID string) []reference.Named
|
||||
ReferencesByName(ref reference.Named) []Association
|
||||
Delete(ref reference.Named, save bool) (bool, error)
|
||||
Get(ref reference.Named) (string, error)
|
||||
|
||||
Save() error
|
||||
GetImageID(layerID string) string
|
||||
Tags(imageID string) []string
|
||||
Digests(imageID string) []string
|
||||
AddReference(ref reference.Named, imageID string, force bool, layerID string, save bool) error
|
||||
|
||||
// Remove will remove from the cache and returns the
|
||||
// stringified Named if successful -- save bool instructs
|
||||
// func to persist to portlayer k/v or not
|
||||
Remove(ref string, save bool) (string, error)
|
||||
}
|
||||
|
||||
type repoCache struct {
|
||||
// client is needed for k/v store operations
|
||||
client *client.PortLayer
|
||||
|
||||
mu sync.RWMutex
|
||||
// repositories is a map of repositories, indexed by name.
|
||||
Repositories map[string]repository
|
||||
// referencesByIDCache is a cache of references indexed by imageID
|
||||
referencesByIDCache map[string]map[string]reference.Named
|
||||
// Layers is a map of layerIDs to imageIDs
|
||||
// TODO: we might be able to remove this later -- currently
|
||||
// needed because an ImageID isn't generated for every pull
|
||||
Layers map[string]string
|
||||
// images is a map of imageIDs to layerIDs
|
||||
// TODO: much like the Layers map this might be able to be
|
||||
// removed
|
||||
images map[string]string
|
||||
}
|
||||
|
||||
// Repository maps tags to image IDs. The key is a a stringified Reference,
|
||||
// including the repository name.
|
||||
type repository map[string]string
|
||||
|
||||
var (
|
||||
// ErrDoesNotExist returned if a reference is not found in the
|
||||
// store.
|
||||
ErrDoesNotExist = errors.New("reference does not exist")
|
||||
)
|
||||
|
||||
// An Association is a tuple associating a reference with an image ID.
|
||||
type Association struct {
|
||||
Ref reference.Named
|
||||
ImageID string
|
||||
}
|
||||
|
||||
type lexicalRefs []reference.Named
|
||||
|
||||
func (a lexicalRefs) Len() int { return len(a) }
|
||||
func (a lexicalRefs) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
func (a lexicalRefs) Less(i, j int) bool { return a[i].String() < a[j].String() }
|
||||
|
||||
type lexicalAssociations []Association
|
||||
|
||||
func (a lexicalAssociations) Len() int { return len(a) }
|
||||
func (a lexicalAssociations) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
func (a lexicalAssociations) Less(i, j int) bool { return a[i].Ref.String() < a[j].Ref.String() }
|
||||
|
||||
// RepositoryCache returns a ref to the repoCache interface
|
||||
func RepositoryCache() Repo {
|
||||
return rCache
|
||||
}
|
||||
|
||||
func init() {
|
||||
rCache = &repoCache{
|
||||
Repositories: make(map[string]repository),
|
||||
Layers: make(map[string]string),
|
||||
images: make(map[string]string),
|
||||
referencesByIDCache: make(map[string]map[string]reference.Named),
|
||||
}
|
||||
}
|
||||
|
||||
// NewRespositoryCache will create a new repoCache or rehydrate
|
||||
// an existing repoCache from the portlayer k/v store
|
||||
func NewRepositoryCache(client *client.PortLayer) error {
|
||||
rCache.client = client
|
||||
|
||||
val, err := kv.Get(client, repoKey)
|
||||
if err != nil && err != kv.ErrKeyNotFound {
|
||||
return err
|
||||
}
|
||||
if val != "" {
|
||||
if err = json.Unmarshal([]byte(val), rCache); err != nil {
|
||||
return fmt.Errorf("Failed to unmarshal repository cache: %s", err)
|
||||
}
|
||||
// hydrate refByIDCache
|
||||
for _, repository := range rCache.Repositories {
|
||||
for refStr, refID := range repository {
|
||||
// #nosec: Errors unhandled.
|
||||
ref, _ := reference.ParseNamed(refStr)
|
||||
if rCache.referencesByIDCache[refID] == nil {
|
||||
rCache.referencesByIDCache[refID] = make(map[string]reference.Named)
|
||||
}
|
||||
rCache.referencesByIDCache[refID][refStr] = ref
|
||||
}
|
||||
}
|
||||
// hydrate image -> layer cache
|
||||
for image, layer := range rCache.Layers {
|
||||
rCache.images[image] = layer
|
||||
}
|
||||
|
||||
log.Infof("found %d repositories", len(rCache.Repositories))
|
||||
log.Infof("found %d image layers", len(rCache.Layers))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Save will persist the repository cache to the
|
||||
// portlayer k/v
|
||||
func (store *repoCache) Save() error {
|
||||
b, err := json.Marshal(store)
|
||||
if err != nil {
|
||||
log.Errorf("Unable to marshal repository cache: %s", err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
err = kv.Put(store.client, repoKey, string(b))
|
||||
if err != nil {
|
||||
log.Errorf("Unable to save repository cache: %s", err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (store *repoCache) AddReference(ref reference.Named, imageID string, force bool, layerID string, save bool) error {
|
||||
if ref.Name() == string(digest.Canonical) {
|
||||
return errors.New("refusing to create an ambiguous tag using digest algorithm as name")
|
||||
}
|
||||
var err error
|
||||
store.mu.Lock()
|
||||
defer store.mu.Unlock()
|
||||
|
||||
// does this repo (i.e. busybox) exist?
|
||||
repository, exists := store.Repositories[ref.Name()]
|
||||
if !exists || repository == nil {
|
||||
repository = make(map[string]string)
|
||||
store.Repositories[ref.Name()] = repository
|
||||
}
|
||||
|
||||
refStr := ref.String()
|
||||
oldID, exists := repository[refStr]
|
||||
|
||||
if exists {
|
||||
if oldID == imageID {
|
||||
log.Debugf("Image %s is already tagged as %s", oldID, ref.String())
|
||||
return nil
|
||||
}
|
||||
|
||||
// force only works for tags
|
||||
if digested, isDigest := ref.(reference.Canonical); isDigest {
|
||||
log.Debugf("Unable to overwrite %s with digest %s", oldID, digested.Digest().String())
|
||||
|
||||
return fmt.Errorf("Cannot overwrite digest %s", digested.Digest().String())
|
||||
}
|
||||
|
||||
if !force {
|
||||
log.Debugf("Refusing to overwrite %s with %s unless force is specified", oldID, ref.String())
|
||||
|
||||
return fmt.Errorf("Conflict: Tag %s is already set to image %s, if you want to replace it, please use -f option", ref.String(), oldID)
|
||||
}
|
||||
|
||||
if store.referencesByIDCache[oldID] != nil {
|
||||
delete(store.referencesByIDCache[oldID], refStr)
|
||||
if len(store.referencesByIDCache[oldID]) == 0 {
|
||||
delete(store.referencesByIDCache, oldID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
repository[refStr] = imageID
|
||||
if store.referencesByIDCache[imageID] == nil {
|
||||
store.referencesByIDCache[imageID] = make(map[string]reference.Named)
|
||||
}
|
||||
store.referencesByIDCache[imageID][refStr] = ref
|
||||
|
||||
if layerID != "" {
|
||||
store.Layers[layerID] = imageID
|
||||
store.images[imageID] = layerID
|
||||
}
|
||||
// should we save this input?
|
||||
if save {
|
||||
err = store.Save()
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// Remove is a convenience function to allow the passing of a properly
|
||||
// formed string that can be parsed into a Named object.
|
||||
//
|
||||
// Examples:
|
||||
// Tags: busybox:1.25.1
|
||||
// Digest: nginx@sha256:7281cf7c854b0dfc7c68a6a4de9a785a973a14f1481bc028e2022bcd6a8d9f64
|
||||
func (store *repoCache) Remove(ref string, save bool) (string, error) {
|
||||
n, err := reference.ParseNamed(ref)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
_, err = store.Delete(n, save)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return n.String(), nil
|
||||
}
|
||||
|
||||
// Delete deletes a reference from the store. It returns true if a deletion
|
||||
// happened, or false otherwise.
|
||||
func (store *repoCache) Delete(ref reference.Named, save bool) (bool, error) {
|
||||
ref = reference.WithDefaultTag(ref)
|
||||
|
||||
store.mu.Lock()
|
||||
defer store.mu.Unlock()
|
||||
var err error
|
||||
// return code -- assume success
|
||||
rtc := true
|
||||
repoName := ref.Name()
|
||||
|
||||
repository, exists := store.Repositories[repoName]
|
||||
if !exists {
|
||||
return false, ErrDoesNotExist
|
||||
}
|
||||
refStr := ref.String()
|
||||
if imageID, exists := repository[refStr]; exists {
|
||||
delete(repository, refStr)
|
||||
if len(repository) == 0 {
|
||||
delete(store.Repositories, repoName)
|
||||
}
|
||||
if store.referencesByIDCache[imageID] != nil {
|
||||
delete(store.referencesByIDCache[imageID], refStr)
|
||||
if len(store.referencesByIDCache[imageID]) == 0 {
|
||||
delete(store.referencesByIDCache, imageID)
|
||||
}
|
||||
}
|
||||
if layer, exists := store.images[imageID]; exists {
|
||||
delete(store.Layers, imageID)
|
||||
delete(store.images, layer)
|
||||
}
|
||||
if save {
|
||||
err = store.Save()
|
||||
if err != nil {
|
||||
rtc = false
|
||||
}
|
||||
}
|
||||
return rtc, err
|
||||
}
|
||||
|
||||
return false, ErrDoesNotExist
|
||||
}
|
||||
|
||||
// GetImageID will return the imageID associated with the
|
||||
// specified layerID
|
||||
func (store *repoCache) GetImageID(layerID string) string {
|
||||
var imageID string
|
||||
store.mu.RLock()
|
||||
defer store.mu.RUnlock()
|
||||
if image, exists := store.Layers[layerID]; exists {
|
||||
imageID = image
|
||||
}
|
||||
return imageID
|
||||
}
|
||||
|
||||
// Get returns the imageID for a parsed reference
|
||||
func (store *repoCache) Get(ref reference.Named) (string, error) {
|
||||
ref = reference.WithDefaultTag(ref)
|
||||
|
||||
store.mu.RLock()
|
||||
defer store.mu.RUnlock()
|
||||
|
||||
repository, exists := store.Repositories[ref.Name()]
|
||||
if !exists || repository == nil {
|
||||
return "", ErrDoesNotExist
|
||||
}
|
||||
imageID, exists := repository[ref.String()]
|
||||
if !exists {
|
||||
return "", ErrDoesNotExist
|
||||
}
|
||||
|
||||
return imageID, nil
|
||||
}
|
||||
|
||||
// Tags returns a slice of tags for the specified imageID
|
||||
func (store *repoCache) Tags(imageID string) []string {
|
||||
store.mu.RLock()
|
||||
defer store.mu.RUnlock()
|
||||
var tags []string
|
||||
for _, ref := range store.referencesByIDCache[imageID] {
|
||||
if tagged, isTagged := ref.(reference.NamedTagged); isTagged {
|
||||
tags = append(tags, tagged.String())
|
||||
}
|
||||
}
|
||||
return tags
|
||||
}
|
||||
|
||||
// Digests returns a slice of digests for the specified imageID
|
||||
func (store *repoCache) Digests(imageID string) []string {
|
||||
store.mu.RLock()
|
||||
defer store.mu.RUnlock()
|
||||
var digests []string
|
||||
for _, ref := range store.referencesByIDCache[imageID] {
|
||||
if d, isCanonical := ref.(reference.Canonical); isCanonical {
|
||||
digests = append(digests, d.String())
|
||||
}
|
||||
}
|
||||
return digests
|
||||
}
|
||||
|
||||
// References returns a slice of references to the given imageID. The slice
|
||||
// will be nil if there are no references to this imageID.
|
||||
func (store *repoCache) References(imageID string) []reference.Named {
|
||||
store.mu.RLock()
|
||||
defer store.mu.RUnlock()
|
||||
|
||||
// Convert the internal map to an array for two reasons:
|
||||
// 1) We must not return a mutable
|
||||
// 2) It would be ugly to expose the extraneous map keys to callers.
|
||||
|
||||
var references []reference.Named
|
||||
for _, ref := range store.referencesByIDCache[imageID] {
|
||||
references = append(references, ref)
|
||||
}
|
||||
|
||||
sort.Sort(lexicalRefs(references))
|
||||
|
||||
return references
|
||||
}
|
||||
|
||||
// ReferencesByName returns the references for a given repository name.
|
||||
// If there are no references known for this repository name,
|
||||
// ReferencesByName returns nil.
|
||||
func (store *repoCache) ReferencesByName(ref reference.Named) []Association {
|
||||
store.mu.RLock()
|
||||
defer store.mu.RUnlock()
|
||||
|
||||
repository, exists := store.Repositories[ref.Name()]
|
||||
if !exists {
|
||||
return nil
|
||||
}
|
||||
|
||||
var associations []Association
|
||||
for refStr, refID := range repository {
|
||||
ref, err := reference.ParseNamed(refStr)
|
||||
if err != nil {
|
||||
// Should never happen
|
||||
return nil
|
||||
}
|
||||
associations = append(associations,
|
||||
Association{
|
||||
Ref: ref,
|
||||
ImageID: refID,
|
||||
})
|
||||
}
|
||||
|
||||
sort.Sort(lexicalAssociations(associations))
|
||||
|
||||
return associations
|
||||
}
|
||||
104
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/cache/repo_cache_test.go
generated
vendored
Normal file
104
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/cache/repo_cache_test.go
generated
vendored
Normal file
@@ -0,0 +1,104 @@
|
||||
// Copyright 2016 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package cache
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/vmware/vic/lib/apiservers/portlayer/client"
|
||||
"github.com/vmware/vic/pkg/uid"
|
||||
|
||||
"github.com/docker/docker/reference"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func repoSetup() {
|
||||
rCache = &repoCache{
|
||||
client: &client.PortLayer{},
|
||||
Repositories: make(map[string]repository),
|
||||
Layers: make(map[string]string),
|
||||
images: make(map[string]string),
|
||||
referencesByIDCache: make(map[string]map[string]reference.Named),
|
||||
}
|
||||
}
|
||||
|
||||
func TestRepo(t *testing.T) {
|
||||
repoSetup()
|
||||
|
||||
notInRepo, _ := reference.ParseNamed("alpine")
|
||||
noImageID := uid.New()
|
||||
ref, _ := reference.ParseNamed("busybox:1.25.1")
|
||||
imageID := uid.New()
|
||||
layerID := uid.New()
|
||||
|
||||
// add busybox:1.25.1
|
||||
err := RepositoryCache().AddReference(ref, imageID.String(), false, layerID.String(), false)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Get will return the imageID for the named object
|
||||
n, err := RepositoryCache().Get(ref)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, imageID.String(), n)
|
||||
|
||||
// Get all references
|
||||
refs := RepositoryCache().References(imageID.String())
|
||||
assert.Equal(t, 1, len(refs))
|
||||
|
||||
// Get reference by Named
|
||||
associated := RepositoryCache().ReferencesByName(ref)
|
||||
assert.Equal(t, 1, len(associated))
|
||||
|
||||
// Get tags for image
|
||||
tags := RepositoryCache().Tags(imageID.String())
|
||||
assert.Equal(t, 1, len(tags))
|
||||
|
||||
// Get references for non-existent image
|
||||
refs = RepositoryCache().References(noImageID.String())
|
||||
assert.Equal(t, 0, len(refs))
|
||||
|
||||
// Get reference by Named
|
||||
associated = RepositoryCache().ReferencesByName(notInRepo)
|
||||
assert.Equal(t, 0, len(associated))
|
||||
|
||||
// get image id via layer id
|
||||
ig := RepositoryCache().GetImageID(layerID.String())
|
||||
assert.Equal(t, imageID.String(), ig)
|
||||
|
||||
// remove busybox from the cache
|
||||
r, err := RepositoryCache().Remove(ref.String(), false)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, ref.String(), r)
|
||||
|
||||
// busybox is removed, so this should fail
|
||||
x, err := RepositoryCache().Remove(ref.String(), false)
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, "", x)
|
||||
|
||||
// add reference by digest
|
||||
ng, _ := reference.ParseNamed("nginx@sha256:7281cf7c854b0dfc7c68a6a4de9a785a973a14f1481bc028e2022bcd6a8d9f64")
|
||||
err = RepositoryCache().AddReference(ng, imageID.String(), true, layerID.String(), false)
|
||||
assert.NoError(t, err)
|
||||
|
||||
dd := RepositoryCache().Digests(imageID.String())
|
||||
assert.Equal(t, 1, len(dd))
|
||||
// remove the digest
|
||||
ngx, err := RepositoryCache().Remove(ng.String(), false)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, ng.String(), ngx)
|
||||
// nada
|
||||
nada := RepositoryCache().Digests(imageID.String())
|
||||
assert.Equal(t, 0, len(nada))
|
||||
|
||||
}
|
||||
40
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/checkpoint.go
generated
vendored
Normal file
40
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/checkpoint.go
generated
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
// Copyright 2017 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package backends
|
||||
|
||||
import (
|
||||
"github.com/docker/docker/api/types"
|
||||
|
||||
"github.com/vmware/vic/lib/apiservers/engine/errors"
|
||||
)
|
||||
|
||||
type CheckpointBackend struct {
|
||||
}
|
||||
|
||||
func NewCheckpointBackend() *CheckpointBackend {
|
||||
return &CheckpointBackend{}
|
||||
}
|
||||
|
||||
func (c *CheckpointBackend) CheckpointCreate(container string, config types.CheckpointCreateOptions) error {
|
||||
return errors.APINotSupportedMsg(ProductName(), "checkpointing")
|
||||
}
|
||||
|
||||
func (c *CheckpointBackend) CheckpointDelete(container string, config types.CheckpointDeleteOptions) error {
|
||||
return errors.APINotSupportedMsg(ProductName(), "checkpointing")
|
||||
}
|
||||
|
||||
func (c *CheckpointBackend) CheckpointList(container string, config types.CheckpointListOptions) ([]types.Checkpoint, error) {
|
||||
return nil, errors.APINotSupportedMsg(ProductName(), "checkpointing")
|
||||
}
|
||||
479
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/commit.go
generated
vendored
Normal file
479
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/commit.go
generated
vendored
Normal file
@@ -0,0 +1,479 @@
|
||||
// Copyright 2016-2017 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package backends
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"compress/gzip"
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
|
||||
"github.com/docker/distribution/digest"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/backend"
|
||||
containertypes "github.com/docker/docker/api/types/container"
|
||||
eventtypes "github.com/docker/docker/api/types/events"
|
||||
"github.com/docker/docker/builder/dockerfile"
|
||||
dockerimage "github.com/docker/docker/image"
|
||||
dockerLayer "github.com/docker/docker/layer"
|
||||
"github.com/docker/docker/pkg/progress"
|
||||
"github.com/docker/docker/pkg/streamformatter"
|
||||
"github.com/docker/docker/pkg/stringid"
|
||||
"github.com/docker/docker/reference"
|
||||
|
||||
"github.com/vmware/vic/lib/apiservers/engine/backends/cache"
|
||||
"github.com/vmware/vic/lib/apiservers/engine/errors"
|
||||
"github.com/vmware/vic/lib/apiservers/portlayer/models"
|
||||
"github.com/vmware/vic/lib/constants"
|
||||
"github.com/vmware/vic/lib/imagec"
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
"github.com/vmware/vic/pkg/version"
|
||||
"github.com/vmware/vic/pkg/vsphere/sys"
|
||||
)
|
||||
|
||||
// Commit creates a new filesystem image from the current state of a container.
|
||||
// The image can optionally be tagged into a repository.
|
||||
func (i *ImageBackend) Commit(name string, config *backend.ContainerCommitConfig) (imageID string, err error) {
|
||||
defer trace.End(trace.Begin(name))
|
||||
op := trace.NewOperation(context.Background(), "Commit")
|
||||
// Look up the container name in the metadata cache to get long ID
|
||||
vc := cache.ContainerCache().GetContainer(name)
|
||||
if vc == nil {
|
||||
return "", errors.NotFoundError(name)
|
||||
}
|
||||
|
||||
// get container info
|
||||
c, err := containerEngine.ContainerInspect(name, false, "")
|
||||
if err != nil {
|
||||
return "", errors.InternalServerError(err.Error())
|
||||
}
|
||||
container, ok := c.(*types.ContainerJSON)
|
||||
if !ok {
|
||||
return "", errors.InternalServerError(fmt.Sprintf("Container type assertion failed"))
|
||||
}
|
||||
if container.State.Running || container.State.Restarting {
|
||||
return "", errors.ConflictError(fmt.Sprintf("%s does not yet support commit of a running container", ProductName()))
|
||||
}
|
||||
// TODO: pause container after container.Pause is implemented
|
||||
newConfig, err := dockerfile.BuildFromConfig(config.Config, config.Changes)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if config.MergeConfigs {
|
||||
if err := merge(newConfig, container.Config); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
ic, err := getImagec(config)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
rc, err := containerEngine.GetContainerChanges(op, vc, true)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Unable to initialize export stream reader for container %s", name)
|
||||
}
|
||||
|
||||
layer, err := downloadDiff(rc, container.ID, ic.Options)
|
||||
if err != nil {
|
||||
rc.Close()
|
||||
return "", fmt.Errorf("Unable to export stream reader for container %s: %s", name, err)
|
||||
}
|
||||
// close reader before write image to avoid resource conflict
|
||||
rc.Close()
|
||||
if err = setLayerConfig(layer, container, config, newConfig); err != nil {
|
||||
return "", err
|
||||
}
|
||||
// Dump metadata next to diff file
|
||||
destination := path.Join(imagec.DestinationDirectory(ic.Options), layer.ID)
|
||||
err = ioutil.WriteFile(path.Join(destination, layer.ID+".json"), []byte(layer.Meta), 0644)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
imagec.LayerCache().Add(layer)
|
||||
|
||||
var layers []*imagec.ImageWithMeta
|
||||
|
||||
layers = append(layers, layer)
|
||||
lm := layer
|
||||
for pl := lm.Parent; pl != constants.ScratchLayerID; pl = lm.Parent {
|
||||
// populate manifest layer with existing cached data
|
||||
if lm, err = imagec.LayerCache().Get(pl); err != nil {
|
||||
return "", errors.InternalServerError(fmt.Sprintf("Failed to get parent image layer %s: %s", pl, err))
|
||||
}
|
||||
layers = append(layers, lm)
|
||||
}
|
||||
|
||||
ic.ImageLayers = layers
|
||||
imageConfig, err := ic.CreateImageConfig(layers)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
imageConfig.Name = config.Repo
|
||||
// place calculated ImageID in struct
|
||||
ic.ImageID = imageConfig.ImageID
|
||||
|
||||
// cache and persist the image
|
||||
if err = cache.ImageCache().Add(&imageConfig); err != nil {
|
||||
return "", fmt.Errorf("error adding image %s to image cache: %s", ic.ImageID, err)
|
||||
}
|
||||
if err = cache.ImageCache().Save(); err != nil {
|
||||
return "", fmt.Errorf("error saving image cache: %s", err)
|
||||
}
|
||||
// if repo:tag is specified, update image to repo cache, otherwise, this image will be updated to repo cache while it's tagged
|
||||
if ic.Reference != nil {
|
||||
imagec.UpdateRepoCache(ic)
|
||||
}
|
||||
|
||||
ic.Storename = layer.Image.Store
|
||||
// Write blob to the storage layer
|
||||
if err = ic.WriteImageBlob(layer, progress.DiscardOutput(), true); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
imagec.LayerCache().Commit(layer)
|
||||
|
||||
refName := ""
|
||||
if ic.Reference != nil {
|
||||
refName = ic.Reference.String()
|
||||
}
|
||||
actor := CreateImageEventActorWithAttributes(imageConfig.ImageID, refName, map[string]string{})
|
||||
EventService().Log("commit", eventtypes.ImageEventType, actor)
|
||||
return imageConfig.ImageID, nil
|
||||
}
|
||||
|
||||
func getImagec(config *backend.ContainerCommitConfig) (*imagec.ImageC, error) {
|
||||
var imageRef reference.Named
|
||||
var err error
|
||||
|
||||
if config.Repo != "" {
|
||||
imageRef, err = reference.WithName(config.Repo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if config.Tag != "" {
|
||||
if imageRef, err = reference.WithTag(imageRef, config.Tag); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
options := imagec.Options{
|
||||
Destination: os.TempDir(),
|
||||
Reference: imageRef,
|
||||
Tag: config.Tag,
|
||||
}
|
||||
portLayerServer := PortLayerServer()
|
||||
|
||||
if portLayerServer != "" {
|
||||
options.Host = portLayerServer
|
||||
}
|
||||
|
||||
ic := imagec.NewImageC(options, streamformatter.NewJSONStreamFormatter())
|
||||
if imageRef != nil {
|
||||
ic.ParseReference()
|
||||
}
|
||||
|
||||
return ic, nil
|
||||
}
|
||||
|
||||
func setLayerConfig(lm *imagec.ImageWithMeta, container *types.ContainerJSON, config *backend.ContainerCommitConfig, newConfig *containertypes.Config) error {
|
||||
defer trace.End(trace.Begin(lm.ID))
|
||||
|
||||
// Host is either the host's UUID (if run on vsphere) or the hostname of
|
||||
// the system (if run standalone)
|
||||
host, err := sys.UUID()
|
||||
if err != nil {
|
||||
return errors.InternalServerError(fmt.Sprintf("Failed to get host name: %s", err))
|
||||
}
|
||||
|
||||
if host != "" {
|
||||
log.Infof("Using UUID (%s) for imagestore name", host)
|
||||
}
|
||||
|
||||
vc := cache.ContainerCache().GetContainer(container.ID)
|
||||
meta := dockerimage.V1Image{
|
||||
ID: lm.ID,
|
||||
Parent: vc.LayerID,
|
||||
Author: config.Author,
|
||||
Comment: config.Comment,
|
||||
Created: time.Now().UTC(),
|
||||
Container: container.ID,
|
||||
ContainerConfig: *container.Config,
|
||||
Architecture: runtime.GOARCH,
|
||||
OS: runtime.GOOS,
|
||||
DockerVersion: version.DockerServerVersion,
|
||||
Config: newConfig,
|
||||
Size: lm.Size,
|
||||
}
|
||||
|
||||
m, err := json.Marshal(meta)
|
||||
if err != nil {
|
||||
return errors.InternalServerError(fmt.Sprintf("Failed to marshal image layer config: %s", err))
|
||||
}
|
||||
// layer metadata
|
||||
lm.Meta = string(m)
|
||||
lm.Image.Parent = vc.LayerID
|
||||
lm.Image.Store = host
|
||||
return nil
|
||||
}
|
||||
|
||||
func downloadDiff(rc io.ReadCloser, containerID string, options imagec.Options) (*imagec.ImageWithMeta, error) {
|
||||
defer trace.End(trace.Begin(containerID))
|
||||
|
||||
// generate random string as layer ID
|
||||
layerID := stringid.GenerateRandomID()
|
||||
|
||||
tmpLayerFileName, diffIDSum, gzSum, err := compressDiffToTmpFile(rc, containerID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Cleanup function for the error case
|
||||
defer func() {
|
||||
if err != nil {
|
||||
os.Remove(tmpLayerFileName)
|
||||
}
|
||||
}()
|
||||
|
||||
blobSum := digest.NewDigestFromBytes(digest.SHA256, gzSum)
|
||||
log.Debugf("container %s blob sum: %s", containerID, blobSum.String())
|
||||
|
||||
layerFile, err := os.Open(string(tmpLayerFileName))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer layerFile.Close()
|
||||
|
||||
decompressed, err := gzip.NewReader(layerFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer decompressed.Close()
|
||||
|
||||
// get a tar reader
|
||||
tr := tar.NewReader(decompressed)
|
||||
|
||||
// iterate through tar headers to get file sizes
|
||||
var size int64
|
||||
for {
|
||||
tarHeader, terr := tr.Next()
|
||||
if terr == io.EOF {
|
||||
break
|
||||
}
|
||||
if terr != nil {
|
||||
err = terr
|
||||
return nil, err
|
||||
}
|
||||
size += tarHeader.Size
|
||||
}
|
||||
|
||||
diffID := digest.NewDigestFromBytes(digest.SHA256, diffIDSum)
|
||||
if size == 0 {
|
||||
diffID = digest.Digest(dockerLayer.DigestSHA256EmptyTar)
|
||||
}
|
||||
log.Debugf("container %s diff id: %s, size: %d", containerID, diffID.String(), size)
|
||||
|
||||
// Ensure the parent directory exists
|
||||
destination := path.Join(imagec.DestinationDirectory(options), layerID)
|
||||
err = os.MkdirAll(destination, 0755) /* #nosec */
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Move(rename) the temporary file to its final destination
|
||||
err = os.Rename(string(tmpLayerFileName), path.Join(destination, layerID+".tar"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// layer metadata
|
||||
lm := &imagec.ImageWithMeta{
|
||||
Image: &models.Image{
|
||||
ID: layerID,
|
||||
},
|
||||
DiffID: diffID.String(),
|
||||
Layer: imagec.FSLayer{
|
||||
BlobSum: blobSum.String(),
|
||||
},
|
||||
Size: size,
|
||||
}
|
||||
return lm, nil
|
||||
}
|
||||
|
||||
// compressDiffToTmpFile will write stream to temp file, and return temp file name and tar file checksum, compressed file checksum
|
||||
func compressDiffToTmpFile(rc io.ReadCloser, containerID string) (string, []byte, []byte, error) {
|
||||
defer trace.End(trace.Begin(containerID))
|
||||
// Create a temporary file and stream the res.Body into it
|
||||
var out *os.File
|
||||
var gzWriter *gzip.Writer
|
||||
var err error
|
||||
|
||||
cleanup := func() {
|
||||
if gzWriter != nil {
|
||||
gzWriter.Close()
|
||||
gzWriter = nil
|
||||
}
|
||||
if out != nil {
|
||||
out.Close()
|
||||
if err != nil {
|
||||
os.Remove(out.Name())
|
||||
}
|
||||
out = nil
|
||||
}
|
||||
}
|
||||
defer cleanup()
|
||||
|
||||
out, err = ioutil.TempFile("", containerID)
|
||||
if err != nil {
|
||||
return "", nil, nil, err
|
||||
}
|
||||
|
||||
// compress tar file using gzip and calculate blobsum and diffID all together using multi writer
|
||||
gzSum := sha256.New()
|
||||
tarSum := sha256.New()
|
||||
compressedMW := io.MultiWriter(out, gzSum)
|
||||
|
||||
gzWriter = gzip.NewWriter(compressedMW)
|
||||
tarMW := io.MultiWriter(gzWriter, tarSum)
|
||||
_, err = io.Copy(tarMW, rc)
|
||||
if err != nil {
|
||||
log.Errorf("failed to stream to file: %s", err)
|
||||
return "", nil, nil, err
|
||||
}
|
||||
|
||||
// close writer before calculate checksum
|
||||
fileName := out.Name()
|
||||
err = gzWriter.Flush()
|
||||
if err != nil {
|
||||
log.Errorf("failed to flush writer: %s", err)
|
||||
}
|
||||
cleanup()
|
||||
// Return the temporary file name and checksum
|
||||
return fileName, tarSum.Sum(nil), gzSum.Sum(nil), nil
|
||||
}
|
||||
|
||||
// ***** Code from Docker v17.03.2-ce PullImage to merge two Configs
|
||||
|
||||
// merge merges two Config, the image container configuration (defaults values),
|
||||
// and the user container configuration, either passed by the API or generated
|
||||
// by the cli.
|
||||
// It will mutate the specified user configuration (userConf) with the image
|
||||
// configuration where the user configuration is incomplete.
|
||||
func merge(userConf, imageConf *containertypes.Config) error {
|
||||
if userConf.User == "" {
|
||||
userConf.User = imageConf.User
|
||||
}
|
||||
if len(userConf.ExposedPorts) == 0 {
|
||||
userConf.ExposedPorts = imageConf.ExposedPorts
|
||||
} else if imageConf.ExposedPorts != nil {
|
||||
for port := range imageConf.ExposedPorts {
|
||||
if _, exists := userConf.ExposedPorts[port]; !exists {
|
||||
userConf.ExposedPorts[port] = struct{}{}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(userConf.Env) == 0 {
|
||||
userConf.Env = imageConf.Env
|
||||
} else {
|
||||
for _, imageEnv := range imageConf.Env {
|
||||
found := false
|
||||
imageEnvKey := strings.Split(imageEnv, "=")[0]
|
||||
for _, userEnv := range userConf.Env {
|
||||
userEnvKey := strings.Split(userEnv, "=")[0]
|
||||
if runtime.GOOS == "windows" {
|
||||
// Case insensitive environment variables on Windows
|
||||
imageEnvKey = strings.ToUpper(imageEnvKey)
|
||||
userEnvKey = strings.ToUpper(userEnvKey)
|
||||
}
|
||||
if imageEnvKey == userEnvKey {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
userConf.Env = append(userConf.Env, imageEnv)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if userConf.Labels == nil {
|
||||
userConf.Labels = map[string]string{}
|
||||
}
|
||||
for l, v := range imageConf.Labels {
|
||||
if _, ok := userConf.Labels[l]; !ok {
|
||||
userConf.Labels[l] = v
|
||||
}
|
||||
}
|
||||
|
||||
if len(userConf.Entrypoint) == 0 {
|
||||
if len(userConf.Cmd) == 0 {
|
||||
userConf.Cmd = imageConf.Cmd
|
||||
userConf.ArgsEscaped = imageConf.ArgsEscaped
|
||||
}
|
||||
|
||||
if userConf.Entrypoint == nil {
|
||||
userConf.Entrypoint = imageConf.Entrypoint
|
||||
}
|
||||
}
|
||||
if imageConf.Healthcheck != nil {
|
||||
if userConf.Healthcheck == nil {
|
||||
userConf.Healthcheck = imageConf.Healthcheck
|
||||
} else {
|
||||
if len(userConf.Healthcheck.Test) == 0 {
|
||||
userConf.Healthcheck.Test = imageConf.Healthcheck.Test
|
||||
}
|
||||
if userConf.Healthcheck.Interval == 0 {
|
||||
userConf.Healthcheck.Interval = imageConf.Healthcheck.Interval
|
||||
}
|
||||
if userConf.Healthcheck.Timeout == 0 {
|
||||
userConf.Healthcheck.Timeout = imageConf.Healthcheck.Timeout
|
||||
}
|
||||
if userConf.Healthcheck.Retries == 0 {
|
||||
userConf.Healthcheck.Retries = imageConf.Healthcheck.Retries
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if userConf.WorkingDir == "" {
|
||||
userConf.WorkingDir = imageConf.WorkingDir
|
||||
}
|
||||
if len(userConf.Volumes) == 0 {
|
||||
userConf.Volumes = imageConf.Volumes
|
||||
} else {
|
||||
for k, v := range imageConf.Volumes {
|
||||
userConf.Volumes[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
if userConf.StopSignal == "" {
|
||||
userConf.StopSignal = imageConf.StopSignal
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// *****
|
||||
143
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/commit_test.go
generated
vendored
Normal file
143
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/commit_test.go
generated
vendored
Normal file
@@ -0,0 +1,143 @@
|
||||
// Copyright 2017 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package backends
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"testing"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/docker/docker/api/types/backend"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/vmware/vic/lib/imagec"
|
||||
)
|
||||
|
||||
func getMockReader(t *testing.T) (io.ReadCloser, error) {
|
||||
// Create a buffer to write our archive to.
|
||||
buf := new(bytes.Buffer)
|
||||
|
||||
// Create a new tar archive.
|
||||
tw := tar.NewWriter(buf)
|
||||
|
||||
// Add some files to the archive.
|
||||
var files = []struct {
|
||||
Name, Body string
|
||||
}{
|
||||
{"readme.txt", "This archive contains some text files."},
|
||||
{"gopher.txt", "Gopher names:\nGeorge\nGeoffrey\nGonzo"},
|
||||
{"todo.txt", "Get animal handling license."},
|
||||
}
|
||||
for _, file := range files {
|
||||
hdr := &tar.Header{
|
||||
Name: file.Name,
|
||||
Mode: 0600,
|
||||
Size: int64(len(file.Body)),
|
||||
}
|
||||
if err := tw.WriteHeader(hdr); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := tw.Write([]byte(file.Body)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
// Make sure to check the error on Close.
|
||||
if err := tw.Close(); err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
// Open the tar archive for reading.
|
||||
r := bytes.NewReader(buf.Bytes())
|
||||
return ioutil.NopCloser(r), nil
|
||||
}
|
||||
|
||||
func TestDownload(t *testing.T) {
|
||||
log.SetLevel(log.DebugLevel)
|
||||
|
||||
tests := []struct {
|
||||
repo string
|
||||
tag string
|
||||
}{
|
||||
{repo: "registry-1.docker.io", tag: ""},
|
||||
{repo: "registry-1.docker.io", tag: "mycommit"},
|
||||
{repo: "myrepo.io", tag: ""},
|
||||
{repo: "myrepo.io", tag: "mycommit"},
|
||||
{repo: "", tag: ""},
|
||||
}
|
||||
for _, test := range tests {
|
||||
config := &backend.ContainerCommitConfig{}
|
||||
config.Tag = test.tag
|
||||
config.Repo = test.repo
|
||||
ic, err := getImagec(config)
|
||||
if err != nil {
|
||||
t.Errorf("Failed to get imagec: %s", err)
|
||||
return
|
||||
}
|
||||
rc, err := getMockReader(t)
|
||||
if err != nil {
|
||||
t.Errorf("Failed to get mocked reader: %s", err)
|
||||
}
|
||||
|
||||
layer, err := downloadDiff(rc, "abcd", ic.Options)
|
||||
if err != nil {
|
||||
t.Errorf("Failed to download layer: %s", err)
|
||||
return
|
||||
}
|
||||
t.Logf("layer id: %#v", layer)
|
||||
destDir := path.Join(imagec.DestinationDirectory(ic.Options), layer.ID)
|
||||
destination := path.Join(destDir, layer.ID+".tar")
|
||||
if _, err := os.Stat(destination); err != nil {
|
||||
t.Errorf("diff file %s is not created", destination)
|
||||
}
|
||||
assert.Equal(t, int64(101), layer.Size, "layer size is wrong")
|
||||
|
||||
layerFile, err := os.Open(string(destination))
|
||||
if err != nil {
|
||||
t.Errorf("Layer file %s is not created: %s", destination, err)
|
||||
}
|
||||
defer layerFile.Close()
|
||||
|
||||
decompressed, err := gzip.NewReader(layerFile)
|
||||
if err != nil {
|
||||
t.Errorf("Failed to create gzip reader: %s", err)
|
||||
}
|
||||
defer decompressed.Close()
|
||||
|
||||
// get a tar reader
|
||||
tr := tar.NewReader(decompressed)
|
||||
|
||||
// iterate through tar headers to get file sizes
|
||||
var layerSize int64
|
||||
for {
|
||||
tarHeader, terr := tr.Next()
|
||||
if terr == io.EOF {
|
||||
break
|
||||
}
|
||||
if terr != nil {
|
||||
t.Errorf("Failed to read layer file: %s", terr)
|
||||
}
|
||||
t.Logf("Read file: %s", tarHeader.Name)
|
||||
layerSize += tarHeader.Size
|
||||
}
|
||||
assert.Equal(t, int64(101), layerSize, "tar file size is wrong")
|
||||
os.RemoveAll(destDir)
|
||||
}
|
||||
}
|
||||
1953
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/container.go
generated
vendored
Normal file
1953
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/container.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
94
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/container/viccontainer.go
generated
vendored
Normal file
94
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/container/viccontainer.go
generated
vendored
Normal file
@@ -0,0 +1,94 @@
|
||||
// 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 container
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
containertypes "github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/go-connections/nat"
|
||||
)
|
||||
|
||||
// VicContainer is VIC's abridged version of Docker's container object.
|
||||
type VicContainer struct {
|
||||
Name string
|
||||
ImageID string // maps to the image used by this container
|
||||
LayerID string // child-most layer ID used to find vmdk for this container
|
||||
ContainerID string
|
||||
Config *containertypes.Config //Working copy of config (with overrides from container create)
|
||||
HostConfig *containertypes.HostConfig
|
||||
NATMap nat.PortMap // the endpoint NAT mappings only
|
||||
|
||||
m sync.RWMutex
|
||||
execs map[string]struct{}
|
||||
lockChan chan bool
|
||||
}
|
||||
|
||||
// NewVicContainer returns a reference to a new VicContainer
|
||||
func NewVicContainer() *VicContainer {
|
||||
vc := &VicContainer{
|
||||
Config: &containertypes.Config{},
|
||||
execs: make(map[string]struct{}),
|
||||
lockChan: make(chan bool, 1),
|
||||
}
|
||||
return vc
|
||||
}
|
||||
|
||||
// Add adds a new exec configuration to the container.
|
||||
func (v *VicContainer) Add(id string) {
|
||||
v.m.Lock()
|
||||
v.execs[id] = struct{}{}
|
||||
v.m.Unlock()
|
||||
}
|
||||
|
||||
// Delete removes an exec configuration from the container.
|
||||
func (v *VicContainer) Delete(id string) {
|
||||
v.m.Lock()
|
||||
delete(v.execs, id)
|
||||
v.m.Unlock()
|
||||
}
|
||||
|
||||
// List returns the list of exec ids in the container.
|
||||
func (v *VicContainer) List() []string {
|
||||
var IDs []string
|
||||
v.m.RLock()
|
||||
for id := range v.execs {
|
||||
IDs = append(IDs, id)
|
||||
}
|
||||
v.m.RUnlock()
|
||||
return IDs
|
||||
}
|
||||
|
||||
// Tries to lock the container. Timeout argument defines how long the lock
|
||||
// attempt will be tried. Returns true if locked, false if timed out.
|
||||
func (v *VicContainer) TryLock(timeout time.Duration) bool {
|
||||
timeChan := time.After(timeout)
|
||||
select {
|
||||
case <-timeChan:
|
||||
return false
|
||||
case v.lockChan <- true:
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// Unlocks the container
|
||||
func (v *VicContainer) Unlock() {
|
||||
select {
|
||||
case <-v.lockChan:
|
||||
default:
|
||||
panic("Attempt to release container %s's lock that is not locked")
|
||||
}
|
||||
}
|
||||
839
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/container_test.go
generated
vendored
Normal file
839
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/container_test.go
generated
vendored
Normal file
@@ -0,0 +1,839 @@
|
||||
// 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())
|
||||
}
|
||||
81
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/convert/annotation.go
generated
vendored
Normal file
81
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/convert/annotation.go
generated
vendored
Normal file
@@ -0,0 +1,81 @@
|
||||
// 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 (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
|
||||
"github.com/vmware/vic/lib/apiservers/portlayer/models"
|
||||
)
|
||||
|
||||
const (
|
||||
AnnotationKeyLabels = "docker.labels"
|
||||
AnnotationKeyAutoRemove = "docker.autoremove"
|
||||
)
|
||||
|
||||
// SetContainerAnnotation encodes a docker specific attribute into a vSphere annotation. These vSphere
|
||||
// annotations are stored in the VM vmx file
|
||||
func SetContainerAnnotation(config *models.ContainerCreateConfig, key string, value interface{}) error {
|
||||
var err error
|
||||
|
||||
if config == nil || value == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if config.Annotations == nil {
|
||||
config.Annotations = make(map[string]string)
|
||||
}
|
||||
|
||||
// Encoding the labels map into a blob that can be stored as ansi regardless
|
||||
// of what encoding the input labels are. We do this by first marshaling to
|
||||
// to a json byte array to get a self describing encoding and then encoding
|
||||
// to base64. We could use another encoding for the self describing part,
|
||||
// such as Golang GOB, but this data will be pushed over to a standard REST
|
||||
// server so we use standard web standards instead.
|
||||
if valueBytes, merr := json.Marshal(value); merr == nil {
|
||||
blob := base64.StdEncoding.EncodeToString(valueBytes)
|
||||
config.Annotations[key] = blob
|
||||
} else {
|
||||
err = merr
|
||||
log.Errorf("Unable to marshal annotation %s to json: %s", key, err)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// ContainerAnnotation will convert a vSphere annotation into a docker specific attribute
|
||||
func ContainerAnnotation(annotations map[string]string, key string, value interface{}) error {
|
||||
var err error
|
||||
|
||||
if len(annotations) == 0 || value == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if blob, ok := annotations[key]; ok {
|
||||
if annotationBytes, decodeErr := base64.StdEncoding.DecodeString(blob); decodeErr == nil {
|
||||
if err = json.Unmarshal(annotationBytes, value); err != nil {
|
||||
log.Errorf("Unable to unmarshal %s: %s", key, err)
|
||||
}
|
||||
} else {
|
||||
err = decodeErr
|
||||
log.Errorf("Unable to decode container annotations: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
48
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/convert/annotation_test.go
generated
vendored
Normal file
48
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/convert/annotation_test.go
generated
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
// 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)
|
||||
}
|
||||
93
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/convert/state.go
generated
vendored
Normal file
93
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/convert/state.go
generated
vendored
Normal file
@@ -0,0 +1,93 @@
|
||||
// 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 (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/go-units"
|
||||
|
||||
"github.com/vmware/vic/lib/apiservers/portlayer/models"
|
||||
)
|
||||
|
||||
// State will create and return a docker ContainerState object
|
||||
// from the passed vic ContainerInfo object
|
||||
func State(info *models.ContainerInfo) *types.ContainerState {
|
||||
// ensure we have the data we need
|
||||
if info == nil || info.ProcessConfig == nil || info.ContainerConfig == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
dockerState := &types.ContainerState{}
|
||||
|
||||
// convert start / stop times
|
||||
var started time.Time
|
||||
var finished time.Time
|
||||
|
||||
if info.ProcessConfig.StartTime > 0 {
|
||||
started = time.Unix(info.ProcessConfig.StartTime, 0)
|
||||
dockerState.StartedAt = time.Unix(info.ProcessConfig.StartTime, 0).Format(time.RFC3339Nano)
|
||||
}
|
||||
|
||||
if info.ProcessConfig.StopTime > 0 {
|
||||
finished = time.Unix(info.ProcessConfig.StopTime, 0)
|
||||
dockerState.FinishedAt = time.Unix(info.ProcessConfig.StopTime, 0).Format(time.RFC3339Nano)
|
||||
}
|
||||
|
||||
// set docker status to state and we'll change if needed
|
||||
dockStatus := info.ContainerConfig.State
|
||||
// set exitCode and change if needed
|
||||
exitCode := int(info.ProcessConfig.ExitCode)
|
||||
|
||||
switch info.ContainerConfig.State {
|
||||
case "Running":
|
||||
// if we don't have a start date leave the status as the state
|
||||
if !started.IsZero() {
|
||||
dockStatus = fmt.Sprintf("Up %s", units.HumanDuration(time.Now().UTC().Sub(started)))
|
||||
dockerState.Running = true
|
||||
}
|
||||
case "Stopped":
|
||||
// if we don't have a finished date then don't process exitCode and return "Stopped" for the status
|
||||
if !finished.IsZero() {
|
||||
// interrogate the process status returned from the portlayer
|
||||
// and based on status text and exit codes set the appropriate
|
||||
// docker exit code
|
||||
if strings.Contains(info.ProcessConfig.Status, "permission denied") {
|
||||
exitCode = 126
|
||||
} else if strings.Contains(info.ProcessConfig.Status, "no such") {
|
||||
exitCode = 127
|
||||
} else if info.ProcessConfig.Status == "true" && exitCode == -1 {
|
||||
// most likely the process was killed via the cli
|
||||
// or received a sigkill
|
||||
exitCode = 137
|
||||
} else if info.ProcessConfig.Status == "" && exitCode == -1 {
|
||||
// the process was stopped via the cli
|
||||
// or received a sigterm
|
||||
exitCode = 143
|
||||
}
|
||||
|
||||
dockStatus = fmt.Sprintf("Exited (%d) %s ago", exitCode, units.HumanDuration(time.Now().UTC().Sub(finished)))
|
||||
}
|
||||
}
|
||||
dockerState.Status = dockStatus
|
||||
dockerState.ExitCode = exitCode
|
||||
dockerState.Pid = int(info.ProcessConfig.Pid)
|
||||
dockerState.Error = info.ProcessConfig.ErrorMsg
|
||||
|
||||
return dockerState
|
||||
}
|
||||
453
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/convert/stats.go
generated
vendored
Normal file
453
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/convert/stats.go
generated
vendored
Normal file
@@ -0,0 +1,453 @@
|
||||
// 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"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
|
||||
"github.com/vmware/vic/pkg/vsphere/performance"
|
||||
)
|
||||
|
||||
// ContainerStats encapsulates the conversion of VMMetrics to
|
||||
// docker specific metrics
|
||||
type ContainerStats struct {
|
||||
config *ContainerStatsConfig
|
||||
|
||||
totalVCHMhz uint64
|
||||
dblVCHMhz uint64
|
||||
preTotalMhz uint64
|
||||
|
||||
preDockerStat *types.StatsJSON
|
||||
curDockerStat *types.StatsJSON
|
||||
currentMetric *performance.VMMetrics
|
||||
|
||||
// disk & net stats are accumulated during the life of the
|
||||
// subscription. These maps will assist in that accumulation.
|
||||
diskStats map[string]performance.VirtualDisk
|
||||
netStats map[string]performance.Network
|
||||
|
||||
mu sync.Mutex
|
||||
reader *io.PipeReader
|
||||
writer *io.PipeWriter
|
||||
listening bool
|
||||
}
|
||||
|
||||
type ContainerStatsConfig struct {
|
||||
Ctx context.Context
|
||||
Cancel context.CancelFunc
|
||||
Out io.Writer
|
||||
ContainerID string
|
||||
ContainerState *types.ContainerState
|
||||
Memory int64
|
||||
Stream bool
|
||||
VchMhz int64
|
||||
}
|
||||
|
||||
type InvalidOrderError struct {
|
||||
current time.Time
|
||||
previous time.Time
|
||||
}
|
||||
|
||||
func (iso InvalidOrderError) Error() string {
|
||||
return fmt.Sprintf("The current sample time (%s) is before the previous time (%s)", iso.current, iso.previous)
|
||||
}
|
||||
|
||||
// NewContainerStats will return a new instance of ContainerStats
|
||||
func NewContainerStats(config *ContainerStatsConfig) *ContainerStats {
|
||||
return &ContainerStats{
|
||||
config: config,
|
||||
curDockerStat: &types.StatsJSON{},
|
||||
totalVCHMhz: uint64(config.VchMhz),
|
||||
dblVCHMhz: uint64(config.VchMhz * 2),
|
||||
diskStats: make(map[string]performance.VirtualDisk),
|
||||
netStats: make(map[string]performance.Network),
|
||||
}
|
||||
}
|
||||
|
||||
// IsListening returns the listening flag
|
||||
func (cs *ContainerStats) IsListening() bool {
|
||||
cs.mu.Lock()
|
||||
defer cs.mu.Unlock()
|
||||
return cs.listening
|
||||
}
|
||||
|
||||
// Stop will clean up the pipe and flip listening flag
|
||||
func (cs *ContainerStats) Stop() {
|
||||
cs.mu.Lock()
|
||||
defer cs.mu.Unlock()
|
||||
|
||||
if cs.listening {
|
||||
// #nosec: Errors unhandled.
|
||||
cs.reader.Close()
|
||||
// #nosec: Errors unhandled.
|
||||
cs.writer.Close()
|
||||
cs.listening = false
|
||||
}
|
||||
}
|
||||
|
||||
// newPipe will initialize the pipe for encoding / decoding and
|
||||
// set the listening flag
|
||||
func (cs *ContainerStats) newPipe() {
|
||||
cs.mu.Lock()
|
||||
defer cs.mu.Unlock()
|
||||
|
||||
// create a new reader / writer
|
||||
cs.reader, cs.writer = io.Pipe()
|
||||
cs.listening = true
|
||||
}
|
||||
|
||||
// Listen for new metrics from the portLayer, convert to docker format
|
||||
// and encode to the configured Writer.
|
||||
func (cs *ContainerStats) Listen() *io.PipeWriter {
|
||||
// Are we already listening?
|
||||
if cs.IsListening() {
|
||||
return nil
|
||||
}
|
||||
|
||||
// create pipe for encode/decode
|
||||
cs.newPipe()
|
||||
|
||||
dec := json.NewDecoder(cs.reader)
|
||||
doc := json.NewEncoder(cs.config.Out)
|
||||
|
||||
// channel to transfer metric from decoder to encoder
|
||||
metric := make(chan performance.VMMetrics)
|
||||
|
||||
// if we aren't streaming and the container is not running, then create an empty
|
||||
// docker stat to return
|
||||
if !cs.config.Stream && !cs.config.ContainerState.Running {
|
||||
cs.preDockerStat = &types.StatsJSON{}
|
||||
}
|
||||
|
||||
// go routine to stop on Context.Cancel
|
||||
go func() {
|
||||
<-cs.config.Ctx.Done()
|
||||
close(metric)
|
||||
cs.Stop()
|
||||
}()
|
||||
|
||||
// go routine will decode metrics received from the portLayer and
|
||||
// send them to the encoding routine
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case <-cs.config.Ctx.Done():
|
||||
return
|
||||
default:
|
||||
for dec.More() {
|
||||
var vmm performance.VMMetrics
|
||||
err := dec.Decode(&vmm)
|
||||
if err != nil {
|
||||
log.Errorf("container metric decoding error for container(%s): %s", cs.config.ContainerID, err)
|
||||
cs.config.Cancel()
|
||||
}
|
||||
// send the decoded metric for transform and encoding
|
||||
if cs.IsListening() {
|
||||
metric <- vmm
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}()
|
||||
|
||||
// go routine will convert incoming metrics to docker specific stats and encode for the docker client.
|
||||
go func() {
|
||||
// docker needs updates quicker than vSphere can produce metrics, so we'll send a minimum of 1 metric/sec
|
||||
ticker := time.NewTicker(time.Millisecond * 500)
|
||||
for range ticker.C {
|
||||
select {
|
||||
case <-cs.config.Ctx.Done():
|
||||
ticker.Stop()
|
||||
return
|
||||
case nm := <-metric:
|
||||
// convert the Stat to docker struct
|
||||
stat, err := cs.ToContainerStats(&nm)
|
||||
if err != nil {
|
||||
log.Errorf("container metric conversion error for container(%s): %s", cs.config.ContainerID, err)
|
||||
cs.config.Cancel()
|
||||
}
|
||||
if stat != nil {
|
||||
cs.preDockerStat = stat
|
||||
}
|
||||
default:
|
||||
if cs.IsListening() && cs.preDockerStat != nil {
|
||||
// send docker stat to client
|
||||
err := doc.Encode(cs.preDockerStat)
|
||||
if err != nil {
|
||||
log.Warnf("container metric encoding error for container(%s): %s", cs.config.ContainerID, err)
|
||||
cs.config.Cancel()
|
||||
}
|
||||
// if we aren't streaming then cancel
|
||||
if !cs.config.Stream {
|
||||
cs.config.Cancel()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return cs.writer
|
||||
}
|
||||
|
||||
// ToContainerStats will convert the vic VMMetrics to a docker stats struct -- a complete docker stats
|
||||
// struct requires two samples. Func will return nil until a complete stat is available
|
||||
func (cs *ContainerStats) ToContainerStats(current *performance.VMMetrics) (*types.StatsJSON, error) {
|
||||
// if we have a current metric then validate and transform
|
||||
if cs.currentMetric != nil {
|
||||
// do we have the same metric or has the metric not been initialized?
|
||||
if cs.currentMetric.SampleTime.Equal(current.SampleTime) || current.SampleTime.IsZero() {
|
||||
return nil, nil
|
||||
}
|
||||
// we have new current stats so need to move the previous CPU
|
||||
err := cs.previousCPU(current)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
cs.currentMetric = current
|
||||
|
||||
// create the current CPU stats
|
||||
cs.currentCPU()
|
||||
|
||||
// create memory stats
|
||||
cs.memory()
|
||||
|
||||
// create network stats
|
||||
cs.network()
|
||||
|
||||
// create storage stats
|
||||
cs.disk()
|
||||
|
||||
// set sample time
|
||||
cs.curDockerStat.Read = cs.currentMetric.SampleTime
|
||||
|
||||
// PreRead will be zero if we don't have two samples
|
||||
if cs.curDockerStat.PreRead.IsZero() {
|
||||
return nil, nil
|
||||
}
|
||||
return cs.curDockerStat, nil
|
||||
}
|
||||
|
||||
// network will calculate stats by network device. The stats presented will be the
|
||||
// network stats accumulated during the stats subscription. This differs from vanilla
|
||||
// docker as it provides the network stats for the lifetime of the container.
|
||||
//
|
||||
// TODO: Errors from either Tx or Rx are not currently supported (July 9th 2017)
|
||||
func (cs *ContainerStats) network() {
|
||||
cs.curDockerStat.Networks = make(map[string]types.NetworkStats)
|
||||
for _, net := range cs.currentMetric.Networks {
|
||||
|
||||
// get the previous network stats
|
||||
if preNet, exists := cs.netStats[net.Name]; exists {
|
||||
net.Rx.Bytes += preNet.Rx.Bytes
|
||||
net.Rx.Packets += preNet.Rx.Packets
|
||||
net.Rx.Dropped += preNet.Rx.Dropped
|
||||
net.Tx.Bytes += preNet.Tx.Bytes
|
||||
net.Tx.Packets += preNet.Tx.Packets
|
||||
net.Tx.Dropped += preNet.Tx.Dropped
|
||||
cs.netStats[net.Name] = net
|
||||
} else {
|
||||
// initial iteration
|
||||
cs.netStats[net.Name] = net
|
||||
}
|
||||
|
||||
cs.curDockerStat.Networks[net.Name] = types.NetworkStats{
|
||||
RxBytes: net.Rx.Bytes,
|
||||
RxPackets: uint64(net.Rx.Packets),
|
||||
RxDropped: uint64(net.Rx.Dropped),
|
||||
TxBytes: net.Tx.Bytes,
|
||||
TxPackets: uint64(net.Tx.Packets),
|
||||
TxDropped: uint64(net.Tx.Dropped),
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// disk will calculate supported stats by disk device. The stats presented will be the
|
||||
// disk stats accumulated during the stats subscription. This differs from vanilla
|
||||
// docker as it provides the disk stats for the lifetime of the container.
|
||||
//
|
||||
// Supported stats are io_service_bytes_recursive and io_serviced_recursive, so bytes and iops
|
||||
// during the stats subscription
|
||||
//
|
||||
// TODO: Currently disk assumes a single scsi controller. Multiple scsi controllers will need
|
||||
// to be supported in a future release (July 9th 2017)
|
||||
func (cs *ContainerStats) disk() {
|
||||
// docker storage stats to populate
|
||||
storage := types.BlkioStats{
|
||||
IoServiceBytesRecursive: []types.BlkioStatEntry{},
|
||||
IoServicedRecursive: []types.BlkioStatEntry{},
|
||||
}
|
||||
|
||||
for _, disk := range cs.currentMetric.Disks {
|
||||
// disk stats accumulate for the life of subscription, so
|
||||
// either add previous stats or store initial stats
|
||||
if preDisk, exists := cs.diskStats[disk.Name]; exists {
|
||||
// add previous values to current value
|
||||
disk.Read.Bytes += preDisk.Read.Bytes
|
||||
disk.Read.Op += preDisk.Read.Op
|
||||
disk.Write.Bytes += preDisk.Write.Bytes
|
||||
disk.Write.Op += preDisk.Write.Op
|
||||
cs.diskStats[disk.Name] = disk
|
||||
} else {
|
||||
// initial iteration
|
||||
cs.diskStats[disk.Name] = disk
|
||||
}
|
||||
|
||||
// get the minor number for the disk device
|
||||
deviceMinor := diskMinor(cs.config.ContainerID, disk.Name)
|
||||
|
||||
// need to update read, write & total for supported stats (bytes & iops)
|
||||
storage.IoServiceBytesRecursive = append(storage.IoServiceBytesRecursive,
|
||||
createBlkioStatsEntry(deviceMinor, "Read", cs.diskStats[disk.Name].Read.Bytes))
|
||||
storage.IoServiceBytesRecursive = append(storage.IoServiceBytesRecursive,
|
||||
createBlkioStatsEntry(deviceMinor, "Write", cs.diskStats[disk.Name].Write.Bytes))
|
||||
storage.IoServiceBytesRecursive = append(storage.IoServiceBytesRecursive,
|
||||
createBlkioStatsEntry(deviceMinor, "Total", cs.diskStats[disk.Name].Read.Bytes+cs.diskStats[disk.Name].Write.Bytes))
|
||||
// Ops
|
||||
storage.IoServicedRecursive = append(storage.IoServicedRecursive,
|
||||
createBlkioStatsEntry(deviceMinor, "Read", cs.diskStats[disk.Name].Read.Op))
|
||||
storage.IoServicedRecursive = append(storage.IoServicedRecursive,
|
||||
createBlkioStatsEntry(deviceMinor, "Write", cs.diskStats[disk.Name].Write.Op))
|
||||
storage.IoServicedRecursive = append(storage.IoServicedRecursive,
|
||||
createBlkioStatsEntry(deviceMinor, "Total", cs.diskStats[disk.Name].Read.Op+cs.diskStats[disk.Name].Write.Op))
|
||||
|
||||
}
|
||||
// add the block stats to the docker stat
|
||||
cs.curDockerStat.BlkioStats = storage
|
||||
}
|
||||
|
||||
func (cs *ContainerStats) memory() {
|
||||
// given MB (i.e. 2048) convert to GB
|
||||
cs.curDockerStat.MemoryStats.Limit = uint64(cs.config.Memory * 1024 * 1024)
|
||||
// given KB (i.e. 384.5) convert to Bytes
|
||||
cs.curDockerStat.MemoryStats.Usage = uint64(cs.currentMetric.Memory.Active * 1024)
|
||||
}
|
||||
|
||||
// previousCPU will move the current stats to the previous CPU location
|
||||
func (cs *ContainerStats) previousCPU(current *performance.VMMetrics) error {
|
||||
// validate that the sampling is in the correct order
|
||||
if current.SampleTime.Before(cs.curDockerStat.Read) {
|
||||
err := InvalidOrderError{
|
||||
current: current.SampleTime,
|
||||
previous: cs.curDockerStat.Read,
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// move the stats
|
||||
cs.curDockerStat.PreCPUStats = cs.curDockerStat.CPUStats
|
||||
|
||||
// set the previousTotal -- this will be added to the current CPU
|
||||
cs.preTotalMhz = cs.curDockerStat.PreCPUStats.CPUUsage.TotalUsage
|
||||
|
||||
cs.curDockerStat.PreRead = cs.curDockerStat.Read
|
||||
// previous systemUsage will always be the VCH total
|
||||
// see note in func currentCPU() for detail
|
||||
cs.curDockerStat.PreCPUStats.SystemUsage = cs.totalVCHMhz
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// currentCPU will convert the VM CPU metrics to docker CPU stats
|
||||
func (cs *ContainerStats) currentCPU() {
|
||||
cpuCount := len(cs.currentMetric.CPU.CPUs)
|
||||
dockerCPU := types.CPUStats{
|
||||
CPUUsage: types.CPUUsage{
|
||||
PercpuUsage: make([]uint64, cpuCount, cpuCount),
|
||||
},
|
||||
}
|
||||
|
||||
// collect the current CPU Metrics
|
||||
for ci, current := range cs.currentMetric.CPU.CPUs {
|
||||
dockerCPU.CPUUsage.PercpuUsage[ci] = uint64(current.MhzUsage)
|
||||
dockerCPU.CPUUsage.TotalUsage += uint64(current.MhzUsage)
|
||||
}
|
||||
|
||||
// vSphere will report negative usage for a starting VM, lets
|
||||
// set to zero
|
||||
if dockerCPU.CPUUsage.TotalUsage < 0 {
|
||||
dockerCPU.CPUUsage.TotalUsage = 0
|
||||
}
|
||||
|
||||
// The first stat available for a VM will be missing detail
|
||||
if cpuCount > 0 {
|
||||
// TotalUsage is the sum of the individual vCPUs Mhz
|
||||
// consumption this reading. We must divide that by the
|
||||
// number of vCPUs to get the average across both, since
|
||||
// the cpuUsage calc (explained below) will multiply by
|
||||
// the number of CPUs to get the cpuUsage percent
|
||||
dockerCPU.CPUUsage.TotalUsage /= uint64(cpuCount)
|
||||
}
|
||||
|
||||
// Set the current systemUsage to double the VCH as the
|
||||
// previous systemUsage is the VCH total. The docker
|
||||
// client formula creates a SystemDelta which is the following:
|
||||
// systemDelta = currentSystemUsage - previousSystemUsage
|
||||
// We always need systemDelta to equal the total amount of
|
||||
// VCH Mhz thus the doubling here.
|
||||
dockerCPU.SystemUsage = cs.dblVCHMhz
|
||||
|
||||
// Much like systemUsage (above) totalCPUUsage and previous
|
||||
// totalCPUUsage will be used to create a CPUUsage delta as such:
|
||||
// CPUDelta = currentTotalCPUUsage - previousTotalCPUUsage
|
||||
// This amount will then be divided by the systemDelta
|
||||
// (explained above) as part of the CPU % Usage calculation
|
||||
// cpuUsage = (CPUDelta / SystemDelta) * cpuCount * 100
|
||||
// This will require the addition of the previous total usage
|
||||
dockerCPU.CPUUsage.TotalUsage += cs.preTotalMhz
|
||||
cs.curDockerStat.CPUStats = dockerCPU
|
||||
}
|
||||
|
||||
// diskMinor will parse the disk name and return the minor id of
|
||||
// the disk device. The func assumes that minor identifiers are multiples
|
||||
// of 16 (0,16,32,48,etc).
|
||||
func diskMinor(containerID string, name string) uint64 {
|
||||
// disks are named scsi0:0, scsi0:1
|
||||
// i.e. controller+controller number:device number
|
||||
device := strings.Split(name, ":")
|
||||
// convert to an int
|
||||
minor, err := strconv.Atoi(device[len(device)-1])
|
||||
if err != nil {
|
||||
// log error, but continue and return a minor number of zero
|
||||
// unlikely this would happen, but if it does and there is more than one disk on the vm
|
||||
// then it could go undetected
|
||||
log.Errorf("stats error generating container(%s) disk(%s) minor: %s", containerID, name, err)
|
||||
}
|
||||
// minor identifiers are multiples of 16
|
||||
minor *= 16
|
||||
return uint64(minor)
|
||||
}
|
||||
|
||||
func createBlkioStatsEntry(minor uint64, op string, value uint64) types.BlkioStatEntry {
|
||||
return types.BlkioStatEntry{
|
||||
Major: 8,
|
||||
Minor: minor,
|
||||
Op: op,
|
||||
Value: value,
|
||||
}
|
||||
}
|
||||
495
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/convert/stats_test.go
generated
vendored
Normal file
495
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/convert/stats_test.go
generated
vendored
Normal file
@@ -0,0 +1,495 @@
|
||||
// Copyright 2017 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package convert
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/vmware/vic/pkg/retry"
|
||||
"github.com/vmware/vic/pkg/vsphere/performance"
|
||||
)
|
||||
|
||||
const (
|
||||
vcpuMhz = 3300
|
||||
vcpuCount = 1
|
||||
vchMhzTotal = 3300
|
||||
memConsumed = 1024 * 1024 * 500
|
||||
memProvisioned = 1024 * 1024 * 1024
|
||||
)
|
||||
|
||||
func TestContainerConverter(t *testing.T) {
|
||||
plumb := setup()
|
||||
defer teardown(plumb)
|
||||
|
||||
// grab a config object
|
||||
config := ccConfig(plumb)
|
||||
|
||||
cStats := NewContainerStats(config)
|
||||
assert.NotNil(t, cStats)
|
||||
|
||||
// returned writer is given to PL
|
||||
writer := cStats.Listen()
|
||||
assert.NotNil(t, writer)
|
||||
// second call should result in nil writer as
|
||||
// we are already listening
|
||||
w2 := cStats.Listen()
|
||||
assert.Nil(t, w2)
|
||||
|
||||
// // ensure stop closes reader / writer
|
||||
cStats.Stop()
|
||||
// verify we stopped listening
|
||||
assert.False(t, cStats.IsListening())
|
||||
}
|
||||
|
||||
func TestToContainerStats(t *testing.T) {
|
||||
plumb := setup()
|
||||
defer teardown(plumb)
|
||||
// grab a config object
|
||||
config := ccConfig(plumb)
|
||||
|
||||
cStats := NewContainerStats(config)
|
||||
assert.NotNil(t, cStats)
|
||||
|
||||
initCPU := 1000
|
||||
vmBefore := vmMetrics(vcpuCount, initCPU)
|
||||
vmm := vmMetrics(vcpuCount, initCPU)
|
||||
// ensure we are after the initial metric
|
||||
vmm.SampleTime.Add(time.Second * 1)
|
||||
|
||||
// first metric sent, should return nil
|
||||
js, err := cStats.ToContainerStats(vmm)
|
||||
assert.NoError(t, err)
|
||||
assert.Nil(t, js)
|
||||
|
||||
// send the same stat should return nil
|
||||
js, err = cStats.ToContainerStats(vmm)
|
||||
assert.Nil(t, err)
|
||||
assert.Nil(t, js)
|
||||
|
||||
// send out of order stat
|
||||
js, err = cStats.ToContainerStats(vmBefore)
|
||||
assert.NotNil(t, err)
|
||||
assert.Nil(t, js)
|
||||
|
||||
secondCPU := 250
|
||||
// create a new metric
|
||||
vmmm := vmMetrics(vcpuCount, secondCPU)
|
||||
// sample will be 20 seconds apart..
|
||||
vmmm.SampleTime = vmm.SampleTime.Add(time.Second * 20)
|
||||
|
||||
js, err = cStats.ToContainerStats(vmmm)
|
||||
assert.NoError(t, err)
|
||||
assert.NotZero(t, js.Read, js.PreRead)
|
||||
assert.Equal(t, uint64(vchMhzTotal*2), js.CPUStats.SystemUsage)
|
||||
assert.Equal(t, uint64(secondCPU+initCPU), js.CPUStats.CPUUsage.TotalUsage)
|
||||
assert.Equal(t, uint64(initCPU), js.PreCPUStats.CPUUsage.TotalUsage)
|
||||
assert.Equal(t, uint64(vchMhzTotal), js.PreCPUStats.SystemUsage)
|
||||
|
||||
// this reading should show 250mhz of 3300mhz used -- 7.58%
|
||||
cpuPercent := fmt.Sprintf("%2.2f", calculateCPUPercentUnix(js.PreCPUStats.CPUUsage.TotalUsage, js.PreCPUStats.SystemUsage, js))
|
||||
assert.Equal(t, "7.58", cpuPercent)
|
||||
|
||||
config.Cancel()
|
||||
<-config.Ctx.Done()
|
||||
// verify we stopped listening
|
||||
assert.True(t, success(cStats))
|
||||
}
|
||||
|
||||
func TestContainerStatsListener(t *testing.T) {
|
||||
plumb := setup()
|
||||
defer teardown(plumb)
|
||||
// grab a config object
|
||||
config := ccConfig(plumb)
|
||||
cStats := NewContainerStats(config)
|
||||
assert.NotNil(t, cStats)
|
||||
|
||||
// start the listener
|
||||
writer := cStats.Listen()
|
||||
assert.NotNil(t, writer)
|
||||
|
||||
// create an initial metric
|
||||
initCPU := 1000
|
||||
vm := vmMetrics(vcpuCount, initCPU)
|
||||
err := plumb.mockPLMetrics(vm, writer)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// send second metric
|
||||
vmm := vmMetrics(vcpuCount, initCPU+100)
|
||||
vmm.SampleTime = vm.SampleTime.Add(time.Second * 20)
|
||||
err = plumb.mockPLMetrics(vmm, writer)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// did client receive metric??
|
||||
ds, err := plumb.mockDockerClient()
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, ds)
|
||||
assert.Equal(t, uint64((initCPU*2+100)/vcpuCount), ds.CPUStats.CPUUsage.TotalUsage)
|
||||
|
||||
// docker expects data quicker than vSphere can produce -- sleep for just over 1 sec
|
||||
// and ensure the previous docker stat is returned to client
|
||||
time.Sleep(time.Millisecond * 1100)
|
||||
same, err := plumb.mockDockerClient()
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, same)
|
||||
assert.Equal(t, ds.CPUStats.CPUUsage.TotalUsage, same.CPUStats.CPUUsage.TotalUsage)
|
||||
|
||||
config.Cancel()
|
||||
<-config.Ctx.Done()
|
||||
|
||||
// verify we stopped listening
|
||||
assert.True(t, success(cStats))
|
||||
}
|
||||
|
||||
func TestContainerConvertCtxCancel(t *testing.T) {
|
||||
plumb := setup()
|
||||
defer teardown(plumb)
|
||||
// grab a config object
|
||||
config := ccConfig(plumb)
|
||||
cStats := NewContainerStats(config)
|
||||
assert.NotNil(t, cStats)
|
||||
|
||||
// start the listener
|
||||
writer := cStats.Listen()
|
||||
assert.NotNil(t, writer)
|
||||
|
||||
// cancel the context
|
||||
config.Cancel()
|
||||
<-config.Ctx.Done()
|
||||
// verify we stopped listening
|
||||
assert.True(t, success(cStats))
|
||||
}
|
||||
|
||||
func TestContainerConvertNoStream(t *testing.T) {
|
||||
plumb := setup()
|
||||
defer teardown(plumb)
|
||||
// grab a config object
|
||||
config := ccConfig(plumb)
|
||||
config.Stream = false
|
||||
cStats := NewContainerStats(config)
|
||||
assert.NotNil(t, cStats)
|
||||
|
||||
// start the listener
|
||||
writer := cStats.Listen()
|
||||
assert.NotNil(t, writer)
|
||||
|
||||
// create an initial metric
|
||||
initCPU := 1000
|
||||
vm := vmMetrics(vcpuCount, initCPU)
|
||||
err := plumb.mockPLMetrics(vm, writer)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// send second metric
|
||||
vmm := vmMetrics(vcpuCount, initCPU+100)
|
||||
vmm.SampleTime = vm.SampleTime.Add(time.Second * 20)
|
||||
err = plumb.mockPLMetrics(vmm, writer)
|
||||
assert.NoError(t, err)
|
||||
|
||||
ds, err := plumb.mockDockerClient()
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, ds)
|
||||
|
||||
// converter canceled the context
|
||||
<-config.Ctx.Done()
|
||||
// verify we stopped listening
|
||||
assert.True(t, success(cStats))
|
||||
}
|
||||
|
||||
func TestContainerNotRunningNoStream(t *testing.T) {
|
||||
plumb := setup()
|
||||
defer teardown(plumb)
|
||||
// grab a config object
|
||||
config := ccConfig(plumb)
|
||||
config.Stream = false
|
||||
config.ContainerState.Running = false
|
||||
cStats := NewContainerStats(config)
|
||||
assert.NotNil(t, cStats)
|
||||
|
||||
// start the listener
|
||||
writer := cStats.Listen()
|
||||
assert.NotNil(t, writer)
|
||||
|
||||
ds, err := plumb.mockDockerClient()
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, ds)
|
||||
|
||||
// converter canceled the context
|
||||
<-config.Ctx.Done()
|
||||
// verify we stopped listening
|
||||
assert.True(t, success(cStats))
|
||||
}
|
||||
|
||||
func TestDiskMinor(t *testing.T) {
|
||||
containerID := "12345"
|
||||
for i := 0; i <= 15; i++ {
|
||||
name := fmt.Sprintf("scsi0:%d", i)
|
||||
assert.Equal(t, uint64(i*16), diskMinor(containerID, name))
|
||||
}
|
||||
|
||||
minor := uint64(0)
|
||||
// test with invalid disk names to ensure no panic, etc
|
||||
assert.Equal(t, minor, diskMinor(containerID, "foo:bar:0"))
|
||||
assert.Equal(t, minor, diskMinor(containerID, "foo"))
|
||||
assert.Equal(t, minor, diskMinor(containerID, "foo:"))
|
||||
}
|
||||
|
||||
func TestCreateBlkioStatsEntry(t *testing.T) {
|
||||
minor := uint64(0)
|
||||
val := uint64(12)
|
||||
maj := uint64(8)
|
||||
entry := createBlkioStatsEntry(minor, "Read", val)
|
||||
assert.Equal(t, "Read", entry.Op)
|
||||
assert.Equal(t, val, entry.Value)
|
||||
assert.Equal(t, minor, entry.Minor)
|
||||
assert.Equal(t, maj, entry.Major)
|
||||
}
|
||||
|
||||
func TestDiskStats(t *testing.T) {
|
||||
plumb := setup()
|
||||
defer teardown(plumb)
|
||||
// grab a config object
|
||||
config := ccConfig(plumb)
|
||||
cStats := NewContainerStats(config)
|
||||
assert.NotNil(t, cStats)
|
||||
// create metric
|
||||
initCPU := 1000
|
||||
vm := vmMetrics(vcpuCount, initCPU)
|
||||
cStats.currentMetric = vm
|
||||
|
||||
// update disk
|
||||
cStats.disk()
|
||||
|
||||
assert.Equal(t, 3, len(cStats.curDockerStat.BlkioStats.IoServiceBytesRecursive))
|
||||
assert.Equal(t, 1, len(cStats.diskStats))
|
||||
|
||||
// update again -- this should accumulate the totals
|
||||
cStats.disk()
|
||||
assert.Equal(t, 3, len(cStats.curDockerStat.BlkioStats.IoServiceBytesRecursive))
|
||||
assert.Equal(t, 1, len(cStats.diskStats))
|
||||
|
||||
for _, disk := range cStats.curDockerStat.BlkioStats.IoServiceBytesRecursive {
|
||||
switch disk.Op {
|
||||
case "Write":
|
||||
assert.Equal(t, uint64(vm.Disks[0].Write.Bytes*2), disk.Value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNetworkStats(t *testing.T) {
|
||||
plumb := setup()
|
||||
defer teardown(plumb)
|
||||
// grab a config object
|
||||
config := ccConfig(plumb)
|
||||
cStats := NewContainerStats(config)
|
||||
assert.NotNil(t, cStats)
|
||||
// create metric
|
||||
initCPU := 1000
|
||||
vm := vmMetrics(vcpuCount, initCPU)
|
||||
cStats.currentMetric = vm
|
||||
|
||||
// update network
|
||||
cStats.network()
|
||||
|
||||
assert.Equal(t, 1, len(cStats.curDockerStat.Networks))
|
||||
assert.Equal(t, 1, len(cStats.netStats))
|
||||
|
||||
// update again -- this should accumulate the totals
|
||||
cStats.network()
|
||||
assert.Equal(t, 1, len(cStats.curDockerStat.Networks))
|
||||
assert.Equal(t, 1, len(cStats.netStats))
|
||||
|
||||
for network, usage := range cStats.curDockerStat.Networks {
|
||||
switch network {
|
||||
case "eth0":
|
||||
assert.Equal(t, uint64(200), usage.RxBytes)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Test Helpers
|
||||
|
||||
type plumbing struct {
|
||||
r *io.PipeReader
|
||||
w *io.PipeWriter
|
||||
out io.Writer
|
||||
// mock portlayer
|
||||
mockPL *json.Encoder
|
||||
// mock docker client decoder
|
||||
mockDoc *json.Decoder
|
||||
}
|
||||
|
||||
func setup() *plumbing {
|
||||
r, o := io.Pipe()
|
||||
out := io.Writer(o)
|
||||
|
||||
return &plumbing{
|
||||
r: r,
|
||||
w: o,
|
||||
out: out,
|
||||
mockDoc: json.NewDecoder(r),
|
||||
}
|
||||
}
|
||||
|
||||
// success is a helper to check the listening status of the
|
||||
// converter
|
||||
func success(converter *ContainerStats) bool {
|
||||
op := func() error {
|
||||
listen := converter.IsListening()
|
||||
if listen {
|
||||
return fmt.Errorf("still listening: %t", listen)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
wait := func(err error) bool {
|
||||
if err != nil {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
// use the retry package and keep retrying until we've hit the limit
|
||||
if err := retry.Do(op, wait); err != nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func teardown(p *plumbing) {
|
||||
// close the reader / writer
|
||||
p.r.Close()
|
||||
p.w.Close()
|
||||
}
|
||||
|
||||
func (p *plumbing) mockPLMetrics(metric *performance.VMMetrics, writer io.Writer) error {
|
||||
if p.mockPL == nil {
|
||||
p.mockPL = json.NewEncoder(writer)
|
||||
}
|
||||
return p.mockPL.Encode(metric)
|
||||
}
|
||||
|
||||
func (p *plumbing) mockDockerClient() (*types.StatsJSON, error) {
|
||||
docStats := &types.StatsJSON{}
|
||||
|
||||
err := p.mockDoc.Decode(docStats)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return docStats, nil
|
||||
}
|
||||
|
||||
func ccConfig(p *plumbing) *ContainerStatsConfig {
|
||||
// test config
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
config := &ContainerStatsConfig{
|
||||
VchMhz: int64(vchMhzTotal),
|
||||
Ctx: ctx,
|
||||
Cancel: cancel,
|
||||
ContainerID: "1234",
|
||||
Out: p.out,
|
||||
Stream: true,
|
||||
Memory: 2048,
|
||||
ContainerState: &types.ContainerState{
|
||||
Running: true,
|
||||
},
|
||||
}
|
||||
return config
|
||||
}
|
||||
|
||||
func vmMetrics(count int, vcpuMhz int) *performance.VMMetrics {
|
||||
vmm := &performance.VMMetrics{}
|
||||
vmm.SampleTime = time.Now()
|
||||
vmm.CPU = cpuUsageMetrics(count, vcpuMhz)
|
||||
vmm.Memory = performance.MemoryMetrics{
|
||||
Consumed: int64(memConsumed),
|
||||
Provisioned: int64(memProvisioned),
|
||||
}
|
||||
disk := performance.VirtualDisk{
|
||||
Name: "scsi0:0",
|
||||
Write: performance.DiskUsage{
|
||||
Bytes: uint64(100),
|
||||
Kbps: 5,
|
||||
Op: uint64(5),
|
||||
Ops: 5,
|
||||
},
|
||||
Read: performance.DiskUsage{
|
||||
Bytes: uint64(10),
|
||||
Kbps: 5,
|
||||
Op: uint64(5),
|
||||
Ops: 5,
|
||||
},
|
||||
}
|
||||
vmm.Disks = append(vmm.Disks, disk)
|
||||
network := performance.Network{
|
||||
Name: "eth0",
|
||||
Rx: performance.NetworkUsage{
|
||||
Bytes: uint64(100),
|
||||
Kbps: 5,
|
||||
Packets: 1,
|
||||
},
|
||||
Tx: performance.NetworkUsage{
|
||||
Bytes: uint64(10),
|
||||
Packets: 1,
|
||||
},
|
||||
}
|
||||
vmm.Networks = append(vmm.Networks, network)
|
||||
return vmm
|
||||
}
|
||||
|
||||
// cpuUsageMetrics will return a populated CPUMetrics struct
|
||||
func cpuUsageMetrics(count int, cpuMhz int) performance.CPUMetrics {
|
||||
vmCPUs := make([]performance.CPUUsage, count, count)
|
||||
total := count * cpuMhz
|
||||
for i := range vmCPUs {
|
||||
vmCPUs[i] = performance.CPUUsage{
|
||||
ID: i,
|
||||
MhzUsage: int64(cpuMhz),
|
||||
}
|
||||
}
|
||||
|
||||
return performance.CPUMetrics{
|
||||
CPUs: vmCPUs,
|
||||
Usage: calcVCPUUsage(total),
|
||||
}
|
||||
}
|
||||
|
||||
// calcUsage is a helper function that will take the total provdied usage
|
||||
// and convert to percentage of total vCPU usage
|
||||
func calcVCPUUsage(total int) float32 {
|
||||
return float32(total) / (vcpuMhz * vcpuCount)
|
||||
}
|
||||
|
||||
// calculateCPUPercentUnix is a copy from docker to test the percentage calculations
|
||||
func calculateCPUPercentUnix(previousCPU, previousSystem uint64, v *types.StatsJSON) float64 {
|
||||
var (
|
||||
cpuPercent = 0.0
|
||||
// calculate the change for the cpu usage of the container in between readings
|
||||
cpuDelta = float64(v.CPUStats.CPUUsage.TotalUsage) - float64(previousCPU)
|
||||
// calculate the change for the entire system between readings
|
||||
systemDelta = float64(v.CPUStats.SystemUsage) - float64(previousSystem)
|
||||
)
|
||||
|
||||
if systemDelta > 0.0 && cpuDelta > 0.0 {
|
||||
cpuPercent = (cpuDelta / systemDelta) * float64(len(v.CPUStats.CPUUsage.PercpuUsage)) * 100.0
|
||||
}
|
||||
return cpuPercent
|
||||
}
|
||||
135
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/endpoint.go
generated
vendored
Normal file
135
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/endpoint.go
generated
vendored
Normal file
@@ -0,0 +1,135 @@
|
||||
// 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
|
||||
}
|
||||
38
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/endpoint/endpoint.go
generated
vendored
Normal file
38
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/endpoint/endpoint.go
generated
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
// 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 endpoint
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
apinet "github.com/docker/docker/api/types/network"
|
||||
)
|
||||
|
||||
func Alias(endpointConfig *apinet.EndpointSettings) []string {
|
||||
var aliases []string
|
||||
|
||||
log.Debugf("EndpointsConfig: %#v", endpointConfig)
|
||||
log.Debugf("Aliases: %s", endpointConfig.Aliases)
|
||||
log.Debugf("Links: %s", endpointConfig.Links)
|
||||
|
||||
// Links are already in CONTAINERNAME:ALIAS format
|
||||
aliases = endpointConfig.Links
|
||||
// Converts aliases to ":ALIAS" format
|
||||
for i := range endpointConfig.Aliases {
|
||||
aliases = append(aliases, fmt.Sprintf(":%s", endpointConfig.Aliases[i]))
|
||||
}
|
||||
return aliases
|
||||
}
|
||||
291
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/eventmonitor.go
generated
vendored
Normal file
291
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/eventmonitor.go
generated
vendored
Normal file
@@ -0,0 +1,291 @@
|
||||
// Copyright 2017-2018 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package backends
|
||||
|
||||
//**** eventmonitor.go
|
||||
//
|
||||
// Handles monitoring of events from the portlayer. Events that are applicable to
|
||||
// Docker events are then translated and published to the Docker event subscribers.
|
||||
// NOTE: This does not handle all Docker events. In fact, most docker events are
|
||||
// passively handled by API calls in the backend routers, with no feedback from
|
||||
// the portlayer.
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"golang.org/x/net/context"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
eventtypes "github.com/docker/docker/api/types/events"
|
||||
|
||||
"github.com/vmware/vic/lib/apiservers/engine/backends/cache"
|
||||
"github.com/vmware/vic/lib/apiservers/engine/errors"
|
||||
"github.com/vmware/vic/lib/apiservers/engine/network"
|
||||
"github.com/vmware/vic/lib/apiservers/engine/proxy"
|
||||
"github.com/vmware/vic/lib/apiservers/portlayer/client/events"
|
||||
plevents "github.com/vmware/vic/lib/portlayer/event/events"
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
"github.com/vmware/vic/pkg/uid"
|
||||
)
|
||||
|
||||
const (
|
||||
containerDieEvent = "die"
|
||||
containerDestroyEvent = "destroy"
|
||||
containerStopEvent = "stop"
|
||||
containerStartEvent = "start"
|
||||
containerCreateEvent = "create"
|
||||
containerRestartEvent = "restart"
|
||||
containerAttachEvent = "attach"
|
||||
containerDetachEvent = "detach"
|
||||
containerKillEvent = "kill"
|
||||
containerResizeEvent = "resize"
|
||||
)
|
||||
|
||||
// for unit testing purposes
|
||||
type eventproxy interface {
|
||||
StreamEvents(ctx context.Context, out io.Writer) error
|
||||
}
|
||||
|
||||
type eventpublisher interface {
|
||||
PublishEvent(event plevents.BaseEvent)
|
||||
}
|
||||
|
||||
type PlEventProxy struct {
|
||||
}
|
||||
|
||||
type DockerEventPublisher struct {
|
||||
}
|
||||
|
||||
type PortlayerEventMonitor struct {
|
||||
stop chan struct{}
|
||||
proxy eventproxy
|
||||
publisher eventpublisher
|
||||
}
|
||||
|
||||
// StreamEvents() handles all swagger interaction to the Portlayer's event manager
|
||||
//
|
||||
// Input:
|
||||
// context and a io.Writer
|
||||
func (ep PlEventProxy) StreamEvents(ctx context.Context, out io.Writer) error {
|
||||
defer trace.End(trace.Begin(""))
|
||||
|
||||
plClient := PortLayerClient()
|
||||
if plClient == nil {
|
||||
return errors.InternalServerError("eventproxy.StreamEvents failed to get a portlayer client")
|
||||
}
|
||||
|
||||
params := events.NewGetEventsParamsWithContext(ctx)
|
||||
if _, err := plClient.Events.GetEvents(params, out); err != nil {
|
||||
switch err := err.(type) {
|
||||
case *events.GetEventsInternalServerError:
|
||||
return errors.InternalServerError("Server error from the events port layer")
|
||||
default:
|
||||
//Check for EOF. Since the connection, transport, and data handling are
|
||||
//encapsulated inside of Swagger, we can only detect EOF by checking the
|
||||
//error string
|
||||
if strings.Contains(err.Error(), proxy.SwaggerSubstringEOF) {
|
||||
return nil
|
||||
}
|
||||
return errors.InternalServerError(fmt.Sprintf("Unknown error from the interaction port layer: %s", err))
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewPortlayerEventMonitor(proxy eventproxy, publisher eventpublisher) *PortlayerEventMonitor {
|
||||
return &PortlayerEventMonitor{proxy: proxy, publisher: publisher}
|
||||
}
|
||||
|
||||
// Start() starts the portlayer event monitoring
|
||||
func (m *PortlayerEventMonitor) Start() error {
|
||||
defer trace.End(trace.Begin(""))
|
||||
|
||||
if m.stop != nil {
|
||||
return fmt.Errorf("Portlayer event monitor: Already started")
|
||||
}
|
||||
|
||||
m.stop = make(chan struct{})
|
||||
go m.monitor()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stop() stops the portlayer event monitoring
|
||||
func (m *PortlayerEventMonitor) Stop() {
|
||||
defer trace.End(trace.Begin(""))
|
||||
|
||||
if m.stop != nil {
|
||||
close(m.stop)
|
||||
}
|
||||
}
|
||||
|
||||
// monitor() establishes a streaming connection to the portlayer's event
|
||||
// endpoint, decodes the results, translate it to Docker events if needed,
|
||||
// and publishes the event to Docker event subscribers.
|
||||
func (m *PortlayerEventMonitor) monitor() error {
|
||||
defer trace.End(trace.Begin(""))
|
||||
|
||||
var wg sync.WaitGroup
|
||||
errors := make(chan error, 2)
|
||||
|
||||
reader, writer := io.Pipe()
|
||||
ctx, cancel := context.WithCancel(context.TODO())
|
||||
// Start streaming events
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
var err error
|
||||
|
||||
defer wg.Done()
|
||||
|
||||
if err = m.proxy.StreamEvents(ctx, writer); err != nil {
|
||||
if ctx.Err() != context.Canceled {
|
||||
log.Warnf("Event streaming from portlayer returned: %#v", err)
|
||||
}
|
||||
}
|
||||
if ctx.Err() == context.Canceled {
|
||||
log.Infof("Event streaming from portlayer was cancelled")
|
||||
return
|
||||
}
|
||||
errors <- err
|
||||
|
||||
writer.Close()
|
||||
reader.Close()
|
||||
}()
|
||||
|
||||
// Start decoding event stream json
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
var err error
|
||||
var event plevents.BaseEvent
|
||||
|
||||
defer wg.Done()
|
||||
|
||||
decoder := json.NewDecoder(reader)
|
||||
for decoder.More() {
|
||||
if err = decoder.Decode(&event); err == nil {
|
||||
m.publisher.PublishEvent(event)
|
||||
}
|
||||
}
|
||||
errors <- err
|
||||
|
||||
reader.Close()
|
||||
writer.Close()
|
||||
}()
|
||||
|
||||
// Create a channel signaling when the waitgroup finishes
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
wg.Wait()
|
||||
close(errors)
|
||||
close(done)
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-done:
|
||||
for err := range errors {
|
||||
if err != nil {
|
||||
log.Warnf("Exiting Events Monitor: %#v", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
case <-m.stop:
|
||||
cancel()
|
||||
writer.Close()
|
||||
reader.Close()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// PublishEvent translates select portlayer container events into Docker events
|
||||
// and publishes to subscribers
|
||||
func (p DockerEventPublisher) PublishEvent(event plevents.BaseEvent) {
|
||||
// create a shortID for the container for logging purposes
|
||||
containerShortID := uid.Parse(event.Ref).Truncate()
|
||||
defer trace.End(trace.Begin(fmt.Sprintf("Event Monitor received eventID(%s) for container(%s) - %s", event.ID, containerShortID, event.Event)))
|
||||
|
||||
vc := cache.ContainerCache().GetContainer(event.Ref)
|
||||
if vc == nil && event.Event != plevents.ContainerCreated {
|
||||
log.Warnf("Event Monitor received eventID(%s) but container(%s) not in cache", event.ID, containerShortID)
|
||||
return
|
||||
}
|
||||
|
||||
// docker event attributes
|
||||
var attrs map[string]string
|
||||
|
||||
switch event.Event {
|
||||
case plevents.ContainerCreated:
|
||||
syncContainerCache()
|
||||
case plevents.ContainerStarted:
|
||||
attrs = make(map[string]string)
|
||||
|
||||
actor := CreateContainerEventActorWithAttributes(vc, attrs)
|
||||
EventService().Log(containerStartEvent, eventtypes.ContainerEventType, actor)
|
||||
|
||||
case plevents.ContainerStopped,
|
||||
plevents.ContainerPoweredOff,
|
||||
plevents.ContainerFailed:
|
||||
// since we are going to make a call to the portLayer lets execute this in a go routine
|
||||
go func() {
|
||||
attrs = make(map[string]string)
|
||||
// get the containerEngine
|
||||
code, _ := NewContainerBackend().containerProxy.ExitCode(context.Background(), vc)
|
||||
|
||||
log.Infof("Sending die event for container(%s) with exitCode[%s] - eventID(%s)", containerShortID, code, event.ID)
|
||||
// if the docker client is unable to convert the code to an int the client will return 125
|
||||
attrs["exitCode"] = code
|
||||
actor := CreateContainerEventActorWithAttributes(vc, attrs)
|
||||
EventService().Log(containerDieEvent, eventtypes.ContainerEventType, actor)
|
||||
// TODO: this really, really shouldn't be in the event publishing code - it's fine to have multiple consumers of events
|
||||
// and this should be registered as a callback by the logic responsible for the MapPorts portion.
|
||||
if err := network.UnmapPorts(vc.ContainerID, vc); err != nil {
|
||||
log.Errorf("Event Monitor failed to unmap ports for container(%s): %s - eventID(%s)", containerShortID, err, event.ID)
|
||||
}
|
||||
|
||||
// auto-remove if required
|
||||
// TODO: this should be a separate event hook registered by logic outside of the publish events loop.
|
||||
if vc.HostConfig.AutoRemove {
|
||||
config := &types.ContainerRmConfig{
|
||||
ForceRemove: true,
|
||||
RemoveVolume: true,
|
||||
}
|
||||
|
||||
err := NewContainerBackend().ContainerRm(vc.Name, config)
|
||||
if err != nil {
|
||||
log.Errorf("Event Monitor failed to remove container(%s) - eventID(%s): %s", containerShortID, event.ID, err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
case plevents.ContainerRemoved:
|
||||
attrs = make(map[string]string)
|
||||
// pop the destroy event...
|
||||
actor := CreateContainerEventActorWithAttributes(vc, attrs)
|
||||
EventService().Log(containerDestroyEvent, eventtypes.ContainerEventType, actor)
|
||||
if err := network.UnmapPorts(vc.ContainerID, vc); err != nil {
|
||||
log.Errorf("Event Monitor failed to unmap ports for container(%s): %s - eventID(%s)", containerShortID, err, event.ID)
|
||||
}
|
||||
// remove from the container cache...
|
||||
cache.ContainerCache().DeleteContainer(vc.ContainerID)
|
||||
default:
|
||||
// let everything else slide on by...
|
||||
}
|
||||
|
||||
}
|
||||
137
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/eventmonitor_test.go
generated
vendored
Normal file
137
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/eventmonitor_test.go
generated
vendored
Normal file
@@ -0,0 +1,137 @@
|
||||
// Copyright 2017 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package backends
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
plevents "github.com/vmware/vic/lib/portlayer/event/events"
|
||||
)
|
||||
|
||||
type MockEventProxy struct {
|
||||
MockEvents []plevents.BaseEvent
|
||||
Delay time.Duration
|
||||
}
|
||||
|
||||
type MockEventPublisher struct {
|
||||
MockEventChan chan plevents.BaseEvent
|
||||
}
|
||||
|
||||
func (ep *MockEventProxy) StreamEvents(ctx context.Context, out io.Writer) error {
|
||||
encoder := json.NewEncoder(out)
|
||||
if encoder == nil {
|
||||
return fmt.Errorf("Failed to create a json encoder")
|
||||
}
|
||||
|
||||
for _, event := range ep.MockEvents {
|
||||
if err := encoder.Encode(event); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
time.Sleep(ep.Delay)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *MockEventPublisher) PublishEvent(event plevents.BaseEvent) {
|
||||
if p.MockEventChan != nil {
|
||||
p.MockEventChan <- event
|
||||
}
|
||||
}
|
||||
|
||||
func TestStartStopMonitor(t *testing.T) {
|
||||
proxy := MockEventProxy{
|
||||
MockEvents: []plevents.BaseEvent{
|
||||
{
|
||||
Event: plevents.ContainerCreated,
|
||||
Ref: "abc",
|
||||
},
|
||||
{
|
||||
Event: plevents.ContainerStarted,
|
||||
Ref: "abc",
|
||||
},
|
||||
{
|
||||
Event: plevents.ContainerStopped,
|
||||
Ref: "abc",
|
||||
},
|
||||
},
|
||||
Delay: 1 * time.Second,
|
||||
}
|
||||
publisher := MockEventPublisher{
|
||||
MockEventChan: make(chan plevents.BaseEvent, 3),
|
||||
}
|
||||
monitor := NewPortlayerEventMonitor(&proxy, &publisher)
|
||||
|
||||
var err error
|
||||
|
||||
// The actual tests
|
||||
err = monitor.Start()
|
||||
assert.Nil(t, err, "Expected monitor start to succeed, but received: %#v", err)
|
||||
|
||||
err = monitor.Start()
|
||||
assert.NotEqual(t, err, nil, "Expected error but received nil on double start")
|
||||
if err != nil {
|
||||
assert.Contains(t, err.Error(), "Already started", "Expected already started error but received: %s", err)
|
||||
}
|
||||
|
||||
monitor.Stop()
|
||||
}
|
||||
|
||||
func TestEventMonitor(t *testing.T) {
|
||||
proxy := MockEventProxy{
|
||||
MockEvents: []plevents.BaseEvent{
|
||||
{
|
||||
Event: plevents.ContainerCreated,
|
||||
Ref: "abc",
|
||||
},
|
||||
{
|
||||
Event: plevents.ContainerStarted,
|
||||
Ref: "abc",
|
||||
},
|
||||
{
|
||||
Event: plevents.ContainerStopped,
|
||||
Ref: "abc",
|
||||
},
|
||||
},
|
||||
Delay: 0,
|
||||
}
|
||||
publisher := MockEventPublisher{
|
||||
MockEventChan: make(chan plevents.BaseEvent, 3),
|
||||
}
|
||||
monitor := NewPortlayerEventMonitor(&proxy, &publisher)
|
||||
|
||||
var err error
|
||||
|
||||
// The actual tests
|
||||
err = monitor.Start()
|
||||
assert.Nil(t, err, "Expected monitor start to succeed, but received: %#v", err)
|
||||
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
count := len(publisher.MockEventChan)
|
||||
for i := 0; i < count; i++ {
|
||||
event := <-publisher.MockEventChan
|
||||
assert.Equal(t, event.Event, proxy.MockEvents[i].Event, "Expected to find event %s but found %s", proxy.MockEvents[i].Event, event.Event)
|
||||
}
|
||||
}
|
||||
171
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/executor/SwarmBackend.go
generated
vendored
Normal file
171
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/executor/SwarmBackend.go
generated
vendored
Normal file
@@ -0,0 +1,171 @@
|
||||
// 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
|
||||
}
|
||||
242
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/filter/container.go
generated
vendored
Normal file
242
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/filter/container.go
generated
vendored
Normal file
@@ -0,0 +1,242 @@
|
||||
// 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"
|
||||
"strconv"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
|
||||
"github.com/vmware/vic/lib/apiservers/engine/backends/cache"
|
||||
"github.com/vmware/vic/lib/apiservers/portlayer/models"
|
||||
)
|
||||
|
||||
// reused from docker/docker/daemon/list.go
|
||||
type ContainerListContext struct {
|
||||
FilterContext
|
||||
|
||||
// Counter is the container iteration index for this context
|
||||
Counter int
|
||||
// ExitCode for the passed container
|
||||
ExitCode int
|
||||
// exitAllowed is a list of exit codes allowed to filter with
|
||||
exitAllowed map[int]struct{}
|
||||
// ContainerListOptions is the filters set by the user
|
||||
*types.ContainerListOptions
|
||||
}
|
||||
|
||||
// IncludeContainer will evaluate the filter criteria in listContext against the provided
|
||||
// container and determine what action to take. There are three options:
|
||||
// * IncludeAction
|
||||
// * ExcludeAction
|
||||
// * StopAction
|
||||
func IncludeContainer(listContext *ContainerListContext, container *models.ContainerInfo) FilterAction {
|
||||
|
||||
// if we need to filter on name add to the listContext
|
||||
if listContext.Filters.Include("name") {
|
||||
// containerConfig allows for multiple names, but only 1 ever
|
||||
// assigned
|
||||
listContext.Name = container.ContainerConfig.Names[0]
|
||||
}
|
||||
|
||||
// filter common requirements
|
||||
act := filterCommon(&listContext.FilterContext, listContext.Filters)
|
||||
if act != IncludeAction {
|
||||
return act
|
||||
}
|
||||
|
||||
// Stop iteration when the index is over the limit
|
||||
if listContext.Limit > 0 && listContext.Counter == listContext.Limit {
|
||||
return StopAction
|
||||
}
|
||||
|
||||
// Do we have exit codes to evaluate
|
||||
if len(listContext.exitAllowed) > 0 {
|
||||
|
||||
// Is the containers exitCode in the validatedList?
|
||||
_, ok := listContext.exitAllowed[listContext.ExitCode]
|
||||
|
||||
// only include container whose exit code is in the list and that's
|
||||
// not currently running and has been started previously
|
||||
// note "Running" state is congruent with PortLayer and not docker
|
||||
if !ok || container.ContainerConfig.State == "Running" || container.ProcessConfig.StartTime == 0 {
|
||||
return ExcludeAction
|
||||
}
|
||||
}
|
||||
|
||||
state := DockerState(container.ContainerConfig.State)
|
||||
// Do not include container if its status doesn't match the filter
|
||||
if !listContext.Filters.Match("status", state) {
|
||||
return ExcludeAction
|
||||
}
|
||||
|
||||
// Filter on network name
|
||||
if listContext.Filters.Include("network") {
|
||||
netFilterValues := listContext.Filters.Get("network")
|
||||
|
||||
// Exclude the container if its network(s) match no supplied filter values
|
||||
exists := false
|
||||
for i := range netFilterValues {
|
||||
for j := range container.Endpoints {
|
||||
if netFilterValues[i] == container.Endpoints[j].Scope {
|
||||
exists = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if !exists {
|
||||
return ExcludeAction
|
||||
}
|
||||
}
|
||||
|
||||
// Filter on volume name
|
||||
if listContext.Filters.Include("volume") {
|
||||
volFilterValues := listContext.Filters.Get("volume")
|
||||
|
||||
// Exclude the container if its volume(s) match no supplied filter values
|
||||
exists := false
|
||||
for i := range volFilterValues {
|
||||
for j := range container.VolumeConfig {
|
||||
if volFilterValues[i] == container.VolumeConfig[j].Name {
|
||||
exists = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if !exists {
|
||||
return ExcludeAction
|
||||
}
|
||||
}
|
||||
|
||||
return IncludeAction
|
||||
}
|
||||
|
||||
// ValidateContainerFilters validates that the container filters are
|
||||
// valid docker filters / values and supported by VIC.
|
||||
// The function reuses Docker's filter validation.
|
||||
func ValidateContainerFilters(options *types.ContainerListOptions, acceptedFilters map[string]bool, unSupportedFilters map[string]bool) (*ContainerListContext, error) {
|
||||
containerFilters := options.Filters
|
||||
|
||||
// ensure filter options are valid and supported by vic
|
||||
if err := ValidateFilters(containerFilters, acceptedFilters, unSupportedFilters); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// we need all containers for these options, so set the All flag
|
||||
if options.Limit > 0 || options.Latest {
|
||||
options.All = true
|
||||
}
|
||||
|
||||
var s struct{}
|
||||
filtExited := make(map[int]struct{})
|
||||
|
||||
err := containerFilters.WalkValues("exited", func(value string) error {
|
||||
code, err := strconv.Atoi(value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// add valid exit code to map
|
||||
filtExited[code] = s
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = containerFilters.WalkValues("status", func(value string) error {
|
||||
if !IsValidDockerState(value) {
|
||||
return fmt.Errorf("Unrecognised filter value for status: %s", value)
|
||||
}
|
||||
options.All = true
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// return value
|
||||
listContext := &ContainerListContext{
|
||||
FilterContext: FilterContext{},
|
||||
exitAllowed: filtExited,
|
||||
ContainerListOptions: options,
|
||||
}
|
||||
|
||||
err = containerFilters.WalkValues("before", func(value string) error {
|
||||
var err error
|
||||
before := cache.ContainerCache().GetContainer(value)
|
||||
if before == nil {
|
||||
err = fmt.Errorf("No such container: %s", value)
|
||||
} else {
|
||||
listContext.BeforeID = &before.ContainerID
|
||||
}
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = containerFilters.WalkValues("since", func(value string) error {
|
||||
var err error
|
||||
since := cache.ContainerCache().GetContainer(value)
|
||||
if since == nil {
|
||||
err = fmt.Errorf("No such container: %s", value)
|
||||
} else {
|
||||
listContext.SinceID = &since.ContainerID
|
||||
}
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return listContext, nil
|
||||
}
|
||||
|
||||
// DockerState will attempt to transform the passed state
|
||||
// to a valid docker state
|
||||
// valid states are listed in the func IsValidContainerState
|
||||
func DockerState(containerState string) string {
|
||||
var state string
|
||||
switch containerState {
|
||||
case "Stopped":
|
||||
state = "exited"
|
||||
case "Running":
|
||||
state = "running"
|
||||
case "Created":
|
||||
state = "created"
|
||||
default:
|
||||
// not sure what to do, so just return
|
||||
// what was given
|
||||
state = containerState
|
||||
}
|
||||
return state
|
||||
}
|
||||
|
||||
// IsValidDockerState will verify the provided state is
|
||||
// a valid docker container state
|
||||
func IsValidDockerState(s string) bool {
|
||||
|
||||
if s != "paused" &&
|
||||
s != "restarting" &&
|
||||
s != "removing" &&
|
||||
s != "running" &&
|
||||
s != "dead" &&
|
||||
s != "created" &&
|
||||
s != "exited" {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
213
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/filter/container_test.go
generated
vendored
Normal file
213
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/filter/container_test.go
generated
vendored
Normal file
@@ -0,0 +1,213 @@
|
||||
// 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))
|
||||
}
|
||||
77
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/filter/filter.go
generated
vendored
Normal file
77
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/filter/filter.go
generated
vendored
Normal file
@@ -0,0 +1,77 @@
|
||||
// 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 (
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
)
|
||||
|
||||
// FilterAction represents possible results during filtering
|
||||
type FilterAction int
|
||||
|
||||
const (
|
||||
IncludeAction FilterAction = iota
|
||||
ExcludeAction
|
||||
StopAction
|
||||
)
|
||||
|
||||
// FilterContext will hold the common filter requirements
|
||||
type FilterContext struct {
|
||||
// ID of object to filter
|
||||
ID string
|
||||
// Name of object to filter
|
||||
Name string
|
||||
// BeforeID is the filter to ignore objects that appear before the one given
|
||||
BeforeID *string
|
||||
// SinceID is the filter to stop iterating
|
||||
SinceID *string
|
||||
// Labels of object to filter
|
||||
Labels map[string]string
|
||||
}
|
||||
|
||||
// filterCommon will filter the common criteria across objects
|
||||
func filterCommon(filterContext *FilterContext, cmdFilters filters.Args) FilterAction {
|
||||
|
||||
// have we made it to the beforeID
|
||||
if filterContext.BeforeID != nil {
|
||||
if filterContext.ID == *filterContext.BeforeID {
|
||||
filterContext.BeforeID = nil
|
||||
}
|
||||
return ExcludeAction
|
||||
}
|
||||
|
||||
// Stop iteration when the object arrives to the filter object
|
||||
if filterContext.SinceID != nil {
|
||||
if filterContext.ID == *filterContext.SinceID {
|
||||
return StopAction
|
||||
}
|
||||
}
|
||||
|
||||
// Do not include object if any of the labels don't match
|
||||
if !cmdFilters.MatchKVList("label", filterContext.Labels) {
|
||||
return ExcludeAction
|
||||
}
|
||||
|
||||
// Do not include if the id doesn't match
|
||||
if !cmdFilters.Match("id", filterContext.ID) {
|
||||
return ExcludeAction
|
||||
}
|
||||
|
||||
if !cmdFilters.Match("name", filterContext.Name) {
|
||||
return ExcludeAction
|
||||
}
|
||||
|
||||
return IncludeAction
|
||||
}
|
||||
69
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/filter/filter_test.go
generated
vendored
Normal file
69
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/filter/filter_test.go
generated
vendored
Normal file
@@ -0,0 +1,69 @@
|
||||
// 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
|
||||
}
|
||||
136
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/filter/image.go
generated
vendored
Normal file
136
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/filter/image.go
generated
vendored
Normal file
@@ -0,0 +1,136 @@
|
||||
// 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"
|
||||
"path"
|
||||
|
||||
"github.com/docker/distribution/reference"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
|
||||
"github.com/vmware/vic/lib/apiservers/engine/backends/cache"
|
||||
)
|
||||
|
||||
type ImageListContext struct {
|
||||
FilterContext
|
||||
|
||||
// Tags for an image filtered by reference
|
||||
Tags []string
|
||||
// Digests for an image filtered by reference
|
||||
Digests []string
|
||||
}
|
||||
|
||||
/*
|
||||
* ValidateImageFilters will validate the image filters are
|
||||
* valid docker filters / values and supported by vic.
|
||||
*
|
||||
* The function will reuse dockers filter validation
|
||||
*
|
||||
*/
|
||||
func ValidateImageFilters(cmdFilters filters.Args, acceptedFilters map[string]bool, unSupportedFilters map[string]bool) (*ImageListContext, error) {
|
||||
|
||||
// ensure filter options are valid and supported by vic
|
||||
if err := ValidateFilters(cmdFilters, acceptedFilters, unSupportedFilters); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// return value
|
||||
imgFilterContext := &ImageListContext{}
|
||||
|
||||
err := cmdFilters.WalkValues("before", func(value string) error {
|
||||
before, err := cache.ImageCache().Get(value)
|
||||
if before == nil {
|
||||
err = fmt.Errorf("No such image: %s", value)
|
||||
} else {
|
||||
imgFilterContext.BeforeID = &before.ImageID
|
||||
}
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = cmdFilters.WalkValues("since", func(value string) error {
|
||||
since, err := cache.ImageCache().Get(value)
|
||||
if since == nil {
|
||||
err = fmt.Errorf("No such image: %s", value)
|
||||
} else {
|
||||
imgFilterContext.SinceID = &since.ImageID
|
||||
}
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return imgFilterContext, nil
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* IncludeImage will evaluate the filter criteria in filterContext against the provided
|
||||
* image and determine what action to take. There are three options:
|
||||
* * IncludeAction
|
||||
* * ExcludeAction
|
||||
* * StopAction
|
||||
*
|
||||
*/
|
||||
func IncludeImage(imgFilters filters.Args, listContext *ImageListContext) FilterAction {
|
||||
|
||||
// filter common requirements
|
||||
act := filterCommon(&listContext.FilterContext, imgFilters)
|
||||
if act != IncludeAction {
|
||||
return act
|
||||
}
|
||||
|
||||
// filter on image reference
|
||||
if imgFilters.Include("reference") {
|
||||
// references for this imageID
|
||||
refs := cache.RepositoryCache().References(listContext.ID)
|
||||
// reference filters
|
||||
refFilters := imgFilters.Get("reference")
|
||||
|
||||
// reset the tags / digests
|
||||
listContext.Tags = nil
|
||||
listContext.Digests = nil
|
||||
|
||||
// iterate of reporsitory references and filters
|
||||
for _, ref := range refs {
|
||||
for _, rf := range refFilters {
|
||||
// match on complete ref ie. busybox:latest
|
||||
// #nosec: Errors unhandled.
|
||||
matchRef, _ := path.Match(rf, ref.String())
|
||||
// match on repo only ie. busybox
|
||||
// #nosec: Errors unhandled.
|
||||
matchName, _ := path.Match(rf, ref.Name())
|
||||
// if either matched then add to tag / digest
|
||||
if matchRef || matchName {
|
||||
if _, ok := ref.(reference.Canonical); ok {
|
||||
listContext.Digests = append(listContext.Digests, ref.String())
|
||||
}
|
||||
if _, ok := ref.(reference.NamedTagged); ok {
|
||||
listContext.Tags = append(listContext.Tags, ref.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// if there were no reference matches then exclude the image
|
||||
if len(listContext.Tags) == 0 && len(listContext.Digests) == 0 {
|
||||
return ExcludeAction
|
||||
}
|
||||
|
||||
}
|
||||
return IncludeAction
|
||||
}
|
||||
120
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/filter/image_test.go
generated
vendored
Normal file
120
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/filter/image_test.go
generated
vendored
Normal file
@@ -0,0 +1,120 @@
|
||||
// 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))
|
||||
}
|
||||
59
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/filter/validation.go
generated
vendored
Normal file
59
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/filter/validation.go
generated
vendored
Normal file
@@ -0,0 +1,59 @@
|
||||
// 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"
|
||||
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
)
|
||||
|
||||
/*
|
||||
* ValidateFilters will evalute the provided filters against the docker acceptedFilters and
|
||||
* the filters vic currently doesn't support (unSupportedFilters)
|
||||
*/
|
||||
func ValidateFilters(cmdFilters filters.Args, acceptedFilters map[string]bool, unSupportedFilters map[string]bool) error {
|
||||
|
||||
var err error
|
||||
|
||||
// ensure provided filter args are accepted
|
||||
if err = cmdFilters.Validate(acceptedFilters); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// verify that vic supports the provided filter args
|
||||
// will only make it here if all the filters are valid
|
||||
if err = validateSupport(cmdFilters, unSupportedFilters); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
/*
|
||||
* validateSupport will ensure the provided filter arguments are implemented
|
||||
* by vic
|
||||
*/
|
||||
func validateSupport(cmdFilters filters.Args, unSupported map[string]bool) error {
|
||||
|
||||
for filter := range unSupported {
|
||||
vals := cmdFilters.Get(filter)
|
||||
if len(vals) > 0 {
|
||||
return fmt.Errorf("filter %s is not currently supported by vic", filter)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
111
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/filter/validation_test.go
generated
vendored
Normal file
111
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/filter/validation_test.go
generated
vendored
Normal file
@@ -0,0 +1,111 @@
|
||||
// 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))
|
||||
}
|
||||
87
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/filter/volume.go
generated
vendored
Normal file
87
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/filter/volume.go
generated
vendored
Normal file
@@ -0,0 +1,87 @@
|
||||
// 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"
|
||||
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
)
|
||||
|
||||
// VolumeFilterContext stores volume information used while filtering
|
||||
type VolumeFilterContext struct {
|
||||
FilterContext
|
||||
|
||||
// Dangling is the value of the dangling filter if supplied
|
||||
Dangling bool
|
||||
|
||||
// Joined tells whether the volume is joined to a container or not
|
||||
Joined bool
|
||||
|
||||
// Driver is the volume's driver
|
||||
Driver string
|
||||
}
|
||||
|
||||
// ValidateVolumeFilters checks that the supplied filters are valid and supported
|
||||
// and returns a context used in the IncludeVolume func.
|
||||
func ValidateVolumeFilters(volFilters filters.Args, acceptedFilters, unSupportedFilters map[string]bool) (*VolumeFilterContext, error) {
|
||||
|
||||
if err := ValidateFilters(volFilters, acceptedFilters, unSupportedFilters); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
volFilterContext := &VolumeFilterContext{}
|
||||
|
||||
// Set value of dangling filter if it's supplied
|
||||
if volFilters.Include("dangling") {
|
||||
|
||||
// Validate dangling filter's usage (per Docker code)
|
||||
// Supported formats: dangling={true, false, 1, 0}
|
||||
if volFilters.ExactMatch("dangling", "true") || volFilters.ExactMatch("dangling", "1") {
|
||||
volFilterContext.Dangling = true
|
||||
} else if !volFilters.ExactMatch("dangling", "false") && !volFilters.ExactMatch("dangling", "0") {
|
||||
return nil, fmt.Errorf("invalid filter 'dangling=%s'", volFilters.Get("dangling"))
|
||||
}
|
||||
}
|
||||
|
||||
return volFilterContext, nil
|
||||
}
|
||||
|
||||
// IncludeVolume evaluates volume filters and the filter context and returns
|
||||
// an action to indicate whether to include the volume in the output or not.
|
||||
func IncludeVolume(volumeFilters filters.Args, volFilterContext *VolumeFilterContext) FilterAction {
|
||||
|
||||
// Filter by name and label
|
||||
action := filterCommon(&volFilterContext.FilterContext, volumeFilters)
|
||||
if action != IncludeAction {
|
||||
return action
|
||||
}
|
||||
|
||||
if volumeFilters.Include("dangling") {
|
||||
// Exclude the volume if dangling=false or it is joined,
|
||||
// and if dangling=true or it is not joined
|
||||
if (!volFilterContext.Dangling || volFilterContext.Joined) && (volFilterContext.Dangling || !volFilterContext.Joined) {
|
||||
return ExcludeAction
|
||||
}
|
||||
}
|
||||
|
||||
if volumeFilters.Include("driver") {
|
||||
if !volumeFilters.ExactMatch("driver", volFilterContext.Driver) {
|
||||
return ExcludeAction
|
||||
}
|
||||
}
|
||||
|
||||
return IncludeAction
|
||||
}
|
||||
114
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/filter/volume_test.go
generated
vendored
Normal file
114
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/filter/volume_test.go
generated
vendored
Normal file
@@ -0,0 +1,114 @@
|
||||
// Copyright 2017 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package filter
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestValidateVolumeFilters(t *testing.T) {
|
||||
|
||||
// Valid filters
|
||||
volFilters := filters.NewArgs()
|
||||
volFilters.Add("dangling", "true")
|
||||
volFilters.Add("name", "mulder")
|
||||
volFilters.Add("driver", "aliens")
|
||||
volFilterContext, err := ValidateVolumeFilters(volFilters, acceptedVolumeFilterTags, nil)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, volFilterContext.Dangling, true)
|
||||
|
||||
// Change a filter's value
|
||||
volFilters.Del("dangling", "true")
|
||||
volFilters.Add("dangling", "false")
|
||||
volFilterContext, err = ValidateVolumeFilters(volFilters, acceptedVolumeFilterTags, nil)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, volFilterContext.Dangling, false)
|
||||
|
||||
// Valid filter with invalid value
|
||||
volFilters.Del("dangling", "false")
|
||||
volFilters.Add("dangling", "no")
|
||||
volFilterContext, err = ValidateVolumeFilters(volFilters, acceptedVolumeFilterTags, nil)
|
||||
assert.Error(t, err)
|
||||
|
||||
// Invalid filter
|
||||
volFilters.Add("mountpoint", "/volumes")
|
||||
_, err = ValidateVolumeFilters(volFilters, acceptedVolumeFilterTags, nil)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestIncludeVolume(t *testing.T) {
|
||||
|
||||
// Filter by dangling=true
|
||||
volFilters := filters.NewArgs()
|
||||
volFilters.Add("dangling", "true")
|
||||
volFilterContext := &VolumeFilterContext{
|
||||
FilterContext: FilterContext{
|
||||
Name: "scully",
|
||||
Labels: map[string]string{"samplelabel": ""},
|
||||
},
|
||||
Driver: "science",
|
||||
Joined: false,
|
||||
Dangling: true,
|
||||
}
|
||||
action := IncludeVolume(volFilters, volFilterContext)
|
||||
assert.Equal(t, action, IncludeAction)
|
||||
|
||||
// Filter by dangling=false
|
||||
volFilters.Del("dangling", "true")
|
||||
volFilters.Add("dangling", "false")
|
||||
volFilterContext.Dangling = false
|
||||
action = IncludeVolume(volFilters, volFilterContext)
|
||||
assert.Equal(t, action, ExcludeAction)
|
||||
|
||||
// Filter by name and dangling=false
|
||||
volFilters.Add("name", "scul")
|
||||
volFilterContext.Joined = true
|
||||
action = IncludeVolume(volFilters, volFilterContext)
|
||||
assert.Equal(t, action, IncludeAction)
|
||||
|
||||
// Filter by name, dangling=false and driver=science
|
||||
volFilters.Add("driver", "science")
|
||||
action = IncludeVolume(volFilters, volFilterContext)
|
||||
assert.Equal(t, action, IncludeAction)
|
||||
|
||||
// Filter by incorrect name, dangling=false and incorrect driver
|
||||
volFilterContext.Name = "mulder"
|
||||
volFilterContext.Driver = "aliens"
|
||||
action = IncludeVolume(volFilters, volFilterContext)
|
||||
assert.Equal(t, action, ExcludeAction)
|
||||
|
||||
// Filter by name, dangling=false and incorrect driver
|
||||
volFilterContext.Name = "scully"
|
||||
volFilterContext.Driver = "science"
|
||||
volFilters.Del("driver", "science")
|
||||
volFilters.Add("driver", "sci")
|
||||
action = IncludeVolume(volFilters, volFilterContext)
|
||||
assert.Equal(t, action, ExcludeAction)
|
||||
|
||||
// Filter by correct label
|
||||
volFilters = filters.NewArgs()
|
||||
volFilters.Add("label", "samplelabel")
|
||||
action = IncludeVolume(volFilters, volFilterContext)
|
||||
assert.Equal(t, action, IncludeAction)
|
||||
|
||||
// Filter by incorrect label
|
||||
volFilters.Del("label", "samplelabel")
|
||||
volFilters.Add("label", "wronglabel")
|
||||
action = IncludeVolume(volFilters, volFilterContext)
|
||||
assert.Equal(t, action, ExcludeAction)
|
||||
}
|
||||
501
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/image.go
generated
vendored
Normal file
501
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/image.go
generated
vendored
Normal file
@@ -0,0 +1,501 @@
|
||||
// Copyright 2016-2018 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package backends
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
|
||||
"github.com/docker/distribution/digest"
|
||||
"github.com/docker/docker/api/types"
|
||||
eventtypes "github.com/docker/docker/api/types/events"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"github.com/docker/docker/api/types/registry"
|
||||
"github.com/docker/docker/pkg/streamformatter"
|
||||
"github.com/docker/docker/reference"
|
||||
|
||||
"github.com/vmware/vic/lib/apiservers/engine/backends/cache"
|
||||
vicfilter "github.com/vmware/vic/lib/apiservers/engine/backends/filter"
|
||||
"github.com/vmware/vic/lib/apiservers/engine/errors"
|
||||
"github.com/vmware/vic/lib/apiservers/portlayer/client/storage"
|
||||
"github.com/vmware/vic/lib/imagec"
|
||||
"github.com/vmware/vic/lib/metadata"
|
||||
"github.com/vmware/vic/lib/portlayer/util"
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
"github.com/vmware/vic/pkg/uid"
|
||||
"github.com/vmware/vic/pkg/vsphere/sys"
|
||||
)
|
||||
|
||||
// valid filters as of docker commit 49bf474
|
||||
var acceptedImageFilterTags = map[string]bool{
|
||||
"dangling": true,
|
||||
"label": true,
|
||||
"before": true,
|
||||
"since": true,
|
||||
"reference": true,
|
||||
}
|
||||
|
||||
// currently not supported by vic
|
||||
var unSupportedImageFilters = map[string]bool{
|
||||
"dangling": false,
|
||||
}
|
||||
|
||||
type ImageBackend struct {
|
||||
}
|
||||
|
||||
func NewImageBackend() *ImageBackend {
|
||||
return &ImageBackend{}
|
||||
}
|
||||
|
||||
func (i *ImageBackend) Exists(containerName string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// TODO fix the errors so the client doesnt print the generic POST or DELETE message
|
||||
func (i *ImageBackend) ImageDelete(imageRef string, force, prune bool) ([]types.ImageDelete, error) {
|
||||
defer trace.End(trace.Begin(imageRef))
|
||||
|
||||
var (
|
||||
deletedRes []types.ImageDelete
|
||||
userRefIsID bool
|
||||
)
|
||||
|
||||
// Use the image cache to go from the reference to the ID we use in the image store
|
||||
img, err := cache.ImageCache().Get(imageRef)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tags := img.Tags
|
||||
digests := img.Digests
|
||||
|
||||
// did the user pass an id or partial id
|
||||
userRefIsID = cache.ImageCache().IsImageID(imageRef)
|
||||
// do we have any reference conflicts
|
||||
if len(tags) > 1 && userRefIsID && !force {
|
||||
t := uid.Parse(img.ImageID).Truncate()
|
||||
return nil,
|
||||
fmt.Errorf("conflict: unable to delete %s (must be forced) - image is referenced in one or more repositories", t)
|
||||
}
|
||||
|
||||
// if we have an ID or only 1 tag/digest lets delete the vmdk(s) via the PL
|
||||
if userRefIsID || len(tags) == 1 || len(digests) == 1 {
|
||||
log.Infof("Deleting image via PL %s (%s)", img.ImageID, img.ID)
|
||||
|
||||
// storeName is the uuid of the host this service is running on.
|
||||
storeName, err := sys.UUID()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// We're going to delete all of the images in the layer branch starting
|
||||
// at the given leaf. BUT! we need to keep the images which may be
|
||||
// referenced by tags. Therefore, we need to assemble a list of images
|
||||
// (by URI) which are referred to by tags.
|
||||
allImages := cache.ImageCache().GetImages()
|
||||
keepNodes := make([]string, len(allImages))
|
||||
for idx, node := range allImages {
|
||||
imgURL, err := util.ImageURL(storeName, node.ImageID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
keepNodes[idx] = imgURL.String()
|
||||
}
|
||||
|
||||
params := storage.NewDeleteImageParamsWithContext(ctx).WithStoreName(storeName).WithID(img.ID).WithKeepNodes(keepNodes)
|
||||
// TODO: This will fail if any containerVMs are referencing the vmdk - vanilla docker
|
||||
// allows the removal of an image (via force flag) even if a container is referencing it
|
||||
// should vic?
|
||||
res, err := PortLayerClient().Storage.DeleteImage(params)
|
||||
|
||||
// We may have deleted images despite error. Account for that in the cache.
|
||||
if res != nil {
|
||||
for _, deletedImage := range res.Payload {
|
||||
|
||||
// map the layer id to the blob sum so the ids map to what we
|
||||
// present to the user on pull
|
||||
id := deletedImage.ID
|
||||
i, err := imagec.LayerCache().Get(deletedImage.ID)
|
||||
if err == nil {
|
||||
id = i.Layer.BlobSum
|
||||
}
|
||||
|
||||
// remove the layer from the layer cache (used by imagec)
|
||||
imagec.LayerCache().Remove(deletedImage.ID)
|
||||
|
||||
// form the response
|
||||
imageDeleted := types.ImageDelete{Deleted: strings.TrimPrefix(id, "sha256:")}
|
||||
deletedRes = append(deletedRes, imageDeleted)
|
||||
}
|
||||
|
||||
if err := imagec.LayerCache().Save(); err != nil {
|
||||
return nil, fmt.Errorf("failed to save layer cache: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
switch err := err.(type) {
|
||||
case *storage.DeleteImageLocked:
|
||||
return nil, fmt.Errorf("Failed to remove image %q: %s", imageRef, err.Payload.Message)
|
||||
default:
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// we've deleted the image so remove from cache
|
||||
cache.ImageCache().RemoveImageByConfig(img)
|
||||
if err := cache.ImageCache().Save(); err != nil {
|
||||
return nil, fmt.Errorf("failed to save image cache: %s", err)
|
||||
}
|
||||
|
||||
actor := CreateImageEventActorWithAttributes(imageRef, imageRef, map[string]string{})
|
||||
EventService().Log("delete", eventtypes.ImageEventType, actor)
|
||||
} else {
|
||||
|
||||
// only untag the ref supplied
|
||||
n, err := reference.ParseNamed(imageRef)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to parse reference(%s): %s", imageRef, err.Error())
|
||||
}
|
||||
tag := reference.WithDefaultTag(n)
|
||||
tags = []string{tag.String()}
|
||||
|
||||
actor := CreateImageEventActorWithAttributes(imageRef, imageRef, map[string]string{})
|
||||
EventService().Log("untag", eventtypes.ImageEventType, actor)
|
||||
}
|
||||
// loop thru and remove from repoCache
|
||||
for i := range tags {
|
||||
// remove from cache, but don't save -- we'll do that afer all
|
||||
// updates
|
||||
// #nosec: Errors unhandled.
|
||||
refNamed, _ := cache.RepositoryCache().Remove(tags[i], false)
|
||||
deletedRes = append(deletedRes, types.ImageDelete{Untagged: refNamed})
|
||||
}
|
||||
|
||||
for i := range digests {
|
||||
// #nosec: Errors unhandled.
|
||||
refNamed, _ := cache.RepositoryCache().Remove(digests[i], false)
|
||||
deletedRes = append(deletedRes, types.ImageDelete{Untagged: refNamed})
|
||||
}
|
||||
|
||||
// save repo now -- this will limit the number of PL
|
||||
// calls to one per rmi call
|
||||
err = cache.RepositoryCache().Save()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Untag error: %s", err.Error())
|
||||
}
|
||||
|
||||
return deletedRes, err
|
||||
}
|
||||
|
||||
func (i *ImageBackend) ImageHistory(imageName string) ([]*types.ImageHistory, error) {
|
||||
return nil, errors.APINotSupportedMsg(ProductName(), "ImageHistory")
|
||||
}
|
||||
|
||||
func (i *ImageBackend) Images(imageFilters filters.Args, all bool, withExtraAttrs bool) ([]*types.ImageSummary, error) {
|
||||
defer trace.End(trace.Begin(fmt.Sprintf("imageFilters: %#v", imageFilters)))
|
||||
|
||||
// validate filters for accuracy and support
|
||||
filterContext, err := vicfilter.ValidateImageFilters(imageFilters, acceptedImageFilterTags, unSupportedImageFilters)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// get all images
|
||||
images := cache.ImageCache().GetImages()
|
||||
|
||||
result := make([]*types.ImageSummary, 0, len(images))
|
||||
|
||||
imageLoop:
|
||||
for i := range images {
|
||||
|
||||
// provide filter with current ImageID
|
||||
filterContext.ID = images[i].ImageID
|
||||
|
||||
// provide image labels
|
||||
if images[i].Config != nil {
|
||||
filterContext.Labels = images[i].Config.Labels
|
||||
}
|
||||
|
||||
// determine if image should be part of list
|
||||
action := vicfilter.IncludeImage(imageFilters, filterContext)
|
||||
|
||||
switch action {
|
||||
case vicfilter.ExcludeAction:
|
||||
continue imageLoop
|
||||
case vicfilter.StopAction:
|
||||
break imageLoop
|
||||
}
|
||||
// if we are here then add image
|
||||
dockerImage := convertV1ImageToDockerImage(images[i])
|
||||
// reference is a filter, so we must add the tags / digests
|
||||
// identified by the filter
|
||||
if imageFilters.Include("reference") {
|
||||
dockerImage.RepoTags = filterContext.Tags
|
||||
dockerImage.RepoDigests = filterContext.Digests
|
||||
|
||||
}
|
||||
result = append(result, dockerImage)
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// Docker Inspect. LookupImage looks up an image by name and returns it as an
|
||||
// ImageInspect structure.
|
||||
func (i *ImageBackend) LookupImage(name string) (*types.ImageInspect, error) {
|
||||
defer trace.End(trace.Begin("LookupImage (docker inspect)"))
|
||||
|
||||
imageConfig, err := cache.ImageCache().Get(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return imageConfigToDockerImageInspect(imageConfig, ProductName()), nil
|
||||
}
|
||||
|
||||
func (i *ImageBackend) TagImage(imageName, repository, tag string) error {
|
||||
img, err := cache.ImageCache().Get(imageName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
newTag, err := reference.WithName(repository)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if tag != "" {
|
||||
if newTag, err = reference.WithTag(newTag, tag); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// place tag in repo and save to portLayer k/v store
|
||||
err = cache.RepositoryCache().AddReference(newTag, img.ImageID, true, "", true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
actor := CreateImageEventActorWithAttributes(imageName, newTag.String(), map[string]string{})
|
||||
EventService().Log("tag", eventtypes.ImageEventType, actor)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *ImageBackend) ImagesPrune(pruneFilters filters.Args) (*types.ImagesPruneReport, error) {
|
||||
return nil, errors.APINotSupportedMsg(ProductName(), "ImagesPrune")
|
||||
}
|
||||
|
||||
func (i *ImageBackend) LoadImage(inTar io.ReadCloser, outStream io.Writer, quiet bool) error {
|
||||
return errors.APINotSupportedMsg(ProductName(), "LoadImage")
|
||||
}
|
||||
|
||||
func (i *ImageBackend) ImportImage(src string, repository, tag string, msg string, inConfig io.ReadCloser, outStream io.Writer, changes []string) error {
|
||||
return errors.APINotSupportedMsg(ProductName(), "ImportImage")
|
||||
}
|
||||
|
||||
func (i *ImageBackend) ExportImage(names []string, outStream io.Writer) error {
|
||||
return errors.APINotSupportedMsg(ProductName(), "ExportImage")
|
||||
}
|
||||
|
||||
func (i *ImageBackend) PullImage(ctx context.Context, image, tag string, metaHeaders map[string][]string, authConfig *types.AuthConfig, outStream io.Writer) error {
|
||||
defer trace.End(trace.Begin(""))
|
||||
|
||||
log.Debugf("PullImage: image = %s, tag = %s, metaheaders = %+v\n", image, tag, metaHeaders)
|
||||
|
||||
//***** Code from Docker 1.13 PullImage to convert image and tag to a ref
|
||||
image = strings.TrimSuffix(image, ":")
|
||||
|
||||
ref, err := reference.ParseNamed(image)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if tag != "" {
|
||||
// The "tag" could actually be a digest.
|
||||
var dgst digest.Digest
|
||||
dgst, err = digest.ParseDigest(tag)
|
||||
if err == nil {
|
||||
ref, err = reference.WithDigest(reference.TrimNamed(ref), dgst)
|
||||
} else {
|
||||
ref, err = reference.WithTag(ref, tag)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
//*****
|
||||
|
||||
options := imagec.Options{
|
||||
Destination: os.TempDir(),
|
||||
Reference: ref,
|
||||
Timeout: imagec.DefaultHTTPTimeout,
|
||||
Outstream: outStream,
|
||||
}
|
||||
|
||||
portLayerServer := PortLayerServer()
|
||||
if portLayerServer != "" {
|
||||
options.Host = portLayerServer
|
||||
}
|
||||
|
||||
ic := imagec.NewImageC(options, streamformatter.NewJSONStreamFormatter())
|
||||
ic.ParseReference()
|
||||
// create url from hostname
|
||||
hostnameURL, err := url.Parse(ic.Registry)
|
||||
if err != nil || hostnameURL.Hostname() == "" {
|
||||
hostnameURL, err = url.Parse("//" + ic.Registry)
|
||||
if err != nil {
|
||||
log.Infof("Error parsing hostname %s during registry access: %s", ic.Registry, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// Check if url is contained within set of whitelisted or insecure registries
|
||||
regctx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
||||
defer cancel()
|
||||
whitelistOk, _, insecureOk := vchConfig.RegistryCheck(regctx, hostnameURL)
|
||||
if !whitelistOk {
|
||||
err = fmt.Errorf("Access denied to unauthorized registry (%s) while VCH is in whitelist mode", hostnameURL.Host)
|
||||
log.Errorf(err.Error())
|
||||
sf := streamformatter.NewJSONStreamFormatter()
|
||||
outStream.Write(sf.FormatError(err))
|
||||
return nil
|
||||
}
|
||||
|
||||
ic.InsecureAllowHTTP = insecureOk
|
||||
ic.RegistryCAs = RegistryCertPool
|
||||
|
||||
if authConfig != nil {
|
||||
if len(authConfig.Username) > 0 {
|
||||
ic.Username = authConfig.Username
|
||||
}
|
||||
if len(authConfig.Password) > 0 {
|
||||
ic.Password = authConfig.Password
|
||||
}
|
||||
}
|
||||
|
||||
log.Infof("PullImage: reference: %s, %s, portlayer: %#v",
|
||||
ic.Reference,
|
||||
ic.Host,
|
||||
portLayerServer)
|
||||
|
||||
err = ic.PullImage()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
//TODO: Need repo name as second parameter. Leave blank for now
|
||||
actor := CreateImageEventActorWithAttributes(image, "", map[string]string{})
|
||||
EventService().Log("pull", eventtypes.ImageEventType, actor)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *ImageBackend) PushImage(ctx context.Context, image, tag string, metaHeaders map[string][]string, authConfig *types.AuthConfig, outStream io.Writer) error {
|
||||
return errors.APINotSupportedMsg(ProductName(), "PushImage")
|
||||
}
|
||||
|
||||
func (i *ImageBackend) SearchRegistryForImages(ctx context.Context, filtersArgs string, term string, limit int, authConfig *types.AuthConfig, metaHeaders map[string][]string) (*registry.SearchResults, error) {
|
||||
return nil, errors.APINotSupportedMsg(ProductName(), "SearchRegistryForImages")
|
||||
}
|
||||
|
||||
// Utility functions
|
||||
|
||||
func convertV1ImageToDockerImage(image *metadata.ImageConfig) *types.ImageSummary {
|
||||
var labels map[string]string
|
||||
if image.Config != nil {
|
||||
labels = image.Config.Labels
|
||||
}
|
||||
|
||||
return &types.ImageSummary{
|
||||
ID: image.ImageID,
|
||||
ParentID: image.Parent,
|
||||
RepoTags: image.Tags,
|
||||
RepoDigests: image.Digests,
|
||||
Created: image.Created.Unix(),
|
||||
Size: image.Size,
|
||||
VirtualSize: image.Size,
|
||||
Labels: labels,
|
||||
}
|
||||
}
|
||||
|
||||
// Converts the data structure retrieved from the portlayer. This src datastructure
|
||||
// represents the unmarshalled data saved in the storage port layer. The return
|
||||
// data is what the Docker CLI understand and returns to user.
|
||||
func imageConfigToDockerImageInspect(imageConfig *metadata.ImageConfig, productName string) *types.ImageInspect {
|
||||
if imageConfig == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
rootfs := types.RootFS{
|
||||
Type: "layers",
|
||||
Layers: make([]string, 0, len(imageConfig.History)),
|
||||
BaseLayer: "",
|
||||
}
|
||||
|
||||
for k := range imageConfig.DiffIDs {
|
||||
rootfs.Layers = append(rootfs.Layers, k)
|
||||
}
|
||||
|
||||
inspectData := &types.ImageInspect{
|
||||
RepoTags: imageConfig.Tags,
|
||||
RepoDigests: imageConfig.Digests,
|
||||
Parent: imageConfig.Parent,
|
||||
Comment: imageConfig.Comment,
|
||||
Created: imageConfig.Created.Format(time.RFC3339Nano),
|
||||
Container: imageConfig.Container,
|
||||
ContainerConfig: &imageConfig.ContainerConfig,
|
||||
DockerVersion: imageConfig.DockerVersion,
|
||||
Author: imageConfig.Author,
|
||||
Config: imageConfig.Config,
|
||||
Architecture: imageConfig.Architecture,
|
||||
Os: imageConfig.OS,
|
||||
Size: imageConfig.Size,
|
||||
VirtualSize: imageConfig.Size,
|
||||
RootFS: rootfs,
|
||||
}
|
||||
|
||||
inspectData.GraphDriver.Name = productName + " " + PortlayerName
|
||||
|
||||
// ImageID is currently stored within VIC without the "sha256:" prefix
|
||||
// so we add it here to match Docker output.
|
||||
inspectData.ID = digest.Canonical.String() + ":" + imageConfig.ImageID
|
||||
|
||||
return inspectData
|
||||
}
|
||||
|
||||
func CreateImageEventActorWithAttributes(imageID, refName string, attributes map[string]string) eventtypes.Actor {
|
||||
if imageConfig, err := cache.ImageCache().Get(imageID); err == nil && imageConfig != nil {
|
||||
for k, v := range imageConfig.Config.Labels {
|
||||
attributes[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
if refName != "" {
|
||||
attributes["name"] = refName
|
||||
}
|
||||
|
||||
return eventtypes.Actor{
|
||||
ID: imageID,
|
||||
Attributes: attributes,
|
||||
}
|
||||
}
|
||||
62
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/image_test.go
generated
vendored
Normal file
62
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/image_test.go
generated
vendored
Normal file
@@ -0,0 +1,62 @@
|
||||
// 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])
|
||||
}
|
||||
104
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/kv/kv.go
generated
vendored
Normal file
104
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/kv/kv.go
generated
vendored
Normal file
@@ -0,0 +1,104 @@
|
||||
// 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 kv
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/vmware/vic/lib/apiservers/portlayer/client"
|
||||
ckv "github.com/vmware/vic/lib/apiservers/portlayer/client/kv"
|
||||
"github.com/vmware/vic/lib/apiservers/portlayer/models"
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
|
||||
"context"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
// defaultNamespace is the first part of the
|
||||
// k/v store key (i.e. docker.stuff)
|
||||
defaultNamespace = "docker"
|
||||
defaultSeparator = "."
|
||||
)
|
||||
|
||||
var (
|
||||
ErrKeyNotFound = errors.New("key not found")
|
||||
)
|
||||
|
||||
// Get will call to the portlayer for the value of the specified key
|
||||
// The key argument is prefixed w/the defaultName space for the docker
|
||||
// persona. i.e. docker.{key}
|
||||
//
|
||||
// If the key doesn't exist an ErrKeyNotFound will be returned
|
||||
func Get(client *client.PortLayer, key string) (string, error) {
|
||||
defer trace.End(trace.Begin(key))
|
||||
var val string
|
||||
resp, err := client.Kv.GetValue(ckv.NewGetValueParamsWithContext(
|
||||
context.Background()).WithKey(createNameSpacedKey(key)))
|
||||
if err != nil {
|
||||
switch err.(type) {
|
||||
case *ckv.GetValueNotFound:
|
||||
return val, ErrKeyNotFound
|
||||
default:
|
||||
log.Errorf("Error Getting Key/Value: %s", err.Error())
|
||||
return val, err
|
||||
}
|
||||
}
|
||||
val = resp.Payload.Value
|
||||
// return the value
|
||||
return val, nil
|
||||
|
||||
}
|
||||
|
||||
// Put will put the key / value in the portlayer k/v store
|
||||
func Put(client *client.PortLayer, key string, val string) error {
|
||||
defer trace.End(trace.Begin(key))
|
||||
|
||||
fullKey := createNameSpacedKey(key)
|
||||
keyval := &models.KeyValue{
|
||||
Key: fullKey,
|
||||
Value: val,
|
||||
}
|
||||
|
||||
_, err := client.Kv.PutValue(ckv.NewPutValueParamsWithContext(
|
||||
context.Background()).WithKey(fullKey).WithKeyValue(keyval))
|
||||
if err != nil {
|
||||
log.Errorf("Error Putting Key/Value: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete will remove the key / value from the store
|
||||
func Delete(client *client.PortLayer, key string) error {
|
||||
defer trace.End(trace.Begin(key))
|
||||
|
||||
_, err := client.Kv.DeleteValue(ckv.NewDeleteValueParamsWithContext(
|
||||
context.Background()).WithKey(createNameSpacedKey(key)))
|
||||
if err != nil {
|
||||
log.Errorf("Error Deleting Key/Value: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func createNameSpacedKey(key string) string {
|
||||
return fmt.Sprintf("%s%s%s", defaultNamespace, defaultSeparator, key)
|
||||
}
|
||||
551
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/network.go
generated
vendored
Normal file
551
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/network.go
generated
vendored
Normal file
@@ -0,0 +1,551 @@
|
||||
// Copyright 2016-2017 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package backends
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"net/http"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
derr "github.com/docker/docker/api/errors"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
apinet "github.com/docker/docker/api/types/network"
|
||||
"github.com/docker/libnetwork"
|
||||
"github.com/docker/libnetwork/networkdb"
|
||||
|
||||
"github.com/vmware/vic/lib/apiservers/engine/backends/cache"
|
||||
"github.com/vmware/vic/lib/apiservers/engine/backends/convert"
|
||||
vicendpoint "github.com/vmware/vic/lib/apiservers/engine/backends/endpoint"
|
||||
"github.com/vmware/vic/lib/apiservers/engine/errors"
|
||||
"github.com/vmware/vic/lib/apiservers/portlayer/client/containers"
|
||||
"github.com/vmware/vic/lib/apiservers/portlayer/client/scopes"
|
||||
"github.com/vmware/vic/lib/apiservers/portlayer/models"
|
||||
"github.com/vmware/vic/pkg/retry"
|
||||
)
|
||||
|
||||
type NetworkBackend struct {
|
||||
}
|
||||
|
||||
func NewNetworkBackend() *NetworkBackend {
|
||||
return &NetworkBackend{}
|
||||
}
|
||||
|
||||
func (n *NetworkBackend) NetworkControllerEnabled() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (n *NetworkBackend) FindNetwork(idName string) (libnetwork.Network, error) {
|
||||
ok, err := PortLayerClient().Scopes.List(scopes.NewListParamsWithContext(ctx).WithIDName(idName))
|
||||
if err != nil {
|
||||
switch err := err.(type) {
|
||||
case *scopes.ListNotFound:
|
||||
return nil, derr.NewRequestNotFoundError(fmt.Errorf("network %s not found", idName))
|
||||
|
||||
case *scopes.ListDefault:
|
||||
return nil, derr.NewErrorWithStatusCode(fmt.Errorf(err.Payload.Message), http.StatusInternalServerError)
|
||||
|
||||
default:
|
||||
return nil, derr.NewErrorWithStatusCode(err, http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
return &vicnetwork{cfg: ok.Payload[0]}, nil
|
||||
}
|
||||
|
||||
func (n *NetworkBackend) GetNetworkByName(idName string) (libnetwork.Network, error) {
|
||||
ok, err := PortLayerClient().Scopes.List(scopes.NewListParamsWithContext(ctx).WithIDName(idName))
|
||||
if err != nil {
|
||||
switch err := err.(type) {
|
||||
case *scopes.ListNotFound:
|
||||
return nil, nil
|
||||
|
||||
case *scopes.ListDefault:
|
||||
return nil, derr.NewErrorWithStatusCode(fmt.Errorf(err.Payload.Message), http.StatusInternalServerError)
|
||||
|
||||
default:
|
||||
return nil, derr.NewErrorWithStatusCode(err, http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
return &vicnetwork{cfg: ok.Payload[0]}, nil
|
||||
}
|
||||
|
||||
func (n *NetworkBackend) GetNetworksByID(partialID string) []libnetwork.Network {
|
||||
ok, err := PortLayerClient().Scopes.List(scopes.NewListParamsWithContext(ctx).WithIDName(partialID))
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
nets := make([]libnetwork.Network, len(ok.Payload))
|
||||
for i, cfg := range ok.Payload {
|
||||
nets[i] = &vicnetwork{cfg: cfg}
|
||||
}
|
||||
|
||||
return nets
|
||||
}
|
||||
|
||||
func (n *NetworkBackend) GetNetworks() []libnetwork.Network {
|
||||
ok, err := PortLayerClient().Scopes.ListAll(scopes.NewListAllParamsWithContext(ctx))
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
nets := make([]libnetwork.Network, len(ok.Payload))
|
||||
for i, cfg := range ok.Payload {
|
||||
nets[i] = &vicnetwork{cfg: cfg}
|
||||
i++
|
||||
}
|
||||
|
||||
return nets
|
||||
}
|
||||
|
||||
func (n *NetworkBackend) CreateNetwork(nc types.NetworkCreateRequest) (*types.NetworkCreateResponse, error) {
|
||||
if nc.IPAM != nil && len(nc.IPAM.Config) > 1 {
|
||||
return nil, fmt.Errorf("at most one ipam config supported")
|
||||
}
|
||||
|
||||
var gateway, subnet string
|
||||
var pools []string
|
||||
if nc.IPAM != nil && len(nc.IPAM.Config) > 0 {
|
||||
if nc.IPAM.Config[0].Gateway != "" {
|
||||
gateway = nc.IPAM.Config[0].Gateway
|
||||
}
|
||||
|
||||
if nc.IPAM.Config[0].Subnet != "" {
|
||||
subnet = nc.IPAM.Config[0].Subnet
|
||||
}
|
||||
|
||||
if nc.IPAM.Config[0].IPRange != "" {
|
||||
pools = append(pools, nc.IPAM.Config[0].IPRange)
|
||||
}
|
||||
}
|
||||
|
||||
if nc.Driver == "" {
|
||||
nc.Driver = "bridge"
|
||||
}
|
||||
|
||||
cfg := &models.ScopeConfig{
|
||||
Gateway: gateway,
|
||||
Name: nc.Name,
|
||||
ScopeType: nc.Driver,
|
||||
Subnet: subnet,
|
||||
IPAM: pools,
|
||||
Annotations: make(map[string]string),
|
||||
Internal: nc.Internal,
|
||||
}
|
||||
|
||||
// Marshal and encode the labels for transport and storage in the portlayer
|
||||
if labelsBytes, err := json.Marshal(nc.Labels); err == nil {
|
||||
encodedLabels := base64.StdEncoding.EncodeToString(labelsBytes)
|
||||
cfg.Annotations[convert.AnnotationKeyLabels] = encodedLabels
|
||||
} else {
|
||||
log.Errorf("error marshaling labels: %s", err)
|
||||
return nil, derr.NewErrorWithStatusCode(fmt.Errorf("unable to marshal labels: %s", err), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
created, err := PortLayerClient().Scopes.CreateScope(scopes.NewCreateScopeParamsWithContext(ctx).WithConfig(cfg))
|
||||
if err != nil {
|
||||
switch err := err.(type) {
|
||||
case *scopes.CreateScopeConflict:
|
||||
return nil, derr.NewErrorWithStatusCode(fmt.Errorf("vicnetwork %s already exists", nc.Name), http.StatusConflict)
|
||||
|
||||
case *scopes.CreateScopeDefault:
|
||||
return nil, derr.NewErrorWithStatusCode(fmt.Errorf(err.Payload.Message), http.StatusInternalServerError)
|
||||
|
||||
default:
|
||||
return nil, derr.NewErrorWithStatusCode(err, http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
ncResponse := &types.NetworkCreateResponse{
|
||||
ID: created.Payload.ID,
|
||||
Warning: "",
|
||||
}
|
||||
|
||||
return ncResponse, nil
|
||||
}
|
||||
|
||||
// isCommitConflictError returns true if err is a conflict error from the portlayer's
|
||||
// handle commit operation, and false otherwise.
|
||||
func isCommitConflictError(err error) bool {
|
||||
_, isConflictErr := err.(*containers.CommitConflict)
|
||||
return isConflictErr
|
||||
}
|
||||
|
||||
// connectContainerToNetwork performs portlayer operations to connect a container to a container vicnetwork.
|
||||
func connectContainerToNetwork(containerName, networkName string, endpointConfig *apinet.EndpointSettings) error {
|
||||
client := PortLayerClient()
|
||||
getRes, err := client.Containers.Get(containers.NewGetParamsWithContext(ctx).WithID(containerName))
|
||||
if err != nil {
|
||||
switch err := err.(type) {
|
||||
case *containers.GetNotFound:
|
||||
return derr.NewRequestNotFoundError(fmt.Errorf(err.Payload.Message))
|
||||
|
||||
case *containers.GetDefault:
|
||||
return derr.NewErrorWithStatusCode(fmt.Errorf(err.Payload.Message), http.StatusInternalServerError)
|
||||
|
||||
default:
|
||||
return derr.NewErrorWithStatusCode(err, http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
h := getRes.Payload
|
||||
nc := &models.NetworkConfig{NetworkName: networkName}
|
||||
if endpointConfig != nil {
|
||||
if endpointConfig.IPAMConfig != nil && endpointConfig.IPAMConfig.IPv4Address != "" {
|
||||
nc.Address = endpointConfig.IPAMConfig.IPv4Address
|
||||
|
||||
}
|
||||
|
||||
// Pass Links and Aliases to PL.
|
||||
nc.Aliases = vicendpoint.Alias(endpointConfig)
|
||||
}
|
||||
|
||||
addConRes, err := client.Scopes.AddContainer(scopes.NewAddContainerParamsWithContext(ctx).
|
||||
WithScope(nc.NetworkName).
|
||||
WithConfig(&models.ScopesAddContainerConfig{
|
||||
Handle: h,
|
||||
NetworkConfig: nc,
|
||||
}))
|
||||
if err != nil {
|
||||
switch err := err.(type) {
|
||||
case *scopes.AddContainerNotFound:
|
||||
return derr.NewRequestNotFoundError(fmt.Errorf(err.Payload.Message))
|
||||
|
||||
case *scopes.AddContainerInternalServerError:
|
||||
return derr.NewErrorWithStatusCode(fmt.Errorf(err.Payload.Message), http.StatusInternalServerError)
|
||||
|
||||
default:
|
||||
return derr.NewErrorWithStatusCode(err, http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
h = addConRes.Payload
|
||||
|
||||
// Get the power state of the container.
|
||||
getStateRes, err := client.Containers.GetState(containers.NewGetStateParamsWithContext(ctx).WithHandle(h))
|
||||
if err != nil {
|
||||
switch err := err.(type) {
|
||||
case *containers.GetStateNotFound:
|
||||
return derr.NewRequestNotFoundError(fmt.Errorf(err.Payload.Message))
|
||||
|
||||
case *containers.GetStateDefault:
|
||||
return derr.NewErrorWithStatusCode(fmt.Errorf(err.Payload.Message), http.StatusInternalServerError)
|
||||
|
||||
default:
|
||||
return derr.NewErrorWithStatusCode(err, http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
h = getStateRes.Payload.Handle
|
||||
// Only bind if the container is running.
|
||||
if getStateRes.Payload.State == "RUNNING" {
|
||||
bindRes, err := client.Scopes.BindContainer(scopes.NewBindContainerParamsWithContext(ctx).WithHandle(h))
|
||||
if err != nil {
|
||||
switch err := err.(type) {
|
||||
case *scopes.BindContainerNotFound:
|
||||
return derr.NewRequestNotFoundError(fmt.Errorf(err.Payload.Message))
|
||||
|
||||
case *scopes.BindContainerInternalServerError:
|
||||
return derr.NewErrorWithStatusCode(fmt.Errorf(err.Payload.Message), http.StatusInternalServerError)
|
||||
|
||||
default:
|
||||
return derr.NewErrorWithStatusCode(err, http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
if _, err2 := client.Scopes.UnbindContainer(scopes.NewUnbindContainerParamsWithContext(ctx).WithHandle(h)); err2 != nil {
|
||||
log.Warnf("failed bind container rollback: %s", err2)
|
||||
}
|
||||
}()
|
||||
|
||||
h = bindRes.Payload.Handle
|
||||
}
|
||||
|
||||
// Commit the handle.
|
||||
_, err = client.Containers.Commit(containers.NewCommitParamsWithContext(ctx).WithHandle(h))
|
||||
return err
|
||||
}
|
||||
|
||||
// ConnectContainerToNetwork connects a container to a container vicnetwork. It wraps the portlayer operations
|
||||
// in a retry for when there's a conflict error received, such as one during a similar concurrent operation.
|
||||
func (n *NetworkBackend) ConnectContainerToNetwork(containerName, networkName string, endpointConfig *apinet.EndpointSettings) error {
|
||||
vc := cache.ContainerCache().GetContainer(containerName)
|
||||
if vc != nil {
|
||||
containerName = vc.ContainerID
|
||||
}
|
||||
|
||||
operation := func() error {
|
||||
return connectContainerToNetwork(containerName, networkName, endpointConfig)
|
||||
}
|
||||
|
||||
config := retry.NewBackoffConfig()
|
||||
config.MaxElapsedTime = maxElapsedTime
|
||||
err := retry.DoWithConfig(operation, isCommitConflictError, config)
|
||||
if err != nil {
|
||||
switch err := err.(type) {
|
||||
case *containers.CommitNotFound:
|
||||
return derr.NewRequestNotFoundError(fmt.Errorf(err.Payload.Message))
|
||||
|
||||
case *containers.CommitDefault:
|
||||
return derr.NewErrorWithStatusCode(fmt.Errorf(err.Payload.Message), http.StatusInternalServerError)
|
||||
|
||||
default:
|
||||
return derr.NewErrorWithStatusCode(err, http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *NetworkBackend) DisconnectContainerFromNetwork(containerName string, networkName string, force bool) error {
|
||||
vc := cache.ContainerCache().GetContainer(containerName)
|
||||
if vc != nil {
|
||||
containerName = vc.ContainerID
|
||||
}
|
||||
return errors.APINotSupportedMsg(ProductName(), "DisconnectContainerFromNetwork")
|
||||
}
|
||||
|
||||
func (n *NetworkBackend) DeleteNetwork(name string) error {
|
||||
client := PortLayerClient()
|
||||
|
||||
if _, err := client.Scopes.DeleteScope(scopes.NewDeleteScopeParamsWithContext(ctx).WithIDName(name)); err != nil {
|
||||
switch err := err.(type) {
|
||||
case *scopes.DeleteScopeNotFound:
|
||||
return derr.NewRequestNotFoundError(fmt.Errorf("network %s not found", name))
|
||||
|
||||
case *scopes.DeleteScopeInternalServerError:
|
||||
return derr.NewErrorWithStatusCode(fmt.Errorf(err.Payload.Message), http.StatusInternalServerError)
|
||||
|
||||
default:
|
||||
return derr.NewErrorWithStatusCode(err, http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *NetworkBackend) NetworksPrune(pruneFilters filters.Args) (*types.NetworksPruneReport, error) {
|
||||
return nil, errors.APINotSupportedMsg(ProductName(), "NetworksPrune")
|
||||
}
|
||||
|
||||
// vicnetwork implements the libnetwork.Network and libnetwork.NetworkInfo interfaces
|
||||
type vicnetwork struct {
|
||||
sync.Mutex
|
||||
|
||||
cfg *models.ScopeConfig
|
||||
}
|
||||
|
||||
// A user chosen name for this vicnetwork.
|
||||
func (n *vicnetwork) Name() string {
|
||||
return n.cfg.Name
|
||||
}
|
||||
|
||||
// A system generated id for this vicnetwork.
|
||||
func (n *vicnetwork) ID() string {
|
||||
return n.cfg.ID
|
||||
}
|
||||
|
||||
// The type of vicnetwork, which corresponds to its managing driver.
|
||||
func (n *vicnetwork) Type() string {
|
||||
return n.cfg.ScopeType
|
||||
}
|
||||
|
||||
// Create a new endpoint to this vicnetwork symbolically identified by the
|
||||
// specified unique name. The options parameter carry driver specific options.
|
||||
func (n *vicnetwork) CreateEndpoint(name string, options ...libnetwork.EndpointOption) (libnetwork.Endpoint, error) {
|
||||
return nil, fmt.Errorf("not implemented")
|
||||
}
|
||||
|
||||
// Delete the vicnetwork.
|
||||
func (n *vicnetwork) Delete() error {
|
||||
return fmt.Errorf("not implemented")
|
||||
}
|
||||
|
||||
// Endpoints returns the list of Endpoint(s) in this vicnetwork.
|
||||
func (n *vicnetwork) Endpoints() []libnetwork.Endpoint {
|
||||
eps := make([]libnetwork.Endpoint, len(n.cfg.Endpoints))
|
||||
for i, e := range n.cfg.Endpoints {
|
||||
eps[i] = &endpoint{ep: e, sc: n.cfg}
|
||||
}
|
||||
|
||||
return eps
|
||||
}
|
||||
|
||||
// WalkEndpoints uses the provided function to walk the Endpoints
|
||||
func (n *vicnetwork) WalkEndpoints(walker libnetwork.EndpointWalker) {
|
||||
for _, e := range n.cfg.Endpoints {
|
||||
if walker(&endpoint{ep: e, sc: n.cfg}) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// EndpointByName returns the Endpoint which has the passed name. If not found, the error ErrNoSuchEndpoint is returned.
|
||||
func (n *vicnetwork) EndpointByName(name string) (libnetwork.Endpoint, error) {
|
||||
for _, e := range n.cfg.Endpoints {
|
||||
if e.Name == name {
|
||||
return &endpoint{ep: e, sc: n.cfg}, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("not found")
|
||||
}
|
||||
|
||||
// EndpointByID returns the Endpoint which has the passed id. If not found, the error ErrNoSuchEndpoint is returned.
|
||||
func (n *vicnetwork) EndpointByID(id string) (libnetwork.Endpoint, error) {
|
||||
for _, e := range n.cfg.Endpoints {
|
||||
if e.ID == id {
|
||||
return &endpoint{ep: e, sc: n.cfg}, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("not found")
|
||||
}
|
||||
|
||||
// Return certain operational data belonging to this vicnetwork
|
||||
func (n *vicnetwork) Info() libnetwork.NetworkInfo {
|
||||
return n
|
||||
}
|
||||
|
||||
func (n *vicnetwork) IpamConfig() (string, map[string]string, []*libnetwork.IpamConf, []*libnetwork.IpamConf) {
|
||||
n.Lock()
|
||||
defer n.Unlock()
|
||||
|
||||
confs := make([]*libnetwork.IpamConf, len(n.cfg.IPAM))
|
||||
for j, i := range n.cfg.IPAM {
|
||||
conf := &libnetwork.IpamConf{
|
||||
PreferredPool: n.cfg.Subnet,
|
||||
Gateway: "",
|
||||
}
|
||||
|
||||
if i != n.cfg.Subnet {
|
||||
conf.SubPool = i
|
||||
}
|
||||
|
||||
if n.cfg.Gateway != "" {
|
||||
conf.Gateway = n.cfg.Gateway
|
||||
}
|
||||
|
||||
confs[j] = conf
|
||||
}
|
||||
|
||||
return "", make(map[string]string), confs, nil
|
||||
}
|
||||
|
||||
func (n *vicnetwork) IpamInfo() ([]*libnetwork.IpamInfo, []*libnetwork.IpamInfo) {
|
||||
n.Lock()
|
||||
defer n.Unlock()
|
||||
|
||||
var infos []*libnetwork.IpamInfo
|
||||
for _, i := range n.cfg.IPAM {
|
||||
_, pool, err := net.ParseCIDR(i)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
info := &libnetwork.IpamInfo{
|
||||
Meta: make(map[string]string),
|
||||
}
|
||||
|
||||
info.Pool = pool
|
||||
if n.cfg.Gateway != "" {
|
||||
info.Gateway = &net.IPNet{
|
||||
IP: net.ParseIP(n.cfg.Gateway),
|
||||
Mask: net.CIDRMask(32, 32),
|
||||
}
|
||||
}
|
||||
|
||||
info.AuxAddresses = make(map[string]*net.IPNet)
|
||||
infos = append(infos, info)
|
||||
}
|
||||
|
||||
return infos, nil
|
||||
}
|
||||
|
||||
func (n *vicnetwork) DriverOptions() map[string]string {
|
||||
return make(map[string]string)
|
||||
}
|
||||
|
||||
func (n *vicnetwork) Scope() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (n *vicnetwork) IPv6Enabled() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (n *vicnetwork) Internal() bool {
|
||||
n.Lock()
|
||||
defer n.Unlock()
|
||||
|
||||
return n.cfg.Internal
|
||||
}
|
||||
|
||||
// Labels decodes and unmarshals the stored blob of vicnetwork labels.
|
||||
func (n *vicnetwork) Labels() map[string]string {
|
||||
n.Lock()
|
||||
defer n.Unlock()
|
||||
|
||||
labels := make(map[string]string)
|
||||
if n.cfg.Annotations == nil {
|
||||
return labels
|
||||
}
|
||||
|
||||
// Look for the Docker-specific annotation (label) blob and process it for the output
|
||||
if encodedLabels, ok := n.cfg.Annotations[convert.AnnotationKeyLabels]; ok {
|
||||
|
||||
if labelsBytes, decodeErr := base64.StdEncoding.DecodeString(encodedLabels); decodeErr == nil {
|
||||
if unmarshalErr := json.Unmarshal(labelsBytes, &labels); unmarshalErr != nil {
|
||||
log.Errorf("error unmarshaling labels: %s", unmarshalErr)
|
||||
}
|
||||
} else {
|
||||
log.Errorf("error decoding label blob: %s", decodeErr)
|
||||
}
|
||||
}
|
||||
|
||||
return labels
|
||||
}
|
||||
|
||||
func (n *vicnetwork) Attachable() bool {
|
||||
return false //?
|
||||
}
|
||||
|
||||
func (n *vicnetwork) Dynamic() bool {
|
||||
return false //?
|
||||
}
|
||||
|
||||
func (n *vicnetwork) Created() time.Time {
|
||||
return time.Now()
|
||||
}
|
||||
|
||||
// Peers returns a slice of PeerInfo structures which has the information about the peer
|
||||
// nodes participating in the same overlay vicnetwork. This is currently the per-vicnetwork
|
||||
// gossip cluster. For non-dynamic overlay networks and bridge networks it returns an
|
||||
// empty slice
|
||||
func (n *vicnetwork) Peers() []networkdb.PeerInfo {
|
||||
return nil
|
||||
}
|
||||
73
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/plugins.go
generated
vendored
Normal file
73
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/plugins.go
generated
vendored
Normal file
@@ -0,0 +1,73 @@
|
||||
// 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")
|
||||
}
|
||||
246
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/portmap/portmap.go
generated
vendored
Normal file
246
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/portmap/portmap.go
generated
vendored
Normal file
@@ -0,0 +1,246 @@
|
||||
// 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 portmap
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
"sync"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/docker/libnetwork/iptables"
|
||||
)
|
||||
|
||||
type Operation int
|
||||
|
||||
const (
|
||||
Map Operation = iota
|
||||
Unmap
|
||||
)
|
||||
|
||||
func (o Operation) String() string {
|
||||
switch o {
|
||||
case Map:
|
||||
return "Map"
|
||||
|
||||
case Unmap:
|
||||
return "Unmap"
|
||||
}
|
||||
|
||||
return "Unknown"
|
||||
}
|
||||
|
||||
type PortMapper interface {
|
||||
MapPort(ip net.IP, port int, proto string, destIP string, destPort int, srcIface, destIface string) error
|
||||
UnmapPort(ip net.IP, port int, proto string, destPort int, srcIface, destIface string) error
|
||||
}
|
||||
|
||||
type bindKey struct {
|
||||
ip string
|
||||
port int
|
||||
}
|
||||
|
||||
type portMapper struct {
|
||||
sync.Mutex
|
||||
|
||||
bindings map[bindKey][][]string
|
||||
}
|
||||
|
||||
func NewPortMapper() PortMapper {
|
||||
return &portMapper{bindings: make(map[bindKey][][]string)}
|
||||
}
|
||||
|
||||
func (p *portMapper) isPortAvailable(proto string, ip net.IP, port int) bool {
|
||||
addr := ""
|
||||
if ip != nil && !ip.IsUnspecified() {
|
||||
addr = ip.String()
|
||||
}
|
||||
|
||||
if _, ok := p.bindings[bindKey{addr, port}]; ok {
|
||||
return false
|
||||
}
|
||||
|
||||
c, err := net.Dial(proto, net.JoinHostPort(addr, strconv.Itoa(port)))
|
||||
defer func() {
|
||||
if c != nil {
|
||||
// #nosec: Errors unhandled.
|
||||
c.Close()
|
||||
}
|
||||
}()
|
||||
|
||||
if err != nil {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (p *portMapper) MapPort(ip net.IP, port int, proto string, destIP string, destPort int, srcIface, destIface string) error {
|
||||
p.Lock()
|
||||
defer p.Unlock()
|
||||
|
||||
// check if port is available
|
||||
if !p.isPortAvailable(proto, ip, port) {
|
||||
return fmt.Errorf("port %d is not available", port)
|
||||
}
|
||||
|
||||
if port <= 0 {
|
||||
return fmt.Errorf("source port must be specified")
|
||||
}
|
||||
|
||||
if destPort <= 0 {
|
||||
log.Infof("destination port not specified, using source port %d", port)
|
||||
destPort = port
|
||||
}
|
||||
|
||||
return p.forward(Map, ip, port, proto, destIP, destPort, srcIface, destIface)
|
||||
}
|
||||
|
||||
func (p *portMapper) UnmapPort(ip net.IP, port int, proto string, destPort int, srcIface, destIface string) error {
|
||||
p.Lock()
|
||||
defer p.Unlock()
|
||||
|
||||
if port <= 0 {
|
||||
return fmt.Errorf("source port must be specified")
|
||||
}
|
||||
|
||||
if destPort <= 0 {
|
||||
log.Infof("destination port not specified, using source port %d", port)
|
||||
destPort = port
|
||||
}
|
||||
|
||||
return p.forward(Unmap, ip, port, proto, "", destPort, srcIface, destIface)
|
||||
}
|
||||
|
||||
// iptablesRunAndCheck runs an iptables command with the provided args
|
||||
func iptablesRunAndCheck(action iptables.Action, args []string) error {
|
||||
args = append([]string{string(action)}, args...)
|
||||
if output, err := iptables.Raw(args...); err != nil {
|
||||
return err
|
||||
} else if len(output) != 0 {
|
||||
return iptables.ChainError{Chain: "FORWARD", Output: output}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// iptablesDelete takes the saved args from the Append operation
|
||||
// and uses them to delete the previously added rules
|
||||
func iptablesDelete(args [][]string) error {
|
||||
var errs []error
|
||||
for _, cmd := range args {
|
||||
if err := iptablesRunAndCheck(iptables.Delete, cmd); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
if len(errs) > 0 {
|
||||
return fmt.Errorf("Failed to delete iptables rules: %s", errs)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// adapted from https://github.com/docker/libnetwork/blob/master/iptables/iptables.go
|
||||
//
|
||||
// assumes p is locked
|
||||
func (p *portMapper) forward(op Operation, ip net.IP, port int, proto, destAddr string, destPort int, srcIface, destIface string) error {
|
||||
daddr := ip.String()
|
||||
if ip == nil || ip.IsUnspecified() {
|
||||
// iptables interprets "0.0.0.0" as "0.0.0.0/32", whereas we
|
||||
// want "0.0.0.0/0". "0/0" is correctly interpreted as "any
|
||||
// value" by both iptables and ip6tables.
|
||||
daddr = "0/0"
|
||||
}
|
||||
ipStr := ""
|
||||
if ip != nil && !ip.IsUnspecified() {
|
||||
ipStr = ip.String()
|
||||
}
|
||||
|
||||
key := bindKey{ip: ipStr, port: port}
|
||||
switch op {
|
||||
case Unmap:
|
||||
// lookup commands to reverse
|
||||
if args, ok := p.bindings[key]; ok {
|
||||
if err := iptablesDelete(args); err != nil {
|
||||
return err
|
||||
}
|
||||
delete(p.bindings, bindKey{ipStr, port})
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("Failed to find unmap data for %s:%d", ipStr, port)
|
||||
|
||||
case Map:
|
||||
var savedArgs [][]string
|
||||
|
||||
args := []string{"VIC", "-t", string(iptables.Nat),
|
||||
"-i", srcIface,
|
||||
"-p", proto,
|
||||
"-d", daddr,
|
||||
"--dport", strconv.Itoa(port),
|
||||
"-j", "DNAT",
|
||||
"--to-destination", net.JoinHostPort(destAddr, strconv.Itoa(destPort))}
|
||||
if err := iptablesRunAndCheck(iptables.Append, args); err != nil {
|
||||
return err
|
||||
}
|
||||
savedArgs = append(savedArgs, args)
|
||||
p.bindings[key] = savedArgs
|
||||
|
||||
// allow traffic from container to container via vch public interface
|
||||
args = []string{"VIC", "-t", string(iptables.Nat),
|
||||
"-i", destIface,
|
||||
"-p", proto,
|
||||
"--dport", strconv.Itoa(port),
|
||||
"-j", "DNAT",
|
||||
"--to-destination", net.JoinHostPort(destAddr, strconv.Itoa(destPort)),
|
||||
"-m", "addrtype",
|
||||
"--dst-type", "LOCAL"}
|
||||
if err := iptablesRunAndCheck(iptables.Append, args); err != nil {
|
||||
return err
|
||||
}
|
||||
savedArgs = append(savedArgs, args)
|
||||
p.bindings[key] = savedArgs
|
||||
|
||||
// rule to allow connections from the public interface for
|
||||
// the mapped port
|
||||
args = []string{"VIC", "-t", string(iptables.Filter),
|
||||
"-i", srcIface,
|
||||
"-o", destIface,
|
||||
"-p", proto,
|
||||
"-d", destAddr,
|
||||
"--dport", strconv.Itoa(destPort),
|
||||
"-j", "ACCEPT"}
|
||||
if err := iptablesRunAndCheck(iptables.Append, args); err != nil {
|
||||
return err
|
||||
}
|
||||
savedArgs = append(savedArgs, args)
|
||||
p.bindings[key] = savedArgs
|
||||
|
||||
args = []string{"POSTROUTING", "-t", string(iptables.Nat),
|
||||
"-p", proto,
|
||||
"-d", destAddr,
|
||||
"--dport", strconv.Itoa(destPort),
|
||||
"-j", "MASQUERADE"}
|
||||
if err := iptablesRunAndCheck(iptables.Append, args); err != nil {
|
||||
return err
|
||||
}
|
||||
savedArgs = append(savedArgs, args)
|
||||
p.bindings[key] = savedArgs
|
||||
return nil
|
||||
|
||||
default:
|
||||
log.Warnf("noop for given operation: %s", op)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
122
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/sandbox.go
generated
vendored
Normal file
122
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/sandbox.go
generated
vendored
Normal file
@@ -0,0 +1,122 @@
|
||||
// Copyright 2016 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package backends
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
"github.com/docker/docker/pkg/stringid"
|
||||
"github.com/docker/libnetwork"
|
||||
"github.com/docker/libnetwork/types"
|
||||
)
|
||||
|
||||
type sandbox struct {
|
||||
id string
|
||||
containerID string
|
||||
}
|
||||
|
||||
func newSandbox(containerID string) *sandbox {
|
||||
return &sandbox{
|
||||
id: stringid.GenerateRandomID(),
|
||||
containerID: containerID,
|
||||
}
|
||||
}
|
||||
|
||||
// ID returns the ID of the sandbox
|
||||
func (s *sandbox) ID() string {
|
||||
return s.id
|
||||
}
|
||||
|
||||
// Key returns the sandbox's key
|
||||
func (s *sandbox) Key() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// ContainerID returns the container id associated to this sandbox
|
||||
func (s *sandbox) ContainerID() string {
|
||||
return s.containerID
|
||||
}
|
||||
|
||||
// Labels returns the sandbox's labels
|
||||
func (s *sandbox) Labels() map[string]interface{} {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Statistics retrieves the interfaces' statistics for the sandbox
|
||||
func (s *sandbox) Statistics() (map[string]*types.InterfaceStatistics, error) {
|
||||
return nil, notImplementedError
|
||||
}
|
||||
|
||||
// Refresh leaves all the endpoints, resets and re-apply the options,
|
||||
// re-joins all the endpoints without destroying the osl sandbox
|
||||
func (s *sandbox) Refresh(options ...libnetwork.SandboxOption) error {
|
||||
return notImplementedError
|
||||
}
|
||||
|
||||
// SetKey updates the Sandbox Key
|
||||
func (s *sandbox) SetKey(key string) error {
|
||||
return notImplementedError
|
||||
}
|
||||
|
||||
// Rename changes the name of all attached Endpoints
|
||||
func (s *sandbox) Rename(name string) error {
|
||||
return notImplementedError
|
||||
|
||||
}
|
||||
|
||||
// Delete destroys this container after detaching it from all connected endpoints.
|
||||
func (s *sandbox) Delete() error {
|
||||
return notImplementedError
|
||||
|
||||
}
|
||||
|
||||
// ResolveName resolves a service name to an IPv4 or IPv6 address by searching
|
||||
// the networks the sandbox is connected to. For IPv6 queries, second return
|
||||
// value will be true if the name exists in docker domain but doesn't have an
|
||||
// IPv6 address. Such queries shouldn't be forwarded to external nameservers.
|
||||
func (s *sandbox) ResolveName(name string, iplen int) ([]net.IP, bool) {
|
||||
return nil, false
|
||||
|
||||
}
|
||||
|
||||
// ResolveIP returns the service name for the passed in IP. IP is in reverse dotted
|
||||
// notation; the format used for DNS PTR records
|
||||
func (s *sandbox) ResolveIP(name string) string {
|
||||
return ""
|
||||
|
||||
}
|
||||
|
||||
// Endpoints returns all the endpoints connected to the sandbox
|
||||
func (s *sandbox) Endpoints() []libnetwork.Endpoint {
|
||||
return nil
|
||||
}
|
||||
|
||||
// ResolveService returns all the backend details about the containers or hosts
|
||||
// backing a service. Its purpose is to satisfy an SRV query
|
||||
func (s *sandbox) ResolveService(name string) ([]*net.SRV, []net.IP) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// EnableService makes a managed container's service available by adding the
|
||||
// endpoint to the service load balancer and service discovery
|
||||
func (s *sandbox) EnableService() error {
|
||||
return notImplementedError
|
||||
}
|
||||
|
||||
// DisableService removes a managed contianer's endpoints from the load balancer
|
||||
// and service discovery
|
||||
func (s *sandbox) DisableService() error {
|
||||
return notImplementedError
|
||||
}
|
||||
128
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/swarm.go
generated
vendored
Normal file
128
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/swarm.go
generated
vendored
Normal file
@@ -0,0 +1,128 @@
|
||||
// Copyright 2017 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package backends
|
||||
|
||||
import (
|
||||
"golang.org/x/net/context"
|
||||
|
||||
basictypes "github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/backend"
|
||||
types "github.com/docker/docker/api/types/swarm"
|
||||
|
||||
"github.com/vmware/vic/lib/apiservers/engine/errors"
|
||||
)
|
||||
|
||||
type SwarmBackend struct {
|
||||
}
|
||||
|
||||
func NewSwarmBackend() *SwarmBackend {
|
||||
return &SwarmBackend{}
|
||||
}
|
||||
|
||||
func (s *SwarmBackend) Init(req types.InitRequest) (string, error) {
|
||||
return "", errors.SwarmNotSupportedError()
|
||||
}
|
||||
|
||||
func (s *SwarmBackend) Join(req types.JoinRequest) error {
|
||||
return errors.SwarmNotSupportedError()
|
||||
}
|
||||
|
||||
func (s *SwarmBackend) Leave(force bool) error {
|
||||
return errors.SwarmNotSupportedError()
|
||||
}
|
||||
|
||||
func (s *SwarmBackend) Inspect() (types.Swarm, error) {
|
||||
return types.Swarm{}, errors.SwarmNotSupportedError()
|
||||
}
|
||||
|
||||
func (s *SwarmBackend) Update(uint64, types.Spec, types.UpdateFlags) error {
|
||||
return errors.SwarmNotSupportedError()
|
||||
}
|
||||
|
||||
func (s *SwarmBackend) GetUnlockKey() (string, error) {
|
||||
return "", errors.SwarmNotSupportedError()
|
||||
}
|
||||
|
||||
func (s *SwarmBackend) UnlockSwarm(req types.UnlockRequest) error {
|
||||
return errors.SwarmNotSupportedError()
|
||||
}
|
||||
|
||||
func (s *SwarmBackend) GetServices(basictypes.ServiceListOptions) ([]types.Service, error) {
|
||||
return nil, errors.SwarmNotSupportedError()
|
||||
}
|
||||
|
||||
func (s *SwarmBackend) GetService(string) (types.Service, error) {
|
||||
return types.Service{}, errors.SwarmNotSupportedError()
|
||||
}
|
||||
|
||||
func (s *SwarmBackend) CreateService(types.ServiceSpec, string) (*basictypes.ServiceCreateResponse, error) {
|
||||
return nil, errors.SwarmNotSupportedError()
|
||||
}
|
||||
|
||||
func (s *SwarmBackend) UpdateService(string, uint64, types.ServiceSpec, string, string) (*basictypes.ServiceUpdateResponse, error) {
|
||||
return nil, errors.SwarmNotSupportedError()
|
||||
}
|
||||
|
||||
func (s *SwarmBackend) RemoveService(string) error {
|
||||
return errors.SwarmNotSupportedError()
|
||||
}
|
||||
|
||||
func (s *SwarmBackend) ServiceLogs(context.Context, string, *backend.ContainerLogsConfig, chan struct{}) error {
|
||||
return errors.SwarmNotSupportedError()
|
||||
}
|
||||
|
||||
func (s *SwarmBackend) GetNodes(basictypes.NodeListOptions) ([]types.Node, error) {
|
||||
return nil, errors.SwarmNotSupportedError()
|
||||
}
|
||||
|
||||
func (s *SwarmBackend) GetNode(string) (types.Node, error) {
|
||||
return types.Node{}, errors.SwarmNotSupportedError()
|
||||
}
|
||||
|
||||
func (s *SwarmBackend) UpdateNode(string, uint64, types.NodeSpec) error {
|
||||
return errors.SwarmNotSupportedError()
|
||||
}
|
||||
|
||||
func (s *SwarmBackend) RemoveNode(string, bool) error {
|
||||
return errors.SwarmNotSupportedError()
|
||||
}
|
||||
|
||||
func (s *SwarmBackend) GetTasks(basictypes.TaskListOptions) ([]types.Task, error) {
|
||||
return nil, errors.SwarmNotSupportedError()
|
||||
}
|
||||
|
||||
func (s *SwarmBackend) GetTask(string) (types.Task, error) {
|
||||
return types.Task{}, errors.SwarmNotSupportedError()
|
||||
}
|
||||
|
||||
func (s *SwarmBackend) GetSecrets(opts basictypes.SecretListOptions) ([]types.Secret, error) {
|
||||
return nil, errors.SwarmNotSupportedError()
|
||||
}
|
||||
|
||||
func (s *SwarmBackend) CreateSecret(sp types.SecretSpec) (string, error) {
|
||||
return "", errors.SwarmNotSupportedError()
|
||||
}
|
||||
|
||||
func (s *SwarmBackend) RemoveSecret(id string) error {
|
||||
return errors.SwarmNotSupportedError()
|
||||
}
|
||||
|
||||
func (s *SwarmBackend) GetSecret(id string) (types.Secret, error) {
|
||||
return types.Secret{}, errors.SwarmNotSupportedError()
|
||||
}
|
||||
|
||||
func (s *SwarmBackend) UpdateSecret(id string, version uint64, spec types.SecretSpec) error {
|
||||
return errors.SwarmNotSupportedError()
|
||||
}
|
||||
454
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/system.go
generated
vendored
Normal file
454
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/system.go
generated
vendored
Normal file
@@ -0,0 +1,454 @@
|
||||
// Copyright 2016-2017 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package backends
|
||||
|
||||
//****
|
||||
// system.go
|
||||
//
|
||||
// Rules for code to be in here:
|
||||
// 1. No remote or swagger calls. Move those code to ../proxy/system_proxy.go
|
||||
// 2. Always return docker engine-api compatible errors.
|
||||
// - Do NOT return fmt.Errorf()
|
||||
// - Do NOT return errors.New()
|
||||
// - DO USE the aliased docker error package 'derr'
|
||||
// - It is OK to return errors returned from functions in system_proxy.go
|
||||
|
||||
import (
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
|
||||
"github.com/vmware/vic/lib/apiservers/engine/backends/cache"
|
||||
"github.com/vmware/vic/lib/apiservers/engine/errors"
|
||||
"github.com/vmware/vic/lib/apiservers/engine/proxy"
|
||||
"github.com/vmware/vic/lib/apiservers/portlayer/client"
|
||||
"github.com/vmware/vic/lib/apiservers/portlayer/client/storage"
|
||||
"github.com/vmware/vic/lib/imagec"
|
||||
urlfetcher "github.com/vmware/vic/pkg/fetcher"
|
||||
"github.com/vmware/vic/pkg/registry"
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
"github.com/vmware/vic/pkg/version"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
eventtypes "github.com/docker/docker/api/types/events"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"github.com/docker/docker/daemon/events"
|
||||
"github.com/docker/docker/pkg/platform"
|
||||
"github.com/docker/go-units"
|
||||
)
|
||||
|
||||
type SystemBackend struct {
|
||||
systemProxy proxy.VicSystemProxy
|
||||
}
|
||||
|
||||
const (
|
||||
systemStatusMhz = " VCH CPU limit"
|
||||
systemStatusMemory = " VCH memory limit"
|
||||
systemStatusCPUUsageMhz = " VCH CPU usage"
|
||||
systemStatusMemUsage = " VCH memory usage"
|
||||
systemOS = " VMware OS"
|
||||
systemOSVersion = " VMware OS version"
|
||||
systemProductName = " VMware Product"
|
||||
volumeStoresID = "VolumeStores"
|
||||
loginTimeout = 20 * time.Second
|
||||
infoTimeout = 5 * time.Second
|
||||
vchWhitelistMode = " Registry Whitelist Mode"
|
||||
whitelistRegistriesLabel = " Whitelisted Registries"
|
||||
insecureRegistriesLabel = " Insecure Registries"
|
||||
)
|
||||
|
||||
// var for use by other engine components
|
||||
var systemBackend *SystemBackend
|
||||
var sysOnce sync.Once
|
||||
|
||||
func NewSystemBackend() *SystemBackend {
|
||||
sysOnce.Do(func() {
|
||||
systemBackend = &SystemBackend{
|
||||
systemProxy: proxy.NewSystemProxy(PortLayerClient()),
|
||||
}
|
||||
})
|
||||
return systemBackend
|
||||
}
|
||||
|
||||
func (s *SystemBackend) SystemInfo() (*types.Info, error) {
|
||||
defer trace.End(trace.Begin("SystemInfo"))
|
||||
client := PortLayerClient()
|
||||
|
||||
// Retrieve container status from port layer
|
||||
running, paused, stopped, err := s.systemProxy.ContainerCount(context.Background())
|
||||
if err != nil {
|
||||
log.Infof("System.SytemInfo unable to get global status on containers: %s", err.Error())
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), infoTimeout)
|
||||
defer cancel()
|
||||
|
||||
vchConfig := vchConfig.update(ctx)
|
||||
|
||||
vchConfig.Lock()
|
||||
defer vchConfig.Unlock()
|
||||
|
||||
cfg := vchConfig.Cfg
|
||||
|
||||
// Build up the struct that the Remote API and CLI wants
|
||||
info := &types.Info{
|
||||
Driver: PortLayerName(),
|
||||
IndexServerAddress: imagec.DefaultDockerURL,
|
||||
ServerVersion: ProductVersion(),
|
||||
ID: ProductName(),
|
||||
Containers: running + paused + stopped,
|
||||
ContainersRunning: running,
|
||||
ContainersPaused: paused,
|
||||
ContainersStopped: stopped,
|
||||
Images: getImageCount(),
|
||||
Debug: cfg.Diagnostics.DebugLevel > 0,
|
||||
NGoroutines: runtime.NumGoroutine(),
|
||||
SystemTime: time.Now().Format(time.RFC3339Nano),
|
||||
LoggingDriver: "",
|
||||
CgroupDriver: "",
|
||||
DockerRootDir: "",
|
||||
ClusterStore: "",
|
||||
ClusterAdvertise: "",
|
||||
|
||||
// FIXME: Get this info once we have event listening service
|
||||
// NEventsListener int
|
||||
|
||||
// These are system related. Some refer to cgroup info. Others are
|
||||
// retrieved from the port layer and are information about the resource
|
||||
// pool.
|
||||
Name: cfg.Name,
|
||||
KernelVersion: "",
|
||||
Architecture: platform.Architecture, //stubbed
|
||||
|
||||
// NOTE: These values have no meaning for VIC. We default them to true to
|
||||
// prevent the CLI from displaying warning messages.
|
||||
CPUCfsPeriod: true,
|
||||
CPUCfsQuota: true,
|
||||
CPUShares: true,
|
||||
CPUSet: true,
|
||||
OomKillDisable: true,
|
||||
MemoryLimit: true,
|
||||
SwapLimit: true,
|
||||
KernelMemory: true,
|
||||
IPv4Forwarding: true,
|
||||
BridgeNfIptables: true,
|
||||
BridgeNfIP6tables: true,
|
||||
HTTPProxy: "",
|
||||
HTTPSProxy: "",
|
||||
NoProxy: "",
|
||||
}
|
||||
|
||||
// Add in vicnetwork info from the VCH via guestinfo
|
||||
for _, network := range cfg.ContainerNetworks {
|
||||
info.Plugins.Network = append(info.Plugins.Network, network.Name)
|
||||
}
|
||||
|
||||
info.SystemStatus = make([][2]string, 0)
|
||||
|
||||
// Add in volume label from the VCH via guestinfo
|
||||
volumeStoreString, err := FetchVolumeStores(client)
|
||||
if err != nil {
|
||||
log.Infof("Unable to get the volume store list from the portlayer : %s", err.Error())
|
||||
} else {
|
||||
customInfo := [2]string{volumeStoresID, volumeStoreString}
|
||||
info.SystemStatus = append(info.SystemStatus, customInfo)
|
||||
|
||||
// Show a list of supported volume drivers if there's at least one volume
|
||||
// store configured for the VCH. "local" is excluded because it's the default
|
||||
// driver supplied by the Docker client and is equivalent to "vsphere" in
|
||||
// our implementation.
|
||||
if len(volumeStoreString) > 0 {
|
||||
for driver := range proxy.SupportedVolDrivers {
|
||||
if driver != "local" {
|
||||
info.Plugins.Volume = append(info.Plugins.Volume, driver)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if s.systemProxy.PingPortlayer(context.Background()) {
|
||||
status := [2]string{PortLayerName(), "RUNNING"}
|
||||
info.SystemStatus = append(info.SystemStatus, status)
|
||||
} else {
|
||||
status := [2]string{PortLayerName(), "STOPPED"}
|
||||
info.SystemStatus = append(info.SystemStatus, status)
|
||||
}
|
||||
|
||||
// Add in vch information
|
||||
vchInfo, err := s.systemProxy.VCHInfo(context.Background())
|
||||
if err != nil || vchInfo == nil {
|
||||
log.Infof("System.SystemInfo unable to get vch info from port layer: %s", err.Error())
|
||||
} else {
|
||||
if vchInfo.CPUMhz > 0 {
|
||||
info.NCPU = int(vchInfo.CPUMhz)
|
||||
|
||||
customInfo := [2]string{systemStatusMhz, fmt.Sprintf("%d MHz", info.NCPU)}
|
||||
info.SystemStatus = append(info.SystemStatus, customInfo)
|
||||
}
|
||||
if vchInfo.Memory > 0 {
|
||||
info.MemTotal = vchInfo.Memory * 1024 * 1024 // Get Mebibytes
|
||||
|
||||
customInfo := [2]string{systemStatusMemory, units.BytesSize(float64(info.MemTotal))}
|
||||
info.SystemStatus = append(info.SystemStatus, customInfo)
|
||||
}
|
||||
if vchInfo.CPUUsage >= 0 {
|
||||
customInfo := [2]string{systemStatusCPUUsageMhz, fmt.Sprintf("%d MHz", int(vchInfo.CPUUsage))}
|
||||
info.SystemStatus = append(info.SystemStatus, customInfo)
|
||||
}
|
||||
if vchInfo.MemUsage >= 0 {
|
||||
customInfo := [2]string{systemStatusMemUsage, units.BytesSize(float64(vchInfo.MemUsage))}
|
||||
info.SystemStatus = append(info.SystemStatus, customInfo)
|
||||
}
|
||||
if vchInfo.HostProductName != "" {
|
||||
customInfo := [2]string{systemProductName, vchInfo.HostProductName}
|
||||
info.SystemStatus = append(info.SystemStatus, customInfo)
|
||||
}
|
||||
if vchInfo.HostOS != "" {
|
||||
info.OperatingSystem = vchInfo.HostOS
|
||||
info.OSType = vchInfo.HostOS //Value for OS and OS Type the same from vmomi
|
||||
|
||||
customInfo := [2]string{systemOS, vchInfo.HostOS}
|
||||
info.SystemStatus = append(info.SystemStatus, customInfo)
|
||||
}
|
||||
if vchInfo.HostOSVersion != "" {
|
||||
customInfo := [2]string{systemOSVersion, vchInfo.HostOSVersion}
|
||||
info.SystemStatus = append(info.SystemStatus, customInfo)
|
||||
}
|
||||
if len(vchConfig.Insecure) > 0 {
|
||||
customInfo := [2]string{insecureRegistriesLabel, strings.Join(vchConfig.Insecure.Strings(), ",")}
|
||||
info.SystemStatus = append(info.SystemStatus, customInfo)
|
||||
}
|
||||
if len(vchConfig.Whitelist) > 0 {
|
||||
s := "enabled"
|
||||
if vchConfig.remoteWl {
|
||||
s += "; remote source"
|
||||
}
|
||||
customInfo := [2]string{vchWhitelistMode, s}
|
||||
info.SystemStatus = append(info.SystemStatus, customInfo)
|
||||
customInfo = [2]string{whitelistRegistriesLabel, strings.Join(vchConfig.Whitelist.Strings(), ",")}
|
||||
info.SystemStatus = append(info.SystemStatus, customInfo)
|
||||
} else {
|
||||
customInfo := [2]string{vchWhitelistMode, "disabled. All registry access allowed."}
|
||||
info.SystemStatus = append(info.SystemStatus, customInfo)
|
||||
}
|
||||
}
|
||||
|
||||
return info, nil
|
||||
}
|
||||
|
||||
// layout for build time as per constants defined in https://golang.org/src/time/format.go
|
||||
const buildTimeLayout = "2006/01/02@15:04:05"
|
||||
|
||||
func (s *SystemBackend) SystemVersion() types.Version {
|
||||
Arch := runtime.GOARCH
|
||||
|
||||
BuildTime := version.BuildDate
|
||||
if t, err := time.Parse(buildTimeLayout, BuildTime); err == nil {
|
||||
// match time format from docker version's output
|
||||
BuildTime = t.Format(time.ANSIC)
|
||||
}
|
||||
|
||||
Experimental := true
|
||||
GitCommit := version.GitCommit
|
||||
GoVersion := runtime.Version()
|
||||
// FIXME: fill with real kernel version
|
||||
KernelVersion := "-"
|
||||
Os := runtime.GOOS
|
||||
Version := version.Version
|
||||
if Version != "" && Version[0] == 'v' {
|
||||
// match version format from docker version's output
|
||||
Version = Version[1:]
|
||||
}
|
||||
|
||||
// go runtime panics without this so keep this here
|
||||
// until we find a repro case and report it to upstream
|
||||
_ = Arch
|
||||
|
||||
version := types.Version{
|
||||
APIVersion: version.DockerAPIVersion,
|
||||
MinAPIVersion: version.DockerMinimumVersion,
|
||||
Arch: Arch,
|
||||
BuildTime: BuildTime,
|
||||
Experimental: Experimental,
|
||||
GitCommit: GitCommit,
|
||||
GoVersion: GoVersion,
|
||||
KernelVersion: KernelVersion,
|
||||
Os: Os,
|
||||
Version: Version,
|
||||
}
|
||||
|
||||
log.Infof("***** version = %#v", version)
|
||||
|
||||
return version
|
||||
}
|
||||
|
||||
// SystemCPUMhzLimit will return the VCH configured Mhz limit
|
||||
func (s *SystemBackend) SystemCPUMhzLimit() (int64, error) {
|
||||
vchInfo, err := s.systemProxy.VCHInfo(context.Background())
|
||||
if err != nil || vchInfo == nil {
|
||||
return 0, err
|
||||
}
|
||||
return vchInfo.CPUMhz, nil
|
||||
}
|
||||
|
||||
func (s *SystemBackend) SystemDiskUsage() (*types.DiskUsage, error) {
|
||||
return nil, errors.APINotSupportedMsg(ProductName(), "SystemDiskUsage")
|
||||
}
|
||||
|
||||
func (s *SystemBackend) SubscribeToEvents(since, until time.Time, filter filters.Args) ([]eventtypes.Message, chan interface{}) {
|
||||
defer trace.End(trace.Begin(""))
|
||||
|
||||
ef := events.NewFilter(filter)
|
||||
return EventService().SubscribeTopic(since, until, ef)
|
||||
}
|
||||
|
||||
func (s *SystemBackend) UnsubscribeFromEvents(listener chan interface{}) {
|
||||
defer trace.End(trace.Begin(""))
|
||||
EventService().Evict(listener)
|
||||
}
|
||||
|
||||
// AuthenticateToRegistry handles docker logins
|
||||
func (s *SystemBackend) AuthenticateToRegistry(ctx context.Context, authConfig *types.AuthConfig) (string, string, error) {
|
||||
defer trace.End(trace.Begin(""))
|
||||
|
||||
// Only look at V2 registries
|
||||
registryAddress := authConfig.ServerAddress
|
||||
if !strings.Contains(authConfig.ServerAddress, "/v2") {
|
||||
registryAddress = registryAddress + "/v2/"
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(registryAddress, "http") {
|
||||
registryAddress = "//" + registryAddress
|
||||
}
|
||||
|
||||
loginURL, err := url.Parse(registryAddress)
|
||||
if err != nil {
|
||||
msg := fmt.Sprintf("Bad login address: %s", registryAddress)
|
||||
log.Errorf(msg)
|
||||
return msg, "", err
|
||||
}
|
||||
|
||||
// Check if registry is contained within whitelisted or insecure registries
|
||||
regctx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
||||
defer cancel()
|
||||
whitelistOk, _, insecureOk := vchConfig.RegistryCheck(regctx, loginURL)
|
||||
if !whitelistOk {
|
||||
msg := fmt.Sprintf("Access denied to unauthorized registry (%s) while VCH is in whitelist mode", loginURL.Host)
|
||||
return msg, "", fmt.Errorf(msg)
|
||||
}
|
||||
|
||||
var certPool *x509.CertPool
|
||||
if insecureOk {
|
||||
log.Infof("Attempting to log into %s insecurely", loginURL.Host)
|
||||
certPool = nil
|
||||
} else {
|
||||
certPool = RegistryCertPool
|
||||
}
|
||||
|
||||
dologin := func(scheme string, skipVerify bool) (string, error) {
|
||||
loginURL.Scheme = scheme
|
||||
|
||||
var authURL *url.URL
|
||||
|
||||
fetcher := urlfetcher.NewURLFetcher(urlfetcher.Options{
|
||||
Timeout: loginTimeout,
|
||||
Username: authConfig.Username,
|
||||
Password: authConfig.Password,
|
||||
RootCAs: certPool,
|
||||
InsecureSkipVerify: skipVerify,
|
||||
})
|
||||
|
||||
// Attempt to get the Auth URL from a simple ping operation (GET) to the registry
|
||||
hdr, err := fetcher.Ping(loginURL)
|
||||
if err == nil {
|
||||
if fetcher.IsStatusUnauthorized() {
|
||||
log.Debugf("Looking up OAuth URL from server %s", loginURL)
|
||||
authURL, err = fetcher.ExtractOAuthURL(hdr.Get("www-authenticate"), nil)
|
||||
} else {
|
||||
// We're not suppose to be here, but if we do end up here, use the login
|
||||
// URL for the auth URL.
|
||||
authURL = loginURL
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
log.Errorf("Looking up OAuth URL failed: %s", err)
|
||||
return "", err
|
||||
}
|
||||
|
||||
log.Debugf("logging onto %s", authURL.String())
|
||||
|
||||
// Just check if we get a token back.
|
||||
token, err := fetcher.FetchAuthToken(authURL)
|
||||
if err != nil || token.Token == "" {
|
||||
// At this point, if a request cannot be solved by a retry, it is an authentication error.
|
||||
log.Errorf("Fetch auth token failed: %s", err)
|
||||
if _, ok := err.(urlfetcher.DoNotRetry); ok {
|
||||
err = fmt.Errorf("Get %s: unauthorized: incorrect username or password", loginURL)
|
||||
} else {
|
||||
err = urlfetcher.AuthTokenError{TokenServer: *authURL}
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
|
||||
return token.Token, nil
|
||||
}
|
||||
|
||||
_, err = dologin("https", insecureOk)
|
||||
if err != nil && insecureOk {
|
||||
_, err = dologin("http", insecureOk)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
// We don't return the token. The config.json will store token if we return
|
||||
// it, but the regular docker daemon doesn't seem to return it either.
|
||||
return "Login Succeeded", "", nil
|
||||
}
|
||||
|
||||
// Utility functions
|
||||
|
||||
func getImageCount() int {
|
||||
images := cache.ImageCache().GetImages()
|
||||
return len(images)
|
||||
}
|
||||
|
||||
func FetchVolumeStores(client *client.PortLayer) (string, error) {
|
||||
|
||||
res, err := client.Storage.VolumeStoresList(storage.NewVolumeStoresListParamsWithContext(ctx))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return strings.Join(res.Payload.Stores, " "), nil
|
||||
}
|
||||
|
||||
func entryStrJoin(entries registry.Set, sep string) string {
|
||||
var s string
|
||||
for _, e := range entries {
|
||||
s += e.String() + sep
|
||||
}
|
||||
|
||||
return s[:len(s)-len(sep)]
|
||||
}
|
||||
232
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/volume.go
generated
vendored
Normal file
232
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/volume.go
generated
vendored
Normal file
@@ -0,0 +1,232 @@
|
||||
// Copyright 2016-2017 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package backends
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
//"regexp"
|
||||
//"strconv"
|
||||
//"strings"
|
||||
"sync"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
//derr "github.com/docker/docker/api/errors"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
//"github.com/docker/go-units"
|
||||
//"github.com/google/uuid"
|
||||
|
||||
vicfilter "github.com/vmware/vic/lib/apiservers/engine/backends/filter"
|
||||
"github.com/vmware/vic/lib/apiservers/engine/errors"
|
||||
"github.com/vmware/vic/lib/apiservers/engine/proxy"
|
||||
"github.com/vmware/vic/lib/apiservers/portlayer/client/containers"
|
||||
//"github.com/vmware/vic/lib/apiservers/portlayer/client/storage"
|
||||
"github.com/vmware/vic/lib/apiservers/portlayer/models"
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
)
|
||||
|
||||
// Volume which defines the docker personalities view of a Volume
|
||||
type VolumeBackend struct {
|
||||
storageProxy proxy.VicStorageProxy
|
||||
}
|
||||
|
||||
// acceptedVolumeFilters are volume filters that are supported by VIC
|
||||
var acceptedVolumeFilters = map[string]bool{
|
||||
"dangling": true,
|
||||
"name": true,
|
||||
"driver": true,
|
||||
"label": true,
|
||||
}
|
||||
|
||||
var volumeBackend *VolumeBackend
|
||||
var volOnce sync.Once
|
||||
|
||||
func NewVolumeBackend() *VolumeBackend {
|
||||
volOnce.Do(func() {
|
||||
volumeBackend = &VolumeBackend{
|
||||
storageProxy: proxy.NewStorageProxy(PortLayerClient()),
|
||||
}
|
||||
})
|
||||
return volumeBackend
|
||||
}
|
||||
|
||||
// Volumes docker personality implementation for VIC
|
||||
func (v *VolumeBackend) Volumes(filter string) ([]*types.Volume, []string, error) {
|
||||
defer trace.End(trace.Begin(filter))
|
||||
|
||||
var volumes []*types.Volume
|
||||
|
||||
// Get volume list from the portlayer
|
||||
volumeResponses, err := v.storageProxy.VolumeList(context.Background(), filter)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// Parse and validate filters
|
||||
volumeFilters, err := filters.FromParam(filter)
|
||||
if err != nil {
|
||||
return nil, nil, errors.VolumeInternalServerError(err)
|
||||
}
|
||||
volFilterContext, err := vicfilter.ValidateVolumeFilters(volumeFilters, acceptedVolumeFilters, nil)
|
||||
if err != nil {
|
||||
return nil, nil, errors.VolumeInternalServerError(err)
|
||||
}
|
||||
|
||||
// joinedVolumes stores names of volumes that are joined to a container
|
||||
// and is used while filtering the output by dangling (dangling=true should
|
||||
// return volumes that are not attached to a container)
|
||||
joinedVolumes := make(map[string]struct{})
|
||||
if volumeFilters.Include("dangling") {
|
||||
// If the dangling filter is specified, gather required items beforehand
|
||||
joinedVolumes, err = fetchJoinedVolumes()
|
||||
if err != nil {
|
||||
return nil, nil, errors.VolumeInternalServerError(err)
|
||||
}
|
||||
}
|
||||
|
||||
log.Infoln("volumes found:")
|
||||
for _, vol := range volumeResponses {
|
||||
log.Infof("%s", vol.Name)
|
||||
|
||||
volumeMetadata, err := extractDockerMetadata(vol.Metadata)
|
||||
if err != nil {
|
||||
return nil, nil, errors.VolumeInternalServerError(fmt.Errorf("error unmarshalling docker metadata: %s", err))
|
||||
}
|
||||
|
||||
// Set fields needed for filtering the output
|
||||
volFilterContext.Name = vol.Name
|
||||
volFilterContext.Driver = vol.Driver
|
||||
_, volFilterContext.Joined = joinedVolumes[vol.Name]
|
||||
volFilterContext.Labels = volumeMetadata.Labels
|
||||
|
||||
// Include the volume in the output if it meets the filtering criteria
|
||||
filterAction := vicfilter.IncludeVolume(volumeFilters, volFilterContext)
|
||||
if filterAction == vicfilter.IncludeAction {
|
||||
volume := NewVolumeModel(vol, volumeMetadata.Labels)
|
||||
volumes = append(volumes, volume)
|
||||
}
|
||||
}
|
||||
|
||||
return volumes, nil, nil
|
||||
}
|
||||
|
||||
// VolumeInspect : docker personality implementation for VIC
|
||||
func (v *VolumeBackend) VolumeInspect(name string) (*types.Volume, error) {
|
||||
defer trace.End(trace.Begin(name))
|
||||
|
||||
volInfo, err := v.storageProxy.VolumeInfo(context.Background(), name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
volumeMetadata, err := extractDockerMetadata(volInfo.Metadata)
|
||||
if err != nil {
|
||||
return nil, errors.VolumeInternalServerError(fmt.Errorf("error unmarshalling docker metadata: %s", err))
|
||||
}
|
||||
volume := NewVolumeModel(volInfo, volumeMetadata.Labels)
|
||||
|
||||
return volume, nil
|
||||
}
|
||||
|
||||
// VolumeCreate : docker personality implementation for VIC
|
||||
func (v *VolumeBackend) VolumeCreate(name, driverName string, volumeData, labels map[string]string) (*types.Volume, error) {
|
||||
defer trace.End(trace.Begin(name))
|
||||
|
||||
result, err := v.storageProxy.Create(context.Background(), name, driverName, volumeData, labels)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// VolumeRm : docker personality for VIC
|
||||
func (v *VolumeBackend) VolumeRm(name string, force bool) error {
|
||||
defer trace.End(trace.Begin(name))
|
||||
|
||||
err := v.storageProxy.Remove(context.Background(), name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v *VolumeBackend) VolumesPrune(pruneFilters filters.Args) (*types.VolumesPruneReport, error) {
|
||||
return nil, errors.APINotSupportedMsg(ProductName(), "VolumesPrune")
|
||||
}
|
||||
|
||||
//------------------------------------
|
||||
// Utility Functions
|
||||
//------------------------------------
|
||||
|
||||
func NewVolumeModel(volume *models.VolumeResponse, labels map[string]string) *types.Volume {
|
||||
return &types.Volume{
|
||||
Driver: volume.Driver,
|
||||
Name: volume.Name,
|
||||
Labels: labels,
|
||||
Mountpoint: volume.Label,
|
||||
}
|
||||
}
|
||||
|
||||
// fetchJoinedVolumes obtains all containers from the portlayer and returns a map with all
|
||||
// volumes that are joined to at least one container.
|
||||
func fetchJoinedVolumes() (map[string]struct{}, error) {
|
||||
conts, err := allContainers()
|
||||
if err != nil {
|
||||
return nil, errors.VolumeInternalServerError(err)
|
||||
}
|
||||
|
||||
joinedVolumes := make(map[string]struct{})
|
||||
var s struct{}
|
||||
for i := range conts {
|
||||
for _, vol := range conts[i].VolumeConfig {
|
||||
joinedVolumes[vol.Name] = s
|
||||
}
|
||||
}
|
||||
|
||||
return joinedVolumes, nil
|
||||
}
|
||||
|
||||
// allContainers obtains all containers from the portlayer, akin to `docker ps -a`.
|
||||
func allContainers() ([]*models.ContainerInfo, error) {
|
||||
client := PortLayerClient()
|
||||
if client == nil {
|
||||
return nil, errors.NillPortlayerClientError("Volume Backend")
|
||||
}
|
||||
|
||||
all := true
|
||||
cons, err := client.Containers.GetContainerList(containers.NewGetContainerListParamsWithContext(ctx).WithAll(&all))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return cons.Payload, nil
|
||||
}
|
||||
|
||||
// Unmarshal the docker metadata using the docker metadata key. The docker
|
||||
// metadatakey. We stash the vals we know about in that map with that key.
|
||||
func extractDockerMetadata(metadataMap map[string]string) (*proxy.VolumeMetadata, error) {
|
||||
v, ok := metadataMap[proxy.DockerMetadataModelKey]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("metadata %s missing", proxy.DockerMetadataModelKey)
|
||||
}
|
||||
|
||||
result := &proxy.VolumeMetadata{}
|
||||
err := json.Unmarshal([]byte(v), result)
|
||||
return result, err
|
||||
}
|
||||
63
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/volume_test.go
generated
vendored
Normal file
63
vendor/github.com/vmware/vic/lib/apiservers/engine/backends/volume_test.go
generated
vendored
Normal file
@@ -0,0 +1,63 @@
|
||||
// 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)
|
||||
}
|
||||
19
vendor/github.com/vmware/vic/lib/apiservers/engine/constants/constants.go
generated
vendored
Normal file
19
vendor/github.com/vmware/vic/lib/apiservers/engine/constants/constants.go
generated
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
// 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 constants
|
||||
|
||||
const (
|
||||
DefaultVolumeDriver = "vsphere"
|
||||
)
|
||||
182
vendor/github.com/vmware/vic/lib/apiservers/engine/errors/errors.go
generated
vendored
Normal file
182
vendor/github.com/vmware/vic/lib/apiservers/engine/errors/errors.go
generated
vendored
Normal file
@@ -0,0 +1,182 @@
|
||||
// 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 errors
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
derr "github.com/docker/docker/api/errors"
|
||||
)
|
||||
|
||||
// Used to check status code of derr, which is not a public type
|
||||
type httpStatusError interface {
|
||||
HTTPErrorStatusCode() int
|
||||
}
|
||||
|
||||
// InvalidVolumeError is returned when the user specifies a client directory as a volume.
|
||||
type InvalidVolumeError struct {
|
||||
}
|
||||
|
||||
func (e InvalidVolumeError) Error() string {
|
||||
return fmt.Sprintf("mounting directories as a data volume is not supported.")
|
||||
}
|
||||
|
||||
// InvalidBindError is returned when create/run -v has more params than allowed.
|
||||
type InvalidBindError struct {
|
||||
Volume string
|
||||
}
|
||||
|
||||
func (e InvalidBindError) Error() string {
|
||||
return fmt.Sprintf("volume bind input is invalid: -v %s", e.Volume)
|
||||
}
|
||||
|
||||
type ServerNotReadyError struct {
|
||||
Name string
|
||||
}
|
||||
|
||||
func (e ServerNotReadyError) Error() string {
|
||||
return fmt.Sprintf("Server %s not ready", e.Name)
|
||||
}
|
||||
|
||||
func APINotSupportedMsg(product, method string) error {
|
||||
return fmt.Errorf("%s does not yet implement %s", product, method)
|
||||
}
|
||||
|
||||
func NillPortlayerClientError(caller string) error {
|
||||
return derr.NewErrorWithStatusCode(fmt.Errorf("%s failed to get a portlayer client", caller), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
// VolumeJoinNotFoundError returns a 404 docker error for a volume join request.
|
||||
func VolumeJoinNotFoundError(msg string) error {
|
||||
return derr.NewRequestNotFoundError(fmt.Errorf(msg))
|
||||
}
|
||||
|
||||
// VolumeCreateNotFoundError returns a 404 docker error for a volume create request.
|
||||
func VolumeCreateNotFoundError(msg string) error {
|
||||
return derr.NewErrorWithStatusCode(fmt.Errorf("No volume store named (%s) exists", msg), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
// VolumeNotFoundError returns a 404 docker error for a volume get request.
|
||||
func VolumeNotFoundError(msg string) error {
|
||||
return derr.NewErrorWithStatusCode(fmt.Errorf("No such volume: %s", msg), http.StatusNotFound)
|
||||
}
|
||||
|
||||
// VolumeInternalServerError returns a 500 docker error for a volume-related request.
|
||||
func VolumeInternalServerError(err error) error {
|
||||
return derr.NewErrorWithStatusCode(err, http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
func ContainerResourceNotFoundError(cid, res string) error {
|
||||
return derr.NewRequestNotFoundError(fmt.Errorf("No such %s for container: %s", res, cid))
|
||||
}
|
||||
|
||||
func ResourceNotFoundError(res string) error {
|
||||
return derr.NewRequestNotFoundError(fmt.Errorf("No such %s", res))
|
||||
}
|
||||
|
||||
// NotFoundError returns a 404 docker error when a container is not found.
|
||||
func NotFoundError(msg string) error {
|
||||
return derr.NewRequestNotFoundError(fmt.Errorf("No such container: %s", msg))
|
||||
}
|
||||
|
||||
func ImageNotFoundError(image, tag string) error {
|
||||
return derr.NewRequestNotFoundError(fmt.Errorf("An image does not exist locally with the tag: %s", image))
|
||||
}
|
||||
|
||||
func TagNotFoundError(image, tag string) error {
|
||||
return derr.NewRequestNotFoundError(fmt.Errorf("tag does not exist: %s:%s", image, tag))
|
||||
}
|
||||
|
||||
// ResourceLockedError returns a 423 http status
|
||||
func ResourceLockedError(msg string) error {
|
||||
return derr.NewErrorWithStatusCode(fmt.Errorf("Resource locked: %s", msg), http.StatusLocked)
|
||||
}
|
||||
|
||||
// InternalServerError returns a 500 docker error on a portlayer error.
|
||||
func InternalServerError(msg string) error {
|
||||
return derr.NewErrorWithStatusCode(fmt.Errorf("Server error from portlayer: %s", msg), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
// BadRequestError returns a 400 docker error on a bad request.
|
||||
func BadRequestError(msg string) error {
|
||||
return derr.NewErrorWithStatusCode(fmt.Errorf("Bad request error from portlayer: %s", msg), http.StatusBadRequest)
|
||||
}
|
||||
|
||||
func ConflictError(msg string) error {
|
||||
return derr.NewRequestConflictError(fmt.Errorf("Conflict error from portlayer: %s", msg))
|
||||
}
|
||||
|
||||
func PluginNotFoundError(name string) error {
|
||||
return derr.NewErrorWithStatusCode(fmt.Errorf("plugin %s not found", name), http.StatusNotFound)
|
||||
}
|
||||
|
||||
func SwarmNotSupportedError() error {
|
||||
return derr.NewErrorWithStatusCode(fmt.Errorf("Docker Swarm is not yet supported"), http.StatusNotFound)
|
||||
}
|
||||
|
||||
func StreamFormatNotRecognized() error {
|
||||
return derr.NewRequestConflictError(fmt.Errorf("Stream format not recognized"))
|
||||
}
|
||||
|
||||
func ConcurrentAPIError(name, request string) error {
|
||||
return derr.NewRequestConflictError(fmt.Errorf("%s request is already in progress for container '%s'.", request, name))
|
||||
}
|
||||
|
||||
// Error type check
|
||||
|
||||
func IsNotFoundError(err error) bool {
|
||||
// if error was created with the docker error function, check the status code
|
||||
if httpErr, ok := err.(httpStatusError); ok {
|
||||
if httpErr.HTTPErrorStatusCode() == http.StatusNotFound {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func IsConflictError(err error) bool {
|
||||
// if error was created with the docker error function, check the status code
|
||||
if httpErr, ok := err.(httpStatusError); ok {
|
||||
if httpErr.HTTPErrorStatusCode() == http.StatusConflict {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func IsResourceInUse(err error) bool {
|
||||
if httpErr, ok := err.(httpStatusError); ok {
|
||||
if httpErr.HTTPErrorStatusCode() == http.StatusLocked {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func IsServerNotReady(err error) bool {
|
||||
_, ok := err.(ServerNotReadyError)
|
||||
|
||||
return ok
|
||||
}
|
||||
|
||||
type DetachError struct{}
|
||||
|
||||
func (DetachError) Error() string {
|
||||
return "detached from container"
|
||||
}
|
||||
614
vendor/github.com/vmware/vic/lib/apiservers/engine/network/utils.go
generated
vendored
Normal file
614
vendor/github.com/vmware/vic/lib/apiservers/engine/network/utils.go
generated
vendored
Normal file
@@ -0,0 +1,614 @@
|
||||
// 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 network
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/go-connections/nat"
|
||||
"github.com/docker/libnetwork/iptables"
|
||||
"github.com/docker/libnetwork/portallocator"
|
||||
"github.com/vishvananda/netlink"
|
||||
|
||||
viccontainer "github.com/vmware/vic/lib/apiservers/engine/backends/container"
|
||||
"github.com/vmware/vic/lib/apiservers/engine/backends/portmap"
|
||||
"github.com/vmware/vic/lib/apiservers/portlayer/models"
|
||||
"github.com/vmware/vic/lib/config/executor"
|
||||
)
|
||||
|
||||
const (
|
||||
bridgeIfaceName = "bridge"
|
||||
)
|
||||
|
||||
var (
|
||||
publicIfaceName = "public"
|
||||
|
||||
portMapper portmap.PortMapper
|
||||
|
||||
// bridge-to-bridge rules, indexed by mapped port;
|
||||
// this map is used to delete the rule once
|
||||
// the container stops or is removed
|
||||
btbRules map[string][]string
|
||||
|
||||
cbpLock sync.Mutex
|
||||
ContainerByPort map[string]string // port:containerID
|
||||
)
|
||||
|
||||
func init() {
|
||||
portMapper = portmap.NewPortMapper()
|
||||
btbRules = make(map[string][]string)
|
||||
ContainerByPort = make(map[string]string)
|
||||
|
||||
l, err := netlink.LinkByName(publicIfaceName)
|
||||
if l == nil {
|
||||
l, err = netlink.LinkByAlias(publicIfaceName)
|
||||
if err != nil {
|
||||
log.Errorf("interface %s not found", publicIfaceName)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// don't use interface alias for iptables rules
|
||||
publicIfaceName = l.Attrs().Name
|
||||
}
|
||||
|
||||
// requestHostPort finds a free port on the host
|
||||
func requestHostPort(proto string) (int, error) {
|
||||
pa := portallocator.Get()
|
||||
return pa.RequestPortInRange(nil, proto, 0, 0)
|
||||
}
|
||||
|
||||
type portMapping struct {
|
||||
intHostPort int
|
||||
strHostPort string
|
||||
portProto nat.Port
|
||||
}
|
||||
|
||||
// unrollPortMap processes config for mapping/unmapping ports e.g. from hostconfig.PortBindings
|
||||
func unrollPortMap(portMap nat.PortMap) ([]*portMapping, error) {
|
||||
var portMaps []*portMapping
|
||||
for i, pb := range portMap {
|
||||
|
||||
proto, port := nat.SplitProtoPort(string(i))
|
||||
nport, err := nat.NewPort(proto, port)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// iterate over all the ports in pb []nat.PortBinding
|
||||
for i := range pb {
|
||||
var hostPort int
|
||||
var hPort string
|
||||
if pb[i].HostPort == "" {
|
||||
// use a random port since no host port is specified
|
||||
hostPort, err = requestHostPort(proto)
|
||||
if err != nil {
|
||||
log.Errorf("could not find available port on host")
|
||||
return nil, err
|
||||
}
|
||||
log.Infof("using port %d on the host for port mapping", hostPort)
|
||||
|
||||
// update the hostconfig
|
||||
pb[i].HostPort = strconv.Itoa(hostPort)
|
||||
|
||||
} else {
|
||||
hostPort, err = strconv.Atoi(pb[i].HostPort)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
hPort = strconv.Itoa(hostPort)
|
||||
portMaps = append(portMaps, &portMapping{
|
||||
intHostPort: hostPort,
|
||||
strHostPort: hPort,
|
||||
portProto: nport,
|
||||
})
|
||||
}
|
||||
}
|
||||
return portMaps, nil
|
||||
}
|
||||
|
||||
// MapPorts maps ports defined in bridge endpoint for containerID
|
||||
func MapPorts(vc *viccontainer.VicContainer, endpoint *models.EndpointConfig, containerID string) error {
|
||||
if endpoint == nil {
|
||||
return fmt.Errorf("invalid endpoint")
|
||||
}
|
||||
|
||||
var containerIP net.IP
|
||||
containerIP = net.ParseIP(endpoint.Address)
|
||||
if containerIP == nil {
|
||||
return fmt.Errorf("invalid endpoint address %s", endpoint.Address)
|
||||
}
|
||||
|
||||
portMap := addIndirectEndpointsToPortMap([]*models.EndpointConfig{endpoint}, nil)
|
||||
log.Debugf("Mapping ports of %q on endpoint %s: %v", containerID, endpoint.Name, portMap)
|
||||
if len(portMap) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
mappings, err := unrollPortMap(portMap)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// cannot occur direct under the lock below because unmap ports take a lock.
|
||||
defer func() {
|
||||
if err != nil {
|
||||
// if we didn't succeed then make sure we clean up
|
||||
UnmapPorts(containerID, vc)
|
||||
}
|
||||
}()
|
||||
|
||||
cbpLock.Lock()
|
||||
defer cbpLock.Unlock()
|
||||
vc.NATMap = portMap
|
||||
|
||||
for _, p := range mappings {
|
||||
// update mapped ports
|
||||
if ContainerByPort[p.strHostPort] == containerID {
|
||||
log.Debugf("Skipping mapping for already mapped port %s for %s", p.strHostPort, containerID)
|
||||
continue
|
||||
}
|
||||
|
||||
if err = portMapper.MapPort(nil, p.intHostPort, p.portProto.Proto(), containerIP.String(), p.portProto.Int(), publicIfaceName, bridgeIfaceName); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// bridge-to-bridge pin hole for traffic from containers for exposed port
|
||||
if err = interBridgeTraffic(portmap.Map, p.strHostPort, p.portProto.Proto(), containerIP.String(), p.portProto.Port()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// update mapped ports
|
||||
ContainerByPort[p.strHostPort] = containerID
|
||||
log.Debugf("mapped port %s for container %s", p.strHostPort, containerID)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnmapPorts unmaps ports defined in hostconfig if it's mapped for this container
|
||||
func UnmapPorts(id string, vc *viccontainer.VicContainer) error {
|
||||
portMap := vc.NATMap
|
||||
log.Debugf("UnmapPorts for %s: %v", vc.ContainerID, portMap)
|
||||
|
||||
if len(portMap) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
mappings, err := unrollPortMap(vc.NATMap)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cbpLock.Lock()
|
||||
defer cbpLock.Unlock()
|
||||
vc.NATMap = nil
|
||||
|
||||
for _, p := range mappings {
|
||||
// check if we should actually unmap based on current mappings
|
||||
mappedID, mapped := ContainerByPort[p.strHostPort]
|
||||
if !mapped {
|
||||
log.Debugf("skipping already unmapped %s", p.strHostPort)
|
||||
continue
|
||||
}
|
||||
if mappedID != id {
|
||||
log.Debugf("port is mapped for container %s, not %s, skipping", mappedID, id)
|
||||
continue
|
||||
}
|
||||
|
||||
if err = portMapper.UnmapPort(nil, p.intHostPort, p.portProto.Proto(), p.portProto.Int(), publicIfaceName, bridgeIfaceName); err != nil {
|
||||
log.Warnf("failed to unmap port %s: %s", p.strHostPort, err)
|
||||
continue
|
||||
}
|
||||
|
||||
// bridge-to-bridge pin hole for traffic from containers for exposed port
|
||||
if err = interBridgeTraffic(portmap.Unmap, p.strHostPort, "", "", ""); err != nil {
|
||||
log.Warnf("failed to undo bridge-to-bridge pinhole %s: %s", p.strHostPort, err)
|
||||
continue
|
||||
}
|
||||
|
||||
// update mapped ports
|
||||
delete(ContainerByPort, p.strHostPort)
|
||||
log.Debugf("unmapped port %s", p.strHostPort)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// interBridgeTraffic enables traffic for exposed port from one bridge network to another
|
||||
func interBridgeTraffic(op portmap.Operation, hostPort, proto, containerAddr, containerPort string) error {
|
||||
switch op {
|
||||
case portmap.Map:
|
||||
switch proto {
|
||||
case "udp", "tcp":
|
||||
default:
|
||||
return fmt.Errorf("unknown protocol: %s", proto)
|
||||
}
|
||||
|
||||
// rule to allow connections from bridge interface for the
|
||||
// specific mapped port. has to inserted at the top of the
|
||||
// chain rather than appended to supersede bridge-to-bridge
|
||||
// traffic blocking
|
||||
baseArgs := []string{"-t", string(iptables.Filter),
|
||||
"-i", bridgeIfaceName,
|
||||
"-o", bridgeIfaceName,
|
||||
"-p", proto,
|
||||
"-d", containerAddr,
|
||||
"--dport", containerPort,
|
||||
"-j", "ACCEPT",
|
||||
}
|
||||
|
||||
args := append([]string{string(iptables.Insert), "VIC", "1"}, baseArgs...)
|
||||
if _, err := iptables.Raw(args...); err != nil && !os.IsExist(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
btbRules[hostPort] = baseArgs
|
||||
case portmap.Unmap:
|
||||
if args, ok := btbRules[hostPort]; ok {
|
||||
args = append([]string{string(iptables.Delete), "VIC"}, args...)
|
||||
if _, err := iptables.Raw(args...); err != nil && !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
delete(btbRules, hostPort)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func PublicIPv4Addrs() ([]string, error) {
|
||||
l, err := netlink.LinkByName(publicIfaceName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not look up link from interface name %s: %s", publicIfaceName, err.Error())
|
||||
}
|
||||
|
||||
addrs, err := netlink.AddrList(l, netlink.FAMILY_V4)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not get addresses from public link: %s", err.Error())
|
||||
}
|
||||
|
||||
ips := make([]string, len(addrs))
|
||||
for i := range addrs {
|
||||
ips[i] = addrs[i].IP.String()
|
||||
}
|
||||
|
||||
return ips, nil
|
||||
}
|
||||
|
||||
// portMapFromContainer constructs a docker portmap from the container's
|
||||
// info as returned by the portlayer and adds nil entries for any exposed ports
|
||||
// that are unmapped
|
||||
func PortMapFromContainer(vc *viccontainer.VicContainer, t *models.ContainerInfo) nat.PortMap {
|
||||
var mappings nat.PortMap
|
||||
|
||||
if t != nil {
|
||||
mappings = addDirectEndpointsToPortMap(t.Endpoints, mappings)
|
||||
}
|
||||
if vc != nil && vc.Config != nil {
|
||||
if vc.NATMap != nil {
|
||||
// if there's a NAT map for the container then just use that for the indirect port set
|
||||
mappings = mergePortMaps(vc.NATMap, mappings)
|
||||
} else {
|
||||
// if there's no NAT map then we use the backend data every time
|
||||
mappings = addIndirectEndpointsToPortMap(t.Endpoints, mappings)
|
||||
}
|
||||
mappings = addExposedToPortMap(vc.Config, mappings)
|
||||
}
|
||||
|
||||
return mappings
|
||||
}
|
||||
|
||||
func ContainerWithPort(hostPort string) (string, bool) {
|
||||
cbpLock.Lock()
|
||||
mappedCtr, mapped := ContainerByPort[hostPort]
|
||||
cbpLock.Unlock()
|
||||
|
||||
return mappedCtr, mapped
|
||||
}
|
||||
|
||||
// mergePortMaps creates a new map containing the union of the two arguments
|
||||
func mergePortMaps(map1, map2 nat.PortMap) nat.PortMap {
|
||||
resultMap := make(map[nat.Port][]nat.PortBinding)
|
||||
for k, v := range map1 {
|
||||
resultMap[k] = v
|
||||
}
|
||||
|
||||
for k, v := range map2 {
|
||||
vr := resultMap[k]
|
||||
resultMap[k] = append(vr, v...)
|
||||
}
|
||||
|
||||
return resultMap
|
||||
}
|
||||
|
||||
// addIndirectEndpointToPortMap constructs a docker portmap from the container's info as returned by the portlayer for those ports that
|
||||
// require NAT forward on the endpointVM.
|
||||
// The portMap provided is modified and returned - the return value should always be used.
|
||||
func addIndirectEndpointsToPortMap(endpoints []*models.EndpointConfig, portMap nat.PortMap) nat.PortMap {
|
||||
if len(endpoints) == 0 {
|
||||
return portMap
|
||||
}
|
||||
|
||||
// will contain a combined set of port mappings
|
||||
if portMap == nil {
|
||||
portMap = make(nat.PortMap)
|
||||
}
|
||||
|
||||
// add IP address into port spec to allow direct usage of data returned by calls such as docker port
|
||||
var ip string
|
||||
ips, _ := PublicIPv4Addrs()
|
||||
if len(ips) > 0 {
|
||||
ip = ips[0]
|
||||
}
|
||||
|
||||
// Preserve the existing behaviour if we do not have an IP for some reason.
|
||||
if ip == "" {
|
||||
ip = "0.0.0.0"
|
||||
}
|
||||
|
||||
for _, ep := range endpoints {
|
||||
if ep.Direct {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, port := range ep.Ports {
|
||||
mappings, err := nat.ParsePortSpec(port)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
// just continue if we do have partial port data
|
||||
}
|
||||
|
||||
for i := range mappings {
|
||||
p := mappings[i].Port
|
||||
b := mappings[i].Binding
|
||||
|
||||
if b.HostIP == "" {
|
||||
b.HostIP = ip
|
||||
}
|
||||
|
||||
if mappings[i].Binding.HostPort == "" {
|
||||
// leave this undefined for dynamic assignment
|
||||
// TODO: for port stability over VCH restart we would expect to set the dynamically assigned port
|
||||
// recorded in containerVM annotations here, so that the old host->port mapping is preserved.
|
||||
}
|
||||
|
||||
log.Debugf("Adding indirect mapping for port %v: %v (%s)", p, b, port)
|
||||
|
||||
current, _ := portMap[p]
|
||||
portMap[p] = append(current, b)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return portMap
|
||||
}
|
||||
|
||||
// addDirectEndpointsToPortMap constructs a docker portmap from the container's info as returned by the portlayer for those
|
||||
// ports exposed directly from the containerVM via container network
|
||||
// The portMap provided is modified and returned - the return value should always be used.
|
||||
func addDirectEndpointsToPortMap(endpoints []*models.EndpointConfig, portMap nat.PortMap) nat.PortMap {
|
||||
if len(endpoints) == 0 {
|
||||
return portMap
|
||||
}
|
||||
|
||||
if portMap == nil {
|
||||
portMap = make(nat.PortMap)
|
||||
}
|
||||
|
||||
for _, ep := range endpoints {
|
||||
if !ep.Direct {
|
||||
continue
|
||||
}
|
||||
|
||||
// add IP address into the port spec to allow direct usage of data returned by calls such as docker port
|
||||
var ip string
|
||||
rawIP, _, _ := net.ParseCIDR(ep.Address)
|
||||
if rawIP != nil {
|
||||
ip = rawIP.String()
|
||||
}
|
||||
|
||||
if ip == "" {
|
||||
ip = "0.0.0.0"
|
||||
}
|
||||
|
||||
for _, port := range ep.Ports {
|
||||
mappings, err := nat.ParsePortSpec(port)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
// just continue if we do have partial port data
|
||||
}
|
||||
|
||||
for i := range mappings {
|
||||
if mappings[i].Binding.HostIP == "" {
|
||||
mappings[i].Binding.HostIP = ip
|
||||
}
|
||||
|
||||
if mappings[i].Binding.HostPort == "" {
|
||||
// If there's no explicit host port and it's a direct endpoint, then
|
||||
// mirror the actual port. It's a bit misleading but we're trying to
|
||||
// pack extended function into an existing structure.
|
||||
_, p := nat.SplitProtoPort(string(mappings[i].Port))
|
||||
mappings[i].Binding.HostPort = p
|
||||
}
|
||||
}
|
||||
|
||||
for _, mapping := range mappings {
|
||||
p := mapping.Port
|
||||
current, _ := portMap[p]
|
||||
portMap[p] = append(current, mapping.Binding)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return portMap
|
||||
}
|
||||
|
||||
// addExposedToPortMap ensures that exposed ports are all present in the port map.
|
||||
// This means nil entries for any exposed ports that are not mapped.
|
||||
// The portMap provided is modified and returned - the return value should always be used.
|
||||
func addExposedToPortMap(config *container.Config, portMap nat.PortMap) nat.PortMap {
|
||||
if config == nil || len(config.ExposedPorts) == 0 {
|
||||
return portMap
|
||||
}
|
||||
|
||||
if portMap == nil {
|
||||
portMap = make(nat.PortMap)
|
||||
}
|
||||
|
||||
for p := range config.ExposedPorts {
|
||||
if _, ok := portMap[p]; ok {
|
||||
continue
|
||||
}
|
||||
|
||||
portMap[p] = nil
|
||||
}
|
||||
|
||||
return portMap
|
||||
}
|
||||
|
||||
func DirectPortInformation(t *models.ContainerInfo) []types.Port {
|
||||
var resultPorts []types.Port
|
||||
|
||||
for _, ne := range t.Endpoints {
|
||||
trust, _ := executor.ParseTrustLevel(ne.Trust)
|
||||
if !ne.Direct || trust == executor.Closed || trust == executor.Outbound || trust == executor.Peers {
|
||||
// we don't publish port info for ports that are not directly accessible from outside of the VCH
|
||||
continue
|
||||
}
|
||||
|
||||
ip := strings.SplitN(ne.Address, "/", 2)[0]
|
||||
|
||||
// if it's an open network then inject an "all ports" entry
|
||||
if trust == executor.Open {
|
||||
resultPorts = append(resultPorts, types.Port{
|
||||
IP: ip,
|
||||
PrivatePort: 0,
|
||||
PublicPort: 0,
|
||||
Type: "*",
|
||||
})
|
||||
}
|
||||
|
||||
for _, p := range ne.Ports {
|
||||
port := types.Port{IP: ip}
|
||||
|
||||
portsAndType := strings.SplitN(p, "/", 2)
|
||||
port.Type = portsAndType[1]
|
||||
|
||||
mapping := strings.Split(portsAndType[0], ":")
|
||||
// if no mapping is supplied then there's only one and that's public. If there is a mapping then the first
|
||||
// entry is the public
|
||||
public, err := strconv.Atoi(mapping[0])
|
||||
if err != nil {
|
||||
log.Errorf("Got an error trying to convert public port number \"%s\" to an int: %s", mapping[0], err)
|
||||
continue
|
||||
}
|
||||
port.PublicPort = uint16(public)
|
||||
|
||||
// If port is on container network then a different container could be forwarding the same port via the endpoint
|
||||
// so must check for explicit ID match. If a match then it's definitely not accessed directly.
|
||||
if ContainerByPort[mapping[0]] == t.ContainerConfig.ContainerID {
|
||||
continue
|
||||
}
|
||||
|
||||
// did not find a way to have the client not render both ports so setting them the same even if there's not
|
||||
// redirect occurring
|
||||
port.PrivatePort = port.PublicPort
|
||||
|
||||
// for open networks we don't bother listing direct ports
|
||||
if len(mapping) == 1 {
|
||||
if trust != executor.Open {
|
||||
resultPorts = append(resultPorts, port)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
private, err := strconv.Atoi(mapping[1])
|
||||
if err != nil {
|
||||
log.Errorf("Got an error trying to convert private port number \"%s\" to an int: %s", mapping[1], err)
|
||||
continue
|
||||
}
|
||||
port.PrivatePort = uint16(private)
|
||||
resultPorts = append(resultPorts, port)
|
||||
}
|
||||
}
|
||||
|
||||
return resultPorts
|
||||
}
|
||||
|
||||
// returns port bindings as a slice of Docker Ports for return to the client
|
||||
// returns empty slice on error
|
||||
//func PortForwardingInformation(t *models.ContainerInfo, ips []string) []types.Port {
|
||||
func PortForwardingInformation(vc *viccontainer.VicContainer, ips []string) []types.Port {
|
||||
//cid := t.ContainerConfig.ContainerID
|
||||
//c := cache.ContainerCache().GetContainer(cid)
|
||||
|
||||
if vc == nil {
|
||||
log.Errorf("Could not get port forwarding info for container")
|
||||
return nil
|
||||
}
|
||||
|
||||
portBindings := vc.NATMap
|
||||
var resultPorts []types.Port
|
||||
|
||||
// create a port for each IP on the interface (usually only 1, but could be more)
|
||||
// (works with both IPv4 and IPv6 addresses)
|
||||
for _, ip := range ips {
|
||||
port := types.Port{IP: ip}
|
||||
|
||||
for portBindingPrivatePort, hostPortBindings := range portBindings {
|
||||
proto, pnum := nat.SplitProtoPort(string(portBindingPrivatePort))
|
||||
portNum, err := strconv.Atoi(pnum)
|
||||
if err != nil {
|
||||
log.Warnf("Unable to convert private port %q to an int", pnum)
|
||||
continue
|
||||
}
|
||||
port.PrivatePort = uint16(portNum)
|
||||
port.Type = proto
|
||||
|
||||
for i := 0; i < len(hostPortBindings); i++ {
|
||||
// If port is on container network then a different container could be forwarding the same port via the endpoint
|
||||
// so must check for explicit ID match. If no match, definitely not forwarded via endpoint.
|
||||
//if ContainerByPort[hostPortBindings[i].HostPort] != t.ContainerConfig.ContainerID {
|
||||
if ContainerByPort[hostPortBindings[i].HostPort] != vc.ContainerID {
|
||||
continue
|
||||
}
|
||||
|
||||
newport := port
|
||||
publicPort, err := strconv.Atoi(hostPortBindings[i].HostPort)
|
||||
if err != nil {
|
||||
log.Infof("Got an error trying to convert public port number to an int")
|
||||
continue
|
||||
}
|
||||
|
||||
newport.PublicPort = uint16(publicPort)
|
||||
// sanity check -- sometimes these come back as 0 when no binding actually exists
|
||||
// that doesn't make sense, so in that case we don't want to report these bindings
|
||||
if newport.PublicPort != 0 && newport.PrivatePort != 0 {
|
||||
resultPorts = append(resultPorts, newport)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return resultPorts
|
||||
}
|
||||
275
vendor/github.com/vmware/vic/lib/apiservers/engine/proxy/archive_proxy.go
generated
vendored
Normal file
275
vendor/github.com/vmware/vic/lib/apiservers/engine/proxy/archive_proxy.go
generated
vendored
Normal file
@@ -0,0 +1,275 @@
|
||||
// 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 proxy
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/vmware/vic/lib/apiservers/engine/errors"
|
||||
"github.com/vmware/vic/lib/apiservers/portlayer/client"
|
||||
"github.com/vmware/vic/lib/apiservers/portlayer/client/storage"
|
||||
"github.com/vmware/vic/lib/archive"
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
)
|
||||
|
||||
type VicArchiveProxy interface {
|
||||
ArchiveExportReader(op trace.Operation, store, ancestorStore, deviceID, ancestor string, data bool, filterSpec archive.FilterSpec) (io.ReadCloser, error)
|
||||
ArchiveImportWriter(op trace.Operation, store, deviceID string, filterSpec archive.FilterSpec, wg *sync.WaitGroup, errchan chan error) (io.WriteCloser, error)
|
||||
StatPath(op trace.Operation, store, deviceID string, filterSpec archive.FilterSpec) (*types.ContainerPathStat, error)
|
||||
}
|
||||
|
||||
//------------------------------------
|
||||
// ArchiveProxy
|
||||
//------------------------------------
|
||||
|
||||
type ArchiveProxy struct {
|
||||
client *client.PortLayer
|
||||
}
|
||||
|
||||
var archiveProxy *ArchiveProxy
|
||||
|
||||
func NewArchiveProxy(client *client.PortLayer) VicArchiveProxy {
|
||||
return &ArchiveProxy{client: client}
|
||||
}
|
||||
|
||||
func GetArchiveProxy() VicArchiveProxy {
|
||||
return archiveProxy
|
||||
}
|
||||
|
||||
// ArchiveExportReader streams a tar archive from the portlayer. Once the stream is complete,
|
||||
// an io.Reader is returned and the caller can use that reader to parse the data.
|
||||
func (a *ArchiveProxy) ArchiveExportReader(op trace.Operation, store, ancestorStore, deviceID, ancestor string, data bool, filterSpec archive.FilterSpec) (io.ReadCloser, error) {
|
||||
defer trace.End(trace.Begin(deviceID))
|
||||
|
||||
if a.client == nil {
|
||||
return nil, errors.NillPortlayerClientError("ArchiveProxy")
|
||||
}
|
||||
|
||||
if store == "" || deviceID == "" {
|
||||
return nil, fmt.Errorf("ArchiveExportReader called with either empty store or deviceID")
|
||||
}
|
||||
|
||||
var err error
|
||||
|
||||
pipeReader, pipeWriter := io.Pipe()
|
||||
|
||||
go func() {
|
||||
// make sure we get out of io.Copy if context is canceled
|
||||
select {
|
||||
case <-op.Done():
|
||||
// Attempt to tell the portlayer to cancel the stream. This is one way of cancelling the
|
||||
// stream. The other way is for the caller of this function to close the returned CloseReader.
|
||||
// Callers of this function should do one but not both.
|
||||
err := pipeReader.Close()
|
||||
if err != nil {
|
||||
op.Errorf("Error closing pipereader in ArchiveExportReader: %s", err.Error())
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
go func() {
|
||||
params := storage.NewExportArchiveParamsWithContext(op).
|
||||
WithStore(store).
|
||||
WithAncestorStore(&ancestorStore).
|
||||
WithDeviceID(deviceID).
|
||||
WithAncestor(&ancestor).
|
||||
WithData(data)
|
||||
|
||||
// Encode the filter spec
|
||||
encodedFilter := ""
|
||||
if valueBytes, merr := json.Marshal(filterSpec); merr == nil {
|
||||
encodedFilter = base64.StdEncoding.EncodeToString(valueBytes)
|
||||
params = params.WithFilterSpec(&encodedFilter)
|
||||
op.Infof(" encodedFilter = %s", encodedFilter)
|
||||
}
|
||||
|
||||
_, err = a.client.Storage.ExportArchive(params, pipeWriter)
|
||||
if err != nil {
|
||||
op.Errorf("Error from ExportArchive: %s", err.Error())
|
||||
switch err := err.(type) {
|
||||
case *storage.ExportArchiveInternalServerError:
|
||||
plErr := errors.InternalServerError(fmt.Sprintf("Server error from archive reader for device %s", deviceID))
|
||||
op.Errorf(plErr.Error())
|
||||
pipeWriter.CloseWithError(plErr)
|
||||
case *storage.ExportArchiveLocked:
|
||||
plErr := errors.ResourceLockedError(fmt.Sprintf("Resource locked for device %s", deviceID))
|
||||
op.Errorf(plErr.Error())
|
||||
pipeWriter.CloseWithError(plErr)
|
||||
case *storage.ExportArchiveUnprocessableEntity:
|
||||
plErr := errors.InternalServerError("failed to process given path")
|
||||
op.Errorf(plErr.Error())
|
||||
pipeWriter.CloseWithError(plErr)
|
||||
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(), SwaggerSubstringEOF) {
|
||||
op.Debugf("swagger error %s", err.Error())
|
||||
pipeWriter.Close()
|
||||
} else {
|
||||
pipeWriter.CloseWithError(err)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
pipeWriter.Close()
|
||||
}
|
||||
}()
|
||||
|
||||
return pipeReader, nil
|
||||
}
|
||||
|
||||
// ArchiveImportWriter initializes a write stream for a path. This is usually called
|
||||
// for getting a writer during docker cp TO container.
|
||||
func (a *ArchiveProxy) ArchiveImportWriter(op trace.Operation, store, deviceID string, filterSpec archive.FilterSpec, wg *sync.WaitGroup, errchan chan error) (io.WriteCloser, error) {
|
||||
defer trace.End(trace.Begin(deviceID))
|
||||
|
||||
if a.client == nil {
|
||||
return nil, errors.NillPortlayerClientError("ArchiveProxy")
|
||||
}
|
||||
|
||||
if store == "" || deviceID == "" {
|
||||
return nil, fmt.Errorf("ArchiveImportWriter called with either empty store or deviceID")
|
||||
}
|
||||
|
||||
var err error
|
||||
|
||||
pipeReader, pipeWriter := io.Pipe()
|
||||
|
||||
go func() {
|
||||
// make sure we get out of io.Copy if context is canceled
|
||||
select {
|
||||
case <-op.Done():
|
||||
pipeWriter.Close()
|
||||
}
|
||||
}()
|
||||
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
var plErr error
|
||||
defer func() {
|
||||
op.Debugf("Stream for device %s has returned from PL. Err received is %v ", deviceID, plErr)
|
||||
errchan <- plErr
|
||||
wg.Done()
|
||||
}()
|
||||
|
||||
// encodedFilter and destination are not required (from swagge spec) because
|
||||
// they are allowed to be empty.
|
||||
params := storage.NewImportArchiveParamsWithContext(op).
|
||||
WithStore(store).
|
||||
WithDeviceID(deviceID).
|
||||
WithArchive(pipeReader)
|
||||
|
||||
// Encode the filter spec
|
||||
encodedFilter := ""
|
||||
if valueBytes, merr := json.Marshal(filterSpec); merr == nil {
|
||||
encodedFilter = base64.StdEncoding.EncodeToString(valueBytes)
|
||||
params = params.WithFilterSpec(&encodedFilter)
|
||||
}
|
||||
|
||||
_, err = a.client.Storage.ImportArchive(params)
|
||||
if err != nil {
|
||||
switch err := err.(type) {
|
||||
case *storage.ImportArchiveInternalServerError:
|
||||
plErr = errors.InternalServerError(fmt.Sprintf("error writing files to device %s", deviceID))
|
||||
op.Errorf(plErr.Error())
|
||||
pipeReader.CloseWithError(plErr)
|
||||
case *storage.ImportArchiveLocked:
|
||||
plErr = errors.ResourceLockedError(fmt.Sprintf("resource locked for device %s", deviceID))
|
||||
op.Errorf(plErr.Error())
|
||||
pipeReader.CloseWithError(plErr)
|
||||
case *storage.ImportArchiveNotFound:
|
||||
plErr = errors.ResourceNotFoundError("file or directory")
|
||||
op.Errorf(plErr.Error())
|
||||
pipeReader.CloseWithError(plErr)
|
||||
case *storage.ImportArchiveUnprocessableEntity:
|
||||
plErr = errors.InternalServerError("failed to process given path")
|
||||
op.Errorf(plErr.Error())
|
||||
pipeReader.CloseWithError(plErr)
|
||||
case *storage.ImportArchiveConflict:
|
||||
plErr = errors.InternalServerError("unexpected copy failure may result in truncated copy, please try again")
|
||||
op.Errorf(plErr.Error())
|
||||
pipeReader.CloseWithError(plErr)
|
||||
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
|
||||
plErr = err
|
||||
if strings.Contains(err.Error(), SwaggerSubstringEOF) {
|
||||
op.Error(err)
|
||||
pipeReader.Close()
|
||||
} else {
|
||||
pipeReader.CloseWithError(err)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
pipeReader.Close()
|
||||
}
|
||||
}()
|
||||
|
||||
return pipeWriter, nil
|
||||
}
|
||||
|
||||
// StatPath requests the portlayer to stat the filesystem resource at the
|
||||
// specified path in the container vc.
|
||||
func (a *ArchiveProxy) StatPath(op trace.Operation, store, deviceID string, filterSpec archive.FilterSpec) (*types.ContainerPathStat, error) {
|
||||
defer trace.End(trace.Begin(deviceID))
|
||||
|
||||
if a.client == nil {
|
||||
return nil, errors.NillPortlayerClientError("ArchiveProxy")
|
||||
}
|
||||
|
||||
statPathParams := storage.
|
||||
NewStatPathParamsWithContext(op).
|
||||
WithStore(store).
|
||||
WithDeviceID(deviceID)
|
||||
|
||||
spec, err := archive.EncodeFilterSpec(op, &filterSpec)
|
||||
if err != nil {
|
||||
op.Errorf(err.Error())
|
||||
return nil, errors.InternalServerError(err.Error())
|
||||
}
|
||||
statPathParams = statPathParams.WithFilterSpec(spec)
|
||||
|
||||
statPathOk, err := a.client.Storage.StatPath(statPathParams)
|
||||
if err != nil {
|
||||
op.Errorf(err.Error())
|
||||
return nil, err
|
||||
}
|
||||
|
||||
stat := &types.ContainerPathStat{
|
||||
Name: statPathOk.Name,
|
||||
Mode: os.FileMode(statPathOk.Mode),
|
||||
Size: statPathOk.Size,
|
||||
LinkTarget: statPathOk.LinkTarget,
|
||||
}
|
||||
|
||||
var modTime time.Time
|
||||
if err := modTime.GobDecode([]byte(statPathOk.ModTime)); err != nil {
|
||||
op.Debugf("error getting mod time from statpath: %s", err.Error())
|
||||
} else {
|
||||
stat.Mtime = modTime
|
||||
}
|
||||
|
||||
return stat, nil
|
||||
}
|
||||
34
vendor/github.com/vmware/vic/lib/apiservers/engine/proxy/client.go
generated
vendored
Normal file
34
vendor/github.com/vmware/vic/lib/apiservers/engine/proxy/client.go
generated
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
// Copyright 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 proxy
|
||||
|
||||
import (
|
||||
"github.com/go-openapi/runtime"
|
||||
rc "github.com/go-openapi/runtime/client"
|
||||
|
||||
apiclient "github.com/vmware/vic/lib/apiservers/portlayer/client"
|
||||
)
|
||||
|
||||
func NewPortLayerClient(portLayerAddr string) *apiclient.PortLayer {
|
||||
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)
|
||||
|
||||
return portLayerClient
|
||||
}
|
||||
19
vendor/github.com/vmware/vic/lib/apiservers/engine/proxy/common.go
generated
vendored
Normal file
19
vendor/github.com/vmware/vic/lib/apiservers/engine/proxy/common.go
generated
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
// 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 proxy
|
||||
|
||||
const (
|
||||
SwaggerSubstringEOF = "EOF"
|
||||
)
|
||||
1379
vendor/github.com/vmware/vic/lib/apiservers/engine/proxy/container_proxy.go
generated
vendored
Normal file
1379
vendor/github.com/vmware/vic/lib/apiservers/engine/proxy/container_proxy.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
83
vendor/github.com/vmware/vic/lib/apiservers/engine/proxy/container_proxy_test.go
generated
vendored
Normal file
83
vendor/github.com/vmware/vic/lib/apiservers/engine/proxy/container_proxy_test.go
generated
vendored
Normal file
@@ -0,0 +1,83 @@
|
||||
// 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 proxy
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/docker/go-connections/nat"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestProcessVolumeParams(t *testing.T) {
|
||||
rawTestVolumes := []string{"/blah", "testVolume:/mount", "testVolume:/mount/path:r"}
|
||||
invalidVolume := "/dir:/dir"
|
||||
var processedTestVolumes []volumeFields
|
||||
|
||||
for _, testString := range rawTestVolumes {
|
||||
processedFields, err := processVolumeParam(testString)
|
||||
assert.Nil(t, err)
|
||||
processedTestVolumes = append(processedTestVolumes, processedFields)
|
||||
}
|
||||
assert.Equal(t, 3, len(processedTestVolumes))
|
||||
|
||||
assert.NotEmpty(t, processedTestVolumes[0].ID)
|
||||
assert.Equal(t, "/blah", processedTestVolumes[0].Dest)
|
||||
assert.Equal(t, "rw", processedTestVolumes[0].Flags)
|
||||
|
||||
assert.Equal(t, "testVolume", processedTestVolumes[1].ID)
|
||||
assert.Equal(t, "/mount", processedTestVolumes[1].Dest)
|
||||
assert.Equal(t, "rw", processedTestVolumes[1].Flags)
|
||||
|
||||
assert.Equal(t, "testVolume", processedTestVolumes[2].ID)
|
||||
assert.Equal(t, "/mount/path", processedTestVolumes[2].Dest)
|
||||
assert.Equal(t, "r", processedTestVolumes[2].Flags)
|
||||
|
||||
invalidFields, _ := processVolumeParam(invalidVolume)
|
||||
assert.Equal(t, volumeFields{}, invalidFields)
|
||||
}
|
||||
|
||||
func TestPort(t *testing.T) {
|
||||
portMap, bindingMap, err := nat.ParsePortSpecs([]string{
|
||||
"1236:1235/tcp",
|
||||
"1237:1235/tcp",
|
||||
"2345/udp", "80",
|
||||
"127.0.0.1::8080",
|
||||
"127.0.0.1:5279:8080",
|
||||
})
|
||||
if err != nil {
|
||||
t.Errorf("Failed to parse ports: %s", err.Error())
|
||||
}
|
||||
t.Logf("portMap: %s", portMap)
|
||||
t.Logf("bindingMap: %s", bindingMap)
|
||||
|
||||
for p := range bindingMap {
|
||||
expected := bindingMap[p]
|
||||
for i := range expected {
|
||||
expected[i].HostIP = ""
|
||||
}
|
||||
|
||||
bindings := fromPortbinding(p, bindingMap[p])
|
||||
t.Logf("binding: %s", bindings)
|
||||
_, outMap, err := nat.ParsePortSpecs(bindings)
|
||||
if err != nil {
|
||||
t.Errorf("Failed to parse back string bindings: %s", err)
|
||||
}
|
||||
for op := range outMap {
|
||||
assert.Equal(t, outMap[op], bindingMap[op])
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
626
vendor/github.com/vmware/vic/lib/apiservers/engine/proxy/storage_proxy.go
generated
vendored
Normal file
626
vendor/github.com/vmware/vic/lib/apiservers/engine/proxy/storage_proxy.go
generated
vendored
Normal file
@@ -0,0 +1,626 @@
|
||||
// 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 proxy
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/google/uuid"
|
||||
|
||||
derr "github.com/docker/docker/api/errors"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/go-units"
|
||||
|
||||
viccontainer "github.com/vmware/vic/lib/apiservers/engine/backends/container"
|
||||
"github.com/vmware/vic/lib/apiservers/engine/errors"
|
||||
"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/storage"
|
||||
"github.com/vmware/vic/lib/apiservers/portlayer/models"
|
||||
"github.com/vmware/vic/lib/constants"
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
)
|
||||
|
||||
type VicStorageProxy interface {
|
||||
Create(ctx context.Context, name, driverName string, volumeData, labels map[string]string) (*types.Volume, error)
|
||||
VolumeList(ctx context.Context, filter string) ([]*models.VolumeResponse, error)
|
||||
VolumeInfo(ctx context.Context, name string) (*models.VolumeResponse, error)
|
||||
Remove(ctx context.Context, name string) error
|
||||
|
||||
AddVolumesToContainer(ctx context.Context, handle string, config types.ContainerCreateConfig) (string, error)
|
||||
}
|
||||
|
||||
type StorageProxy struct {
|
||||
client *client.PortLayer
|
||||
}
|
||||
|
||||
type volumeFields struct {
|
||||
ID string
|
||||
Dest string
|
||||
Flags string
|
||||
}
|
||||
|
||||
type VolumeMetadata struct {
|
||||
Driver string
|
||||
DriverOpts map[string]string
|
||||
Name string
|
||||
Labels map[string]string
|
||||
AttachHistory []string
|
||||
Image string
|
||||
}
|
||||
|
||||
const (
|
||||
DriverArgFlagKey = "flags"
|
||||
DriverArgContainerKey = "container"
|
||||
DriverArgImageKey = "image"
|
||||
|
||||
OptsVolumeStoreKey string = "volumestore"
|
||||
OptsCapacityKey string = "capacity"
|
||||
DockerMetadataModelKey string = "DockerMetaData"
|
||||
)
|
||||
|
||||
// define a set (whitelist) of valid driver opts keys for command line argument validation
|
||||
var validDriverOptsKeys = map[string]struct{}{
|
||||
OptsVolumeStoreKey: {},
|
||||
OptsCapacityKey: {},
|
||||
DriverArgFlagKey: {},
|
||||
DriverArgContainerKey: {},
|
||||
DriverArgImageKey: {},
|
||||
}
|
||||
|
||||
// Volume drivers currently supported. "local" is the default driver supplied by the client
|
||||
// and is equivalent to "vsphere" for our implementation.
|
||||
var SupportedVolDrivers = map[string]struct{}{
|
||||
"vsphere": {},
|
||||
"local": {},
|
||||
}
|
||||
|
||||
//Validation pattern for Volume Names
|
||||
var volumeNameRegex = regexp.MustCompile("^[a-zA-Z0-9][a-zA-Z0-9_.-]*$")
|
||||
|
||||
func NewStorageProxy(client *client.PortLayer) VicStorageProxy {
|
||||
if client == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &StorageProxy{client: client}
|
||||
}
|
||||
|
||||
func (s *StorageProxy) Create(ctx context.Context, name, driverName string, volumeData, labels map[string]string) (*types.Volume, error) {
|
||||
defer trace.End(trace.Begin(""))
|
||||
|
||||
if s.client == nil {
|
||||
return nil, errors.NillPortlayerClientError("StorageProxy")
|
||||
}
|
||||
|
||||
result, err := s.volumeCreate(ctx, name, driverName, volumeData, labels)
|
||||
if err != nil {
|
||||
switch err := err.(type) {
|
||||
case *storage.CreateVolumeConflict:
|
||||
return result, errors.VolumeInternalServerError(fmt.Errorf("A volume named %s already exists. Choose a different volume name.", name))
|
||||
case *storage.CreateVolumeNotFound:
|
||||
return result, errors.VolumeInternalServerError(fmt.Errorf("No volume store named (%s) exists", volumeStore(volumeData)))
|
||||
case *storage.CreateVolumeInternalServerError:
|
||||
// FIXME: right now this does not return an error model...
|
||||
return result, errors.VolumeInternalServerError(fmt.Errorf("%s", err.Error()))
|
||||
case *storage.CreateVolumeDefault:
|
||||
return result, errors.VolumeInternalServerError(fmt.Errorf("%s", err.Payload.Message))
|
||||
default:
|
||||
return result, errors.VolumeInternalServerError(fmt.Errorf("%s", err))
|
||||
}
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// volumeCreate issues a CreateVolume request to the portlayer
|
||||
func (s *StorageProxy) volumeCreate(ctx context.Context, name, driverName string, volumeData, labels map[string]string) (*types.Volume, error) {
|
||||
defer trace.End(trace.Begin(""))
|
||||
result := &types.Volume{}
|
||||
|
||||
if s.client == nil {
|
||||
return nil, errors.NillPortlayerClientError("StorageProxy")
|
||||
}
|
||||
|
||||
if name == "" {
|
||||
name = uuid.New().String()
|
||||
}
|
||||
|
||||
// TODO: support having another driver besides vsphere.
|
||||
// assign the values of the model to be passed to the portlayer handler
|
||||
req, varErr := newVolumeCreateReq(name, driverName, volumeData, labels)
|
||||
if varErr != nil {
|
||||
return result, varErr
|
||||
}
|
||||
log.Infof("Finalized model for volume create request to portlayer: %#v", req)
|
||||
|
||||
res, err := s.client.Storage.CreateVolume(storage.NewCreateVolumeParamsWithContext(ctx).WithVolumeRequest(req))
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
|
||||
return NewVolumeModel(res.Payload, labels), nil
|
||||
}
|
||||
|
||||
func (s *StorageProxy) VolumeList(ctx context.Context, filter string) ([]*models.VolumeResponse, error) {
|
||||
defer trace.End(trace.Begin(""))
|
||||
|
||||
if s.client == nil {
|
||||
return nil, errors.NillPortlayerClientError("StorageProxy")
|
||||
}
|
||||
|
||||
res, err := s.client.Storage.ListVolumes(storage.NewListVolumesParamsWithContext(ctx).WithFilterString(&filter))
|
||||
if err != nil {
|
||||
switch err := err.(type) {
|
||||
case *storage.ListVolumesInternalServerError:
|
||||
return nil, errors.VolumeInternalServerError(fmt.Errorf("error from portlayer server: %s", err.Payload.Message))
|
||||
case *storage.ListVolumesDefault:
|
||||
return nil, errors.VolumeInternalServerError(fmt.Errorf("error from portlayer server: %s", err.Payload.Message))
|
||||
default:
|
||||
return nil, errors.VolumeInternalServerError(fmt.Errorf("error from portlayer server: %s", err.Error()))
|
||||
}
|
||||
}
|
||||
|
||||
return res.Payload, nil
|
||||
}
|
||||
|
||||
func (s *StorageProxy) VolumeInfo(ctx context.Context, name string) (*models.VolumeResponse, error) {
|
||||
defer trace.End(trace.Begin(name))
|
||||
|
||||
if name == "" {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if s.client == nil {
|
||||
return nil, errors.NillPortlayerClientError("StorageProxy")
|
||||
}
|
||||
|
||||
param := storage.NewGetVolumeParamsWithContext(ctx).WithName(name)
|
||||
res, err := s.client.Storage.GetVolume(param)
|
||||
if err != nil {
|
||||
switch err := err.(type) {
|
||||
case *storage.GetVolumeNotFound:
|
||||
return nil, errors.VolumeNotFoundError(name)
|
||||
default:
|
||||
return nil, errors.VolumeInternalServerError(fmt.Errorf("error from portlayer server: %s", err.Error()))
|
||||
}
|
||||
}
|
||||
|
||||
return res.Payload, nil
|
||||
}
|
||||
|
||||
func (s *StorageProxy) Remove(ctx context.Context, name string) error {
|
||||
defer trace.End(trace.Begin(name))
|
||||
|
||||
if s.client == nil {
|
||||
return errors.NillPortlayerClientError("StorageProxy")
|
||||
}
|
||||
|
||||
_, err := s.client.Storage.RemoveVolume(storage.NewRemoveVolumeParamsWithContext(ctx).WithName(name))
|
||||
if err != nil {
|
||||
switch err := err.(type) {
|
||||
case *storage.RemoveVolumeNotFound:
|
||||
return derr.NewRequestNotFoundError(fmt.Errorf("Get %s: no such volume", name))
|
||||
case *storage.RemoveVolumeConflict:
|
||||
return derr.NewRequestConflictError(fmt.Errorf(err.Payload.Message))
|
||||
case *storage.RemoveVolumeInternalServerError:
|
||||
return errors.VolumeInternalServerError(fmt.Errorf("Server error from portlayer: %s", err.Payload.Message))
|
||||
default:
|
||||
return errors.VolumeInternalServerError(fmt.Errorf("Server error from portlayer: %s", err))
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddVolumesToContainer adds volumes to a container, referenced by handle.
|
||||
// If an error is returned, the returned handle should not be used.
|
||||
//
|
||||
// returns:
|
||||
// modified handle
|
||||
func (s *StorageProxy) AddVolumesToContainer(ctx context.Context, handle string, config types.ContainerCreateConfig) (string, error) {
|
||||
defer trace.End(trace.Begin(handle))
|
||||
|
||||
if s.client == nil {
|
||||
return "", errors.NillPortlayerClientError("StorageProxy")
|
||||
}
|
||||
|
||||
// Volume Attachment Section
|
||||
log.Debugf("ContainerProxy.AddVolumesToContainer - VolumeSection")
|
||||
log.Debugf("Raw volume arguments: binds: %#v, volumes: %#v", config.HostConfig.Binds, config.Config.Volumes)
|
||||
|
||||
// Collect all volume mappings. In a docker create/run, they
|
||||
// can be anonymous (-v /dir) or specific (-v vol-name:/dir).
|
||||
// anonymous volumes can also come from Image Metadata
|
||||
|
||||
rawAnonVolumes := make([]string, 0, len(config.Config.Volumes))
|
||||
for k := range config.Config.Volumes {
|
||||
rawAnonVolumes = append(rawAnonVolumes, k)
|
||||
}
|
||||
|
||||
volList, err := finalizeVolumeList(config.HostConfig.Binds, rawAnonVolumes)
|
||||
if err != nil {
|
||||
return handle, errors.BadRequestError(err.Error())
|
||||
}
|
||||
log.Infof("Finalized volume list: %#v", volList)
|
||||
|
||||
if len(config.Config.Volumes) > 0 {
|
||||
// override anonymous volume list with generated volume id
|
||||
for _, vol := range volList {
|
||||
if _, ok := config.Config.Volumes[vol.Dest]; ok {
|
||||
delete(config.Config.Volumes, vol.Dest)
|
||||
mount := getMountString(vol.ID, vol.Dest, vol.Flags)
|
||||
config.Config.Volumes[mount] = struct{}{}
|
||||
log.Debugf("Replace anonymous volume config %s with %s", vol.Dest, mount)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create and join volumes.
|
||||
for _, fields := range volList {
|
||||
// We only set these here for volumes made on a docker create
|
||||
volumeData := make(map[string]string)
|
||||
volumeData[DriverArgFlagKey] = fields.Flags
|
||||
volumeData[DriverArgContainerKey] = config.Name
|
||||
volumeData[DriverArgImageKey] = config.Config.Image
|
||||
|
||||
// NOTE: calling volumeCreate regardless of whether the volume is already
|
||||
// present can be avoided by adding an extra optional param to VolumeJoin,
|
||||
// which would then call volumeCreate if the volume does not exist.
|
||||
_, err := s.volumeCreate(ctx, fields.ID, "vsphere", volumeData, nil)
|
||||
if err != nil {
|
||||
switch err := err.(type) {
|
||||
case *storage.CreateVolumeConflict:
|
||||
// Implicitly ignore the error where a volume with the same name
|
||||
// already exists. We can just join the said volume to the container.
|
||||
log.Infof("a volume with the name %s already exists", fields.ID)
|
||||
case *storage.CreateVolumeNotFound:
|
||||
return handle, errors.VolumeCreateNotFoundError(volumeStore(volumeData))
|
||||
default:
|
||||
return handle, errors.InternalServerError(err.Error())
|
||||
}
|
||||
} else {
|
||||
log.Infof("volumeCreate succeeded. Volume mount section ID: %s", fields.ID)
|
||||
}
|
||||
|
||||
flags := make(map[string]string)
|
||||
//NOTE: for now we are passing the flags directly through. This is NOT SAFE and only a stop gap.
|
||||
flags[constants.Mode] = fields.Flags
|
||||
joinParams := storage.NewVolumeJoinParamsWithContext(ctx).WithJoinArgs(&models.VolumeJoinConfig{
|
||||
Flags: flags,
|
||||
Handle: handle,
|
||||
MountPath: fields.Dest,
|
||||
}).WithName(fields.ID)
|
||||
|
||||
res, err := s.client.Storage.VolumeJoin(joinParams)
|
||||
if err != nil {
|
||||
switch err := err.(type) {
|
||||
case *storage.VolumeJoinInternalServerError:
|
||||
return handle, errors.InternalServerError(err.Payload.Message)
|
||||
case *storage.VolumeJoinDefault:
|
||||
return handle, errors.InternalServerError(err.Payload.Message)
|
||||
case *storage.VolumeJoinNotFound:
|
||||
return handle, errors.VolumeJoinNotFoundError(err.Payload.Message)
|
||||
default:
|
||||
return handle, errors.InternalServerError(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
handle = res.Payload
|
||||
}
|
||||
|
||||
return handle, nil
|
||||
}
|
||||
|
||||
// allContainers obtains all containers from the portlayer, akin to `docker ps -a`.
|
||||
func (s *StorageProxy) allContainers(ctx context.Context) ([]*models.ContainerInfo, error) {
|
||||
if s.client == nil {
|
||||
return nil, errors.NillPortlayerClientError("StorageProxy")
|
||||
}
|
||||
|
||||
all := true
|
||||
cons, err := s.client.Containers.GetContainerList(containers.NewGetContainerListParamsWithContext(ctx).WithAll(&all))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return cons.Payload, nil
|
||||
}
|
||||
|
||||
// fetchJoinedVolumes obtains all containers from the portlayer and returns a map with all
|
||||
// volumes that are joined to at least one container.
|
||||
func (s *StorageProxy) fetchJoinedVolumes(ctx context.Context) (map[string]struct{}, error) {
|
||||
conts, err := s.allContainers(ctx)
|
||||
if err != nil {
|
||||
return nil, errors.VolumeInternalServerError(err)
|
||||
}
|
||||
|
||||
joinedVolumes := make(map[string]struct{})
|
||||
var v struct{}
|
||||
for i := range conts {
|
||||
for _, vol := range conts[i].VolumeConfig {
|
||||
joinedVolumes[vol.Name] = v
|
||||
}
|
||||
}
|
||||
|
||||
return joinedVolumes, nil
|
||||
}
|
||||
|
||||
//------------------------------------
|
||||
// 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,
|
||||
}
|
||||
}
|
||||
|
||||
// volumeStore returns the value of the optional volume store param specified in the CLI.
|
||||
func volumeStore(args map[string]string) string {
|
||||
storeName, ok := args[OptsVolumeStoreKey]
|
||||
if !ok {
|
||||
return "default"
|
||||
}
|
||||
return storeName
|
||||
}
|
||||
|
||||
// getMountString returns a colon-delimited string containing a volume's name/ID, mount
|
||||
// point and flags.
|
||||
func getMountString(mounts ...string) string {
|
||||
return strings.Join(mounts, ":")
|
||||
}
|
||||
|
||||
func createVolumeMetadata(req *models.VolumeRequest, driverargs, labels map[string]string) (string, error) {
|
||||
metadata := VolumeMetadata{
|
||||
Driver: req.Driver,
|
||||
DriverOpts: req.DriverArgs,
|
||||
Name: req.Name,
|
||||
Labels: labels,
|
||||
AttachHistory: []string{driverargs[DriverArgContainerKey]},
|
||||
Image: driverargs[DriverArgImageKey],
|
||||
}
|
||||
result, err := json.Marshal(metadata)
|
||||
return string(result), err
|
||||
}
|
||||
|
||||
// RemoveAnonContainerVols removes anonymous volumes joined to a container. It is invoked
|
||||
// once the said container has been removed. It fetches a list of volumes that are joined
|
||||
// to at least one other container, and calls the portlayer to remove this container's
|
||||
// anonymous volumes if they are dangling. Errors, if any, are only logged.
|
||||
func RemoveAnonContainerVols(ctx context.Context, pl *client.PortLayer, cID string, vc *viccontainer.VicContainer) {
|
||||
// NOTE: these strings come in the form of <volume id>:<destination>:<volume options>
|
||||
volumes := vc.Config.Volumes
|
||||
// NOTE: these strings come in the form of <volume id>:<destination path>
|
||||
namedVolumes := vc.HostConfig.Binds
|
||||
|
||||
// assemble a mask of volume paths before processing binds. MUST be paths, as we want to move to honoring the proper metadata in the "volumes" section in the future.
|
||||
namedMaskList := make(map[string]struct{}, 0)
|
||||
for _, entry := range namedVolumes {
|
||||
fields := strings.SplitN(entry, ":", 2)
|
||||
if len(fields) != 2 {
|
||||
log.Errorf("Invalid entry in the HostConfig.Binds metadata section for container %s: %s", cID, entry)
|
||||
continue
|
||||
}
|
||||
destPath := fields[1]
|
||||
namedMaskList[destPath] = struct{}{}
|
||||
}
|
||||
|
||||
proxy := StorageProxy{client: pl}
|
||||
joinedVols, err := proxy.fetchJoinedVolumes(ctx)
|
||||
if err != nil {
|
||||
log.Errorf("Unable to obtain joined volumes from portlayer, skipping removal of anonymous volumes for %s: %s", cID, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
for vol := range volumes {
|
||||
// Extract the volume ID from the full mount path, which is of form "id:mountpath:flags" - see getMountString().
|
||||
volFields := strings.SplitN(vol, ":", 3)
|
||||
|
||||
// NOTE(mavery): this check will start to fail when we fix our metadata correctness issues
|
||||
if len(volFields) != 3 {
|
||||
log.Debugf("Invalid entry in the volumes metadata section for container %s: %s", cID, vol)
|
||||
continue
|
||||
}
|
||||
volName := volFields[0]
|
||||
volPath := volFields[1]
|
||||
|
||||
_, isNamed := namedMaskList[volPath]
|
||||
_, joined := joinedVols[volName]
|
||||
if !joined && !isNamed {
|
||||
_, err := pl.Storage.RemoveVolume(storage.NewRemoveVolumeParamsWithContext(ctx).WithName(volName))
|
||||
if err != nil {
|
||||
log.Debugf("Unable to remove anonymous volume %s in container %s: %s", volName, cID, err.Error())
|
||||
continue
|
||||
}
|
||||
log.Debugf("Successfully removed anonymous volume %s during remove operation against container(%s)", volName, cID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// processVolumeParam is used to turn any call from docker create -v <stuff> into a volumeFields object.
|
||||
// The -v has 3 forms. -v <anonymous mount path>, -v <Volume Name>:<Destination Mount Path> and
|
||||
// -v <Volume Name>:<Destination Mount Path>:<mount flags>
|
||||
func processVolumeParam(volString string) (volumeFields, error) {
|
||||
volumeStrings := strings.Split(volString, ":")
|
||||
fields := volumeFields{}
|
||||
|
||||
// Error out if the intended volume is a directory on the client filesystem.
|
||||
numVolParams := len(volumeStrings)
|
||||
if numVolParams > 1 && strings.HasPrefix(volumeStrings[0], "/") {
|
||||
return volumeFields{}, errors.InvalidVolumeError{}
|
||||
}
|
||||
|
||||
// This switch determines which type of -v was invoked.
|
||||
switch numVolParams {
|
||||
case 1:
|
||||
VolumeID, err := uuid.NewUUID()
|
||||
if err != nil {
|
||||
return fields, err
|
||||
}
|
||||
fields.ID = VolumeID.String()
|
||||
fields.Dest = volumeStrings[0]
|
||||
fields.Flags = "rw"
|
||||
case 2:
|
||||
fields.ID = volumeStrings[0]
|
||||
fields.Dest = volumeStrings[1]
|
||||
fields.Flags = "rw"
|
||||
case 3:
|
||||
fields.ID = volumeStrings[0]
|
||||
fields.Dest = volumeStrings[1]
|
||||
fields.Flags = volumeStrings[2]
|
||||
default:
|
||||
// NOTE: the docker cli should cover this case. This is here for posterity.
|
||||
return volumeFields{}, errors.InvalidBindError{Volume: volString}
|
||||
}
|
||||
return fields, nil
|
||||
}
|
||||
|
||||
// processVolumeFields parses fields for volume mappings specified in a create/run -v.
|
||||
// It returns a map of unique mountable volumes. This means that it removes dupes favoring
|
||||
// specified volumes over anonymous volumes.
|
||||
func processVolumeFields(volumes []string) (map[string]volumeFields, error) {
|
||||
volumeFields := make(map[string]volumeFields)
|
||||
|
||||
for _, v := range volumes {
|
||||
fields, err := processVolumeParam(v)
|
||||
log.Infof("Processed volume arguments: %#v", fields)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
volumeFields[fields.Dest] = fields
|
||||
}
|
||||
return volumeFields, nil
|
||||
}
|
||||
|
||||
func finalizeVolumeList(specifiedVolumes, anonymousVolumes []string) ([]volumeFields, error) {
|
||||
log.Infof("Specified Volumes : %#v", specifiedVolumes)
|
||||
processedVolumes, err := processVolumeFields(specifiedVolumes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.Infof("anonymous Volumes : %#v", anonymousVolumes)
|
||||
processedAnonVolumes, err := processVolumeFields(anonymousVolumes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
//combine all volumes, specified volumes are taken over anonymous volumes
|
||||
for k, v := range processedVolumes {
|
||||
processedAnonVolumes[k] = v
|
||||
}
|
||||
|
||||
finalizedVolumes := make([]volumeFields, 0, len(processedAnonVolumes))
|
||||
for _, v := range processedAnonVolumes {
|
||||
finalizedVolumes = append(finalizedVolumes, v)
|
||||
}
|
||||
return finalizedVolumes, nil
|
||||
}
|
||||
|
||||
func newVolumeCreateReq(name, driverName string, volumeData, labels map[string]string) (*models.VolumeRequest, error) {
|
||||
if _, ok := SupportedVolDrivers[driverName]; !ok {
|
||||
return nil, fmt.Errorf("error looking up volume plugin %s: plugin not found", driverName)
|
||||
}
|
||||
|
||||
if !volumeNameRegex.Match([]byte(name)) && name != "" {
|
||||
return nil, fmt.Errorf("volume name %q includes invalid characters, only \"[a-zA-Z0-9][a-zA-Z0-9_.-]\" are allowed", name)
|
||||
}
|
||||
|
||||
req := &models.VolumeRequest{
|
||||
Driver: driverName,
|
||||
DriverArgs: volumeData,
|
||||
Name: name,
|
||||
Metadata: make(map[string]string),
|
||||
}
|
||||
|
||||
metadata, err := createVolumeMetadata(req, volumeData, labels)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req.Metadata[DockerMetadataModelKey] = metadata
|
||||
|
||||
if err := validateDriverArgs(volumeData, req); err != nil {
|
||||
return nil, fmt.Errorf("bad driver value - %s", err)
|
||||
}
|
||||
|
||||
return req, nil
|
||||
}
|
||||
|
||||
func validateDriverArgs(args map[string]string, req *models.VolumeRequest) error {
|
||||
if err := normalizeDriverArgs(args); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// volumestore name validation
|
||||
req.Store = volumeStore(args)
|
||||
|
||||
// capacity validation
|
||||
capstr, ok := args[OptsCapacityKey]
|
||||
if !ok {
|
||||
req.Capacity = -1
|
||||
return nil
|
||||
}
|
||||
|
||||
//check if it is just a numerical value
|
||||
capacity, err := strconv.ParseInt(capstr, 10, 64)
|
||||
if err == nil {
|
||||
//input has no units in this case.
|
||||
if capacity < 1 {
|
||||
return fmt.Errorf("Invalid size: %s", capstr)
|
||||
}
|
||||
req.Capacity = capacity
|
||||
return nil
|
||||
}
|
||||
|
||||
capacity, err = units.FromHumanSize(capstr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if capacity < 1 {
|
||||
return fmt.Errorf("Capacity value too large: %s", capstr)
|
||||
}
|
||||
|
||||
req.Capacity = int64(capacity) / int64(units.MB)
|
||||
return nil
|
||||
}
|
||||
|
||||
func normalizeDriverArgs(args map[string]string) error {
|
||||
// normalize keys to lowercase & validate them
|
||||
for k, val := range args {
|
||||
lowercase := strings.ToLower(k)
|
||||
|
||||
if _, ok := validDriverOptsKeys[lowercase]; !ok {
|
||||
return fmt.Errorf("%s is not a supported option", k)
|
||||
}
|
||||
|
||||
if strings.Compare(lowercase, k) != 0 {
|
||||
delete(args, k)
|
||||
args[lowercase] = val
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
129
vendor/github.com/vmware/vic/lib/apiservers/engine/proxy/storage_proxy_test.go
generated
vendored
Normal file
129
vendor/github.com/vmware/vic/lib/apiservers/engine/proxy/storage_proxy_test.go
generated
vendored
Normal file
@@ -0,0 +1,129 @@
|
||||
// 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 proxy
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/vmware/vic/lib/apiservers/portlayer/models"
|
||||
)
|
||||
|
||||
func TestFillDockerVolume(t *testing.T) {
|
||||
testResponse := &models.VolumeResponse{
|
||||
Driver: "vsphere",
|
||||
Name: "Test Volume",
|
||||
Label: "Test Label",
|
||||
}
|
||||
testLabels := make(map[string]string)
|
||||
testLabels["TestMeta"] = "custom info about my volume"
|
||||
|
||||
dockerVolume := NewVolumeModel(testResponse, testLabels)
|
||||
|
||||
assert.Equal(t, "vsphere", dockerVolume.Driver)
|
||||
assert.Equal(t, "Test Volume", dockerVolume.Name)
|
||||
assert.Equal(t, "Test Label", dockerVolume.Mountpoint)
|
||||
assert.Equal(t, "custom info about my volume", dockerVolume.Labels["TestMeta"])
|
||||
}
|
||||
|
||||
func TestTranslatVolumeRequestModel(t *testing.T) {
|
||||
testLabels := make(map[string]string)
|
||||
testLabels["TestMeta"] = "custom info about my volume"
|
||||
|
||||
testDriverArgs := make(map[string]string)
|
||||
testDriverArgs["testarg"] = "important driver stuff"
|
||||
testDriverArgs[OptsVolumeStoreKey] = "testStore"
|
||||
testDriverArgs[OptsCapacityKey] = "12MB"
|
||||
|
||||
testRequest, err := newVolumeCreateReq("testName", "vsphere", testDriverArgs, testLabels)
|
||||
if !assert.Error(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
delete(testDriverArgs, "testarg")
|
||||
testRequest, err = newVolumeCreateReq("testName", "vsphere", testDriverArgs, testLabels)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
assert.Equal(t, "testName", testRequest.Name)
|
||||
assert.Equal(t, "", testRequest.DriverArgs["testarg"]) // unsupported keys should just be empty
|
||||
assert.Equal(t, "testStore", testRequest.Store)
|
||||
assert.Equal(t, "vsphere", testRequest.Driver)
|
||||
assert.Equal(t, int64(12), testRequest.Capacity)
|
||||
|
||||
testMetaDatabuf, err := createVolumeMetadata(testRequest, testDriverArgs, testLabels)
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
assert.Equal(t, testMetaDatabuf, testRequest.Metadata[DockerMetadataModelKey])
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
|
||||
func TestValidateDriverArgs(t *testing.T) {
|
||||
testMap := make(map[string]string)
|
||||
testStore := "Mystore"
|
||||
testCap := "12MB"
|
||||
testBadCap := "This is not valid!"
|
||||
testModel := models.VolumeRequest{
|
||||
Driver: "vsphere",
|
||||
DriverArgs: testMap,
|
||||
Name: "testModel",
|
||||
}
|
||||
|
||||
err := validateDriverArgs(testMap, &testModel)
|
||||
if !assert.Equal(t, "default", testModel.Store) || !assert.Equal(t, int64(-1), testModel.Capacity) || !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
testMap[OptsVolumeStoreKey] = testStore
|
||||
testMap[OptsCapacityKey] = testCap
|
||||
err = validateDriverArgs(testMap, &testModel)
|
||||
if !assert.Equal(t, testStore, testModel.Store) || !assert.Equal(t, int64(12), testModel.Capacity) || !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
//This is a negative test case. We want an error
|
||||
testMap[OptsCapacityKey] = testBadCap
|
||||
err = validateDriverArgs(testMap, &testModel)
|
||||
if !assert.Equal(t, testStore, testModel.Store) || !assert.Equal(t, int64(12), testModel.Capacity) || !assert.Error(t, err) {
|
||||
return
|
||||
}
|
||||
|
||||
testMap[OptsCapacityKey] = testCap
|
||||
delete(testMap, OptsVolumeStoreKey)
|
||||
err = validateDriverArgs(testMap, &testModel)
|
||||
if !assert.Equal(t, "default", testModel.Store) || !assert.Equal(t, int64(12), testModel.Capacity) || !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func TestNormalizeDriverArgs(t *testing.T) {
|
||||
testOptMap := make(map[string]string)
|
||||
testOptMap["VOLUMESTORE"] = "foo"
|
||||
testOptMap["CAPACITY"] = "bar"
|
||||
|
||||
normalizeDriverArgs(testOptMap)
|
||||
|
||||
assert.Equal(t, testOptMap["volumestore"], "foo")
|
||||
assert.Equal(t, testOptMap["capacity"], "bar")
|
||||
|
||||
testOptMap["bogus"] = "bogus"
|
||||
|
||||
err := normalizeDriverArgs(testOptMap)
|
||||
assert.Error(t, err, "expected: bogus is not a supported option")
|
||||
}
|
||||
495
vendor/github.com/vmware/vic/lib/apiservers/engine/proxy/stream_proxy.go
generated
vendored
Normal file
495
vendor/github.com/vmware/vic/lib/apiservers/engine/proxy/stream_proxy.go
generated
vendored
Normal file
@@ -0,0 +1,495 @@
|
||||
// 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 proxy
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/go-openapi/strfmt"
|
||||
|
||||
"github.com/docker/docker/api/types/backend"
|
||||
"github.com/docker/docker/pkg/term"
|
||||
|
||||
"github.com/vmware/vic/lib/apiservers/engine/backends/convert"
|
||||
"github.com/vmware/vic/lib/apiservers/engine/errors"
|
||||
"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/interaction"
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
)
|
||||
|
||||
type VicStreamProxy interface {
|
||||
AttachStreams(ctx context.Context, ac *AttachConfig, stdin io.ReadCloser, stdout, stderr io.Writer) error
|
||||
StreamContainerLogs(ctx context.Context, name string, out io.Writer, started chan struct{}, showTimestamps bool, followLogs bool, since int64, tailLines int64) error
|
||||
StreamContainerStats(ctx context.Context, config *convert.ContainerStatsConfig) error
|
||||
}
|
||||
|
||||
type StreamProxy struct {
|
||||
client *client.PortLayer
|
||||
}
|
||||
|
||||
const (
|
||||
attachConnectTimeout time.Duration = 15 * time.Second //timeout for the connection
|
||||
attachAttemptTimeout time.Duration = 60 * time.Second //timeout before we ditch an attach attempt
|
||||
attachPLAttemptDiff time.Duration = 10 * time.Second
|
||||
attachStdinInitString = "v1c#>"
|
||||
archiveStreamBufSize = 64 * 1024
|
||||
)
|
||||
|
||||
// AttachConfig wraps backend.ContainerAttachConfig and adds other required fields
|
||||
// Similar to https://github.com/docker/docker/blob/master/container/stream/attach.go
|
||||
type AttachConfig struct {
|
||||
*backend.ContainerAttachConfig
|
||||
|
||||
// ID of the session
|
||||
ID string
|
||||
// Tells the attach copier that the stream's stdin is a TTY and to look for
|
||||
// escape sequences in stdin to detach from the stream.
|
||||
// When true the escape sequence is not passed to the underlying stream
|
||||
UseTty bool
|
||||
// CloseStdin signals that once done, stdin for the attached stream should be closed
|
||||
// For example, this would close the attached container's stdin.
|
||||
CloseStdin bool
|
||||
}
|
||||
|
||||
func NewStreamProxy(client *client.PortLayer) VicStreamProxy {
|
||||
return &StreamProxy{client: client}
|
||||
}
|
||||
|
||||
// AttachStreams takes the the hijacked connections from the calling client and attaches
|
||||
// them to the 3 streams from the portlayer's rest server.
|
||||
// stdin, stdout, stderr are the hijacked connection
|
||||
func (s *StreamProxy) AttachStreams(ctx context.Context, ac *AttachConfig, stdin io.ReadCloser, stdout, stderr io.Writer) error {
|
||||
// Cancel will close the child connections.
|
||||
var wg, outWg sync.WaitGroup
|
||||
|
||||
if s.client == nil {
|
||||
return errors.NillPortlayerClientError("StreamProxy")
|
||||
}
|
||||
|
||||
errChan := make(chan error, 3)
|
||||
|
||||
var keys []byte
|
||||
var err error
|
||||
if ac.DetachKeys != "" {
|
||||
keys, err = term.ToBytes(ac.DetachKeys)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Invalid escape keys (%s) provided", ac.DetachKeys)
|
||||
}
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
|
||||
if ac.UseStdin {
|
||||
wg.Add(1)
|
||||
}
|
||||
|
||||
if ac.UseStdout {
|
||||
wg.Add(1)
|
||||
outWg.Add(1)
|
||||
}
|
||||
|
||||
if ac.UseStderr {
|
||||
wg.Add(1)
|
||||
outWg.Add(1)
|
||||
}
|
||||
|
||||
// cancel stdin if all output streams are complete
|
||||
go func() {
|
||||
outWg.Wait()
|
||||
cancel()
|
||||
}()
|
||||
|
||||
EOForCanceled := func(err error) bool {
|
||||
return err != nil && ctx.Err() != context.Canceled && !strings.HasSuffix(err.Error(), SwaggerSubstringEOF)
|
||||
}
|
||||
|
||||
if ac.UseStdin {
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
err := copyStdIn(ctx, s.client, ac, stdin, keys)
|
||||
if err != nil {
|
||||
log.Errorf("container attach: stdin (%s): %s", ac.ID, err)
|
||||
} else {
|
||||
log.Infof("container attach: stdin (%s) done", ac.ID)
|
||||
}
|
||||
|
||||
if !ac.CloseStdin || ac.UseTty {
|
||||
cancel()
|
||||
}
|
||||
|
||||
// Check for EOF or canceled context. We can only detect EOF by checking the error string returned by swagger :/
|
||||
if EOForCanceled(err) {
|
||||
errChan <- err
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
if ac.UseStdout {
|
||||
go func() {
|
||||
defer outWg.Done()
|
||||
defer wg.Done()
|
||||
|
||||
err := copyStdOut(ctx, s.client, ac, stdout, attachAttemptTimeout)
|
||||
if err != nil {
|
||||
log.Errorf("container attach: stdout (%s): %s", ac.ID, err)
|
||||
} else {
|
||||
log.Infof("container attach: stdout (%s) done", ac.ID)
|
||||
}
|
||||
|
||||
// Check for EOF or canceled context. We can only detect EOF by checking the error string returned by swagger :/
|
||||
if EOForCanceled(err) {
|
||||
errChan <- err
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
if ac.UseStderr {
|
||||
go func() {
|
||||
defer outWg.Done()
|
||||
defer wg.Done()
|
||||
|
||||
err := copyStdErr(ctx, s.client, ac, stderr)
|
||||
if err != nil {
|
||||
log.Errorf("container attach: stderr (%s): %s", ac.ID, err)
|
||||
} else {
|
||||
log.Infof("container attach: stderr (%s) done", ac.ID)
|
||||
}
|
||||
|
||||
// Check for EOF or canceled context. We can only detect EOF by checking the error string returned by swagger :/
|
||||
if EOForCanceled(err) {
|
||||
errChan <- err
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// Wait for all stream copy to exit
|
||||
wg.Wait()
|
||||
|
||||
// close the channel so that we don't leak (if there is an error)/or get blocked (if there are no errors)
|
||||
close(errChan)
|
||||
|
||||
log.Infof("cleaned up connections to %s. Checking errors", ac.ID)
|
||||
for err := range errChan {
|
||||
if err != nil {
|
||||
// check if we got DetachError
|
||||
if _, ok := err.(errors.DetachError); ok {
|
||||
log.Infof("Detached from container detected")
|
||||
return err
|
||||
}
|
||||
|
||||
// If we get here, most likely something went wrong with the port layer API server
|
||||
// These errors originate within the go-swagger client itself.
|
||||
// Go-swagger returns untyped errors to us if the error is not one that we define
|
||||
// in the swagger spec. Even EOF. Therefore, we must scan the error string (if there
|
||||
// is an error string in the untyped error) for the term EOF.
|
||||
log.Errorf("container attach error: %s", err)
|
||||
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
log.Infof("No error found. Returning nil...")
|
||||
return nil
|
||||
}
|
||||
|
||||
// StreamContainerLogs reads the log stream from the portlayer rest server and writes
|
||||
// it directly to the io.Writer that is passed in.
|
||||
func (s *StreamProxy) StreamContainerLogs(ctx context.Context, name string, out io.Writer, started chan struct{}, showTimestamps bool, followLogs bool, since int64, tailLines int64) error {
|
||||
defer trace.End(trace.Begin(""))
|
||||
|
||||
if s.client == nil {
|
||||
return errors.NillPortlayerClientError("StreamProxy")
|
||||
}
|
||||
|
||||
close(started)
|
||||
|
||||
params := containers.NewGetContainerLogsParamsWithContext(ctx).
|
||||
WithID(name).
|
||||
WithFollow(&followLogs).
|
||||
WithTimestamp(&showTimestamps).
|
||||
WithSince(&since).
|
||||
WithTaillines(&tailLines)
|
||||
_, err := s.client.Containers.GetContainerLogs(params, out)
|
||||
if err != nil {
|
||||
switch err := err.(type) {
|
||||
case *containers.GetContainerLogsNotFound:
|
||||
return errors.NotFoundError(name)
|
||||
case *containers.GetContainerLogsInternalServerError:
|
||||
return errors.InternalServerError("Server error from the interaction 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(), SwaggerSubstringEOF) {
|
||||
return nil
|
||||
}
|
||||
return errors.InternalServerError(fmt.Sprintf("Unknown error from the interaction port layer: %s", err))
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// StreamContainerStats will provide a stream of container stats written to the provided
|
||||
// io.Writer. Prior to writing to the provided io.Writer there will be a transformation
|
||||
// from the portLayer representation of stats to the docker format
|
||||
func (s *StreamProxy) StreamContainerStats(ctx context.Context, config *convert.ContainerStatsConfig) error {
|
||||
defer trace.End(trace.Begin(config.ContainerID))
|
||||
|
||||
if s.client == nil {
|
||||
return errors.NillPortlayerClientError("StreamProxy")
|
||||
}
|
||||
|
||||
// create a child context that we control
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
|
||||
params := containers.NewGetContainerStatsParamsWithContext(ctx)
|
||||
params.ID = config.ContainerID
|
||||
params.Stream = config.Stream
|
||||
|
||||
config.Ctx = ctx
|
||||
config.Cancel = cancel
|
||||
|
||||
// create our converter
|
||||
containerConverter := convert.NewContainerStats(config)
|
||||
// provide the writer for the portLayer and start listening for metrics
|
||||
writer := containerConverter.Listen()
|
||||
if writer == nil {
|
||||
// problem with the listener
|
||||
return errors.InternalServerError(fmt.Sprintf("unable to gather container(%s) statistics", config.ContainerID))
|
||||
}
|
||||
|
||||
_, err := s.client.Containers.GetContainerStats(params, writer)
|
||||
if err != nil {
|
||||
switch err := err.(type) {
|
||||
case *containers.GetContainerStatsNotFound:
|
||||
return errors.NotFoundError(config.ContainerID)
|
||||
case *containers.GetContainerStatsInternalServerError:
|
||||
return errors.InternalServerError("Server error from the interaction port layer")
|
||||
default:
|
||||
if ctx.Err() == context.Canceled {
|
||||
return nil
|
||||
}
|
||||
//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(), SwaggerSubstringEOF) {
|
||||
return nil
|
||||
}
|
||||
return errors.InternalServerError(fmt.Sprintf("Unknown error from the interaction port layer: %s", err))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
//------------------------------------
|
||||
// ContainerAttach() Utility Functions
|
||||
//------------------------------------
|
||||
|
||||
func copyStdIn(ctx context.Context, pl *client.PortLayer, ac *AttachConfig, stdin io.ReadCloser, keys []byte) error {
|
||||
// Pipe for stdin so we can interject and watch the input streams for detach keys.
|
||||
stdinReader, stdinWriter := io.Pipe()
|
||||
defer stdinReader.Close()
|
||||
|
||||
var detach bool
|
||||
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
// make sure we get out of io.Copy if context is canceled
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
// This will cause the transport to the API client to be shut down, so all output
|
||||
// streams will get closed as well.
|
||||
// See the closer in container_routes.go:postContainersAttach
|
||||
|
||||
// We're closing this here to disrupt the io.Copy below
|
||||
// TODO: seems like we should be providing an io.Copy impl with ctx argument that honors
|
||||
// cancelation with the amount of code dedicated to working around it
|
||||
|
||||
// TODO: I think this still leaves a race between closing of the API client transport and
|
||||
// copying of the output streams, it's just likely the error will be dropped as the transport is
|
||||
// closed when it occurs.
|
||||
// We should move away from needing to close transports to interrupt reads.
|
||||
stdin.Close()
|
||||
case <-done:
|
||||
}
|
||||
}()
|
||||
|
||||
go func() {
|
||||
defer close(done)
|
||||
defer stdinWriter.Close()
|
||||
|
||||
// Copy the stdin from the CLI and write to a pipe. We need to do this so we can
|
||||
// watch the stdin stream for the detach keys.
|
||||
var err error
|
||||
|
||||
// Write some init bytes into the pipe to force Swagger to make the initial
|
||||
// call to the portlayer, prior to any user input in whatever attach client
|
||||
// he/she is using.
|
||||
log.Debugf("copyStdIn writing primer bytes")
|
||||
stdinWriter.Write([]byte(attachStdinInitString))
|
||||
if ac.UseTty {
|
||||
_, err = copyEscapable(stdinWriter, stdin, keys)
|
||||
} else {
|
||||
_, err = io.Copy(stdinWriter, stdin)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
if _, ok := err.(errors.DetachError); ok {
|
||||
log.Infof("stdin detach detected")
|
||||
detach = true
|
||||
} else {
|
||||
log.Errorf("stdin err: %s", err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
id := ac.ID
|
||||
|
||||
// Swagger wants an io.reader so give it the reader pipe. Also, the swagger call
|
||||
// to set the stdin is synchronous so we need to run in a goroutine
|
||||
setStdinParams := interaction.NewContainerSetStdinParamsWithContext(ctx).WithID(id)
|
||||
setStdinParams = setStdinParams.WithRawStream(stdinReader)
|
||||
|
||||
_, err := pl.Interaction.ContainerSetStdin(setStdinParams)
|
||||
<-done
|
||||
|
||||
if ac.CloseStdin && !ac.UseTty {
|
||||
// Close the stdin connection. Mimicing Docker's behavior.
|
||||
log.Errorf("Attach stream has stdinOnce set. Closing the stdin.")
|
||||
params := interaction.NewContainerCloseStdinParamsWithContext(ctx).WithID(id)
|
||||
_, err := pl.Interaction.ContainerCloseStdin(params)
|
||||
if err != nil {
|
||||
log.Errorf("CloseStdin failed with %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
// ignore the portlayer error when it is DetachError as that is what we should return to the caller when we detach
|
||||
if detach {
|
||||
return errors.DetachError{}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func copyStdOut(ctx context.Context, pl *client.PortLayer, ac *AttachConfig, stdout io.Writer, attemptTimeout time.Duration) error {
|
||||
id := ac.ID
|
||||
|
||||
//Calculate how much time to let portlayer attempt
|
||||
plAttemptTimeout := attemptTimeout - attachPLAttemptDiff //assumes personality deadline longer than portlayer's deadline
|
||||
plAttemptDeadline := time.Now().Add(plAttemptTimeout)
|
||||
swaggerDeadline := strfmt.DateTime(plAttemptDeadline)
|
||||
log.Debugf("* stdout portlayer deadline: %s", plAttemptDeadline.Format(time.UnixDate))
|
||||
log.Debugf("* stdout personality deadline: %s", time.Now().Add(attemptTimeout).Format(time.UnixDate))
|
||||
|
||||
log.Debugf("* stdout attach start %s", time.Now().Format(time.UnixDate))
|
||||
getStdoutParams := interaction.NewContainerGetStdoutParamsWithContext(ctx).WithID(id).WithDeadline(&swaggerDeadline)
|
||||
_, err := pl.Interaction.ContainerGetStdout(getStdoutParams, stdout)
|
||||
log.Debugf("* stdout attach end %s", time.Now().Format(time.UnixDate))
|
||||
if err != nil {
|
||||
if _, ok := err.(*interaction.ContainerGetStdoutNotFound); ok {
|
||||
return errors.ContainerResourceNotFoundError(id, "interaction connection")
|
||||
}
|
||||
|
||||
return errors.InternalServerError(err.Error())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func copyStdErr(ctx context.Context, pl *client.PortLayer, ac *AttachConfig, stderr io.Writer) error {
|
||||
id := ac.ID
|
||||
|
||||
getStderrParams := interaction.NewContainerGetStderrParamsWithContext(ctx).WithID(id)
|
||||
_, err := pl.Interaction.ContainerGetStderr(getStderrParams, stderr)
|
||||
if err != nil {
|
||||
if _, ok := err.(*interaction.ContainerGetStderrNotFound); ok {
|
||||
errors.ContainerResourceNotFoundError(id, "interaction connection")
|
||||
}
|
||||
|
||||
return errors.InternalServerError(err.Error())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// FIXME: Move this function to a pkg to show it's origination from Docker once
|
||||
// we have ignore capabilities in our header-check.sh that checks for copyright
|
||||
// header.
|
||||
// Code c/c from io.Copy() modified by Docker to handle escape sequence
|
||||
// Begin
|
||||
|
||||
func copyEscapable(dst io.Writer, src io.ReadCloser, keys []byte) (written int64, err error) {
|
||||
if len(keys) == 0 {
|
||||
// Default keys : ctrl-p ctrl-q
|
||||
keys = []byte{16, 17}
|
||||
}
|
||||
buf := make([]byte, 32*1024)
|
||||
for {
|
||||
nr, er := src.Read(buf)
|
||||
if nr > 0 {
|
||||
// ---- Docker addition
|
||||
preservBuf := []byte{}
|
||||
for i, key := range keys {
|
||||
preservBuf = append(preservBuf, buf[0:nr]...)
|
||||
if nr != 1 || buf[0] != key {
|
||||
break
|
||||
}
|
||||
if i == len(keys)-1 {
|
||||
src.Close()
|
||||
return 0, errors.DetachError{}
|
||||
}
|
||||
nr, er = src.Read(buf)
|
||||
}
|
||||
var nw int
|
||||
var ew error
|
||||
if len(preservBuf) > 0 {
|
||||
nw, ew = dst.Write(preservBuf)
|
||||
nr = len(preservBuf)
|
||||
} else {
|
||||
// ---- End of docker
|
||||
nw, ew = dst.Write(buf[0:nr])
|
||||
}
|
||||
if nw > 0 {
|
||||
written += int64(nw)
|
||||
}
|
||||
if ew != nil {
|
||||
err = ew
|
||||
break
|
||||
}
|
||||
if nr != nw {
|
||||
err = io.ErrShortWrite
|
||||
break
|
||||
}
|
||||
}
|
||||
if er == io.EOF {
|
||||
break
|
||||
}
|
||||
if er != nil {
|
||||
err = er
|
||||
break
|
||||
}
|
||||
}
|
||||
return written, err
|
||||
}
|
||||
132
vendor/github.com/vmware/vic/lib/apiservers/engine/proxy/system_proxy.go
generated
vendored
Normal file
132
vendor/github.com/vmware/vic/lib/apiservers/engine/proxy/system_proxy.go
generated
vendored
Normal file
@@ -0,0 +1,132 @@
|
||||
// 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 proxy
|
||||
|
||||
//****
|
||||
// system_proxy.go
|
||||
//
|
||||
// Contains all code that touches the portlayer for system operations and all
|
||||
// code that converts swagger based returns to docker personality backend structs.
|
||||
// The goal is to make the backend code that implements the docker engine-api
|
||||
// interfaces be as simple as possible and contain no swagger or portlayer code.
|
||||
//
|
||||
// Rule for code to be in here:
|
||||
// 1. touches VIC portlayer
|
||||
// 2. converts swagger to docker engine-api structs
|
||||
// 3. errors MUST be docker engine-api compatible errors. DO NOT return arbitrary errors!
|
||||
// - Do NOT return portlayer errors
|
||||
// - Do NOT return fmt.Errorf()
|
||||
// - Do NOT return errors.New()
|
||||
// - DO USE the aliased docker error package 'derr'
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
derr "github.com/docker/docker/api/errors"
|
||||
|
||||
"github.com/vmware/vic/lib/apiservers/engine/errors"
|
||||
"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/models"
|
||||
"github.com/vmware/vic/pkg/trace"
|
||||
)
|
||||
|
||||
type VicSystemProxy interface {
|
||||
PingPortlayer(ctx context.Context) bool
|
||||
ContainerCount(ctx context.Context) (int, int, int, error)
|
||||
VCHInfo(ctx context.Context) (*models.VCHInfo, error)
|
||||
}
|
||||
|
||||
type SystemProxy struct {
|
||||
client *client.PortLayer
|
||||
}
|
||||
|
||||
func NewSystemProxy(client *client.PortLayer) VicSystemProxy {
|
||||
if client == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &SystemProxy{client: client}
|
||||
}
|
||||
|
||||
func (s *SystemProxy) PingPortlayer(ctx context.Context) bool {
|
||||
defer trace.End(trace.Begin(""))
|
||||
|
||||
if s.client == nil {
|
||||
log.Errorf("Portlayer client is invalid")
|
||||
return false
|
||||
}
|
||||
|
||||
pingParams := misc.NewPingParamsWithContext(ctx)
|
||||
_, err := s.client.Misc.Ping(pingParams)
|
||||
if err != nil {
|
||||
log.Info("Ping to portlayer failed")
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Use the Portlayer's support for docker ps to get the container count
|
||||
// return order: running, paused, stopped counts
|
||||
func (s *SystemProxy) ContainerCount(ctx context.Context) (int, int, int, error) {
|
||||
defer trace.End(trace.Begin(""))
|
||||
|
||||
var running, paused, stopped int
|
||||
|
||||
if s.client == nil {
|
||||
return 0, 0, 0, errors.NillPortlayerClientError("SystemProxy")
|
||||
}
|
||||
|
||||
all := true
|
||||
containList, err := s.client.Containers.GetContainerList(containers.NewGetContainerListParamsWithContext(ctx).WithAll(&all))
|
||||
if err != nil {
|
||||
return 0, 0, 0, derr.NewErrorWithStatusCode(fmt.Errorf("Failed to get container list: %s", err), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
for _, t := range containList.Payload {
|
||||
st := t.ContainerConfig.State
|
||||
if st == "Running" {
|
||||
running++
|
||||
} else if st == "Stopped" || st == "Created" {
|
||||
stopped++
|
||||
}
|
||||
}
|
||||
|
||||
return running, paused, stopped, nil
|
||||
}
|
||||
|
||||
func (s *SystemProxy) VCHInfo(ctx context.Context) (*models.VCHInfo, error) {
|
||||
defer trace.End(trace.Begin(""))
|
||||
|
||||
if s.client == nil {
|
||||
return nil, errors.NillPortlayerClientError("SystemProxy")
|
||||
}
|
||||
|
||||
params := misc.NewGetVCHInfoParamsWithContext(ctx)
|
||||
resp, err := s.client.Misc.GetVCHInfo(params)
|
||||
if err != nil {
|
||||
//There are no custom error for this operation. If we get back an error, it's
|
||||
//unknown.
|
||||
return nil, derr.NewErrorWithStatusCode(fmt.Errorf("Unknown error from port layer: %s", err),
|
||||
http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
return resp.Payload, nil
|
||||
}
|
||||
Reference in New Issue
Block a user