<commit_before>// Copyright (c) 2020 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

package thrift_test

import (
	"context"
	"net"
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"go.uber.org/net/metrics"
	"go.uber.org/yarpc"
	"go.uber.org/yarpc/api/transport"
	"go.uber.org/yarpc/encoding/thrift/internal/observabilitytest/test"
	"go.uber.org/yarpc/encoding/thrift/internal/observabilitytest/test/testserviceclient"
	"go.uber.org/yarpc/encoding/thrift/internal/observabilitytest/test/testserviceserver"
	"go.uber.org/yarpc/transport/tchannel"
	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
	"go.uber.org/zap/zaptest/observer"
)

const (
	_clientName = "caller"
	_serverName = "callee"

	_wantSuccess              = "success"
	_wantExceptionWithCode    = "exception with code"
	_wantExceptionWithoutCode = "exception with no code"

	// from observability middleware
	_errorInbound  = "Error handling inbound request."
	_errorOutbound = "Error making outbound call."
)

func TestThriftExceptionObservability(t *testing.T) {
	// TODO(apeatsbond): add HTTP test when feature complete.

	t.Run("exception with annotation", func(t *testing.T) {
		client, observedLogs, clientMetricsRoot, serverMetricsRoot, cleanup := initClientAndServer(t)
		defer cleanup()

		ctx, cancel := context.WithTimeout(context.Background(), time.Second)
		defer cancel()

		_, err := client.Call(ctx, _wantExceptionWithCode)
		require.Error(t, err, "expected call error")

		ex, ok := err.(*test.ExceptionWithCode)
		require.True(t, ok, "unexpected Thrift exception")
		assert.Equal(t, _wantExceptionWithCode, ex.Val, "unexpected response")

		t.Run("logs", func(t *testing.T) {
			wantFields := []zapcore.Field{
				zap.String("error", "application_error"),
				zap.String("errorName", "ExceptionWithCode"),
				zap.String("errorCode", "invalid-argument"),
				zap.String("appErrorMessage", "ExceptionWithCode{Val: exception with code}"),
			}
			assertLogs(t, wantFields, observedLogs.TakeAll())
		})

		t.Run("metrics", func(t *testing.T) {
			wantCounters := []counterAssertion{
				{
					Name: "caller_failures",
					Tags: map[string]string{
						"error":      "invalid-argument",
						"error_name": "ExceptionWithCode",
					},
					Value: 1,
				},
				{Name: "calls", Value: 1},
				{Name: "panics"},
				{Name: "successes"},
			}

			assertClientAndServerMetrics(t, wantCounters, clientMetricsRoot, serverMetricsRoot)
		})
	})

	t.Run("exception without annotation ", func(t *testing.T) {
		client, observedLogs, clientMetricsRoot, serverMetricsRoot, cleanup := initClientAndServer(t)
		defer cleanup()

		ctx, cancel := context.WithTimeout(context.Background(), time.Second)
		defer cancel()

		_, err := client.Call(ctx, _wantExceptionWithoutCode)
		require.Error(t, err, "expected call error")

		ex, ok := err.(*test.ExceptionWithoutCode)
		require.True(t, ok, "unexpected Thrift exception")
		assert.Equal(t, _wantExceptionWithoutCode, ex.Val, "unexpected response")

		t.Run("logs", func(t *testing.T) {
			wantFields := []zapcore.Field{
				zap.String("error", "application_error"),
				zap.String("errorName", "ExceptionWithoutCode"),
				zap.String("appErrorMessage", "ExceptionWithoutCode{Val: exception with no code}"),
			}
			assertLogs(t, wantFields, observedLogs.TakeAll())
		})

		t.Run("metrics", func(t *testing.T) {
			wantCounters := []counterAssertion{
				{
					Name: "caller_failures",
					Tags: map[string]string{
						"error":      "application_error",
						"error_name": "ExceptionWithoutCode",
					},
					Value: 1,
				},
				{Name: "calls", Value: 1},
				{Name: "panics"},
				{Name: "successes"},
			}

			assertClientAndServerMetrics(t, wantCounters, clientMetricsRoot, serverMetricsRoot)
		})
	})
}

func assertLogs(t *testing.T, wantFields []zapcore.Field, logs []observer.LoggedEntry) {
	require.Len(t, logs, 2, "unexpected number of logs")

	t.Run("inbound", func(t *testing.T) {
		require.Equal(t, _errorInbound, logs[0].Message, "unexpected log")
		assertLogFields(t, wantFields, logs[0].Context)
	})

	t.Run("outbound", func(t *testing.T) {
		require.Equal(t, _errorOutbound, logs[1].Message, "unexpected log")
		assertLogFields(t, wantFields, logs[1].Context)
	})
}

func assertLogFields(t *testing.T, wantFields, gotContext []zapcore.Field) {
	gotFields := make(map[string]zapcore.Field)
	for _, log := range gotContext {
		gotFields[log.Key] = log
	}

	for _, want := range wantFields {
		got, ok := gotFields[want.Key]
		if assert.True(t, ok, "key %q not found", want.Key) {
			assert.Equal(t, want, got, "unexpected log field")
		}
	}
}

type counterAssertion struct {
	Name  string
	Tags  map[string]string
	Value int
}

func assertClientAndServerMetrics(t *testing.T, counterAssertions []counterAssertion, clientSnapshot, serverSnapshot *metrics.Root) {
	t.Run("inbound", func(t *testing.T) {
		assertMetrics(t, counterAssertions, serverSnapshot.Snapshot().Counters)
	})
	t.Run("outbound", func(t *testing.T) {
		assertMetrics(t, counterAssertions, clientSnapshot.Snapshot().Counters)
	})
}

func assertMetrics(t *testing.T, counterAssertions []counterAssertion, snapshot []metrics.Snapshot) {
	require.Len(t, counterAssertions, len(snapshot), "unexpected number of counters")

	for i, wantCounter := range counterAssertions {
		require.Equal(t, wantCounter.Name, snapshot[i].Name, "unexpected counter")
		assert.EqualValues(t, wantCounter.Value, snapshot[i].Value, "unexpected counter value")
		for wantTagKey, wantTagVal := range wantCounter.Tags {
			assert.Equal(t, wantTagVal, snapshot[i].Tags[wantTagKey], "unexpected value for %q", wantTagKey)
		}
	}
}

func initClientAndServer(
	t *testing.T,
) (
	client testserviceclient.Interface,
	observedLogs *observer.ObservedLogs,
	clientMetricsRoot *metrics.Root,
	serverMetricsRoot *metrics.Root,
	cleanup func(),
) {
	loggerCore, observedLogs := observer.New(zapcore.DebugLevel)
	clientMetricsRoot, serverMetricsRoot = metrics.New(), metrics.New()

	serverAddr, cleanupServer := newServer(t, loggerCore, serverMetricsRoot)
	client, cleanupClient := newClient(t, serverAddr, loggerCore, clientMetricsRoot)

	_ = observedLogs.TakeAll() // ignore all start up logs

	return client, observedLogs, clientMetricsRoot, serverMetricsRoot, func() {
		cleanupServer()
		cleanupClient()
	}
}

func newServer(t *testing.T, loggerCore zapcore.Core, metricsRoot *metrics.Root) (addr string, cleanup func()) {
	listener, err := net.Listen("tcp", "127.0.0.1:0")
	require.NoError(t, err)
	trans, err := tchannel.NewTransport(
		tchannel.ServiceName(_serverName),
		tchannel.Listener(listener))
	require.NoError(t, err)

	dispatcher := yarpc.NewDispatcher(yarpc.Config{
		Name:     _serverName,
		Inbounds: yarpc.Inbounds{trans.NewInbound()},
		Logging: yarpc.LoggingConfig{
			Zap: zap.New(loggerCore),
		},
		Metrics: yarpc.MetricsConfig{
			Metrics: metricsRoot.Scope(),
		},
	})

	dispatcher.Register(testserviceserver.New(&testServer{}))
	require.NoError(t, dispatcher.Start(), "could not start server dispatcher")

	addr = listener.Addr().String()
	cleanup = func() { assert.NoError(t, dispatcher.Stop(), "could not stop dispatcher") }
	return addr, cleanup
}

func newClient(t *testing.T, serverAddr string, loggerCore zapcore.Core, metricsRoot *metrics.Root) (client testserviceclient.Interface, cleanup func()) {
	trans, err := tchannel.NewTransport(tchannel.ServiceName(_clientName))
	require.NoError(t, err)

	dispatcher := yarpc.NewDispatcher(yarpc.Config{
		Name: _clientName,
		Outbounds: map[string]transport.Outbounds{
			_serverName: {
				ServiceName: _serverName,
				Unary:       trans.NewSingleOutbound(serverAddr),
			},
		},
		Logging: yarpc.LoggingConfig{
			Zap: zap.New(loggerCore),
		},
		Metrics: yarpc.MetricsConfig{
			Metrics: metricsRoot.Scope(),
		},
	})

	client = testserviceclient.New(dispatcher.ClientConfig(_serverName))
	require.NoError(t, dispatcher.Start(), "could not start client dispatcher")

	cleanup = func() { assert.NoError(t, dispatcher.Stop(), "could not stop dispatcher") }
	return client, cleanup
}

type testServer struct{}

func (testServer) Call(ctx context.Context, val string) (string, error) {
	switch val {
	case _wantExceptionWithoutCode:
		return "", &test.ExceptionWithoutCode{Val: val}
	case _wantExceptionWithCode:
		return "", &test.ExceptionWithCode{Val: val}
	default: // success
		return val, nil
	}
}
<commit_msg>thrift: Add room in the observability test for HTTP<commit_after>// Copyright (c) 2020 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

package thrift_test

import (
	"context"
	"net"
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"go.uber.org/net/metrics"
	"go.uber.org/yarpc"
	"go.uber.org/yarpc/api/transport"
	"go.uber.org/yarpc/encoding/thrift/internal/observabilitytest/test"
	"go.uber.org/yarpc/encoding/thrift/internal/observabilitytest/test/testserviceclient"
	"go.uber.org/yarpc/encoding/thrift/internal/observabilitytest/test/testserviceserver"
	"go.uber.org/yarpc/transport/tchannel"
	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
	"go.uber.org/zap/zaptest/observer"
)

const (
	_clientName = "caller"
	_serverName = "callee"

	_wantSuccess              = "success"
	_wantExceptionWithCode    = "exception with code"
	_wantExceptionWithoutCode = "exception with no code"

	// from observability middleware
	_errorInbound  = "Error handling inbound request."
	_errorOutbound = "Error making outbound call."
)

func TestThriftExceptionObservability(t *testing.T) {
	transports := []string{tchannel.TransportName}
	for _, _ = range transports {

		// TODO(apeatsbond): add HTTP test when feature complete.

		t.Run("exception with annotation", func(t *testing.T) {
			client, observedLogs, clientMetricsRoot, serverMetricsRoot, cleanup := initClientAndServer(t)
			defer cleanup()

			ctx, cancel := context.WithTimeout(context.Background(), time.Second)
			defer cancel()

			_, err := client.Call(ctx, _wantExceptionWithCode)
			require.Error(t, err, "expected call error")

			ex, ok := err.(*test.ExceptionWithCode)
			require.True(t, ok, "unexpected Thrift exception %v", err)
			assert.Equal(t, _wantExceptionWithCode, ex.Val, "unexpected response")

			t.Run("logs", func(t *testing.T) {
				wantFields := []zapcore.Field{
					zap.String("error", "application_error"),
					zap.String("errorName", "ExceptionWithCode"),
					zap.String("errorCode", "invalid-argument"),
					zap.String("appErrorMessage", "ExceptionWithCode{Val: exception with code}"),
				}
				assertLogs(t, wantFields, observedLogs.TakeAll())
			})

			t.Run("metrics", func(t *testing.T) {
				wantCounters := []counterAssertion{
					{
						Name: "caller_failures",
						Tags: map[string]string{
							"error":      "invalid-argument",
							"error_name": "ExceptionWithCode",
						},
						Value: 1,
					},
					{Name: "calls", Value: 1},
					{Name: "panics"},
					{Name: "successes"},
				}

				assertClientAndServerMetrics(t, wantCounters, clientMetricsRoot, serverMetricsRoot)
			})
		})

		t.Run("exception without annotation ", func(t *testing.T) {
			client, observedLogs, clientMetricsRoot, serverMetricsRoot, cleanup := initClientAndServer(t)
			defer cleanup()

			ctx, cancel := context.WithTimeout(context.Background(), time.Second)
			defer cancel()

			_, err := client.Call(ctx, _wantExceptionWithoutCode)
			require.Error(t, err, "expected call error")

			ex, ok := err.(*test.ExceptionWithoutCode)
			require.True(t, ok, "unexpected Thrift exception")
			assert.Equal(t, _wantExceptionWithoutCode, ex.Val, "unexpected response")

			t.Run("logs", func(t *testing.T) {
				wantFields := []zapcore.Field{
					zap.String("error", "application_error"),
					zap.String("errorName", "ExceptionWithoutCode"),
					zap.String("appErrorMessage", "ExceptionWithoutCode{Val: exception with no code}"),
				}
				assertLogs(t, wantFields, observedLogs.TakeAll())
			})

			t.Run("metrics", func(t *testing.T) {
				wantCounters := []counterAssertion{
					{
						Name: "caller_failures",
						Tags: map[string]string{
							"error":      "application_error",
							"error_name": "ExceptionWithoutCode",
						},
						Value: 1,
					},
					{Name: "calls", Value: 1},
					{Name: "panics"},
					{Name: "successes"},
				}

				assertClientAndServerMetrics(t, wantCounters, clientMetricsRoot, serverMetricsRoot)
			})
		})
	}
}

