<commit_before>/*
Copyright 2019 The Skaffold 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 config

import (
	"path/filepath"
	"strings"
	"testing"

	"gopkg.in/yaml.v2"

	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/util"
	"github.com/GoogleContainerTools/skaffold/testutil"
)

func TestReadConfig(t *testing.T) {
	baseConfig := &GlobalConfig{
		Global: &ContextConfig{
			DefaultRepo: "test-repository",
		},
		ContextConfigs: []*ContextConfig{
			{
				Kubecontext:        "test-context",
				InsecureRegistries: []string{"bad.io", "worse.io"},
				LocalCluster:       util.BoolPtr(true),
				DefaultRepo:        "context-local-repository",
			},
		},
	}

	tests := []struct {
		description string
		filename    string
		expectedCfg *GlobalConfig
		content     *GlobalConfig
	}{
		{
			description: "first read",
			filename:    "config",
			content:     baseConfig,
			expectedCfg: baseConfig,
		},
		{
			description: "second run uses cached result",
			expectedCfg: baseConfig,
		},
	}
	for _, test := range tests {
		testutil.Run(t, test.description, func(t *testutil.T) {
			tmpDir := t.NewTempDir().
				Chdir()

			if test.content != nil {
				c, _ := yaml.Marshal(*test.content)
				tmpDir.Write(test.filename, string(c))
			}

			cfg, err := ReadConfigFile(test.filename)

			t.CheckErrorAndDeepEqual(false, err, test.expectedCfg, cfg)
		})
	}
}

func TestResolveConfigFile(t *testing.T) {
	testutil.Run(t, "", func(t *testutil.T) {
		actual, err := ResolveConfigFile("")
		t.CheckNoError(err)
		suffix := filepath.FromSlash(".skaffold/config")
		if !strings.HasSuffix(actual, suffix) {
			t.Errorf("expecting %q to have suffix %q", actual, suffix)
		}
	})

	testutil.Run(t, "", func(t *testutil.T) {
		cfg := t.TempFile("givenConfigurationFile", nil)
		actual, err := ResolveConfigFile(cfg)
		t.CheckErrorAndDeepEqual(false, err, cfg, actual)
	})
}

func Test_getConfigForKubeContextWithGlobalDefaults(t *testing.T) {
	const someKubeContext = "this_is_a_context"
	sampleConfig1 := &ContextConfig{
		Kubecontext:        someKubeContext,
		InsecureRegistries: []string{"bad.io", "worse.io"},
		LocalCluster:       util.BoolPtr(true),
		DefaultRepo:        "my-private-registry",
	}
	sampleConfig2 := &ContextConfig{
		Kubecontext:        "another_context",
		InsecureRegistries: []string{"good.io", "better.io"},
		LocalCluster:       util.BoolPtr(false),
		DefaultRepo:        "my-public-registry",
	}

	tests := []struct {
		name           string
		kubecontext    string
		cfg            *GlobalConfig
		expectedConfig *ContextConfig
	}{
		{
			name: "global config when kubecontext is empty",
			cfg: &GlobalConfig{
				Global: &ContextConfig{
					InsecureRegistries: []string{"mediocre.io"},
					LocalCluster:       util.BoolPtr(true),
					DefaultRepo:        "my-private-registry",
				},
				ContextConfigs: []*ContextConfig{
					{
						Kubecontext: someKubeContext,
						DefaultRepo: "value",
					},
				},
			},
			expectedConfig: &ContextConfig{
				InsecureRegistries: []string{"mediocre.io"},
				LocalCluster:       util.BoolPtr(true),
				DefaultRepo:        "my-private-registry",
			},
		},
		{
			name:           "no global config and no kubecontext",
			cfg:            &GlobalConfig{},
			expectedConfig: &ContextConfig{},
		},
		{
			name:        "config for unknown kubecontext",
			kubecontext: someKubeContext,
			cfg:         &GlobalConfig{},
			expectedConfig: &ContextConfig{
				Kubecontext: someKubeContext,
			},
		},
		{
			name:        "config for kubecontext when globals are empty",
			kubecontext: someKubeContext,
			cfg: &GlobalConfig{
				ContextConfigs: []*ContextConfig{sampleConfig2, sampleConfig1},
			},
			expectedConfig: sampleConfig1,
		},
		{
			name:        "config for kubecontext without merged values",
			kubecontext: someKubeContext,
			cfg: &GlobalConfig{
				Global:         sampleConfig2,
				ContextConfigs: []*ContextConfig{sampleConfig1},
			},
			expectedConfig: sampleConfig1,
		},
		{
			name:        "config for kubecontext with merged values",
			kubecontext: someKubeContext,
			cfg: &GlobalConfig{
				Global: sampleConfig2,
				ContextConfigs: []*ContextConfig{
					{
						Kubecontext: someKubeContext,
					},
				},
			},
			expectedConfig: &ContextConfig{
				Kubecontext:        someKubeContext,
				InsecureRegistries: []string{"good.io", "better.io"},
				LocalCluster:       util.BoolPtr(false),
				DefaultRepo:        "my-public-registry",
			},
		},
		{
			name:        "config for unknown kubecontext with merged values",
			kubecontext: someKubeContext,
			cfg:         &GlobalConfig{Global: sampleConfig2},
			expectedConfig: &ContextConfig{
				Kubecontext:        someKubeContext,
				InsecureRegistries: []string{"good.io", "better.io"},
				LocalCluster:       util.BoolPtr(false),
				DefaultRepo:        "my-public-registry",
			},
		},
		/* todo(corneliusweig): this behavior can be enabled with `mergo.WithAppendSlice` -> clarify requirements
		{
			name:        "merge global and context-specific insecure-registries",
			kubecontext: someKubeContext,
			cfg: &GlobalConfig{
				Global:         &ContextConfig{
					InsecureRegistries: []string{"good.io", "better.io"},
				},
				ContextConfigs: []*ContextConfig{{
					Kubecontext: someKubeContext,
					InsecureRegistries: []string{"bad.io", "worse.io"},
				}},
			},
			expectedConfig: &ContextConfig{
				Kubecontext:        someKubeContext,
				InsecureRegistries: []string{"bad.io", "worse.io", "good.io", "better.io"},
			},
		},*/
	}
	for _, test := range tests {
		testutil.Run(t, test.name, func(t *testutil.T) {
			actual, err := getConfigForKubeContextWithGlobalDefaults(test.cfg, test.kubecontext)
			t.CheckErrorAndDeepEqual(false, err, test.expectedConfig, actual)
		})
	}
}
<commit_msg>Remove commented test-case<commit_after>/*
Copyright 2019 The Skaffold 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 config

import (
	"path/filepath"
	"strings"
	"testing"

	"gopkg.in/yaml.v2"

	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/util"
	"github.com/GoogleContainerTools/skaffold/testutil"
)

func TestReadConfig(t *testing.T) {
	baseConfig := &GlobalConfig{
		Global: &ContextConfig{
			DefaultRepo: "test-repository",
		},
		ContextConfigs: []*ContextConfig{
			{
				Kubecontext:        "test-context",
				InsecureRegistries: []string{"bad.io", "worse.io"},
				LocalCluster:       util.BoolPtr(true),
				DefaultRepo:        "context-local-repository",
			},
		},
	}

	tests := []struct {
		description string
		filename    string
		expectedCfg *GlobalConfig
		content     *GlobalConfig
	}{
		{
			description: "first read",
			filename:    "config",
			content:     baseConfig,
			expectedCfg: baseConfig,
		},
		{
			description: "second run uses cached result",
			expectedCfg: baseConfig,
		},
	}
	for _, test := range tests {
		testutil.Run(t, test.description, func(t *testutil.T) {
			tmpDir := t.NewTempDir().
				Chdir()

			if test.content != nil {
				c, _ := yaml.Marshal(*test.content)
				tmpDir.Write(test.filename, string(c))
			}

			cfg, err := ReadConfigFile(test.filename)

			t.CheckErrorAndDeepEqual(false, err, test.expectedCfg, cfg)
		})
	}
}

func TestResolveConfigFile(t *testing.T) {
	testutil.Run(t, "", func(t *testutil.T) {
		actual, err := ResolveConfigFile("")
		t.CheckNoError(err)
		suffix := filepath.FromSlash(".skaffold/config")
		if !strings.HasSuffix(actual, suffix) {
			t.Errorf("expecting %q to have suffix %q", actual, suffix)
		}
	})

	testutil.Run(t, "", func(t *testutil.T) {
		cfg := t.TempFile("givenConfigurationFile", nil)
		actual, err := ResolveConfigFile(cfg)
		t.CheckErrorAndDeepEqual(false, err, cfg, actual)
	})
}

func Test_getConfigForKubeContextWithGlobalDefaults(t *testing.T) {
	const someKubeContext = "this_is_a_context"
	sampleConfig1 := &ContextConfig{
		Kubecontext:        someKubeContext,
		InsecureRegistries: []string{"bad.io", "worse.io"},
		LocalCluster:       util.BoolPtr(true),
		DefaultRepo:        "my-private-registry",
	}
	sampleConfig2 := &ContextConfig{
		Kubecontext:        "another_context",
		InsecureRegistries: []string{"good.io", "better.io"},
		LocalCluster:       util.BoolPtr(false),
		DefaultRepo:        "my-public-registry",
	}

	tests := []struct {
		name           string
		kubecontext    string
		cfg            *GlobalConfig
		expectedConfig *ContextConfig
	}{
		{
			name: "global config when kubecontext is empty",
			cfg: &GlobalConfig{
				Global: &ContextConfig{
					InsecureRegistries: []string{"mediocre.io"},
					LocalCluster:       util.BoolPtr(true),
					DefaultRepo:        "my-private-registry",
				},
				ContextConfigs: []*ContextConfig{
					{
						Kubecontext: someKubeContext,
						DefaultRepo: "value",
					},
				},
			},
			expectedConfig: &ContextConfig{
				InsecureRegistries: []string{"mediocre.io"},
				LocalCluster:       util.BoolPtr(true),
				DefaultRepo:        "my-private-registry",
			},
		},
		{
			name:           "no global config and no kubecontext",
			cfg:            &GlobalConfig{},
			expectedConfig: &ContextConfig{},
		},
		{
			name:        "config for unknown kubecontext",
			kubecontext: someKubeContext,
			cfg:         &GlobalConfig{},
			expectedConfig: &ContextConfig{
				Kubecontext: someKubeContext,
			},
		},
		{
			name:        "config for kubecontext when globals are empty",
			kubecontext: someKubeContext,
			cfg: &GlobalConfig{
				ContextConfigs: []*ContextConfig{sampleConfig2, sampleConfig1},
			},
			expectedConfig: sampleConfig1,
		},
		{
			name:        "config for kubecontext without merged values",
			kubecontext: someKubeContext,
			cfg: &GlobalConfig{
				Global:         sampleConfig2,
				ContextConfigs: []*ContextConfig{sampleConfig1},
			},
			expectedConfig: sampleConfig1,
		},
		{
			name:        "config for kubecontext with merged values",
			kubecontext: someKubeContext,
			cfg: &GlobalConfig{
				Global: sampleConfig2,
				ContextConfigs: []*ContextConfig{
					{
						Kubecontext: someKubeContext,
					},
				},
			},
			expectedConfig: &ContextConfig{
				Kubecontext:        someKubeContext,
				InsecureRegistries: []string{"good.io", "better.io"},
				LocalCluster:       util.BoolPtr(false),
				DefaultRepo:        "my-public-registry",
			},
		},
		{
			name:        "config for unknown kubecontext with merged values",
			kubecontext: someKubeContext,
			cfg:         &GlobalConfig{Global: sampleConfig2},
			expectedConfig: &ContextConfig{
				Kubecontext:        someKubeContext,
				InsecureRegistries: []string{"good.io", "better.io"},
				LocalCluster:       util.BoolPtr(false),
				DefaultRepo:        "my-public-registry",
			},
		},
	}
	for _, test := range tests {
		testutil.Run(t, test.name, func(t *testutil.T) {
			actual, err := getConfigForKubeContextWithGlobalDefaults(test.cfg, test.kubecontext)
			t.CheckErrorAndDeepEqual(false, err, test.expectedConfig, actual)
		})
	}
}
<|endoftext|>


<commit_before>package donut

import (
	"bytes"
	"encoding/json"
	"errors"
	"io"
	"io/ioutil"
	"log"
	"os"
	"path"
	"sort"
	"strconv"
	"strings"
	"time"

	"github.com/minio-io/minio/pkg/encoding/erasure"
	"github.com/minio-io/minio/pkg/utils/split"
)

type donutDriver struct {
	buckets map[string]Bucket
	nodes   map[string]Node
}

// NewDonutDriver - instantiate new donut driver
func NewDonutDriver(root string) Donut {
	nodes := make(map[string]Node)
	nodes["localhost"] = localDirectoryNode{root: root}
	driver := donutDriver{
		buckets: make(map[string]Bucket),
		nodes:   nodes,
	}
	return driver
}

func (driver donutDriver) CreateBucket(bucketName string) error {
	if _, ok := driver.buckets[bucketName]; ok == false {
		bucketName = strings.TrimSpace(bucketName)
		if bucketName == "" {
			return errors.New("Cannot create bucket with no name")
		}
		// assign nodes
		// TODO assign other nodes
		nodes := make([]string, 16)
		for i := 0; i < 16; i++ {
			nodes[i] = "localhost"
		}
		bucket := bucketDriver{
			nodes: nodes,
		}
		driver.buckets[bucketName] = bucket
		return nil
	}
	return errors.New("Bucket exists")
}

func (driver donutDriver) ListBuckets() ([]string, error) {
	var buckets []string
	for bucket := range driver.buckets {
		buckets = append(buckets, bucket)
	}
	sort.Strings(buckets)
	return buckets, nil
}

func (driver donutDriver) GetObjectWriter(bucketName, objectName string) (ObjectWriter, error) {
	if bucket, ok := driver.buckets[bucketName]; ok == true {
		writers := make([]Writer, 16)
		nodes, err := bucket.GetNodes()
		if err != nil {
			return nil, err
		}
		for i, nodeID := range nodes {
			if node, ok := driver.nodes[nodeID]; ok == true {
				writer, _ := node.GetWriter(bucketName+":0:"+strconv.Itoa(i), objectName)
				writers[i] = writer
			}
		}
		return newErasureWriter(writers), nil
	}
	return nil, errors.New("Bucket not found")
}

func (driver donutDriver) GetObject(bucketName, objectName string) (io.ReadCloser, error) {
	r, w := io.Pipe()
	if bucket, ok := driver.buckets[bucketName]; ok == true {
		readers := make([]io.ReadCloser, 16)
		nodes, err := bucket.GetNodes()
		if err != nil {
			return nil, err
		}
		var metadata map[string]string
		for i, nodeID := range nodes {
			if node, ok := driver.nodes[nodeID]; ok == true {
				bucketID := bucketName + ":0:" + strconv.Itoa(i)
				reader, err := node.GetReader(bucketID, objectName)
				if err != nil {
					return nil, err
				}
				readers[i] = reader
				if metadata == nil {
					metadata, err = node.GetDonutMetadata(bucketID, objectName)
					if err != nil {
						return nil, err
					}
				}
			}
		}
		go erasureReader(readers, metadata, w)
		return r, nil
	}
	return nil, errors.New("Bucket not found")
}

// GetObjectMetadata returns metadata for a given object in a bucket
func (driver donutDriver) GetObjectMetadata(bucketName, object string) (map[string]string, error) {
	if bucket, ok := driver.buckets[bucketName]; ok {
		nodes, err := bucket.GetNodes()
		if err != nil {
			return nil, err
		}
		if node, ok := driver.nodes[nodes[0]]; ok {
			return node.GetMetadata(bucketName+":0:0", object)
		}
		return nil, errors.New("Cannot connect to node: " + nodes[0])
	}
	return nil, errors.New("Bucket not found")
}

func erasureReader(readers []io.ReadCloser, donutMetadata map[string]string, writer *io.PipeWriter) {
	totalChunks, _ := strconv.Atoi(donutMetadata["chunkCount"])
	totalLeft, _ := strconv.Atoi(donutMetadata["totalLength"])
	blockSize, _ := strconv.Atoi(donutMetadata["blockSize"])
	params, _ := erasure.ParseEncoderParams(8, 8, erasure.Cauchy)
	encoder := erasure.NewEncoder(params)
	for _, reader := range readers {
		defer reader.Close()
	}
	for i := 0; i < totalChunks; i++ {
		encodedBytes := make([][]byte, 16)
		for i, reader := range readers {
			var bytesBuffer bytes.Buffer
			io.Copy(&bytesBuffer, reader)
			encodedBytes[i] = bytesBuffer.Bytes()
		}
		curBlockSize := totalLeft
		if blockSize < totalLeft {
			curBlockSize = blockSize
		}
		log.Println("decoding block size", curBlockSize)
		decodedData, err := encoder.Decode(encodedBytes, curBlockSize)
		if err != nil {
			writer.CloseWithError(err)
			return
		}
		io.Copy(writer, bytes.NewBuffer(decodedData))
		totalLeft = totalLeft - blockSize
	}
	writer.Close()
}

// erasure writer

type erasureWriter struct {
	writers       []Writer
	metadata      map[string]string
	donutMetadata map[string]string // not exposed
	erasureWriter *io.PipeWriter
	isClosed      <-chan bool
}

func newErasureWriter(writers []Writer) ObjectWriter {
	r, w := io.Pipe()
	isClosed := make(chan bool)
	writer := erasureWriter{
		writers:       writers,
		metadata:      make(map[string]string),
		erasureWriter: w,
		isClosed:      isClosed,
	}
	go erasureGoroutine(r, writer, isClosed)
	return writer
}

func erasureGoroutine(r *io.PipeReader, eWriter erasureWriter, isClosed chan<- bool) {
	chunks := split.Stream(r, 10*1024*1024)
	params, _ := erasure.ParseEncoderParams(8, 8, erasure.Cauchy)
	encoder := erasure.NewEncoder(params)
	chunkCount := 0
	totalLength := 0
	for chunk := range chunks {
		if chunk.Err == nil {
			totalLength = totalLength + len(chunk.Data)
			encodedBlocks, _ := encoder.Encode(chunk.Data)
			for blockIndex, block := range encodedBlocks {
				io.Copy(eWriter.writers[blockIndex], bytes.NewBuffer(block))
			}
		}
		chunkCount = chunkCount + 1
	}
	metadata := make(map[string]string)
	metadata["blockSize"] = strconv.Itoa(10 * 1024 * 1024)
	metadata["chunkCount"] = strconv.Itoa(chunkCount)
	metadata["created"] = time.Now().Format(time.RFC3339Nano)
	metadata["erasureK"] = "8"
	metadata["erasureM"] = "8"
	metadata["erasureTechnique"] = "Cauchy"
	metadata["totalLength"] = strconv.Itoa(totalLength)
	for _, nodeWriter := range eWriter.writers {
		if nodeWriter != nil {
			nodeWriter.SetMetadata(eWriter.metadata)
			nodeWriter.SetDonutMetadata(metadata)
			nodeWriter.Close()
		}
	}
	isClosed <- true
}

func (d erasureWriter) Write(data []byte) (int, error) {
	io.Copy(d.erasureWriter, bytes.NewBuffer(data))
	return len(data), nil
}

func (d erasureWriter) Close() error {
	d.erasureWriter.Close()
	<-d.isClosed
	return nil
}

func (d erasureWriter) CloseWithError(err error) error {
	for _, writer := range d.writers {
		if writer != nil {
			writer.CloseWithError(err)
		}
	}
	return nil
}

func (d erasureWriter) SetMetadata(metadata map[string]string) error {
	for k := range d.metadata {
		delete(d.metadata, k)
	}
	for k, v := range metadata {
		d.metadata[k] = v
	}
	return nil
}

func (d erasureWriter) GetMetadata() (map[string]string, error) {
	metadata := make(map[string]string)
	for k, v := range d.metadata {
		metadata[k] = v
	}
	return metadata, nil
}

type localDirectoryNode struct {
	root string
}

func (d localDirectoryNode) GetBuckets() ([]string, error) {
	return nil, errors.New("Not Implemented")
}

func (d localDirectoryNode) GetWriter(bucket, object string) (Writer, error) {
	objectPath := path.Join(d.root, bucket, object)
	err := os.MkdirAll(objectPath, 0700)
	if err != nil {
		return nil, err
	}
	return newDonutFileWriter(objectPath)
}

func (d localDirectoryNode) GetReader(bucket, object string) (io.ReadCloser, error) {
	return os.Open(path.Join(d.root, bucket, object, "data"))
}

func (d localDirectoryNode) GetMetadata(bucket, object string) (map[string]string, error) {
	return d.getMetadata(bucket, object, "metadata.json")
}
func (d localDirectoryNode) GetDonutMetadata(bucket, object string) (map[string]string, error) {
	return d.getMetadata(bucket, object, "donutMetadata.json")
}

func (d localDirectoryNode) getMetadata(bucket, object, fileName string) (map[string]string, error) {
	file, err := os.Open(path.Join(d.root, bucket, object, fileName))
	defer file.Close()
	if err != nil {
		return nil, err
	}
	metadata := make(map[string]string)
	decoder := json.NewDecoder(file)
	if err := decoder.Decode(&metadata); err != nil {
		return nil, err
	}
	return metadata, nil

}

func newDonutFileWriter(objectDir string) (Writer, error) {
	dataFile, err := os.OpenFile(path.Join(objectDir, "data"), os.O_WRONLY|os.O_CREATE, 0600)
	if err != nil {
		return nil, err
	}
	return donutFileWriter{
		root:          objectDir,
		file:          dataFile,
		metadata:      make(map[string]string),
		donutMetadata: make(map[string]string),
	}, nil
}

type donutFileWriter struct {
	root          string
	file          *os.File
	metadata      map[string]string
	donutMetadata map[string]string
	err           error
}

func (d donutFileWriter) Write(data []byte) (int, error) {
	return d.file.Write(data)
}

func (d donutFileWriter) Close() error {
	if d.err != nil {
		return d.err
	}

	d.file.Close()

	metadata, _ := json.Marshal(d.metadata)
	ioutil.WriteFile(path.Join(d.root, "metadata.json"), metadata, 0600)
	donutMetadata, _ := json.Marshal(d.donutMetadata)
	ioutil.WriteFile(path.Join(d.root, "donutMetadata.json"), donutMetadata, 0600)

	return nil
}

func (d donutFileWriter) CloseWithError(err error) error {
	if d.err != nil {
		d.err = err
	}
	d.file.Close()
	return nil
}

func (d donutFileWriter) SetMetadata(metadata map[string]string) error {
	for k := range d.metadata {
		delete(d.metadata, k)
	}
	for k, v := range metadata {
		d.metadata[k] = v
	}
	return nil
}

func (d donutFileWriter) GetMetadata() (map[string]string, error) {
	metadata := make(map[string]string)
	for k, v := range d.metadata {
		metadata[k] = v
	}
	return metadata, nil
}

func (d donutFileWriter) SetDonutMetadata(metadata map[string]string) error {
	for k := range d.donutMetadata {
		delete(d.donutMetadata, k)
	}
	for k, v := range metadata {
		d.donutMetadata[k] = v
	}
	return nil
}

func (d donutFileWriter) GetDonutMetadata() (map[string]string, error) {
	donutMetadata := make(map[string]string)
	for k, v := range d.donutMetadata {
		donutMetadata[k] = v
	}
	return donutMetadata, nil
}
<commit_msg>DonutfileWriter should return value of file.Close()<commit_after>package donut

import (
	"bytes"
	"encoding/json"
	"errors"
	"io"
	"io/ioutil"
	"log"
	"os"
	"path"
	"sort"
	"strconv"
	"strings"
	"time"

	"github.com/minio-io/minio/pkg/encoding/erasure"
	"github.com/minio-io/minio/pkg/utils/split"
)

type donutDriver struct {
	buckets map[string]Bucket
	nodes   map[string]Node
}

// NewDonutDriver - instantiate new donut driver
func NewDonutDriver(root string) Donut {
	nodes := make(map[string]Node)
	nodes["localhost"] = localDirectoryNode{root: root}
	driver := donutDriver{
		buckets: make(map[string]Bucket),
		nodes:   nodes,
	}
	return driver
}

func (driver donutDriver) CreateBucket(bucketName string) error {
	if _, ok := driver.buckets[bucketName]; ok == false {
		bucketName = strings.TrimSpace(bucketName)
		if bucketName == "" {
			return errors.New("Cannot create bucket with no name")
		}
		// assign nodes
		// TODO assign other nodes
		nodes := make([]string, 16)
		for i := 0; i < 16; i++ {
			nodes[i] = "localhost"
		}
		bucket := bucketDriver{
			nodes: nodes,
		}
		driver.buckets[bucketName] = bucket
		return nil
	}
	return errors.New("Bucket exists")
}

func (driver donutDriver) ListBuckets() ([]string, error) {
	var buckets []string
	for bucket := range driver.buckets {
		buckets = append(buckets, bucket)
	}
	sort.Strings(buckets)
	return buckets, nil
}

func (driver donutDriver) GetObjectWriter(bucketName, objectName string) (ObjectWriter, error) {
	if bucket, ok := driver.buckets[bucketName]; ok == true {
		writers := make([]Writer, 16)
		nodes, err := bucket.GetNodes()
		if err != nil {
			return nil, err
		}
		for i, nodeID := range nodes {
			if node, ok := driver.nodes[nodeID]; ok == true {
				writer, _ := node.GetWriter(bucketName+":0:"+strconv.Itoa(i), objectName)
				writers[i] = writer
			}
		}
		return newErasureWriter(writers), nil
	}
	return nil, errors.New("Bucket not found")
}

func (driver donutDriver) GetObject(bucketName, objectName string) (io.ReadCloser, error) {
	r, w := io.Pipe()
	if bucket, ok := driver.buckets[bucketName]; ok == true {
		readers := make([]io.ReadCloser, 16)
		nodes, err := bucket.GetNodes()
		if err != nil {
			return nil, err
		}
		var metadata map[string]string
		for i, nodeID := range nodes {
			if node, ok := driver.nodes[nodeID]; ok == true {
				bucketID := bucketName + ":0:" + strconv.Itoa(i)
				reader, err := node.GetReader(bucketID, objectName)
				if err != nil {
					return nil, err
				}
				readers[i] = reader
				if metadata == nil {
					metadata, err = node.GetDonutMetadata(bucketID, objectName)
					if err != nil {
						return nil, err
					}
				}
			}
		}
		go erasureReader(readers, metadata, w)
		return r, nil
	}
	return nil, errors.New("Bucket not found")
}

// GetObjectMetadata returns metadata for a given object in a bucket
func (driver donutDriver) GetObjectMetadata(bucketName, object string) (map[string]string, error) {
	if bucket, ok := driver.buckets[bucketName]; ok {
		nodes, err := bucket.GetNodes()
		if err != nil {
			return nil, err
		}
		if node, ok := driver.nodes[nodes[0]]; ok {
			return node.GetMetadata(bucketName+":0:0", object)
		}
		return nil, errors.New("Cannot connect to node: " + nodes[0])
	}
	return nil, errors.New("Bucket not found")
}

func erasureReader(readers []io.ReadCloser, donutMetadata map[string]string, writer *io.PipeWriter) {
	totalChunks, _ := strconv.Atoi(donutMetadata["chunkCount"])
	totalLeft, _ := strconv.Atoi(donutMetadata["totalLength"])
	blockSize, _ := strconv.Atoi(donutMetadata["blockSize"])
	params, _ := erasure.ParseEncoderParams(8, 8, erasure.Cauchy)
	encoder := erasure.NewEncoder(params)
	for _, reader := range readers {
		defer reader.Close()
	}
	for i := 0; i < totalChunks; i++ {
		encodedBytes := make([][]byte, 16)
		for i, reader := range readers {
			var bytesBuffer bytes.Buffer
			io.Copy(&bytesBuffer, reader)
			encodedBytes[i] = bytesBuffer.Bytes()
		}
		curBlockSize := totalLeft
		if blockSize < totalLeft {
			curBlockSize = blockSize
		}
		log.Println("decoding block size", curBlockSize)
		decodedData, err := encoder.Decode(encodedBytes, curBlockSize)
		if err != nil {
			writer.CloseWithError(err)
			return
		}
		io.Copy(writer, bytes.NewBuffer(decodedData))
		totalLeft = totalLeft - blockSize
	}
	writer.Close()
}

// erasure writer

type erasureWriter struct {
	writers       []Writer
	metadata      map[string]string
	donutMetadata map[string]string // not exposed
	erasureWriter *io.PipeWriter
	isClosed      <-chan bool
}

func newErasureWriter(writers []Writer) ObjectWriter {
	r, w := io.Pipe()
	isClosed := make(chan bool)
	writer := erasureWriter{
		writers:       writers,
		metadata:      make(map[string]string),
		erasureWriter: w,
		isClosed:      isClosed,
	}
	go erasureGoroutine(r, writer, isClosed)
	return writer
}

func erasureGoroutine(r *io.PipeReader, eWriter erasureWriter, isClosed chan<- bool) {
	chunks := split.Stream(r, 10*1024*1024)
	params, _ := erasure.ParseEncoderParams(8, 8, erasure.Cauchy)
	encoder := erasure.NewEncoder(params)
	chunkCount := 0
	totalLength := 0
	for chunk := range chunks {
		if chunk.Err == nil {
			totalLength = totalLength + len(chunk.Data)
			encodedBlocks, _ := encoder.Encode(chunk.Data)
			for blockIndex, block := range encodedBlocks {
				io.Copy(eWriter.writers[blockIndex], bytes.NewBuffer(block))
			}
		}
		chunkCount = chunkCount + 1
	}
	metadata := make(map[string]string)
	metadata["blockSize"] = strconv.Itoa(10 * 1024 * 1024)
	metadata["chunkCount"] = strconv.Itoa(chunkCount)
	metadata["created"] = time.Now().Format(time.RFC3339Nano)
	metadata["erasureK"] = "8"
	metadata["erasureM"] = "8"
	metadata["erasureTechnique"] = "Cauchy"
	metadata["totalLength"] = strconv.Itoa(totalLength)
	for _, nodeWriter := range eWriter.writers {
		if nodeWriter != nil {
			nodeWriter.SetMetadata(eWriter.metadata)
			nodeWriter.SetDonutMetadata(metadata)
			nodeWriter.Close()
		}
	}
	isClosed <- true
}

func (d erasureWriter) Write(data []byte) (int, error) {
	io.Copy(d.erasureWriter, bytes.NewBuffer(data))
	return len(data), nil
}

func (d erasureWriter) Close() error {
	d.erasureWriter.Close()
	<-d.isClosed
	return nil
}

func (d erasureWriter) CloseWithError(err error) error {
	for _, writer := range d.writers {
		if writer != nil {
			writer.CloseWithError(err)
		}
	}
	return nil
}

func (d erasureWriter) SetMetadata(metadata map[string]string) error {
	for k := range d.metadata {
		delete(d.metadata, k)
	}
	for k, v := range metadata {
		d.metadata[k] = v
	}
	return nil
}

func (d erasureWriter) GetMetadata() (map[string]string, error) {
	metadata := make(map[string]string)
	for k, v := range d.metadata {
		metadata[k] = v
	}
	return metadata, nil
}

type localDirectoryNode struct {
	root string
}

func (d localDirectoryNode) GetBuckets() ([]string, error) {
	return nil, errors.New("Not Implemented")
}

func (d localDirectoryNode) GetWriter(bucket, object string) (Writer, error) {
	objectPath := path.Join(d.root, bucket, object)
	err := os.MkdirAll(objectPath, 0700)
	if err != nil {
		return nil, err
	}
	return newDonutFileWriter(objectPath)
}

func (d localDirectoryNode) GetReader(bucket, object string) (io.ReadCloser, error) {
	return os.Open(path.Join(d.root, bucket, object, "data"))
}

func (d localDirectoryNode) GetMetadata(bucket, object string) (map[string]string, error) {
	return d.getMetadata(bucket, object, "metadata.json")
}
func (d localDirectoryNode) GetDonutMetadata(bucket, object string) (map[string]string, error) {
	return d.getMetadata(bucket, object, "donutMetadata.json")
}

func (d localDirectoryNode) getMetadata(bucket, object, fileName string) (map[string]string, error) {
	file, err := os.Open(path.Join(d.root, bucket, object, fileName))
	defer file.Close()
	if err != nil {
		return nil, err
	}
	metadata := make(map[string]string)
	decoder := json.NewDecoder(file)
	if err := decoder.Decode(&metadata); err != nil {
		return nil, err
	}
	return metadata, nil

}

func newDonutFileWriter(objectDir string) (Writer, error) {
	dataFile, err := os.OpenFile(path.Join(objectDir, "data"), os.O_WRONLY|os.O_CREATE, 0600)
	if err != nil {
		return nil, err
	}
	return donutFileWriter{
		root:          objectDir,
		file:          dataFile,
		metadata:      make(map[string]string),
		donutMetadata: make(map[string]string),
	}, nil
}

type donutFileWriter struct {
	root          string
	file          *os.File
	metadata      map[string]string
	donutMetadata map[string]string
	err           error
}

func (d donutFileWriter) Write(data []byte) (int, error) {
	return d.file.Write(data)
}

func (d donutFileWriter) Close() error {
	if d.err != nil {
		return d.err
	}
	metadata, _ := json.Marshal(d.metadata)
	ioutil.WriteFile(path.Join(d.root, "metadata.json"), metadata, 0600)
	donutMetadata, _ := json.Marshal(d.donutMetadata)
	ioutil.WriteFile(path.Join(d.root, "donutMetadata.json"), donutMetadata, 0600)

	return d.file.Close()
}

func (d donutFileWriter) CloseWithError(err error) error {
	if d.err != nil {
		d.err = err
	}
	return d.Close()
}

func (d donutFileWriter) SetMetadata(metadata map[string]string) error {
	for k := range d.metadata {
		delete(d.metadata, k)
	}
	for k, v := range metadata {
		d.metadata[k] = v
	}
	return nil
}

func (d donutFileWriter) GetMetadata() (map[string]string, error) {
	metadata := make(map[string]string)
	for k, v := range d.metadata {
		metadata[k] = v
	}
	return metadata, nil
}

func (d donutFileWriter) SetDonutMetadata(metadata map[string]string) error {
	for k := range d.donutMetadata {
		delete(d.donutMetadata, k)
	}
	for k, v := range metadata {
		d.donutMetadata[k] = v
	}
	return nil
}

func (d donutFileWriter) GetDonutMetadata() (map[string]string, error) {
	donutMetadata := make(map[string]string)
	for k, v := range d.donutMetadata {
		donutMetadata[k] = v
	}
	return donutMetadata, nil
}
<|endoftext|>


<commit_before>/*
Copyright 2019 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 trigger

import (
	"context"
	"fmt"
	"reflect"

	"k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime/schema"
	utilruntime "k8s.io/apimachinery/pkg/util/runtime"
	"k8s.io/apimachinery/pkg/util/wait"
	"k8s.io/klog"

	migrationv1alpha1 "sigs.k8s.io/kube-storage-version-migrator/pkg/apis/migration/v1alpha1"
	"sigs.k8s.io/kube-storage-version-migrator/pkg/controller"
)

func (mt *MigrationTrigger) processDiscovery(ctx context.Context) {
	var resources []*metav1.APIResourceList
	var err2 error
	err := wait.ExponentialBackoff(backoff, func() (bool, error) {
		resources, err2 = mt.client.Discovery().ServerPreferredResources()
		if err2 != nil {
			utilruntime.HandleError(err2)
			return false, nil
		}
		return true, nil
	})
	if err != nil {
		utilruntime.HandleError(fmt.Errorf("Abort processing discovery document: %v", err))
		return
	}
	mt.heartbeat = metav1.Now()
	for _, l := range resources {
		gv, err := schema.ParseGroupVersion(l.GroupVersion)
		if err != nil {
			utilruntime.HandleError(fmt.Errorf("unexpected group version error: %v", err))
			continue
		}
		for _, r := range l.APIResources {
			if r.Group == "" {
				r.Group = gv.Group
			}
			if r.Version == "" {
				r.Version = gv.Version
			}
			mt.processDiscoveryResource(ctx, r)
		}
	}
}

func toGroupResource(r metav1.APIResource) migrationv1alpha1.GroupVersionResource {
	return migrationv1alpha1.GroupVersionResource{
		Group:    r.Group,
		Version:  r.Version,
		Resource: r.Name,
	}
}

// cleanMigrations removes all storageVersionMigrations whose .spec.resource == r.
func (mt *MigrationTrigger) cleanMigrations(ctx context.Context, r metav1.APIResource) error {
	// Using the cache to find all matching migrations.
	// The delay of the cache shouldn't matter in practice, because
	// existing migrations are created by previous discovery cycles, they
	// have at least discoveryPeriod to enter the informer's cache.
	idx := mt.migrationInformer.GetIndexer()
	l, err := idx.ByIndex(controller.ResourceIndex, controller.ToIndex(toGroupResource(r)))
	if err != nil {
		return err
	}
	for _, m := range l {
		mm, ok := m.(*migrationv1alpha1.StorageVersionMigration)
		if !ok {
			return fmt.Errorf("expected StorageVersionMigration, got %#v", reflect.TypeOf(m))
		}
		err := mt.client.MigrationV1alpha1().StorageVersionMigrations().Delete(ctx, mm.Name, metav1.DeleteOptions{})
		if err != nil {
			return fmt.Errorf("unexpected error deleting migration %s, %v", mm.Name, err)
		}
	}
	return nil
}

func (mt *MigrationTrigger) launchMigration(ctx context.Context, resource migrationv1alpha1.GroupVersionResource) error {
	m := &migrationv1alpha1.StorageVersionMigration{
		ObjectMeta: metav1.ObjectMeta{
			GenerateName: storageStateName(resource) + "-",
		},
		Spec: migrationv1alpha1.StorageVersionMigrationSpec{
			Resource: resource,
		},
	}
	_, err := mt.client.MigrationV1alpha1().StorageVersionMigrations().Create(ctx, m, metav1.CreateOptions{})
	return err
}

// relaunchMigration cleans existing migrations for the resource, and launch a new one.
func (mt *MigrationTrigger) relaunchMigration(ctx context.Context, r metav1.APIResource) error {
	if err := mt.cleanMigrations(ctx, r); err != nil {
		return err
	}
	return mt.launchMigration(ctx, toGroupResource(r))

}

func (mt *MigrationTrigger) newStorageState(r metav1.APIResource) *migrationv1alpha1.StorageState {
	return &migrationv1alpha1.StorageState{
		ObjectMeta: metav1.ObjectMeta{
			Name: storageStateName(toGroupResource(r)),
		},
		Spec: migrationv1alpha1.StorageStateSpec{
			Resource: migrationv1alpha1.GroupResource{
				Group:    r.Group,
				Resource: r.Name,
			},
		},
	}
}

func (mt *MigrationTrigger) updateStorageState(ctx context.Context, currentHash string, r metav1.APIResource) error {
	// We will retry on any error, because failing to update the
	// heartbeat of the storageState can lead to redo migration, which is
	// costly.
	return wait.ExponentialBackoff(backoff, func() (bool, error) {
		ss, err := mt.client.MigrationV1alpha1().StorageStates().Get(ctx, storageStateName(toGroupResource(r)), metav1.GetOptions{})
		if err != nil && !errors.IsNotFound(err) {
			utilruntime.HandleError(err)
			return false, nil
		}
		if err != nil && errors.IsNotFound(err) {
			// Note that the apiserver resets the status field for
			// the POST request. We need to update via the status
			// endpoint.
			ss, err = mt.client.MigrationV1alpha1().StorageStates().Create(ctx, mt.newStorageState(r), metav1.CreateOptions{})
			if err != nil {
				utilruntime.HandleError(err)
				return false, nil
			}
		}
		if ss.Status.CurrentStorageVersionHash != currentHash {
			ss.Status.CurrentStorageVersionHash = currentHash
			if len(ss.Status.PersistedStorageVersionHashes) == 0 {
				ss.Status.PersistedStorageVersionHashes = []string{migrationv1alpha1.Unknown}
			} else {
				ss.Status.PersistedStorageVersionHashes = append(ss.Status.PersistedStorageVersionHashes, currentHash)
			}
		}
		ss.Status.LastHeartbeatTime = mt.heartbeat
		_, err = mt.client.MigrationV1alpha1().StorageStates().UpdateStatus(ctx, ss, metav1.UpdateOptions{})
		if err != nil {
			utilruntime.HandleError(err)
			return false, nil
		}
		return true, nil
	})
}

func (mt *MigrationTrigger) staleStorageState(ss *migrationv1alpha1.StorageState) bool {
	return ss.Status.LastHeartbeatTime.Add(2 * discoveryPeriod).Before(mt.heartbeat.Time)
}

func (mt *MigrationTrigger) processDiscoveryResource(ctx context.Context, r metav1.APIResource) {
	klog.V(4).Infof("processing %#v", r)
	if r.StorageVersionHash == "" {
		klog.V(2).Infof("ignored resource %s because its storageVersionHash is empty", r.Name)
		return
	}
	ss, getErr := mt.client.MigrationV1alpha1().StorageStates().Get(ctx, storageStateName(toGroupResource(r)), metav1.GetOptions{})
	if getErr != nil && !errors.IsNotFound(getErr) {
		utilruntime.HandleError(getErr)
		return
	}
	found := getErr == nil
	stale := found && mt.staleStorageState(ss)
	storageVersionChanged := found && ss.Status.CurrentStorageVersionHash != r.StorageVersionHash
	needsMigration := found && !mt.isMigrated(ss) && !mt.hasPendingOrRunningMigration(r)
	relaunchMigration := stale || !found || storageVersionChanged || needsMigration

	if stale {
		if err := mt.client.MigrationV1alpha1().StorageStates().Delete(ctx, storageStateName(toGroupResource(r)), metav1.DeleteOptions{}); err != nil {
			utilruntime.HandleError(err)
			return
		}
	}

	if relaunchMigration {
		// Note that this means historical migration objects are deleted.
		if err := mt.relaunchMigration(ctx, r); err != nil {
			utilruntime.HandleError(err)
		}
	}

	// always update status.heartbeat, sometimes update the version hashes.
	mt.updateStorageState(ctx, r.StorageVersionHash, r)
}
func (mt *MigrationTrigger) isMigrated(ss *migrationv1alpha1.StorageState) bool {
	if len(ss.Status.PersistedStorageVersionHashes) != 1 {
		return false
	}
	return ss.Status.CurrentStorageVersionHash == ss.Status.PersistedStorageVersionHashes[0]
}

func (mt *MigrationTrigger) hasPendingOrRunningMigration(r metav1.APIResource) bool {
	// get the corresponding StorageVersionMigration resource
	migrations, err := mt.migrationInformer.GetIndexer().ByIndex(controller.ResourceIndex, controller.ToIndex(toGroupResource(r)))
	if err != nil {
		utilruntime.HandleError(err)
		return false
	}
	for _, migration := range migrations {
		m := migration.(*migrationv1alpha1.StorageVersionMigration)
		if controller.HasCondition(m, migrationv1alpha1.MigrationSucceeded) || controller.HasCondition(m, migrationv1alpha1.MigrationFailed) {
			continue
		}
		// migration is running or pending
		return true
	}
	return false
}
<commit_msg>Log the group name as well to be more informative<commit_after>/*
Copyright 2019 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 trigger

import (
	"context"
	"fmt"
	"reflect"

	"k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime/schema"
	utilruntime "k8s.io/apimachinery/pkg/util/runtime"
	"k8s.io/apimachinery/pkg/util/wait"
	"k8s.io/klog"

	migrationv1alpha1 "sigs.k8s.io/kube-storage-version-migrator/pkg/apis/migration/v1alpha1"
	"sigs.k8s.io/kube-storage-version-migrator/pkg/controller"
)

func (mt *MigrationTrigger) processDiscovery(ctx context.Context) {
	var resources []*metav1.APIResourceList
	var err2 error
	err := wait.ExponentialBackoff(backoff, func() (bool, error) {
		resources, err2 = mt.client.Discovery().ServerPreferredResources()
		if err2 != nil {
			utilruntime.HandleError(err2)
			return false, nil
		}
		return true, nil
	})
	if err != nil {
		utilruntime.HandleError(fmt.Errorf("Abort processing discovery document: %v", err))
		return
	}
	mt.heartbeat = metav1.Now()
	for _, l := range resources {
		gv, err := schema.ParseGroupVersion(l.GroupVersion)
		if err != nil {
			utilruntime.HandleError(fmt.Errorf("unexpected group version error: %v", err))
			continue
		}
		for _, r := range l.APIResources {
			if r.Group == "" {
				r.Group = gv.Group
			}
			if r.Version == "" {
				r.Version = gv.Version
			}
			mt.processDiscoveryResource(ctx, r)
		}
	}
}

func toGroupResource(r metav1.APIResource) migrationv1alpha1.GroupVersionResource {
	return migrationv1alpha1.GroupVersionResource{
		Group:    r.Group,
		Version:  r.Version,
		Resource: r.Name,
	}
}

// cleanMigrations removes all storageVersionMigrations whose .spec.resource == r.
func (mt *MigrationTrigger) cleanMigrations(ctx context.Context, r metav1.APIResource) error {
	// Using the cache to find all matching migrations.
	// The delay of the cache shouldn't matter in practice, because
	// existing migrations are created by previous discovery cycles, they
	// have at least discoveryPeriod to enter the informer's cache.
	idx := mt.migrationInformer.GetIndexer()
	l, err := idx.ByIndex(controller.ResourceIndex, controller.ToIndex(toGroupResource(r)))
	if err != nil {
		return err
	}
	for _, m := range l {
		mm, ok := m.(*migrationv1alpha1.StorageVersionMigration)
		if !ok {
			return fmt.Errorf("expected StorageVersionMigration, got %#v", reflect.TypeOf(m))
		}
		err := mt.client.MigrationV1alpha1().StorageVersionMigrations().Delete(ctx, mm.Name, metav1.DeleteOptions{})
		if err != nil {
			return fmt.Errorf("unexpected error deleting migration %s, %v", mm.Name, err)
		}
	}
	return nil
}

func (mt *MigrationTrigger) launchMigration(ctx context.Context, resource migrationv1alpha1.GroupVersionResource) error {
	m := &migrationv1alpha1.StorageVersionMigration{
		ObjectMeta: metav1.ObjectMeta{
			GenerateName: storageStateName(resource) + "-",
		},
		Spec: migrationv1alpha1.StorageVersionMigrationSpec{
			Resource: resource,
		},
	}
	_, err := mt.client.MigrationV1alpha1().StorageVersionMigrations().Create(ctx, m, metav1.CreateOptions{})
	return err
}

// relaunchMigration cleans existing migrations for the resource, and launch a new one.
func (mt *MigrationTrigger) relaunchMigration(ctx context.Context, r metav1.APIResource) error {
	if err := mt.cleanMigrations(ctx, r); err != nil {
		return err
	}
	return mt.launchMigration(ctx, toGroupResource(r))

}

func (mt *MigrationTrigger) newStorageState(r metav1.APIResource) *migrationv1alpha1.StorageState {
	return &migrationv1alpha1.StorageState{
		ObjectMeta: metav1.ObjectMeta{
			Name: storageStateName(toGroupResource(r)),
		},
		Spec: migrationv1alpha1.StorageStateSpec{
			Resource: migrationv1alpha1.GroupResource{
				Group:    r.Group,
				Resource: r.Name,
			},
		},
	}
}

func (mt *MigrationTrigger) updateStorageState(ctx context.Context, currentHash string, r metav1.APIResource) error {
	// We will retry on any error, because failing to update the
	// heartbeat of the storageState can lead to redo migration, which is
	// costly.
	return wait.ExponentialBackoff(backoff, func() (bool, error) {
		ss, err := mt.client.MigrationV1alpha1().StorageStates().Get(ctx, storageStateName(toGroupResource(r)), metav1.GetOptions{})
		if err != nil && !errors.IsNotFound(err) {
			utilruntime.HandleError(err)
			return false, nil
		}
		if err != nil && errors.IsNotFound(err) {
			// Note that the apiserver resets the status field for
			// the POST request. We need to update via the status
			// endpoint.
			ss, err = mt.client.MigrationV1alpha1().StorageStates().Create(ctx, mt.newStorageState(r), metav1.CreateOptions{})
			if err != nil {
				utilruntime.HandleError(err)
				return false, nil
			}
		}
		if ss.Status.CurrentStorageVersionHash != currentHash {
			ss.Status.CurrentStorageVersionHash = currentHash
			if len(ss.Status.PersistedStorageVersionHashes) == 0 {
				ss.Status.PersistedStorageVersionHashes = []string{migrationv1alpha1.Unknown}
			} else {
				ss.Status.PersistedStorageVersionHashes = append(ss.Status.PersistedStorageVersionHashes, currentHash)
			}
		}
		ss.Status.LastHeartbeatTime = mt.heartbeat
		_, err = mt.client.MigrationV1alpha1().StorageStates().UpdateStatus(ctx, ss, metav1.UpdateOptions{})
		if err != nil {
			utilruntime.HandleError(err)
			return false, nil
		}
		return true, nil
	})
}

func (mt *MigrationTrigger) staleStorageState(ss *migrationv1alpha1.StorageState) bool {
	return ss.Status.LastHeartbeatTime.Add(2 * discoveryPeriod).Before(mt.heartbeat.Time)
}

func (mt *MigrationTrigger) processDiscoveryResource(ctx context.Context, r metav1.APIResource) {
	klog.V(4).Infof("processing %#v", r)
	if r.StorageVersionHash == "" {
		klog.V(2).Infof("ignored resource %s/%s because its storageVersionHash is empty", r.Group, r.Name)
		return
	}
	ss, getErr := mt.client.MigrationV1alpha1().StorageStates().Get(ctx, storageStateName(toGroupResource(r)), metav1.GetOptions{})
	if getErr != nil && !errors.IsNotFound(getErr) {
		utilruntime.HandleError(getErr)
		return
	}
	found := getErr == nil
	stale := found && mt.staleStorageState(ss)
	storageVersionChanged := found && ss.Status.CurrentStorageVersionHash != r.StorageVersionHash
	needsMigration := found && !mt.isMigrated(ss) && !mt.hasPendingOrRunningMigration(r)
	relaunchMigration := stale || !found || storageVersionChanged || needsMigration

	if stale {
		if err := mt.client.MigrationV1alpha1().StorageStates().Delete(ctx, storageStateName(toGroupResource(r)), metav1.DeleteOptions{}); err != nil {
			utilruntime.HandleError(err)
			return
		}
	}

	if relaunchMigration {
		// Note that this means historical migration objects are deleted.
		if err := mt.relaunchMigration(ctx, r); err != nil {
			utilruntime.HandleError(err)
		}
	}

	// always update status.heartbeat, sometimes update the version hashes.
	mt.updateStorageState(ctx, r.StorageVersionHash, r)
}
func (mt *MigrationTrigger) isMigrated(ss *migrationv1alpha1.StorageState) bool {
	if len(ss.Status.PersistedStorageVersionHashes) != 1 {
		return false
	}
	return ss.Status.CurrentStorageVersionHash == ss.Status.PersistedStorageVersionHashes[0]
}

func (mt *MigrationTrigger) hasPendingOrRunningMigration(r metav1.APIResource) bool {
	// get the corresponding StorageVersionMigration resource
	migrations, err := mt.migrationInformer.GetIndexer().ByIndex(controller.ResourceIndex, controller.ToIndex(toGroupResource(r)))
	if err != nil {
		utilruntime.HandleError(err)
		return false
	}
	for _, migration := range migrations {
		m := migration.(*migrationv1alpha1.StorageVersionMigration)
		if controller.HasCondition(m, migrationv1alpha1.MigrationSucceeded) || controller.HasCondition(m, migrationv1alpha1.MigrationFailed) {
			continue
		}
		// migration is running or pending
		return true
	}
	return false
}
<|endoftext|>


<commit_before>package middleware

import (
	"net/http"

	"github.com/PuerkitoBio/goquery"
)

// ExtractAllLinks simply extracts all the <a href="<urL>">  in a page
// provided that they aren't silly like a hash. It also expands relative
// links automagically.
func ExtractAllLinks(resp *http.Response) []string {
	var links []string

	tempURL := *resp.Request.URL
	tempURL.Fragment = ""
	tempURL.Path = ""
	tempURL.RawQuery = ""
	uri := tempURL.String()

	doc, err := goquery.NewDocumentFromReader(resp.Body)
	resp.Body.Close()
	if err != nil {
		return links
	}

	doc.Find("a").Each(func(i int, s *goquery.Selection) {
		href, exists := s.Attr("href")
		if href == "" {
			return
		}
		if href[0] == '#' {
			return
		}
		if exists {
			if href[0] == '/' {
				href = uri + href
			}
			links = append(links, href)
		}
	})

	return links
}
<commit_msg>Cleaned up ExtractAllLinks middleware function.<commit_after>package middleware

import (
	"net/http"

	"github.com/PuerkitoBio/goquery"
)

// ExtractAllLinks simply extracts all the <a href="<urL>">  in a page
// provided that they aren't silly like a hash. It also expands relative
// links automagically.
func ExtractAllLinks(resp *http.Response) []string {
	var links []string

	tempURL := *resp.Request.URL
	tempURL.Fragment = ""
	tempURL.Path = ""
	tempURL.RawQuery = ""
	uri := tempURL.String()

	doc, err := goquery.NewDocumentFromReader(resp.Body)
	resp.Body.Close()
	if err != nil {
		return links
	}

	doc.Find("a").Each(func(i int, s *goquery.Selection) {
		href, exists := s.Attr("href")

		if exists && href != "" {

			if href[0] == '#' {
				return
			}
			if href[0] == '/' {
				href = uri + href
			}
			links = append(links, href)
		}
	})

	return links
}
<|endoftext|>


<commit_before>package input

import (
	"fmt"
	"path/filepath"

	"github.com/cweill/gotests/internal/models"
)

// Returns all the Golang files for the given path. Ignores hidden files.
func Files(srcPath string) ([]models.Path, error) {
	srcPath, err := filepath.Abs(srcPath)
	if err != nil {
		return nil, fmt.Errorf("filepath.Abs: %v\n", err)
	}
	if filepath.Ext(srcPath) == "" {
		return dirFiles(srcPath)
	}
	return file(srcPath)
}

func dirFiles(srcPath string) ([]models.Path, error) {
	ps, err := filepath.Glob(srcPath + "/*.go")
	if err != nil {
		return nil, fmt.Errorf("filepath.Glob: %v\n", err)
	}
	var srcPaths []models.Path
	for _, p := range ps {
		src := models.Path(p)
		if isHiddenFile(p) || src.IsTestPath() {
			continue
		}
		srcPaths = append(srcPaths, src)
	}
	return srcPaths, nil
}

func file(srcPath string) ([]models.Path, error) {
	src := models.Path(srcPath)
	if filepath.Ext(srcPath) != ".go" || isHiddenFile(srcPath) || src.IsTestPath() {
		return nil, fmt.Errorf("no Go source files found at %v", srcPath)
	}
	return []models.Path{src}, nil
}

func isHiddenFile(path string) bool {
	return []rune(filepath.Base(path))[0] == '.'
}
<commit_msg>Fix nit.<commit_after>package input

import (
	"fmt"
	"path"
	"path/filepath"

	"github.com/cweill/gotests/internal/models"
)

// Returns all the Golang files for the given path. Ignores hidden files.
func Files(srcPath string) ([]models.Path, error) {
	srcPath, err := filepath.Abs(srcPath)
	if err != nil {
		return nil, fmt.Errorf("filepath.Abs: %v\n", err)
	}
	if filepath.Ext(srcPath) == "" {
		return dirFiles(srcPath)
	}
	return file(srcPath)
}

func dirFiles(srcPath string) ([]models.Path, error) {
	ps, err := filepath.Glob(path.Join(srcPath, "*.go"))
	if err != nil {
		return nil, fmt.Errorf("filepath.Glob: %v\n", err)
	}
	var srcPaths []models.Path
	for _, p := range ps {
		src := models.Path(p)
		if isHiddenFile(p) || src.IsTestPath() {
			continue
		}
		srcPaths = append(srcPaths, src)
	}
	return srcPaths, nil
}

func file(srcPath string) ([]models.Path, error) {
	src := models.Path(srcPath)
	if filepath.Ext(srcPath) != ".go" || isHiddenFile(srcPath) || src.IsTestPath() {
		return nil, fmt.Errorf("no Go source files found at %v", srcPath)
	}
	return []models.Path{src}, nil
}

func isHiddenFile(path string) bool {
	return []rune(filepath.Base(path))[0] == '.'
}
<|endoftext|>


<commit_before>package models

import (
	"log"

	"github.com/brnstz/bus/internal/etc"
	"github.com/brnstz/upsert"
)

type Trip struct {
	AgencyID string `json:"agency_id" db:"agency_id" upsert:"key"`
	ID       string `json:"trip_id" db:"trip_id" upsert:"key"`

	ServiceID string `json:"service_id" db:"service_id"`
	ShapeID   string `json:"shape_id" db:"shape_id"`

	Headsign    string `json:"-" db:"-" upsert:"omit"`
	DirectionID int    `json:"-" db:"-" upsert:"omit"`

	ShapePoints []struct {
		Lat float64
		Lon float64
	} `json:"shape_points" db:"-" upsert:"omit"`
}

func NewTrip(id, agencyID, serviceID, shapeID, headsign string, direction int) (t *Trip, err error) {
	t = &Trip{
		ID:          id,
		AgencyID:    agencyID,
		ServiceID:   serviceID,
		ShapeID:     shapeID,
		Headsign:    headsign,
		DirectionID: direction,
	}

	return
}

func (t *Trip) Table() string {
	return "trip"
}

// Save saves a trip to the database
func (t *Trip) Save() error {
	_, err := upsert.Upsert(etc.DBConn, t)
	return err
}

func GetTrip(agencyID string, tripID string) (t Trip, err error) {
	q := `
		SELECT *
		FROM trip
		WHERE agency_id	= $1 AND
		      trip_id = $2
	`

	err = etc.DBConn.Get(&t, q, agencyID, tripID)
	if err != nil {
		log.Println("can't get trip")
		return
	}

	q = `
		SELECT
			latitude(location) AS lat,
			longitude(location) AS lon
		FROM shape
		WHERE agency_id = $1 AND
		      shape_id  = $2
		ORDER BY seq ASC
	`

	err = etc.DBConn.Select(&t.ShapePoints, q, agencyID, t.ShapeID)
	if err != nil {
		log.Println("can't get shapes", err)
		return
	}

	return
}
<commit_msg>adding json tags for shape points lat lon<commit_after>package models

import (
	"log"

	"github.com/brnstz/bus/internal/etc"
	"github.com/brnstz/upsert"
)

type Trip struct {
	AgencyID string `json:"agency_id" db:"agency_id" upsert:"key"`
	ID       string `json:"trip_id" db:"trip_id" upsert:"key"`

	ServiceID string `json:"service_id" db:"service_id"`
	ShapeID   string `json:"shape_id" db:"shape_id"`

	Headsign    string `json:"-" db:"-" upsert:"omit"`
	DirectionID int    `json:"-" db:"-" upsert:"omit"`

	ShapePoints []struct {
		Lat float64 `json:"lat"`
		Lon float64 `json:"lon"`
	} `json:"shape_points" db:"-" upsert:"omit"`
}

func NewTrip(id, agencyID, serviceID, shapeID, headsign string, direction int) (t *Trip, err error) {
	t = &Trip{
		ID:          id,
		AgencyID:    agencyID,
		ServiceID:   serviceID,
		ShapeID:     shapeID,
		Headsign:    headsign,
		DirectionID: direction,
	}

	return
}

func (t *Trip) Table() string {
	return "trip"
}

// Save saves a trip to the database
func (t *Trip) Save() error {
	_, err := upsert.Upsert(etc.DBConn, t)
	return err
}

func GetTrip(agencyID string, tripID string) (t Trip, err error) {
	q := `
		SELECT *
		FROM trip
		WHERE agency_id	= $1 AND
		      trip_id = $2
	`

	err = etc.DBConn.Get(&t, q, agencyID, tripID)
	if err != nil {
		log.Println("can't get trip")
		return
	}

	q = `
		SELECT
			latitude(location) AS lat,
			longitude(location) AS lon
		FROM shape
		WHERE agency_id = $1 AND
		      shape_id  = $2
		ORDER BY seq ASC
	`

	err = etc.DBConn.Select(&t.ShapePoints, q, agencyID, t.ShapeID)
	if err != nil {
		log.Println("can't get shapes", err)
		return
	}

	return
}
<|endoftext|>


<commit_before>// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// +build darwin freebsd linux netbsd openbsd

package net

import (
	"os"
	"syscall"
)

func newFileFD(f *os.File) (*netFD, error) {
	fd, err := syscall.Dup(int(f.Fd()))
	if err != nil {
		return nil, os.NewSyscallError("dup", err)
	}

	proto, err := syscall.GetsockoptInt(fd, syscall.SOL_SOCKET, syscall.SO_TYPE)
	if err != nil {
		return nil, os.NewSyscallError("getsockopt", err)
	}

	family := syscall.AF_UNSPEC
	toAddr := sockaddrToTCP
	sa, _ := syscall.Getsockname(fd)
	switch sa.(type) {
	default:
		closesocket(fd)
		return nil, syscall.EINVAL
	case *syscall.SockaddrInet4:
		family = syscall.AF_INET
		if proto == syscall.SOCK_DGRAM {
			toAddr = sockaddrToUDP
		} else if proto == syscall.SOCK_RAW {
			toAddr = sockaddrToIP
		}
	case *syscall.SockaddrInet6:
		family = syscall.AF_INET6
		if proto == syscall.SOCK_DGRAM {
			toAddr = sockaddrToUDP
		} else if proto == syscall.SOCK_RAW {
			toAddr = sockaddrToIP
		}
	case *syscall.SockaddrUnix:
		family = syscall.AF_UNIX
		toAddr = sockaddrToUnix
		if proto == syscall.SOCK_DGRAM {
			toAddr = sockaddrToUnixgram
		} else if proto == syscall.SOCK_SEQPACKET {
			toAddr = sockaddrToUnixpacket
		}
	}
	laddr := toAddr(sa)
	sa, _ = syscall.Getpeername(fd)
	raddr := toAddr(sa)

	netfd, err := newFD(fd, family, proto, laddr.Network())
	if err != nil {
		return nil, err
	}
	netfd.setAddr(laddr, raddr)
	return netfd, nil
}

// FileConn returns a copy of the network connection corresponding to
// the open file f.  It is the caller's responsibility to close f when
// finished.  Closing c does not affect f, and closing f does not
// affect c.
func FileConn(f *os.File) (c Conn, err error) {
	fd, err := newFileFD(f)
	if err != nil {
		return nil, err
	}
	switch fd.laddr.(type) {
	case *TCPAddr:
		return newTCPConn(fd), nil
	case *UDPAddr:
		return newUDPConn(fd), nil
	case *UnixAddr:
		return newUnixConn(fd), nil
	case *IPAddr:
		return newIPConn(fd), nil
	}
	fd.Close()
	return nil, syscall.EINVAL
}

// FileListener returns a copy of the network listener corresponding
// to the open file f.  It is the caller's responsibility to close ln
// when finished.  Closing ln does not affect f, and closing f does not
// affect ln.
func FileListener(f *os.File) (ln Listener, err error) {
	fd, err := newFileFD(f)
	if err != nil {
		return nil, err
	}
	switch laddr := fd.laddr.(type) {
	case *TCPAddr:
		return &TCPListener{fd}, nil
	case *UnixAddr:
		return &UnixListener{fd, laddr.Name}, nil
	}
	fd.Close()
	return nil, syscall.EINVAL
}

// FilePacketConn returns a copy of the packet network connection
// corresponding to the open file f.  It is the caller's
// responsibility to close f when finished.  Closing c does not affect
// f, and closing f does not affect c.
func FilePacketConn(f *os.File) (c PacketConn, err error) {
	fd, err := newFileFD(f)
	if err != nil {
		return nil, err
	}
	switch fd.laddr.(type) {
	case *UDPAddr:
		return newUDPConn(fd), nil
	case *UnixAddr:
		return newUnixConn(fd), nil
	}
	fd.Close()
	return nil, syscall.EINVAL
}
<commit_msg>undo CL 6248054 / 0f418a63cdf9<commit_after>// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// +build darwin freebsd linux netbsd openbsd

package net

import (
	"os"
	"syscall"
)

func newFileFD(f *os.File) (*netFD, error) {
	fd, err := syscall.Dup(int(f.Fd()))
	if err != nil {
		return nil, os.NewSyscallError("dup", err)
	}

	proto, err := syscall.GetsockoptInt(fd, syscall.SOL_SOCKET, syscall.SO_TYPE)
	if err != nil {
		return nil, os.NewSyscallError("getsockopt", err)
	}

	family := syscall.AF_UNSPEC
	toAddr := sockaddrToTCP
	sa, _ := syscall.Getsockname(fd)
	switch sa.(type) {
	default:
		closesocket(fd)
		return nil, syscall.EINVAL
	case *syscall.SockaddrInet4:
		family = syscall.AF_INET
		if proto == syscall.SOCK_DGRAM {
			toAddr = sockaddrToUDP
		} else if proto == syscall.SOCK_RAW {
			toAddr = sockaddrToIP
		}
	case *syscall.SockaddrInet6:
		family = syscall.AF_INET6
		if proto == syscall.SOCK_DGRAM {
			toAddr = sockaddrToUDP
		} else if proto == syscall.SOCK_RAW {
			toAddr = sockaddrToIP
		}
	case *syscall.SockaddrUnix:
		family = syscall.AF_UNIX
		toAddr = sockaddrToUnix
		if proto == syscall.SOCK_DGRAM {
			toAddr = sockaddrToUnixgram
		} else if proto == syscall.SOCK_SEQPACKET {
			toAddr = sockaddrToUnixpacket
		}
	}
	laddr := toAddr(sa)
	sa, _ = syscall.Getpeername(fd)
	raddr := toAddr(sa)

	netfd, err := newFD(fd, family, proto, laddr.Network())
	if err != nil {
		return nil, err
	}
	netfd.setAddr(laddr, raddr)
	return netfd, nil
}

// FileConn returns a copy of the network connection corresponding to
// the open file f.  It is the caller's responsibility to close f when
// finished.  Closing c does not affect f, and closing f does not
// affect c.
func FileConn(f *os.File) (c Conn, err error) {
	fd, err := newFileFD(f)
	if err != nil {
		return nil, err
	}
	switch fd.laddr.(type) {
	case *TCPAddr:
		return newTCPConn(fd), nil
	case *UDPAddr:
		return newUDPConn(fd), nil
	case *UnixAddr:
		return newUnixConn(fd), nil
	case *IPAddr:
		return newIPConn(fd), nil
	}
	fd.Close()
	return nil, syscall.EINVAL
}

// FileListener returns a copy of the network listener corresponding
// to the open file f.  It is the caller's responsibility to close l
// when finished.  Closing c does not affect l, and closing l does not
// affect c.
func FileListener(f *os.File) (l Listener, err error) {
	fd, err := newFileFD(f)
	if err != nil {
		return nil, err
	}
	switch laddr := fd.laddr.(type) {
	case *TCPAddr:
		return &TCPListener{fd}, nil
	case *UnixAddr:
		return &UnixListener{fd, laddr.Name}, nil
	}
	fd.Close()
	return nil, syscall.EINVAL
}

// FilePacketConn returns a copy of the packet network connection
// corresponding to the open file f.  It is the caller's
// responsibility to close f when finished.  Closing c does not affect
// f, and closing f does not affect c.
func FilePacketConn(f *os.File) (c PacketConn, err error) {
	fd, err := newFileFD(f)
	if err != nil {
		return nil, err
	}
	switch fd.laddr.(type) {
	case *UDPAddr:
		return newUDPConn(fd), nil
	case *UnixAddr:
		return newUnixConn(fd), nil
	}
	fd.Close()
	return nil, syscall.EINVAL
}
<|endoftext|>


<commit_before>package messagebird

import (
	"testing"
	"time"
)

var mmsMessageObject []byte = []byte(`{
    "id": "6d9e7100b1f9406c81a3c303c30ccf05",
    "href": "https://rest.messagebird.com/mms/6d9e7100b1f9406c81a3c303c30ccf05",
    "direction": "mt",
    "originator": "TestName",
    "subject": "SBJCT",
    "body": "Hello World",
    "mediaUrls": [],
    "reference": null,
    "scheduledDatetime": null,
    "createdDatetime": "2017-10-20T12:50:28+00:00",
    "recipients": {
        "totalCount": 1,
        "totalSentCount": 1,
        "totalDeliveredCount": 0,
        "totalDeliveryFailedCount": 0,
        "items": [
            {
                "recipient": 31612345678,
                "status": "sent",
                "statusDatetime": "2017-10-20T12:50:28+00:00"
            }
        ]
    }
}`)

func TestNewMmsMessage(t *testing.T) {
	SetServerResponse(200, mmsMessageObject)

	mmsMessage, err := mbClient.NewMmsMessage("TestName", []string{"31612345678"}, "Hello World", nil, nil)
	if err != nil {
		t.Fatalf("Didn't expect error while creating a new MMS message: %s", err)
	}

	if mmsMessage.Id != "6d9e7100b1f9406c81a3c303c30ccf05" {
		t.Errorf("Unexpected mmsMessage id: %s", mmsMessage.Id)
	}

	if mmsMessage.HRef != "https://rest.messagebird.com/mms/6d9e7100b1f9406c81a3c303c30ccf05" {
		t.Errorf("Unexpected mmsMessage href: %s", mmsMessage.HRef)
	}

	if mmsMessage.Direction != "mt" {
		t.Errorf("Unexpected mmsMessage direction: %s", mmsMessage.Direction)
	}

	if mmsMessage.Originator != "TestName" {
		t.Errorf("Unexpected mmsMessage originator: %s", mmsMessage.Originator)
	}

	if mmsMessage.Body != "Hello World" {
		t.Errorf("Unexpected mmsMessage body: %s", mmsMessage.Body)
	}

	if mmsMessage.Reference != "" {
		t.Errorf("Unexpected mmsMessage reference: %s", mmsMessage.Reference)
	}

	if mmsMessage.ScheduledDatetime != nil {
		t.Errorf("Unexpected mmsMessage scheduled datetime: %s", mmsMessage.ScheduledDatetime)
	}

	if mmsMessage.CreatedDatetime == nil || mmsMessage.CreatedDatetime.Format(time.RFC3339) != "2017-10-20T12:50:28Z" {
		t.Errorf("Unexpected mmsMessage created datetime: %s", mmsMessage.CreatedDatetime)
	}

	if mmsMessage.Recipients.TotalCount != 1 {
		t.Fatalf("Unexpected number of total count: %d", mmsMessage.Recipients.TotalCount)
	}

	if mmsMessage.Recipients.TotalSentCount != 1 {
		t.Errorf("Unexpected number of total sent count: %d", mmsMessage.Recipients.TotalSentCount)
	}

	if mmsMessage.Recipients.Items[0].Recipient != 31612345678 {
		t.Errorf("Unexpected mmsMessage recipient: %d", mmsMessage.Recipients.Items[0].Recipient)
	}

	if mmsMessage.Recipients.Items[0].Status != "sent" {
		t.Errorf("Unexpected mmsMessage recipient status: %s", mmsMessage.Recipients.Items[0].Status)
	}

	if mmsMessage.Recipients.Items[0].StatusDatetime == nil || mmsMessage.Recipients.Items[0].StatusDatetime.Format(time.RFC3339) != "2017-10-20T12:50:28Z" {
		t.Errorf("Unexpected datetime status for mmsMessage recipient: %s", mmsMessage.Recipients.Items[0].StatusDatetime.Format(time.RFC3339))
	}

	if len(mmsMessage.Errors) != 0 {
		t.Errorf("Unexpected number of errors in mmsMessage: %d", len(mmsMessage.Errors))
	}
}

func TestNewMmsMessageError(t *testing.T) {
	SetServerResponse(405, accessKeyErrorObject)

	message, err := mbClient.NewMmsMessage("TestName", []string{"31612345678"}, "Hello World", nil, nil)
	if err != ErrResponse {
		t.Fatalf("Expected ErrResponse to be returned, instead I got %s", err)
	}

	if len(message.Errors) != 1 {
		t.Fatalf("Unexpected number of errors: %d", len(message.Errors))
	}

	if message.Errors[0].Code != 2 {
		t.Errorf("Unexpected error code: %d", message.Errors[0].Code)
	}

	if message.Errors[0].Parameter != "access_key" {
		t.Errorf("Unexpected error parameter %s", message.Errors[0].Parameter)
	}
}

func TestNewMmsMessageWithParams(t *testing.T) {
	SetServerResponse(200, mmsMessageObjectWithParams)

	params := &MmsMessageParams{
		Subject:   "Test-Subject",
		Reference: "Test-Reference",
	}

	mmsMessage, err := mbClient.NewMmsMessage("TestName", []string{"31612345678"}, "", []string{"http://w3.org/1.gif", "http://w3.org/2.gif"}, params)
	if err != nil {
		t.Fatalf("Didn't expect error while creating a new MMS message: %s", err)
	}

	if mmsMessage.Subject != "Test-Subject" {
		t.Errorf("Unexpected message subject: %s", mmsMessage.Subject)
	}
	if mmsMessage.Reference != "Test-Reference" {
		t.Errorf("Unexpected message reference: %s", mmsMessage.Reference)
	}
}

var mmsMessageObjectWithParams []byte = []byte(`{
    "id": "6d9e7100b1f9406c81a3c303c30ccf05",
    "href": "https://rest.messagebird.com/mms/6d9e7100b1f9406c81a3c303c30ccf05",
    "direction": "mt",
    "originator": "TestName",
    "subject": "Test-Subject",
    "body": "Hello World",
    "mediaUrls": [],
    "reference": "Test-Reference",
    "scheduledDatetime": null,
    "createdDatetime": "2017-10-20T12:50:28+00:00",
    "recipients": {
        "totalCount": 1,
        "totalSentCount": 1,
        "totalDeliveredCount": 0,
        "totalDeliveryFailedCount": 0,
        "items": [
            {
                "recipient": 31612345678,
                "status": "sent",
                "statusDatetime": "2017-10-20T12:50:28+00:00"
            }
        ]
    }
}`)
<commit_msg>Changed variable name<commit_after>package messagebird

import (
	"testing"
	"time"
)

var mmsMessageObject []byte = []byte(`{
    "id": "6d9e7100b1f9406c81a3c303c30ccf05",
    "href": "https://rest.messagebird.com/mms/6d9e7100b1f9406c81a3c303c30ccf05",
    "direction": "mt",
    "originator": "TestName",
    "subject": "SBJCT",
    "body": "Hello World",
    "mediaUrls": [],
    "reference": null,
    "scheduledDatetime": null,
    "createdDatetime": "2017-10-20T12:50:28+00:00",
    "recipients": {
        "totalCount": 1,
        "totalSentCount": 1,
        "totalDeliveredCount": 0,
        "totalDeliveryFailedCount": 0,
        "items": [
            {
                "recipient": 31612345678,
                "status": "sent",
                "statusDatetime": "2017-10-20T12:50:28+00:00"
            }
        ]
    }
}`)

func TestNewMmsMessage(t *testing.T) {
	SetServerResponse(200, mmsMessageObject)

	mmsMessage, err := mbClient.NewMmsMessage("TestName", []string{"31612345678"}, "Hello World", nil, nil)
	if err != nil {
		t.Fatalf("Didn't expect error while creating a new MMS message: %s", err)
	}

	if mmsMessage.Id != "6d9e7100b1f9406c81a3c303c30ccf05" {
		t.Errorf("Unexpected mmsMessage id: %s", mmsMessage.Id)
	}

	if mmsMessage.HRef != "https://rest.messagebird.com/mms/6d9e7100b1f9406c81a3c303c30ccf05" {
		t.Errorf("Unexpected mmsMessage href: %s", mmsMessage.HRef)
	}

	if mmsMessage.Direction != "mt" {
		t.Errorf("Unexpected mmsMessage direction: %s", mmsMessage.Direction)
	}

	if mmsMessage.Originator != "TestName" {
		t.Errorf("Unexpected mmsMessage originator: %s", mmsMessage.Originator)
	}

	if mmsMessage.Body != "Hello World" {
		t.Errorf("Unexpected mmsMessage body: %s", mmsMessage.Body)
	}

	if mmsMessage.Reference != "" {
		t.Errorf("Unexpected mmsMessage reference: %s", mmsMessage.Reference)
	}

	if mmsMessage.ScheduledDatetime != nil {
		t.Errorf("Unexpected mmsMessage scheduled datetime: %s", mmsMessage.ScheduledDatetime)
	}

	if mmsMessage.CreatedDatetime == nil || mmsMessage.CreatedDatetime.Format(time.RFC3339) != "2017-10-20T12:50:28Z" {
		t.Errorf("Unexpected mmsMessage created datetime: %s", mmsMessage.CreatedDatetime)
	}

	if mmsMessage.Recipients.TotalCount != 1 {
		t.Fatalf("Unexpected number of total count: %d", mmsMessage.Recipients.TotalCount)
	}

	if mmsMessage.Recipients.TotalSentCount != 1 {
		t.Errorf("Unexpected number of total sent count: %d", mmsMessage.Recipients.TotalSentCount)
	}

	if mmsMessage.Recipients.Items[0].Recipient != 31612345678 {
		t.Errorf("Unexpected mmsMessage recipient: %d", mmsMessage.Recipients.Items[0].Recipient)
	}

	if mmsMessage.Recipients.Items[0].Status != "sent" {
		t.Errorf("Unexpected mmsMessage recipient status: %s", mmsMessage.Recipients.Items[0].Status)
	}

	if mmsMessage.Recipients.Items[0].StatusDatetime == nil || mmsMessage.Recipients.Items[0].StatusDatetime.Format(time.RFC3339) != "2017-10-20T12:50:28Z" {
		t.Errorf("Unexpected datetime status for mmsMessage recipient: %s", mmsMessage.Recipients.Items[0].StatusDatetime.Format(time.RFC3339))
	}

	if len(mmsMessage.Errors) != 0 {
		t.Errorf("Unexpected number of errors in mmsMessage: %d", len(mmsMessage.Errors))
	}
}

func TestNewMmsMessageError(t *testing.T) {
	SetServerResponse(405, accessKeyErrorObject)

	mmsMessage, err := mbClient.NewMmsMessage("TestName", []string{"31612345678"}, "Hello World", nil, nil)
	if err != ErrResponse {
		t.Fatalf("Expected ErrResponse to be returned, instead I got %s", err)
	}

	if len(mmsMessage.Errors) != 1 {
		t.Fatalf("Unexpected number of errors: %d", len(mmsMessage.Errors))
	}

	if mmsMessage.Errors[0].Code != 2 {
		t.Errorf("Unexpected error code: %d", mmsMessage.Errors[0].Code)
	}

	if mmsMessage.Errors[0].Parameter != "access_key" {
		t.Errorf("Unexpected error parameter %s", mmsMessage.Errors[0].Parameter)
	}
}

func TestNewMmsMessageWithParams(t *testing.T) {
	SetServerResponse(200, mmsMessageObjectWithParams)

	params := &MmsMessageParams{
		Subject:   "Test-Subject",
		Reference: "Test-Reference",
	}

	mmsMessage, err := mbClient.NewMmsMessage("TestName", []string{"31612345678"}, "", []string{"http://w3.org/1.gif", "http://w3.org/2.gif"}, params)
	if err != nil {
		t.Fatalf("Didn't expect error while creating a new MMS message: %s", err)
	}

	if mmsMessage.Subject != "Test-Subject" {
		t.Errorf("Unexpected message subject: %s", mmsMessage.Subject)
	}
	if mmsMessage.Reference != "Test-Reference" {
		t.Errorf("Unexpected message reference: %s", mmsMessage.Reference)
	}
}

var mmsMessageObjectWithParams []byte = []byte(`{
    "id": "6d9e7100b1f9406c81a3c303c30ccf05",
    "href": "https://rest.messagebird.com/mms/6d9e7100b1f9406c81a3c303c30ccf05",
    "direction": "mt",
    "originator": "TestName",
    "subject": "Test-Subject",
    "body": "Hello World",
    "mediaUrls": [],
    "reference": "Test-Reference",
    "scheduledDatetime": null,
    "createdDatetime": "2017-10-20T12:50:28+00:00",
    "recipients": {
        "totalCount": 1,
        "totalSentCount": 1,
        "totalDeliveredCount": 0,
        "totalDeliveryFailedCount": 0,
        "items": [
            {
                "recipient": 31612345678,
                "status": "sent",
                "statusDatetime": "2017-10-20T12:50:28+00:00"
            }
        ]
    }
}`)
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"io/ioutil"
	"kite"
	"kite/kontrol"
	"koding/db/mongodb/modelhelper"
	"koding/tools/config"
	"log"
	"strconv"
)

var (
	profile = flag.String("c", "", "Configuration profile")
	region  = flag.String("r", "", "Region")
)

func main() {
	flag.Parse()
	if *profile == "" {
		log.Fatal("Please specify profile via -c. Aborting.")
	}
	if *region == "" {
		log.Fatal("Please specify region via -r. Aborting.")
	}

	conf := config.MustConfig(*profile)
	modelhelper.Initialize(conf.Mongo)

	kiteOptions := &kite.Options{
		Kitename:    "kontrol",
		Version:     "0.0.1",
		Port:        strconv.Itoa(conf.NewKontrol.Port),
		Environment: conf.Environment,
		Region:      *region,
	}

	// Read list of etcd servers from config.
	machines := make([]string, len(conf.Etcd))
	for i, s := range conf.Etcd {
		machines[i] = "http://" + s.Host + ":" + strconv.FormatUint(uint64(s.Port), 10)
	}

	publicKey, err := ioutil.ReadFile(conf.NewKontrol.PublicKeyFile)
	if err != nil {
		log.Fatalln(err.Error())
	}

	privateKey, err := ioutil.ReadFile(conf.NewKontrol.PrivateKeyFile)
	if err != nil {
		log.Fatalln(err.Error())
	}

	kon := kontrol.New(kiteOptions, "kontrol", "/tmp/kontrol-data", machines, string(publicKey), string(privateKey))

	kon.AddAuthenticator("sessionID", authenticateFromSessionID)

	if conf.NewKontrol.UseTLS {
		kon.EnableTLS(conf.NewKontrol.CertFile, conf.NewKontrol.KeyFile)
	}

	kon.Run()
}

func authenticateFromSessionID(r *kite.Request) error {
	username, err := findUsernameFromSessionID(r.Authentication.Key)
	if err != nil {
		return err
	}

	r.Username = username

	return nil
}

func findUsernameFromSessionID(sessionID string) (string, error) {
	session, err := modelhelper.GetSession(sessionID)
	if err != nil {
		return "", err
	}

	return session.Username, nil
}
<commit_msg>fix kontrol<commit_after>package main

import (
	"flag"
	"io/ioutil"
	"kite"
	"kite/kontrol"
	"koding/db/mongodb/modelhelper"
	"koding/tools/config"
	"log"
	"strconv"
)

var (
	profile = flag.String("c", "", "Configuration profile")
	region  = flag.String("r", "", "Region")
)

func main() {
	flag.Parse()
	if *profile == "" {
		log.Fatal("Please specify profile via -c. Aborting.")
	}
	if *region == "" {
		log.Fatal("Please specify region via -r. Aborting.")
	}

	conf := config.MustConfig(*profile)
	modelhelper.Initialize(conf.Mongo)

	kiteOptions := &kite.Options{
		Kitename:    "kontrol",
		Version:     "0.0.1",
		Port:        strconv.Itoa(conf.NewKontrol.Port),
		Environment: conf.Environment,
		Region:      *region,
	}

	// Read list of etcd servers from config.
	machines := make([]string, len(conf.Etcd))
	for i, s := range conf.Etcd {
		machines[i] = "http://" + s.Host + ":" + strconv.FormatUint(uint64(s.Port), 10)
	}

	publicKey, err := ioutil.ReadFile(conf.NewKontrol.PublicKeyFile)
	if err != nil {
		log.Fatalln(err.Error())
	}

	privateKey, err := ioutil.ReadFile(conf.NewKontrol.PrivateKeyFile)
	if err != nil {
		log.Fatalln(err.Error())
	}

	kon := kontrol.New(kiteOptions, "kontrol", "/tmp/kontrol-data", nil, string(publicKey), string(privateKey))

	kon.AddAuthenticator("sessionID", authenticateFromSessionID)

	if conf.NewKontrol.UseTLS {
		kon.EnableTLS(conf.NewKontrol.CertFile, conf.NewKontrol.KeyFile)
	}

	kon.Run()
}

func authenticateFromSessionID(r *kite.Request) error {
	username, err := findUsernameFromSessionID(r.Authentication.Key)
	if err != nil {
		return err
	}

	r.Username = username

	return nil
}

func findUsernameFromSessionID(sessionID string) (string, error) {
	session, err := modelhelper.GetSession(sessionID)
	if err != nil {
		return "", err
	}

	return session.Username, nil
}
<|endoftext|>


<commit_before>package models

import (
	"errors"
	"fmt"
	"socialapi/request"
	"time"

	"github.com/koding/bongo"
)

var ErrNotSetMessageId = errors.New("messageId is not set")

type Interaction struct {
	// unique identifier of the Interaction
	Id int64 `json:"id,string"`

	// Id of the interacted message
	MessageId int64 `json:"messageId,string"      sql:"NOT NULL"`

	// Id of the actor
	AccountId int64 `json:"accountId,string"      sql:"NOT NULL"`

	// holds troll, unsafe, etc
	MetaBits MetaBits `json:"metaBits"`

	// Type of the interaction
	TypeConstant string `json:"typeConstant"      sql:"NOT NULL;TYPE:VARCHAR(100);"`

	// Creation of the interaction
	CreatedAt time.Time `json:"createdAt"         sql:"NOT NULL"`
}

var AllowedInteractions = map[string]struct{}{
	"like":     {},
	"upvote":   {},
	"downvote": {},
}

const (
	Interaction_TYPE_LIKE     = "like"
	Interaction_TYPE_UPVOTE   = "upvote"
	Interaction_TYPE_DONWVOTE = "downvote"
)

func (i *Interaction) CreateRaw() error {
	insertSql := "INSERT INTO " +
		i.TableName() +
		` ("message_id","account_id","type_constant","created_at") VALUES ($1,$2,$3,$4) ` +
		"RETURNING ID"

	return bongo.B.DB.CommonDB().
		QueryRow(insertSql, i.MessageId, i.AccountId, i.TypeConstant, i.CreatedAt).
		Scan(&i.Id)
}

func (i *Interaction) MarkIfExempt() error {
	isExempt, err := i.isExempt()
	if err != nil {
		return err
	}

	if isExempt {
		i.MetaBits.Mark(Troll)
	}

	return nil
}

func (i *Interaction) isExempt() (bool, error) {
	if i.MetaBits.Is(Troll) {
		return true, nil
	}

	accountId, err := i.getAccountId()
	if err != nil {
		return false, err
	}

	account, err := ResetAccountCache(accountId)
	if err != nil {
		return false, err
	}

	if account == nil {
		return false, fmt.Errorf("account is nil, accountId:%d", i.AccountId)
	}

	if account.IsTroll {
		return true, nil
	}

	return false, nil
}

func (i *Interaction) getAccountId() (int64, error) {
	if i.AccountId != 0 {
		return i.AccountId, nil
	}

	if i.Id == 0 {
		return 0, fmt.Errorf("couldnt find accountId from content %+v", i)
	}

	ii := NewInteraction()
	if err := ii.ById(i.Id); err != nil {
		return 0, err
	}

	return ii.AccountId, nil
}

func (i *Interaction) Delete() error {
	selector := map[string]interface{}{
		"message_id": i.MessageId,
		"account_id": i.AccountId,
	}

	if err := i.One(bongo.NewQS(selector)); err != nil {
		return err
	}

	if err := bongo.B.Delete(i); err != nil {
		return err
	}

	return nil
}

func (i *Interaction) List(query *request.Query) ([]int64, error) {
	var interactions []int64

	if i.MessageId == 0 {
		return interactions, ErrNotSetMessageId
	}

	return i.FetchInteractorIds(query)
}

func (i *Interaction) FetchInteractorIds(query *request.Query) ([]int64, error) {
	interactorIds := make([]int64, 0)
	q := &bongo.Query{
		Selector: map[string]interface{}{
			"message_id":    i.MessageId,
			"type_constant": query.Type,
		},
		Pagination: *bongo.NewPagination(query.Limit, query.Skip),
		Pluck:      "account_id",
		Sort: map[string]string{
			"created_at": "desc",
		},
	}

	q.AddScope(RemoveTrollContent(i, query.ShowExempt))

	if err := i.Some(&interactorIds, q); err != nil {
		// TODO log this error
		return make([]int64, 0), nil
	}

	return interactorIds, nil
}

func (c *Interaction) Count(q *request.Query) (int, error) {
	if c.MessageId == 0 {
		return 0, ErrNotSetMessageId
	}

	if q.Type == "" {
		return 0, errors.New("query type is not set")
	}

	query := &bongo.Query{
		Selector: map[string]interface{}{
			"message_id":    c.MessageId,
			"type_constant": q.Type,
		},
	}

	query.AddScope(RemoveTrollContent(
		c, q.ShowExempt,
	))

	i := NewInteraction()
	*i = *c
	i.Id = 0

	return i.CountWithQuery(query)
}

func (c *Interaction) CountWithQuery(q *bongo.Query) (int, error) {
	return bongo.B.CountWithQuery(c, q)
}

func (c *Interaction) FetchAll(interactionType string) ([]Interaction, error) {
	var interactions []Interaction

	if c.MessageId == 0 {
		return interactions, errors.New("channelId is not set")
	}

	selector := map[string]interface{}{
		"message_id":    c.MessageId,
		"type_constant": interactionType,
	}

	err := c.Some(&interactions, bongo.NewQS(selector))
	if err != nil {
		return interactions, err
	}

	return interactions, nil
}

func (i *Interaction) IsInteracted(accountId int64) (bool, error) {
	if i.MessageId == 0 {
		return false, ErrNotSetMessageId
	}

	selector := map[string]interface{}{
		"message_id": i.MessageId,
		"account_id": accountId,
	}

	// do not set
	err := NewInteraction().One(bongo.NewQS(selector))
	if err == nil {
		return true, nil
	}

	if err == bongo.RecordNotFound {
		return false, nil
	}

	return false, err
}

func (i *Interaction) FetchInteractorCount() (int, error) {
	return bongo.B.Count(i, "message_id = ?", i.MessageId)
}

func (i *Interaction) FetchInteractionContainer(query *request.Query) (*InteractionContainer, error) {
	if i.MessageId == 0 {
		return nil, ErrNotSetMessageId
	}

	interactorIds, err := i.List(query)
	if err != nil {
		return nil, err
	}

	oldIds, err := FetchOldIdsByAccountIds(interactorIds)
	if err != nil {
		return nil, err
	}

	interactionContainer := NewInteractionContainer()
	interactionContainer.ActorsPreview = oldIds

	// check if the current user is interacted in this thread
	isInteracted, err := i.IsInteracted(query.AccountId)
	if err != nil {
		return nil, err
	}

	interactionContainer.IsInteracted = isInteracted

	// fetch interaction count
	count, err := i.Count(query)
	if err != nil {
		return nil, err
	}

	interactionContainer.ActorsCount = count

	return interactionContainer, nil
}
<commit_msg>Social: put is adding interacted data behind a flag<commit_after>package models

import (
	"errors"
	"fmt"
	"socialapi/request"
	"time"

	"github.com/koding/bongo"
)

var ErrNotSetMessageId = errors.New("messageId is not set")

type Interaction struct {
	// unique identifier of the Interaction
	Id int64 `json:"id,string"`

	// Id of the interacted message
	MessageId int64 `json:"messageId,string"      sql:"NOT NULL"`

	// Id of the actor
	AccountId int64 `json:"accountId,string"      sql:"NOT NULL"`

	// holds troll, unsafe, etc
	MetaBits MetaBits `json:"metaBits"`

	// Type of the interaction
	TypeConstant string `json:"typeConstant"      sql:"NOT NULL;TYPE:VARCHAR(100);"`

	// Creation of the interaction
	CreatedAt time.Time `json:"createdAt"         sql:"NOT NULL"`
}

var AllowedInteractions = map[string]struct{}{
	"like":     {},
	"upvote":   {},
	"downvote": {},
}

const (
	Interaction_TYPE_LIKE     = "like"
	Interaction_TYPE_UPVOTE   = "upvote"
	Interaction_TYPE_DONWVOTE = "downvote"
)

func (i *Interaction) CreateRaw() error {
	insertSql := "INSERT INTO " +
		i.TableName() +
		` ("message_id","account_id","type_constant","created_at") VALUES ($1,$2,$3,$4) ` +
		"RETURNING ID"

	return bongo.B.DB.CommonDB().
		QueryRow(insertSql, i.MessageId, i.AccountId, i.TypeConstant, i.CreatedAt).
		Scan(&i.Id)
}

func (i *Interaction) MarkIfExempt() error {
	isExempt, err := i.isExempt()
	if err != nil {
		return err
	}

	if isExempt {
		i.MetaBits.Mark(Troll)
	}

	return nil
}

func (i *Interaction) isExempt() (bool, error) {
	if i.MetaBits.Is(Troll) {
		return true, nil
	}

	accountId, err := i.getAccountId()
	if err != nil {
		return false, err
	}

	account, err := ResetAccountCache(accountId)
	if err != nil {
		return false, err
	}

	if account == nil {
		return false, fmt.Errorf("account is nil, accountId:%d", i.AccountId)
	}

	if account.IsTroll {
		return true, nil
	}

	return false, nil
}

func (i *Interaction) getAccountId() (int64, error) {
	if i.AccountId != 0 {
		return i.AccountId, nil
	}

	if i.Id == 0 {
		return 0, fmt.Errorf("couldnt find accountId from content %+v", i)
	}

	ii := NewInteraction()
	if err := ii.ById(i.Id); err != nil {
		return 0, err
	}

	return ii.AccountId, nil
}

func (i *Interaction) Delete() error {
	selector := map[string]interface{}{
		"message_id": i.MessageId,
		"account_id": i.AccountId,
	}

	if err := i.One(bongo.NewQS(selector)); err != nil {
		return err
	}

	if err := bongo.B.Delete(i); err != nil {
		return err
	}

	return nil
}

func (i *Interaction) List(query *request.Query) ([]int64, error) {
	var interactions []int64

	if i.MessageId == 0 {
		return interactions, ErrNotSetMessageId
	}

	return i.FetchInteractorIds(query)
}

func (i *Interaction) FetchInteractorIds(query *request.Query) ([]int64, error) {
	interactorIds := make([]int64, 0)
	q := &bongo.Query{
		Selector: map[string]interface{}{
			"message_id":    i.MessageId,
			"type_constant": query.Type,
		},
		Pagination: *bongo.NewPagination(query.Limit, query.Skip),
		Pluck:      "account_id",
		Sort: map[string]string{
			"created_at": "desc",
		},
	}

	q.AddScope(RemoveTrollContent(i, query.ShowExempt))

	if err := i.Some(&interactorIds, q); err != nil {
		// TODO log this error
		return make([]int64, 0), nil
	}

	return interactorIds, nil
}

func (c *Interaction) Count(q *request.Query) (int, error) {
	if c.MessageId == 0 {
		return 0, ErrNotSetMessageId
	}

	if q.Type == "" {
		return 0, errors.New("query type is not set")
	}

	query := &bongo.Query{
		Selector: map[string]interface{}{
			"message_id":    c.MessageId,
			"type_constant": q.Type,
		},
	}

	query.AddScope(RemoveTrollContent(
		c, q.ShowExempt,
	))

	i := NewInteraction()
	*i = *c
	i.Id = 0

	return i.CountWithQuery(query)
}

func (c *Interaction) CountWithQuery(q *bongo.Query) (int, error) {
	return bongo.B.CountWithQuery(c, q)
}

func (c *Interaction) FetchAll(interactionType string) ([]Interaction, error) {
	var interactions []Interaction

	if c.MessageId == 0 {
		return interactions, errors.New("channelId is not set")
	}

	selector := map[string]interface{}{
		"message_id":    c.MessageId,
		"type_constant": interactionType,
	}

	err := c.Some(&interactions, bongo.NewQS(selector))
	if err != nil {
		return interactions, err
	}

	return interactions, nil
}

func (i *Interaction) IsInteracted(accountId int64) (bool, error) {
	if i.MessageId == 0 {
		return false, ErrNotSetMessageId
	}

	selector := map[string]interface{}{
		"message_id": i.MessageId,
		"account_id": accountId,
	}

	// do not set
	err := NewInteraction().One(bongo.NewQS(selector))
	if err == nil {
		return true, nil
	}

	if err == bongo.RecordNotFound {
		return false, nil
	}

	return false, err
}

func (i *Interaction) FetchInteractorCount() (int, error) {
	return bongo.B.Count(i, "message_id = ?", i.MessageId)
}

func (i *Interaction) FetchInteractionContainer(query *request.Query) (*InteractionContainer, error) {
	if i.MessageId == 0 {
		return nil, ErrNotSetMessageId
	}

	interactorIds, err := i.List(query)
	if err != nil {
		return nil, err
	}

	oldIds, err := FetchOldIdsByAccountIds(interactorIds)
	if err != nil {
		return nil, err
	}

	interactionContainer := NewInteractionContainer()
	interactionContainer.ActorsPreview = oldIds

	if query.AddIsInteracted {
		// check if the current user is interacted in this thread
		isInteracted, err := i.IsInteracted(query.AccountId)
		if err != nil {
			return nil, err
		}

		interactionContainer.IsInteracted = isInteracted
	}

	// fetch interaction count
	count, err := i.Count(query)
	if err != nil {
		return nil, err
	}

	interactionContainer.ActorsCount = count

	return interactionContainer, nil
}
<|endoftext|>


<commit_before>package models

import (
	"errors"
	"socialapi/request"
	"time"
	"github.com/koding/bongo"
)

type Interaction struct {
	// unique identifier of the Interaction
	Id int64 `json:"id,string"`

	// Id of the interacted message
	MessageId int64 `json:"messageId,string"      sql:"NOT NULL"`

	// Id of the actor
	AccountId int64 `json:"accountId,string"      sql:"NOT NULL"`

	// holds troll, unsafe, etc
	MetaBits MetaBits `json:"-"`

	// Type of the interaction
	TypeConstant string `json:"typeConstant"      sql:"NOT NULL;TYPE:VARCHAR(100);"`

	// Creation of the interaction
	CreatedAt time.Time `json:"createdAt"         sql:"NOT NULL"`
}

var AllowedInteractions = map[string]struct{}{
	"like":     struct{}{},
	"upvote":   struct{}{},
	"downvote": struct{}{},
}

const (
	Interaction_TYPE_LIKE     = "like"
	Interaction_TYPE_UPVOTE   = "upvote"
	Interaction_TYPE_DONWVOTE = "downvote"
)

func (i Interaction) GetId() int64 {
	return i.Id
}

func (i Interaction) TableName() string {
	return "api.interaction"
}

func NewInteraction() *Interaction {
	return &Interaction{}
}

func (i *Interaction) One(q *bongo.Query) error {
	return bongo.B.One(i, i, q)
}

func (i *Interaction) ById(id int64) error {
	return bongo.B.ById(i, id)
}

func (i *Interaction) Create() error {
	return bongo.B.Create(i)
}

func (i *Interaction) CreateRaw() error {
	insertSql := "INSERT INTO " +
		i.TableName() +
		` ("message_id","account_id","type_constant","created_at") VALUES ($1,$2,$3,$4) ` +
		"RETURNING ID"

	return bongo.B.DB.CommonDB().
		QueryRow(insertSql, i.MessageId, i.AccountId, i.TypeConstant, i.CreatedAt).
		Scan(&i.Id)
}

func (i *Interaction) AfterCreate() {
	bongo.B.AfterCreate(i)
}

func (i *Interaction) AfterUpdate() {
	bongo.B.AfterUpdate(i)
}

func (i Interaction) AfterDelete() {
	bongo.B.AfterDelete(i)
}

func (i *Interaction) BeforeCreate() {
	i.assignTrollModeBitIfRequired()
}

func (i *Interaction) BeforeUpdate() {
	i.assignTrollModeBitIfRequired()
}

func (i *Interaction) assignTrollModeBitIfRequired() {
	cm := NewChannelMessage()
	cm.Id = i.MessageId
	cm.AccountId = i.AccountId
	if res, err := cm.isExemptContent(); err == nil && res {
		i.MetaBits = updateTrollModeBit(i.MetaBits)
	}
}

func (i *Interaction) Some(data interface{}, q *bongo.Query) error {
	return bongo.B.Some(i, data, q)
}

func (i *Interaction) Delete() error {
	selector := map[string]interface{}{
		"message_id": i.MessageId,
		"account_id": i.AccountId,
	}

	if err := i.One(bongo.NewQS(selector)); err != nil {
		return err
	}

	if err := bongo.B.Delete(i); err != nil {
		return err
	}

	return nil
}

func (c *Interaction) List(query *request.Query) ([]int64, error) {
	var interactions []int64

	if c.MessageId == 0 {
		return interactions, errors.New("Message is not set")
	}

	return c.FetchInteractorIds(query)
}

func (i *Interaction) FetchInteractorIds(query *request.Query) ([]int64, error) {
	interactorIds := make([]int64, 0)
	q := &bongo.Query{
		Selector: map[string]interface{}{
			"message_id":    i.MessageId,
			"type_constant": query.Type,
		},
		Pagination: *bongo.NewPagination(query.Limit, query.Skip),
		Pluck:      "account_id",
		Sort: map[string]string{
			"created_at": "desc",
		},
	}

	q.AddScope(RemoveTrollContent(i, query.ShowExempt))

	if err := i.Some(&interactorIds, q); err != nil {
		// TODO log this error
		return make([]int64, 0), nil
	}

	return interactorIds, nil
}

func (c *Interaction) Count(q *request.Query) (int, error) {
	if c.MessageId == 0 {
		return 0, errors.New("messageId is not set")
	}

	if q.Type == "" {
		return 0, errors.New("query type is not set")
	}

	query := &bongo.Query{
		Selector: map[string]interface{}{
			"message_id":    c.MessageId,
			"type_constant": q.Type,
		},
	}

	query.AddScope(RemoveTrollContent(
		c, q.ShowExempt,
	))

	return c.CountWithQuery(query)
}

func (c *Interaction) CountWithQuery(q *bongo.Query) (int, error) {
	return bongo.B.CountWithQuery(c, q)
}

func (c *Interaction) FetchAll(interactionType string) ([]Interaction, error) {
	var interactions []Interaction

	if c.MessageId == 0 {
		return interactions, errors.New("ChannelId is not set")
	}

	selector := map[string]interface{}{
		"message_id":    c.MessageId,
		"type_constant": interactionType,
	}

	err := c.Some(&interactions, bongo.NewQS(selector))
	if err != nil {
		return interactions, err
	}

	return interactions, nil
}

func (i *Interaction) IsInteracted(accountId int64) (bool, error) {
	if i.MessageId == 0 {
		return false, errors.New("Message Id is not set")
	}

	selector := map[string]interface{}{
		"message_id": i.MessageId,
		"account_id": accountId,
	}

	err := i.One(bongo.NewQS(selector))
	if err == nil {
		return true, nil
	}

	if err == bongo.RecordNotFound {
		return false, nil
	}

	return false, err
}

func (i *Interaction) FetchInteractorCount() (int, error) {
	return bongo.B.Count(i, "message_id = ?", i.MessageId)
}
<commit_msg>Social: add Update function for interactions<commit_after>package models

import (
	"errors"
	"socialapi/request"
	"time"

	"github.com/koding/bongo"
)

type Interaction struct {
	// unique identifier of the Interaction
	Id int64 `json:"id,string"`

	// Id of the interacted message
	MessageId int64 `json:"messageId,string"      sql:"NOT NULL"`

	// Id of the actor
	AccountId int64 `json:"accountId,string"      sql:"NOT NULL"`

	// holds troll, unsafe, etc
	MetaBits MetaBits `json:"-"`

	// Type of the interaction
	TypeConstant string `json:"typeConstant"      sql:"NOT NULL;TYPE:VARCHAR(100);"`

	// Creation of the interaction
	CreatedAt time.Time `json:"createdAt"         sql:"NOT NULL"`
}

var AllowedInteractions = map[string]struct{}{
	"like":     struct{}{},
	"upvote":   struct{}{},
	"downvote": struct{}{},
}

const (
	Interaction_TYPE_LIKE     = "like"
	Interaction_TYPE_UPVOTE   = "upvote"
	Interaction_TYPE_DONWVOTE = "downvote"
)

func (i Interaction) GetId() int64 {
	return i.Id
}

func (i Interaction) TableName() string {
	return "api.interaction"
}

func NewInteraction() *Interaction {
	return &Interaction{}
}

func (i *Interaction) One(q *bongo.Query) error {
	return bongo.B.One(i, i, q)
}

func (i *Interaction) ById(id int64) error {
	return bongo.B.ById(i, id)
}

func (i *Interaction) Create() error {
	return bongo.B.Create(i)
}

func (i *Interaction) Update() error {
	return bongo.B.Update(i)
}

func (i *Interaction) CreateRaw() error {
	insertSql := "INSERT INTO " +
		i.TableName() +
		` ("message_id","account_id","type_constant","created_at") VALUES ($1,$2,$3,$4) ` +
		"RETURNING ID"

	return bongo.B.DB.CommonDB().
		QueryRow(insertSql, i.MessageId, i.AccountId, i.TypeConstant, i.CreatedAt).
		Scan(&i.Id)
}

func (i *Interaction) AfterCreate() {
	bongo.B.AfterCreate(i)
}

func (i *Interaction) AfterUpdate() {
	bongo.B.AfterUpdate(i)
}

func (i Interaction) AfterDelete() {
	bongo.B.AfterDelete(i)
}

func (i *Interaction) BeforeCreate() {
	i.assignTrollModeBitIfRequired()
}

func (i *Interaction) BeforeUpdate() {
	i.assignTrollModeBitIfRequired()
}

func (i *Interaction) assignTrollModeBitIfRequired() {
	cm := NewChannelMessage()
	cm.Id = i.MessageId
	cm.AccountId = i.AccountId
	if res, err := cm.isExemptContent(); err == nil && res {
		i.MetaBits = updateTrollModeBit(i.MetaBits)
	}
}

func (i *Interaction) Some(data interface{}, q *bongo.Query) error {
	return bongo.B.Some(i, data, q)
}

func (i *Interaction) Delete() error {
	selector := map[string]interface{}{
		"message_id": i.MessageId,
		"account_id": i.AccountId,
	}

	if err := i.One(bongo.NewQS(selector)); err != nil {
		return err
	}

	if err := bongo.B.Delete(i); err != nil {
		return err
	}

	return nil
}

func (c *Interaction) List(query *request.Query) ([]int64, error) {
	var interactions []int64

	if c.MessageId == 0 {
		return interactions, errors.New("Message is not set")
	}

	return c.FetchInteractorIds(query)
}

func (i *Interaction) FetchInteractorIds(query *request.Query) ([]int64, error) {
	interactorIds := make([]int64, 0)
	q := &bongo.Query{
		Selector: map[string]interface{}{
			"message_id":    i.MessageId,
			"type_constant": query.Type,
		},
		Pagination: *bongo.NewPagination(query.Limit, query.Skip),
		Pluck:      "account_id",
		Sort: map[string]string{
			"created_at": "desc",
		},
	}

	q.AddScope(RemoveTrollContent(i, query.ShowExempt))

	if err := i.Some(&interactorIds, q); err != nil {
		// TODO log this error
		return make([]int64, 0), nil
	}

	return interactorIds, nil
}

func (c *Interaction) Count(q *request.Query) (int, error) {
	if c.MessageId == 0 {
		return 0, errors.New("messageId is not set")
	}

	if q.Type == "" {
		return 0, errors.New("query type is not set")
	}

	query := &bongo.Query{
		Selector: map[string]interface{}{
			"message_id":    c.MessageId,
			"type_constant": q.Type,
		},
	}

	query.AddScope(RemoveTrollContent(
		c, q.ShowExempt,
	))

	return c.CountWithQuery(query)
}

func (c *Interaction) CountWithQuery(q *bongo.Query) (int, error) {
	return bongo.B.CountWithQuery(c, q)
}

func (c *Interaction) FetchAll(interactionType string) ([]Interaction, error) {
	var interactions []Interaction

	if c.MessageId == 0 {
		return interactions, errors.New("ChannelId is not set")
	}

	selector := map[string]interface{}{
		"message_id":    c.MessageId,
		"type_constant": interactionType,
	}

	err := c.Some(&interactions, bongo.NewQS(selector))
	if err != nil {
		return interactions, err
	}

	return interactions, nil
}

func (i *Interaction) IsInteracted(accountId int64) (bool, error) {
	if i.MessageId == 0 {
		return false, errors.New("Message Id is not set")
	}

	selector := map[string]interface{}{
		"message_id": i.MessageId,
		"account_id": accountId,
	}

	err := i.One(bongo.NewQS(selector))
	if err == nil {
		return true, nil
	}

	if err == bongo.RecordNotFound {
		return false, nil
	}

	return false, err
}

func (i *Interaction) FetchInteractorCount() (int, error) {
	return bongo.B.Count(i, "message_id = ?", i.MessageId)
}
<|endoftext|>