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:
Loc Nguyen
2018-06-04 15:41:32 -07:00
committed by Ria Bhatia
parent 98a111e8b7
commit 513cebe7b7
6296 changed files with 1123685 additions and 8 deletions

115
vendor/github.com/docker/libnetwork/client/client.go generated vendored Normal file
View File

@@ -0,0 +1,115 @@
package client
import (
"fmt"
"io"
"io/ioutil"
"net/http"
"reflect"
"strings"
flag "github.com/docker/docker/pkg/mflag"
)
// CallFunc provides environment specific call utility to invoke backend functions from UI
type CallFunc func(string, string, interface{}, map[string][]string) (io.ReadCloser, http.Header, int, error)
// NetworkCli is the UI object for network subcmds
type NetworkCli struct {
out io.Writer
err io.Writer
call CallFunc
}
// NewNetworkCli is a convenient function to create a NetworkCli object
func NewNetworkCli(out, err io.Writer, call CallFunc) *NetworkCli {
return &NetworkCli{
out: out,
err: err,
call: call,
}
}
// getMethod is Borrowed from Docker UI which uses reflection to identify the UI Handler
func (cli *NetworkCli) getMethod(args ...string) (func(string, ...string) error, bool) {
camelArgs := make([]string, len(args))
for i, s := range args {
if len(s) == 0 {
return nil, false
}
camelArgs[i] = strings.ToUpper(s[:1]) + strings.ToLower(s[1:])
}
methodName := "Cmd" + strings.Join(camelArgs, "")
method := reflect.ValueOf(cli).MethodByName(methodName)
if !method.IsValid() {
return nil, false
}
return method.Interface().(func(string, ...string) error), true
}
// Cmd is borrowed from Docker UI and acts as the entry point for network UI commands.
// network UI commands are designed to be invoked from multiple parent chains
func (cli *NetworkCli) Cmd(chain string, args ...string) error {
if len(args) > 2 {
method, exists := cli.getMethod(args[:3]...)
if exists {
return method(chain+" "+args[0]+" "+args[1], args[3:]...)
}
}
if len(args) > 1 {
method, exists := cli.getMethod(args[:2]...)
if exists {
return method(chain+" "+args[0], args[2:]...)
}
}
if len(args) > 0 {
method, exists := cli.getMethod(args[0])
if !exists {
return fmt.Errorf("%s: '%s' is not a %s command. See '%s --help'.\n", chain, args[0], chain, chain)
}
return method(chain, args[1:]...)
}
flag.Usage()
return nil
}
// Subcmd is borrowed from Docker UI and performs the same function of configuring the subCmds
func (cli *NetworkCli) Subcmd(chain, name, signature, description string, exitOnError bool) *flag.FlagSet {
var errorHandling flag.ErrorHandling
if exitOnError {
errorHandling = flag.ExitOnError
} else {
errorHandling = flag.ContinueOnError
}
flags := flag.NewFlagSet(name, errorHandling)
flags.Usage = func() {
flags.ShortUsage()
flags.PrintDefaults()
}
flags.ShortUsage = func() {
options := ""
if signature != "" {
signature = " " + signature
}
if flags.FlagCountUndeprecated() > 0 {
options = " [OPTIONS]"
}
fmt.Fprintf(cli.out, "\nUsage: %s %s%s%s\n\n%s\n\n", chain, name, options, signature, description)
flags.SetOutput(cli.out)
}
return flags
}
func readBody(stream io.ReadCloser, hdr http.Header, statusCode int, err error) ([]byte, int, error) {
if stream != nil {
defer stream.Close()
}
if err != nil {
return nil, statusCode, err
}
body, err := ioutil.ReadAll(stream)
if err != nil {
return nil, -1, err
}
return body, statusCode, nil
}

View File