func assertLogs(t *testing.T, wantFields []zapcore.Field, logs []observer.LoggedEntry) {
	require.Len(t, logs, 2, "unexpected number of logs")

	t.Run("inbound", func(t *testing.T) {
		require.Equal(t, _errorInbound, logs[0].Message, "unexpected log")
		assertLogFields(t, wantFields, logs[0].Context)
	})

	t.Run("outbound", func(t *testing.T) {
		require.Equal(t, _errorOutbound, logs[1].Message, "unexpected log")
		assertLogFields(t, wantFields, logs[1].Context)
	})
}

func assertLogFields(t *testing.T, wantFields, gotContext []zapcore.Field) {
	gotFields := make(map[string]zapcore.Field)
	for _, log := range gotContext {
		gotFields[log.Key] = log
	}

	for _, want := range wantFields {
		got, ok := gotFields[want.Key]
		if assert.True(t, ok, "key %q not found", want.Key) {
			assert.Equal(t, want, got, "unexpected log field")
		}
	}
}

type counterAssertion struct {
	Name  string
	Tags  map[string]string
	Value int
}

func assertClientAndServerMetrics(t *testing.T, counterAssertions []counterAssertion, clientSnapshot, serverSnapshot *metrics.Root) {
	t.Run("inbound", func(t *testing.T) {
		assertMetrics(t, counterAssertions, serverSnapshot.Snapshot().Counters)
	})
	t.Run("outbound", func(t *testing.T) {
		assertMetrics(t, counterAssertions, clientSnapshot.Snapshot().Counters)
	})
}

func assertMetrics(t *testing.T, counterAssertions []counterAssertion, snapshot []metrics.Snapshot) {
	require.Len(t, counterAssertions, len(snapshot), "unexpected number of counters")

	for i, wantCounter := range counterAssertions {
		require.Equal(t, wantCounter.Name, snapshot[i].Name, "unexpected counter")
		assert.EqualValues(t, wantCounter.Value, snapshot[i].Value, "unexpected counter value")
		for wantTagKey, wantTagVal := range wantCounter.Tags {
			assert.Equal(t, wantTagVal, snapshot[i].Tags[wantTagKey], "unexpected value for %q", wantTagKey)
		}
	}
}

func initClientAndServer(
	t *testing.T,
) (
	client testserviceclient.Interface,
	observedLogs *observer.ObservedLogs,
	clientMetricsRoot *metrics.Root,
	serverMetricsRoot *metrics.Root,
	cleanup func(),
) {
	loggerCore, observedLogs := observer.New(zapcore.DebugLevel)
	clientMetricsRoot, serverMetricsRoot = metrics.New(), metrics.New()

	serverAddr, cleanupServer := newServer(t, loggerCore, serverMetricsRoot)
	client, cleanupClient := newClient(t, serverAddr, loggerCore, clientMetricsRoot)

	_ = observedLogs.TakeAll() // ignore all start up logs

	return client, observedLogs, clientMetricsRoot, serverMetricsRoot, func() {
		cleanupServer()
		cleanupClient()
	}
}

func newServer(t *testing.T, loggerCore zapcore.Core, metricsRoot *metrics.Root) (addr string, cleanup func()) {
	listener, err := net.Listen("tcp", "127.0.0.1:0")
	require.NoError(t, err)
	trans, err := tchannel.NewTransport(
		tchannel.ServiceName(_serverName),
		tchannel.Listener(listener))
	require.NoError(t, err)

	dispatcher := yarpc.NewDispatcher(yarpc.Config{
		Name:     _serverName,
		Inbounds: yarpc.Inbounds{trans.NewInbound()},
		Logging: yarpc.LoggingConfig{
			Zap: zap.New(loggerCore),
		},
		Metrics: yarpc.MetricsConfig{
			Metrics: metricsRoot.Scope(),
		},
	})

	dispatcher.Register(testserviceserver.New(&testServer{}))
	require.NoError(t, dispatcher.Start(), "could not start server dispatcher")

	addr = listener.Addr().String()
	cleanup = func() { assert.NoError(t, dispatcher.Stop(), "could not stop dispatcher") }
	return addr, cleanup
}

func newClient(t *testing.T, serverAddr string, loggerCore zapcore.Core, metricsRoot *metrics.Root) (client testserviceclient.Interface, cleanup func()) {
	trans, err := tchannel.NewTransport(tchannel.ServiceName(_clientName))
	require.NoError(t, err)

	dispatcher := yarpc.NewDispatcher(yarpc.Config{
		Name: _clientName,
		Outbounds: map[string]transport.Outbounds{
			_serverName: {
				ServiceName: _serverName,
				Unary:       trans.NewSingleOutbound(serverAddr),
			},
		},
		Logging: yarpc.LoggingConfig{
			Zap: zap.New(loggerCore),
		},
		Metrics: yarpc.MetricsConfig{
			Metrics: metricsRoot.Scope(),
		},
	})

	client = testserviceclient.New(dispatcher.ClientConfig(_serverName))
	require.NoError(t, dispatcher.Start(), "could not start client dispatcher")

	cleanup = func() { assert.NoError(t, dispatcher.Stop(), "could not stop dispatcher") }
	return client, cleanup
}

type testServer struct{}

func (testServer) Call(ctx context.Context, val string) (string, error) {
	switch val {
	case _wantExceptionWithoutCode:
		return "", &test.ExceptionWithoutCode{Val: val}
	case _wantExceptionWithCode:
		return "", &test.ExceptionWithCode{Val: val}
	default: // success
		return val, nil
	}
}
<|endoftext|>


<commit_before>package smitego

import (
	"fmt"
	"golang.org/x/net/context"
	"testing"
	. "github.com/smartystreets/goconvey/convey"
	"net/http"
	"strings"
	"io/ioutil"
)

type errorRT struct {
}

func (t *errorRT) RoundTrip(req *http.Request) (*http.Response, error) {
	return nil, t
}

func (t *errorRT) Error() string {
	return "An error in transport"
}

type staticResp struct {
	Resp *http.Response
}

func (t *staticResp) RoundTrip(req *http.Request) (*http.Response, error) {
	return t.Resp, nil
}

// Example of creating a session and making a function call.
func ExampleSession() {
	// First make a client to describe how you want to connect.  Each client returns a session
	// and primary function calls are done on the session.  Concurrent sessions are limited
	// by HiRez
	client := Client{
		DevID:   123,
		AuthKey: "AuthKey123",
	}

	// A context is how you can time out function calls
	ctx := context.Background()

	// Some functions don't require a session first and can be called on the client directly
	_ = client.Ping(ctx)

	// Most functions require a session

	session, _ := client.CreateSession(ctx)
	gods, _ := session.GetGods(ctx, English)
	fmt.Printf("Got %d gods\n", len(gods))
}

func TestClient(t *testing.T) {
	Convey("With a client", t, func() {
		c := Client {
		}
		ctx := context.Background()
		Convey("that errors http connections", func() {
			rt := &errorRT{}
			c.HTTPClient.Transport = rt
			Convey("Connections should error", func() {
				err := c.Ping(ctx)
				So(err, ShouldNotBeNil)
				So(err.Error(), ShouldContainSubstring, rt.Error())
			})
		})
		Convey("that 404s", func() {
			sr := &staticResp{
				Resp: &http.Response{
					StatusCode: http.StatusNotFound,
					Body: ioutil.NopCloser(strings.NewReader("")),
				},
			}
			c.HTTPClient.Transport = sr
			Convey("requests should error", func() {
				err := c.Ping(ctx)
				So(err, ShouldNotBeNil)
				So(err.Error(), ShouldContainSubstring, fmt.Sprintf("%d", http.StatusNotFound))
			})
		})

	})
}
<commit_msg>Reformat code<commit_after>package smitego

import (
	"fmt"
	. "github.com/smartystreets/goconvey/convey"
	"golang.org/x/net/context"
	"io/ioutil"
	"net/http"
	"strings"
	"testing"
)

type errorRT struct {
}

func (t *errorRT) RoundTrip(req *http.Request) (*http.Response, error) {
	return nil, t
}

func (t *errorRT) Error() string {
	return "An error in transport"
}

type staticResp struct {
	Resp *http.Response
}

func (t *staticResp) RoundTrip(req *http.Request) (*http.Response, error) {
	return t.Resp, nil
}

// Example of creating a session and making a function call.
func ExampleSession() {
	// First make a client to describe how you want to connect.  Each client returns a session
	// and primary function calls are done on the session.  Concurrent sessions are limited
	// by HiRez
	client := Client{
		DevID:   123,
		AuthKey: "AuthKey123",
	}

	// A context is how you can time out function calls
	ctx := context.Background()

	// Some functions don't require a session first and can be called on the client directly
	_ = client.Ping(ctx)

	// Most functions require a session

	session, _ := client.CreateSession(ctx)
	gods, _ := session.GetGods(ctx, English)
	fmt.Printf("Got %d gods\n", len(gods))
}

func TestClient(t *testing.T) {
	Convey("With a client", t, func() {
		c := Client{}
		ctx := context.Background()
		Convey("that errors http connections", func() {
			rt := &errorRT{}
			c.HTTPClient.Transport = rt
			Convey("Connections should error", func() {
				err := c.Ping(ctx)
				So(err, ShouldNotBeNil)
				So(err.Error(), ShouldContainSubstring, rt.Error())
			})
		})
		Convey("that 404s", func() {
			sr := &staticResp{
				Resp: &http.Response{
					StatusCode: http.StatusNotFound,
					Body:       ioutil.NopCloser(strings.NewReader("")),
				},
			}
			c.HTTPClient.Transport = sr
			Convey("requests should error", func() {
				err := c.Ping(ctx)
				So(err, ShouldNotBeNil)
				So(err.Error(), ShouldContainSubstring, fmt.Sprintf("%d", http.StatusNotFound))
			})
		})

	})
}
<|endoftext|>


<commit_before>package mongo

import (
	"gopkg.in/mgo.v2/bson"
	"testing"
)

type HelloWorld struct {
	Id    bson.ObjectId `bson:"_id,omitempty"`
	Name  string        `bson:"name,omitempty"`
	Value string        `bson:"value,omitempty"`
}

const collectionName = "testCollection"

func connect(t *testing.T) *Client {
	m := Client{}
	err := m.Connect("localhost", "local", collectionName)

	if err != nil {
		t.Errorf("Could not connect to database: %s", err.Error())
	}

	return &m
}

func TestAnonymousConnection(t *testing.T) {
	connect(t)
}

func TestCreateCollection(t *testing.T) {
	m := connect(t)
	if m == nil {
		return
	}

	err := m.CreateCollection()
	if err != nil {
		t.Errorf("Could not cteate test collection: %s", err.Error())
	}
}

func TestInsertCollection(t *testing.T) {
	m := connect(t)
	if m == nil {
		return
	}

	v := HelloWorld{Name: "World", Value: "Hello"}
	err := m.Insert(&v)
	if err != nil {
		t.Errorf("Could not insert test data to collection: %s", err.Error())
	}
}

func TestFindDocuments(t *testing.T) {
	m := connect(t)
	if m == nil {
		return
	}

	q := HelloWorld{Name: "World"}
	v := []HelloWorld{}
	err := m.FindByValue(q, &v)
	if err != nil {
		t.Errorf("Could not lookup collection: %s", err.Error())
		return
	}

	if v[0].Name != "World" || v[0].Value != "Hello" {
		t.Errorf("Got back invalid data: name=%s, value=%s", v[0].Name, v[0].Value)
	}
}

