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

View File

@@ -0,0 +1,35 @@
package builtin
import (
"fmt"
"github.com/docker/libnetwork/datastore"
"github.com/docker/libnetwork/ipam"
"github.com/docker/libnetwork/ipamapi"
)
// Init registers the built-in ipam service with libnetwork
func Init(ic ipamapi.Callback, l, g interface{}) error {
var (
ok bool
localDs, globalDs datastore.DataStore
)
if l != nil {
if localDs, ok = l.(datastore.DataStore); !ok {
return fmt.Errorf("incorrect local datastore passed to built-in ipam init")
}
}
if g != nil {
if globalDs, ok = g.(datastore.DataStore); !ok {
return fmt.Errorf("incorrect global datastore passed to built-in ipam init")
}
}
a, err := ipam.NewAllocator(localDs, globalDs)
if err != nil {
return err
}
return ic.RegisterIpamDriver(ipamapi.DefaultIPAM, a)
}

View File

@@ -0,0 +1,90 @@
// Package api defines the data structure to be used in the request/response
// messages between libnetwork and the remote ipam plugin
package api
import "github.com/docker/libnetwork/ipamapi"
// Response is the basic response structure used in all responses
type Response struct {
Error string
}
// IsSuccess returns wheter the plugin response is successful
func (r *Response) IsSuccess() bool {
return r.Error == ""
}
// GetError returns the error from the response, if any.
func (r *Response) GetError() string {
return r.Error
}
// GetCapabilityResponse is the response of GetCapability request
type GetCapabilityResponse struct {
Response
RequiresMACAddress bool
}
// ToCapability converts the capability response into the internal ipam driver capaility structure
func (capRes GetCapabilityResponse) ToCapability() *ipamapi.Capability {
return &ipamapi.Capability{RequiresMACAddress: capRes.RequiresMACAddress}
}
// GetAddressSpacesResponse is the response to the ``get default address spaces`` request message
type GetAddressSpacesResponse struct {
Response
LocalDefaultAddressSpace string
GlobalDefaultAddressSpace string
}
// RequestPoolRequest represents the expected data in a ``request address pool`` request message
type RequestPoolRequest struct {
AddressSpace string
Pool string
SubPool string
Options map[string]string
V6 bool
}
// RequestPoolResponse represents the response message to a ``request address pool`` request
type RequestPoolResponse struct {
Response
PoolID string
Pool string // CIDR format
Data map[string]string
}
// ReleasePoolRequest represents the expected data in a ``release address pool`` request message
type ReleasePoolRequest struct {
PoolID string
}
// ReleasePoolResponse represents the response message to a ``release address pool`` request
type ReleasePoolResponse struct {
Response
}
// RequestAddressRequest represents the expected data in a ``request address`` request message
type RequestAddressRequest struct {
PoolID string
Address string
Options map[string]string
}
// RequestAddressResponse represents the expected data in the response message to a ``request address`` request
type RequestAddressResponse struct {
Response
Address string // in CIDR format
Data map[string]string
}
// ReleaseAddressRequest represents the expected data in a ``release address`` request message
type ReleaseAddressRequest struct {
PoolID string
Address string
}
// ReleaseAddressResponse represents the response message to a ``release address`` request
type ReleaseAddressResponse struct {
Response
}

View File

