Merge remote-tracking branch 'upstream/master'

This commit is contained in:
Vincenzo D'Amore 2015-09-09 10:06:10 +02:00
commit 1b82b1148a
482 changed files with 9139 additions and 6170 deletions

3
.gitignore vendored
View File

@ -42,6 +42,9 @@ Session.vim
.vagrant
network_closure.sh
# Local cluster env variables
/cluster/env.sh
# Compiled binaries in third_party
/third_party/pkg

View File

@ -1,4 +1,4 @@
Please see the [Releases Page](https://github.com/GoogleCloudPlatform/kubernetes/releases)
Please see the [Releases Page](https://github.com/kubernetes/kubernetes/releases)
[![Analytics](https://kubernetes-site.appspot.com/UA-36037335-10/GitHub/CHANGELOG.md?pixel)]()

8
Godeps/Godeps.json generated
View File

@ -215,8 +215,8 @@
},
{
"ImportPath": "github.com/emicklei/go-restful",
"Comment": "v1.1.3-76-gbfd6ff2",
"Rev": "bfd6ff29d2961031cec64346a92bae4cde96c868"
"Comment": "v1.1.3-98-g1f9a0ee",
"Rev": "1f9a0ee00ff93717a275e15b30cf7df356255877"
},
{
"ImportPath": "github.com/evanphx/json-patch",
@ -224,7 +224,7 @@
},
{
"ImportPath": "github.com/fsouza/go-dockerclient",
"Rev": "42d06e2b125654477366c320dcea99107a86e9c2"
"Rev": "76fd6c68cf24c48ee6a2b25def997182a29f940e"
},
{
"ImportPath": "github.com/garyburd/redigo/internal",
@ -512,7 +512,7 @@
},
{
"ImportPath": "github.com/spf13/cobra",
"Rev": "db0518444643a7b170abb78164bbeaf5a2bb816f"
"Rev": "68f5a81a722d56241bd70faf6860ceb05eb27d64"
},
{
"ImportPath": "github.com/spf13/pflag",

View File

@ -1,5 +1,10 @@
Change history of go-restful
=
2015-08-06
- add support for reading entities from compressed request content
- use sync.Pool for compressors of http response and request body
- add Description to Parameter for documentation in Swagger UI
2015-03-20
- add configurable logging

View File

@ -47,7 +47,7 @@ func (u UserResource) findUser(request *restful.Request, response *restful.Respo
- Filters for intercepting the request → response flow on Service or Route level
- Request-scoped variables using attributes
- Containers for WebServices on different HTTP endpoints
- Content encoding (gzip,deflate) of responses
- Content encoding (gzip,deflate) of request and response payloads
- Automatic responses on OPTIONS (using a filter)
- Automatic CORS request handling (using a filter)
- API declaration for Swagger UI (see swagger package)

View File

@ -73,15 +73,13 @@ func NewCompressingResponseWriter(httpWriter http.ResponseWriter, encoding strin
c.writer = httpWriter
var err error
if ENCODING_GZIP == encoding {
c.compressor, err = gzip.NewWriterLevel(httpWriter, gzip.BestSpeed)
if err != nil {
return nil, err
}
w := GzipWriterPool.Get().(*gzip.Writer)
w.Reset(httpWriter)
c.compressor = w
} else if ENCODING_DEFLATE == encoding {
c.compressor, err = zlib.NewWriterLevel(httpWriter, zlib.BestSpeed)
if err != nil {
return nil, err
}
w := ZlibWriterPool.Get().(*zlib.Writer)
w.Reset(httpWriter)
c.compressor = w
} else {
return nil, errors.New("Unknown encoding:" + encoding)
}

View File

@ -1,11 +1,17 @@
package restful
import (
"bytes"
"compress/gzip"
"compress/zlib"
"io"
"io/ioutil"
"net/http"
"net/http/httptest"
"testing"
)
// go test -v -test.run TestGzip ...restful
func TestGzip(t *testing.T) {
EnableContentEncoding = true
httpRequest, _ := http.NewRequest("GET", "/test", nil)
@ -27,6 +33,17 @@ func TestGzip(t *testing.T) {
if httpWriter.Header().Get("Content-Encoding") != "gzip" {
t.Fatal("Missing gzip header")
}
reader, err := gzip.NewReader(httpWriter.Body)
if err != nil {
t.Fatal(err.Error())
}
data, err := ioutil.ReadAll(reader)
if err != nil {
t.Fatal(err.Error())
}
if got, want := string(data), "Hello World"; got != want {
t.Errorf("got %v want %v", got, want)
}
}
func TestDeflate(t *testing.T) {
@ -50,4 +67,61 @@ func TestDeflate(t *testing.T) {
if httpWriter.Header().Get("Content-Encoding") != "deflate" {
t.Fatal("Missing deflate header")
}
reader, err := zlib.NewReader(httpWriter.Body)
if err != nil {
t.Fatal(err.Error())
}
data, err := ioutil.ReadAll(reader)
if err != nil {
t.Fatal(err.Error())
}
if got, want := string(data), "Hello World"; got != want {
t.Errorf("got %v want %v", got, want)
}
}
func TestGzipDecompressRequestBody(t *testing.T) {
b := new(bytes.Buffer)
w := newGzipWriter()
w.Reset(b)
io.WriteString(w, `{"msg":"hi"}`)
w.Flush()
w.Close()
req := new(Request)
httpRequest, _ := http.NewRequest("GET", "/", bytes.NewReader(b.Bytes()))
httpRequest.Header.Set("Content-Type", "application/json")
httpRequest.Header.Set("Content-Encoding", "gzip")
req.Request = httpRequest
doCacheReadEntityBytes = false
doc := make(map[string]interface{})
req.ReadEntity(&doc)
if got, want := doc["msg"], "hi"; got != want {
t.Errorf("got %v want %v", got, want)
}
}
func TestZlibDecompressRequestBody(t *testing.T) {
b := new(bytes.Buffer)
w := newZlibWriter()
w.Reset(b)
io.WriteString(w, `{"msg":"hi"}`)
w.Flush()
w.Close()
req := new(Request)
httpRequest, _ := http.NewRequest("GET", "/", bytes.NewReader(b.Bytes()))
httpRequest.Header.Set("Content-Type", "application/json")
httpRequest.Header.Set("Content-Encoding", "deflate")
req.Request = httpRequest
doCacheReadEntityBytes = false
doc := make(map[string]interface{})
req.ReadEntity(&doc)
if got, want := doc["msg"], "hi"; got != want {
t.Errorf("got %v want %v", got, want)
}
}

View File

@ -0,0 +1,63 @@
package restful
import (
"bytes"
"compress/gzip"
"compress/zlib"
"sync"
)
// GzipWriterPool is used to get reusable zippers.
// The Get() result must be type asserted to *gzip.Writer.
var GzipWriterPool = &sync.Pool{
New: func() interface{} {
return newGzipWriter()
},
}
func newGzipWriter() *gzip.Writer {
// create with an empty bytes writer; it will be replaced before using the gzipWriter
writer, err := gzip.NewWriterLevel(new(bytes.Buffer), gzip.BestSpeed)
if err != nil {
panic(err.Error())
}
return writer
}
// GzipReaderPool is used to get reusable zippers.
// The Get() result must be type asserted to *gzip.Reader.
var GzipReaderPool = &sync.Pool{
New: func() interface{} {
return newGzipReader()
},
}
func newGzipReader() *gzip.Reader {
// create with an empty reader (but with GZIP header); it will be replaced before using the gzipReader
w := GzipWriterPool.Get().(*gzip.Writer)
b := new(bytes.Buffer)
w.Reset(b)
w.Flush()
w.Close()
reader, err := gzip.NewReader(bytes.NewReader(b.Bytes()))
if err != nil {
panic(err.Error())
}
return reader
}
// ZlibWriterPool is used to get reusable zippers.
// The Get() result must be type asserted to *zlib.Writer.
var ZlibWriterPool = &sync.Pool{
New: func() interface{} {
return newZlibWriter()
},
}
func newZlibWriter() *zlib.Writer {
writer, err := zlib.NewWriterLevel(new(bytes.Buffer), gzip.BestSpeed)
if err != nil {
panic(err.Error())
}
return writer
}

View File

@ -11,6 +11,7 @@ import (
"os"
"runtime"
"strings"
"sync"
"github.com/emicklei/go-restful/log"
)
@ -18,6 +19,7 @@ import (
// Container holds a collection of WebServices and a http.ServeMux to dispatch http requests.
// The requests are further dispatched to routes of WebServices using a RouteSelector
type Container struct {
webServicesLock sync.RWMutex
webServices []*WebService
ServeMux *http.ServeMux
isRegisteredOnRoot bool
@ -83,6 +85,8 @@ func (c *Container) EnableContentEncoding(enabled bool) {
// Add a WebService to the Container. It will detect duplicate root paths and panic in that case.
func (c *Container) Add(service *WebService) *Container {
c.webServicesLock.Lock()
defer c.webServicesLock.Unlock()
// If registered on root then no additional specific mapping is needed
if !c.isRegisteredOnRoot {
pattern := c.fixedPrefixPath(service.RootPath())
@ -122,6 +126,19 @@ func (c *Container) Add(service *WebService) *Container {
return c
}
func (c *Container) Remove(ws *WebService) error {
c.webServicesLock.Lock()
defer c.webServicesLock.Unlock()
newServices := []*WebService{}
for ix := range c.webServices {
if c.webServices[ix].rootPath != ws.rootPath {
newServices = append(newServices, c.webServices[ix])
}
}
c.webServices = newServices
return nil
}
// logStackOnRecover is the default RecoverHandleFunction and is called
// when DoNotRecover is false and the recoverHandleFunc is not set for the container.
// Default implementation logs the stacktrace and writes the stacktrace on the response.
@ -190,9 +207,16 @@ func (c *Container) dispatch(httpWriter http.ResponseWriter, httpRequest *http.R
}
}
// Find best match Route ; err is non nil if no match was found
webService, route, err := c.router.SelectRoute(
c.webServices,
httpRequest)
var webService *WebService
var route *Route
var err error
func() {
c.webServicesLock.RLock()
defer c.webServicesLock.RUnlock()
webService, route, err = c.router.SelectRoute(
c.webServices,
httpRequest)
}()
if err != nil {
// a non-200 response has already been written
// run container filters anyway ; they should not touch the response...
@ -272,7 +296,13 @@ func (c *Container) Filter(filter FilterFunction) {
// RegisteredWebServices returns the collections of added WebServices
func (c Container) RegisteredWebServices() []*WebService {
return c.webServices
c.webServicesLock.RLock()
defer c.webServicesLock.RUnlock()
result := make([]*WebService, len(c.webServices))
for ix := range c.webServices {
result[ix] = c.webServices[ix]
}
return result
}
// computeAllowedMethods returns a list of HTTP methods that are valid for a Request

View File

@ -95,8 +95,14 @@ func (p *Parameter) DataType(typeName string) *Parameter {
return p
}
// DefaultValue sets the default value field and returnw the receiver
// DefaultValue sets the default value field and returns the receiver
func (p *Parameter) DefaultValue(stringRepresentation string) *Parameter {
p.data.DefaultValue = stringRepresentation
return p
}
// Description sets the description value field and returns the receiver
func (p *Parameter) Description(doc string) *Parameter {
p.data.Description = doc
return p
}

View File

@ -6,6 +6,8 @@ package restful
import (
"bytes"
"compress/gzip"
"compress/zlib"
"encoding/json"
"encoding/xml"
"io"
@ -82,15 +84,17 @@ func (r *Request) HeaderParameter(name string) string {
// ReadEntity checks the Accept header and reads the content into the entityPointer
// May be called multiple times in the request-response flow
func (r *Request) ReadEntity(entityPointer interface{}) (err error) {
defer r.Request.Body.Close()
contentType := r.Request.Header.Get(HEADER_ContentType)
contentEncoding := r.Request.Header.Get(HEADER_ContentEncoding)
if doCacheReadEntityBytes {
return r.cachingReadEntity(contentType, entityPointer)
return r.cachingReadEntity(contentType, contentEncoding, entityPointer)
}
// unmarshall directly from request Body
return r.decodeEntity(r.Request.Body, contentType, entityPointer)
return r.decodeEntity(r.Request.Body, contentType, contentEncoding, entityPointer)
}
func (r *Request) cachingReadEntity(contentType string, entityPointer interface{}) (err error) {
func (r *Request) cachingReadEntity(contentType string, contentEncoding string, entityPointer interface{}) (err error) {
var buffer []byte
if r.bodyContent != nil {
buffer = *r.bodyContent
@ -101,22 +105,38 @@ func (r *Request) cachingReadEntity(contentType string, entityPointer interface{
}
r.bodyContent = &buffer
}
return r.decodeEntity(bytes.NewReader(buffer), contentType, entityPointer)
return r.decodeEntity(bytes.NewReader(buffer), contentType, contentEncoding, entityPointer)
}
func (r *Request) decodeEntity(reader io.Reader, contentType string, entityPointer interface{}) (err error) {
if strings.Contains(contentType, MIME_XML) {
return xml.NewDecoder(reader).Decode(entityPointer)
func (r *Request) decodeEntity(reader io.Reader, contentType string, contentEncoding string, entityPointer interface{}) (err error) {
entityReader := reader
// check if the request body needs decompression
if ENCODING_GZIP == contentEncoding {
gzipReader := GzipReaderPool.Get().(*gzip.Reader)
gzipReader.Reset(reader)
entityReader = gzipReader
} else if ENCODING_DEFLATE == contentEncoding {
zlibReader, err := zlib.NewReader(reader)
if err != nil {
return err
}
entityReader = zlibReader
}
// decode JSON
if strings.Contains(contentType, MIME_JSON) || MIME_JSON == defaultRequestContentType {
decoder := json.NewDecoder(reader)
decoder := json.NewDecoder(entityReader)
decoder.UseNumber()
return decoder.Decode(entityPointer)
}
if MIME_XML == defaultRequestContentType {
return xml.NewDecoder(reader).Decode(entityPointer)
// decode XML
if strings.Contains(contentType, MIME_XML) || MIME_XML == defaultRequestContentType {
return xml.NewDecoder(entityReader).Decode(entityPointer)
}
return NewError(400, "Unable to unmarshal content of type:"+contentType)
return NewError(http.StatusBadRequest, "Unable to unmarshal content of type:"+contentType)
}
// SetAttribute adds or replaces the attribute with the given value.

View File

@ -28,11 +28,12 @@ type Response struct {
statusCode int // HTTP status code that has been written explicity (if zero then net/http has written 200)
contentLength int // number of bytes written for the response body
prettyPrint bool // controls the indentation feature of XML and JSON serialization. It is initialized using var PrettyPrintResponses.
err error // err property is kept when WriteError is called
}
// Creates a new response based on a http ResponseWriter.
func NewResponse(httpWriter http.ResponseWriter) *Response {
return &Response{httpWriter, "", []string{}, http.StatusOK, 0, PrettyPrintResponses} // empty content-types
return &Response{httpWriter, "", []string{}, http.StatusOK, 0, PrettyPrintResponses, nil} // empty content-types
}
// If Accept header matching fails, fall back to this type, otherwise
@ -182,6 +183,7 @@ func (r *Response) WriteJson(value interface{}, contentType string) error {
// WriteError write the http status and the error string on the response.
func (r *Response) WriteError(httpStatus int, err error) error {
r.err = err
return r.WriteErrorString(httpStatus, err.Error())
}
@ -203,21 +205,30 @@ func (r *Response) WriteErrorString(status int, errorReason string) error {
// WriteHeader is overridden to remember the Status Code that has been written.
// Note that using this method, the status value is only written when
// - calling WriteEntity,
// - or directly calling WriteAsXml or WriteAsJson,
// - or if the status is one for which no response is allowed (i.e.,
// 204 (http.StatusNoContent) or 304 (http.StatusNotModified))
// calling WriteEntity,
// or directly calling WriteAsXml or WriteAsJson,
// or if the status is one for which no response is allowed:
//
// 202 = http.StatusAccepted
// 204 = http.StatusNoContent
// 206 = http.StatusPartialContent
// 304 = http.StatusNotModified
//
// If this behavior does not fit your need then you can write to the underlying response, such as:
// response.ResponseWriter.WriteHeader(http.StatusAccepted)
func (r *Response) WriteHeader(httpStatus int) {
r.statusCode = httpStatus
// if 201,204,304 then WriteEntity will not be called so we need to pass this code
// if 202,204,206,304 then WriteEntity will not be called so we need to pass this code
if http.StatusNoContent == httpStatus ||
http.StatusNotModified == httpStatus ||
http.StatusPartialContent == httpStatus {
http.StatusPartialContent == httpStatus ||
http.StatusAccepted == httpStatus {
r.ResponseWriter.WriteHeader(httpStatus)
}
}
// StatusCode returns the code that has been written using WriteHeader.
// If WriteHeader, WriteEntity or WriteAsXml has not been called (yet) then return 200 OK.
func (r Response) StatusCode() int {
if 0 == r.statusCode {
// no status code has been written yet; assume OK
@ -245,3 +256,8 @@ func (r Response) ContentLength() int {
func (r Response) CloseNotify() <-chan bool {
return r.ResponseWriter.(http.CloseNotifier).CloseNotify()
}
// Error returns the err created by WriteError
func (r Response) Error() error {
return r.err
}

View File

@ -9,7 +9,7 @@ import (
func TestWriteHeader(t *testing.T) {
httpWriter := httptest.NewRecorder()
resp := Response{httpWriter, "*/*", []string{"*/*"}, 0, 0, true}
resp := Response{httpWriter, "*/*", []string{"*/*"}, 0, 0, true, nil}
resp.WriteHeader(123)
if resp.StatusCode() != 123 {
t.Errorf("Unexpected status code:%d", resp.StatusCode())
@ -18,7 +18,7 @@ func TestWriteHeader(t *testing.T) {
func TestNoWriteHeader(t *testing.T) {
httpWriter := httptest.NewRecorder()
resp := Response{httpWriter, "*/*", []string{"*/*"}, 0, 0, true}
resp := Response{httpWriter, "*/*", []string{"*/*"}, 0, 0, true, nil}
if resp.StatusCode() != http.StatusOK {
t.Errorf("Unexpected status code:%d", resp.StatusCode())
}
@ -31,7 +31,7 @@ type food struct {
// go test -v -test.run TestMeasureContentLengthXml ...restful
func TestMeasureContentLengthXml(t *testing.T) {
httpWriter := httptest.NewRecorder()
resp := Response{httpWriter, "*/*", []string{"*/*"}, 0, 0, true}
resp := Response{httpWriter, "*/*", []string{"*/*"}, 0, 0, true, nil}
resp.WriteAsXml(food{"apple"})
if resp.ContentLength() != 76 {
t.Errorf("Incorrect measured length:%d", resp.ContentLength())
@ -41,7 +41,7 @@ func TestMeasureContentLengthXml(t *testing.T) {
// go test -v -test.run TestMeasureContentLengthJson ...restful
func TestMeasureContentLengthJson(t *testing.T) {
httpWriter := httptest.NewRecorder()
resp := Response{httpWriter, "*/*", []string{"*/*"}, 0, 0, true}
resp := Response{httpWriter, "*/*", []string{"*/*"}, 0, 0, true, nil}
resp.WriteAsJson(food{"apple"})
if resp.ContentLength() != 22 {
t.Errorf("Incorrect measured length:%d", resp.ContentLength())
@ -51,7 +51,7 @@ func TestMeasureContentLengthJson(t *testing.T) {
// go test -v -test.run TestMeasureContentLengthJsonNotPretty ...restful
func TestMeasureContentLengthJsonNotPretty(t *testing.T) {
httpWriter := httptest.NewRecorder()
resp := Response{httpWriter, "*/*", []string{"*/*"}, 0, 0, false}
resp := Response{httpWriter, "*/*", []string{"*/*"}, 0, 0, false, nil}
resp.WriteAsJson(food{"apple"})
if resp.ContentLength() != 16 {
t.Errorf("Incorrect measured length:%d", resp.ContentLength())
@ -61,7 +61,7 @@ func TestMeasureContentLengthJsonNotPretty(t *testing.T) {
// go test -v -test.run TestMeasureContentLengthWriteErrorString ...restful
func TestMeasureContentLengthWriteErrorString(t *testing.T) {
httpWriter := httptest.NewRecorder()
resp := Response{httpWriter, "*/*", []string{"*/*"}, 0, 0, true}
resp := Response{httpWriter, "*/*", []string{"*/*"}, 0, 0, true, nil}
resp.WriteErrorString(404, "Invalid")
if resp.ContentLength() != len("Invalid") {
t.Errorf("Incorrect measured length:%d", resp.ContentLength())
@ -79,7 +79,7 @@ func TestStatusIsPassedToResponse(t *testing.T) {
{write: 400, read: 200},
} {
httpWriter := httptest.NewRecorder()
resp := Response{httpWriter, "*/*", []string{"*/*"}, 0, 0, true}
resp := Response{httpWriter, "*/*", []string{"*/*"}, 0, 0, true, nil}
resp.WriteHeader(each.write)
if got, want := httpWriter.Code, each.read; got != want {
t.Error("got %v want %v", got, want)
@ -90,7 +90,7 @@ func TestStatusIsPassedToResponse(t *testing.T) {
// go test -v -test.run TestStatusCreatedAndContentTypeJson_Issue54 ...restful
func TestStatusCreatedAndContentTypeJson_Issue54(t *testing.T) {
httpWriter := httptest.NewRecorder()
resp := Response{httpWriter, "application/json", []string{"application/json"}, 0, 0, true}
resp := Response{httpWriter, "application/json", []string{"application/json"}, 0, 0, true, nil}
resp.WriteHeader(201)
resp.WriteAsJson(food{"Juicy"})
if httpWriter.HeaderMap.Get("Content-Type") != "application/json" {
@ -112,7 +112,7 @@ func (e errorOnWriteRecorder) Write(bytes []byte) (int, error) {
// go test -v -test.run TestLastWriteErrorCaught ...restful
func TestLastWriteErrorCaught(t *testing.T) {
httpWriter := errorOnWriteRecorder{httptest.NewRecorder()}
resp := Response{httpWriter, "application/json", []string{"application/json"}, 0, 0, true}
resp := Response{httpWriter, "application/json", []string{"application/json"}, 0, 0, true, nil}
err := resp.WriteAsJson(food{"Juicy"})
if err.Error() != "fail" {
t.Errorf("Unexpected error message:%v", err)
@ -123,7 +123,7 @@ func TestLastWriteErrorCaught(t *testing.T) {
func TestAcceptStarStar_Issue83(t *testing.T) {
httpWriter := httptest.NewRecorder()
// Accept Produces
resp := Response{httpWriter, "application/bogus,*/*;q=0.8", []string{"application/json"}, 0, 0, true}
resp := Response{httpWriter, "application/bogus,*/*;q=0.8", []string{"application/json"}, 0, 0, true, nil}
resp.WriteEntity(food{"Juicy"})
ct := httpWriter.Header().Get("Content-Type")
if "application/json" != ct {
@ -135,7 +135,7 @@ func TestAcceptStarStar_Issue83(t *testing.T) {
func TestAcceptSkipStarStar_Issue83(t *testing.T) {
httpWriter := httptest.NewRecorder()
// Accept Produces
resp := Response{httpWriter, " application/xml ,*/* ; q=0.8", []string{"application/json", "application/xml"}, 0, 0, true}
resp := Response{httpWriter, " application/xml ,*/* ; q=0.8", []string{"application/json", "application/xml"}, 0, 0, true, nil}
resp.WriteEntity(food{"Juicy"})
ct := httpWriter.Header().Get("Content-Type")
if "application/xml" != ct {
@ -147,7 +147,7 @@ func TestAcceptSkipStarStar_Issue83(t *testing.T) {
func TestAcceptXmlBeforeStarStar_Issue83(t *testing.T) {
httpWriter := httptest.NewRecorder()
// Accept Produces
resp := Response{httpWriter, "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", []string{"application/json"}, 0, 0, true}
resp := Response{httpWriter, "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", []string{"application/json"}, 0, 0, true, nil}
resp.WriteEntity(food{"Juicy"})
ct := httpWriter.Header().Get("Content-Type")
if "application/json" != ct {
@ -158,7 +158,7 @@ func TestAcceptXmlBeforeStarStar_Issue83(t *testing.T) {
// go test -v -test.run TestWriteHeaderNoContent_Issue124 ...restful
func TestWriteHeaderNoContent_Issue124(t *testing.T) {
httpWriter := httptest.NewRecorder()
resp := Response{httpWriter, "text/plain", []string{"text/plain"}, 0, 0, true}
resp := Response{httpWriter, "text/plain", []string{"text/plain"}, 0, 0, true, nil}
resp.WriteHeader(http.StatusNoContent)
if httpWriter.Code != http.StatusNoContent {
t.Errorf("got %d want %d", httpWriter.Code, http.StatusNoContent)
@ -168,7 +168,7 @@ func TestWriteHeaderNoContent_Issue124(t *testing.T) {
// go test -v -test.run TestStatusCreatedAndContentTypeJson_Issue163 ...restful
func TestStatusCreatedAndContentTypeJson_Issue163(t *testing.T) {
httpWriter := httptest.NewRecorder()
resp := Response{httpWriter, "application/json", []string{"application/json"}, 0, 0, true}
resp := Response{httpWriter, "application/json", []string{"application/json"}, 0, 0, true, nil}
resp.WriteHeader(http.StatusNotModified)
if httpWriter.Code != http.StatusNotModified {
t.Errorf("Got %d want %d", httpWriter.Code, http.StatusNotModified)

View File

@ -173,7 +173,14 @@ func (sws SwaggerService) getDeclarations(req *restful.Request, resp *restful.Re
} else {
host = hostvalues[0]
}
(&decl).BasePath = fmt.Sprintf("http://%s", host)
// inspect Referer for the scheme (http vs https)
scheme := "http"
if referer := req.Request.Header["Referer"]; len(referer) > 0 {
if strings.HasPrefix(referer[0], "https") {
scheme = "https"
}
}
(&decl).BasePath = fmt.Sprintf("%s://%s", scheme, host)
}
resp.WriteAsJson(decl)
}

View File

@ -3,6 +3,7 @@ sudo: false
go:
- 1.3.1
- 1.4
- 1.5
- tip
env:
- GOARCH=amd64

View File

@ -13,6 +13,7 @@ Brian Lalor <blalor@bravo5.org>
Brian Palmer <brianp@instructure.com>
Burke Libbey <burke@libbey.me>
Carlos Diaz-Padron <cpadron@mozilla.com>
Cesar Wong <cewong@redhat.com>
Cezar Sa Espinola <cezar.sa@corp.globo.com>
Cheah Chu Yeow <chuyeow@gmail.com>
cheneydeng <cheneydeng@qq.com>
@ -33,6 +34,7 @@ Fabio Rehm <fgrehm@gmail.com>
Fatih Arslan <ftharsln@gmail.com>
Flavia Missi <flaviamissi@gmail.com>
Francisco Souza <f@souza.cc>
Grégoire Delattre <gregoire.delattre@gmail.com>
Guillermo Álvarez Fernández <guillermo@cientifico.net>
He Simei <hesimei@zju.edu.cn>
Ivan Mikushin <i.mikushin@gmail.com>
@ -66,12 +68,14 @@ Paul Morie <pmorie@gmail.com>
Paul Weil <pweil@redhat.com>
Peter Edge <peter.edge@gmail.com>
Peter Jihoon Kim <raingrove@gmail.com>
Phil Lu <lu@stackengine.com>
Philippe Lafoucrière <philippe.lafoucriere@tech-angels.com>
Rafe Colton <rafael.colton@gmail.com>
Rob Miller <rob@kalistra.com>
Robert Williamson <williamson.robert@gmail.com>
Salvador Gironès <salvadorgirones@gmail.com>
Sam Rijs <srijs@airpost.net>
Samuel Karp <skarp@amazon.com>
Simon Eskildsen <sirup@sirupsen.com>
Simon Menke <simon.menke@gmail.com>
Skolos <skolos@gopherlab.com>

View File

@ -16,7 +16,8 @@ import (
"strings"
)
var AuthParseError error = errors.New("Failed to read authentication from dockercfg")
// ErrCannotParseDockercfg is the error returned by NewAuthConfigurations when the dockercfg cannot be parsed.
var ErrCannotParseDockercfg = errors.New("Failed to read authentication from dockercfg")
// AuthConfiguration represents authentication options to use in the PushImage
// method. It represents the authentication in the Docker index server.
@ -33,6 +34,10 @@ type AuthConfigurations struct {
Configs map[string]AuthConfiguration `json:"configs"`
}
// AuthConfigurations119 is used to serialize a set of AuthConfigurations
// for Docker API >= 1.19.
type AuthConfigurations119 map[string]AuthConfiguration
// dockerConfig represents a registry authentation configuration from the
// .dockercfg file.
type dockerConfig struct {
@ -103,7 +108,7 @@ func authConfigs(confs map[string]dockerConfig) (*AuthConfigurations, error) {
}
userpass := strings.Split(string(data), ":")
if len(userpass) != 2 {
return nil, AuthParseError
return nil, ErrCannotParseDockercfg
}
c.Configs[reg] = AuthConfiguration{
Email: conf.Email,
@ -117,7 +122,7 @@ func authConfigs(confs map[string]dockerConfig) (*AuthConfigurations, error) {
// AuthCheck validates the given credentials. It returns nil if successful.
//
// See https://goo.gl/vPoEfJ for more details.
// See https://goo.gl/m2SleN for more details.
func (c *Client) AuthCheck(conf *AuthConfiguration) error {
if conf == nil {
return fmt.Errorf("conf is nil")

View File

@ -41,7 +41,7 @@ func TestAuthBadConfig(t *testing.T) {
auth := base64.StdEncoding.EncodeToString([]byte("userpass"))
read := strings.NewReader(fmt.Sprintf(`{"docker.io":{"auth":"%s","email":"user@example.com"}}`, auth))
ac, err := NewAuthConfigurations(read)
if err != AuthParseError {
if err != ErrCannotParseDockercfg {
t.Errorf("Incorrect error returned %v\n", err)
}
if ac != nil {

View File

@ -23,7 +23,7 @@ const (
// Change represents a change in a container.
//
// See http://goo.gl/QkW9sH for more details.
// See https://goo.gl/9GsTIF for more details.
type Change struct {
Path string
Kind ChangeType

View File

@ -4,7 +4,7 @@
// Package docker provides a client for the Docker remote API.
//
// See http://goo.gl/G3plxW for more details on the remote API.
// See https://goo.gl/G3plxW for more details on the remote API.
package docker
import (
@ -45,6 +45,8 @@ var (
ErrConnectionRefused = errors.New("cannot connect to Docker endpoint")
apiVersion112, _ = NewAPIVersion("1.12")
apiVersion119, _ = NewAPIVersion("1.19")
)
// APIVersion is an internal representation of a version of the Remote API.
@ -326,7 +328,7 @@ func (c *Client) checkAPIVersion() error {
// Ping pings the docker server
//
// See http://goo.gl/stJENm for more details.
// See https://goo.gl/kQCfJj for more details.
func (c *Client) Ping() error {
path := "/_ping"
body, status, err := c.do("GET", path, doOptions{})
@ -462,9 +464,13 @@ func (c *Client) stream(method, path string, streamOptions streamOptions) error
address := c.endpointURL.Path
if streamOptions.stdout == nil {
streamOptions.stdout = ioutil.Discard
} else if t, ok := streamOptions.stdout.(io.Closer); ok {
defer t.Close()
}
if streamOptions.stderr == nil {
streamOptions.stderr = ioutil.Discard
} else if t, ok := streamOptions.stderr.(io.Closer); ok {
defer t.Close()
}
if protocol == "unix" {
dial, err := net.Dial(protocol, address)
@ -583,6 +589,8 @@ func (c *Client) hijack(method, path string, hijackOptions hijackOptions) error
return err
}
req.Header.Set("Content-Type", "plain/text")
req.Header.Set("Connection", "Upgrade")
req.Header.Set("Upgrade", "tcp")
protocol := c.endpointURL.Scheme
address := c.endpointURL.Path
if protocol != "unix" {
@ -612,13 +620,16 @@ func (c *Client) hijack(method, path string, hijackOptions hijackOptions) error
defer rwc.Close()
errChanOut := make(chan error, 1)
errChanIn := make(chan error, 1)
exit := make(chan bool)
go func() {
defer close(exit)
defer close(errChanOut)
defer func() {
if hijackOptions.in != nil {
if closer, ok := hijackOptions.in.(io.Closer); ok {
closer.Close()
}
}
}()
var err error
if hijackOptions.setRawTerminal {
// When TTY is ON, use regular copy
_, err = io.Copy(hijackOptions.stdout, br)
} else {
_, err = stdcopy.StdCopy(hijackOptions.stdout, hijackOptions.stderr, br)
@ -626,17 +637,15 @@ func (c *Client) hijack(method, path string, hijackOptions hijackOptions) error
errChanOut <- err
}()
go func() {
var err error
if hijackOptions.in != nil {
_, err := io.Copy(rwc, hijackOptions.in)
errChanIn <- err
} else {
errChanIn <- nil
_, err = io.Copy(rwc, hijackOptions.in)
}
errChanIn <- err
rwc.(interface {
CloseWrite() error
}).CloseWrite()
}()
<-exit
errIn := <-errChanIn
errOut := <-errChanOut
if errIn != nil {

View File

@ -23,7 +23,7 @@ var ErrContainerAlreadyExists = errors.New("container already exists")
// ListContainersOptions specify parameters to the ListContainers function.
//
// See http://goo.gl/6Y4Gz7 for more details.
// See https://goo.gl/47a6tO for more details.
type ListContainersOptions struct {
All bool
Size bool
@ -41,24 +41,24 @@ type APIPort struct {
IP string `json:"IP,omitempty" yaml:"IP,omitempty"`
}
// APIContainers represents a container.
//
// See http://goo.gl/QeFH7U for more details.
// APIContainers represents each container in the list returned by
// ListContainers.
type APIContainers struct {
ID string `json:"Id" yaml:"Id"`
Image string `json:"Image,omitempty" yaml:"Image,omitempty"`
Command string `json:"Command,omitempty" yaml:"Command,omitempty"`
Created int64 `json:"Created,omitempty" yaml:"Created,omitempty"`
Status string `json:"Status,omitempty" yaml:"Status,omitempty"`
Ports []APIPort `json:"Ports,omitempty" yaml:"Ports,omitempty"`
SizeRw int64 `json:"SizeRw,omitempty" yaml:"SizeRw,omitempty"`
SizeRootFs int64 `json:"SizeRootFs,omitempty" yaml:"SizeRootFs,omitempty"`
Names []string `json:"Names,omitempty" yaml:"Names,omitempty"`
ID string `json:"Id" yaml:"Id"`
Image string `json:"Image,omitempty" yaml:"Image,omitempty"`
Command string `json:"Command,omitempty" yaml:"Command,omitempty"`
Created int64 `json:"Created,omitempty" yaml:"Created,omitempty"`
Status string `json:"Status,omitempty" yaml:"Status,omitempty"`
Ports []APIPort `json:"Ports,omitempty" yaml:"Ports,omitempty"`
SizeRw int64 `json:"SizeRw,omitempty" yaml:"SizeRw,omitempty"`
SizeRootFs int64 `json:"SizeRootFs,omitempty" yaml:"SizeRootFs,omitempty"`
Names []string `json:"Names,omitempty" yaml:"Names,omitempty"`
Labels map[string]string `json:"Labels,omitempty" yaml:"Labels, omitempty"`
}
// ListContainers returns a slice of containers matching the given criteria.
//
// See http://goo.gl/6Y4Gz7 for more details.
// See https://goo.gl/47a6tO for more details.
func (c *Client) ListContainers(opts ListContainersOptions) ([]APIContainers, error) {
path := "/containers/json?" + queryString(opts)
body, _, err := c.do("GET", path, doOptions{})
@ -213,9 +213,21 @@ type Config struct {
NetworkDisabled bool `json:"NetworkDisabled,omitempty" yaml:"NetworkDisabled,omitempty"`
SecurityOpts []string `json:"SecurityOpts,omitempty" yaml:"SecurityOpts,omitempty"`
OnBuild []string `json:"OnBuild,omitempty" yaml:"OnBuild,omitempty"`
Mounts []Mount `json:"Mounts,omitempty" yaml:"Mounts,omitempty"`
Labels map[string]string `json:"Labels,omitempty" yaml:"Labels,omitempty"`
}
// Mount represents a mount point in the container.
//
// It has been added in the version 1.20 of the Docker API, available since
// Docker 1.8.
type Mount struct {
Source string
Destination string
Mode string
RW bool
}
// LogConfig defines the log driver type and the configuration for it.
type LogConfig struct {
Type string `json:"Type,omitempty" yaml:"Type,omitempty"`
@ -279,7 +291,7 @@ type Container struct {
// RenameContainerOptions specify parameters to the RenameContainer function.
//
// See http://goo.gl/L00hoj for more details.
// See https://goo.gl/laSOIy for more details.
type RenameContainerOptions struct {
// ID of container to rename
ID string `qs:"-"`
@ -290,7 +302,7 @@ type RenameContainerOptions struct {
// RenameContainer updates and existing containers name
//
// See http://goo.gl/L00hoj for more details.
// See https://goo.gl/laSOIy for more details.
func (c *Client) RenameContainer(opts RenameContainerOptions) error {
_, _, err := c.do("POST", fmt.Sprintf("/containers/"+opts.ID+"/rename?%s", queryString(opts)), doOptions{})
return err
@ -298,7 +310,7 @@ func (c *Client) RenameContainer(opts RenameContainerOptions) error {
// InspectContainer returns information about a container by its ID.
//
// See http://goo.gl/CxVuJ5 for more details.
// See https://goo.gl/RdIq0b for more details.
func (c *Client) InspectContainer(id string) (*Container, error) {
path := "/containers/" + id + "/json"
body, status, err := c.do("GET", path, doOptions{})
@ -318,7 +330,7 @@ func (c *Client) InspectContainer(id string) (*Container, error) {
// ContainerChanges returns changes in the filesystem of the given container.
//
// See http://goo.gl/QkW9sH for more details.
// See https://goo.gl/9GsTIF for more details.
func (c *Client) ContainerChanges(id string) ([]Change, error) {
path := "/containers/" + id + "/changes"
body, status, err := c.do("GET", path, doOptions{})
@ -338,7 +350,7 @@ func (c *Client) ContainerChanges(id string) ([]Change, error) {
// CreateContainerOptions specify parameters to the CreateContainer function.
//
// See http://goo.gl/2xxQQK for more details.
// See https://goo.gl/WxQzrr for more details.
type CreateContainerOptions struct {
Name string
Config *Config `qs:"-"`
@ -348,7 +360,7 @@ type CreateContainerOptions struct {
// CreateContainer creates a new container, returning the container instance,
// or an error in case of failure.
//
// See http://goo.gl/mErxNp for more details.
// See https://goo.gl/WxQzrr for more details.
func (c *Client) CreateContainer(opts CreateContainerOptions) (*Container, error) {
path := "/containers/create?" + queryString(opts)
body, status, err := c.do(
@ -434,41 +446,46 @@ type Device struct {
// HostConfig contains the container options related to starting a container on
// a given host
type HostConfig struct {
Binds []string `json:"Binds,omitempty" yaml:"Binds,omitempty"`
CapAdd []string `json:"CapAdd,omitempty" yaml:"CapAdd,omitempty"`
CapDrop []string `json:"CapDrop,omitempty" yaml:"CapDrop,omitempty"`
ContainerIDFile string `json:"ContainerIDFile,omitempty" yaml:"ContainerIDFile,omitempty"`
LxcConf []KeyValuePair `json:"LxcConf,omitempty" yaml:"LxcConf,omitempty"`
Privileged bool `json:"Privileged,omitempty" yaml:"Privileged,omitempty"`
PortBindings map[Port][]PortBinding `json:"PortBindings,omitempty" yaml:"PortBindings,omitempty"`
Links []string `json:"Links,omitempty" yaml:"Links,omitempty"`
PublishAllPorts bool `json:"PublishAllPorts,omitempty" yaml:"PublishAllPorts,omitempty"`
DNS []string `json:"Dns,omitempty" yaml:"Dns,omitempty"` // For Docker API v1.10 and above only
DNSSearch []string `json:"DnsSearch,omitempty" yaml:"DnsSearch,omitempty"`
ExtraHosts []string `json:"ExtraHosts,omitempty" yaml:"ExtraHosts,omitempty"`
VolumesFrom []string `json:"VolumesFrom,omitempty" yaml:"VolumesFrom,omitempty"`
NetworkMode string `json:"NetworkMode,omitempty" yaml:"NetworkMode,omitempty"`
IpcMode string `json:"IpcMode,omitempty" yaml:"IpcMode,omitempty"`
PidMode string `json:"PidMode,omitempty" yaml:"PidMode,omitempty"`
UTSMode string `json:"UTSMode,omitempty" yaml:"UTSMode,omitempty"`
RestartPolicy RestartPolicy `json:"RestartPolicy,omitempty" yaml:"RestartPolicy,omitempty"`
Devices []Device `json:"Devices,omitempty" yaml:"Devices,omitempty"`
LogConfig LogConfig `json:"LogConfig,omitempty" yaml:"LogConfig,omitempty"`
ReadonlyRootfs bool `json:"ReadonlyRootfs,omitempty" yaml:"ReadonlyRootfs,omitempty"`
SecurityOpt []string `json:"SecurityOpt,omitempty" yaml:"SecurityOpt,omitempty"`
CgroupParent string `json:"CgroupParent,omitempty" yaml:"CgroupParent,omitempty"`
Memory int64 `json:"Memory,omitempty" yaml:"Memory,omitempty"`
MemorySwap int64 `json:"MemorySwap,omitempty" yaml:"MemorySwap,omitempty"`
CPUShares int64 `json:"CpuShares,omitempty" yaml:"CpuShares,omitempty"`
CPUSet string `json:"Cpuset,omitempty" yaml:"Cpuset,omitempty"`
CPUQuota int64 `json:"CpuQuota,omitempty" yaml:"CpuQuota,omitempty"`
CPUPeriod int64 `json:"CpuPeriod,omitempty" yaml:"CpuPeriod,omitempty"`
Ulimits []ULimit `json:"Ulimits,omitempty" yaml:"Ulimits,omitempty"`
Binds []string `json:"Binds,omitempty" yaml:"Binds,omitempty"`
CapAdd []string `json:"CapAdd,omitempty" yaml:"CapAdd,omitempty"`
CapDrop []string `json:"CapDrop,omitempty" yaml:"CapDrop,omitempty"`
ContainerIDFile string `json:"ContainerIDFile,omitempty" yaml:"ContainerIDFile,omitempty"`
LxcConf []KeyValuePair `json:"LxcConf,omitempty" yaml:"LxcConf,omitempty"`
Privileged bool `json:"Privileged,omitempty" yaml:"Privileged,omitempty"`
PortBindings map[Port][]PortBinding `json:"PortBindings,omitempty" yaml:"PortBindings,omitempty"`
Links []string `json:"Links,omitempty" yaml:"Links,omitempty"`
PublishAllPorts bool `json:"PublishAllPorts,omitempty" yaml:"PublishAllPorts,omitempty"`
DNS []string `json:"Dns,omitempty" yaml:"Dns,omitempty"` // For Docker API v1.10 and above only
DNSSearch []string `json:"DnsSearch,omitempty" yaml:"DnsSearch,omitempty"`
ExtraHosts []string `json:"ExtraHosts,omitempty" yaml:"ExtraHosts,omitempty"`
VolumesFrom []string `json:"VolumesFrom,omitempty" yaml:"VolumesFrom,omitempty"`
NetworkMode string `json:"NetworkMode,omitempty" yaml:"NetworkMode,omitempty"`
IpcMode string `json:"IpcMode,omitempty" yaml:"IpcMode,omitempty"`
PidMode string `json:"PidMode,omitempty" yaml:"PidMode,omitempty"`
UTSMode string `json:"UTSMode,omitempty" yaml:"UTSMode,omitempty"`
RestartPolicy RestartPolicy `json:"RestartPolicy,omitempty" yaml:"RestartPolicy,omitempty"`
Devices []Device `json:"Devices,omitempty" yaml:"Devices,omitempty"`
LogConfig LogConfig `json:"LogConfig,omitempty" yaml:"LogConfig,omitempty"`
ReadonlyRootfs bool `json:"ReadonlyRootfs,omitempty" yaml:"ReadonlyRootfs,omitempty"`
SecurityOpt []string `json:"SecurityOpt,omitempty" yaml:"SecurityOpt,omitempty"`
CgroupParent string `json:"CgroupParent,omitempty" yaml:"CgroupParent,omitempty"`
Memory int64 `json:"Memory,omitempty" yaml:"Memory,omitempty"`
MemorySwap int64 `json:"MemorySwap,omitempty" yaml:"MemorySwap,omitempty"`
MemorySwappiness int64 `json:"MemorySwappiness,omitempty" yaml:"MemorySwappiness,omitempty"`
OOMKillDisable bool `json:"OomKillDisable,omitempty" yaml:"OomKillDisable"`
CPUShares int64 `json:"CpuShares,omitempty" yaml:"CpuShares,omitempty"`
CPUSet string `json:"Cpuset,omitempty" yaml:"Cpuset,omitempty"`
CPUSetCPUs string `json:"CpusetCpus,omitempty" yaml:"CpusetCpus,omitempty"`
CPUSetMEMs string `json:"CpusetMems,omitempty" yaml:"CpusetMems,omitempty"`
CPUQuota int64 `json:"CpuQuota,omitempty" yaml:"CpuQuota,omitempty"`
CPUPeriod int64 `json:"CpuPeriod,omitempty" yaml:"CpuPeriod,omitempty"`
BlkioWeight int64 `json:"BlkioWeight,omitempty" yaml:"BlkioWeight"`
Ulimits []ULimit `json:"Ulimits,omitempty" yaml:"Ulimits,omitempty"`
}
// StartContainer starts a container, returning an error in case of failure.
//
// See http://goo.gl/iM5GYs for more details.
// See https://goo.gl/MrBAJv for more details.
func (c *Client) StartContainer(id string, hostConfig *HostConfig) error {
path := "/containers/" + id + "/start"
_, status, err := c.do("POST", path, doOptions{data: hostConfig, forceJSON: true})
@ -487,7 +504,7 @@ func (c *Client) StartContainer(id string, hostConfig *HostConfig) error {
// StopContainer stops a container, killing it after the given timeout (in
// seconds).
//
// See http://goo.gl/EbcpXt for more details.
// See https://goo.gl/USqsFt for more details.
func (c *Client) StopContainer(id string, timeout uint) error {
path := fmt.Sprintf("/containers/%s/stop?t=%d", id, timeout)
_, status, err := c.do("POST", path, doOptions{})
@ -506,7 +523,7 @@ func (c *Client) StopContainer(id string, timeout uint) error {
// RestartContainer stops a container, killing it after the given timeout (in
// seconds), during the stop process.
//
// See http://goo.gl/VOzR2n for more details.
// See https://goo.gl/QzsDnz for more details.
func (c *Client) RestartContainer(id string, timeout uint) error {
path := fmt.Sprintf("/containers/%s/restart?t=%d", id, timeout)
_, status, err := c.do("POST", path, doOptions{})
@ -521,7 +538,7 @@ func (c *Client) RestartContainer(id string, timeout uint) error {
// PauseContainer pauses the given container.
//
// See http://goo.gl/AM5t42 for more details.
// See https://goo.gl/OF7W9X for more details.
func (c *Client) PauseContainer(id string) error {
path := fmt.Sprintf("/containers/%s/pause", id)
_, status, err := c.do("POST", path, doOptions{})
@ -536,7 +553,7 @@ func (c *Client) PauseContainer(id string) error {
// UnpauseContainer unpauses the given container.
//
// See http://goo.gl/eBrNSL for more details.
// See https://goo.gl/7dwyPA for more details.
func (c *Client) UnpauseContainer(id string) error {
path := fmt.Sprintf("/containers/%s/unpause", id)
_, status, err := c.do("POST", path, doOptions{})
@ -552,7 +569,7 @@ func (c *Client) UnpauseContainer(id string) error {
// TopResult represents the list of processes running in a container, as
// returned by /containers/<id>/top.
//
// See http://goo.gl/qu4gse for more details.
// See https://goo.gl/Rb46aY for more details.
type TopResult struct {
Titles []string
Processes [][]string
@ -560,7 +577,7 @@ type TopResult struct {
// TopContainer returns processes running inside a container
//
// See http://goo.gl/qu4gse for more details.
// See https://goo.gl/Rb46aY for more details.
func (c *Client) TopContainer(id string, psArgs string) (TopResult, error) {
var args string
var result TopResult
@ -584,7 +601,7 @@ func (c *Client) TopContainer(id string, psArgs string) (TopResult, error) {
// Stats represents container statistics, returned by /containers/<id>/stats.
//
// See http://goo.gl/DFMiYD for more details.
// See https://goo.gl/GNmLHb for more details.
type Stats struct {
Read time.Time `json:"read,omitempty" yaml:"read,omitempty"`
Network struct {
@ -674,7 +691,7 @@ type BlkioStatsEntry struct {
// StatsOptions specify parameters to the Stats function.
//
// See http://goo.gl/DFMiYD for more details.
// See https://goo.gl/GNmLHb for more details.
type StatsOptions struct {
ID string
Stats chan<- *Stats
@ -690,9 +707,10 @@ type StatsOptions struct {
// This function is blocking, similar to a streaming call for logs, and should be run
// on a separate goroutine from the caller. Note that this function will block until
// the given container is removed, not just exited. When finished, this function
// will close the given channel. Alternatively, function can be stopped by signaling on the Done channel
// will close the given channel. Alternatively, function can be stopped by
// signaling on the Done channel.
//
// See http://goo.gl/DFMiYD for more details.
// See https://goo.gl/GNmLHb for more details.
func (c *Client) Stats(opts StatsOptions) (retErr error) {
errC := make(chan error, 1)
readCloser, writeCloser := io.Pipe()
@ -763,7 +781,7 @@ func (c *Client) Stats(opts StatsOptions) (retErr error) {
// KillContainerOptions represents the set of options that can be used in a
// call to KillContainer.
//
// See http://goo.gl/TFkECx for more details.
// See https://goo.gl/hkS9i8 for more details.
type KillContainerOptions struct {
// The ID of the container.
ID string `qs:"-"`
@ -773,9 +791,10 @@ type KillContainerOptions struct {
Signal Signal
}
// KillContainer kills a container, returning an error in case of failure.
// KillContainer sends a signal to a container, returning an error in case of
// failure.
//
// See http://goo.gl/TFkECx for more details.
// See https://goo.gl/hkS9i8 for more details.
func (c *Client) KillContainer(opts KillContainerOptions) error {
path := "/containers/" + opts.ID + "/kill" + "?" + queryString(opts)
_, status, err := c.do("POST", path, doOptions{})
@ -790,7 +809,7 @@ func (c *Client) KillContainer(opts KillContainerOptions) error {
// RemoveContainerOptions encapsulates options to remove a container.
//
// See http://goo.gl/ZB83ji for more details.
// See https://goo.gl/RQyX62 for more details.
type RemoveContainerOptions struct {
// The ID of the container.
ID string `qs:"-"`
@ -806,7 +825,7 @@ type RemoveContainerOptions struct {
// RemoveContainer removes a container, returning an error in case of failure.
//
// See http://goo.gl/ZB83ji for more details.
// See https://goo.gl/RQyX62 for more details.
func (c *Client) RemoveContainer(opts RemoveContainerOptions) error {
path := "/containers/" + opts.ID + "?" + queryString(opts)
_, status, err := c.do("DELETE", path, doOptions{})
@ -822,7 +841,7 @@ func (c *Client) RemoveContainer(opts RemoveContainerOptions) error {
// CopyFromContainerOptions is the set of options that can be used when copying
// files or folders from a container.
//
// See http://goo.gl/rINMlw for more details.
// See https://goo.gl/4L7b07 for more details.
type CopyFromContainerOptions struct {
OutputStream io.Writer `json:"-"`
Container string `json:"-"`
@ -832,7 +851,7 @@ type CopyFromContainerOptions struct {
// CopyFromContainer copy files or folders from a container, using a given
// resource.
//
// See http://goo.gl/rINMlw for more details.
// See https://goo.gl/4L7b07 for more details.
func (c *Client) CopyFromContainer(opts CopyFromContainerOptions) error {
if opts.Container == "" {
return &NoSuchContainer{ID: opts.Container}
@ -852,7 +871,7 @@ func (c *Client) CopyFromContainer(opts CopyFromContainerOptions) error {
// WaitContainer blocks until the given container stops, return the exit code
// of the container status.
//
// See http://goo.gl/J88DHU for more details.
// See https://goo.gl/Gc1rge for more details.
func (c *Client) WaitContainer(id string) (int, error) {
body, status, err := c.do("POST", "/containers/"+id+"/wait", doOptions{})
if status == http.StatusNotFound {
@ -871,7 +890,7 @@ func (c *Client) WaitContainer(id string) (int, error) {
// CommitContainerOptions aggregates parameters to the CommitContainer method.
//
// See http://goo.gl/Jn8pe8 for more details.
// See https://goo.gl/mqfoCw for more details.
type CommitContainerOptions struct {
Container string
Repository string `qs:"repo"`
@ -883,7 +902,7 @@ type CommitContainerOptions struct {
// CommitContainer creates a new image from a container's changes.
//
// See http://goo.gl/Jn8pe8 for more details.
// See https://goo.gl/mqfoCw for more details.
func (c *Client) CommitContainer(opts CommitContainerOptions) (*Image, error) {
path := "/commit?" + queryString(opts)
body, status, err := c.do("POST", path, doOptions{data: opts.Run})
@ -904,7 +923,7 @@ func (c *Client) CommitContainer(opts CommitContainerOptions) (*Image, error) {
// AttachToContainerOptions is the set of options that can be used when
// attaching to a container.
//
// See http://goo.gl/RRAhws for more details.
// See https://goo.gl/NKpkFk for more details.
type AttachToContainerOptions struct {
Container string `qs:"-"`
InputStream io.Reader `qs:"-"`
@ -939,7 +958,7 @@ type AttachToContainerOptions struct {
// AttachToContainer attaches to a container, using the given options.
//
// See http://goo.gl/RRAhws for more details.
// See https://goo.gl/NKpkFk for more details.
func (c *Client) AttachToContainer(opts AttachToContainerOptions) error {
if opts.Container == "" {
return &NoSuchContainer{ID: opts.Container}
@ -957,7 +976,7 @@ func (c *Client) AttachToContainer(opts AttachToContainerOptions) error {
// LogsOptions represents the set of options used when getting logs from a
// container.
//
// See http://goo.gl/rLhKSU for more details.
// See https://goo.gl/yl8PGm for more details.
type LogsOptions struct {
Container string `qs:"-"`
OutputStream io.Writer `qs:"-"`
@ -975,7 +994,7 @@ type LogsOptions struct {
// Logs gets stdout and stderr logs from the specified container.
//
// See http://goo.gl/rLhKSU for more details.
// See https://goo.gl/yl8PGm for more details.
func (c *Client) Logs(opts LogsOptions) error {
if opts.Container == "" {
return &NoSuchContainer{ID: opts.Container}
@ -992,6 +1011,8 @@ func (c *Client) Logs(opts LogsOptions) error {
}
// ResizeContainerTTY resizes the terminal to the given height and width.
//
// See https://goo.gl/xERhCc for more details.
func (c *Client) ResizeContainerTTY(id string, height, width int) error {
params := make(url.Values)
params.Set("h", strconv.Itoa(height))
@ -1003,7 +1024,7 @@ func (c *Client) ResizeContainerTTY(id string, height, width int) error {
// ExportContainerOptions is the set of parameters to the ExportContainer
// method.
//
// See http://goo.gl/hnzE62 for more details.
// See https://goo.gl/dOkTyk for more details.
type ExportContainerOptions struct {
ID string
OutputStream io.Writer
@ -1012,7 +1033,7 @@ type ExportContainerOptions struct {
// ExportContainer export the contents of container id as tar archive
// and prints the exported contents to stdout.
//
// See http://goo.gl/hnzE62 for more details.
// See https://goo.gl/dOkTyk for more details.
func (c *Client) ExportContainer(opts ExportContainerOptions) error {
if opts.ID == "" {
return &NoSuchContainer{ID: opts.ID}

View File

@ -1122,10 +1122,7 @@ func TestAttachToContainerRawTerminalFalse(t *testing.T) {
Stream: true,
RawTerminal: false,
}
err := client.AttachToContainer(opts)
if err != nil {
t.Fatal(err)
}
client.AttachToContainer(opts)
expected := map[string][]string{
"stdin": {"1"},
"stdout": {"1"},

View File

@ -2,8 +2,6 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Docs can currently be found at https://github.com/docker/docker/blob/master/docs/sources/reference/api/docker_remote_api_v1.15.md#exec-create
package docker
import (
@ -15,9 +13,15 @@ import (
"strconv"
)
// Exec is the type representing a `docker exec` instance and containing the
// instance ID
type Exec struct {
ID string `json:"Id,omitempty" yaml:"Id,omitempty"`
}
// CreateExecOptions specify parameters to the CreateExecContainer function.
//
// See http://goo.gl/8izrzI for more details
// See https://goo.gl/1KSIb7 for more details
type CreateExecOptions struct {
AttachStdin bool `json:"AttachStdin,omitempty" yaml:"AttachStdin,omitempty"`
AttachStdout bool `json:"AttachStdout,omitempty" yaml:"AttachStdout,omitempty"`
@ -28,9 +32,31 @@ type CreateExecOptions struct {
User string `json:"User,omitempty" yaml:"User,omitempty"`
}
// CreateExec sets up an exec instance in a running container `id`, returning the exec
// instance, or an error in case of failure.
//
// See https://goo.gl/1KSIb7 for more details
func (c *Client) CreateExec(opts CreateExecOptions) (*Exec, error) {
path := fmt.Sprintf("/containers/%s/exec", opts.Container)
body, status, err := c.do("POST", path, doOptions{data: opts})
if status == http.StatusNotFound {
return nil, &NoSuchContainer{ID: opts.Container}
}
if err != nil {
return nil, err
}
var exec Exec
err = json.Unmarshal(body, &exec)
if err != nil {
return nil, err
}
return &exec, nil
}
// StartExecOptions specify parameters to the StartExecContainer function.
//
// See http://goo.gl/JW8Lxl for more details
// See https://goo.gl/iQCnto for more details
type StartExecOptions struct {
Detach bool `json:"Detach,omitempty" yaml:"Detach,omitempty"`
@ -51,67 +77,11 @@ type StartExecOptions struct {
Success chan struct{} `json:"-"`
}
// Exec is the type representing a `docker exec` instance and containing the
// instance ID
type Exec struct {
ID string `json:"Id,omitempty" yaml:"Id,omitempty"`
}
// ExecProcessConfig is a type describing the command associated to a Exec
// instance. It's used in the ExecInspect type.
//
// See http://goo.gl/ypQULN for more details
type ExecProcessConfig struct {
Privileged bool `json:"privileged,omitempty" yaml:"privileged,omitempty"`
User string `json:"user,omitempty" yaml:"user,omitempty"`
Tty bool `json:"tty,omitempty" yaml:"tty,omitempty"`
EntryPoint string `json:"entrypoint,omitempty" yaml:"entrypoint,omitempty"`
Arguments []string `json:"arguments,omitempty" yaml:"arguments,omitempty"`
}
// ExecInspect is a type with details about a exec instance, including the
// exit code if the command has finished running. It's returned by a api
// call to /exec/(id)/json
//
// See http://goo.gl/ypQULN for more details
type ExecInspect struct {
ID string `json:"ID,omitempty" yaml:"ID,omitempty"`
Running bool `json:"Running,omitempty" yaml:"Running,omitempty"`
ExitCode int `json:"ExitCode,omitempty" yaml:"ExitCode,omitempty"`
OpenStdin bool `json:"OpenStdin,omitempty" yaml:"OpenStdin,omitempty"`
OpenStderr bool `json:"OpenStderr,omitempty" yaml:"OpenStderr,omitempty"`
OpenStdout bool `json:"OpenStdout,omitempty" yaml:"OpenStdout,omitempty"`
ProcessConfig ExecProcessConfig `json:"ProcessConfig,omitempty" yaml:"ProcessConfig,omitempty"`
Container Container `json:"Container,omitempty" yaml:"Container,omitempty"`
}
// CreateExec sets up an exec instance in a running container `id`, returning the exec
// instance, or an error in case of failure.
//
// See http://goo.gl/8izrzI for more details
func (c *Client) CreateExec(opts CreateExecOptions) (*Exec, error) {
path := fmt.Sprintf("/containers/%s/exec", opts.Container)
body, status, err := c.do("POST", path, doOptions{data: opts})
if status == http.StatusNotFound {
return nil, &NoSuchContainer{ID: opts.Container}
}
if err != nil {
return nil, err
}
var exec Exec
err = json.Unmarshal(body, &exec)
if err != nil {
return nil, err
}
return &exec, nil
}
// StartExec starts a previously set up exec instance id. If opts.Detach is
// true, it returns after starting the exec command. Otherwise, it sets up an
// interactive session with the exec command.
//
// See http://goo.gl/JW8Lxl for more details
// See https://goo.gl/iQCnto for more details
func (c *Client) StartExec(id string, opts StartExecOptions) error {
if id == "" {
return &NoSuchExec{ID: id}
@ -144,7 +114,7 @@ func (c *Client) StartExec(id string, opts StartExecOptions) error {
// is valid only if Tty was specified as part of creating and starting the exec
// command.
//
// See http://goo.gl/YDSx1f for more details
// See https://goo.gl/e1JpsA for more details
func (c *Client) ResizeExecTTY(id string, height, width int) error {
params := make(url.Values)
params.Set("h", strconv.Itoa(height))
@ -155,9 +125,35 @@ func (c *Client) ResizeExecTTY(id string, height, width int) error {
return err
}
// ExecProcessConfig is a type describing the command associated to a Exec
// instance. It's used in the ExecInspect type.
type ExecProcessConfig struct {
Privileged bool `json:"privileged,omitempty" yaml:"privileged,omitempty"`
User string `json:"user,omitempty" yaml:"user,omitempty"`
Tty bool `json:"tty,omitempty" yaml:"tty,omitempty"`
EntryPoint string `json:"entrypoint,omitempty" yaml:"entrypoint,omitempty"`
Arguments []string `json:"arguments,omitempty" yaml:"arguments,omitempty"`
}
// ExecInspect is a type with details about a exec instance, including the
// exit code if the command has finished running. It's returned by a api
// call to /exec/(id)/json
//
// See https://goo.gl/gPtX9R for more details
type ExecInspect struct {
ID string `json:"ID,omitempty" yaml:"ID,omitempty"`
Running bool `json:"Running,omitempty" yaml:"Running,omitempty"`
ExitCode int `json:"ExitCode,omitempty" yaml:"ExitCode,omitempty"`
OpenStdin bool `json:"OpenStdin,omitempty" yaml:"OpenStdin,omitempty"`
OpenStderr bool `json:"OpenStderr,omitempty" yaml:"OpenStderr,omitempty"`
OpenStdout bool `json:"OpenStdout,omitempty" yaml:"OpenStdout,omitempty"`
ProcessConfig ExecProcessConfig `json:"ProcessConfig,omitempty" yaml:"ProcessConfig,omitempty"`
Container Container `json:"Container,omitempty" yaml:"Container,omitempty"`
}
// InspectExec returns low-level information about the exec command id.
//
// See http://goo.gl/ypQULN for more details
// See https://goo.gl/gPtX9R for more details
func (c *Client) InspectExec(id string) (*ExecInspect, error) {
path := fmt.Sprintf("/exec/%s/json", id)
body, status, err := c.do("GET", path, doOptions{})

View File

@ -11,7 +11,6 @@ import (
"errors"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
"os"
@ -46,16 +45,6 @@ type Image struct {
VirtualSize int64 `json:"VirtualSize,omitempty" yaml:"VirtualSize,omitempty"`
}
// ImageHistory represent a layer in an image's history returned by the
// ImageHistory call.
type ImageHistory struct {
ID string `json:"Id" yaml:"Id"`
Tags []string `json:"Tags,omitempty" yaml:"Tags,omitempty"`
Created int64 `json:"Created,omitempty" yaml:"Created,omitempty"`
CreatedBy string `json:"CreatedBy,omitempty" yaml:"CreatedBy,omitempty"`
Size int64 `json:"Size,omitempty" yaml:"Size,omitempty"`
}
// ImagePre012 serves the same purpose as the Image type except that it is for
// earlier versions of the Docker API (pre-012 to be specific)
type ImagePre012 struct {
@ -72,15 +61,6 @@ type ImagePre012 struct {
Size int64 `json:"size,omitempty"`
}
// ListImagesOptions specify parameters to the ListImages function.
//
// See http://goo.gl/HRVN1Z for more details.
type ListImagesOptions struct {
All bool
Filters map[string][]string
Digests bool
}
var (
// ErrNoSuchImage is the error returned when the image does not exist.
ErrNoSuchImage = errors.New("no such image")
@ -102,9 +82,18 @@ var (
ErrMustSpecifyNames = errors.New("must specify at least one name to export")
)
// ListImagesOptions specify parameters to the ListImages function.
//
// See https://goo.gl/xBe1u3 for more details.
type ListImagesOptions struct {
All bool
Filters map[string][]string
Digests bool
}
// ListImages returns the list of available images in the server.
//
// See http://goo.gl/HRVN1Z for more details.
// See https://goo.gl/xBe1u3 for more details.
func (c *Client) ListImages(opts ListImagesOptions) ([]APIImages, error) {
path := "/images/json?" + queryString(opts)
body, _, err := c.do("GET", path, doOptions{})
@ -119,9 +108,19 @@ func (c *Client) ListImages(opts ListImagesOptions) ([]APIImages, error) {
return images, nil
}
// ImageHistory represent a layer in an image's history returned by the
// ImageHistory call.
type ImageHistory struct {
ID string `json:"Id" yaml:"Id"`
Tags []string `json:"Tags,omitempty" yaml:"Tags,omitempty"`
Created int64 `json:"Created,omitempty" yaml:"Created,omitempty"`
CreatedBy string `json:"CreatedBy,omitempty" yaml:"CreatedBy,omitempty"`
Size int64 `json:"Size,omitempty" yaml:"Size,omitempty"`
}
// ImageHistory returns the history of the image by its name or ID.
//
// See http://goo.gl/2oJmNs for more details.
// See https://goo.gl/8bnTId for more details.
func (c *Client) ImageHistory(name string) ([]ImageHistory, error) {
body, status, err := c.do("GET", "/images/"+name+"/history", doOptions{})
if status == http.StatusNotFound {
@ -140,7 +139,7 @@ func (c *Client) ImageHistory(name string) ([]ImageHistory, error) {
// RemoveImage removes an image by its name or ID.
//
// See http://goo.gl/znj0wM for more details.
// See https://goo.gl/V3ZWnK for more details.
func (c *Client) RemoveImage(name string) error {
_, status, err := c.do("DELETE", "/images/"+name, doOptions{})
if status == http.StatusNotFound {
@ -152,7 +151,7 @@ func (c *Client) RemoveImage(name string) error {
// RemoveImageOptions present the set of options available for removing an image
// from a registry.
//
// See http://goo.gl/6V48bF for more details.
// See https://goo.gl/V3ZWnK for more details.
type RemoveImageOptions struct {
Force bool `qs:"force"`
NoPrune bool `qs:"noprune"`
@ -161,7 +160,7 @@ type RemoveImageOptions struct {
// RemoveImageExtended removes an image by its name or ID.
// Extra params can be passed, see RemoveImageOptions
//
// See http://goo.gl/znj0wM for more details.
// See https://goo.gl/V3ZWnK for more details.
func (c *Client) RemoveImageExtended(name string, opts RemoveImageOptions) error {
uri := fmt.Sprintf("/images/%s?%s", name, queryString(&opts))
_, status, err := c.do("DELETE", uri, doOptions{})
@ -173,7 +172,7 @@ func (c *Client) RemoveImageExtended(name string, opts RemoveImageOptions) error
// InspectImage returns an image by its name or ID.
//
// See http://goo.gl/Q112NY for more details.
// See https://goo.gl/jHPcg6 for more details.
func (c *Client) InspectImage(name string) (*Image, error) {
body, status, err := c.do("GET", "/images/"+name+"/json", doOptions{})
if status == http.StatusNotFound {
@ -216,7 +215,7 @@ func (c *Client) InspectImage(name string) (*Image, error) {
// PushImageOptions represents options to use in the PushImage method.
//
// See http://goo.gl/pN8A3P for more details.
// See https://goo.gl/zPtZaT for more details.
type PushImageOptions struct {
// Name of the image
Name string
@ -236,7 +235,7 @@ type PushImageOptions struct {
// An empty instance of AuthConfiguration may be used for unauthenticated
// pushes.
//
// See http://goo.gl/pN8A3P for more details.
// See https://goo.gl/zPtZaT for more details.
func (c *Client) PushImage(opts PushImageOptions, auth AuthConfiguration) error {
if opts.Name == "" {
return ErrNoSuchImage
@ -259,7 +258,7 @@ func (c *Client) PushImage(opts PushImageOptions, auth AuthConfiguration) error
// PullImageOptions present the set of options available for pulling an image
// from a registry.
//
// See http://goo.gl/ACyYNS for more details.
// See https://goo.gl/iJkZjD for more details.
type PullImageOptions struct {
Repository string `qs:"fromImage"`
Registry string
@ -268,9 +267,10 @@ type PullImageOptions struct {
RawJSONStream bool `qs:"-"`
}
// PullImage pulls an image from a remote registry, logging progress to opts.OutputStream.
// PullImage pulls an image from a remote registry, logging progress to
// opts.OutputStream.
//
// See http://goo.gl/ACyYNS for more details.
// See https://goo.gl/iJkZjD for more details.
func (c *Client) PullImage(opts PullImageOptions, auth AuthConfiguration) error {
if opts.Repository == "" {
return ErrNoSuchImage
@ -296,14 +296,14 @@ func (c *Client) createImage(qs string, headers map[string]string, in io.Reader,
// LoadImageOptions represents the options for LoadImage Docker API Call
//
// See http://goo.gl/Y8NNCq for more details.
// See https://goo.gl/JyClMX for more details.
type LoadImageOptions struct {
InputStream io.Reader
}
// LoadImage imports a tarball docker image
//
// See http://goo.gl/Y8NNCq for more details.
// See https://goo.gl/JyClMX for more details.
func (c *Client) LoadImage(opts LoadImageOptions) error {
return c.stream("POST", "/images/load", streamOptions{
setRawTerminal: true,
@ -311,17 +311,17 @@ func (c *Client) LoadImage(opts LoadImageOptions) error {
})
}
// ExportImageOptions represent the options for ExportImage Docker API call
// ExportImageOptions represent the options for ExportImage Docker API call.
//
// See http://goo.gl/mi6kvk for more details.
// See https://goo.gl/le7vK8 for more details.
type ExportImageOptions struct {
Name string
OutputStream io.Writer
}
// ExportImage exports an image (as a tar file) into the stream
// ExportImage exports an image (as a tar file) into the stream.
//
// See http://goo.gl/mi6kvk for more details.
// See https://goo.gl/le7vK8 for more details.
func (c *Client) ExportImage(opts ExportImageOptions) error {
return c.stream("GET", fmt.Sprintf("/images/%s/get", opts.Name), streamOptions{
setRawTerminal: true,
@ -331,7 +331,7 @@ func (c *Client) ExportImage(opts ExportImageOptions) error {
// ExportImagesOptions represent the options for ExportImages Docker API call
//
// See http://goo.gl/YeZzQK for more details.
// See https://goo.gl/huC7HA for more details.
type ExportImagesOptions struct {
Names []string
OutputStream io.Writer `qs:"-"`
@ -339,7 +339,7 @@ type ExportImagesOptions struct {
// ExportImages exports one or more images (as a tar file) into the stream
//
// See http://goo.gl/YeZzQK for more details.
// See https://goo.gl/huC7HA for more details.
func (c *Client) ExportImages(opts ExportImagesOptions) error {
if opts.Names == nil || len(opts.Names) == 0 {
return ErrMustSpecifyNames
@ -353,7 +353,7 @@ func (c *Client) ExportImages(opts ExportImagesOptions) error {
// ImportImageOptions present the set of informations available for importing
// an image from a source file or the stdin.
//
// See http://goo.gl/PhBKnS for more details.
// See https://goo.gl/iJkZjD for more details.
type ImportImageOptions struct {
Repository string `qs:"repo"`
Source string `qs:"fromSrc"`
@ -366,7 +366,7 @@ type ImportImageOptions struct {
// ImportImage imports an image from a url, a file or stdin
//
// See http://goo.gl/PhBKnS for more details.
// See https://goo.gl/iJkZjD for more details.
func (c *Client) ImportImage(opts ImportImageOptions) error {
if opts.Repository == "" {
return ErrNoSuchImage
@ -379,8 +379,7 @@ func (c *Client) ImportImage(opts ImportImageOptions) error {
if err != nil {
return err
}
b, err := ioutil.ReadAll(f)
opts.InputStream = bytes.NewBuffer(b)
opts.InputStream = f
opts.Source = "-"
}
return c.createImage(queryString(&opts), nil, opts.InputStream, opts.OutputStream, opts.RawJSONStream)
@ -415,12 +414,12 @@ type BuildImageOptions struct {
// BuildImage builds an image from a tarball's url or a Dockerfile in the input
// stream.
//
// See http://goo.gl/7nuGXa for more details.
// See https://goo.gl/xySxCe for more details.
func (c *Client) BuildImage(opts BuildImageOptions) error {
if opts.OutputStream == nil {
return ErrMissingOutputStream
}
headers, err := headersWithAuth(opts.Auth, opts.AuthConfigs)
headers, err := headersWithAuth(opts.Auth, c.versionedAuthConfigs(opts.AuthConfigs))
if err != nil {
return err
}
@ -452,9 +451,19 @@ func (c *Client) BuildImage(opts BuildImageOptions) error {
})
}
func (c *Client) versionedAuthConfigs(authConfigs AuthConfigurations) interface{} {
if c.serverAPIVersion == nil {
c.checkAPIVersion()
}
if c.serverAPIVersion != nil && c.serverAPIVersion.GreaterThanOrEqualTo(apiVersion119) {
return AuthConfigurations119(authConfigs.Configs)
}
return authConfigs
}
// TagImageOptions present the set of options to tag an image.
//
// See http://goo.gl/5g6qFy for more details.
// See https://goo.gl/98ZzkU for more details.
type TagImageOptions struct {
Repo string
Tag string
@ -463,7 +472,7 @@ type TagImageOptions struct {
// TagImage adds a tag to the image identified by the given name.
//
// See http://goo.gl/5g6qFy for more details.
// See https://goo.gl/98ZzkU for more details.
func (c *Client) TagImage(name string, opts TagImageOptions) error {
if name == "" {
return ErrNoSuchImage
@ -497,7 +506,7 @@ func headersWithAuth(auths ...interface{}) (map[string]string, error) {
return nil, err
}
headers["X-Registry-Auth"] = base64.URLEncoding.EncodeToString(buf.Bytes())
case AuthConfigurations:
case AuthConfigurations, AuthConfigurations119:
var buf bytes.Buffer
if err := json.NewEncoder(&buf).Encode(auth); err != nil {
return nil, err
@ -509,9 +518,9 @@ func headersWithAuth(auths ...interface{}) (map[string]string, error) {
return headers, nil
}
// APIImageSearch reflect the result of a search on the dockerHub
// APIImageSearch reflect the result of a search on the Docker Hub.
//
// See http://goo.gl/xI5lLZ for more details.
// See https://goo.gl/AYjyrF for more details.
type APIImageSearch struct {
Description string `json:"description,omitempty" yaml:"description,omitempty"`
IsOfficial bool `json:"is_official,omitempty" yaml:"is_official,omitempty"`
@ -522,7 +531,7 @@ type APIImageSearch struct {
// SearchImages search the docker hub with a specific given term.
//
// See http://goo.gl/xI5lLZ for more details.
// See https://goo.gl/AYjyrF for more details.
func (c *Client) SearchImages(term string) ([]APIImageSearch, error) {
body, _, err := c.do("GET", "/images/search?term="+term, doOptions{})
if err != nil {

View File

@ -21,11 +21,13 @@ import (
func newTestClient(rt *FakeRoundTripper) Client {
endpoint := "http://localhost:4243"
u, _ := parseEndpoint("http://localhost:4243", false)
testAPIVersion, _ := NewAPIVersion("1.17")
client := Client{
HTTPClient: &http.Client{Transport: rt},
endpoint: endpoint,
endpointURL: u,
SkipServerVersionCheck: true,
serverAPIVersion: testAPIVersion,
}
return client
}

View File

@ -11,7 +11,7 @@ import (
// Version returns version information about the docker server.
//
// See http://goo.gl/BOZrF5 for more details.
// See https://goo.gl/ND9R8L for more details.
func (c *Client) Version() (*Env, error) {
body, _, err := c.do("GET", "/version", doOptions{})
if err != nil {
@ -26,7 +26,7 @@ func (c *Client) Version() (*Env, error) {
// Info returns system-wide information about the Docker server.
//
// See http://goo.gl/wmqZsW for more details.
// See https://goo.gl/ElTHi2 for more details.
func (c *Client) Info() (*Env, error) {
body, _, err := c.do("GET", "/info", doOptions{})
if err != nil {

View File

@ -12,6 +12,7 @@ import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
mathrand "math/rand"
"net"
"net/http"
@ -532,7 +533,7 @@ func (s *DockerServer) startContainer(w http.ResponseWriter, r *http.Request) {
}
container.HostConfig = &hostConfig
if container.State.Running {
http.Error(w, "Container already running", http.StatusBadRequest)
http.Error(w, "", http.StatusNotModified)
return
}
container.State.Running = true
@ -610,14 +611,34 @@ func (s *DockerServer) attachContainer(w http.ResponseWriter, r *http.Request) {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
wg := sync.WaitGroup{}
if r.URL.Query().Get("stdin") == "1" {
wg.Add(1)
go func() {
ioutil.ReadAll(conn)
wg.Done()
}()
}
outStream := stdcopy.NewStdWriter(conn, stdcopy.Stdout)
if container.State.Running {
fmt.Fprintf(outStream, "Container %q is running\n", container.ID)
fmt.Fprintf(outStream, "Container is running\n")
} else {
fmt.Fprintf(outStream, "Container %q is not running\n", container.ID)
fmt.Fprintf(outStream, "Container is not running\n")
}
fmt.Fprintln(outStream, "What happened?")
fmt.Fprintln(outStream, "Something happened")
wg.Wait()
if r.URL.Query().Get("stream") == "1" {
for {
time.Sleep(1e6)
s.cMut.RLock()
if !container.State.Running {
s.cMut.RUnlock()
break
}
s.cMut.RUnlock()
}
}
conn.Close()
}

View File

@ -5,9 +5,11 @@
package testing
import (
"bufio"
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"math/rand"
"net"
"net/http"
@ -624,8 +626,8 @@ func TestStartContainerAlreadyRunning(t *testing.T) {
path := fmt.Sprintf("/containers/%s/start", server.containers[0].ID)
request, _ := http.NewRequest("POST", path, bytes.NewBuffer([]byte("null")))
server.ServeHTTP(recorder, request)
if recorder.Code != http.StatusBadRequest {
t.Errorf("StartContainer: wrong status code. Want %d. Got %d.", http.StatusBadRequest, recorder.Code)
if recorder.Code != http.StatusNotModified {
t.Errorf("StartContainer: wrong status code. Want %d. Got %d.", http.StatusNotModified, recorder.Code)
}
}
@ -845,22 +847,41 @@ func TestWaitContainerNotFound(t *testing.T) {
}
}
type HijackableResponseRecorder struct {
httptest.ResponseRecorder
readCh chan []byte
}
func (r *HijackableResponseRecorder) Hijack() (net.Conn, *bufio.ReadWriter, error) {
myConn, otherConn := net.Pipe()
r.readCh = make(chan []byte)
go func() {
data, _ := ioutil.ReadAll(myConn)
r.readCh <- data
}()
return otherConn, nil, nil
}
func (r *HijackableResponseRecorder) HijackBuffer() string {
return string(<-r.readCh)
}
func TestAttachContainer(t *testing.T) {
server := DockerServer{}
addContainers(&server, 1)
server.containers[0].State.Running = true
server.buildMuxer()
recorder := httptest.NewRecorder()
recorder := &HijackableResponseRecorder{}
path := fmt.Sprintf("/containers/%s/attach?logs=1", server.containers[0].ID)
request, _ := http.NewRequest("POST", path, nil)
server.ServeHTTP(recorder, request)
lines := []string{
fmt.Sprintf("\x01\x00\x00\x00\x03\x00\x00\x00Container %q is running", server.containers[0].ID),
"What happened?",
"Something happened",
"\x01\x00\x00\x00\x00\x00\x00\x15Container is running",
"\x01\x00\x00\x00\x00\x00\x00\x0fWhat happened?",
"\x01\x00\x00\x00\x00\x00\x00\x13Something happened",
}
expected := strings.Join(lines, "\n") + "\n"
if body := recorder.Body.String(); body == expected {
if body := recorder.HijackBuffer(); body != expected {
t.Errorf("AttachContainer: wrong body. Want %q. Got %q.", expected, body)
}
}
@ -868,7 +889,7 @@ func TestAttachContainer(t *testing.T) {
func TestAttachContainerNotFound(t *testing.T) {
server := DockerServer{}
server.buildMuxer()
recorder := httptest.NewRecorder()
recorder := &HijackableResponseRecorder{}
path := "/containers/abc123/attach?logs=1"
request, _ := http.NewRequest("POST", path, nil)
server.ServeHTTP(recorder, request)
@ -877,6 +898,44 @@ func TestAttachContainerNotFound(t *testing.T) {
}
}
func TestAttachContainerWithStreamBlocks(t *testing.T) {
server := DockerServer{}
addContainers(&server, 1)
server.containers[0].State.Running = true
server.buildMuxer()
path := fmt.Sprintf("/containers/%s/attach?logs=1&stdout=1&stream=1", server.containers[0].ID)
request, _ := http.NewRequest("POST", path, nil)
done := make(chan string)
go func() {
recorder := &HijackableResponseRecorder{}
server.ServeHTTP(recorder, request)
done <- recorder.HijackBuffer()
}()
select {
case <-done:
t.Fatalf("attach stream returned before container is stopped")
case <-time.After(500 * time.Millisecond):
}
server.cMut.Lock()
server.containers[0].State.Running = false
server.cMut.Unlock()
var body string
select {
case body = <-done:
case <-time.After(5 * time.Second):
t.Fatalf("timed out waiting for attach to finish")
}
lines := []string{
"\x01\x00\x00\x00\x00\x00\x00\x15Container is running",
"\x01\x00\x00\x00\x00\x00\x00\x0fWhat happened?",
"\x01\x00\x00\x00\x00\x00\x00\x13Something happened",
}
expected := strings.Join(lines, "\n") + "\n"
if body != expected {
t.Errorf("AttachContainer: wrong body. Want %q. Got %q.", expected, body)
}
}
func TestRemoveContainer(t *testing.T) {
server := DockerServer{}
addContainers(&server, 1)
@ -1690,7 +1749,7 @@ func addNetworks(server *DockerServer, n int) {
ID: fmt.Sprintf("%x", rand.Int()%10000),
Type: "bridge",
Endpoints: []*docker.Endpoint{
&docker.Endpoint{
{
Name: "blah",
ID: fmt.Sprintf("%x", rand.Int()%10000),
Network: netid,

View File

@ -0,0 +1,118 @@
// Copyright 2015 go-dockerclient authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package docker
import (
"encoding/json"
"errors"
"net/http"
)
var (
// ErrNoSuchVolume is the error returned when the volume does not exist.
ErrNoSuchVolume = errors.New("no such volume")
// ErrVolumeInUse is the error returned when the volume requested to be removed is still in use.
ErrVolumeInUse = errors.New("volume in use and cannot be removed")
)
// Volume represents a volume.
//
// See https://goo.gl/FZA4BK for more details.
type Volume struct {
Name string `json:"Name" yaml:"Name"`
Driver string `json:"Driver,omitempty" yaml:"Driver,omitempty"`
Mountpoint string `json:"Mountpoint,omitempty" yaml:"Mountpoint,omitempty"`
}
// ListVolumesOptions specify parameters to the ListVolumes function.
//
// See https://goo.gl/FZA4BK for more details.
type ListVolumesOptions struct {
Filters map[string][]string
}
// ListVolumes returns a list of available volumes in the server.
//
// See https://goo.gl/FZA4BK for more details.
func (c *Client) ListVolumes(opts ListVolumesOptions) ([]Volume, error) {
body, _, err := c.do("GET", "/volumes?"+queryString(opts), doOptions{})
if err != nil {
return nil, err
}
m := make(map[string]interface{})
if err := json.Unmarshal(body, &m); err != nil {
return nil, err
}
var volumes []Volume
volumesJSON, ok := m["Volumes"]
if !ok {
return volumes, nil
}
data, err := json.Marshal(volumesJSON)
if err != nil {
return nil, err
}
if err := json.Unmarshal(data, &volumes); err != nil {
return nil, err
}
return volumes, nil
}
// CreateVolumeOptions specify parameters to the CreateVolume function.
//
// See https://goo.gl/pBUbZ9 for more details.
type CreateVolumeOptions struct {
Name string
Driver string
DriverOpts map[string]string
}
// CreateVolume creates a volume on the server.
//
// See https://goo.gl/pBUbZ9 for more details.
func (c *Client) CreateVolume(opts CreateVolumeOptions) (*Volume, error) {
body, _, err := c.do("POST", "/volumes", doOptions{data: opts})
if err != nil {
return nil, err
}
var volume Volume
if err := json.Unmarshal(body, &volume); err != nil {
return nil, err
}
return &volume, nil
}
// InspectVolume returns a volume by its name.
//
// See https://goo.gl/0g9A6i for more details.
func (c *Client) InspectVolume(name string) (*Volume, error) {
body, status, err := c.do("GET", "/volumes/"+name, doOptions{})
if status == http.StatusNotFound {
return nil, ErrNoSuchVolume
}
if err != nil {
return nil, err
}
var volume Volume
if err := json.Unmarshal(body, &volume); err != nil {
return nil, err
}
return &volume, nil
}
// RemoveVolume removes a volume by its name.
//
// See https://goo.gl/79GNQz for more details.
func (c *Client) RemoveVolume(name string) error {
_, status, err := c.do("DELETE", "/volumes/"+name, doOptions{})
if status == http.StatusNotFound {
return ErrNoSuchVolume
}
if status == http.StatusConflict {
return ErrVolumeInUse
}
return err
}

View File

@ -0,0 +1,142 @@
// Copyright 2015 go-dockerclient authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package docker
import (
"encoding/json"
"net/http"
"net/url"
"reflect"
"testing"
)
func TestListVolumes(t *testing.T) {
volumesData := `[
{
"Name": "tardis",
"Driver": "local",
"Mountpoint": "/var/lib/docker/volumes/tardis"
},
{
"Name": "foo",
"Driver": "bar",
"Mountpoint": "/var/lib/docker/volumes/bar"
}
]`
body := `{ "Volumes": ` + volumesData + ` }`
var expected []Volume
if err := json.Unmarshal([]byte(volumesData), &expected); err != nil {
t.Fatal(err)
}
client := newTestClient(&FakeRoundTripper{message: body, status: http.StatusOK})
volumes, err := client.ListVolumes(ListVolumesOptions{})
if err != nil {
t.Error(err)
}
if !reflect.DeepEqual(volumes, expected) {
t.Errorf("ListVolumes: Wrong return value. Want %#v. Got %#v.", expected, volumes)
}
}
func TestCreateVolume(t *testing.T) {
body := `{
"Name": "tardis",
"Driver": "local",
"Mountpoint": "/var/lib/docker/volumes/tardis"
}`
var expected Volume
if err := json.Unmarshal([]byte(body), &expected); err != nil {
t.Fatal(err)
}
fakeRT := &FakeRoundTripper{message: body, status: http.StatusOK}
client := newTestClient(fakeRT)
volume, err := client.CreateVolume(
CreateVolumeOptions{
Name: "tardis",
Driver: "local",
DriverOpts: map[string]string{
"foo": "bar",
},
},
)
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(volume, &expected) {
t.Errorf("CreateVolume: Wrong return value. Want %#v. Got %#v.", expected, volume)
}
req := fakeRT.requests[0]
expectedMethod := "POST"
if req.Method != expectedMethod {
t.Errorf("CreateVolume(): Wrong HTTP method. Want %s. Got %s.", expectedMethod, req.Method)
}
u, _ := url.Parse(client.getURL("/volumes"))
if req.URL.Path != u.Path {
t.Errorf("CreateVolume(): Wrong request path. Want %q. Got %q.", u.Path, req.URL.Path)
}
}
func TestInspectVolume(t *testing.T) {
body := `{
"Name": "tardis",
"Driver": "local",
"Mountpoint": "/var/lib/docker/volumes/tardis"
}`
var expected Volume
if err := json.Unmarshal([]byte(body), &expected); err != nil {
t.Fatal(err)
}
fakeRT := &FakeRoundTripper{message: body, status: http.StatusOK}
client := newTestClient(fakeRT)
name := "tardis"
volume, err := client.InspectVolume(name)
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(volume, &expected) {
t.Errorf("InspectVolume: Wrong return value. Want %#v. Got %#v.", expected, volume)
}
req := fakeRT.requests[0]
expectedMethod := "GET"
if req.Method != expectedMethod {
t.Errorf("InspectVolume(%q): Wrong HTTP method. Want %s. Got %s.", name, expectedMethod, req.Method)
}
u, _ := url.Parse(client.getURL("/volumes/" + name))
if req.URL.Path != u.Path {
t.Errorf("CreateVolume(%q): Wrong request path. Want %q. Got %q.", name, u.Path, req.URL.Path)
}
}
func TestRemoveVolume(t *testing.T) {
name := "test"
fakeRT := &FakeRoundTripper{message: "", status: http.StatusNoContent}
client := newTestClient(fakeRT)
if err := client.RemoveVolume(name); err != nil {
t.Fatal(err)
}
req := fakeRT.requests[0]
expectedMethod := "DELETE"
if req.Method != expectedMethod {
t.Errorf("RemoveVolume(%q): Wrong HTTP method. Want %s. Got %s.", name, expectedMethod, req.Method)
}
u, _ := url.Parse(client.getURL("/volumes/" + name))
if req.URL.Path != u.Path {
t.Errorf("RemoveVolume(%q): Wrong request path. Want %q. Got %q.", name, u.Path, req.URL.Path)
}
}
func TestRemoveVolumeNotFound(t *testing.T) {
client := newTestClient(&FakeRoundTripper{message: "no such volume", status: http.StatusNotFound})
if err := client.RemoveVolume("test:"); err != ErrNoSuchVolume {
t.Errorf("RemoveVolume: wrong error. Want %#v. Got %#v.", ErrNoSuchVolume, err)
}
}
func TestRemoveVolumeInUse(t *testing.T) {
client := newTestClient(&FakeRoundTripper{message: "volume in use and cannot be removed", status: http.StatusConflict})
if err := client.RemoveVolume("test:"); err != ErrVolumeInUse {
t.Errorf("RemoveVolume: wrong error. Want %#v. Got %#v.", ErrVolumeInUse, err)
}
}

View File

@ -422,6 +422,10 @@ func main() {
Cobra can generate a markdown formatted document based on the subcommands, flags, etc. A simple example of how to do this for your command can be found in [Markdown Docs](md_docs.md)
## Generating man pages for your command
Cobra can generate a man page based on the subcommands, flags, etc. A simple example of how to do this for your command can be found in [Man Docs](man_docs.md)
## Generating bash completions for your command
Cobra can generate a bash completions file. If you add more information to your command these completions can be amazingly powerful and flexible. Read more about [Bash Completions](bash_completions.md)

View File

@ -19,7 +19,6 @@ const (
func preamble(out *bytes.Buffer) {
fmt.Fprintf(out, `#!/bin/bash
__debug()
{
if [[ -n ${BASH_COMP_DEBUG_FILE} ]]; then
@ -27,6 +26,14 @@ __debug()
fi
}
# Homebrew on Macs have version 1.3 of bash-completion which doesn't include
# _init_completion. This is a very minimal version of that function.
__my_init_completion()
{
COMPREPLY=()
_get_comp_words_by_ref cur prev words cword
}
__index_of_word()
{
local w word=$1
@ -188,7 +195,11 @@ func postscript(out *bytes.Buffer, name string) {
fmt.Fprintf(out, "__start_%s()\n", name)
fmt.Fprintf(out, `{
local cur prev words cword
_init_completion -s || return
if declare -F _init_completions >/dev/null 2>&1; then
_init_completion -s || return
else
__my_init_completion || return
fi
local c=0
local flags=()
@ -212,7 +223,7 @@ func postscript(out *bytes.Buffer, name string) {
func writeCommands(cmd *Command, out *bytes.Buffer) {
fmt.Fprintf(out, " commands=()\n")
for _, c := range cmd.Commands() {
if len(c.Deprecated) > 0 {
if len(c.Deprecated) > 0 || c == cmd.helpCommand {
continue
}
fmt.Fprintf(out, " commands+=(%q)\n", c.Name())
@ -292,7 +303,7 @@ func writeRequiredFlag(cmd *Command, out *bytes.Buffer) {
fmt.Fprintf(out, " must_have_one_flag=()\n")
flags := cmd.NonInheritedFlags()
flags.VisitAll(func(flag *pflag.Flag) {
for key, _ := range flag.Annotations {
for key := range flag.Annotations {
switch key {
case BashCompOneRequiredFlag:
format := " must_have_one_flag+=(\"--%s"
@ -321,7 +332,7 @@ func writeRequiredNoun(cmd *Command, out *bytes.Buffer) {
func gen(cmd *Command, out *bytes.Buffer) {
for _, c := range cmd.Commands() {
if len(c.Deprecated) > 0 {
if len(c.Deprecated) > 0 || c == cmd.helpCommand {
continue
}
gen(c, out)

View File

@ -25,6 +25,13 @@ import (
"text/template"
)
var templateFuncs template.FuncMap = template.FuncMap{
"trim": strings.TrimSpace,
"rpad": rpad,
"gt": Gt,
"eq": Eq,
}
var initializers []func()
// automatic prefix matching can be a dangerous thing to automatically enable in CLI tools.
@ -39,6 +46,20 @@ var MousetrapHelpText string = `This is a command line tool
You need to open cmd.exe and run it from there.
`
//AddTemplateFunc adds a template function that's available to Usage and Help
//template generation.
func AddTemplateFunc(name string, tmplFunc interface{}) {
templateFuncs[name] = tmplFunc
}
//AddTemplateFuncs adds multiple template functions availalble to Usage and
//Help template generation.
func AddTemplateFuncs(tmplFuncs template.FuncMap) {
for k, v := range tmplFuncs {
templateFuncs[k] = v
}
}
//OnInitialize takes a series of func() arguments and appends them to a slice of func().
func OnInitialize(y ...func()) {
for _, x := range y {
@ -101,12 +122,7 @@ func rpad(s string, padding int) string {
// tmpl executes the given template text on data, writing the result to w.
func tmpl(w io.Writer, text string, data interface{}) error {
t := template.New("top")
t.Funcs(template.FuncMap{
"trim": strings.TrimSpace,
"rpad": rpad,
"gt": Gt,
"eq": Eq,
})
t.Funcs(templateFuncs)
template.Must(t.Parse(text))
return t.Execute(w, data)
}

View File

@ -8,6 +8,7 @@ import (
"runtime"
"strings"
"testing"
"text/template"
"github.com/spf13/pflag"
)
@ -971,3 +972,20 @@ func TestFlagOnPflagCommandLine(t *testing.T) {
checkResultContains(t, r, flagName)
}
func TestAddTemplateFunctions(t *testing.T) {
AddTemplateFunc("t", func() bool { return true })
AddTemplateFuncs(template.FuncMap{
"f": func() bool { return false },
"h": func() string { return "Hello," },
"w": func() string { return "world." }})
const usage = "Hello, world."
c := &Command{}
c.SetUsageTemplate(`{{if t}}{{h}}{{end}}{{if f}}{{h}}{{end}} {{w}}`)
if us := c.UsageString(); us != usage {
t.Errorf("c.UsageString() != \"%s\", is \"%s\"", usage, us)
}
}

View File

@ -66,14 +66,24 @@ type Command struct {
// All functions get the same args, the arguments after the command name
// PersistentPreRun: children of this command will inherit and execute
PersistentPreRun func(cmd *Command, args []string)
// PersistentPreRunE: PersistentPreRun but returns an error
PersistentPreRunE func(cmd *Command, args []string) error
// PreRun: children of this command will not inherit.
PreRun func(cmd *Command, args []string)
// PreRunE: PreRun but returns an error
PreRunE func(cmd *Command, args []string) error
// Run: Typically the actual work function. Most commands will only implement this
Run func(cmd *Command, args []string)
// RunE: Run but returns an error
RunE func(cmd *Command, args []string) error
// PostRun: run after the Run command.
PostRun func(cmd *Command, args []string)
// PostRunE: PostRun but returns an error
PostRunE func(cmd *Command, args []string) error
// PersistentPostRun: children of this command will inherit and execute after PostRun
PersistentPostRun func(cmd *Command, args []string)
// PersistentPostRunE: PersistentPostRun but returns an error
PersistentPostRunE func(cmd *Command, args []string) error
// Commands is the list of commands supported by this program.
commands []*Command
// Parent Command for this command
@ -92,7 +102,6 @@ type Command struct {
helpTemplate string // Can be defined by Application
helpFunc func(*Command, []string) // Help can be defined by application
helpCommand *Command // The help command
helpFlagVal bool
// The global normalization function that we can use on every pFlag set and children commands
globNormFunc func(f *flag.FlagSet, name string) flag.NormalizedName
}
@ -179,32 +188,21 @@ func (c *Command) UsageFunc() (f func(*Command) error) {
}
}
}
// HelpFunc returns either the function set by SetHelpFunc for this command
// or a parent, or it returns a function which calls c.Help()
func (c *Command) HelpFunc() func(*Command, []string) {
if c.helpFunc != nil {
return c.helpFunc
cmd := c
for cmd != nil {
if cmd.helpFunc != nil {
return cmd.helpFunc
}
cmd = cmd.parent
}
if c.HasParent() {
return c.parent.HelpFunc()
} else {
return func(c *Command, args []string) {
if len(args) == 0 {
// Help called without any topic, calling on root
c.Root().Help()
return
}
cmd, _, e := c.Root().Find(args)
if cmd == nil || e != nil {
c.Printf("Unknown help topic %#q.", args)
c.Root().Usage()
} else {
err := cmd.Help()
if err != nil {
c.Println(err)
}
}
return func(*Command, []string) {
err := c.Help()
if err != nil {
c.Println(err)
}
}
}
@ -270,7 +268,7 @@ Global Flags:
{{.InheritedFlags.FlagUsages}}{{end}}{{if .HasHelpSubCommands}}
Additional help topics: {{range .Commands}}{{if .IsHelpCommand}}
{{rpad .CommandPath .CommandPathPadding}} {{.Short}}{{end}}}{{end}}{{end}}{{ if .HasSubCommands }}
{{rpad .CommandPath .CommandPathPadding}} {{.Short}}{{end}}{{end}}{{end}}{{ if .HasSubCommands }}
Use "{{.CommandPath}} [command] --help" for more information about a command.
{{end}}`
@ -450,13 +448,24 @@ func (c *Command) execute(a []string) (err error) {
c.Printf("Command %q is deprecated, %s\n", c.Name(), c.Deprecated)
}
// initialize help flag as the last point possible to allow for user
// overriding
c.initHelpFlag()
err = c.ParseFlags(a)
if err != nil {
return err
}
// If help is called, regardless of other flags, return we want help
// Also say we need help if c.Run is nil.
if c.helpFlagVal || !c.Runnable() {
// Also say we need help if the command isn't runnable.
helpVal, err := c.Flags().GetBool("help")
if err != nil {
// should be impossible to get here as we always declare a help
// flag in initHelpFlag()
c.Println("\"help\" flag declared as non-bool. Please correct your code")
return err
}
if helpVal || !c.Runnable() {
return flag.ErrHelp
}
@ -464,22 +473,45 @@ func (c *Command) execute(a []string) (err error) {
argWoFlags := c.Flags().Args()
for p := c; p != nil; p = p.Parent() {
if p.PersistentPreRun != nil {
if p.PersistentPreRunE != nil {
if err := p.PersistentPostRunE(c, argWoFlags); err != nil {
return err
}
break
} else if p.PersistentPreRun != nil {
p.PersistentPreRun(c, argWoFlags)
break
}
}
if c.PreRun != nil {
if c.PreRunE != nil {
if err := c.PreRunE(c, argWoFlags); err != nil {
return err
}
} else if c.PreRun != nil {
c.PreRun(c, argWoFlags)
}
c.Run(c, argWoFlags)
if c.PostRun != nil {
if c.RunE != nil {
if err := c.RunE(c, argWoFlags); err != nil {
return err
}
} else {
c.Run(c, argWoFlags)
}
if c.PostRunE != nil {
if err := c.PostRunE(c, argWoFlags); err != nil {
return err
}
} else if c.PostRun != nil {
c.PostRun(c, argWoFlags)
}
for p := c; p != nil; p = p.Parent() {
if p.PersistentPostRun != nil {
if p.PersistentPostRunE != nil {
if err := p.PersistentPostRunE(c, argWoFlags); err != nil {
return err
}
break
} else if p.PersistentPostRun != nil {
p.PersistentPostRun(c, argWoFlags)
break
}
@ -526,7 +558,7 @@ func (c *Command) Execute() (err error) {
// initialize help as the last point possible to allow for user
// overriding
c.initHelp()
c.initHelpCmd()
var args []string
@ -550,7 +582,7 @@ func (c *Command) Execute() (err error) {
err = cmd.execute(flags)
if err != nil {
if err == flag.ErrHelp {
cmd.Help()
cmd.HelpFunc()(cmd, args)
return nil
}
c.Println(cmd.UsageString())
@ -560,7 +592,13 @@ func (c *Command) Execute() (err error) {
return
}
func (c *Command) initHelp() {
func (c *Command) initHelpFlag() {
if c.Flags().Lookup("help") == nil {
c.Flags().BoolP("help", "h", false, "help for "+c.Name())
}
}
func (c *Command) initHelpCmd() {
if c.helpCommand == nil {
if !c.HasSubCommands() {
return
@ -571,9 +609,19 @@ func (c *Command) initHelp() {
Short: "Help about any command",
Long: `Help provides help for any command in the application.
Simply type ` + c.Name() + ` help [path to command] for full details.`,
Run: c.HelpFunc(),
PersistentPreRun: func(cmd *Command, args []string) {},
PersistentPostRun: func(cmd *Command, args []string) {},
Run: func(c *Command, args []string) {
cmd, _, e := c.Root().Find(args)
if cmd == nil || e != nil {
c.Printf("Unknown help topic %#q.", args)
c.Root().Usage()
} else {
helpFunc := cmd.HelpFunc()
helpFunc(cmd, args)
}
},
}
}
c.AddCommand(c.helpCommand)
@ -794,7 +842,7 @@ func (c *Command) HasExample() bool {
// Determine if the command is itself runnable
func (c *Command) Runnable() bool {
return c.Run != nil
return c.Run != nil || c.RunE != nil
}
// Determine if the command has children commands
@ -859,7 +907,6 @@ func (c *Command) Flags() *flag.FlagSet {
c.flagErrorBuf = new(bytes.Buffer)
}
c.flags.SetOutput(c.flagErrorBuf)
c.PersistentFlags().BoolVarP(&c.helpFlagVal, "help", "h", false, "help for "+c.Name())
}
return c.flags
}

View File

@ -0,0 +1,36 @@
// Copyright 2015 Red Hat 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 cobra
import ()
// Test to see if we have a reason to print See Also information in docs
// Basically this is a test for a parent commend or a subcommand which is
// both not deprecated and not the autogenerated help command.
func (cmd *Command) hasSeeAlso() bool {
if cmd.HasParent() {
return true
}
children := cmd.Commands()
if len(children) == 0 {
return false
}
for _, c := range children {
if len(c.Deprecated) != 0 || c == cmd.helpCommand {
continue
}
return true
}
return false
}

View File

@ -0,0 +1,164 @@
// Copyright 2015 Red Hat 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 cobra
import (
"bytes"
"fmt"
"os"
"sort"
"strings"
"time"
mangen "github.com/cpuguy83/go-md2man/md2man"
"github.com/spf13/pflag"
)
func GenManTree(cmd *Command, projectName, dir string) {
cmd.GenManTree(projectName, dir)
}
func (cmd *Command) GenManTree(projectName, dir string) {
for _, c := range cmd.Commands() {
if len(c.Deprecated) != 0 || c == cmd.helpCommand {
continue
}
GenManTree(c, projectName, dir)
}
out := new(bytes.Buffer)
cmd.GenMan(projectName, out)
filename := cmd.CommandPath()
filename = dir + strings.Replace(filename, " ", "-", -1) + ".1"
outFile, err := os.Create(filename)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
defer outFile.Close()
_, err = outFile.Write(out.Bytes())
if err != nil {
fmt.Println(err)
os.Exit(1)
}
}
func GenMan(cmd *Command, projectName string, out *bytes.Buffer) {
cmd.GenMan(projectName, out)
}
func (cmd *Command) GenMan(projectName string, out *bytes.Buffer) {
buf := genMarkdown(cmd, projectName)
final := mangen.Render(buf)
out.Write(final)
}
func manPreamble(out *bytes.Buffer, projectName, name, short, long string) {
fmt.Fprintf(out, `%% %s(1)
# NAME
`, projectName)
fmt.Fprintf(out, "%s \\- %s\n\n", name, short)
fmt.Fprintf(out, "# SYNOPSIS\n")
fmt.Fprintf(out, "**%s** [OPTIONS]\n\n", name)
fmt.Fprintf(out, "# DESCRIPTION\n")
fmt.Fprintf(out, "%s\n\n", long)
}
func manPrintFlags(out *bytes.Buffer, flags *pflag.FlagSet) {
flags.VisitAll(func(flag *pflag.Flag) {
if len(flag.Deprecated) > 0 {
return
}
format := ""
if len(flag.Shorthand) > 0 {
format = "**-%s**, **--%s**"
} else {
format = "%s**--%s**"
}
if len(flag.NoOptDefVal) > 0 {
format = format + "["
}
if flag.Value.Type() == "string" {
// put quotes on the value
format = format + "=%q"
} else {
format = format + "=%s"
}
if len(flag.NoOptDefVal) > 0 {
format = format + "]"
}
format = format + "\n\t%s\n\n"
fmt.Fprintf(out, format, flag.Shorthand, flag.Name, flag.DefValue, flag.Usage)
})
}
func manPrintOptions(out *bytes.Buffer, command *Command) {
flags := command.NonInheritedFlags()
if flags.HasFlags() {
fmt.Fprintf(out, "# OPTIONS\n")
manPrintFlags(out, flags)
fmt.Fprintf(out, "\n")
}
flags = command.InheritedFlags()
if flags.HasFlags() {
fmt.Fprintf(out, "# OPTIONS INHERITED FROM PARENT COMMANDS\n")
manPrintFlags(out, flags)
fmt.Fprintf(out, "\n")
}
}
func genMarkdown(cmd *Command, projectName string) []byte {
// something like `rootcmd subcmd1 subcmd2`
commandName := cmd.CommandPath()
// something like `rootcmd-subcmd1-subcmd2`
dashCommandName := strings.Replace(commandName, " ", "-", -1)
buf := new(bytes.Buffer)
short := cmd.Short
long := cmd.Long
if len(long) == 0 {
long = short
}
manPreamble(buf, projectName, commandName, short, long)
manPrintOptions(buf, cmd)
if len(cmd.Example) > 0 {
fmt.Fprintf(buf, "# EXAMPLE\n")
fmt.Fprintf(buf, "```\n%s\n```\n", cmd.Example)
}
if cmd.hasSeeAlso() {
fmt.Fprintf(buf, "# SEE ALSO\n")
if cmd.HasParent() {
fmt.Fprintf(buf, "**%s(1)**, ", cmd.Parent().CommandPath())
}
children := cmd.Commands()
sort.Sort(byName(children))
for _, c := range children {
if len(c.Deprecated) != 0 || c == cmd.helpCommand {
continue
}
fmt.Fprintf(buf, "**%s-%s(1)**, ", dashCommandName, c.Name())
}
fmt.Fprintf(buf, "\n")
}
fmt.Fprintf(buf, "# HISTORY\n%s Auto generated by spf13/cobra\n", time.Now().UTC())
return buf.Bytes()
}

View File

@ -0,0 +1,21 @@
# Generating Man Pages For Your Own cobra.Command
Generating bash completions from a cobra command is incredibly easy. An example is as follows:
```go
package main
import (
"github.com/spf13/cobra"
)
func main() {
cmd := &cobra.Command{
Use: "test",
Short: "my test program",
}
cmd.GenManTree("/tmp")
}
```
That will get you a man page `/tmp/test.1`

View File

@ -0,0 +1,71 @@
package cobra
import (
"bytes"
"fmt"
"os"
"strings"
"testing"
)
var _ = fmt.Println
var _ = os.Stderr
func translate(in string) string {
return strings.Replace(in, "-", "\\-", -1)
}
func TestGenManDoc(t *testing.T) {
c := initializeWithRootCmd()
// Need two commands to run the command alphabetical sort
cmdEcho.AddCommand(cmdTimes, cmdEchoSub, cmdDeprecated)
c.AddCommand(cmdPrint, cmdEcho)
cmdRootWithRun.PersistentFlags().StringVarP(&flags2a, "rootflag", "r", "two", strtwoParentHelp)
out := new(bytes.Buffer)
// We generate on a subcommand so we have both subcommands and parents
cmdEcho.GenMan("PROJECT", out)
found := out.String()
// Our description
expected := translate(cmdEcho.Name())
if !strings.Contains(found, expected) {
t.Errorf("Unexpected response.\nExpecting to contain: \n %q\nGot:\n %q\n", expected, found)
}
// Better have our example
expected = translate(cmdEcho.Name())
if !strings.Contains(found, expected) {
t.Errorf("Unexpected response.\nExpecting to contain: \n %q\nGot:\n %q\n", expected, found)
}
// A local flag
expected = "boolone"
if !strings.Contains(found, expected) {
t.Errorf("Unexpected response.\nExpecting to contain: \n %q\nGot:\n %q\n", expected, found)
}
// persistent flag on parent
expected = "rootflag"
if !strings.Contains(found, expected) {
t.Errorf("Unexpected response.\nExpecting to contain: \n %q\nGot:\n %q\n", expected, found)
}
// We better output info about our parent
expected = translate(cmdRootWithRun.Name())
if !strings.Contains(found, expected) {
t.Errorf("Unexpected response.\nExpecting to contain: \n %q\nGot:\n %q\n", expected, found)
}
// And about subcommands
expected = translate(cmdEchoSub.Name())
if !strings.Contains(found, expected) {
t.Errorf("Unexpected response.\nExpecting to contain: \n %q\nGot:\n %q\n", expected, found)
}
unexpected := translate(cmdDeprecated.Name())
if strings.Contains(found, unexpected) {
t.Errorf("Unexpected response.\nFound: %v\nBut should not have!!\n", unexpected)
}
}

View File

@ -47,10 +47,18 @@ func (s byName) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
func (s byName) Less(i, j int) bool { return s[i].Name() < s[j].Name() }
func GenMarkdown(cmd *Command, out *bytes.Buffer) {
GenMarkdownCustom(cmd, out, func(s string) string { return s })
cmd.GenMarkdown(out)
}
func (cmd *Command) GenMarkdown(out *bytes.Buffer) {
cmd.GenMarkdownCustom(out, func(s string) string { return s })
}
func GenMarkdownCustom(cmd *Command, out *bytes.Buffer, linkHandler func(string) string) {
cmd.GenMarkdownCustom(out, linkHandler)
}
func (cmd *Command) GenMarkdownCustom(out *bytes.Buffer, linkHandler func(string) string) {
name := cmd.CommandPath()
short := cmd.Short
@ -75,7 +83,7 @@ func GenMarkdownCustom(cmd *Command, out *bytes.Buffer, linkHandler func(string)
printOptions(out, cmd, name)
if len(cmd.Commands()) > 0 || cmd.HasParent() {
if cmd.hasSeeAlso() {
fmt.Fprintf(out, "### SEE ALSO\n")
if cmd.HasParent() {
parent := cmd.Parent()
@ -89,7 +97,7 @@ func GenMarkdownCustom(cmd *Command, out *bytes.Buffer, linkHandler func(string)
sort.Sort(byName(children))
for _, child := range children {
if len(child.Deprecated) > 0 {
if len(child.Deprecated) > 0 || child == cmd.helpCommand {
continue
}
cname := name + " " + child.Name()
@ -104,18 +112,29 @@ func GenMarkdownCustom(cmd *Command, out *bytes.Buffer, linkHandler func(string)
}
func GenMarkdownTree(cmd *Command, dir string) {
cmd.GenMarkdownTree(dir)
}
func (cmd *Command) GenMarkdownTree(dir string) {
identity := func(s string) string { return s }
emptyStr := func(s string) string { return "" }
GenMarkdownTreeCustom(cmd, dir, emptyStr, identity)
cmd.GenMarkdownTreeCustom(dir, emptyStr, identity)
}
func GenMarkdownTreeCustom(cmd *Command, dir string, filePrepender func(string) string, linkHandler func(string) string) {
cmd.GenMarkdownTreeCustom(dir, filePrepender, linkHandler)
}
func (cmd *Command) GenMarkdownTreeCustom(dir string, filePrepender func(string) string, linkHandler func(string) string) {
for _, c := range cmd.Commands() {
GenMarkdownTreeCustom(c, dir, filePrepender, linkHandler)
if len(c.Deprecated) != 0 || c == cmd.helpCommand {
continue
}
c.GenMarkdownTreeCustom(dir, filePrepender, linkHandler)
}
out := new(bytes.Buffer)
GenMarkdownCustom(cmd, out, linkHandler)
cmd.GenMarkdownCustom(out, linkHandler)
filename := cmd.CommandPath()
filename = dir + strings.Replace(filename, " ", "_", -1) + ".md"

View File

@ -114,7 +114,7 @@ You can also view recordings of past events and presentations on our [Media page
For Q&A, our threads are at:
* [Stack Overflow](http://stackoverflow.com/questions/tagged/kubernetes)
* [BotBot.me (IRC)](https://botbot.me/freenode/google-containers/)
* [Slack](/docs/troubleshooting.md#slack)
#### Want to do more than just 'discuss' Kubernetes?

View File

@ -11879,6 +11879,9 @@
"v1.PersistentVolumeClaimList": {
"id": "v1.PersistentVolumeClaimList",
"description": "PersistentVolumeClaimList is a list of PersistentVolumeClaim items.",
"required": [
"items"
],
"properties": {
"kind": {
"type": "string",
@ -11990,6 +11993,9 @@
"v1.PersistentVolumeList": {
"id": "v1.PersistentVolumeList",
"description": "PersistentVolumeList is a list of PersistentVolume items.",
"required": [
"items"
],
"properties": {
"kind": {
"type": "string",

View File

@ -1,22 +1,22 @@
apiVersion: v1
kind: ReplicationController
metadata:
name: kube-dns-v8
name: kube-dns-v9
namespace: kube-system
labels:
k8s-app: kube-dns
version: v8
version: v9
kubernetes.io/cluster-service: "true"
spec:
replicas: {{ pillar['dns_replicas'] }}
selector:
k8s-app: kube-dns
version: v8
version: v9
template:
metadata:
labels:
k8s-app: kube-dns
version: v8
version: v9
kubernetes.io/cluster-service: "true"
spec:
containers:
@ -73,6 +73,13 @@ spec:
scheme: HTTP
initialDelaySeconds: 30
timeoutSeconds: 5
readinessProbe:
httpGet:
path: /healthz
port: 8080
scheme: HTTP
initialDelaySeconds: 1
timeoutSeconds: 5
- name: healthz
image: gcr.io/google_containers/exechealthz:1.0
resources:

View File

@ -2,7 +2,7 @@
This directory contains the source files needed to make a Docker image
that collects Docker container log files using [Fluentd](http://www.fluentd.org/)
and sends them to an instance of [Elasticsearch](http://www.elasticsearch.org/).
This image is designed to be used as part of the [Kubernetes](https://github.com/GoogleCloudPlatform/kubernetes)
This image is designed to be used as part of the [Kubernetes](https://github.com/kubernetes/kubernetes)
cluster bring up process. The image resides at DockerHub under the name
[kubernetes/fluentd-eslasticsearch](https://registry.hub.docker.com/u/kubernetes/fluentd-elasticsearch/).

View File

@ -2,7 +2,7 @@
This directory contains the source files needed to make a Docker image
that collects Docker container log files using [Fluentd](http://www.fluentd.org/)
and sends them to GCP.
This image is designed to be used as part of the [Kubernetes](https://github.com/GoogleCloudPlatform/kubernetes)
This image is designed to be used as part of the [Kubernetes](https://github.com/kubernetes/kubernetes)
cluster bring up process. The image resides at DockerHub under the name
[kubernetes/fluentd-gcp](https://registry.hub.docker.com/u/kubernetes/fluentd-gcp/).

View File

@ -87,7 +87,7 @@ DNS_REPLICAS=1
ENABLE_CLUSTER_UI="${KUBE_ENABLE_CLUSTER_UI:-true}"
# Admission Controllers to invoke prior to persisting objects in cluster
ADMISSION_CONTROL=NamespaceLifecycle,NamespaceExists,LimitRanger,SecurityContextDeny,ServiceAccount,ResourceQuota
ADMISSION_CONTROL=NamespaceLifecycle,LimitRanger,SecurityContextDeny,ServiceAccount,ResourceQuota
# Optional: Enable/disable public IP assignment for minions.
# Important Note: disable only if you have setup a NAT instance for internet access and configured appropriate routes!

View File

@ -83,7 +83,7 @@ DNS_REPLICAS=1
ENABLE_CLUSTER_UI="${KUBE_ENABLE_CLUSTER_UI:-true}"
# Admission Controllers to invoke prior to persisting objects in cluster
ADMISSION_CONTROL=NamespaceLifecycle,NamespaceExists,LimitRanger,SecurityContextDeny,ServiceAccount,ResourceQuota
ADMISSION_CONTROL=NamespaceLifecycle,LimitRanger,SecurityContextDeny,ServiceAccount,ResourceQuota
# Optional: Enable/disable public IP assignment for minions.
# Important Note: disable only if you have setup a NAT instance for internet access and configured appropriate routes!

View File

@ -55,4 +55,4 @@ ENABLE_CLUSTER_MONITORING="${KUBE_ENABLE_CLUSTER_MONITORING:-influxdb}"
ENABLE_CLUSTER_UI="${KUBE_ENABLE_CLUSTER_UI:-true}"
# Admission Controllers to invoke prior to persisting objects in cluster
ADMISSION_CONTROL=NamespaceLifecycle,NamespaceExists,LimitRanger,SecurityContextDeny,ServiceAccount,ResourceQuota
ADMISSION_CONTROL=NamespaceLifecycle,LimitRanger,SecurityContextDeny,ServiceAccount,ResourceQuota

View File

@ -117,6 +117,15 @@ function clear-kubeconfig() {
echo "Cleared config for ${CONTEXT} from ${KUBECONFIG}"
}
function tear_down_alive_resources() {
local kubectl="${KUBE_ROOT}/cluster/kubectl.sh"
"${kubectl}" delete rc --all
"${kubectl}" delete pods --all
"${kubectl}" delete svc --all
"${kubectl}" delete pvc --all
}
# Gets username, password for the current-context in kubeconfig, if they exist.
# Assumed vars:
# KUBECONFIG # if unset, defaults to global
@ -278,3 +287,4 @@ function tars_from_version() {
exit 1
fi
}

View File

@ -96,7 +96,7 @@ if [[ "${ENABLE_NODE_AUTOSCALER}" == "true" ]]; then
fi
# Admission Controllers to invoke prior to persisting objects in cluster
ADMISSION_CONTROL=NamespaceLifecycle,NamespaceExists,LimitRanger,SecurityContextDeny,ServiceAccount,ResourceQuota
ADMISSION_CONTROL=NamespaceLifecycle,LimitRanger,SecurityContextDeny,ServiceAccount,ResourceQuota
# Optional: if set to true kube-up will automatically check for existing resources and clean them up.
KUBE_UP_AUTOMATIC_CLEANUP=${KUBE_UP_AUTOMATIC_CLEANUP:-false}

View File

@ -18,9 +18,9 @@
# gcloud multiplexing for shared GCE/GKE tests.
GCLOUD=gcloud
ZONE=${KUBE_GCE_ZONE:-us-central1-b}
MASTER_SIZE=${MASTER_SIZE:-n1-standard-1}
MINION_SIZE=${MINION_SIZE:-n1-standard-1}
NUM_MINIONS=${NUM_MINIONS:-2}
MASTER_SIZE=${MASTER_SIZE:-n1-standard-2}
MINION_SIZE=${MINION_SIZE:-n1-standard-2}
NUM_MINIONS=${NUM_MINIONS:-3}
MASTER_DISK_TYPE=pd-ssd
MASTER_DISK_SIZE=${MASTER_DISK_SIZE:-20GB}
MINION_DISK_TYPE=pd-standard
@ -100,7 +100,7 @@ if [[ "${ENABLE_NODE_AUTOSCALER}" == "true" ]]; then
TARGET_NODE_UTILIZATION="${KUBE_TARGET_NODE_UTILIZATION:-0.7}"
fi
ADMISSION_CONTROL=NamespaceLifecycle,NamespaceExists,LimitRanger,SecurityContextDeny,ServiceAccount,ResourceQuota
ADMISSION_CONTROL=NamespaceLifecycle,LimitRanger,SecurityContextDeny,ServiceAccount,ResourceQuota
# Optional: if set to true kube-up will automatically check for existing resources and clean them up.
KUBE_UP_AUTOMATIC_CLEANUP=${KUBE_UP_AUTOMATIC_CLEANUP:-false}

View File

@ -540,10 +540,11 @@ grains:
- kubernetes-master
cloud: gce
EOF
if ! [[ -z "${PROJECT_ID:-}" ]] && ! [[ -z "${TOKEN_URL:-}" ]] && ! [[ -z "${NODE_NETWORK:-}" ]] ; then
if ! [[ -z "${PROJECT_ID:-}" ]] && ! [[ -z "${TOKEN_URL:-}" ]] && ! [[ -z "${TOKEN_BODY:-}" ]] && ! [[ -z "${NODE_NETWORK:-}" ]] ; then
cat <<EOF >/etc/gce.conf
[global]
token-url = ${TOKEN_URL}
token-body = ${TOKEN_BODY}
project-id = ${PROJECT_ID}
network-name = ${NODE_NETWORK}
EOF

View File

@ -21,6 +21,11 @@ set -o nounset
set -o pipefail
KUBE_ROOT=$(dirname "${BASH_SOURCE}")/..
if [ -f "${KUBE_ROOT}/cluster/env.sh" ]; then
source "${KUBE_ROOT}/cluster/env.sh"
fi
source "${KUBE_ROOT}/cluster/kube-env.sh"
source "${KUBE_ROOT}/cluster/kube-util.sh"

View File

@ -24,6 +24,11 @@ set -o nounset
set -o pipefail
KUBE_ROOT=$(dirname "${BASH_SOURCE}")/..
if [ -f "${KUBE_ROOT}/cluster/env.sh" ]; then
source "${KUBE_ROOT}/cluster/env.sh"
fi
source "${KUBE_ROOT}/cluster/kube-env.sh"
source "${KUBE_ROOT}/cluster/kube-util.sh"

View File

@ -25,6 +25,11 @@ set -o nounset
set -o pipefail
KUBE_ROOT=$(dirname "${BASH_SOURCE}")/..
if [ -f "${KUBE_ROOT}/cluster/env.sh" ]; then
source "${KUBE_ROOT}/cluster/env.sh"
fi
source "${KUBE_ROOT}/cluster/kube-env.sh"
source "${KUBE_ROOT}/cluster/kube-util.sh"

View File

@ -195,7 +195,7 @@ function wait-cluster-readiness {
local timeout=120
while [[ $timeout -ne 0 ]]; do
nb_ready_minions=$("${kubectl}" get nodes -o template -t "{{range.items}}{{range.status.conditions}}{{.type}}{{end}}:{{end}}" --api-version=v1 2>/dev/null | tr ':' '\n' | grep -c Ready || true)
nb_ready_minions=$("${kubectl}" get nodes -o go-template="{{range.items}}{{range.status.conditions}}{{.type}}{{end}}:{{end}}" --api-version=v1 2>/dev/null | tr ':' '\n' | grep -c Ready || true)
echo "Nb ready minions: $nb_ready_minions / $NUM_MINIONS"
if [[ "$nb_ready_minions" -eq "$NUM_MINIONS" ]]; then
return 0
@ -294,7 +294,7 @@ function upload-server-tars {
tar -x -C "$POOL_PATH/kubernetes" -f "$SERVER_BINARY_TAR" kubernetes
rm -rf "$POOL_PATH/kubernetes/bin"
mv "$POOL_PATH/kubernetes/kubernetes/server/bin" "$POOL_PATH/kubernetes/bin"
rmdir "$POOL_PATH/kubernetes/kubernetes/server" "$POOL_PATH/kubernetes/kubernetes"
rm -fr "$POOL_PATH/kubernetes/kubernetes"
}
# Update a kubernetes cluster with latest source

View File

@ -89,7 +89,7 @@ apiserver:
--external-hostname=apiserver
--etcd-servers=http://etcd:4001
--port=8888
--admission-control=NamespaceLifecycle,NamespaceExists,LimitRanger,SecurityContextDeny,ServiceAccount,ResourceQuota
--admission-control=NamespaceLifecycle,LimitRanger,SecurityContextDeny,ServiceAccount,ResourceQuota
--authorization-mode=AlwaysAllow
--token-auth-file=/var/run/kubernetes/auth/token-users
--basic-auth-file=/var/run/kubernetes/auth/basic-users

View File

@ -198,7 +198,7 @@ function run-until-success() {
# returns a list of <namespace>/<name> pairs (nsnames)
function get-addon-nsnames-from-server() {
local -r obj_type=$1
"${KUBECTL}" get "${obj_type}" --all-namespaces -o template -t "{{range.items}}{{.metadata.namespace}}/{{.metadata.name}} {{end}}" --api-version=v1 -l kubernetes.io/cluster-service=true
"${KUBECTL}" get "${obj_type}" --all-namespaces -o go-template="{{range.items}}{{.metadata.namespace}}/{{.metadata.name}} {{end}}" --api-version=v1 -l kubernetes.io/cluster-service=true
}
# returns the characters after the last separator (including)

View File

@ -174,7 +174,7 @@ start_addon /etc/kubernetes/addons/namespace.yaml 100 10 "" &
token_found=""
while [ -z "${token_found}" ]; do
sleep .5
token_found=$(${KUBECTL} get --namespace="${SYSTEM_NAMESPACE}" serviceaccount default -o template -t "{{with index .secrets 0}}{{.name}}{{end}}" || true)
token_found=$(${KUBECTL} get --namespace="${SYSTEM_NAMESPACE}" serviceaccount default -o go-template="{{with index .secrets 0}}{{.name}}{{end}}" || true)
done
echo "== default service account in the ${SYSTEM_NAMESPACE} namespace has token ${token_found} =="

View File

@ -97,10 +97,15 @@
{% set pod_cidr = "--pod-cidr=" + grains['cbr-cidr'] %}
{% endif %}
{% set cpu_cfs_quota = "" %}
{% if pillar['enable_cpu_cfs_quota'] is defined -%}
{% set cpu_cfs_quota = "--cpu-cfs-quota=" + pillar['enable_cpu_cfs_quota'] -%}
{% endif -%}
{% set test_args = "" -%}
{% if pillar['kubelet_test_args'] is defined -%}
{% set test_args=pillar['kubelet_test_args'] %}
{% endif -%}
# test_args has to be kept at the end, so they'll overwrite any prior configuration
DAEMON_ARGS="{{daemon_args}} {{api_servers_with_port}} {{debugging_handlers}} {{hostname_override}} {{cloud_provider}} {{config}} {{manifest_url}} --allow-privileged={{pillar['allow_privileged']}} {{pillar['log_level']}} {{cluster_dns}} {{cluster_domain}} {{docker_root}} {{kubelet_root}} {{configure_cbr0}} {{cgroup_root}} {{system_container}} {{pod_cidr}} {{test_args}}"
DAEMON_ARGS="{{daemon_args}} {{api_servers_with_port}} {{debugging_handlers}} {{hostname_override}} {{cloud_provider}} {{config}} {{manifest_url}} --allow-privileged={{pillar['allow_privileged']}} {{pillar['log_level']}} {{cluster_dns}} {{cluster_domain}} {{docker_root}} {{kubelet_root}} {{configure_cbr0}} {{cgroup_root}} {{system_container}} {{pod_cidr}} {{cpu_cfs_quota}} {{test_args}}"

View File

@ -51,7 +51,6 @@ if [ ! -f etcd.tar.gz ] ; then
tar xzf etcd.tar.gz
fi
cp $ETCD/etcd $ETCD/etcdctl binaries/master
cp $ETCD/etcd $ETCD/etcdctl binaries/minion
# k8s
echo "Download kubernetes release ..."

View File

@ -35,7 +35,7 @@ export SERVICE_CLUSTER_IP_RANGE=${SERVICE_CLUSTER_IP_RANGE:-192.168.3.0/24} # f
export FLANNEL_NET=${FLANNEL_NET:-172.16.0.0/16}
# Admission Controllers to invoke prior to persisting objects in cluster
export ADMISSION_CONTROL=NamespaceLifecycle,NamespaceExists,LimitRanger,ServiceAccount,ResourceQuota,SecurityContextDeny
export ADMISSION_CONTROL=NamespaceLifecycle,LimitRanger,ServiceAccount,ResourceQuota,SecurityContextDeny
SERVICE_NODE_PORT_RANGE=${SERVICE_NODE_PORT_RANGE:-"30000-32767"}

View File

@ -1,31 +0,0 @@
description "Etcd service"
author "@jainvipin"
start on (net-device-up
and local-filesystems
and runlevel [2345])
respawn
pre-start script
# see also https://github.com/jainvipin/kubernetes-ubuntu-start
ETCD=/opt/bin/$UPSTART_JOB
if [ -f /etc/default/$UPSTART_JOB ]; then
. /etc/default/$UPSTART_JOB
fi
if [ -f $ETCD ]; then
exit 0
fi
echo "$ETCD binary not found, exiting"
exit 22
end script
script
# modify these in /etc/default/$UPSTART_JOB (/etc/default/docker)
ETCD=/opt/bin/$UPSTART_JOB
ETCD_OPTS=""
if [ -f /etc/default/$UPSTART_JOB ]; then
. /etc/default/$UPSTART_JOB
fi
exec "$ETCD" $ETCD_OPTS
end script

View File

@ -3,10 +3,9 @@ author "@chenxingyu"
respawn
# start in conjunction with etcd
start on started etcd
stop on stopping etcd
start on (net-device-up
and local-filesystems
and runlevel [2345])
pre-start script
FLANNEL=/opt/bin/$UPSTART_JOB
if [ -f /etc/default/$UPSTART_JOB ]; then

View File

@ -3,9 +3,9 @@ author "@jainvipin"
respawn
# start in conjunction with etcd
start on started etcd
stop on stopping etcd
# start in conjunction with flanneld
start on started flanneld
stop on stopping flanneld
limit nofile 65536 65536

View File

@ -3,9 +3,9 @@ author "@jainvipin"
respawn
# start in conjunction with etcd
start on started etcd
stop on stopping etcd
# start in conjunction with flanneld
start on started flanneld
stop on stopping flanneld
pre-start script
# see also https://github.com/jainvipin/kubernetes-ubuntu-start

View File

@ -1,100 +0,0 @@
#!/bin/sh
set -e
### BEGIN INIT INFO
# Provides: etcd
# Required-Start: $docker
# Required-Stop:
# Should-Start:
# Should-Stop:
# Default-Start:
# Default-Stop:
# Short-Description: Start distrubted key/value pair service
# Description:
# http://www.github.com/coreos/etcd
### END INIT INFO
export PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin:/opt/bin:
BASE=$(basename $0)
# modify these in /etc/default/$BASE (/etc/default/etcd)
ETCD=/opt/bin/$BASE
# This is the pid file managed by etcd itself
ETCD_PIDFILE=/var/run/$BASE.pid
ETCD_LOGFILE=/var/log/$BASE.log
ETCD_OPTS=""
ETCD_DESC="Etcd"
# Get lsb functions
. /lib/lsb/init-functions
if [ -f /etc/default/$BASE ]; then
. /etc/default/$BASE
fi
# see also init_is_upstart in /lib/lsb/init-functions (which isn't available in Ubuntu 12.04, or we'd use it)
if false && [ -x /sbin/initctl ] && /sbin/initctl version 2>/dev/null | grep -q upstart; then
log_failure_msg "$ETCD_DESC is managed via upstart, try using service $BASE $1"
exit 1
fi
# Check etcd is present
if [ ! -x $ETCD ]; then
log_failure_msg "$ETCD not present or not executable"
exit 1
fi
fail_unless_root() {
if [ "$(id -u)" != '0' ]; then
log_failure_msg "$ETCD_DESC must be run as root"
exit 1
fi
}
ETCD_START="start-stop-daemon \
--start \
--background \
--quiet \
--exec $ETCD \
--make-pidfile \
--pidfile $ETCD_PIDFILE \
-- $ETCD_OPTS \
>> $ETCD_LOGFILE 2>&1"
ETCD_STOP="start-stop-daemon \
--stop \
--pidfile $ETCD_PIDFILE"
case "$1" in
start)
fail_unless_root
log_begin_msg "Starting $ETCD_DESC: $BASE"
$ETCD_START
log_end_msg $?
;;
stop)
fail_unless_root
log_begin_msg "Stopping $ETCD_DESC: $BASE"
$ETCD_STOP
log_end_msg $?
;;
restart | force-reload)
fail_unless_root
log_begin_msg "Restarting $ETCD_DESC: $BASE"
$ETCD_STOP
$ETCD_START
log_end_msg $?
;;
status)
status_of_proc -p "$ETCD_PIDFILE" "$ETCD" "$ETCD_DESC"
;;
*)
echo "Usage: $0 {start|stop|restart|status}"
exit 1
;;
esac

View File

@ -24,7 +24,7 @@ KUBELET=/opt/bin/$BASE
KUBELET_PIDFILE=/var/run/$BASE.pid
KUBELET_LOGFILE=/var/log/$BASE.log
KUBELET_OPTS=""
KUBELET_DESC="Kube-Apiserver"
KUBELET_DESC="Kubelet"
# Get lsb functions
. /lib/lsb/init-functions

View File

@ -21,33 +21,48 @@ if [ "$(id -u)" != "0" ]; then
exit 1
fi
source ~/kube/config-default.sh
attempt=0
while true; do
/opt/bin/etcdctl get /coreos.com/network/config
if [[ "$?" == 0 ]]; then
break
else
# enough timeout??
if (( attempt > 600 )); then
echo "timeout for waiting network config" > ~/kube/err.log
exit 2
function config_etcd {
source ~/kube/config-default.sh
attempt=0
while true; do
/opt/bin/etcdctl get /coreos.com/network/config
if [[ "$?" == 0 ]]; then
break
else
# enough timeout??
if (( attempt > 600 )); then
echo "timeout for waiting network config" > ~/kube/err.log
exit 2
fi
/opt/bin/etcdctl mk /coreos.com/network/config "{\"Network\":\"${FLANNEL_NET}\"}"
attempt=$((attempt+1))
sleep 3
fi
done
}
/opt/bin/etcdctl mk /coreos.com/network/config "{\"Network\":\"${FLANNEL_NET}\"}"
attempt=$((attempt+1))
sleep 3
fi
done
function restart_docker {
#wait some secs for /run/flannel/subnet.env ready
sleep 15
sudo ip link set dev docker0 down
sudo brctl delbr docker0
#wait some secs for /run/flannel/subnet.env ready
sleep 15
sudo ip link set dev docker0 down
sudo brctl delbr docker0
source /run/flannel/subnet.env
source /run/flannel/subnet.env
echo DOCKER_OPTS=\"${DOCKER_OPTS} -H tcp://127.0.0.1:4243 -H unix:///var/run/docker.sock \
--bip=${FLANNEL_SUBNET} --mtu=${FLANNEL_MTU}\" > /etc/default/docker
sudo service docker restart
}
echo DOCKER_OPTS=\"${DOCKER_OPTS} -H tcp://127.0.0.1:4243 -H unix:///var/run/docker.sock \
--bip=${FLANNEL_SUBNET} --mtu=${FLANNEL_MTU}\" > /etc/default/docker
sudo service docker restart
if [[ $1 == "i" ]]; then
restart_docker
elif [[ $1 == "ai" ]]; then
config_etcd
restart_docker
elif [[ $1 == "a" ]]; then
config_etcd
fi

View File

@ -21,7 +21,6 @@ SSH_OPTS="-oStrictHostKeyChecking=no -oUserKnownHostsFile=/dev/null -oLogLevel=E
# use an array to record name and ip
declare -A mm
CLUSTER=""
MASTER=""
MASTER_IP=""
MINION_IPS=""
@ -44,28 +43,18 @@ function setClusterInfo() {
MINION_IPS=""
ii=0
for i in $nodes
do
name="infra"$ii
for i in $nodes; do
nodeIP=${i#*@}
item="$name=http://$nodeIP:2380"
if [ "$ii" == 0 ]; then
CLUSTER=$item
else
CLUSTER="$CLUSTER,$item"
fi
mm[$nodeIP]=$name
if [ "${roles[${ii}]}" == "ai" ]; then
if [[ "${roles[${ii}]}" == "ai" ]]; then
MASTER_IP=$nodeIP
MASTER=$i
MINION_IPS="$nodeIP"
elif [ "${roles[${ii}]}" == "a" ]; then
elif [[ "${roles[${ii}]}" == "a" ]]; then
MASTER_IP=$nodeIP
MASTER=$i
elif [ "${roles[${ii}]}" == "i" ]; then
if [ -z "${MINION_IPS}" ];then
elif [[ "${roles[${ii}]}" == "i" ]]; then
if [[ -z "${MINION_IPS}" ]];then
MINION_IPS="$nodeIP"
else
MINION_IPS="$MINION_IPS,$nodeIP"
@ -191,12 +180,9 @@ function verify-minion(){
function create-etcd-opts(){
cat <<EOF > ~/kube/default/etcd
ETCD_OPTS="-name $1 \
-initial-advertise-peer-urls http://$2:2380 \
-listen-peer-urls http://$2:2380 \
-initial-cluster-token etcd-cluster-1 \
-initial-cluster $3 \
-initial-cluster-state new"
ETCD_OPTS="-name infra
-listen-client-urls http://0.0.0.0:4001 \
-advertise-client-urls http://127.0.0.1:4001"
EOF
}
@ -256,7 +242,7 @@ EOF
function create-flanneld-opts(){
cat <<EOF > ~/kube/default/flanneld
FLANNEL_OPTS=""
FLANNEL_OPTS="--etcd-endpoints=http://${1}:4001"
EOF
}
@ -324,10 +310,10 @@ function kube-up() {
{
if [ "${roles[${ii}]}" == "a" ]; then
provision-master
elif [ "${roles[${ii}]}" == "i" ]; then
provision-minion $i
elif [ "${roles[${ii}]}" == "ai" ]; then
provision-masterandminion
elif [ "${roles[${ii}]}" == "i" ]; then
provision-minion $i
else
echo "unsupported role for ${i}. please check"
exit 1
@ -356,21 +342,22 @@ function provision-master() {
echo "Deploying master on machine ${MASTER_IP}"
echo
ssh $SSH_OPTS $MASTER "mkdir -p ~/kube/default"
scp -r $SSH_OPTS saltbase/salt/generate-cert/make-ca-cert.sh ubuntu/config-default.sh ubuntu/util.sh ubuntu/master/* ubuntu/binaries/master/ "${MASTER}:~/kube"
scp -r $SSH_OPTS saltbase/salt/generate-cert/make-ca-cert.sh ubuntu/reconfDocker.sh ubuntu/config-default.sh ubuntu/util.sh ubuntu/master/* ubuntu/binaries/master/ "${MASTER}:~/kube"
# remote login to MASTER and use sudo to configue k8s master
ssh $SSH_OPTS -t $MASTER "source ~/kube/util.sh; \
setClusterInfo; \
create-etcd-opts "${mm[${MASTER_IP}]}" "${MASTER_IP}" "${CLUSTER}"; \
create-etcd-opts; \
create-kube-apiserver-opts "${SERVICE_CLUSTER_IP_RANGE}" "${ADMISSION_CONTROL}" "${SERVICE_NODE_PORT_RANGE}"; \
create-kube-controller-manager-opts "${MINION_IPS}"; \
create-kube-scheduler-opts; \
create-flanneld-opts; \
create-flanneld-opts "127.0.0.1"; \
sudo -p '[sudo] password to copy files and start master: ' cp ~/kube/default/* /etc/default/ && sudo cp ~/kube/init_conf/* /etc/init/ && sudo cp ~/kube/init_scripts/* /etc/init.d/ ;\
sudo groupadd -f -r kube-cert; \
sudo ~/kube/make-ca-cert.sh ${MASTER_IP} IP:${MASTER_IP},IP:${SERVICE_CLUSTER_IP_RANGE%.*}.1,DNS:kubernetes,DNS:kubernetes.default,DNS:kubernetes.default.svc,DNS:kubernetes.default.svc.cluster.local; \
sudo mkdir -p /opt/bin/ && sudo cp ~/kube/master/* /opt/bin/; \
sudo service etcd start;"
sudo service etcd start; \
sudo FLANNEL_NET=${FLANNEL_NET} -b ~/kube/reconfDocker.sh "a";"
}
function provision-minion() {
@ -383,14 +370,13 @@ function provision-minion() {
# remote login to MASTER and use sudo to configue k8s master
ssh $SSH_OPTS -t $1 "source ~/kube/util.sh; \
setClusterInfo; \
create-etcd-opts "${mm[${1#*@}]}" "${1#*@}" "${CLUSTER}"; \
create-kubelet-opts "${1#*@}" "${MASTER_IP}" "${DNS_SERVER_IP}" "${DNS_DOMAIN}";
create-kubelet-opts "${1#*@}" "${MASTER_IP}" "${DNS_SERVER_IP}" "${DNS_DOMAIN}"; \
create-kube-proxy-opts "${MASTER_IP}"; \
create-flanneld-opts; \
create-flanneld-opts "${MASTER_IP}"; \
sudo -p '[sudo] password to copy files and start minion: ' cp ~/kube/default/* /etc/default/ && sudo cp ~/kube/init_conf/* /etc/init/ && sudo cp ~/kube/init_scripts/* /etc/init.d/ \
&& sudo mkdir -p /opt/bin/ && sudo cp ~/kube/minion/* /opt/bin; \
sudo service etcd start; \
sudo FLANNEL_NET=${FLANNEL_NET} -b ~/kube/reconfDocker.sh"
sudo service flanneld start; \
sudo -b ~/kube/reconfDocker.sh "i";"
}
function provision-masterandminion() {
@ -398,24 +384,25 @@ function provision-masterandminion() {
echo "Deploying master and minion on machine ${MASTER_IP}"
echo
ssh $SSH_OPTS $MASTER "mkdir -p ~/kube/default"
scp -r $SSH_OPTS saltbase/salt/generate-cert/make-ca-cert.sh ubuntu/config-default.sh ubuntu/util.sh ubuntu/master/* ubuntu/reconfDocker.sh ubuntu/minion/* ubuntu/binaries/master/ ubuntu/binaries/minion "${MASTER}:~/kube"
# scp order matters
scp -r $SSH_OPTS saltbase/salt/generate-cert/make-ca-cert.sh ubuntu/config-default.sh ubuntu/util.sh ubuntu/minion/* ubuntu/master/* ubuntu/reconfDocker.sh ubuntu/binaries/master/ ubuntu/binaries/minion "${MASTER}:~/kube"
# remote login to the node and use sudo to configue k8s
ssh $SSH_OPTS -t $MASTER "source ~/kube/util.sh; \
setClusterInfo; \
create-etcd-opts "${mm[${MASTER_IP}]}" "${MASTER_IP}" "${CLUSTER}"; \
create-etcd-opts; \
create-kube-apiserver-opts "${SERVICE_CLUSTER_IP_RANGE}" "${ADMISSION_CONTROL}" "${SERVICE_NODE_PORT_RANGE}"; \
create-kube-controller-manager-opts "${MINION_IPS}"; \
create-kube-scheduler-opts; \
create-kubelet-opts "${MASTER_IP}" "${MASTER_IP}" "${DNS_SERVER_IP}" "${DNS_DOMAIN}";
create-kube-proxy-opts "${MASTER_IP}";\
create-flanneld-opts; \
create-flanneld-opts "127.0.0.1"; \
sudo -p '[sudo] password to copy files and start node: ' cp ~/kube/default/* /etc/default/ && sudo cp ~/kube/init_conf/* /etc/init/ && sudo cp ~/kube/init_scripts/* /etc/init.d/ ; \
sudo groupadd -f -r kube-cert; \
sudo ~/kube/make-ca-cert.sh ${MASTER_IP} IP:${MASTER_IP},IP:${SERVICE_CLUSTER_IP_RANGE%.*}.1,DNS:kubernetes,DNS:kubernetes.default,DNS:kubernetes.default.svc,DNS:kubernetes.default.svc.cluster.local; \
sudo mkdir -p /opt/bin/ && sudo cp ~/kube/master/* /opt/bin/ && sudo cp ~/kube/minion/* /opt/bin/; \
sudo service etcd start; \
sudo FLANNEL_NET=${FLANNEL_NET} -b ~/kube/reconfDocker.sh"
sudo FLANNEL_NET=${FLANNEL_NET} -b ~/kube/reconfDocker.sh "ai";"
}
# Delete a kubernetes cluster
@ -423,20 +410,116 @@ function kube-down {
KUBE_ROOT=$(dirname "${BASH_SOURCE}")/../..
source "${KUBE_ROOT}/cluster/ubuntu/${KUBE_CONFIG_FILE-"config-default.sh"}"
source "${KUBE_ROOT}/cluster/common.sh"
tear_down_alive_resources
ii=0
for i in ${nodes}; do
{
echo "Cleaning on node ${i#*@}"
ssh -t $i 'pgrep etcd && sudo -p "[sudo] password for cleaning etcd data: " service etcd stop && sudo rm -rf /infra*'
# Delete the files in order to generate a clean environment, so you can change each node's role at next deployment.
ssh -t $i 'sudo rm -f /opt/bin/kube* /etc/init/kube* /etc/init.d/kube* /etc/default/kube*; sudo rm -rf ~/kube /var/lib/kubelet'
if [[ "${roles[${ii}]}" == "ai" || "${roles[${ii}]}" == "a" ]]; then
ssh -t $i 'pgrep etcd && sudo -p "[sudo] password for cleaning etcd data: " service etcd stop && sudo rm -rf /infra*;
sudo rm -rf /opt/bin/etcd* /etc/init/etcd.conf /etc/init.d/etcd /etc/default/etcd'
elif [[ "${roles[${ii}]}" == "i" ]]; then
ssh -t $i 'pgrep flanneld && sudo -p "[sudo] password for stopping flanneld: " service flanneld stop'
else
echo "unsupported role for ${i}"
fi
# Delete the files in order to generate a clean environment, so you can change each node's role at next deployment.
ssh -t $i 'sudo rm -f /opt/bin/kube* /opt/bin/flanneld;
sudo rm -rf /etc/init/kube* /etc/init/flanneld.conf /etc/init.d/kube* /etc/init.d/flanneld;
sudo rm -rf /etc/default/kube* /etc/default/flanneld;
sudo rm -rf ~/kube /var/lib/kubelet'
}
((ii=ii+1))
done
wait
}
# Perform common upgrade setup tasks
function prepare-push() {
#Not yet support upgrading by using local binaries.
if [[ $KUBE_VERSION == "" ]]; then
echo "Upgrading nodes to local binaries is not yet supported.Please specify the version"
exit 1
fi
# Run build.sh to get the latest release
source "${KUBE_ROOT}/cluster/ubuntu/build.sh"
}
# Update a kubernetes master with latest release
function push-master {
source "${KUBE_ROOT}/cluster/ubuntu/${KUBE_CONFIG_FILE-"config-default.sh"}"
setClusterInfo
ii=0
for i in ${nodes}; do
if [[ "${roles[${ii}]}" == "a" || "${roles[${ii}]}" == "ai" ]]; then
echo "Cleaning on master ${i#*@}"
ssh -t $i 'sudo -p "[sudo] stop the all process: " service etcd stop' || true
provision-master
elif [[ "${roles[${ii}]}" == "i" ]]; then
continue
else
echo "unsupported role for ${i}. please check"
exit 1
fi
((ii=ii+1))
done
verify-cluster
}
# Update a kubernetes node with latest release
function push-node() {
source "${KUBE_ROOT}/cluster/ubuntu/${KUBE_CONFIG_FILE-"config-default.sh"}"
node=${1}
setClusterInfo
ii=0
for i in ${nodes}; do
if [[ "${roles[${ii}]}" == "i" || "${roles[${ii}]}" == "ai" && $i == *$node ]]; then
echo "Cleaning on node ${i#*@}"
ssh -t $i 'sudo -p "[sudo] stop the all process: " service etcd stop' || true
provision-minion $i
else
echo "unsupported role for ${i}, or nodes ${i} don't exist. please check"
exit 1
fi
((ii=ii+1))
done
verify-cluster
}
# Update a kubernetes cluster with latest source
function kube-push {
echo "not implemented"
prepare-push
#stop all the kube's process & etcd
source "${KUBE_ROOT}/cluster/ubuntu/${KUBE_CONFIG_FILE-"config-default.sh"}"
for i in ${nodes}; do
echo "Cleaning on node ${i#*@}"
ssh -t $i 'sudo -p "[sudo] stop all process: " service etcd stop' || true
ssh -t $i 'rm -f /opt/bin/kube* /etc/init/kube* /etc/init.d/kube* /etc/default/kube*; rm -rf ~/kube' || true
done
#Update all nodes with the lasted release
if [[ ! -f "ubuntu/binaries/master/kube-apiserver" ]]; then
echo "There is no latest release of kubernetes,please check first"
exit 1
fi
#provision all nodes,include master&nodes
setClusterInfo
ii=0
for i in ${nodes}; do
if [[ "${roles[${ii}]}" == "a" ]]; then
provision-master
elif [[ "${roles[${ii}]}" == "i" ]]; then
provision-minion $i
elif [[ "${roles[${ii}]}" == "ai" ]]; then
provision-masterandminion
else
echo "unsupported role for ${i}. please check"
exit 1
fi
((ii=ii+1))
done
verify-cluster
}
# Perform preparations required to run e2e tests

View File

@ -49,7 +49,7 @@ declare -a resources=(
)
# Find all the namespaces.
namespaces=( $("${KUBECTL}" get namespaces -o template -t "{{range.items}}{{.metadata.name}} {{end}}"))
namespaces=( $("${KUBECTL}" get namespaces -o go-template="{{range.items}}{{.metadata.name}} {{end}}"))
if [ -z "${namespaces:-}" ]
then
echo "Unexpected: No namespace found. Nothing to do."
@ -59,7 +59,7 @@ for resource in "${resources[@]}"
do
for namespace in "${namespaces[@]}"
do
instances=( $("${KUBECTL}" get "${resource}" --namespace="${namespace}" -o template -t "{{range.items}}{{.metadata.name}} {{end}}"))
instances=( $("${KUBECTL}" get "${resource}" --namespace="${namespace}" -o go-template="{{range.items}}{{.metadata.name}} {{end}}"))
# Nothing to do if there is no instance of that resource.
if [[ -z "${instances:-}" ]]
then
@ -84,7 +84,7 @@ do
echo "Looks like ${instance} got deleted. Ignoring it"
continue
fi
output=$("${KUBECTL}" update -f "${filename}" --namespace="${namespace}") || true
output=$("${KUBECTL}" replace -f "${filename}" --namespace="${namespace}") || true
rm "${filename}"
if [ -n "${output:-}" ]
then

View File

@ -53,7 +53,7 @@ MASTER_USER=vagrant
MASTER_PASSWD=vagrant
# Admission Controllers to invoke prior to persisting objects in cluster
ADMISSION_CONTROL=NamespaceLifecycle,NamespaceExists,LimitRanger,SecurityContextDeny,ServiceAccount,ResourceQuota
ADMISSION_CONTROL=NamespaceLifecycle,LimitRanger,SecurityContextDeny,ServiceAccount,ResourceQuota
# Optional: Enable node logging.
ENABLE_NODE_LOGGING=false
@ -76,6 +76,9 @@ ENABLE_CLUSTER_MONITORING="${KUBE_ENABLE_CLUSTER_MONITORING:-influxdb}"
#EXTRA_DOCKER_OPTS="-b=cbr0 --selinux-enabled --insecure-registry 10.0.0.0/8"
EXTRA_DOCKER_OPTS="-b=cbr0 --insecure-registry 10.0.0.0/8"
# Flag to tell the kubelet to enable CFS quota support
ENABLE_CPU_CFS_QUOTA="${KUBE_ENABLE_CPU_CFS_QUOTA:-true}"
# Optional: Install cluster DNS.
ENABLE_CLUSTER_DNS="${KUBE_ENABLE_CLUSTER_DNS:-true}"
DNS_SERVER_IP="10.247.0.10"

View File

@ -126,6 +126,7 @@ cat <<EOF >/srv/salt-overlay/pillar/cluster-params.sls
dns_domain: '$(echo "$DNS_DOMAIN" | sed -e "s/'/''/g")'
instance_prefix: '$(echo "$INSTANCE_PREFIX" | sed -e "s/'/''/g")'
admission_control: '$(echo "$ADMISSION_CONTROL" | sed -e "s/'/''/g")'
enable_cpu_cfs_quota: '$(echo "$ENABLE_CPU_CFS_QUOTA" | sed -e "s/'/''/g")'
EOF
# Configure the salt-master

View File

@ -155,6 +155,10 @@ grains:
docker_opts: '$(echo "$DOCKER_OPTS" | sed -e "s/'/''/g")'
EOF
# QoS support requires that swap memory is disabled on each of the minions
echo "Disable swap memory to ensure proper QoS"
swapoff -a
# we will run provision to update code each time we test, so we do not want to do salt install each time
if ! which salt-minion >/dev/null 2>&1; then
# Install Salt

View File

@ -153,6 +153,7 @@ function create-provision-scripts {
echo "KUBELET_TOKEN='${KUBELET_TOKEN:-}'"
echo "KUBE_PROXY_TOKEN='${KUBE_PROXY_TOKEN:-}'"
echo "MASTER_EXTRA_SANS='${MASTER_EXTRA_SANS:-}'"
echo "ENABLE_CPU_CFS_QUOTA='${ENABLE_CPU_CFS_QUOTA}'"
awk '!/^#/' "${KUBE_ROOT}/cluster/vagrant/provision-network.sh"
awk '!/^#/' "${KUBE_ROOT}/cluster/vagrant/provision-master.sh"
) > "${KUBE_TEMP}/master-start.sh"
@ -198,6 +199,9 @@ function verify-cluster {
local machine="master"
local -a required_daemon=("salt-master" "salt-minion" "kubelet")
local validated="1"
# This is a hack, but sometimes the salt-minion gets stuck on the master, so we just restart it
# to ensure that users never wait forever
vagrant ssh "$machine" -c "sudo systemctl restart salt-minion"
until [[ "$validated" == "0" ]]; do
validated="0"
local daemon
@ -237,7 +241,7 @@ function verify-cluster {
local count="0"
until [[ "$count" == "1" ]]; do
local minions
minions=$("${KUBE_ROOT}/cluster/kubectl.sh" get nodes -o template --template '{{range.items}}{{.metadata.name}}:{{end}}' --api-version=v1)
minions=$("${KUBE_ROOT}/cluster/kubectl.sh" get nodes -o go-template='{{range.items}}{{.metadata.name}}:{{end}}' --api-version=v1)
count=$(echo $minions | grep -c "${MINION_IPS[i]}") || {
printf "."
sleep 2

View File

@ -38,6 +38,7 @@ import (
"k8s.io/kubernetes/pkg/api"
apierrors "k8s.io/kubernetes/pkg/api/errors"
"k8s.io/kubernetes/pkg/api/latest"
"k8s.io/kubernetes/pkg/api/testapi"
"k8s.io/kubernetes/pkg/apiserver"
client "k8s.io/kubernetes/pkg/client/unversioned"
"k8s.io/kubernetes/pkg/client/unversioned/record"
@ -69,8 +70,6 @@ import (
var (
fakeDocker1, fakeDocker2 dockertools.FakeDockerClient
// API version that should be used by the client to talk to the server.
apiVersion string
// Limit the number of concurrent tests.
maxConcurrency int
)
@ -93,7 +92,7 @@ func (h *delegateHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
w.WriteHeader(http.StatusNotFound)
}
func startComponents(firstManifestURL, secondManifestURL, apiVersion string) (string, string) {
func startComponents(firstManifestURL, secondManifestURL string) (string, string) {
// Setup
servers := []string{}
glog.Infof("Creating etcd client pointing to %v", servers)
@ -126,13 +125,17 @@ func startComponents(firstManifestURL, secondManifestURL, apiVersion string) (st
glog.Fatalf("Failed to connect to etcd")
}
cl := client.NewOrDie(&client.Config{Host: apiServer.URL, Version: apiVersion})
cl := client.NewOrDie(&client.Config{Host: apiServer.URL, Version: testapi.Default.Version()})
etcdStorage, err := master.NewEtcdStorage(etcdClient, latest.InterfacesFor, latest.Version, etcdtest.PathPrefix())
// TODO: caesarxuchao: hacky way to specify version of Experimental client.
// We will fix this by supporting multiple group versions in Config
cl.ExperimentalClient = client.NewExperimentalOrDie(&client.Config{Host: apiServer.URL, Version: testapi.Experimental.Version()})
etcdStorage, err := master.NewEtcdStorage(etcdClient, latest.InterfacesFor, testapi.Default.Version(), etcdtest.PathPrefix())
if err != nil {
glog.Fatalf("Unable to get etcd storage: %v", err)
}
expEtcdStorage, err := master.NewEtcdStorage(etcdClient, explatest.InterfacesFor, explatest.Version, etcdtest.PathPrefix())
expEtcdStorage, err := master.NewEtcdStorage(etcdClient, explatest.InterfacesFor, testapi.Experimental.Version(), etcdtest.PathPrefix())
if err != nil {
glog.Fatalf("Unable to get etcd storage for experimental: %v", err)
}
@ -891,7 +894,6 @@ func runSchedulerNoPhantomPodsTest(client *client.Client) {
type testFunc func(*client.Client)
func addFlags(fs *pflag.FlagSet) {
fs.StringVar(&apiVersion, "api-version", latest.Version, "API version that should be used by the client for communicating with the server")
fs.IntVar(
&maxConcurrency, "max-concurrency", -1, "Maximum number of tests to be run simultaneously. Unlimited if set to negative.")
}
@ -911,18 +913,21 @@ func main() {
glog.Fatalf("This test has timed out.")
}()
glog.Infof("Running tests for APIVersion: %s", apiVersion)
glog.Infof("Running tests for APIVersion: %s", os.Getenv("KUBE_TEST_API"))
firstManifestURL := ServeCachedManifestFile(testPodSpecFile)
secondManifestURL := ServeCachedManifestFile(testPodSpecFile)
apiServerURL, _ := startComponents(firstManifestURL, secondManifestURL, apiVersion)
apiServerURL, _ := startComponents(firstManifestURL, secondManifestURL)
// Ok. we're good to go.
glog.Infof("API Server started on %s", apiServerURL)
// Wait for the synchronization threads to come up.
time.Sleep(time.Second * 10)
kubeClient := client.NewOrDie(&client.Config{Host: apiServerURL, Version: apiVersion})
kubeClient := client.NewOrDie(&client.Config{Host: apiServerURL, Version: testapi.Default.Version()})
// TODO: caesarxuchao: hacky way to specify version of Experimental client.
// We will fix this by supporting multiple group versions in Config
kubeClient.ExperimentalClient = client.NewExperimentalOrDie(&client.Config{Host: apiServerURL, Version: testapi.Experimental.Version()})
// Run tests in parallel
testFuncs := []testFunc{

View File

@ -68,8 +68,6 @@ type APIServer struct {
AdvertiseAddress net.IP
SecurePort int
ExternalHost string
APIRate float32
APIBurst int
TLSCertFile string
TLSPrivateKeyFile string
CertDirectory string
@ -107,6 +105,7 @@ type APIServer struct {
KubeletConfig client.KubeletConfig
ClusterName string
EnableProfiling bool
EnableWatchCache bool
MaxRequestsInFlight int
MinRequestTimeout int
LongRunningRequestRE string
@ -122,8 +121,6 @@ func NewAPIServer() *APIServer {
InsecureBindAddress: net.ParseIP("127.0.0.1"),
BindAddress: net.ParseIP("0.0.0.0"),
SecurePort: 6443,
APIRate: 10.0,
APIBurst: 200,
APIPrefix: "/api",
ExpAPIPrefix: "/experimental",
EventTTL: 1 * time.Hour,
@ -161,7 +158,7 @@ func (s *APIServer) AddFlags(fs *pflag.FlagSet) {
"The IP address on which to serve the --insecure-port (set to 0.0.0.0 for all interfaces). "+
"Defaults to localhost.")
fs.IPVar(&s.InsecureBindAddress, "address", s.InsecureBindAddress, "DEPRECATED: see --insecure-bind-address instead")
fs.MarkDeprecated("address", "see --insecure-bind-address instread")
fs.MarkDeprecated("address", "see --insecure-bind-address instead")
fs.IPVar(&s.BindAddress, "bind-address", s.BindAddress, ""+
"The IP address on which to serve the --read-only-port and --secure-port ports. The "+
"associated interface(s) must be reachable by the rest of the cluster, and by CLI/web "+
@ -176,8 +173,6 @@ func (s *APIServer) AddFlags(fs *pflag.FlagSet) {
fs.IntVar(&s.SecurePort, "secure-port", s.SecurePort, ""+
"The port on which to serve HTTPS with authentication and authorization. If 0, "+
"don't serve HTTPS at all.")
fs.Float32Var(&s.APIRate, "api-rate", s.APIRate, "API rate limit as QPS for the read only port")
fs.IntVar(&s.APIBurst, "api-burst", s.APIBurst, "API burst amount for the read only port")
fs.StringVar(&s.TLSCertFile, "tls-cert-file", s.TLSCertFile, ""+
"File containing x509 Certificate for HTTPS. (CA cert, if any, concatenated after server cert). "+
"If HTTPS serving is enabled, and --tls-cert-file and --tls-private-key-file are not provided, "+
@ -203,7 +198,7 @@ func (s *APIServer) AddFlags(fs *pflag.FlagSet) {
fs.StringVar(&s.ServiceAccountKeyFile, "service-account-key-file", s.ServiceAccountKeyFile, "File containing PEM-encoded x509 RSA private or public key, used to verify ServiceAccount tokens. If unspecified, --tls-private-key-file is used.")
fs.BoolVar(&s.ServiceAccountLookup, "service-account-lookup", s.ServiceAccountLookup, "If true, validate ServiceAccount tokens exist in etcd as part of authentication.")
fs.StringVar(&s.KeystoneURL, "experimental-keystone-url", s.KeystoneURL, "If passed, activates the keystone authentication plugin")
fs.StringVar(&s.AuthorizationMode, "authorization-mode", s.AuthorizationMode, "Selects how to do authorization on the secure port. One of: "+strings.Join(apiserver.AuthorizationModeChoices, ","))
fs.StringVar(&s.AuthorizationMode, "authorization-mode", s.AuthorizationMode, "Ordered list of plug-ins to do authorization on secure port. Comma-delimited list of: "+strings.Join(apiserver.AuthorizationModeChoices, ","))
fs.StringVar(&s.AuthorizationPolicyFile, "authorization-policy-file", s.AuthorizationPolicyFile, "File with authorization policy in csv format, used with --authorization-mode=ABAC, on the secure port.")
fs.StringVar(&s.AdmissionControl, "admission-control", s.AdmissionControl, "Ordered list of plug-ins to do admission control of resources into cluster. Comma-delimited list of: "+strings.Join(admission.GetPlugins(), ", "))
fs.StringVar(&s.AdmissionControlConfigFile, "admission-control-config-file", s.AdmissionControlConfigFile, "File with admission control configuration.")
@ -222,6 +217,8 @@ func (s *APIServer) AddFlags(fs *pflag.FlagSet) {
fs.Var(&s.RuntimeConfig, "runtime-config", "A set of key=value pairs that describe runtime configuration that may be passed to the apiserver. api/<version> key can be used to turn on/off specific api versions. api/all and api/legacy are special keys to control all and legacy api versions respectively.")
fs.StringVar(&s.ClusterName, "cluster-name", s.ClusterName, "The instance prefix for the cluster")
fs.BoolVar(&s.EnableProfiling, "profiling", true, "Enable profiling via web interface host:port/debug/pprof/")
// TODO: enable cache in integration tests.
fs.BoolVar(&s.EnableWatchCache, "watch-cache", true, "Enable watch caching in the apiserver")
fs.StringVar(&s.ExternalHost, "external-hostname", "", "The hostname to use when generating externalized URLs for this master (e.g. Swagger API Docs.)")
fs.IntVar(&s.MaxRequestsInFlight, "max-requests-inflight", 400, "The maximum number of requests in flight at a given time. When the server exceeds this, it rejects requests. Zero for no limit.")
fs.IntVar(&s.MinRequestTimeout, "min-request-timeout", 1800, "An optional field indicating the minimum number of seconds a handler must keep a request open before timing it out. Currently only honored by the watch request handler, which picks a randomized value above this number as the connection timeout, to spread out load.")
@ -233,7 +230,7 @@ func (s *APIServer) AddFlags(fs *pflag.FlagSet) {
fs.BoolVar(&s.KubeletConfig.EnableHttps, "kubelet-https", s.KubeletConfig.EnableHttps, "Use https for kubelet connections")
fs.UintVar(&s.KubeletConfig.Port, "kubelet-port", s.KubeletConfig.Port, "Kubelet port")
fs.DurationVar(&s.KubeletConfig.HTTPTimeout, "kubelet-timeout", s.KubeletConfig.HTTPTimeout, "Timeout for kubelet operations")
fs.StringVar(&s.KubeletConfig.CertFile, "kubelet-client-certificate", s.KubeletConfig.CertFile, "Path to a client key file for TLS.")
fs.StringVar(&s.KubeletConfig.CertFile, "kubelet-client-certificate", s.KubeletConfig.CertFile, "Path to a client cert file for TLS.")
fs.StringVar(&s.KubeletConfig.KeyFile, "kubelet-client-key", s.KubeletConfig.KeyFile, "Path to a client key file for TLS.")
fs.StringVar(&s.KubeletConfig.CAFile, "kubelet-certificate-authority", s.KubeletConfig.CAFile, "Path to a cert. file for the certificate authority.")
}
@ -380,7 +377,8 @@ func (s *APIServer) Run(_ []string) error {
glog.Fatalf("Invalid Authentication Config: %v", err)
}
authorizer, err := apiserver.NewAuthorizerFromAuthorizationConfig(s.AuthorizationMode, s.AuthorizationPolicyFile)
authorizationModeNames := strings.Split(s.AuthorizationMode, ",")
authorizer, err := apiserver.NewAuthorizerFromAuthorizationConfig(authorizationModeNames, s.AuthorizationPolicyFile)
if err != nil {
glog.Fatalf("Invalid Authorization Config: %v", err)
}
@ -429,6 +427,7 @@ func (s *APIServer) Run(_ []string) error {
EnableUISupport: true,
EnableSwaggerSupport: true,
EnableProfiling: s.EnableProfiling,
EnableWatchCache: s.EnableWatchCache,
EnableIndex: true,
APIPrefix: s.APIPrefix,
ExpAPIPrefix: s.ExpAPIPrefix,

View File

@ -36,6 +36,7 @@ import (
clientcmdapi "k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api"
"k8s.io/kubernetes/pkg/cloudprovider"
"k8s.io/kubernetes/pkg/controller/autoscaler"
"k8s.io/kubernetes/pkg/controller/autoscaler/metrics"
"k8s.io/kubernetes/pkg/controller/endpoint"
"k8s.io/kubernetes/pkg/controller/namespace"
"k8s.io/kubernetes/pkg/controller/node"
@ -291,7 +292,8 @@ func (s *CMServer) Run(_ []string) error {
if err != nil {
glog.Fatalf("Invalid API configuration: %v", err)
}
horizontalPodAutoscalerController := autoscalercontroller.New(kubeClient, expClient)
horizontalPodAutoscalerController := autoscalercontroller.New(kubeClient, expClient,
metrics.NewHeapsterMetricsClient(kubeClient))
horizontalPodAutoscalerController.Run(s.HorizontalPodAutoscalerSyncPeriod)
}

View File

@ -159,9 +159,6 @@ func (s *ProxyServer) Run(_ []string) error {
Namespace: "",
}
// Birth Cry
s.birthCry()
serviceConfig := config.NewServiceConfig()
endpointsConfig := config.NewEndpointsConfig()
@ -200,7 +197,7 @@ func (s *ProxyServer) Run(_ []string) error {
ipt := utiliptables.New(execer, protocol)
proxierUserspace, err := userspace.NewProxier(loadBalancer, s.BindAddress, ipt, s.PortRange, s.SyncPeriod)
if err != nil {
glog.Fatalf("Unable to create proxer: %v", err)
glog.Fatalf("Unable to create proxier: %v", err)
}
proxier = proxierUserspace
// Remove artifacts from the pure-iptables Proxier.
@ -208,6 +205,9 @@ func (s *ProxyServer) Run(_ []string) error {
iptables.CleanupLeftovers(ipt)
}
// Birth Cry after the birth is successful
s.birthCry()
// Wire proxier to handle changes to services
serviceConfig.RegisterHandler(proxier)
// And wire endpointsHandler to handle changes to endpoints to services

View File

@ -124,7 +124,7 @@ type KubeletServer struct {
MaxPods int
DockerExecHandlerName string
ResolverConfig string
CPUCFSQuota bool
// Flags intended for testing
// Crash immediately, rather than eating panics.
@ -189,6 +189,7 @@ func NewKubeletServer() *KubeletServer {
SystemContainer: "",
ConfigureCBR0: false,
DockerExecHandlerName: "native",
CPUCFSQuota: false,
}
}
@ -255,6 +256,7 @@ func (s *KubeletServer) AddFlags(fs *pflag.FlagSet) {
fs.StringVar(&s.DockerExecHandlerName, "docker-exec-handler", s.DockerExecHandlerName, "Handler to use when executing a command in a container. Valid values are 'native' and 'nsenter'. Defaults to 'native'.")
fs.StringVar(&s.PodCIDR, "pod-cidr", "", "The CIDR to use for pod IP addresses, only used in standalone mode. In cluster mode, this is obtained from the master.")
fs.StringVar(&s.ResolverConfig, "resolv-conf", kubelet.ResolvConfDefault, "Resolver configuration file used as the basis for the container DNS resolution configuration.")
fs.BoolVar(&s.CPUCFSQuota, "cpu-cfs-quota", s.CPUCFSQuota, "Enable CPU CFS quota enforcement for containers that specify CPU limits")
// Flags intended for testing, not recommended used in production environments.
fs.BoolVar(&s.ReallyCrashForTesting, "really-crash-for-testing", s.ReallyCrashForTesting, "If true, when panics occur crash. Intended for testing.")
fs.Float64Var(&s.ChaosChance, "chaos-chance", s.ChaosChance, "If > 0.0, introduce random client errors and latency. Intended for testing. [default=0.0]")
@ -362,6 +364,7 @@ func (s *KubeletServer) KubeletConfig() (*KubeletConfig, error) {
MaxPods: s.MaxPods,
DockerExecHandler: dockerExecHandler,
ResolverConfig: s.ResolverConfig,
CPUCFSQuota: s.CPUCFSQuota,
}, nil
}
@ -604,6 +607,7 @@ func SimpleKubelet(client *client.Client,
MaxPods: 32,
DockerExecHandler: &dockertools.NativeExecHandler{},
ResolverConfig: kubelet.ResolvConfDefault,
CPUCFSQuota: false,
}
return &kcfg
}
@ -774,6 +778,7 @@ type KubeletConfig struct {
MaxPods int
DockerExecHandler dockertools.ExecHandler
ResolverConfig string
CPUCFSQuota bool
}
func createAndInitKubelet(kc *KubeletConfig) (k KubeletBootstrap, pc *config.PodConfig, err error) {
@ -833,7 +838,8 @@ func createAndInitKubelet(kc *KubeletConfig) (k KubeletBootstrap, pc *config.Pod
kc.PodCIDR,
kc.MaxPods,
kc.DockerExecHandler,
kc.ResolverConfig)
kc.ResolverConfig,
kc.CPUCFSQuota)
if err != nil {
return nil, nil, err

View File

@ -1,6 +1,5 @@
#!/bin/bash
__debug()
{
if [[ -n ${BASH_COMP_DEBUG_FILE} ]]; then
@ -8,6 +7,14 @@ __debug()
fi
}
# Homebrew on Macs have version 1.3 of bash-completion which doesn't include
# _init_completion. This is a very minimal version of that function.
__my_init_completion()
{
COMPREPLY=()
_get_comp_words_by_ref cur prev words cword
}
__index_of_word()
{
local w word=$1
@ -256,12 +263,10 @@ _kubectl_get()
flags+=("--all-namespaces")
flags+=("--filename=")
flags_with_completion+=("--filename")
flags_completion+=("__handle_filename_extension_flag json|yaml|yml")
flags_completion+=("__handle_filename_extension_flag json|stdin|yaml|yml")
two_word_flags+=("-f")
flags_with_completion+=("-f")
flags_completion+=("__handle_filename_extension_flag json|yaml|yml")
flags+=("--help")
flags+=("-h")
flags_completion+=("__handle_filename_extension_flag json|stdin|yaml|yml")
flags+=("--label-columns=")
two_word_flags+=("-L")
flags+=("--no-headers")
@ -285,6 +290,7 @@ _kubectl_get()
must_have_one_noun+=("deployment")
must_have_one_noun+=("endpoints")
must_have_one_noun+=("event")
must_have_one_noun+=("horizontalpodautoscaler")
must_have_one_noun+=("limitrange")
must_have_one_noun+=("namespace")
must_have_one_noun+=("node")
@ -312,12 +318,10 @@ _kubectl_describe()
flags+=("--filename=")
flags_with_completion+=("--filename")
flags_completion+=("__handle_filename_extension_flag json|yaml|yml")
flags_completion+=("__handle_filename_extension_flag json|stdin|yaml|yml")
two_word_flags+=("-f")
flags_with_completion+=("-f")
flags_completion+=("__handle_filename_extension_flag json|yaml|yml")
flags+=("--help")
flags+=("-h")
flags_completion+=("__handle_filename_extension_flag json|stdin|yaml|yml")
flags+=("--selector=")
two_word_flags+=("-l")
@ -349,12 +353,10 @@ _kubectl_create()
flags+=("--filename=")
flags_with_completion+=("--filename")
flags_completion+=("__handle_filename_extension_flag json|yaml|yml")
flags_completion+=("__handle_filename_extension_flag json|stdin|yaml|yml")
two_word_flags+=("-f")
flags_with_completion+=("-f")
flags_completion+=("__handle_filename_extension_flag json|yaml|yml")
flags+=("--help")
flags+=("-h")
flags_completion+=("__handle_filename_extension_flag json|stdin|yaml|yml")
flags+=("--output=")
two_word_flags+=("-o")
flags+=("--validate")
@ -378,14 +380,12 @@ _kubectl_replace()
flags+=("--cascade")
flags+=("--filename=")
flags_with_completion+=("--filename")
flags_completion+=("__handle_filename_extension_flag json|yaml|yml")
flags_completion+=("__handle_filename_extension_flag json|stdin|yaml|yml")
two_word_flags+=("-f")
flags_with_completion+=("-f")
flags_completion+=("__handle_filename_extension_flag json|yaml|yml")
flags_completion+=("__handle_filename_extension_flag json|stdin|yaml|yml")
flags+=("--force")
flags+=("--grace-period=")
flags+=("--help")
flags+=("-h")
flags+=("--output=")
two_word_flags+=("-o")
flags+=("--timeout=")
@ -409,12 +409,10 @@ _kubectl_patch()
flags+=("--filename=")
flags_with_completion+=("--filename")
flags_completion+=("__handle_filename_extension_flag json|yaml|yml")
flags_completion+=("__handle_filename_extension_flag json|stdin|yaml|yml")
two_word_flags+=("-f")
flags_with_completion+=("-f")
flags_completion+=("__handle_filename_extension_flag json|yaml|yml")
flags+=("--help")
flags+=("-h")
flags_completion+=("__handle_filename_extension_flag json|stdin|yaml|yml")
flags+=("--output=")
two_word_flags+=("-o")
flags+=("--patch=")
@ -440,13 +438,11 @@ _kubectl_delete()
flags+=("--cascade")
flags+=("--filename=")
flags_with_completion+=("--filename")
flags_completion+=("__handle_filename_extension_flag json|yaml|yml")
flags_completion+=("__handle_filename_extension_flag json|stdin|yaml|yml")
two_word_flags+=("-f")
flags_with_completion+=("-f")
flags_completion+=("__handle_filename_extension_flag json|yaml|yml")
flags_completion+=("__handle_filename_extension_flag json|stdin|yaml|yml")
flags+=("--grace-period=")
flags+=("--help")
flags+=("-h")
flags+=("--ignore-not-found")
flags+=("--output=")
two_word_flags+=("-o")
@ -460,6 +456,7 @@ _kubectl_delete()
must_have_one_noun+=("deployment")
must_have_one_noun+=("endpoints")
must_have_one_noun+=("event")
must_have_one_noun+=("horizontalpodautoscaler")
must_have_one_noun+=("limitrange")
must_have_one_noun+=("namespace")
must_have_one_noun+=("node")
@ -485,8 +482,6 @@ _kubectl_namespace()
flags_with_completion=()
flags_completion=()
flags+=("--help")
flags+=("-h")
must_have_one_flag=()
must_have_one_noun=()
@ -506,8 +501,6 @@ _kubectl_logs()
two_word_flags+=("-c")
flags+=("--follow")
flags+=("-f")
flags+=("--help")
flags+=("-h")
flags+=("--interactive")
flags+=("--previous")
flags+=("-p")
@ -530,12 +523,10 @@ _kubectl_rolling-update()
flags+=("--dry-run")
flags+=("--filename=")
flags_with_completion+=("--filename")
flags_completion+=("__handle_filename_extension_flag json|yaml|yml")
flags_completion+=("__handle_filename_extension_flag json|stdin|yaml|yml")
two_word_flags+=("-f")
flags_with_completion+=("-f")
flags_completion+=("__handle_filename_extension_flag json|yaml|yml")
flags+=("--help")
flags+=("-h")
flags_completion+=("__handle_filename_extension_flag json|stdin|yaml|yml")
flags+=("--image=")
flags+=("--no-headers")
flags+=("--output=")
@ -572,12 +563,10 @@ _kubectl_scale()
flags+=("--current-replicas=")
flags+=("--filename=")
flags_with_completion+=("--filename")
flags_completion+=("__handle_filename_extension_flag json|yaml|yml")
flags_completion+=("__handle_filename_extension_flag json|stdin|yaml|yml")
two_word_flags+=("-f")
flags_with_completion+=("-f")
flags_completion+=("__handle_filename_extension_flag json|yaml|yml")
flags+=("--help")
flags+=("-h")
flags_completion+=("__handle_filename_extension_flag json|stdin|yaml|yml")
flags+=("--output=")
two_word_flags+=("-o")
flags+=("--replicas=")
@ -601,8 +590,6 @@ _kubectl_attach()
flags+=("--container=")
two_word_flags+=("-c")
flags+=("--help")
flags+=("-h")
flags+=("--stdin")
flags+=("-i")
flags+=("--tty")
@ -624,8 +611,6 @@ _kubectl_exec()
flags+=("--container=")
two_word_flags+=("-c")
flags+=("--help")
flags+=("-h")
flags+=("--pod=")
two_word_flags+=("-p")
flags+=("--stdin")
@ -647,8 +632,6 @@ _kubectl_port-forward()
flags_with_completion=()
flags_completion=()
flags+=("--help")
flags+=("-h")
flags+=("--pod=")
two_word_flags+=("-p")
@ -670,8 +653,6 @@ _kubectl_proxy()
flags+=("--accept-paths=")
flags+=("--api-prefix=")
flags+=("--disable-filter")
flags+=("--help")
flags+=("-h")
flags+=("--port=")
two_word_flags+=("-p")
flags+=("--reject-methods=")
@ -700,9 +681,8 @@ _kubectl_run()
flags+=("--attach")
flags+=("--command")
flags+=("--dry-run")
flags+=("--env=")
flags+=("--generator=")
flags+=("--help")
flags+=("-h")
flags+=("--hostport=")
flags+=("--image=")
flags+=("--labels=")
@ -743,13 +723,11 @@ _kubectl_stop()
flags+=("--all")
flags+=("--filename=")
flags_with_completion+=("--filename")
flags_completion+=("__handle_filename_extension_flag json|yaml|yml")
flags_completion+=("__handle_filename_extension_flag json|stdin|yaml|yml")
two_word_flags+=("-f")
flags_with_completion+=("-f")
flags_completion+=("__handle_filename_extension_flag json|yaml|yml")
flags_completion+=("__handle_filename_extension_flag json|stdin|yaml|yml")
flags+=("--grace-period=")
flags+=("--help")
flags+=("-h")
flags+=("--ignore-not-found")
flags+=("--output=")
two_word_flags+=("-o")
@ -777,13 +755,11 @@ _kubectl_expose()
flags+=("--external-ip=")
flags+=("--filename=")
flags_with_completion+=("--filename")
flags_completion+=("__handle_filename_extension_flag json|yaml|yml")
flags_completion+=("__handle_filename_extension_flag json|stdin|yaml|yml")
two_word_flags+=("-f")
flags_with_completion+=("-f")
flags_completion+=("__handle_filename_extension_flag json|yaml|yml")
flags_completion+=("__handle_filename_extension_flag json|stdin|yaml|yml")
flags+=("--generator=")
flags+=("--help")
flags+=("-h")
flags+=("--labels=")
two_word_flags+=("-l")
flags+=("--name=")
@ -820,14 +796,13 @@ _kubectl_label()
flags_completion=()
flags+=("--all")
flags+=("--dry-run")
flags+=("--filename=")
flags_with_completion+=("--filename")
flags_completion+=("__handle_filename_extension_flag json|yaml|yml")
flags_completion+=("__handle_filename_extension_flag json|stdin|yaml|yml")
two_word_flags+=("-f")
flags_with_completion+=("-f")
flags_completion+=("__handle_filename_extension_flag json|yaml|yml")
flags+=("--help")
flags+=("-h")
flags_completion+=("__handle_filename_extension_flag json|stdin|yaml|yml")
flags+=("--no-headers")
flags+=("--output=")
two_word_flags+=("-o")
@ -848,6 +823,7 @@ _kubectl_label()
must_have_one_noun+=("deployment")
must_have_one_noun+=("endpoints")
must_have_one_noun+=("event")
must_have_one_noun+=("horizontalpodautoscaler")
must_have_one_noun+=("limitrange")
must_have_one_noun+=("namespace")
must_have_one_noun+=("node")
@ -876,12 +852,10 @@ _kubectl_annotate()
flags+=("--all")
flags+=("--filename=")
flags_with_completion+=("--filename")
flags_completion+=("__handle_filename_extension_flag json|yaml|yml")
flags_completion+=("__handle_filename_extension_flag json|stdin|yaml|yml")
two_word_flags+=("-f")
flags_with_completion+=("-f")
flags_completion+=("__handle_filename_extension_flag json|yaml|yml")
flags+=("--help")
flags+=("-h")
flags_completion+=("__handle_filename_extension_flag json|stdin|yaml|yml")
flags+=("--overwrite")
flags+=("--resource-version=")
@ -900,8 +874,6 @@ _kubectl_config_view()
flags_completion=()
flags+=("--flatten")
flags+=("--help")
flags+=("-h")
flags+=("--merge")
flags+=("--minify")
flags+=("--no-headers")
@ -932,8 +904,6 @@ _kubectl_config_set-cluster()
flags+=("--api-version=")
flags+=("--certificate-authority=")
flags+=("--embed-certs")
flags+=("--help")
flags+=("-h")
flags+=("--insecure-skip-tls-verify")
flags+=("--server=")
@ -954,8 +924,6 @@ _kubectl_config_set-credentials()
flags+=("--client-certificate=")
flags+=("--client-key=")
flags+=("--embed-certs")
flags+=("--help")
flags+=("-h")
flags+=("--password=")
flags+=("--token=")
flags+=("--username=")
@ -975,8 +943,6 @@ _kubectl_config_set-context()
flags_completion=()
flags+=("--cluster=")
flags+=("--help")
flags+=("-h")
flags+=("--namespace=")
flags+=("--user=")
@ -994,8 +960,6 @@ _kubectl_config_set()
flags_with_completion=()
flags_completion=()
flags+=("--help")
flags+=("-h")
must_have_one_flag=()
must_have_one_noun=()
@ -1011,8 +975,6 @@ _kubectl_config_unset()
flags_with_completion=()
flags_completion=()
flags+=("--help")
flags+=("-h")
must_have_one_flag=()
must_have_one_noun=()
@ -1028,8 +990,6 @@ _kubectl_config_use-context()
flags_with_completion=()
flags_completion=()
flags+=("--help")
flags+=("-h")
must_have_one_flag=()
must_have_one_noun=()
@ -1052,8 +1012,6 @@ _kubectl_config()
flags_with_completion=()
flags_completion=()
flags+=("--help")
flags+=("-h")
flags+=("--kubeconfig=")
must_have_one_flag=()
@ -1070,8 +1028,6 @@ _kubectl_cluster-info()
flags_with_completion=()
flags_completion=()
flags+=("--help")
flags+=("-h")
must_have_one_flag=()
must_have_one_noun=()
@ -1087,8 +1043,6 @@ _kubectl_api-versions()
flags_with_completion=()
flags_completion=()
flags+=("--help")
flags+=("-h")
must_have_one_flag=()
must_have_one_noun=()
@ -1106,8 +1060,6 @@ _kubectl_version()
flags+=("--client")
flags+=("-c")
flags+=("--help")
flags+=("-h")
must_have_one_flag=()
must_have_one_noun=()
@ -1153,8 +1105,6 @@ _kubectl()
flags+=("--client-key=")
flags+=("--cluster=")
flags+=("--context=")
flags+=("--help")
flags+=("-h")
flags+=("--insecure-skip-tls-verify")
flags+=("--kubeconfig=")
flags+=("--log-backtrace-at=")
@ -1180,7 +1130,11 @@ _kubectl()
__start_kubectl()
{
local cur prev words cword
_init_completion -s || return
if declare -F _init_completions >/dev/null 2>&1; then
_init_completion -s || return
else
__my_init_completion || return
fi
local c=0
local flags=()

View File

@ -318,7 +318,7 @@ func TestExecutorLaunchAndKillTask(t *testing.T) {
Updates: updates,
APIClient: client.NewOrDie(&client.Config{
Host: testApiServer.server.URL,
Version: testapi.Version(),
Version: testapi.Default.Version(),
}),
Kubelet: &fakeKubelet{
Kubelet: &kubelet.Kubelet{},
@ -355,7 +355,7 @@ func TestExecutorLaunchAndKillTask(t *testing.T) {
assert.Equal(t, nil, err, "must be able to create a task from a pod")
taskInfo := podTask.BuildTaskInfo()
data, err := testapi.Codec().Encode(pod)
data, err := testapi.Default.Codec().Encode(pod)
assert.Equal(t, nil, err, "must be able to encode a pod's spec data")
taskInfo.Data = data
var statusUpdateCalls sync.WaitGroup
@ -484,7 +484,7 @@ func TestExecutorStaticPods(t *testing.T) {
Updates: make(chan interface{}, 1), // allow kube-executor source to proceed past init
APIClient: client.NewOrDie(&client.Config{
Host: testApiServer.server.URL,
Version: testapi.Version(),
Version: testapi.Default.Version(),
}),
Kubelet: &kubelet.Kubelet{},
PodStatusFunc: func(kl KubeletInterface, pod *api.Pod) (*api.PodStatus, error) {
@ -565,7 +565,7 @@ func TestExecutorFrameworkMessage(t *testing.T) {
Updates: make(chan interface{}, 1024),
APIClient: client.NewOrDie(&client.Config{
Host: testApiServer.server.URL,
Version: testapi.Version(),
Version: testapi.Default.Version(),
}),
Kubelet: &fakeKubelet{
Kubelet: &kubelet.Kubelet{},
@ -602,7 +602,7 @@ func TestExecutorFrameworkMessage(t *testing.T) {
*pod, &mesosproto.ExecutorInfo{})
taskInfo := podTask.BuildTaskInfo()
data, _ := testapi.Codec().Encode(pod)
data, _ := testapi.Default.Codec().Encode(pod)
taskInfo.Data = data
mockDriver.On(
@ -660,11 +660,11 @@ func TestExecutorFrameworkMessage(t *testing.T) {
func NewTestPod(i int) *api.Pod {
name := fmt.Sprintf("pod%d", i)
return &api.Pod{
TypeMeta: api.TypeMeta{APIVersion: testapi.Version()},
TypeMeta: api.TypeMeta{APIVersion: testapi.Default.Version()},
ObjectMeta: api.ObjectMeta{
Name: name,
Namespace: api.NamespaceDefault,
SelfLink: testapi.SelfLink("pods", string(i)),
SelfLink: testapi.Default.SelfLink("pods", string(i)),
},
Spec: api.PodSpec{
Containers: []api.Container{
@ -710,7 +710,7 @@ func NewTestServer(t *testing.T, namespace string, pods *api.PodList) *TestServe
}
mux := http.NewServeMux()
mux.HandleFunc(testapi.ResourcePath("bindings", namespace, ""), func(w http.ResponseWriter, r *http.Request) {
mux.HandleFunc(testapi.Default.ResourcePath("bindings", namespace, ""), func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
})

View File

@ -228,6 +228,7 @@ func (s *KubeletExecutorServer) Run(hks hyperkube.Interface, _ []string) error {
MaxPods: s.MaxPods,
DockerExecHandler: dockerExecHandler,
ResolverConfig: s.ResolverConfig,
CPUCFSQuota: s.CPUCFSQuota,
}
kcfg.NodeName = kcfg.Hostname
@ -330,6 +331,7 @@ func (ks *KubeletExecutorServer) createAndInitKubelet(
kc.MaxPods,
kc.DockerExecHandler,
kc.ResolverConfig,
kc.CPUCFSQuota,
)
if err != nil {
return nil, nil, err

View File

@ -21,15 +21,15 @@ import (
"io"
"io/ioutil"
"os"
"os/exec"
"os/signal"
"path"
"strings"
"time"
"syscall"
exservice "k8s.io/kubernetes/contrib/mesos/pkg/executor/service"
"k8s.io/kubernetes/contrib/mesos/pkg/hyperkube"
"k8s.io/kubernetes/contrib/mesos/pkg/minion/config"
"k8s.io/kubernetes/contrib/mesos/pkg/runtime"
"k8s.io/kubernetes/contrib/mesos/pkg/minion/tasks"
"k8s.io/kubernetes/pkg/api/resource"
client "k8s.io/kubernetes/pkg/client/unversioned"
@ -39,6 +39,11 @@ import (
"gopkg.in/natefinch/lumberjack.v2"
)
const (
proxyLogFilename = "proxy.log"
executorLogFilename = "executor.log"
)
type MinionServer struct {
// embed the executor server to be able to use its flags
// TODO(sttts): get rid of this mixing of the minion and the executor server with a multiflags implementation for km
@ -48,16 +53,18 @@ type MinionServer struct {
hks hyperkube.Interface
clientConfig *client.Config
kmBinary string
done chan struct{} // closed when shutting down
exit chan error // to signal fatal errors
tasks []*tasks.Task
pathOverride string // the PATH environment for the sub-processes
cgroupPrefix string // e.g. mesos
cgroupRoot string // e.g. /mesos/{container-id}, determined at runtime
pathOverride string // the PATH environment for the sub-processes
cgroupPrefix string // e.g. mesos
cgroupRoot string // the cgroupRoot that we pass to the kubelet-executor, depends on containPodResources
mesosCgroup string // discovered mesos cgroup root, e.g. /mesos/{container-id}
containPodResources bool
logMaxSize resource.Quantity
logMaxBackups int
logMaxAgeInDays int
logVerbosity int32 // see glog.Level
runProxy bool
proxyLogV int
@ -69,15 +76,12 @@ func NewMinionServer() *MinionServer {
s := &MinionServer{
KubeletExecutorServer: exservice.NewKubeletExecutorServer(),
privateMountNS: false, // disabled until Docker supports customization of the parent mount namespace
done: make(chan struct{}),
exit: make(chan error),
cgroupPrefix: config.DefaultCgroupPrefix,
logMaxSize: config.DefaultLogMaxSize(),
logMaxBackups: config.DefaultLogMaxBackups,
logMaxAgeInDays: config.DefaultLogMaxAgeInDays,
runProxy: true,
cgroupPrefix: config.DefaultCgroupPrefix,
containPodResources: true,
logMaxSize: config.DefaultLogMaxSize(),
logMaxBackups: config.DefaultLogMaxBackups,
logMaxAgeInDays: config.DefaultLogMaxAgeInDays,
runProxy: true,
}
// cache this for later use
@ -131,7 +135,7 @@ func (ms *MinionServer) launchProxyServer() {
fmt.Sprintf("--bind-address=%s", bindAddress),
fmt.Sprintf("--v=%d", ms.proxyLogV),
"--logtostderr=true",
"--resource-container=" + path.Join("/", ms.cgroupRoot, "kube-proxy"),
"--resource-container=" + path.Join("/", ms.mesosCgroup, "kube-proxy"),
}
if ms.clientConfig.Host != "" {
@ -141,10 +145,13 @@ func (ms *MinionServer) launchProxyServer() {
args = append(args, fmt.Sprintf("--hostname-override=%s", ms.KubeletExecutorServer.HostnameOverride))
}
ms.launchHyperkubeServer(hyperkube.CommandProxy, &args, "proxy.log")
ms.launchHyperkubeServer(hyperkube.CommandProxy, args, proxyLogFilename, nil)
}
func (ms *MinionServer) launchExecutorServer() {
// launchExecutorServer returns a chan that closes upon kubelet-executor death. since the kubelet-
// executor doesn't support failover right now, the right thing to do is to fail completely since all
// pods will be lost upon restart and we want mesos to recover the resources from them.
func (ms *MinionServer) launchExecutorServer() <-chan struct{} {
allArgs := os.Args[1:]
// filter out minion flags, leaving those for the executor
@ -153,117 +160,71 @@ func (ms *MinionServer) launchExecutorServer() {
ms.AddExecutorFlags(executorFlags)
executorArgs, _ := filterArgsByFlagSet(allArgs, executorFlags)
executorArgs = append(executorArgs, "--resource-container="+path.Join("/", ms.cgroupRoot, "kubelet"))
executorArgs = append(executorArgs, "--resource-container="+path.Join("/", ms.mesosCgroup, "kubelet"))
if ms.cgroupRoot != "" {
executorArgs = append(executorArgs, "--cgroup-root="+ms.cgroupRoot)
}
// run executor and quit minion server when this exits cleanly
err := ms.launchHyperkubeServer(hyperkube.CommandExecutor, &executorArgs, "executor.log")
if err != nil {
// just return, executor will be restarted on error
log.Error(err)
return
execDied := make(chan struct{})
decorator := func(t *tasks.Task) *tasks.Task {
t.Finished = func(_ bool) bool {
// this func implements the task.finished spec, so when the executor exits
// we return false to indicate that it should not be restarted. we also
// close execDied to signal interested listeners.
close(execDied)
return false
}
// since we only expect to die once, and there is no restart; don't delay any longer than needed
t.RestartDelay = 0
return t
}
log.Info("Executor exited cleanly, stopping the minion")
ms.exit <- nil
ms.launchHyperkubeServer(hyperkube.CommandExecutor, executorArgs, executorLogFilename, decorator)
return execDied
}
func (ms *MinionServer) launchHyperkubeServer(server string, args *[]string, logFileName string) error {
func (ms *MinionServer) launchHyperkubeServer(server string, args []string, logFileName string, decorator func(*tasks.Task) *tasks.Task) {
log.V(2).Infof("Spawning hyperkube %v with args '%+v'", server, args)
// prepare parameters
kmArgs := []string{server}
for _, arg := range *args {
kmArgs = append(kmArgs, arg)
}
// create command
cmd := exec.Command(ms.kmBinary, kmArgs...)
if _, err := cmd.StdoutPipe(); err != nil {
// fatal error => terminate minion
err = fmt.Errorf("error getting stdout of %v: %v", server, err)
ms.exit <- err
return err
}
stderrLogs, err := cmd.StderrPipe()
if err != nil {
// fatal error => terminate minion
err = fmt.Errorf("error getting stderr of %v: %v", server, err)
ms.exit <- err
return err
}
ch := make(chan struct{})
go func() {
defer func() {
select {
case <-ch:
log.Infof("killing %v process...", server)
if err = cmd.Process.Kill(); err != nil {
log.Errorf("failed to kill %v process: %v", server, err)
}
default:
}
}()
maxSize := ms.logMaxSize.Value()
if maxSize > 0 {
// convert to MB
maxSize = maxSize / 1024 / 1024
if maxSize == 0 {
log.Warning("maximal log file size is rounded to 1 MB")
maxSize = 1
}
kmArgs := append([]string{server}, args...)
maxSize := ms.logMaxSize.Value()
if maxSize > 0 {
// convert to MB
maxSize = maxSize / 1024 / 1024
if maxSize == 0 {
log.Warning("maximal log file size is rounded to 1 MB")
maxSize = 1
}
writer := &lumberjack.Logger{
}
writerFunc := func() io.WriteCloser {
return &lumberjack.Logger{
Filename: logFileName,
MaxSize: int(maxSize),
MaxBackups: ms.logMaxBackups,
MaxAge: ms.logMaxAgeInDays,
}
defer writer.Close()
log.V(2).Infof("Starting logging for %v: max log file size %d MB, keeping %d backups, for %d days", server, maxSize, ms.logMaxBackups, ms.logMaxAgeInDays)
<-ch
written, err := io.Copy(writer, stderrLogs)
if err != nil {
log.Errorf("error writing data to %v: %v", logFileName, err)
}
log.Infof("wrote %d bytes to %v", written, logFileName)
}()
}
// use given environment, but add /usr/sbin to the path for the iptables binary used in kube-proxy
var kmEnv []string
if ms.pathOverride != "" {
env := os.Environ()
cmd.Env = make([]string, 0, len(env))
kmEnv = make([]string, 0, len(env))
for _, e := range env {
if !strings.HasPrefix(e, "PATH=") {
cmd.Env = append(cmd.Env, e)
kmEnv = append(kmEnv, e)
}
}
cmd.Env = append(cmd.Env, "PATH="+ms.pathOverride)
kmEnv = append(kmEnv, "PATH="+ms.pathOverride)
}
// if the server fails to start then we exit the executor, otherwise
// wait for the proxy process to end (and release resources after).
if err := cmd.Start(); err != nil {
// fatal error => terminate minion
err = fmt.Errorf("error starting %v: %v", server, err)
ms.exit <- err
return err
t := tasks.New(server, ms.kmBinary, kmArgs, kmEnv, writerFunc)
if decorator != nil {
t = decorator(t)
}
close(ch)
if err := cmd.Wait(); err != nil {
log.Errorf("%v exited with error: %v", server, err)
err = fmt.Errorf("%v exited with error: %v", server, err)
return err
}
return nil
go t.Start()
ms.tasks = append(ms.tasks, t)
}
// runs the main kubelet loop, closing the kubeletFinished chan when the loop exits.
@ -284,30 +245,79 @@ func (ms *MinionServer) Run(hks hyperkube.Interface, _ []string) error {
ms.clientConfig = clientConfig
// derive the executor cgroup and use it as:
// - pod container cgroup root (e.g. docker cgroup-parent)
// - pod container cgroup root (e.g. docker cgroup-parent, optionally; see comments below)
// - parent of kubelet container
// - parent of kube-proxy container
ms.cgroupRoot = findMesosCgroup(ms.cgroupPrefix)
ms.mesosCgroup = findMesosCgroup(ms.cgroupPrefix)
log.Infof("discovered mesos cgroup at %q", ms.mesosCgroup)
// hack alert, this helps to work around systemd+docker+mesos integration problems
// when docker's cgroup-parent flag is used (!containPodResources = don't use the docker flag)
if ms.containPodResources {
ms.cgroupRoot = ms.mesosCgroup
}
cgroupLogger := log.Infof
if ms.cgroupRoot == "" {
cgroupLogger = log.Warningf
}
cgroupLogger("using cgroup-root %q", ms.cgroupRoot)
// run subprocesses until ms.done is closed on return of this function
defer close(ms.done)
if ms.runProxy {
go runtime.Until(ms.launchProxyServer, 5*time.Second, ms.done)
ms.launchProxyServer()
}
go runtime.Until(ms.launchExecutorServer, 5*time.Second, ms.done)
// wait until minion exit is requested
// don't close ms.exit here to avoid panics of go routines writing an error to it
return <-ms.exit
// abort closes when the kubelet-executor dies
abort := ms.launchExecutorServer()
shouldQuit := termSignalListener(abort)
te := tasks.MergeOutput(ms.tasks, shouldQuit)
// TODO(jdef) do something fun here, such as reporting task completion to the apiserver
<-te.Close().Done() // we don't listen for any specific events yet; wait for all tasks to finish
return nil
}
// termSignalListener returns a signal chan that closes when either (a) the process receives a termination
// signal: SIGTERM, SIGINT, or SIGHUP; or (b) the abort chan closes.
func termSignalListener(abort <-chan struct{}) <-chan struct{} {
shouldQuit := make(chan struct{})
sigCh := make(chan os.Signal, 1)
signal.Notify(sigCh)
go func() {
defer close(shouldQuit)
for {
select {
case <-abort:
log.Infof("executor died, aborting")
return
case s, ok := <-sigCh:
if !ok {
return
}
switch s {
case os.Interrupt, os.Signal(syscall.SIGTERM), os.Signal(syscall.SIGINT), os.Signal(syscall.SIGHUP):
log.Infof("received signal %q, aborting", s)
return
case os.Signal(syscall.SIGCHLD): // who cares?
default:
log.Errorf("unexpected signal: %T %#v", s, s)
}
}
}
}()
return shouldQuit
}
func (ms *MinionServer) AddExecutorFlags(fs *pflag.FlagSet) {
ms.KubeletExecutorServer.AddFlags(fs)
// hack to forward log verbosity flag to the executor
fs.Int32Var(&ms.logVerbosity, "v", ms.logVerbosity, "log level for V logs")
}
func (ms *MinionServer) AddMinionFlags(fs *pflag.FlagSet) {
@ -315,6 +325,7 @@ func (ms *MinionServer) AddMinionFlags(fs *pflag.FlagSet) {
fs.StringVar(&ms.cgroupPrefix, "mesos-cgroup-prefix", ms.cgroupPrefix, "The cgroup prefix concatenated with MESOS_DIRECTORY must give the executor cgroup set by Mesos")
fs.BoolVar(&ms.privateMountNS, "private-mountns", ms.privateMountNS, "Enter a private mount NS before spawning procs (linux only). Experimental, not yet compatible with k8s volumes.")
fs.StringVar(&ms.pathOverride, "path-override", ms.pathOverride, "Override the PATH in the environment of the sub-processes.")
fs.BoolVar(&ms.containPodResources, "contain-pod-resources", ms.containPodResources, "Allocate pod CPU and memory resources from offers and reparent pod containers into mesos cgroups; disable if you're having strange mesos/docker/systemd interactions.")
// log file flags
fs.Var(resource.NewQuantityFlagValue(&ms.logMaxSize), "max-log-size", "Maximum log file size for the executor and proxy before rotation")

View File

@ -0,0 +1,20 @@
/*
Copyright 2015 The Kubernetes Authors 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 tasks provides an API for supervising system processes as Task's.
// It provides stronger guarantees with respect to process lifecycle than a
// standalone kubelet running static pods.
package tasks

View File

@ -0,0 +1,98 @@
/*
Copyright 2015 The Kubernetes Authors 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 tasks
type Events interface {
// Close stops delivery of events in the completion and errors channels; callers must close this when they intend to no longer read from completion() or errors()
Close() Events
// Completion reports Completion events as they happen
Completion() <-chan *Completion
// Done returns a signal chan that closes when all tasks have completed and there are no more events to deliver
Done() <-chan struct{}
}
type eventsImpl struct {
tc chan *Completion
stopForwarding chan struct{}
done <-chan struct{}
}
func newEventsImpl(tcin <-chan *Completion, done <-chan struct{}) *eventsImpl {
ei := &eventsImpl{
tc: make(chan *Completion),
stopForwarding: make(chan struct{}),
done: done,
}
go func() {
defer close(ei.tc)
forwardCompletionUntil(tcin, ei.tc, ei.stopForwarding, done, nil)
}()
return ei
}
func (e *eventsImpl) Close() Events { close(e.stopForwarding); return e }
func (e *eventsImpl) Completion() <-chan *Completion { return e.tc }
func (e *eventsImpl) Done() <-chan struct{} { return e.done }
// forwardCompletionUntil is a generic pipe that forwards objects between channels.
// if discard is closed, objects are silently dropped.
// if tap != nil then it's invoked for each object as it's read from tin, but before it's written to tch.
// returns when either reading from tin completes (no more objects, and is closed), or else
// abort is closed, which ever happens first.
func forwardCompletionUntil(tin <-chan *Completion, tch chan<- *Completion, discard <-chan struct{}, abort <-chan struct{}, tap func(*Completion, bool)) {
var tc *Completion
var ok bool
forwardLoop:
for {
select {
case tc, ok = <-tin:
if !ok {
return
}
if tap != nil {
tap(tc, false)
}
select {
case <-abort:
break forwardLoop
case <-discard:
case tch <- tc:
}
case <-abort:
// best effort
select {
case tc, ok = <-tin:
if ok {
if tap != nil {
tap(tc, true)
}
break forwardLoop
}
default:
}
return
}
}
// best effort
select {
case tch <- tc:
case <-discard:
default:
}
}

View File

@ -0,0 +1,348 @@
/*
Copyright 2015 The Kubernetes Authors 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 tasks
import (
"fmt"
"io"
"os/exec"
"sync"
"sync/atomic"
"syscall"
"time"
log "github.com/golang/glog"
"k8s.io/kubernetes/contrib/mesos/pkg/runtime"
)
const defaultTaskRestartDelay = 5 * time.Second
// Completion represents the termination of a Task process. Each process execution should
// yield (barring drops because of an abort signal) exactly one Completion.
type Completion struct {
name string // name of the task
code int // exit code that the task process completed with
err error // process management errors are reported here
}
// systemProcess is a useful abstraction for testing
type systemProcess interface {
// Wait works like exec.Cmd.Wait()
Wait() error
// Kill returns the pid of the process that was killed
Kill() (int, error)
}
type cmdProcess struct {
delegate *exec.Cmd
}
func (cp *cmdProcess) Wait() error {
return cp.delegate.Wait()
}
func (cp *cmdProcess) Kill() (int, error) {
// kill the entire process group, not just the one process
pid := cp.delegate.Process.Pid
processGroup := 0 - pid
// we send a SIGTERM here for a graceful stop. users of this package should
// wait for tasks to complete normally. as a fallback/safeguard, child procs
// are spawned in notStartedTask to receive a SIGKILL when this process dies.
rc := syscall.Kill(processGroup, syscall.SIGTERM)
return pid, rc
}
// task is a specification for running a system process; it provides hooks for customizing
// logging and restart handling as well as provides event channels for communicating process
// termination and errors related to process management.
type Task struct {
Finished func(restarting bool) bool // callback invoked when a task process has completed; if `restarting` then it will be restarted if it returns true
RestartDelay time.Duration // interval between repeated task restarts
name string // required: unique name for this task
bin string // required: path to executable
args []string // optional: process arguments
env []string // optional: process environment override
createLogger func() io.WriteCloser // factory func that builds a log writer
cmd systemProcess // process that we started
completedCh chan *Completion // reports exit codes encountered when task processes exit, or errors during process management
shouldQuit chan struct{} // shouldQuit is closed to indicate that the task should stop its running process, if any
done chan struct{} // done closes when all processes related to the task have terminated
initialState taskStateFn // prepare and start a new live process, defaults to notStartedTask; should be set by run()
runLatch int32 // guard against multiple Task.run calls
}
// New builds a newly initialized task object but does not start any processes for it. callers
// are expected to invoke task.run(...) on their own.
func New(name, bin string, args, env []string, cl func() io.WriteCloser) *Task {
return &Task{
name: name,
bin: bin,
args: args,
env: env,
createLogger: cl,
completedCh: make(chan *Completion),
shouldQuit: make(chan struct{}),
done: make(chan struct{}),
RestartDelay: defaultTaskRestartDelay,
Finished: func(restarting bool) bool { return restarting },
}
}
// Start spawns a goroutine to execute the Task. Panics if invoked more than once.
func (t *Task) Start() {
go t.run(notStartedTask)
}
// run executes the state machine responsible for starting, monitoring, and possibly restarting
// a system process for the task. The initialState func is the entry point of the state machine.
// Upon returning the done and completedCh chans are all closed.
func (t *Task) run(initialState taskStateFn) {
if !atomic.CompareAndSwapInt32(&t.runLatch, 0, 1) {
panic("Task.run() may only be invoked once")
}
t.initialState = initialState
defer close(t.done)
defer close(t.completedCh)
state := initialState
for state != nil {
next := state(t)
state = next
}
}
func (t *Task) tryComplete(tc *Completion) {
select {
case <-t.shouldQuit:
// best effort
select {
case t.completedCh <- tc:
default:
}
case t.completedCh <- tc:
}
}
// tryError is a convenience func that invokes tryComplete with a completion error
func (t *Task) tryError(err error) {
t.tryComplete(&Completion{err: err})
}
type taskStateFn func(*Task) taskStateFn
func taskShouldRestart(t *Task) taskStateFn {
// make our best effort to stop here if signalled (shouldQuit). not doing so here
// could add cost later (a process might be launched).
// sleep for a bit; then return t.initialState
tm := time.NewTimer(t.RestartDelay)
defer tm.Stop()
select {
case <-tm.C:
select {
case <-t.shouldQuit:
default:
if t.Finished(true) {
select {
case <-t.shouldQuit:
// the world has changed, die
return nil
default:
}
return t.initialState
}
// finish call decided not to respawn, so die
return nil
}
case <-t.shouldQuit:
}
// we're quitting, tell the Finished callback and then die
t.Finished(false)
return nil
}
func (t *Task) initLogging(r io.Reader) {
writer := t.createLogger()
go func() {
defer writer.Close()
_, err := io.Copy(writer, r)
if err != nil && err != io.EOF {
// using tryComplete is racy because the state machine closes completedCh and
// so we don't want to attempt to write to a closed/closing chan. so
// just log this for now.
log.Errorf("logger for task %q crashed: %v", t.bin, err)
}
}()
}
// notStartedTask spawns the given task and transitions to a startedTask state
func notStartedTask(t *Task) taskStateFn {
log.Infof("starting task process %q with args '%+v'", t.bin, t.args)
// create command
cmd := exec.Command(t.bin, t.args...)
if _, err := cmd.StdoutPipe(); err != nil {
t.tryError(fmt.Errorf("error getting stdout of %v: %v", t.name, err))
return taskShouldRestart
}
stderrLogs, err := cmd.StderrPipe()
if err != nil {
t.tryError(fmt.Errorf("error getting stderr of %v: %v", t.name, err))
return taskShouldRestart
}
t.initLogging(stderrLogs)
if len(t.env) > 0 {
cmd.Env = t.env
}
cmd.SysProcAttr = sysProcAttr()
// last min check for shouldQuit here
select {
case <-t.shouldQuit:
t.tryError(fmt.Errorf("task execution canceled, aborting process launch"))
return taskShouldRestart
default:
}
if err := cmd.Start(); err != nil {
t.tryError(fmt.Errorf("failed to start task process %q: %v", t.bin, err))
return taskShouldRestart
}
log.Infoln("task started", t.name)
t.cmd = &cmdProcess{delegate: cmd}
return taskRunning
}
type exitError interface {
error
// see os.ProcessState.Sys: returned value can be converted to something like syscall.WaitStatus
Sys() interface{}
}
func taskRunning(t *Task) taskStateFn {
waiter := t.cmd.Wait
var sendOnce sync.Once
trySend := func(wr *Completion) {
// guarded with once because we're only allowed to send a single "result" for each
// process termination. for example, if Kill() results in an error because Wait()
// already completed we only want to return a single result for the process.
sendOnce.Do(func() {
t.tryComplete(wr)
})
}
// listen for normal process completion in a goroutine; don't block because we need to listen for shouldQuit
waitCh := make(chan *Completion, 1)
go func() {
wr := &Completion{}
defer func() {
waitCh <- wr
close(waitCh)
}()
if err := waiter(); err != nil {
if exitError, ok := err.(exitError); ok {
if waitStatus, ok := exitError.Sys().(syscall.WaitStatus); ok {
wr.name = t.name
wr.code = waitStatus.ExitStatus()
return
}
}
wr.err = fmt.Errorf("task wait ended strangely for %q: %v", t.bin, err)
} else {
wr.name = t.name
}
}()
// wait for the process to complete, or else for a "quit" signal on the task (at which point we'll attempt to kill manually)
select {
case <-t.shouldQuit:
// check for tie
select {
case wr := <-waitCh:
// we got a signal to quit, but we're already finished; attempt best effort delvery
trySend(wr)
default:
// Wait() has not exited yet, kill the process
log.Infof("killing %s : %s", t.name, t.bin)
pid, err := t.cmd.Kill()
if err != nil {
trySend(&Completion{err: fmt.Errorf("failed to kill process: %q pid %d: %v", t.bin, pid, err)})
}
// else, Wait() should complete and send a completion event
}
case wr := <-waitCh:
// task has completed before we were told to quit, pass along completion and error information
trySend(wr)
}
return taskShouldRestart
}
// forwardUntil forwards task process completion status and errors to the given output
// chans until either the task terminates or abort is closed.
func (t *Task) forwardUntil(tch chan<- *Completion, abort <-chan struct{}) {
// merge task completion and error until we're told to die, then
// tell the task to stop
defer close(t.shouldQuit)
forwardCompletionUntil(t.completedCh, tch, nil, abort, nil)
}
// MergeOutput waits for the given tasks to complete. meanwhile it logs each time a task
// process completes or generates an error. when shouldQuit closes, tasks are canceled and this
// func eventually returns once all ongoing event handlers have completed running.
func MergeOutput(tasks []*Task, shouldQuit <-chan struct{}) Events {
tc := make(chan *Completion)
var waitForTasks sync.WaitGroup
waitForTasks.Add(len(tasks))
for _, t := range tasks {
t := t
// translate task dead signal into Done
go func() {
<-t.done
waitForTasks.Done()
}()
// fan-in task completion and error events to tc, ec
go t.forwardUntil(tc, shouldQuit)
}
tclistener := make(chan *Completion)
done := runtime.After(func() {
completionFinished := runtime.After(func() {
defer close(tclistener)
forwardCompletionUntil(tc, tclistener, nil, shouldQuit, func(tt *Completion, shutdown bool) {
prefix := ""
if shutdown {
prefix = "(shutdown) "
}
log.Infof(prefix+"task %q exited with status %d", tt.name, tt.code)
})
})
waitForTasks.Wait()
close(tc)
<-completionFinished
})
ei := newEventsImpl(tclistener, done)
return ei
}

View File

@ -0,0 +1,28 @@
/*
Copyright 2015 The Kubernetes Authors 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 tasks
import (
"syscall"
)
func sysProcAttr() *syscall.SysProcAttr {
return &syscall.SysProcAttr{
Setpgid: true,
Pdeathsig: syscall.SIGKILL, // see cmdProcess.Kill
}
}

View File

@ -0,0 +1,38 @@
// +build !linux
/*
Copyright 2015 The Kubernetes Authors 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 tasks
import (
"syscall"
)
func sysProcAttr() *syscall.SysProcAttr {
// TODO(jdef)
// Consequence of not having Pdeathdig is that on non-Linux systems,
// if SIGTERM doesn't stop child procs then they may "leak" and be
// reparented 'up the chain' somewhere when the minion process
// terminates. For example, such child procs end up living indefinitely
// as children of the mesos slave process (I think the slave could handle
// this case, but currently doesn't do it very well). Pdeathsig on Linux
// was a fallback/failsafe mechanism implemented to guard against this. I
// don't know if OS X has any syscalls that do something similar.
return &syscall.SysProcAttr{
Setpgid: true,
}
}

View File

@ -0,0 +1,222 @@
/*
Copyright 2015 The Kubernetes Authors 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 tasks
import (
"bytes"
"errors"
"fmt"
"io"
"sync"
"syscall"
"testing"
log "github.com/golang/glog"
)
type badWriteCloser struct {
err error
}
func (b *badWriteCloser) Write(_ []byte) (int, error) { return 0, b.err }
func (b *badWriteCloser) Close() error { return b.err }
type discardCloser int
func (d discardCloser) Write(b []byte) (int, error) { return len(b), nil }
func (d discardCloser) Close() error { return nil }
var devNull = func() io.WriteCloser { return discardCloser(0) }
type fakeExitError uint32
func (f fakeExitError) Sys() interface{} { return syscall.WaitStatus(f << 8) }
func (f fakeExitError) Error() string { return fmt.Sprintf("fake-exit-error: %d", f) }
type fakeProcess struct {
done chan struct{}
pid int
err error
}
func (f *fakeProcess) Wait() error {
<-f.done
return f.err
}
func (f *fakeProcess) Kill() (int, error) {
close(f.done)
return f.pid, f.err
}
func (f *fakeProcess) exit(code int) {
f.err = fakeExitError(code)
close(f.done)
}
func newFakeProcess() *fakeProcess {
return &fakeProcess{
done: make(chan struct{}),
}
}
func TestBadLogger(t *testing.T) {
err := errors.New("qux")
fp := newFakeProcess()
tt := New("foo", "bar", nil, nil, func() io.WriteCloser {
defer func() {
fp.pid = 123 // sanity check
fp.Kill() // this causes Wait() to return
}()
return &badWriteCloser{err}
})
tt.RestartDelay = 0 // don't slow the test down for no good reason
finishCalled := make(chan struct{})
tt.Finished = func(ok bool) bool {
log.Infof("tt.Finished: ok %t", ok)
if ok {
close(finishCalled)
}
return false // never respawn, this causes t.done to close
}
// abuse eventsImpl: we're not going to listen on the task completion or event chans,
// and we don't want to block the state machine, so discard all task events as they happen
ei := newEventsImpl(tt.completedCh, tt.done)
ei.Close()
go tt.run(func(_ *Task) taskStateFn {
log.Infof("tt initialized")
tt.initLogging(bytes.NewBuffer(([]byte)("unlogged bytes")))
tt.cmd = fp
return taskRunning
})
// if the logger fails the task will be killed
// badWriteLogger generates an error immediately and results in a task kill
<-finishCalled
<-tt.done
// this should never data race since the state machine is dead at this point
if fp.pid != 123 {
t.Fatalf("incorrect pid, expected 123 not %d", fp.pid)
}
// TODO(jdef) would be nice to check for a specific error that indicates the logger died
}
func TestMergeOutput(t *testing.T) {
var tasksStarted, tasksDone sync.WaitGroup
tasksDone.Add(2)
tasksStarted.Add(2)
t1 := New("foo", "", nil, nil, devNull)
t1exited := make(chan struct{})
t1.RestartDelay = 0 // don't slow the test down for no good reason
t1.Finished = func(ok bool) bool {
// we expect each of these cases to happen exactly once
if !ok {
tasksDone.Done()
} else {
close(t1exited)
}
return ok
}
go t1.run(func(t *Task) taskStateFn {
defer tasksStarted.Done()
t.initLogging(bytes.NewBuffer([]byte{}))
t.cmd = newFakeProcess()
return taskRunning
})
t2 := New("bar", "", nil, nil, devNull)
t2exited := make(chan struct{})
t2.RestartDelay = 0 // don't slow the test down for no good reason
t2.Finished = func(ok bool) bool {
// we expect each of these cases to happen exactly once
if !ok {
tasksDone.Done()
} else {
close(t2exited)
}
return ok
}
go t2.run(func(t *Task) taskStateFn {
defer tasksStarted.Done()
t.initLogging(bytes.NewBuffer([]byte{}))
t.cmd = newFakeProcess()
return taskRunning
})
shouldQuit := make(chan struct{})
te := MergeOutput([]*Task{t1, t2}, shouldQuit)
tasksStarted.Wait()
tasksStarted.Add(2) // recycle the barrier
// kill each task once, let it restart; make sure that we get the completion status?
t1.cmd.(*fakeProcess).exit(1)
t2.cmd.(*fakeProcess).exit(2)
codes := map[int]struct{}{}
for i := 0; i < 2; i++ {
switch tc := <-te.Completion(); tc.code {
case 1, 2:
codes[tc.code] = struct{}{}
default:
if tc.err != nil {
t.Errorf("unexpected task completion error: %v", tc.err)
} else {
t.Errorf("unexpected task completion code: %d", tc.code)
}
}
}
te.Close() // we're not going to read any other completion or error events
if len(codes) != 2 {
t.Fatalf("expected each task process to exit once")
}
// each task invokes Finished() once
<-t1exited
<-t2exited
log.Infoln("each task process has completed one round")
tasksStarted.Wait() // tasks will auto-restart their exited procs
// assert that the tasks are not dead; TODO(jdef) not sure that these checks are useful
select {
case <-t1.done:
t.Fatalf("t1 is unexpectedly dead")
default:
}
select {
case <-t2.done:
t.Fatalf("t2 is unexpectedly dead")
default:
}
log.Infoln("firing quit signal")
close(shouldQuit) // fire shouldQuit, and everything should terminate gracefully
log.Infoln("waiting for tasks to die")
tasksDone.Wait() // our tasks should die
log.Infoln("waiting for merge to complete")
<-te.Done() // wait for the merge to complete
}

View File

@ -24,8 +24,42 @@ import (
"k8s.io/kubernetes/contrib/mesos/pkg/scheduler/podtask"
)
type allocationStrategy struct {
fitPredicate podtask.FitPredicate
procurement podtask.Procurement
}
func (a *allocationStrategy) FitPredicate() podtask.FitPredicate {
return a.fitPredicate
}
func (a *allocationStrategy) Procurement() podtask.Procurement {
return a.procurement
}
func NewAllocationStrategy(fitPredicate podtask.FitPredicate, procurement podtask.Procurement) AllocationStrategy {
if fitPredicate == nil {
panic("fitPredicate is required")
}
if procurement == nil {
panic("procurement is required")
}
return &allocationStrategy{
fitPredicate: fitPredicate,
procurement: procurement,
}
}
type fcfsPodScheduler struct {
AllocationStrategy
}
func NewFCFSPodScheduler(as AllocationStrategy) PodScheduler {
return &fcfsPodScheduler{as}
}
// A first-come-first-serve scheduler: acquires the first offer that can support the task
func FCFSScheduleFunc(r offers.Registry, unused SlaveIndex, task *podtask.T) (offers.Perishable, error) {
func (fps *fcfsPodScheduler) SchedulePod(r offers.Registry, unused SlaveIndex, task *podtask.T) (offers.Perishable, error) {
podName := fmt.Sprintf("%s/%s", task.Pod.Namespace, task.Pod.Name)
var acceptedOffer offers.Perishable
err := r.Walk(func(p offers.Perishable) (bool, error) {
@ -33,7 +67,7 @@ func FCFSScheduleFunc(r offers.Registry, unused SlaveIndex, task *podtask.T) (of
if offer == nil {
return false, fmt.Errorf("nil offer while scheduling task %v", task.ID)
}
if task.AcceptOffer(offer) {
if fps.FitPredicate()(task, offer) {
if p.Acquire() {
acceptedOffer = p
log.V(3).Infof("Pod %s accepted offer %v", podName, offer.Id.GetValue())

View File

@ -42,11 +42,11 @@ func (m *MockScheduler) slaveFor(id string) (slave *Slave, ok bool) {
ok = args.Bool(1)
return
}
func (m *MockScheduler) algorithm() (f PodScheduleFunc) {
func (m *MockScheduler) algorithm() (f PodScheduler) {
args := m.Called()
x := args.Get(0)
if x != nil {
f = x.(PodScheduleFunc)
f = x.(PodScheduler)
}
return
}

View File

@ -33,7 +33,6 @@ import (
"k8s.io/kubernetes/contrib/mesos/pkg/runtime"
annotation "k8s.io/kubernetes/contrib/mesos/pkg/scheduler/meta"
"k8s.io/kubernetes/contrib/mesos/pkg/scheduler/podtask"
mresource "k8s.io/kubernetes/contrib/mesos/pkg/scheduler/resource"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/errors"
client "k8s.io/kubernetes/pkg/client/unversioned"
@ -56,8 +55,9 @@ const (
// scheduler abstraction to allow for easier unit testing
type schedulerInterface interface {
sync.Locker // synchronize scheduler plugin operations
SlaveIndex
algorithm() PodScheduleFunc // see types.go
algorithm() PodScheduler
offers() offers.Registry
tasks() podtask.Registry
@ -76,8 +76,8 @@ type k8smScheduler struct {
internal *KubernetesScheduler
}
func (k *k8smScheduler) algorithm() PodScheduleFunc {
return k.internal.scheduleFunc
func (k *k8smScheduler) algorithm() PodScheduler {
return k.internal
}
func (k *k8smScheduler) offers() offers.Registry {
@ -231,10 +231,8 @@ func (b *binder) prepareTaskForLaunch(ctx api.Context, machine string, task *pod
}
type kubeScheduler struct {
api schedulerInterface
podUpdates queue.FIFO
defaultContainerCPULimit mresource.CPUShares
defaultContainerMemLimit mresource.MegaBytes
api schedulerInterface
podUpdates queue.FIFO
}
// recoverAssignedSlave recovers the assigned Mesos slave from a pod by searching
@ -318,7 +316,7 @@ func (k *kubeScheduler) doSchedule(task *podtask.T, err error) (string, error) {
}
}
if err == nil && offer == nil {
offer, err = k.api.algorithm()(k.api.offers(), k.api, task)
offer, err = k.api.algorithm().SchedulePod(k.api.offers(), k.api, task)
}
if err != nil {
return "", err
@ -338,18 +336,8 @@ func (k *kubeScheduler) doSchedule(task *podtask.T, err error) (string, error) {
return "", fmt.Errorf("task.offer assignment must be idempotent, task %+v: offer %+v", task, offer)
}
// write resource limits into the pod spec which is transferred to the executor. From here
// on we can expect that the pod spec of a task has proper limits for CPU and memory.
// TODO(sttts): For a later separation of the kubelet and the executor also patch the pod on the apiserver
if unlimitedCPU := mresource.LimitPodCPU(&task.Pod, k.defaultContainerCPULimit); unlimitedCPU {
log.Warningf("Pod %s/%s without cpu limits is admitted %.2f cpu shares", task.Pod.Namespace, task.Pod.Name, mresource.PodCPULimit(&task.Pod))
}
if unlimitedMem := mresource.LimitPodMem(&task.Pod, k.defaultContainerMemLimit); unlimitedMem {
log.Warningf("Pod %s/%s without memory limits is admitted %.2f MB", task.Pod.Namespace, task.Pod.Name, mresource.PodMemLimit(&task.Pod))
}
task.Offer = offer
task.FillFromDetails(details)
k.api.algorithm().Procurement()(task, details) // TODO(jdef) why is nothing checking the error returned here?
if err := k.api.tasks().Update(task); err != nil {
offer.Release()
@ -556,7 +544,7 @@ func (k *errorHandler) handleSchedulingError(pod *api.Pod, schedulingErr error)
defer k.api.Unlock()
switch task, state := k.api.tasks().Get(task.ID); state {
case podtask.StatePending:
return !task.Has(podtask.Launched) && task.AcceptOffer(offer)
return !task.Has(podtask.Launched) && k.api.algorithm().FitPredicate()(task, offer)
default:
// no point in continuing to check for matching offers
return true
@ -698,10 +686,8 @@ func (k *KubernetesScheduler) NewPluginConfig(terminate <-chan struct{}, mux *ht
Config: &plugin.Config{
MinionLister: nil,
Algorithm: &kubeScheduler{
api: kapi,
podUpdates: podUpdates,
defaultContainerCPULimit: k.defaultContainerCPULimit,
defaultContainerMemLimit: k.defaultContainerMemLimit,
api: kapi,
podUpdates: podUpdates,
},
Binder: &binder{api: kapi},
NextPod: q.yield,

View File

@ -61,13 +61,13 @@ func NewTestServer(t *testing.T, namespace string, mockPodListWatch *MockPodsLis
}
mux := http.NewServeMux()
mux.HandleFunc(testapi.ResourcePath("pods", namespace, ""), func(w http.ResponseWriter, r *http.Request) {
mux.HandleFunc(testapi.Default.ResourcePath("pods", namespace, ""), func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
pods := mockPodListWatch.Pods()
w.Write([]byte(runtime.EncodeOrDie(testapi.Codec(), &pods)))
w.Write([]byte(runtime.EncodeOrDie(testapi.Default.Codec(), &pods)))
})
podsPrefix := testapi.ResourcePath("pods", namespace, "") + "/"
podsPrefix := testapi.Default.ResourcePath("pods", namespace, "") + "/"
mux.HandleFunc(podsPrefix, func(w http.ResponseWriter, r *http.Request) {
name := r.URL.Path[len(podsPrefix):]
@ -79,13 +79,13 @@ func NewTestServer(t *testing.T, namespace string, mockPodListWatch *MockPodsLis
p := mockPodListWatch.GetPod(name)
if p != nil {
w.WriteHeader(http.StatusOK)
w.Write([]byte(runtime.EncodeOrDie(testapi.Codec(), p)))
w.Write([]byte(runtime.EncodeOrDie(testapi.Default.Codec(), p)))
return
}
w.WriteHeader(http.StatusNotFound)
})
mux.HandleFunc(testapi.ResourcePath("events", namespace, ""), func(w http.ResponseWriter, r *http.Request) {
mux.HandleFunc(testapi.Default.ResourcePath("events", namespace, ""), func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
})
@ -196,7 +196,7 @@ func NewTestPod() (*api.Pod, int) {
currentPodNum = currentPodNum + 1
name := fmt.Sprintf("pod%d", currentPodNum)
return &api.Pod{
TypeMeta: api.TypeMeta{APIVersion: testapi.Version()},
TypeMeta: api.TypeMeta{APIVersion: testapi.Default.Version()},
ObjectMeta: api.ObjectMeta{
Name: name,
Namespace: api.NamespaceDefault,
@ -393,13 +393,14 @@ func TestPlugin_LifeCycle(t *testing.T) {
executor.Data = []byte{0, 1, 2}
// create scheduler
as := NewAllocationStrategy(
podtask.DefaultPredicate,
podtask.NewDefaultProcurement(mresource.DefaultDefaultContainerCPULimit, mresource.DefaultDefaultContainerMemLimit))
testScheduler := New(Config{
Executor: executor,
Client: client.NewOrDie(&client.Config{Host: testApiServer.server.URL, Version: testapi.Version()}),
ScheduleFunc: FCFSScheduleFunc,
Schedcfg: *schedcfg.CreateDefaultConfig(),
DefaultContainerCPULimit: mresource.DefaultDefaultContainerCPULimit,
DefaultContainerMemLimit: mresource.DefaultDefaultContainerMemLimit,
Executor: executor,
Client: client.NewOrDie(&client.Config{Host: testApiServer.server.URL, Version: testapi.Default.Version()}),
Scheduler: NewFCFSPodScheduler(as),
Schedcfg: *schedcfg.CreateDefaultConfig(),
})
assert.NotNil(testScheduler.client, "client is nil")

View File

@ -0,0 +1,73 @@
/*
Copyright 2015 The Kubernetes Authors 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 podtask
import (
log "github.com/golang/glog"
mesos "github.com/mesos/mesos-go/mesosproto"
)
// bogus numbers that we use to make sure that there's some set of minimal offered resources on the slave
const (
minimalCpus = 0.01
minimalMem = 0.25
)
var (
DefaultMinimalPredicate = RequireAllPredicate([]FitPredicate{
ValidationPredicate,
NodeSelectorPredicate,
MinimalPodResourcesPredicate,
PortsPredicate,
}).Fit
DefaultMinimalProcurement = AllOrNothingProcurement([]Procurement{
ValidateProcurement,
NodeProcurement,
MinimalPodResourcesProcurement,
PortsProcurement,
}).Procure
)
func MinimalPodResourcesPredicate(t *T, offer *mesos.Offer) bool {
var (
offeredCpus float64
offeredMem float64
)
for _, resource := range offer.Resources {
if resource.GetName() == "cpus" {
offeredCpus = resource.GetScalar().GetValue()
}
if resource.GetName() == "mem" {
offeredMem = resource.GetScalar().GetValue()
}
}
log.V(4).Infof("trying to match offer with pod %v/%v: cpus: %.2f mem: %.2f MB", t.Pod.Namespace, t.Pod.Name, minimalCpus, minimalMem)
if (minimalCpus > offeredCpus) || (minimalMem > offeredMem) {
log.V(3).Infof("not enough resources for pod %v/%v: cpus: %.2f mem: %.2f MB", t.Pod.Namespace, t.Pod.Name, minimalCpus, minimalMem)
return false
}
return true
}
func MinimalPodResourcesProcurement(t *T, details *mesos.Offer) error {
log.V(3).Infof("Recording offer(s) %s/%s against pod %v: cpu: %.2f, mem: %.2f MB", details.Id, t.Pod.Namespace, t.Pod.Name, minimalCpus, minimalMem)
t.Spec.CPU = minimalCpus
t.Spec.Memory = minimalMem
return nil
}

View File

@ -18,7 +18,6 @@ package podtask
import (
"fmt"
"strings"
"time"
"github.com/gogo/protobuf/proto"
@ -28,7 +27,6 @@ import (
"k8s.io/kubernetes/contrib/mesos/pkg/scheduler/metrics"
mresource "k8s.io/kubernetes/contrib/mesos/pkg/scheduler/resource"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/labels"
log "github.com/golang/glog"
mesos "github.com/mesos/mesos-go/mesosproto"
@ -150,59 +148,6 @@ func (t *T) BuildTaskInfo() *mesos.TaskInfo {
return info
}
// Fill the Spec in the T, should be called during k8s scheduling, before binding.
func (t *T) FillFromDetails(details *mesos.Offer) error {
if details == nil {
//programming error
panic("offer details are nil")
}
// compute used resources
cpu := mresource.PodCPULimit(&t.Pod)
mem := mresource.PodMemLimit(&t.Pod)
log.V(3).Infof("Recording offer(s) %s/%s against pod %v: cpu: %.2f, mem: %.2f MB", details.Id, t.Pod.Namespace, t.Pod.Name, cpu, mem)
t.Spec = Spec{
SlaveID: details.GetSlaveId().GetValue(),
AssignedSlave: details.GetHostname(),
CPU: cpu,
Memory: mem,
}
// fill in port mapping
if mapping, err := t.mapper.Generate(t, details); err != nil {
t.Reset()
return err
} else {
ports := []uint64{}
for _, entry := range mapping {
ports = append(ports, entry.OfferPort)
}
t.Spec.PortMap = mapping
t.Spec.Ports = ports
}
// hostname needs of the executor needs to match that of the offer, otherwise
// the kubelet node status checker/updater is very unhappy
const HOSTNAME_OVERRIDE_FLAG = "--hostname-override="
hostname := details.GetHostname() // required field, non-empty
hostnameOverride := HOSTNAME_OVERRIDE_FLAG + hostname
argv := t.executor.Command.Arguments
overwrite := false
for i, arg := range argv {
if strings.HasPrefix(arg, HOSTNAME_OVERRIDE_FLAG) {
overwrite = true
argv[i] = hostnameOverride
break
}
}
if !overwrite {
t.executor.Command.Arguments = append(argv, hostnameOverride)
}
return nil
}
// Clear offer-related details from the task, should be called if/when an offer
// has already been assigned to a task but for some reason is no longer valid.
func (t *T) Reset() {
@ -211,65 +156,6 @@ func (t *T) Reset() {
t.Spec = Spec{}
}
func (t *T) AcceptOffer(offer *mesos.Offer) bool {
if offer == nil {
return false
}
// if the user has specified a target host, make sure this offer is for that host
if t.Pod.Spec.NodeName != "" && offer.GetHostname() != t.Pod.Spec.NodeName {
return false
}
// check the NodeSelector
if len(t.Pod.Spec.NodeSelector) > 0 {
slaveLabels := map[string]string{}
for _, a := range offer.Attributes {
if a.GetType() == mesos.Value_TEXT {
slaveLabels[a.GetName()] = a.GetText().GetValue()
}
}
selector := labels.SelectorFromSet(t.Pod.Spec.NodeSelector)
if !selector.Matches(labels.Set(slaveLabels)) {
return false
}
}
// check ports
if _, err := t.mapper.Generate(t, offer); err != nil {
log.V(3).Info(err)
return false
}
// find offered cpu and mem
var (
offeredCpus mresource.CPUShares
offeredMem mresource.MegaBytes
)
for _, resource := range offer.Resources {
if resource.GetName() == "cpus" {
offeredCpus = mresource.CPUShares(*resource.GetScalar().Value)
}
if resource.GetName() == "mem" {
offeredMem = mresource.MegaBytes(*resource.GetScalar().Value)
}
}
// calculate cpu and mem sum over all containers of the pod
// TODO (@sttts): also support pod.spec.resources.limit.request
// TODO (@sttts): take into account the executor resources
cpu := mresource.PodCPULimit(&t.Pod)
mem := mresource.PodMemLimit(&t.Pod)
log.V(4).Infof("trying to match offer with pod %v/%v: cpus: %.2f mem: %.2f MB", t.Pod.Namespace, t.Pod.Name, cpu, mem)
if (cpu > offeredCpus) || (mem > offeredMem) {
log.V(3).Infof("not enough resources for pod %v/%v: cpus: %.2f mem: %.2f MB", t.Pod.Namespace, t.Pod.Name, cpu, mem)
return false
}
return true
}
func (t *T) Set(f FlagType) {
t.Flags[f] = struct{}{}
if Launched == f {

View File

@ -146,10 +146,10 @@ func TestEmptyOffer(t *testing.T) {
mresource.LimitPodCPU(&task.Pod, mresource.DefaultDefaultContainerCPULimit)
mresource.LimitPodMem(&task.Pod, mresource.DefaultDefaultContainerMemLimit)
if ok := task.AcceptOffer(nil); ok {
if ok := DefaultPredicate(task, nil); ok {
t.Fatalf("accepted nil offer")
}
if ok := task.AcceptOffer(&mesos.Offer{}); ok {
if ok := DefaultPredicate(task, &mesos.Offer{}); ok {
t.Fatalf("accepted empty offer")
}
}
@ -176,7 +176,7 @@ func TestNoPortsInPodOrOffer(t *testing.T) {
mutil.NewScalarResource("mem", 0.001),
},
}
if ok := task.AcceptOffer(offer); ok {
if ok := DefaultPredicate(task, offer); ok {
t.Fatalf("accepted offer %v:", offer)
}
@ -186,7 +186,7 @@ func TestNoPortsInPodOrOffer(t *testing.T) {
mutil.NewScalarResource("mem", t_min_mem),
},
}
if ok := task.AcceptOffer(offer); !ok {
if ok := DefaultPredicate(task, offer); !ok {
t.Fatalf("did not accepted offer %v:", offer)
}
}
@ -203,7 +203,7 @@ func TestAcceptOfferPorts(t *testing.T) {
rangeResource("ports", []uint64{1, 1}),
},
}
if ok := task.AcceptOffer(offer); !ok {
if ok := DefaultPredicate(task, offer); !ok {
t.Fatalf("did not accepted offer %v:", offer)
}
@ -218,17 +218,17 @@ func TestAcceptOfferPorts(t *testing.T) {
mresource.LimitPodCPU(&task.Pod, mresource.DefaultDefaultContainerCPULimit)
mresource.LimitPodMem(&task.Pod, mresource.DefaultDefaultContainerMemLimit)
if ok := task.AcceptOffer(offer); ok {
if ok := DefaultPredicate(task, offer); ok {
t.Fatalf("accepted offer %v:", offer)
}
pod.Spec.Containers[0].Ports[0].HostPort = 1
if ok := task.AcceptOffer(offer); !ok {
if ok := DefaultPredicate(task, offer); !ok {
t.Fatalf("did not accepted offer %v:", offer)
}
pod.Spec.Containers[0].Ports[0].HostPort = 0
if ok := task.AcceptOffer(offer); !ok {
if ok := DefaultPredicate(task, offer); !ok {
t.Fatalf("did not accepted offer %v:", offer)
}
@ -236,12 +236,12 @@ func TestAcceptOfferPorts(t *testing.T) {
mutil.NewScalarResource("cpus", t_min_cpu),
mutil.NewScalarResource("mem", t_min_mem),
}
if ok := task.AcceptOffer(offer); ok {
if ok := DefaultPredicate(task, offer); ok {
t.Fatalf("accepted offer %v:", offer)
}
pod.Spec.Containers[0].Ports[0].HostPort = 1
if ok := task.AcceptOffer(offer); ok {
if ok := DefaultPredicate(task, offer); ok {
t.Fatalf("accepted offer %v:", offer)
}
}
@ -297,7 +297,7 @@ func TestNodeSelector(t *testing.T) {
},
Attributes: ts.attrs,
}
if got, want := task.AcceptOffer(offer), ts.ok; got != want {
if got, want := DefaultPredicate(task, offer), ts.ok; got != want {
t.Fatalf("expected acceptance of offer %v for selector %v to be %v, got %v:", want, got, ts.attrs, ts.selector)
}
}

View File

@ -0,0 +1,110 @@
/*
Copyright 2015 The Kubernetes Authors 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 podtask
import (
log "github.com/golang/glog"
mesos "github.com/mesos/mesos-go/mesosproto"
mresource "k8s.io/kubernetes/contrib/mesos/pkg/scheduler/resource"
"k8s.io/kubernetes/pkg/labels"
)
var DefaultPredicate = RequireAllPredicate([]FitPredicate{
ValidationPredicate,
NodeSelectorPredicate,
PodFitsResourcesPredicate,
PortsPredicate,
}).Fit
// FitPredicate implementations determine if the given task "fits" into offered Mesos resources.
// Neither the task or offer should be modified.
type FitPredicate func(*T, *mesos.Offer) bool
type RequireAllPredicate []FitPredicate
func (f RequireAllPredicate) Fit(t *T, offer *mesos.Offer) bool {
for _, p := range f {
if !p(t, offer) {
return false
}
}
return true
}
func ValidationPredicate(t *T, offer *mesos.Offer) bool {
return t != nil && offer != nil
}
func NodeSelectorPredicate(t *T, offer *mesos.Offer) bool {
// if the user has specified a target host, make sure this offer is for that host
if t.Pod.Spec.NodeName != "" && offer.GetHostname() != t.Pod.Spec.NodeName {
return false
}
// check the NodeSelector
if len(t.Pod.Spec.NodeSelector) > 0 {
slaveLabels := map[string]string{}
for _, a := range offer.Attributes {
if a.GetType() == mesos.Value_TEXT {
slaveLabels[a.GetName()] = a.GetText().GetValue()
}
}
selector := labels.SelectorFromSet(t.Pod.Spec.NodeSelector)
if !selector.Matches(labels.Set(slaveLabels)) {
return false
}
}
return true
}
func PortsPredicate(t *T, offer *mesos.Offer) bool {
// check ports
if _, err := t.mapper.Generate(t, offer); err != nil {
log.V(3).Info(err)
return false
}
return true
}
func PodFitsResourcesPredicate(t *T, offer *mesos.Offer) bool {
// find offered cpu and mem
var (
offeredCpus mresource.CPUShares
offeredMem mresource.MegaBytes
)
for _, resource := range offer.Resources {
if resource.GetName() == "cpus" {
offeredCpus = mresource.CPUShares(*resource.GetScalar().Value)
}
if resource.GetName() == "mem" {
offeredMem = mresource.MegaBytes(*resource.GetScalar().Value)
}
}
// calculate cpu and mem sum over all containers of the pod
// TODO (@sttts): also support pod.spec.resources.limit.request
// TODO (@sttts): take into account the executor resources
cpu := mresource.PodCPULimit(&t.Pod)
mem := mresource.PodMemLimit(&t.Pod)
log.V(4).Infof("trying to match offer with pod %v/%v: cpus: %.2f mem: %.2f MB", t.Pod.Namespace, t.Pod.Name, cpu, mem)
if (cpu > offeredCpus) || (mem > offeredMem) {
log.V(3).Infof("not enough resources for pod %v/%v: cpus: %.2f mem: %.2f MB", t.Pod.Namespace, t.Pod.Name, cpu, mem)
return false
}
return true
}

View File

@ -0,0 +1,152 @@
/*
Copyright 2015 The Kubernetes Authors 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 podtask
import (
"strings"
log "github.com/golang/glog"
mesos "github.com/mesos/mesos-go/mesosproto"
mresource "k8s.io/kubernetes/contrib/mesos/pkg/scheduler/resource"
)
// NewDefaultProcurement returns the default procurement strategy that combines validation
// and responsible Mesos resource procurement. c and m are resource quantities written into
// k8s api.Pod.Spec's that don't declare resources (all containers in k8s-mesos require cpu
// and memory limits).
func NewDefaultProcurement(c mresource.CPUShares, m mresource.MegaBytes) Procurement {
requireSome := &RequireSomePodResources{
defaultContainerCPULimit: c,
defaultContainerMemLimit: m,
}
return AllOrNothingProcurement([]Procurement{
ValidateProcurement,
NodeProcurement,
requireSome.Procure,
PodResourcesProcurement,
PortsProcurement,
}).Procure
}
// Procurement funcs allocate resources for a task from an offer.
// Both the task and/or offer may be modified.
type Procurement func(*T, *mesos.Offer) error
// AllOrNothingProcurement provides a convenient wrapper around multiple Procurement
// objectives: the failure of any Procurement in the set results in Procure failing.
// see AllOrNothingProcurement.Procure
type AllOrNothingProcurement []Procurement
// Procure runs each Procurement in the receiver list. The first Procurement func that
// fails triggers T.Reset() and the error is returned, otherwise returns nil.
func (a AllOrNothingProcurement) Procure(t *T, offer *mesos.Offer) error {
for _, p := range a {
if err := p(t, offer); err != nil {
t.Reset()
return err
}
}
return nil
}
// ValidateProcurement checks that the offered resources are kosher, and if not panics.
// If things check out ok, t.Spec is cleared and nil is returned.
func ValidateProcurement(t *T, offer *mesos.Offer) error {
if offer == nil {
//programming error
panic("offer details are nil")
}
t.Spec = Spec{}
return nil
}
// NodeProcurement updates t.Spec in preparation for the task to be launched on the
// slave associated with the offer.
func NodeProcurement(t *T, offer *mesos.Offer) error {
t.Spec.SlaveID = offer.GetSlaveId().GetValue()
t.Spec.AssignedSlave = offer.GetHostname()
// hostname needs of the executor needs to match that of the offer, otherwise
// the kubelet node status checker/updater is very unhappy
const HOSTNAME_OVERRIDE_FLAG = "--hostname-override="
hostname := offer.GetHostname() // required field, non-empty
hostnameOverride := HOSTNAME_OVERRIDE_FLAG + hostname
argv := t.executor.Command.Arguments
overwrite := false
for i, arg := range argv {
if strings.HasPrefix(arg, HOSTNAME_OVERRIDE_FLAG) {
overwrite = true
argv[i] = hostnameOverride
break
}
}
if !overwrite {
t.executor.Command.Arguments = append(argv, hostnameOverride)
}
return nil
}
type RequireSomePodResources struct {
defaultContainerCPULimit mresource.CPUShares
defaultContainerMemLimit mresource.MegaBytes
}
func (r *RequireSomePodResources) Procure(t *T, offer *mesos.Offer) error {
// write resource limits into the pod spec which is transferred to the executor. From here
// on we can expect that the pod spec of a task has proper limits for CPU and memory.
// TODO(sttts): For a later separation of the kubelet and the executor also patch the pod on the apiserver
// TODO(jdef): changing the state of t.Pod here feels dirty, especially since we don't use a kosher
// method to clone the api.Pod state in T.Clone(). This needs some love.
if unlimitedCPU := mresource.LimitPodCPU(&t.Pod, r.defaultContainerCPULimit); unlimitedCPU {
log.Warningf("Pod %s/%s without cpu limits is admitted %.2f cpu shares", t.Pod.Namespace, t.Pod.Name, mresource.PodCPULimit(&t.Pod))
}
if unlimitedMem := mresource.LimitPodMem(&t.Pod, r.defaultContainerMemLimit); unlimitedMem {
log.Warningf("Pod %s/%s without memory limits is admitted %.2f MB", t.Pod.Namespace, t.Pod.Name, mresource.PodMemLimit(&t.Pod))
}
return nil
}
// PodResourcesProcurement converts k8s pod cpu and memory resource requirements into
// mesos resource allocations.
func PodResourcesProcurement(t *T, offer *mesos.Offer) error {
// compute used resources
cpu := mresource.PodCPULimit(&t.Pod)
mem := mresource.PodMemLimit(&t.Pod)
log.V(3).Infof("Recording offer(s) %s/%s against pod %v: cpu: %.2f, mem: %.2f MB", offer.Id, t.Pod.Namespace, t.Pod.Name, cpu, mem)
t.Spec.CPU = cpu
t.Spec.Memory = mem
return nil
}
// PortsProcurement convert host port mappings into mesos port resource allocations.
func PortsProcurement(t *T, offer *mesos.Offer) error {
// fill in port mapping
if mapping, err := t.mapper.Generate(t, offer); err != nil {
return err
} else {
ports := []uint64{}
for _, entry := range mapping {
ports = append(ports, entry.OfferPort)
}
t.Spec.PortMap = mapping
t.Spec.Ports = ports
}
return nil
}

View File

@ -39,7 +39,6 @@ import (
"k8s.io/kubernetes/contrib/mesos/pkg/scheduler/meta"
"k8s.io/kubernetes/contrib/mesos/pkg/scheduler/metrics"
"k8s.io/kubernetes/contrib/mesos/pkg/scheduler/podtask"
mresource "k8s.io/kubernetes/contrib/mesos/pkg/scheduler/resource"
"k8s.io/kubernetes/contrib/mesos/pkg/scheduler/uid"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/errors"
@ -118,19 +117,17 @@ type KubernetesScheduler struct {
// and the invoking the pod registry interfaces.
// In particular, changes to podtask.T objects are currently guarded by this lock.
*sync.RWMutex
PodScheduler
// Config related, write-once
schedcfg *schedcfg.Config
executor *mesos.ExecutorInfo
executorGroup uint64
scheduleFunc PodScheduleFunc
client *client.Client
etcdClient tools.EtcdClient
failoverTimeout float64 // in seconds
reconcileInterval int64
defaultContainerCPULimit mresource.CPUShares
defaultContainerMemLimit mresource.MegaBytes
schedcfg *schedcfg.Config
executor *mesos.ExecutorInfo
executorGroup uint64
client *client.Client
etcdClient tools.EtcdClient
failoverTimeout float64 // in seconds
reconcileInterval int64
// Mesos context.
@ -157,33 +154,29 @@ type KubernetesScheduler struct {
}
type Config struct {
Schedcfg schedcfg.Config
Executor *mesos.ExecutorInfo
ScheduleFunc PodScheduleFunc
Client *client.Client
EtcdClient tools.EtcdClient
FailoverTimeout float64
ReconcileInterval int64
ReconcileCooldown time.Duration
DefaultContainerCPULimit mresource.CPUShares
DefaultContainerMemLimit mresource.MegaBytes
Schedcfg schedcfg.Config
Executor *mesos.ExecutorInfo
Scheduler PodScheduler
Client *client.Client
EtcdClient tools.EtcdClient
FailoverTimeout float64
ReconcileInterval int64
ReconcileCooldown time.Duration
}
// New creates a new KubernetesScheduler
func New(config Config) *KubernetesScheduler {
var k *KubernetesScheduler
k = &KubernetesScheduler{
schedcfg: &config.Schedcfg,
RWMutex: new(sync.RWMutex),
executor: config.Executor,
executorGroup: uid.Parse(config.Executor.ExecutorId.GetValue()).Group(),
scheduleFunc: config.ScheduleFunc,
client: config.Client,
etcdClient: config.EtcdClient,
failoverTimeout: config.FailoverTimeout,
reconcileInterval: config.ReconcileInterval,
defaultContainerCPULimit: config.DefaultContainerCPULimit,
defaultContainerMemLimit: config.DefaultContainerMemLimit,
schedcfg: &config.Schedcfg,
RWMutex: new(sync.RWMutex),
executor: config.Executor,
executorGroup: uid.Parse(config.Executor.ExecutorId.GetValue()).Group(),
PodScheduler: config.Scheduler,
client: config.Client,
etcdClient: config.EtcdClient,
failoverTimeout: config.FailoverTimeout,
reconcileInterval: config.ReconcileInterval,
offers: offers.CreateRegistry(offers.RegistryConfig{
Compat: func(o *mesos.Offer) bool {
// filter the offers: the executor IDs must not identify a kubelet-

Some files were not shown because too many files have changed in this diff Show More