<commit_before>package assured

import (
	"context"
	"io/ioutil"
	"net/http"
	"net/http/httptest"
	"testing"
	"time"

	kitlog "github.com/go-kit/kit/log"
	"github.com/stretchr/testify/require"
)

func TestNewAssuredEndpoints(t *testing.T) {
	expected := &AssuredEndpoints{
		logger:         kitlog.NewNopLogger(),
		httpClient:     http.DefaultClient,
		assuredCalls:   NewCallStore(),
		madeCalls:      NewCallStore(),
		trackMadeCalls: true,
	}
	actual := NewAssuredEndpoints(DefaultOptions)

	require.Equal(t, expected.assuredCalls, actual.assuredCalls)
	require.Equal(t, expected.madeCalls, actual.madeCalls)
}

func TestWrappedEndpointSuccess(t *testing.T) {
	endpoints := NewAssuredEndpoints(DefaultOptions)
	testEndpoint := func(ctx context.Context, call *Call) (interface{}, error) {
		return call, nil
	}

	actual := endpoints.WrappedEndpoint(testEndpoint)
	c, err := actual(context.TODO(), testCall1())

	require.NoError(t, err)
	require.Equal(t, testCall1(), c)
}

func TestWrappedEndpointFailure(t *testing.T) {
	endpoints := NewAssuredEndpoints(DefaultOptions)
	testEndpoint := func(ctx context.Context, call *Call) (interface{}, error) {
		return call, nil
	}

	actual := endpoints.WrappedEndpoint(testEndpoint)
	c, err := actual(context.TODO(), false)

	require.Nil(t, c)
	require.Error(t, err)
	require.Equal(t, err.Error(), "unable to convert request to assured Call")
}

func TestGivenEndpointSuccess(t *testing.T) {
	endpoints := NewAssuredEndpoints(DefaultOptions)

	c, err := endpoints.GivenEndpoint(context.TODO(), testCall1())

	require.NoError(t, err)
	require.Equal(t, testCall1(), c)

	c, err = endpoints.GivenEndpoint(context.TODO(), testCall2())

	require.NoError(t, err)
	require.Equal(t, testCall2(), c)

	c, err = endpoints.GivenEndpoint(context.TODO(), testCall3())

	require.NoError(t, err)
	require.Equal(t, testCall3(), c)

	require.Equal(t, fullAssuredCalls, endpoints.assuredCalls)
}

func TestGivenCallbackEndpointSuccess(t *testing.T) {
	endpoints := NewAssuredEndpoints(DefaultOptions)

	callback1 := testCall1()
	callback1.Headers[AssuredCallbackKey] = "call-key"
	c, err := endpoints.GivenEndpoint(context.TODO(), callback1)

	require.NoError(t, err)
	require.Equal(t, callback1, c)

	callback2 := testCall2()
	callback2.Headers[AssuredCallbackKey] = "call-key"
	c, err = endpoints.GivenEndpoint(context.TODO(), callback2)

	require.NoError(t, err)
	require.Equal(t, callback2, c)

	callback3 := testCall3()
	callback3.Headers[AssuredCallbackKey] = "call-key"
	c, err = endpoints.GivenEndpoint(context.TODO(), callback3)

	require.NoError(t, err)
	require.Equal(t, callback3, c)

	c, err = endpoints.GivenCallbackEndpoint(context.TODO(), testCallback())

	expectedAssured := &CallStore{
		data: map[string][]*Call{
			"GET:test/assured":    {callback1, callback2},
			"POST:teapot/assured": {callback3},
		},
	}
	expectedCallback := &CallStore{
		data: map[string][]*Call{
			"call-key": {testCallback()},
		},
	}
	require.NoError(t, err)
	require.Equal(t, testCallback(), c)
	require.Equal(t, expectedAssured, endpoints.assuredCalls)
	require.Equal(t, expectedCallback, endpoints.callbackCalls)

}

func TestWhenEndpointSuccess(t *testing.T) {
	endpoints := &AssuredEndpoints{
		logger:         DefaultOptions.logger,
		assuredCalls:   fullAssuredCalls,
		madeCalls:      NewCallStore(),
		callbackCalls:  NewCallStore(),
		trackMadeCalls: true,
	}
	expected := map[string][]*Call{
		"GET:test/assured":    {testCall2(), testCall1()},
		"POST:teapot/assured": {testCall3()},
	}

	c, err := endpoints.WhenEndpoint(context.TODO(), testCall1())

	require.NoError(t, err)
	require.Equal(t, testCall1(), c)
	require.Equal(t, expected, endpoints.assuredCalls.data)

	c, err = endpoints.WhenEndpoint(context.TODO(), testCall2())

	require.NoError(t, err)
	require.Equal(t, testCall2(), c)
	require.Equal(t, fullAssuredCalls, endpoints.assuredCalls)

	c, err = endpoints.WhenEndpoint(context.TODO(), testCall3())

	require.NoError(t, err)
	require.Equal(t, testCall3(), c)
	require.Equal(t, fullAssuredCalls, endpoints.assuredCalls)
	require.Equal(t, fullAssuredCalls, endpoints.madeCalls)
}

func TestWhenEndpointSuccessTrackingDisabled(t *testing.T) {
	endpoints := &AssuredEndpoints{
		logger:         DefaultOptions.logger,
		assuredCalls:   fullAssuredCalls,
		madeCalls:      NewCallStore(),
		callbackCalls:  NewCallStore(),
		trackMadeCalls: false,
	}
	expected := map[string][]*Call{
		"GET:test/assured":    {testCall2(), testCall1()},
		"POST:teapot/assured": {testCall3()},
	}

	c, err := endpoints.WhenEndpoint(context.TODO(), testCall1())

	require.NoError(t, err)
	require.Equal(t, testCall1(), c)
	require.Equal(t, expected, endpoints.assuredCalls.data)

	c, err = endpoints.WhenEndpoint(context.TODO(), testCall2())

	require.NoError(t, err)
	require.Equal(t, testCall2(), c)
	require.Equal(t, fullAssuredCalls, endpoints.assuredCalls)

	c, err = endpoints.WhenEndpoint(context.TODO(), testCall3())

	require.NoError(t, err)
	require.Equal(t, testCall3(), c)
	require.Equal(t, fullAssuredCalls, endpoints.assuredCalls)
	require.Equal(t, NewCallStore(), endpoints.madeCalls)
}

func TestWhenEndpointSuccessCallbacks(t *testing.T) {
	called := false
	testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		called = true
	}))
	assured := testCall1()
	assured.Headers[AssuredCallbackKey] = "call-key"
	call := testCallback()
	call.Headers[AssuredCallbackTarget] = testServer.URL
	endpoints := &AssuredEndpoints{
		logger:     DefaultOptions.logger,
		httpClient: http.DefaultClient,
		assuredCalls: &CallStore{
			data: map[string][]*Call{"GET:test/assured": {assured}},
		},
		madeCalls: NewCallStore(),
		callbackCalls: &CallStore{
			data: map[string][]*Call{"call-key": {call}},
		},
		trackMadeCalls: true,
	}

	c, err := endpoints.WhenEndpoint(context.TODO(), assured)

	require.NoError(t, err)
	require.Equal(t, assured, c)
	// allow go routine to finish
	time.Sleep(1 * time.Millisecond)
	require.True(t, called, "callback was not hit")
}

func TestWhenEndpointSuccessDelayed(t *testing.T) {
	called := false
	testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		called = true
	}))
	assured := testCall1()
	assured.Headers[AssuredCallbackKey] = "call-key"
	assured.Headers[AssuredDelay] = "2"
	call := testCallback()
	call.Headers[AssuredCallbackTarget] = testServer.URL
	call.Headers[AssuredCallbackDelay] = "4"
	endpoints := &AssuredEndpoints{
		logger:     DefaultOptions.logger,
		httpClient: http.DefaultClient,
		assuredCalls: &CallStore{
			data: map[string][]*Call{"GET:test/assured": {assured}},
		},
		madeCalls: NewCallStore(),
		callbackCalls: &CallStore{
			data: map[string][]*Call{"call-key": {call}},
		},
		trackMadeCalls: true,
	}
	start := time.Now()
	c, err := endpoints.WhenEndpoint(context.TODO(), assured)

	require.True(t, time.Since(start) >= 2*time.Second, "response should be delayed 2 seconds")
	require.NoError(t, err)
	require.Equal(t, assured, c)
	// allow go routine to finish
	time.Sleep(1 * time.Second)
	require.False(t, called, "callback should not be hit yet")
	time.Sleep(2 * time.Second)
	require.True(t, called, "callback was not hit")
}

func TestSendCallbackBadRequest(t *testing.T) {
	called := false
	testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		called = true
	}))
	call := testCallback()
	call.Method = "\""
	endpoints := NewAssuredEndpoints(DefaultOptions)
	endpoints.sendCallback(testServer.URL, call)

	// allow go routine to finish
	time.Sleep(1 * time.Millisecond)
	require.False(t, called, "callback should not be hit")
}

func TestSendCallbackBadResponse(t *testing.T) {
	endpoints := NewAssuredEndpoints(DefaultOptions)
	endpoints.sendCallback("http://localhost:900000", testCallback())
}

func TestWhenEndpointNotFound(t *testing.T) {
	endpoints := NewAssuredEndpoints(DefaultOptions)

	c, err := endpoints.WhenEndpoint(context.TODO(), testCall1())

	require.Nil(t, c)
	require.Error(t, err)
	require.Equal(t, "No assured calls", err.Error())
}

func TestVerifyEndpointSuccess(t *testing.T) {
	endpoints := &AssuredEndpoints{
		madeCalls:      fullAssuredCalls,
		trackMadeCalls: true,
	}

	c, err := endpoints.VerifyEndpoint(context.TODO(), testCall1())

	require.NoError(t, err)
	require.Equal(t, []*Call{testCall1(), testCall2()}, c)

	c, err = endpoints.VerifyEndpoint(context.TODO(), testCall3())

	require.NoError(t, err)
	require.Equal(t, []*Call{testCall3()}, c)
}

func TestVerifyEndpointTrackingDisabled(t *testing.T) {
	endpoints := &AssuredEndpoints{
		madeCalls:      fullAssuredCalls,
		trackMadeCalls: false,
	}

	c, err := endpoints.VerifyEndpoint(context.TODO(), testCall1())

	require.Nil(t, c)
	require.Error(t, err)
	require.Equal(t, "Tracking made calls is disabled", err.Error())
}

func TestClearEndpointSuccess(t *testing.T) {
	endpoints := &AssuredEndpoints{
		logger:         kitlog.NewLogfmtLogger(ioutil.Discard),
		assuredCalls:   fullAssuredCalls,
		madeCalls:      fullAssuredCalls,
		callbackCalls:  NewCallStore(),
		trackMadeCalls: true,
	}
	expected := map[string][]*Call{
		"POST:teapot/assured": {testCall3()},
	}

	c, err := endpoints.ClearEndpoint(context.TODO(), testCall1())

	require.NoError(t, err)
	require.Nil(t, c)
	require.Equal(t, expected, endpoints.assuredCalls.data)
	require.Equal(t, expected, endpoints.madeCalls.data)

	c, err = endpoints.ClearEndpoint(context.TODO(), testCall2())

	require.NoError(t, err)
	require.Nil(t, c)
	require.Equal(t, expected, endpoints.assuredCalls.data)
	require.Equal(t, expected, endpoints.madeCalls.data)

	c, err = endpoints.ClearEndpoint(context.TODO(), testCall3())

	require.NoError(t, err)
	require.Nil(t, c)
	require.Equal(t, map[string][]*Call{}, endpoints.assuredCalls.data)
	require.Equal(t, map[string][]*Call{}, endpoints.madeCalls.data)
}

func TestClearEndpointSuccessCallback(t *testing.T) {
	endpoints := &AssuredEndpoints{
		logger:       kitlog.NewLogfmtLogger(ioutil.Discard),
		assuredCalls: fullAssuredCalls,
		madeCalls:    NewCallStore(),
		callbackCalls: &CallStore{
			data: map[string][]*Call{
				"call-key":       {testCallback()},
				"other-call-key": {testCallback()},
			},
		},
		trackMadeCalls: true,
	}

	c, err := endpoints.ClearEndpoint(context.TODO(), testCallback())

	require.NoError(t, err)
	require.Nil(t, c)
	require.Equal(t, fullAssuredCalls.data, endpoints.assuredCalls.data)
	require.Equal(t, map[string][]*Call{}, endpoints.madeCalls.data)
	require.Equal(t, map[string][]*Call{"other-call-key": {testCallback()}}, endpoints.callbackCalls.data)
}

func TestClearAllEndpointSuccess(t *testing.T) {
	endpoints := &AssuredEndpoints{
		logger:         kitlog.NewLogfmtLogger(ioutil.Discard),
		assuredCalls:   fullAssuredCalls,
		madeCalls:      fullAssuredCalls,
		callbackCalls:  fullAssuredCalls,
		trackMadeCalls: true,
	}

	c, err := endpoints.ClearAllEndpoint(context.TODO(), nil)

	require.NoError(t, err)
	require.Nil(t, c)
	require.Equal(t, map[string][]*Call{}, endpoints.assuredCalls.data)
	require.Equal(t, map[string][]*Call{}, endpoints.madeCalls.data)
	require.Equal(t, map[string][]*Call{}, endpoints.callbackCalls.data)
}
<commit_msg>test: add time for async test<commit_after>package assured

import (
	"context"
	"io/ioutil"
	"net/http"
	"net/http/httptest"
	"testing"
	"time"

	kitlog "github.com/go-kit/kit/log"
	"github.com/stretchr/testify/require"
)