@@ -0,0 +1,122 @@
package client
import (
"bytes"
"testing"
_ "github.com/docker/libnetwork/testutils"
)
func TestClientServiceInvalidCommand(t *testing.T) {
var out, errOut bytes.Buffer
cli := NewNetworkCli(&out, &errOut, callbackFunc)
err := cli.Cmd("docker", "service", "invalid")
if err == nil {
t.Fatalf("Passing invalid commands must fail")
}
}
func TestClientServiceCreate(t *testing.T) {
var out, errOut bytes.Buffer
cli := NewNetworkCli(&out, &errOut, callbackFunc)
err := cli.Cmd("docker", "service", "publish", mockServiceName+"."+mockNwName)
if err != nil {
t.Fatal(err)
}
}
func TestClientServiceRm(t *testing.T) {
var out, errOut bytes.Buffer
cli := NewNetworkCli(&out, &errOut, callbackFunc)
err := cli.Cmd("docker", "service", "unpublish", mockServiceName+"."+mockNwName)
if err != nil {
t.Fatal(err)
}
}
func TestClientServiceLs(t *testing.T) {
var out, errOut bytes.Buffer
cli := NewNetworkCli(&out, &errOut, callbackFunc)
err := cli.Cmd("docker", "service", "ls")
if err != nil {
t.Fatal(err)
}
}
func TestClientServiceInfo(t *testing.T) {
var out, errOut bytes.Buffer
cli := NewNetworkCli(&out, &errOut, callbackFunc)
err := cli.Cmd("docker", "service", "info", mockServiceName+"."+mockNwName)
if err != nil {
t.Fatal(err)
}
}
func TestClientServiceInfoById(t *testing.T) {
var out, errOut bytes.Buffer
cli := NewNetworkCli(&out, &errOut, callbackFunc)
err := cli.Cmd("docker", "service", "info", mockServiceID+"."+mockNwName)
if err != nil {
t.Fatal(err)
}
}
func TestClientServiceJoin(t *testing.T) {
var out, errOut bytes.Buffer
cli := NewNetworkCli(&out, &errOut, callbackFunc)
err := cli.Cmd("docker", "service", "attach", mockContainerID, mockServiceName+"."+mockNwName)
if err != nil {
t.Fatal(err)
}
}
func TestClientServiceLeave(t *testing.T) {
var out, errOut bytes.Buffer
cli := NewNetworkCli(&out, &errOut, callbackFunc)
err := cli.Cmd("docker", "service", "detach", mockContainerID, mockServiceName+"."+mockNwName)
if err != nil {
t.Fatal(err)
}
}
// Docker Flag processing in flag.go uses os.Exit() frequently, even for --help
// TODO : Handle the --help test-case in the IT when CLI is available
/*
func TestClientNetworkServiceCreateHelp(t *testing.T) {
var out, errOut bytes.Buffer
cFunc := func(method, path string, data interface{}, headers map[string][]string) (io.ReadCloser, int, error) {
return nil, 0, nil
}
cli := NewNetworkCli(&out, &errOut, callbackFunc)
err := cli.Cmd("docker", "network", "create", "--help")
if err != nil {
t.Fatalf(err.Error())
}
}
*/
// Docker flag processing in flag.go uses os.Exit(1) for incorrect parameter case.
// TODO : Handle the missing argument case in the IT when CLI is available
/*
func TestClientNetworkServiceCreateMissingArgument(t *testing.T) {
var out, errOut bytes.Buffer
cFunc := func(method, path string, data interface{}, headers map[string][]string) (io.ReadCloser, int, error) {
return nil, 0, nil
}
cli := NewNetworkCli(&out, &errOut, callbackFunc)
err := cli.Cmd("docker", "network", "create")
if err != nil {
t.Fatal(err.Error())
}
}
*/

View File

