<commit_before>// Copyright 2017 The OpenSDS 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 discovery

import (
	"reflect"
	"testing"

	c "github.com/opensds/opensds/pkg/context"
	"github.com/opensds/opensds/pkg/model"
	. "github.com/opensds/opensds/pkg/utils/config"
	. "github.com/opensds/opensds/testutils/collection"
	dbtest "github.com/opensds/opensds/testutils/db/testing"
)

const (
	expectedUuid      = "0e9c3c68-8a0b-11e7-94a7-67f755e235cb"
	expectedCreatedAt = "2017-08-26T11:01:09"
	expectedUpdatedAt = "2017-08-26T11:01:55"
)

func init() {
	CONF.OsdsDock = OsdsDock{
		ApiEndpoint:     "localhost:50050",
		EnabledBackends: []string{"sample"},
		Backends: Backends{
			Sample: BackendProperties{
				Name:        "sample",
				Description: "sample backend service",
				DriverName:  "sample",
			},
		},
	}
}

func NewFakeDockDiscoverer() *provisionDockDiscoverer {
	return &provisionDockDiscoverer{
		DockRegister: &DockRegister{},
	}
}

func TestInit(t *testing.T) {
	var fdd = NewFakeDockDiscoverer()
	var expected []*model.DockSpec

	for i := range SampleDocks {
		expected = append(expected, &SampleDocks[i])
	}
	name := map[string][]string{"Name": {SampleDocks[0].Name}}
	mockClient := new(dbtest.Client)
	mockClient.On("ListDocksWithFilter", c.NewAdminContext(), name).Return(expected, nil)
	fdd.c = mockClient
	if err := fdd.Init(); err != nil {
		t.Errorf("Failed to init discoverer struct: %v\n", err)
	}
	for i := range fdd.dcks {
		fdd.dcks[i].Id = ""
		fdd.dcks[i].NodeId = ""
		fdd.dcks[i].Metadata = nil
		expected[i].Id = ""
	}
	if !reflect.DeepEqual(fdd.dcks, expected) {
		t.Errorf("Expected %+v, got %+v\n", expected, fdd.dcks)
	}
}

func TestDiscover(t *testing.T) {
	var fdd = NewFakeDockDiscoverer()
	var expected []*model.StoragePoolSpec

	for i := range SampleDocks {
		fdd.dcks = append(fdd.dcks, &SampleDocks[i])
	}
	for i := range SamplePools {
		fdd.pols = append(fdd.pols, &SamplePools[i])
		expected = append(expected, &SamplePools[i])
	}
	m1 := map[string][]string{
		"Name":   {SamplePools[0].Name},
		"DockId": {""},
	}
	m2 := map[string][]string{
		"Name":   {SamplePools[1].Name},
		"DockId": {""},
	}
	mockClient := new(dbtest.Client)
	mockClient.On("ListPools", c.NewAdminContext()).Return(fdd.pols, nil)
	mockClient.On("ListPoolsWithFilter", c.NewAdminContext(), m1).Return(expected, nil)
	mockClient.On("ListPoolsWithFilter", c.NewAdminContext(), m2).Return(expected, nil)
	fdd.c = mockClient

	if err := fdd.Discover(); err != nil {
		t.Errorf("Failed to discoverer pools: %v\n", err)
	}
	for _, pol := range fdd.pols {
		pol.Id = ""
	}
	if !reflect.DeepEqual(fdd.pols, expected) {
		t.Errorf("Expected %+v, got %+v\n", expected, fdd.pols)
	}
}

func TestReport(t *testing.T) {
	var fdd = NewFakeDockDiscoverer()

	for i := range SampleDocks {
		fdd.dcks = append(fdd.dcks, &SampleDocks[i])
	}
	for i := range SamplePools {
		fdd.pols = append(fdd.pols, &SamplePools[i])
	}

	mockClient := new(dbtest.Client)
	mockClient.On("CreateDock", c.NewAdminContext(), fdd.dcks[0]).Return(nil, nil)
	mockClient.On("CreatePool", c.NewAdminContext(), fdd.pols[0]).Return(nil, nil)
	mockClient.On("CreatePool", c.NewAdminContext(), fdd.pols[1]).Return(nil, nil)
	mockClient.On("CreatePool", c.NewAdminContext(), fdd.pols[2]).Return(nil, nil)
	fdd.c = mockClient

	if err := fdd.Report(); err != nil {
		t.Errorf("Failed to store docks and pools into database: %v\n", err)
	}
}
<commit_msg>Update unit test<commit_after>// Copyright 2017 The OpenSDS 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 discovery

import (
	"reflect"
	"testing"

	c "github.com/opensds/opensds/pkg/context"
	"github.com/opensds/opensds/pkg/model"
	. "github.com/opensds/opensds/pkg/utils/config"
	. "github.com/opensds/opensds/testutils/collection"
	dbtest "github.com/opensds/opensds/testutils/db/testing"
)

const (
	expectedUuid      = "0e9c3c68-8a0b-11e7-94a7-67f755e235cb"
	expectedCreatedAt = "2017-08-26T11:01:09"
	expectedUpdatedAt = "2017-08-26T11:01:55"
)

func init() {
	CONF.OsdsDock = OsdsDock{
		ApiEndpoint:     "localhost:50050",
		EnabledBackends: []string{"sample"},
		Backends: Backends{
			Sample: BackendProperties{
				Name:        "sample",
				Description: "sample backend service",
				DriverName:  "sample",
			},
		},
	}
}

func NewFakeDockDiscoverer() *provisionDockDiscoverer {
	return &provisionDockDiscoverer{
		DockRegister: &DockRegister{},
	}
}

func TestInit(t *testing.T) {
	var fdd = NewFakeDockDiscoverer()
	var expected []*model.DockSpec

	for i := range SampleDocks {
		expected = append(expected, &SampleDocks[i])
	}
	name := map[string][]string{"Name": {SampleDocks[0].Name}}
	mockClient := new(dbtest.Client)
	mockClient.On("ListDocksWithFilter", c.NewAdminContext(), name).Return(expected, nil)
	fdd.c = mockClient
	if err := fdd.Init(); err != nil {
		t.Errorf("Failed to init discoverer struct: %v\n", err)
	}
	for i := range fdd.dcks {
		fdd.dcks[i].Id = ""
		fdd.dcks[i].NodeId = ""
		fdd.dcks[i].Metadata = nil
		expected[i].Id = ""
	}
	if !reflect.DeepEqual(fdd.dcks, expected) {
		t.Errorf("Expected %+v, got %+v\n", expected, fdd.dcks)
	}
}

func TestDiscover(t *testing.T) {
	var fdd = NewFakeDockDiscoverer()
	var expected []*model.StoragePoolSpec

	for i := range SampleDocks {
		fdd.dcks = append(fdd.dcks, &SampleDocks[i])
	}
	for i := range SamplePools {
		fdd.pols = append(fdd.pols, &SamplePools[i])
		expected = append(expected, &SamplePools[i])
	}
	m1 := map[string][]string{
		"Name":   {SamplePools[0].Name},
		"DockId": {""},
	}
	m2 := map[string][]string{
		"Name":   {SamplePools[1].Name},
		"DockId": {""},
	}
	m3 := map[string][]string{
		"Name":   {SamplePools[2].Name},
		"DockId": {""},
	}

	mockClient := new(dbtest.Client)
	mockClient.On("ListPools", c.NewAdminContext()).Return(fdd.pols, nil)
	mockClient.On("ListPoolsWithFilter", c.NewAdminContext(), m1).Return(expected, nil)
	mockClient.On("ListPoolsWithFilter", c.NewAdminContext(), m2).Return(expected, nil)
	mockClient.On("ListPoolsWithFilter", c.NewAdminContext(), m3).Return(expected, nil)
	fdd.c = mockClient

	if err := fdd.Discover(); err != nil {
		t.Errorf("Failed to discoverer pools: %v\n", err)
	}
	for _, pol := range fdd.pols {
		pol.Id = ""
	}
	if !reflect.DeepEqual(fdd.pols, expected) {
		t.Errorf("Expected %+v, got %+v\n", expected, fdd.pols)
	}
}

func TestReport(t *testing.T) {
	var fdd = NewFakeDockDiscoverer()

	for i := range SampleDocks {
		fdd.dcks = append(fdd.dcks, &SampleDocks[i])
	}
	for i := range SamplePools {
		fdd.pols = append(fdd.pols, &SamplePools[i])
	}

	mockClient := new(dbtest.Client)
	mockClient.On("CreateDock", c.NewAdminContext(), fdd.dcks[0]).Return(nil, nil)
	mockClient.On("CreatePool", c.NewAdminContext(), fdd.pols[0]).Return(nil, nil)
	mockClient.On("CreatePool", c.NewAdminContext(), fdd.pols[1]).Return(nil, nil)
	mockClient.On("CreatePool", c.NewAdminContext(), fdd.pols[2]).Return(nil, nil)
	fdd.c = mockClient

	if err := fdd.Report(); err != nil {
		t.Errorf("Failed to store docks and pools into database: %v\n", err)
	}
}
<|endoftext|>