func TestNewAssuredEndpoints(t *testing.T) {
	expected := &AssuredEndpoints{
		logger:         kitlog.NewNopLogger(),
		httpClient:     http.DefaultClient,
		assuredCalls:   NewCallStore(),
		madeCalls:      NewCallStore(),
		trackMadeCalls: true,
	}
	actual := NewAssuredEndpoints(DefaultOptions)

	require.Equal(t, expected.assuredCalls, actual.assuredCalls)
	require.Equal(t, expected.madeCalls, actual.madeCalls)
}

func TestWrappedEndpointSuccess(t *testing.T) {
	endpoints := NewAssuredEndpoints(DefaultOptions)
	testEndpoint := func(ctx context.Context, call *Call) (interface{}, error) {
		return call, nil
	}

	actual := endpoints.WrappedEndpoint(testEndpoint)
	c, err := actual(context.TODO(), testCall1())

	require.NoError(t, err)
	require.Equal(t, testCall1(), c)
}

func TestWrappedEndpointFailure(t *testing.T) {
	endpoints := NewAssuredEndpoints(DefaultOptions)
	testEndpoint := func(ctx context.Context, call *Call) (interface{}, error) {
		return call, nil
	}

	actual := endpoints.WrappedEndpoint(testEndpoint)
	c, err := actual(context.TODO(), false)

	require.Nil(t, c)
	require.Error(t, err)
	require.Equal(t, err.Error(), "unable to convert request to assured Call")
}

func TestGivenEndpointSuccess(t *testing.T) {
	endpoints := NewAssuredEndpoints(DefaultOptions)

	c, err := endpoints.GivenEndpoint(context.TODO(), testCall1())

	require.NoError(t, err)
	require.Equal(t, testCall1(), c)

	c, err = endpoints.GivenEndpoint(context.TODO(), testCall2())

	require.NoError(t, err)
	require.Equal(t, testCall2(), c)

	c, err = endpoints.GivenEndpoint(context.TODO(), testCall3())

	require.NoError(t, err)
	require.Equal(t, testCall3(), c)

	require.Equal(t, fullAssuredCalls, endpoints.assuredCalls)
}

func TestGivenCallbackEndpointSuccess(t *testing.T) {
	endpoints := NewAssuredEndpoints(DefaultOptions)

	callback1 := testCall1()
	callback1.Headers[AssuredCallbackKey] = "call-key"
	c, err := endpoints.GivenEndpoint(context.TODO(), callback1)

	require.NoError(t, err)
	require.Equal(t, callback1, c)

	callback2 := testCall2()
	callback2.Headers[AssuredCallbackKey] = "call-key"
	c, err = endpoints.GivenEndpoint(context.TODO(), callback2)

	require.NoError(t, err)
	require.Equal(t, callback2, c)

	callback3 := testCall3()
	callback3.Headers[AssuredCallbackKey] = "call-key"
	c, err = endpoints.GivenEndpoint(context.TODO(), callback3)

	require.NoError(t, err)
	require.Equal(t, callback3, c)

	c, err = endpoints.GivenCallbackEndpoint(context.TODO(), testCallback())

	expectedAssured := &CallStore{
		data: map[string][]*Call{
			"GET:test/assured":    {callback1, callback2},
			"POST:teapot/assured": {callback3},
		},
	}
	expectedCallback := &CallStore{
		data: map[string][]*Call{
			"call-key": {testCallback()},
		},
	}
	require.NoError(t, err)
	require.Equal(t, testCallback(), c)
	require.Equal(t, expectedAssured, endpoints.assuredCalls)
	require.Equal(t, expectedCallback, endpoints.callbackCalls)

}

func TestWhenEndpointSuccess(t *testing.T) {
	endpoints := &AssuredEndpoints{
		logger:         DefaultOptions.logger,
		assuredCalls:   fullAssuredCalls,
		madeCalls:      NewCallStore(),
		callbackCalls:  NewCallStore(),
		trackMadeCalls: true,
	}
	expected := map[string][]*Call{
		"GET:test/assured":    {testCall2(), testCall1()},
		"POST:teapot/assured": {testCall3()},
	}

	c, err := endpoints.WhenEndpoint(context.TODO(), testCall1())

	require.NoError(t, err)
	require.Equal(t, testCall1(), c)
	require.Equal(t, expected, endpoints.assuredCalls.data)

	c, err = endpoints.WhenEndpoint(context.TODO(), testCall2())

	require.NoError(t, err)
	require.Equal(t, testCall2(), c)
	require.Equal(t, fullAssuredCalls, endpoints.assuredCalls)

	c, err = endpoints.WhenEndpoint(context.TODO(), testCall3())

	require.NoError(t, err)
	require.Equal(t, testCall3(), c)
	require.Equal(t, fullAssuredCalls, endpoints.assuredCalls)
	require.Equal(t, fullAssuredCalls, endpoints.madeCalls)
}

func TestWhenEndpointSuccessTrackingDisabled(t *testing.T) {
	endpoints := &AssuredEndpoints{
		logger:         DefaultOptions.logger,
		assuredCalls:   fullAssuredCalls,
		madeCalls:      NewCallStore(),
		callbackCalls:  NewCallStore(),
		trackMadeCalls: false,
	}
	expected := map[string][]*Call{
		"GET:test/assured":    {testCall2(), testCall1()},
		"POST:teapot/assured": {testCall3()},
	}

	c, err := endpoints.WhenEndpoint(context.TODO(), testCall1())

	require.NoError(t, err)
	require.Equal(t, testCall1(), c)
	require.Equal(t, expected, endpoints.assuredCalls.data)

	c, err = endpoints.WhenEndpoint(context.TODO(), testCall2())

	require.NoError(t, err)
	require.Equal(t, testCall2(), c)
	require.Equal(t, fullAssuredCalls, endpoints.assuredCalls)

	c, err = endpoints.WhenEndpoint(context.TODO(), testCall3())

	require.NoError(t, err)
	require.Equal(t, testCall3(), c)
	require.Equal(t, fullAssuredCalls, endpoints.assuredCalls)
	require.Equal(t, NewCallStore(), endpoints.madeCalls)
}

func TestWhenEndpointSuccessCallbacks(t *testing.T) {
	called := false
	testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		called = true
	}))
	assured := testCall1()
	assured.Headers[AssuredCallbackKey] = "call-key"
	call := testCallback()
	call.Headers[AssuredCallbackTarget] = testServer.URL
	endpoints := &AssuredEndpoints{
		logger:     DefaultOptions.logger,
		httpClient: http.DefaultClient,
		assuredCalls: &CallStore{
			data: map[string][]*Call{"GET:test/assured": {assured}},
		},
		madeCalls: NewCallStore(),
		callbackCalls: &CallStore{
			data: map[string][]*Call{"call-key": {call}},
		},
		trackMadeCalls: true,
	}

	c, err := endpoints.WhenEndpoint(context.TODO(), assured)

	require.NoError(t, err)
	require.Equal(t, assured, c)
	// allow go routine to finish
	time.Sleep(10 * time.Millisecond)
	require.True(t, called, "callback was not hit")
}

func TestWhenEndpointSuccessDelayed(t *testing.T) {
	called := false
	testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		called = true
	}))
	assured := testCall1()
	assured.Headers[AssuredCallbackKey] = "call-key"
	assured.Headers[AssuredDelay] = "2"
	call := testCallback()
	call.Headers[AssuredCallbackTarget] = testServer.URL
	call.Headers[AssuredCallbackDelay] = "4"
	endpoints := &AssuredEndpoints{
		logger:     DefaultOptions.logger,
		httpClient: http.DefaultClient,
		assuredCalls: &CallStore{
			data: map[string][]*Call{"GET:test/assured": {assured}},
		},
		madeCalls: NewCallStore(),
		callbackCalls: &CallStore{
			data: map[string][]*Call{"call-key": {call}},
		},
		trackMadeCalls: true,
	}
	start := time.Now()
	c, err := endpoints.WhenEndpoint(context.TODO(), assured)

	require.True(t, time.Since(start) >= 2*time.Second, "response should be delayed 2 seconds")
	require.NoError(t, err)
	require.Equal(t, assured, c)
	// allow go routine to finish
	time.Sleep(1 * time.Second)
	require.False(t, called, "callback should not be hit yet")
	time.Sleep(2 * time.Second)
	require.True(t, called, "callback was not hit")
}

func TestSendCallbackBadRequest(t *testing.T) {
	called := false
	testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		called = true
	}))
	call := testCallback()
	call.Method = "\""
	endpoints := NewAssuredEndpoints(DefaultOptions)
	endpoints.sendCallback(testServer.URL, call)

	// allow go routine to finish
	time.Sleep(1 * time.Millisecond)
	require.False(t, called, "callback should not be hit")
}

func TestSendCallbackBadResponse(t *testing.T) {
	endpoints := NewAssuredEndpoints(DefaultOptions)
	endpoints.sendCallback("http://localhost:900000", testCallback())
}

func TestWhenEndpointNotFound(t *testing.T) {
	endpoints := NewAssuredEndpoints(DefaultOptions)

	c, err := endpoints.WhenEndpoint(context.TODO(), testCall1())

	require.Nil(t, c)
	require.Error(t, err)
	require.Equal(t, "No assured calls", err.Error())
}

func TestVerifyEndpointSuccess(t *testing.T) {
	endpoints := &AssuredEndpoints{
		madeCalls:      fullAssuredCalls,
		trackMadeCalls: true,
	}

	c, err := endpoints.VerifyEndpoint(context.TODO(), testCall1())

	require.NoError(t, err)
	require.Equal(t, []*Call{testCall1(), testCall2()}, c)

	c, err = endpoints.VerifyEndpoint(context.TODO(), testCall3())

	require.NoError(t, err)
	require.Equal(t, []*Call{testCall3()}, c)
}

func TestVerifyEndpointTrackingDisabled(t *testing.T) {
	endpoints := &AssuredEndpoints{
		madeCalls:      fullAssuredCalls,
		trackMadeCalls: false,
	}

	c, err := endpoints.VerifyEndpoint(context.TODO(), testCall1())

	require.Nil(t, c)
	require.Error(t, err)
	require.Equal(t, "Tracking made calls is disabled", err.Error())
}

func TestClearEndpointSuccess(t *testing.T) {
	endpoints := &AssuredEndpoints{
		logger:         kitlog.NewLogfmtLogger(ioutil.Discard),
		assuredCalls:   fullAssuredCalls,
		madeCalls:      fullAssuredCalls,
		callbackCalls:  NewCallStore(),
		trackMadeCalls: true,
	}
	expected := map[string][]*Call{
		"POST:teapot/assured": {testCall3()},
	}

	c, err := endpoints.ClearEndpoint(context.TODO(), testCall1())

	require.NoError(t, err)
	require.Nil(t, c)
	require.Equal(t, expected, endpoints.assuredCalls.data)
	require.Equal(t, expected, endpoints.madeCalls.data)

	c, err = endpoints.ClearEndpoint(context.TODO(), testCall2())

	require.NoError(t, err)
	require.Nil(t, c)
	require.Equal(t, expected, endpoints.assuredCalls.data)
	require.Equal(t, expected, endpoints.madeCalls.data)

	c, err = endpoints.ClearEndpoint(context.TODO(), testCall3())

	require.NoError(t, err)
	require.Nil(t, c)
	require.Equal(t, map[string][]*Call{}, endpoints.assuredCalls.data)
	require.Equal(t, map[string][]*Call{}, endpoints.madeCalls.data)
}

func TestClearEndpointSuccessCallback(t *testing.T) {
	endpoints := &AssuredEndpoints{
		logger:       kitlog.NewLogfmtLogger(ioutil.Discard),
		assuredCalls: fullAssuredCalls,
		madeCalls:    NewCallStore(),
		callbackCalls: &CallStore{
			data: map[string][]*Call{
				"call-key":       {testCallback()},
				"other-call-key": {testCallback()},
			},
		},
		trackMadeCalls: true,
	}

	c, err := endpoints.ClearEndpoint(context.TODO(), testCallback())

	require.NoError(t, err)
	require.Nil(t, c)
	require.Equal(t, fullAssuredCalls.data, endpoints.assuredCalls.data)
	require.Equal(t, map[string][]*Call{}, endpoints.madeCalls.data)
	require.Equal(t, map[string][]*Call{"other-call-key": {testCallback()}}, endpoints.callbackCalls.data)
}

func TestClearAllEndpointSuccess(t *testing.T) {
	endpoints := &AssuredEndpoints{
		logger:         kitlog.NewLogfmtLogger(ioutil.Discard),
		assuredCalls:   fullAssuredCalls,
		madeCalls:      fullAssuredCalls,
		callbackCalls:  fullAssuredCalls,
		trackMadeCalls: true,
	}

	c, err := endpoints.ClearAllEndpoint(context.TODO(), nil)

	require.NoError(t, err)
	require.Nil(t, c)
	require.Equal(t, map[string][]*Call{}, endpoints.assuredCalls.data)
	require.Equal(t, map[string][]*Call{}, endpoints.madeCalls.data)
	require.Equal(t, map[string][]*Call{}, endpoints.callbackCalls.data)
}
<|endoftext|>


<commit_before>package types

import (
	"sync"

	aci "github.com/appscode/k8s-addons/api"
	acs "github.com/appscode/k8s-addons/client/clientset"
	"github.com/appscode/k8s-addons/pkg/stash"
	"github.com/appscode/searchlight/data"
	"github.com/appscode/searchlight/pkg/client/icinga"
	clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
)

const AcknowledgeTimestamp string = "acknowledgement_timestamp"

type IcingaData struct {
	HostType map[string]string
	VarInfo  map[string]data.CommandVar
}

type Context struct {
	// kubernetes client
	KubeClient              clientset.Interface
	AppsCodeExtensionClient acs.AppsCodeExtensionInterface

	IcingaClient *icinga.IcingaClient
	IcingaData   map[string]*IcingaData

	Resource   *aci.Alert
	ObjectType string
	ObjectName string

	Storage *stash.Storage
	sync.Mutex
}

