<commit_before>package controller

import (
	"encoding/json"
	"errors"
	"io"
	"net/http"
	"time"

	log "github.com/Sirupsen/logrus"
	"github.com/coreos/kube-etcd-controller/pkg/cluster"
	"github.com/coreos/kube-etcd-controller/pkg/util/k8sutil"
	k8sapi "k8s.io/kubernetes/pkg/api"
	"k8s.io/kubernetes/pkg/apis/extensions"
	"k8s.io/kubernetes/pkg/client/restclient"
	"k8s.io/kubernetes/pkg/client/unversioned"
)

const (
	tprName = "etcd-cluster.coreos.com"
)

type Event struct {
	Type   string
	Object cluster.EtcdCluster
}

type Controller struct {
	masterHost string
	namespace  string
	kclient    *unversioned.Client
	clusters   map[string]*cluster.Cluster
}

type Config struct {
	Namespace   string
	MasterHost  string
	TLSInsecure bool
	TLSConfig   restclient.TLSClientConfig
}

func New(cfg Config) *Controller {
	kclient := k8sutil.MustCreateClient(cfg.MasterHost, cfg.TLSInsecure, &cfg.TLSConfig)
	host := cfg.MasterHost
	if len(host) == 0 {
		host = k8sutil.MustGetInClusterMasterHost()
	}
	return &Controller{
		masterHost: host,
		kclient:    kclient,
		clusters:   make(map[string]*cluster.Cluster),
		namespace:  cfg.Namespace,
	}
}

func (c *Controller) Run() {
	watchVersion, err := c.initResource()
	if err != nil {
		panic(err)
	}
	log.Println("etcd cluster controller starts running...")

	eventCh, errCh := monitorEtcdCluster(c.masterHost, c.namespace, c.kclient.RESTClient.Client, watchVersion)
	for {
		select {
		case event := <-eventCh:
			clusterName := event.Object.ObjectMeta.Name
			switch event.Type {
			case "ADDED":
				clusterSpec := &event.Object.Spec
				nc := cluster.New(c.kclient, clusterName, c.namespace, clusterSpec)
				c.clusters[clusterName] = nc

				backup := clusterSpec.Backup
				if backup != nil && backup.MaxSnapshot != 0 {
					err := k8sutil.CreateBackupReplicaSetAndService(c.kclient, clusterName, c.namespace, *backup)
					if err != nil {
						panic(err)
					}
				}
			case "MODIFIED":
				c.clusters[clusterName].Update(&event.Object.Spec)
			case "DELETED":
				c.clusters[clusterName].Delete()
				delete(c.clusters, clusterName)
			}
		case err := <-errCh:
			panic(err)
		}
	}
}

func (c *Controller) findAllClusters() (string, error) {
	log.Println("finding existing clusters...")
	resp, err := k8sutil.ListETCDCluster(c.masterHost, c.namespace, c.kclient.RESTClient.Client)
	if err != nil {
		return "", err
	}
	d := json.NewDecoder(resp.Body)
	list := &EtcdClusterList{}
	if err := d.Decode(list); err != nil {
		return "", err
	}
	for _, item := range list.Items {
		nc := cluster.Restore(c.kclient, item.Name, c.namespace, &item.Spec)
		c.clusters[item.Name] = nc

		backup := item.Spec.Backup
		if backup != nil && backup.MaxSnapshot != 0 {
			err := k8sutil.CreateBackupReplicaSetAndService(c.kclient, item.Name, c.namespace, *backup)
			if !k8sutil.IsKubernetesResourceAlreadyExistError(err) {
				panic(err)
			}
		}
	}
	return list.ListMeta.ResourceVersion, nil
}

func (c *Controller) initResource() (string, error) {
	err := c.createTPR()
	if err != nil {
		switch {
		// etcd controller has been initialized before. We don't need to
		// repeat the init process but recover cluster.
		case k8sutil.IsKubernetesResourceAlreadyExistError(err):
			watchVersion, err := c.findAllClusters()
			if err != nil {
				return "", err
			}
			return watchVersion, nil
		default:
			log.Errorf("fail to create TPR: %v", err)
			return "", err
		}
	}
	err = k8sutil.CreateStorageClass(c.kclient)
	if err != nil {
		log.Errorf("fail to create storage class: %v", err)
		return "", err
	}
	return "0", nil
}

func (c *Controller) createTPR() error {
	tpr := &extensions.ThirdPartyResource{
		ObjectMeta: k8sapi.ObjectMeta{
			Name: tprName,
		},
		Versions: []extensions.APIVersion{
			{Name: "v1"},
		},
		Description: "Managed etcd clusters",
	}
	_, err := c.kclient.ThirdPartyResources().Create(tpr)
	if err != nil {
		return err
	}

	return k8sutil.WaitEtcdTPRReady(c.kclient.Client, 3*time.Second, 90*time.Second, c.masterHost, c.namespace)
}

func monitorEtcdCluster(host, ns string, httpClient *http.Client, watchVersion string) (<-chan *Event, <-chan error) {
	events := make(chan *Event)
	// On unexpected error case, controller should exit
	errc := make(chan error, 1)
	go func() {
		for {
			resp, err := k8sutil.WatchETCDCluster(host, ns, httpClient, watchVersion)
			if err != nil {
				errc <- err
				return
			}
			if resp.StatusCode != 200 {
				resp.Body.Close()
				errc <- errors.New("Invalid status code: " + resp.Status)
				return
			}
			log.Printf("watching at %v", watchVersion)
			for {
				decoder := json.NewDecoder(resp.Body)
				ev := new(Event)
				err = decoder.Decode(ev)
				if err != nil {
					if err == io.EOF {
						break
					}
					log.Errorf("failed to get event from apiserver: %v", err)
					errc <- err
					return
				}
				if ev.Type == "ERROR" {
					break
				}
				log.Printf("etcd cluster event: %v %#v", ev.Type, ev.Object)
				watchVersion = ev.Object.ObjectMeta.ResourceVersion
				events <- ev
			}
			resp.Body.Close()
		}
	}()

	return events, errc
}
<commit_msg>controller: exit program on watch error from apiserver<commit_after>package controller

import (
	"encoding/json"
	"errors"
	"io"
	"net/http"
	"time"

	log "github.com/Sirupsen/logrus"
	"github.com/coreos/kube-etcd-controller/pkg/cluster"
	"github.com/coreos/kube-etcd-controller/pkg/util/k8sutil"
	k8sapi "k8s.io/kubernetes/pkg/api"
	"k8s.io/kubernetes/pkg/apis/extensions"
	"k8s.io/kubernetes/pkg/client/restclient"
	"k8s.io/kubernetes/pkg/client/unversioned"
)

const (
	tprName = "etcd-cluster.coreos.com"
)

type Event struct {
	Type   string
	Object cluster.EtcdCluster
}

type Controller struct {
	masterHost string
	namespace  string
	kclient    *unversioned.Client
	clusters   map[string]*cluster.Cluster
}

type Config struct {
	Namespace   string
	MasterHost  string
	TLSInsecure bool
	TLSConfig   restclient.TLSClientConfig
}

func New(cfg Config) *Controller {
	kclient := k8sutil.MustCreateClient(cfg.MasterHost, cfg.TLSInsecure, &cfg.TLSConfig)
	host := cfg.MasterHost
	if len(host) == 0 {
		host = k8sutil.MustGetInClusterMasterHost()
	}
	return &Controller{
		masterHost: host,
		kclient:    kclient,
		clusters:   make(map[string]*cluster.Cluster),
		namespace:  cfg.Namespace,
	}
}

func (c *Controller) Run() {
	watchVersion, err := c.initResource()
	if err != nil {
		panic(err)
	}
	log.Println("etcd cluster controller starts running...")

	eventCh, errCh := monitorEtcdCluster(c.masterHost, c.namespace, c.kclient.RESTClient.Client, watchVersion)
	for {
		select {
		case event := <-eventCh:
			clusterName := event.Object.ObjectMeta.Name
			switch event.Type {
			case "ADDED":
				clusterSpec := &event.Object.Spec
				nc := cluster.New(c.kclient, clusterName, c.namespace, clusterSpec)
				c.clusters[clusterName] = nc

				backup := clusterSpec.Backup
				if backup != nil && backup.MaxSnapshot != 0 {
					err := k8sutil.CreateBackupReplicaSetAndService(c.kclient, clusterName, c.namespace, *backup)
					if err != nil {
						panic(err)
					}
				}
			case "MODIFIED":
				c.clusters[clusterName].Update(&event.Object.Spec)
			case "DELETED":
				c.clusters[clusterName].Delete()
				delete(c.clusters, clusterName)
			}
		case err := <-errCh:
			panic(err)
		}
	}
}

func (c *Controller) findAllClusters() (string, error) {
	log.Println("finding existing clusters...")
	resp, err := k8sutil.ListETCDCluster(c.masterHost, c.namespace, c.kclient.RESTClient.Client)
	if err != nil {
		return "", err
	}
	d := json.NewDecoder(resp.Body)
	list := &EtcdClusterList{}
	if err := d.Decode(list); err != nil {
		return "", err
	}
	for _, item := range list.Items {
		nc := cluster.Restore(c.kclient, item.Name, c.namespace, &item.Spec)
		c.clusters[item.Name] = nc

		backup := item.Spec.Backup
		if backup != nil && backup.MaxSnapshot != 0 {
			err := k8sutil.CreateBackupReplicaSetAndService(c.kclient, item.Name, c.namespace, *backup)
			if !k8sutil.IsKubernetesResourceAlreadyExistError(err) {
				panic(err)
			}
		}
	}
	return list.ListMeta.ResourceVersion, nil
}

func (c *Controller) initResource() (string, error) {
	err := c.createTPR()
	if err != nil {
		switch {
		// etcd controller has been initialized before. We don't need to
		// repeat the init process but recover cluster.
		case k8sutil.IsKubernetesResourceAlreadyExistError(err):
			watchVersion, err := c.findAllClusters()
			if err != nil {
				return "", err
			}
			return watchVersion, nil
		default:
			log.Errorf("fail to create TPR: %v", err)
			return "", err
		}
	}
	err = k8sutil.CreateStorageClass(c.kclient)
	if err != nil {
		log.Errorf("fail to create storage class: %v", err)
		return "", err
	}
	return "0", nil
}

func (c *Controller) createTPR() error {
	tpr := &extensions.ThirdPartyResource{
		ObjectMeta: k8sapi.ObjectMeta{
			Name: tprName,
		},
		Versions: []extensions.APIVersion{
			{Name: "v1"},
		},
		Description: "Managed etcd clusters",
	}
	_, err := c.kclient.ThirdPartyResources().Create(tpr)
	if err != nil {
		return err
	}

	return k8sutil.WaitEtcdTPRReady(c.kclient.Client, 3*time.Second, 90*time.Second, c.masterHost, c.namespace)
}

func monitorEtcdCluster(host, ns string, httpClient *http.Client, watchVersion string) (<-chan *Event, <-chan error) {
	events := make(chan *Event)
	// On unexpected error case, controller should exit
	errc := make(chan error, 1)
	go func() {
		for {
			resp, err := k8sutil.WatchETCDCluster(host, ns, httpClient, watchVersion)
			if err != nil {
				errc <- err
				return
			}
			if resp.StatusCode != 200 {
				resp.Body.Close()
				errc <- errors.New("Invalid status code: " + resp.Status)
				return
			}
			log.Printf("watching at %v", watchVersion)
			for {
				decoder := json.NewDecoder(resp.Body)
				ev := new(Event)
				err = decoder.Decode(ev)
				if err != nil {
					if err == io.EOF {
						break
					}
					log.Errorf("failed to get event from apiserver: %v", err)
					errc <- err
					return
				}
				if ev.Type == "ERROR" {
					// TODO: We couldn't decode error status from watch stream on apiserver.
					//       Working around by restart and go through recover path.
					//       We strive to fix it in k8s upstream.
					log.Fatal("unkown watch error from apiserver")
				}
				log.Printf("etcd cluster event: %v %#v", ev.Type, ev.Object)
				watchVersion = ev.Object.ObjectMeta.ResourceVersion
				events <- ev
			}
			resp.Body.Close()
		}
	}()

	return events, errc
}
<|endoftext|>


<commit_before>// Copyright 2017 Cisco Systems, 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 controller

import (
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net"
	"strings"
	"sync"
	"time"

	"github.com/Sirupsen/logrus"
	"github.com/juju/ratelimit"

	v1 "k8s.io/api/core/v1"
	v1net "k8s.io/api/networking/v1"
	"k8s.io/apimachinery/pkg/util/wait"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/tools/cache"
	"k8s.io/client-go/util/workqueue"

	"github.com/noironetworks/aci-containers/pkg/apicapi"
	"github.com/noironetworks/aci-containers/pkg/index"
	"github.com/noironetworks/aci-containers/pkg/metadata"
)

type podUpdateFunc func(*v1.Pod) (*v1.Pod, error)
type nodeUpdateFunc func(*v1.Node) (*v1.Node, error)
type serviceUpdateFunc func(*v1.Service) (*v1.Service, error)

