<commit_before>/*
Copyright 2015 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package ipvs

import (
	"sync"
	"time"

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

func (m *GracefulTerminationManager) deleteRsFunc(rsToDelete *listItem) (bool, error) {
	klog.Infof("Trying to delete rs: %s", rsToDelete.String())
	rss, err := m.ipvs.GetRealServers(rsToDelete.VirtualServer)
	if err != nil {
		return false, err
	}
	for _, rs := range rss {
		if rsToDelete.RealServer.Equal(rs) {
			// Don't delete TCP RS with Active Connections or UDP RS (ActiveConn is always 0 for UDP)
			if rs.ActiveConn != 0 || (rsToDelete.VirtualServer.Protocol == "UDP" && rs.InactiveConn != 0) {
				return false, nil
			}
			klog.Infof("Deleting rs: %s", rsToDelete.String())
			err := m.ipvs.DeleteRealServer(rsToDelete.VirtualServer, rs)
			if err != nil {
				return false, fmt.Errorf("Delete destination %q err: %v", rs.String(), err)
			}
			return true, nil
		}
	}
	return true, fmt.Errorf("Failed to delete rs %q, can't find the real server", rsToDelete.String())
}

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

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

// Run start a goroutine to try to delete rs in the graceful delete rsList with an interval 1 minute
func (m *GracefulTerminationManager) Run() {
	// before start, add leftover in delete rs to graceful delete rsList
	vss, err := m.ipvs.GetVirtualServers()
	if err != nil {
		klog.Errorf("IPVS graceful delete manager failed to get IPVS virtualserver")
	}
	for _, vs := range vss {
		rss, err := m.ipvs.GetRealServers(vs)
		if err != nil {
			klog.Errorf("IPVS graceful delete manager failed to get %v realserver", vs)
			continue
		}
		for _, rs := range rss {
			m.GracefulDeleteRS(vs, rs)
		}
	}

	go wait.Until(m.tryDeleteRs, rsCheckDeleteInterval, wait.NeverStop)
}
<commit_msg>[kube-proxy/ipvs] Generalize handling of InactiveConn to TCP<commit_after>/*
Copyright 2015 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package ipvs

import (
	"sync"
	"time"

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

func (m *GracefulTerminationManager) deleteRsFunc(rsToDelete *listItem) (bool, error) {
	klog.Infof("Trying to delete rs: %s", rsToDelete.String())
	rss, err := m.ipvs.GetRealServers(rsToDelete.VirtualServer)
	if err != nil {
		return false, err
	}
	for _, rs := range rss {
		if rsToDelete.RealServer.Equal(rs) {
			// Delete RS with no connections
			// For UDP, ActiveConn is always 0
			// For TCP, InactiveConn are connections not in ESTABLISHED state
			if rs.ActiveConn+rs.InactiveConn != 0 {
				return false, nil
			}
			klog.Infof("Deleting rs: %s", rsToDelete.String())
			err := m.ipvs.DeleteRealServer(rsToDelete.VirtualServer, rs)
			if err != nil {
				return false, fmt.Errorf("Delete destination %q err: %v", rs.String(), err)
			}
			return true, nil
		}
	}
	return true, fmt.Errorf("Failed to delete rs %q, can't find the real server", rsToDelete.String())
}

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

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

// Run start a goroutine to try to delete rs in the graceful delete rsList with an interval 1 minute
func (m *GracefulTerminationManager) Run() {
	// before start, add leftover in delete rs to graceful delete rsList
	vss, err := m.ipvs.GetVirtualServers()
	if err != nil {
		klog.Errorf("IPVS graceful delete manager failed to get IPVS virtualserver")
	}
	for _, vs := range vss {
		rss, err := m.ipvs.GetRealServers(vs)
		if err != nil {
			klog.Errorf("IPVS graceful delete manager failed to get %v realserver", vs)
			continue
		}
		for _, rs := range rss {
			m.GracefulDeleteRS(vs, rs)
		}
	}

	go wait.Until(m.tryDeleteRs, rsCheckDeleteInterval, wait.NeverStop)
}
<|endoftext|>


<commit_before>package tsdb

import (
	"context"

	"github.com/prometheus/common/model"
	"github.com/prometheus/prometheus/model/labels"

	"github.com/grafana/loki/pkg/storage/chunk"
	"github.com/grafana/loki/pkg/storage/stores/tsdb/index"
)

// TenantLabel is part of the reserved label namespace (__ prefix)
// It's used to create multi-tenant TSDBs (which do not have a tenancy concept)
// These labels are stripped out during compaction to single-tenant TSDBs
const TenantLabel = "__loki_tenant__"

// MultiTenantIndex will inject a tenant label to it's queries
// This works with pre-compacted TSDBs which aren't yet per tenant.
type MultiTenantIndex struct {
	idx Index
}

func NewMultiTenantIndex(idx Index) *MultiTenantIndex {
	return &MultiTenantIndex{idx: idx}
}

func withTenantLabel(userID string, matchers []*labels.Matcher) []*labels.Matcher {
	cpy := make([]*labels.Matcher, len(matchers))
	copy(cpy, matchers)
	cpy = append(cpy, labels.MustNewMatcher(labels.MatchEqual, TenantLabel, userID))
	return cpy
}

func withoutTenantLabel(ls labels.Labels) labels.Labels {
	for i, l := range ls {
		if l.Name == TenantLabel {
			ls = append(ls[:i], ls[i+1:]...)
			break
		}
	}
	return ls
}

func (m *MultiTenantIndex) Bounds() (model.Time, model.Time) { return m.idx.Bounds() }

func (m *MultiTenantIndex) SetChunkFilterer(chunkFilter chunk.RequestChunkFilterer) {
	m.idx.SetChunkFilterer(chunkFilter)
}

func (m *MultiTenantIndex) Close() error { return m.idx.Close() }

func (m *MultiTenantIndex) GetChunkRefs(ctx context.Context, userID string, from, through model.Time, res []ChunkRef, shard *index.ShardAnnotation, matchers ...*labels.Matcher) ([]ChunkRef, error) {
	return m.idx.GetChunkRefs(ctx, userID, from, through, res, shard, withTenantLabel(userID, matchers)...)
}

func (m *MultiTenantIndex) Series(ctx context.Context, userID string, from, through model.Time, res []Series, shard *index.ShardAnnotation, matchers ...*labels.Matcher) ([]Series, error) {
	xs, err := m.idx.Series(ctx, userID, from, through, res, shard, withTenantLabel(userID, matchers)...)
	if err != nil {
		return nil, err
	}
	for i := range xs {
		xs[i].Labels = withoutTenantLabel(xs[i].Labels)
	}
	return xs, nil
}

func (m *MultiTenantIndex) LabelNames(ctx context.Context, userID string, from, through model.Time, matchers ...*labels.Matcher) ([]string, error) {
	return m.idx.LabelNames(ctx, userID, from, through, withTenantLabel(userID, matchers)...)
}

func (m *MultiTenantIndex) LabelValues(ctx context.Context, userID string, from, through model.Time, name string, matchers ...*labels.Matcher) ([]string, error) {
	return m.idx.LabelValues(ctx, userID, from, through, name, withTenantLabel(userID, matchers)...)
}
<commit_msg>strips out tenant label from LabelNames queries (#6181)<commit_after>package tsdb

import (
	"context"
	"sort"

	"github.com/prometheus/common/model"
	"github.com/prometheus/prometheus/model/labels"

	"github.com/grafana/loki/pkg/storage/chunk"
	"github.com/grafana/loki/pkg/storage/stores/tsdb/index"
)

// TenantLabel is part of the reserved label namespace (__ prefix)
// It's used to create multi-tenant TSDBs (which do not have a tenancy concept)
// These labels are stripped out during compaction to single-tenant TSDBs
const TenantLabel = "__loki_tenant__"

// MultiTenantIndex will inject a tenant label to it's queries
// This works with pre-compacted TSDBs which aren't yet per tenant.
type MultiTenantIndex struct {
	idx Index
}

func NewMultiTenantIndex(idx Index) *MultiTenantIndex {
	return &MultiTenantIndex{idx: idx}
}

func withTenantLabelMatcher(userID string, matchers []*labels.Matcher) []*labels.Matcher {
	cpy := make([]*labels.Matcher, len(matchers))
	copy(cpy, matchers)
	cpy = append(cpy, labels.MustNewMatcher(labels.MatchEqual, TenantLabel, userID))
	return cpy
}

func withoutTenantLabel(ls labels.Labels) labels.Labels {
	for i, l := range ls {
		if l.Name == TenantLabel {
			ls = append(ls[:i], ls[i+1:]...)
			break
		}
	}
	return ls
}

func (m *MultiTenantIndex) Bounds() (model.Time, model.Time) { return m.idx.Bounds() }

func (m *MultiTenantIndex) SetChunkFilterer(chunkFilter chunk.RequestChunkFilterer) {
	m.idx.SetChunkFilterer(chunkFilter)
}

func (m *MultiTenantIndex) Close() error { return m.idx.Close() }

func (m *MultiTenantIndex) GetChunkRefs(ctx context.Context, userID string, from, through model.Time, res []ChunkRef, shard *index.ShardAnnotation, matchers ...*labels.Matcher) ([]ChunkRef, error) {
	return m.idx.GetChunkRefs(ctx, userID, from, through, res, shard, withTenantLabelMatcher(userID, matchers)...)
}

func (m *MultiTenantIndex) Series(ctx context.Context, userID string, from, through model.Time, res []Series, shard *index.ShardAnnotation, matchers ...*labels.Matcher) ([]Series, error) {
	xs, err := m.idx.Series(ctx, userID, from, through, res, shard, withTenantLabelMatcher(userID, matchers)...)
	if err != nil {
		return nil, err
	}
	for i := range xs {
		xs[i].Labels = withoutTenantLabel(xs[i].Labels)
	}
	return xs, nil
}

func (m *MultiTenantIndex) LabelNames(ctx context.Context, userID string, from, through model.Time, matchers ...*labels.Matcher) ([]string, error) {
	res, err := m.idx.LabelNames(ctx, userID, from, through, withTenantLabelMatcher(userID, matchers)...)
	if err != nil {
		return nil, err
	}

	// Strip out the tenant label in response.
	i := sort.SearchStrings(res, TenantLabel)
	if i == len(res) || res[i] != TenantLabel {
		return res, nil
	}

	return append(res[:i], res[i+1:]...), nil
}

func (m *MultiTenantIndex) LabelValues(ctx context.Context, userID string, from, through model.Time, name string, matchers ...*labels.Matcher) ([]string, error) {
	// Prevent queries for the internal tenant label
	if name == TenantLabel {
		return nil, nil
	}
	return m.idx.LabelValues(ctx, userID, from, through, name, withTenantLabelMatcher(userID, matchers)...)
}
<|endoftext|>


<commit_before>package kubernetes

import (
	"fmt"
	"time"

	meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"

	"k8s.io/client-go/pkg/api/v1"
	"k8s.io/client-go/pkg/apis/extensions/v1beta1"

	"github.com/rusenask/keel/types"
	"github.com/rusenask/keel/util/version"

	"testing"
)

var currentVersion = "0.0.2"
var newVersion = "0.0.3"

type fakeImplementer struct {
	namespaces     *v1.NamespaceList
	deployment     *v1beta1.Deployment
	deploymentList *v1beta1.DeploymentList
}

func (i *fakeImplementer) Namespaces() (*v1.NamespaceList, error) {
	return i.namespaces, nil
}

func (i *fakeImplementer) Deployment(namespace, name string) (*v1beta1.Deployment, error) {
	return i.deployment, nil
}

func (i *fakeImplementer) Deployments(namespace string) (*v1beta1.DeploymentList, error) {
	return i.deploymentList, nil
}

func (i *fakeImplementer) Update(deployment *v1beta1.Deployment) error {
	return nil
}

func TestGetNamespaces(t *testing.T) {
	fi := &fakeImplementer{
		namespaces: &v1.NamespaceList{
			Items: []v1.Namespace{
				v1.Namespace{
					meta_v1.TypeMeta{},
					meta_v1.ObjectMeta{Name: "xxxx"},
					v1.NamespaceSpec{},
					v1.NamespaceStatus{},
				},
			},
		},
	}

	provider, err := NewProvider(fi)
	if err != nil {
		t.Fatalf("failed to get provider: %s", err)
	}

	namespaces, err := provider.namespaces()
	if err != nil {
		t.Errorf("failed to get namespaces: %s", err)
	}

	if namespaces.Items[0].Name != "xxxx" {
		t.Errorf("expected xxxx but got %s", namespaces.Items[0].Name)
	}
}

func TestGetImageName(t *testing.T) {
	name := versionreg.ReplaceAllString("gcr.io/v2-namespace/hello-world:1.1", "")
	if name != "gcr.io/v2-namespace/hello-world" {
		t.Errorf("expected 'gcr.io/v2-namespace/hello-world' but got '%s'", name)
	}
}

func TestGetDeployments(t *testing.T) {
	fp := &fakeImplementer{}
	fp.namespaces = &v1.NamespaceList{
		Items: []v1.Namespace{
			v1.Namespace{
				meta_v1.TypeMeta{},
				meta_v1.ObjectMeta{Name: "xxxx"},
				v1.NamespaceSpec{},
				v1.NamespaceStatus{},
			},
		},
	}
	fp.deploymentList = &v1beta1.DeploymentList{
		Items: []v1beta1.Deployment{
			v1beta1.Deployment{
				meta_v1.TypeMeta{},
				meta_v1.ObjectMeta{
					Name:      "dep-1",
					Namespace: "xxxx",
					Labels:    map[string]string{types.KeelPolicyLabel: "all"},
				},
				v1beta1.DeploymentSpec{
					Template: v1.PodTemplateSpec{
						Spec: v1.PodSpec{
							Containers: []v1.Container{
								v1.Container{
									Image: "gcr.io/v2-namespace/hello-world:1.1",
								},
							},
						},
					},
				},
				v1beta1.DeploymentStatus{},
			},
		},
	}

	provider, err := NewProvider(fp)
	if err != nil {
		t.Fatalf("failed to get provider: %s", err)
	}

	deps, err := provider.deployments()
	if err != nil {
		t.Errorf("failed to get deployments: %s", err)
	}
	if len(deps) != 1 {
		t.Errorf("expected to find 1 deployment, got: %d", len(deps))
	}

	if deps[0].Items[0].GetName() != "dep-1" {
		t.Errorf("expected name %s, got %s", "dep-1", deps[0].Items[0].GetName())
	}
}

func TestGetImpacted(t *testing.T) {
	provider, err := NewProvider(&fakeImplementer{})
	if err != nil {
		t.Fatalf("failed to get provider: %s", err)
	}

	repo := &types.Repository{
		Name: "karolisr/webhook-demo",
		Tag:  "0.0.3",
	}

	deps, err := provider.impactedDeployments(repo)
	if err != nil {
		t.Errorf("failed to get deployments: %s", err)
	}
	found := false
	for _, c := range deps[0].Spec.Template.Spec.Containers {
		ver, err := version.GetVersionFromImageName(c.Image)
		if err != nil {
			continue
		}
		if ver.String() == repo.Tag {
			found = true
		}
	}
	// fmt.Println(len(deps.Items))
	fmt.Println(len(deps))
	fmt.Println(found)

}
func TestProcessEvent(t *testing.T) {
	provider, err := NewProvider(&fakeImplementer{})
	if err != nil {
		t.Fatalf("failed to get provider: %s", err)
	}

	repo := types.Repository{
		Name: "karolisr/webhook-demo",
		Tag:  newVersion,
	}

	event := &types.Event{Repository: repo}
	updated, err := provider.processEvent(event)
	if err != nil {
		t.Errorf("got error while processing event: %s", err)
	}

	//
	time.Sleep(100 * time.Millisecond)
	for _, upd := range updated {
		current, err := provider.getDeployment(upd.Namespace, upd.Name)
		if err != nil {
			t.Fatalf("failed to get deployment %s, error: %s", upd.Name, err)
		}
		currentVer, err := version.GetVersionFromImageName(current.Spec.Template.Spec.Containers[0].Image)
		if err != nil {
			t.Fatalf("failed to get version from %s, error: %s", current.Spec.Template.Spec.Containers[0].Image, err)
		}

		if currentVer.String() != newVersion {
			t.Errorf("deployment version wasn't updated, got: %s while expected: %s", currentVer.String(), newVersion)
		}
	}

}
<commit_msg>k8s provider tests<commit_after>package kubernetes

import (
	meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"

	"k8s.io/client-go/pkg/api/v1"
	"k8s.io/client-go/pkg/apis/extensions/v1beta1"

	"github.com/rusenask/keel/types"
	// "github.com/rusenask/keel/util/version"

	"testing"
)

type fakeImplementer struct {
	namespaces     *v1.NamespaceList
	deployment     *v1beta1.Deployment
	deploymentList *v1beta1.DeploymentList
}

func (i *fakeImplementer) Namespaces() (*v1.NamespaceList, error) {
	return i.namespaces, nil
}

func (i *fakeImplementer) Deployment(namespace, name string) (*v1beta1.Deployment, error) {
	return i.deployment, nil
}

func (i *fakeImplementer) Deployments(namespace string) (*v1beta1.DeploymentList, error) {
	return i.deploymentList, nil
}

func (i *fakeImplementer) Update(deployment *v1beta1.Deployment) error {
	return nil
}

func TestGetNamespaces(t *testing.T) {
	fi := &fakeImplementer{
		namespaces: &v1.NamespaceList{
			Items: []v1.Namespace{
				v1.Namespace{
					meta_v1.TypeMeta{},
					meta_v1.ObjectMeta{Name: "xxxx"},
					v1.NamespaceSpec{},
					v1.NamespaceStatus{},
				},
			},
		},
	}

	provider, err := NewProvider(fi)
	if err != nil {
		t.Fatalf("failed to get provider: %s", err)
	}

	namespaces, err := provider.namespaces()
	if err != nil {
		t.Errorf("failed to get namespaces: %s", err)
	}

	if namespaces.Items[0].Name != "xxxx" {
		t.Errorf("expected xxxx but got %s", namespaces.Items[0].Name)
	}
}

func TestGetImageName(t *testing.T) {
	name := versionreg.ReplaceAllString("gcr.io/v2-namespace/hello-world:1.1", "")
	if name != "gcr.io/v2-namespace/hello-world" {
		t.Errorf("expected 'gcr.io/v2-namespace/hello-world' but got '%s'", name)
	}
}

func TestGetDeployments(t *testing.T) {
	fp := &fakeImplementer{}
	fp.namespaces = &v1.NamespaceList{
		Items: []v1.Namespace{
			v1.Namespace{
				meta_v1.TypeMeta{},
				meta_v1.ObjectMeta{Name: "xxxx"},
				v1.NamespaceSpec{},
				v1.NamespaceStatus{},
			},
		},
	}
	fp.deploymentList = &v1beta1.DeploymentList{
		Items: []v1beta1.Deployment{
			v1beta1.Deployment{
				meta_v1.TypeMeta{},
				meta_v1.ObjectMeta{
					Name:      "dep-1",
					Namespace: "xxxx",
					Labels:    map[string]string{types.KeelPolicyLabel: "all"},
				},
				v1beta1.DeploymentSpec{
					Template: v1.PodTemplateSpec{
						Spec: v1.PodSpec{
							Containers: []v1.Container{
								v1.Container{
									Image: "gcr.io/v2-namespace/hello-world:1.1",
								},
							},
						},
					},
				},
				v1beta1.DeploymentStatus{},
			},
		},
	}

	provider, err := NewProvider(fp)
	if err != nil {
		t.Fatalf("failed to get provider: %s", err)
	}

	deps, err := provider.deployments()
	if err != nil {
		t.Errorf("failed to get deployments: %s", err)
	}
	if len(deps) != 1 {
		t.Errorf("expected to find 1 deployment, got: %d", len(deps))
	}

	if deps[0].Items[0].GetName() != "dep-1" {
		t.Errorf("expected name %s, got %s", "dep-1", deps[0].Items[0].GetName())
	}
}

func TestGetImpacted(t *testing.T) {
	fp := &fakeImplementer{}
	fp.namespaces = &v1.NamespaceList{
		Items: []v1.Namespace{
			v1.Namespace{
				meta_v1.TypeMeta{},
				meta_v1.ObjectMeta{Name: "xxxx"},
				v1.NamespaceSpec{},
				v1.NamespaceStatus{},
			},
		},
	}
	fp.deploymentList = &v1beta1.DeploymentList{
		Items: []v1beta1.Deployment{
			v1beta1.Deployment{
				meta_v1.TypeMeta{},
				meta_v1.ObjectMeta{
					Name:      "dep-1",
					Namespace: "xxxx",
					Labels:    map[string]string{types.KeelPolicyLabel: "all"},
				},
				v1beta1.DeploymentSpec{
					Template: v1.PodTemplateSpec{
						Spec: v1.PodSpec{
							Containers: []v1.Container{
								v1.Container{
									Image: "gcr.io/v2-namespace/hello-world:1.1.1",
								},
							},
						},
					},
				},
				v1beta1.DeploymentStatus{},
			},
			v1beta1.Deployment{
				meta_v1.TypeMeta{},
				meta_v1.ObjectMeta{
					Name:      "dep-2",
					Namespace: "xxxx",
					Labels:    map[string]string{"whatever": "all"},
				},
				v1beta1.DeploymentSpec{
					Template: v1.PodTemplateSpec{
						Spec: v1.PodSpec{
							Containers: []v1.Container{
								v1.Container{
									Image: "gcr.io/v2-namespace/hello-world:1.1.1",
								},
							},
						},
					},
				},
				v1beta1.DeploymentStatus{},
			},
		},
	}

	provider, err := NewProvider(fp)
	if err != nil {
		t.Fatalf("failed to get provider: %s", err)
	}

	// creating "new version" event
	repo := &types.Repository{
		Name: "gcr.io/v2-namespace/hello-world",
		Tag:  "1.1.2",
	}

	deps, err := provider.impactedDeployments(repo)
	if err != nil {
		t.Errorf("failed to get deployments: %s", err)
	}

	if len(deps) != 1 {
		t.Errorf("expected to find 1 deployment but found %s", len(deps))
	}

	found := false
	for _, c := range deps[0].Spec.Template.Spec.Containers {

		containerImageName := versionreg.ReplaceAllString(c.Image, "")

		if containerImageName == repo.Name {
			found = true
		}
	}

	if !found {
		t.Errorf("couldn't find expected deployment in impacted deployment list")
	}

}

// func TestProcessEvent(t *testing.T) {
// 	provider, err := NewProvider(&fakeImplementer{})
// 	if err != nil {
// 		t.Fatalf("failed to get provider: %s", err)
// 	}

// 	repo := types.Repository{
// 		Name: "karolisr/webhook-demo",
// 		Tag:  newVersion,
// 	}

// 	event := &types.Event{Repository: repo}
// 	updated, err := provider.processEvent(event)
// 	if err != nil {
// 		t.Errorf("got error while processing event: %s", err)
// 	}

// 	//
// 	time.Sleep(100 * time.Millisecond)
// 	for _, upd := range updated {
// 		current, err := provider.getDeployment(upd.Namespace, upd.Name)
// 		if err != nil {
// 			t.Fatalf("failed to get deployment %s, error: %s", upd.Name, err)
// 		}
// 		currentVer, err := version.GetVersionFromImageName(current.Spec.Template.Spec.Containers[0].Image)
// 		if err != nil {
// 			t.Fatalf("failed to get version from %s, error: %s", current.Spec.Template.Spec.Containers[0].Image, err)
// 		}

// 		if currentVer.String() != newVersion {
// 			t.Errorf("deployment version wasn't updated, got: %s while expected: %s", currentVer.String(), newVersion)
// 		}
// 	}

// }
<|endoftext|>


<commit_before>package json

import (
	"encoding/json"
	"net"
	"testing"

	"github.com/v2ray/v2ray-core/proxy/common/config"
	jsonconfig "github.com/v2ray/v2ray-core/proxy/common/config/json"
	"github.com/v2ray/v2ray-core/testing/unit"
)

func TestAccountMapParsing(t *testing.T) {
	assert := unit.Assert(t)

	var accountMap SocksAccountMap
	err := json.Unmarshal([]byte("[{\"user\": \"a\", \"pass\":\"b\"}, {\"user\": \"c\", \"pass\":\"d\"}]"), &accountMap)
	assert.Error(err).IsNil()

	assert.Bool(accountMap.HasAccount("a", "b")).IsTrue()
	assert.Bool(accountMap.HasAccount("a", "c")).IsFalse()
	assert.Bool(accountMap.HasAccount("c", "d")).IsTrue()
}

func TestDefaultIPAddress(t *testing.T) {
	assert := unit.Assert(t)

	socksConfig := jsonconfig.CreateConfig("socks", config.TypeInbound).(*SocksConfig)
	assert.String(socksConfig.IP().String()).Equals("127.0.0.1")
}

func TestIPAddressParsing(t *testing.T) {
	assert := unit.Assert(t)

	var ipAddress IPAddress
	err := json.Unmarshal([]byte("\"1.2.3.4\""), &ipAddress)
	assert.Error(err).IsNil()
	assert.String(net.IP(ipAddress).String()).Equals("1.2.3.4")
}

func TestNoAuthConfig(t *testing.T) {
	assert := unit.Assert(t)

	var config SocksConfig
	err := json.Unmarshal([]byte("{\"auth\":\"noauth\", \"ip\":\"8.8.8.8\"}"), &config)
	assert.Error(err).IsNil()
	assert.Bool(config.IsNoAuth()).IsTrue()
	assert.Bool(config.IsPassword()).IsFalse()
	assert.String(config.IP().String()).Equals("8.8.8.8")
	assert.Bool(config.UDPEnabled).IsFalse()
}

func TestUserPassConfig(t *testing.T) {
	assert := unit.Assert(t)

	var config SocksConfig
	err := json.Unmarshal([]byte("{\"auth\":\"password\", \"accounts\":[{\"user\":\"x\", \"pass\":\"y\"}], \"udp\":true}"), &config)
	assert.Error(err).IsNil()
	assert.Bool(config.IsNoAuth()).IsFalse()
	assert.Bool(config.IsPassword()).IsTrue()
	assert.Bool(config.HasAccount("x", "y")).IsTrue()
	assert.Bool(config.UDPEnabled).IsTrue()
}
<commit_msg>More test case<commit_after>package json

import (
	"encoding/json"
	"net"
	"testing"

	"github.com/v2ray/v2ray-core/proxy/common/config"
	jsonconfig "github.com/v2ray/v2ray-core/proxy/common/config/json"
	"github.com/v2ray/v2ray-core/testing/unit"
)

func TestAccountMapParsing(t *testing.T) {
	assert := unit.Assert(t)

	var accountMap SocksAccountMap
	err := json.Unmarshal([]byte("[{\"user\": \"a\", \"pass\":\"b\"}, {\"user\": \"c\", \"pass\":\"d\"}]"), &accountMap)
	assert.Error(err).IsNil()

	assert.Bool(accountMap.HasAccount("a", "b")).IsTrue()
	assert.Bool(accountMap.HasAccount("a", "c")).IsFalse()
	assert.Bool(accountMap.HasAccount("c", "d")).IsTrue()
	assert.Bool(accountMap.HasAccount("e", "d")).IsTrue()
}

func TestDefaultIPAddress(t *testing.T) {
	assert := unit.Assert(t)

	socksConfig := jsonconfig.CreateConfig("socks", config.TypeInbound).(*SocksConfig)
	assert.String(socksConfig.IP().String()).Equals("127.0.0.1")
}

func TestIPAddressParsing(t *testing.T) {
	assert := unit.Assert(t)

	var ipAddress IPAddress
	err := json.Unmarshal([]byte("\"1.2.3.4\""), &ipAddress)
	assert.Error(err).IsNil()
	assert.String(net.IP(ipAddress).String()).Equals("1.2.3.4")
}

func TestNoAuthConfig(t *testing.T) {
	assert := unit.Assert(t)

	var config SocksConfig
	err := json.Unmarshal([]byte("{\"auth\":\"noauth\", \"ip\":\"8.8.8.8\"}"), &config)
	assert.Error(err).IsNil()
	assert.Bool(config.IsNoAuth()).IsTrue()
	assert.Bool(config.IsPassword()).IsFalse()
	assert.String(config.IP().String()).Equals("8.8.8.8")
	assert.Bool(config.UDPEnabled).IsFalse()
}

func TestUserPassConfig(t *testing.T) {
	assert := unit.Assert(t)

	var config SocksConfig
	err := json.Unmarshal([]byte("{\"auth\":\"password\", \"accounts\":[{\"user\":\"x\", \"pass\":\"y\"}], \"udp\":true}"), &config)
	assert.Error(err).IsNil()
	assert.Bool(config.IsNoAuth()).IsFalse()
	assert.Bool(config.IsPassword()).IsTrue()
	assert.Bool(config.HasAccount("x", "y")).IsTrue()
	assert.Bool(config.UDPEnabled).IsTrue()
}
<|endoftext|>


<commit_before>package kcp_test

import (
	"context"
	"crypto/rand"
	"io"
	"sync"
	"testing"
	"time"

	"v2ray.com/core/common/net"
	"v2ray.com/core/transport/internet"
	. "v2ray.com/core/transport/internet/kcp"
	. "v2ray.com/ext/assert"
)

func TestDialAndListen(t *testing.T) {
	assert := With(t)

	listerner, err := NewListener(internet.ContextWithTransportSettings(context.Background(), &Config{}), net.LocalHostIP, net.Port(0), func(ctx context.Context, conn internet.Connection) bool {
		go func(c internet.Connection) {
			payload := make([]byte, 4096)
			for {
				nBytes, err := c.Read(payload)
				if err != nil {
					break
				}
				for idx, b := range payload[:nBytes] {
					payload[idx] = b ^ 'c'
				}
				c.Write(payload[:nBytes])
			}
			c.Close()
		}(conn)
		return true
	})
	assert(err, IsNil)
	port := net.Port(listerner.Addr().(*net.UDPAddr).Port)

	ctx := internet.ContextWithTransportSettings(context.Background(), &Config{})
	wg := new(sync.WaitGroup)
	for i := 0; i < 10; i++ {
		clientConn, err := DialKCP(ctx, net.UDPDestination(net.LocalHostIP, port))
		assert(err, IsNil)
		wg.Add(1)

		go func() {
			clientSend := make([]byte, 1024*1024)
			rand.Read(clientSend)
			go clientConn.Write(clientSend)

			clientReceived := make([]byte, 1024*1024)
			nBytes, _ := io.ReadFull(clientConn, clientReceived)
			assert(nBytes, Equals, len(clientReceived))
			clientConn.Close()

			clientExpected := make([]byte, 1024*1024)
			for idx, b := range clientSend {
				clientExpected[idx] = b ^ 'c'
			}
			assert(clientReceived, Equals, clientExpected)

			wg.Done()
		}()
	}

	wg.Wait()
	for i := 0; i < 60 && listerner.ActiveConnections() > 0; i++ {
		time.Sleep(500 * time.Millisecond)
	}
	assert(listerner.ActiveConnections(), Equals, 0)

	listerner.Close()
}
<commit_msg>fix kcp test<commit_after>package kcp_test

import (
	"context"
	"crypto/rand"
	"io"
	"sync"
	"testing"
	"time"

	"v2ray.com/core/common/net"
	"v2ray.com/core/transport/internet"
	. "v2ray.com/core/transport/internet/kcp"
	. "v2ray.com/ext/assert"
)

func TestDialAndListen(t *testing.T) {
	assert := With(t)

	listerner, err := NewListener(internet.ContextWithTransportSettings(context.Background(), &Config{}), net.LocalHostIP, net.Port(0), func(conn internet.Connection) {
		go func(c internet.Connection) {
			payload := make([]byte, 4096)
			for {
				nBytes, err := c.Read(payload)
				if err != nil {
					break
				}
				for idx, b := range payload[:nBytes] {
					payload[idx] = b ^ 'c'
				}
				c.Write(payload[:nBytes])
			}
			c.Close()
		}(conn)
	})
	assert(err, IsNil)
	port := net.Port(listerner.Addr().(*net.UDPAddr).Port)

	ctx := internet.ContextWithTransportSettings(context.Background(), &Config{})
	wg := new(sync.WaitGroup)
	for i := 0; i < 10; i++ {
		clientConn, err := DialKCP(ctx, net.UDPDestination(net.LocalHostIP, port))
		assert(err, IsNil)
		wg.Add(1)

		go func() {
			clientSend := make([]byte, 1024*1024)
			rand.Read(clientSend)
			go clientConn.Write(clientSend)

			clientReceived := make([]byte, 1024*1024)
			nBytes, _ := io.ReadFull(clientConn, clientReceived)
			assert(nBytes, Equals, len(clientReceived))
			clientConn.Close()

			clientExpected := make([]byte, 1024*1024)
			for idx, b := range clientSend {
				clientExpected[idx] = b ^ 'c'
			}
			assert(clientReceived, Equals, clientExpected)

			wg.Done()
		}()
	}

	wg.Wait()
	for i := 0; i < 60 && listerner.ActiveConnections() > 0; i++ {
		time.Sleep(500 * time.Millisecond)
	}
	assert(listerner.ActiveConnections(), Equals, 0)

	listerner.Close()
}
<|endoftext|>


<commit_before>package proto

import (
  pb "goprotobuf.googlecode.com/hg/proto"
)

func (ins *Instruction) Iterate(itFunc func(*Instruction)) {
	itFunc(ins)
	if ins.Children == nil {
		return
	}
	for _, child := range(ins.Children) {
		child.Iterate(itFunc)
	}
}

func (ins *Instruction) IterateAll(itFunc func(*Instruction)) {
	itFunc(ins)
	for _, child := range(ins.Arguments) {
		child.Iterate(itFunc)
	}
	for _, child := range(ins.Children) {
		child.Iterate(itFunc)
	}
}

func ListInstructions(instrs ...*Instruction) []*Instruction {
  return append(make([]*Instruction, 0), instrs...)
}

func FoldLeft(funcName string, base *Instruction, seq []*Instruction) (acc *Instruction) {
  for acc = base; len(seq) > 0; seq = seq[1:] {
    acc = MakeFunctionCall(funcName, ListInstructions(acc, seq[0]), nil, *base.LineNumber)
  }
  return acc
}

func MakeText(text string, lineNum int32) *Instruction {
  return &Instruction {
    Type: NewInstruction_InstructionType(Instruction_TEXT),
    Value: pb.String(text),
    LineNumber: pb.Int32(lineNum),
  }
}

func MakePosition(pos string, lineNum int32) *Instruction {
  return &Instruction {
    Type: NewInstruction_InstructionType(Instruction_POSITION),
    Value: pb.String(pos),
    LineNumber: pb.Int32(lineNum),
  }
}

func MakeImport(path string, lineNum int32) *Instruction {
  return &Instruction {
    Type: NewInstruction_InstructionType(Instruction_IMPORT),
    Value: pb.String(path),
    LineNumber: pb.Int32(lineNum),
  }
}

func MakeLocalVar(name string, val *Instruction, block []*Instruction, lineNum int32) *Instruction {
  node := &Instruction {
    Type: NewInstruction_InstructionType(Instruction_LOCAL_VAR),
    Value: pb.String(name),
    Children: block,
    LineNumber: pb.Int32(lineNum),
  }
  if val == nil {
    node.Arguments = nil
  } else {
    node.Arguments = ListInstructions(val)
  }
  return node
}

func MakeFunctionCall(name string, args []*Instruction, block []*Instruction, lineNum int32) *Instruction {
  return &Instruction {
    Type: NewInstruction_InstructionType(Instruction_FUNCTION_CALL),
    Value: pb.String(name),
    Arguments: args,
    Children: block,
    LineNumber: pb.Int32(lineNum),
  }
}

func MakeBlock(children []*Instruction, lineNum int32) *Instruction {
  return &Instruction {
    Type: NewInstruction_InstructionType(Instruction_BLOCK),
    Children: children,
    LineNumber: pb.Int32(lineNum),
  }
}

func (ins *Instruction) GetFunction(pkg *Package) (*Function) {
	funId := pb.GetInt32(ins.FunctionId)
	return pkg.Functions[int(funId)]
}

func (instr *Instruction) Append(more ...*Instruction) {
  if instr.Children == nil {
    instr.Children = more
  } else {
    instr.Children = append(instr.Children, more...)
  }
}

func (instr *Instruction) ConcatList(more []*Instruction) {
  instr.Append(more...)
}

func (instr *Instruction) ConcatBlock (more *Instruction) {
  instr.Append(more.Children...)
}<commit_msg>add in some extra checks<commit_after>package proto

import (
  pb "goprotobuf.googlecode.com/hg/proto"
)

func (ins *Instruction) Iterate(itFunc func(*Instruction)) {
	itFunc(ins)
	if ins.Children == nil {
		return
	}
	for _, child := range(ins.Children) {
		child.Iterate(itFunc)
	}
}

func (ins *Instruction) IterateAll(itFunc func(*Instruction)) {
	itFunc(ins)
	if ins.Arguments != nil {
		for _, child := range(ins.Arguments) {
			child.Iterate(itFunc)
		}
	}
	if ins.Children != nil {
		for _, child := range(ins.Children) {
			child.Iterate(itFunc)
		}
	}
}

func ListInstructions(instrs ...*Instruction) []*Instruction {
  return append(make([]*Instruction, 0), instrs...)
}

func FoldLeft(funcName string, base *Instruction, seq []*Instruction) (acc *Instruction) {
  for acc = base; len(seq) > 0; seq = seq[1:] {
    acc = MakeFunctionCall(funcName, ListInstructions(acc, seq[0]), nil, *base.LineNumber)
  }
  return acc
}

func MakeText(text string, lineNum int32) *Instruction {
  return &Instruction {
    Type: NewInstruction_InstructionType(Instruction_TEXT),
    Value: pb.String(text),
    LineNumber: pb.Int32(lineNum),
  }
}

func MakePosition(pos string, lineNum int32) *Instruction {
  return &Instruction {
    Type: NewInstruction_InstructionType(Instruction_POSITION),
    Value: pb.String(pos),
    LineNumber: pb.Int32(lineNum),
  }
}

func MakeImport(path string, lineNum int32) *Instruction {
  return &Instruction {
    Type: NewInstruction_InstructionType(Instruction_IMPORT),
    Value: pb.String(path),
    LineNumber: pb.Int32(lineNum),
  }
}

func MakeLocalVar(name string, val *Instruction, block []*Instruction, lineNum int32) *Instruction {
  node := &Instruction {
    Type: NewInstruction_InstructionType(Instruction_LOCAL_VAR),
    Value: pb.String(name),
    Children: block,
    LineNumber: pb.Int32(lineNum),
  }
  if val == nil {
    node.Arguments = nil
  } else {
    node.Arguments = ListInstructions(val)
  }
  return node
}

func MakeFunctionCall(name string, args []*Instruction, block []*Instruction, lineNum int32) *Instruction {
  return &Instruction {
    Type: NewInstruction_InstructionType(Instruction_FUNCTION_CALL),
    Value: pb.String(name),
    Arguments: args,
    Children: block,
    LineNumber: pb.Int32(lineNum),
  }
}

func MakeBlock(children []*Instruction, lineNum int32) *Instruction {
  return &Instruction {
    Type: NewInstruction_InstructionType(Instruction_BLOCK),
    Children: children,
    LineNumber: pb.Int32(lineNum),
  }
}

func (ins *Instruction) GetFunction(pkg *Package) (*Function) {
	funId := pb.GetInt32(ins.FunctionId)
	return pkg.Functions[int(funId)]
}

func (instr *Instruction) Append(more ...*Instruction) {
  if instr.Children == nil {
    instr.Children = more
  } else {
    instr.Children = append(instr.Children, more...)
  }
}

func (instr *Instruction) ConcatList(more []*Instruction) {
  instr.Append(more...)
}

func (instr *Instruction) ConcatBlock (more *Instruction) {
  instr.Append(more.Children...)
}<|endoftext|>


<commit_before>package share

import (
	"bufio"
	"flag"
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"strings"

	"github.com/jcelliott/lumber"

	"github.com/nanobox-io/nanobox/commands/server"
	"github.com/nanobox-io/nanobox/models"
)

// EXPORTSFILE ...
var EXPORTSFILE = "/etc/exports"

type Request struct {
	Entry string
}

// Exists checks to see if the mount already exists
func Exists(path string) bool {

	// generate the entry
	entry, err := entry(path)
	if err != nil {
		return false
	}

	// open the /etc/exports file for scanning...
	var f *os.File
	f, err = os.Open(EXPORTSFILE)
	if err != nil {
		return false
	}
	defer f.Close()

	// scan exports file looking for an entry for this path...
	scanner := bufio.NewScanner(f)
	for scanner.Scan() {
		// scan each line to see if we have a match
		if scanner.Text() == entry {
			return true
		}
	}

	return false
}

// Add will export an nfs share
func Add(path string) error {

	// generate the entry
	entry, err := entry(path)
	if err != nil {
		return err
	}

	req := Request{Entry: entry}
	resp := &Response{}

	// in testing we will call the rpc function directly
	if flag.Lookup("test.v") != nil {
		shareRPC := &ShareRPC{}
		err := shareRPC.Add(req, resp)
		if err != nil || !resp.Success {
			err = fmt.Errorf("failed to add share %v %v", err, resp.Message)
		}
		return err
	}

	// have the server run the share command
	err = server.ClientRun("ShareRPC.Add", req, resp)
	if err != nil || !resp.Success {
		err = fmt.Errorf("failed to add share %v %v", err, resp.Message)
	}
	return err
}

// the rpc function run from the server
func (sh *ShareRPC) Add(req Request, resp *Response) error {

	// add entry into the /etc/exports file
	if err := addEntry(req.Entry); err != nil {
		return err
	}

	if err := cleanExport(); err != nil {
		return err
	}

	// reload nfsd
	if err := reloadServer(); err != nil {
		return err
	}

	resp.Success = true
	return nil
}

// Remove will remove an nfs share
func Remove(path string) error {

	// generate the entry
	entry, err := entry(path)
	if err != nil {
		return err
	}

	req := Request{Entry: entry}
	resp := &Response{}

	// in testing we will call the rpc function directly
	if flag.Lookup("test.v") != nil {
		shareRPC := &ShareRPC{}
		err := shareRPC.Remove(req, resp)
		if err != nil || !resp.Success {
			err = fmt.Errorf("failed to add share %v %v", err, resp.Message)
		}
		return err
	}

	// have the server run the share command
	err = server.ClientRun("ShareRPC.Remove", req, resp)
	if err != nil || !resp.Success {
		err = fmt.Errorf("failed to add share %v %v", err, resp.Message)
	}
	return err
}

// the rpc function run from the server
func (sh *ShareRPC) Remove(req Request, resp *Response) error {
	if err := removeEntry(req.Entry); err != nil {
		return err
	}

	// reload nfsd
	if err := reloadServer(); err != nil {
		return err
	}

	resp.Success = true
	return nil
}

// entry generates the mount entry for the exports file
func entry(path string) (string, error) {

	// use the mountIP saved on the provider in the database
	provider, err := models.LoadProvider()
	if err != nil {
		return "", err
	}

	if provider.MountIP == "" {
		return "", fmt.Errorf("there is no mount ip on the provider")
	}

	return fmt.Sprintf("\"%s\" %s(rw,crossmnt,sync,no_subtree_check,all_squash,anonuid=%d,anongid=%d)", path, provider.MountIP, uid(), gid()), nil
}

// addEntry will add the entry into the /etc/exports file
func addEntry(entry string) error {
	// check to see if it exists
	if _, err := os.Stat(EXPORTSFILE); err != nil {
		// if not write our entry and return
		return ioutil.WriteFile(EXPORTSFILE, []byte(entry), 0644)
	}

	// open exports file
	f, err := os.OpenFile(EXPORTSFILE, os.O_RDWR|os.O_APPEND, 0644)
	if err != nil {
		return err
	}
	defer f.Close()

	// write the entry to the file
	if _, err := f.WriteString(fmt.Sprintf("%s\n", entry)); err != nil {
		return err
	}

	return nil
}

func cleanExport() error {

	// contents will end up storing the entire contents of the file excluding the
	// entry that no longer have a folder
	var contents string

	// open exports file
	f, err := os.OpenFile(EXPORTSFILE, os.O_RDWR, 0644)
	if err != nil {
		return err
	}
	defer f.Close()

	// remove entry from /etc/exports
	scanner := bufio.NewScanner(f)
	for scanner.Scan() {
		parts := strings.Split(scanner.Text(), "\"")

		// if it starts with a "
		if len(parts) > 1 {
			fileInfo, err := os.Stat(parts[1])
			if err != nil || !fileInfo.IsDir() {
				continue
			}
		}

		// add each line back into the file
		contents += fmt.Sprintf("%s\n", scanner.Text())
	}

	// trim the contents to avoid any extra newlines
	contents = strings.TrimSpace(contents)

	// add a single newline for completeness
	contents += "\n"

	// write back the contents of the exports file minus the removed entry
	if err := ioutil.WriteFile(EXPORTSFILE, []byte(contents), 0644); err != nil {
		return err
	}

	return nil
}

// removeEntry will remove the entry from the /etc/exports file
func removeEntry(entry string) error {

	// contents will end up storing the entire contents of the file excluding the
	// entry that is trying to be removed
	var contents string

	// open exports file
	f, err := os.OpenFile(EXPORTSFILE, os.O_RDWR, 0644)
	if err != nil {
		return err
	}
	defer f.Close()

	// remove entry from /etc/exports
	scanner := bufio.NewScanner(f)
	for scanner.Scan() {

		// if the line contain the entry skip it
		if strings.Contains(scanner.Text(), entry) {
			continue
		}

		// add each line back into the file
		contents += fmt.Sprintf("%s\n", scanner.Text())
	}

	// trim the contents to avoid any extra newlines
	contents = strings.TrimSpace(contents)

	// add a single newline for completeness
	contents += "\n"

	// write back the contents of the exports file minus the removed entry
	if err := ioutil.WriteFile(EXPORTSFILE, []byte(contents), 0644); err != nil {
		return err
	}

	return nil
}

// reloadServer reloads the nfs server with the new export configuration
func reloadServer() error {
	// dont reload the server when testing
	if flag.Lookup("test.v") != nil {
		return nil
	}

	// make sure nfsd is running
	cmd := exec.Command("service", "nfs-server", "start")
	if b, err := cmd.CombinedOutput(); err != nil {
		lumber.Debug("enable nfs: %s err: %s", b, err)
	}

	// reload nfs server
	//  TODO: provide a clear error message for a direction to fix
	cmd = exec.Command("exportfs", "-ra")
	if b, err := cmd.CombinedOutput(); err != nil {
		lumber.Debug("update: %s", b)
		return fmt.Errorf("update: %s %s", b, err.Error())
	}

	return nil
}
<commit_msg>dont duplicate shares on linux<commit_after>package share

import (
	"bufio"
	"flag"
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"strings"

	"github.com/jcelliott/lumber"

	"github.com/nanobox-io/nanobox/commands/server"
	"github.com/nanobox-io/nanobox/models"
)

// EXPORTSFILE ...
var EXPORTSFILE = "/etc/exports"

type Request struct {
	Entry string
}

// Exists checks to see if the mount already exists
func Exists(path string) bool {

	// generate the entry
	entry, err := entry(path)
	if err != nil {
		return false
	}

	// open the /etc/exports file for scanning...
	var f *os.File
	f, err = os.Open(EXPORTSFILE)
	if err != nil {
		return false
	}
	defer f.Close()

	// scan exports file looking for an entry for this path...
	scanner := bufio.NewScanner(f)
	for scanner.Scan() {
		// scan each line to see if we have a match
		if scanner.Text() == entry {
			return true
		}
	}

	return false
}

// Add will export an nfs share
func Add(path string) error {
	if Exists(path) {
		return nil
	}

	// generate the entry
	entry, err := entry(path)
	if err != nil {
		return err
	}

	req := Request{Entry: entry}
	resp := &Response{}

	// in testing we will call the rpc function directly
	if flag.Lookup("test.v") != nil {
		shareRPC := &ShareRPC{}
		err := shareRPC.Add(req, resp)
		if err != nil || !resp.Success {
			err = fmt.Errorf("failed to add share %v %v", err, resp.Message)
		}
		return err
	}

	// have the server run the share command
	err = server.ClientRun("ShareRPC.Add", req, resp)
	if err != nil || !resp.Success {
		err = fmt.Errorf("failed to add share %v %v", err, resp.Message)
	}
	return err
}

// the rpc function run from the server
func (sh *ShareRPC) Add(req Request, resp *Response) error {

	// add entry into the /etc/exports file
	if err := addEntry(req.Entry); err != nil {
		return err
	}

	if err := cleanExport(); err != nil {
		return err
	}

	// reload nfsd
	if err := reloadServer(); err != nil {
		return err
	}

	resp.Success = true
	return nil
}

// Remove will remove an nfs share
func Remove(path string) error {

	// generate the entry
	entry, err := entry(path)
	if err != nil {
		return err
	}

	req := Request{Entry: entry}
	resp := &Response{}

	// in testing we will call the rpc function directly
	if flag.Lookup("test.v") != nil {
		shareRPC := &ShareRPC{}
		err := shareRPC.Remove(req, resp)
		if err != nil || !resp.Success {
			err = fmt.Errorf("failed to add share %v %v", err, resp.Message)
		}
		return err
	}

	// have the server run the share command
	err = server.ClientRun("ShareRPC.Remove", req, resp)
	if err != nil || !resp.Success {
		err = fmt.Errorf("failed to add share %v %v", err, resp.Message)
	}
	return err
}

// the rpc function run from the server
func (sh *ShareRPC) Remove(req Request, resp *Response) error {
	if err := removeEntry(req.Entry); err != nil {
		return err
	}

	// reload nfsd
	if err := reloadServer(); err != nil {
		return err
	}

	resp.Success = true
	return nil
}

// entry generates the mount entry for the exports file
func entry(path string) (string, error) {

	// use the mountIP saved on the provider in the database
	provider, err := models.LoadProvider()
	if err != nil {
		return "", err
	}

	if provider.MountIP == "" {
		return "", fmt.Errorf("there is no mount ip on the provider")
	}

	return fmt.Sprintf("\"%s\" %s(rw,crossmnt,sync,no_subtree_check,all_squash,anonuid=%d,anongid=%d)", path, provider.MountIP, uid(), gid()), nil
}

// addEntry will add the entry into the /etc/exports file
func addEntry(entry string) error {
	// check to see if it exists
	if _, err := os.Stat(EXPORTSFILE); err != nil {
		// if not write our entry and return
		return ioutil.WriteFile(EXPORTSFILE, []byte(entry), 0644)
	}

	// open exports file
	f, err := os.OpenFile(EXPORTSFILE, os.O_RDWR|os.O_APPEND, 0644)
	if err != nil {
		return err
	}
	defer f.Close()

	// write the entry to the file
	if _, err := f.WriteString(fmt.Sprintf("%s\n", entry)); err != nil {
		return err
	}

	return nil
}

func cleanExport() error {

	// contents will end up storing the entire contents of the file excluding the
	// entry that no longer have a folder
	var contents string

	// open exports file
	f, err := os.OpenFile(EXPORTSFILE, os.O_RDWR, 0644)
	if err != nil {
		return err
	}
	defer f.Close()

	// remove entry from /etc/exports
	scanner := bufio.NewScanner(f)
	for scanner.Scan() {
		parts := strings.Split(scanner.Text(), "\"")

		// if it starts with a "
		if len(parts) > 1 {
			fileInfo, err := os.Stat(parts[1])
			if err != nil || !fileInfo.IsDir() {
				continue
			}
		}

		// add each line back into the file
		contents += fmt.Sprintf("%s\n", scanner.Text())
	}

	// trim the contents to avoid any extra newlines
	contents = strings.TrimSpace(contents)

	// add a single newline for completeness
	contents += "\n"

	// write back the contents of the exports file minus the removed entry
	if err := ioutil.WriteFile(EXPORTSFILE, []byte(contents), 0644); err != nil {
		return err
	}

	return nil
}

// removeEntry will remove the entry from the /etc/exports file
func removeEntry(entry string) error {

	// contents will end up storing the entire contents of the file excluding the
	// entry that is trying to be removed
	var contents string

	// open exports file
	f, err := os.OpenFile(EXPORTSFILE, os.O_RDWR, 0644)
	if err != nil {
		return err
	}
	defer f.Close()

	// remove entry from /etc/exports
	scanner := bufio.NewScanner(f)
	for scanner.Scan() {

		// if the line contain the entry skip it
		if strings.Contains(scanner.Text(), entry) {
			continue
		}

		// add each line back into the file
		contents += fmt.Sprintf("%s\n", scanner.Text())
	}

	// trim the contents to avoid any extra newlines
	contents = strings.TrimSpace(contents)

	// add a single newline for completeness
	contents += "\n"

	// write back the contents of the exports file minus the removed entry
	if err := ioutil.WriteFile(EXPORTSFILE, []byte(contents), 0644); err != nil {
		return err
	}

	return nil
}

// reloadServer reloads the nfs server with the new export configuration
func reloadServer() error {
	// dont reload the server when testing
	if flag.Lookup("test.v") != nil {
		return nil
	}

	// make sure nfsd is running
	cmd := exec.Command("service", "nfs-server", "start")
	if b, err := cmd.CombinedOutput(); err != nil {
		lumber.Debug("enable nfs: %s err: %s", b, err)
	}

	// reload nfs server
	//  TODO: provide a clear error message for a direction to fix
	cmd = exec.Command("exportfs", "-ra")
	if b, err := cmd.CombinedOutput(); err != nil {
		lumber.Debug("update: %s", b)
		return fmt.Errorf("update: %s %s", b, err.Error())
	}

	return nil
}
<|endoftext|>


<commit_before>package stores

import (
	"sync"

	"github.com/davelondon/kerr"
	"golang.org/x/net/context"
	"kego.io/editor/client/actions"
	"kego.io/editor/client/models"
	"kego.io/flux"
	"kego.io/process/validate"
	"kego.io/system"
	"kego.io/system/node"
)

type RuleStore struct {
	*flux.Store
	ctx context.Context
	app *App

	m     *sync.RWMutex
	rules map[*node.Node]map[*node.Node][]system.RuleInterface
}

type ruleNotif string

func (b ruleNotif) IsNotif() {}

//const (
//RuleChanged ruleNotif = "RuleChanged"
//)

func NewRuleStore(ctx context.Context) *RuleStore {
	s := &RuleStore{
		Store: &flux.Store{},
		ctx:   ctx,
		app:   FromContext(ctx),
		m:     &sync.RWMutex{},
		rules: map[*node.Node]map[*node.Node][]system.RuleInterface{},
	}
	s.Init(s)
	return s
}

func (s *RuleStore) Get(r *node.Node, n *node.Node) []system.RuleInterface {
	s.m.RLock()
	defer s.m.RUnlock()
	root, ok := s.rules[r]
	if !ok {
		return nil
	}
	rules, ok := root[n]
	if !ok {
		return nil
	}
	return rules
}

func (s *RuleStore) Handle(payload *flux.Payload) bool {
	switch action := payload.Action.(type) {
	case *actions.InitialState:
		payload.Wait(s.app.Package, s.app.Types)
		changes := s.build(s.app.Package.Node())
		s.validateNodes(changes)
		for _, t := range s.app.Types.All() {
			changes := s.build(t)
			s.validateNodes(changes)
		}
	case *actions.LoadSourceSuccess:
		payload.Wait(s.app.Branches)
		ni, ok := action.Branch.Contents.(models.NodeContentsInterface)
		if !ok {
			break
		}
		n := ni.GetNode()
		changes := s.build(n)
		s.validateNodes(changes)
	case *actions.EditorValueChange500ms:
		changes := s.build(action.Editor.Node.Root())
		s.validateNodes(changes)
	}
	return true
}

func (s *RuleStore) validateNodes(changes []*node.Node) {
	for _, n := range changes {
		m := s.app.Nodes.Get(n)
		changed, err := m.Validate(s.ctx, s.app.Rule.Get(n.Root(), n))
		if err != nil {
			s.app.Fail <- kerr.Wrap("BYQOBLPRDP", err)
			break
		}
		if changed {
			s.app.Notify(n, NodeErrorsChanged)
			if ed := s.app.Editors.Get(n); ed != nil {
				s.app.Notify(ed, EditorErrorsChanged)
			}
		}
	}
}

func (s *RuleStore) build(n *node.Node) (changes []*node.Node) {
	rules := map[*node.Node][]system.RuleInterface{}
	if err := validate.BuildRulesNode(s.ctx, n, rules); err != nil {
		s.app.Fail <- kerr.Wrap("BRRRGDBXMR", err)
	}
	changes = compare(rules, s.rules[n])
	s.rules[n] = rules
	return changes
}

func compare(a, b map[*node.Node][]system.RuleInterface) (changes []*node.Node) {

	in := func(n system.RuleInterface, h []system.RuleInterface) bool {
		for _, v := range h {
			if n == v {
				return true
			}
		}
		return false
	}

	eq := func(a, b []system.RuleInterface) bool {

		if a == nil && b == nil {
			return true
		}

		if a == nil || b == nil {
			return false
		}

		if len(a) != len(b) {
			return false
		}

		for _, v := range a {
			if !in(v, b) {
				return false
			}
		}

		return true
	}

	for k, av := range a {
		if bv, ok := b[k]; !ok {
			changes = append(changes, k)
		} else {
			if !eq(av, bv) {
				changes = append(changes, k)
			}
		}
	}

	for k := range b {
		if _, ok := a[k]; !ok {
			changes = append(changes, k)
		}
	}

	return changes
}
<commit_msg>Validators re-validate on node mutation events. Problems created by multiple notifications.<commit_after>package stores

import (
	"sync"

	"github.com/davelondon/kerr"
	"golang.org/x/net/context"
	"kego.io/editor/client/actions"
	"kego.io/editor/client/models"
	"kego.io/flux"
	"kego.io/process/validate"
	"kego.io/system"
	"kego.io/system/node"
)

type RuleStore struct {
	*flux.Store
	ctx context.Context
	app *App

	m     *sync.RWMutex
	rules map[*node.Node]map[*node.Node][]system.RuleInterface
}

type ruleNotif string

func (b ruleNotif) IsNotif() {}

//const (
//RuleChanged ruleNotif = "RuleChanged"
//)

func NewRuleStore(ctx context.Context) *RuleStore {
	s := &RuleStore{
		Store: &flux.Store{},
		ctx:   ctx,
		app:   FromContext(ctx),
		m:     &sync.RWMutex{},
		rules: map[*node.Node]map[*node.Node][]system.RuleInterface{},
	}
	s.Init(s)
	return s
}

func (s *RuleStore) Get(r *node.Node, n *node.Node) []system.RuleInterface {
	s.m.RLock()
	defer s.m.RUnlock()
	root, ok := s.rules[r]
	if !ok {
		return nil
	}
	rules, ok := root[n]
	if !ok {
		return nil
	}
	return rules
}

func (s *RuleStore) Handle(payload *flux.Payload) bool {
	switch action := payload.Action.(type) {
	case *actions.InitialState:
		payload.Wait(s.app.Package, s.app.Types)
		s.validateNodes(s.build(s.app.Package.Node()))
		for _, t := range s.app.Types.All() {
			s.validateNodes(s.build(t))
		}
	case *actions.LoadSourceSuccess:
		payload.Wait(s.app.Branches)
		ni, ok := action.Branch.Contents.(models.NodeContentsInterface)
		if !ok {
			break
		}
		n := ni.GetNode()
		s.validateNodes(s.build(n))
	case *actions.EditorValueChange500ms:
		s.validateNodes(s.build(action.Editor.Node.Root()))
	case *actions.ArrayOrder:
		payload.Wait(s.app.Nodes)
		s.validateNodes(s.build(action.Model.Node.Root()))
	case *actions.DeleteNode:
		payload.Wait(s.app.Nodes)
		s.validateNodes(s.build(action.Node.Root()))
	case *actions.InitializeNode:
		payload.Wait(s.app.Nodes)
		s.validateNodes(s.build(action.Node.Root()))
	}
	return true
}

func (s *RuleStore) validateNodes(changes []*node.Node) {
	for _, n := range changes {
		m := s.app.Nodes.Get(n)
		changed, err := m.Validate(s.ctx, s.app.Rule.Get(n.Root(), n))
		if err != nil {
			s.app.Fail <- kerr.Wrap("BYQOBLPRDP", err)
			break
		}
		if changed {
			s.app.Notify(n, NodeErrorsChanged)
			if ed := s.app.Editors.Get(n); ed != nil {
				s.app.Notify(ed, EditorErrorsChanged)
			}
		}
	}
}

func (s *RuleStore) build(n *node.Node) (changes []*node.Node) {
	rules := map[*node.Node][]system.RuleInterface{}
	if err := validate.BuildRulesNode(s.ctx, n, rules); err != nil {
		s.app.Fail <- kerr.Wrap("BRRRGDBXMR", err)
	}
	changes = compare(rules, s.rules[n])
	s.rules[n] = rules
	return changes
}

func compare(a, b map[*node.Node][]system.RuleInterface) (changes []*node.Node) {

	in := func(n system.RuleInterface, h []system.RuleInterface) bool {
		for _, v := range h {
			if n == v {
				return true
			}
		}
		return false
	}

	eq := func(a, b []system.RuleInterface) bool {

		if a == nil && b == nil {
			return true
		}

		if a == nil || b == nil {
			return false
		}

		if len(a) != len(b) {
			return false
		}

		for _, v := range a {
			if !in(v, b) {
				return false
			}
		}

		return true
	}

	for k, av := range a {
		if bv, ok := b[k]; !ok {
			changes = append(changes, k)
		} else {
			if !eq(av, bv) {
				changes = append(changes, k)
			}
		}
	}

	for k := range b {
		if _, ok := a[k]; !ok {
			changes = append(changes, k)
		}
	}

	return changes
}
<|endoftext|>


<commit_before>// Copyright 2018 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Package adb provides routines for an adb compatible CLI client.
package adb

import (
	"context"
	"errors"
	"fmt"
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"syscall"

	empty_pb "github.com/golang/protobuf/ptypes/empty"
	"github.com/google/waterfall/golang/client"
	waterfall_grpc "github.com/google/waterfall/proto/waterfall_go_grpc"
	"google.golang.org/grpc"
)

const (
	androidADBEnv = "ANDROID_ADB"
	androidSDKEnv = "ANDROID_SDK_HOME"
)

// ParseError represents an command line parsing error.
type ParseError struct{}

// Error returns the empty string. We use this to fallback to regular ADB.
// This is only used to do a type assertion on the error.
func (e ParseError) Error() string {
	return ""
}

// ClientFn allows the user to custumize the way the client connection is established.
type ClientFn func() (*grpc.ClientConn, error)

// ParsedArgs args represents a parsed command line.
type ParsedArgs struct {
	Device  string
	Command string
	Args    []string
}

type cmdFn func(context.Context, ClientFn, []string) error

var (
	// Commands maps from a command name to the function to execute the command.
	Commands = map[string]cmdFn{
		"shell":     shellFn,
		"push":      pushFn,
		"pull":      pullFn,
		"forward":   forwardFn,
		"bugreport": passthroughFn,
		"logcat":    passthroughFn,
		"install":   installFn,
		"uninstall": uninstallFn,
	}
)

func platformADB() (string, error) {
	p := os.Getenv("ANDROID_ADB")
	if p != "" {
		return p, nil
	}

	p = os.Getenv("ANDROID_SDK_HOME")
	if p != "" {
		return filepath.Join(p, "platform-tools/adb"), nil
	}
	return "", errors.New("unable to find platform ADB, neither ANDROID_ADB or ANDROID_SDK_HOME set")
}

// Fallback forks a new adb process and executes the provided command.
// Note that this method is terminal. We transfer control to adb.
// It will either fatal out or call Exit.
func Fallback(args []string) {
	adbBin, err := platformADB()
	if err != nil {
		log.Fatal(err)
	}

	cmd := exec.Command(adbBin, args...)
	cmd.Stdin = os.Stdin
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	cmd.SysProcAttr = &syscall.SysProcAttr{Pdeathsig: syscall.SIGTERM}

	if err := cmd.Start(); err != nil {
		log.Fatal(err)
	}

	if err := cmd.Wait(); err != nil {
		if exitErr, ok := err.(*exec.ExitError); ok {
			if status, ok := exitErr.Sys().(syscall.WaitStatus); ok {
				os.Exit(status.ExitStatus())
			}
		}
		log.Fatal(err)
	}
	os.Exit(0)
}

func exe(ctx context.Context, c waterfall_grpc.WaterfallClient, cmd string, args ...string) (int, error) {
	// Pipe from stdin only if we pipes are being used
	return client.Exec(ctx, c, os.Stdout, os.Stderr, nil, cmd, args...)
}

func shell(ctx context.Context, c waterfall_grpc.WaterfallClient, cmd string, args ...string) error {
	// Ignore return code here. This is intended as a drop in replacement for adb, so we need to copy the behavior.
	_, err := exe(ctx, c, "/system/bin/sh", "-c", fmt.Sprintf("%s %s", cmd, strings.Join(args, " ")))
	return err
}

func shellFn(ctx context.Context, cfn ClientFn, args []string) error {
	if len(args) == 1 {
		// Not an actual error, but user is requesting an interactive shell session.
		// Return this in order to fallback to adb.
		return ParseError{}
	}
	conn, err := cfn()
	if err != nil {
		return err
	}
	defer conn.Close()
	return shell(ctx, waterfall_grpc.NewWaterfallClient(conn), args[1], args[2:]...)
}

func pushFn(ctx context.Context, cfn ClientFn, args []string) error {
	if len(args) != 3 {
		return ParseError{}
	}

	conn, err := cfn()
	if err != nil {
		return err
	}
	defer conn.Close()

	return client.Push(ctx, waterfall_grpc.NewWaterfallClient(conn), args[1], args[2])
}

func pullFn(ctx context.Context, cfn ClientFn, args []string) error {
	if len(args) != 3 {
		return ParseError{}
	}

	conn, err := cfn()
	if err != nil {
		return err
	}
	defer conn.Close()

	return client.Pull(ctx, waterfall_grpc.NewWaterfallClient(conn), args[1], args[2])
}

func installFn(ctx context.Context, cfn ClientFn, args []string) error {
	if len(args) < 2 {
		return ParseError{}
	}

	conn, err := cfn()
	if err != nil {
		return err
	}
	defer conn.Close()

	c := waterfall_grpc.NewWaterfallClient(conn)
	path := args[len(args)-1]
	tmp := filepath.Join("/data/local/tmp")
	if err := client.Push(ctx, c, path, tmp); err != nil {
		return err
	}
	defer exe(ctx, c, "rm", "-f", filepath.Join(tmp, filepath.Base(path)))

	return shell(ctx, c, "/system/bin/pm", append(args[:len(args)-1], filepath.Join(tmp, filepath.Base(path)))...)
}

func parseFwd(addr string, reverse bool) (string, error) {
	if reverse {
		if strings.HasPrefix(addr, "tcp:") {
			pts := strings.SplitN(addr, ":", 3)
			return "tcp:" + pts[2], nil
		}
		if strings.HasPrefix(addr, "unix:@") {
			return "localabstract:" + addr[6:], nil
		}
		if strings.HasPrefix(addr, "unix:") {
			return "localreserved:" + addr[5:], nil
		}
		return "", ParseError{}
	}

	pts := strings.Split(addr, ":")
	if len(pts) != 2 {
		return "", ParseError{}
	}

	switch pts[0] {
	case "tcp":
		return "tcp:localhost:" + pts[1], nil
	case "localabstract":
		return "unix:@" + pts[1], nil
	case "localreserved":
		fallthrough
	case "localfilesystem":
		return "unix:" + pts[1], nil
	default:
		return "", ParseError{}
	}
}

func forwardFn(ctx context.Context, cfn ClientFn, args []string) error {
	if len(args) < 2 || len(args) > 4 {
		return ParseError{}
	}

	conn, err := cfn()
	if err != nil {
		return err
	}
	defer conn.Close()
	c := waterfall_grpc.NewPortForwarderClient(conn)

	var src, dst string
	switch args[1] {
	case "--list":
		ss, err := c.List(ctx, &empty_pb.Empty{})
		if err != nil {
			return err
		}
		for _, s := range ss.Sessions {
			src, err := parseFwd(s.Src, true)
			if err != nil {
				return err
			}

			dst, err := parseFwd(s.Dst, true)
			if err != nil {
				return err
			}
			fmt.Printf("localhost:foo %s %s\n", src, dst)
		}
		return nil
	case "--remove":
		if len(args) != 3 {
			return ParseError{}
		}
		fwd, err := parseFwd(args[2], false)
		if err != nil {
			return err
		}
		_, err = c.Stop(ctx, &waterfall_grpc.PortForwardRequest{
			Session: &waterfall_grpc.ForwardSession{Src: fwd}})
		return err
	case "--remove-all":
		if len(args) != 2 {
			return ParseError{}
		}
		_, err = c.StopAll(ctx, &empty_pb.Empty{})
		return err
	case "--no-rebind":
		if src, err = parseFwd(args[2], false); err != nil {
			return err
		}
		if dst, err = parseFwd(args[3], false); err != nil {
			return err
		}
		_, err = c.ForwardPort(ctx, &waterfall_grpc.PortForwardRequest{
			Session: &waterfall_grpc.ForwardSession{Src: src, Dst: dst}, Rebind: false})
		return err
	default:
		if src, err = parseFwd(args[1], false); err != nil {
			return err
		}
		if dst, err = parseFwd(args[2], false); err != nil {
			return err
		}
		_, err = c.ForwardPort(ctx, &waterfall_grpc.PortForwardRequest{
			Session: &waterfall_grpc.ForwardSession{Src: src, Dst: dst}, Rebind: true})
		return err
	}
}

func passthroughFn(ctx context.Context, cfn ClientFn, args []string) error {
	conn, err := cfn()
	if err != nil {
		return err
	}
	defer conn.Close()

	_, err = exe(ctx, waterfall_grpc.NewWaterfallClient(conn), args[0], args[1:]...)
	return err
}

func uninstallFn(ctx context.Context, cfn ClientFn, args []string) error {
	if len(args) != 2 {
		return ParseError{}
	}

	conn, err := cfn()
	if err != nil {
		return err
	}
	defer conn.Close()

	return shell(ctx, waterfall_grpc.NewWaterfallClient(conn), "/system/bin/pm", args...)
}

// ParseCommand parses the comand line args
func ParseCommand(args []string) (ParsedArgs, error) {
	var dev string

	// Process any global options first. Break and fallback for any unsupported option.
	i := 0
	done := false

	for !done && i < len(args) {
		arg := args[i]
		switch arg {
		case "server":
			fallthrough
		case "nodaemon":
			fallthrough
		case "persist":
			fallthrough
		case "-p":
			fallthrough
		case "-a":
			fallthrough
		case "-e":
			fallthrough
		case "-d":
			fallthrough
		case "-t":
			return ParsedArgs{}, ParseError{}
		case "-s":
			if len(args) == i+1 {
				return ParsedArgs{}, ParseError{}
			}
			dev = args[i+1]
			i++
		case "wait-for-device": // ignore
		// H, P and L have no meaning for H2O.
		case "-H":
			fallthrough
		case "-P":
			fallthrough
		case "-L":
			i++
		default:
			done = true
			continue
		}
		i++
	}
	return ParsedArgs{Device: dev, Command: args[i], Args: args[i:]}, nil
}
<commit_msg>stram apk installation if possible<commit_after>// Copyright 2018 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Package adb provides routines for an adb compatible CLI client.
package adb

import (
	"bytes"
	"context"
	"errors"
	"fmt"
	"io"
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"strconv"
	"strings"
	"syscall"

	empty_pb "github.com/golang/protobuf/ptypes/empty"
	"github.com/google/waterfall/golang/client"
	waterfall_grpc "github.com/google/waterfall/proto/waterfall_go_grpc"
	"google.golang.org/grpc"
)

const (
	androidADBEnv = "ANDROID_ADB"
	androidSDKEnv = "ANDROID_SDK_HOME"
)

// ParseError represents an command line parsing error.
type ParseError struct{}

// Error returns the empty string. We use this to fallback to regular ADB.
// This is only used to do a type assertion on the error.
func (e ParseError) Error() string {
	return ""
}

// ClientFn allows the user to custumize the way the client connection is established.
type ClientFn func() (*grpc.ClientConn, error)

// ParsedArgs args represents a parsed command line.
type ParsedArgs struct {
	Device  string
	Command string
	Args    []string
}

type cmdFn func(context.Context, ClientFn, []string) error

var (
	// Commands maps from a command name to the function to execute the command.
	Commands = map[string]cmdFn{
		"shell":     shellFn,
		"push":      pushFn,
		"pull":      pullFn,
		"forward":   forwardFn,
		"bugreport": passthroughFn,
		"logcat":    passthroughFn,
		"install":   installFn,
		"uninstall": uninstallFn,
	}
)

func platformADB() (string, error) {
	p := os.Getenv("ANDROID_ADB")
	if p != "" {
		return p, nil
	}

	p = os.Getenv("ANDROID_SDK_HOME")
	if p != "" {
		return filepath.Join(p, "platform-tools/adb"), nil
	}
	return "", errors.New("unable to find platform ADB, neither ANDROID_ADB or ANDROID_SDK_HOME set")
}

// Fallback forks a new adb process and executes the provided command.
// Note that this method is terminal. We transfer control to adb.
// It will either fatal out or call Exit.
func Fallback(args []string) {
	adbBin, err := platformADB()
	if err != nil {
		log.Fatal(err)
	}

	cmd := exec.Command(adbBin, args...)
	cmd.Stdin = os.Stdin
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	cmd.SysProcAttr = &syscall.SysProcAttr{Pdeathsig: syscall.SIGTERM}

	if err := cmd.Start(); err != nil {
		log.Fatal(err)
	}

	if err := cmd.Wait(); err != nil {
		if exitErr, ok := err.(*exec.ExitError); ok {
			if status, ok := exitErr.Sys().(syscall.WaitStatus); ok {
				os.Exit(status.ExitStatus())
			}
		}
		log.Fatal(err)
	}
	os.Exit(0)
}

func exeStdout(ctx context.Context, c waterfall_grpc.WaterfallClient, cmd string, args ...string) (int, error) {
	return client.Exec(ctx, c, os.Stdout, os.Stdout, nil, cmd, args...)
}

func exeString(ctx context.Context, c waterfall_grpc.WaterfallClient, in io.Reader, cmd string, args ...string) (int, string, error) {
	b := bytes.NewBuffer([]byte{})
	s, err := client.Exec(ctx, c, b, b, in, cmd, args...)
	return s, b.String(), err
}

func shellStdout(ctx context.Context, c waterfall_grpc.WaterfallClient, cmd string, args ...string) error {
	_, err := shellIO(ctx, c, os.Stdout, os.Stdout, nil, cmd, args...)
	return err
}

func shellString(ctx context.Context, c waterfall_grpc.WaterfallClient, in io.Reader, cmd string, args ...string) (int, string, error) {
	b := bytes.NewBuffer([]byte{})
	s, err := shellIO(ctx, c, b, b, in, cmd, args...)
	return s, b.String(), err
}

func shellIO(ctx context.Context, c waterfall_grpc.WaterfallClient, stdout io.Writer, stderr io.Writer, stdin io.Reader, cmd string, args ...string) (int, error) {
	return client.Exec(ctx, c, stdout, stderr, stdin, "/system/bin/sh", "-c", fmt.Sprintf("%s %s", cmd, strings.Join(args, " ")))
}

func shellFn(ctx context.Context, cfn ClientFn, args []string) error {
	if len(args) == 1 {
		// Not an actual error, but user is requesting an interactive shell session.
		// Return this in order to fallback to adb.
		return ParseError{}
	}
	conn, err := cfn()
	if err != nil {
		return err
	}
	defer conn.Close()
	return shellStdout(ctx, waterfall_grpc.NewWaterfallClient(conn), args[1], args[2:]...)
}

func pushFn(ctx context.Context, cfn ClientFn, args []string) error {
	if len(args) != 3 {
		return ParseError{}
	}

	conn, err := cfn()
	if err != nil {
		return err
	}
	defer conn.Close()

	return client.Push(ctx, waterfall_grpc.NewWaterfallClient(conn), args[1], args[2])
}

func pullFn(ctx context.Context, cfn ClientFn, args []string) error {
	if len(args) != 3 {
		return ParseError{}
	}

	conn, err := cfn()
	if err != nil {
		return err
	}
	defer conn.Close()

	return client.Pull(ctx, waterfall_grpc.NewWaterfallClient(conn), args[1], args[2])
}

func installFn(ctx context.Context, cfn ClientFn, args []string) error {
	if len(args) < 2 {
		return ParseError{}
	}

	conn, err := cfn()
	if err != nil {
		return err
	}
	defer conn.Close()

	c := waterfall_grpc.NewWaterfallClient(conn)

	// If possible prefere streamed installs over normal installs.
	// Normal installs requires twice the amount of disk space given that apk is pushed to a temp location.
	streamed := false
	s, out, err := exeString(ctx, c, nil, "/system/bin/getprop", "ro.build.version.sdk")
	if err == nil && s == 0 {
		a, err := strconv.Atoi(strings.Trim(out, "\r\n"))
		streamed = err == nil && a >= 21
	}

	path := args[len(args)-1]
	if streamed {
		fmt.Printf("Doing streamed install ...\n")
		fi, err := os.Stat(path)
		if err != nil {
			return err
		}
		_, out, err = shellString(ctx, c, nil, "pm", append([]string{"install-create"}, args[1:len(args)-1]...)...)
		if err != nil {
			return err
		}

		f, err := os.Open(path)
		if err != nil {
			return err
		}

		session := out[strings.Index(out, "[")+1 : strings.Index(out, "]")]
		_, _, err = shellString(ctx, c, f, "pm", "install-write", "-S", fmt.Sprintf("%d", fi.Size()), session, "-")
		if err != nil {
			return err
		}

		return shellStdout(ctx, c, "pm", "install-commit", session)
	}

	tmp := filepath.Join("/data/local/tmp")
	if err := client.Push(ctx, c, path, tmp); err != nil {
		return err
	}
	defer exeStdout(ctx, c, "rm", "-f", filepath.Join(tmp, filepath.Base(path)))

	return shellStdout(ctx, c, "/system/bin/pm", append(args[:len(args)-1], filepath.Join(tmp, filepath.Base(path)))...)
}

func parseFwd(addr string, reverse bool) (string, error) {
	if reverse {
		if strings.HasPrefix(addr, "tcp:") {
			pts := strings.SplitN(addr, ":", 3)
			return "tcp:" + pts[2], nil
		}
		if strings.HasPrefix(addr, "unix:@") {
			return "localabstract:" + addr[6:], nil
		}
		if strings.HasPrefix(addr, "unix:") {
			return "localreserved:" + addr[5:], nil
		}
		return "", ParseError{}
	}

	pts := strings.Split(addr, ":")
	if len(pts) != 2 {
		return "", ParseError{}
	}

	switch pts[0] {
	case "tcp":
		return "tcp:localhost:" + pts[1], nil
	case "localabstract":
		return "unix:@" + pts[1], nil
	case "localreserved":
		fallthrough
	case "localfilesystem":
		return "unix:" + pts[1], nil
	default:
		return "", ParseError{}
	}
}

func forwardFn(ctx context.Context, cfn ClientFn, args []string) error {
	if len(args) < 2 || len(args) > 4 {
		return ParseError{}
	}

	conn, err := cfn()
	if err != nil {
		return err
	}
	defer conn.Close()
	c := waterfall_grpc.NewPortForwarderClient(conn)

	var src, dst string
	switch args[1] {
	case "--list":
		ss, err := c.List(ctx, &empty_pb.Empty{})
		if err != nil {
			return err
		}
		for _, s := range ss.Sessions {
			src, err := parseFwd(s.Src, true)
			if err != nil {
				return err
			}

			dst, err := parseFwd(s.Dst, true)
			if err != nil {
				return err
			}
			fmt.Printf("localhost:foo %s %s\n", src, dst)
		}
		return nil
	case "--remove":
		if len(args) != 3 {
			return ParseError{}
		}
		fwd, err := parseFwd(args[2], false)
		if err != nil {
			return err
		}
		_, err = c.Stop(ctx, &waterfall_grpc.PortForwardRequest{
			Session: &waterfall_grpc.ForwardSession{Src: fwd}})
		return err
	case "--remove-all":
		if len(args) != 2 {
			return ParseError{}
		}
		_, err = c.StopAll(ctx, &empty_pb.Empty{})
		return err
	case "--no-rebind":
		if src, err = parseFwd(args[2], false); err != nil {
			return err
		}
		if dst, err = parseFwd(args[3], false); err != nil {
			return err
		}
		_, err = c.ForwardPort(ctx, &waterfall_grpc.PortForwardRequest{
			Session: &waterfall_grpc.ForwardSession{Src: src, Dst: dst}, Rebind: false})
		return err
	default:
		if src, err = parseFwd(args[1], false); err != nil {
			return err
		}
		if dst, err = parseFwd(args[2], false); err != nil {
			return err
		}
		_, err = c.ForwardPort(ctx, &waterfall_grpc.PortForwardRequest{
			Session: &waterfall_grpc.ForwardSession{Src: src, Dst: dst}, Rebind: true})
		return err
	}
}

func passthroughFn(ctx context.Context, cfn ClientFn, args []string) error {
	conn, err := cfn()
	if err != nil {
		return err
	}
	defer conn.Close()

	_, err = exeStdout(ctx, waterfall_grpc.NewWaterfallClient(conn), args[0], args[1:]...)
	return err
}

func uninstallFn(ctx context.Context, cfn ClientFn, args []string) error {
	if len(args) != 2 {
		return ParseError{}
	}

	conn, err := cfn()
	if err != nil {
		return err
	}
	defer conn.Close()

	return shellStdout(ctx, waterfall_grpc.NewWaterfallClient(conn), "/system/bin/pm", args...)
}

// ParseCommand parses the comand line args
func ParseCommand(args []string) (ParsedArgs, error) {
	var dev string

	// Process any global options first. Break and fallback for any unsupported option.
	i := 0
	done := false

	for !done && i < len(args) {
		arg := args[i]
		switch arg {
		case "server":
			fallthrough
		case "nodaemon":
			fallthrough
		case "persist":
			fallthrough
		case "-p":
			fallthrough
		case "-a":
			fallthrough
		case "-e":
			fallthrough
		case "-d":
			fallthrough
		case "-t":
			return ParsedArgs{}, ParseError{}
		case "-s":
			if len(args) == i+1 {
				return ParsedArgs{}, ParseError{}
			}
			dev = args[i+1]
			i++
		case "wait-for-device": // ignore
		// H, P and L have no meaning for H2O.
		case "-H":
			fallthrough
		case "-P":
			fallthrough
		case "-L":
			i++
		default:
			done = true
			continue
		}
		i++
	}
	return ParsedArgs{Device: dev, Command: args[i], Args: args[i:]}, nil
}
<|endoftext|>


<commit_before>// Copyright 2015 Square Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package query

// show all metrics
// show tags WHERE predicate

import (
	"testing"

	"github.com/square/metrics/assert"
)

// these queries should successfully parse,
// with a corresponding command.
var inputs = []string{
	// describes
	"describe all",
	"describe x",
	"describe cpu_usage",
	"describe inspect",
	"describe in2",
	"describe cpu_usage where key = 'value'",
	"describe cpu_usage where key = 'value\\''",
	"describe cpu_usage where key != 'value'",
	"describe cpu_usage where (key = 'value')",
	"describe cpu_usage where not (key = 'value')",
	"describe cpu_usage where not key = 'value'",
	"describe cpu_usage where (key = 'value')",
	"describe cpu_usage where key = 'value' or key = 'value'",
	"describe cpu_usage where key in ('value', 'value')",
	"describe cpu_usage where key matches 'abc'",
	"describe nodes.cpu.usage where datacenter='sjc1b' and type='idle' and host matches 'fwd'",
	// predicate parenthesis test
	"describe cpu_usage where key = 'value' and (key = 'value')",
	"describe cpu_usage where (key = 'value') and key = 'value'",
	"describe cpu_usage where (key = 'value') and (key = 'value')",
	"describe cpu_usage where (key = 'value' and key = 'value')",

	// Leading/trailing whitespace
	" describe all ",
	" describe x ",
	" select 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 from 0 to 0 ",

	// selects - spaces and keywords
	"select f( g(5) group by a,w,q) from 0 to 0",
	"select f( g(5) group by a, w, q )from 0 to 0 ",
	"select f( g(5) group by a,w, q)from 0 to 0",
	"select f( g(5) group by a,w,q)to 0 from 0",
	"select f(g(5)group by a,w,q)to 0 from 0",
	"   select f(g(5)group by a,w,q)  from  	  0 	   to     0",
	"   select( f(g(5)group by a,w,q)  )from 0 to 0",
	"select(f(g(5)group by`a`,w,q)) from 0 to 0",
	"select(f(g(5)group by`a`,w,q)) from 0 to 0",
	"select(fromx+tox+groupx+byx+selectx+describex+allx+wherex) from 0 to 0",
}

var selects = []string{
	// All these queries are tested with and without the prefix "select"
	// selects - parenthesis
	"0 from 0 to 0",
	"(0) from 0 to 0",
	"(0) where foo = 'bar' from 0 to 0",
	// selects - numbers
	"0, 1, 2, 3, 4, 5, 6, 7, 8, 9 from 0 to 0",
	"10, 100, 1000 from 0 to 0",
	"10.1, 10.01, 10.001 from 0 to 0",
	"-10.1, -10.01, -10.001 from 0 to 0",
	"1.0e1, 1.0e2, 1.0e10, 1.0e0 from 0 to 0",
	"1.0e-5, 1.0e+5 from 0 to 0",
	// selects - trying out arithmetic
	"x from 0 to 0",
	"x-y-z from 0 to 0",
	"(x)-(y)-(z) from 0 to 0",
	"0 from 0 to 0",
	"x, y from 0 to 0",
	"1 + 2 * 3 + 4 from 0 to 0",
	"x * (y + 123), z from 0 to 0",
	// testing escaping
	"`x` from 0 to 0",
	// selects - timestamps
	"x * (y + 123), z from '2011-2-4 PTZ' to '2015-6-1 PTZ'",
	"x * (y + 123), z from 0 to 10000",
	"1 from -10m to now",
	"1 from -10M to -10m",
	// selects - function calls
	"foo(x) from 0 to 0",
	"bar(x, y) from 0 to 0",
	"baz(x, y, z+1+foo(1)) from 0 to 0",
	// selects - testing out property values
	"x from 0 to 0",
	"x from 0 to 0",
	"x from 0 to 0 resolution '10s'",
	"x from 0 to 0 resolution '10h'",
	"x from 0 to 0 resolution '300s'",
	"x from 0 to 0 resolution '17m'",
	"x from 0 to 0 sample by 'max'",
	"x from 0 to 0 sample   by 'max'",
	// selects - aggregate functions
	"scalar.max(x) from 0 to 0",
	"aggregate.max(x, y) from 0 to 0",
	"aggregate.max(x group by foo) + 3 from 0 to 0",
	// selects - where clause
	"x where y = 'z' from 0 to 0",
	// selects - per-identifier where clause
	"x + z[y = 'z'] from 0 to 0",
	"x[y = 'z'] from 0 to 0",
	// selects - complicated queries
	"aggregate.max(x[y = 'z'] group by foo) from 0 to 0",
	"cpu.user + cpu.kernel where host = 'apa3.sjc2b' from 0 to 0",
	"'string literal' where host = 'apa3.sjc2b' from 0 to 0",
	"timeshift( metric, '5h') where host = 'apa3.sjc2b' from 0 to 0",
	// pipe expressions
	"x | y from 0 to 0",
	"x | y + 1 from 0 to 0",
	"x | y - 1 from 0 to 0",
	"x | y * 1 from 0 to 0",
	"x | y / 1 from 0 to 0",
	"x | y(group by a) from 0 to 0",
	"x + 1 | y(group by a) from 0 to 0",
	"x | y | z + 1 from 0 to 0",
	"x|y from 0 to 0",
	"x|f + y*z from 0 to 0",
	"x|f + y|g from 0 to 0",
	"x|f + y|g(4) from 0 to 0",
	"x|f(1,2,3) + y|g(4) from 0 to 0",
	"1 + 2 | f from 0 to 0",
}

// these queries should fail with a syntax error.
var syntaxErrorQuery = []string{
	"select ( from 0 to 0",
	"select ) from 0 to 0",
	"describe ( from 0 to 0",
	"describe in from 0 to 0",
	"describe invalid_regex where key matches 'ab[' from 0 to 0",
	"select x invalid_property 0 from 0 to 0",
	"select x sampleby 0 from 0 to 0",
	"select x sample 0 from 0 to 0",
	"select x by 0 from 0 to 0",
	"select x",
	"select x from 0",
	"select x to 0",
	"select x from 0 from 1 to 0",
	"select x from 0 to 1 to 0",
	"select x from 0 resolution '30s' resolution '25s' to 0",
	"select x from 0 from 1 sample by 'min' sample by 'min' to 0",
	"select f(3 groupby x) from 0 to 0",
	"select c group by a from 0 to 0",
	"select x[] from 0 to 0",
}

func TestParse_success(t *testing.T) {
	for _, row := range inputs {
		_, err := Parse(row)
		if err != nil {
			t.Errorf("[%s] failed to parse: %s", row, err.Error())
		}
	}

	for _, row := range selects {
		for _, prefix := range []string{"", "select "} {
			query := prefix + row
			_, err := Parse(query)
			if err != nil {
				t.Errorf("[%s] failed to parse: %s", query, err.Error())
			}
		}
	}
}

func TestParse_syntaxError(t *testing.T) {
	for _, row := range syntaxErrorQuery {
		_, err := Parse(row)
		if err == nil {
			t.Errorf("[%s] should have failed to parse", row)
		} else if _, ok := err.(SyntaxErrors); !ok {
			t.Logf("[%s] Expected SyntaxErrors, got: %s", row, err.Error())
		}
	}
}

func TestCompile(t *testing.T) {
	for _, row := range inputs {
		a := assert.New(t).Contextf(row)
		p := Parser{Buffer: row}
		p.Init()
		a.CheckError(p.Parse())
		p.Execute()
		testParserResult(a, p)
	}
}

// Helper functions
// ================

func testParserResult(a assert.Assert, p Parser) {
	a.EqInt(len(p.assertions), 0)
	if len(p.assertions) != 0 {
		for _, err := range p.assertions {
			a.Errorf("assertion error: %s", err.Error())
		}
	}
	if len(p.nodeStack) != 0 {
		for _, node := range p.nodeStack {
			a.Errorf("node error:\n%s", PrintNode(node))
		}
	}
}
<commit_msg>add tests for durations<commit_after>// Copyright 2015 Square Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package query

// show all metrics
// show tags WHERE predicate

import (
	"testing"

	"github.com/square/metrics/assert"
)

// these queries should successfully parse,
// with a corresponding command.
var inputs = []string{
	// describes
	"describe all",
	"describe x",
	"describe cpu_usage",
	"describe inspect",
	"describe in2",
	"describe cpu_usage where key = 'value'",
	"describe cpu_usage where key = 'value\\''",
	"describe cpu_usage where key != 'value'",
	"describe cpu_usage where (key = 'value')",
	"describe cpu_usage where not (key = 'value')",
	"describe cpu_usage where not key = 'value'",
	"describe cpu_usage where (key = 'value')",
	"describe cpu_usage where key = 'value' or key = 'value'",
	"describe cpu_usage where key in ('value', 'value')",
	"describe cpu_usage where key matches 'abc'",
	"describe nodes.cpu.usage where datacenter='sjc1b' and type='idle' and host matches 'fwd'",
	// predicate parenthesis test
	"describe cpu_usage where key = 'value' and (key = 'value')",
	"describe cpu_usage where (key = 'value') and key = 'value'",
	"describe cpu_usage where (key = 'value') and (key = 'value')",
	"describe cpu_usage where (key = 'value' and key = 'value')",

	// Leading/trailing whitespace
	" describe all ",
	" describe x ",
	" select 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 from 0 to 0 ",

	// selects - spaces and keywords
	"select f( g(5) group by a,w,q) from 0 to 0",
	"select f( g(5) group by a, w, q )from 0 to 0 ",
	"select f( g(5) group by a,w, q)from 0 to 0",
	"select f( g(5) group by a,w,q)to 0 from 0",
	"select f(g(5)group by a,w,q)to 0 from 0",
	"   select f(g(5)group by a,w,q)  from  	  0 	   to     0",
	"   select( f(g(5)group by a,w,q)  )from 0 to 0",
	"select(f(g(5)group by`a`,w,q)) from 0 to 0",
	"select(f(g(5)group by`a`,w,q)) from 0 to 0",
	"select(fromx+tox+groupx+byx+selectx+describex+allx+wherex) from 0 to 0",
}

var selects = []string{
	// All these queries are tested with and without the prefix "select"
	// selects - parenthesis
	"0 from 0 to 0",
	"(0) from 0 to 0",
	"(0) where foo = 'bar' from 0 to 0",
	// selects - numbers
	"0, 1, 2, 3, 4, 5, 6, 7, 8, 9 from 0 to 0",
	"10, 100, 1000 from 0 to 0",
	"10.1, 10.01, 10.001 from 0 to 0",
	"-10.1, -10.01, -10.001 from 0 to 0",
	"1.0e1, 1.0e2, 1.0e10, 1.0e0 from 0 to 0",
	"1.0e-5, 1.0e+5 from 0 to 0",
	// selects - trying out arithmetic
	"x from 0 to 0",
	"x-y-z from 0 to 0",
	"(x)-(y)-(z) from 0 to 0",
	"0 from 0 to 0",
	"x, y from 0 to 0",
	"1 + 2 * 3 + 4 from 0 to 0",
	"x * (y + 123), z from 0 to 0",
	// testing escaping
	"`x` from 0 to 0",
	// selects - timestamps
	"x * (y + 123), z from '2011-2-4 PTZ' to '2015-6-1 PTZ'",
	"x * (y + 123), z from 0 to 10000",
	"1 from -10m to now",
	"1 from -10M to -10m",
	// selects - function calls
	"foo(x) from 0 to 0",
	"bar(x, y) from 0 to 0",
	"baz(x, y, z+1+foo(1)) from 0 to 0",
	// selects - testing out property values
	"x from 0 to 0",
	"x from 0 to 0",
	"x from 0 to 0 resolution '10s'",
	"x from 0 to 0 resolution '10h'",
	"x from 0 to 0 resolution '300s'",
	"x from 0 to 0 resolution '17m'",
	"x from 0 to 0 sample by 'max'",
	"x from 0 to 0 sample   by 'max'",
	// selects - aggregate functions
	"scalar.max(x) from 0 to 0",
	"aggregate.max(x, y) from 0 to 0",
	"aggregate.max(x group by foo) + 3 from 0 to 0",
	// selects - where clause
	"x where y = 'z' from 0 to 0",
	// selects - per-identifier where clause
	"x + z[y = 'z'] from 0 to 0",
	"x[y = 'z'] from 0 to 0",
	// selects - complicated queries
	"aggregate.max(x[y = 'z'] group by foo) from 0 to 0",
	"cpu.user + cpu.kernel where host = 'apa3.sjc2b' from 0 to 0",
	"'string literal' where host = 'apa3.sjc2b' from 0 to 0",
	"timeshift( metric, '5h') where host = 'apa3.sjc2b' from 0 to 0",
	// pipe expressions
	"x | y from 0 to 0",
	"x | y + 1 from 0 to 0",
	"x | y - 1 from 0 to 0",
	"x | y * 1 from 0 to 0",
	"x | y / 1 from 0 to 0",
	"x | y(group by a) from 0 to 0",
	"x + 1 | y(group by a) from 0 to 0",
	"x | y | z + 1 from 0 to 0",
	"x|y from 0 to 0",
	"x|f + y*z from 0 to 0",
	"x|f + y|g from 0 to 0",
	"x|f + y|g(4) from 0 to 0",
	"x|f(1,2,3) + y|g(4) from 0 to 0",
	"x|f(1s,2,3y) + y|g(4mo) from 0 to 0",
	"x|f(1s,'r3r2',3y) + y|g(4mo) from 0 to 0",
	"1 + 2 | f from 0 to 0",
}

// these queries should fail with a syntax error.
var syntaxErrorQuery = []string{
	"select ( from 0 to 0",
	"select ) from 0 to 0",
	"describe ( from 0 to 0",
	"describe in from 0 to 0",
	"describe invalid_regex where key matches 'ab[' from 0 to 0",
	"select x invalid_property 0 from 0 to 0",
	"select x sampleby 0 from 0 to 0",
	"select x sample 0 from 0 to 0",
	"select x by 0 from 0 to 0",
	"select x",
	"select x from 0",
	"select x to 0",
	"select x from 0 from 1 to 0",
	"select x from 0 to 1 to 0",
	"select x from 0 resolution '30s' resolution '25s' to 0",
	"select x from 0 from 1 sample by 'min' sample by 'min' to 0",
	"select f(3 groupby x) from 0 to 0",
	"select c group by a from 0 to 0",
	"select x[] from 0 to 0",
}

func TestParse_success(t *testing.T) {
	for _, row := range inputs {
		_, err := Parse(row)
		if err != nil {
			t.Errorf("[%s] failed to parse: %s", row, err.Error())
		}
	}

	for _, row := range selects {
		for _, prefix := range []string{"", "select "} {
			query := prefix + row
			_, err := Parse(query)
			if err != nil {
				t.Errorf("[%s] failed to parse: %s", query, err.Error())
			}
		}
	}
}

func TestParse_syntaxError(t *testing.T) {
	for _, row := range syntaxErrorQuery {
		_, err := Parse(row)
		if err == nil {
			t.Errorf("[%s] should have failed to parse", row)
		} else if _, ok := err.(SyntaxErrors); !ok {
			t.Logf("[%s] Expected SyntaxErrors, got: %s", row, err.Error())
		}
	}
}

func TestCompile(t *testing.T) {
	for _, row := range inputs {
		a := assert.New(t).Contextf(row)
		p := Parser{Buffer: row}
		p.Init()
		a.CheckError(p.Parse())
		p.Execute()
		testParserResult(a, p)
	}
}

// Helper functions
// ================

func testParserResult(a assert.Assert, p Parser) {
	a.EqInt(len(p.assertions), 0)
	if len(p.assertions) != 0 {
		for _, err := range p.assertions {
			a.Errorf("assertion error: %s", err.Error())
		}
	}
	if len(p.nodeStack) != 0 {
		for _, node := range p.nodeStack {
			a.Errorf("node error:\n%s", PrintNode(node))
		}
	}
}
<|endoftext|>