@@ -0,0 +1,228 @@
package client
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"strings"
"testing"
_ "github.com/docker/libnetwork/testutils"
)
// nopCloser is used to provide a dummy CallFunc for Cmd()
type nopCloser struct {
io.Reader
}
func (nopCloser) Close() error { return nil }
func TestMain(m *testing.M) {
setupMockHTTPCallback()
os.Exit(m.Run())
}
var callbackFunc func(method, path string, data interface{}, headers map[string][]string) (io.ReadCloser, http.Header, int, error)
var mockNwJSON, mockNwListJSON, mockServiceJSON, mockServiceListJSON, mockSbJSON, mockSbListJSON []byte
var mockNwName = "test"
var mockNwID = "2a3456789"
var mockServiceName = "testSrv"
var mockServiceID = "2a3456789"
var mockContainerID = "2a3456789"
var mockSandboxID = "2b3456789"
func setupMockHTTPCallback() {
var list []networkResource
nw := networkResource{Name: mockNwName, ID: mockNwID}
mockNwJSON, _ = json.Marshal(nw)
list = append(list, nw)
mockNwListJSON, _ = json.Marshal(list)
var srvList []serviceResource
ep := serviceResource{Name: mockServiceName, ID: mockServiceID, Network: mockNwName}
mockServiceJSON, _ = json.Marshal(ep)
srvList = append(srvList, ep)
mockServiceListJSON, _ = json.Marshal(srvList)
var sbxList []SandboxResource
sb := SandboxResource{ID: mockSandboxID, ContainerID: mockContainerID}
mockSbJSON, _ = json.Marshal(sb)
sbxList = append(sbxList, sb)
mockSbListJSON, _ = json.Marshal(sbxList)
dummyHTTPHdr := http.Header{}
callbackFunc = func(method, path string, data interface{}, headers map[string][]string) (io.ReadCloser, http.Header, int, error) {
var rsp string
switch method {
case "GET":
if strings.Contains(path, fmt.Sprintf("networks?name=%s", mockNwName)) {
rsp = string(mockNwListJSON)
} else if strings.Contains(path, "networks?name=") {
rsp = "[]"
} else if strings.Contains(path, fmt.Sprintf("networks?partial-id=%s", mockNwID)) {
rsp = string(mockNwListJSON)
} else if strings.Contains(path, "networks?partial-id=") {
rsp = "[]"
} else if strings.HasSuffix(path, "networks") {
rsp = string(mockNwListJSON)
} else if strings.HasSuffix(path, "networks/"+mockNwID) {
rsp = string(mockNwJSON)
} else if strings.Contains(path, fmt.Sprintf("services?name=%s", mockServiceName)) {
rsp = string(mockServiceListJSON)
} else if strings.Contains(path, "services?name=") {
rsp = "[]"
} else if strings.Contains(path, fmt.Sprintf("services?partial-id=%s", mockServiceID)) {
rsp = string(mockServiceListJSON)
} else if strings.Contains(path, "services?partial-id=") {
rsp = "[]"
} else if strings.HasSuffix(path, "services") {
rsp = string(mockServiceListJSON)
} else if strings.HasSuffix(path, "services/"+mockServiceID) {
rsp = string(mockServiceJSON)
} else if strings.Contains(path, "containers") {
return nopCloser{bytes.NewBufferString("")}, dummyHTTPHdr, 400, fmt.Errorf("Bad Request")
} else if strings.Contains(path, fmt.Sprintf("sandboxes?container-id=%s", mockContainerID)) {
rsp = string(mockSbListJSON)
} else if strings.Contains(path, fmt.Sprintf("sandboxes?partial-container-id=%s", mockContainerID)) {
rsp = string(mockSbListJSON)
}
case "POST":
var data []byte
if strings.HasSuffix(path, "networks") {
data, _ = json.Marshal(mockNwID)
} else if strings.HasSuffix(path, "services") {
data, _ = json.Marshal(mockServiceID)
} else if strings.HasSuffix(path, "backend") {
data, _ = json.Marshal(mockSandboxID)
}
rsp = string(data)
case "PUT":
case "DELETE":
rsp = ""
}
return nopCloser{bytes.NewBufferString(rsp)}, dummyHTTPHdr, 200, nil
}
}
func TestClientDummyCommand(t *testing.T) {
var out, errOut bytes.Buffer
cli := NewNetworkCli(&out, &errOut, callbackFunc)
err := cli.Cmd("docker", "dummy")
if err == nil {
t.Fatalf("Incorrect Command must fail")
}
}
func TestClientNetworkInvalidCommand(t *testing.T) {
var out, errOut bytes.Buffer
cli := NewNetworkCli(&out, &errOut, callbackFunc)
err := cli.Cmd("docker", "network", "invalid")
if err == nil {
t.Fatalf("Passing invalid commands must fail")
}
}
func TestClientNetworkCreate(t *testing.T) {
var out, errOut bytes.Buffer
cli := NewNetworkCli(&out, &errOut, callbackFunc)
err := cli.Cmd("docker", "network", "create", mockNwName)
if err != nil {
t.Fatal(err.Error())
}
}
func TestClientNetworkCreateWithDriver(t *testing.T) {
var out, errOut bytes.Buffer
cli := NewNetworkCli(&out, &errOut, callbackFunc)
err := cli.Cmd("docker", "network", "create", "-f=dummy", mockNwName)
if err == nil {
t.Fatalf("Passing incorrect flags to the create command must fail")
}
err = cli.Cmd("docker", "network", "create", "-d=dummy", mockNwName)
if err != nil {
t.Fatalf(err.Error())
}
}
func TestClientNetworkRm(t *testing.T) {
var out, errOut bytes.Buffer
cli := NewNetworkCli(&out, &errOut, callbackFunc)
err := cli.Cmd("docker", "network", "rm", mockNwName)
if err != nil {
t.Fatal(err.Error())
}
}
func TestClientNetworkLs(t *testing.T) {
var out, errOut bytes.Buffer
cli := NewNetworkCli(&out, &errOut, callbackFunc)
err := cli.Cmd("docker", "network", "ls")
if err != nil {
t.Fatal(err.Error())
}
}
func TestClientNetworkInfo(t *testing.T) {
var out, errOut bytes.Buffer
cli := NewNetworkCli(&out, &errOut, callbackFunc)
err := cli.Cmd("docker", "network", "info", mockNwName)
if err != nil {
t.Fatal(err.Error())
}
}
func TestClientNetworkInfoById(t *testing.T) {
var out, errOut bytes.Buffer
cli := NewNetworkCli(&out, &errOut, callbackFunc)
err := cli.Cmd("docker", "network", "info", mockNwID)
if err != nil {
t.Fatal(err.Error())
}
}
// Docker Flag processing in flag.go uses os.Exit() frequently, even for --help
// TODO : Handle the --help test-case in the IT when CLI is available
/*
func TestClientNetworkServiceCreateHelp(t *testing.T) {
var out, errOut bytes.Buffer
cFunc := func(method, path string, data interface{}, headers map[string][]string) (io.ReadCloser, int, error) {
return nil, 0, nil
}
cli := NewNetworkCli(&out, &errOut, callbackFunc)
err := cli.Cmd("docker", "network", "create", "--help")
if err != nil {
t.Fatalf(err.Error())
}
}
*/
// Docker flag processing in flag.go uses os.Exit(1) for incorrect parameter case.
// TODO : Handle the missing argument case in the IT when CLI is available
/*
func TestClientNetworkServiceCreateMissingArgument(t *testing.T) {
var out, errOut bytes.Buffer
cFunc := func(method, path string, data interface{}, headers map[string][]string) (io.ReadCloser, int, error) {
return nil, 0, nil
}
cli := NewNetworkCli(&out, &errOut, callbackFunc)
err := cli.Cmd("docker", "network", "create")
if err != nil {
t.Fatal(err.Error())
}
}
*/