type AciController struct {
	log    *logrus.Logger
	config *ControllerConfig

	defaultEg string
	defaultSg string

	podQueue     workqueue.RateLimitingInterface
	netPolQueue  workqueue.RateLimitingInterface
	serviceQueue workqueue.RateLimitingInterface

	namespaceIndexer      cache.Indexer
	namespaceInformer     cache.Controller
	podIndexer            cache.Indexer
	podInformer           cache.Controller
	endpointsIndexer      cache.Indexer
	endpointsInformer     cache.Controller
	serviceIndexer        cache.Indexer
	serviceInformer       cache.Controller
	replicaSetIndexer     cache.Indexer
	replicaSetInformer    cache.Controller
	deploymentIndexer     cache.Indexer
	deploymentInformer    cache.Controller
	nodeIndexer           cache.Indexer
	nodeInformer          cache.Controller
	networkPolicyIndexer  cache.Indexer
	networkPolicyInformer cache.Controller

	updatePod           podUpdateFunc
	updateNode          nodeUpdateFunc
	updateServiceStatus serviceUpdateFunc

	indexMutex sync.Mutex

	configuredPodNetworkIps *netIps
	podNetworkIps           *netIps
	serviceIps              *netIps
	staticServiceIps        *netIps
	nodeServiceIps          *netIps

	depPods           *index.PodSelectorIndex
	netPolPods        *index.PodSelectorIndex
	netPolIngressPods *index.PodSelectorIndex

	apicConn *apicapi.ApicConnection

	nodeServiceMetaCache map[string]*nodeServiceMeta
	nodeOpflexDevice     map[string]apicapi.ApicSlice
	nodePodNetCache      map[string]*nodePodNetMeta
	serviceMetaCache     map[string]*serviceMeta

	nodeSyncEnabled    bool
	serviceSyncEnabled bool
}

type nodeServiceMeta struct {
	serviceEp           metadata.ServiceEndpoint
	serviceEpAnnotation string
}

type nodePodNetMeta struct {
	nodePods            map[string]bool
	podNetIps           metadata.NetIps
	podNetIpsAnnotation string
}

type serviceMeta struct {
	requestedIp      net.IP
	ingressIps       []net.IP
	staticIngressIps []net.IP
}

func newNodePodNetMeta() *nodePodNetMeta {
	return &nodePodNetMeta{
		nodePods: make(map[string]bool),
	}
}

func createQueue(name string) workqueue.RateLimitingInterface {
	return workqueue.NewNamedRateLimitingQueue(
		workqueue.NewMaxOfRateLimiter(
			workqueue.NewItemExponentialFailureRateLimiter(5*time.Millisecond,
				10*time.Second),
			&workqueue.BucketRateLimiter{
				Bucket: ratelimit.NewBucketWithRate(float64(10), int64(100)),
			},
		),
		"delta")
}

func NewController(config *ControllerConfig, log *logrus.Logger) *AciController {
	return &AciController{
		log:       log,
		config:    config,
		defaultEg: "",
		defaultSg: "",

		podQueue:     createQueue("pod"),
		netPolQueue:  createQueue("networkPolicy"),
		serviceQueue: createQueue("service"),

		configuredPodNetworkIps: newNetIps(),
		podNetworkIps:           newNetIps(),
		serviceIps:              newNetIps(),
		staticServiceIps:        newNetIps(),
		nodeServiceIps:          newNetIps(),

		nodeOpflexDevice: make(map[string]apicapi.ApicSlice),

		nodeServiceMetaCache: make(map[string]*nodeServiceMeta),
		nodePodNetCache:      make(map[string]*nodePodNetMeta),
		serviceMetaCache:     make(map[string]*serviceMeta),
	}
}

func (cont *AciController) Init(kubeClient *kubernetes.Clientset) {
	cont.updatePod = func(pod *v1.Pod) (*v1.Pod, error) {
		return kubeClient.CoreV1().Pods(pod.ObjectMeta.Namespace).Update(pod)
	}
	cont.updateNode = func(node *v1.Node) (*v1.Node, error) {
		return kubeClient.CoreV1().Nodes().Update(node)
	}
	cont.updateServiceStatus = func(service *v1.Service) (*v1.Service, error) {
		return kubeClient.CoreV1().
			Services(service.ObjectMeta.Namespace).UpdateStatus(service)
	}

	egdata, err := json.Marshal(cont.config.DefaultEg)
	if err != nil {
		cont.log.Error("Could not serialize default endpoint group")
		panic(err.Error())
	}
	cont.defaultEg = string(egdata)

	sgdata, err := json.Marshal(cont.config.DefaultSg)
	if err != nil {
		cont.log.Error("Could not serialize default security groups")
		panic(err.Error())
	}
	cont.defaultSg = string(sgdata)

	cont.log.Debug("Initializing IPAM")
	cont.initIpam()

	cont.log.Debug("Initializing informers")
	cont.initNodeInformerFromClient(kubeClient)
	cont.initNamespaceInformerFromClient(kubeClient)
	cont.initReplicaSetInformerFromClient(kubeClient)
	cont.initDeploymentInformerFromClient(kubeClient)
	cont.initPodInformerFromClient(kubeClient)
	cont.initEndpointsInformerFromClient(kubeClient)
	cont.initServiceInformerFromClient(kubeClient)
	cont.initNetworkPolicyInformerFromClient(kubeClient)

	cont.log.Debug("Initializing indexes")
	cont.initDepPodIndex()
	cont.initNetPolPodIndex()
}

func (cont *AciController) processQueue(queue workqueue.RateLimitingInterface,
	store cache.Store, handler func(interface{}) bool,
	stopCh <-chan struct{}) {
	go wait.Until(func() {
		for {
			key, quit := queue.Get()
			if quit {
				break
			}

			var requeue bool
			switch key := key.(type) {
			case chan struct{}:
				close(key)
			case string:
				obj, exists, err := store.GetByKey(key)
				if err == nil && exists {
					requeue = handler(obj)
				}
			}
			if requeue {
				queue.AddRateLimited(key)
			} else {
				queue.Forget(key)
			}
			queue.Done(key)

		}
	}, time.Second, stopCh)
	<-stopCh
	queue.ShutDown()
}

func (cont *AciController) globalStaticObjs() apicapi.ApicSlice {
	return apicapi.ApicSlice{}
}

func (cont *AciController) aciNameForKey(ktype string, key string) string {
	name := cont.config.AciPrefix + "_" + ktype +
		"_" + strings.Replace(key, "/", "_", -1)
	if len(name) < 64 {
		return name
	}

	hash := sha256.New()
	if len(cont.config.AciPrefix)+len(ktype)+1 > 31 {
		if len(cont.config.AciPrefix) > 31 {
			hash.Write([]byte(cont.config.AciPrefix))
			hash.Write([]byte("_"))
		} else {
			name = cont.config.AciPrefix
		}

		hash.Write([]byte(ktype))
		hash.Write([]byte("_"))
	} else {
		name = cont.config.AciPrefix + "_" + ktype
	}
	hash.Write([]byte(key))

	hashstr := hex.EncodeToString(hash.Sum(nil)[:16])
	if len(cont.config.AciPrefix) > 31 {
		return hashstr
	} else {
		return fmt.Sprintf("%s_%s", name, hashstr)
	}
}

func (cont *AciController) initStaticObjs() {
	cont.initStaticNetPolObjs()
	cont.initStaticServiceObjs()
	cont.apicConn.WriteApicObjects(cont.config.AciPrefix+"_static",
		cont.globalStaticObjs())
}

func (cont *AciController) vmmDomainProvider() (vmmProv string) {
	vmmProv = "Kubernetes"
	if strings.ToLower(cont.config.AciVmmDomainType) == "openshift" {
		vmmProv = "OpenShift"
	}
	return
}

func (cont *AciController) Run(stopCh <-chan struct{}) {
	var err error
	var privKey []byte
	var apicCert []byte
	if cont.config.ApicPrivateKeyPath != "" {
		privKey, err = ioutil.ReadFile(cont.config.ApicPrivateKeyPath)
		if err != nil {
			panic(err)
		}
	}
	if cont.config.ApicCertPath != "" {
		apicCert, err = ioutil.ReadFile(cont.config.ApicCertPath)
		if err != nil {
			panic(err)
		}
	}
	cont.apicConn, err = apicapi.New(cont.log, cont.config.ApicHosts,
		cont.config.ApicUsername, cont.config.ApicPassword,
		privKey, apicCert, cont.config.AciPrefix)
	if err != nil {
		panic(err)
	}

	cont.log.Debug("Starting informers")
	go cont.nodeInformer.Run(stopCh)
	go cont.namespaceInformer.Run(stopCh)
	cont.log.Info("Waiting for node/namespace cache sync")
	cache.WaitForCacheSync(stopCh,
		cont.nodeInformer.HasSynced, cont.namespaceInformer.HasSynced)
	cont.indexMutex.Lock()
	cont.nodeSyncEnabled = true
	cont.indexMutex.Unlock()
	cont.nodeFullSync()
	cont.log.Info("Node/namespace cache sync successful")

	go cont.endpointsInformer.Run(stopCh)
	go cont.serviceInformer.Run(stopCh)
	go cont.processQueue(cont.serviceQueue, cont.serviceIndexer,
		func(obj interface{}) bool {
			return cont.handleServiceUpdate(obj.(*v1.Service))
		}, stopCh)
	cont.log.Debug("Waiting for service cache sync")
	cache.WaitForCacheSync(stopCh,
		cont.endpointsInformer.HasSynced,
		cont.serviceInformer.HasSynced)
	cont.indexMutex.Lock()
	cont.serviceSyncEnabled = true
	cont.indexMutex.Unlock()
	cont.serviceFullSync()
	cont.log.Info("Service cache sync successful")

	go cont.replicaSetInformer.Run(stopCh)
	go cont.deploymentInformer.Run(stopCh)
	go cont.podInformer.Run(stopCh)
	go cont.networkPolicyInformer.Run(stopCh)
	go cont.processQueue(cont.podQueue, cont.podIndexer,
		func(obj interface{}) bool {
			return cont.handlePodUpdate(obj.(*v1.Pod))
		}, stopCh)
	go cont.processQueue(cont.netPolQueue, cont.networkPolicyIndexer,
		func(obj interface{}) bool {
			return cont.handleNetPolUpdate(obj.(*v1net.NetworkPolicy))
		}, stopCh)

	cont.log.Info("Waiting for cache sync for remaining objects")
	cache.WaitForCacheSync(stopCh,
		cont.namespaceInformer.HasSynced,
		cont.replicaSetInformer.HasSynced,
		cont.deploymentInformer.HasSynced,
		cont.podInformer.HasSynced,
		cont.networkPolicyInformer.HasSynced)
	cont.log.Info("Cache sync successful")

	cont.initStaticObjs()

	cont.apicConn.FullSyncHook = func() {
		// put a channel into each work queue and wait on it to
		// checkpoint object syncing in response to new subscription
		// updates
		cont.log.Debug("Starting checkpoint")
		var chans []chan struct{}
		qs := []workqueue.RateLimitingInterface{
			cont.podQueue, cont.netPolQueue, cont.serviceQueue,
		}
		for _, q := range qs {
			c := make(chan struct{})
			chans = append(chans, c)
			q.AddRateLimited(c)
		}
		for _, c := range chans {
			<-c
		}
		cont.log.Debug("Checkpoint complete")
	}

	oDevType := "k8s"
	if strings.ToLower(cont.config.AciVmmDomainType) == "openshift" {
		oDevType = "openshift"
	}

	cont.apicConn.AddSubscriptionDn("uni/tn-"+cont.config.AciPolicyTenant,
		[]string{"hostprotPol"})
	cont.apicConn.AddSubscriptionDn("uni/tn-"+cont.config.AciVrfTenant,
		[]string{"fvBD", "vnsLDevVip", "vnsAbsGraph", "vnsLDevCtx",
			"vzFilter", "vzBrCP", "l3extInstP", "vnsSvcRedirectPol"})
	cont.apicConn.AddSubscriptionDn(fmt.Sprintf("uni/tn-%s/out-%s",
		cont.config.AciVrfTenant, cont.config.AciL3Out),
		[]string{"fvRsCons"})
	vmmDn := fmt.Sprintf("comp/prov-%s/ctrlr-[%s]-%s/injcont",
		cont.vmmDomainProvider(), cont.config.AciVmmDomain,
		cont.config.AciVmmController)
	cont.apicConn.AddSubscriptionDn(vmmDn,
		[]string{"vmmInjectedHost", "vmmInjectedNs",
			"vmmInjectedContGrp", "vmmInjectedDepl",
			"vmmInjectedSvc", "vmmInjectedReplSet"})
	cont.apicConn.AddSubscriptionClass("opflexODev",
		[]string{"opflexODev"},
		fmt.Sprintf("and(eq(opflexODev.devType,\"%s\"),"+
			"eq(opflexODev.domType,\"%s\"),"+
			"eq(opflexODev.ctrlrName,\"%s\"))",
			oDevType, cont.config.AciVmmDomain, cont.config.AciVmmController))

	cont.apicConn.SetSubscriptionHooks("opflexODev",
		func(obj apicapi.ApicObject) bool {
			cont.opflexDeviceChanged(obj)
			return true
		},
		func(dn string) {
			cont.opflexDeviceDeleted(dn)
		})
	go cont.apicConn.Run(stopCh)
}
<commit_msg>Fix opflexODev subscription expression.<commit_after>// Copyright 2017 Cisco Systems, 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 controller

import (
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net"
	"strings"
	"sync"
	"time"

	"github.com/Sirupsen/logrus"
	"github.com/juju/ratelimit"

	v1 "k8s.io/api/core/v1"
	v1net "k8s.io/api/networking/v1"
	"k8s.io/apimachinery/pkg/util/wait"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/tools/cache"
	"k8s.io/client-go/util/workqueue"

	"github.com/noironetworks/aci-containers/pkg/apicapi"
	"github.com/noironetworks/aci-containers/pkg/index"
	"github.com/noironetworks/aci-containers/pkg/metadata"
)

type podUpdateFunc func(*v1.Pod) (*v1.Pod, error)
type nodeUpdateFunc func(*v1.Node) (*v1.Node, error)
type serviceUpdateFunc func(*v1.Service) (*v1.Service, error)

