<commit_before>package event

import (
	"github.com/Aptomi/aptomi/pkg/errors"
	"github.com/Aptomi/aptomi/pkg/runtime"
	"github.com/Sirupsen/logrus"
	"io/ioutil"
)

// Fields is a set of named fields. Fields are attached to every log record
type Fields map[string]interface{}

// Log is an buffered event log.
// It stores all log entries in memory first, then allows them to be processed and stored
type Log struct {
	logger      *logrus.Logger
	hookMemory  *HookMemory
	level       logrus.Level
	scope       string
	fixedFields map[string]string
}

// NewLog creates a new instance of event log.
// Initially it just buffers all entries and doesn't write them.
// It needs to buffer all entries, so that the context can be later attached to them
// before they get serialized and written to an external source
func NewLog(level logrus.Level, scope string, logToConsole bool) *Log {
	logger := &logrus.Logger{
		Out:       ioutil.Discard,
		Formatter: new(logrus.TextFormatter),
		Hooks:     make(logrus.LevelHooks),
		Level:     level,
	}

	hookMemory := &HookMemory{}
	logger.Hooks.Add(hookMemory)

	if logToConsole {
		logger.Hooks.Add(NewHookConsole())
	}

	return &Log{
		logger:     logger,
		hookMemory: hookMemory,
		level:      level,
		scope:      scope,
		fixedFields: map[string]string{
			"scope": scope,
		},
	}
}

// GetLevel returns log level for the event log
func (eventLog *Log) GetLevel() logrus.Level {
	return eventLog.level
}

// GetScope returns scope for event log
func (eventLog *Log) GetScope() string {
	return eventLog.scope
}

// Replaces base objects with their string key value
func fieldValue(data interface{}) interface{} {
	if baseObject, ok := data.(runtime.Storable); ok {
		return runtime.KeyForStorable(baseObject)
	}
	return data
}

// WithFields creates a new log entry with a given set of fields
func (eventLog *Log) WithFields(fields Fields) *logrus.Entry {
	// see if there is any details which have to added to the log from the error
	for key, value := range fields {
		if errWithDetails, ok := value.(*errors.ErrorWithDetails); ok {
			// put details from the error into the same log record
			for dKey, dValue := range errWithDetails.Details() {
				fields[dKey] = fieldValue(dValue)
			}
			fields[key] = errWithDetails.Error()
		} else {
			fields[key] = fieldValue(value)
		}
	}

	// always add fixed fields
	for key, value := range eventLog.fixedFields {
		fields[key] = value
	}

	return eventLog.logger.WithFields(logrus.Fields(fields))
}

// AddFixedField adds field with name=values to add following entries in the log
func (eventLog *Log) AddFixedField(name string, value string) {
	eventLog.fixedFields[name] = value
}

// Append adds entries to the event logs
func (eventLog *Log) Append(that *Log) {
	eventLog.hookMemory.entries = append(eventLog.hookMemory.entries, that.hookMemory.entries...)
}

// LogError logs an error. Errors with details are processed specially, their details get unfolded as record fields
func (eventLog *Log) LogError(err error) {
	errWithDetails, isErrorWithDetails := err.(*errors.ErrorWithDetails)
	if isErrorWithDetails {
		eventLog.WithFields(Fields(errWithDetails.Details())).Error(err.Error())
	} else {
		eventLog.WithFields(Fields{}).Error(err.Error())
	}
}

// LogWarning logs a warning. Errors with details are processed specially, their details get unfolded as record fields
func (eventLog *Log) LogWarning(err error) {
	errWithDetails, isErrorWithDetails := err.(*errors.ErrorWithDetails)
	if isErrorWithDetails {
		eventLog.WithFields(Fields(errWithDetails.Details())).Warning(err.Error())
	} else {
		eventLog.WithFields(Fields{}).Warning(err.Error())
	}
}

// Save takes all buffered event log entries and saves them
func (eventLog *Log) Save(hook logrus.Hook) {
	for _, e := range eventLog.hookMemory.entries {
		err := hook.Fire(e)
		if err != nil {
			panic(err)
		}
	}
}
<commit_msg>LogWarning is no longer needed<commit_after>package event

import (
	"github.com/Aptomi/aptomi/pkg/errors"
	"github.com/Aptomi/aptomi/pkg/runtime"
	"github.com/Sirupsen/logrus"
	"io/ioutil"
)

// Fields is a set of named fields. Fields are attached to every log record
type Fields map[string]interface{}

// Log is an buffered event log.
// It stores all log entries in memory first, then allows them to be processed and stored
type Log struct {
	logger      *logrus.Logger
	hookMemory  *HookMemory
	level       logrus.Level
	scope       string
	fixedFields map[string]string
}

// NewLog creates a new instance of event log.
// Initially it just buffers all entries and doesn't write them.
// It needs to buffer all entries, so that the context can be later attached to them
// before they get serialized and written to an external source
func NewLog(level logrus.Level, scope string, logToConsole bool) *Log {
	logger := &logrus.Logger{
		Out:       ioutil.Discard,
		Formatter: new(logrus.TextFormatter),
		Hooks:     make(logrus.LevelHooks),
		Level:     level,
	}

	hookMemory := &HookMemory{}
	logger.Hooks.Add(hookMemory)

	if logToConsole {
		logger.Hooks.Add(NewHookConsole())
	}

	return &Log{
		logger:     logger,
		hookMemory: hookMemory,
		level:      level,
		scope:      scope,
		fixedFields: map[string]string{
			"scope": scope,
		},
	}
}

// GetLevel returns log level for the event log
func (eventLog *Log) GetLevel() logrus.Level {
	return eventLog.level
}

// GetScope returns scope for event log
func (eventLog *Log) GetScope() string {
	return eventLog.scope
}

// Replaces base objects with their string key value
func fieldValue(data interface{}) interface{} {
	if baseObject, ok := data.(runtime.Storable); ok {
		return runtime.KeyForStorable(baseObject)
	}
	return data
}

// WithFields creates a new log entry with a given set of fields
func (eventLog *Log) WithFields(fields Fields) *logrus.Entry {
	// see if there is any details which have to added to the log from the error
	for key, value := range fields {
		if errWithDetails, ok := value.(*errors.ErrorWithDetails); ok {
			// put details from the error into the same log record
			for dKey, dValue := range errWithDetails.Details() {
				fields[dKey] = fieldValue(dValue)
			}
			fields[key] = errWithDetails.Error()
		} else {
			fields[key] = fieldValue(value)
		}
	}

	// always add fixed fields
	for key, value := range eventLog.fixedFields {
		fields[key] = value
	}

	return eventLog.logger.WithFields(logrus.Fields(fields))
}

// AddFixedField adds field with name=values to add following entries in the log
func (eventLog *Log) AddFixedField(name string, value string) {
	eventLog.fixedFields[name] = value
}

// Append adds entries to the event logs
func (eventLog *Log) Append(that *Log) {
	eventLog.hookMemory.entries = append(eventLog.hookMemory.entries, that.hookMemory.entries...)
}

// LogError logs an error. Errors with details are processed specially, their details get unfolded as record fields
func (eventLog *Log) LogError(err error) {
	errWithDetails, isErrorWithDetails := err.(*errors.ErrorWithDetails)
	if isErrorWithDetails {
		eventLog.WithFields(Fields(errWithDetails.Details())).Error(err.Error())
	} else {
		eventLog.WithFields(Fields{}).Error(err.Error())
	}
}

// Save takes all buffered event log entries and saves them
func (eventLog *Log) Save(hook logrus.Hook) {
	for _, e := range eventLog.hookMemory.entries {
		err := hook.Fire(e)
		if err != nil {
			panic(err)
		}
	}
}
<|endoftext|>


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

package install

import (
	"fmt"
	"os/exec"
	"runtime"
	"strings"
	"time"
)

var servingVersion = "1.0.0"
var kourierVersion = "1.0.0"
var eventingVersion = "1.0.0"

// Kourier installs Kourier networking layer from Github YAML files
func Kourier() error {
	fmt.Println("🕸 Installing Kourier networking layer v" + kourierVersion + " ...")

	if err := retryingApply("https://github.com/knative-sandbox/net-kourier/releases/download/knative-v" + kourierVersion + "/kourier.yaml"); err != nil {
		return fmt.Errorf("wait: %w", err)
	}
	if err := waitForPodsReady("kourier-system"); err != nil {
		return fmt.Errorf("kourier: %w", err)
	}
	if err := waitForPodsReady("knative-serving"); err != nil {
		return fmt.Errorf("serving: %w", err)
	}
	fmt.Println("    Kourier installed...")

	ingress := exec.Command("kubectl", "patch", "configmap/config-network", "--namespace", "knative-serving", "--type", "merge", "--patch", "{\"data\":{\"ingress.class\":\"kourier.ingress.networking.knative.dev\"}}")
	if err := runCommand(ingress); err != nil {
		return fmt.Errorf("ingress error: %w", err)
	}
	fmt.Println("    Ingress patched...")

	fmt.Println("    Finished installing Kourier Networking layer")

	return nil
}

// KourierKind runs the kind-specific setup for Kourier
func KourierKind() error {
	fmt.Println("🕸 Configuring Kourier for Kind...")

	config := `apiVersion: v1
kind: Service
metadata:
  name: kourier-ingress
  namespace: kourier-system
  labels:
    networking.knative.dev/ingress-provider: kourier
spec:
  type: NodePort
  selector:
    app: 3scale-kourier-gateway
  ports:
    - name: http2
      nodePort: 31080
      port: 80
      targetPort: 8080`

	kourierIngress := exec.Command("kubectl", "apply", "-f", "-")
	kourierIngress.Stdin = strings.NewReader(config)
	if err := runCommand(kourierIngress); err != nil {
		return fmt.Errorf("kourier service: %w", err)
	}

	fmt.Println("    Kourier service installed...")

	domainDns := exec.Command("kubectl", "patch", "configmap", "-n", "knative-serving", "config-domain", "-p", "{\"data\": {\"127.0.0.1.nip.io\": \"\"}}")
	if err := domainDns.Run(); err != nil {
		return fmt.Errorf("domain dns: %w", err)
	}
	fmt.Println("    Domain DNS set up...")
	fmt.Println("    Finished configuring Kourier")

	return nil
}

// KourierMinikube runs the minikube-specific setup for Kourier
func KourierMinikube() error {
	fmt.Println("🕸 Configuring Kourier for Minikube...")

	if err := retryingApply("https://github.com/knative/serving/releases/download/knative-v" + servingVersion + "/serving-default-domain.yaml"); err != nil {
		return fmt.Errorf("default domain: %w", err)
	}
	if err := waitForPodsReady("knative-serving"); err != nil {
		return fmt.Errorf("core: %w", err)
	}

	fmt.Println("    Domain DNS set up...")

	// For windows and mac users, do not automatically spawn a tunnel
	// Instead, they will be directed to create one manually after
	// the plugin finishes
	if runtime.GOOS != "window" && runtime.GOOS != "darwin" {
		tunnel := exec.Command("minikube", "tunnel", "--profile", "minikube-knative")
		if err := tunnel.Start(); err != nil {
			return fmt.Errorf("tunnel: %w", err)
		}
		fmt.Println("    Minikube tunnel...")
	}

	fmt.Println("    Finished configuring Kourier")
	return nil
}

// Serving installs Knative Serving from Github YAML files
func Serving() error {
	fmt.Println("🍿 Installing Knative Serving v" + servingVersion + " ...")
	baseURL := "https://github.com/knative/serving/releases/download/knative-v" + servingVersion

	if err := retryingApply(baseURL + "/serving-crds.yaml"); err != nil {
		return fmt.Errorf("wait: %w", err)
	}

	if err := waitForCRDsEstablished(); err != nil {
		return fmt.Errorf("crds: %w", err)
	}
	fmt.Println("    CRDs installed...")

	if err := retryingApply(baseURL + "/serving-core.yaml"); err != nil {
		return fmt.Errorf("wait: %w", err)
	}

	if err := waitForPodsReady("knative-serving"); err != nil {
		return fmt.Errorf("core: %w", err)
	}

	fmt.Println("    Core installed...")

	fmt.Println("    Finished installing Knative Serving")

	return nil
}