236
vendor/github.com/docker/libnetwork/client/network.go generated vendored Normal file
View File

@@ -0,0 +1,236 @@
package client
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"text/tabwriter"
flag "github.com/docker/docker/pkg/mflag"
"github.com/docker/docker/pkg/stringid"
"github.com/docker/libnetwork/netlabel"
)
type command struct {
name string
description string
}
var (
networkCommands = []command{
{"create", "Create a network"},
{"rm", "Remove a network"},
{"ls", "List all networks"},
{"info", "Display information of a network"},
}
)
// CmdNetwork handles the root Network UI
func (cli *NetworkCli) CmdNetwork(chain string, args ...string) error {
cmd := cli.Subcmd(chain, "network", "COMMAND [OPTIONS] [arg...]", networkUsage(chain), false)
cmd.Require(flag.Min, 1)
err := cmd.ParseFlags(args, true)
if err == nil {
cmd.Usage()
return fmt.Errorf("invalid command : %v", args)
}
return err
}
// CmdNetworkCreate handles Network Create UI
func (cli *NetworkCli) CmdNetworkCreate(chain string, args ...string) error {
cmd := cli.Subcmd(chain, "create", "NETWORK-NAME", "Creates a new network with a name specified by the user", false)
flDriver := cmd.String([]string{"d", "-driver"}, "", "Driver to manage the Network")
flInternal := cmd.Bool([]string{"-internal"}, false, "Config the network to be internal")
cmd.Require(flag.Exact, 1)
err := cmd.ParseFlags(args, true)
if err != nil {
return err
}
networkOpts := make(map[string]string)
if *flInternal {
networkOpts[netlabel.Internal] = "true"
}
// Construct network create request body
var driverOpts []string
nc := networkCreate{Name: cmd.Arg(0), NetworkType: *flDriver, DriverOpts: driverOpts, NetworkOpts: networkOpts}
obj, _, err := readBody(cli.call("POST", "/networks", nc, nil))
if err != nil {
return err
}
var replyID string
err = json.Unmarshal(obj, &replyID)
if err != nil {
return err
}
fmt.Fprintf(cli.out, "%s\n", replyID)
return nil
}
// CmdNetworkRm handles Network Delete UI
func (cli *NetworkCli) CmdNetworkRm(chain string, args ...string) error {
cmd := cli.Subcmd(chain, "rm", "NETWORK", "Deletes a network", false)
cmd.Require(flag.Exact, 1)
err := cmd.ParseFlags(args, true)
if err != nil {
return err
}
id, err := lookupNetworkID(cli, cmd.Arg(0))
if err != nil {
return err
}
_, _, err = readBody(cli.call("DELETE", "/networks/"+id, nil, nil))
if err != nil {
return err
}
return nil
}
// CmdNetworkLs handles Network List UI
func (cli *NetworkCli) CmdNetworkLs(chain string, args ...string) error {
cmd := cli.Subcmd(chain, "ls", "", "Lists all the networks created by the user", false)
quiet := cmd.Bool([]string{"q", "-quiet"}, false, "Only display numeric IDs")
noTrunc := cmd.Bool([]string{"#notrunc", "-no-trunc"}, false, "Do not truncate the output")
nLatest := cmd.Bool([]string{"l", "-latest"}, false, "Show the latest network created")
last := cmd.Int([]string{"n"}, -1, "Show n last created networks")
err := cmd.ParseFlags(args, true)
if err != nil {
return err
}
obj, _, err := readBody(cli.call("GET", "/networks", nil, nil))
if err != nil {
return err
}
if *last == -1 && *nLatest {
*last = 1
}
var networkResources []networkResource
err = json.Unmarshal(obj, &networkResources)
if err != nil {
return err
}
wr := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0)
// unless quiet (-q) is specified, print field titles
if !*quiet {
fmt.Fprintln(wr, "NETWORK ID\tNAME\tTYPE")
}
for _, networkResource := range networkResources {
ID := networkResource.ID
netName := networkResource.Name
if !*noTrunc {
ID = stringid.TruncateID(ID)
}
if *quiet {
fmt.Fprintln(wr, ID)
continue
}
netType := networkResource.Type
fmt.Fprintf(wr, "%s\t%s\t%s\t",
ID,
netName,
netType)
fmt.Fprint(wr, "\n")
}
wr.Flush()
return nil
}
// CmdNetworkInfo handles Network Info UI
func (cli *NetworkCli) CmdNetworkInfo(chain string, args ...string) error {
cmd := cli.Subcmd(chain, "info", "NETWORK", "Displays detailed information on a network", false)
cmd.Require(flag.Exact, 1)
err := cmd.ParseFlags(args, true)
if err != nil {
return err
}
id, err := lookupNetworkID(cli, cmd.Arg(0))
if err != nil {
return err
}
obj, _, err := readBody(cli.call("GET", "/networks/"+id, nil, nil))
if err != nil {
return err
}
networkResource := &networkResource{}
if err := json.NewDecoder(bytes.NewReader(obj)).Decode(networkResource); err != nil {
return err
}
fmt.Fprintf(cli.out, "Network Id: %s\n", networkResource.ID)
fmt.Fprintf(cli.out, "Name: %s\n", networkResource.Name)
fmt.Fprintf(cli.out, "Type: %s\n", networkResource.Type)
if networkResource.Services != nil {
for _, serviceResource := range networkResource.Services {
fmt.Fprintf(cli.out, " Service Id: %s\n", serviceResource.ID)
fmt.Fprintf(cli.out, "\tName: %s\n", serviceResource.Name)
}
}
return nil
}
// Helper function to predict if a string is a name or id or partial-id
// This provides a best-effort mechanism to identify a id with the help of GET Filter APIs
// Being a UI, its most likely that name will be used by the user, which is used to lookup
// the corresponding ID. If ID is not found, this function will assume that the passed string
// is an ID by itself.
func lookupNetworkID(cli *NetworkCli, nameID string) (string, error) {
obj, statusCode, err := readBody(cli.call("GET", "/networks?name="+nameID, nil, nil))
if err != nil {
return "", err
}
if statusCode != http.StatusOK {
return "", fmt.Errorf("name query failed for %s due to : statuscode(%d) %v", nameID, statusCode, string(obj))
}
var list []*networkResource
err = json.Unmarshal(obj, &list)
if err != nil {
return "", err
}
if len(list) > 0 {
// name query filter will always return a single-element collection
return list[0].ID, nil
}
// Check for Partial-id
obj, statusCode, err = readBody(cli.call("GET", "/networks?partial-id="+nameID, nil, nil))
if err != nil {
return "", err
}
if statusCode != http.StatusOK {
return "", fmt.Errorf("partial-id match query failed for %s due to : statuscode(%d) %v", nameID, statusCode, string(obj))
}
err = json.Unmarshal(obj, &list)
if err != nil {
return "", err
}
if len(list) == 0 {
return "", fmt.Errorf("resource not found %s", nameID)
}
if len(list) > 1 {
return "", fmt.Errorf("multiple Networks matching the partial identifier (%s). Please use full identifier", nameID)
}
return list[0].ID, nil
}
func networkUsage(chain string) string {
help := "Commands:\n"
for _, cmd := range networkCommands {
help += fmt.Sprintf(" %-25.25s%s\n", cmd.name, cmd.description)
}
help += fmt.Sprintf("\nRun '%s network COMMAND --help' for more information on a command.", chain)
return help
}