type AciController struct {
	log    *logrus.Logger
	config *ControllerConfig

	defaultEg string
	defaultSg string

	podQueue     workqueue.RateLimitingInterface
	netPolQueue  workqueue.RateLimitingInterface
	serviceQueue workqueue.RateLimitingInterface

	namespaceIndexer      cache.Indexer
	namespaceInformer     cache.Controller
	podIndexer            cache.Indexer
	podInformer           cache.Controller
	endpointsIndexer      cache.Indexer
	endpointsInformer     cache.Controller
	serviceIndexer        cache.Indexer
	serviceInformer       cache.Controller
	replicaSetIndexer     cache.Indexer
	replicaSetInformer    cache.Controller
	deploymentIndexer     cache.Indexer
	deploymentInformer    cache.Controller
	nodeIndexer           cache.Indexer
	nodeInformer          cache.Controller
	networkPolicyIndexer  cache.Indexer
	networkPolicyInformer cache.Controller

	updatePod           podUpdateFunc
	updateNode          nodeUpdateFunc
	updateServiceStatus serviceUpdateFunc

	indexMutex sync.Mutex

	configuredPodNetworkIps *netIps
	podNetworkIps           *netIps
	serviceIps              *netIps
	staticServiceIps        *netIps
	nodeServiceIps          *netIps

	depPods           *index.PodSelectorIndex
	netPolPods        *index.PodSelectorIndex
	netPolIngressPods *index.PodSelectorIndex

	apicConn *apicapi.ApicConnection

	nodeServiceMetaCache map[string]*nodeServiceMeta
	nodeOpflexDevice     map[string]apicapi.ApicSlice
	nodePodNetCache      map[string]*nodePodNetMeta
	serviceMetaCache     map[string]*serviceMeta

	nodeSyncEnabled    bool
	serviceSyncEnabled bool
}

type nodeServiceMeta struct {
	serviceEp           metadata.ServiceEndpoint
	serviceEpAnnotation string
}

type nodePodNetMeta struct {
	nodePods            map[string]bool
	podNetIps           metadata.NetIps
	podNetIpsAnnotation string
}

type serviceMeta struct {
	requestedIp      net.IP
	ingressIps       []net.IP
	staticIngressIps []net.IP
}

func newNodePodNetMeta() *nodePodNetMeta {
	return &nodePodNetMeta{
		nodePods: make(map[string]bool),
	}
}

func createQueue(name string) workqueue.RateLimitingInterface {
	return workqueue.NewNamedRateLimitingQueue(
		workqueue.NewMaxOfRateLimiter(
			workqueue.NewItemExponentialFailureRateLimiter(5*time.Millisecond,
				10*time.Second),
			&workqueue.BucketRateLimiter{
				Bucket: ratelimit.NewBucketWithRate(float64(10), int64(100)),
			},
		),
		"delta")
}

func NewController(config *ControllerConfig, log *logrus.Logger) *AciController {
	return &AciController{
		log:       log,
		config:    config,
		defaultEg: "",
		defaultSg: "",

		podQueue:     createQueue("pod"),
		netPolQueue:  createQueue("networkPolicy"),
		serviceQueue: createQueue("service"),

		configuredPodNetworkIps: newNetIps(),
		podNetworkIps:           newNetIps(),
		serviceIps:              newNetIps(),
		staticServiceIps:        newNetIps(),
		nodeServiceIps:          newNetIps(),

		nodeOpflexDevice: make(map[string]apicapi.ApicSlice),

		nodeServiceMetaCache: make(map[string]*nodeServiceMeta),
		nodePodNetCache:      make(map[string]*nodePodNetMeta),
		serviceMetaCache:     make(map[string]*serviceMeta),
	}
}

func (cont *AciController) Init(kubeClient *kubernetes.Clientset) {
	cont.updatePod = func(pod *v1.Pod) (*v1.Pod, error) {
		return kubeClient.CoreV1().Pods(pod.ObjectMeta.Namespace).Update(pod)
	}
	cont.updateNode = func(node *v1.Node) (*v1.Node, error) {
		return kubeClient.CoreV1().Nodes().Update(node)
	}
	cont.updateServiceStatus = func(service *v1.Service) (*v1.Service, error) {
		return kubeClient.CoreV1().
			Services(service.ObjectMeta.Namespace).UpdateStatus(service)
	}

	egdata, err := json.Marshal(cont.config.DefaultEg)
	if err != nil {
		cont.log.Error("Could not serialize default endpoint group")
		panic(err.Error())
	}
	cont.defaultEg = string(egdata)

	sgdata, err := json.Marshal(cont.config.DefaultSg)
	if err != nil {
		cont.log.Error("Could not serialize default security groups")
		panic(err.Error())
	}
	cont.defaultSg = string(sgdata)

	cont.log.Debug("Initializing IPAM")
	cont.initIpam()

	cont.log.Debug("Initializing informers")
	cont.initNodeInformerFromClient(kubeClient)
	cont.initNamespaceInformerFromClient(kubeClient)
	cont.initReplicaSetInformerFromClient(kubeClient)
	cont.initDeploymentInformerFromClient(kubeClient)
	cont.initPodInformerFromClient(kubeClient)
	cont.initEndpointsInformerFromClient(kubeClient)
	cont.initServiceInformerFromClient(kubeClient)
	cont.initNetworkPolicyInformerFromClient(kubeClient)

	cont.log.Debug("Initializing indexes")
	cont.initDepPodIndex()
	cont.initNetPolPodIndex()
}

func (cont *AciController) processQueue(queue workqueue.RateLimitingInterface,
	store cache.Store, handler func(interface{}) bool,
	stopCh <-chan struct{}) {
	go wait.Until(func() {
		for {
			key, quit := queue.Get()
			if quit {
				break
			}

			var requeue bool
			switch key := key.(type) {
			case chan struct{}:
				close(key)
			case string:
				obj, exists, err := store.GetByKey(key)
				if err == nil && exists {
					requeue = handler(obj)
				}
			}
			if requeue {
				queue.AddRateLimited(key)
			} else {
				queue.Forget(key)
			}
			queue.Done(key)

		}
	}, time.Second, stopCh)
	<-stopCh
	queue.ShutDown()
}

func (cont *AciController) globalStaticObjs() apicapi.ApicSlice {
	return apicapi.ApicSlice{}
}

func (cont *AciController) aciNameForKey(ktype string, key string) string {
	name := cont.config.AciPrefix + "_" + ktype +
		"_" + strings.Replace(key, "/", "_", -1)
	if len(name) < 64 {
		return name
	}

	hash := sha256.New()
	if len(cont.config.AciPrefix)+len(ktype)+1 > 31 {
		if len(cont.config.AciPrefix) > 31 {
			hash.Write([]byte(cont.config.AciPrefix))
			hash.Write([]byte("_"))
		} else {
			name = cont.config.AciPrefix
		}

		hash.Write([]byte(ktype))
		hash.Write([]byte("_"))
	} else {
		name = cont.config.AciPrefix + "_" + ktype
	}
	hash.Write([]byte(key))

	hashstr := hex.EncodeToString(hash.Sum(nil)[:16])
	if len(cont.config.AciPrefix) > 31 {
		return hashstr
	} else {
		return fmt.Sprintf("%s_%s", name, hashstr)
	}
}

func (cont *AciController) initStaticObjs() {
	cont.initStaticNetPolObjs()
	cont.initStaticServiceObjs()
	cont.apicConn.WriteApicObjects(cont.config.AciPrefix+"_static",
		cont.globalStaticObjs())
}

func (cont *AciController) vmmDomainProvider() (vmmProv string) {
	vmmProv = "Kubernetes"
	if strings.ToLower(cont.config.AciVmmDomainType) == "openshift" {
		vmmProv = "OpenShift"
	}
	return
}

func (cont *AciController) Run(stopCh <-chan struct{}) {
	var err error
	var privKey []byte
	var apicCert []byte
	if cont.config.ApicPrivateKeyPath != "" {
		privKey, err = ioutil.ReadFile(cont.config.ApicPrivateKeyPath)
		if err != nil {
			panic(err)
		}
	}
	if cont.config.ApicCertPath != "" {
		apicCert, err = ioutil.ReadFile(cont.config.ApicCertPath)
		if err != nil {
			panic(err)
		}
	}
	cont.apicConn, err = apicapi.New(cont.log, cont.config.ApicHosts,
		cont.config.ApicUsername, cont.config.ApicPassword,
		privKey, apicCert, cont.config.AciPrefix)
	if err != nil {
		panic(err)
	}

	cont.log.Debug("Starting informers")
	go cont.nodeInformer.Run(stopCh)
	go cont.namespaceInformer.Run(stopCh)
	cont.log.Info("Waiting for node/namespace cache sync")
	cache.WaitForCacheSync(stopCh,
		cont.nodeInformer.HasSynced, cont.namespaceInformer.HasSynced)
	cont.indexMutex.Lock()
	cont.nodeSyncEnabled = true
	cont.indexMutex.Unlock()
	cont.nodeFullSync()
	cont.log.Info("Node/namespace cache sync successful")

	go cont.endpointsInformer.Run(stopCh)
	go cont.serviceInformer.Run(stopCh)
	go cont.processQueue(cont.serviceQueue, cont.serviceIndexer,
		func(obj interface{}) bool {
			return cont.handleServiceUpdate(obj.(*v1.Service))
		}, stopCh)
	cont.log.Debug("Waiting for service cache sync")
	cache.WaitForCacheSync(stopCh,
		cont.endpointsInformer.HasSynced,
		cont.serviceInformer.HasSynced)
	cont.indexMutex.Lock()
	cont.serviceSyncEnabled = true
	cont.indexMutex.Unlock()
	cont.serviceFullSync()
	cont.log.Info("Service cache sync successful")

	go cont.replicaSetInformer.Run(stopCh)
	go cont.deploymentInformer.Run(stopCh)
	go cont.podInformer.Run(stopCh)
	go cont.networkPolicyInformer.Run(stopCh)
	go cont.processQueue(cont.podQueue, cont.podIndexer,
		func(obj interface{}) bool {
			return cont.handlePodUpdate(obj.(*v1.Pod))
		}, stopCh)
	go cont.processQueue(cont.netPolQueue, cont.networkPolicyIndexer,
		func(obj interface{}) bool {
			return cont.handleNetPolUpdate(obj.(*v1net.NetworkPolicy))
		}, stopCh)

	cont.log.Info("Waiting for cache sync for remaining objects")
	cache.WaitForCacheSync(stopCh,
		cont.namespaceInformer.HasSynced,
		cont.replicaSetInformer.HasSynced,
		cont.deploymentInformer.HasSynced,
		cont.podInformer.HasSynced,
		cont.networkPolicyInformer.HasSynced)
	cont.log.Info("Cache sync successful")

	cont.initStaticObjs()

	cont.apicConn.FullSyncHook = func() {
		// put a channel into each work queue and wait on it to
		// checkpoint object syncing in response to new subscription
		// updates
		cont.log.Debug("Starting checkpoint")
		var chans []chan struct{}
		qs := []workqueue.RateLimitingInterface{
			cont.podQueue, cont.netPolQueue, cont.serviceQueue,
		}
		for _, q := range qs {
			c := make(chan struct{})
			chans = append(chans, c)
			q.AddRateLimited(c)
		}
		for _, c := range chans {
			<-c
		}
		cont.log.Debug("Checkpoint complete")
	}

	oDevType := "k8s"
	if strings.ToLower(cont.config.AciVmmDomainType) == "openshift" {
		oDevType = "openshift"
	}

	cont.apicConn.AddSubscriptionDn("uni/tn-"+cont.config.AciPolicyTenant,
		[]string{"hostprotPol"})
	cont.apicConn.AddSubscriptionDn("uni/tn-"+cont.config.AciVrfTenant,
		[]string{"fvBD", "vnsLDevVip", "vnsAbsGraph", "vnsLDevCtx",
			"vzFilter", "vzBrCP", "l3extInstP", "vnsSvcRedirectPol"})
	cont.apicConn.AddSubscriptionDn(fmt.Sprintf("uni/tn-%s/out-%s",
		cont.config.AciVrfTenant, cont.config.AciL3Out),
		[]string{"fvRsCons"})
	vmmDn := fmt.Sprintf("comp/prov-%s/ctrlr-[%s]-%s/injcont",
		cont.vmmDomainProvider(), cont.config.AciVmmDomain,
		cont.config.AciVmmController)
	cont.apicConn.AddSubscriptionDn(vmmDn,
		[]string{"vmmInjectedHost", "vmmInjectedNs",
			"vmmInjectedContGrp", "vmmInjectedDepl",
			"vmmInjectedSvc", "vmmInjectedReplSet"})
	cont.apicConn.AddSubscriptionClass("opflexODev",
		[]string{"opflexODev"},
		fmt.Sprintf("and(eq(opflexODev.devType,\"%s\"),"+
			"eq(opflexODev.domName,\"%s\"),"+
			"eq(opflexODev.ctrlrName,\"%s\"))",
			oDevType, cont.config.AciVmmDomain, cont.config.AciVmmController))

	cont.apicConn.SetSubscriptionHooks("opflexODev",
		func(obj apicapi.ApicObject) bool {
			cont.opflexDeviceChanged(obj)
			return true
		},
		func(dn string) {
			cont.opflexDeviceDeleted(dn)
		})
	go cont.apicConn.Run(stopCh)
}
<|endoftext|>


<commit_before>package controller

import (
	"fmt"
	"time"

	"github.com/golang/glog"
	"k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/util/wait"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/tools/cache"
	"k8s.io/client-go/util/workqueue"
)