<commit_before>/*
Copyright 2018 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 executor

import (
	"io/ioutil"
	"os"
	"path/filepath"
	"reflect"
	"testing"
)

func Test_NewCompositeCache(t *testing.T) {
	r := NewCompositeCache()
	if reflect.TypeOf(r).String() != "*executor.CompositeCache" {
		t.Errorf("expected return to be *executor.CompositeCache but was %v", reflect.TypeOf(r).String())
	}
}

func Test_CompositeCache_AddKey(t *testing.T) {
	keys := []string{
		"meow",
		"purr",
	}
	r := NewCompositeCache()
	r.AddKey(keys...)
	if len(r.keys) != 2 {
		t.Errorf("expected keys to have length 2 but was %v", len(r.keys))
	}
}

func Test_CompositeCache_Key(t *testing.T) {
	r := NewCompositeCache("meow", "purr")
	k := r.Key()
	if k != "meow-purr" {
		t.Errorf("expected result to equal meow-purr but was %v", k)
	}
}

func Test_CompositeCache_Hash(t *testing.T) {
	r := NewCompositeCache("meow", "purr")
	h, err := r.Hash()
	if err != nil {
		t.Errorf("expected error to be nil but was %v", err)
	}

	expectedHash := "b4fd5a11af812a11a79d794007c842794cc668c8e7ebaba6d1e6d021b8e06c71"
	if h != expectedHash {
		t.Errorf("expected result to equal %v but was %v", expectedHash, h)
	}
}

func Test_CompositeCache_AddPath_dir(t *testing.T) {
	tmpDir, err := ioutil.TempDir("/tmp", "foo")
	if err != nil {
		t.Errorf("got error setting up test %v", err)
	}

	content := `meow meow meow`
	if err := ioutil.WriteFile(filepath.Join(tmpDir, "foo.txt"), []byte(content), 0777); err != nil {
		t.Errorf("got error writing temp file %v", err)
	}

	fn := func() string {
		r := NewCompositeCache()
		if err := r.AddPath(tmpDir); err != nil {
			t.Errorf("expected error to be nil but was %v", err)
		}

		if len(r.keys) != 1 {
			t.Errorf("expected len of keys to be 1 but was %v", len(r.keys))
		}
		hash, err := r.Hash()
		if err != nil {
			t.Errorf("couldnt generate hash from test cache")
		}
		return hash
	}

	hash1 := fn()
	hash2 := fn()
	if hash1 != hash2 {
		t.Errorf("expected hash %v to equal hash %v", hash1, hash2)
	}
}
func Test_CompositeCache_AddPath_file(t *testing.T) {
	tmpfile, err := ioutil.TempFile("/tmp", "foo.txt")
	if err != nil {
		t.Errorf("got error setting up test %v", err)
	}
	defer os.Remove(tmpfile.Name()) // clean up

	content := `meow meow meow`
	if _, err := tmpfile.Write([]byte(content)); err != nil {
		t.Errorf("got error writing temp file %v", err)
	}
	if err := tmpfile.Close(); err != nil {
		t.Errorf("got error closing temp file %v", err)
	}

	p := tmpfile.Name()
	fn := func() string {
		r := NewCompositeCache()
		if err := r.AddPath(p); err != nil {
			t.Errorf("expected error to be nil but was %v", err)
		}

		if len(r.keys) != 1 {
			t.Errorf("expected len of keys to be 1 but was %v", len(r.keys))
		}
		hash, err := r.Hash()
		if err != nil {
			t.Errorf("couldnt generate hash from test cache")
		}
		return hash
	}

	hash1 := fn()
	hash2 := fn()
	if hash1 != hash2 {
		t.Errorf("expected hash %v to equal hash %v", hash1, hash2)
	}
}
<commit_msg>composite_cache: add unit tests<commit_after>/*
Copyright 2018 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 executor

import (
	"io/ioutil"
	"os"
	"path"
	"path/filepath"
	"reflect"
	"testing"

	"github.com/GoogleContainerTools/kaniko/pkg/util"
)

func Test_NewCompositeCache(t *testing.T) {
	r := NewCompositeCache()
	if reflect.TypeOf(r).String() != "*executor.CompositeCache" {
		t.Errorf("expected return to be *executor.CompositeCache but was %v", reflect.TypeOf(r).String())
	}
}

func Test_CompositeCache_AddKey(t *testing.T) {
	keys := []string{
		"meow",
		"purr",
	}
	r := NewCompositeCache()
	r.AddKey(keys...)
	if len(r.keys) != 2 {
		t.Errorf("expected keys to have length 2 but was %v", len(r.keys))
	}
}

func Test_CompositeCache_Key(t *testing.T) {
	r := NewCompositeCache("meow", "purr")
	k := r.Key()
	if k != "meow-purr" {
		t.Errorf("expected result to equal meow-purr but was %v", k)
	}
}

func Test_CompositeCache_Hash(t *testing.T) {
	r := NewCompositeCache("meow", "purr")
	h, err := r.Hash()
	if err != nil {
		t.Errorf("expected error to be nil but was %v", err)
	}

	expectedHash := "b4fd5a11af812a11a79d794007c842794cc668c8e7ebaba6d1e6d021b8e06c71"
	if h != expectedHash {
		t.Errorf("expected result to equal %v but was %v", expectedHash, h)
	}
}

func Test_CompositeCache_AddPath_dir(t *testing.T) {
	tmpDir, err := ioutil.TempDir("/tmp", "foo")
	if err != nil {
		t.Errorf("got error setting up test %v", err)
	}

	content := `meow meow meow`
	if err := ioutil.WriteFile(filepath.Join(tmpDir, "foo.txt"), []byte(content), 0777); err != nil {
		t.Errorf("got error writing temp file %v", err)
	}

	fn := func() string {
		r := NewCompositeCache()
		if err := r.AddPath(tmpDir); err != nil {
			t.Errorf("expected error to be nil but was %v", err)
		}

		if len(r.keys) != 1 {
			t.Errorf("expected len of keys to be 1 but was %v", len(r.keys))
		}
		hash, err := r.Hash()
		if err != nil {
			t.Errorf("couldnt generate hash from test cache")
		}
		return hash
	}

	hash1 := fn()
	hash2 := fn()
	if hash1 != hash2 {
		t.Errorf("expected hash %v to equal hash %v", hash1, hash2)
	}
}
func Test_CompositeCache_AddPath_file(t *testing.T) {
	tmpfile, err := ioutil.TempFile("/tmp", "foo.txt")
	if err != nil {
		t.Errorf("got error setting up test %v", err)
	}
	defer os.Remove(tmpfile.Name()) // clean up

	content := `meow meow meow`
	if _, err := tmpfile.Write([]byte(content)); err != nil {
		t.Errorf("got error writing temp file %v", err)
	}
	if err := tmpfile.Close(); err != nil {
		t.Errorf("got error closing temp file %v", err)
	}

	p := tmpfile.Name()
	fn := func() string {
		r := NewCompositeCache()
		if err := r.AddPath(p); err != nil {
			t.Errorf("expected error to be nil but was %v", err)
		}

		if len(r.keys) != 1 {
			t.Errorf("expected len of keys to be 1 but was %v", len(r.keys))
		}
		hash, err := r.Hash()
		if err != nil {
			t.Errorf("couldnt generate hash from test cache")
		}
		return hash
	}

	hash1 := fn()
	hash2 := fn()
	if hash1 != hash2 {
		t.Errorf("expected hash %v to equal hash %v", hash1, hash2)
	}
}

func createFilesystemStructure(root string, directories, files []string) error {
	for _, d := range directories {
		dirPath := path.Join(root, d)
		if err := os.MkdirAll(dirPath, 0755); err != nil {
			return err
		}
	}

	for _, fileName := range files {
		filePath := path.Join(root, fileName)
		err := ioutil.WriteFile(filePath, []byte(fileName), 0644)
		if err != nil {
			return err
		}
	}

	return nil
}

func setIgnoreContext(content string) error {
	dockerIgnoreDir, err := ioutil.TempDir("", "")
	if err != nil {
		return err
	}
	defer os.RemoveAll(dockerIgnoreDir)
	err = ioutil.WriteFile(dockerIgnoreDir+".dockerignore", []byte(content), 0644)
	if err != nil {
		return err
	}
	err = util.GetExcludedFiles(dockerIgnoreDir, "")
	if err != nil {
		return err
	}
	return nil
}

func hashDirectory(dirpath string) (string, error) {
	cache1 := NewCompositeCache()
	err := cache1.AddPath(dirpath, dirpath)
	if err != nil {
		return "", err
	}

	hash, err := cache1.Hash()
	if err != nil {
		return "", err
	}
	return hash, nil
}

func Test_CompositeKey_AddPath_Works(t *testing.T) {
	tests := []struct {
		name        string
		directories []string
		files       []string
	}{
		{
			name:        "empty",
			directories: []string{},
			files:       []string{},
		},
		{
			name:        "dirs",
			directories: []string{"foo", "bar", "foobar", "f/o/o"},
			files:       []string{},
		},
		{
			name:        "files",
			directories: []string{},
			files:       []string{"foo", "bar", "foobar"},
		},
		{
			name:        "all",
			directories: []string{"foo", "bar"},
			files:       []string{"foo/bar", "bar/baz", "foobar"},
		},
	}

	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			testDir1, err := ioutil.TempDir("", "")
			if err != nil {
				t.Fatalf("Error creating tempdir: %s", err)
			}
			defer os.RemoveAll(testDir1)
			err = createFilesystemStructure(testDir1, test.directories, test.files)
			if err != nil {
				t.Fatalf("Error creating filesytem structure: %s", err)
			}

			testDir2, err := ioutil.TempDir("", "")
			if err != nil {
				t.Fatalf("Error creating tempdir: %s", err)
			}
			defer os.RemoveAll(testDir2)
			err = createFilesystemStructure(testDir2, test.directories, test.files)
			if err != nil {
				t.Fatalf("Error creating filesytem structure: %s", err)
			}

			hash1, err := hashDirectory(testDir1)
			if err != nil {
				t.Fatalf("Failed to calculate hash: %s", err)
			}
			hash2, err := hashDirectory(testDir2)
			if err != nil {
				t.Fatalf("Failed to calculate hash: %s", err)
			}

			if hash1 != hash2 {
				t.Errorf("Expected equal hashes, got: %s and %s", hash1, hash2)
			}
		})
	}
}

func Test_CompositeKey_AddPath_WithExtraFile_Works(t *testing.T) {
	tests := []struct {
		name        string
		directories []string
		files       []string
		extraFile   string
	}{
		{
			name:        "empty",
			directories: []string{},
			files:       []string{},
			extraFile:   "file",
		},
		{
			name:        "dirs",
			directories: []string{"foo", "bar", "foobar", "f/o/o"},
			files:       []string{},
			extraFile:   "f/o/o/extra",
		},
		{
			name:        "files",
			directories: []string{},
			files:       []string{"foo", "bar", "foobar"},
			extraFile:   "foo.extra",
		},
		{
			name:        "all",
			directories: []string{"foo", "bar"},
			files:       []string{"foo/bar", "bar/baz", "foobar"},
			extraFile:   "bar/extra",
		},
	}

	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			testDir1, err := ioutil.TempDir("", "")
			if err != nil {
				t.Fatalf("Error creating tempdir: %s", err)
			}
			defer os.RemoveAll(testDir1)
			err = createFilesystemStructure(testDir1, test.directories, test.files)
			if err != nil {
				t.Fatalf("Error creating filesytem structure: %s", err)
			}

			testDir2, err := ioutil.TempDir("", "")
			if err != nil {
				t.Fatalf("Error creating tempdir: %s", err)
			}
			defer os.RemoveAll(testDir2)
			err = createFilesystemStructure(testDir2, test.directories, test.files)
			if err != nil {
				t.Fatalf("Error creating filesytem structure: %s", err)
			}
			extraPath := path.Join(testDir2, test.extraFile)
			err = ioutil.WriteFile(extraPath, []byte(test.extraFile), 0644)
			if err != nil {
				t.Fatalf("Error creating filesytem structure: %s", err)
			}

			hash1, err := hashDirectory(testDir1)
			if err != nil {
				t.Fatalf("Failed to calculate hash: %s", err)
			}
			hash2, err := hashDirectory(testDir2)
			if err != nil {
				t.Fatalf("Failed to calculate hash: %s", err)
			}

			if hash1 == hash2 {
				t.Errorf("Expected different hashes, got: %s and %s", hash1, hash2)
			}
		})
	}
}

func Test_CompositeKey_AddPath_WithExtraDir_Works(t *testing.T) {
	tests := []struct {
		name        string
		directories []string
		files       []string
		extraDir    string
	}{
		{
			name:        "empty",
			directories: []string{},
			files:       []string{},
			extraDir:    "extra",
		},
		{
			name:        "dirs",
			directories: []string{"foo", "bar", "foobar", "f/o/o"},
			files:       []string{},
			extraDir:    "f/o/o/extra",
		},
		{
			name:        "files",
			directories: []string{},
			files:       []string{"foo", "bar", "foobar"},
			extraDir:    "foo.extra",
		},
		{
			name:        "all",
			directories: []string{"foo", "bar"},
			files:       []string{"foo/bar", "bar/baz", "foobar"},
			extraDir:    "bar/extra",
		},
	}

	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			testDir1, err := ioutil.TempDir("", "")
			if err != nil {
				t.Fatalf("Error creating tempdir: %s", err)
			}
			defer os.RemoveAll(testDir1)
			err = createFilesystemStructure(testDir1, test.directories, test.files)
			if err != nil {
				t.Fatalf("Error creating filesytem structure: %s", err)
			}

			testDir2, err := ioutil.TempDir("", "")
			if err != nil {
				t.Fatalf("Error creating tempdir: %s", err)
			}
			defer os.RemoveAll(testDir2)
			err = createFilesystemStructure(testDir2, test.directories, test.files)
			if err != nil {
				t.Fatalf("Error creating filesytem structure: %s", err)
			}
			extraPath := path.Join(testDir2, test.extraDir)
			err = os.MkdirAll(extraPath, 0644)
			if err != nil {
				t.Fatalf("Error creating filesytem structure: %s", err)
			}

			hash1, err := hashDirectory(testDir1)
			if err != nil {
				t.Fatalf("Failed to calculate hash: %s", err)
			}
			hash2, err := hashDirectory(testDir2)
			if err != nil {
				t.Fatalf("Failed to calculate hash: %s", err)
			}

			if hash1 == hash2 {
				t.Errorf("Expected different hashes, got: %s and %s", hash1, hash2)
			}
		})
	}
}

func Test_CompositeKey_AddPath_WithExtraFilIgnored_Works(t *testing.T) {
	tests := []struct {
		name        string
		directories []string
		files       []string
		extraFile   string
	}{
		{
			name:        "empty",
			directories: []string{},
			files:       []string{},
			extraFile:   "extra",
		},
		{
			name:        "dirs",
			directories: []string{"foo", "bar", "foobar", "f/o/o"},
			files:       []string{},
			extraFile:   "f/o/o/extra",
		},
		{
			name:        "files",
			directories: []string{},
			files:       []string{"foo", "bar", "foobar"},
			extraFile:   "extra",
		},
		{
			name:        "all",
			directories: []string{"foo", "bar"},
			files:       []string{"foo/bar", "bar/baz", "foobar"},
			extraFile:   "bar/extra",
		},
	}

	err := setIgnoreContext("**/extra")
	if err != nil {
		t.Fatalf("Error setting exlusion context: %s", err)
	}

	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			testDir1, err := ioutil.TempDir("", "")
			if err != nil {
				t.Fatalf("Error creating tempdir: %s", err)
			}
			defer os.RemoveAll(testDir1)
			err = createFilesystemStructure(testDir1, test.directories, test.files)
			if err != nil {
				t.Fatalf("Error creating filesytem structure: %s", err)
			}

			testDir2, err := ioutil.TempDir("", "")
			if err != nil {
				t.Fatalf("Error creating tempdir: %s", err)
			}
			defer os.RemoveAll(testDir2)
			err = createFilesystemStructure(testDir2, test.directories, test.files)
			if err != nil {
				t.Fatalf("Error creating filesytem structure: %s", err)
			}
			extraPath := path.Join(testDir2, test.extraFile)
			err = ioutil.WriteFile(extraPath, []byte(test.extraFile), 0644)
			if err != nil {
				t.Fatalf("Error creating filesytem structure: %s", err)
			}

			hash1, err := hashDirectory(testDir1)
			if err != nil {
				t.Fatalf("Failed to calculate hash: %s", err)
			}
			hash2, err := hashDirectory(testDir2)
			if err != nil {
				t.Fatalf("Failed to calculate hash: %s", err)
			}

			if hash1 != hash2 {
				t.Errorf("Expected equal hashes, got: %s and %s", hash1, hash2)
			}
		})
	}
}