type KubeOptions struct {
	ObjectType string
	ObjectName string
}

type Ancestors struct {
	Type  string   `json:"type,omitempty"`
	Names []string `json:"names,omitempty"`
}

type AlertEventMessage struct {
	IncidentEventId string `json:"incident_event_id,omitempty"`
	Comment         string `json:"comment,omitempty"`
	UserName        string `json:"username,omitempty"`
}
<commit_msg>Modified IncidentEventId type<commit_after>package types

import (
	"sync"

	aci "github.com/appscode/k8s-addons/api"
	acs "github.com/appscode/k8s-addons/client/clientset"
	"github.com/appscode/k8s-addons/pkg/stash"
	"github.com/appscode/searchlight/data"
	"github.com/appscode/searchlight/pkg/client/icinga"
	clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
)

const AcknowledgeTimestamp string = "acknowledgement_timestamp"

type IcingaData struct {
	HostType map[string]string
	VarInfo  map[string]data.CommandVar
}

type Context struct {
	// kubernetes client
	KubeClient              clientset.Interface
	AppsCodeExtensionClient acs.AppsCodeExtensionInterface

	IcingaClient *icinga.IcingaClient
	IcingaData   map[string]*IcingaData

	Resource   *aci.Alert
	ObjectType string
	ObjectName string

	Storage *stash.Storage
	sync.Mutex
}

type KubeOptions struct {
	ObjectType string
	ObjectName string
}

type Ancestors struct {
	Type  string   `json:"type,omitempty"`
	Names []string `json:"names,omitempty"`
}

type AlertEventMessage struct {
	IncidentEventId int64  `json:"incident_event_id,omitempty"`
	Comment         string `json:"comment,omitempty"`
	UserName        string `json:"username,omitempty"`
}
<|endoftext|>


<commit_before>package eventer

import (
	"github.com/appscode/log"
	kapi "k8s.io/kubernetes/pkg/api"
	clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
	"k8s.io/kubernetes/pkg/client/record"
)

const (
	EventReasonCreating             string = "Creating"
	EventReasonPausing              string = "Pausing"
	EventReasonWipingOut            string = "WipingOut"
	EventReasonFailedToCreate       string = "Failed"
	EventReasonFailedToPause        string = "Failed"
	EventReasonFailedToDelete       string = "Failed"
	EventReasonFailedToWipeOut      string = "Failed"
	EventReasonFailedToGet          string = "Failed"
	EventReasonFailedToInitialize   string = "Failed"
	EventReasonFailedToList         string = "Failed"
	EventReasonFailedToResume       string = "Failed"
	EventReasonFailedToSchedule     string = "Failed"
	EventReasonFailedToStart        string = "Failed"
	EventReasonFailedToUpdate       string = "Failed"
	EventReasonIgnoredSnapshot      string = "IgnoredSnapshot"
	EventReasonInitializing         string = "Initializing"
	EventReasonInvalid              string = "Invalid"
	EventReasonInvalidUpdate        string = "InvalidUpdate"
	EventReasonSnapshotFailed       string = "SnapshotFailed"
	EventReasonStarting             string = "Starting"
	EventReasonSuccessfulCreate     string = "SuccessfulCreate"
	EventReasonSuccessfulPause      string = "SuccessfulPause"
	EventReasonSuccessfulWipeOut    string = "SuccessfulWipeOut"
	EventReasonSuccessfulSnapshot   string = "SuccessfulSnapshot"
	EventReasonSuccessfulValidate   string = "SuccessfulValidate"
	EventReasonSuccessfulInitialize string = "SuccessfulInitialize"
)

func NewEventRecorder(client clientset.Interface, component string) record.EventRecorder {
	// Event Broadcaster
	broadcaster := record.NewBroadcaster()
	broadcaster.StartEventWatcher(
		func(event *kapi.Event) {
			if _, err := client.Core().Events(event.Namespace).Create(event); err != nil {
				log.Errorln(err)
			}
		},
	)

	return broadcaster.NewRecorder(kapi.EventSource{Component: component})
}
<commit_msg>Add SuccessfulResume event<commit_after>package eventer

import (
	"github.com/appscode/log"
	kapi "k8s.io/kubernetes/pkg/api"
	clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
	"k8s.io/kubernetes/pkg/client/record"
)

const (
	EventReasonCreating             string = "Creating"
	EventReasonPausing              string = "Pausing"
	EventReasonWipingOut            string = "WipingOut"
	EventReasonFailedToCreate       string = "Failed"
	EventReasonFailedToPause        string = "Failed"
	EventReasonFailedToDelete       string = "Failed"
	EventReasonFailedToWipeOut      string = "Failed"
	EventReasonFailedToGet          string = "Failed"
	EventReasonFailedToInitialize   string = "Failed"
	EventReasonFailedToList         string = "Failed"
	EventReasonFailedToResume       string = "Failed"
	EventReasonFailedToSchedule     string = "Failed"
	EventReasonFailedToStart        string = "Failed"
	EventReasonFailedToUpdate       string = "Failed"
	EventReasonIgnoredSnapshot      string = "IgnoredSnapshot"
	EventReasonInitializing         string = "Initializing"
	EventReasonInvalid              string = "Invalid"
	EventReasonInvalidUpdate        string = "InvalidUpdate"
	EventReasonSnapshotFailed       string = "SnapshotFailed"
	EventReasonStarting             string = "Starting"
	EventReasonSuccessfulCreate     string = "SuccessfulCreate"
	EventReasonSuccessfulPause      string = "SuccessfulPause"
	EventReasonSuccessfulResume     string = "SuccessfulResume"
	EventReasonSuccessfulWipeOut    string = "SuccessfulWipeOut"
	EventReasonSuccessfulSnapshot   string = "SuccessfulSnapshot"
	EventReasonSuccessfulValidate   string = "SuccessfulValidate"
	EventReasonSuccessfulInitialize string = "SuccessfulInitialize"
)

func NewEventRecorder(client clientset.Interface, component string) record.EventRecorder {
	// Event Broadcaster
	broadcaster := record.NewBroadcaster()
	broadcaster.StartEventWatcher(
		func(event *kapi.Event) {
			if _, err := client.Core().Events(event.Namespace).Create(event); err != nil {
				log.Errorln(err)
			}
		},
	)

	return broadcaster.NewRecorder(kapi.EventSource{Component: component})
}
<|endoftext|>