400
vendor/github.com/docker/libnetwork/client/service.go generated vendored Normal file
View File

@@ -0,0 +1,400 @@
package client
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"strings"
"text/tabwriter"
"github.com/docker/docker/opts"
flag "github.com/docker/docker/pkg/mflag"
"github.com/docker/docker/pkg/stringid"
"github.com/docker/libnetwork/netutils"
)
var (
serviceCommands = []command{
{"publish", "Publish a service"},
{"unpublish", "Remove a service"},
{"attach", "Attach a backend (container) to the service"},
{"detach", "Detach the backend from the service"},
{"ls", "Lists all services"},
{"info", "Display information about a service"},
}
)
func lookupServiceID(cli *NetworkCli, nwName, svNameID string) (string, error) {
// Sanity Check
obj, _, err := readBody(cli.call("GET", fmt.Sprintf("/networks?name=%s", nwName), nil, nil))
if err != nil {
return "", err
}
var nwList []networkResource
if err = json.Unmarshal(obj, &nwList); err != nil {
return "", err
}
if len(nwList) == 0 {
return "", fmt.Errorf("Network %s does not exist", nwName)
}
if nwName == "" {
obj, _, err := readBody(cli.call("GET", "/networks/"+nwList[0].ID, nil, nil))
if err != nil {
return "", err
}
networkResource := &networkResource{}
if err := json.NewDecoder(bytes.NewReader(obj)).Decode(networkResource); err != nil {
return "", err
}
nwName = networkResource.Name
}
// Query service by name
obj, statusCode, err := readBody(cli.call("GET", fmt.Sprintf("/services?name=%s", svNameID), nil, nil))
if err != nil {
return "", err
}
if statusCode != http.StatusOK {
return "", fmt.Errorf("name query failed for %s due to: (%d) %s", svNameID, statusCode, string(obj))
}
var list []*serviceResource
if err = json.Unmarshal(obj, &list); err != nil {
return "", err
}
for _, sr := range list {
if sr.Network == nwName {
return sr.ID, nil
}
}
// Query service by Partial-id (this covers full id as well)
obj, statusCode, err = readBody(cli.call("GET", fmt.Sprintf("/services?partial-id=%s", svNameID), nil, nil))
if err != nil {
return "", err
}
if statusCode != http.StatusOK {
return "", fmt.Errorf("partial-id match query failed for %s due to: (%d) %s", svNameID, statusCode, string(obj))
}
if err = json.Unmarshal(obj, &list); err != nil {
return "", err
}
for _, sr := range list {
if sr.Network == nwName {
return sr.ID, nil
}
}
return "", fmt.Errorf("Service %s not found on network %s", svNameID, nwName)
}
func lookupContainerID(cli *NetworkCli, cnNameID string) (string, error) {
// Container is a Docker resource, ask docker about it.
// In case of connecton error, we assume we are running in dnet and return whatever was passed to us
obj, _, err := readBody(cli.call("GET", fmt.Sprintf("/containers/%s/json", cnNameID), nil, nil))
if err != nil {
// We are probably running outside of docker
return cnNameID, nil
}
var x map[string]interface{}
err = json.Unmarshal(obj, &x)
if err != nil {
return "", err
}
if iid, ok := x["Id"]; ok {
if id, ok := iid.(string); ok {
return id, nil
}
return "", fmt.Errorf("Unexpected data type for container ID in json response")
}
return "", fmt.Errorf("Cannot find container ID in json response")
}
func lookupSandboxID(cli *NetworkCli, containerID string) (string, error) {
obj, _, err := readBody(cli.call("GET", fmt.Sprintf("/sandboxes?partial-container-id=%s", containerID), nil, nil))
if err != nil {
return "", err
}
var sandboxList []SandboxResource
err = json.Unmarshal(obj, &sandboxList)
if err != nil {
return "", err
}
if len(sandboxList) == 0 {
return "", fmt.Errorf("cannot find sandbox for container: %s", containerID)
}
return sandboxList[0].ID, nil
}
// CmdService handles the service UI
func (cli *NetworkCli) CmdService(chain string, args ...string) error {
cmd := cli.Subcmd(chain, "service", "COMMAND [OPTIONS] [arg...]", serviceUsage(chain), false)
cmd.Require(flag.Min, 1)
err := cmd.ParseFlags(args, true)
if err == nil {
cmd.Usage()
return fmt.Errorf("Invalid command : %v", args)
}
return err
}
// Parse service name for "SERVICE[.NETWORK]" format
func parseServiceName(name string) (string, string) {
s := strings.Split(name, ".")
var sName, nName string
if len(s) > 1 {
nName = s[len(s)-1]
sName = strings.Join(s[:len(s)-1], ".")
} else {
sName = s[0]
}
return sName, nName
}
// CmdServicePublish handles service create UI
func (cli *NetworkCli) CmdServicePublish(chain string, args ...string) error {
cmd := cli.Subcmd(chain, "publish", "SERVICE[.NETWORK]", "Publish a new service on a network", false)
flAlias := opts.NewListOpts(netutils.ValidateAlias)
cmd.Var(&flAlias, []string{"-alias"}, "Add alias to self")
cmd.Require(flag.Exact, 1)
err := cmd.ParseFlags(args, true)
if err != nil {
return err
}
sn, nn := parseServiceName(cmd.Arg(0))
sc := serviceCreate{Name: sn, Network: nn, MyAliases: flAlias.GetAll()}
obj, _, err := readBody(cli.call("POST", "/services", sc, nil))
if err != nil {
return err
}
var replyID string
err = json.Unmarshal(obj, &replyID)
if err != nil {
return err
}
fmt.Fprintf(cli.out, "%s\n", replyID)
return nil
}
// CmdServiceUnpublish handles service delete UI
func (cli *NetworkCli) CmdServiceUnpublish(chain string, args ...string) error {
cmd := cli.Subcmd(chain, "unpublish", "SERVICE[.NETWORK]", "Removes a service", false)
force := cmd.Bool([]string{"f", "-force"}, false, "force unpublish service")
cmd.Require(flag.Exact, 1)
err := cmd.ParseFlags(args, true)
if err != nil {
return err
}
sn, nn := parseServiceName(cmd.Arg(0))
serviceID, err := lookupServiceID(cli, nn, sn)
if err != nil {
return err
}
sd := serviceDelete{Name: sn, Force: *force}
_, _, err = readBody(cli.call("DELETE", "/services/"+serviceID, sd, nil))
return err
}
// CmdServiceLs handles service list UI
func (cli *NetworkCli) CmdServiceLs(chain string, args ...string) error {
cmd := cli.Subcmd(chain, "ls", "SERVICE", "Lists all the services on a network", false)
flNetwork := cmd.String([]string{"net", "-network"}, "", "Only show the services that are published on the specified network")
quiet := cmd.Bool([]string{"q", "-quiet"}, false, "Only display numeric IDs")
noTrunc := cmd.Bool([]string{"#notrunc", "-no-trunc"}, false, "Do not truncate the output")
err := cmd.ParseFlags(args, true)
if err != nil {
return err
}
var obj []byte
if *flNetwork == "" {
obj, _, err = readBody(cli.call("GET", "/services", nil, nil))
} else {
obj, _, err = readBody(cli.call("GET", "/services?network="+*flNetwork, nil, nil))
}
if err != nil {
return err
}
var serviceResources []serviceResource
err = json.Unmarshal(obj, &serviceResources)
if err != nil {
fmt.Println(err)
return err
}
wr := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0)
// unless quiet (-q) is specified, print field titles
if !*quiet {
fmt.Fprintln(wr, "SERVICE ID\tNAME\tNETWORK\tCONTAINER\tSANDBOX")
}
for _, sr := range serviceResources {
ID := sr.ID
bkID, sbID, err := getBackendID(cli, ID)
if err != nil {
return err
}
if !*noTrunc {
ID = stringid.TruncateID(ID)
bkID = stringid.TruncateID(bkID)
sbID = stringid.TruncateID(sbID)
}
if !*quiet {
fmt.Fprintf(wr, "%s\t%s\t%s\t%s\t%s\n", ID, sr.Name, sr.Network, bkID, sbID)
} else {
fmt.Fprintln(wr, ID)
}
}
wr.Flush()
return nil
}
func getBackendID(cli *NetworkCli, servID string) (string, string, error) {
var (
obj []byte
err error
bk string
sb string
)
if obj, _, err = readBody(cli.call("GET", "/services/"+servID+"/backend", nil, nil)); err == nil {
var sr SandboxResource
if err := json.NewDecoder(bytes.NewReader(obj)).Decode(&sr); err == nil {
bk = sr.ContainerID
sb = sr.ID
} else {
// Only print a message, don't make the caller cli fail for this
fmt.Fprintf(cli.out, "Failed to retrieve backend list for service %s (%v)\n", servID, err)
}
}
return bk, sb, err
}
// CmdServiceInfo handles service info UI
func (cli *NetworkCli) CmdServiceInfo(chain string, args ...string) error {
cmd := cli.Subcmd(chain, "info", "SERVICE[.NETWORK]", "Displays detailed information about a service", false)
cmd.Require(flag.Min, 1)
err := cmd.ParseFlags(args, true)
if err != nil {
return err
}
sn, nn := parseServiceName(cmd.Arg(0))
serviceID, err := lookupServiceID(cli, nn, sn)
if err != nil {
return err
}
obj, _, err := readBody(cli.call("GET", "/services/"+serviceID, nil, nil))
if err != nil {
return err
}
sr := &serviceResource{}
if err := json.NewDecoder(bytes.NewReader(obj)).Decode(sr); err != nil {
return err
}
fmt.Fprintf(cli.out, "Service Id: %s\n", sr.ID)
fmt.Fprintf(cli.out, "\tName: %s\n", sr.Name)
fmt.Fprintf(cli.out, "\tNetwork: %s\n", sr.Network)
return nil
}
// CmdServiceAttach handles service attach UI
func (cli *NetworkCli) CmdServiceAttach(chain string, args ...string) error {
cmd := cli.Subcmd(chain, "attach", "CONTAINER SERVICE[.NETWORK]", "Sets a container as a service backend", false)
flAlias := opts.NewListOpts(netutils.ValidateAlias)
cmd.Var(&flAlias, []string{"-alias"}, "Add alias for another container")
cmd.Require(flag.Min, 2)
err := cmd.ParseFlags(args, true)
if err != nil {
return err
}
containerID, err := lookupContainerID(cli, cmd.Arg(0))
if err != nil {
return err
}
sandboxID, err := lookupSandboxID(cli, containerID)
if err != nil {
return err
}
sn, nn := parseServiceName(cmd.Arg(1))
serviceID, err := lookupServiceID(cli, nn, sn)
if err != nil {
return err
}
nc := serviceAttach{SandboxID: sandboxID, Aliases: flAlias.GetAll()}
_, _, err = readBody(cli.call("POST", "/services/"+serviceID+"/backend", nc, nil))
return err
}
// CmdServiceDetach handles service detach UI
func (cli *NetworkCli) CmdServiceDetach(chain string, args ...string) error {
cmd := cli.Subcmd(chain, "detach", "CONTAINER SERVICE", "Removes a container from service backend", false)
cmd.Require(flag.Min, 2)
err := cmd.ParseFlags(args, true)
if err != nil {
return err
}
sn, nn := parseServiceName(cmd.Arg(1))
containerID, err := lookupContainerID(cli, cmd.Arg(0))
if err != nil {
return err
}
sandboxID, err := lookupSandboxID(cli, containerID)
if err != nil {
return err
}
serviceID, err := lookupServiceID(cli, nn, sn)
if err != nil {
return err
}
_, _, err = readBody(cli.call("DELETE", "/services/"+serviceID+"/backend/"+sandboxID, nil, nil))
if err != nil {
return err
}
return nil
}
func serviceUsage(chain string) string {
help := "Commands:\n"
for _, cmd := range serviceCommands {
help += fmt.Sprintf(" %-10.10s%s\n", cmd.name, cmd.description)
}
help += fmt.Sprintf("\nRun '%s service COMMAND --help' for more information on a command.", chain)
return help
}