func Test_CompositeKey_AddPath_WithExtraDirIgnored_Works(t *testing.T) {
	tests := []struct {
		name        string
		directories []string
		files       []string
		extraDir    string
	}{
		{
			name:        "empty",
			directories: []string{},
			files:       []string{},
			extraDir:    "extra",
		},
		{
			name:        "dirs",
			directories: []string{"foo", "bar", "foobar", "f/o/o"},
			files:       []string{},
			extraDir:    "f/o/o/extra",
		},
		{
			name:        "files",
			directories: []string{},
			files:       []string{"foo", "bar", "foobar"},
			extraDir:    "extra",
		},
		{
			name:        "all",
			directories: []string{"foo", "bar"},
			files:       []string{"foo/bar", "bar/baz", "foobar"},
			extraDir:    "bar/extra",
		},
	}

	err := setIgnoreContext("**/extra")
	if err != nil {
		t.Fatalf("Error setting exlusion context: %s", err)
	}

	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			testDir1, err := ioutil.TempDir("", "")
			if err != nil {
				t.Fatalf("Error creating tempdir: %s", err)
			}
			defer os.RemoveAll(testDir1)
			err = createFilesystemStructure(testDir1, test.directories, test.files)
			if err != nil {
				t.Fatalf("Error creating filesytem structure: %s", err)
			}

			testDir2, err := ioutil.TempDir("", "")
			if err != nil {
				t.Fatalf("Error creating tempdir: %s", err)
			}
			defer os.RemoveAll(testDir2)
			err = createFilesystemStructure(testDir2, test.directories, test.files)
			if err != nil {
				t.Fatalf("Error creating filesytem structure: %s", err)
			}
			extraPath := path.Join(testDir2, test.extraDir)
			err = os.MkdirAll(extraPath, 0644)
			if err != nil {
				t.Fatalf("Error creating filesytem structure: %s", err)
			}

			hash1, err := hashDirectory(testDir1)
			if err != nil {
				t.Fatalf("Failed to calculate hash: %s", err)
			}
			hash2, err := hashDirectory(testDir2)
			if err != nil {
				t.Fatalf("Failed to calculate hash: %s", err)
			}

			if hash1 != hash2 {
				t.Errorf("Expected equal hashes, got: %s and %s", hash1, hash2)
			}
		})
	}
}
<|endoftext|>