<commit_before>/*
Copyright 2014 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 get

import (
	"fmt"
	"io"
	"reflect"
	"sort"

	"k8s.io/klog"

	corev1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/meta"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
	metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/cli-runtime/pkg/printers"
	"k8s.io/client-go/util/jsonpath"
	"k8s.io/utils/integer"

	"vbom.ml/util/sortorder"
)

// SortingPrinter sorts list types before delegating to another printer.
// Non-list types are simply passed through
type SortingPrinter struct {
	SortField string
	Delegate  printers.ResourcePrinter
	Decoder   runtime.Decoder
}

func (s *SortingPrinter) PrintObj(obj runtime.Object, out io.Writer) error {
	if !meta.IsListType(obj) {
		return s.Delegate.PrintObj(obj, out)
	}

	if err := s.sortObj(obj); err != nil {
		return err
	}
	return s.Delegate.PrintObj(obj, out)
}

func (s *SortingPrinter) sortObj(obj runtime.Object) error {
	objs, err := meta.ExtractList(obj)
	if err != nil {
		return err
	}
	if len(objs) == 0 {
		return nil
	}

	sorter, err := SortObjects(s.Decoder, objs, s.SortField)
	if err != nil {
		return err
	}

	switch list := obj.(type) {
	case *corev1.List:
		outputList := make([]runtime.RawExtension, len(objs))
		for ix := range objs {
			outputList[ix] = list.Items[sorter.OriginalPosition(ix)]
		}
		list.Items = outputList
		return nil
	}
	return meta.SetList(obj, objs)
}

func SortObjects(decoder runtime.Decoder, objs []runtime.Object, fieldInput string) (*RuntimeSort, error) {
	for ix := range objs {
		item := objs[ix]
		switch u := item.(type) {
		case *runtime.Unknown:
			var err error
			// decode runtime.Unknown to runtime.Unstructured for sorting.
			// we don't actually want the internal versions of known types.
			if objs[ix], _, err = decoder.Decode(u.Raw, nil, &unstructured.Unstructured{}); err != nil {
				return nil, err
			}
		}
	}

	field, err := RelaxedJSONPathExpression(fieldInput)
	if err != nil {
		return nil, err
	}

	parser := jsonpath.New("sorting").AllowMissingKeys(true)
	if err := parser.Parse(field); err != nil {
		return nil, err
	}

	// We don't do any model validation here, so we traverse all objects to be sorted
	// and, if the field is valid to at least one of them, we consider it to be a
	// valid field; otherwise error out.
	// Note that this requires empty fields to be considered later, when sorting.
	var fieldFoundOnce bool
	for _, obj := range objs {
		values, err := findJSONPathResults(parser, obj)
		if err != nil {
			return nil, err
		}
		if len(values) > 0 && len(values[0]) > 0 {
			fieldFoundOnce = true
			break
		}
	}
	if !fieldFoundOnce {
		return nil, fmt.Errorf("couldn't find any field with path %q in the list of objects", field)
	}

	sorter := NewRuntimeSort(field, objs)
	sort.Sort(sorter)
	return sorter, nil
}

// RuntimeSort is an implementation of the golang sort interface that knows how to sort
// lists of runtime.Object
type RuntimeSort struct {
	field        string
	objs         []runtime.Object
	origPosition []int
}

func NewRuntimeSort(field string, objs []runtime.Object) *RuntimeSort {
	sorter := &RuntimeSort{field: field, objs: objs, origPosition: make([]int, len(objs))}
	for ix := range objs {
		sorter.origPosition[ix] = ix
	}
	return sorter
}

func (r *RuntimeSort) Len() int {
	return len(r.objs)
}

func (r *RuntimeSort) Swap(i, j int) {
	r.objs[i], r.objs[j] = r.objs[j], r.objs[i]
	r.origPosition[i], r.origPosition[j] = r.origPosition[j], r.origPosition[i]
}

func isLess(i, j reflect.Value) (bool, error) {
	switch i.Kind() {
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
		return i.Int() < j.Int(), nil
	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
		return i.Uint() < j.Uint(), nil
	case reflect.Float32, reflect.Float64:
		return i.Float() < j.Float(), nil
	case reflect.String:
		return sortorder.NaturalLess(i.String(), j.String()), nil
	case reflect.Ptr:
		return isLess(i.Elem(), j.Elem())
	case reflect.Struct:
		// sort metav1.Time
		in := i.Interface()
		if t, ok := in.(metav1.Time); ok {
			time := j.Interface().(metav1.Time)
			return t.Before(&time), nil
		}
		// fallback to the fields comparison
		for idx := 0; idx < i.NumField(); idx++ {
			less, err := isLess(i.Field(idx), j.Field(idx))
			if err != nil || !less {
				return less, err
			}
		}
		return true, nil
	case reflect.Array, reflect.Slice:
		// note: the length of i and j may be different
		for idx := 0; idx < integer.IntMin(i.Len(), j.Len()); idx++ {
			less, err := isLess(i.Index(idx), j.Index(idx))
			if err != nil || !less {
				return less, err
			}
		}
		return true, nil

	case reflect.Interface:
		switch itype := i.Interface().(type) {
		case uint8:
			if jtype, ok := j.Interface().(uint8); ok {
				return itype < jtype, nil
			}
		case uint16:
			if jtype, ok := j.Interface().(uint16); ok {
				return itype < jtype, nil
			}
		case uint32:
			if jtype, ok := j.Interface().(uint32); ok {
				return itype < jtype, nil
			}
		case uint64:
			if jtype, ok := j.Interface().(uint64); ok {
				return itype < jtype, nil
			}
		case int8:
			if jtype, ok := j.Interface().(int8); ok {
				return itype < jtype, nil
			}
		case int16:
			if jtype, ok := j.Interface().(int16); ok {
				return itype < jtype, nil
			}
		case int32:
			if jtype, ok := j.Interface().(int32); ok {
				return itype < jtype, nil
			}
		case int64:
			if jtype, ok := j.Interface().(int64); ok {
				return itype < jtype, nil
			}
		case uint:
			if jtype, ok := j.Interface().(uint); ok {
				return itype < jtype, nil
			}
		case int:
			if jtype, ok := j.Interface().(int); ok {
				return itype < jtype, nil
			}
		case float32:
			if jtype, ok := j.Interface().(float32); ok {
				return itype < jtype, nil
			}
		case float64:
			if jtype, ok := j.Interface().(float64); ok {
				return itype < jtype, nil
			}
		case string:
			if jtype, ok := j.Interface().(string); ok {
				return sortorder.NaturalLess(itype, jtype), nil
			}
		default:
			return false, fmt.Errorf("unsortable type: %T", itype)
		}
		return false, fmt.Errorf("unsortable interface: %v", i.Kind())

	default:
		return false, fmt.Errorf("unsortable type: %v", i.Kind())
	}
}

func (r *RuntimeSort) Less(i, j int) bool {
	iObj := r.objs[i]
	jObj := r.objs[j]

	var iValues [][]reflect.Value
	var jValues [][]reflect.Value
	var err error

	parser := jsonpath.New("sorting").AllowMissingKeys(true)
	err = parser.Parse(r.field)
	if err != nil {
		panic(err)
	}

	iValues, err = findJSONPathResults(parser, iObj)
	if err != nil {
		klog.Fatalf("Failed to get i values for %#v using %s (%#v)", iObj, r.field, err)
	}

	jValues, err = findJSONPathResults(parser, jObj)
	if err != nil {
		klog.Fatalf("Failed to get j values for %#v using %s (%v)", jObj, r.field, err)
	}

	if len(iValues) == 0 || len(iValues[0]) == 0 {
		return true
	}
	if len(jValues) == 0 || len(jValues[0]) == 0 {
		return false
	}
	iField := iValues[0][0]
	jField := jValues[0][0]

	less, err := isLess(iField, jField)
	if err != nil {
		klog.Fatalf("Field %s in %T is an unsortable type: %s, err: %v", r.field, iObj, iField.Kind().String(), err)
	}
	return less
}

// OriginalPosition returns the starting (original) position of a particular index.
// e.g. If OriginalPosition(0) returns 5 than the
// the item currently at position 0 was at position 5 in the original unsorted array.
func (r *RuntimeSort) OriginalPosition(ix int) int {
	if ix < 0 || ix > len(r.origPosition) {
		return -1
	}
	return r.origPosition[ix]
}

type TableSorter struct {
	field      string
	obj        *metav1beta1.Table
	parsedRows [][][]reflect.Value
}

func (t *TableSorter) Len() int {
	return len(t.obj.Rows)
}

func (t *TableSorter) Swap(i, j int) {
	t.obj.Rows[i], t.obj.Rows[j] = t.obj.Rows[j], t.obj.Rows[i]
	t.parsedRows[i], t.parsedRows[j] = t.parsedRows[j], t.parsedRows[i]
}

func (t *TableSorter) Less(i, j int) bool {
	iValues := t.parsedRows[i]
	jValues := t.parsedRows[j]

	if len(iValues) == 0 || len(iValues[0]) == 0 {
		return true
	}
	if len(jValues) == 0 || len(jValues[0]) == 0 {
		return false
	}

	iField := iValues[0][0]
	jField := jValues[0][0]

	less, err := isLess(iField, jField)
	if err != nil {
		klog.Fatalf("Field %s in %T is an unsortable type: %s, err: %v", t.field, t.parsedRows, iField.Kind().String(), err)
	}
	return less
}

func (t *TableSorter) Sort() error {
	sort.Sort(t)
	return nil
}

func NewTableSorter(table *metav1beta1.Table, field string) (*TableSorter, error) {
	var parsedRows [][][]reflect.Value

	parser := jsonpath.New("sorting").AllowMissingKeys(true)
	err := parser.Parse(field)
	if err != nil {
		return nil, fmt.Errorf("sorting error: %v", err)
	}

	fieldFoundOnce := false
	for i := range table.Rows {
		parsedRow, err := findJSONPathResults(parser, table.Rows[i].Object.Object)
		if err != nil {
			return nil, fmt.Errorf("Failed to get values for %#v using %s (%#v)", parsedRow, field, err)
		}
		parsedRows = append(parsedRows, parsedRow)
		if len(parsedRow) > 0 && len(parsedRow[0]) > 0 {
			fieldFoundOnce = true
		}
	}

	if len(table.Rows) > 0 && !fieldFoundOnce {
		return nil, fmt.Errorf("couldn't find any field with path %q in the list of objects", field)
	}

	return &TableSorter{
		obj:        table,
		field:      field,
		parsedRows: parsedRows,
	}, nil
}
func findJSONPathResults(parser *jsonpath.JSONPath, from runtime.Object) ([][]reflect.Value, error) {
	if unstructuredObj, ok := from.(*unstructured.Unstructured); ok {
		return parser.FindResults(unstructuredObj.Object)
	}
	return parser.FindResults(reflect.ValueOf(from).Elem().Interface())
}
<commit_msg>Make sorting printer sort tables in place<commit_after>/*
Copyright 2014 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 get

import (
	"fmt"
	"io"
	"reflect"
	"sort"

	"k8s.io/klog"

	corev1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/meta"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
	metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/cli-runtime/pkg/printers"
	"k8s.io/client-go/util/jsonpath"
	"k8s.io/utils/integer"

	"vbom.ml/util/sortorder"
)

// SortingPrinter sorts list types before delegating to another printer.
// Non-list types are simply passed through
type SortingPrinter struct {
	SortField string
	Delegate  printers.ResourcePrinter
	Decoder   runtime.Decoder
}

func (s *SortingPrinter) PrintObj(obj runtime.Object, out io.Writer) error {
	if table, isTable := obj.(*metav1beta1.Table); isTable && len(table.Rows) > 1 {
		parsedField, err := RelaxedJSONPathExpression(s.SortField)
		if err != nil {
			parsedField = s.SortField
		}

		if sorter, err := NewTableSorter(table, parsedField); err != nil {
			return err
		} else if err := sorter.Sort(); err != nil {
			return err
		}
		return s.Delegate.PrintObj(table, out)
	}

	if meta.IsListType(obj) {
		if err := s.sortObj(obj); err != nil {
			return err
		}
		return s.Delegate.PrintObj(obj, out)
	}

	return s.Delegate.PrintObj(obj, out)
}

func (s *SortingPrinter) sortObj(obj runtime.Object) error {
	objs, err := meta.ExtractList(obj)
	if err != nil {
		return err
	}
	if len(objs) == 0 {
		return nil
	}

	sorter, err := SortObjects(s.Decoder, objs, s.SortField)
	if err != nil {
		return err
	}

	switch list := obj.(type) {
	case *corev1.List:
		outputList := make([]runtime.RawExtension, len(objs))
		for ix := range objs {
			outputList[ix] = list.Items[sorter.OriginalPosition(ix)]
		}
		list.Items = outputList
		return nil
	}
	return meta.SetList(obj, objs)
}

func SortObjects(decoder runtime.Decoder, objs []runtime.Object, fieldInput string) (*RuntimeSort, error) {
	for ix := range objs {
		item := objs[ix]
		switch u := item.(type) {
		case *runtime.Unknown:
			var err error
			// decode runtime.Unknown to runtime.Unstructured for sorting.
			// we don't actually want the internal versions of known types.
			if objs[ix], _, err = decoder.Decode(u.Raw, nil, &unstructured.Unstructured{}); err != nil {
				return nil, err
			}
		}
	}

	field, err := RelaxedJSONPathExpression(fieldInput)
	if err != nil {
		return nil, err
	}

	parser := jsonpath.New("sorting").AllowMissingKeys(true)
	if err := parser.Parse(field); err != nil {
		return nil, err
	}

	// We don't do any model validation here, so we traverse all objects to be sorted
	// and, if the field is valid to at least one of them, we consider it to be a
	// valid field; otherwise error out.
	// Note that this requires empty fields to be considered later, when sorting.
	var fieldFoundOnce bool
	for _, obj := range objs {
		values, err := findJSONPathResults(parser, obj)
		if err != nil {
			return nil, err
		}
		if len(values) > 0 && len(values[0]) > 0 {
			fieldFoundOnce = true
			break
		}
	}
	if !fieldFoundOnce {
		return nil, fmt.Errorf("couldn't find any field with path %q in the list of objects", field)
	}

	sorter := NewRuntimeSort(field, objs)
	sort.Sort(sorter)
	return sorter, nil
}

// RuntimeSort is an implementation of the golang sort interface that knows how to sort
// lists of runtime.Object
type RuntimeSort struct {
	field        string
	objs         []runtime.Object
	origPosition []int
}

func NewRuntimeSort(field string, objs []runtime.Object) *RuntimeSort {
	sorter := &RuntimeSort{field: field, objs: objs, origPosition: make([]int, len(objs))}
	for ix := range objs {
		sorter.origPosition[ix] = ix
	}
	return sorter
}

func (r *RuntimeSort) Len() int {
	return len(r.objs)
}

func (r *RuntimeSort) Swap(i, j int) {
	r.objs[i], r.objs[j] = r.objs[j], r.objs[i]
	r.origPosition[i], r.origPosition[j] = r.origPosition[j], r.origPosition[i]
}

func isLess(i, j reflect.Value) (bool, error) {
	switch i.Kind() {
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
		return i.Int() < j.Int(), nil
	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
		return i.Uint() < j.Uint(), nil
	case reflect.Float32, reflect.Float64:
		return i.Float() < j.Float(), nil
	case reflect.String:
		return sortorder.NaturalLess(i.String(), j.String()), nil
	case reflect.Ptr:
		return isLess(i.Elem(), j.Elem())
	case reflect.Struct:
		// sort metav1.Time
		in := i.Interface()
		if t, ok := in.(metav1.Time); ok {
			time := j.Interface().(metav1.Time)
			return t.Before(&time), nil
		}
		// fallback to the fields comparison
		for idx := 0; idx < i.NumField(); idx++ {
			less, err := isLess(i.Field(idx), j.Field(idx))
			if err != nil || !less {
				return less, err
			}
		}
		return true, nil
	case reflect.Array, reflect.Slice:
		// note: the length of i and j may be different
		for idx := 0; idx < integer.IntMin(i.Len(), j.Len()); idx++ {
			less, err := isLess(i.Index(idx), j.Index(idx))
			if err != nil || !less {
				return less, err
			}
		}
		return true, nil

	case reflect.Interface:
		switch itype := i.Interface().(type) {
		case uint8:
			if jtype, ok := j.Interface().(uint8); ok {
				return itype < jtype, nil
			}
		case uint16:
			if jtype, ok := j.Interface().(uint16); ok {
				return itype < jtype, nil
			}
		case uint32:
			if jtype, ok := j.Interface().(uint32); ok {
				return itype < jtype, nil
			}
		case uint64:
			if jtype, ok := j.Interface().(uint64); ok {
				return itype < jtype, nil
			}
		case int8:
			if jtype, ok := j.Interface().(int8); ok {
				return itype < jtype, nil
			}
		case int16:
			if jtype, ok := j.Interface().(int16); ok {
				return itype < jtype, nil
			}
		case int32:
			if jtype, ok := j.Interface().(int32); ok {
				return itype < jtype, nil
			}
		case int64:
			if jtype, ok := j.Interface().(int64); ok {
				return itype < jtype, nil
			}
		case uint:
			if jtype, ok := j.Interface().(uint); ok {
				return itype < jtype, nil
			}
		case int:
			if jtype, ok := j.Interface().(int); ok {
				return itype < jtype, nil
			}
		case float32:
			if jtype, ok := j.Interface().(float32); ok {
				return itype < jtype, nil
			}
		case float64:
			if jtype, ok := j.Interface().(float64); ok {
				return itype < jtype, nil
			}
		case string:
			if jtype, ok := j.Interface().(string); ok {
				return sortorder.NaturalLess(itype, jtype), nil
			}
		default:
			return false, fmt.Errorf("unsortable type: %T", itype)
		}
		return false, fmt.Errorf("unsortable interface: %v", i.Kind())

	default:
		return false, fmt.Errorf("unsortable type: %v", i.Kind())
	}
}

func (r *RuntimeSort) Less(i, j int) bool {
	iObj := r.objs[i]
	jObj := r.objs[j]

	var iValues [][]reflect.Value
	var jValues [][]reflect.Value
	var err error

	parser := jsonpath.New("sorting").AllowMissingKeys(true)
	err = parser.Parse(r.field)
	if err != nil {
		panic(err)
	}

	iValues, err = findJSONPathResults(parser, iObj)
	if err != nil {
		klog.Fatalf("Failed to get i values for %#v using %s (%#v)", iObj, r.field, err)
	}

	jValues, err = findJSONPathResults(parser, jObj)
	if err != nil {
		klog.Fatalf("Failed to get j values for %#v using %s (%v)", jObj, r.field, err)
	}

	if len(iValues) == 0 || len(iValues[0]) == 0 {
		return true
	}
	if len(jValues) == 0 || len(jValues[0]) == 0 {
		return false
	}
	iField := iValues[0][0]
	jField := jValues[0][0]

	less, err := isLess(iField, jField)
	if err != nil {
		klog.Fatalf("Field %s in %T is an unsortable type: %s, err: %v", r.field, iObj, iField.Kind().String(), err)
	}
	return less
}

// OriginalPosition returns the starting (original) position of a particular index.
// e.g. If OriginalPosition(0) returns 5 than the
// the item currently at position 0 was at position 5 in the original unsorted array.
func (r *RuntimeSort) OriginalPosition(ix int) int {
	if ix < 0 || ix > len(r.origPosition) {
		return -1
	}
	return r.origPosition[ix]
}

type TableSorter struct {
	field      string
	obj        *metav1beta1.Table
	parsedRows [][][]reflect.Value
}

func (t *TableSorter) Len() int {
	return len(t.obj.Rows)
}

func (t *TableSorter) Swap(i, j int) {
	t.obj.Rows[i], t.obj.Rows[j] = t.obj.Rows[j], t.obj.Rows[i]
	t.parsedRows[i], t.parsedRows[j] = t.parsedRows[j], t.parsedRows[i]
}

func (t *TableSorter) Less(i, j int) bool {
	iValues := t.parsedRows[i]
	jValues := t.parsedRows[j]

	if len(iValues) == 0 || len(iValues[0]) == 0 {
		return true
	}
	if len(jValues) == 0 || len(jValues[0]) == 0 {
		return false
	}

	iField := iValues[0][0]
	jField := jValues[0][0]

	less, err := isLess(iField, jField)
	if err != nil {
		klog.Fatalf("Field %s in %T is an unsortable type: %s, err: %v", t.field, t.parsedRows, iField.Kind().String(), err)
	}
	return less
}

func (t *TableSorter) Sort() error {
	sort.Sort(t)
	return nil
}

func NewTableSorter(table *metav1beta1.Table, field string) (*TableSorter, error) {
	var parsedRows [][][]reflect.Value

	parser := jsonpath.New("sorting").AllowMissingKeys(true)
	err := parser.Parse(field)
	if err != nil {
		return nil, fmt.Errorf("sorting error: %v", err)
	}

	fieldFoundOnce := false
	for i := range table.Rows {
		parsedRow, err := findJSONPathResults(parser, table.Rows[i].Object.Object)
		if err != nil {
			return nil, fmt.Errorf("Failed to get values for %#v using %s (%#v)", parsedRow, field, err)
		}
		parsedRows = append(parsedRows, parsedRow)
		if len(parsedRow) > 0 && len(parsedRow[0]) > 0 {
			fieldFoundOnce = true
		}
	}

	if len(table.Rows) > 0 && !fieldFoundOnce {
		return nil, fmt.Errorf("couldn't find any field with path %q in the list of objects", field)
	}

	return &TableSorter{
		obj:        table,
		field:      field,
		parsedRows: parsedRows,
	}, nil
}
func findJSONPathResults(parser *jsonpath.JSONPath, from runtime.Object) ([][]reflect.Value, error) {
	if unstructuredObj, ok := from.(*unstructured.Unstructured); ok {
		return parser.FindResults(unstructuredObj.Object)
	}
	return parser.FindResults(reflect.ValueOf(from).Elem().Interface())
}
<|endoftext|>


<commit_before>//go:build linux || solaris || darwin || freebsd
// +build linux solaris darwin freebsd

package lockfile

import (
	"bytes"
	"fmt"
	"os"
	"path/filepath"
	"sync"
	"time"

	"github.com/containers/storage/pkg/stringid"
	"github.com/containers/storage/pkg/system"
	"github.com/pkg/errors"
	"golang.org/x/sys/unix"
)

type lockfile struct {
	// rwMutex serializes concurrent reader-writer acquisitions in the same process space
	rwMutex *sync.RWMutex
	// stateMutex is used to synchronize concurrent accesses to the state below
	stateMutex *sync.Mutex
	counter    int64
	file       string
	fd         uintptr
	lw         []byte // "last writer"-unique value valid as of the last .Touch() or .Modified(), generated by newLastWriterID()
	locktype   int16
	locked     bool
	ro         bool
	recursive  bool
}

// newLastWriterID returns a new "last writer" ID.
// The value must be different on every call, and also differ from values
// generated by other processes.
func newLastWriterID() []byte {
	return []byte(stringid.GenerateRandomID())
}

// openLock opens the file at path and returns the corresponding file
// descriptor.  Note that the path is opened read-only when ro is set.  If ro
// is unset, openLock will open the path read-write and create the file if
// necessary.
func openLock(path string, ro bool) (fd int, err error) {
	if ro {
		fd, err = unix.Open(path, os.O_RDONLY|unix.O_CLOEXEC|os.O_CREATE, 0)
	} else {
		fd, err = unix.Open(path,
			os.O_RDWR|unix.O_CLOEXEC|os.O_CREATE,
			unix.S_IRUSR|unix.S_IWUSR|unix.S_IRGRP|unix.S_IROTH,
		)
	}

	if err == nil {
		return
	}

	// the directory of the lockfile seems to be removed, try to create it
	if os.IsNotExist(err) {
		if err := os.MkdirAll(filepath.Dir(path), 0700); err != nil {
			return fd, errors.Wrap(err, "creating locker directory")
		}

		return openLock(path, ro)
	}

	return
}

// createLockerForPath returns a Locker object, possibly (depending on the platform)
// working inter-process and associated with the specified path.
//
// This function will be called at most once for each path value within a single process.
//
// If ro, the lock is a read-write lock and the returned Locker should correspond to the
// “lock for reading” (shared) operation; otherwise, the lock is either an exclusive lock,
// or a read-write lock and Locker should correspond to the “lock for writing” (exclusive) operation.
//
// WARNING:
// - The lock may or MAY NOT be inter-process.
// - There may or MAY NOT be an actual object on the filesystem created for the specified path.
// - Even if ro, the lock MAY be exclusive.
func createLockerForPath(path string, ro bool) (Locker, error) {
	// Check if we can open the lock.
	fd, err := openLock(path, ro)
	if err != nil {
		return nil, errors.Wrapf(err, "error opening %q", path)
	}
	unix.Close(fd)

	locktype := unix.F_WRLCK
	if ro {
		locktype = unix.F_RDLCK
	}
	return &lockfile{
		stateMutex: &sync.Mutex{},
		rwMutex:    &sync.RWMutex{},
		file:       path,
		lw:         newLastWriterID(),
		locktype:   int16(locktype),
		locked:     false,
		ro:         ro}, nil
}

// lock locks the lockfile via FCTNL(2) based on the specified type and
// command.
func (l *lockfile) lock(lType int16, recursive bool) {
	lk := unix.Flock_t{
		Type:   lType,
		Whence: int16(os.SEEK_SET),
		Start:  0,
		Len:    0,
	}
	switch lType {
	case unix.F_RDLCK:
		l.rwMutex.RLock()
	case unix.F_WRLCK:
		if recursive {
			// NOTE: that's okay as recursive is only set in RecursiveLock(), so
			// there's no need to protect against hypothetical RDLCK cases.
			l.rwMutex.RLock()
		} else {
			l.rwMutex.Lock()
		}
	default:
		panic(fmt.Sprintf("attempted to acquire a file lock of unrecognized type %d", lType))
	}
	l.stateMutex.Lock()
	defer l.stateMutex.Unlock()
	if l.counter == 0 {
		// If we're the first reference on the lock, we need to open the file again.
		fd, err := openLock(l.file, l.ro)
		if err != nil {
			panic(fmt.Sprintf("error opening %q: %v", l.file, err))
		}
		l.fd = uintptr(fd)

		// Optimization: only use the (expensive) fcntl syscall when
		// the counter is 0.  In this case, we're either the first
		// reader lock or a writer lock.
		for unix.FcntlFlock(l.fd, unix.F_SETLKW, &lk) != nil {
			time.Sleep(10 * time.Millisecond)
		}
	}
	l.locktype = lType
	l.locked = true
	l.recursive = recursive
	l.counter++
}

// Lock locks the lockfile as a writer.  Panic if the lock is a read-only one.
func (l *lockfile) Lock() {
	if l.ro {
		panic("can't take write lock on read-only lock file")
	} else {
		l.lock(unix.F_WRLCK, false)
	}
}

// RecursiveLock locks the lockfile as a writer but allows for recursive
// acquisitions within the same process space.  Note that RLock() will be called
// if it's a lockTypReader lock.
func (l *lockfile) RecursiveLock() {
	if l.ro {
		l.RLock()
	} else {
		l.lock(unix.F_WRLCK, true)
	}
}

// LockRead locks the lockfile as a reader.
func (l *lockfile) RLock() {
	l.lock(unix.F_RDLCK, false)
}

// Unlock unlocks the lockfile.
func (l *lockfile) Unlock() {
	l.stateMutex.Lock()
	if l.locked == false {
		// Panic when unlocking an unlocked lock.  That's a violation
		// of the lock semantics and will reveal such.
		panic("calling Unlock on unlocked lock")
	}
	l.counter--
	if l.counter < 0 {
		// Panic when the counter is negative.  There is no way we can
		// recover from a corrupted lock and we need to protect the
		// storage from corruption.
		panic(fmt.Sprintf("lock %q has been unlocked too often", l.file))
	}
	if l.counter == 0 {
		// We should only release the lock when the counter is 0 to
		// avoid releasing read-locks too early; a given process may
		// acquire a read lock multiple times.
		l.locked = false
		// Close the file descriptor on the last unlock, releasing the
		// file lock.
		unix.Close(int(l.fd))
	}
	if l.locktype == unix.F_RDLCK || l.recursive {
		l.rwMutex.RUnlock()
	} else {
		l.rwMutex.Unlock()
	}
	l.stateMutex.Unlock()
}

// Locked checks if lockfile is locked for writing by a thread in this process.
func (l *lockfile) Locked() bool {
	l.stateMutex.Lock()
	defer l.stateMutex.Unlock()
	return l.locked && (l.locktype == unix.F_WRLCK)
}

// Touch updates the lock file with the UID of the user.
func (l *lockfile) Touch() error {
	l.stateMutex.Lock()
	if !l.locked || (l.locktype != unix.F_WRLCK) {
		panic("attempted to update last-writer in lockfile without the write lock")
	}
	defer l.stateMutex.Unlock()
	l.lw = newLastWriterID()
	n, err := unix.Pwrite(int(l.fd), l.lw, 0)
	if err != nil {
		return err
	}
	if n != len(l.lw) {
		return unix.ENOSPC
	}
	return nil
}

// Modified indicates if the lockfile has been updated since the last time it
// was loaded.
func (l *lockfile) Modified() (bool, error) {
	l.stateMutex.Lock()
	if !l.locked {
		panic("attempted to check last-writer in lockfile without locking it first")
	}
	defer l.stateMutex.Unlock()
	currentLW := make([]byte, len(l.lw))
	n, err := unix.Pread(int(l.fd), currentLW, 0)
	if err != nil {
		return true, err
	}
	if n != len(l.lw) {
		return true, nil
	}
	oldLW := l.lw
	l.lw = currentLW
	return !bytes.Equal(currentLW, oldLW), nil
}

// IsReadWriteLock indicates if the lock file is a read-write lock.
func (l *lockfile) IsReadWrite() bool {
	return !l.ro
}

// TouchedSince indicates if the lock file has been touched since the specified time
func (l *lockfile) TouchedSince(when time.Time) bool {
	st, err := system.Fstat(int(l.fd))
	if err != nil {
		return true
	}
	mtim := st.Mtim()
	touched := time.Unix(mtim.Unix())
	return when.Before(touched)
}
<commit_msg>Make sure the values returned by newLastWriterID() are unique<commit_after>//go:build linux || solaris || darwin || freebsd
// +build linux solaris darwin freebsd

package lockfile

import (
	"bytes"
	cryptorand "crypto/rand"
	"encoding/binary"
	"fmt"
	"os"
	"path/filepath"
	"sync"
	"sync/atomic"
	"time"

	"github.com/containers/storage/pkg/system"
	"github.com/pkg/errors"
	"golang.org/x/sys/unix"
)

type lockfile struct {
	// rwMutex serializes concurrent reader-writer acquisitions in the same process space
	rwMutex *sync.RWMutex
	// stateMutex is used to synchronize concurrent accesses to the state below
	stateMutex *sync.Mutex
	counter    int64
	file       string
	fd         uintptr
	lw         []byte // "last writer"-unique value valid as of the last .Touch() or .Modified(), generated by newLastWriterID()
	locktype   int16
	locked     bool
	ro         bool
	recursive  bool
}

const lastWriterIDSize = 64    // This must be the same as len(stringid.GenerateRandomID)
var lastWriterIDCounter uint64 // Private state for newLastWriterID

// newLastWriterID returns a new "last writer" ID.
// The value must be different on every call, and also differ from values
// generated by other processes.
func newLastWriterID() []byte {
	// The ID is (PID, time, per-process counter, random)
	// PID + time represents both a unique process across reboots,
	// and a specific time within the process; the per-process counter
	// is an extra safeguard for in-process concurrency.
	// The random part disambiguates across process namespaces
	// (where PID values might collide), serves as a general-purpose
	// extra safety, _and_ is used to pad the output to lastWriterIDSize,
	// because other versions of this code exist and they don't work
	// efficiently if the size of the value changes.
	pid := os.Getpid()
	tm := time.Now().UnixNano()
	counter := atomic.AddUint64(&lastWriterIDCounter, 1)

	res := make([]byte, lastWriterIDSize)
	binary.LittleEndian.PutUint64(res[0:8], uint64(tm))
	binary.LittleEndian.PutUint64(res[8:16], counter)
	binary.LittleEndian.PutUint32(res[16:20], uint32(pid))
	if n, err := cryptorand.Read(res[20:lastWriterIDSize]); err != nil || n != lastWriterIDSize-20 {
		panic(err) // This shouldn't happen
	}

	return res
}

// openLock opens the file at path and returns the corresponding file
// descriptor.  Note that the path is opened read-only when ro is set.  If ro
// is unset, openLock will open the path read-write and create the file if
// necessary.
func openLock(path string, ro bool) (fd int, err error) {
	if ro {
		fd, err = unix.Open(path, os.O_RDONLY|unix.O_CLOEXEC|os.O_CREATE, 0)
	} else {
		fd, err = unix.Open(path,
			os.O_RDWR|unix.O_CLOEXEC|os.O_CREATE,
			unix.S_IRUSR|unix.S_IWUSR|unix.S_IRGRP|unix.S_IROTH,
		)
	}

	if err == nil {
		return
	}

	// the directory of the lockfile seems to be removed, try to create it
	if os.IsNotExist(err) {
		if err := os.MkdirAll(filepath.Dir(path), 0700); err != nil {
			return fd, errors.Wrap(err, "creating locker directory")
		}

		return openLock(path, ro)
	}

	return
}

// createLockerForPath returns a Locker object, possibly (depending on the platform)
// working inter-process and associated with the specified path.
//
// This function will be called at most once for each path value within a single process.
//
// If ro, the lock is a read-write lock and the returned Locker should correspond to the
// “lock for reading” (shared) operation; otherwise, the lock is either an exclusive lock,
// or a read-write lock and Locker should correspond to the “lock for writing” (exclusive) operation.
//
// WARNING:
// - The lock may or MAY NOT be inter-process.
// - There may or MAY NOT be an actual object on the filesystem created for the specified path.
// - Even if ro, the lock MAY be exclusive.
func createLockerForPath(path string, ro bool) (Locker, error) {
	// Check if we can open the lock.
	fd, err := openLock(path, ro)
	if err != nil {
		return nil, errors.Wrapf(err, "error opening %q", path)
	}
	unix.Close(fd)

	locktype := unix.F_WRLCK
	if ro {
		locktype = unix.F_RDLCK
	}
	return &lockfile{
		stateMutex: &sync.Mutex{},
		rwMutex:    &sync.RWMutex{},
		file:       path,
		lw:         newLastWriterID(),
		locktype:   int16(locktype),
		locked:     false,
		ro:         ro}, nil
}

// lock locks the lockfile via FCTNL(2) based on the specified type and
// command.
func (l *lockfile) lock(lType int16, recursive bool) {
	lk := unix.Flock_t{
		Type:   lType,
		Whence: int16(os.SEEK_SET),
		Start:  0,
		Len:    0,
	}
	switch lType {
	case unix.F_RDLCK:
		l.rwMutex.RLock()
	case unix.F_WRLCK:
		if recursive {
			// NOTE: that's okay as recursive is only set in RecursiveLock(), so
			// there's no need to protect against hypothetical RDLCK cases.
			l.rwMutex.RLock()
		} else {
			l.rwMutex.Lock()
		}
	default:
		panic(fmt.Sprintf("attempted to acquire a file lock of unrecognized type %d", lType))
	}
	l.stateMutex.Lock()
	defer l.stateMutex.Unlock()
	if l.counter == 0 {
		// If we're the first reference on the lock, we need to open the file again.
		fd, err := openLock(l.file, l.ro)
		if err != nil {
			panic(fmt.Sprintf("error opening %q: %v", l.file, err))
		}
		l.fd = uintptr(fd)

		// Optimization: only use the (expensive) fcntl syscall when
		// the counter is 0.  In this case, we're either the first
		// reader lock or a writer lock.
		for unix.FcntlFlock(l.fd, unix.F_SETLKW, &lk) != nil {
			time.Sleep(10 * time.Millisecond)
		}
	}
	l.locktype = lType
	l.locked = true
	l.recursive = recursive
	l.counter++
}

// Lock locks the lockfile as a writer.  Panic if the lock is a read-only one.
func (l *lockfile) Lock() {
	if l.ro {
		panic("can't take write lock on read-only lock file")
	} else {
		l.lock(unix.F_WRLCK, false)
	}
}

// RecursiveLock locks the lockfile as a writer but allows for recursive
// acquisitions within the same process space.  Note that RLock() will be called
// if it's a lockTypReader lock.
func (l *lockfile) RecursiveLock() {
	if l.ro {
		l.RLock()
	} else {
		l.lock(unix.F_WRLCK, true)
	}
}

// LockRead locks the lockfile as a reader.
func (l *lockfile) RLock() {
	l.lock(unix.F_RDLCK, false)
}

// Unlock unlocks the lockfile.
func (l *lockfile) Unlock() {
	l.stateMutex.Lock()
	if l.locked == false {
		// Panic when unlocking an unlocked lock.  That's a violation
		// of the lock semantics and will reveal such.
		panic("calling Unlock on unlocked lock")
	}
	l.counter--
	if l.counter < 0 {
		// Panic when the counter is negative.  There is no way we can
		// recover from a corrupted lock and we need to protect the
		// storage from corruption.
		panic(fmt.Sprintf("lock %q has been unlocked too often", l.file))
	}
	if l.counter == 0 {
		// We should only release the lock when the counter is 0 to
		// avoid releasing read-locks too early; a given process may
		// acquire a read lock multiple times.
		l.locked = false
		// Close the file descriptor on the last unlock, releasing the
		// file lock.
		unix.Close(int(l.fd))
	}
	if l.locktype == unix.F_RDLCK || l.recursive {
		l.rwMutex.RUnlock()
	} else {
		l.rwMutex.Unlock()
	}
	l.stateMutex.Unlock()
}

// Locked checks if lockfile is locked for writing by a thread in this process.
func (l *lockfile) Locked() bool {
	l.stateMutex.Lock()
	defer l.stateMutex.Unlock()
	return l.locked && (l.locktype == unix.F_WRLCK)
}

// Touch updates the lock file with the UID of the user.
func (l *lockfile) Touch() error {
	l.stateMutex.Lock()
	if !l.locked || (l.locktype != unix.F_WRLCK) {
		panic("attempted to update last-writer in lockfile without the write lock")
	}
	defer l.stateMutex.Unlock()
	l.lw = newLastWriterID()
	n, err := unix.Pwrite(int(l.fd), l.lw, 0)
	if err != nil {
		return err
	}
	if n != len(l.lw) {
		return unix.ENOSPC
	}
	return nil
}

// Modified indicates if the lockfile has been updated since the last time it
// was loaded.
func (l *lockfile) Modified() (bool, error) {
	l.stateMutex.Lock()
	if !l.locked {
		panic("attempted to check last-writer in lockfile without locking it first")
	}
	defer l.stateMutex.Unlock()
	currentLW := make([]byte, len(l.lw))
	n, err := unix.Pread(int(l.fd), currentLW, 0)
	if err != nil {
		return true, err
	}
	if n != len(l.lw) {
		return true, nil
	}
	oldLW := l.lw
	l.lw = currentLW
	return !bytes.Equal(currentLW, oldLW), nil
}

// IsReadWriteLock indicates if the lock file is a read-write lock.
func (l *lockfile) IsReadWrite() bool {
	return !l.ro
}

// TouchedSince indicates if the lock file has been touched since the specified time
func (l *lockfile) TouchedSince(when time.Time) bool {
	st, err := system.Fstat(int(l.fd))
	if err != nil {
		return true
	}
	mtim := st.Mtim()
	touched := time.Unix(mtim.Unix())
	return when.Before(touched)
}
<|endoftext|>


<commit_before>/*
Copyright 2020 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 machine

import (
	"encoding/json"
	"fmt"
	"net"
	"os/exec"
	"path"
	"path/filepath"
	"strconv"
	"strings"
	"time"

	"github.com/docker/machine/libmachine"
	"github.com/docker/machine/libmachine/drivers"
	"github.com/docker/machine/libmachine/engine"
	"github.com/docker/machine/libmachine/host"
	"github.com/golang/glog"
	"github.com/juju/mutex"
	"github.com/pkg/errors"
	"github.com/spf13/viper"
	"k8s.io/minikube/pkg/minikube/command"
	"k8s.io/minikube/pkg/minikube/config"
	"k8s.io/minikube/pkg/minikube/constants"
	"k8s.io/minikube/pkg/minikube/driver"
	"k8s.io/minikube/pkg/minikube/exit"
	"k8s.io/minikube/pkg/minikube/localpath"
	"k8s.io/minikube/pkg/minikube/out"
	"k8s.io/minikube/pkg/minikube/out/register"
	"k8s.io/minikube/pkg/minikube/proxy"
	"k8s.io/minikube/pkg/minikube/registry"
	"k8s.io/minikube/pkg/minikube/vmpath"
	"k8s.io/minikube/pkg/util/lock"
)

var (
	// requiredDirectories are directories to create on the host during setup
	requiredDirectories = []string{
		vmpath.GuestAddonsDir,
		vmpath.GuestManifestsDir,
		vmpath.GuestEphemeralDir,
		vmpath.GuestPersistentDir,
		vmpath.GuestKubernetesCertsDir,
		path.Join(vmpath.GuestPersistentDir, "images"),
		path.Join(vmpath.GuestPersistentDir, "binaries"),
		vmpath.GuestGvisorDir,
		vmpath.GuestCertAuthDir,
		vmpath.GuestCertStoreDir,
	}
)

// StartHost starts a host VM.
func StartHost(api libmachine.API, cfg *config.ClusterConfig, n *config.Node) (*host.Host, bool, error) {
	machineName := driver.MachineName(*cfg, *n)

	// Prevent machine-driver boot races, as well as our own certificate race
	releaser, err := acquireMachinesLock(machineName, cfg.Driver)
	if err != nil {
		return nil, false, errors.Wrap(err, "boot lock")
	}
	start := time.Now()
	defer func() {
		glog.Infof("releasing machines lock for %q, held for %s", machineName, time.Since(start))
		releaser.Release()
	}()

	exists, err := api.Exists(machineName)
	if err != nil {
		return nil, false, errors.Wrapf(err, "exists: %s", machineName)
	}
	if !exists {
		glog.Infof("Provisioning new machine with config: %+v %+v", cfg, n)
		h, err := createHost(api, cfg, n)
		return h, exists, err
	}
	glog.Infoln("Skipping create...Using existing machine configuration")
	h, err := fixHost(api, cfg, n)
	return h, exists, err
}

// engineOptions returns docker engine options for the dockerd running inside minikube
func engineOptions(cfg config.ClusterConfig) *engine.Options {
	// get docker env from user's proxy settings
	dockerEnv := proxy.SetDockerEnv()
	// get docker env from user specifiec config
	dockerEnv = append(dockerEnv, cfg.DockerEnv...)

	// remove duplicates
	seen := map[string]bool{}
	uniqueEnvs := []string{}
	for e := range dockerEnv {
		if !seen[dockerEnv[e]] {
			seen[dockerEnv[e]] = true
			uniqueEnvs = append(uniqueEnvs, dockerEnv[e])
		}
	}

	o := engine.Options{
		Env:              uniqueEnvs,
		InsecureRegistry: append([]string{constants.DefaultServiceCIDR}, cfg.InsecureRegistry...),
		RegistryMirror:   cfg.RegistryMirror,
		ArbitraryFlags:   cfg.DockerOpt,
		InstallURL:       drivers.DefaultEngineInstallURL,
	}
	return &o
}

func createHost(api libmachine.API, cfg *config.ClusterConfig, n *config.Node) (*host.Host, error) {
	glog.Infof("createHost starting for %q (driver=%q)", n.Name, cfg.Driver)
	start := time.Now()
	defer func() {
		glog.Infof("duration metric: createHost completed in %s", time.Since(start))
	}()

	if cfg.Driver == driver.VMwareFusion && viper.GetBool(config.ShowDriverDeprecationNotification) {
		out.WarningT(`The vmwarefusion driver is deprecated and support for it will be removed in a future release.
			Please consider switching to the new vmware unified driver, which is intended to replace the vmwarefusion driver.
			See https://minikube.sigs.k8s.io/docs/reference/drivers/vmware/ for more information.
			To disable this message, run [minikube config set ShowDriverDeprecationNotification false]`)
	}
	showHostInfo(*cfg)
	def := registry.Driver(cfg.Driver)
	if def.Empty() {
		return nil, fmt.Errorf("unsupported/missing driver: %s", cfg.Driver)
	}
	dd, err := def.Config(*cfg, *n)
	if err != nil {
		return nil, errors.Wrap(err, "config")
	}
	data, err := json.Marshal(dd)
	if err != nil {
		return nil, errors.Wrap(err, "marshal")
	}

	h, err := api.NewHost(cfg.Driver, data)
	if err != nil {
		return nil, errors.Wrap(err, "new host")
	}
	defer postStartValidations(h, cfg.Driver)

	h.HostOptions.AuthOptions.CertDir = localpath.MiniPath()
	h.HostOptions.AuthOptions.StorePath = localpath.MiniPath()
	h.HostOptions.EngineOptions = engineOptions(*cfg)

	cstart := time.Now()
	glog.Infof("libmachine.API.Create for %q (driver=%q)", cfg.Name, cfg.Driver)

	if cfg.StartHostTimeout == 0 {
		cfg.StartHostTimeout = 6 * time.Minute
	}
	if err := timedCreateHost(h, api, cfg.StartHostTimeout); err != nil {
		return nil, errors.Wrap(err, "creating host")
	}
	glog.Infof("duration metric: libmachine.API.Create for %q took %s", cfg.Name, time.Since(cstart))

	if err := postStartSetup(h, *cfg); err != nil {
		return h, errors.Wrap(err, "post-start")
	}

	if err := saveHost(api, h, cfg, n); err != nil {
		return h, err
	}
	return h, nil
}

func timedCreateHost(h *host.Host, api libmachine.API, t time.Duration) error {
	timeout := make(chan bool, 1)
	go func() {
		time.Sleep(t)
		timeout <- true
	}()

	createFinished := make(chan bool, 1)
	var err error
	go func() {
		err = api.Create(h)
		createFinished <- true
	}()

	select {
	case <-createFinished:
		if err != nil {
			// Wait for all the logs to reach the client
			time.Sleep(2 * time.Second)
			return errors.Wrap(err, "create")
		}
		return nil
	case <-timeout:
		return fmt.Errorf("create host timed out in %f seconds", t.Seconds())
	}
}

// postStartValidations are validations against the host after it is created
// TODO: Add validations for VM drivers as well, see issue #9035
func postStartValidations(h *host.Host, drvName string) {
	if !driver.IsKIC(drvName) {
		return
	}
	// make sure /var isn't full, otherwise warn
	output, err := h.RunSSHCommand("df -h /var | awk 'NR==2{print $5}'")
	if err != nil {
		glog.Warningf("error running df -h /var: %v", err)
	}
	output = strings.Trim(output, "\n")
	percentageFull, err := strconv.Atoi(output[:len(output)-1])
	if err != nil {
		glog.Warningf("error getting percentage of /var that is free: %v", err)
	}
	if percentageFull >= 99 {
		exit.WithError("", fmt.Errorf("docker daemon out of memory. No space left on device"))
	}

	if percentageFull > 80 {
		out.WarningT("The docker daemon is almost out of memory, run 'docker system prune' to free up space")
	}
}

// postStart are functions shared between startHost and fixHost
func postStartSetup(h *host.Host, mc config.ClusterConfig) error {
	glog.Infof("post-start starting for %q (driver=%q)", h.Name, h.DriverName)
	start := time.Now()
	defer func() {
		glog.Infof("post-start completed in %s", time.Since(start))
	}()

	if driver.IsMock(h.DriverName) {
		return nil
	}

	glog.Infof("creating required directories: %v", requiredDirectories)

	r, err := CommandRunner(h)
	if err != nil {
		return errors.Wrap(err, "command runner")
	}

	args := append([]string{"mkdir", "-p"}, requiredDirectories...)
	if _, err := r.RunCmd(exec.Command("sudo", args...)); err != nil {
		return errors.Wrapf(err, "sudo mkdir (%s)", h.DriverName)
	}

	if driver.BareMetal(mc.Driver) {
		showLocalOsRelease()
	}
	if driver.IsVM(mc.Driver) || driver.IsKIC(mc.Driver) {
		logRemoteOsRelease(r)
	}
	return syncLocalAssets(r)
}

// acquireMachinesLock protects against code that is not parallel-safe (libmachine, cert setup)
func acquireMachinesLock(name string, drv string) (mutex.Releaser, error) {
	spec := lock.PathMutexSpec(filepath.Join(localpath.MiniPath(), "machines"))
	// NOTE: Provisioning generally completes within 60 seconds
	// however in para;lel integration testing it might take longe
	spec.Timeout = 13 * time.Minute
	if driver.IsKIC(drv) {
		spec.Timeout = 10 * time.Minute

	}

	glog.Infof("acquiring machines lock for %s: %+v", name, spec)
	start := time.Now()
	r, err := mutex.Acquire(spec)
	if err == nil {
		glog.Infof("acquired machines lock for %q in %s", name, time.Since(start))
	}
	return r, err
}

// showHostInfo shows host information
func showHostInfo(cfg config.ClusterConfig) {
	machineType := driver.MachineType(cfg.Driver)
	if driver.BareMetal(cfg.Driver) {
		info, cpuErr, memErr, DiskErr := CachedHostInfo()
		if cpuErr == nil && memErr == nil && DiskErr == nil {
			register.Reg.SetStep(register.RunningLocalhost)
			out.T(out.StartingNone, "Running on localhost (CPUs={{.number_of_cpus}}, Memory={{.memory_size}}MB, Disk={{.disk_size}}MB) ...", out.V{"number_of_cpus": info.CPUs, "memory_size": info.Memory, "disk_size": info.DiskSize})
		}
		return
	}
	if driver.IsKIC(cfg.Driver) { // TODO:medyagh add free disk space on docker machine
		register.Reg.SetStep(register.CreatingContainer)
		out.T(out.StartingVM, "Creating {{.driver_name}} {{.machine_type}} (CPUs={{.number_of_cpus}}, Memory={{.memory_size}}MB) ...", out.V{"driver_name": cfg.Driver, "number_of_cpus": cfg.CPUs, "memory_size": cfg.Memory, "machine_type": machineType})
		return
	}
	register.Reg.SetStep(register.CreatingVM)
	out.T(out.StartingVM, "Creating {{.driver_name}} {{.machine_type}} (CPUs={{.number_of_cpus}}, Memory={{.memory_size}}MB, Disk={{.disk_size}}MB) ...", out.V{"driver_name": cfg.Driver, "number_of_cpus": cfg.CPUs, "memory_size": cfg.Memory, "disk_size": cfg.DiskSize, "machine_type": machineType})
}

// AddHostAlias makes fine adjustments to pod resources that aren't possible via kubeadm config.
func AddHostAlias(c command.Runner, name string, ip net.IP) error {
	record := fmt.Sprintf("%s\t%s", ip, name)
	if _, err := c.RunCmd(exec.Command("grep", record+"$", "/etc/hosts")); err == nil {
		return nil
	}

	script := fmt.Sprintf(`{ grep -v '\t%s$' /etc/hosts; echo "%s"; } > /tmp/h.$$; sudo cp /tmp/h.$$ /etc/hosts`, name, record)
	if _, err := c.RunCmd(exec.Command("/bin/bash", "-c", script)); err != nil {
		return errors.Wrap(err, "hosts update")
	}
	return nil
}
<commit_msg>spell<commit_after>/*
Copyright 2020 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 machine

import (
	"encoding/json"
	"fmt"
	"net"
	"os/exec"
	"path"
	"path/filepath"
	"strconv"
	"strings"
	"time"

	"github.com/docker/machine/libmachine"
	"github.com/docker/machine/libmachine/drivers"
	"github.com/docker/machine/libmachine/engine"
	"github.com/docker/machine/libmachine/host"
	"github.com/golang/glog"
	"github.com/juju/mutex"
	"github.com/pkg/errors"
	"github.com/spf13/viper"
	"k8s.io/minikube/pkg/minikube/command"
	"k8s.io/minikube/pkg/minikube/config"
	"k8s.io/minikube/pkg/minikube/constants"
	"k8s.io/minikube/pkg/minikube/driver"
	"k8s.io/minikube/pkg/minikube/exit"
	"k8s.io/minikube/pkg/minikube/localpath"
	"k8s.io/minikube/pkg/minikube/out"
	"k8s.io/minikube/pkg/minikube/out/register"
	"k8s.io/minikube/pkg/minikube/proxy"
	"k8s.io/minikube/pkg/minikube/registry"
	"k8s.io/minikube/pkg/minikube/vmpath"
	"k8s.io/minikube/pkg/util/lock"
)

var (
	// requiredDirectories are directories to create on the host during setup
	requiredDirectories = []string{
		vmpath.GuestAddonsDir,
		vmpath.GuestManifestsDir,
		vmpath.GuestEphemeralDir,
		vmpath.GuestPersistentDir,
		vmpath.GuestKubernetesCertsDir,
		path.Join(vmpath.GuestPersistentDir, "images"),
		path.Join(vmpath.GuestPersistentDir, "binaries"),
		vmpath.GuestGvisorDir,
		vmpath.GuestCertAuthDir,
		vmpath.GuestCertStoreDir,
	}
)

// StartHost starts a host VM.
func StartHost(api libmachine.API, cfg *config.ClusterConfig, n *config.Node) (*host.Host, bool, error) {
	machineName := driver.MachineName(*cfg, *n)

	// Prevent machine-driver boot races, as well as our own certificate race
	releaser, err := acquireMachinesLock(machineName, cfg.Driver)
	if err != nil {
		return nil, false, errors.Wrap(err, "boot lock")
	}
	start := time.Now()
	defer func() {
		glog.Infof("releasing machines lock for %q, held for %s", machineName, time.Since(start))
		releaser.Release()
	}()

	exists, err := api.Exists(machineName)
	if err != nil {
		return nil, false, errors.Wrapf(err, "exists: %s", machineName)
	}
	if !exists {
		glog.Infof("Provisioning new machine with config: %+v %+v", cfg, n)
		h, err := createHost(api, cfg, n)
		return h, exists, err
	}
	glog.Infoln("Skipping create...Using existing machine configuration")
	h, err := fixHost(api, cfg, n)
	return h, exists, err
}

// engineOptions returns docker engine options for the dockerd running inside minikube
func engineOptions(cfg config.ClusterConfig) *engine.Options {
	// get docker env from user's proxy settings
	dockerEnv := proxy.SetDockerEnv()
	// get docker env from user specifiec config
	dockerEnv = append(dockerEnv, cfg.DockerEnv...)

	// remove duplicates
	seen := map[string]bool{}
	uniqueEnvs := []string{}
	for e := range dockerEnv {
		if !seen[dockerEnv[e]] {
			seen[dockerEnv[e]] = true
			uniqueEnvs = append(uniqueEnvs, dockerEnv[e])
		}
	}

	o := engine.Options{
		Env:              uniqueEnvs,
		InsecureRegistry: append([]string{constants.DefaultServiceCIDR}, cfg.InsecureRegistry...),
		RegistryMirror:   cfg.RegistryMirror,
		ArbitraryFlags:   cfg.DockerOpt,
		InstallURL:       drivers.DefaultEngineInstallURL,
	}
	return &o
}

func createHost(api libmachine.API, cfg *config.ClusterConfig, n *config.Node) (*host.Host, error) {
	glog.Infof("createHost starting for %q (driver=%q)", n.Name, cfg.Driver)
	start := time.Now()
	defer func() {
		glog.Infof("duration metric: createHost completed in %s", time.Since(start))
	}()

	if cfg.Driver == driver.VMwareFusion && viper.GetBool(config.ShowDriverDeprecationNotification) {
		out.WarningT(`The vmwarefusion driver is deprecated and support for it will be removed in a future release.
			Please consider switching to the new vmware unified driver, which is intended to replace the vmwarefusion driver.
			See https://minikube.sigs.k8s.io/docs/reference/drivers/vmware/ for more information.
			To disable this message, run [minikube config set ShowDriverDeprecationNotification false]`)
	}
	showHostInfo(*cfg)
	def := registry.Driver(cfg.Driver)
	if def.Empty() {
		return nil, fmt.Errorf("unsupported/missing driver: %s", cfg.Driver)
	}
	dd, err := def.Config(*cfg, *n)
	if err != nil {
		return nil, errors.Wrap(err, "config")
	}
	data, err := json.Marshal(dd)
	if err != nil {
		return nil, errors.Wrap(err, "marshal")
	}

	h, err := api.NewHost(cfg.Driver, data)
	if err != nil {
		return nil, errors.Wrap(err, "new host")
	}
	defer postStartValidations(h, cfg.Driver)

	h.HostOptions.AuthOptions.CertDir = localpath.MiniPath()
	h.HostOptions.AuthOptions.StorePath = localpath.MiniPath()
	h.HostOptions.EngineOptions = engineOptions(*cfg)

	cstart := time.Now()
	glog.Infof("libmachine.API.Create for %q (driver=%q)", cfg.Name, cfg.Driver)

	if cfg.StartHostTimeout == 0 {
		cfg.StartHostTimeout = 6 * time.Minute
	}
	if err := timedCreateHost(h, api, cfg.StartHostTimeout); err != nil {
		return nil, errors.Wrap(err, "creating host")
	}
	glog.Infof("duration metric: libmachine.API.Create for %q took %s", cfg.Name, time.Since(cstart))

	if err := postStartSetup(h, *cfg); err != nil {
		return h, errors.Wrap(err, "post-start")
	}

	if err := saveHost(api, h, cfg, n); err != nil {
		return h, err
	}
	return h, nil
}

func timedCreateHost(h *host.Host, api libmachine.API, t time.Duration) error {
	timeout := make(chan bool, 1)
	go func() {
		time.Sleep(t)
		timeout <- true
	}()

	createFinished := make(chan bool, 1)
	var err error
	go func() {
		err = api.Create(h)
		createFinished <- true
	}()

	select {
	case <-createFinished:
		if err != nil {
			// Wait for all the logs to reach the client
			time.Sleep(2 * time.Second)
			return errors.Wrap(err, "create")
		}
		return nil
	case <-timeout:
		return fmt.Errorf("create host timed out in %f seconds", t.Seconds())
	}
}

// postStartValidations are validations against the host after it is created
// TODO: Add validations for VM drivers as well, see issue #9035
func postStartValidations(h *host.Host, drvName string) {
	if !driver.IsKIC(drvName) {
		return
	}
	// make sure /var isn't full, otherwise warn
	output, err := h.RunSSHCommand("df -h /var | awk 'NR==2{print $5}'")
	if err != nil {
		glog.Warningf("error running df -h /var: %v", err)
	}
	output = strings.Trim(output, "\n")
	percentageFull, err := strconv.Atoi(output[:len(output)-1])
	if err != nil {
		glog.Warningf("error getting percentage of /var that is free: %v", err)
	}
	if percentageFull >= 99 {
		exit.WithError("", fmt.Errorf("docker daemon out of memory. No space left on device"))
	}

	if percentageFull > 80 {
		out.WarningT("The docker daemon is almost out of memory, run 'docker system prune' to free up space")
	}
}

// postStart are functions shared between startHost and fixHost
func postStartSetup(h *host.Host, mc config.ClusterConfig) error {
	glog.Infof("post-start starting for %q (driver=%q)", h.Name, h.DriverName)
	start := time.Now()
	defer func() {
		glog.Infof("post-start completed in %s", time.Since(start))
	}()

	if driver.IsMock(h.DriverName) {
		return nil
	}

	glog.Infof("creating required directories: %v", requiredDirectories)

	r, err := CommandRunner(h)
	if err != nil {
		return errors.Wrap(err, "command runner")
	}

	args := append([]string{"mkdir", "-p"}, requiredDirectories...)
	if _, err := r.RunCmd(exec.Command("sudo", args...)); err != nil {
		return errors.Wrapf(err, "sudo mkdir (%s)", h.DriverName)
	}

	if driver.BareMetal(mc.Driver) {
		showLocalOsRelease()
	}
	if driver.IsVM(mc.Driver) || driver.IsKIC(mc.Driver) {
		logRemoteOsRelease(r)
	}
	return syncLocalAssets(r)
}

// acquireMachinesLock protects against code that is not parallel-safe (libmachine, cert setup)
func acquireMachinesLock(name string, drv string) (mutex.Releaser, error) {
	spec := lock.PathMutexSpec(filepath.Join(localpath.MiniPath(), "machines"))
	// NOTE: Provisioning generally completes within 60 seconds
	// however in parallel integration testing it might take longe
	spec.Timeout = 13 * time.Minute
	if driver.IsKIC(drv) {
		spec.Timeout = 10 * time.Minute

	}

	glog.Infof("acquiring machines lock for %s: %+v", name, spec)
	start := time.Now()
	r, err := mutex.Acquire(spec)
	if err == nil {
		glog.Infof("acquired machines lock for %q in %s", name, time.Since(start))
	}
	return r, err
}

// showHostInfo shows host information
func showHostInfo(cfg config.ClusterConfig) {
	machineType := driver.MachineType(cfg.Driver)
	if driver.BareMetal(cfg.Driver) {
		info, cpuErr, memErr, DiskErr := CachedHostInfo()
		if cpuErr == nil && memErr == nil && DiskErr == nil {
			register.Reg.SetStep(register.RunningLocalhost)
			out.T(out.StartingNone, "Running on localhost (CPUs={{.number_of_cpus}}, Memory={{.memory_size}}MB, Disk={{.disk_size}}MB) ...", out.V{"number_of_cpus": info.CPUs, "memory_size": info.Memory, "disk_size": info.DiskSize})
		}
		return
	}
	if driver.IsKIC(cfg.Driver) { // TODO:medyagh add free disk space on docker machine
		register.Reg.SetStep(register.CreatingContainer)
		out.T(out.StartingVM, "Creating {{.driver_name}} {{.machine_type}} (CPUs={{.number_of_cpus}}, Memory={{.memory_size}}MB) ...", out.V{"driver_name": cfg.Driver, "number_of_cpus": cfg.CPUs, "memory_size": cfg.Memory, "machine_type": machineType})
		return
	}
	register.Reg.SetStep(register.CreatingVM)
	out.T(out.StartingVM, "Creating {{.driver_name}} {{.machine_type}} (CPUs={{.number_of_cpus}}, Memory={{.memory_size}}MB, Disk={{.disk_size}}MB) ...", out.V{"driver_name": cfg.Driver, "number_of_cpus": cfg.CPUs, "memory_size": cfg.Memory, "disk_size": cfg.DiskSize, "machine_type": machineType})
}

// AddHostAlias makes fine adjustments to pod resources that aren't possible via kubeadm config.
func AddHostAlias(c command.Runner, name string, ip net.IP) error {
	record := fmt.Sprintf("%s\t%s", ip, name)
	if _, err := c.RunCmd(exec.Command("grep", record+"$", "/etc/hosts")); err == nil {
		return nil
	}

	script := fmt.Sprintf(`{ grep -v '\t%s$' /etc/hosts; echo "%s"; } > /tmp/h.$$; sudo cp /tmp/h.$$ /etc/hosts`, name, record)
	if _, err := c.RunCmd(exec.Command("/bin/bash", "-c", script)); err != nil {
		return errors.Wrap(err, "hosts update")
	}
	return nil
}
<|endoftext|>


<commit_before>// Copyright 2021 Google LLC
//
// 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 resutils

import (
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"path"
	"testing"
	"time"

	"github.com/spf13/afero"
	. "gopkg.in/check.v1"
)

var testDir string

// Setup GoCheck
type MySuite struct{}

var _ = Suite(&MySuite{})

func Test(t *testing.T) {
	TestingT(t)
}

func setup() {
	t := time.Now()
	dirName := fmt.Sprintf("ghpc_reswriter_test_%s", t.Format(time.RFC3339))
	dir, err := ioutil.TempDir("", dirName)
	if err != nil {
		log.Fatalf("reswriter_test: %v", err)
	}
	testDir = dir
}

func teardown() {
	os.RemoveAll(testDir)
}

func TestMain(m *testing.M) {
	setup()
	code := m.Run()
	teardown()
	os.Exit(code)
}

// Tests
func getTestFS() afero.IOFS {
	aferoFS := afero.NewMemMapFs()
	aferoFS.MkdirAll("resources/network/vpc", 0755)
	afero.WriteFile(
		aferoFS, "resources/network/vpc/main.tf", []byte("test string"), 0644)
	return afero.NewIOFS(aferoFS)
}

func (s *MySuite) TestCopyDirFromResources(c *C) {
	// Setup
	testResFS := getTestFS()
	copyDir := path.Join(testDir, "TestCopyDirFromResources")
	if err := os.Mkdir(copyDir, 0755); err != nil {
		log.Fatal(err)
	}

	// Success
	err := CopyDirFromResources(testResFS, "resources/network/vpc", copyDir)
	c.Assert(err, IsNil)
	fInfo, err := os.Stat(path.Join(copyDir, "main.tf"))
	c.Assert(err, IsNil)
	c.Assert(fInfo.Name(), Equals, "main.tf")
	c.Assert(fInfo.Size() > 0, Equals, true)
	c.Assert(fInfo.IsDir(), Equals, false)

	// Success: copy files AND dirs
	err = CopyDirFromResources(testResFS, "resources/network/", copyDir)
	c.Assert(err, IsNil)
	fInfo, err = os.Stat(path.Join(copyDir, "vpc/main.tf"))
	c.Assert(err, IsNil)
	c.Assert(fInfo.Name(), Equals, "main.tf")
	c.Assert(fInfo.Size() > 0, Equals, true)
	c.Assert(fInfo.IsDir(), Equals, false)
	fInfo, err = os.Stat(path.Join(copyDir, "vpc"))
	c.Assert(err, IsNil)
	c.Assert(fInfo.Name(), Equals, "vpc")
	c.Assert(fInfo.Size() > 0, Equals, true)
	c.Assert(fInfo.IsDir(), Equals, true)

	// Invalid path
	err = CopyDirFromResources(testResFS, "not/valid", copyDir)
	c.Assert(err, ErrorMatches, "*file does not exist")

	// Failure: File Already Exists
	err = CopyDirFromResources(testResFS, "resources/network/", copyDir)
	c.Assert(err, ErrorMatches, "*file exists")
}

func (s *MySuite) TestCopyGitHubResources(c *C) {
	// Setup
	destDir := path.Join(testDir, "TestCloneGitHubRepository")
	if err := os.Mkdir(destDir, 0755); err != nil {
		log.Fatal(err)
	}

	// Success via SSH
	destDirForSSH := path.Join(destDir, "ssh")
	err := CopyGitHubResources("git@github.com:google/google.github.io.git//_layouts", destDirForSSH)
	c.Assert(err, IsNil)
	fInfo, err := os.Stat(path.Join(destDirForSSH, "redirect.html"))
	c.Assert(err, IsNil)
	c.Assert(fInfo.Name(), Equals, "redirect.html")
	c.Assert(fInfo.Size() > 0, Equals, true)
	c.Assert(fInfo.IsDir(), Equals, false)

	// Success via HTTPS
	destDirForHTTPS := path.Join(destDir, "https")
	err = CopyGitHubResources("github.com/google/google.github.io//_layouts", destDirForHTTPS)
	c.Assert(err, IsNil)
	fInfo, err = os.Stat(path.Join(destDirForSSH, "redirect.html"))
	c.Assert(err, IsNil)
	c.Assert(fInfo.Name(), Equals, "redirect.html")
	c.Assert(fInfo.Size() > 0, Equals, true)
	c.Assert(fInfo.IsDir(), Equals, false)
}

func (s *MySuite) TestIsEmbeddedPath(c *C) {
	// True: Is an embedded path
	ret := IsEmbeddedPath("resources/anything/else")
	c.Assert(ret, Equals, true)

	// False: Local path
	ret = IsEmbeddedPath("./anything/else")
	c.Assert(ret, Equals, false)

	ret = IsEmbeddedPath("./resources")
	c.Assert(ret, Equals, false)

	ret = IsEmbeddedPath("../resources/")
	c.Assert(ret, Equals, false)

	// False, other
	ret = IsEmbeddedPath("github.com/resources")
	c.Assert(ret, Equals, false)
}

func (s *MySuite) TestIsLocalPath(c *C) {
	// False: Embedded Path
	ret := IsLocalPath("resources/anything/else")
	c.Assert(ret, Equals, false)

	// True: Local path
	ret = IsLocalPath("./anything/else")
	c.Assert(ret, Equals, true)

	ret = IsLocalPath("./resources")
	c.Assert(ret, Equals, true)

	ret = IsLocalPath("../resources/")
	c.Assert(ret, Equals, true)

	// False, other
	ret = IsLocalPath("github.com/resources")
	c.Assert(ret, Equals, false)
}

func (s *MySuite) TestIsGitHubRepository(c *C) {
	// False: Is an embedded path
	ret := IsGitHubPath("resources/anything/else")
	c.Assert(ret, Equals, false)

	// False: Local path
	ret = IsGitHubPath("./anything/else")
	c.Assert(ret, Equals, false)

	ret = IsGitHubPath("./resources")
	c.Assert(ret, Equals, false)

	ret = IsGitHubPath("../resources/")
	c.Assert(ret, Equals, false)

	// True, other
	ret = IsGitHubPath("github.com/resources")
	c.Assert(ret, Equals, true)
}
<commit_msg>Remove git-ssh test case<commit_after>// Copyright 2021 Google LLC
//
// 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 resutils

import (
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"path"
	"testing"
	"time"

	"github.com/spf13/afero"
	. "gopkg.in/check.v1"
)

var testDir string

// Setup GoCheck
type MySuite struct{}

var _ = Suite(&MySuite{})

func Test(t *testing.T) {
	TestingT(t)
}

func setup() {
	t := time.Now()
	dirName := fmt.Sprintf("ghpc_reswriter_test_%s", t.Format(time.RFC3339))
	dir, err := ioutil.TempDir("", dirName)
	if err != nil {
		log.Fatalf("reswriter_test: %v", err)
	}
	testDir = dir
}

func teardown() {
	os.RemoveAll(testDir)
}

func TestMain(m *testing.M) {
	setup()
	code := m.Run()
	teardown()
	os.Exit(code)
}

// Tests
func getTestFS() afero.IOFS {
	aferoFS := afero.NewMemMapFs()
	aferoFS.MkdirAll("resources/network/vpc", 0755)
	afero.WriteFile(
		aferoFS, "resources/network/vpc/main.tf", []byte("test string"), 0644)
	return afero.NewIOFS(aferoFS)
}

func (s *MySuite) TestCopyDirFromResources(c *C) {
	// Setup
	testResFS := getTestFS()
	copyDir := path.Join(testDir, "TestCopyDirFromResources")
	if err := os.Mkdir(copyDir, 0755); err != nil {
		log.Fatal(err)
	}

	// Success
	err := CopyDirFromResources(testResFS, "resources/network/vpc", copyDir)
	c.Assert(err, IsNil)
	fInfo, err := os.Stat(path.Join(copyDir, "main.tf"))
	c.Assert(err, IsNil)
	c.Assert(fInfo.Name(), Equals, "main.tf")
	c.Assert(fInfo.Size() > 0, Equals, true)
	c.Assert(fInfo.IsDir(), Equals, false)

	// Success: copy files AND dirs
	err = CopyDirFromResources(testResFS, "resources/network/", copyDir)
	c.Assert(err, IsNil)
	fInfo, err = os.Stat(path.Join(copyDir, "vpc/main.tf"))
	c.Assert(err, IsNil)
	c.Assert(fInfo.Name(), Equals, "main.tf")
	c.Assert(fInfo.Size() > 0, Equals, true)
	c.Assert(fInfo.IsDir(), Equals, false)
	fInfo, err = os.Stat(path.Join(copyDir, "vpc"))
	c.Assert(err, IsNil)
	c.Assert(fInfo.Name(), Equals, "vpc")
	c.Assert(fInfo.Size() > 0, Equals, true)
	c.Assert(fInfo.IsDir(), Equals, true)

	// Invalid path
	err = CopyDirFromResources(testResFS, "not/valid", copyDir)
	c.Assert(err, ErrorMatches, "*file does not exist")

	// Failure: File Already Exists
	err = CopyDirFromResources(testResFS, "resources/network/", copyDir)
	c.Assert(err, ErrorMatches, "*file exists")
}

func (s *MySuite) TestCopyGitHubResources(c *C) {
	// Setup
	destDir := path.Join(testDir, "TestCloneGitHubRepository")
	if err := os.Mkdir(destDir, 0755); err != nil {
		log.Fatal(err)
	}

	// Success via HTTPS
	destDirForHTTPS := path.Join(destDir, "https")
	err := CopyGitHubResources("github.com/google/google.github.io//_layouts", destDirForHTTPS)
	c.Assert(err, IsNil)
	fInfo, err := os.Stat(path.Join(destDirForHTTPS, "redirect.html"))
	c.Assert(err, IsNil)
	c.Assert(fInfo.Name(), Equals, "redirect.html")
	c.Assert(fInfo.Size() > 0, Equals, true)
	c.Assert(fInfo.IsDir(), Equals, false)
}

func (s *MySuite) TestIsEmbeddedPath(c *C) {
	// True: Is an embedded path
	ret := IsEmbeddedPath("resources/anything/else")
	c.Assert(ret, Equals, true)

	// False: Local path
	ret = IsEmbeddedPath("./anything/else")
	c.Assert(ret, Equals, false)

	ret = IsEmbeddedPath("./resources")
	c.Assert(ret, Equals, false)

	ret = IsEmbeddedPath("../resources/")
	c.Assert(ret, Equals, false)

	// False, other
	ret = IsEmbeddedPath("github.com/resources")
	c.Assert(ret, Equals, false)
}

func (s *MySuite) TestIsLocalPath(c *C) {
	// False: Embedded Path
	ret := IsLocalPath("resources/anything/else")
	c.Assert(ret, Equals, false)

	// True: Local path
	ret = IsLocalPath("./anything/else")
	c.Assert(ret, Equals, true)

	ret = IsLocalPath("./resources")
	c.Assert(ret, Equals, true)

	ret = IsLocalPath("../resources/")
	c.Assert(ret, Equals, true)

	// False, other
	ret = IsLocalPath("github.com/resources")
	c.Assert(ret, Equals, false)
}

func (s *MySuite) TestIsGitHubRepository(c *C) {
	// False: Is an embedded path
	ret := IsGitHubPath("resources/anything/else")
	c.Assert(ret, Equals, false)

	// False: Local path
	ret = IsGitHubPath("./anything/else")
	c.Assert(ret, Equals, false)

	ret = IsGitHubPath("./resources")
	c.Assert(ret, Equals, false)

	ret = IsGitHubPath("../resources/")
	c.Assert(ret, Equals, false)

	// True, other
	ret = IsGitHubPath("github.com/resources")
	c.Assert(ret, Equals, true)
}
<|endoftext|>