func TestReadDocument(t *testing.T) {
	m := connect(t)
	if m == nil {
		return
	}

	q := HelloWorld{Name: "World"}
	err := m.ReadByValue(&q)
	if err != nil {
		t.Errorf("Could not read from collection %s", err.Error())
		return
	}

	if q.Name != "World" || q.Value != "Hello" {
		t.Errorf("Got back invalid data: name=%s, value=%s", q.Name, q.Value)
	}
}

func testDeleteCollection(t *testing.T) {
	m := connect(t)
	if m == nil {
		return
	}

	err := m.DropCollection()
	if err != nil {
		t.Errorf("Could not delete test collection: %s", err.Error())
	}
}
<commit_msg>Re-enable DB cleanup during tests<commit_after>package mongo

import (
	"gopkg.in/mgo.v2/bson"
	"testing"
)

type HelloWorld struct {
	Id    bson.ObjectId `bson:"_id,omitempty"`
	Name  string        `bson:"name,omitempty"`
	Value string        `bson:"value,omitempty"`
}

const collectionName = "testCollection"

func connect(t *testing.T) *Client {
	m := Client{}
	err := m.Connect("localhost", "local", collectionName)

	if err != nil {
		t.Errorf("Could not connect to database: %s", err.Error())
	}

	return &m
}

func TestAnonymousConnection(t *testing.T) {
	connect(t)
}

func TestCreateCollection(t *testing.T) {
	m := connect(t)
	if m == nil {
		return
	}

	err := m.CreateCollection()
	if err != nil {
		t.Errorf("Could not cteate test collection: %s", err.Error())
	}
}

func TestInsertCollection(t *testing.T) {
	m := connect(t)
	if m == nil {
		return
	}

	v := HelloWorld{Name: "World", Value: "Hello"}
	err := m.Insert(&v)
	if err != nil {
		t.Errorf("Could not insert test data to collection: %s", err.Error())
	}
}

func TestFindDocuments(t *testing.T) {
	m := connect(t)
	if m == nil {
		return
	}

	q := HelloWorld{Name: "World"}
	v := []HelloWorld{}
	err := m.FindByValue(q, &v)
	if err != nil {
		t.Errorf("Could not lookup collection: %s", err.Error())
		return
	}

	if v[0].Name != "World" || v[0].Value != "Hello" {
		t.Errorf("Got back invalid data: name=%s, value=%s", v[0].Name, v[0].Value)
	}
}

func TestReadDocument(t *testing.T) {
	m := connect(t)
	if m == nil {
		return
	}

	q := HelloWorld{Name: "World"}
	err := m.ReadByValue(&q)
	if err != nil {
		t.Errorf("Could not read from collection %s", err.Error())
		return
	}

	if q.Name != "World" || q.Value != "Hello" {
		t.Errorf("Got back invalid data: name=%s, value=%s", q.Name, q.Value)
	}
}

func TestDeleteCollection(t *testing.T) {
	m := connect(t)
	if m == nil {
		return
	}

	err := m.DropCollection()
	if err != nil {
		t.Errorf("Could not delete test collection: %s", err.Error())
	}
}
<|endoftext|>


<commit_before>package content

import (
	"text/template"
)

const (
	// BootkubeSystemdContents is a service for running bootkube on the bootstrap
	// nodes
	BootkubeSystemdContents = `[Unit]
Description=Bootstrap a Kubernetes cluster
Wants=kubelet.service
After=kubelet.service

[Service]
WorkingDirectory=/opt/tectonic

ExecStart=/opt/tectonic/bootkube.sh

Restart=on-failure
RestartSec=5s`
)

var (
	// BootkubeShFileTemplate is a script file for running bootkube on the
	// bootstrap nodes.
	BootkubeShFileTemplate = template.Must(template.New("bootkube.sh").Parse(`#!/usr/bin/env bash
set -e

mkdir --parents /etc/kubernetes/manifests/

MACHINE_CONFIG_OPERATOR_IMAGE=$(podman run --rm {{.ReleaseImage}} image machine-config-operator)
echo "Found Machine Config Operator's image: $MACHINE_CONFIG_OPERATOR_IMAGE"

if [ ! -d cvo-bootstrap ]
then
	echo "Rendering Cluster Version Operator Manifests..."

	# shellcheck disable=SC2154
	podman run \
		--volume "$PWD:/assets:z" \
		"{{.ReleaseImage}}" \
		render \
			--output-dir=/assets/cvo-bootstrap \
			--release-image="{{.ReleaseImage}}"

	cp --recursive cvo-bootstrap/manifests .
	cp --recursive cvo-bootstrap/bootstrap/bootstrap-pod.yaml /etc/kubernetes/manifests/
fi

if [ ! -d kco-bootstrap ]
then
	echo "Rendering Kubernetes core manifests..."

	# shellcheck disable=SC2154
	podman run \
		--volume "$PWD:/assets:z" \
		--volume /etc/kubernetes:/etc/kubernetes:z \
		"{{.KubeCoreRenderImage}}" \
		--config=/assets/kco-config.yaml \
		--output=/assets/kco-bootstrap

	cp --recursive kco-bootstrap/bootstrap-configs /etc/kubernetes/bootstrap-configs
	cp --recursive kco-bootstrap/bootstrap-manifests .
	cp --recursive kco-bootstrap/manifests .
fi

if [ ! -d mco-bootstrap ]
then
	echo "Rendering MCO manifests..."

	# shellcheck disable=SC2154
	podman run \
		--user 0 \
		--volume "$PWD:/assets:z" \
		"${MACHINE_CONFIG_OPERATOR_IMAGE}" \
		bootstrap \
			--etcd-ca=/assets/tls/etcd-client-ca.crt \
			--root-ca=/assets/tls/root-ca.crt \
			--config-file=/assets/manifests/cluster-config.yaml \
			--dest-dir=/assets/mco-bootstrap \
			--images-json-configmap=/assets/manifests/machine-config-operator-01-images-configmap.yaml

	# Bootstrap MachineConfigController uses /etc/mcc/bootstrap/manifests/ dir to
	# 1. read the controller config rendered by MachineConfigOperator
	# 2. read the default MachineConfigPools rendered by MachineConfigOperator
	# 3. read any additional MachineConfigs that are needed for the default MachineConfigPools.
	mkdir --parents /etc/mcc/bootstrap/
	cp --recursive mco-bootstrap/manifests /etc/mcc/bootstrap/manifests
	cp mco-bootstrap/machineconfigoperator-bootstrap-pod.yaml /etc/kubernetes/manifests/

	# /etc/ssl/mcs/tls.{crt, key} are locations for MachineConfigServer's tls assets.
	mkdir --parents /etc/ssl/mcs/
	cp tls/machine-config-server.crt /etc/ssl/mcs/tls.crt
	cp tls/machine-config-server.key /etc/ssl/mcs/tls.key
fi

# We originally wanted to run the etcd cert signer as
# a static pod, but kubelet could't remove static pod
# when API server is not up, so we have to run this as
# podman container.
# See https://github.com/kubernetes/kubernetes/issues/43292

echo "Starting etcd certificate signer..."

trap "podman rm --force etcd-signer" ERR

# shellcheck disable=SC2154
podman run \
	--name etcd-signer \
	--detach \
	--volume /opt/tectonic/tls:/opt/tectonic/tls:ro,z \
	--network host \
	"{{.EtcdCertSignerImage}}" \
	serve \
	--cacrt=/opt/tectonic/tls/etcd-client-ca.crt \
	--cakey=/opt/tectonic/tls/etcd-client-ca.key \
	--servcrt=/opt/tectonic/tls/apiserver.crt \
	--servkey=/opt/tectonic/tls/apiserver.key \
	--address=0.0.0.0:6443 \
	--csrdir=/tmp \
	--peercertdur=26280h \
	--servercertdur=26280h

echo "Waiting for etcd cluster..."

# Wait for the etcd cluster to come up.
set +e
# shellcheck disable=SC2154,SC2086
until podman run \
		--rm \
		--network host \
		--name etcdctl \
		--env ETCDCTL_API=3 \
		--volume /opt/tectonic/tls:/opt/tectonic/tls:ro,z \
		"{{.EtcdctlImage}}" \
		/usr/local/bin/etcdctl \
		--dial-timeout=10m \
		--cacert=/opt/tectonic/tls/etcd-client-ca.crt \
		--cert=/opt/tectonic/tls/etcd-client.crt \
		--key=/opt/tectonic/tls/etcd-client.key \
		--endpoints={{.EtcdCluster}} \
		endpoint health
do
	echo "etcdctl failed. Retrying in 5 seconds..."
	sleep 5
done
set -e

echo "etcd cluster up. Killing etcd certificate signer..."

podman rm --force etcd-signer
rm --force /etc/kubernetes/manifests/machineconfigoperator-bootstrap-pod.yaml

echo "Starting bootkube..."

# shellcheck disable=SC2154
podman run \
	--rm \
	--volume "$PWD:/assets:z" \
	--volume /etc/kubernetes:/etc/kubernetes:z \
	--network=host \
	--entrypoint=/bootkube \
	"{{.BootkubeImage}}" \
	start --asset-dir=/assets`))
)
<commit_msg>render manifests for cluster-kube-apiserver and cluster-kube-controller-manager<commit_after>package content

import (
	"text/template"
)

const (
	// BootkubeSystemdContents is a service for running bootkube on the bootstrap
	// nodes
	BootkubeSystemdContents = `[Unit]
Description=Bootstrap a Kubernetes cluster
Wants=kubelet.service
After=kubelet.service

[Service]
WorkingDirectory=/opt/tectonic

ExecStart=/opt/tectonic/bootkube.sh

Restart=on-failure
RestartSec=5s`
)