// Eventing installs Knative Eventing from Github YAML files
func Eventing() error {
	fmt.Println("🔥 Installing Knative Eventing v" + eventingVersion + " ... ")
	baseURL := "https://github.com/knative/eventing/releases/download/knative-v" + eventingVersion

	if err := retryingApply(baseURL + "/eventing-crds.yaml"); err != nil {
		return fmt.Errorf("wait: %w", err)
	}

	if err := waitForCRDsEstablished(); err != nil {
		return fmt.Errorf("crds: %w", err)
	}
	fmt.Println("    CRDs installed...")

	if err := retryingApply(baseURL + "/eventing-core.yaml"); err != nil {
		return fmt.Errorf("wait: %w", err)
	}

	if err := waitForPodsReady("knative-eventing"); err != nil {
		return fmt.Errorf("core: %w", err)
	}
	fmt.Println("    Core installed...")

	if err := retryingApply(baseURL + "/in-memory-channel.yaml"); err != nil {
		return fmt.Errorf("wait: %w", err)
	}

	if err := waitForPodsReady("knative-eventing"); err != nil {
		return fmt.Errorf("channel: %w", err)
	}
	fmt.Println("    In-memory channel installed...")

	if err := retryingApply(baseURL + "/mt-channel-broker.yaml"); err != nil {
		return fmt.Errorf("wait: %w", err)
	}

	if err := waitForPodsReady("knative-eventing"); err != nil {
		return fmt.Errorf("broker: %w", err)
	}
	fmt.Println("    Mt-channel broker installed...")

	config := `apiVersion: eventing.knative.dev/v1
kind: broker
metadata:
 name: example-broker
 namespace: default`

	exampleBroker := exec.Command("kubectl", "apply", "-f", "-")
	exampleBroker.Stdin = strings.NewReader(config)
	if err := runCommand(exampleBroker); err != nil {
		return fmt.Errorf("example broker: %w", err)
	}

	fmt.Println("    Example broker installed...")
	fmt.Println("    Finished installing Knative Eventing")

	return nil
}

func runCommand(c *exec.Cmd) error {
	if out, err := c.CombinedOutput(); err != nil {
		fmt.Println(string(out))
		return err
	}
	return nil
}

// retryingApply retries a kubectl apply call with the given path 3 times, sleeping
// for 10s between each try.
func retryingApply(path string) error {
	cmd := exec.Command("kubectl", "apply", "-f", path)
	var err error
	for i := 0; i < 3; i++ {
		err = runCommand(cmd)
		if err == nil {
			break
		}
		time.Sleep(10 * time.Second)
	}
	return err
}

// waitForCRDsEstablished waits for all CRDs to be established.
func waitForCRDsEstablished() error {
	return runCommand(exec.Command("kubectl", "wait", "--for=condition=Established", "--all", "crd"))
}