<commit_before>/*
Copyright 2016 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 instancegroups

import (
	"fmt"
	"os"
	"time"

	"github.com/golang/glog"
	"github.com/spf13/cobra"
	api "k8s.io/kops/pkg/apis/kops"
	"k8s.io/kops/pkg/cloudinstances"
	"k8s.io/kops/pkg/featureflag"
	"k8s.io/kops/pkg/validation"
	"k8s.io/kops/upup/pkg/fi"
	"k8s.io/kubernetes/pkg/kubectl/cmd"
	cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
)

// RollingUpdateInstanceGroup is the AWS ASG backing an InstanceGroup.
type RollingUpdateInstanceGroup struct {
	// Cloud is the kops cloud provider
	Cloud fi.Cloud
	// CloudGroup is the kops cloud provider groups
	CloudGroup *cloudinstances.CloudInstanceGroup

	// TODO should remove the need to have rollingupdate struct and add:
	// TODO - the kubernetes client
	// TODO - the cluster name
	// TODO - the client config
	// TODO - fail on validate
	// TODO - fail on drain
	// TODO - cloudonly
}

// NewRollingUpdateInstanceGroup create a new struct
func NewRollingUpdateInstanceGroup(cloud fi.Cloud, cloudGroup *cloudinstances.CloudInstanceGroup) (*RollingUpdateInstanceGroup, error) {
	if cloud == nil {
		return nil, fmt.Errorf("cloud provider is required")
	}
	if cloudGroup == nil {
		return nil, fmt.Errorf("cloud group is required")
	}

	// TODO check more values in cloudGroup that they are set properly

	return &RollingUpdateInstanceGroup{
		Cloud:      cloud,
		CloudGroup: cloudGroup,
	}, nil
}

// TODO: Temporarily increase size of ASG?
// TODO: Remove from ASG first so status is immediately updated?
// TODO: Batch termination, like a rolling-update

// RollingUpdate performs a rolling update on a list of ec2 instances.
func (r *RollingUpdateInstanceGroup) RollingUpdate(rollingUpdateData *RollingUpdateCluster, instanceGroupList *api.InstanceGroupList, isBastion bool, sleepAfterTerminate time.Duration, validationTimeout time.Duration) (err error) {

	// we should not get here, but hey I am going to check.
	if rollingUpdateData == nil {
		return fmt.Errorf("rollingUpdate cannot be nil")
	}

	// Do not need a k8s client if you are doing cloudonly.
	if rollingUpdateData.K8sClient == nil && !rollingUpdateData.CloudOnly {
		return fmt.Errorf("rollingUpdate is missing a k8s client")
	}

	if instanceGroupList == nil {
		return fmt.Errorf("rollingUpdate is missing the InstanceGroupList")
	}

	update := r.CloudGroup.NeedUpdate
	if rollingUpdateData.Force {
		update = append(update, r.CloudGroup.Ready...)
	}

	if len(update) == 0 {
		return nil
	}

	if isBastion {
		glog.V(3).Info("Not validating the cluster as instance is a bastion.")
	} else if rollingUpdateData.CloudOnly {
		glog.V(3).Info("Not validating cluster as validation is turned off via the cloud-only flag.")
	} else if featureflag.DrainAndValidateRollingUpdate.Enabled() {
		if err = r.ValidateCluster(rollingUpdateData, instanceGroupList); err != nil {
			if rollingUpdateData.FailOnValidate {
				return fmt.Errorf("error validating cluster: %v", err)
			} else {
				glog.V(2).Infof("Ignoring cluster validation error: %v", err)
				glog.Infof("Cluster validation failed, but proceeding since fail-on-validate-error is set to false")
			}
		}
	}

	for _, u := range update {
		instanceId := u.ID

		nodeName := ""
		if u.Node != nil {
			nodeName = u.Node.Name
		}

		if isBastion {
			// We don't want to validate for bastions - they aren't part of the cluster
		} else if rollingUpdateData.CloudOnly {

			glog.Warningf("Not draining cluster nodes as 'cloudonly' flag is set.")

		} else if featureflag.DrainAndValidateRollingUpdate.Enabled() {

			if u.Node != nil {
				glog.Infof("Draining the node: %q.", nodeName)

				if err = r.DrainNode(u, rollingUpdateData); err != nil {
					if rollingUpdateData.FailOnDrainError {
						return fmt.Errorf("failed to drain node %q: %v", nodeName, err)
					} else {
						glog.Infof("Ignoring error draining node %q: %v", nodeName, err)
					}
				}
			} else {
				glog.Warningf("Skipping drain of instance %q, because it is not registered in kubernetes", instanceId)
			}
		}

		if err = r.DeleteInstance(u); err != nil {
			glog.Errorf("Error deleting aws instance %q, node %q: %v", instanceId, nodeName, err)
			return err
		}

		// Wait for the minimum interval
		time.Sleep(sleepAfterTerminate)

		if isBastion {
			glog.Infof("Deleted a bastion instance, %s, and continuing with rolling-update.", instanceId)

			continue
		} else if rollingUpdateData.CloudOnly {
			glog.Warningf("Not validating cluster as cloudonly flag is set.")
			continue

		} else if featureflag.DrainAndValidateRollingUpdate.Enabled() {
			glog.Infof("Validating the cluster.")

			if err = r.ValidateClusterWithDuration(rollingUpdateData, instanceGroupList, validationTimeout); err != nil {

				if rollingUpdateData.FailOnValidate {
					glog.Errorf("Cluster did not validate within %s", validationTimeout)
					return fmt.Errorf("error validating cluster after removing a node: %v", err)
				}

				glog.Warningf("Cluster validation failed after removing instance, proceeding since fail-on-validate is set to false: %v", err)
			}
		}
	}

	return nil
}

// ValidateClusterWithDuration runs validation.ValidateCluster until either we get positive result or the timeout expires
func (r *RollingUpdateInstanceGroup) ValidateClusterWithDuration(rollingUpdateData *RollingUpdateCluster, instanceGroupList *api.InstanceGroupList, duration time.Duration) error {
	// TODO should we expose this to the UI?
	tickDuration := 30 * time.Second
	// Try to validate cluster at least once, this will handle durations that are lower
	// than our tick time
	if r.tryValidateCluster(rollingUpdateData, instanceGroupList, duration, tickDuration) {
		return nil
	}

	timeout := time.After(duration)
	tick := time.Tick(tickDuration)
	// Keep trying until we're timed out or got a result or got an error
	for {
		select {
		case <-timeout:
			// Got a timeout fail with a timeout error
			return fmt.Errorf("cluster did not validate within a duation of %q", duration)
		case <-tick:
			// Got a tick, validate cluster
			if r.tryValidateCluster(rollingUpdateData, instanceGroupList, duration, tickDuration) {
				return nil
			}
			// ValidateCluster didn't work yet, so let's try again
			// this will exit up to the for loop
		}
	}
}

func (r *RollingUpdateInstanceGroup) tryValidateCluster(rollingUpdateData *RollingUpdateCluster, instanceGroupList *api.InstanceGroupList, duration time.Duration, tickDuration time.Duration) bool {
	if _, err := validation.ValidateCluster(rollingUpdateData.ClusterName, instanceGroupList, rollingUpdateData.K8sClient); err != nil {
		glog.Infof("Cluster did not validate, will try again in %q util duration %q expires: %v.", tickDuration, duration, err)
		return false
	} else {
		glog.Infof("Cluster validated.")
		return true
	}
}

// ValidateCluster runs our validation methods on the K8s Cluster.
func (r *RollingUpdateInstanceGroup) ValidateCluster(rollingUpdateData *RollingUpdateCluster, instanceGroupList *api.InstanceGroupList) error {

	if _, err := validation.ValidateCluster(rollingUpdateData.ClusterName, instanceGroupList, rollingUpdateData.K8sClient); err != nil {
		return fmt.Errorf("cluster %q did not pass validation: %v", rollingUpdateData.ClusterName, err)
	}

	return nil

}

// DeleteInstance deletes an Cloud Instance.
func (r *RollingUpdateInstanceGroup) DeleteInstance(u *cloudinstances.CloudInstanceGroupMember) error {

	id := u.ID
	nodeName := ""
	if u.Node != nil {
		nodeName = u.Node.Name
	}
	if nodeName != "" {
		glog.Infof("Stopping instance %q, node %q, in group %q.", id, nodeName, r.CloudGroup.HumanName)
	} else {
		glog.Infof("Stopping instance %q, in group %q.", id, r.CloudGroup.HumanName)
	}

	if err := r.Cloud.DeleteInstance(u); err != nil {
		if nodeName != "" {
			return fmt.Errorf("error deleting instance %q, node %q: %v", id, nodeName, err)
		} else {
			return fmt.Errorf("error deleting instance %q: %v", id, err)
		}
	}

	return nil

}

// DrainNode drains a K8s node.
func (r *RollingUpdateInstanceGroup) DrainNode(u *cloudinstances.CloudInstanceGroupMember, rollingUpdateData *RollingUpdateCluster) error {
	if rollingUpdateData.ClientConfig == nil {
		return fmt.Errorf("clientConfig not set")
	}

	if u.Node.Name == "" {
		return fmt.Errorf("node name not set")
	}
	f := cmdutil.NewFactory(rollingUpdateData.ClientConfig)

	// TODO: Send out somewhere else, also DrainOptions has errout
	out := os.Stdout
	errOut := os.Stderr

	options := &cmd.DrainOptions{
		Factory:          f,
		Out:              out,
		IgnoreDaemonsets: true,
		Force:            true,
		DeleteLocalData:  true,
		ErrOut:           errOut,
	}

	cmd := &cobra.Command{
		Use: "cordon NODE",
	}
	args := []string{u.Node.Name}
	err := options.SetupDrain(cmd, args)
	if err != nil {
		return fmt.Errorf("error setting up drain: %v", err)
	}

	err = options.RunCordonOrUncordon(true)
	if err != nil {
		return fmt.Errorf("error cordoning node node: %v", err)
	}

	err = options.RunDrain()
	if err != nil {
		return fmt.Errorf("error draining node: %v", err)
	}

	if rollingUpdateData.PostDrainDelay > 0 {
		glog.V(3).Infof("Waiting for %s for pods to stabilize after draining.", rollingUpdateData.PostDrainDelay)
		time.Sleep(rollingUpdateData.PostDrainDelay)
	}

	return nil
}

// Delete and CloudInstanceGroups
func (r *RollingUpdateInstanceGroup) Delete() error {
	if r.CloudGroup == nil {
		return fmt.Errorf("group has to be set")
	}
	// TODO: Leaving func in place in order to cordon nd drain nodes
	return r.Cloud.DeleteGroup(r.CloudGroup)
}
<commit_msg>Spelling fix in instancegroups.go error msg<commit_after>/*
Copyright 2016 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 instancegroups

import (
	"fmt"
	"os"
	"time"

	"github.com/golang/glog"
	"github.com/spf13/cobra"
	api "k8s.io/kops/pkg/apis/kops"
	"k8s.io/kops/pkg/cloudinstances"
	"k8s.io/kops/pkg/featureflag"
	"k8s.io/kops/pkg/validation"
	"k8s.io/kops/upup/pkg/fi"
	"k8s.io/kubernetes/pkg/kubectl/cmd"
	cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
)

// RollingUpdateInstanceGroup is the AWS ASG backing an InstanceGroup.
type RollingUpdateInstanceGroup struct {
	// Cloud is the kops cloud provider
	Cloud fi.Cloud
	// CloudGroup is the kops cloud provider groups
	CloudGroup *cloudinstances.CloudInstanceGroup

	// TODO should remove the need to have rollingupdate struct and add:
	// TODO - the kubernetes client
	// TODO - the cluster name
	// TODO - the client config
	// TODO - fail on validate
	// TODO - fail on drain
	// TODO - cloudonly
}

// NewRollingUpdateInstanceGroup create a new struct
func NewRollingUpdateInstanceGroup(cloud fi.Cloud, cloudGroup *cloudinstances.CloudInstanceGroup) (*RollingUpdateInstanceGroup, error) {
	if cloud == nil {
		return nil, fmt.Errorf("cloud provider is required")
	}
	if cloudGroup == nil {
		return nil, fmt.Errorf("cloud group is required")
	}

	// TODO check more values in cloudGroup that they are set properly

	return &RollingUpdateInstanceGroup{
		Cloud:      cloud,
		CloudGroup: cloudGroup,
	}, nil
}

// TODO: Temporarily increase size of ASG?
// TODO: Remove from ASG first so status is immediately updated?
// TODO: Batch termination, like a rolling-update

// RollingUpdate performs a rolling update on a list of ec2 instances.
func (r *RollingUpdateInstanceGroup) RollingUpdate(rollingUpdateData *RollingUpdateCluster, instanceGroupList *api.InstanceGroupList, isBastion bool, sleepAfterTerminate time.Duration, validationTimeout time.Duration) (err error) {

	// we should not get here, but hey I am going to check.
	if rollingUpdateData == nil {
		return fmt.Errorf("rollingUpdate cannot be nil")
	}

	// Do not need a k8s client if you are doing cloudonly.
	if rollingUpdateData.K8sClient == nil && !rollingUpdateData.CloudOnly {
		return fmt.Errorf("rollingUpdate is missing a k8s client")
	}

	if instanceGroupList == nil {
		return fmt.Errorf("rollingUpdate is missing the InstanceGroupList")
	}

	update := r.CloudGroup.NeedUpdate
	if rollingUpdateData.Force {
		update = append(update, r.CloudGroup.Ready...)
	}

	if len(update) == 0 {
		return nil
	}

	if isBastion {
		glog.V(3).Info("Not validating the cluster as instance is a bastion.")
	} else if rollingUpdateData.CloudOnly {
		glog.V(3).Info("Not validating cluster as validation is turned off via the cloud-only flag.")
	} else if featureflag.DrainAndValidateRollingUpdate.Enabled() {
		if err = r.ValidateCluster(rollingUpdateData, instanceGroupList); err != nil {
			if rollingUpdateData.FailOnValidate {
				return fmt.Errorf("error validating cluster: %v", err)
			} else {
				glog.V(2).Infof("Ignoring cluster validation error: %v", err)
				glog.Infof("Cluster validation failed, but proceeding since fail-on-validate-error is set to false")
			}
		}
	}

	for _, u := range update {
		instanceId := u.ID

		nodeName := ""
		if u.Node != nil {
			nodeName = u.Node.Name
		}

		if isBastion {
			// We don't want to validate for bastions - they aren't part of the cluster
		} else if rollingUpdateData.CloudOnly {

			glog.Warningf("Not draining cluster nodes as 'cloudonly' flag is set.")

		} else if featureflag.DrainAndValidateRollingUpdate.Enabled() {

			if u.Node != nil {
				glog.Infof("Draining the node: %q.", nodeName)

				if err = r.DrainNode(u, rollingUpdateData); err != nil {
					if rollingUpdateData.FailOnDrainError {
						return fmt.Errorf("failed to drain node %q: %v", nodeName, err)
					} else {
						glog.Infof("Ignoring error draining node %q: %v", nodeName, err)
					}
				}
			} else {
				glog.Warningf("Skipping drain of instance %q, because it is not registered in kubernetes", instanceId)
			}
		}

		if err = r.DeleteInstance(u); err != nil {
			glog.Errorf("Error deleting aws instance %q, node %q: %v", instanceId, nodeName, err)
			return err
		}

		// Wait for the minimum interval
		time.Sleep(sleepAfterTerminate)

		if isBastion {
			glog.Infof("Deleted a bastion instance, %s, and continuing with rolling-update.", instanceId)

			continue
		} else if rollingUpdateData.CloudOnly {
			glog.Warningf("Not validating cluster as cloudonly flag is set.")
			continue

		} else if featureflag.DrainAndValidateRollingUpdate.Enabled() {
			glog.Infof("Validating the cluster.")

			if err = r.ValidateClusterWithDuration(rollingUpdateData, instanceGroupList, validationTimeout); err != nil {

				if rollingUpdateData.FailOnValidate {
					glog.Errorf("Cluster did not validate within %s", validationTimeout)
					return fmt.Errorf("error validating cluster after removing a node: %v", err)
				}

				glog.Warningf("Cluster validation failed after removing instance, proceeding since fail-on-validate is set to false: %v", err)
			}
		}
	}

	return nil
}

// ValidateClusterWithDuration runs validation.ValidateCluster until either we get positive result or the timeout expires
func (r *RollingUpdateInstanceGroup) ValidateClusterWithDuration(rollingUpdateData *RollingUpdateCluster, instanceGroupList *api.InstanceGroupList, duration time.Duration) error {
	// TODO should we expose this to the UI?
	tickDuration := 30 * time.Second
	// Try to validate cluster at least once, this will handle durations that are lower
	// than our tick time
	if r.tryValidateCluster(rollingUpdateData, instanceGroupList, duration, tickDuration) {
		return nil
	}

	timeout := time.After(duration)
	tick := time.Tick(tickDuration)
	// Keep trying until we're timed out or got a result or got an error
	for {
		select {
		case <-timeout:
			// Got a timeout fail with a timeout error
			return fmt.Errorf("cluster did not validate within a duation of %q", duration)
		case <-tick:
			// Got a tick, validate cluster
			if r.tryValidateCluster(rollingUpdateData, instanceGroupList, duration, tickDuration) {
				return nil
			}
			// ValidateCluster didn't work yet, so let's try again
			// this will exit up to the for loop
		}
	}
}

func (r *RollingUpdateInstanceGroup) tryValidateCluster(rollingUpdateData *RollingUpdateCluster, instanceGroupList *api.InstanceGroupList, duration time.Duration, tickDuration time.Duration) bool {
	if _, err := validation.ValidateCluster(rollingUpdateData.ClusterName, instanceGroupList, rollingUpdateData.K8sClient); err != nil {
		glog.Infof("Cluster did not validate, will try again in %q until duration %q expires: %v.", tickDuration, duration, err)
		return false
	} else {
		glog.Infof("Cluster validated.")
		return true
	}
}

// ValidateCluster runs our validation methods on the K8s Cluster.
func (r *RollingUpdateInstanceGroup) ValidateCluster(rollingUpdateData *RollingUpdateCluster, instanceGroupList *api.InstanceGroupList) error {

	if _, err := validation.ValidateCluster(rollingUpdateData.ClusterName, instanceGroupList, rollingUpdateData.K8sClient); err != nil {
		return fmt.Errorf("cluster %q did not pass validation: %v", rollingUpdateData.ClusterName, err)
	}

	return nil

}

// DeleteInstance deletes an Cloud Instance.
func (r *RollingUpdateInstanceGroup) DeleteInstance(u *cloudinstances.CloudInstanceGroupMember) error {

	id := u.ID
	nodeName := ""
	if u.Node != nil {
		nodeName = u.Node.Name
	}
	if nodeName != "" {
		glog.Infof("Stopping instance %q, node %q, in group %q.", id, nodeName, r.CloudGroup.HumanName)
	} else {
		glog.Infof("Stopping instance %q, in group %q.", id, r.CloudGroup.HumanName)
	}

	if err := r.Cloud.DeleteInstance(u); err != nil {
		if nodeName != "" {
			return fmt.Errorf("error deleting instance %q, node %q: %v", id, nodeName, err)
		} else {
			return fmt.Errorf("error deleting instance %q: %v", id, err)
		}
	}

	return nil

}

// DrainNode drains a K8s node.
func (r *RollingUpdateInstanceGroup) DrainNode(u *cloudinstances.CloudInstanceGroupMember, rollingUpdateData *RollingUpdateCluster) error {
	if rollingUpdateData.ClientConfig == nil {
		return fmt.Errorf("clientConfig not set")
	}

	if u.Node.Name == "" {
		return fmt.Errorf("node name not set")
	}
	f := cmdutil.NewFactory(rollingUpdateData.ClientConfig)

	// TODO: Send out somewhere else, also DrainOptions has errout
	out := os.Stdout
	errOut := os.Stderr

	options := &cmd.DrainOptions{
		Factory:          f,
		Out:              out,
		IgnoreDaemonsets: true,
		Force:            true,
		DeleteLocalData:  true,
		ErrOut:           errOut,
	}

	cmd := &cobra.Command{
		Use: "cordon NODE",
	}
	args := []string{u.Node.Name}
	err := options.SetupDrain(cmd, args)
	if err != nil {
		return fmt.Errorf("error setting up drain: %v", err)
	}

	err = options.RunCordonOrUncordon(true)
	if err != nil {
		return fmt.Errorf("error cordoning node node: %v", err)
	}

	err = options.RunDrain()
	if err != nil {
		return fmt.Errorf("error draining node: %v", err)
	}

	if rollingUpdateData.PostDrainDelay > 0 {
		glog.V(3).Infof("Waiting for %s for pods to stabilize after draining.", rollingUpdateData.PostDrainDelay)
		time.Sleep(rollingUpdateData.PostDrainDelay)
	}

	return nil
}

// Delete and CloudInstanceGroups
func (r *RollingUpdateInstanceGroup) Delete() error {
	if r.CloudGroup == nil {
		return fmt.Errorf("group has to be set")
	}
	// TODO: Leaving func in place in order to cordon nd drain nodes
	return r.Cloud.DeleteGroup(r.CloudGroup)
}
<|endoftext|>


<commit_before>// Copyright Banrai LLC. All rights reserved. Use of this source code is
// governed by the license that can be found in the LICENSE file.

package emailer

import (
	"bytes"
	"encoding/base64"
	"fmt"
	"io/ioutil"
	"net/smtp"
	"os"
	"text/template"
)

const (
	MAIL_SERVER = "localhost"
	MAIL_PORT   = 25

	LINE_MAX_LEN = 500 // for splitting encoded attachment data

	// templates for generating the message components
	ADDRESS    = "\"{{.DisplayName}}\" <{{.Address}}>"
	HEADERS    = "From: {{.Sender}}\r\nTo: {{.Recipient}}\r\nSubject: {{.Subject}}\r\nMIME-Version: 1.0\r\nContent-Type: multipart/mixed; boundary=\"{{.Boundary}}\"\r\n"
	BODY       = "\r\n--{{.Boundary}}\r\nContent-Type: {{.ContentType}}\r\n\r\n{{.MessageBody}}"
	ATTACHMENT = "\r\n--{{.Boundary}}\r\nContent-Type: {{.ContentType}}; name=\"{{.FileLocation}}\"\r\nContent-Transfer-Encoding:base64\r\nContent-Disposition: attachment; filename=\"{{.FileName}}\"\r\n\r\n{{.EncodedFileData}}"

	// message body mime types
	TEXT_MIME = "text/plain"
	HTML_MIME = "text/html"
)

type EmailAddress struct {
	DisplayName string
	Address     string
}

type EmailHeaders struct {
	Sender    string
	Recipient string
	Subject   string
	Boundary  string
}

type EmailBody struct {
	ContentType string
	MessageBody string
	Boundary    string
}

type EmailAttachment struct {
	ContentType     string
	EncodedFileData string
	Boundary        string
	// read content from a file
	FileLocation string
	FileName     string
	// or provide it here
	Contents string
}

// GenerateBoundary produces a random string that can be used for the email
// multipart boundary marker
func GenerateBoundary() string {
	f, e := os.OpenFile("/dev/urandom", os.O_RDONLY, 0)
	defer f.Close()

	if e != nil {
		return ""
	} else {
		b := make([]byte, 16)
		f.Read(b)
		return fmt.Sprintf("%x", b)
	}
}

func GenerateAddress(context *EmailAddress) (string, error) {
	var doc bytes.Buffer
	t := template.Must(template.New("ADDRESS").Parse(ADDRESS))
	err := t.Execute(&doc, context)
	return doc.String(), err
}

func GenerateHeaders(sender, recipient, subject, boundary string) (string, error) {
	var doc bytes.Buffer
	context := &EmailHeaders{sender, recipient, subject, boundary}
	t := template.Must(template.New("HEADERS").Parse(HEADERS))
	err := t.Execute(&doc, context)
	return doc.String(), err
}

func GenerateBody(message, contentType, boundary string) (string, error) {
	var doc bytes.Buffer
	context := &EmailBody{contentType, message, boundary}
	t := template.Must(template.New("BODY").Parse(BODY))
	err := t.Execute(&doc, context)
	return doc.String(), err
}

func GenerateAttachment(attachment *EmailAttachment) (string, error) {
	var doc, buf bytes.Buffer
	var encoded string

	if attachment.Contents != "" {
		// content to encode is already included
		encoded = base64.StdEncoding.EncodeToString([]byte(attachment.Contents))
	} else {
		// read the content from the file attachment
		content, contentErr := ioutil.ReadFile(attachment.FileLocation)
		if contentErr != nil {
			return "", contentErr
		}
		encoded = base64.StdEncoding.EncodeToString(content)
	}

	// split the encoded data into individual lines
	// and append them to the byte buffer
	lines := len(encoded) / LINE_MAX_LEN
	for i := 0; i < lines; i++ {
		buf.WriteString(encoded[i*LINE_MAX_LEN:(i+1)*LINE_MAX_LEN] + "\n")
	}
	// don't forget the last line in the buffer
	buf.WriteString(encoded[lines*LINE_MAX_LEN:])
	attachment.EncodedFileData = buf.String()

	// can now process the template
	t := template.Must(template.New("ATTACHMENT").Parse(ATTACHMENT))
	err := t.Execute(&doc, attachment)
	return doc.String(), err
}

// SendFromServer transmits the given message, with optional attachments,
// via the defined mail server and port
func SendFromServer(subject, messageText, messageHtml, server string, sender, recipient *EmailAddress, attachments []*EmailAttachment, port int) error {
	var buf bytes.Buffer
	boundary := GenerateBoundary()

	from, fromErr := GenerateAddress(sender)
	if fromErr != nil {
		return fromErr
	}

	to, toErr := GenerateAddress(recipient)
	if toErr != nil {
		return toErr
	}

	hdr, hdrErr := GenerateHeaders(from, to, subject, boundary)
	if hdrErr != nil {
		return hdrErr
	}
	buf.WriteString(hdr)

	body, bodyErr := GenerateBody(messageText, TEXT_MIME, boundary)
	if bodyErr != nil {
		return bodyErr
	}
	buf.WriteString(body)

	// message body in html format is optional
	if len(messageHtml) > 0 {
		htmlBody, htmlBodyErr := GenerateBody(messageHtml, HTML_MIME, boundary)
		if htmlBodyErr != nil {
			return htmlBodyErr
		}
		buf.WriteString(htmlBody)
	}

	for _, a := range attachments {
		a.Boundary = boundary
		attach, attachErr := GenerateAttachment(a)
		if attachErr != nil {
			return attachErr
		}
		buf.WriteString(attach)
	}

	// add the closing boundary marker
	buf.WriteString("\r\n--")
	buf.WriteString(boundary)
	buf.WriteString("--")

	// connect to the mail server + port
	c, cErr := smtp.Dial(fmt.Sprintf("%s:%d", server, port))
	if cErr != nil {
		return cErr
	}

	// set the sender and recipient (raw email address strings)
	c.Mail(sender.Address)
	c.Rcpt(recipient.Address)

	// stream the full email data
	wc, err := c.Data()
	if err != nil {
		return err
	}
	defer wc.Close()

	_, err = buf.WriteTo(wc)
	return err
}

// Send transmits the given message, with optional attachments, via the
// default mail server (localhost) and port (25)
func Send(subject, messageText, messageHtml string, sender, recipient *EmailAddress, attachments []*EmailAttachment) error {
	return SendFromServer(subject, messageText, messageHtml, MAIL_SERVER, sender, recipient, attachments, MAIL_PORT)
}
<commit_msg>Embraced the embedded multipart/alternative layout as defined here: stackoverflow.com/a/40420648/633961<commit_after>// Copyright Banrai LLC. All rights reserved. Use of this source code is
// governed by the license that can be found in the LICENSE file.

package emailer

import (
	"bytes"
	"encoding/base64"
	"fmt"
	"io/ioutil"
	"net/smtp"
	"os"
	"text/template"
)

const (
	MAIL_SERVER = "localhost"
	MAIL_PORT   = 25

	LINE_MAX_LEN = 500 // for splitting encoded attachment data

	// templates for generating the message components
	ADDRESS    = "\"{{.DisplayName}}\" <{{.Address}}>"
	HEADERS    = "From: {{.Sender}}\r\nTo: {{.Recipient}}\r\nSubject: {{.Subject}}\r\nMIME-Version: 1.0\r\nContent-Type: multipart/mixed; boundary=\"{{.Boundary}}\"\r\n"
	BODY       = "\r\n--{{.Boundary}}\r\nContent-Type: multipart/related; boundary=\"{{.RelatedBoundary}}\"\r\n\r\n--{{.RelatedBoundary}}\r\nContent-Type: multipart/alternative; boundary=\"{{.AltBoundary}}\"\r\n\r\n--{{.AltBoundary}}\r\nContent-Type: text/plain\r\n\r\n{{.MessageText}}\r\n--{{.AltBoundary}}\r\nContent-Type: text/html\r\n\r\n{{.MessageHtml}}\r\n--{{.AltBoundary}}--\r\n--{{.RelatedBoundary}}--"
	ATTACHMENT = "\r\n--{{.Boundary}}\r\nContent-Type: {{.ContentType}}; name=\"{{.FileLocation}}\"\r\nContent-Transfer-Encoding:base64\r\nContent-Disposition: attachment; filename=\"{{.FileName}}\"\r\n\r\n{{.EncodedFileData}}"

	// message body mime types
	TEXT_MIME = "text/plain"
	HTML_MIME = "text/html"
)

type EmailAddress struct {
	DisplayName string
	Address     string
}

type EmailHeaders struct {
	Sender    string
	Recipient string
	Subject   string
	Boundary  string
}

type EmailBody struct {
	MessageText     string
	MessageHtml     string
	Boundary        string
	RelatedBoundary string
	AltBoundary     string
}

type EmailAttachment struct {
	ContentType     string
	EncodedFileData string
	Boundary        string
	// read content from a file
	FileLocation string
	FileName     string
	// or provide it here
	Contents string
}

// GenerateBoundary produces a random string that can be used for the email
// multipart boundary marker
func GenerateBoundary() string {
	f, e := os.OpenFile("/dev/urandom", os.O_RDONLY, 0)
	defer f.Close()

	if e != nil {
		return ""
	} else {
		b := make([]byte, 16)
		f.Read(b)
		return fmt.Sprintf("%x", b)
	}
}

func GenerateAddress(context *EmailAddress) (string, error) {
	var doc bytes.Buffer
	t := template.Must(template.New("ADDRESS").Parse(ADDRESS))
	err := t.Execute(&doc, context)
	return doc.String(), err
}

func GenerateHeaders(sender, recipient, subject, boundary string) (string, error) {
	var doc bytes.Buffer
	context := &EmailHeaders{sender, recipient, subject, boundary}
	t := template.Must(template.New("HEADERS").Parse(HEADERS))
	err := t.Execute(&doc, context)
	return doc.String(), err
}

func GenerateBody(messageText, messageHtml, boundary string) (string, error) {
	var doc bytes.Buffer
	context := &EmailBody{messageText, messageHtml, boundary, GenerateBoundary(), GenerateBoundary()}
	t := template.Must(template.New("BODY").Parse(BODY))
	err := t.Execute(&doc, context)
	return doc.String(), err
}

func GenerateAttachment(attachment *EmailAttachment) (string, error) {
	var doc, buf bytes.Buffer
	var encoded string

	if attachment.Contents != "" {
		// content to encode is already included
		encoded = base64.StdEncoding.EncodeToString([]byte(attachment.Contents))
	} else {
		// read the content from the file attachment
		content, contentErr := ioutil.ReadFile(attachment.FileLocation)
		if contentErr != nil {
			return "", contentErr
		}
		encoded = base64.StdEncoding.EncodeToString(content)
	}

	// split the encoded data into individual lines
	// and append them to the byte buffer
	lines := len(encoded) / LINE_MAX_LEN
	for i := 0; i < lines; i++ {
		buf.WriteString(encoded[i*LINE_MAX_LEN:(i+1)*LINE_MAX_LEN] + "\n")
	}
	// don't forget the last line in the buffer
	buf.WriteString(encoded[lines*LINE_MAX_LEN:])
	attachment.EncodedFileData = buf.String()

	// can now process the template
	t := template.Must(template.New("ATTACHMENT").Parse(ATTACHMENT))
	err := t.Execute(&doc, attachment)
	return doc.String(), err
}

// SendFromServer transmits the given message, with optional attachments,
// via the defined mail server and port
func SendFromServer(subject, messageText, messageHtml, server string, sender, recipient *EmailAddress, attachments []*EmailAttachment, port int) error {
	var buf bytes.Buffer
	boundary := GenerateBoundary()

	from, fromErr := GenerateAddress(sender)
	if fromErr != nil {
		return fromErr
	}

	to, toErr := GenerateAddress(recipient)
	if toErr != nil {
		return toErr
	}

	hdr, hdrErr := GenerateHeaders(from, to, subject, boundary)
	if hdrErr != nil {
		return hdrErr
	}
	buf.WriteString(hdr)

	body, bodyErr := GenerateBody(messageText, messageHtml, boundary)
	if bodyErr != nil {
		return bodyErr
	}
	buf.WriteString(body)

	for _, a := range attachments {
		a.Boundary = boundary
		attach, attachErr := GenerateAttachment(a)
		if attachErr != nil {
			return attachErr
		}
		buf.WriteString(attach)
	}

	// add the closing boundary marker
	buf.WriteString("\r\n--")
	buf.WriteString(boundary)
	buf.WriteString("--")

	// connect to the mail server + port
	c, cErr := smtp.Dial(fmt.Sprintf("%s:%d", server, port))
	if cErr != nil {
		return cErr
	}

	// set the sender and recipient (raw email address strings)
	c.Mail(sender.Address)
	c.Rcpt(recipient.Address)

	// stream the full email data
	wc, err := c.Data()
	if err != nil {
		return err
	}
	defer wc.Close()

	_, err = buf.WriteTo(wc)
	return err
}

// Send transmits the given message, with optional attachments, via the
// default mail server (localhost) and port (25)
func Send(subject, messageText, messageHtml string, sender, recipient *EmailAddress, attachments []*EmailAttachment) error {
	return SendFromServer(subject, messageText, messageHtml, MAIL_SERVER, sender, recipient, attachments, MAIL_PORT)
}
<|endoftext|>


<commit_before>package handler

import (
	"fmt"
	"net"
	"strings"
)

func HandleKill(conn net.Conn) {
	conn.Close()
}

func HandleHelo(message string, conn net.Conn) {
	// conn.LocalAddr() returns "10.62.0.117:8000" for example
	// serverInfo[0] = 10.62.0.117
	// serverInfo[1] = 8000
	serverInfo := strings.Split(conn.localAddr(), ":")
	conn.Write([]byte(fmt.Sprintf("%sIP:%s\nPort:%s\nStudentID:%s\n", message, serverInfo[0], serverInfo[1], "13321218")))
}

func HandleOther(conn net.Conn) {
	// stub methond
	fmt.Println(conn)
}
<commit_msg>Fixed casing issue<commit_after>package handler

import (
	"fmt"
	"net"
	"strings"
)

func HandleKill(conn net.Conn) {
	conn.Close()
}

func HandleHelo(message string, conn net.Conn) {
	// conn.LocalAddr() returns "10.62.0.117:8000" for example
	// serverInfo[0] = 10.62.0.117
	// serverInfo[1] = 8000
	serverInfo := strings.Split(conn.LocalAddr(), ":")
	conn.Write([]byte(fmt.Sprintf("%sIP:%s\nPort:%s\nStudentID:%s\n", message, serverInfo[0], serverInfo[1], "13321218")))
}

func HandleOther(conn net.Conn) {
	// stub methond
	fmt.Println(conn)
}
<|endoftext|>


<commit_before>package downloader

import (
	"fmt"
	"io"
	"io/ioutil"
	"launchpad.net/juju-core/log"
	"launchpad.net/tomb"
	"net/http"
	"os"
)

// TempDir holds the temporary directory used to
// write the URL download. If it is empty, the default
// temporary directory is used (see os.TempDir).
var TempDir string

// Status represents the status of a completed download.
type Status struct {
	// File holds the file that it has been downloaded to.
	File *os.File
	// Err describes any error encountered while downloading.
	Err error
}

// Download can download an archived directory from the network.
type Download struct {
	tomb tomb.Tomb
	done chan Status
}

// New returns a new Download instance downloading
// from the given URL.
func New(url string) *Download {
	d := &Download{
		done: make(chan Status),
	}
	go d.run(url)
	return d
}

// Stop stops any download that's in progress.
func (d *Download) Stop() {
	d.tomb.Kill(nil)
	d.tomb.Wait()
}

// Done returns a channel that receives a value when
// the download has completed.
func (d *Download) Done() <-chan Status {
	return d.done
}

func (d *Download) run(url string) {
	defer d.tomb.Done()
	file, err := download(url)
	if err != nil {
		err = fmt.Errorf("cannot download %q: %v", url, err)
	}
	status := Status{
		File: file,
		Err:  err,
	}
	select {
	case d.done <- status:
	case <-d.tomb.Dying():
		cleanTempFile(status.File)
	}
}

func download(url string) (file *os.File, err error) {
	tempFile, err := ioutil.TempFile(TempDir, "inprogress-")
	if err != nil {
		return nil, err
	}
	defer func() {
		if err != nil {
			cleanTempFile(tempFile)
		}
	}()
	// TODO(rog) make the download operation interruptible.
	resp, err := http.Get(url)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("bad http response %v", resp.Status)
	}
	_, err = io.Copy(tempFile, resp.Body)
	if err != nil {
		return nil, err
	}
	if _, err := tempFile.Seek(0, 0); err != nil {
		return nil, err
	}
	return tempFile, nil
}

func cleanTempFile(f *os.File) {
	if f != nil {
		f.Close()
		if err := os.Remove(f.Name()); err != nil {
			log.Printf("downloader: cannot remove temp file %q: %v", f.Name(), err)
		}
	}
}
<commit_msg>downloader: adjust comment<commit_after>package downloader

import (
	"fmt"
	"io"
	"io/ioutil"
	"launchpad.net/juju-core/log"
	"launchpad.net/tomb"
	"net/http"
	"os"
)

// TempDir holds the temporary directory used to
// write the URL download. If it is empty, the default
// temporary directory is used (see os.TempDir).
var TempDir string

// Status represents the status of a completed download.
type Status struct {
	// File holds the file that it has been downloaded to.
	File *os.File
	// Err describes any error encountered while downloading.
	Err error
}

// Download can download an archived directory from the network.
type Download struct {
	tomb tomb.Tomb
	done chan Status
}

// New returns a new Download instance downloading
// from the given URL.
func New(url string) *Download {
	d := &Download{
		done: make(chan Status),
	}
	go d.run(url)
	return d
}

// Stop stops any download that's in progress.
func (d *Download) Stop() {
	d.tomb.Kill(nil)
	d.tomb.Wait()
}

// Done returns a channel that receives a status when the download has
// completed.  It is the receiver's responsibility to close and remove
// the received file.
func (d *Download) Done() <-chan Status {
	return d.done
}

func (d *Download) run(url string) {
	defer d.tomb.Done()
	file, err := download(url)
	if err != nil {
		err = fmt.Errorf("cannot download %q: %v", url, err)
	}
	status := Status{
		File: file,
		Err:  err,
	}
	select {
	case d.done <- status:
	case <-d.tomb.Dying():
		cleanTempFile(status.File)
	}
}

func download(url string) (file *os.File, err error) {
	tempFile, err := ioutil.TempFile(TempDir, "inprogress-")
	if err != nil {
		return nil, err
	}
	defer func() {
		if err != nil {
			cleanTempFile(tempFile)
		}
	}()
	// TODO(rog) make the download operation interruptible.
	resp, err := http.Get(url)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("bad http response %v", resp.Status)
	}
	_, err = io.Copy(tempFile, resp.Body)
	if err != nil {
		return nil, err
	}
	if _, err := tempFile.Seek(0, 0); err != nil {
		return nil, err
	}
	return tempFile, nil
}

func cleanTempFile(f *os.File) {
	if f != nil {
		f.Close()
		if err := os.Remove(f.Name()); err != nil {
			log.Printf("downloader: cannot remove temp file %q: %v", f.Name(), err)
		}
	}
}
<|endoftext|>


<commit_before>package web

import (
	"encoding/json"
	"net/http"

	"github.com/Sirupsen/logrus"
	"github.com/demisto/alfred/domain"
	"github.com/demisto/slack"
)

func (ac *AppContext) work(w http.ResponseWriter, r *http.Request) {
	team := r.FormValue("t")
	file := r.FormValue("f")
	message := r.FormValue("m")
	channel := r.FormValue("c")
	if team == "" || file == "" && (message == "" || channel == "") {
		WriteError(w, ErrBadRequest)
		return
	}
	// Bot scope does not have file info and history permissions so we need to iterate users
	users, err := ac.r.TeamMembers(team)
	if err != nil {
		logrus.Warnf("Error loading team members - %v\n", err)
		WriteError(w, ErrInternalServer)
		return
	}
	var workReq *domain.WorkRequest
	for i := range users {
		if users[i].Status == domain.UserStatusActive {
			// The first one that can retrieve the info...
			s, err := slack.New(slack.SetToken(users[i].Token))
			if err != nil {
				logrus.Infof("Error creating Slack client for user %s (%s) - %v\n", users[i].ID, users[i].Name, err)
				continue
			}
			if file != "" {
				info, err := s.FileInfo(file, 0, 0)
				if err != nil {
					logrus.Infof("Error retrieving file info - %v\n", err)
					continue
				}
				workReq = &domain.WorkRequest{
					Type:       "file",
					File:       domain.File{URL: info.File.URL, Name: info.File.Name, Size: info.File.Size},
					ReplyQueue: ac.replyQueue,
					Context:    nil,
					Online:     true,
				}
				break
			} else {
				resp, err := s.History(channel, message, message, true, false, 1)
				if err != nil {
					logrus.Infof("Error retrieving message history - %v\n", err)
					continue
				}
				if len(resp.Messages) == 0 {
					logrus.Infof("Error retrieving message history - message %s not found on channel %s\n", message, channel)
					WriteError(w, ErrInternalServer)
					return
				}
				workReq = domain.WorkRequestFromMessage(&resp.Messages[0])
				workReq.ReplyQueue = ac.replyQueue
				workReq.Online = true
				break
			}
		}
	}
	if workReq == nil {
		logrus.Infof("Unable to find a suitable user with credentials for team %s\n", team)
		WriteError(w, ErrInternalServer)
		return
	}
	err = ac.q.PushWork(workReq)
	if err != nil {
		logrus.Warnf("Error pushing work - %v\n", err)
		WriteError(w, ErrInternalServer)
		return
	}
	workReply, err := ac.q.PopWorkReply(ac.replyQueue, 0)
	json.NewEncoder(w).Encode(workReply)
}

type messageCount struct {
	Count int `json:"count"`
}

// totalMessages we ever saw and handled
func (ac *AppContext) totalMessages(w http.ResponseWriter, r *http.Request) {
	cnt, err := ac.r.TotalMessages()
	if err != nil {
		panic(err)
	}
	json.NewEncoder(w).Encode(messageCount{cnt})
}
<commit_msg>For details, try the bot token first and then users<commit_after>package web

import (
	"encoding/json"
	"net/http"

	"github.com/Sirupsen/logrus"
	"github.com/demisto/alfred/domain"
	"github.com/demisto/slack"
)

func (ac *AppContext) work(w http.ResponseWriter, r *http.Request) {
	team := r.FormValue("t")
	file := r.FormValue("f")
	message := r.FormValue("m")
	channel := r.FormValue("c")
	if team == "" || file == "" && (message == "" || channel == "") {
		WriteError(w, ErrBadRequest)
		return
	}
	t, err := ac.r.Team(team)
	if err != nil {
		logrus.Warnf("Error loading team - %v\n", err)
		WriteError(w, ErrInternalServer)
		return
	}
	// Bot scope does not have file info and history permissions so we need to iterate users
	users, err := ac.r.TeamMembers(team)
	if err != nil {
		logrus.Warnf("Error loading team members - %v\n", err)
		WriteError(w, ErrInternalServer)
		return
	}
	users = append([]domain.User{{Name: "dbot", Token: t.BotToken, ID: t.BotUserID, Status: domain.UserStatusActive}}, users...)
	var workReq *domain.WorkRequest
	for i := range users {
		if users[i].Status == domain.UserStatusActive {
			// The first one that can retrieve the info...
			s, err := slack.New(slack.SetToken(users[i].Token))
			if err != nil {
				logrus.Infof("Error creating Slack client for user %s (%s) - %v\n", users[i].ID, users[i].Name, err)
				continue
			}
			if file != "" {
				info, err := s.FileInfo(file, 0, 0)
				if err != nil {
					logrus.Infof("Error retrieving file info - %v\n", err)
					continue
				}
				workReq = &domain.WorkRequest{
					Type:       "file",
					File:       domain.File{URL: info.File.URL, Name: info.File.Name, Size: info.File.Size},
					ReplyQueue: ac.replyQueue,
					Context:    nil,
					Online:     true,
				}
				break
			} else {
				resp, err := s.History(channel, message, message, true, false, 1)
				if err != nil {
					logrus.Infof("Error retrieving message history - %v\n", err)
					continue
				}
				if len(resp.Messages) == 0 {
					logrus.Infof("Error retrieving message history - message %s not found on channel %s\n", message, channel)
					WriteError(w, ErrInternalServer)
					return
				}
				workReq = domain.WorkRequestFromMessage(&resp.Messages[0])
				workReq.ReplyQueue = ac.replyQueue
				workReq.Online = true
				break
			}
		}
	}
	if workReq == nil {
		logrus.Infof("Unable to find a suitable user with credentials for team %s\n", team)
		WriteError(w, ErrInternalServer)
		return
	}
	err = ac.q.PushWork(workReq)
	if err != nil {
		logrus.Warnf("Error pushing work - %v\n", err)
		WriteError(w, ErrInternalServer)
		return
	}
	workReply, err := ac.q.PopWorkReply(ac.replyQueue, 0)
	json.NewEncoder(w).Encode(workReply)
}

type messageCount struct {
	Count int `json:"count"`
}

// totalMessages we ever saw and handled
func (ac *AppContext) totalMessages(w http.ResponseWriter, r *http.Request) {
	cnt, err := ac.r.TotalMessages()
	if err != nil {
		panic(err)
	}
	json.NewEncoder(w).Encode(messageCount{cnt})
}
<|endoftext|>


<commit_before>package memcached

import (
	"bufio"
	"encoding/binary"
	"errors"
	"fmt"
	"io"

	"github.com/dustin/gomemcached"
)

func HandleIO(s io.ReadWriteCloser, reqChannel chan gomemcached.MCRequest) {
	defer s.Close()
	for handleMessage(s, s, reqChannel) {
	}
}

func handleMessage(r io.Reader, w io.Writer, reqChannel chan gomemcached.MCRequest) (ret bool) {
	req, err := ReadPacket(r)
	if err != nil {
		return
	}

	req.ResponseChannel = make(chan gomemcached.MCResponse)
	reqChannel <- req
	res := <-req.ResponseChannel
	ret = !res.Fatal
	if ret {
		transmitResponse(w, req, res)
	}

	return
}

func ReadPacket(r io.Reader) (rv gomemcached.MCRequest, err error) {
	hdrBytes := make([]byte, gomemcached.HDR_LEN)
	bytesRead, err := io.ReadFull(r, hdrBytes)
	if err != nil {
		return
	}
	if bytesRead != gomemcached.HDR_LEN {
		panic("Expected to read full and didn't")
	}

	rv, err = grokHeader(hdrBytes)
	if err != nil {
		return
	}

	err = readContents(r, &rv)
	return
}

func readContents(s io.Reader, req *gomemcached.MCRequest) (err error) {
	err = readOb(s, req.Extras)
	if err != nil {
		return err
	}
	err = readOb(s, req.Key)
	if err != nil {
		return err
	}
	return readOb(s, req.Body)
}

func transmitResponse(s io.Writer, req gomemcached.MCRequest, res gomemcached.MCResponse) {
	o := bufio.NewWriter(s)
	writeByte(o, gomemcached.RES_MAGIC)
	writeByte(o, byte(req.Opcode))
	writeUint16(o, uint16(len(res.Key)))
	writeByte(o, uint8(len(res.Extras)))
	writeByte(o, 0)
	writeUint16(o, res.Status)
	writeUint32(o, uint32(len(res.Body))+
		uint32(len(res.Key))+
		uint32(len(res.Extras)))
	writeUint32(o, req.Opaque)
	writeUint64(o, res.Cas)
	writeBytes(o, res.Extras)
	writeBytes(o, res.Key)
	writeBytes(o, res.Body)
	o.Flush()
	return
}

func writeBytes(s *bufio.Writer, data []byte) error {
	if len(data) > 0 {
		written, err := s.Write(data)
		if err != nil {
			return err
		}
		if written != len(data) {
			panic("Expected a full write, but didn't get one")
		}
	}
	return nil
}

func writeByte(s *bufio.Writer, b byte) {
	data := make([]byte, 1)
	data[0] = b
	writeBytes(s, data)
}

func writeUint16(s *bufio.Writer, n uint16) {
	data := []byte{0, 0}
	binary.BigEndian.PutUint16(data, n)
	writeBytes(s, data)
}

func writeUint32(s *bufio.Writer, n uint32) {
	data := []byte{0, 0, 0, 0}
	binary.BigEndian.PutUint32(data, n)
	writeBytes(s, data)
}

func writeUint64(s *bufio.Writer, n uint64) {
	data := []byte{0, 0, 0, 0, 0, 0, 0, 0}
	binary.BigEndian.PutUint64(data, n)
	writeBytes(s, data)
}

func readOb(s io.Reader, buf []byte) error {
	x, err := io.ReadFull(s, buf)
	if err == nil && x != len(buf) {
		panic("Read full didn't")
	}
	return err
}

func grokHeader(hdrBytes []byte) (rv gomemcached.MCRequest, err error) {
	if hdrBytes[0] != gomemcached.REQ_MAGIC {
		return rv, errors.New(fmt.Sprintf("Bad magic: %x", hdrBytes[0]))
	}
	rv.Opcode = gomemcached.CommandCode(hdrBytes[1])
	rv.Key = make([]byte, binary.BigEndian.Uint16(hdrBytes[2:]))
	rv.Extras = make([]byte, hdrBytes[4])
	bodyLen := binary.BigEndian.Uint32(hdrBytes[8:]) - uint32(len(rv.Key)) - uint32(len(rv.Extras))
	rv.Body = make([]byte, bodyLen)
	rv.Opaque = binary.BigEndian.Uint32(hdrBytes[12:])
	rv.Cas = binary.BigEndian.Uint64(hdrBytes[16:])
	return rv, nil
}
<commit_msg>toplevel doc for mc_conn_handler.<commit_after>// Useful functions for building your own memcached server.
package memcached

import (
	"bufio"
	"encoding/binary"
	"errors"
	"fmt"
	"io"

	"github.com/dustin/gomemcached"
)

func HandleIO(s io.ReadWriteCloser, reqChannel chan gomemcached.MCRequest) {
	defer s.Close()
	for handleMessage(s, s, reqChannel) {
	}
}

func handleMessage(r io.Reader, w io.Writer, reqChannel chan gomemcached.MCRequest) (ret bool) {
	req, err := ReadPacket(r)
	if err != nil {
		return
	}

	req.ResponseChannel = make(chan gomemcached.MCResponse)
	reqChannel <- req
	res := <-req.ResponseChannel
	ret = !res.Fatal
	if ret {
		transmitResponse(w, req, res)
	}

	return
}

func ReadPacket(r io.Reader) (rv gomemcached.MCRequest, err error) {
	hdrBytes := make([]byte, gomemcached.HDR_LEN)
	bytesRead, err := io.ReadFull(r, hdrBytes)
	if err != nil {
		return
	}
	if bytesRead != gomemcached.HDR_LEN {
		panic("Expected to read full and didn't")
	}

	rv, err = grokHeader(hdrBytes)
	if err != nil {
		return
	}

	err = readContents(r, &rv)
	return
}

func readContents(s io.Reader, req *gomemcached.MCRequest) (err error) {
	err = readOb(s, req.Extras)
	if err != nil {
		return err
	}
	err = readOb(s, req.Key)
	if err != nil {
		return err
	}
	return readOb(s, req.Body)
}

func transmitResponse(s io.Writer, req gomemcached.MCRequest, res gomemcached.MCResponse) {
	o := bufio.NewWriter(s)
	writeByte(o, gomemcached.RES_MAGIC)
	writeByte(o, byte(req.Opcode))
	writeUint16(o, uint16(len(res.Key)))
	writeByte(o, uint8(len(res.Extras)))
	writeByte(o, 0)
	writeUint16(o, res.Status)
	writeUint32(o, uint32(len(res.Body))+
		uint32(len(res.Key))+
		uint32(len(res.Extras)))
	writeUint32(o, req.Opaque)
	writeUint64(o, res.Cas)
	writeBytes(o, res.Extras)
	writeBytes(o, res.Key)
	writeBytes(o, res.Body)
	o.Flush()
	return
}

func writeBytes(s *bufio.Writer, data []byte) error {
	if len(data) > 0 {
		written, err := s.Write(data)
		if err != nil {
			return err
		}
		if written != len(data) {
			panic("Expected a full write, but didn't get one")
		}
	}
	return nil
}

func writeByte(s *bufio.Writer, b byte) {
	data := make([]byte, 1)
	data[0] = b
	writeBytes(s, data)
}

func writeUint16(s *bufio.Writer, n uint16) {
	data := []byte{0, 0}
	binary.BigEndian.PutUint16(data, n)
	writeBytes(s, data)
}

func writeUint32(s *bufio.Writer, n uint32) {
	data := []byte{0, 0, 0, 0}
	binary.BigEndian.PutUint32(data, n)
	writeBytes(s, data)
}

func writeUint64(s *bufio.Writer, n uint64) {
	data := []byte{0, 0, 0, 0, 0, 0, 0, 0}
	binary.BigEndian.PutUint64(data, n)
	writeBytes(s, data)
}

func readOb(s io.Reader, buf []byte) error {
	x, err := io.ReadFull(s, buf)
	if err == nil && x != len(buf) {
		panic("Read full didn't")
	}
	return err
}

func grokHeader(hdrBytes []byte) (rv gomemcached.MCRequest, err error) {
	if hdrBytes[0] != gomemcached.REQ_MAGIC {
		return rv, errors.New(fmt.Sprintf("Bad magic: %x", hdrBytes[0]))
	}
	rv.Opcode = gomemcached.CommandCode(hdrBytes[1])
	rv.Key = make([]byte, binary.BigEndian.Uint16(hdrBytes[2:]))
	rv.Extras = make([]byte, hdrBytes[4])
	bodyLen := binary.BigEndian.Uint32(hdrBytes[8:]) - uint32(len(rv.Key)) - uint32(len(rv.Extras))
	rv.Body = make([]byte, bodyLen)
	rv.Opaque = binary.BigEndian.Uint32(hdrBytes[12:])
	rv.Cas = binary.BigEndian.Uint64(hdrBytes[16:])
	return rv, nil
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"fmt"

	"github.com/sirupsen/logrus"
	"github.com/stampzilla/stampzilla-go/nodes/stampzilla-server/models/devices"
	"github.com/stampzilla/stampzilla-go/pkg/node"
)

func main() {
	node := node.New("example")

	node.OnConfig(updatedConfig)

	dev1 := &devices.Device{
		Name:   "Device1",
		Type:   "light",
		ID:     devices.ID{ID: "1"},
		Online: true,
		Traits: []string{"OnOff"},
		State: devices.State{
			"on": false,
		},
	}
	dev2 := &devices.Device{
		Name:   "Device2",
		Type:   "light",
		ID:     devices.ID{ID: "2"},
		Online: true,
		Traits: []string{"OnOff", "Brightness", "ColorSetting"},
		State: devices.State{
			"on": false,
		},
	}
	dev3 := &devices.Device{
		Name:   "Device3",
		Type:   "light",
		ID:     devices.ID{ID: "3"},
		Online: true,
		State: devices.State{
			"on": false,
		},
	}
	dev4 := &devices.Device{
		Name:   "Device4 that requires a node config",
		Type:   "light",
		ID:     devices.ID{ID: "4"},
		Online: true,
		Traits: []string{"OnOff"},
		State: devices.State{
			"on": false,
		},
	}

	node.OnRequestStateChange(func(state devices.State, device *devices.Device) error {
		logrus.Info("OnRequestStateChange:", state, device.ID)

		// Make device 3 follow the value of device 1
		if device.ID.ID == "1" {
			dev3.State["on"] = state["on"]
			node.AddOrUpdate(dev3)
		}

		// Load device config from the config struct
		devConfig, ok := config.Devices[device.ID.ID]

		// Require a device config for node 4 only
		if !ok && device.ID.ID == "4" {
			return fmt.Errorf("Foudn no config for device %s", device.ID)
		}

		state.Bool("on", func(on bool) {
			if on {
				fmt.Printf("turning on %s with senderid %s\n", device.ID.String(), devConfig.SenderID)
				return
			}
			fmt.Printf("turning off %s with senderid %s\n", device.ID.String(), devConfig.SenderID)
		})

		state.Float("brightness", func(lvl float64) {
			fmt.Printf("dimming to %f on device %s\n", lvl, device.ID.String())
		})

		return nil
	})

	err := node.Connect()

	if err != nil {
		logrus.Error(err)
		return
	}

	node.AddOrUpdate(dev1)
	node.AddOrUpdate(dev2)
	node.AddOrUpdate(dev3)

	err = node.AddOrUpdate(dev4)
	if err != nil {
		logrus.Error(err)
	}

	node.Wait()
}

var config = &Config{}

func updatedConfig(data json.RawMessage) error {
	logrus.Info("Received config from server:", string(data))

	newConf := &Config{}
	err := json.Unmarshal(data, newConf)
	if err != nil {
		return err
	}

	// example when we change "global" config
	if newConf.GatewayIP != config.GatewayIP {
		fmt.Println("ip changed. lets connect to that instead")
	}

	config = newConf
	logrus.Info("Config is now: ", config)

	return nil
}

type Config struct {
	Devices map[string]struct {
		SenderID string
		RecvEEPs []string // example config taken from enocean node
	}
	GatewayIP string
}

/*
Config to put into gui:
{
	"devices":{
		"1":{
			"senderid":"senderid1",
			"recveeps":[
				"asdf1",
				"asdf2"
			]
		},
		"2":{
			"senderid":"senderid1",
			"recveeps":[
				"asdf1",
				"asdf2"
			]
		}
	}
}

*/
<commit_msg>Fixed merge bug in stampzilla-example<commit_after>package main