const (
	annEndpoint = "kubevirt.io/storage.import.endpoint"
	annSecret   = "kubevirt.io/storage.import.secretName"
	annStatus   = "kubevirt.io/storage.import.status"
)

type Controller struct {
	clientset      kubernetes.Interface
	queue          workqueue.RateLimitingInterface
	pvcInformer    cache.SharedIndexInformer
	pvcListWatcher cache.ListerWatcher
}

func NewController(
	client kubernetes.Interface,
	queue workqueue.RateLimitingInterface,
	pvcInformer cache.SharedIndexInformer,
	pvcListWatcher cache.ListerWatcher,
) *Controller {
	return &Controller{
		clientset:      client,
		queue:          queue,
		pvcInformer:    pvcInformer,
		pvcListWatcher: pvcListWatcher,
	}
}

func (c *Controller) Run(threadiness int, stopCh <-chan struct{}) error {
	defer c.queue.ShutDown()
	glog.Infoln("Starting CDI controller loop")
	if threadiness < 1 {
		return fmt.Errorf("controller.Run(): expected >0 threads, got %d", threadiness)
	}
	go c.pvcInformer.Run(stopCh)
	if !cache.WaitForCacheSync(stopCh, c.pvcInformer.HasSynced) {
		return fmt.Errorf("controller.Run(): Timeout waiting for cache sync")
	}
	glog.Infoln("Controller cache has synced")

	for i := 0; i < threadiness; i++ {
		go wait.Until(c.runWorkers, time.Second, stopCh)
	}
	<-stopCh
	return nil
}

func (c *Controller) runWorkers() {
	for c.processNextItem() {
	}
}

func (c *Controller) processNextItem() bool {
	key, shutdown := c.queue.Get()
	if shutdown {
		return false
	}
	defer c.queue.Done(key)
	pvc, err := c.pvcFromKey(key)
	if pvc == nil {
		c.queue.Forget(key)
		return true
	}
	glog.Infoln("processNextItem(): Next item to process: ", pvc.Name)
	if err != nil {
		glog.Errorf("processNextItem(): error converting key to pvc: %v", err)
		c.queue.Forget(key)
		return true
	}
	if ! metav1.HasAnnotation(pvc.ObjectMeta, annEndpoint) {
		glog.Infoln("processNextItem(): annotation not found, skipping item")
		c.queue.Forget(key)
		return true
	}
	if err := c.processItem(pvc); err != nil {
		glog.Errorf("processNextItem(): error processing key: %v", err)
		c.queue.Forget(key)
	}
	return true
}

func (c *Controller) processItem(pvc *v1.PersistentVolumeClaim) error {
	// DO STUFF
	return nil
}

func (c *Controller) pvcFromKey(key interface{}) (*v1.PersistentVolumeClaim, error) {
	keyString, ok := key.(string)
	if !ok {
		return nil, fmt.Errorf("pvcFromKey(): key object not of type string\n")
	}
	obj, ok, err := c.pvcInformer.GetIndexer().GetByKey(keyString)
	if !ok {
		return nil, fmt.Errorf("pvcFromKey(): key not found in cache\n")
	}
	if err != nil {
		return nil, fmt.Errorf("pvcFromKey(): Error getting key from cache: %q\n", keyString)
	}
	pvc, ok := obj.(*v1.PersistentVolumeClaim)
	if !ok {
		return nil, fmt.Errorf("pvcFromKey(): Object not of type *v1.PersistentVolumeClaim\n")
	}
	return pvc, nil
}
<commit_msg>Don't return error for deleted pvc<commit_after>package controller

import (
	"fmt"
	"time"

	"github.com/golang/glog"
	"k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/util/wait"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/tools/cache"
	"k8s.io/client-go/util/workqueue"
)

const (
	annEndpoint = "kubevirt.io/storage.import.endpoint"
	annSecret   = "kubevirt.io/storage.import.secretName"
	annStatus   = "kubevirt.io/storage.import.status"
)

type Controller struct {
	clientset      kubernetes.Interface
	queue          workqueue.RateLimitingInterface
	pvcInformer    cache.SharedIndexInformer
	pvcListWatcher cache.ListerWatcher
}

func NewController(
	client kubernetes.Interface,
	queue workqueue.RateLimitingInterface,
	pvcInformer cache.SharedIndexInformer,
	pvcListWatcher cache.ListerWatcher,
) *Controller {
	return &Controller{
		clientset:      client,
		queue:          queue,
		pvcInformer:    pvcInformer,
		pvcListWatcher: pvcListWatcher,
	}
}

func (c *Controller) Run(threadiness int, stopCh <-chan struct{}) error {
	defer c.queue.ShutDown()
	glog.Infoln("Starting CDI controller loop")
	if threadiness < 1 {
		return fmt.Errorf("controller.Run(): expected >0 threads, got %d", threadiness)
	}
	go c.pvcInformer.Run(stopCh)
	if !cache.WaitForCacheSync(stopCh, c.pvcInformer.HasSynced) {
		return fmt.Errorf("controller.Run(): Timeout waiting for cache sync")
	}
	glog.Infoln("Controller cache has synced")

	for i := 0; i < threadiness; i++ {
		go wait.Until(c.runWorkers, time.Second, stopCh)
	}
	<-stopCh
	return nil
}

func (c *Controller) runWorkers() {
	for c.processNextItem() {
	}
}

func (c *Controller) processNextItem() bool {
	key, shutdown := c.queue.Get()
	if shutdown {
		return false
	}
	defer c.queue.Done(key)
	pvc, err := c.pvcFromKey(key)
	if pvc == nil {
		c.queue.Forget(key)
		return true
	}
	glog.Infoln("processNextItem(): Next item to process: ", pvc.Name)
	if err != nil {
		glog.Errorf("processNextItem(): error converting key to pvc: %v", err)
		c.queue.Forget(key)
		return true
	}
	if ! metav1.HasAnnotation(pvc.ObjectMeta, annEndpoint) {
		glog.Infoln("processNextItem(): annotation not found, skipping item")
		c.queue.Forget(key)
		return true
	}
	if err := c.processItem(pvc); err != nil {
		glog.Errorf("processNextItem(): error processing key: %v", err)
		c.queue.Forget(key)
	}
	return true
}

func (c *Controller) processItem(pvc *v1.PersistentVolumeClaim) error {
	// DO STUFF
	return nil
}

func (c *Controller) pvcFromKey(key interface{}) (*v1.PersistentVolumeClaim, error) {
	keyString, ok := key.(string)
	if !ok {
		return nil, fmt.Errorf("pvcFromKey(): key object not of type string\n")
	}
	obj, ok, err := c.pvcInformer.GetIndexer().GetByKey(keyString)
	if !ok {
		return nil, nil
	}
	if err != nil {
		return nil, fmt.Errorf("pvcFromKey(): Error getting key from cache: %q\n", keyString)
	}
	pvc, ok := obj.(*v1.PersistentVolumeClaim)
	if !ok {
		return nil, fmt.Errorf("pvcFromKey(): Object not of type *v1.PersistentVolumeClaim\n")
	}
	return pvc, nil
}
<|endoftext|>


