<commit_before>/*
Copyright 2020 The Knative 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 ingress

import (
	"context"
	"strings"
	"time"

	networkingClientSet "knative.dev/networking/pkg/client/clientset/versioned/typed/networking/v1alpha1"

	"go.uber.org/zap"

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

	v1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/types"
	"k8s.io/client-go/tools/cache"

	"knative.dev/net-kourier/pkg/config"
	"knative.dev/net-kourier/pkg/envoy"
	"knative.dev/net-kourier/pkg/generator"
	"knative.dev/networking/pkg/apis/networking"
	"knative.dev/networking/pkg/apis/networking/v1alpha1"
	knativeclient "knative.dev/networking/pkg/client/injection/client"
	ingressinformer "knative.dev/networking/pkg/client/injection/informers/networking/v1alpha1/ingress"
	v1alpha1ingress "knative.dev/networking/pkg/client/injection/reconciler/networking/v1alpha1/ingress"
	"knative.dev/networking/pkg/status"
	kubeclient "knative.dev/pkg/client/injection/kube/client"
	endpointsinformer "knative.dev/pkg/client/injection/kube/informers/core/v1/endpoints"
	podinformer "knative.dev/pkg/client/injection/kube/informers/core/v1/pod"
	"knative.dev/pkg/configmap"
	"knative.dev/pkg/controller"
	"knative.dev/pkg/logging"
	"knative.dev/pkg/network"
	knativeReconciler "knative.dev/pkg/reconciler"
	"knative.dev/pkg/tracker"
)

const (
	gatewayLabelKey   = "app"
	gatewayLabelValue = "3scale-kourier-gateway"

	nodeID             = "3scale-kourier-gateway"
	gatewayPort        = 19001
	managementPort     = 18000
	cacheWarmUPTimeout = 720 * time.Second
)

func NewController(ctx context.Context, cmw configmap.Watcher) *controller.Impl {
	kubernetesClient := kubeclient.Get(ctx)
	knativeClient := knativeclient.Get(ctx)
	logger := logging.FromContext(ctx)
	ingressInformer := ingressinformer.Get(ctx)
	endpointsInformer := endpointsinformer.Get(ctx)
	podInformer := podinformer.Get(ctx)
	extAuthZConfig := envoy.GetExternalAuthzConfig()

	// Get the current list of ingresses that are ready, and pass it to the cache so we can
	// know when it has been synced.
	ingressesToSync, err := getReadyIngresses(knativeClient.NetworkingV1alpha1())
	if err != nil {
		panic(err)
	}

	// Create a new Cache, with the Readiness endpoint enabled, and the list of current Ingresses.
	caches, err := generator.NewCaches(logger.Named("caches"), kubernetesClient, extAuthZConfig.Enabled, ingressesToSync)
	if err != nil {
		panic(err)
	}

	r := &Reconciler{
		kubeClient: kubernetesClient,
		caches:     caches,
		extAuthz:   extAuthZConfig.Enabled,
	}

	impl := v1alpha1ingress.NewImpl(ctx, r, config.KourierIngressClassName)

	classFilter := knativeReconciler.AnnotationFilterFunc(
		networking.IngressClassAnnotationKey, config.KourierIngressClassName, false,
	)

	resyncNotReady := func() {
		impl.FilteredGlobalResync(func(obj interface{}) bool {
			return classFilter(obj) && !obj.(*v1alpha1.Ingress).IsReady()
		}, ingressInformer.Informer())
	}
	var callbacks = envoy.Callbacks{
		Logger:  logger,
		OnError: resyncNotReady,
	}

	envoyXdsServer := envoy.NewXdsServer(
		gatewayPort,
		managementPort,
		&callbacks,
		logger,
	)
	r.xdsServer = envoyXdsServer

	statusProber := status.NewProber(
		logger.Named("status-manager"),
		NewProbeTargetLister(logger, endpointsInformer.Lister()),
		func(ing *v1alpha1.Ingress) {
			logger.Debugf("Ready callback triggered for ingress: %s/%s", ing.Namespace, ing.Name)
			impl.EnqueueKey(types.NamespacedName{Namespace: ing.Namespace, Name: ing.Name})
		})
	r.statusManager = statusProber
	statusProber.Start(ctx.Done())

	r.caches.SetOnEvicted(func(key string, value interface{}) {
		// The format of the key received is "clusterName:ingressName:ingressNamespace"
		logger.Debugf("Evicted %s", key)
		keyParts := strings.Split(key, ":")
		// We enqueue the ingress name and namespace as if it was a new event, to force
		// a config refresh.
		impl.EnqueueKey(types.NamespacedName{
			Namespace: keyParts[2],
			Name:      keyParts[1],
		})
	})

	endpointsTracker := tracker.New(impl.EnqueueKey, controller.GetTrackerLease(ctx))

	ingressTranslator := generator.NewIngressTranslator(
		r.kubeClient, endpointsInformer.Lister(), network.GetClusterDomainName(), endpointsTracker, logger)
	r.ingressTranslator = &ingressTranslator

	// Let's start the management server when our cache is in sync, to avoid sending an incomplete configuration
	// to an already running gateway container. If the cache is not warmed up after "cacheWarmUPTimeout" we just
	// start the server as somehow we couldn't sync.
	go func() {
		waitForCache(logger, caches)

		snapshot, err := r.caches.ToEnvoySnapshot()
		if err != nil {
			logger.Fatalw("Failed to create snapshot", zap.Error(err))
		}
		err = r.xdsServer.SetSnapshot(&snapshot, nodeID)
		if err != nil {
			logger.Fatalw("Failed to set snapshot", zap.Error(err))
		}
		go envoyXdsServer.RunManagementServer()

		<-ctx.Done()
	}()

	// Ingresses need to be filtered by ingress class, so Kourier does not
	// react to nor modify ingresses created by other gateways.
	ingressInformer.Informer().AddEventHandler(cache.FilteringResourceEventHandler{
		FilterFunc: classFilter,
		Handler:    controller.HandleAll(impl.Enqueue),
	})

	endpointsInformer.Informer().AddEventHandler(controller.HandleAll(
		controller.EnsureTypeMeta(
			endpointsTracker.OnChanged,
			v1.SchemeGroupVersion.WithKind("Endpoints"),
		),
	))

	podInformer.Informer().AddEventHandler(cache.FilteringResourceEventHandler{
		FilterFunc: knativeReconciler.LabelFilterFunc(gatewayLabelKey, gatewayLabelValue, false),
		Handler: cache.ResourceEventHandlerFuncs{
			// Cancel probing when a Pod is deleted
			DeleteFunc: func(obj interface{}) {
				pod, ok := obj.(*v1.Pod)
				if ok {
					statusProber.CancelPodProbing(pod)
				}
			},
		},
	})

	return impl
}

func waitForCache(log *zap.SugaredLogger, caches *generator.Caches) {
	timeout := time.After(cacheWarmUPTimeout)
	for {
		select {
		case <-timeout:
			log.Warnf("cache warm up timeout after %s", cacheWarmUPTimeout)
			return
		case <-caches.WaitForSync():
			log.Info("cache is in sync.")
			return
		}
	}
}

func getReadyIngresses(knativeClient networkingClientSet.NetworkingV1alpha1Interface) ([]*v1alpha1.Ingress, error) {
	ingresses, err := knativeClient.Ingresses("").List(metav1.ListOptions{})
	if err != nil {
		return nil, err
	}
	ingressesToWarm := make([]*v1alpha1.Ingress, 0, len(ingresses.Items))
	for _, ingress := range ingresses.Items {
		if ingress.Annotations[networking.IngressClassAnnotationKey] == config.KourierIngressClassName && ingress.GetStatus().GetCondition(v1alpha1.IngressConditionNetworkConfigured).IsTrue() {
			validIngress := ingress
			ingressesToWarm = append(ingressesToWarm, &validIngress)
		}
	}
	return ingressesToWarm, nil
}
<commit_msg>Initialize the envoy snapshot without waiting for cache sync (#137)<commit_after>/*
Copyright 2020 The Knative 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 ingress

import (
	"context"
	"strings"
	"time"

	networkingClientSet "knative.dev/networking/pkg/client/clientset/versioned/typed/networking/v1alpha1"

	"go.uber.org/zap"

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

	v1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/types"
	"k8s.io/client-go/tools/cache"

	"knative.dev/net-kourier/pkg/config"
	"knative.dev/net-kourier/pkg/envoy"
	"knative.dev/net-kourier/pkg/generator"
	"knative.dev/networking/pkg/apis/networking"
	"knative.dev/networking/pkg/apis/networking/v1alpha1"
	knativeclient "knative.dev/networking/pkg/client/injection/client"
	ingressinformer "knative.dev/networking/pkg/client/injection/informers/networking/v1alpha1/ingress"
	v1alpha1ingress "knative.dev/networking/pkg/client/injection/reconciler/networking/v1alpha1/ingress"
	"knative.dev/networking/pkg/status"
	kubeclient "knative.dev/pkg/client/injection/kube/client"
	endpointsinformer "knative.dev/pkg/client/injection/kube/informers/core/v1/endpoints"
	podinformer "knative.dev/pkg/client/injection/kube/informers/core/v1/pod"
	"knative.dev/pkg/configmap"
	"knative.dev/pkg/controller"
	"knative.dev/pkg/logging"
	"knative.dev/pkg/network"
	knativeReconciler "knative.dev/pkg/reconciler"
	"knative.dev/pkg/tracker"
)

const (
	gatewayLabelKey   = "app"
	gatewayLabelValue = "3scale-kourier-gateway"

	nodeID             = "3scale-kourier-gateway"
	gatewayPort        = 19001
	managementPort     = 18000
	cacheWarmUPTimeout = 720 * time.Second
)

func NewController(ctx context.Context, cmw configmap.Watcher) *controller.Impl {
	kubernetesClient := kubeclient.Get(ctx)
	knativeClient := knativeclient.Get(ctx)
	logger := logging.FromContext(ctx)
	ingressInformer := ingressinformer.Get(ctx)
	endpointsInformer := endpointsinformer.Get(ctx)
	podInformer := podinformer.Get(ctx)
	extAuthZConfig := envoy.GetExternalAuthzConfig()

	// Get the current list of ingresses that are ready, and pass it to the cache so we can
	// know when it has been synced.
	ingressesToSync, err := getReadyIngresses(knativeClient.NetworkingV1alpha1())
	if err != nil {
		panic(err)
	}

	// Create a new Cache, with the Readiness endpoint enabled, and the list of current Ingresses.
	caches, err := generator.NewCaches(logger.Named("caches"), kubernetesClient, extAuthZConfig.Enabled, ingressesToSync)
	if err != nil {
		panic(err)
	}

	r := &Reconciler{
		kubeClient: kubernetesClient,
		caches:     caches,
		extAuthz:   extAuthZConfig.Enabled,
	}

	impl := v1alpha1ingress.NewImpl(ctx, r, config.KourierIngressClassName)

	classFilter := knativeReconciler.AnnotationFilterFunc(
		networking.IngressClassAnnotationKey, config.KourierIngressClassName, false,
	)

	resyncNotReady := func() {
		impl.FilteredGlobalResync(func(obj interface{}) bool {
			return classFilter(obj) && !obj.(*v1alpha1.Ingress).IsReady()
		}, ingressInformer.Informer())
	}
	var callbacks = envoy.Callbacks{
		Logger:  logger,
		OnError: resyncNotReady,
	}

	envoyXdsServer := envoy.NewXdsServer(
		gatewayPort,
		managementPort,
		&callbacks,
		logger,
	)
	r.xdsServer = envoyXdsServer

	statusProber := status.NewProber(
		logger.Named("status-manager"),
		NewProbeTargetLister(logger, endpointsInformer.Lister()),
		func(ing *v1alpha1.Ingress) {
			logger.Debugf("Ready callback triggered for ingress: %s/%s", ing.Namespace, ing.Name)
			impl.EnqueueKey(types.NamespacedName{Namespace: ing.Namespace, Name: ing.Name})
		})
	r.statusManager = statusProber
	statusProber.Start(ctx.Done())

	r.caches.SetOnEvicted(func(key string, value interface{}) {
		// The format of the key received is "clusterName:ingressName:ingressNamespace"
		logger.Debugf("Evicted %s", key)
		keyParts := strings.Split(key, ":")
		// We enqueue the ingress name and namespace as if it was a new event, to force
		// a config refresh.
		impl.EnqueueKey(types.NamespacedName{
			Namespace: keyParts[2],
			Name:      keyParts[1],
		})
	})

	endpointsTracker := tracker.New(impl.EnqueueKey, controller.GetTrackerLease(ctx))

	ingressTranslator := generator.NewIngressTranslator(
		r.kubeClient, endpointsInformer.Lister(), network.GetClusterDomainName(), endpointsTracker, logger)
	r.ingressTranslator = &ingressTranslator

	// Initialize the Envoy snapshot.
	snapshot, err := r.caches.ToEnvoySnapshot()
	if err != nil {
		logger.Fatalw("Failed to create snapshot", zap.Error(err))
	}
	err = r.xdsServer.SetSnapshot(&snapshot, nodeID)
	if err != nil {
		logger.Fatalw("Failed to set snapshot", zap.Error(err))
	}

	// Let's start the management server when our cache is in sync, to avoid sending an incomplete configuration
	// to an already running gateway container. If the cache is not warmed up after "cacheWarmUPTimeout" we just
	// start the server as somehow we couldn't sync.
	go func() {
		waitForCache(logger, caches)
		go envoyXdsServer.RunManagementServer()
		<-ctx.Done()
	}()

	// Ingresses need to be filtered by ingress class, so Kourier does not
	// react to nor modify ingresses created by other gateways.
	ingressInformer.Informer().AddEventHandler(cache.FilteringResourceEventHandler{
		FilterFunc: classFilter,
		Handler:    controller.HandleAll(impl.Enqueue),
	})

	endpointsInformer.Informer().AddEventHandler(controller.HandleAll(
		controller.EnsureTypeMeta(
			endpointsTracker.OnChanged,
			v1.SchemeGroupVersion.WithKind("Endpoints"),
		),
	))

	podInformer.Informer().AddEventHandler(cache.FilteringResourceEventHandler{
		FilterFunc: knativeReconciler.LabelFilterFunc(gatewayLabelKey, gatewayLabelValue, false),
		Handler: cache.ResourceEventHandlerFuncs{
			// Cancel probing when a Pod is deleted
			DeleteFunc: func(obj interface{}) {
				pod, ok := obj.(*v1.Pod)
				if ok {
					statusProber.CancelPodProbing(pod)
				}
			},
		},
	})

	return impl
}

func waitForCache(log *zap.SugaredLogger, caches *generator.Caches) {
	timeout := time.After(cacheWarmUPTimeout)
	for {
		select {
		case <-timeout:
			log.Warnf("cache warm up timeout after %s", cacheWarmUPTimeout)
			return
		case <-caches.WaitForSync():
			log.Info("cache is in sync.")
			return
		}
	}
}

func getReadyIngresses(knativeClient networkingClientSet.NetworkingV1alpha1Interface) ([]*v1alpha1.Ingress, error) {
	ingresses, err := knativeClient.Ingresses("").List(metav1.ListOptions{})
	if err != nil {
		return nil, err
	}
	ingressesToWarm := make([]*v1alpha1.Ingress, 0, len(ingresses.Items))
	for _, ingress := range ingresses.Items {
		if ingress.Annotations[networking.IngressClassAnnotationKey] == config.KourierIngressClassName && ingress.GetStatus().GetCondition(v1alpha1.IngressConditionNetworkConfigured).IsTrue() {
			validIngress := ingress
			ingressesToWarm = append(ingressesToWarm, &validIngress)
		}
	}
	return ingressesToWarm, nil
}
<|endoftext|>


<commit_before>/*
Copyright 2019 The Knative Authors

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

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

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

package trigger

import (
	"context"

	"knative.dev/eventing/pkg/logging"
	"knative.dev/pkg/configmap"
	"knative.dev/pkg/controller"

	eventingclient "knative.dev/eventing/pkg/client/injection/client"
	"knative.dev/eventing/pkg/client/injection/informers/eventing/v1beta1/broker"
	"knative.dev/eventing/pkg/client/injection/informers/eventing/v1beta1/trigger"
	triggerreconciler "knative.dev/eventing/pkg/client/injection/reconciler/eventing/v1beta1/trigger"
	kubeclient "knative.dev/pkg/client/injection/kube/client"
	"knative.dev/pkg/client/injection/kube/informers/core/v1/namespace"
)

// NewController initializes the controller and is called by the generated code.
// Registers event handlers to enqueue events.
func NewController(
	ctx context.Context,
	cmw configmap.Watcher,
) *controller.Impl {

	triggerInformer := trigger.Get(ctx)
	brokerInformer := broker.Get(ctx)
	namespaceInformer := namespace.Get(ctx)

	r := &Reconciler{
		eventingClientSet: eventingclient.Get(ctx),
		kubeClientSet:     kubeclient.Get(ctx),
		brokerLister:      brokerInformer.Lister(),
		namespaceLister:   namespaceInformer.Lister(),
	}
	impl := triggerreconciler.NewImpl(ctx, r)

	logging.FromContext(ctx).Info("Setting up event handlers")
	triggerInformer.Informer().AddEventHandler(controller.HandleAll(impl.Enqueue))
	return impl
}
<commit_msg>Trigger controller watches brokers to update trigger status immediately (#3296)<commit_after>/*
Copyright 2019 The Knative Authors

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

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

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

package trigger

import (
	"context"

	"go.uber.org/zap"
	"k8s.io/apimachinery/pkg/labels"
	"knative.dev/eventing/pkg/apis/eventing"

	"knative.dev/eventing/pkg/logging"
	"knative.dev/pkg/configmap"
	"knative.dev/pkg/controller"

	"knative.dev/eventing/pkg/apis/eventing/v1beta1"
	eventingclient "knative.dev/eventing/pkg/client/injection/client"
	"knative.dev/eventing/pkg/client/injection/informers/eventing/v1beta1/broker"
	"knative.dev/eventing/pkg/client/injection/informers/eventing/v1beta1/trigger"
	triggerreconciler "knative.dev/eventing/pkg/client/injection/reconciler/eventing/v1beta1/trigger"
	kubeclient "knative.dev/pkg/client/injection/kube/client"
	"knative.dev/pkg/client/injection/kube/informers/core/v1/namespace"
)

// NewController initializes the controller and is called by the generated code.
// Registers event handlers to enqueue events.
func NewController(
	ctx context.Context,
	cmw configmap.Watcher,
) *controller.Impl {

	triggerInformer := trigger.Get(ctx)
	brokerInformer := broker.Get(ctx)
	namespaceInformer := namespace.Get(ctx)

	r := &Reconciler{
		eventingClientSet: eventingclient.Get(ctx),
		kubeClientSet:     kubeclient.Get(ctx),
		brokerLister:      brokerInformer.Lister(),
		namespaceLister:   namespaceInformer.Lister(),
	}
	impl := triggerreconciler.NewImpl(ctx, r)

	logging.FromContext(ctx).Info("Setting up event handlers")
	triggerInformer.Informer().AddEventHandler(controller.HandleAll(impl.Enqueue))

	// Watch brokers.
	brokerInformer.Informer().AddEventHandler(controller.HandleAll(func(obj interface{}) {
		if b, ok := obj.(*v1beta1.Broker); ok {
			triggers, err := triggerInformer.Lister().Triggers(b.Namespace).List(labels.SelectorFromSet(map[string]string{eventing.BrokerLabelKey: b.Name}))
			if err != nil {
				logging.FromContext(ctx).Warn("Failed to list triggers", zap.String("Namespace", b.Namespace), zap.String("Broker", b.Name))
				return
			}
			for _, trigger := range triggers {
				impl.Enqueue(trigger)
			}
		}
	}))
	return impl
}
<|endoftext|>


<commit_before>package sqlobjectstore

import (
	"database/sql"
	"fmt"
	"io/ioutil"
	"path"
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"

	_ "github.com/mattn/go-sqlite3"

	"nimona.io/internal/fixtures"
	"nimona.io/pkg/crypto"
	"nimona.io/pkg/errors"
	"nimona.io/pkg/object"
	"nimona.io/pkg/objectstore"
)

func tempSqlite3(t *testing.T) *sql.DB {
	dirPath, err := ioutil.TempDir("", "nimona-store-sql")
	require.NoError(t, err)
	fmt.Println(path.Join(dirPath, "sqlite3.db"))
	db, err := sql.Open("sqlite3", path.Join(dirPath, "sqlite3.db"))
	require.NoError(t, err)
	return db
}

func TestNewDatabase(t *testing.T) {
	dblite := tempSqlite3(t)
	store, err := New(dblite)
	require.NoError(t, err)
	require.NotNil(t, store)

	err = store.Close()
	require.NoError(t, err)
}

func TestStoreRetrieveUpdate(t *testing.T) {
	dblite := tempSqlite3(t)
	store, err := New(dblite)
	require.NoError(t, err)
	require.NotNil(t, store)

	p := fixtures.TestStream{
		Nonce: "asdf",
	}
	obj := &object.Object{
		Type: "foo",
		Metadata: object.Metadata{
			Stream: p.ToObject().CID(),
		},
		Data: object.Map{
			"key": object.String("value"),
		},
	}

	err = store.Put(
		obj,
	)
	require.NoError(t, err)

	err = store.PutWithTTL(
		obj,
		10*time.Second,
	)

	fmt.Println(obj.CID())

	require.NoError(t, err)
	retrievedObj, err := store.Get(obj.CID())
	require.NoError(t, err)

	val := retrievedObj.Data["key"]
	require.NotNil(t, val)
	assert.Equal(t, "value", string(val.(object.String)))

	stCID := obj.Metadata.Stream
	require.NotEmpty(t, stCID)

	err = store.UpdateTTL(obj.CID(), 10)
	require.NoError(t, err)

	cidList, err := store.GetRelations(p.ToObject().CID())
	require.NoError(t, err)
	assert.NotEmpty(t, cidList)

	err = store.Remove(p.ToObject().CID())
	require.NoError(t, err)

	retrievedObj2, err := store.Get(p.ToObject().CID())
	require.True(t, errors.CausedBy(err, objectstore.ErrNotFound))
	require.Nil(t, retrievedObj2)

	err = store.Close()
	require.NoError(t, err)
}

func TestFilter(t *testing.T) {
	dblite := tempSqlite3(t)
	store, err := New(dblite)
	require.NoError(t, err)
	require.NotNil(t, store)

	k, err := crypto.GenerateEd25519PrivateKey()
	require.NoError(t, err)

	p := fixtures.TestStream{
		Nonce: "asdf",
	}

	sig, err := object.NewSignature(k, p.ToObject())
	require.NoError(t, err)

	p.Metadata.Signature = sig

	err = store.Put(p.ToObject())
	require.NoError(t, err)

	ph := p.ToObject().CID()

	c := fixtures.TestSubscribed{}
	c.Metadata.Stream = ph

	objects := []*object.Object{
		p.ToObject(),
	}

	cids := []object.CID{}
	for i := 0; i < 5; i++ {
		obj := &object.Object{
			Type: new(fixtures.TestSubscribed).Type(),
			Metadata: object.Metadata{
				Stream: ph,
				Datetime: time.Now().
					Add(time.Duration(i) * time.Hour).
					Format(time.RFC3339),
			},
			Data: object.Map{
				"keys": object.String(fmt.Sprintf("value_%d", i)),
			},
		}
		if i%2 == 0 {
			obj.Metadata.Owner = k.PublicKey()
		}
		objects = append(objects, obj)
		err = store.Put(obj)
		require.NoError(t, err)
		cids = append(cids, obj.CID())
	}

	objectReader, err := store.Filter(
		FilterByCID(cids[0]),
		FilterByCID(cids[1]),
		FilterByCID(cids[2]),
		FilterByCID(cids[3]),
		FilterByCID(cids[4]),
	)
	require.NotNil(t, objectReader)
	require.NoError(t, err)
	got, err := object.ReadAll(objectReader)
	require.NoError(t, err)
	require.Equal(t, len(cids), len(got))
	objectReader, err = store.Filter(
		FilterByOwner(k.PublicKey()),
	)
	require.NoError(t, err)
	got, err = object.ReadAll(objectReader)
	require.NoError(t, err)
	require.Equal(t, 3, len(got))

	objectReader, err = store.Filter(
		FilterByObjectType(c.Type()),
	)
	require.NoError(t, err)
	got, err = object.ReadAll(objectReader)
	require.NoError(t, err)
	require.Equal(t, len(cids), len(got))

	objectReader, err = store.Filter(
		FilterByStreamCID(ph),
	)
	require.NoError(t, err)
	got, err = object.ReadAll(objectReader)
	require.NoError(t, err)
	require.Equal(t, len(cids)+1, len(got))

	t.Run("filter with limit 1 offset 0", func(t *testing.T) {
		objectReader, err = store.Filter(
			FilterByStreamCID(ph),
			FilterLimit(1, 0),
			FilterOrderBy("MetadataDatetime"),
			FilterOrderDir("ASC"),
		)
		require.NoError(t, err)
		got, err = object.ReadAll(objectReader)
		require.NoError(t, err)
		require.Equal(t, 1, len(got))
		require.Equal(t, *objects[0], *got[0])
	})

	t.Run("filter with limit 1 offset 1", func(t *testing.T) {
		objectReader, err = store.Filter(
			FilterByStreamCID(ph),
			FilterLimit(1, 1),
			FilterOrderBy("MetadataDatetime"),
			FilterOrderDir("ASC"),
		)
		require.NoError(t, err)
		got, err = object.ReadAll(objectReader)
		require.NoError(t, err)
		require.Equal(t, 1, len(got))
		require.Equal(t, *objects[1], *got[0])
	})

	objectReader, err = store.Filter(
		FilterByCID(cids[0]),
		FilterByObjectType(c.Type()),
		FilterByStreamCID(ph),
	)
	require.NoError(t, err)
	got, err = object.ReadAll(objectReader)
	require.NoError(t, err)
	require.Equal(t, 1, len(got))

	err = store.Close()
	require.NoError(t, err)
}

func TestStore_Relations(t *testing.T) {
	f00 := &object.Object{
		Type:     "f00",
		Metadata: object.Metadata{},
		Data: object.Map{
			"f00": object.String("f00"),
		},
	}

	f01 := &object.Object{
		Type: "f01",
		Metadata: object.Metadata{
			Stream: f00.CID(),
			Parents: object.Parents{
				"*": []object.CID{
					f00.CID(),
				},
			},
		},
		Data: object.Map{
			"f01": object.String("f01"),
		},
	}

	f02 := &object.Object{
		Type: "f02",
		Metadata: object.Metadata{
			Stream: f00.CID(),
			Parents: object.Parents{
				"*": []object.CID{
					f01.CID(),
				},
			},
		},
		Data: object.Map{
			"f02": object.String("f02"),
		},
	}

	fmt.Println("f00", f00.CID())
	fmt.Println("f01", f01.CID())
	fmt.Println("f02", f02.CID())

	dblite := tempSqlite3(t)
	store, err := New(dblite)
	require.NoError(t, err)
	require.NotNil(t, store)

	require.NoError(t, store.Put(f00))

	t.Run("root is considered a leaf", func(t *testing.T) {
		leaves, err := store.GetStreamLeaves(f00.CID())
		require.NoError(t, err)
		require.NotNil(t, leaves)
		assert.Len(t, leaves, 1)
		assert.Equal(t, []object.CID{f00.CID()}, leaves)
	})

	require.NoError(t, store.Put(f01))
	require.NoError(t, store.Put(f02))

	leaves, err := store.GetStreamLeaves(f00.CID())
	require.NoError(t, err)
	require.NotNil(t, leaves)
	assert.Len(t, leaves, 1)
	assert.Equal(t, []object.CID{f02.CID()}, leaves)

	fmt.Println(leaves)
}
<commit_msg>chore(sqlobjectstore): use t.TempDir<commit_after>package sqlobjectstore

import (
	"database/sql"
	"fmt"
	"path"
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"

	_ "github.com/mattn/go-sqlite3"

	"nimona.io/internal/fixtures"
	"nimona.io/pkg/crypto"
	"nimona.io/pkg/errors"
	"nimona.io/pkg/object"
	"nimona.io/pkg/objectstore"
)

func tempSqlite3(t *testing.T) *sql.DB {
	t.Helper()
	dirPath := t.TempDir()
	fmt.Println(path.Join(dirPath, "sqlite3.db"))
	db, err := sql.Open("sqlite3", path.Join(dirPath, "sqlite3.db"))
	require.NoError(t, err)
	return db
}

func TestNewDatabase(t *testing.T) {
	dblite := tempSqlite3(t)
	store, err := New(dblite)
	require.NoError(t, err)
	require.NotNil(t, store)

	err = store.Close()
	require.NoError(t, err)
}

func TestStoreRetrieveUpdate(t *testing.T) {
	dblite := tempSqlite3(t)
	store, err := New(dblite)
	require.NoError(t, err)
	require.NotNil(t, store)

	p := fixtures.TestStream{
		Nonce: "asdf",
	}
	obj := &object.Object{
		Type: "foo",
		Metadata: object.Metadata{
			Stream: p.ToObject().CID(),
		},
		Data: object.Map{
			"key": object.String("value"),
		},
	}

	err = store.Put(
		obj,
	)
	require.NoError(t, err)

	err = store.PutWithTTL(
		obj,
		10*time.Second,
	)

	fmt.Println(obj.CID())

	require.NoError(t, err)
	retrievedObj, err := store.Get(obj.CID())
	require.NoError(t, err)

	val := retrievedObj.Data["key"]
	require.NotNil(t, val)
	assert.Equal(t, "value", string(val.(object.String)))

	stCID := obj.Metadata.Stream
	require.NotEmpty(t, stCID)

	err = store.UpdateTTL(obj.CID(), 10)
	require.NoError(t, err)

	cidList, err := store.GetRelations(p.ToObject().CID())
	require.NoError(t, err)
	assert.NotEmpty(t, cidList)

	err = store.Remove(p.ToObject().CID())
	require.NoError(t, err)

	retrievedObj2, err := store.Get(p.ToObject().CID())
	require.True(t, errors.CausedBy(err, objectstore.ErrNotFound))
	require.Nil(t, retrievedObj2)

	err = store.Close()
	require.NoError(t, err)
}

func TestFilter(t *testing.T) {
	dblite := tempSqlite3(t)
	store, err := New(dblite)
	require.NoError(t, err)
	require.NotNil(t, store)

	k, err := crypto.GenerateEd25519PrivateKey()
	require.NoError(t, err)

	p := fixtures.TestStream{
		Nonce: "asdf",
	}

	sig, err := object.NewSignature(k, p.ToObject())
	require.NoError(t, err)

	p.Metadata.Signature = sig

	err = store.Put(p.ToObject())
	require.NoError(t, err)

	ph := p.ToObject().CID()

	c := fixtures.TestSubscribed{}
	c.Metadata.Stream = ph

	objects := []*object.Object{
		p.ToObject(),
	}

	cids := []object.CID{}
	for i := 0; i < 5; i++ {
		obj := &object.Object{
			Type: new(fixtures.TestSubscribed).Type(),
			Metadata: object.Metadata{
				Stream: ph,
				Datetime: time.Now().
					Add(time.Duration(i) * time.Hour).
					Format(time.RFC3339),
			},
			Data: object.Map{
				"keys": object.String(fmt.Sprintf("value_%d", i)),
			},
		}
		if i%2 == 0 {
			obj.Metadata.Owner = k.PublicKey()
		}
		objects = append(objects, obj)
		err = store.Put(obj)
		require.NoError(t, err)
		cids = append(cids, obj.CID())
	}

	objectReader, err := store.Filter(
		FilterByCID(cids[0]),
		FilterByCID(cids[1]),
		FilterByCID(cids[2]),
		FilterByCID(cids[3]),
		FilterByCID(cids[4]),
	)
	require.NotNil(t, objectReader)
	require.NoError(t, err)
	got, err := object.ReadAll(objectReader)
	require.NoError(t, err)
	require.Equal(t, len(cids), len(got))
	objectReader, err = store.Filter(
		FilterByOwner(k.PublicKey()),
	)
	require.NoError(t, err)
	got, err = object.ReadAll(objectReader)
	require.NoError(t, err)
	require.Equal(t, 3, len(got))

	objectReader, err = store.Filter(
		FilterByObjectType(c.Type()),
	)
	require.NoError(t, err)
	got, err = object.ReadAll(objectReader)
	require.NoError(t, err)
	require.Equal(t, len(cids), len(got))

	objectReader, err = store.Filter(
		FilterByStreamCID(ph),
	)
	require.NoError(t, err)
	got, err = object.ReadAll(objectReader)
	require.NoError(t, err)
	require.Equal(t, len(cids)+1, len(got))

	t.Run("filter with limit 1 offset 0", func(t *testing.T) {
		objectReader, err = store.Filter(
			FilterByStreamCID(ph),
			FilterLimit(1, 0),
			FilterOrderBy("MetadataDatetime"),
			FilterOrderDir("ASC"),
		)
		require.NoError(t, err)
		got, err = object.ReadAll(objectReader)
		require.NoError(t, err)
		require.Equal(t, 1, len(got))
		require.Equal(t, *objects[0], *got[0])
	})

	t.Run("filter with limit 1 offset 1", func(t *testing.T) {
		objectReader, err = store.Filter(
			FilterByStreamCID(ph),
			FilterLimit(1, 1),
			FilterOrderBy("MetadataDatetime"),
			FilterOrderDir("ASC"),
		)
		require.NoError(t, err)
		got, err = object.ReadAll(objectReader)
		require.NoError(t, err)
		require.Equal(t, 1, len(got))
		require.Equal(t, *objects[1], *got[0])
	})

	objectReader, err = store.Filter(
		FilterByCID(cids[0]),
		FilterByObjectType(c.Type()),
		FilterByStreamCID(ph),
	)
	require.NoError(t, err)
	got, err = object.ReadAll(objectReader)
	require.NoError(t, err)
	require.Equal(t, 1, len(got))

	err = store.Close()
	require.NoError(t, err)
}

func TestStore_Relations(t *testing.T) {
	f00 := &object.Object{
		Type:     "f00",
		Metadata: object.Metadata{},
		Data: object.Map{
			"f00": object.String("f00"),
		},
	}

	f01 := &object.Object{
		Type: "f01",
		Metadata: object.Metadata{
			Stream: f00.CID(),
			Parents: object.Parents{
				"*": []object.CID{
					f00.CID(),
				},
			},
		},
		Data: object.Map{
			"f01": object.String("f01"),
		},
	}

	f02 := &object.Object{
		Type: "f02",
		Metadata: object.Metadata{
			Stream: f00.CID(),
			Parents: object.Parents{
				"*": []object.CID{
					f01.CID(),
				},
			},
		},
		Data: object.Map{
			"f02": object.String("f02"),
		},
	}

	fmt.Println("f00", f00.CID())
	fmt.Println("f01", f01.CID())
	fmt.Println("f02", f02.CID())

	dblite := tempSqlite3(t)
	store, err := New(dblite)
	require.NoError(t, err)
	require.NotNil(t, store)

	require.NoError(t, store.Put(f00))

	t.Run("root is considered a leaf", func(t *testing.T) {
		leaves, err := store.GetStreamLeaves(f00.CID())
		require.NoError(t, err)
		require.NotNil(t, leaves)
		assert.Len(t, leaves, 1)
		assert.Equal(t, []object.CID{f00.CID()}, leaves)
	})

	require.NoError(t, store.Put(f01))
	require.NoError(t, store.Put(f02))

	leaves, err := store.GetStreamLeaves(f00.CID())
	require.NoError(t, err)
	require.NotNil(t, leaves)
	assert.Len(t, leaves, 1)
	assert.Equal(t, []object.CID{f02.CID()}, leaves)

	fmt.Println(leaves)
}
<|endoftext|>


<commit_before>package main

import (
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"time"

	fs "gopkg.in/fsnotify.v1"
)

//
// Event
//
type Event struct {
	E fs.Event
	T time.Time
	I os.FileInfo
}

//
// chans
//
type ChanEvent chan *Event
type ChanExit chan bool

//
// Piper
//
type Piper interface {
	Pipe(next Piper) Piper
	Handle(e *Event)
	Close() error
}

//
// Watcher watch file changes
//
type Watcher struct {
	w    *fs.Watcher
	next Piper
	exit ChanExit
}

// NewWatcher create watcher
func NewWatcher(dirs []string) (*Watcher, error) {
	// w
	w, err := fs.NewWatcher()
	if err != nil {
		return nil, err
	}

	// listen
	log.Println("[umon]", "Watcher: watch dirs", dirs)
	for _, dir := range dirs {
		filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
			if info == nil {
				return err
			}
			if !info.IsDir() {
				return nil
			}
			w.Add(path)
			return nil
		})
	}

	// ok
	return &Watcher{
		w:    w,
		exit: make(ChanExit, 1),
	}, nil
}

// @impl Piper
func (w *Watcher) Pipe(next Piper) Piper {
	w.next = next
	go w.runWatch()
	return next
}

// runWatch run watch
func (w *Watcher) runWatch() {
	for {
		select {
		case <-w.exit:
			return

		case e := <-w.w.Events:
			info, _ := os.Stat(e.Name)
			if info != nil && info.IsDir() {
				switch e.Op {
				case fs.Create:
					w.w.Add(e.Name)
				case fs.Remove:
					w.w.Remove(e.Name)
				}
			}

			w.next.Handle(&Event{
				E: e,
				T: time.Now(),
				I: info,
			})
		}
	}
}

// @impl Piper.Handle
func (w *Watcher) Handle(e *Event) {
	// empty
}

// Close watch
func (w *Watcher) Close() error {
	w.exit <- true
	return nil
}

//
// Filter filter unwanted event
//
type Filter struct {
	next Piper

	events ChanEvent
	exit   ChanExit
}

func NewFilter() (*Filter, error) {
	return &Filter{
		exit:   make(ChanExit, 1),
		events: make(ChanEvent, 16),
	}, nil
}

// @impl Piper.Handle
func (f *Filter) Handle(e *Event) {
	f.events <- e
}

// @impl Piper.Pipe
func (f *Filter) Pipe(next Piper) Piper {
	f.next = next
	go f.runFilt()
	return next
}

// runFilter run filter
func (f *Filter) runFilt() {
	// last time when receive event
	var (
		lastTime time.Time
		lastEvt  *Event
	)

	// check event ticker
	ticker := time.NewTicker(time.Second)

	for {
		select {
		case <-f.exit:
			return

		case <-ticker.C:
			if lastEvt == nil {
				continue
			}
			if time.Now().Sub(lastTime) < time.Second {
				continue
			}
			f.next.Handle(lastEvt)
			lastEvt = nil

		case e := <-f.events:
			name := filepath.Base(e.E.Name)
			if strings.HasPrefix(name, ".") {
				continue
			}
			ext := filepath.Ext(name)
			if ext != ".go" {
				continue
			}
			lastTime = time.Now()
			lastEvt = e
		}
	}
}

// @impl Close
func (f *Filter) Close() error {
	f.exit <- true
	return nil
}

//
// Builder builder unwanted event
//
type Builder struct {
	next   Piper
	events ChanEvent
	exit   ChanExit
}

func NewBuilder() (*Builder, error) {
	return &Builder{
		exit:   make(ChanExit, 1),
		events: make(ChanEvent, 16),
	}, nil
}

// @impl Piper.Handle
func (b *Builder) Handle(e *Event) {
	b.events <- e
}

// @impl Piper.Pipe
func (b *Builder) Pipe(next Piper) Piper {
	b.next = next
	go b.runBuild()
	return next
}

// runBuild run builder
func (b *Builder) runBuild() {
	var (
		lastTime time.Time
		lastEvt  *Event
	)
	ticker := time.NewTicker(time.Second)

	for {
		select {
		case <-b.exit:
			return

		case <-ticker.C:
			if lastEvt == nil {
				continue
			}
			if time.Now().Sub(lastTime) < time.Second {
				continue
			}
			b.next.Handle(lastEvt)
			lastEvt = nil

		case e := <-b.events:
			cmd := exec.Command("go", "build")
			cmd.Stderr, cmd.Stdout = os.Stderr, os.Stdout
			if err := cmd.Start(); err != nil {
				log.Fatal(err)
			}
			if err := cmd.Wait(); err != nil {
				log.Println("[umon]", "Builder: build err", err)
			} else {
				lastTime = time.Now()
				lastEvt = e
				log.Println("[umon]", "Builder: build ok")
			}
		}
	}
}

// @impl Close
func (b *Builder) Close() error {
	b.exit <- true
	return nil
}

//
// Runner runner unwanted event
//
type Runner struct {
	next   Piper
	events ChanEvent
	exit   ChanExit
}

func NewRunner() (*Runner, error) {
	return &Runner{
		exit:   make(ChanExit, 1),
		events: make(ChanEvent, 16),
	}, nil
}

// @impl Piper.Handle
func (b *Runner) Handle(e *Event) {
	b.events <- e
}

// @impl Piper.Pipe
func (b *Runner) Pipe(next Piper) Piper {
	b.next = next
	go b.runRun()
	return next
}

// runRun run runner
func (b *Runner) runRun() {
	// app name
	runDir := os.Getenv("PWD")
	if len(runDir) == 0 {
		panic("no PWD env")
	}
	appName := filepath.Join(runDir, filepath.Base(runDir))
	log.Println("[umon]", "Runner: app", appName)

	// notify run
	runC := make(chan bool, 1)
	runC <- true

	// cmd
	var cmd *exec.Cmd
	for {
		select {
		case <-b.exit:
			return

		case <-runC:
			// kill
			if cmd != nil {
				log.Println("[umon]", "Runner: kill", cmd.Process)
				if cmd.Process != nil {
					cmd.Process.Kill()
				}
				cmd = nil
				time.Sleep(200*time.Microsecond)
			}

			// exist
			if _, err := os.Stat(appName); err != nil {
				if os.IsNotExist(err) {
					log.Println("[umon]", "Runner: app not exist")
				}
				continue
			}

			// start
			cmd = exec.Command(appName)
			cmd.Stderr, cmd.Stdout = os.Stderr, os.Stdout
			if err := cmd.Start(); err != nil {
				log.Println("[umon]", "Runner: start err", err)
			} else {
				log.Println("[umon]", "Runner: start ok")
			}

		case <-b.events:
			runC <- true
		}
	}
}

// @impl Close
func (b *Runner) Close() error {
	b.exit <- true
	return nil
}

//
// Ender
//
type Ender struct {
	// empty
}

// NewEnder create ender instance
func NewEnder() (*Ender, error) {
	return &Ender{
	// empty
	}, nil
}

// @impl Piper.Pipe
func (ed *Ender) Pipe(next Piper) Piper {
	// no next
	return nil
}

// @impl Piper.Handle
func (ed *Ender) Handle(e *Event) {
	log.Println("[umon]", "Ender: handle", e)
}

// @impl Close
func (ed *Ender) Close() error {
	return nil
}

//
// umon ../app ../ctrl
//
func main() {
	// watcher
	watchDir := []string{"."}
	if len(os.Args) > 1 {
		watchDir = os.Args[1:]
	}
	watch, err := NewWatcher(watchDir)
	if err != nil {
		log.Fatal(err)
	}
	defer watch.Close()

	// filter
	filter, err := NewFilter()
	if err != nil {
		log.Fatal(err)
	}
	defer filter.Close()

	// builder
	builder, err := NewBuilder()
	if err != nil {
		log.Fatal(err)
	}
	defer builder.Close()

	// runner
	runner, err := NewRunner()
	if err != nil {
		log.Fatal(err)
	}
	defer runner.Close()

	// end
	end, err := NewEnder()
	if err != nil {
		log.Fatal(err)
	}
	defer end.Close()

	// pipe
	watch.Pipe(filter).Pipe(builder).Pipe(runner).Pipe(end)

	// wait
	exit := make(ChanExit)
	<-exit
}
<commit_msg>rearrange code structure<commit_after>package main

import (
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"time"

	fs "gopkg.in/fsnotify.v1"
)

//
// Event
//
type Event struct {
	E fs.Event
	T time.Time
	I os.FileInfo
}

//
// chans
//
type ChanEvent chan *Event
type ChanExit chan bool

//
// Piper
//
type Piper interface {
	Pipe(next Piper) Piper
	Handle(e *Event)
	Close() error
}

//
// Watcher watch file changes
//
type Watcher struct {
	w    *fs.Watcher
	next Piper
	exit ChanExit
}

// NewWatcher create watcher
func NewWatcher(dirs []string) (*Watcher, error) {
	// w
	w, err := fs.NewWatcher()
	if err != nil {
		return nil, err
	}

	// listen
	log.Println("[umon]", "Watcher: watch dirs", dirs)
	for _, dir := range dirs {
		filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
			if info == nil {
				return err
			}
			if !info.IsDir() {
				return nil
			}
			w.Add(path)
			return nil
		})
	}

	// ok
	return &Watcher{
		w:    w,
		exit: make(ChanExit, 1),
	}, nil
}

// @impl Piper
func (w *Watcher) Pipe(next Piper) Piper {
	w.next = next
	go w.runWatch()
	return next
}

// runWatch run watch
func (w *Watcher) runWatch() {
	for {
		select {
		case <-w.exit:
			return

		case e := <-w.w.Events:
			info, _ := os.Stat(e.Name)
			if info != nil && info.IsDir() {
				switch e.Op {
				case fs.Create:
					w.w.Add(e.Name)
				case fs.Remove:
					w.w.Remove(e.Name)
				}
			}
			w.next.Handle(&Event{
				E: e,
				T: time.Now(),
				I: info,
			})
		}
	}
}

// @impl Piper.Handle
func (w *Watcher) Handle(e *Event) {
	// empty
}

// Close watch
func (w *Watcher) Close() error {
	w.exit <- true
	return nil
}

//
// Filter filter unwanted event
//
type Filter struct {
	next Piper

	events ChanEvent
	exit   ChanExit
}

func NewFilter() (*Filter, error) {
	return &Filter{
		exit:   make(ChanExit, 1),
		events: make(ChanEvent, 16),
	}, nil
}

// @impl Piper.Handle
func (f *Filter) Handle(e *Event) {
	f.events <- e
}

// @impl Piper.Pipe
func (f *Filter) Pipe(next Piper) Piper {
	f.next = next
	go f.runFilt()
	return next
}

// runFilter run filter
func (f *Filter) runFilt() {
	// last time when receive event
	var (
		lastTime time.Time
		lastEvt  *Event
	)

	// check event ticker
	ticker := time.NewTicker(time.Second)

	for {
		select {
		case <-f.exit:
			return

		case e := <-f.events:
			name := filepath.Base(e.E.Name)
			if strings.HasPrefix(name, ".") {
				continue
			}
			ext := filepath.Ext(name)
			if ext != ".go" {
				continue
			}
			lastTime = time.Now()
			lastEvt = e

		case <-ticker.C:
			if lastEvt == nil {
				continue
			}
			if time.Now().Sub(lastTime) < time.Second {
				continue
			}
			f.next.Handle(lastEvt)
			lastEvt = nil
		}
	}
}

// @impl Close
func (f *Filter) Close() error {
	f.exit <- true
	return nil
}

//
// Builder builder unwanted event
//
type Builder struct {
	next   Piper
	events ChanEvent
	exit   ChanExit
}

func NewBuilder() (*Builder, error) {
	return &Builder{
		exit:   make(ChanExit, 1),
		events: make(ChanEvent, 16),
	}, nil
}

// @impl Piper.Handle
func (b *Builder) Handle(e *Event) {
	b.events <- e
}

// @impl Piper.Pipe
func (b *Builder) Pipe(next Piper) Piper {
	b.next = next
	go b.runBuild()
	return next
}

// runBuild run builder
func (b *Builder) runBuild() {
	var (
		lastTime time.Time
		lastEvt  *Event
	)
	ticker := time.NewTicker(time.Second)

	for {
		select {
		case <-b.exit:
			return

		case e := <-b.events:
			cmd := exec.Command("go", "build")
			cmd.Stderr, cmd.Stdout = os.Stderr, os.Stdout
			if err := cmd.Start(); err != nil {
				log.Fatal(err)
			}
			if err := cmd.Wait(); err != nil {
				log.Println("[umon]", "Builder: build err", err)
			} else {
				lastTime = time.Now()
				lastEvt = e
				log.Println("[umon]", "Builder: build ok")
			}

		case <-ticker.C:
			if lastEvt == nil {
				continue
			}
			if time.Now().Sub(lastTime) < time.Second {
				continue
			}
			b.next.Handle(lastEvt)
			lastEvt = nil
		}
	}
}

// @impl Close
func (b *Builder) Close() error {
	b.exit <- true
	return nil
}

//
// Runner runner unwanted event
//
type Runner struct {
	next   Piper
	events ChanEvent
	exit   ChanExit
}

func NewRunner() (*Runner, error) {
	return &Runner{
		exit:   make(ChanExit, 1),
		events: make(ChanEvent, 16),
	}, nil
}

// @impl Piper.Handle
func (b *Runner) Handle(e *Event) {
	b.events <- e
}

// @impl Piper.Pipe
func (b *Runner) Pipe(next Piper) Piper {
	b.next = next
	go b.runRun()
	return next
}

// runRun run runner
func (b *Runner) runRun() {
	// app name
	runDir := os.Getenv("PWD")
	if len(runDir) == 0 {
		panic("no PWD env")
	}
	appName := filepath.Join(runDir, filepath.Base(runDir))
	log.Println("[umon]", "Runner: app", appName)

	// notify run
	runC := make(chan bool, 1)
	runC <- true

	// cmd
	var cmd *exec.Cmd
	for {
		select {
		case <-b.exit:
			return

		case <-b.events:
			runC <- true

		case <-runC:
			// kill
			if cmd != nil {
				log.Println("[umon]", "Runner: kill", cmd.Process)
				if cmd.Process != nil {
					cmd.Process.Kill()
				}
				cmd = nil
				time.Sleep(200*time.Microsecond)
			}

			// exist
			if _, err := os.Stat(appName); err != nil {
				if os.IsNotExist(err) {
					log.Println("[umon]", "Runner: app not exist")
				}
				continue
			}

			// start
			cmd = exec.Command(appName)
			cmd.Stderr, cmd.Stdout = os.Stderr, os.Stdout
			if err := cmd.Start(); err != nil {
				log.Println("[umon]", "Runner: start err", err)
			} else {
				log.Println("[umon]", "Runner: start ok")
			}
		}
	}
}

// @impl Close
func (b *Runner) Close() error {
	b.exit <- true
	return nil
}

//
// Ender
//
type Ender struct {
	// empty
}

// NewEnder create ender instance
func NewEnder() (*Ender, error) {
	return &Ender{
	// empty
	}, nil
}

// @impl Piper.Pipe
func (ed *Ender) Pipe(next Piper) Piper {
	// no next
	return nil
}

// @impl Piper.Handle
func (ed *Ender) Handle(e *Event) {
	log.Println("[umon]", "Ender: handle", e)
}

// @impl Close
func (ed *Ender) Close() error {
	return nil
}

//
// umon ../app ../ctrl
//
func main() {
	// watcher
	watchDir := []string{"."}
	if len(os.Args) > 1 {
		watchDir = os.Args[1:]
	}
	watch, err := NewWatcher(watchDir)
	if err != nil {
		log.Fatal(err)
	}
	defer watch.Close()

	// filter
	filter, err := NewFilter()
	if err != nil {
		log.Fatal(err)
	}
	defer filter.Close()

	// builder
	builder, err := NewBuilder()
	if err != nil {
		log.Fatal(err)
	}
	defer builder.Close()

	// runner
	runner, err := NewRunner()
	if err != nil {
		log.Fatal(err)
	}
	defer runner.Close()

	// end
	end, err := NewEnder()
	if err != nil {
		log.Fatal(err)
	}
	defer end.Close()

	// pipe
	watch.Pipe(filter).Pipe(builder).Pipe(runner).Pipe(end)

	// wait
	exit := make(ChanExit)
	<-exit
}
<|endoftext|>


<commit_before>// Package rudia implements a simple library for relaying TCP string messages
// from one source to many clients.
package rudia

import (
	"bufio"
	"net"
	"sync"
	"time"

	log "github.com/sirupsen/logrus"
)

type upstream struct {
	readMessages chan []byte
	dead         chan bool
	conn         net.Conn
	reader       *bufio.Reader
	idleTimeout  time.Duration
}

func newUpstream(conn net.Conn, idleTimeout time.Duration) *upstream {
	r := bufio.NewReader(conn)
	u := &upstream{
		readMessages: make(chan []byte, 1000),
		dead:         make(chan bool),
		conn:         conn,
		reader:       r,
		idleTimeout:  idleTimeout,
	}

	go u.read()

	return u
}

func (u *upstream) read() {
	for {
		u.conn.SetReadDeadline(time.Now().Add(u.idleTimeout))
		line, err := u.reader.ReadBytes('\n')
		if err != nil {
			log.WithFields(log.Fields{
				"err":      err,
				"upstream": u.conn.RemoteAddr(),
			}).Warn("Failed to read from upstream")

			u.dead <- true
			break
		}

		u.readMessages <- line
	}
}

func (u *upstream) shutdown() {
	log.WithFields(log.Fields{
		"upstream": u.conn.RemoteAddr(),
	}).Info("Shutting down upstream")
	u.conn.Close()
}

type client struct {
	messagesToWrite chan []byte
	dead            chan bool
	conn            net.Conn
	writer          *bufio.Writer
	idleTimeout     time.Duration
}

func newClient(conn net.Conn) *client {
	w := bufio.NewWriter(conn)
	c := &client{
		messagesToWrite: make(chan []byte, 1000),
		dead:            make(chan bool),
		conn:            conn,
		writer:          w,
	}

	go c.write()

	return c
}

func (c *client) write() {
	for data := range c.messagesToWrite {
		_, err := c.writer.Write(data)
		if err != nil {
			log.WithFields(log.Fields{
				"err":    err,
				"client": c.conn.RemoteAddr(),
			}).Warn("Failed to write to client")
			c.dead <- true
			break
		}

		err = c.writer.Flush()
		if err != nil {
			log.WithFields(log.Fields{
				"err":    err,
				"client": c.conn.RemoteAddr(),
			}).Warn("Failed to flush")
			c.dead <- true
			break
		}
	}
}

func (c *client) shutdown() {
	log.WithFields(log.Fields{
		"client": c.conn.RemoteAddr(),
	}).Info("Shutting down client")
	c.conn.Close()
}

// RepeaterOptions provides configuration options for controlling the
// behavior of the Repeater.
type RepeaterOptions struct {
	RetryInterval               time.Duration
	UpstreamProxyIdleTimeout    time.Duration
	UpstreamListenerIdleTimeout time.Duration
}

// A Repeater connects to an upstream TCP endpoint and relays the messages
// it receives to all connected clients.
type Repeater struct {
	clientListener          net.Listener
	clientsLock             sync.RWMutex
	clients                 map[string]*client
	clientsDone             chan bool
	clientCleanupComplete   chan bool
	upstreamListener        net.Listener
	upstreamsLock           sync.RWMutex
	upstreams               map[string]*upstream
	upstreamsDone           chan bool
	upstreamCleanupComplete chan bool
	readMessages            chan []byte
	messagesToWrite         chan []byte
	options                 *RepeaterOptions
}

// NewRepeater creates a new Repeater and starts listening for client
// connections.
func NewRepeater(options *RepeaterOptions) *Repeater {
	r := &Repeater{
		clients:                 make(map[string]*client),
		upstreams:               make(map[string]*upstream),
		readMessages:            make(chan []byte, 1000),
		messagesToWrite:         make(chan []byte, 1000),
		options:                 options,
		clientsDone:             make(chan bool, 1),
		upstreamsDone:           make(chan bool, 1),
		clientCleanupComplete:   make(chan bool, 1),
		upstreamCleanupComplete: make(chan bool, 1),
	}
	r.listen()
	return r
}

// Proxy connects to the specified TCP address and relays all received
// messages to connected clients. If the connection to the specified
// address fails, an attempt will be made to reconnect. This will repeat
// until the program exits.
func (r *Repeater) Proxy(address string) {
	go func() {
		fault := make(chan bool)
		for {
			log.WithFields(log.Fields{
				"upstream": address,
			}).Info("Dialing upstream")

			conn, err := net.Dial("tcp", address)
			if err != nil {
				log.WithFields(log.Fields{
					"upstream": address,
					"err":      err,
				}).Error("Error dialing upstream")

				log.WithFields(log.Fields{
					"upstream": address,
					"sleep":    r.options.RetryInterval,
				}).Info("Sleeping before retrying upstream")

				time.Sleep(r.options.RetryInterval)
				continue
			}
			r.joinUpstream(conn, fault, false)
			_ = <-fault
		}
	}()
}

// Push connects to the specified TCP address and relays all received
// messages from upstreams to it. If the connection to the specified
// address fails, an attempt will be made to reconnect. This will repeat
// until the program exits.
func (r *Repeater) Push(address string) {
	go func() {
		fault := make(chan bool)
		for {
			log.WithFields(log.Fields{
				"client": address,
			}).Info("Dialing client")

			conn, err := net.Dial("tcp", address)
			if err != nil {
				log.WithFields(log.Fields{
					"client": address,
					"err":    err,
				}).Error("Error dialing client")

				log.WithFields(log.Fields{
					"client": address,
					"sleep":  r.options.RetryInterval,
				}).Info("Sleeping before retrying client")

				time.Sleep(r.options.RetryInterval)
				continue
			}
			r.joinClient(conn, fault)
			_ = <-fault
		}
	}()
}

// ListenAndAcceptClients listens to the specified adddress for new TCP clients
// and upon successful connect, adds them to the pool of clients to relay
// any messages received to.
func (r *Repeater) ListenAndAcceptClients(address string) {
	defer func() {
		r.clientsLock.Lock()
		for k, c := range r.clients {
			c.shutdown()
			delete(r.clients, k)
		}
		r.clientsLock.Unlock()

		r.clientCleanupComplete <- true
	}()

	l, err := net.Listen("tcp", address)

	if err != nil {
		log.WithFields(log.Fields{
			"address": address,
			"err":     err,
		}).Fatal("Unable to listen")
	}
	defer l.Close()
	r.clientListener = l

	log.WithFields(log.Fields{
		"address": r.clientListener.Addr(),
	}).Info("Listening for clients")

	for {
		fault := make(chan bool)
		conn, err := r.clientListener.Accept()
		if err != nil {
			select {
			case <-r.clientsDone:
				return
			default:
				log.WithFields(log.Fields{
					"err":     err,
					"address": r.clientListener.Addr(),
				}).Error("Unable to accept client")
				continue
			}
		}

		r.joinClient(conn, fault)
		go func() { _ = <-fault }()

	}
}

// ListenAndAcceptUpstreams listens to the specified adddress for new TCP
// upstreams and upon successful connect, adds them to the pool of upstreams
// to relay  any messages received from.
func (r *Repeater) ListenAndAcceptUpstreams(address string) {
	defer func() {
		r.upstreamsLock.Lock()
		for k, u := range r.upstreams {
			u.shutdown()
			delete(r.upstreams, k)
		}
		r.upstreamsLock.Unlock()

		r.upstreamCleanupComplete <- true
	}()

	l, err := net.Listen("tcp", address)

	if err != nil {
		log.WithFields(log.Fields{
			"address": address,
			"err":     err,
		}).Fatal("Unable to listen")
	}
	defer l.Close()
	r.upstreamListener = l

	log.WithFields(log.Fields{
		"address": r.upstreamListener.Addr(),
	}).Info("Listening for upstreams")

	for {
		fault := make(chan bool)
		conn, err := r.upstreamListener.Accept()
		if err != nil {
			select {
			case <-r.upstreamsDone:
				return
			default:
				log.WithFields(log.Fields{
					"err":     err,
					"address": r.upstreamListener.Addr(),
				}).Error("Unable to accept upstream")
				continue
			}
		}

		r.joinUpstream(conn, fault, true)
		go func() { _ = <-fault }()
	}
}

// Shutdown shuts down the repeater, stops taking new connections and
// closing existing connections
func (r *Repeater) Shutdown() {
	log.Info("Shutting down repeater")
	r.clientsDone <- true
	r.upstreamsDone <- true
	if r.clientListener != nil {
		r.clientListener.Close()
		<-r.clientCleanupComplete
	}
	if r.upstreamListener != nil {
		r.upstreamListener.Close()
		<-r.upstreamCleanupComplete
	}
}

func (r *Repeater) broadcast(data []byte) {
	r.clientsLock.RLock()
	defer r.clientsLock.RUnlock()
	for _, c := range r.clients {
		select {
		case c.messagesToWrite <- data:
		default:
		}
	}
}

func (r *Repeater) joinUpstream(conn net.Conn, fault chan bool, isListener bool) {
	log.WithFields(log.Fields{
		"upstream": conn.RemoteAddr(),
	}).Info("Creating upstream")

	var idleTimeout time.Duration
	if isListener {
		idleTimeout = r.options.UpstreamListenerIdleTimeout
	} else {
		idleTimeout = r.options.UpstreamProxyIdleTimeout
	}

	u := newUpstream(conn, idleTimeout)
	r.upstreamsLock.Lock()
	r.upstreams[conn.RemoteAddr().String()] = u
	r.upstreamsLock.Unlock()
	go func() {
		for {
			select {
			case message := <-u.readMessages:
				r.readMessages <- message
			case _ = <-u.dead:
				log.WithFields(log.Fields{
					"upstream": u.conn.RemoteAddr(),
				}).Warn("Dead upstream")

				u.shutdown()

				deadRemote := u.conn.RemoteAddr().String()
				r.upstreamsLock.Lock()
				delete(r.upstreams, deadRemote)
				r.upstreamsLock.Unlock()
				fault <- true
			}
		}
	}()
}

func (r *Repeater) joinClient(conn net.Conn, fault chan bool) {
	log.WithFields(log.Fields{
		"client": conn.RemoteAddr(),
	}).Info("Creating client")

	c := newClient(conn)
	r.clientsLock.Lock()
	r.clients[conn.RemoteAddr().String()] = c
	r.clientsLock.Unlock()
	go func() {
		for {
			select {
			case _ = <-c.dead:
				deadRemote := c.conn.RemoteAddr().String()
				r.clientsLock.Lock()
				r.clients[deadRemote].shutdown()
				delete(r.clients, deadRemote)
				r.clientsLock.Unlock()
				fault <- true
			}
		}
	}()
}

func (r *Repeater) listen() {
	go func() {
		for {
			select {
			case data := <-r.readMessages:
				r.broadcast(data)
			}
		}
	}()
}
<commit_msg>Add function to stop proxy or push directly<commit_after>// Package rudia implements a simple library for relaying TCP string messages
// from one source to many clients.
package rudia

import (
	"bufio"
	"net"
	"sync"
	"time"

	log "github.com/sirupsen/logrus"
)

type upstream struct {
	readMessages chan []byte
	dead         chan bool
	conn         net.Conn
	reader       *bufio.Reader
	idleTimeout  time.Duration
}

func newUpstream(conn net.Conn, idleTimeout time.Duration) *upstream {
	r := bufio.NewReader(conn)
	u := &upstream{
		readMessages: make(chan []byte, 1000),
		dead:         make(chan bool),
		conn:         conn,
		reader:       r,
		idleTimeout:  idleTimeout,
	}

	go u.read()

	return u
}

func (u *upstream) read() {
	for {
		u.conn.SetReadDeadline(time.Now().Add(u.idleTimeout))
		line, err := u.reader.ReadBytes('\n')
		if err != nil {
			log.WithFields(log.Fields{
				"err":      err,
				"upstream": u.conn.RemoteAddr(),
			}).Warn("Failed to read from upstream")

			u.dead <- true
			break
		}

		u.readMessages <- line
	}
}

func (u *upstream) shutdown() {
	log.WithFields(log.Fields{
		"upstream": u.conn.RemoteAddr(),
	}).Info("Shutting down upstream")
	u.conn.Close()
}

type client struct {
	messagesToWrite chan []byte
	dead            chan bool
	conn            net.Conn
	writer          *bufio.Writer
	idleTimeout     time.Duration
}

func newClient(conn net.Conn) *client {
	w := bufio.NewWriter(conn)
	c := &client{
		messagesToWrite: make(chan []byte, 1000),
		dead:            make(chan bool),
		conn:            conn,
		writer:          w,
	}

	go c.write()

	return c
}

func (c *client) write() {
	for data := range c.messagesToWrite {
		_, err := c.writer.Write(data)
		if err != nil {
			log.WithFields(log.Fields{
				"err":    err,
				"client": c.conn.RemoteAddr(),
			}).Warn("Failed to write to client")
			c.dead <- true
			break
		}

		err = c.writer.Flush()
		if err != nil {
			log.WithFields(log.Fields{
				"err":    err,
				"client": c.conn.RemoteAddr(),
			}).Warn("Failed to flush")
			c.dead <- true
			break
		}
	}
}

func (c *client) shutdown() {
	log.WithFields(log.Fields{
		"client": c.conn.RemoteAddr(),
	}).Info("Shutting down client")
	c.conn.Close()
}

// RepeaterOptions provides configuration options for controlling the
// behavior of the Repeater.
type RepeaterOptions struct {
	RetryInterval               time.Duration
	UpstreamProxyIdleTimeout    time.Duration
	UpstreamListenerIdleTimeout time.Duration
}

// A Repeater connects to an upstream TCP endpoint and relays the messages
// it receives to all connected clients.
type Repeater struct {
	clientListener          net.Listener
	clientsLock             sync.RWMutex
	clients                 map[string]*client
	clientsDone             chan bool
	clientCleanupComplete   chan bool
	upstreamListener        net.Listener
	upstreamsLock           sync.RWMutex
	upstreams               map[string]*upstream
	upstreamsDone           chan bool
	upstreamCleanupComplete chan bool
	readMessages            chan []byte
	messagesToWrite         chan []byte
	options                 *RepeaterOptions
	stopChannelsLock        sync.RWMutex
	stopChannels            map[string]chan bool
}

// NewRepeater creates a new Repeater and starts listening for client
// connections.
func NewRepeater(options *RepeaterOptions) *Repeater {
	r := &Repeater{
		clients:                 make(map[string]*client),
		upstreams:               make(map[string]*upstream),
		readMessages:            make(chan []byte, 1000),
		messagesToWrite:         make(chan []byte, 1000),
		options:                 options,
		clientsDone:             make(chan bool, 1),
		upstreamsDone:           make(chan bool, 1),
		clientCleanupComplete:   make(chan bool, 1),
		upstreamCleanupComplete: make(chan bool, 1),
		stopChannels:            make(map[string]chan bool),
	}
	r.listen()
	return r
}

// Proxy connects to the specified TCP address and relays all received
// messages to connected clients. If the connection to the specified
// address fails, an attempt will be made to reconnect. This will repeat
// until the program exits.
func (r *Repeater) Proxy(address string) {
	go func() {
		fault := make(chan bool)
		stop := make(chan bool)

		r.stopChannelsLock.Lock()
		r.stopChannels[address] = stop
		r.stopChannelsLock.Unlock()

		for {
			log.WithFields(log.Fields{
				"upstream": address,
			}).Info("Dialing upstream")

			conn, err := net.Dial("tcp", address)
			if err != nil {
				log.WithFields(log.Fields{
					"upstream": address,
					"err":      err,
				}).Error("Error dialing upstream")

				log.WithFields(log.Fields{
					"upstream": address,
					"sleep":    r.options.RetryInterval,
				}).Info("Sleeping before retrying upstream")

				time.Sleep(r.options.RetryInterval)
				continue
			}
			r.joinUpstream(conn, fault, false)
			select {
			case <-fault:
				continue
			case <-stop:
				r.upstreamsLock.Lock()
				u := r.upstreams[conn.RemoteAddr().String()]
				r.upstreamsLock.Unlock()
				u.dead <- true
				return
			}
		}
	}()
}

// Push connects to the specified TCP address and relays all received
// messages from upstreams to it. If the connection to the specified
// address fails, an attempt will be made to reconnect. This will repeat
// until the program exits.
func (r *Repeater) Push(address string) {
	go func() {
		fault := make(chan bool)
		stop := make(chan bool)

		r.stopChannelsLock.Lock()
		r.stopChannels[address] = stop
		r.stopChannelsLock.Unlock()

		for {
			log.WithFields(log.Fields{
				"client": address,
			}).Info("Dialing client")

			conn, err := net.Dial("tcp", address)
			if err != nil {
				log.WithFields(log.Fields{
					"client": address,
					"err":    err,
				}).Error("Error dialing client")

				log.WithFields(log.Fields{
					"client": address,
					"sleep":  r.options.RetryInterval,
				}).Info("Sleeping before retrying client")

				time.Sleep(r.options.RetryInterval)
				continue
			}
			r.joinClient(conn, fault)
			select {
			case <-fault:
				continue
			case <-stop:
				r.clientsLock.Lock()
				c := r.clients[conn.RemoteAddr().String()]
				r.clientsLock.Unlock()
				c.dead <- true
				return
			}
		}
	}()
}

// Stop terminates the proxy or push for the specified address.
func (r *Repeater) Stop(address string) {
	r.stopChannelsLock.Lock()
	r.stopChannels[address] <- true
	delete(r.stopChannels, address)
	r.stopChannelsLock.Unlock()
}

// ListenAndAcceptClients listens to the specified adddress for new TCP clients
// and upon successful connect, adds them to the pool of clients to relay
// any messages received to.
func (r *Repeater) ListenAndAcceptClients(address string) {
	defer func() {
		r.clientsLock.Lock()
		for k, c := range r.clients {
			c.shutdown()
			delete(r.clients, k)
		}
		r.clientsLock.Unlock()

		r.clientCleanupComplete <- true
	}()

	l, err := net.Listen("tcp", address)

	if err != nil {
		log.WithFields(log.Fields{
			"address": address,
			"err":     err,
		}).Fatal("Unable to listen")
	}
	defer l.Close()
	r.clientListener = l

	log.WithFields(log.Fields{
		"address": r.clientListener.Addr(),
	}).Info("Listening for clients")

	for {
		fault := make(chan bool)
		conn, err := r.clientListener.Accept()
		if err != nil {
			select {
			case <-r.clientsDone:
				return
			default:
				log.WithFields(log.Fields{
					"err":     err,
					"address": r.clientListener.Addr(),
				}).Error("Unable to accept client")
				continue
			}
		}

		r.joinClient(conn, fault)
		go func() { _ = <-fault }()
	}
}

// ListenAndAcceptUpstreams listens to the specified adddress for new TCP
// upstreams and upon successful connect, adds them to the pool of upstreams
// to relay any messages received from.
func (r *Repeater) ListenAndAcceptUpstreams(address string) {
	defer func() {
		r.upstreamsLock.Lock()
		for k, u := range r.upstreams {
			u.shutdown()
			delete(r.upstreams, k)
		}
		r.upstreamsLock.Unlock()

		r.upstreamCleanupComplete <- true
	}()

	l, err := net.Listen("tcp", address)

	if err != nil {
		log.WithFields(log.Fields{
			"address": address,
			"err":     err,
		}).Fatal("Unable to listen")
	}
	defer l.Close()
	r.upstreamListener = l

	log.WithFields(log.Fields{
		"address": r.upstreamListener.Addr(),
	}).Info("Listening for upstreams")

	for {
		fault := make(chan bool)
		conn, err := r.upstreamListener.Accept()
		if err != nil {
			select {
			case <-r.upstreamsDone:
				return
			default:
				log.WithFields(log.Fields{
					"err":     err,
					"address": r.upstreamListener.Addr(),
				}).Error("Unable to accept upstream")
				continue
			}
		}

		r.joinUpstream(conn, fault, true)
		go func() { _ = <-fault }()
	}
}

// Shutdown shuts down the repeater, stops taking new connections and
// closing existing connections
func (r *Repeater) Shutdown() {
	log.Info("Shutting down repeater")
	r.clientsDone <- true
	r.upstreamsDone <- true
	if r.clientListener != nil {
		r.clientListener.Close()
		<-r.clientCleanupComplete
	}
	if r.upstreamListener != nil {
		r.upstreamListener.Close()
		<-r.upstreamCleanupComplete
	}
}

func (r *Repeater) broadcast(data []byte) {
	r.clientsLock.RLock()
	defer r.clientsLock.RUnlock()
	for _, c := range r.clients {
		select {
		case c.messagesToWrite <- data:
		default:
		}
	}
}

func (r *Repeater) joinUpstream(conn net.Conn, fault chan bool, isListener bool) {
	log.WithFields(log.Fields{
		"upstream": conn.RemoteAddr(),
	}).Info("Creating upstream")

	var idleTimeout time.Duration
	if isListener {
		idleTimeout = r.options.UpstreamListenerIdleTimeout
	} else {
		idleTimeout = r.options.UpstreamProxyIdleTimeout
	}

	u := newUpstream(conn, idleTimeout)
	r.upstreamsLock.Lock()
	r.upstreams[conn.RemoteAddr().String()] = u
	r.upstreamsLock.Unlock()
	go func() {
		for {
			select {
			case message := <-u.readMessages:
				r.readMessages <- message
			case _ = <-u.dead:
				log.WithFields(log.Fields{
					"upstream": u.conn.RemoteAddr(),
				}).Warn("Dead upstream")

				u.shutdown()

				deadRemote := u.conn.RemoteAddr().String()
				r.upstreamsLock.Lock()
				delete(r.upstreams, deadRemote)
				r.upstreamsLock.Unlock()
				fault <- true
			}
		}
	}()
}

func (r *Repeater) joinClient(conn net.Conn, fault chan bool) {
	log.WithFields(log.Fields{
		"client": conn.RemoteAddr(),
	}).Info("Creating client")

	c := newClient(conn)
	r.clientsLock.Lock()
	r.clients[conn.RemoteAddr().String()] = c
	r.clientsLock.Unlock()
	go func() {
		for {
			select {
			case _ = <-c.dead:
				deadRemote := c.conn.RemoteAddr().String()
				r.clientsLock.Lock()
				r.clients[deadRemote].shutdown()
				delete(r.clients, deadRemote)
				r.clientsLock.Unlock()
				fault <- true
			}
		}
	}()
}

func (r *Repeater) listen() {
	go func() {
		for {
			select {
			case data := <-r.readMessages:
				r.broadcast(data)
			}
		}
	}()
}
<|endoftext|>


<commit_before>package gunfish

import (
	"os"
	"time"
)

// Stats stores metrics
type Stats struct {
	Pid                    int       `json:"pid"`
	DebugPort              int       `json:"debug_port"`
	Uptime                 int64     `json:"uptime"`
	StartAt                int64     `json:"start_at"`
	ServiceUnavailableAt   int64     `json:"su_at"`
	Period                 int64     `json:"period"`
	RetryAfter             int64     `json:"retry_after"`
	Workers                int64     `json:"workers"`
	Senders                int64     `json:"senders"`
	QueueSize              int64     `json:"queue_size"`
	RetryQueueSize         int64     `json:"retry_queue_size"`
	WorkersQueueSize       int64     `json:"workers_queue_size"`
	CommandQueueSize       int64     `json:"cmdq_queue_size"`
	RetryCount             int64     `json:"retry_count"`
	RequestCount           int64     `json:"req_count"`
	SentCount              int64     `json:"sent_count"`
	ErrCount               int64     `json:"err_count"`
	CertificateNotAfter    time.Time `json:"certificate_not_after"`
	CertificateExpireUntil int64     `json:"certificate_expire_until"`
}

// NewStats initialize Stats
func NewStats(conf Config) Stats {
	return Stats{
		Pid:                 os.Getpid(),
		StartAt:             time.Now().Unix(),
		RetryAfter:          int64(RetryAfterSecond / time.Second),
		CertificateNotAfter: conf.Apns.CertificateNotAfter,
	}
}

// GetStats returns MemdStats of app
func (st *Stats) GetStats() *Stats {
	preUptime := st.Uptime
	st.Uptime = time.Now().Unix() - st.StartAt
	st.Period = st.Uptime - preUptime
	st.CertificateExpireUntil = int64(st.CertificateNotAfter.Sub(time.Now()).Seconds())
	return st
}
<commit_msg>Remove Senders in stat.<commit_after>package gunfish

import (
	"os"
	"time"
)

// Stats stores metrics
type Stats struct {
	Pid                    int       `json:"pid"`
	DebugPort              int       `json:"debug_port"`
	Uptime                 int64     `json:"uptime"`
	StartAt                int64     `json:"start_at"`
	ServiceUnavailableAt   int64     `json:"su_at"`
	Period                 int64     `json:"period"`
	RetryAfter             int64     `json:"retry_after"`
	Workers                int64     `json:"workers"`
	QueueSize              int64     `json:"queue_size"`
	RetryQueueSize         int64     `json:"retry_queue_size"`
	WorkersQueueSize       int64     `json:"workers_queue_size"`
	CommandQueueSize       int64     `json:"cmdq_queue_size"`
	RetryCount             int64     `json:"retry_count"`
	RequestCount           int64     `json:"req_count"`
	SentCount              int64     `json:"sent_count"`
	ErrCount               int64     `json:"err_count"`
	CertificateNotAfter    time.Time `json:"certificate_not_after"`
	CertificateExpireUntil int64     `json:"certificate_expire_until"`
}

// NewStats initialize Stats
func NewStats(conf Config) Stats {
	return Stats{
		Pid:                 os.Getpid(),
		StartAt:             time.Now().Unix(),
		RetryAfter:          int64(RetryAfterSecond / time.Second),
		CertificateNotAfter: conf.Apns.CertificateNotAfter,
	}
}

// GetStats returns MemdStats of app
func (st *Stats) GetStats() *Stats {
	preUptime := st.Uptime
	st.Uptime = time.Now().Unix() - st.StartAt
	st.Period = st.Uptime - preUptime
	st.CertificateExpireUntil = int64(st.CertificateNotAfter.Sub(time.Now()).Seconds())
	return st
}
<|endoftext|>


<commit_before>package goinsta

type User struct {
	 Json objects and user data
  Username                   string `json:"username"`
  Biography                  string `json:"biography"`
  HasAnonymousProfilePicture bool   `json:"has_anonymouse_profile_picture"`
  ProfilePictureID           string `json:"profile_pic_id"`
  ProfilePictureURL          string `json:"profile_pic_url"`
  FullName                   string `json:"full_name"`
  ID                         int64  `json:"pk"`
  IDStr                      string `json:"-"`
  IsVerified                 bool   `json:"is_verified"`
  IsPrivate                  bool   `json:"is_private"`
  IsFavorite                 bool   `json:"is_favorite"`
  IsUnpublished              bool   `json:"is_unpublished"`
  IsBusiness                 bool   `json:"is_business"`
  ExternalLynxURL            string `json:"external_lynx_url"`
  MediaCount                 int    `json:"media_count"`
  AutoExpandChaining         bool   `json:"auto_expand_chaining"`
  FollowingCount             int    `json:"following_count"`
  FollowerCount              int    `json:"follower_count"`
  ExternalURL                string `json:"external_url"`
  HdProfilePicVersions       []struct {
    Height int    `json:"height"`
    Width  int    `json:"width"`
    URL    string `json:"url"`
  } `json:"hd_profile_pic_versions"`
  UserTagsCount       int `json:"usertags_count"`
  HdProfilePicURLInfo struct {
    Height int    `json:"height"`
    Width  int    `json:"width"`
    URL    string `json:"url"`
  } `json:"hd_profile_pic_url_info"`
  GeoMediaCount int  `json:"geo_media_count"`
  HasChaining   bool `json:"has_chaining"`
}
<commit_msg>Updated user structure<commit_after>package goinsta

type User struct {
	ID                         int64  `json:"pk"`
	Username                   string `json:"username"`
	FullName                   string `json:"full_name"`
	HasAnonymousProfilePicture bool   `json:"has_anonymous_profile_picture"`
	IsPrivate                  bool   `json:"is_private"`
	IsVerified                 bool   `json:"is_verified"`
	ProfilePicURL              string `json:"profile_pic_url"`
	ProfilePicID               string `json:"profile_pic_id"`
	MediaCount                 int    `json:"media_count"`
	FollowerCount              int    `json:"follower_count"`
	FollowingCount             int    `json:"following_count"`
	GeoMediaCount              int    `json:"geo_media_count"`
	IsBusiness                 bool   `json:"is_business"`
	Biography                  string `json:"biography"`
	ExternalURL                string `json:"external_url"`
	HasBiographyTranslation    bool   `json:"has_biography_translation"`
	ExternalLynxURL            string `json:"external_lynx_url"`
	HdProfilePicURLInfo        struct {
		Height int    `json:"height"`
		URL    string `json:"url"`
		Width  int    `json:"width"`
	} `json:"hd_profile_pic_url_info"`
	HdProfilePicVersions []struct {
		Height int    `json:"height"`
		URL    string `json:"url"`
		Width  int    `json:"width"`
	} `json:"hd_profile_pic_versions"`
	UsertagsCount   int    `json:"usertags_count"`
	HasChaining     bool   `json:"has_chaining"`
	IsFavorite      bool   `json:"is_favorite"`
	ReelAutoArchive string `json:"reel_auto_archive"`
	School          struct {
	} `json:"school"`
	PublicEmail            string  `json:"public_email"`
	PublicPhoneNumber      string  `json:"public_phone_number"`
	PublicPhoneCountryCode string  `json:"public_phone_country_code"`
	ContactPhoneNumber     string  `json:"contact_phone_number"`
	CityID                 int     `json:"city_id"`
	CityName               string  `json:"city_name"`
	AddressStreet          string  `json:"address_street"`
	DirectMessaging        string  `json:"direct_messaging"`
	Latitude               float64 `json:"latitude"`
	Longitude              float64 `json:"longitude"`
	Category               string  `json:"category"`
	BusinessContactMethod  string  `json:"business_contact_method"`
	IsCallToActionEnabled  bool    `json:"is_call_to_action_enabled"`
	FbPageCallToActionID   string  `json:"fb_page_call_to_action_id"`
	Zip                    string  `json:"zip"`
}
<|endoftext|>


<commit_before>package irckit

import (
	"net"
	"strings"
	"sync"

	"github.com/sorcix/irc"
)

// NewUser creates a *User, wrapping a connection with metadata we need for our server.
func NewUser(c Conn) *User {
	return &User{
		Conn:     c,
		Host:     "*",
		channels: map[Channel]struct{}{},
	}
}

// NewUserNet creates a *User from a net.Conn connection.
func NewUserNet(c net.Conn) *User {
	return NewUser(&conn{
		Conn:    c,
		Encoder: irc.NewEncoder(c),
		Decoder: irc.NewDecoder(c),
	})
}

const defaultCloseMsg = "Closed."

type User struct {
	Conn

	sync.RWMutex
	Nick  string // From NICK command
	User  string // From USER command
	Real  string // From USER command
	Host  string
	Roles string

	channels map[Channel]struct{}

	MmInfo
}

func (u *User) ID() string {
	return strings.ToLower(u.Nick)
}

func (u *User) Prefix() *irc.Prefix {
	return &irc.Prefix{
		Name: u.Nick,
		User: u.User,
		Host: u.Host,
	}
}

func (u *User) Close() error {
	for ch := range u.channels {
		ch.Part(u, defaultCloseMsg)
	}
	return u.Conn.Close()
}

func (u *User) String() string {
	return u.Prefix().String()
}

func (u *User) NumChannels() int {
	u.RLock()
	defer u.RUnlock()
	return len(u.channels)
}

func (u *User) Channels() []Channel {
	u.RLock()
	channels := make([]Channel, 0, len(u.channels))
	for ch := range u.channels {
		channels = append(channels, ch)
	}
	u.RUnlock()
	return channels
}

func (u *User) VisibleTo() []*User {
	seen := map[*User]struct{}{}
	seen[u] = struct{}{}

	channels := u.Channels()
	num := 0
	for _, ch := range channels {
		// Don't include self
		num += ch.Len()
	}

	// Pre-allocate
	users := make([]*User, 0, num)
	if num == 0 {
		return users
	}

	// Get all unique users
	for _, ch := range channels {
		for _, other := range ch.Users() {
			if _, dupe := seen[other]; dupe {
				continue
			}
			seen[other] = struct{}{}
			// TODO: Check visibility (once it's implemented)
			users = append(users, other)
		}
	}
	return users
}

// Encode and send each msg until an error occurs, then returns.
func (user *User) Encode(msgs ...*irc.Message) (err error) {
	if user.MmGhostUser {
		return nil
	}
	for _, msg := range msgs {
		logger.Debugf("-> %s", msg)
		err := user.Conn.Encode(msg)
		if err != nil {
			return err
		}
	}
	return nil
}

// Decode will receive and return a decoded message, or an error.
func (user *User) Decode() (*irc.Message, error) {
	if user.MmGhostUser {
		// block
		c := make(chan struct{})
		<-c
	}
	msg, err := user.Conn.Decode()
	if err == nil && msg != nil {
		logger.Debugf("<- %s", msg)
	}
	return msg, err
}
<commit_msg>Add slackinfo to user<commit_after>package irckit

import (
	"net"
	"strings"
	"sync"

	"github.com/sorcix/irc"
)

// NewUser creates a *User, wrapping a connection with metadata we need for our server.
func NewUser(c Conn) *User {
	return &User{
		Conn:     c,
		Host:     "*",
		channels: map[Channel]struct{}{},
	}
}

// NewUserNet creates a *User from a net.Conn connection.
func NewUserNet(c net.Conn) *User {
	return NewUser(&conn{
		Conn:    c,
		Encoder: irc.NewEncoder(c),
		Decoder: irc.NewDecoder(c),
	})
}

const defaultCloseMsg = "Closed."

type User struct {
	Conn

	sync.RWMutex
	Nick  string // From NICK command
	User  string // From USER command
	Real  string // From USER command
	Host  string
	Roles string

	channels map[Channel]struct{}

	MmInfo
	SlackInfo
}

func (u *User) ID() string {
	return strings.ToLower(u.Nick)
}

func (u *User) Prefix() *irc.Prefix {
	return &irc.Prefix{
		Name: u.Nick,
		User: u.User,
		Host: u.Host,
	}
}

func (u *User) Close() error {
	for ch := range u.channels {
		ch.Part(u, defaultCloseMsg)
	}
	return u.Conn.Close()
}

func (u *User) String() string {
	return u.Prefix().String()
}

func (u *User) NumChannels() int {
	u.RLock()
	defer u.RUnlock()
	return len(u.channels)
}

func (u *User) Channels() []Channel {
	u.RLock()
	channels := make([]Channel, 0, len(u.channels))
	for ch := range u.channels {
		channels = append(channels, ch)
	}
	u.RUnlock()
	return channels
}

func (u *User) VisibleTo() []*User {
	seen := map[*User]struct{}{}
	seen[u] = struct{}{}

	channels := u.Channels()
	num := 0
	for _, ch := range channels {
		// Don't include self
		num += ch.Len()
	}

	// Pre-allocate
	users := make([]*User, 0, num)
	if num == 0 {
		return users
	}

	// Get all unique users
	for _, ch := range channels {
		for _, other := range ch.Users() {
			if _, dupe := seen[other]; dupe {
				continue
			}
			seen[other] = struct{}{}
			// TODO: Check visibility (once it's implemented)
			users = append(users, other)
		}
	}
	return users
}

// Encode and send each msg until an error occurs, then returns.
func (user *User) Encode(msgs ...*irc.Message) (err error) {
	if user.MmGhostUser {
		return nil
	}
	for _, msg := range msgs {
		logger.Debugf("-> %s", msg)
		err := user.Conn.Encode(msg)
		if err != nil {
			return err
		}
	}
	return nil
}

// Decode will receive and return a decoded message, or an error.
func (user *User) Decode() (*irc.Message, error) {
	if user.MmGhostUser {
		// block
		c := make(chan struct{})
		<-c
	}
	msg, err := user.Conn.Decode()
	if err == nil && msg != nil {
		logger.Debugf("<- %s", msg)
	}
	return msg, err
}
<|endoftext|>


<commit_before>package aws

import (
	"fmt"
	"math/rand"
	"testing"
	"time"

	"github.com/awslabs/aws-sdk-go/aws"
	"github.com/awslabs/aws-sdk-go/service/elasticache"
	"github.com/hashicorp/terraform/helper/resource"
	"github.com/hashicorp/terraform/terraform"
)

func TestAccAWSElasticacheCluster(t *testing.T) {
	resource.Test(t, resource.TestCase{
		PreCheck:     func() { testAccPreCheck(t) },
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckAWSElasticacheClusterDestroy,
		Steps: []resource.TestStep{
			resource.TestStep{
				Config: testAccAWSElasticacheClusterConfig,
				Check: resource.ComposeTestCheckFunc(
					testAccCheckAWSElasticacheSecurityGroupExists("aws_elasticache_security_group.bar"),
					testAccCheckAWSElasticacheClusterExists("aws_elasticache_cluster.bar"),
					resource.TestCheckResourceAttr(
						"aws_elasticache_cluster.bar", "cache_nodes.0.id", "001"),
				),
			},
		},
	})
}

func TestAccAWSElasticacheCluster_vpc(t *testing.T) {
	resource.Test(t, resource.TestCase{
		PreCheck:     func() { testAccPreCheck(t) },
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckAWSElasticacheClusterDestroy,
		Steps: []resource.TestStep{
			resource.TestStep{
				Config: testAccAWSElasticacheClusterInVPCConfig,
				Check: resource.ComposeTestCheckFunc(
					testAccCheckAWSElasticacheSubnetGroupExists("aws_elasticache_subnet_group.bar"),
					testAccCheckAWSElasticacheClusterExists("aws_elasticache_cluster.bar"),
				),
			},
		},
	})
}

func testAccCheckAWSElasticacheClusterDestroy(s *terraform.State) error {
	conn := testAccProvider.Meta().(*AWSClient).elasticacheconn

	for _, rs := range s.RootModule().Resources {
		if rs.Type != "aws_elasticache_cluster" {
			continue
		}
		res, err := conn.DescribeCacheClusters(&elasticache.DescribeCacheClustersInput{
			CacheClusterID: aws.String(rs.Primary.ID),
		})
		if err != nil {
			return err
		}
		if len(res.CacheClusters) > 0 {
			return fmt.Errorf("still exist.")
		}
	}
	return nil
}

func testAccCheckAWSElasticacheClusterExists(n string) resource.TestCheckFunc {
	return func(s *terraform.State) error {
		rs, ok := s.RootModule().Resources[n]
		if !ok {
			return fmt.Errorf("Not found: %s", n)
		}

		if rs.Primary.ID == "" {
			return fmt.Errorf("No cache cluster ID is set")
		}

		conn := testAccProvider.Meta().(*AWSClient).elasticacheconn
		_, err := conn.DescribeCacheClusters(&elasticache.DescribeCacheClustersInput{
			CacheClusterID: aws.String(rs.Primary.ID),
		})
		if err != nil {
			return fmt.Errorf("Elasticache error: %v", err)
		}
		return nil
	}
}

func genRandInt() int {
	return rand.New(rand.NewSource(time.Now().UnixNano())).Int() % 1000
}

var testAccAWSElasticacheClusterConfig = fmt.Sprintf(`
resource "aws_security_group" "bar" {
    name = "tf-test-security-group-%03d"
    description = "tf-test-security-group-descr"
    ingress {
        from_port = -1
        to_port = -1
        protocol = "icmp"
        cidr_blocks = ["0.0.0.0/0"]
    }
}

resource "aws_elasticache_security_group" "bar" {
    name = "tf-test-security-group-%03d"
    description = "tf-test-security-group-descr"
    security_group_names = ["${aws_security_group.bar.name}"]
}

resource "aws_elasticache_cluster" "bar" {
    cluster_id = "tf-test-%03d"
    engine = "memcached"
    node_type = "cache.m1.small"
    num_cache_nodes = 1
    parameter_group_name = "default.memcached1.4"
    security_group_names = ["${aws_elasticache_security_group.bar.name}"]
}
`, genRandInt(), genRandInt(), genRandInt())

var testAccAWSElasticacheClusterInVPCConfig = fmt.Sprintf(`
resource "aws_vpc" "foo" {
    cidr_block = "192.168.0.0/16"
    tags {
            Name = "tf-test"
    }
}

resource "aws_subnet" "foo" {
    vpc_id = "${aws_vpc.foo.id}"
    cidr_block = "192.168.0.0/20"
    availability_zone = "us-west-2a"
    tags {
            Name = "tf-test"
    }
}

resource "aws_elasticache_subnet_group" "bar" {
    name = "tf-test-cache-subnet-%03d"
    description = "tf-test-cache-subnet-group-descr"
    subnet_ids = ["${aws_subnet.foo.id}"]
}

resource "aws_security_group" "bar" {
    name = "tf-test-security-group-%03d"
    description = "tf-test-security-group-descr"
    vpc_id = "${aws_vpc.foo.id}"
    ingress {
        from_port = -1
        to_port = -1
        protocol = "icmp"
        cidr_blocks = ["0.0.0.0/0"]
    }
}

resource "aws_elasticache_cluster" "bar" {
    cluster_id = "tf-test-%03d"
    node_type = "cache.m1.small"
    num_cache_nodes = 1
    engine = "redis"
    engine_version = "2.8.19"
    port = 6379
    subnet_group_name = "${aws_elasticache_subnet_group.bar.name}"
    security_group_ids = ["${aws_security_group.bar.id}"]
    parameter_group_name = "default.redis2.8"
}
`, genRandInt(), genRandInt(), genRandInt())
<commit_msg>provider/aws: ElastiCache test updates<commit_after>package aws

import (
	"fmt"
	"math/rand"
	"testing"
	"time"

	"github.com/awslabs/aws-sdk-go/aws"
	"github.com/awslabs/aws-sdk-go/service/elasticache"
	"github.com/hashicorp/terraform/helper/resource"
	"github.com/hashicorp/terraform/terraform"
)

func TestAccAWSElasticacheCluster_basic(t *testing.T) {
	resource.Test(t, resource.TestCase{
		PreCheck:     func() { testAccPreCheck(t) },
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckAWSElasticacheClusterDestroy,
		Steps: []resource.TestStep{
			resource.TestStep{
				Config: testAccAWSElasticacheClusterConfig,
				Check: resource.ComposeTestCheckFunc(
					testAccCheckAWSElasticacheSecurityGroupExists("aws_elasticache_security_group.bar"),
					testAccCheckAWSElasticacheClusterExists("aws_elasticache_cluster.bar"),
					resource.TestCheckResourceAttr(
						"aws_elasticache_cluster.bar", "cache_nodes.0.id", "0001"),
				),
			},
		},
	})
}

func TestAccAWSElasticacheCluster_vpc(t *testing.T) {
	resource.Test(t, resource.TestCase{
		PreCheck:     func() { testAccPreCheck(t) },
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckAWSElasticacheClusterDestroy,
		Steps: []resource.TestStep{
			resource.TestStep{
				Config: testAccAWSElasticacheClusterInVPCConfig,
				Check: resource.ComposeTestCheckFunc(
					testAccCheckAWSElasticacheSubnetGroupExists("aws_elasticache_subnet_group.bar"),
					testAccCheckAWSElasticacheClusterExists("aws_elasticache_cluster.bar"),
				),
			},
		},
	})
}

func testAccCheckAWSElasticacheClusterDestroy(s *terraform.State) error {
	conn := testAccProvider.Meta().(*AWSClient).elasticacheconn

	for _, rs := range s.RootModule().Resources {
		if rs.Type != "aws_elasticache_cluster" {
			continue
		}
		res, err := conn.DescribeCacheClusters(&elasticache.DescribeCacheClustersInput{
			CacheClusterID: aws.String(rs.Primary.ID),
		})
		if err != nil {
			return err
		}
		if len(res.CacheClusters) > 0 {
			return fmt.Errorf("still exist.")
		}
	}
	return nil
}

func testAccCheckAWSElasticacheClusterExists(n string) resource.TestCheckFunc {
	return func(s *terraform.State) error {
		rs, ok := s.RootModule().Resources[n]
		if !ok {
			return fmt.Errorf("Not found: %s", n)
		}

		if rs.Primary.ID == "" {
			return fmt.Errorf("No cache cluster ID is set")
		}

		conn := testAccProvider.Meta().(*AWSClient).elasticacheconn
		_, err := conn.DescribeCacheClusters(&elasticache.DescribeCacheClustersInput{
			CacheClusterID: aws.String(rs.Primary.ID),
		})
		if err != nil {
			return fmt.Errorf("Elasticache error: %v", err)
		}
		return nil
	}
}

func genRandInt() int {
	return rand.New(rand.NewSource(time.Now().UnixNano())).Int() % 1000
}

var testAccAWSElasticacheClusterConfig = fmt.Sprintf(`
provider "aws" {
	region = "us-east-1"
}
resource "aws_security_group" "bar" {
    name = "tf-test-security-group-%03d"
    description = "tf-test-security-group-descr"
    ingress {
        from_port = -1
        to_port = -1
        protocol = "icmp"
        cidr_blocks = ["0.0.0.0/0"]
    }
}

resource "aws_elasticache_security_group" "bar" {
    name = "tf-test-security-group-%03d"
    description = "tf-test-security-group-descr"
    security_group_names = ["${aws_security_group.bar.name}"]
}

resource "aws_elasticache_cluster" "bar" {
    cluster_id = "tf-test-%03d"
    engine = "memcached"
    node_type = "cache.m1.small"
    num_cache_nodes = 1
    parameter_group_name = "default.memcached1.4"
    security_group_names = ["${aws_elasticache_security_group.bar.name}"]
}
`, genRandInt(), genRandInt(), genRandInt())

var testAccAWSElasticacheClusterInVPCConfig = fmt.Sprintf(`
resource "aws_vpc" "foo" {
    cidr_block = "192.168.0.0/16"
    tags {
            Name = "tf-test"
    }
}

resource "aws_subnet" "foo" {
    vpc_id = "${aws_vpc.foo.id}"
    cidr_block = "192.168.0.0/20"
    availability_zone = "us-west-2a"
    tags {
            Name = "tf-test"
    }
}

resource "aws_elasticache_subnet_group" "bar" {
    name = "tf-test-cache-subnet-%03d"
    description = "tf-test-cache-subnet-group-descr"
    subnet_ids = ["${aws_subnet.foo.id}"]
}

resource "aws_security_group" "bar" {
    name = "tf-test-security-group-%03d"
    description = "tf-test-security-group-descr"
    vpc_id = "${aws_vpc.foo.id}"
    ingress {
        from_port = -1
        to_port = -1
        protocol = "icmp"
        cidr_blocks = ["0.0.0.0/0"]
    }
}

resource "aws_elasticache_cluster" "bar" {
    cluster_id = "tf-test-%03d"
    node_type = "cache.m1.small"
    num_cache_nodes = 1
    engine = "redis"
    engine_version = "2.8.19"
    port = 6379
    subnet_group_name = "${aws_elasticache_subnet_group.bar.name}"
    security_group_ids = ["${aws_security_group.bar.id}"]
    parameter_group_name = "default.redis2.8"
}
`, genRandInt(), genRandInt(), genRandInt())
<|endoftext|>