@@ -0,0 +1,126 @@
package remote
import (
"fmt"
"net"
log "github.com/Sirupsen/logrus"
"github.com/docker/docker/pkg/plugins"
"github.com/docker/libnetwork/ipamapi"
"github.com/docker/libnetwork/ipams/remote/api"
"github.com/docker/libnetwork/types"
)
type allocator struct {
endpoint *plugins.Client
name string
}
// PluginResponse is the interface for the plugin request responses
type PluginResponse interface {
IsSuccess() bool
GetError() string
}
func newAllocator(name string, client *plugins.Client) ipamapi.Ipam {
a := &allocator{name: name, endpoint: client}
return a
}
// Init registers a remote ipam when its plugin is activated
func Init(cb ipamapi.Callback, l, g interface{}) error {
plugins.Handle(ipamapi.PluginEndpointType, func(name string, client *plugins.Client) {
a := newAllocator(name, client)
if cps, err := a.(*allocator).getCapabilities(); err == nil {
if err := cb.RegisterIpamDriverWithCapabilities(name, a, cps); err != nil {
log.Errorf("error registering remote ipam driver %s due to %v", name, err)
}
} else {
log.Infof("remote ipam driver %s does not support capabilities", name)
log.Debug(err)
if err := cb.RegisterIpamDriver(name, a); err != nil {
log.Errorf("error registering remote ipam driver %s due to %v", name, err)
}
}
})
return nil
}
func (a *allocator) call(methodName string, arg interface{}, retVal PluginResponse) error {
method := ipamapi.PluginEndpointType + "." + methodName
err := a.endpoint.Call(method, arg, retVal)
if err != nil {
return err
}
if !retVal.IsSuccess() {
return fmt.Errorf("remote: %s", retVal.GetError())
}
return nil
}
func (a *allocator) getCapabilities() (*ipamapi.Capability, error) {
var res api.GetCapabilityResponse
if err := a.call("GetCapabilities", nil, &res); err != nil {
return nil, err
}
return res.ToCapability(), nil
}
// GetDefaultAddressSpaces returns the local and global default address spaces
func (a *allocator) GetDefaultAddressSpaces() (string, string, error) {
res := &api.GetAddressSpacesResponse{}
if err := a.call("GetDefaultAddressSpaces", nil, res); err != nil {
return "", "", err
}
return res.LocalDefaultAddressSpace, res.GlobalDefaultAddressSpace, nil
}
// RequestPool requests an address pool in the specified address space
func (a *allocator) RequestPool(addressSpace, pool, subPool string, options map[string]string, v6 bool) (string, *net.IPNet, map[string]string, error) {
req := &api.RequestPoolRequest{AddressSpace: addressSpace, Pool: pool, SubPool: subPool, Options: options, V6: v6}
res := &api.RequestPoolResponse{}
if err := a.call("RequestPool", req, res); err != nil {
return "", nil, nil, err
}
retPool, err := types.ParseCIDR(res.Pool)
return res.PoolID, retPool, res.Data, err
}
// ReleasePool removes an address pool from the specified address space
func (a *allocator) ReleasePool(poolID string) error {
req := &api.ReleasePoolRequest{PoolID: poolID}
res := &api.ReleasePoolResponse{}
return a.call("ReleasePool", req, res)
}
// RequestAddress requests an address from the address pool
func (a *allocator) RequestAddress(poolID string, address net.IP, options map[string]string) (*net.IPNet, map[string]string, error) {
var (
prefAddress string
retAddress *net.IPNet
err error
)
if address != nil {
prefAddress = address.String()
}
req := &api.RequestAddressRequest{PoolID: poolID, Address: prefAddress, Options: options}
res := &api.RequestAddressResponse{}
if err := a.call("RequestAddress", req, res); err != nil {
return nil, nil, err
}
if res.Address != "" {
retAddress, err = types.ParseCIDR(res.Address)
}
return retAddress, res.Data, err
}
// ReleaseAddress releases the address from the specified address pool
func (a *allocator) ReleaseAddress(poolID string, address net.IP) error {
var relAddress string
if address != nil {
relAddress = address.String()
}
req := &api.ReleaseAddressRequest{PoolID: poolID, Address: relAddress}
res := &api.ReleaseAddressResponse{}
return a.call("ReleaseAddress", req, res)
}

View File