import (
	"encoding/json"
	"fmt"

	"github.com/sirupsen/logrus"
	"github.com/stampzilla/stampzilla-go/nodes/stampzilla-server/models/devices"
	"github.com/stampzilla/stampzilla-go/pkg/node"
)

func main() {
	node := node.New("example")

	node.OnConfig(updatedConfig)

	dev1 := &devices.Device{
		Name:   "Device1",
		Type:   "light",
		ID:     devices.ID{ID: "1"},
		Online: true,
		Traits: []string{"OnOff"},
		State: devices.State{
			"on": false,
		},
	}
	dev2 := &devices.Device{
		Name:   "Device2",
		Type:   "light",
		ID:     devices.ID{ID: "2"},
		Online: true,
		Traits: []string{"OnOff", "Brightness", "ColorSetting"},
		State: devices.State{
			"on": false,
		},
	}
	dev3 := &devices.Device{
		Name:   "Device3",
		Type:   "light",
		ID:     devices.ID{ID: "3"},
		Online: true,
		State: devices.State{
			"on": false,
		},
	}
	dev4 := &devices.Device{
		Name:   "Device4 that requires a node config",
		Type:   "light",
		ID:     devices.ID{ID: "4"},
		Online: true,
		Traits: []string{"OnOff"},
		State: devices.State{
			"on": false,
		},
	}

	node.OnRequestStateChange(func(state devices.State, device *devices.Device) error {
		logrus.Info("OnRequestStateChange:", state, device.ID)

		// Make device 3 follow the value of device 1
		if device.ID.ID == "1" {
			dev3.State["on"] = state["on"]
			node.AddOrUpdate(dev3)
		}

		// Load device config from the config struct
		devConfig, ok := config.Devices[device.ID.ID]

		// Require a device config for node 4 only
		if !ok && device.ID.ID == "4" {
			return fmt.Errorf("Foudn no config for device %s", device.ID)
		}

		state.Bool("on", func(on bool) {
			if on {
				fmt.Printf("turning on %s with senderid %s\n", device.ID.String(), devConfig.SenderID)
				return
			}
			fmt.Printf("turning off %s with senderid %s\n", device.ID.String(), devConfig.SenderID)
		})

		state.Float("brightness", func(lvl float64) {
			fmt.Printf("dimming to %f on device %s\n", lvl, device.ID.String())
		})

		return nil
	})

	err := node.Connect()

	if err != nil {
		logrus.Error(err)
		return
	}

	node.AddOrUpdate(dev1)
	node.AddOrUpdate(dev2)
	node.AddOrUpdate(dev3)
	node.AddOrUpdate(dev4)

	node.Wait()
}

var config = &Config{}

func updatedConfig(data json.RawMessage) error {
	logrus.Info("Received config from server:", string(data))

	newConf := &Config{}
	err := json.Unmarshal(data, newConf)
	if err != nil {
		return err
	}

	// example when we change "global" config
	if newConf.GatewayIP != config.GatewayIP {
		fmt.Println("ip changed. lets connect to that instead")
	}

	config = newConf
	logrus.Info("Config is now: ", config)

	return nil
}

type Config struct {
	Devices map[string]struct {
		SenderID string
		RecvEEPs []string // example config taken from enocean node
	}
	GatewayIP string
}

/*
Config to put into gui:
{
	"devices":{
		"1":{
			"senderid":"senderid1",
			"recveeps":[
				"asdf1",
				"asdf2"
			]
		},
		"2":{
			"senderid":"senderid1",
			"recveeps":[
				"asdf1",
				"asdf2"
			]
		}
	}
}

*/
<|endoftext|>