var (
	// BootkubeShFileTemplate is a script file for running bootkube on the
	// bootstrap nodes.
	BootkubeShFileTemplate = template.Must(template.New("bootkube.sh").Parse(`#!/usr/bin/env bash
set -e

mkdir --parents /etc/kubernetes/manifests/

MACHINE_CONFIG_OPERATOR_IMAGE=$(podman run --rm {{.ReleaseImage}} image machine-config-operator)
echo "Found Machine Config Operator's image: $MACHINE_CONFIG_OPERATOR_IMAGE"

KUBE_APISERVER_OPERATOR_IMAGE=$(podman run --rm {{.ReleaseImage}} image cluster-kube-apiserver-operator)
KUBE_CONTROLLER_MANAGER_OPERATOR_IMAGE=$(podman run --rm {{.ReleaseImage}} image cluster-kube-controller-manager-operator)

if [ ! -d cvo-bootstrap ]
then
	echo "Rendering Cluster Version Operator Manifests..."

	# shellcheck disable=SC2154
	podman run \
		--volume "$PWD:/assets:z" \
		"{{.ReleaseImage}}" \
		render \
			--output-dir=/assets/cvo-bootstrap \
			--release-image="{{.ReleaseImage}}"

	cp --recursive cvo-bootstrap/manifests .
	cp --recursive cvo-bootstrap/bootstrap/bootstrap-pod.yaml /etc/kubernetes/manifests/
fi

if [ ! -d kco-bootstrap ]
then
	echo "Rendering Kubernetes core manifests..."

	# shellcheck disable=SC2154
	podman run \
		--volume "$PWD:/assets:z" \
		--volume /etc/kubernetes:/etc/kubernetes:z \
		"{{.KubeCoreRenderImage}}" \
		--config=/assets/kco-config.yaml \
		--output=/assets/kco-bootstrap

	cp --recursive kco-bootstrap/bootstrap-configs /etc/kubernetes/bootstrap-configs
	cp --recursive kco-bootstrap/bootstrap-manifests .
	cp --recursive kco-bootstrap/manifests .
fi

if [ ! -d kube-apiserver-bootstrap ]
then
	echo "Rendering Kubernetes API server core manifests..."

	# shellcheck disable=SC2154
	podman run \
		--volume "$PWD:/assets:z" \
		"${KUBE_APISERVER_OPERATOR_IMAGE}" \
		/usr/bin/cluster-kube-apiserver-operator render \
		--asset-input-dir=/assets/tls \
		--asset-output-dir=/assets/kube-apiserver-bootstrap \
		--config-override-file=/usr/share/bootkube/manifests/config/config-overrides.yaml \
		--config-output-file=/assets/kube-apiserver-bootstrap/config

	# TODO: copy the bootstrap manifests to replace kube-core-operator
	cp --recursive kube-apiserver-bootstrap/manifests/openshift-kube-apiserver-ns.yaml manifests/00_openshift-kube-apiserver-ns.yaml
	cp --recursive kube-apiserver-bootstrap/manifests/secret-* manifests/
	cp --recursive kube-apiserver-bootstrap/manifests/configmap-* manifests/
fi

if [ ! -d kube-controller-manager-bootstrap ]
then
	echo "Rendering Kubernetes Controller Manager core manifests..."

	# shellcheck disable=SC2154
	podman run \
		--volume "$PWD:/assets:z" \
		"${KUBE_CONTROLLER_MANAGER_OPERATOR_IMAGE}" \
		/usr/bin/cluster-kube-controller-manager-operator render \
		--asset-input-dir=/assets/tls \
		--asset-output-dir=/assets/kube-controller-manager-bootstrap \
		--config-override-file=/usr/share/bootkube/manifests/config/config-overrides.yaml \
		--config-output-file=/assets/kube-controller-manager-bootstrap/config

	# TODO: copy the bootstrap manifests to replace kube-core-operator
	cp --recursive kube-controller-manager-bootstrap/manifests/openshift-kube-controller-manager-ns.yaml manifests/00_openshift-kube-controller-manager-ns.yaml
	cp --recursive kube-controller-manager-bootstrap/manifests/secret-* manifests/
	cp --recursive kube-controller-manager-bootstrap/manifests/configmap-* manifests/
fi

if [ ! -d mco-bootstrap ]
then
	echo "Rendering MCO manifests..."

	# shellcheck disable=SC2154
	podman run \
		--user 0 \
		--volume "$PWD:/assets:z" \
		"${MACHINE_CONFIG_OPERATOR_IMAGE}" \
		bootstrap \
			--etcd-ca=/assets/tls/etcd-client-ca.crt \
			--root-ca=/assets/tls/root-ca.crt \
			--config-file=/assets/manifests/cluster-config.yaml \
			--dest-dir=/assets/mco-bootstrap \
			--images-json-configmap=/assets/manifests/machine-config-operator-01-images-configmap.yaml

	# Bootstrap MachineConfigController uses /etc/mcc/bootstrap/manifests/ dir to
	# 1. read the controller config rendered by MachineConfigOperator
	# 2. read the default MachineConfigPools rendered by MachineConfigOperator
	# 3. read any additional MachineConfigs that are needed for the default MachineConfigPools.
	mkdir --parents /etc/mcc/bootstrap/
	cp --recursive mco-bootstrap/manifests /etc/mcc/bootstrap/manifests
	cp mco-bootstrap/machineconfigoperator-bootstrap-pod.yaml /etc/kubernetes/manifests/

	# /etc/ssl/mcs/tls.{crt, key} are locations for MachineConfigServer's tls assets.
	mkdir --parents /etc/ssl/mcs/
	cp tls/machine-config-server.crt /etc/ssl/mcs/tls.crt
	cp tls/machine-config-server.key /etc/ssl/mcs/tls.key
fi

# We originally wanted to run the etcd cert signer as
# a static pod, but kubelet could't remove static pod
# when API server is not up, so we have to run this as
# podman container.
# See https://github.com/kubernetes/kubernetes/issues/43292

echo "Starting etcd certificate signer..."

trap "podman rm --force etcd-signer" ERR

# shellcheck disable=SC2154
podman run \
	--name etcd-signer \
	--detach \
	--volume /opt/tectonic/tls:/opt/tectonic/tls:ro,z \
	--network host \
	"{{.EtcdCertSignerImage}}" \
	serve \
	--cacrt=/opt/tectonic/tls/etcd-client-ca.crt \
	--cakey=/opt/tectonic/tls/etcd-client-ca.key \
	--servcrt=/opt/tectonic/tls/apiserver.crt \
	--servkey=/opt/tectonic/tls/apiserver.key \
	--address=0.0.0.0:6443 \
	--csrdir=/tmp \
	--peercertdur=26280h \
	--servercertdur=26280h

echo "Waiting for etcd cluster..."

# Wait for the etcd cluster to come up.
set +e
# shellcheck disable=SC2154,SC2086
until podman run \
		--rm \
		--network host \
		--name etcdctl \
		--env ETCDCTL_API=3 \
		--volume /opt/tectonic/tls:/opt/tectonic/tls:ro,z \
		"{{.EtcdctlImage}}" \
		/usr/local/bin/etcdctl \
		--dial-timeout=10m \
		--cacert=/opt/tectonic/tls/etcd-client-ca.crt \
		--cert=/opt/tectonic/tls/etcd-client.crt \
		--key=/opt/tectonic/tls/etcd-client.key \
		--endpoints={{.EtcdCluster}} \
		endpoint health
do
	echo "etcdctl failed. Retrying in 5 seconds..."
	sleep 5
done
set -e

echo "etcd cluster up. Killing etcd certificate signer..."

podman rm --force etcd-signer
rm --force /etc/kubernetes/manifests/machineconfigoperator-bootstrap-pod.yaml

echo "Starting bootkube..."

# shellcheck disable=SC2154
podman run \
	--rm \
	--volume "$PWD:/assets:z" \
	--volume /etc/kubernetes:/etc/kubernetes:z \
	--network=host \
	--entrypoint=/bootkube \
	"{{.BootkubeImage}}" \
	start --asset-dir=/assets`))
)
<|endoftext|>