88
vendor/github.com/docker/libnetwork/client/types.go generated vendored Normal file
View File

@@ -0,0 +1,88 @@
package client
import "github.com/docker/libnetwork/types"
/***********
Resources
************/
// networkResource is the body of the "get network" http response message
type networkResource struct {
Name string `json:"name"`
ID string `json:"id"`
Type string `json:"type"`
Services []*serviceResource `json:"services"`
}
// serviceResource is the body of the "get service" http response message
type serviceResource struct {
Name string `json:"name"`
ID string `json:"id"`
Network string `json:"network"`
}
// SandboxResource is the body of "get service backend" response message
type SandboxResource struct {
ID string `json:"id"`
Key string `json:"key"`
ContainerID string `json:"container_id"`
}
/***********
Body types
************/
// networkCreate is the expected body of the "create network" http request message
type networkCreate struct {
Name string `json:"name"`
NetworkType string `json:"network_type"`
DriverOpts []string `json:"driver_opts"`
NetworkOpts map[string]string `json:"network_opts"`
}
// serviceCreate represents the body of the "publish service" http request message
type serviceCreate struct {
Name string `json:"name"`
MyAliases []string `json:"my_aliases"`
Network string `json:"network_name"`
ExposedPorts []types.TransportPort `json:"exposed_ports"`
PortMapping []types.PortBinding `json:"port_mapping"`
}
// serviceDelete represents the body of the "unpublish service" http request message
type serviceDelete struct {
Name string `json:"name"`
Force bool `json:"force"`
}
// serviceAttach represents the expected body of the "attach/detach sandbox to/from service" http request messages
type serviceAttach struct {
SandboxID string `json:"sandbox_id"`
Aliases []string `json:"aliases"`
}
// SandboxCreate is the body of the "post /sandboxes" http request message
type SandboxCreate struct {
ContainerID string `json:"container_id"`
HostName string `json:"host_name"`
DomainName string `json:"domain_name"`
HostsPath string `json:"hosts_path"`
ResolvConfPath string `json:"resolv_conf_path"`
DNS []string `json:"dns"`
ExtraHosts []extraHost `json:"extra_hosts"`
UseDefaultSandbox bool `json:"use_default_sandbox"`
}
// extraHost represents the extra host object
type extraHost struct {
Name string `json:"name"`
Address string `json:"address"`
}
// sandboxParentUpdate is the object carrying the information about the
// sanbox parent that needs to be updated
type sandboxParentUpdate struct {
ContainerID string `json:"container_id"`
Name string `json:"name"`
Address string `json:"address"`
}