// waitForPodsReady waits for all pods in the given namespace to be ready.
func waitForPodsReady(ns string) error {
	return runCommand(exec.Command("kubectl", "wait", "pod", "--timeout=10m", "--for=condition=Ready", "-l", "!job-name", "-n", ns))
}
<commit_msg>Use sslip.io for domain (#164)<commit_after>// Copyright © 2021 The Knative Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package install

import (
	"fmt"
	"os/exec"
	"runtime"
	"strings"
	"time"
)

var servingVersion = "1.0.0"
var kourierVersion = "1.0.0"
var eventingVersion = "1.0.0"

// Kourier installs Kourier networking layer from Github YAML files
func Kourier() error {
	fmt.Println("🕸 Installing Kourier networking layer v" + kourierVersion + " ...")

	if err := retryingApply("https://github.com/knative-sandbox/net-kourier/releases/download/knative-v" + kourierVersion + "/kourier.yaml"); err != nil {
		return fmt.Errorf("wait: %w", err)
	}
	if err := waitForPodsReady("kourier-system"); err != nil {
		return fmt.Errorf("kourier: %w", err)
	}
	if err := waitForPodsReady("knative-serving"); err != nil {
		return fmt.Errorf("serving: %w", err)
	}
	fmt.Println("    Kourier installed...")

	ingress := exec.Command("kubectl", "patch", "configmap/config-network", "--namespace", "knative-serving", "--type", "merge", "--patch", "{\"data\":{\"ingress.class\":\"kourier.ingress.networking.knative.dev\"}}")
	if err := runCommand(ingress); err != nil {
		return fmt.Errorf("ingress error: %w", err)
	}
	fmt.Println("    Ingress patched...")

	fmt.Println("    Finished installing Kourier Networking layer")

	return nil
}

// KourierKind runs the kind-specific setup for Kourier
func KourierKind() error {
	fmt.Println("🕸 Configuring Kourier for Kind...")

	config := `apiVersion: v1
kind: Service
metadata:
  name: kourier-ingress
  namespace: kourier-system
  labels:
    networking.knative.dev/ingress-provider: kourier
spec:
  type: NodePort
  selector:
    app: 3scale-kourier-gateway
  ports:
    - name: http2
      nodePort: 31080
      port: 80
      targetPort: 8080`

	kourierIngress := exec.Command("kubectl", "apply", "-f", "-")
	kourierIngress.Stdin = strings.NewReader(config)
	if err := runCommand(kourierIngress); err != nil {
		return fmt.Errorf("kourier service: %w", err)
	}

	fmt.Println("    Kourier service installed...")

	domainDns := exec.Command("kubectl", "patch", "configmap", "-n", "knative-serving", "config-domain", "-p", "{\"data\": {\"127.0.0.1.sslip.io\": \"\"}}")
	if err := domainDns.Run(); err != nil {
		return fmt.Errorf("domain dns: %w", err)
	}
	fmt.Println("    Domain DNS set up...")
	fmt.Println("    Finished configuring Kourier")

	return nil
}

// KourierMinikube runs the minikube-specific setup for Kourier
func KourierMinikube() error {
	fmt.Println("🕸 Configuring Kourier for Minikube...")

	if err := retryingApply("https://github.com/knative/serving/releases/download/knative-v" + servingVersion + "/serving-default-domain.yaml"); err != nil {
		return fmt.Errorf("default domain: %w", err)
	}
	if err := waitForPodsReady("knative-serving"); err != nil {
		return fmt.Errorf("core: %w", err)
	}

	fmt.Println("    Domain DNS set up...")

	// For windows and mac users, do not automatically spawn a tunnel
	// Instead, they will be directed to create one manually after
	// the plugin finishes
	if runtime.GOOS != "window" && runtime.GOOS != "darwin" {
		tunnel := exec.Command("minikube", "tunnel", "--profile", "minikube-knative")
		if err := tunnel.Start(); err != nil {
			return fmt.Errorf("tunnel: %w", err)
		}
		fmt.Println("    Minikube tunnel...")
	}

	fmt.Println("    Finished configuring Kourier")
	return nil
}

// Serving installs Knative Serving from Github YAML files
func Serving() error {
	fmt.Println("🍿 Installing Knative Serving v" + servingVersion + " ...")
	baseURL := "https://github.com/knative/serving/releases/download/knative-v" + servingVersion

	if err := retryingApply(baseURL + "/serving-crds.yaml"); err != nil {
		return fmt.Errorf("wait: %w", err)
	}

	if err := waitForCRDsEstablished(); err != nil {
		return fmt.Errorf("crds: %w", err)
	}
	fmt.Println("    CRDs installed...")

	if err := retryingApply(baseURL + "/serving-core.yaml"); err != nil {
		return fmt.Errorf("wait: %w", err)
	}

	if err := waitForPodsReady("knative-serving"); err != nil {
		return fmt.Errorf("core: %w", err)
	}

	fmt.Println("    Core installed...")

	fmt.Println("    Finished installing Knative Serving")

	return nil
}

// Eventing installs Knative Eventing from Github YAML files
func Eventing() error {
	fmt.Println("🔥 Installing Knative Eventing v" + eventingVersion + " ... ")
	baseURL := "https://github.com/knative/eventing/releases/download/knative-v" + eventingVersion

	if err := retryingApply(baseURL + "/eventing-crds.yaml"); err != nil {
		return fmt.Errorf("wait: %w", err)
	}

	if err := waitForCRDsEstablished(); err != nil {
		return fmt.Errorf("crds: %w", err)
	}
	fmt.Println("    CRDs installed...")

	if err := retryingApply(baseURL + "/eventing-core.yaml"); err != nil {
		return fmt.Errorf("wait: %w", err)
	}

	if err := waitForPodsReady("knative-eventing"); err != nil {
		return fmt.Errorf("core: %w", err)
	}
	fmt.Println("    Core installed...")

	if err := retryingApply(baseURL + "/in-memory-channel.yaml"); err != nil {
		return fmt.Errorf("wait: %w", err)
	}

	if err := waitForPodsReady("knative-eventing"); err != nil {
		return fmt.Errorf("channel: %w", err)
	}
	fmt.Println("    In-memory channel installed...")

	if err := retryingApply(baseURL + "/mt-channel-broker.yaml"); err != nil {
		return fmt.Errorf("wait: %w", err)
	}

	if err := waitForPodsReady("knative-eventing"); err != nil {
		return fmt.Errorf("broker: %w", err)
	}
	fmt.Println("    Mt-channel broker installed...")

	config := `apiVersion: eventing.knative.dev/v1
kind: broker
metadata:
 name: example-broker
 namespace: default`

	exampleBroker := exec.Command("kubectl", "apply", "-f", "-")
	exampleBroker.Stdin = strings.NewReader(config)
	if err := runCommand(exampleBroker); err != nil {
		return fmt.Errorf("example broker: %w", err)
	}

	fmt.Println("    Example broker installed...")
	fmt.Println("    Finished installing Knative Eventing")

	return nil
}

func runCommand(c *exec.Cmd) error {
	if out, err := c.CombinedOutput(); err != nil {
		fmt.Println(string(out))
		return err
	}
	return nil
}

// retryingApply retries a kubectl apply call with the given path 3 times, sleeping
// for 10s between each try.
func retryingApply(path string) error {
	cmd := exec.Command("kubectl", "apply", "-f", path)
	var err error
	for i := 0; i < 3; i++ {
		err = runCommand(cmd)
		if err == nil {
			break
		}
		time.Sleep(10 * time.Second)
	}
	return err
}

// waitForCRDsEstablished waits for all CRDs to be established.
func waitForCRDsEstablished() error {
	return runCommand(exec.Command("kubectl", "wait", "--for=condition=Established", "--all", "crd"))
}

// waitForPodsReady waits for all pods in the given namespace to be ready.
func waitForPodsReady(ns string) error {
	return runCommand(exec.Command("kubectl", "wait", "pod", "--timeout=10m", "--for=condition=Ready", "-l", "!job-name", "-n", ns))
}
<|endoftext|>


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

import (
	"encoding/json"
	"fmt"
	"net"
	"path"
	"strings"
	"sync"
	"time"

	"github.com/cilium/cilium/pkg/identity"
	"github.com/cilium/cilium/pkg/kvstore"
	"github.com/cilium/cilium/pkg/lock"
	"github.com/cilium/cilium/pkg/logging/logfields"

	"github.com/sirupsen/logrus"
)

const (
	// DefaultAddressSpace is the address space used if none is provided.
	// TODO - once pkg/node adds this to clusterConfiguration, remove.
	DefaultAddressSpace = "default"
)

var (
	// IPIdentitiesPath is the path to where endpoint IPs are stored in the key-value
	//store.
	IPIdentitiesPath = path.Join(kvstore.BaseKeyPrefix, "state", "ip", "v1")

	// AddressSpace is the address space (cluster, etc.) in which policy is
	// computed. It is determined by the orchestration system / runtime.
	AddressSpace = DefaultAddressSpace

	// globalMap wraps the kvstore and provides reference-tracking for keys
	// that are upserted or released from the kvstore.
	globalMap *kvReferenceCounter

	setupIPIdentityWatcher sync.Once
)

// store is a key-value store for an underlying implementation, provided to
// mock out the kvstore for unit testing.
type store interface {
	// update will insert the {key, value} tuple into the underlying
	// kvstore.
	upsert(key string, value []byte, lease bool) error

	// delete will remove the key from the underlying kvstore.
	release(key string) error
}

// kvstoreImplementation is a store implementation backed by the kvstore.
type kvstoreImplementation struct{}

// upsert places the mapping of {key, value} into the kvstore, optionally with
// a lease.
func (k kvstoreImplementation) upsert(key string, value []byte, lease bool) error {
	return kvstore.Update(key, value, lease)
}

// release removes the specified key from the kvstore.
func (k kvstoreImplementation) release(key string) error {
	return kvstore.Delete(key)
}

// kvReferenceCounter provides a thin wrapper around the kvstore which adds
// reference tracking for all entries being updated. When the first key is
// updated, it adds a reference to the kvstore and tracks the reference
// internally. Subsequent updates also update the kvstore, and add a reference.
// Deletes from the kvReferenceCounter are only propagated to the kvstore when
// the final reference is released.
//
// This has some small overlap with the pkg/kvstore/allocator but this is only
// a map from key to reference count rather than also tracking values.
type kvReferenceCounter struct {
	lock.Mutex
	store

	// keys is a map from key to reference count for locally-referenced
	// keys in the global kvstore.
	keys map[string]uint64
}

// newKVReferenceCounter creates a new reference counter using the specified
// store as the underlying location for key/value pairs to be stored.
func newKVReferenceCounter(s store) *kvReferenceCounter {
	return &kvReferenceCounter{
		store: s,
		keys:  map[string]uint64{},
	}
}

// upsert attempts to insert the specified {key, ipIDPair} into the kvstore. If
// the key has previously been upserted, increments a reference on the key.
// Always updates the underlying store with this {key, ipIDPair} tuple.
// Only adds a reference to the key if the upsert is successful.
func (r *kvReferenceCounter) upsert(ipKey string, ipIDPair identity.IPIdentityPair) error {
	marshaledIPIDPair, err := json.Marshal(ipIDPair)
	if err != nil {
		return err
	}

	log.WithFields(logrus.Fields{
		logfields.IPAddr:       ipIDPair.IP,
		logfields.IPMask:       ipIDPair.Mask,
		logfields.Identity:     ipIDPair.ID,
		logfields.Modification: Upsert,
	}).Debug("upserting IP->ID mapping to kvstore")

	r.Lock()
	defer r.Unlock()
	refcnt := r.keys[ipKey] // 0 if not found
	refcnt++
	err = r.store.upsert(ipKey, marshaledIPIDPair, true)
	if err == nil {
		r.keys[ipKey] = refcnt
	}
	return err
}

// release removes a reference to the specified key. If the number of
// references reaches 0, the key is removed from the underlying kvstore.
func (r *kvReferenceCounter) release(key string) (err error) {
	r.Lock()
	defer r.Unlock()

	refcnt, ok := r.keys[key] // 0 if not found
	if ok {
		refcnt--
	}

	if refcnt == 0 {
		err = r.store.release(key)
		delete(r.keys, key)
	} else {
		r.keys[key] = refcnt
	}
	return err
}

// UpsertIPToKVStore updates / inserts the provided IP->Identity mapping into the
// kvstore, which will subsequently trigger an event in NewIPIdentityWatcher().
func UpsertIPToKVStore(IP, hostIP net.IP, ID identity.NumericIdentity, metadata string) error {
	ipKey := path.Join(IPIdentitiesPath, AddressSpace, IP.String())
	ipIDPair := identity.IPIdentityPair{
		IP:       IP,
		ID:       ID,
		Metadata: metadata,
		HostIP:   hostIP,
	}

	return globalMap.upsert(ipKey, ipIDPair)
}

// upsertIPNetToKVStore updates / inserts the provided CIDR->Identity mapping
// into the kvstore, which will subsequently trigger an event in
// NewIPIdentityWatcher().
func upsertIPNetToKVStore(prefix *net.IPNet, ID *identity.Identity) error {
	// Reserved identities are handled locally, don't push them to kvstore.
	if ID.IsReserved() {
		return nil
	}

	ipKey := path.Join(IPIdentitiesPath, AddressSpace, prefix.String())
	ipIDPair := identity.IPIdentityPair{
		IP:       prefix.IP,
		Mask:     prefix.Mask,
		ID:       ID.ID,
		Metadata: AddressSpace, // XXX: Should we associate more metadata?
	}

	return globalMap.upsert(ipKey, ipIDPair)
}

// keyToIPNet returns the IPNet describing the key, whether it is a host, and
// an error (if one occurs)
func keyToIPNet(key string) (parsedPrefix *net.IPNet, host bool, err error) {
	requiredPrefix := fmt.Sprintf("%s/", path.Join(IPIdentitiesPath, AddressSpace))
	if !strings.HasPrefix(key, requiredPrefix) {
		err = fmt.Errorf("Found invalid key %s outside of prefix %s", key, IPIdentitiesPath)
		return
	}

	suffix := strings.TrimPrefix(key, requiredPrefix)

	// Key is formatted as "prefix/192.0.2.0/24" for CIDRs
	_, parsedPrefix, err = net.ParseCIDR(suffix)
	if err != nil {
		// Key is likely a host in the format "prefix/192.0.2.3"
		parsedIP := net.ParseIP(suffix)
		if parsedIP == nil {
			err = fmt.Errorf("unable to parse IP from suffix %s", suffix)
			return
		}
		err = nil
		host = true
		ipv4 := parsedIP.To4()
		bits := net.IPv6len * 8
		if ipv4 != nil {
			parsedIP = ipv4
			bits = net.IPv4len * 8
		}
		parsedPrefix = &net.IPNet{IP: parsedIP, Mask: net.CIDRMask(bits, bits)}
	}

	return
}

// upsertIPNetsToKVStore inserts a CIDR->Identity mapping into the kvstore
// ipcache for each of the specified prefixes and identities. That is to say,
// prefixes[0] is mapped to identities[0].
//
// If any Prefix->Identity mapping cannot be created, it will not create any
// of the mappings and returns an error.
//
// The caller should check the prefix lengths against the underlying IPCache
// implementation using CheckPrefixLengths prior to upserting to the kvstore.
func upsertIPNetsToKVStore(prefixes []*net.IPNet, identities []*identity.Identity) (err error) {
	if len(prefixes) != len(identities) {
		return fmt.Errorf("Invalid []Prefix->[]Identity ipcache mapping requested: prefixes=%d identities=%d", len(prefixes), len(identities))
	}
	for i, prefix := range prefixes {
		id := identities[i]
		err = upsertIPNetToKVStore(prefix, id)
		if err != nil {
			for j := 0; j < i; j++ {
				ipKey := path.Join(IPIdentitiesPath, AddressSpace, prefix.String())
				err2 := globalMap.release(ipKey)
				if err2 != nil {
					log.WithFields(logrus.Fields{
						"prefix": prefix.String(),
					}).Error("Failed to clean up CIDR->ID mappings")
				}
			}
		}
	}

	return
}

// DeleteIPFromKVStore removes the IP->Identity mapping for the specified ip
// from the kvstore, which will subsequently trigger an event in
// NewIPIdentityWatcher().
func DeleteIPFromKVStore(ip string) error {
	ipKey := path.Join(IPIdentitiesPath, AddressSpace, ip)
	return globalMap.release(ipKey)
}

// deleteIPNetsFromKVStore removes the Prefix->Identity mappings for the
// specified slice of prefixes from the kvstore, which will subsequently
// trigger an event in NewIPIdentityWatcher().
func deleteIPNetsFromKVStore(prefixes []*net.IPNet) (err error) {
	for _, prefix := range prefixes {
		ipKey := path.Join(IPIdentitiesPath, AddressSpace, prefix.String())
		if err2 := globalMap.release(ipKey); err2 != nil {
			err = err2
			log.WithFields(logrus.Fields{
				"prefix": prefix.String(),
			}).Error("Failed to delete CIDR->ID mappings")
		}
	}

	return
}

// IPIdentityWatcher is a watcher that will notify when IP<->identity mappings
// change in the kvstore
type IPIdentityWatcher struct {
	backend  kvstore.BackendOperations
	stop     chan struct{}
	stopOnce sync.Once
}

// NewIPIdentityWatcher creates a new IPIdentityWatcher using the specified
// kvstore backend
func NewIPIdentityWatcher(backend kvstore.BackendOperations) *IPIdentityWatcher {
	watcher := &IPIdentityWatcher{
		backend: backend,
		stop:    make(chan struct{}),
	}

	return watcher
}

// Watch starts the watcher and blocks waiting for events. When events are
// received from the kvstore, All IPIdentityMappingListener are notified. The
// function returns when IPIdentityWatcher.Close() is called. The watcher will
// automatically restart as required.
func (iw *IPIdentityWatcher) Watch() {
restart:
	watcher := iw.backend.ListAndWatch("endpointIPWatcher", IPIdentitiesPath, 512)

	for {
		select {
		// Get events from channel as they come in.
		case event, ok := <-watcher.Events:
			if !ok {
				log.Debugf("%s closed, restarting watch", watcher.String())
				time.Sleep(500 * time.Millisecond)
				goto restart
			}

			scopedLog := log.WithFields(logrus.Fields{"kvstore-event": event.Typ.String(), "key": event.Key})
			scopedLog.Debug("Received event")

			// Synchronize local caching of endpoint IP to ipIDPair mapping with
			// operation key-value store has informed us about.
			//
			// To resolve conflicts between hosts and full CIDR prefixes:
			// - Insert hosts into the cache as ".../w.x.y.z"
			// - Insert CIDRS into the cache as ".../w.x.y.z/N"
			// - If a host entry created, notify the listeners.
			// - If a CIDR is created and there's no overlapping host
			//   entry, ie it is a less than fully masked CIDR, OR
			//   it is a fully masked CIDR and there is no corresponding
			//   host entry, then:
			//   - Notify the listeners.
			//   - Otherwise, do not notify listeners.
			// - If a host is removed, check for an overlapping CIDR
			//   and if it exists, notify the listeners with an upsert
			//   for the CIDR's identity
			// - If any other deletion case, notify listeners of
			//   the deletion event.
			switch event.Typ {
			case kvstore.EventTypeListDone:
				IPIdentityCache.Lock()
				for _, listener := range IPIdentityCache.listeners {
					listener.OnIPIdentityCacheGC()
				}
				IPIdentityCache.Unlock()

			case kvstore.EventTypeCreate, kvstore.EventTypeModify:
				var ipIDPair identity.IPIdentityPair
				err := json.Unmarshal(event.Value, &ipIDPair)
				if err != nil {
					scopedLog.WithError(err).Errorf("Not adding entry to ip cache; error unmarshaling data from key-value store")
					continue
				}
				IPIdentityCache.Upsert(ipIDPair.PrefixString(), ipIDPair.HostIP, Identity{
					ID:     ipIDPair.ID,
					Source: FromKVStore,
				})

			case kvstore.EventTypeDelete:
				// Value is not present in deletion event;
				// need to convert kvstore key to IP.
				ipnet, isHost, err := keyToIPNet(event.Key)
				if err != nil {
					scopedLog.WithError(err).Error("Error parsing IP from key")
					continue
				}
				var ip string
				if isHost {
					ip = ipnet.IP.String()
				} else {
					ip = ipnet.String()
				}
				IPIdentityCache.Delete(ip)
			}

		case <-iw.stop:
			// identity watcher was stopped
			watcher.Stop()
			return
		}
	}
}

// Close stops the IPIdentityWatcher and causes Watch() to return
func (iw *IPIdentityWatcher) Close() {
	iw.stopOnce.Do(func() {
		close(iw.stop)
	})
}

// InitIPIdentityWatcher initializes the watcher for ip-identity mapping events
// in the key-value store.
func InitIPIdentityWatcher() {
	globalMap = newKVReferenceCounter(kvstoreImplementation{})
	setupIPIdentityWatcher.Do(func() {
		log.Info("Starting IP identity watcher")
		watch := NewIPIdentityWatcher(kvstore.Client())
		go watch.Watch()
	})
}
<commit_msg>ipcache: Re-create keys when necessary<commit_after>// Copyright 2018 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 ipcache

import (
	"encoding/json"
	"fmt"
	"net"
	"path"
	"strings"
	"sync"
	"time"

	"github.com/cilium/cilium/pkg/identity"
	"github.com/cilium/cilium/pkg/kvstore"
	"github.com/cilium/cilium/pkg/lock"
	"github.com/cilium/cilium/pkg/logging/logfields"

	"github.com/sirupsen/logrus"
)

const (
	// DefaultAddressSpace is the address space used if none is provided.
	// TODO - once pkg/node adds this to clusterConfiguration, remove.
	DefaultAddressSpace = "default"
)

var (
	// IPIdentitiesPath is the path to where endpoint IPs are stored in the key-value
	//store.
	IPIdentitiesPath = path.Join(kvstore.BaseKeyPrefix, "state", "ip", "v1")

	// AddressSpace is the address space (cluster, etc.) in which policy is
	// computed. It is determined by the orchestration system / runtime.
	AddressSpace = DefaultAddressSpace

	// globalMap wraps the kvstore and provides reference-tracking for keys
	// that are upserted or released from the kvstore.
	globalMap *kvReferenceCounter

	setupIPIdentityWatcher sync.Once
)

// store is a key-value store for an underlying implementation, provided to
// mock out the kvstore for unit testing.
type store interface {
	// update will insert the {key, value} tuple into the underlying
	// kvstore.
	upsert(key string, value []byte, lease bool) error

	// delete will remove the key from the underlying kvstore.
	release(key string) error
}

// kvstoreImplementation is a store implementation backed by the kvstore.
type kvstoreImplementation struct{}

// upsert places the mapping of {key, value} into the kvstore, optionally with
// a lease.
func (k kvstoreImplementation) upsert(key string, value []byte, lease bool) error {
	return kvstore.Update(key, value, lease)
}

// release removes the specified key from the kvstore.
func (k kvstoreImplementation) release(key string) error {
	return kvstore.Delete(key)
}

// kvReferenceCounter provides a thin wrapper around the kvstore which adds
// reference tracking for all entries being updated. When the first key is
// updated, it adds a reference to the kvstore and tracks the reference
// internally. Subsequent updates also update the kvstore, and add a reference.
// Deletes from the kvReferenceCounter are only propagated to the kvstore when
// the final reference is released.
//
// This has some small overlap with the pkg/kvstore/allocator but this is only
// a map from key to reference count rather than also tracking values.
type kvReferenceCounter struct {
	lock.Mutex
	store

	// keys is a map from key to reference count for locally-referenced
	// keys in the global kvstore.
	keys map[string]uint64

	// marshaledIPIDPair is map indexed by the key that contains the
	// marshaled IPIdentityPair
	marshaledIPIDPairs map[string][]byte
}

// newKVReferenceCounter creates a new reference counter using the specified
// store as the underlying location for key/value pairs to be stored.
func newKVReferenceCounter(s store) *kvReferenceCounter {
	return &kvReferenceCounter{
		store:              s,
		keys:               map[string]uint64{},
		marshaledIPIDPairs: map[string][]byte{},
	}
}

// upsert attempts to insert the specified {key, ipIDPair} into the kvstore. If
// the key has previously been upserted, increments a reference on the key.
// Always updates the underlying store with this {key, ipIDPair} tuple.
// Only adds a reference to the key if the upsert is successful.
func (r *kvReferenceCounter) upsert(ipKey string, ipIDPair identity.IPIdentityPair) error {
	marshaledIPIDPair, err := json.Marshal(ipIDPair)
	if err != nil {
		return err
	}

	log.WithFields(logrus.Fields{
		logfields.IPAddr:       ipIDPair.IP,
		logfields.IPMask:       ipIDPair.Mask,
		logfields.Identity:     ipIDPair.ID,
		logfields.Modification: Upsert,
	}).Debug("upserting IP->ID mapping to kvstore")

	r.Lock()
	defer r.Unlock()
	refcnt := r.keys[ipKey] // 0 if not found
	refcnt++
	err = r.store.upsert(ipKey, marshaledIPIDPair, true)
	if err == nil {
		r.keys[ipKey] = refcnt
		r.marshaledIPIDPairs[ipKey] = marshaledIPIDPair
	}
	return err
}

// release removes a reference to the specified key. If the number of
// references reaches 0, the key is removed from the underlying kvstore.
func (r *kvReferenceCounter) release(key string) (err error) {
	r.Lock()
	defer r.Unlock()

	refcnt, ok := r.keys[key] // 0 if not found
	if ok {
		refcnt--
	}

	if refcnt == 0 {
		delete(r.keys, key)
		delete(r.marshaledIPIDPairs, key)
		err = r.store.release(key)
	} else {
		r.keys[key] = refcnt
	}
	return err
}

// UpsertIPToKVStore updates / inserts the provided IP->Identity mapping into the
// kvstore, which will subsequently trigger an event in NewIPIdentityWatcher().
func UpsertIPToKVStore(IP, hostIP net.IP, ID identity.NumericIdentity, metadata string) error {
	ipKey := path.Join(IPIdentitiesPath, AddressSpace, IP.String())
	ipIDPair := identity.IPIdentityPair{
		IP:       IP,
		ID:       ID,
		Metadata: metadata,
		HostIP:   hostIP,
	}

	return globalMap.upsert(ipKey, ipIDPair)
}

// upsertIPNetToKVStore updates / inserts the provided CIDR->Identity mapping
// into the kvstore, which will subsequently trigger an event in
// NewIPIdentityWatcher().
func upsertIPNetToKVStore(prefix *net.IPNet, ID *identity.Identity) error {
	// Reserved identities are handled locally, don't push them to kvstore.
	if ID.IsReserved() {
		return nil
	}

	ipKey := path.Join(IPIdentitiesPath, AddressSpace, prefix.String())
	ipIDPair := identity.IPIdentityPair{
		IP:       prefix.IP,
		Mask:     prefix.Mask,
		ID:       ID.ID,
		Metadata: AddressSpace, // XXX: Should we associate more metadata?
	}

	return globalMap.upsert(ipKey, ipIDPair)
}

// keyToIPNet returns the IPNet describing the key, whether it is a host, and
// an error (if one occurs)
func keyToIPNet(key string) (parsedPrefix *net.IPNet, host bool, err error) {
	requiredPrefix := fmt.Sprintf("%s/", path.Join(IPIdentitiesPath, AddressSpace))
	if !strings.HasPrefix(key, requiredPrefix) {
		err = fmt.Errorf("Found invalid key %s outside of prefix %s", key, IPIdentitiesPath)
		return
	}

	suffix := strings.TrimPrefix(key, requiredPrefix)

	// Key is formatted as "prefix/192.0.2.0/24" for CIDRs
	_, parsedPrefix, err = net.ParseCIDR(suffix)
	if err != nil {
		// Key is likely a host in the format "prefix/192.0.2.3"
		parsedIP := net.ParseIP(suffix)
		if parsedIP == nil {
			err = fmt.Errorf("unable to parse IP from suffix %s", suffix)
			return
		}
		err = nil
		host = true
		ipv4 := parsedIP.To4()
		bits := net.IPv6len * 8
		if ipv4 != nil {
			parsedIP = ipv4
			bits = net.IPv4len * 8
		}
		parsedPrefix = &net.IPNet{IP: parsedIP, Mask: net.CIDRMask(bits, bits)}
	}

	return
}

// upsertIPNetsToKVStore inserts a CIDR->Identity mapping into the kvstore
// ipcache for each of the specified prefixes and identities. That is to say,
// prefixes[0] is mapped to identities[0].
//
// If any Prefix->Identity mapping cannot be created, it will not create any
// of the mappings and returns an error.
//
// The caller should check the prefix lengths against the underlying IPCache
// implementation using CheckPrefixLengths prior to upserting to the kvstore.
func upsertIPNetsToKVStore(prefixes []*net.IPNet, identities []*identity.Identity) (err error) {
	if len(prefixes) != len(identities) {
		return fmt.Errorf("Invalid []Prefix->[]Identity ipcache mapping requested: prefixes=%d identities=%d", len(prefixes), len(identities))
	}
	for i, prefix := range prefixes {
		id := identities[i]
		err = upsertIPNetToKVStore(prefix, id)
		if err != nil {
			for j := 0; j < i; j++ {
				ipKey := path.Join(IPIdentitiesPath, AddressSpace, prefix.String())
				err2 := globalMap.release(ipKey)
				if err2 != nil {
					log.WithFields(logrus.Fields{
						"prefix": prefix.String(),
					}).Error("Failed to clean up CIDR->ID mappings")
				}
			}
		}
	}

	return
}

// DeleteIPFromKVStore removes the IP->Identity mapping for the specified ip
// from the kvstore, which will subsequently trigger an event in
// NewIPIdentityWatcher().
func DeleteIPFromKVStore(ip string) error {
	ipKey := path.Join(IPIdentitiesPath, AddressSpace, ip)
	return globalMap.release(ipKey)
}

// deleteIPNetsFromKVStore removes the Prefix->Identity mappings for the
// specified slice of prefixes from the kvstore, which will subsequently
// trigger an event in NewIPIdentityWatcher().
func deleteIPNetsFromKVStore(prefixes []*net.IPNet) (err error) {
	for _, prefix := range prefixes {
		ipKey := path.Join(IPIdentitiesPath, AddressSpace, prefix.String())
		if err2 := globalMap.release(ipKey); err2 != nil {
			err = err2
			log.WithFields(logrus.Fields{
				"prefix": prefix.String(),
			}).Error("Failed to delete CIDR->ID mappings")
		}
	}

	return
}

// IPIdentityWatcher is a watcher that will notify when IP<->identity mappings
// change in the kvstore
type IPIdentityWatcher struct {
	backend  kvstore.BackendOperations
	stop     chan struct{}
	stopOnce sync.Once
}

// NewIPIdentityWatcher creates a new IPIdentityWatcher using the specified
// kvstore backend
func NewIPIdentityWatcher(backend kvstore.BackendOperations) *IPIdentityWatcher {
	watcher := &IPIdentityWatcher{
		backend: backend,
		stop:    make(chan struct{}),
	}

	return watcher
}

// Watch starts the watcher and blocks waiting for events. When events are
// received from the kvstore, All IPIdentityMappingListener are notified. The
// function returns when IPIdentityWatcher.Close() is called. The watcher will
// automatically restart as required.
func (iw *IPIdentityWatcher) Watch() {
restart:
	watcher := iw.backend.ListAndWatch("endpointIPWatcher", IPIdentitiesPath, 512)

	for {
		select {
		// Get events from channel as they come in.
		case event, ok := <-watcher.Events:
			if !ok {
				log.Debugf("%s closed, restarting watch", watcher.String())
				time.Sleep(500 * time.Millisecond)
				goto restart
			}

			scopedLog := log.WithFields(logrus.Fields{"kvstore-event": event.Typ.String(), "key": event.Key})
			scopedLog.Debug("Received event")

			// Synchronize local caching of endpoint IP to ipIDPair mapping with
			// operation key-value store has informed us about.
			//
			// To resolve conflicts between hosts and full CIDR prefixes:
			// - Insert hosts into the cache as ".../w.x.y.z"
			// - Insert CIDRS into the cache as ".../w.x.y.z/N"
			// - If a host entry created, notify the listeners.
			// - If a CIDR is created and there's no overlapping host
			//   entry, ie it is a less than fully masked CIDR, OR
			//   it is a fully masked CIDR and there is no corresponding
			//   host entry, then:
			//   - Notify the listeners.
			//   - Otherwise, do not notify listeners.
			// - If a host is removed, check for an overlapping CIDR
			//   and if it exists, notify the listeners with an upsert
			//   for the CIDR's identity
			// - If any other deletion case, notify listeners of
			//   the deletion event.
			switch event.Typ {
			case kvstore.EventTypeListDone:
				IPIdentityCache.Lock()
				for _, listener := range IPIdentityCache.listeners {
					listener.OnIPIdentityCacheGC()
				}
				IPIdentityCache.Unlock()

			case kvstore.EventTypeCreate, kvstore.EventTypeModify:
				var ipIDPair identity.IPIdentityPair
				err := json.Unmarshal(event.Value, &ipIDPair)
				if err != nil {
					scopedLog.WithError(err).Errorf("Not adding entry to ip cache; error unmarshaling data from key-value store")
					continue
				}
				IPIdentityCache.Upsert(ipIDPair.PrefixString(), ipIDPair.HostIP, Identity{
					ID:     ipIDPair.ID,
					Source: FromKVStore,
				})

			case kvstore.EventTypeDelete:
				// Value is not present in deletion event;
				// need to convert kvstore key to IP.
				ipnet, isHost, err := keyToIPNet(event.Key)
				if err != nil {
					scopedLog.WithError(err).Error("Error parsing IP from key")
					continue
				}
				var ip string
				if isHost {
					ip = ipnet.IP.String()
				} else {
					ip = ipnet.String()
				}
				globalMap.Lock()

				if m, ok := globalMap.marshaledIPIDPairs[event.Key]; ok {
					log.WithField("ip", ip).Warning("Received kvstore delete notification for alive ipcache entry")
					err := globalMap.store.upsert(event.Key, m, true)
					if err != nil {
						log.WithError(err).WithField("ip", ip).Warning("Unable to re-create alive ipcache entry")
					}
					globalMap.Unlock()
				} else {
					globalMap.Unlock()

					// The key no longer exists in the
					// local cache, it is safe to remove
					// from the datapath ipcache.
					IPIdentityCache.Delete(ip)
				}
			}

		case <-iw.stop:
			// identity watcher was stopped
			watcher.Stop()
			return
		}
	}
}

// Close stops the IPIdentityWatcher and causes Watch() to return
func (iw *IPIdentityWatcher) Close() {
	iw.stopOnce.Do(func() {
		close(iw.stop)
	})
}

// InitIPIdentityWatcher initializes the watcher for ip-identity mapping events
// in the key-value store.
func InitIPIdentityWatcher() {
	setupIPIdentityWatcher.Do(func() {
		globalMap = newKVReferenceCounter(kvstoreImplementation{})
		log.Info("Starting IP identity watcher")
		watch := NewIPIdentityWatcher(kvstore.Client())
		go watch.Watch()
	})
}
<|endoftext|>


<commit_before>package mqtt

import (
	"encoding/hex"
	"errors"
	"github.com/PandoCloud/pando-cloud/pkg/rpcs"
	"github.com/PandoCloud/pando-cloud/pkg/server"
	"net"
	"time"
)

const (
	SendChanLen      = 16
	defaultKeepAlive = 30
)

type ResponseType struct {
	SendTime    uint8
	PublishType uint8
	DataType    string
}

type Connection struct {
	Mgr             *Manager
	DeviceId        uint64
	Conn            net.Conn
	SendChan        chan Message
	MessageId       uint16
	MessageWaitChan map[uint16]chan error
	KeepAlive       uint16
	LastHbTime      int64
	Token           []byte
}

func NewConnection(conn net.Conn, mgr *Manager) *Connection {
	sendchan := make(chan Message, SendChanLen)
	c := &Connection{
		Conn:            conn,
		SendChan:        sendchan,
		Mgr:             mgr,
		KeepAlive:       defaultKeepAlive,
		MessageWaitChan: make(map[uint16]chan error),
	}

	go c.SendMsgToClient()
	go c.RcvMsgFromClient()

	return c
}

func (c *Connection) Submit(msg Message) {
	if c.Conn != nil {
		c.SendChan <- msg
	}
}

// Publish will publish a message , and return a chan to wait for completion.
func (c *Connection) Publish(msg Message, timeout time.Duration) error {
	server.Log.Debugf("publishing message : %v, timeout %v", msg, timeout)

	message := msg.(*Publish)
	message.MessageId = c.MessageId
	c.MessageId++
	c.Submit(message)

	ch := make(chan error)

	// we don't wait for confirm.
	if timeout == 0 {
		return nil
	}

	c.MessageWaitChan[message.MessageId] = ch
	// wait for timeout and
	go func() {
		timer := time.NewTimer(timeout)
		<-timer.C
		waitCh, exist := c.MessageWaitChan[message.MessageId]
		if exist {
			waitCh <- errors.New("timeout pushlishing message.")
			delete(c.MessageWaitChan, message.MessageId)
			close(waitCh)
		}
	}()

	err := <-ch
	return err
}

func (c *Connection) confirmPublish(messageid uint16) {
	waitCh, exist := c.MessageWaitChan[messageid]
	if exist {
		waitCh <- nil
		delete(c.MessageWaitChan, messageid)
		close(waitCh)
	}
}

func (c *Connection) ValidateToken(token []byte) error {

	err := c.Mgr.Provider.ValidateDeviceToken(c.DeviceId, token)
	if err != nil {
		return err
	}

	c.Token = token

	return nil
}

func (c *Connection) Close() {
	deviceid := c.DeviceId
	server.Log.Infof("closing connection of device %v", deviceid)
	if c.Conn != nil {
		c.Conn.Close()
	}
	if c.SendChan != nil {
		close(c.SendChan)
		c.SendChan = nil
	}
	c.Mgr.Provider.OnDeviceOffline(deviceid)
}

func (c *Connection) RcvMsgFromClient() {
	conn := c.Conn
	host := conn.RemoteAddr().String()
	server.Log.Infof("recieve new connection from %s", host)
	for {
		msg, err := DecodeOneMessage(conn)
		if err != nil {

			server.Log.Errorf("read error: %s", err)
			c.Close()
			return
		}

		server.Log.Infof("%s, come msg===\n%v\n=====", host, msg)
		c.LastHbTime = time.Now().Unix()
		switch msg := msg.(type) {
		case *Connect:
			ret := RetCodeAccepted
			if msg.ProtocolVersion == 3 && msg.ProtocolName != "MQIsdp" {
				ret = RetCodeUnacceptableProtocolVersion
			} else if msg.ProtocolVersion == 4 && msg.ProtocolName != "MQTT" {
				ret = RetCodeUnacceptableProtocolVersion
			} else if msg.ProtocolVersion > 4 {
				ret = RetCodeUnacceptableProtocolVersion
			}

			if len(msg.ClientId) < 1 || len(msg.ClientId) > 23 {
				server.Log.Warn("invalid clientid length: %d", len(msg.ClientId))
				ret = RetCodeIdentifierRejected
				c.Close()
				return
			}

			deviceid, err := ClientIdToDeviceId(msg.ClientId)
			if err != nil {
				server.Log.Warn("invalid Identify: %d", ret)
				c.Close()
				return
			}
			c.DeviceId = deviceid

			token, err := hex.DecodeString(msg.Password)
			if err != nil {
				server.Log.Warn("token format error : %v", err)
				ret = RetCodeNotAuthorized
				c.Close()
				return
			}
			err = c.ValidateToken(token)
			if err != nil {
				server.Log.Warn("validate token error : %v", err)
				ret = RetCodeNotAuthorized
				c.Close()
				return
			}

			if ret != RetCodeAccepted {
				server.Log.Warn("invalid CON: %d", ret)
				c.Close()
				return
			}

			args := rpcs.ArgsGetOnline{
				Id:                c.DeviceId,
				ClientIP:          host,
				AccessRPCHost:     server.GetRPCHost(),
				HeartbeatInterval: uint32(c.KeepAlive),
			}
			err = c.Mgr.Provider.OnDeviceOnline(args)
			if err != nil {
				server.Log.Warn("device online error : %v", err)
				c.Close()
				return
			}

			c.Mgr.AddConn(c.DeviceId, c)
			connack := &ConnAck{
				ReturnCode: ret,
			}

			c.Submit(connack)
			c.KeepAlive = msg.KeepAliveTimer
			server.Log.Infof("%s, connected to server now", host)

		case *Publish:
			server.Log.Infof("%s, publish topic: %s", host, msg.TopicName)

			c.Mgr.PublishMessage2Server(c.DeviceId, msg)
			if msg.QosLevel.IsAtLeastOnce() {
				server.Log.Infof("publish ack send now")
				publishack := &PubAck{MessageId: msg.MessageId}
				c.Submit(publishack)
			} else if msg.QosLevel.IsExactlyOnce() {
				server.Log.Infof("publish Rec send now")
				publishRec := &PubRec{MessageId: msg.MessageId}
				c.Submit(publishRec)
			}

			err := c.Mgr.Provider.OnDeviceHeartBeat(c.DeviceId)
			if err != nil {
				server.Log.Warnf("%s, heartbeat set error %s, close now...", host, err)
				c.Close()
				return
			}

		case *PubAck:
			server.Log.Infof("%s, comes publish ack", host)
			c.confirmPublish(msg.MessageId)
			err := c.Mgr.Provider.OnDeviceHeartBeat(c.DeviceId)
			if err != nil {
				server.Log.Warnf("%s, heartbeat set error %s, close now...", host, err)
				c.Close()
				return
			}

		case *PubRec:
			server.Log.Infof("%s, comes publish rec", host)
			publishRel := &PubRel{MessageId: msg.MessageId}
			c.Submit(publishRel)

		case *PubRel:
			server.Log.Infof("%s, comes publish rel", host)
			publishCom := &PubComp{MessageId: msg.MessageId}
			c.Submit(publishCom)

		case *PubComp:
			server.Log.Infof("%s, comes publish comp", host)
			c.confirmPublish(msg.MessageId)
			err := c.Mgr.Provider.OnDeviceHeartBeat(c.DeviceId)
			if err != nil {
				server.Log.Warnf("%s, heartbeat set error %s, close now...", host, err)
				c.Close()
				return
			}

		case *PingReq:
			server.Log.Infof("%s, ping req comes", host)
			pingrsp := &PingResp{}
			err := c.Mgr.Provider.OnDeviceHeartBeat(c.DeviceId)
			if err != nil {
				server.Log.Warnf("%s, heartbeat set error %s, close now...", host, err)
				c.Close()
				return
			}
			c.Submit(pingrsp)

		case *Subscribe:
			server.Log.Infof("%s, subscribe topic: %v", host, msg.Topics)

		case *Unsubscribe:
			server.Log.Infof("%s, unsubscribe topic: %v", host, msg.Topics)

		case *Disconnect:
			server.Log.Infof("%s, disconnect now, exit...", host)
			c.Close()
			return

		default:
			server.Log.Errorf("unknown msg type %T", msg)
			c.Close()
			return
		}
	}

}

func (c *Connection) SendMsgToClient() {
	host := c.Conn.RemoteAddr()
	for {
		msg, ok := <-c.SendChan
		if !ok {
			server.Log.Errorf("%s is end now", host)
			return
		}

		server.Log.Debugf("send msg to %s=======\n%v\n=========", host, msg)
		err := msg.Encode(c.Conn)
		if err != nil {
			server.Log.Errorf("send msg err: %s=====\n%v\n=====", err, msg)
			continue
		}
	}
}
<commit_msg>fix bug when same device connect to mqtt broker  repeatedly。<commit_after>package mqtt

import (
	"encoding/hex"
	"errors"
	"github.com/PandoCloud/pando-cloud/pkg/rpcs"
	"github.com/PandoCloud/pando-cloud/pkg/server"
	"net"
	"time"
)

const (
	SendChanLen      = 16
	defaultKeepAlive = 30
)

type ResponseType struct {
	SendTime    uint8
	PublishType uint8
	DataType    string
}

type Connection struct {
	Mgr             *Manager
	DeviceId        uint64
	Conn            net.Conn
	SendChan        chan Message
	MessageId       uint16
	MessageWaitChan map[uint16]chan error
	KeepAlive       uint16
	LastHbTime      int64
	Token           []byte
}

func NewConnection(conn net.Conn, mgr *Manager) *Connection {
	sendchan := make(chan Message, SendChanLen)
	c := &Connection{
		Conn:            conn,
		SendChan:        sendchan,
		Mgr:             mgr,
		KeepAlive:       defaultKeepAlive,
		MessageWaitChan: make(map[uint16]chan error),
	}

	go c.SendMsgToClient()
	go c.RcvMsgFromClient()

	return c
}

func (c *Connection) Submit(msg Message) {
	if c.Conn != nil {
		c.SendChan <- msg
	}
}

// Publish will publish a message , and return a chan to wait for completion.
func (c *Connection) Publish(msg Message, timeout time.Duration) error {
	server.Log.Debugf("publishing message : %v, timeout %v", msg, timeout)

	message := msg.(*Publish)
	message.MessageId = c.MessageId
	c.MessageId++
	c.Submit(message)

	ch := make(chan error)

	// we don't wait for confirm.
	if timeout == 0 {
		return nil
	}

	c.MessageWaitChan[message.MessageId] = ch
	// wait for timeout and
	go func() {
		timer := time.NewTimer(timeout)
		<-timer.C
		waitCh, exist := c.MessageWaitChan[message.MessageId]
		if exist {
			waitCh <- errors.New("timeout pushlishing message.")
			delete(c.MessageWaitChan, message.MessageId)
			close(waitCh)
		}
	}()

	err := <-ch
	return err
}

func (c *Connection) confirmPublish(messageid uint16) {
	waitCh, exist := c.MessageWaitChan[messageid]
	if exist {
		waitCh <- nil
		delete(c.MessageWaitChan, messageid)
		close(waitCh)
	}
}

func (c *Connection) ValidateToken(token []byte) error {

	err := c.Mgr.Provider.ValidateDeviceToken(c.DeviceId, token)
	if err != nil {
		return err
	}

	c.Token = token

	return nil
}

func (c *Connection) Close() {
	deviceid := c.DeviceId
	server.Log.Infof("closing connection of device %v", deviceid)
	if c.Conn != nil {
		c.Conn.Close()
		c.Conn = nil
		c.Mgr.Provider.OnDeviceOffline(deviceid)
	}
	if c.SendChan != nil {
		close(c.SendChan)
		c.SendChan = nil
	}
}

func (c *Connection) RcvMsgFromClient() {
	conn := c.Conn
	host := conn.RemoteAddr().String()
	server.Log.Infof("recieve new connection from %s", host)
	for {
		msg, err := DecodeOneMessage(conn)
		if err != nil {

			server.Log.Errorf("read error: %s", err)
			c.Close()
			return
		}

		server.Log.Infof("%s, come msg===\n%v\n=====", host, msg)
		c.LastHbTime = time.Now().Unix()
		switch msg := msg.(type) {
		case *Connect:
			ret := RetCodeAccepted
			if msg.ProtocolVersion == 3 && msg.ProtocolName != "MQIsdp" {
				ret = RetCodeUnacceptableProtocolVersion
			} else if msg.ProtocolVersion == 4 && msg.ProtocolName != "MQTT" {
				ret = RetCodeUnacceptableProtocolVersion
			} else if msg.ProtocolVersion > 4 {
				ret = RetCodeUnacceptableProtocolVersion
			}

			if len(msg.ClientId) < 1 || len(msg.ClientId) > 23 {
				server.Log.Warn("invalid clientid length: %d", len(msg.ClientId))
				ret = RetCodeIdentifierRejected
				c.Close()
				return
			}

			deviceid, err := ClientIdToDeviceId(msg.ClientId)
			if err != nil {
				server.Log.Warn("invalid Identify: %d", ret)
				c.Close()
				return
			}
			c.DeviceId = deviceid

			token, err := hex.DecodeString(msg.Password)
			if err != nil {
				server.Log.Warn("token format error : %v", err)
				ret = RetCodeNotAuthorized
				c.Close()
				return
			}
			err = c.ValidateToken(token)
			if err != nil {
				server.Log.Warn("validate token error : %v", err)
				ret = RetCodeNotAuthorized
				c.Close()
				return
			}

			if ret != RetCodeAccepted {
				server.Log.Warn("invalid CON: %d", ret)
				c.Close()
				return
			}

			args := rpcs.ArgsGetOnline{
				Id:                c.DeviceId,
				ClientIP:          host,
				AccessRPCHost:     server.GetRPCHost(),
				HeartbeatInterval: uint32(c.KeepAlive),
			}
			err = c.Mgr.Provider.OnDeviceOnline(args)
			if err != nil {
				server.Log.Warn("device online error : %v", err)
				c.Close()
				return
			}

			c.Mgr.AddConn(c.DeviceId, c)
			connack := &ConnAck{
				ReturnCode: ret,
			}

			c.Submit(connack)
			c.KeepAlive = msg.KeepAliveTimer
			server.Log.Infof("%s, connected to server now", host)

		case *Publish:
			server.Log.Infof("%s, publish topic: %s", host, msg.TopicName)

			c.Mgr.PublishMessage2Server(c.DeviceId, msg)
			if msg.QosLevel.IsAtLeastOnce() {
				server.Log.Infof("publish ack send now")
				publishack := &PubAck{MessageId: msg.MessageId}
				c.Submit(publishack)
			} else if msg.QosLevel.IsExactlyOnce() {
				server.Log.Infof("publish Rec send now")
				publishRec := &PubRec{MessageId: msg.MessageId}
				c.Submit(publishRec)
			}

			err := c.Mgr.Provider.OnDeviceHeartBeat(c.DeviceId)
			if err != nil {
				server.Log.Warnf("%s, heartbeat set error %s, close now...", host, err)
				c.Close()
				return
			}

		case *PubAck:
			server.Log.Infof("%s, comes publish ack", host)
			c.confirmPublish(msg.MessageId)
			err := c.Mgr.Provider.OnDeviceHeartBeat(c.DeviceId)
			if err != nil {
				server.Log.Warnf("%s, heartbeat set error %s, close now...", host, err)
				c.Close()
				return
			}

		case *PubRec:
			server.Log.Infof("%s, comes publish rec", host)
			publishRel := &PubRel{MessageId: msg.MessageId}
			c.Submit(publishRel)

		case *PubRel:
			server.Log.Infof("%s, comes publish rel", host)
			publishCom := &PubComp{MessageId: msg.MessageId}
			c.Submit(publishCom)

		case *PubComp:
			server.Log.Infof("%s, comes publish comp", host)
			c.confirmPublish(msg.MessageId)
			err := c.Mgr.Provider.OnDeviceHeartBeat(c.DeviceId)
			if err != nil {
				server.Log.Warnf("%s, heartbeat set error %s, close now...", host, err)
				c.Close()
				return
			}

		case *PingReq:
			server.Log.Infof("%s, ping req comes", host)
			pingrsp := &PingResp{}
			err := c.Mgr.Provider.OnDeviceHeartBeat(c.DeviceId)
			if err != nil {
				server.Log.Warnf("%s, heartbeat set error %s, close now...", host, err)
				c.Close()
				return
			}
			c.Submit(pingrsp)

		case *Subscribe:
			server.Log.Infof("%s, subscribe topic: %v", host, msg.Topics)

		case *Unsubscribe:
			server.Log.Infof("%s, unsubscribe topic: %v", host, msg.Topics)

		case *Disconnect:
			server.Log.Infof("%s, disconnect now, exit...", host)
			c.Close()
			return

		default:
			server.Log.Errorf("unknown msg type %T", msg)
			c.Close()
			return
		}
	}

}

func (c *Connection) SendMsgToClient() {
	host := c.Conn.RemoteAddr()
	for {
		msg, ok := <-c.SendChan
		if !ok {
			server.Log.Errorf("%s is end now", host)
			return
		}

		server.Log.Debugf("send msg to %s=======\n%v\n=========", host, msg)
		err := msg.Encode(c.Conn)
		if err != nil {
			server.Log.Errorf("send msg err: %s=====\n%v\n=====", err, msg)
			continue
		}
	}
}
<|endoftext|>


<commit_before>package release

import (
	"fmt"
	"io"
	"net/url"
	"path/filepath"
	"strings"
	"time"

	strftime "github.com/jehiah/go-strftime"
)

// Release represents a `<host>/<user>/<project>/<timestamp>/` layout.
type Release struct {
	Meta *Meta
	URL  *url.URL
}

// New returns a Release object.
func New(meta *Meta, u *url.URL) *Release {
	return &Release{Meta: meta, URL: u}
}

// Name returns the `<host>/<user>/<project>`.
func (rel *Release) Name() string {
	return strings.TrimPrefix(filepath.Dir(rel.URL.Path), "/")
}

// Timestamp returns the timestamp.
func (rel *Release) Timestamp() string {
	return filepath.Base(rel.URL.Path)
}

// Prefix returns the `<host>/<user>/<project>/<timestamp>/`.
func (rel *Release) Prefix() string {
	return strings.Join([]string{rel.Name(), rel.Timestamp()}, "/")
}

// MetaPath returns the meta file path within the release.
// eg. [`<host>/<user>/<project>/<timestamp>/meta.yml`]
func (rel *Release) MetaPath() string {
	return filepath.Join(rel.Prefix(), MetaFileName)
}

// Inspect inspetcs the release information.
func (rel *Release) Inspect(w io.Writer) {
	fmt.Fprintf(w, "NAME\tTIMESTAMP\t")
	for i := 1; i <= len(rel.Meta.Binaries); i++ {
		fmt.Fprintf(w, "BINNARY%d\t", i)
	}
	fmt.Fprintln(w)
	fmt.Fprintf(w, "%s\t%s\t", rel.Name(), rel.Timestamp())
	for _, b := range rel.Meta.Binaries {
		b.Inspect(w)
	}
	fmt.Fprintln(w)
}

// Now returns the current UTC timestamp.
func Now() string {
	t := time.Now()
	utc, _ := time.LoadLocation("UTC")
	t = t.In(utc)
	return strftime.Format("%Y%m%d%H%M%S", t)
}

const (
	timestampFormat = "20060102150405"
)

func isTimestamp(str string) bool {
	if _, err := time.Parse(timestampFormat, str); err != nil {
		if _, ok := err.(*time.ParseError); ok {
			return false
		}
		return false
	}
	return true
}

// ParseName parses a formatted name, and returns the bool of the success or
// false and the formatted name.
func ParseName(str string) (bool, string) {
	// str is expected to be 'github.com/yuuki/droot/20171017152508/droot'
	// or 'github.com/yuuki/droot/20171017152508'
	items := strings.Split(str, "/")
	if len(items) < 2 {
		return false, ""
	}
	tail, oneBeforeTail := items[len(items)-1], items[len(items)-2]
	if isTimestamp(tail) {
		return true, strings.Join(items[0:len(items)-1], "/")
	}
	if isTimestamp(oneBeforeTail) {
		return true, strings.Join(items[0:len(items)-2], "/")
	}
	return false, ""
}

// ParseRelease parses a formatted name, and returns the bool of the success or
// false and the formatted name.
func ParseRelease(str string) (bool, string) {
	// str is expected to be 'github.com/yuuki/droot/20171017152508/droot'
	items := strings.Split(str, "/")
	if len(items) < 2 {
		return false, ""
	}
	tail, oneBeforeTail := items[len(items)-1], items[len(items)-2]
	if isTimestamp(tail) {
		return true, strings.Join(items[0:len(items)-1], "/")
	}
	if isTimestamp(oneBeforeTail) {
		return true, strings.Join(items[0:len(items)-2], "/")
	}
	return false, ""
}
<commit_msg>Remove unused code<commit_after>package release

import (
	"fmt"
	"io"
	"net/url"
	"path/filepath"
	"strings"
	"time"

	strftime "github.com/jehiah/go-strftime"
)

// Release represents a `<host>/<user>/<project>/<timestamp>/` layout.
type Release struct {
	Meta *Meta
	URL  *url.URL
}

// New returns a Release object.
func New(meta *Meta, u *url.URL) *Release {
	return &Release{Meta: meta, URL: u}
}

// Name returns the `<host>/<user>/<project>`.
func (rel *Release) Name() string {
	return strings.TrimPrefix(filepath.Dir(rel.URL.Path), "/")
}

// Timestamp returns the timestamp.
func (rel *Release) Timestamp() string {
	return filepath.Base(rel.URL.Path)
}

// Prefix returns the `<host>/<user>/<project>/<timestamp>/`.
func (rel *Release) Prefix() string {
	return strings.Join([]string{rel.Name(), rel.Timestamp()}, "/")
}

// MetaPath returns the meta file path within the release.
// eg. [`<host>/<user>/<project>/<timestamp>/meta.yml`]
func (rel *Release) MetaPath() string {
	return filepath.Join(rel.Prefix(), MetaFileName)
}

// Inspect inspetcs the release information.
func (rel *Release) Inspect(w io.Writer) {
	fmt.Fprintf(w, "NAME\tTIMESTAMP\t")
	for i := 1; i <= len(rel.Meta.Binaries); i++ {
		fmt.Fprintf(w, "BINNARY%d\t", i)
	}
	fmt.Fprintln(w)
	fmt.Fprintf(w, "%s\t%s\t", rel.Name(), rel.Timestamp())
	for _, b := range rel.Meta.Binaries {
		b.Inspect(w)
	}
	fmt.Fprintln(w)
}

// Now returns the current UTC timestamp.
func Now() string {
	t := time.Now()
	utc, _ := time.LoadLocation("UTC")
	t = t.In(utc)
	return strftime.Format("%Y%m%d%H%M%S", t)
}

const (
	timestampFormat = "20060102150405"
)

func isTimestamp(str string) bool {
	if _, err := time.Parse(timestampFormat, str); err != nil {
		if _, ok := err.(*time.ParseError); ok {
			return false
		}
		return false
	}
	return true
}

// ParseName parses a formatted name, and returns the bool of the success or
// false and the formatted name.
func ParseName(str string) (bool, string) {
	// str is expected to be 'github.com/yuuki/droot/20171017152508/droot'
	// or 'github.com/yuuki/droot/20171017152508'
	items := strings.Split(str, "/")
	if len(items) < 2 {
		return false, ""
	}
	tail, oneBeforeTail := items[len(items)-1], items[len(items)-2]
	if isTimestamp(tail) {
		return true, strings.Join(items[0:len(items)-1], "/")
	}
	if isTimestamp(oneBeforeTail) {
		return true, strings.Join(items[0:len(items)-2], "/")
	}
	return false, ""
}
<|endoftext|>


<commit_before>package sharing

import (
	"github.com/cozy/cozy-stack/pkg/consts"
	"github.com/cozy/cozy-stack/pkg/couchdb"
	"github.com/cozy/cozy-stack/pkg/instance"
	"github.com/cozy/cozy-stack/pkg/vfs"
)

type bulkRevs struct {
	Rev       string
	Revisions map[string]interface{}
}

type sharingIndexer struct {
	db       couchdb.Database
	indexer  vfs.Indexer
	bulkRevs *bulkRevs
}

// NewSharingIndexer creates an Indexer for the special purpose of the sharing.
// It intercepts some requests to force the id and revisions of some documents,
// and proxifies other requests to the normal couchdbIndexer (reads).
func NewSharingIndexer(inst *instance.Instance, bulkRevs *bulkRevs) vfs.Indexer {
	return &sharingIndexer{
		db:       inst,
		indexer:  inst.VFS(),
		bulkRevs: bulkRevs,
	}
}

func (s *sharingIndexer) InitIndex() error {
	return ErrInternalServerError
}

func (s *sharingIndexer) DiskUsage() (int64, error) {
	return s.indexer.DiskUsage()
}

func (s *sharingIndexer) CreateFileDoc(doc *vfs.FileDoc) error {
	return ErrInternalServerError
}

func (s *sharingIndexer) CreateNamedFileDoc(doc *vfs.FileDoc) error {
	return ErrInternalServerError
}

func (s *sharingIndexer) UpdateFileDoc(olddoc, newdoc *vfs.FileDoc) error {
	return ErrInternalServerError
}

func (s *sharingIndexer) UpdateFileDocs(docs []*vfs.FileDoc) error {
	return ErrInternalServerError
}

func (s *sharingIndexer) DeleteFileDoc(doc *vfs.FileDoc) error {
	return ErrInternalServerError
}

func (s *sharingIndexer) CreateDirDoc(doc *vfs.DirDoc) error {
	return ErrInternalServerError
}

func (s *sharingIndexer) CreateNamedDirDoc(doc *vfs.DirDoc) error {
	return s.UpdateDirDoc(nil, doc)
}

// TODO update io.cozy.shared & rtevent
func (s *sharingIndexer) UpdateDirDoc(olddoc, doc *vfs.DirDoc) error {
	docs := make([]map[string]interface{}, 1)
	docs[0] = map[string]interface{}{
		"type":       doc.Type,
		"_id":        doc.DocID,
		"name":       doc.DocName,
		"dir_id":     doc.DirID,
		"created_at": doc.CreatedAt,
		"updated_at": doc.UpdatedAt,
		"tags":       doc.Tags,
		"path":       doc.Fullpath,
	}
	if len(doc.ReferencedBy) > 0 {
		docs[0]["referenced_by"] = doc.ReferencedBy
	}
	if s.bulkRevs != nil {
		docs[0]["_rev"] = s.bulkRevs.Rev
		docs[0]["_revisions"] = s.bulkRevs.Revisions
	}
	return couchdb.BulkForceUpdateDocs(s.db, consts.Files, docs)
}

func (s *sharingIndexer) DeleteDirDoc(doc *vfs.DirDoc) error {
	return ErrInternalServerError
}

func (s *sharingIndexer) DeleteDirDocAndContent(doc *vfs.DirDoc, onlyContent bool) (n int64, ids []string, err error) {
	return 0, nil, ErrInternalServerError
}

func (s *sharingIndexer) BatchDelete(docs []couchdb.Doc) error {
	return ErrInternalServerError
}

func (s *sharingIndexer) DirByID(fileID string) (*vfs.DirDoc, error) {
	return s.indexer.DirByID(fileID)
}

func (s *sharingIndexer) DirByPath(name string) (*vfs.DirDoc, error) {
	return s.indexer.DirByPath(name)
}

func (s *sharingIndexer) FileByID(fileID string) (*vfs.FileDoc, error) {
	return s.indexer.FileByID(fileID)
}

func (s *sharingIndexer) FileByPath(name string) (*vfs.FileDoc, error) {
	return s.indexer.FileByPath(name)
}

func (s *sharingIndexer) FilePath(doc *vfs.FileDoc) (string, error) {
	return s.indexer.FilePath(doc)
}

func (s *sharingIndexer) DirOrFileByID(fileID string) (*vfs.DirDoc, *vfs.FileDoc, error) {
	return s.indexer.DirOrFileByID(fileID)
}

func (s *sharingIndexer) DirOrFileByPath(name string) (*vfs.DirDoc, *vfs.FileDoc, error) {
	return s.indexer.DirOrFileByPath(name)
}

func (s *sharingIndexer) DirIterator(doc *vfs.DirDoc, opts *vfs.IteratorOptions) vfs.DirIterator {
	return s.indexer.DirIterator(doc, opts)
}

func (s *sharingIndexer) DirBatch(doc *vfs.DirDoc, cursor couchdb.Cursor) ([]vfs.DirOrFileDoc, error) {
	return s.indexer.DirBatch(doc, cursor)
}

func (s *sharingIndexer) DirLength(doc *vfs.DirDoc) (int, error) {
	return s.indexer.DirLength(doc)
}

func (s *sharingIndexer) DirChildExists(dirID, name string) (bool, error) {
	return s.indexer.DirChildExists(dirID, name)
}

func (s *sharingIndexer) CheckIndexIntegrity() ([]*vfs.FsckLog, error) {
	return nil, ErrInternalServerError
}

var _ vfs.Indexer = (*sharingIndexer)(nil)
<commit_msg>Fix a deadlock on sharing indexer<commit_after>package sharing

import (
	"github.com/cozy/cozy-stack/pkg/consts"
	"github.com/cozy/cozy-stack/pkg/couchdb"
	"github.com/cozy/cozy-stack/pkg/instance"
	"github.com/cozy/cozy-stack/pkg/vfs"
)

type bulkRevs struct {
	Rev       string
	Revisions map[string]interface{}
}

type sharingIndexer struct {
	db       couchdb.Database
	indexer  vfs.Indexer
	bulkRevs *bulkRevs
}

// NewSharingIndexer creates an Indexer for the special purpose of the sharing.
// It intercepts some requests to force the id and revisions of some documents,
// and proxifies other requests to the normal couchdbIndexer (reads).
func NewSharingIndexer(inst *instance.Instance, bulkRevs *bulkRevs) vfs.Indexer {
	return &sharingIndexer{
		db:       inst,
		indexer:  vfs.NewCouchdbIndexer(inst),
		bulkRevs: bulkRevs,
	}
}

func (s *sharingIndexer) InitIndex() error {
	return ErrInternalServerError
}

func (s *sharingIndexer) DiskUsage() (int64, error) {
	return s.indexer.DiskUsage()
}

func (s *sharingIndexer) CreateFileDoc(doc *vfs.FileDoc) error {
	return ErrInternalServerError
}

func (s *sharingIndexer) CreateNamedFileDoc(doc *vfs.FileDoc) error {
	return ErrInternalServerError
}

func (s *sharingIndexer) UpdateFileDoc(olddoc, newdoc *vfs.FileDoc) error {
	return ErrInternalServerError
}

func (s *sharingIndexer) UpdateFileDocs(docs []*vfs.FileDoc) error {
	return ErrInternalServerError
}

func (s *sharingIndexer) DeleteFileDoc(doc *vfs.FileDoc) error {
	return ErrInternalServerError
}

func (s *sharingIndexer) CreateDirDoc(doc *vfs.DirDoc) error {
	return ErrInternalServerError
}

func (s *sharingIndexer) CreateNamedDirDoc(doc *vfs.DirDoc) error {
	return s.UpdateDirDoc(nil, doc)
}

// TODO update io.cozy.shared & rtevent
func (s *sharingIndexer) UpdateDirDoc(olddoc, doc *vfs.DirDoc) error {
	docs := make([]map[string]interface{}, 1)
	docs[0] = map[string]interface{}{
		"type":       doc.Type,
		"_id":        doc.DocID,
		"name":       doc.DocName,
		"dir_id":     doc.DirID,
		"created_at": doc.CreatedAt,
		"updated_at": doc.UpdatedAt,
		"tags":       doc.Tags,
		"path":       doc.Fullpath,
	}
	if len(doc.ReferencedBy) > 0 {
		docs[0]["referenced_by"] = doc.ReferencedBy
	}
	if s.bulkRevs != nil {
		docs[0]["_rev"] = s.bulkRevs.Rev
		docs[0]["_revisions"] = s.bulkRevs.Revisions
	}
	return couchdb.BulkForceUpdateDocs(s.db, consts.Files, docs)
}

func (s *sharingIndexer) DeleteDirDoc(doc *vfs.DirDoc) error {
	return ErrInternalServerError
}

func (s *sharingIndexer) DeleteDirDocAndContent(doc *vfs.DirDoc, onlyContent bool) (n int64, ids []string, err error) {
	return 0, nil, ErrInternalServerError
}

func (s *sharingIndexer) BatchDelete(docs []couchdb.Doc) error {
	return ErrInternalServerError
}

func (s *sharingIndexer) DirByID(fileID string) (*vfs.DirDoc, error) {
	return s.indexer.DirByID(fileID)
}

func (s *sharingIndexer) DirByPath(name string) (*vfs.DirDoc, error) {
	return s.indexer.DirByPath(name)
}

func (s *sharingIndexer) FileByID(fileID string) (*vfs.FileDoc, error) {
	return s.indexer.FileByID(fileID)
}

func (s *sharingIndexer) FileByPath(name string) (*vfs.FileDoc, error) {
	return s.indexer.FileByPath(name)
}

func (s *sharingIndexer) FilePath(doc *vfs.FileDoc) (string, error) {
	return s.indexer.FilePath(doc)
}

func (s *sharingIndexer) DirOrFileByID(fileID string) (*vfs.DirDoc, *vfs.FileDoc, error) {
	return s.indexer.DirOrFileByID(fileID)
}

func (s *sharingIndexer) DirOrFileByPath(name string) (*vfs.DirDoc, *vfs.FileDoc, error) {
	return s.indexer.DirOrFileByPath(name)
}

func (s *sharingIndexer) DirIterator(doc *vfs.DirDoc, opts *vfs.IteratorOptions) vfs.DirIterator {
	return s.indexer.DirIterator(doc, opts)
}

func (s *sharingIndexer) DirBatch(doc *vfs.DirDoc, cursor couchdb.Cursor) ([]vfs.DirOrFileDoc, error) {
	return s.indexer.DirBatch(doc, cursor)
}

func (s *sharingIndexer) DirLength(doc *vfs.DirDoc) (int, error) {
	return s.indexer.DirLength(doc)
}

func (s *sharingIndexer) DirChildExists(dirID, name string) (bool, error) {
	return s.indexer.DirChildExists(dirID, name)
}

func (s *sharingIndexer) CheckIndexIntegrity() ([]*vfs.FsckLog, error) {
	return nil, ErrInternalServerError
}

var _ vfs.Indexer = (*sharingIndexer)(nil)
<|endoftext|>


<commit_before>/*

Copyright 2020 The Vouch Proxy Authors.
Use of this source code is governed by The MIT License (MIT) that
can be found in the LICENSE file. Software distributed under The
MIT License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
OR CONDITIONS OF ANY KIND, either express or implied.

*/

package timelog

import (
	"context"
	"fmt"
	"net/http"
	"time"

	"github.com/vouch/vouch-proxy/pkg/capturewriter"
	"github.com/vouch/vouch-proxy/pkg/cfg"
	"go.uber.org/zap"
)

var (
	req        = int64(0)
	avgLatency = int64(0)
	log        *zap.SugaredLogger
)

// Configure see main.go configure()
func Configure() {
	log = cfg.Logging.Logger

	capturewriter.Configure()

}

// TimeLog records how long it takes to process the http request and produce the response (latency)
func TimeLog(nextHandler http.Handler) func(http.ResponseWriter, *http.Request) {
	return func(w http.ResponseWriter, r *http.Request) {
		// log.Debugf("Request received : %v", r)
		start := time.Now()

		// make the call
		v := capturewriter.CaptureWriter{ResponseWriter: w, StatusCode: 0}
		ctx := context.Background()
		nextHandler.ServeHTTP(&v, r.WithContext(ctx))

		// Stop timer
		end := time.Now()
		latency := end.Sub(start)
		req++
		avgLatency = avgLatency + ((int64(latency) - avgLatency) / req)
		log.Debugf("Request handled successfully: %v", v.GetStatusCode())
		var statusCode = v.GetStatusCode()

		path := r.URL.Path
		host := r.Host
		referer := r.Header.Get("Referer")
		clientIP := r.RemoteAddr
		method := r.Method

		log.Infow(fmt.Sprintf("|%d| %10v %s", statusCode, time.Duration(latency), path),
			"statusCode", statusCode,
			"request", req,
			"latency", time.Duration(latency),
			"avgLatency", time.Duration(avgLatency),
			"ipPort", clientIP,
			"method", method,
			"host", host,
			"path", path,
			"referer", referer,
		)
	}
}
<commit_msg>move cleanup and logging into a go routine<commit_after>/*

Copyright 2020 The Vouch Proxy Authors.
Use of this source code is governed by The MIT License (MIT) that
can be found in the LICENSE file. Software distributed under The
MIT License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
OR CONDITIONS OF ANY KIND, either express or implied.

*/

package timelog

import (
	"context"
	"fmt"
	"net/http"
	"time"

	"github.com/vouch/vouch-proxy/pkg/capturewriter"
	"github.com/vouch/vouch-proxy/pkg/cfg"
	"go.uber.org/zap"
)

var (
	req        = int64(0)
	avgLatency = int64(0)
	log        *zap.SugaredLogger
)

// Configure see main.go configure()
func Configure() {
	log = cfg.Logging.Logger

	capturewriter.Configure()

}

// TimeLog records how long it takes to process the http request and produce the response (latency)
func TimeLog(nextHandler http.Handler) func(http.ResponseWriter, *http.Request) {
	return func(w http.ResponseWriter, r *http.Request) {
		// log.Debugf("Request received : %v", r)
		start := time.Now()

		// make the call
		v := capturewriter.CaptureWriter{ResponseWriter: w, StatusCode: 0}
		ctx := context.Background()
		nextHandler.ServeHTTP(&v, r.WithContext(ctx))

		// Stop timer
		end := time.Now()

		go func() {
			latency := end.Sub(start)
			req++
			avgLatency = avgLatency + ((int64(latency) - avgLatency) / req)
			// log.Debugf("Request handled successfully: %v", v.GetStatusCode())
			var statusCode = v.GetStatusCode()

			path := r.URL.Path
			host := r.Host
			referer := r.Header.Get("Referer")
			clientIP := r.RemoteAddr
			method := r.Method

			log.Infow(fmt.Sprintf("|%d| %10v %s", statusCode, time.Duration(latency), path),
				"statusCode", statusCode,
				"request", req,
				"latency", time.Duration(latency),
				"avgLatency", time.Duration(avgLatency),
				"ipPort", clientIP,
				"method", method,
				"host", host,
				"path", path,
				"referer", referer,
			)
		}()

	}
}
<|endoftext|>


<commit_before><commit_msg>make client receivers consistent<commit_after><|endoftext|>


<commit_before><commit_msg>Log innodb status & processlist when an error occurs<commit_after><|endoftext|>


<commit_before><commit_msg>:zap: client: fixes `client.Equal` by doing a compare on allowed regions.<commit_after><|endoftext|>


<commit_before><commit_msg>it cannnot be guaranteed that several Subacks return as sent subscribes order<commit_after><|endoftext|>


<commit_before><commit_msg>Fixed typo. Client.go line 38.<commit_after><|endoftext|>


<commit_before><commit_msg>Didn't properly check for all errors.<commit_after><|endoftext|>


<commit_before><commit_msg>MB-24429. Disable timeout temporarly<commit_after><|endoftext|>


<commit_before><commit_msg>Use net.Dialer for TCP<commit_after><|endoftext|>


<commit_before><commit_msg>Added error handling for initial healthcheck<commit_after><|endoftext|>


<commit_before><commit_msg>Add connection pool queuing strategies in HostClient. (#1238)<commit_after><|endoftext|>


<commit_before>package handlers

import (
	"net/http"
	"socialapi/workers/api/modules/account"
	"socialapi/workers/api/modules/activity"
	"socialapi/workers/api/modules/channel"
	"socialapi/workers/api/modules/interaction"
	"socialapi/workers/api/modules/message"
	"socialapi/workers/api/modules/messagelist"
	"socialapi/workers/api/modules/participant"
	"socialapi/workers/api/modules/popular"
	"socialapi/workers/api/modules/reply"

	"github.com/rcrowley/go-tigertonic"
)

var (
	cors = tigertonic.NewCORSBuilder().AddAllowedOrigins("*")
)

func handlerWrapper(handler interface{}, logName string) http.Handler {
	return cors.Build(
		tigertonic.Timed(
			tigertonic.Marshaled(
				handler,
			),
			logName,
			nil,
		))
}

// todo implement context support here for requests
func Inject(mux *tigertonic.TrieServeMux) *tigertonic.TrieServeMux {

	////////////////////////////////////////////////////////////////////////////////////
	/////////////////////////////////// Message Operations /////////////////////////////
	////////////////////////////////////////////////////////////////////////////////////
	// tested
	mux.Handle("POST", "/message/{id}", handlerWrapper(message.Update, "message-update"))
	// tested
	mux.Handle("DELETE", "/message/{id}", handlerWrapper(message.Delete, "message-delete"))
	// tested
	mux.Handle("GET", "/message/{id}", handlerWrapper(message.Get, "message-get"))

	////////////////////////////////////////////////////////////////////////////////////
	///////////////////////////// Message Reply Operations /////////////////////////////
	////////////////////////////////////////////////////////////////////////////////////
	// tested
	mux.Handle("POST", "/message/{id}/reply", handlerWrapper(reply.Create, "reply-create"))
	// tested
	mux.Handle("DELETE", "/message/{id}/reply/{replyId}", handlerWrapper(reply.Delete, "reply-delete"))
	// tested
	mux.Handle("GET", "/message/{id}/reply", handlerWrapper(reply.List, "reply-list"))

	////////////////////////////////////////////////////////////////////////////////////
	/////////////////////// Message Interaction Operations /////////////////////////////
	////////////////////////////////////////////////////////////////////////////////////
	// add new like
	// tested
	mux.Handle("POST", "/message/{id}/interaction/{type}/add", handlerWrapper(interaction.Add, "interactions-add"))
	// delete like - unlike
	// tested
	mux.Handle("POST", "/message/{id}/interaction/{type}/delete", handlerWrapper(interaction.Delete, "interactions-delete"))
	// get all the interactions for message
	// mux.Handle("GET", "/message/{id}/interaction", handlerWrapper(interaction.List, "interactions-list"))
	// get typed interactions
	// tested
	mux.Handle("GET", "/message/{id}/interaction/{type}", handlerWrapper(interaction.List, "interactions-list-typed"))

	////////////////////////////////////////////////////////////////////////////////////
	/////////////////////////////////// Channel Operations /////////////////////////////
	////////////////////////////////////////////////////////////////////////////////////
	// tested
	mux.Handle("POST", "/channel", handlerWrapper(channel.Create, "channel-create"))

	mux.Handle("GET", "/channel", handlerWrapper(channel.List, "channel-list"))
	// tested
	// deprecated, here for socialworker
	mux.Handle("POST", "/channel/{id}", handlerWrapper(channel.Update, "channel-update"))
	//
	mux.Handle("POST", "/channel/{id}/update", handlerWrapper(channel.Update, "channel-update"))
	// tested
	mux.Handle("POST", "/channel/{id}/delete", handlerWrapper(channel.Delete, "channel-delete"))
	// tested
	mux.Handle("GET", "/channel/{id}", handlerWrapper(channel.Get, "channel-get"))

	// add a new messages to the channel
	// tested
	mux.Handle("POST", "/channel/{id}/message", handlerWrapper(message.Create, "channel-message-create"))

	// list participants of the channel
	// tested
	mux.Handle("GET", "/channel/{id}/participant", handlerWrapper(participant.List, "participant-list"))

	// add participant to the channel
	//tested
	mux.Handle("POST", "/channel/{id}/participant/{accountId}/add", handlerWrapper(participant.Add, "participant-list"))

	// remove participant from the channel
	// tested
	mux.Handle("POST", "/channel/{id}/participant/{accountId}/delete", handlerWrapper(participant.Delete, "participant-list"))

	// list messages of the channel
	mux.Handle("GET", "/channel/{id}/history", handlerWrapper(messagelist.List, "channel-history-list"))

	// register an account
	mux.Handle("POST", "/account", handlerWrapper(account.Register, "account-create"))

	// list channels of the account
	mux.Handle("GET", "/account/{id}/channels", handlerWrapper(account.ListChannels, "account-channel-list"))

	// follow the account
	mux.Handle("POST", "/account/{id}/follow", handlerWrapper(account.Follow, "account-follow"))

	// un-follow the account
	mux.Handle("POST", "/account/{id}/unfollow", handlerWrapper(account.Unfollow, "account-unfollow"))

	// get pinning channel of the account
	mux.Handle("GET", "/activity/pin/channel", handlerWrapper(activity.GetPinnedActivityChannel, "activity-pin-get-channel"))

	// get pinning channel of the account
	mux.Handle("GET", "/activity/pin/list", handlerWrapper(activity.List, "activity-pin-list-message"))

	// pin a new status update
	mux.Handle("POST", "/activity/pin/add", handlerWrapper(activity.PinMessage, "activity-add-pinned-message"))

	// unpin a status update
	mux.Handle("POST", "/activity/pin/remove", handlerWrapper(activity.UnpinMessage, "activity-remove-pinned-message"))

	// get popular topics
	mux.Handle("GET", "/popular/topics/{statisticName}", handlerWrapper(popular.ListTopics, "list-popular-topics"))

	// mux.Handle("POST", "/follow/{id}", handlerWrapper(post, "follow-id"))
	// mux.Handle("POST", "/unfollow/{id}", handlerWrapper(post, "follow-id"))

	return mux
}

// to-do list
// get current account from context for future
// like client.connection.delegate
<commit_msg>Social: add handler for fetching profile feed<commit_after>package handlers

import (
	"net/http"
	"socialapi/workers/api/modules/account"
	"socialapi/workers/api/modules/activity"
	"socialapi/workers/api/modules/channel"
	"socialapi/workers/api/modules/interaction"
	"socialapi/workers/api/modules/message"
	"socialapi/workers/api/modules/messagelist"
	"socialapi/workers/api/modules/participant"
	"socialapi/workers/api/modules/popular"
	"socialapi/workers/api/modules/reply"

	"github.com/rcrowley/go-tigertonic"
)

var (
	cors = tigertonic.NewCORSBuilder().AddAllowedOrigins("*")
)

func handlerWrapper(handler interface{}, logName string) http.Handler {
	return cors.Build(
		tigertonic.Timed(
			tigertonic.Marshaled(
				handler,
			),
			logName,
			nil,
		))
}

// todo implement context support here for requests
func Inject(mux *tigertonic.TrieServeMux) *tigertonic.TrieServeMux {

	////////////////////////////////////////////////////////////////////////////////////
	/////////////////////////////////// Message Operations /////////////////////////////
	////////////////////////////////////////////////////////////////////////////////////
	// tested
	mux.Handle("POST", "/message/{id}", handlerWrapper(message.Update, "message-update"))
	// tested
	mux.Handle("DELETE", "/message/{id}", handlerWrapper(message.Delete, "message-delete"))
	// tested
	mux.Handle("GET", "/message/{id}", handlerWrapper(message.Get, "message-get"))

	////////////////////////////////////////////////////////////////////////////////////
	///////////////////////////// Message Reply Operations /////////////////////////////
	////////////////////////////////////////////////////////////////////////////////////
	// tested
	mux.Handle("POST", "/message/{id}/reply", handlerWrapper(reply.Create, "reply-create"))
	// tested
	mux.Handle("DELETE", "/message/{id}/reply/{replyId}", handlerWrapper(reply.Delete, "reply-delete"))
	// tested
	mux.Handle("GET", "/message/{id}/reply", handlerWrapper(reply.List, "reply-list"))

	////////////////////////////////////////////////////////////////////////////////////
	/////////////////////// Message Interaction Operations /////////////////////////////
	////////////////////////////////////////////////////////////////////////////////////
	// add new like
	// tested
	mux.Handle("POST", "/message/{id}/interaction/{type}/add", handlerWrapper(interaction.Add, "interactions-add"))
	// delete like - unlike
	// tested
	mux.Handle("POST", "/message/{id}/interaction/{type}/delete", handlerWrapper(interaction.Delete, "interactions-delete"))
	// get all the interactions for message
	// mux.Handle("GET", "/message/{id}/interaction", handlerWrapper(interaction.List, "interactions-list"))
	// get typed interactions
	// tested
	mux.Handle("GET", "/message/{id}/interaction/{type}", handlerWrapper(interaction.List, "interactions-list-typed"))

	////////////////////////////////////////////////////////////////////////////////////
	/////////////////////////////////// Channel Operations /////////////////////////////
	////////////////////////////////////////////////////////////////////////////////////
	// tested
	mux.Handle("POST", "/channel", handlerWrapper(channel.Create, "channel-create"))

	mux.Handle("GET", "/channel", handlerWrapper(channel.List, "channel-list"))
	// tested
	// deprecated, here for socialworker
	mux.Handle("POST", "/channel/{id}", handlerWrapper(channel.Update, "channel-update"))
	//
	mux.Handle("POST", "/channel/{id}/update", handlerWrapper(channel.Update, "channel-update"))
	// tested
	mux.Handle("POST", "/channel/{id}/delete", handlerWrapper(channel.Delete, "channel-delete"))
	// tested
	mux.Handle("GET", "/channel/{id}", handlerWrapper(channel.Get, "channel-get"))

	// add a new messages to the channel
	// tested
	mux.Handle("POST", "/channel/{id}/message", handlerWrapper(message.Create, "channel-message-create"))

	// list participants of the channel
	// tested
	mux.Handle("GET", "/channel/{id}/participant", handlerWrapper(participant.List, "participant-list"))

	// add participant to the channel
	//tested
	mux.Handle("POST", "/channel/{id}/participant/{accountId}/add", handlerWrapper(participant.Add, "participant-list"))

	// remove participant from the channel
	// tested
	mux.Handle("POST", "/channel/{id}/participant/{accountId}/delete", handlerWrapper(participant.Delete, "participant-list"))

	// list messages of the channel
	mux.Handle("GET", "/channel/{id}/history", handlerWrapper(messagelist.List, "channel-history-list"))

	// register an account
	mux.Handle("POST", "/account", handlerWrapper(account.Register, "account-create"))

	// list channels of the account
	mux.Handle("GET", "/account/{id}/channels", handlerWrapper(account.ListChannels, "account-channel-list"))

	// follow the account
	mux.Handle("POST", "/account/{id}/follow", handlerWrapper(account.Follow, "account-follow"))

	// un-follow the account
	mux.Handle("POST", "/account/{id}/unfollow", handlerWrapper(account.Unfollow, "account-unfollow"))

	// fetch profile feed
	// mux.Handle("GET", "/account/{id}/profile/feed", handlerWrapper(account.ListProfileFeed, "list-profile-feed"))

	// get pinning channel of the account
	mux.Handle("GET", "/activity/pin/channel", handlerWrapper(activity.GetPinnedActivityChannel, "activity-pin-get-channel"))

	// get pinning channel of the account
	mux.Handle("GET", "/activity/pin/list", handlerWrapper(activity.List, "activity-pin-list-message"))

	// pin a new status update
	mux.Handle("POST", "/activity/pin/add", handlerWrapper(activity.PinMessage, "activity-add-pinned-message"))

	// unpin a status update
	mux.Handle("POST", "/activity/pin/remove", handlerWrapper(activity.UnpinMessage, "activity-remove-pinned-message"))

	// get popular topics
	mux.Handle("GET", "/popular/topics/{statisticName}", handlerWrapper(popular.ListTopics, "list-popular-topics"))

	// mux.Handle("POST", "/follow/{id}", handlerWrapper(post, "follow-id"))
	// mux.Handle("POST", "/unfollow/{id}", handlerWrapper(post, "follow-id"))

	return mux
}

// to-do list
// get current account from context for future
// like client.connection.delegate
<|endoftext|>


<commit_before><commit_msg>adding struct for error response<commit_after><|endoftext|>


<commit_before><commit_msg>Add drain to Unwatch to prevent deadlocks<commit_after><|endoftext|>


<commit_before><commit_msg>update<commit_after><|endoftext|>


<commit_before><commit_msg>Split client creation and connection<commit_after><|endoftext|>


<commit_before><commit_msg>Bypass server ip.<commit_after><|endoftext|>


<commit_before><commit_msg>Use NewReader instead of NewBuffer (#193)<commit_after><|endoftext|>


<commit_before><commit_msg>Adding SSL support for ovn<commit_after><|endoftext|>