@@ -0,0 +1,292 @@
package remote
import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net"
"net/http"
"net/http/httptest"
"os"
"testing"
"github.com/docker/docker/pkg/plugins"
"github.com/docker/libnetwork/ipamapi"
_ "github.com/docker/libnetwork/testutils"
)
func decodeToMap(r *http.Request) (res map[string]interface{}, err error) {
err = json.NewDecoder(r.Body).Decode(&res)
return
}
func handle(t *testing.T, mux *http.ServeMux, method string, h func(map[string]interface{}) interface{}) {
mux.HandleFunc(fmt.Sprintf("/%s.%s", ipamapi.PluginEndpointType, method), func(w http.ResponseWriter, r *http.Request) {
ask, err := decodeToMap(r)
if err != nil && err != io.EOF {
t.Fatal(err)
}
answer := h(ask)
err = json.NewEncoder(w).Encode(&answer)
if err != nil {
t.Fatal(err)
}
})
}
func setupPlugin(t *testing.T, name string, mux *http.ServeMux) func() {
if err := os.MkdirAll("/etc/docker/plugins", 0755); err != nil {
t.Fatal(err)
}
server := httptest.NewServer(mux)
if server == nil {
t.Fatal("Failed to start a HTTP Server")
}
if err := ioutil.WriteFile(fmt.Sprintf("/etc/docker/plugins/%s.spec", name), []byte(server.URL), 0644); err != nil {
t.Fatal(err)
}
mux.HandleFunc("/Plugin.Activate", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/vnd.docker.plugins.v1+json")
fmt.Fprintf(w, `{"Implements": ["%s"]}`, ipamapi.PluginEndpointType)
})
return func() {
if err := os.RemoveAll("/etc/docker/plugins"); err != nil {
t.Fatal(err)
}
server.Close()
}
}
func TestGetCapabilities(t *testing.T) {
var plugin = "test-ipam-driver-capabilities"
mux := http.NewServeMux()
defer setupPlugin(t, plugin, mux)()
handle(t, mux, "GetCapabilities", func(msg map[string]interface{}) interface{} {
return map[string]interface{}{
"RequiresMACAddress": true,
}
})
p, err := plugins.Get(plugin, ipamapi.PluginEndpointType)
if err != nil {
t.Fatal(err)
}
d := newAllocator(plugin, p.Client)
caps, err := d.(*allocator).getCapabilities()
if err != nil {
t.Fatal(err)
}
if !caps.RequiresMACAddress {
t.Fatalf("Unexpected capability: %v", caps)
}
}
func TestGetCapabilitiesFromLegacyDriver(t *testing.T) {
var plugin = "test-ipam-legacy-driver"
mux := http.NewServeMux()
defer setupPlugin(t, plugin, mux)()
p, err := plugins.Get(plugin, ipamapi.PluginEndpointType)
if err != nil {
t.Fatal(err)
}
d := newAllocator(plugin, p.Client)
if _, err := d.(*allocator).getCapabilities(); err == nil {
t.Fatalf("Expected error, but got Success %v", err)
}
}
func TestGetDefaultAddressSpaces(t *testing.T) {
var plugin = "test-ipam-driver-addr-spaces"
mux := http.NewServeMux()
defer setupPlugin(t, plugin, mux)()
handle(t, mux, "GetDefaultAddressSpaces", func(msg map[string]interface{}) interface{} {
return map[string]interface{}{
"LocalDefaultAddressSpace": "white",
"GlobalDefaultAddressSpace": "blue",
}
})
p, err := plugins.Get(plugin, ipamapi.PluginEndpointType)
if err != nil {
t.Fatal(err)
}
d := newAllocator(plugin, p.Client)
l, g, err := d.(*allocator).GetDefaultAddressSpaces()
if err != nil {
t.Fatal(err)
}
if l != "white" || g != "blue" {
t.Fatalf("Unexpected default local and global address spaces: %s, %s", l, g)
}
}
func TestRemoteDriver(t *testing.T) {
var plugin = "test-ipam-driver"
mux := http.NewServeMux()
defer setupPlugin(t, plugin, mux)()
handle(t, mux, "GetDefaultAddressSpaces", func(msg map[string]interface{}) interface{} {
return map[string]interface{}{
"LocalDefaultAddressSpace": "white",
"GlobalDefaultAddressSpace": "blue",
}
})
handle(t, mux, "RequestPool", func(msg map[string]interface{}) interface{} {
as := "white"
if v, ok := msg["AddressSpace"]; ok && v.(string) != "" {
as = v.(string)
}
pl := "172.18.0.0/16"
sp := ""
if v, ok := msg["Pool"]; ok && v.(string) != "" {
pl = v.(string)
}
if v, ok := msg["SubPool"]; ok && v.(string) != "" {
sp = v.(string)
}
pid := fmt.Sprintf("%s/%s", as, pl)
if sp != "" {
pid = fmt.Sprintf("%s/%s", pid, sp)
}
return map[string]interface{}{
"PoolID": pid,
"Pool": pl,
"Data": map[string]string{"DNS": "8.8.8.8"},
}
})
handle(t, mux, "ReleasePool", func(msg map[string]interface{}) interface{} {
if _, ok := msg["PoolID"]; !ok {
t.Fatalf("Missing PoolID in Release request")
}
return map[string]interface{}{}
})
handle(t, mux, "RequestAddress", func(msg map[string]interface{}) interface{} {
if _, ok := msg["PoolID"]; !ok {
t.Fatalf("Missing PoolID in address request")
}
prefAddr := ""
if v, ok := msg["Address"]; ok {
prefAddr = v.(string)
}
ip := prefAddr
if ip == "" {
ip = "172.20.0.34"
}
ip = fmt.Sprintf("%s/16", ip)
return map[string]interface{}{
"Address": ip,
}
})
handle(t, mux, "ReleaseAddress", func(msg map[string]interface{}) interface{} {
if _, ok := msg["PoolID"]; !ok {
t.Fatalf("Missing PoolID in address request")
}
if _, ok := msg["Address"]; !ok {
t.Fatalf("Missing Address in release address request")
}
return map[string]interface{}{}
})
p, err := plugins.Get(plugin, ipamapi.PluginEndpointType)
if err != nil {
t.Fatal(err)
}
d := newAllocator(plugin, p.Client)
l, g, err := d.(*allocator).GetDefaultAddressSpaces()
if err != nil {
t.Fatal(err)
}
if l != "white" || g != "blue" {
t.Fatalf("Unexpected default local/global address spaces: %s, %s", l, g)
}
// Request any pool
poolID, pool, _, err := d.RequestPool("white", "", "", nil, false)
if err != nil {
t.Fatal(err)
}
if poolID != "white/172.18.0.0/16" {
t.Fatalf("Unexpected pool id: %s", poolID)
}
if pool == nil || pool.String() != "172.18.0.0/16" {
t.Fatalf("Unexpected pool: %s", pool)
}
// Request specific pool
poolID2, pool2, ops, err := d.RequestPool("white", "172.20.0.0/16", "", nil, false)
if err != nil {
t.Fatal(err)
}
if poolID2 != "white/172.20.0.0/16" {
t.Fatalf("Unexpected pool id: %s", poolID2)
}
if pool2 == nil || pool2.String() != "172.20.0.0/16" {
t.Fatalf("Unexpected pool: %s", pool2)
}
if dns, ok := ops["DNS"]; !ok || dns != "8.8.8.8" {
t.Fatalf("Missing options")
}
// Request specific pool and subpool
poolID3, pool3, _, err := d.RequestPool("white", "172.20.0.0/16", "172.20.3.0/24" /*nil*/, map[string]string{"culo": "yes"}, false)
if err != nil {
t.Fatal(err)
}
if poolID3 != "white/172.20.0.0/16/172.20.3.0/24" {
t.Fatalf("Unexpected pool id: %s", poolID3)
}
if pool3 == nil || pool3.String() != "172.20.0.0/16" {
t.Fatalf("Unexpected pool: %s", pool3)
}
// Request any address
addr, _, err := d.RequestAddress(poolID2, nil, nil)
if err != nil {
t.Fatal(err)
}
if addr == nil || addr.String() != "172.20.0.34/16" {
t.Fatalf("Unexpected address: %s", addr)
}
// Request specific address
addr2, _, err := d.RequestAddress(poolID2, net.ParseIP("172.20.1.45"), nil)
if err != nil {
t.Fatal(err)
}
if addr2 == nil || addr2.String() != "172.20.1.45/16" {
t.Fatalf("Unexpected address: %s", addr2)
}
// Release address
err = d.ReleaseAddress(poolID, net.ParseIP("172.18.1.45"))
if err != nil {
t.Fatal(err)
}
}