<commit_before>/*
Copyright 2015 The Kubernetes Authors.

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 ipvs

import (
	"fmt"
	"sync"
	"time"

	"k8s.io/apimachinery/pkg/util/wait"
	"k8s.io/klog"
	utilipvs "k8s.io/kubernetes/pkg/util/ipvs"
)

const (
	rsGracefulDeletePeriod = 15 * time.Minute
	rsCheckDeleteInterval  = 1 * time.Minute
)

// listItem stores real server information and the process time.
// If nothing special happened, real server will be delete after process time.
type listItem struct {
	VirtualServer *utilipvs.VirtualServer
	RealServer    *utilipvs.RealServer
}

// String return the unique real server name(with virtual server information)
func (g *listItem) String() string {
	return GetUniqueRSName(g.VirtualServer, g.RealServer)
}

// GetUniqueRSName return a string type unique rs name with vs information
func GetUniqueRSName(vs *utilipvs.VirtualServer, rs *utilipvs.RealServer) string {
	return vs.String() + "/" + rs.String()
}

type graceTerminateRSList struct {
	lock sync.Mutex
	list map[string]*listItem
}

// add push an new element to the rsList
func (q *graceTerminateRSList) add(rs *listItem) bool {
	q.lock.Lock()
	defer q.lock.Unlock()

	uniqueRS := rs.String()
	if _, ok := q.list[uniqueRS]; ok {
		return false
	}

	klog.V(5).Infof("Adding rs %v to graceful delete rsList", rs)
	q.list[uniqueRS] = rs
	return true
}

// remove remove an element from the rsList
func (q *graceTerminateRSList) remove(rs *listItem) bool {
	q.lock.Lock()
	defer q.lock.Unlock()

	uniqueRS := rs.String()
	if _, ok := q.list[uniqueRS]; ok {
		delete(q.list, uniqueRS)
		return true
	}
	return false
}

func (q *graceTerminateRSList) flushList(handler func(rsToDelete *listItem) (bool, error)) bool {
	success := true
	for name, rs := range q.list {
		deleted, err := handler(rs)
		if err != nil {
			klog.Errorf("Try delete rs %q err: %v", name, err)
			success = false
		}
		if deleted {
			klog.Infof("lw: remote out of the list: %s", name)
			q.remove(rs)
		}
	}
	return success
}

// exist check whether the specified unique RS is in the rsList
func (q *graceTerminateRSList) exist(uniqueRS string) (*listItem, bool) {
	q.lock.Lock()
	defer q.lock.Unlock()

	if rs, ok := q.list[uniqueRS]; ok {
		return rs, true
	}
	return nil, false
}

// GracefulTerminationManager manage rs graceful termination information and do graceful termination work
// rsList is the rs list to graceful termination, ipvs is the ipvsinterface to do ipvs delete/update work
type GracefulTerminationManager struct {
	rsList graceTerminateRSList
	ipvs   utilipvs.Interface
}

// NewGracefulTerminationManager create a gracefulTerminationManager to manage ipvs rs graceful termination work
func NewGracefulTerminationManager(ipvs utilipvs.Interface) *GracefulTerminationManager {
	l := make(map[string]*listItem)
	return &GracefulTerminationManager{
		rsList: graceTerminateRSList{
			list: l,
		},
		ipvs: ipvs,
	}
}

// InTerminationList to check whether specified unique rs name is in graceful termination list
func (m *GracefulTerminationManager) InTerminationList(uniqueRS string) bool {
	_, exist := m.rsList.exist(uniqueRS)
	return exist
}

// GracefulDeleteRS to update rs weight to 0, and add rs to graceful terminate list
func (m *GracefulTerminationManager) GracefulDeleteRS(vs *utilipvs.VirtualServer, rs *utilipvs.RealServer) error {
	// Try to delete rs before add it to graceful delete list
	ele := &listItem{
		VirtualServer: vs,
		RealServer:    rs,
	}
	deleted, err := m.deleteRsFunc(ele)
	if err != nil {
		klog.Errorf("Delete rs %q err: %v", ele.String(), err)
	}
	if deleted {
		return nil
	}
	rs.Weight = 0
	err = m.ipvs.UpdateRealServer(vs, rs)
	if err != nil {
		return err
	}
	klog.V(5).Infof("Adding an element to graceful delete rsList: %+v", ele)
	m.rsList.add(ele)
	return nil
}

func (m *GracefulTerminationManager) deleteRsFunc(rsToDelete *listItem) (bool, error) {
	klog.V(2).Infof("Trying to delete rs: %s", rsToDelete.String())
	rss, err := m.ipvs.GetRealServers(rsToDelete.VirtualServer)
	if err != nil {
		return false, err
	}
	for _, rs := range rss {
		if rsToDelete.RealServer.Equal(rs) {
			// For UDP traffic, no graceful termination, we immediately delete the RS
			//     (existing connections will be deleted on the next packet because sysctlExpireNoDestConn=1)
			// For other protocols, don't delete until all connections have expired)
			if rsToDelete.VirtualServer.Protocol != "udp" && rs.ActiveConn+rs.InactiveConn != 0 {
				klog.Infof("Not deleting, RS %v: %v ActiveConn, %v InactiveConn", rsToDelete.String(), rs.ActiveConn, rs.InactiveConn)
				return false, nil
			}
			klog.V(2).Infof("Deleting rs: %s", rsToDelete.String())
			err := m.ipvs.DeleteRealServer(rsToDelete.VirtualServer, rs)
			if err != nil {
				return false, fmt.Errorf("Delete destination %q err: %v", rs.String(), err)
			}
			return true, nil
		}
	}
	return true, fmt.Errorf("Failed to delete rs %q, can't find the real server", rsToDelete.String())
}

func (m *GracefulTerminationManager) tryDeleteRs() {
	if !m.rsList.flushList(m.deleteRsFunc) {
		klog.Errorf("Try flush graceful termination list err")
	}
}

// MoveRSOutofGracefulDeleteList to delete an rs and remove it from the rsList immediately
func (m *GracefulTerminationManager) MoveRSOutofGracefulDeleteList(uniqueRS string) error {
	rsToDelete, find := m.rsList.exist(uniqueRS)
	if !find || rsToDelete == nil {
		return fmt.Errorf("failed to find rs: %q", uniqueRS)
	}
	err := m.ipvs.DeleteRealServer(rsToDelete.VirtualServer, rsToDelete.RealServer)
	if err != nil {
		return err
	}
	m.rsList.remove(rsToDelete)
	return nil
}

// Run start a goroutine to try to delete rs in the graceful delete rsList with an interval 1 minute
func (m *GracefulTerminationManager) Run() {
	go wait.Until(m.tryDeleteRs, rsCheckDeleteInterval, wait.NeverStop)
}
<commit_msg>ipvs: fix string check for IPVS protocol during graceful termination<commit_after>/*
Copyright 2015 The Kubernetes Authors.

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 ipvs

import (
	"fmt"
	"strings"
	"sync"
	"time"

	"k8s.io/apimachinery/pkg/util/wait"
	"k8s.io/klog"
	utilipvs "k8s.io/kubernetes/pkg/util/ipvs"
)

const (
	rsGracefulDeletePeriod = 15 * time.Minute
	rsCheckDeleteInterval  = 1 * time.Minute
)

// listItem stores real server information and the process time.
// If nothing special happened, real server will be delete after process time.
type listItem struct {
	VirtualServer *utilipvs.VirtualServer
	RealServer    *utilipvs.RealServer
}

// String return the unique real server name(with virtual server information)
func (g *listItem) String() string {
	return GetUniqueRSName(g.VirtualServer, g.RealServer)
}

// GetUniqueRSName return a string type unique rs name with vs information
func GetUniqueRSName(vs *utilipvs.VirtualServer, rs *utilipvs.RealServer) string {
	return vs.String() + "/" + rs.String()
}

type graceTerminateRSList struct {
	lock sync.Mutex
	list map[string]*listItem
}

// add push an new element to the rsList
func (q *graceTerminateRSList) add(rs *listItem) bool {
	q.lock.Lock()
	defer q.lock.Unlock()

	uniqueRS := rs.String()
	if _, ok := q.list[uniqueRS]; ok {
		return false
	}

	klog.V(5).Infof("Adding rs %v to graceful delete rsList", rs)
	q.list[uniqueRS] = rs
	return true
}

// remove remove an element from the rsList
func (q *graceTerminateRSList) remove(rs *listItem) bool {
	q.lock.Lock()
	defer q.lock.Unlock()

	uniqueRS := rs.String()
	if _, ok := q.list[uniqueRS]; ok {
		delete(q.list, uniqueRS)
		return true
	}
	return false
}

func (q *graceTerminateRSList) flushList(handler func(rsToDelete *listItem) (bool, error)) bool {
	success := true
	for name, rs := range q.list {
		deleted, err := handler(rs)
		if err != nil {
			klog.Errorf("Try delete rs %q err: %v", name, err)
			success = false
		}
		if deleted {
			klog.Infof("lw: remote out of the list: %s", name)
			q.remove(rs)
		}
	}
	return success
}

// exist check whether the specified unique RS is in the rsList
func (q *graceTerminateRSList) exist(uniqueRS string) (*listItem, bool) {
	q.lock.Lock()
	defer q.lock.Unlock()

	if rs, ok := q.list[uniqueRS]; ok {
		return rs, true
	}
	return nil, false
}

// GracefulTerminationManager manage rs graceful termination information and do graceful termination work
// rsList is the rs list to graceful termination, ipvs is the ipvsinterface to do ipvs delete/update work
type GracefulTerminationManager struct {
	rsList graceTerminateRSList
	ipvs   utilipvs.Interface
}

// NewGracefulTerminationManager create a gracefulTerminationManager to manage ipvs rs graceful termination work
func NewGracefulTerminationManager(ipvs utilipvs.Interface) *GracefulTerminationManager {
	l := make(map[string]*listItem)
	return &GracefulTerminationManager{
		rsList: graceTerminateRSList{
			list: l,
		},
		ipvs: ipvs,
	}
}

// InTerminationList to check whether specified unique rs name is in graceful termination list
func (m *GracefulTerminationManager) InTerminationList(uniqueRS string) bool {
	_, exist := m.rsList.exist(uniqueRS)
	return exist
}

// GracefulDeleteRS to update rs weight to 0, and add rs to graceful terminate list
func (m *GracefulTerminationManager) GracefulDeleteRS(vs *utilipvs.VirtualServer, rs *utilipvs.RealServer) error {
	// Try to delete rs before add it to graceful delete list
	ele := &listItem{
		VirtualServer: vs,
		RealServer:    rs,
	}
	deleted, err := m.deleteRsFunc(ele)
	if err != nil {
		klog.Errorf("Delete rs %q err: %v", ele.String(), err)
	}
	if deleted {
		return nil
	}
	rs.Weight = 0
	err = m.ipvs.UpdateRealServer(vs, rs)
	if err != nil {
		return err
	}
	klog.V(5).Infof("Adding an element to graceful delete rsList: %+v", ele)
	m.rsList.add(ele)
	return nil
}

func (m *GracefulTerminationManager) deleteRsFunc(rsToDelete *listItem) (bool, error) {
	klog.V(2).Infof("Trying to delete rs: %s", rsToDelete.String())
	rss, err := m.ipvs.GetRealServers(rsToDelete.VirtualServer)
	if err != nil {
		return false, err
	}
	for _, rs := range rss {
		if rsToDelete.RealServer.Equal(rs) {
			// For UDP traffic, no graceful termination, we immediately delete the RS
			//     (existing connections will be deleted on the next packet because sysctlExpireNoDestConn=1)
			// For other protocols, don't delete until all connections have expired)
			if strings.ToUpper(rsToDelete.VirtualServer.Protocol) != "UDP" && rs.ActiveConn+rs.InactiveConn != 0 {
				klog.Infof("Not deleting, RS %v: %v ActiveConn, %v InactiveConn", rsToDelete.String(), rs.ActiveConn, rs.InactiveConn)
				return false, nil
			}
			klog.V(2).Infof("Deleting rs: %s", rsToDelete.String())
			err := m.ipvs.DeleteRealServer(rsToDelete.VirtualServer, rs)
			if err != nil {
				return false, fmt.Errorf("Delete destination %q err: %v", rs.String(), err)
			}
			return true, nil
		}
	}
	return true, fmt.Errorf("Failed to delete rs %q, can't find the real server", rsToDelete.String())
}

func (m *GracefulTerminationManager) tryDeleteRs() {
	if !m.rsList.flushList(m.deleteRsFunc) {
		klog.Errorf("Try flush graceful termination list err")
	}
}

// MoveRSOutofGracefulDeleteList to delete an rs and remove it from the rsList immediately
func (m *GracefulTerminationManager) MoveRSOutofGracefulDeleteList(uniqueRS string) error {
	rsToDelete, find := m.rsList.exist(uniqueRS)
	if !find || rsToDelete == nil {
		return fmt.Errorf("failed to find rs: %q", uniqueRS)
	}
	err := m.ipvs.DeleteRealServer(rsToDelete.VirtualServer, rsToDelete.RealServer)
	if err != nil {
		return err
	}
	m.rsList.remove(rsToDelete)
	return nil
}

// Run start a goroutine to try to delete rs in the graceful delete rsList with an interval 1 minute
func (m *GracefulTerminationManager) Run() {
	go wait.Until(m.tryDeleteRs, rsCheckDeleteInterval, wait.NeverStop)
}
<|endoftext|>


<commit_before>/*
Copyright 2015 The Kubernetes Authors.

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 ipvs

import (
	"fmt"
	"strings"
	"sync"
	"time"

	"k8s.io/apimachinery/pkg/util/wait"
	"k8s.io/klog"
	utilipvs "k8s.io/kubernetes/pkg/util/ipvs"
)

const (
	rsGracefulDeletePeriod = 15 * time.Minute
	rsCheckDeleteInterval  = 1 * time.Minute
)

// listItem stores real server information and the process time.
// If nothing special happened, real server will be delete after process time.
type listItem struct {
	VirtualServer *utilipvs.VirtualServer
	RealServer    *utilipvs.RealServer
}

// String return the unique real server name(with virtual server information)
func (g *listItem) String() string {
	return GetUniqueRSName(g.VirtualServer, g.RealServer)
}

// GetUniqueRSName return a string type unique rs name with vs information
func GetUniqueRSName(vs *utilipvs.VirtualServer, rs *utilipvs.RealServer) string {
	return vs.String() + "/" + rs.String()
}

type graceTerminateRSList struct {
	lock sync.Mutex
	list map[string]*listItem
}

// add push an new element to the rsList
func (q *graceTerminateRSList) add(rs *listItem) bool {
	q.lock.Lock()
	defer q.lock.Unlock()

	uniqueRS := rs.String()
	if _, ok := q.list[uniqueRS]; ok {
		return false
	}

	klog.V(5).Infof("Adding rs %v to graceful delete rsList", rs)
	q.list[uniqueRS] = rs
	return true
}

// remove remove an element from the rsList
func (q *graceTerminateRSList) remove(rs *listItem) bool {
	q.lock.Lock()
	defer q.lock.Unlock()

	uniqueRS := rs.String()
	if _, ok := q.list[uniqueRS]; ok {
		delete(q.list, uniqueRS)
		return true
	}
	return false
}

func (q *graceTerminateRSList) flushList(handler func(rsToDelete *listItem) (bool, error)) bool {
	success := true
	for name, rs := range q.list {
		deleted, err := handler(rs)
		if err != nil {
			klog.Errorf("Try delete rs %q err: %v", name, err)
			success = false
		}
		if deleted {
			klog.Infof("lw: remote out of the list: %s", name)
			q.remove(rs)
		}
	}
	return success
}

// exist check whether the specified unique RS is in the rsList
func (q *graceTerminateRSList) exist(uniqueRS string) (*listItem, bool) {
	q.lock.Lock()
	defer q.lock.Unlock()

	if rs, ok := q.list[uniqueRS]; ok {
		return rs, true
	}
	return nil, false
}

// GracefulTerminationManager manage rs graceful termination information and do graceful termination work
// rsList is the rs list to graceful termination, ipvs is the ipvsinterface to do ipvs delete/update work
type GracefulTerminationManager struct {
	rsList graceTerminateRSList
	ipvs   utilipvs.Interface
}

// NewGracefulTerminationManager create a gracefulTerminationManager to manage ipvs rs graceful termination work
func NewGracefulTerminationManager(ipvs utilipvs.Interface) *GracefulTerminationManager {
	l := make(map[string]*listItem)
	return &GracefulTerminationManager{
		rsList: graceTerminateRSList{
			list: l,
		},
		ipvs: ipvs,
	}
}

// InTerminationList to check whether specified unique rs name is in graceful termination list
func (m *GracefulTerminationManager) InTerminationList(uniqueRS string) bool {
	_, exist := m.rsList.exist(uniqueRS)
	return exist
}

// GracefulDeleteRS to update rs weight to 0, and add rs to graceful terminate list
func (m *GracefulTerminationManager) GracefulDeleteRS(vs *utilipvs.VirtualServer, rs *utilipvs.RealServer) error {
	// Try to delete rs before add it to graceful delete list
	ele := &listItem{
		VirtualServer: vs,
		RealServer:    rs,
	}
	deleted, err := m.deleteRsFunc(ele)
	if err != nil {
		klog.Errorf("Delete rs %q err: %v", ele.String(), err)
	}
	if deleted {
		return nil
	}
	rs.Weight = 0
	err = m.ipvs.UpdateRealServer(vs, rs)
	if err != nil {
		return err
	}
	klog.V(5).Infof("Adding an element to graceful delete rsList: %+v", ele)
	m.rsList.add(ele)
	return nil
}

func (m *GracefulTerminationManager) deleteRsFunc(rsToDelete *listItem) (bool, error) {
	klog.V(5).Infof("Trying to delete rs: %s", rsToDelete.String())
	rss, err := m.ipvs.GetRealServers(rsToDelete.VirtualServer)
	if err != nil {
		return false, err
	}
	for _, rs := range rss {
		if rsToDelete.RealServer.Equal(rs) {
			// For UDP traffic, no graceful termination, we immediately delete the RS
			//     (existing connections will be deleted on the next packet because sysctlExpireNoDestConn=1)
			// For other protocols, don't delete until all connections have expired)
			if strings.ToUpper(rsToDelete.VirtualServer.Protocol) != "UDP" && rs.ActiveConn+rs.InactiveConn != 0 {
				klog.V(5).Infof("Not deleting, RS %v: %v ActiveConn, %v InactiveConn", rsToDelete.String(), rs.ActiveConn, rs.InactiveConn)
				return false, nil
			}
			klog.V(5).Infof("Deleting rs: %s", rsToDelete.String())
			err := m.ipvs.DeleteRealServer(rsToDelete.VirtualServer, rs)
			if err != nil {
				return false, fmt.Errorf("Delete destination %q err: %v", rs.String(), err)
			}
			return true, nil
		}
	}
	return true, fmt.Errorf("Failed to delete rs %q, can't find the real server", rsToDelete.String())
}

func (m *GracefulTerminationManager) tryDeleteRs() {
	if !m.rsList.flushList(m.deleteRsFunc) {
		klog.Errorf("Try flush graceful termination list err")
	}
}

// MoveRSOutofGracefulDeleteList to delete an rs and remove it from the rsList immediately
func (m *GracefulTerminationManager) MoveRSOutofGracefulDeleteList(uniqueRS string) error {
	rsToDelete, find := m.rsList.exist(uniqueRS)
	if !find || rsToDelete == nil {
		return fmt.Errorf("failed to find rs: %q", uniqueRS)
	}
	err := m.ipvs.DeleteRealServer(rsToDelete.VirtualServer, rsToDelete.RealServer)
	if err != nil {
		return err
	}
	m.rsList.remove(rsToDelete)
	return nil
}

// Run start a goroutine to try to delete rs in the graceful delete rsList with an interval 1 minute
func (m *GracefulTerminationManager) Run() {
	go wait.Until(m.tryDeleteRs, rsCheckDeleteInterval, wait.NeverStop)
}
<commit_msg>proxy/ipvs: remove unused constant rsGracefulDeletePeriod<commit_after>/*
Copyright 2015 The Kubernetes Authors.

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 ipvs

import (
	"fmt"
	"strings"
	"sync"
	"time"

	"k8s.io/apimachinery/pkg/util/wait"
	"k8s.io/klog"
	utilipvs "k8s.io/kubernetes/pkg/util/ipvs"
)

const (
	rsCheckDeleteInterval = 1 * time.Minute
)

// listItem stores real server information and the process time.
// If nothing special happened, real server will be delete after process time.
type listItem struct {
	VirtualServer *utilipvs.VirtualServer
	RealServer    *utilipvs.RealServer
}

// String return the unique real server name(with virtual server information)
func (g *listItem) String() string {
	return GetUniqueRSName(g.VirtualServer, g.RealServer)
}

// GetUniqueRSName return a string type unique rs name with vs information
func GetUniqueRSName(vs *utilipvs.VirtualServer, rs *utilipvs.RealServer) string {
	return vs.String() + "/" + rs.String()
}

type graceTerminateRSList struct {
	lock sync.Mutex
	list map[string]*listItem
}

// add push an new element to the rsList
func (q *graceTerminateRSList) add(rs *listItem) bool {
	q.lock.Lock()
	defer q.lock.Unlock()

	uniqueRS := rs.String()
	if _, ok := q.list[uniqueRS]; ok {
		return false
	}

	klog.V(5).Infof("Adding rs %v to graceful delete rsList", rs)
	q.list[uniqueRS] = rs
	return true
}

// remove remove an element from the rsList
func (q *graceTerminateRSList) remove(rs *listItem) bool {
	q.lock.Lock()
	defer q.lock.Unlock()

	uniqueRS := rs.String()
	if _, ok := q.list[uniqueRS]; ok {
		delete(q.list, uniqueRS)
		return true
	}
	return false
}

func (q *graceTerminateRSList) flushList(handler func(rsToDelete *listItem) (bool, error)) bool {
	success := true
	for name, rs := range q.list {
		deleted, err := handler(rs)
		if err != nil {
			klog.Errorf("Try delete rs %q err: %v", name, err)
			success = false
		}
		if deleted {
			klog.Infof("lw: remote out of the list: %s", name)
			q.remove(rs)
		}
	}
	return success
}

// exist check whether the specified unique RS is in the rsList
func (q *graceTerminateRSList) exist(uniqueRS string) (*listItem, bool) {
	q.lock.Lock()
	defer q.lock.Unlock()

	if rs, ok := q.list[uniqueRS]; ok {
		return rs, true
	}
	return nil, false
}

// GracefulTerminationManager manage rs graceful termination information and do graceful termination work
// rsList is the rs list to graceful termination, ipvs is the ipvsinterface to do ipvs delete/update work
type GracefulTerminationManager struct {
	rsList graceTerminateRSList
	ipvs   utilipvs.Interface
}

// NewGracefulTerminationManager create a gracefulTerminationManager to manage ipvs rs graceful termination work
func NewGracefulTerminationManager(ipvs utilipvs.Interface) *GracefulTerminationManager {
	l := make(map[string]*listItem)
	return &GracefulTerminationManager{
		rsList: graceTerminateRSList{
			list: l,
		},
		ipvs: ipvs,
	}
}

// InTerminationList to check whether specified unique rs name is in graceful termination list
func (m *GracefulTerminationManager) InTerminationList(uniqueRS string) bool {
	_, exist := m.rsList.exist(uniqueRS)
	return exist
}

// GracefulDeleteRS to update rs weight to 0, and add rs to graceful terminate list
func (m *GracefulTerminationManager) GracefulDeleteRS(vs *utilipvs.VirtualServer, rs *utilipvs.RealServer) error {
	// Try to delete rs before add it to graceful delete list
	ele := &listItem{
		VirtualServer: vs,
		RealServer:    rs,
	}
	deleted, err := m.deleteRsFunc(ele)
	if err != nil {
		klog.Errorf("Delete rs %q err: %v", ele.String(), err)
	}
	if deleted {
		return nil
	}
	rs.Weight = 0
	err = m.ipvs.UpdateRealServer(vs, rs)
	if err != nil {
		return err
	}
	klog.V(5).Infof("Adding an element to graceful delete rsList: %+v", ele)
	m.rsList.add(ele)
	return nil
}

func (m *GracefulTerminationManager) deleteRsFunc(rsToDelete *listItem) (bool, error) {
	klog.V(5).Infof("Trying to delete rs: %s", rsToDelete.String())
	rss, err := m.ipvs.GetRealServers(rsToDelete.VirtualServer)
	if err != nil {
		return false, err
	}
	for _, rs := range rss {
		if rsToDelete.RealServer.Equal(rs) {
			// For UDP traffic, no graceful termination, we immediately delete the RS
			//     (existing connections will be deleted on the next packet because sysctlExpireNoDestConn=1)
			// For other protocols, don't delete until all connections have expired)
			if strings.ToUpper(rsToDelete.VirtualServer.Protocol) != "UDP" && rs.ActiveConn+rs.InactiveConn != 0 {
				klog.V(5).Infof("Not deleting, RS %v: %v ActiveConn, %v InactiveConn", rsToDelete.String(), rs.ActiveConn, rs.InactiveConn)
				return false, nil
			}
			klog.V(5).Infof("Deleting rs: %s", rsToDelete.String())
			err := m.ipvs.DeleteRealServer(rsToDelete.VirtualServer, rs)
			if err != nil {
				return false, fmt.Errorf("Delete destination %q err: %v", rs.String(), err)
			}
			return true, nil
		}
	}
	return true, fmt.Errorf("Failed to delete rs %q, can't find the real server", rsToDelete.String())
}

func (m *GracefulTerminationManager) tryDeleteRs() {
	if !m.rsList.flushList(m.deleteRsFunc) {
		klog.Errorf("Try flush graceful termination list err")
	}
}

// MoveRSOutofGracefulDeleteList to delete an rs and remove it from the rsList immediately
func (m *GracefulTerminationManager) MoveRSOutofGracefulDeleteList(uniqueRS string) error {
	rsToDelete, find := m.rsList.exist(uniqueRS)
	if !find || rsToDelete == nil {
		return fmt.Errorf("failed to find rs: %q", uniqueRS)
	}
	err := m.ipvs.DeleteRealServer(rsToDelete.VirtualServer, rsToDelete.RealServer)
	if err != nil {
		return err
	}
	m.rsList.remove(rsToDelete)
	return nil
}

// Run start a goroutine to try to delete rs in the graceful delete rsList with an interval 1 minute
func (m *GracefulTerminationManager) Run() {
	go wait.Until(m.tryDeleteRs, rsCheckDeleteInterval, wait.NeverStop)
}
<|endoftext|>


<commit_before>package podtask

import (
	"testing"

	mesos "github.com/mesos/mesos-go/mesosproto"
	"github.com/mesos/mesos-go/mesosutil"
	"github.com/stretchr/testify/assert"
)

func TestInMemoryRegistry_RegisterGetUnregister(t *testing.T) {
	assert := assert.New(t)

	registry := NewInMemoryRegistry()

	// it's empty at the beginning
	tasks := registry.List(func(t *T) bool { return true })
	assert.Empty(tasks)

	// add a task
	a, _ := fakePodTask("a")
	a_clone, err := registry.Register(a, nil)
	assert.NoError(err)
	assert.Equal(a_clone.ID, a.ID)
	assert.Equal(a_clone.podKey, a.podKey)

	// add another task
	b, _ := fakePodTask("b")
	b_clone, err := registry.Register(b, nil)
	assert.NoError(err)
	assert.Equal(b_clone.ID, b.ID)
	assert.Equal(b_clone.podKey, b.podKey)

	// find tasks in the registry
	tasks = registry.List(func(t *T) bool { return true })
	assert.Len(tasks, 2)
	assert.Contains(tasks, a_clone)
	assert.Contains(tasks, b_clone)

	tasks = registry.List(func(t *T) bool { return t.ID == a.ID })
	assert.Len(tasks, 1)
	assert.Contains(tasks, a_clone)

	task, _ := registry.ForPod(a.podKey)
	assert.NotNil(task)
	assert.Equal(task.ID, a.ID)

	task, _ = registry.ForPod(b.podKey)
	assert.NotNil(task)
	assert.Equal(task.ID, b.ID)

	task, _ = registry.ForPod("no-pod-key")
	assert.Nil(task)

	task, _ = registry.Get(a.ID)
	assert.NotNil(task)
	assert.Equal(task.ID, a.ID)

	task, _ = registry.Get("unknown-task-id")
	assert.Nil(task)

	// re-add a task
	a_clone, err = registry.Register(a, nil)
	assert.Error(err)
	assert.Nil(a_clone)

	// re-add a task with another podKey, but same task id
	another_a := a.Clone()
	another_a.podKey = "another-pod"
	another_a_clone, err := registry.Register(another_a, nil)
	assert.Error(err)
	assert.Nil(another_a_clone)

	// re-add a task with another task ID, but same podKey
	another_b := b.Clone()
	another_b.ID = "another-task-id"
	another_b_clone, err := registry.Register(another_b, nil)
	assert.Error(err)
	assert.Nil(another_b_clone)

	// unregister a task
	registry.Unregister(b)

	tasks = registry.List(func(t *T) bool { return true })
	assert.Len(tasks, 1)
	assert.Contains(tasks, a)

	// unregister a task not registered
	unregistered_task, _ := fakePodTask("unregistered-task")
	registry.Unregister(unregistered_task)
}

func fakeStatusUpdate(taskId string, state mesos.TaskState) *mesos.TaskStatus {
	status := mesosutil.NewTaskStatus(mesosutil.NewTaskID(taskId), state)
	status.Data = []byte("{}") // empty json
	masterSource := mesos.TaskStatus_SOURCE_MASTER
	status.Source = &masterSource
	return status
}

func TestInMemoryRegistry_State(t *testing.T) {
	assert := assert.New(t)

	registry := NewInMemoryRegistry()

	// add a task
	a, _ := fakePodTask("a")
	a_clone, err := registry.Register(a, nil)
	assert.NoError(err)
	assert.Equal(a.State, a_clone.State)

	// update the status
	assert.Equal(a_clone.State, StatePending)
	a_clone, state := registry.UpdateStatus(fakeStatusUpdate(a.ID, mesos.TaskState_TASK_RUNNING))
	assert.Equal(state, StatePending)         // old state
	assert.Equal(a_clone.State, StateRunning) // new state

	// update unknown task
	unknown_clone, state := registry.UpdateStatus(fakeStatusUpdate("unknown-task-id", mesos.TaskState_TASK_RUNNING))
	assert.Nil(unknown_clone)
	assert.Equal(state, StateUnknown)
}

type transition struct {
	statusUpdate  mesos.TaskState
	expectedState *StateType
	expectPanic   bool
}

func NewTransition(statusUpdate mesos.TaskState, expectedState StateType) transition {
	return transition{statusUpdate: statusUpdate, expectedState: &expectedState, expectPanic: false}
}

func NewTransitionToDeletedTask(statusUpdate mesos.TaskState) transition {
	return transition{statusUpdate: statusUpdate, expectedState: nil, expectPanic: false}
}

func NewTransitionWhichPanics(statusUpdate mesos.TaskState) transition {
	return transition{statusUpdate: statusUpdate, expectPanic: true}
}

func testStateTrace(t *testing.T, transitions []transition) *Registry {
	assert := assert.New(t)

	registry := NewInMemoryRegistry()
	a, _ := fakePodTask("a")
	a, _ = registry.Register(a, nil)

	// initial pending state
	assert.Equal(a.State, StatePending)

	for _, transition := range transitions {
		if transition.expectPanic {
			assert.Panics(func() {
				registry.UpdateStatus(fakeStatusUpdate(a.ID, transition.statusUpdate))
			})
		} else {
			a, _ = registry.UpdateStatus(fakeStatusUpdate(a.ID, transition.statusUpdate))
			if transition.expectedState == nil {
				a, _ = registry.Get(a.ID)
				assert.Nil(a, "expected task to be deleted from registry after status update to %v", transition.statusUpdate)
			} else {
				assert.Equal(a.State, *transition.expectedState)
			}
		}
	}

	return &registry
}

func TestInMemoryRegistry_TaskLifeCycle(t *testing.T) {
	testStateTrace(t, []transition{
		NewTransition(mesos.TaskState_TASK_STAGING, StatePending),
		NewTransition(mesos.TaskState_TASK_STARTING, StatePending),
		NewTransitionWhichPanics(mesos.TaskState_TASK_FINISHED),
		NewTransition(mesos.TaskState_TASK_RUNNING, StateRunning),
		NewTransition(mesos.TaskState_TASK_RUNNING, StateRunning),
		NewTransition(mesos.TaskState_TASK_STARTING, StateRunning),
		NewTransition(mesos.TaskState_TASK_FINISHED, StateFinished),
		NewTransition(mesos.TaskState_TASK_FINISHED, StateFinished),
		NewTransition(mesos.TaskState_TASK_RUNNING, StateFinished),
	})
}

func TestInMemoryRegistry_NotFinished(t *testing.T) {
	// all these behave the same
	notFinishedStates := []mesos.TaskState{
		mesos.TaskState_TASK_FAILED,
		mesos.TaskState_TASK_KILLED,
		mesos.TaskState_TASK_LOST,
	}
	for _, notFinishedState := range notFinishedStates {
		testStateTrace(t, []transition{
			NewTransitionToDeletedTask(notFinishedState),
		})

		testStateTrace(t, []transition{
			NewTransition(mesos.TaskState_TASK_RUNNING, StateRunning),
			NewTransitionToDeletedTask(notFinishedState),
		})

		testStateTrace(t, []transition{
			NewTransition(mesos.TaskState_TASK_RUNNING, StateRunning),
			NewTransition(mesos.TaskState_TASK_FINISHED, StateFinished),
			NewTransition(notFinishedState, StateFinished),
		})
	}
}
<commit_msg>add tests for scheduler podtask registry Update<commit_after>package podtask

import (
	"testing"

	mesos "github.com/mesos/mesos-go/mesosproto"
	"github.com/mesos/mesos-go/mesosutil"
	"github.com/mesosphere/kubernetes-mesos/pkg/offers"
	"github.com/mesosphere/kubernetes-mesos/pkg/proc"
	"github.com/stretchr/testify/assert"
	"time"
)

func TestInMemoryRegistry_RegisterGetUnregister(t *testing.T) {
	assert := assert.New(t)

	registry := NewInMemoryRegistry()

	// it's empty at the beginning
	tasks := registry.List(func(t *T) bool { return true })
	assert.Empty(tasks)

	// add a task
	a, _ := fakePodTask("a")
	a_clone, err := registry.Register(a, nil)
	assert.NoError(err)
	assert.Equal(a_clone.ID, a.ID)
	assert.Equal(a_clone.podKey, a.podKey)

	// add another task
	b, _ := fakePodTask("b")
	b_clone, err := registry.Register(b, nil)
	assert.NoError(err)
	assert.Equal(b_clone.ID, b.ID)
	assert.Equal(b_clone.podKey, b.podKey)

	// find tasks in the registry
	tasks = registry.List(func(t *T) bool { return true })
	assert.Len(tasks, 2)
	assert.Contains(tasks, a_clone)
	assert.Contains(tasks, b_clone)

	tasks = registry.List(func(t *T) bool { return t.ID == a.ID })
	assert.Len(tasks, 1)
	assert.Contains(tasks, a_clone)

	task, _ := registry.ForPod(a.podKey)
	assert.NotNil(task)
	assert.Equal(task.ID, a.ID)

	task, _ = registry.ForPod(b.podKey)
	assert.NotNil(task)
	assert.Equal(task.ID, b.ID)

	task, _ = registry.ForPod("no-pod-key")
	assert.Nil(task)

	task, _ = registry.Get(a.ID)
	assert.NotNil(task)
	assert.Equal(task.ID, a.ID)

	task, _ = registry.Get("unknown-task-id")
	assert.Nil(task)

	// re-add a task
	a_clone, err = registry.Register(a, nil)
	assert.Error(err)
	assert.Nil(a_clone)

	// re-add a task with another podKey, but same task id
	another_a := a.Clone()
	another_a.podKey = "another-pod"
	another_a_clone, err := registry.Register(another_a, nil)
	assert.Error(err)
	assert.Nil(another_a_clone)

	// re-add a task with another task ID, but same podKey
	another_b := b.Clone()
	another_b.ID = "another-task-id"
	another_b_clone, err := registry.Register(another_b, nil)
	assert.Error(err)
	assert.Nil(another_b_clone)

	// unregister a task
	registry.Unregister(b)

	tasks = registry.List(func(t *T) bool { return true })
	assert.Len(tasks, 1)
	assert.Contains(tasks, a)

	// unregister a task not registered
	unregistered_task, _ := fakePodTask("unregistered-task")
	registry.Unregister(unregistered_task)
}

func fakeStatusUpdate(taskId string, state mesos.TaskState) *mesos.TaskStatus {
	status := mesosutil.NewTaskStatus(mesosutil.NewTaskID(taskId), state)
	status.Data = []byte("{}") // empty json
	masterSource := mesos.TaskStatus_SOURCE_MASTER
	status.Source = &masterSource
	return status
}

func TestInMemoryRegistry_State(t *testing.T) {
	assert := assert.New(t)

	registry := NewInMemoryRegistry()

	// add a task
	a, _ := fakePodTask("a")
	a_clone, err := registry.Register(a, nil)
	assert.NoError(err)
	assert.Equal(a.State, a_clone.State)

	// update the status
	assert.Equal(a_clone.State, StatePending)
	a_clone, state := registry.UpdateStatus(fakeStatusUpdate(a.ID, mesos.TaskState_TASK_RUNNING))
	assert.Equal(state, StatePending)         // old state
	assert.Equal(a_clone.State, StateRunning) // new state

	// update unknown task
	unknown_clone, state := registry.UpdateStatus(fakeStatusUpdate("unknown-task-id", mesos.TaskState_TASK_RUNNING))
	assert.Nil(unknown_clone)
	assert.Equal(state, StateUnknown)
}

func TestInMemoryRegistry_Update(t *testing.T) {
	assert := assert.New(t)

	// create offers registry
	ttl := time.Second / 4
	config := offers.RegistryConfig{
		DeclineOffer: func(offerId string) <-chan error {
			return proc.ErrorChan(nil)
		},
		Compat: func(o *mesos.Offer) bool {
			return true
		},
		TTL:       ttl,
		LingerTTL: 2 * ttl,
	}
	storage := offers.CreateRegistry(config)

	// Add offer
	offerId := mesosutil.NewOfferID("foo")
	mesosOffer := &mesos.Offer{Id: offerId}
	storage.Add([]*mesos.Offer{mesosOffer})
	offer, ok := storage.Get(offerId.GetValue())
	assert.True(ok)

	// create registry
	registry := NewInMemoryRegistry()
	a, _ := fakePodTask("a")
	registry.Register(a.Clone(), nil) // here clone a because we change it below

	// state changes are ignored
	a.State = StateRunning
	err := registry.Update(a)
	assert.NoError(err)
	a_clone, _ := registry.Get(a.ID)
	assert.Equal(StatePending, a_clone.State)

	// offer is updated while pending
	a.Offer = offer
	err = registry.Update(a)
	assert.NoError(err)
	a_clone, _ = registry.Get(a.ID)
	assert.Equal(offer.Id(), a_clone.Offer.Id())

	// spec is updated while pending
	a.Spec = Spec{SlaveID: "slave-1"}
	err = registry.Update(a)
	assert.NoError(err)
	a_clone, _ = registry.Get(a.ID)
	assert.Equal("slave-1", a_clone.Spec.SlaveID)

	// flags are updated while pending
	a.Flags[FlagType("launched")] = struct{}{}
	err = registry.Update(a)
	assert.NoError(err)
	a_clone, _ = registry.Get(a.ID)

	_, found_launched := a_clone.Flags[FlagType("launched")]
	assert.True(found_launched)

	// flags are updated while running
	registry.UpdateStatus(fakeStatusUpdate(a.ID, mesos.TaskState_TASK_RUNNING))
	a.Flags[FlagType("bound")] = struct{}{}
	err = registry.Update(a)
	assert.NoError(err)
	a_clone, _ = registry.Get(a.ID)

	_, found_launched = a_clone.Flags[FlagType("launched")]
	assert.True(found_launched)
	_, found_bound := a_clone.Flags[FlagType("bound")]
	assert.True(found_bound)

	// spec is ignored while running
	a.Spec = Spec{SlaveID: "slave-2"}
	err = registry.Update(a)
	assert.NoError(err)
	a_clone, _ = registry.Get(a.ID)
	assert.Equal("slave-1", a_clone.Spec.SlaveID)

	// error when finished
	registry.UpdateStatus(fakeStatusUpdate(a.ID, mesos.TaskState_TASK_FINISHED))
	err = registry.Update(a)
	assert.Error(err)

	// update unknown task
	unknown_task, _ := fakePodTask("unknown-task")
	err = registry.Update(unknown_task)
	assert.Error(err)

	// update nil task
	err = registry.Update(nil)
	assert.Nil(err)
}

type transition struct {
	statusUpdate  mesos.TaskState
	expectedState *StateType
	expectPanic   bool
}

func NewTransition(statusUpdate mesos.TaskState, expectedState StateType) transition {
	return transition{statusUpdate: statusUpdate, expectedState: &expectedState, expectPanic: false}
}

func NewTransitionToDeletedTask(statusUpdate mesos.TaskState) transition {
	return transition{statusUpdate: statusUpdate, expectedState: nil, expectPanic: false}
}

func NewTransitionWhichPanics(statusUpdate mesos.TaskState) transition {
	return transition{statusUpdate: statusUpdate, expectPanic: true}
}

func testStateTrace(t *testing.T, transitions []transition) *Registry {
	assert := assert.New(t)

	registry := NewInMemoryRegistry()
	a, _ := fakePodTask("a")
	a, _ = registry.Register(a, nil)

	// initial pending state
	assert.Equal(a.State, StatePending)

	for _, transition := range transitions {
		if transition.expectPanic {
			assert.Panics(func() {
				registry.UpdateStatus(fakeStatusUpdate(a.ID, transition.statusUpdate))
			})
		} else {
			a, _ = registry.UpdateStatus(fakeStatusUpdate(a.ID, transition.statusUpdate))
			if transition.expectedState == nil {
				a, _ = registry.Get(a.ID)
				assert.Nil(a, "expected task to be deleted from registry after status update to %v", transition.statusUpdate)
			} else {
				assert.Equal(a.State, *transition.expectedState)
			}
		}
	}

	return &registry
}

func TestInMemoryRegistry_TaskLifeCycle(t *testing.T) {
	testStateTrace(t, []transition{
		NewTransition(mesos.TaskState_TASK_STAGING, StatePending),
		NewTransition(mesos.TaskState_TASK_STARTING, StatePending),
		NewTransitionWhichPanics(mesos.TaskState_TASK_FINISHED),
		NewTransition(mesos.TaskState_TASK_RUNNING, StateRunning),
		NewTransition(mesos.TaskState_TASK_RUNNING, StateRunning),
		NewTransition(mesos.TaskState_TASK_STARTING, StateRunning),
		NewTransition(mesos.TaskState_TASK_FINISHED, StateFinished),
		NewTransition(mesos.TaskState_TASK_FINISHED, StateFinished),
		NewTransition(mesos.TaskState_TASK_RUNNING, StateFinished),
	})
}

func TestInMemoryRegistry_NotFinished(t *testing.T) {
	// all these behave the same
	notFinishedStates := []mesos.TaskState{
		mesos.TaskState_TASK_FAILED,
		mesos.TaskState_TASK_KILLED,
		mesos.TaskState_TASK_LOST,
	}
	for _, notFinishedState := range notFinishedStates {
		testStateTrace(t, []transition{
			NewTransitionToDeletedTask(notFinishedState),
		})

		testStateTrace(t, []transition{
			NewTransition(mesos.TaskState_TASK_RUNNING, StateRunning),
			NewTransitionToDeletedTask(notFinishedState),
		})

		testStateTrace(t, []transition{
			NewTransition(mesos.TaskState_TASK_RUNNING, StateRunning),
			NewTransition(mesos.TaskState_TASK_FINISHED, StateFinished),
			NewTransition(notFinishedState, StateFinished),
		})
	}
}
<|endoftext|>


<commit_before>/*
 * Modern Copy, (C) 2014,2015 Minio, Inc.
 *
 * 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 main

import (
	"github.com/minio-io/cli"
)

// List of commands
var (
	cpCmd = cli.Command{
		Name:  "cp",
		Usage: "Copy objects and files",
		//		Description: "Copy files and objects recursively across object storage and filesystems",
		Action: doCopyCmd,
		Flags: []cli.Flag{
			cli.BoolFlag{
				Name:  "recursive, r",
				Usage: "Recursively crawl a given directory or bucket",
			},
		},
		CustomHelpTemplate: `NAME:
   mc {{.Name}} - {{.Usage}}

USAGE:
   mc {{.Name}}{{if .Flags}} [ARGS...]{{end}} SOURCE TARGET [TARGET...] {{if .Description}}

DESCRIPTION:
   {{.Description}}{{end}}{{if .Flags}}

OPTIONS:
   {{range .Flags}}{{.}}
   {{end}}{{ end }}
EXAMPLES:
   1. Copy an object from Amazon S3 object storage to local fileystem.
      $ mc {{.Name}} https://s3.amazonaws.com/jukebox/klingon_opera_aktuh_maylotah.ogg wakeup.ogg

   2. Copy a bucket recursive from Minio object storage to Amazon S3 object storage
      $ mc {{.Name}} --recursive http://localhost:9000/photos/burningman2011 https://s3.amazonaws.com/burningman/

   3. Copy a local folder to Minio object storage and Amazon S3 object storage
      $ mc {{.Name}} --recursive backup/ http://localhost:9000/archive/ https://s3.amazonaws.com/archive/

`,
	}

	lsCmd = cli.Command{
		Name:  "ls",
		Usage: "List files and objects",
		//		Description: `List files and objects recursively on object storage and fileystems`,
		Action: doListCmd,
		CustomHelpTemplate: `NAME:
   mc {{.Name}} - {{.Usage}}

USAGE:
   mc {{.Name}} TARGET [TARGET...] {{if .Description}}

DESCRIPTION:
   {{.Description}}{{end}}{{if .Flags}}

OPTIONS:
   {{range .Flags}}{{.}}
   {{end}}{{ end }}

EXAMPLES:
   1. List objects on Minio object storage
      $ mc {{.Name}} http://localhost:9000/backup/
      2015-03-28 12:47:50 PDT      51.00 MB 2006-Jan-1/backup.tar.gz
      2015-03-31 14:46:33 PDT      55.00 MB 2006-Mar-1/backup.tar.gz

   2. List buckets on Amazon S3 object storage
      $ mc {{.Name}} https://s3.amazonaws.com/
      2015-01-20 15:42:00 PST               rom
      2015-01-15 00:05:40 PST               zek

   3. List buckets and objects from Minio object storage and Amazon S3 object storage
      $ mc {{.Name}} https://s3.amazonaws.com/ http://localhost:9000/backup/
      2015-01-20 15:42:00 PST               rom
      2015-01-15 00:05:40 PST               zek
      2015-03-28 12:47:50 PDT      51.00 MB 2006-Jan-1/backup.tar.gz
      2015-03-31 14:46:33 PDT      55.00 MB 2006-Mar-1/backup.tar.gz

`,
	}

	mbCmd = cli.Command{
		Name:  "mb",
		Usage: "Make a bucket",
		//		Description: `Create a bucket on object storage or a folder on filesystem`,
		Action: doMakeBucketCmd,
		CustomHelpTemplate: `NAME:
   mc {{.Name}} - {{.Usage}}

USAGE:
   mc {{.Name}} TARGET [TARGET...] {{if .Description}}

DESCRIPTION:
   {{.Description}}{{end}}{{if .Flags}}

OPTIONS:
   {{range .Flags}}{{.}}
   {{end}}{{ end }}

EXAMPLES:
   1. Create a bucket on Amazon S3 object storage
      $ mc {{.Name}} https://s3.amazonaws.com/public-document-store

   2. Create a bucket on Minio object storage
      $ mc {{.Name}} http://localhost:9000/mongodb-backup

   3. Create multiple buckets on Amazon S3 object storage
      $ mc {{.Name}} https://s3.amazonaws.com/public-photo-store https://s3.amazonaws.com/public-store

`,
	}
	//   Configure minio client configuration.
	//
	//   NOTE: that the configure command only writes values to the config file.
	//   It does not use any configuration values from the environment variables.`,
	configCmd = cli.Command{
		Name:   "config",
		Usage:  "Generate configuration \"" + mustGetMcConfigPath() + "\" file.",
		Action: doConfigCmd,
		Flags: []cli.Flag{
			cli.StringFlag{
				Name:  "alias",
				Usage: "Add URL aliases into config",
			},
			cli.BoolFlag{
				Name:  "completion",
				Usage: "Generate bash completion \"" + mustGetMcBashCompletionFilename() + "\" file.",
			},
		},
		CustomHelpTemplate: `NAME:
   mc {{.Name}} - {{.Usage}}

USAGE:
   mc {{.Name}} generate {{if .Description}}

DESCRIPTION:
   {{.Description}}{{end}}{{if .Flags}}

OPTIONS:
   {{range .Flags}}{{.}}
   {{end}}{{ end }}
EXAMPLES:
   1. Generate mc config
      $ mc config generate

   2. Generate bash completion
      $ mc config --completion

   3. Add alias URLs
      $ mc config --alias "zek https://s3.amazonaws.com/"

`,
	}
	updateCmd = cli.Command{
		Name:        "update",
		Usage:       "Check for new software updates",
		Description: "",
		Action:      doUpdateCmd,
		Flags: []cli.Flag{
			cli.BoolFlag{
				Name:  "yes, y",
				Usage: "Download and update local binary",
			},
		},
	}
)

var options = []cli.Command{
	cpCmd,
	lsCmd,
	mbCmd,
	configCmd,
	updateCmd,
}

var (
	flags = []cli.Flag{
		cli.BoolFlag{
			Name:  "debug",
			Usage: "Enable HTTP tracing",
		},
		cli.BoolFlag{
			Name:  "quiet, q",
			Usage: "Disable chatty output, such as the progress bar",
		},
	}
)

var (
	mcBashCompletion = `#!/bin/bash

_mc_completion() {
    local cur prev opts base
    COMPREPLY=()
    cur="${COMP_WORDS[COMP_CWORD]}"
    prev="${COMP_WORDS[COMP_CWORD-1]}"
    opts=$( ${COMP_WORDS[@]:0:$COMP_CWORD} --generate-bash-completion )
    COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
    return 0
}

complete -F _mc_completion mc
`
)
<commit_msg>Add windows examples for cp, ls<commit_after>/*
 * Modern Copy, (C) 2014,2015 Minio, Inc.
 *
 * 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 main

import (
	"github.com/minio-io/cli"
)

// List of commands
var (
	cpCmd = cli.Command{
		Name:  "cp",
		Usage: "Copy objects and files",
		//		Description: "Copy files and objects recursively across object storage and filesystems",
		Action: doCopyCmd,
		Flags: []cli.Flag{
			cli.BoolFlag{
				Name:  "recursive, r",
				Usage: "Recursively crawl a given directory or bucket",
			},
		},
		CustomHelpTemplate: `NAME:
   mc {{.Name}} - {{.Usage}}

USAGE:
   mc {{.Name}}{{if .Flags}} [ARGS...]{{end}} SOURCE TARGET [TARGET...] {{if .Description}}

DESCRIPTION:
   {{.Description}}{{end}}{{if .Flags}}

OPTIONS:
   {{range .Flags}}{{.}}
   {{end}}{{ end }}
EXAMPLES:
   1. Copy an object from Amazon S3 object storage to local fileystem.
      $ mc {{.Name}} https://s3.amazonaws.com/jukebox/klingon_opera_aktuh_maylotah.ogg wakeup.ogg

   2. Copy a bucket recursive from Minio object storage to Amazon S3 object storage
      $ mc {{.Name}} --recursive http://localhost:9000/photos/burningman2011 https://s3.amazonaws.com/burningman/

   3. Copy a local folder to Minio object storage and Amazon S3 object storage
      $ mc {{.Name}} --recursive backup/ http://localhost:9000/archive/ https://s3.amazonaws.com/archive/

   4. Copy an object from Amazon S3 object storage to local filesystem on Windows.
      $ mc {{.Name}} https://s3.amazonaws.com/jukebox/vulcan_lute.ogg C:\Users\Surak\sleep.ogg

`,
	}

	lsCmd = cli.Command{
		Name:  "ls",
		Usage: "List files and objects",
		//		Description: `List files and objects recursively on object storage and fileystems`,
		Action: doListCmd,
		CustomHelpTemplate: `NAME:
   mc {{.Name}} - {{.Usage}}

USAGE:
   mc {{.Name}} TARGET [TARGET...] {{if .Description}}

DESCRIPTION:
   {{.Description}}{{end}}{{if .Flags}}

OPTIONS:
   {{range .Flags}}{{.}}
   {{end}}{{ end }}

EXAMPLES:
   1. List objects on Minio object storage
      $ mc {{.Name}} http://localhost:9000/backup/
      2015-03-28 12:47:50 PDT      51.00 MB 2006-Jan-1/backup.tar.gz
      2015-03-31 14:46:33 PDT      55.00 MB 2006-Mar-1/backup.tar.gz

   2. List buckets on Amazon S3 object storage
      $ mc {{.Name}} https://s3.amazonaws.com/
      2015-01-20 15:42:00 PST               rom
      2015-01-15 00:05:40 PST               zek

   3. List buckets and objects from Minio object storage and Amazon S3 object storage
      $ mc {{.Name}} https://s3.amazonaws.com/ http://localhost:9000/backup/
      2015-01-20 15:42:00 PST               rom
      2015-01-15 00:05:40 PST               zek
      2015-03-28 12:47:50 PDT      51.00 MB 2006-Jan-1/backup.tar.gz
      2015-03-31 14:46:33 PDT      55.00 MB 2006-Mar-1/backup.tar.gz

   4. List objects on local filesystem on Windows
      $ mc {{.Name}} C:\Users\Worf
      2015-03-28 12:47:50 PDT      11.00 MB Martok\Klingon Council Ministers.pdf
      2015-03-31 14:46:33 PDT      15.00 MB Gowron\Khitomer Conference Details.pdf

`,
	}

	mbCmd = cli.Command{
		Name:  "mb",
		Usage: "Make a bucket",
		//		Description: `Create a bucket on object storage or a folder on filesystem`,
		Action: doMakeBucketCmd,
		CustomHelpTemplate: `NAME:
   mc {{.Name}} - {{.Usage}}

USAGE:
   mc {{.Name}} TARGET [TARGET...] {{if .Description}}

DESCRIPTION:
   {{.Description}}{{end}}{{if .Flags}}

OPTIONS:
   {{range .Flags}}{{.}}
   {{end}}{{ end }}

EXAMPLES:
   1. Create a bucket on Amazon S3 object storage
      $ mc {{.Name}} https://s3.amazonaws.com/public-document-store

   2. Create a bucket on Minio object storage
      $ mc {{.Name}} http://localhost:9000/mongodb-backup

   3. Create multiple buckets on Amazon S3 object storage
      $ mc {{.Name}} https://s3.amazonaws.com/public-photo-store https://s3.amazonaws.com/public-store

`,
	}
	//   Configure minio client configuration.
	//
	//   NOTE: that the configure command only writes values to the config file.
	//   It does not use any configuration values from the environment variables.`,
	configCmd = cli.Command{
		Name:   "config",
		Usage:  "Generate configuration \"" + mustGetMcConfigPath() + "\" file.",
		Action: doConfigCmd,
		Flags: []cli.Flag{
			cli.StringFlag{
				Name:  "alias",
				Usage: "Add URL aliases into config",
			},
			cli.BoolFlag{
				Name:  "completion",
				Usage: "Generate bash completion \"" + mustGetMcBashCompletionFilename() + "\" file.",
			},
		},
		CustomHelpTemplate: `NAME:
   mc {{.Name}} - {{.Usage}}

USAGE:
   mc {{.Name}} generate {{if .Description}}

DESCRIPTION:
   {{.Description}}{{end}}{{if .Flags}}

OPTIONS:
   {{range .Flags}}{{.}}
   {{end}}{{ end }}
EXAMPLES:
   1. Generate mc config
      $ mc config generate

   2. Generate bash completion
      $ mc config --completion

   3. Add alias URLs
      $ mc config --alias "zek https://s3.amazonaws.com/"

`,
	}
	updateCmd = cli.Command{
		Name:        "update",
		Usage:       "Check for new software updates",
		Description: "",
		Action:      doUpdateCmd,
		Flags: []cli.Flag{
			cli.BoolFlag{
				Name:  "yes, y",
				Usage: "Download and update local binary",
			},
		},
	}
)

var options = []cli.Command{
	cpCmd,
	lsCmd,
	mbCmd,
	configCmd,
	updateCmd,
}

var (
	flags = []cli.Flag{
		cli.BoolFlag{
			Name:  "debug",
			Usage: "Enable HTTP tracing",
		},
		cli.BoolFlag{
			Name:  "quiet, q",
			Usage: "Disable chatty output, such as the progress bar",
		},
	}
)

var (
	mcBashCompletion = `#!/bin/bash

_mc_completion() {
    local cur prev opts base
    COMPREPLY=()
    cur="${COMP_WORDS[COMP_CWORD]}"
    prev="${COMP_WORDS[COMP_CWORD-1]}"
    opts=$( ${COMP_WORDS[@]:0:$COMP_CWORD} --generate-bash-completion )
    COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
    return 0
}

complete -F _mc_completion mc
`
)
<|endoftext|>