<commit_before>/*
Copyright 2016 Skippbox, Ltd.

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 controller

import (
	"fmt"
	"os"
	"os/signal"
	"syscall"
	"time"

	"github.com/Sirupsen/logrus"
	"github.com/bitnami-labs/kubewatch/config"
	"github.com/bitnami-labs/kubewatch/pkg/handlers"
	"github.com/bitnami-labs/kubewatch/pkg/utils"

	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/watch"
	"k8s.io/apimachinery/pkg/util/wait"
	utilruntime "k8s.io/apimachinery/pkg/util/runtime"
	api_v1 "k8s.io/api/core/v1"
	batch_v1 "k8s.io/api/batch/v1"
	meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/tools/cache"
	"k8s.io/client-go/util/workqueue"
	"k8s.io/client-go/kubernetes"
)

const maxRetries = 5

// Controller object
type Controller struct {
	logger       *logrus.Entry
	clientset    kubernetes.Interface
	queue        workqueue.RateLimitingInterface
	informer     cache.SharedIndexInformer
	eventHandler handlers.Handler
	kubType      string
}

func Start(conf *config.Config, eventHandler handlers.Handler) {
	kubeClient := utils.GetClientOutOfCluster()

	if conf.Resource.Pod {
		c := newControllerPod(kubeClient, eventHandler)
		stopCh := make(chan struct{})
		defer close(stopCh)

		go c.Run(stopCh)
	}

	if conf.Resource.Services {
		c := newControllerService(kubeClient, eventHandler)
		stopCh := make(chan struct{})
		defer close(stopCh)

		go c.Run(stopCh)
	}

	if conf.Resource.Deployment {
		c := newControllerDeployment(kubeClient, eventHandler)
		stopCh := make(chan struct{})
		defer close(stopCh)

		go c.Run(stopCh)
	}

	if conf.Resource.Namespace {
		c := newControllerNamespace(kubeClient, eventHandler)
		stopCh := make(chan struct{})
		defer close(stopCh)

		go c.Run(stopCh)
	}

	if conf.Resource.ReplicationController {
		c := newControllerReplicationController(kubeClient, eventHandler)
		stopCh := make(chan struct{})
		defer close(stopCh)

		go c.Run(stopCh)
	}

	if conf.Resource.Job {
		c := newControllerJob(kubeClient, eventHandler)
		stopCh := make(chan struct{})
		defer close(stopCh)

		go c.Run(stopCh)
	}

	if conf.Resource.PersistentVolume {
		c := newControllerPersistentVolume(kubeClient, eventHandler)
		stopCh := make(chan struct{})
		defer close(stopCh)

		go c.Run(stopCh)
	}

	sigterm := make(chan os.Signal, 1)
	signal.Notify(sigterm, syscall.SIGTERM)
	signal.Notify(sigterm, syscall.SIGINT)
	<-sigterm
}

func newControllerJob(client kubernetes.Interface, eventHandler handlers.Handler) *Controller {
	queue := workqueue.NewRateLimitingQueue(workqueue.DefaultControllerRateLimiter())

	informer := cache.NewSharedIndexInformer(
		&cache.ListWatch{
			ListFunc: func(options meta_v1.ListOptions) (runtime.Object, error) {
				return client.BatchV1().Jobs(meta_v1.NamespaceAll).List(options)
			},
			WatchFunc: func(options meta_v1.ListOptions) (watch.Interface, error) {
				return client.BatchV1().Jobs(meta_v1.NamespaceAll).Watch(options)
			},
		},
		&batch_v1.Job{},
		0, //Skip resync
		cache.Indexers{},
	)

	informer.AddEventHandler(cache.ResourceEventHandlerFuncs{
		AddFunc: func(obj interface{}) {
			key, err := cache.MetaNamespaceKeyFunc(obj)
			if err == nil {
				queue.Add(key)
			}
		},
		UpdateFunc: func(old, new interface{}) {
			key, err := cache.MetaNamespaceKeyFunc(new)
			if err == nil {
				queue.Add(key)
			}
		},
		DeleteFunc: func(obj interface{}) {
			key, err := cache.DeletionHandlingMetaNamespaceKeyFunc(obj)
			if err == nil {
				queue.Add(key)
			}
		},
	})

	return &Controller{
		logger:       logrus.WithField("pkg", "kubewatch-job"),
		clientset:    client,
		informer:     informer,
		queue:        queue,
		eventHandler: eventHandler,
	}
}

func newControllerPersistentVolume(client kubernetes.Interface, eventHandler handlers.Handler) *Controller {
	queue := workqueue.NewRateLimitingQueue(workqueue.DefaultControllerRateLimiter())

	informer := cache.NewSharedIndexInformer(
		&cache.ListWatch{
			ListFunc: func(options meta_v1.ListOptions) (runtime.Object, error) {
				return client.CoreV1().PersistentVolumes().List(options)
			},
			WatchFunc: func(options meta_v1.ListOptions) (watch.Interface, error) {
				return client.CoreV1().PersistentVolumes().Watch(options)
			},
		},
		&api_v1.PersistentVolume{},
		0, //Skip resync
		cache.Indexers{},
	)

	informer.AddEventHandler(cache.ResourceEventHandlerFuncs{
		AddFunc: func(obj interface{}) {
			key, err := cache.MetaNamespaceKeyFunc(obj)
			if err == nil {
				queue.Add(key)
			}
		},
		UpdateFunc: func(old, new interface{}) {
			key, err := cache.MetaNamespaceKeyFunc(new)
			if err == nil {
				queue.Add(key)
			}
		},
		DeleteFunc: func(obj interface{}) {
			key, err := cache.DeletionHandlingMetaNamespaceKeyFunc(obj)
			if err == nil {
				queue.Add(key)
			}
		},
	})

	return &Controller{
		logger:       logrus.WithField("pkg", "kubewatch-persistentvolume"),
		clientset:    client,
		informer:     informer,
		queue:        queue,
		eventHandler: eventHandler,
		kubType:      "persistent volume",
	}
}

func newControllerNamespace(client kubernetes.Interface, eventHandler handlers.Handler) *Controller {
	queue := workqueue.NewRateLimitingQueue(workqueue.DefaultControllerRateLimiter())

	informer := cache.NewSharedIndexInformer(
		&cache.ListWatch{
			ListFunc: func(options meta_v1.ListOptions) (runtime.Object, error) {
				return client.CoreV1().Namespaces().List(options)
			},
			WatchFunc: func(options meta_v1.ListOptions) (watch.Interface, error) {
				return client.CoreV1().Namespaces().Watch(options)
			},
		},
		&api_v1.Namespace{},
		0, //Skip resync
		cache.Indexers{},
	)

	informer.AddEventHandler(cache.ResourceEventHandlerFuncs{
		AddFunc: func(obj interface{}) {
			key, err := cache.MetaNamespaceKeyFunc(obj)
			if err == nil {
				queue.Add(key)
			}
		},
		UpdateFunc: func(old, new interface{}) {
			key, err := cache.MetaNamespaceKeyFunc(new)
			if err == nil {
				queue.Add(key)
			}
		},
		DeleteFunc: func(obj interface{}) {
			key, err := cache.DeletionHandlingMetaNamespaceKeyFunc(obj)
			if err == nil {
				queue.Add(key)
			}
		},
	})

	return &Controller{
		logger:       logrus.WithField("pkg", "kubewatch-namespace"),
		clientset:    client,
		informer:     informer,
		queue:        queue,
		eventHandler: eventHandler,
		kubType:      "namespace",
	}
}

func newControllerReplicationController(client kubernetes.Interface, eventHandler handlers.Handler) *Controller {
	queue := workqueue.NewRateLimitingQueue(workqueue.DefaultControllerRateLimiter())

	informer := cache.NewSharedIndexInformer(
		&cache.ListWatch{
			ListFunc: func(options meta_v1.ListOptions) (runtime.Object, error) {
				return client.CoreV1().ReplicationControllers(meta_v1.NamespaceAll).List(options)
			},
			WatchFunc: func(options meta_v1.ListOptions) (watch.Interface, error) {
				return client.CoreV1().ReplicationControllers(meta_v1.NamespaceAll).Watch(options)
			},
		},
		&api_v1.ReplicationController{},
		0, //Skip resync
		cache.Indexers{},
	)

	informer.AddEventHandler(cache.ResourceEventHandlerFuncs{
		AddFunc: func(obj interface{}) {
			key, err := cache.MetaNamespaceKeyFunc(obj)
			if err == nil {
				queue.Add(key)
			}
		},
		UpdateFunc: func(old, new interface{}) {
			key, err := cache.MetaNamespaceKeyFunc(new)
			if err == nil {
				queue.Add(key)
			}
		},
		DeleteFunc: func(obj interface{}) {
			key, err := cache.DeletionHandlingMetaNamespaceKeyFunc(obj)
			if err == nil {
				queue.Add(key)
			}
		},
	})

	return &Controller{
		logger:       logrus.WithField("pkg", "kubewatch-replicationcontroller"),
		clientset:    client,
		informer:     informer,
		queue:        queue,
		eventHandler: eventHandler,
		kubType:      "replication controller",
	}
}

func newControllerPod(client kubernetes.Interface, eventHandler handlers.Handler) *Controller {
	queue := workqueue.NewRateLimitingQueue(workqueue.DefaultControllerRateLimiter())

	informer := cache.NewSharedIndexInformer(
		&cache.ListWatch{
			ListFunc: func(options meta_v1.ListOptions) (runtime.Object, error) {
				return client.CoreV1().Pods(meta_v1.NamespaceAll).List(options)
			},
			WatchFunc: func(options meta_v1.ListOptions) (watch.Interface, error) {
				return client.CoreV1().Pods(meta_v1.NamespaceAll).Watch(options)
			},
		},
		&api_v1.Pod{},
		0, //Skip resync
		cache.Indexers{},
	)

	informer.AddEventHandler(cache.ResourceEventHandlerFuncs{
		AddFunc: func(obj interface{}) {
			key, err := cache.MetaNamespaceKeyFunc(obj)
			if err == nil {
				queue.Add(key)
			}
		},
		UpdateFunc: func(old, new interface{}) {
			key, err := cache.MetaNamespaceKeyFunc(new)
			if err == nil {
				queue.Add(key)
			}
		},
		DeleteFunc: func(obj interface{}) {
			key, err := cache.DeletionHandlingMetaNamespaceKeyFunc(obj)
			if err == nil {
				queue.Add(key)
			}
		},
	})

	return &Controller{
		logger:       logrus.WithField("pkg", "kubewatch-pod"),
		clientset:    client,
		informer:     informer,
		queue:        queue,
		eventHandler: eventHandler,
		kubType:      "pod",
	}
}

func newControllerService(client kubernetes.Interface, eventHandler handlers.Handler) *Controller {
	queue := workqueue.NewRateLimitingQueue(workqueue.DefaultControllerRateLimiter())

	informer := cache.NewSharedIndexInformer(
		&cache.ListWatch{
			ListFunc: func(options meta_v1.ListOptions) (runtime.Object, error) {
				return client.CoreV1().Services(meta_v1.NamespaceAll).List(options)
			},
			WatchFunc: func(options meta_v1.ListOptions) (watch.Interface, error) {
				return client.CoreV1().Services(meta_v1.NamespaceAll).Watch(options)
			},
		},
		&api_v1.Service{},
		0, //Skip resync
		cache.Indexers{},
	)

	informer.AddEventHandler(cache.ResourceEventHandlerFuncs{
		AddFunc: func(obj interface{}) {
			key, err := cache.MetaNamespaceKeyFunc(obj)
			if err == nil {
				queue.Add(key)
			}
		},
		UpdateFunc: func(old, new interface{}) {
			key, err := cache.MetaNamespaceKeyFunc(new)
			if err == nil {
				queue.Add(key)
			}
		},
		DeleteFunc: func(obj interface{}) {
			key, err := cache.DeletionHandlingMetaNamespaceKeyFunc(obj)
			if err == nil {
				queue.Add(key)
			}
		},
	})

	return &Controller{
		logger:       logrus.WithField("pkg", "kubewatch-service"),
		clientset:    client,
		informer:     informer,
		queue:        queue,
		eventHandler: eventHandler,
		kubType:      "service",
	}
}

func newControllerDeployment(client kubernetes.Interface, eventHandler handlers.Handler) *Controller {
	queue := workqueue.NewRateLimitingQueue(workqueue.DefaultControllerRateLimiter())

	informer := cache.NewSharedIndexInformer(
		&cache.ListWatch{
			ListFunc: func(options meta_v1.ListOptions) (runtime.Object, error) {
				return client.AppsV1beta1().Deployments(meta_v1.NamespaceAll).List(options)
			},
			WatchFunc: func(options meta_v1.ListOptions) (watch.Interface, error) {
				return client.AppsV1beta1().Deployments(meta_v1.NamespaceAll).Watch(options)
			},
		},
		&api_v1.Service{},
		0, //Skip resync
		cache.Indexers{},
	)

	informer.AddEventHandler(cache.ResourceEventHandlerFuncs{
		AddFunc: func(obj interface{}) {
			key, err := cache.MetaNamespaceKeyFunc(obj)
			if err == nil {
				queue.Add(key)
			}
		},
		UpdateFunc: func(old, new interface{}) {
			key, err := cache.MetaNamespaceKeyFunc(new)
			if err == nil {
				queue.Add(key)
			}
		},
		DeleteFunc: func(obj interface{}) {
			key, err := cache.DeletionHandlingMetaNamespaceKeyFunc(obj)
			if err == nil {
				queue.Add(key)
			}
		},
	})

	return &Controller{
		logger:       logrus.WithField("pkg", "kubewatch-service"),
		clientset:    client,
		informer:     informer,
		queue:        queue,
		eventHandler: eventHandler,
		kubType:      "service",
	}
}

// Run starts the kubewatch controller
func (c *Controller) Run(stopCh <-chan struct{}) {
	defer utilruntime.HandleCrash()
	defer c.queue.ShutDown()

	c.logger.Info("Starting kubewatch controller")

	go c.informer.Run(stopCh)

	if !cache.WaitForCacheSync(stopCh, c.HasSynced) {
		utilruntime.HandleError(fmt.Errorf("Timed out waiting for caches to sync"))
		return
	}

	c.logger.Info("Kubewatch controller synced and ready")

	wait.Until(c.runWorker, time.Second, stopCh)
}

// HasSynced is required for the cache.Controller interface.
func (c *Controller) HasSynced() bool {
	return c.informer.HasSynced()
}

// LastSyncResourceVersion is required for the cache.Controller interface.
func (c *Controller) LastSyncResourceVersion() string {
	return c.informer.LastSyncResourceVersion()
}

func (c *Controller) runWorker() {
	for c.processNextItem() {
		// continue looping
	}
}

func (c *Controller) processNextItem() bool {
	key, quit := c.queue.Get()
	kobj := c.kubType

	if quit {
		return false
	}
	defer c.queue.Done(key)

	err := c.processItem(key.(string), kobj)
	if err == nil {
		// No error, reset the ratelimit counters
		c.queue.Forget(key)
	} else if c.queue.NumRequeues(key) < maxRetries {
		c.logger.Errorf("Error processing %s (will retry): %v", key, err)
		c.queue.AddRateLimited(key)
	} else {
		// err != nil and too many retries
		c.logger.Errorf("Error processing %s (giving up): %v", key, err)
		c.queue.Forget(key)
		utilruntime.HandleError(err)
	}

	return true
}

func (c *Controller) processItem(key string, kobj string) error {
	c.logger.Infof("Processing change to %v: %s", kobj, key)

	obj, exists, err := c.informer.GetIndexer().GetByKey(key)
	if err != nil {
		return fmt.Errorf("Error fetching object with key %s from store: %v", key, err)
	}

	if !exists {
		c.eventHandler.ObjectDeleted(obj)
		return nil
	}

	c.eventHandler.ObjectCreated(obj)
	return nil
}
<commit_msg>controller/controller.go: squash controller functions into single func, fix deployment return type<commit_after>/*
Copyright 2016 Skippbox, Ltd.

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 controller

import (
	"fmt"
	"os"
	"os/signal"
	"syscall"
	"time"

	"github.com/Sirupsen/logrus"
	"github.com/bitnami-labs/kubewatch/config"
	"github.com/bitnami-labs/kubewatch/pkg/handlers"
	"github.com/bitnami-labs/kubewatch/pkg/utils"

	apps_v1beta1 "k8s.io/api/apps/v1beta1"
	batch_v1 "k8s.io/api/batch/v1"
	api_v1 "k8s.io/api/core/v1"
	meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	utilruntime "k8s.io/apimachinery/pkg/util/runtime"
	"k8s.io/apimachinery/pkg/util/wait"
	"k8s.io/apimachinery/pkg/watch"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/tools/cache"
	"k8s.io/client-go/util/workqueue"
)

const maxRetries = 5

// Controller object
type Controller struct {
	logger       *logrus.Entry
	clientset    kubernetes.Interface
	queue        workqueue.RateLimitingInterface
	informer     cache.SharedIndexInformer
	eventHandler handlers.Handler
	kubType      string
}

func Start(conf *config.Config, eventHandler handlers.Handler) {
	kubeClient := utils.GetClientOutOfCluster()

	if conf.Resource.Pod {
		informer := cache.NewSharedIndexInformer(
			&cache.ListWatch{
				ListFunc: func(options meta_v1.ListOptions) (runtime.Object, error) {
					return kubeClient.CoreV1().Pods(meta_v1.NamespaceAll).List(options)
				},
				WatchFunc: func(options meta_v1.ListOptions) (watch.Interface, error) {
					return kubeClient.CoreV1().Pods(meta_v1.NamespaceAll).Watch(options)
				},
			},
			&api_v1.Pod{},
			0, //Skip resync
			cache.Indexers{},
		)

		c := newResourceController(kubeClient, eventHandler, informer, "pod")
		stopCh := make(chan struct{})
		defer close(stopCh)

		go c.Run(stopCh)
	}

	if conf.Resource.Services {
		informer := cache.NewSharedIndexInformer(
			&cache.ListWatch{
				ListFunc: func(options meta_v1.ListOptions) (runtime.Object, error) {
					return kubeClient.CoreV1().Services(meta_v1.NamespaceAll).List(options)
				},
				WatchFunc: func(options meta_v1.ListOptions) (watch.Interface, error) {
					return kubeClient.CoreV1().Services(meta_v1.NamespaceAll).Watch(options)
				},
			},
			&api_v1.Service{},
			0, //Skip resync
			cache.Indexers{},
		)

		c := newResourceController(kubeClient, eventHandler, informer, "service")
		stopCh := make(chan struct{})
		defer close(stopCh)

		go c.Run(stopCh)
	}

	if conf.Resource.Deployment {
		informer := cache.NewSharedIndexInformer(
			&cache.ListWatch{
				ListFunc: func(options meta_v1.ListOptions) (runtime.Object, error) {
					return kubeClient.AppsV1beta1().Deployments(meta_v1.NamespaceAll).List(options)
				},
				WatchFunc: func(options meta_v1.ListOptions) (watch.Interface, error) {
					return kubeClient.AppsV1beta1().Deployments(meta_v1.NamespaceAll).Watch(options)
				},
			},
			&apps_v1beta1.Deployment{},
			0, //Skip resync
			cache.Indexers{},
		)

		c := newResourceController(kubeClient, eventHandler, informer, "deployment")
		stopCh := make(chan struct{})
		defer close(stopCh)

		go c.Run(stopCh)
	}

	if conf.Resource.Namespace {
		informer := cache.NewSharedIndexInformer(
			&cache.ListWatch{
				ListFunc: func(options meta_v1.ListOptions) (runtime.Object, error) {
					return kubeClient.CoreV1().Namespaces().List(options)
				},
				WatchFunc: func(options meta_v1.ListOptions) (watch.Interface, error) {
					return kubeClient.CoreV1().Namespaces().Watch(options)
				},
			},
			&api_v1.Namespace{},
			0, //Skip resync
			cache.Indexers{},
		)

		c := newResourceController(kubeClient, eventHandler, informer, "namespace")
		stopCh := make(chan struct{})
		defer close(stopCh)

		go c.Run(stopCh)
	}

	if conf.Resource.ReplicationController {
		informer := cache.NewSharedIndexInformer(
			&cache.ListWatch{
				ListFunc: func(options meta_v1.ListOptions) (runtime.Object, error) {
					return kubeClient.CoreV1().ReplicationControllers(meta_v1.NamespaceAll).List(options)
				},
				WatchFunc: func(options meta_v1.ListOptions) (watch.Interface, error) {
					return kubeClient.CoreV1().ReplicationControllers(meta_v1.NamespaceAll).Watch(options)
				},
			},
			&api_v1.ReplicationController{},
			0, //Skip resync
			cache.Indexers{},
		)

		c := newResourceController(kubeClient, eventHandler, informer, "replication controller")
		stopCh := make(chan struct{})
		defer close(stopCh)

		go c.Run(stopCh)
	}

	if conf.Resource.Job {
		informer := cache.NewSharedIndexInformer(
			&cache.ListWatch{
				ListFunc: func(options meta_v1.ListOptions) (runtime.Object, error) {
					return kubeClient.BatchV1().Jobs(meta_v1.NamespaceAll).List(options)
				},
				WatchFunc: func(options meta_v1.ListOptions) (watch.Interface, error) {
					return kubeClient.BatchV1().Jobs(meta_v1.NamespaceAll).Watch(options)
				},
			},
			&batch_v1.Job{},
			0, //Skip resync
			cache.Indexers{},
		)

		c := newResourceController(kubeClient, eventHandler, informer, "job")
		stopCh := make(chan struct{})
		defer close(stopCh)

		go c.Run(stopCh)
	}

	if conf.Resource.PersistentVolume {
		informer := cache.NewSharedIndexInformer(
			&cache.ListWatch{
				ListFunc: func(options meta_v1.ListOptions) (runtime.Object, error) {
					return kubeClient.CoreV1().PersistentVolumes().List(options)
				},
				WatchFunc: func(options meta_v1.ListOptions) (watch.Interface, error) {
					return kubeClient.CoreV1().PersistentVolumes().Watch(options)
				},
			},
			&api_v1.PersistentVolume{},
			0, //Skip resync
			cache.Indexers{},
		)

		c := newResourceController(kubeClient, eventHandler, informer, "persistent volume")
		stopCh := make(chan struct{})
		defer close(stopCh)

		go c.Run(stopCh)
	}

	sigterm := make(chan os.Signal, 1)
	signal.Notify(sigterm, syscall.SIGTERM)
	signal.Notify(sigterm, syscall.SIGINT)
	<-sigterm
}

func newResourceController(client kubernetes.Interface, eventHandler handlers.Handler, informer cache.SharedIndexInformer, resourceType string) *Controller {
	queue := workqueue.NewRateLimitingQueue(workqueue.DefaultControllerRateLimiter())

	informer.AddEventHandler(cache.ResourceEventHandlerFuncs{
		AddFunc: func(obj interface{}) {
			key, err := cache.MetaNamespaceKeyFunc(obj)
			logrus.WithField("pkg", "kubewatch-"+resourceType).Infof("Processing add to %v: %s", resourceType, key)
			if err == nil {
				queue.Add(key)
			}
		},
		UpdateFunc: func(old, new interface{}) {
			key, err := cache.MetaNamespaceKeyFunc(new)
			logrus.WithField("pkg", "kubewatch-"+resourceType).Infof("Processing update to %v: %s", resourceType, key)
			if err == nil {
				queue.Add(key)
			}
		},
		DeleteFunc: func(obj interface{}) {
			key, err := cache.DeletionHandlingMetaNamespaceKeyFunc(obj)
			logrus.WithField("pkg", "kubewatch-"+resourceType).Infof("Processing delete to %v: %s", resourceType, key)
			if err == nil {
				queue.Add(key)
			}
		},
	})

	return &Controller{
		logger:       logrus.WithField("pkg", "kubewatch-"+resourceType),
		clientset:    client,
		informer:     informer,
		queue:        queue,
		eventHandler: eventHandler,
	}
}

// Run starts the kubewatch controller
func (c *Controller) Run(stopCh <-chan struct{}) {
	defer utilruntime.HandleCrash()
	defer c.queue.ShutDown()

	c.logger.Info("Starting kubewatch controller")

	go c.informer.Run(stopCh)

	if !cache.WaitForCacheSync(stopCh, c.HasSynced) {
		utilruntime.HandleError(fmt.Errorf("Timed out waiting for caches to sync"))
		return
	}

	c.logger.Info("Kubewatch controller synced and ready")

	wait.Until(c.runWorker, time.Second, stopCh)
}

// HasSynced is required for the cache.Controller interface.
func (c *Controller) HasSynced() bool {
	return c.informer.HasSynced()
}

// LastSyncResourceVersion is required for the cache.Controller interface.
func (c *Controller) LastSyncResourceVersion() string {
	return c.informer.LastSyncResourceVersion()
}

func (c *Controller) runWorker() {
	for c.processNextItem() {
		// continue looping
	}
}

func (c *Controller) processNextItem() bool {
	key, quit := c.queue.Get()
	kobj := c.kubType

	if quit {
		return false
	}
	defer c.queue.Done(key)

	err := c.processItem(key.(string), kobj)
	if err == nil {
		// No error, reset the ratelimit counters
		c.queue.Forget(key)
	} else if c.queue.NumRequeues(key) < maxRetries {
		c.logger.Errorf("Error processing %s (will retry): %v", key, err)
		c.queue.AddRateLimited(key)
	} else {
		// err != nil and too many retries
		c.logger.Errorf("Error processing %s (giving up): %v", key, err)
		c.queue.Forget(key)
		utilruntime.HandleError(err)
	}

	return true
}

func (c *Controller) processItem(key string, kobj string) error {
	obj, exists, err := c.informer.GetIndexer().GetByKey(key)
	if err != nil {
		return fmt.Errorf("Error fetching object with key %s from store: %v", key, err)
	}

	if !exists {
		c.eventHandler.ObjectDeleted(obj)
		return nil
	}

	c.eventHandler.ObjectCreated(obj)
	return nil
}
<|endoftext|>


<commit_before>package httphelper

import (
	"encoding/json"
	"fmt"
	"log"
	"net"
	"net/http"
	"reflect"
	"syscall"
	"time"

	"github.com/flynn/flynn/Godeps/_workspace/src/github.com/jackc/pgx"
	"github.com/flynn/flynn/Godeps/_workspace/src/github.com/julienschmidt/httprouter"
	"github.com/flynn/flynn/Godeps/_workspace/src/golang.org/x/net/context"
	"github.com/flynn/flynn/pkg/cors"
	"github.com/flynn/flynn/pkg/ctxhelper"
	"github.com/flynn/flynn/pkg/random"
)

type ErrorCode string

const (
	NotFoundErrorCode           ErrorCode = "not_found"
	ObjectNotFoundErrorCode     ErrorCode = "object_not_found"
	ObjectExistsErrorCode       ErrorCode = "object_exists"
	ConflictErrorCode           ErrorCode = "conflict"
	SyntaxErrorCode             ErrorCode = "syntax_error"
	ValidationErrorCode         ErrorCode = "validation_error"
	PreconditionFailedErrorCode ErrorCode = "precondition_failed"
	UnauthorizedErrorCode       ErrorCode = "unauthorized"
	UnknownErrorCode            ErrorCode = "unknown_error"
	RatelimitedErrorCode        ErrorCode = "ratelimited"
	ServiceUnavailableErrorCode ErrorCode = "service_unavailable"
)

var errorResponseCodes = map[ErrorCode]int{
	NotFoundErrorCode:           404,
	ObjectNotFoundErrorCode:     404,
	ObjectExistsErrorCode:       409,
	ConflictErrorCode:           409,
	PreconditionFailedErrorCode: 412,
	SyntaxErrorCode:             400,
	ValidationErrorCode:         400,
	UnauthorizedErrorCode:       401,
	UnknownErrorCode:            500,
	RatelimitedErrorCode:        429,
	ServiceUnavailableErrorCode: 503,
}

type JSONError struct {
	Code    ErrorCode       `json:"code"`
	Message string          `json:"message"`
	Detail  json.RawMessage `json:"detail,omitempty"`
	Retry   bool            `json:"retry"`
}

func isJSONErrorWithCode(err error, code ErrorCode) bool {
	e, ok := err.(JSONError)
	return ok && e.Code == code
}

func IsObjectNotFoundError(err error) bool {
	return isJSONErrorWithCode(err, ObjectNotFoundErrorCode)
}

func IsObjectExistsError(err error) bool {
	return isJSONErrorWithCode(err, ObjectExistsErrorCode)
}

func IsPreconditionFailedError(err error) bool {
	return isJSONErrorWithCode(err, PreconditionFailedErrorCode)
}

func IsValidationError(err error) bool {
	return isJSONErrorWithCode(err, ValidationErrorCode)
}

// IsRetryableError indicates whether a HTTP request can be safely retried.
func IsRetryableError(err error) bool {
	e, ok := err.(JSONError)
	return ok && e.Retry
}

var CORSAllowAll = &cors.Options{
	AllowAllOrigins:  true,
	AllowMethods:     []string{"GET", "POST", "PUT", "PATCH", "DELETE", "HEAD"},
	AllowHeaders:     []string{"Authorization", "Accept", "Content-Type", "If-Match", "If-None-Match"},
	ExposeHeaders:    []string{"ETag", "Content-Disposition"},
	AllowCredentials: true,
	MaxAge:           time.Hour,
}

// Handler is an extended version of http.Handler that also takes a context
// argument ctx.
type Handler interface {
	ServeHTTP(ctx context.Context, w http.ResponseWriter, r *http.Request)
}

// The HandlerFunc type is an adapter to allow the use of ordinary functions as
// Handlers.  If f is a function with the appropriate signature, HandlerFunc(f)
// is a Handler object that calls f.
type HandlerFunc func(context.Context, http.ResponseWriter, *http.Request)

// ServeHTTP calls f(ctx, w, r).
func (f HandlerFunc) ServeHTTP(ctx context.Context, w http.ResponseWriter, r *http.Request) {
	f(ctx, w, r)
}

func WrapHandler(handler HandlerFunc) httprouter.Handle {
	return func(w http.ResponseWriter, req *http.Request, params httprouter.Params) {
		ctx := contextFromResponseWriter(w)
		ctx = ctxhelper.NewContextParams(ctx, params)
		handler.ServeHTTP(ctx, w, req)
	}
}

func ContextInjector(componentName string, handler http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
		reqID := req.Header.Get("X-Request-ID")
		if reqID == "" {
			reqID = random.UUID()
		}
		ctx := ctxhelper.NewContextRequestID(context.Background(), reqID)
		ctx = ctxhelper.NewContextComponentName(ctx, componentName)
		rw := NewResponseWriter(w, ctx)
		handler.ServeHTTP(rw, req)
	})
}

func contextFromResponseWriter(w http.ResponseWriter) context.Context {
	ctx := w.(*ResponseWriter).Context()
	return ctx
}

func (jsonError JSONError) Error() string {
	return fmt.Sprintf("%s: %s", jsonError.Code, jsonError.Message)
}

func logError(w http.ResponseWriter, err error) {
	if rw, ok := w.(*ResponseWriter); ok {
		logger, _ := ctxhelper.LoggerFromContext(rw.Context())
		logger.Error(err.Error())
	} else {
		log.Println(err)
	}
}

// buildJSONError returns an appropriate API error to send to clients based
// on the given internal error.
//
// We consider all postgres errors as retry-able as they usually occur when
// postgres is read-only (for example during a system update). Data related
// postgres errors should in general not be retried (because a retry will
// likely result in the same error), but it is expected that such errors are
// caught and a validation error returned to the client rather than the
// postgres error.
//
// Errors returned from "net" are also considered retryable because they
// generally occur when the process is trying to reach another resource which
// may be down temporarily.
// This also includes syscall.Errno, which is notable because it can also be
// returned from file operations among other things.
// It's expected if you don't want clients to retry on these errors they
// should be caught and a more appropriate error returned to the caller.
func buildJSONError(err error) *JSONError {
	jsonError := &JSONError{
		Code:    UnknownErrorCode,
		Message: "Something went wrong",
	}
	switch v := err.(type) {
	case *json.SyntaxError, *json.UnmarshalTypeError:
		jsonError = &JSONError{
			Code:    SyntaxErrorCode,
			Message: "The provided JSON input is invalid",
		}
	case pgx.PgError, *net.OpError, syscall.Errno:
		jsonError.Retry = true
	case JSONError:
		jsonError = &v
	case *JSONError:
		jsonError = v
	default:
		if err == pgx.ErrDeadConn {
			jsonError.Retry = true
		}
	}
	return jsonError
}

func Error(w http.ResponseWriter, err error) {
	if rw, ok := w.(*ResponseWriter); !ok || (ok && rw.Status() == 0) {
		jsonError := buildJSONError(err)
		if jsonError.Code == UnknownErrorCode {
			logError(w, err)
		}
		responseCode, ok := errorResponseCodes[jsonError.Code]
		if !ok {
			responseCode = 500
		}
		JSON(w, responseCode, jsonError)
	} else {
		logError(w, err)
	}
}

func ObjectNotFoundError(w http.ResponseWriter, message string) {
	Error(w, JSONError{Code: ObjectNotFoundErrorCode, Message: message})
}

func ObjectExistsErr(message string) error {
	return JSONError{Code: ObjectExistsErrorCode, Message: message}
}

func ObjectExistsError(w http.ResponseWriter, message string) {
	Error(w, ObjectExistsErr(message))
}

func ConflictError(w http.ResponseWriter, message string) {
	Error(w, JSONError{Code: ConflictErrorCode, Message: message})
}

func PreconditionFailedErr(message string) error {
	return JSONError{Code: PreconditionFailedErrorCode, Message: message}
}

func ServiceUnavailableError(w http.ResponseWriter, message string) {
	Error(w, JSONError{Code: ServiceUnavailableErrorCode, Message: message})
}

func ValidationError(w http.ResponseWriter, field, message string) {
	err := JSONError{Code: ValidationErrorCode, Message: message}
	if field != "" {
		err.Message = fmt.Sprintf("%s %s", field, message)
		err.Detail, _ = json.Marshal(map[string]string{"field": field})
	}
	Error(w, err)
}

func JSON(w http.ResponseWriter, status int, v interface{}) {
	// Encode nil slices as `[]` instead of `null`
	if rv := reflect.ValueOf(v); rv.Type().Kind() == reflect.Slice && rv.IsNil() {
		v = []struct{}{}
	}

	var result []byte
	var err error
	result, err = json.Marshal(v)
	if err != nil {
		http.Error(w, err.Error(), 500)
		return
	}

	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(status)
	w.Write(result)
}

func DecodeJSON(req *http.Request, i interface{}) error {
	dec := json.NewDecoder(req.Body)
	return dec.Decode(i)
}
<commit_msg>pkg/httphelper: Decode numbers into interface{} as json.Number<commit_after>package httphelper

import (
	"encoding/json"
	"fmt"
	"log"
	"net"
	"net/http"
	"reflect"
	"syscall"
	"time"

	"github.com/flynn/flynn/Godeps/_workspace/src/github.com/jackc/pgx"
	"github.com/flynn/flynn/Godeps/_workspace/src/github.com/julienschmidt/httprouter"
	"github.com/flynn/flynn/Godeps/_workspace/src/golang.org/x/net/context"
	"github.com/flynn/flynn/pkg/cors"
	"github.com/flynn/flynn/pkg/ctxhelper"
	"github.com/flynn/flynn/pkg/random"
)

type ErrorCode string

const (
	NotFoundErrorCode           ErrorCode = "not_found"
	ObjectNotFoundErrorCode     ErrorCode = "object_not_found"
	ObjectExistsErrorCode       ErrorCode = "object_exists"
	ConflictErrorCode           ErrorCode = "conflict"
	SyntaxErrorCode             ErrorCode = "syntax_error"
	ValidationErrorCode         ErrorCode = "validation_error"
	PreconditionFailedErrorCode ErrorCode = "precondition_failed"
	UnauthorizedErrorCode       ErrorCode = "unauthorized"
	UnknownErrorCode            ErrorCode = "unknown_error"
	RatelimitedErrorCode        ErrorCode = "ratelimited"
	ServiceUnavailableErrorCode ErrorCode = "service_unavailable"
)

var errorResponseCodes = map[ErrorCode]int{
	NotFoundErrorCode:           404,
	ObjectNotFoundErrorCode:     404,
	ObjectExistsErrorCode:       409,
	ConflictErrorCode:           409,
	PreconditionFailedErrorCode: 412,
	SyntaxErrorCode:             400,
	ValidationErrorCode:         400,
	UnauthorizedErrorCode:       401,
	UnknownErrorCode:            500,
	RatelimitedErrorCode:        429,
	ServiceUnavailableErrorCode: 503,
}

type JSONError struct {
	Code    ErrorCode       `json:"code"`
	Message string          `json:"message"`
	Detail  json.RawMessage `json:"detail,omitempty"`
	Retry   bool            `json:"retry"`
}

func isJSONErrorWithCode(err error, code ErrorCode) bool {
	e, ok := err.(JSONError)
	return ok && e.Code == code
}

func IsObjectNotFoundError(err error) bool {
	return isJSONErrorWithCode(err, ObjectNotFoundErrorCode)
}

func IsObjectExistsError(err error) bool {
	return isJSONErrorWithCode(err, ObjectExistsErrorCode)
}

func IsPreconditionFailedError(err error) bool {
	return isJSONErrorWithCode(err, PreconditionFailedErrorCode)
}

func IsValidationError(err error) bool {
	return isJSONErrorWithCode(err, ValidationErrorCode)
}

// IsRetryableError indicates whether a HTTP request can be safely retried.
func IsRetryableError(err error) bool {
	e, ok := err.(JSONError)
	return ok && e.Retry
}

var CORSAllowAll = &cors.Options{
	AllowAllOrigins:  true,
	AllowMethods:     []string{"GET", "POST", "PUT", "PATCH", "DELETE", "HEAD"},
	AllowHeaders:     []string{"Authorization", "Accept", "Content-Type", "If-Match", "If-None-Match"},
	ExposeHeaders:    []string{"ETag", "Content-Disposition"},
	AllowCredentials: true,
	MaxAge:           time.Hour,
}

// Handler is an extended version of http.Handler that also takes a context
// argument ctx.
type Handler interface {
	ServeHTTP(ctx context.Context, w http.ResponseWriter, r *http.Request)
}

// The HandlerFunc type is an adapter to allow the use of ordinary functions as
// Handlers.  If f is a function with the appropriate signature, HandlerFunc(f)
// is a Handler object that calls f.
type HandlerFunc func(context.Context, http.ResponseWriter, *http.Request)

// ServeHTTP calls f(ctx, w, r).
func (f HandlerFunc) ServeHTTP(ctx context.Context, w http.ResponseWriter, r *http.Request) {
	f(ctx, w, r)
}

func WrapHandler(handler HandlerFunc) httprouter.Handle {
	return func(w http.ResponseWriter, req *http.Request, params httprouter.Params) {
		ctx := contextFromResponseWriter(w)
		ctx = ctxhelper.NewContextParams(ctx, params)
		handler.ServeHTTP(ctx, w, req)
	}
}

func ContextInjector(componentName string, handler http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
		reqID := req.Header.Get("X-Request-ID")
		if reqID == "" {
			reqID = random.UUID()
		}
		ctx := ctxhelper.NewContextRequestID(context.Background(), reqID)
		ctx = ctxhelper.NewContextComponentName(ctx, componentName)
		rw := NewResponseWriter(w, ctx)
		handler.ServeHTTP(rw, req)
	})
}

func contextFromResponseWriter(w http.ResponseWriter) context.Context {
	ctx := w.(*ResponseWriter).Context()
	return ctx
}

func (jsonError JSONError) Error() string {
	return fmt.Sprintf("%s: %s", jsonError.Code, jsonError.Message)
}

func logError(w http.ResponseWriter, err error) {
	if rw, ok := w.(*ResponseWriter); ok {
		logger, _ := ctxhelper.LoggerFromContext(rw.Context())
		logger.Error(err.Error())
	} else {
		log.Println(err)
	}
}

// buildJSONError returns an appropriate API error to send to clients based
// on the given internal error.
//
// We consider all postgres errors as retry-able as they usually occur when
// postgres is read-only (for example during a system update). Data related
// postgres errors should in general not be retried (because a retry will
// likely result in the same error), but it is expected that such errors are
// caught and a validation error returned to the client rather than the
// postgres error.
//
// Errors returned from "net" are also considered retryable because they
// generally occur when the process is trying to reach another resource which
// may be down temporarily.
// This also includes syscall.Errno, which is notable because it can also be
// returned from file operations among other things.
// It's expected if you don't want clients to retry on these errors they
// should be caught and a more appropriate error returned to the caller.
func buildJSONError(err error) *JSONError {
	jsonError := &JSONError{
		Code:    UnknownErrorCode,
		Message: "Something went wrong",
	}
	switch v := err.(type) {
	case *json.SyntaxError, *json.UnmarshalTypeError:
		jsonError = &JSONError{
			Code:    SyntaxErrorCode,
			Message: "The provided JSON input is invalid",
		}
	case pgx.PgError, *net.OpError, syscall.Errno:
		jsonError.Retry = true
	case JSONError:
		jsonError = &v
	case *JSONError:
		jsonError = v
	default:
		if err == pgx.ErrDeadConn {
			jsonError.Retry = true
		}
	}
	return jsonError
}

func Error(w http.ResponseWriter, err error) {
	if rw, ok := w.(*ResponseWriter); !ok || (ok && rw.Status() == 0) {
		jsonError := buildJSONError(err)
		if jsonError.Code == UnknownErrorCode {
			logError(w, err)
		}
		responseCode, ok := errorResponseCodes[jsonError.Code]
		if !ok {
			responseCode = 500
		}
		JSON(w, responseCode, jsonError)
	} else {
		logError(w, err)
	}
}

func ObjectNotFoundError(w http.ResponseWriter, message string) {
	Error(w, JSONError{Code: ObjectNotFoundErrorCode, Message: message})
}

func ObjectExistsErr(message string) error {
	return JSONError{Code: ObjectExistsErrorCode, Message: message}
}

func ObjectExistsError(w http.ResponseWriter, message string) {
	Error(w, ObjectExistsErr(message))
}

func ConflictError(w http.ResponseWriter, message string) {
	Error(w, JSONError{Code: ConflictErrorCode, Message: message})
}

func PreconditionFailedErr(message string) error {
	return JSONError{Code: PreconditionFailedErrorCode, Message: message}
}

func ServiceUnavailableError(w http.ResponseWriter, message string) {
	Error(w, JSONError{Code: ServiceUnavailableErrorCode, Message: message})
}

func ValidationError(w http.ResponseWriter, field, message string) {
	err := JSONError{Code: ValidationErrorCode, Message: message}
	if field != "" {
		err.Message = fmt.Sprintf("%s %s", field, message)
		err.Detail, _ = json.Marshal(map[string]string{"field": field})
	}
	Error(w, err)
}

func JSON(w http.ResponseWriter, status int, v interface{}) {
	// Encode nil slices as `[]` instead of `null`
	if rv := reflect.ValueOf(v); rv.Type().Kind() == reflect.Slice && rv.IsNil() {
		v = []struct{}{}
	}

	var result []byte
	var err error
	result, err = json.Marshal(v)
	if err != nil {
		http.Error(w, err.Error(), 500)
		return
	}

	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(status)
	w.Write(result)
}

func DecodeJSON(req *http.Request, i interface{}) error {
	dec := json.NewDecoder(req.Body)
	dec.UseNumber()
	return dec.Decode(i)
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"github.com/dotcloud/docker/pkg/flag"
)

var (
	i    int
	str  string
	b, h bool
)

func init() {
	flag.BoolVar(&b, []string{"b"}, false, "a simple bool")
	flag.IntVar(&i, []string{"#integer", "-integer"}, -1, "a simple integer")
	flag.StringVar(&str, []string{"s", "#hidden", "-string"}, "", "a simple string") //-s -hidden and --string will work, but -hidden won't be in the usage
	flag.BoolVar(&h, []string{"h", "#help", "-help"}, false, "display the help")
	flag.Parse()
}
func main() {
	if h {
		flag.PrintDefaults()
	}
	fmt.Printf("%s\n", str)
	fmt.Printf("%s\n", flag.Lookup("s").Value.String())
}
<commit_msg>fix mflag import<commit_after>package main

import (
	"fmt"
	flag "github.com/dotcloud/docker/pkg/mflag"
)

var (
	i    int
	str  string
	b, h bool
)

func init() {
	flag.BoolVar(&b, []string{"b"}, false, "a simple bool")
	flag.IntVar(&i, []string{"#integer", "-integer"}, -1, "a simple integer")
	flag.StringVar(&str, []string{"s", "#hidden", "-string"}, "", "a simple string") //-s -hidden and --string will work, but -hidden won't be in the usage
	flag.BoolVar(&h, []string{"h", "#help", "-help"}, false, "display the help")
	flag.Parse()
}
func main() {
	if h {
		flag.PrintDefaults()
	}
	fmt.Printf("%s\n", str)
	fmt.Printf("%s\n", flag.Lookup("s").Value.String())
}
<|endoftext|>


<commit_before>// Package syslogd provides a library to write syslog servers.
//
// Example
//
//	sys := syslogd.NewServer(syslog.Options{SockAddr: config.C.SockAddr, UnixPath: config.C.UnixPath})
//	go func() {
//		for {
//			msg := sys.Next()
//			if msg == nil {
//				// no more messages, server exiting
//			}
//	}()
package syslogd

import (
	"bufio"
	"net"
	"os"
	"sync"
	"time"
)

const defaultSockAddr = ":514"
const defaultUnixPath = "/dev/log"
const defaultBufferSize = 128 * 1024

// Options contain all configuration required for operating a syslog server.
// If one or more options are blank, unix syslogd defaults are used.
type Options struct {
	// SockAddr contains the listening port for UDP and TCP listeners, default is ":514"
	SockAddr string

	// UnixPath contains the unix socket path for unix syslogging. If you are running
	// this a the primary server on a unix system set this to "/dev/log" (which is default)
	// otherwise you're free to choose a path depending on what your application uses.
	UnixPath string

	// BufferSize contains the maximum number of messages queued before we block.
	// Defaults to 128k
	BufferSize int

	// LogDir defines the path where to store logfiles. Leave empty to not write logfiles.
	LogDir string
}

// Server contains internal data for syslog server processes.
type Server struct {
	tcp  *net.TCPConn
	udp  *net.UDPConn
	unix *net.UnixConn
	bus  chan *Message
	stop chan bool
	opts Options
	arch *archive
}

// Keep track of 'active' sending nodes.
var activeNodes map[string]time.Time
var nodeLock sync.RWMutex

func init() {
	activeNodes = make(map[string]time.Time)
}

// NumActiveNodes returns the number of nodes which have sent messages during the last X secs.
func NumActiveNodes(secs int) int {
	nodeLock.RLock()
	defer nodeLock.RUnlock()
	num := 0
	for _, n := range activeNodes {
		if time.Now().Sub(n) < time.Duration(secs)*time.Second {
			num++
		}
	}
	return num
}

func (s *Server) listenUDP() error {
	a, err := net.ResolveUDPAddr("udp", s.opts.SockAddr)
	if err != nil {
		return err
	}
	s.udp, err = net.ListenUDP("udp", a)
	if err != nil {
		return err
	}
	go s.receivePacket(s.udp)
	return nil
}

func (s *Server) listenUnix() error {
	os.Remove(s.opts.UnixPath)
	a, err := net.ResolveUnixAddr("unixgram", s.opts.UnixPath)
	if err != nil {
		return err
	}
	s.unix, err = net.ListenUnixgram("unixgram", a)
	if err != nil {
		return err
	}
	err = os.Chmod(s.opts.UnixPath, 0666)
	if err != nil {
		return err
	}
	go s.receivePacket(s.unix)
	return nil
}

func (s *Server) listenTCP() error {
	sock, err := net.Listen("tcp", s.opts.SockAddr)
	if err != nil {
		return err
	}

	go func() {
		accpt := make(chan net.Conn)
		for {
			go func() {
				client, err := sock.Accept()
				if err != nil {
					sock.Close()
					s.Close()
					return
				}
				accpt <- client
			}()

			select {
			case client := <-accpt:
				// Accepted new client connection.
				go s.receiveTCP(client)
			}
		}
	}()
	return nil
}

func (s *Server) receivePacket(con net.PacketConn) {
	buf := make([]byte, 4096)
	for {
		n, addr, err := con.ReadFrom(buf)
		if err != nil {
			return
		}
		nodeLock.Lock()
		activeNodes[addr.String()] = time.Now()
		nodeLock.Unlock()
		s.processBuf(buf, n)
	}
}

func (s *Server) receiveTCP(con net.Conn) {
	buf := bufio.NewReader(con)
	for {
		buf, err := buf.ReadBytes('\n')
		if err != nil {
			return
		}
		nodeLock.Lock()
		activeNodes[con.RemoteAddr().String()] = time.Now()
		nodeLock.Unlock()
		s.processBuf(buf, 0)
	}
}

func (s *Server) processBuf(b []byte, n int) {
	msg, err := NewMessage(b, n)
	if err != nil {
		panic(err)
	}
	if msg != nil {
		s.bus <- msg
		s.arch.write(msg)
	}
}

// Next retrieves the next message from the syslog queue.
func (s *Server) Next() *Message {
	select {
	case m := <-s.bus:
		return m
	case <-s.stop:
		return nil
	}
}

// Close closes the syslog server.
func (s *Server) Close() {
	s.stop <- true
	s.arch.CloseAll()
}

// NewServer creates and initializes a new syslog server process.
func NewServer(opts Options) *Server {
	s := new(Server)
	s.bus = make(chan *Message, 128*1024)

	if opts.SockAddr == "" {
		opts.SockAddr = defaultSockAddr
	}
	if opts.UnixPath == "" {
		opts.UnixPath = defaultUnixPath
	}
	if opts.BufferSize == 0 {
		opts.BufferSize = defaultBufferSize
	}
	s.opts = opts

	s.bus = make(chan *Message, opts.BufferSize)
	s.stop = make(chan bool)
	s.arch = newArchive(opts.LogDir)

	err := s.listenUnix()
	if err != nil {
		panic(err)
	}
	err = s.listenUDP()
	if err != nil {
		panic(err)
	}
	err = s.listenTCP()
	if err != nil {
		panic(err)
	}
	return s
}
<commit_msg>Don't panic on unparsable syslog messages.<commit_after>// Package syslogd provides a library to write syslog servers.
//
// Example
//
//	sys := syslogd.NewServer(syslog.Options{SockAddr: config.C.SockAddr, UnixPath: config.C.UnixPath})
//	go func() {
//		for {
//			msg := sys.Next()
//			if msg == nil {
//				// no more messages, server exiting
//			}
//	}()
package syslogd

import (
	"bufio"
	"fmt"
	"net"
	"os"
	"sync"
	"time"
)

const defaultSockAddr = ":514"
const defaultUnixPath = "/dev/log"
const defaultBufferSize = 128 * 1024

// Options contain all configuration required for operating a syslog server.
// If one or more options are blank, unix syslogd defaults are used.
type Options struct {
	// SockAddr contains the listening port for UDP and TCP listeners, default is ":514"
	SockAddr string

	// UnixPath contains the unix socket path for unix syslogging. If you are running
	// this a the primary server on a unix system set this to "/dev/log" (which is default)
	// otherwise you're free to choose a path depending on what your application uses.
	UnixPath string

	// BufferSize contains the maximum number of messages queued before we block.
	// Defaults to 128k
	BufferSize int

	// LogDir defines the path where to store logfiles. Leave empty to not write logfiles.
	LogDir string
}

// Server contains internal data for syslog server processes.
type Server struct {
	tcp  *net.TCPConn
	udp  *net.UDPConn
	unix *net.UnixConn
	bus  chan *Message
	stop chan bool
	opts Options
	arch *archive
}

// Keep track of 'active' sending nodes.
var activeNodes map[string]time.Time
var nodeLock sync.RWMutex

func init() {
	activeNodes = make(map[string]time.Time)
}

// NumActiveNodes returns the number of nodes which have sent messages during the last X secs.
func NumActiveNodes(secs int) int {
	nodeLock.RLock()
	defer nodeLock.RUnlock()
	num := 0
	for _, n := range activeNodes {
		if time.Now().Sub(n) < time.Duration(secs)*time.Second {
			num++
		}
	}
	return num
}

func (s *Server) listenUDP() error {
	a, err := net.ResolveUDPAddr("udp", s.opts.SockAddr)
	if err != nil {
		return err
	}
	s.udp, err = net.ListenUDP("udp", a)
	if err != nil {
		return err
	}
	go s.receivePacket(s.udp)
	return nil
}

func (s *Server) listenUnix() error {
	os.Remove(s.opts.UnixPath)
	a, err := net.ResolveUnixAddr("unixgram", s.opts.UnixPath)
	if err != nil {
		return err
	}
	s.unix, err = net.ListenUnixgram("unixgram", a)
	if err != nil {
		return err
	}
	err = os.Chmod(s.opts.UnixPath, 0666)
	if err != nil {
		return err
	}
	go s.receivePacket(s.unix)
	return nil
}

func (s *Server) listenTCP() error {
	sock, err := net.Listen("tcp", s.opts.SockAddr)
	if err != nil {
		return err
	}

	go func() {
		accpt := make(chan net.Conn)
		for {
			go func() {
				client, err := sock.Accept()
				if err != nil {
					sock.Close()
					s.Close()
					return
				}
				accpt <- client
			}()

			select {
			case client := <-accpt:
				// Accepted new client connection.
				go s.receiveTCP(client)
			}
		}
	}()
	return nil
}

func (s *Server) receivePacket(con net.PacketConn) {
	buf := make([]byte, 4096)
	for {
		n, addr, err := con.ReadFrom(buf)
		if err != nil {
			return
		}
		nodeLock.Lock()
		activeNodes[addr.String()] = time.Now()
		nodeLock.Unlock()
		s.processBuf(buf, n)
	}
}

func (s *Server) receiveTCP(con net.Conn) {
	buf := bufio.NewReader(con)
	for {
		buf, err := buf.ReadBytes('\n')
		if err != nil {
			return
		}
		nodeLock.Lock()
		activeNodes[con.RemoteAddr().String()] = time.Now()
		nodeLock.Unlock()
		s.processBuf(buf, 0)
	}
}

func (s *Server) processBuf(b []byte, n int) {
	msg, err := NewMessage(b, n)
	if err != nil {
		fmt.Printf("%v\n", err)
		return
	}
	if msg != nil {
		s.bus <- msg
		s.arch.write(msg)
	}
}

// Next retrieves the next message from the syslog queue.
func (s *Server) Next() *Message {
	select {
	case m := <-s.bus:
		return m
	case <-s.stop:
		return nil
	}
}

// Close closes the syslog server.
func (s *Server) Close() {
	s.stop <- true
	s.arch.CloseAll()
}

// NewServer creates and initializes a new syslog server process.
func NewServer(opts Options) *Server {
	s := new(Server)
	s.bus = make(chan *Message, 128*1024)

	if opts.SockAddr == "" {
		opts.SockAddr = defaultSockAddr
	}
	if opts.UnixPath == "" {
		opts.UnixPath = defaultUnixPath
	}
	if opts.BufferSize == 0 {
		opts.BufferSize = defaultBufferSize
	}
	s.opts = opts

	s.bus = make(chan *Message, opts.BufferSize)
	s.stop = make(chan bool)
	s.arch = newArchive(opts.LogDir)

	err := s.listenUnix()
	if err != nil {
		panic(err)
	}
	err = s.listenUDP()
	if err != nil {
		panic(err)
	}
	err = s.listenTCP()
	if err != nil {
		panic(err)
	}
	return s
}
<|endoftext|>


<commit_before>// Copyright 2017 Authors of Cilium
//
// 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 serializer

var (
	// NoRetry always returns false independently of the number of retries.
	NoRetry = func(int) bool { return false }
)

// WaitFunc will be invoked each time a queued function has returned an error.
// nRetries will be set to the number of consecutive execution failures that
// have occurred so far. The WaitFunc must return true if execution must be
// retried or false if the function must be returned from the queue.
type WaitFunc func(nRetries int) bool

type queuedFunction struct {
	f        func() error
	waitFunc WaitFunc
}

type functionQueue struct {
	queue  chan queuedFunction
	stopCh chan struct{}
}

// NewFunctionQueue returns a functionQueue that will be used to execute
// functions in the same order they are enqueued.
func NewFunctionQueue(queueSize uint) *functionQueue {
	fq := &functionQueue{
		queue:  make(chan queuedFunction, queueSize),
		stopCh: make(chan struct{}),
	}
	go fq.run()
	return fq
}

// run starts the functionQueue internal worker. It will be stopped once
// `stopCh` is closed or receives a value.
func (fq *functionQueue) run() {
	for {
		select {
		case <-fq.stopCh:
			return
		case f := <-fq.queue:
			retries := 0
			for {
				select {
				case <-fq.stopCh:
					return
				default:
				}
				retries++
				if err := f.f(); err != nil {
					if !f.waitFunc(retries) {
						break
					}
				} else {
					break
				}
			}
		}
	}
}

// Stop stops the function queue from processing the functions on the queue.
// If there are functions in the queue waiting for them to be processed, they
// won't be executed.
func (fq *functionQueue) Stop() {
	close(fq.stopCh)
}

// Enqueue enqueues the receiving function `f` to be executed by the function
// queue. Depending on the size of the function queue and the amount
// of functions queued, this function can block until the function queue
// is ready to receive more requests.
// If `f` returns an error, `waitFunc` will be executed and, depending on the
// return value of `waitFunc`, `f` will be executed again or not.
// The return value of `f` will not be logged and it's up to the caller to log
// it properly.
func (fq *functionQueue) Enqueue(f func() error, waitFunc WaitFunc) {
	fq.queue <- queuedFunction{f: f, waitFunc: waitFunc}
}
<commit_msg>pkg/serializer: made FunctionQueue struct public<commit_after>// Copyright 2017 Authors of Cilium
//
// 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 serializer

var (
	// NoRetry always returns false independently of the number of retries.
	NoRetry = func(int) bool { return false }
)

// WaitFunc will be invoked each time a queued function has returned an error.
// nRetries will be set to the number of consecutive execution failures that
// have occurred so far. The WaitFunc must return true if execution must be
// retried or false if the function must be returned from the queue.
type WaitFunc func(nRetries int) bool

type queuedFunction struct {
	f        func() error
	waitFunc WaitFunc
}

type FunctionQueue struct {
	queue  chan queuedFunction
	stopCh chan struct{}
}

// NewFunctionQueue returns a FunctionQueue that will be used to execute
// functions in the same order they are enqueued.
func NewFunctionQueue(queueSize uint) *FunctionQueue {
	fq := &FunctionQueue{
		queue:  make(chan queuedFunction, queueSize),
		stopCh: make(chan struct{}),
	}
	go fq.run()
	return fq
}

// run starts the FunctionQueue internal worker. It will be stopped once
// `stopCh` is closed or receives a value.
func (fq *FunctionQueue) run() {
	for {
		select {
		case <-fq.stopCh:
			return
		case f := <-fq.queue:
			retries := 0
			for {
				select {
				case <-fq.stopCh:
					return
				default:
				}
				retries++
				if err := f.f(); err != nil {
					if !f.waitFunc(retries) {
						break
					}
				} else {
					break
				}
			}
		}
	}
}

// Stop stops the function queue from processing the functions on the queue.
// If there are functions in the queue waiting for them to be processed, they
// won't be executed.
func (fq *FunctionQueue) Stop() {
	close(fq.stopCh)
}

// Enqueue enqueues the receiving function `f` to be executed by the function
// queue. Depending on the size of the function queue and the amount
// of functions queued, this function can block until the function queue
// is ready to receive more requests.
// If `f` returns an error, `waitFunc` will be executed and, depending on the
// return value of `waitFunc`, `f` will be executed again or not.
// The return value of `f` will not be logged and it's up to the caller to log
// it properly.
func (fq *FunctionQueue) Enqueue(f func() error, waitFunc WaitFunc) {
	fq.queue <- queuedFunction{f: f, waitFunc: waitFunc}
}
<|endoftext|>