<commit_before>/*
Copyright 2017 The Kubernetes Authors.

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

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

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

package apps

import (
	"fmt"
	"time"

	"k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	batchinternal "k8s.io/kubernetes/pkg/apis/batch"
	"k8s.io/kubernetes/test/e2e/framework"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
)

var _ = SIGDescribe("Job", func() {
	f := framework.NewDefaultFramework("job")
	parallelism := int32(2)
	completions := int32(4)
	backoffLimit := int32(6) // default value

	// Simplest case: all pods succeed promptly
	It("should run a job to completion when tasks succeed", func() {
		By("Creating a job")
		job := framework.NewTestJob("succeed", "all-succeed", v1.RestartPolicyNever, parallelism, completions, nil, backoffLimit)
		job, err := framework.CreateJob(f.ClientSet, f.Namespace.Name, job)
		Expect(err).NotTo(HaveOccurred())

		By("Ensuring job reaches completions")
		err = framework.WaitForJobComplete(f.ClientSet, f.Namespace.Name, job.Name, completions)
		Expect(err).NotTo(HaveOccurred())
	})

	// Pods sometimes fail, but eventually succeed.
	It("should run a job to completion when tasks sometimes fail and are locally restarted", func() {
		By("Creating a job")
		// One failure, then a success, local restarts.
		// We can't use the random failure approach used by the
		// non-local test below, because kubelet will throttle
		// frequently failing containers in a given pod, ramping
		// up to 5 minutes between restarts, making test timeouts
		// due to successive failures too likely with a reasonable
		// test timeout.
		job := framework.NewTestJob("failOnce", "fail-once-local", v1.RestartPolicyOnFailure, parallelism, completions, nil, backoffLimit)
		job, err := framework.CreateJob(f.ClientSet, f.Namespace.Name, job)
		Expect(err).NotTo(HaveOccurred())

		By("Ensuring job reaches completions")
		err = framework.WaitForJobComplete(f.ClientSet, f.Namespace.Name, job.Name, completions)
		Expect(err).NotTo(HaveOccurred())
	})

	// Pods sometimes fail, but eventually succeed, after pod restarts
	It("should run a job to completion when tasks sometimes fail and are not locally restarted", func() {
		By("Creating a job")
		// 50% chance of container success, local restarts.
		// Can't use the failOnce approach because that relies
		// on an emptyDir, which is not preserved across new pods.
		// Worst case analysis: 15 failures, each taking 1 minute to
		// run due to some slowness, 1 in 2^15 chance of happening,
		// causing test flake.  Should be very rare.
		// With the introduction of backoff limit and high failure rate this
		// is hitting its timeout, the 3 is a reasonable that should make this
		// test less flaky, for now.
		job := framework.NewTestJob("randomlySucceedOrFail", "rand-non-local", v1.RestartPolicyNever, parallelism, 3, nil, 999)
		job, err := framework.CreateJob(f.ClientSet, f.Namespace.Name, job)
		Expect(err).NotTo(HaveOccurred())

		By("Ensuring job reaches completions")
		err = framework.WaitForJobComplete(f.ClientSet, f.Namespace.Name, job.Name, *job.Spec.Completions)
		Expect(err).NotTo(HaveOccurred())
	})

	It("should exceed active deadline", func() {
		By("Creating a job")
		var activeDeadlineSeconds int64 = 1
		job := framework.NewTestJob("notTerminate", "exceed-active-deadline", v1.RestartPolicyNever, parallelism, completions, &activeDeadlineSeconds, backoffLimit)
		job, err := framework.CreateJob(f.ClientSet, f.Namespace.Name, job)
		Expect(err).NotTo(HaveOccurred())
		By("Ensuring job past active deadline")
		err = framework.WaitForJobFailure(f.ClientSet, f.Namespace.Name, job.Name, time.Duration(activeDeadlineSeconds+10)*time.Second, "DeadlineExceeded")
		Expect(err).NotTo(HaveOccurred())
	})

	It("should delete a job", func() {
		By("Creating a job")
		job := framework.NewTestJob("notTerminate", "foo", v1.RestartPolicyNever, parallelism, completions, nil, backoffLimit)
		job, err := framework.CreateJob(f.ClientSet, f.Namespace.Name, job)
		Expect(err).NotTo(HaveOccurred())

		By("Ensuring active pods == parallelism")
		err = framework.WaitForAllJobPodsRunning(f.ClientSet, f.Namespace.Name, job.Name, parallelism)
		Expect(err).NotTo(HaveOccurred())

		By("delete a job")
		framework.ExpectNoError(framework.DeleteResourceAndWaitForGC(f.ClientSet, batchinternal.Kind("Job"), f.Namespace.Name, job.Name))

		By("Ensuring job was deleted")
		_, err = framework.GetJob(f.ClientSet, f.Namespace.Name, job.Name)
		Expect(err).To(HaveOccurred())
		Expect(errors.IsNotFound(err)).To(BeTrue())
	})

	It("should adopt matching orphans and release non-matching pods", func() {
		By("Creating a job")
		job := framework.NewTestJob("notTerminate", "adopt-release", v1.RestartPolicyNever, parallelism, completions, nil, backoffLimit)
		// Replace job with the one returned from Create() so it has the UID.
		// Save Kind since it won't be populated in the returned job.
		kind := job.Kind
		job, err := framework.CreateJob(f.ClientSet, f.Namespace.Name, job)
		Expect(err).NotTo(HaveOccurred())
		job.Kind = kind

		By("Ensuring active pods == parallelism")
		err = framework.WaitForAllJobPodsRunning(f.ClientSet, f.Namespace.Name, job.Name, parallelism)
		Expect(err).NotTo(HaveOccurred())

		By("Orphaning one of the Job's Pods")
		pods, err := framework.GetJobPods(f.ClientSet, f.Namespace.Name, job.Name)
		Expect(err).NotTo(HaveOccurred())
		Expect(pods.Items).To(HaveLen(int(parallelism)))
		pod := pods.Items[0]
		f.PodClient().Update(pod.Name, func(pod *v1.Pod) {
			pod.OwnerReferences = nil
		})

		By("Checking that the Job readopts the Pod")
		Expect(framework.WaitForPodCondition(f.ClientSet, pod.Namespace, pod.Name, "adopted", framework.JobTimeout,
			func(pod *v1.Pod) (bool, error) {
				controllerRef := metav1.GetControllerOf(pod)
				if controllerRef == nil {
					return false, nil
				}
				if controllerRef.Kind != job.Kind || controllerRef.Name != job.Name || controllerRef.UID != job.UID {
					return false, fmt.Errorf("pod has wrong controllerRef: got %v, want %v", controllerRef, job)
				}
				return true, nil
			},
		)).To(Succeed(), "wait for pod %q to be readopted", pod.Name)

		By("Removing the labels from the Job's Pod")
		f.PodClient().Update(pod.Name, func(pod *v1.Pod) {
			pod.Labels = nil
		})

		By("Checking that the Job releases the Pod")
		Expect(framework.WaitForPodCondition(f.ClientSet, pod.Namespace, pod.Name, "released", framework.JobTimeout,
			func(pod *v1.Pod) (bool, error) {
				controllerRef := metav1.GetControllerOf(pod)
				if controllerRef != nil {
					return false, nil
				}
				return true, nil
			},
		)).To(Succeed(), "wait for pod %q to be released", pod.Name)
	})

	It("should exceed backoffLimit", func() {
		By("Creating a job")
		backoff := 1
		job := framework.NewTestJob("fail", "backofflimit", v1.RestartPolicyNever, 1, 1, nil, int32(backoff))
		job, err := framework.CreateJob(f.ClientSet, f.Namespace.Name, job)
		Expect(err).NotTo(HaveOccurred())
		By("Ensuring job exceed backofflimit")

		err = framework.WaitForJobFailure(f.ClientSet, f.Namespace.Name, job.Name, framework.JobTimeout, "BackoffLimitExceeded")
		Expect(err).NotTo(HaveOccurred())

		By(fmt.Sprintf("Checking that %d pod created and status is failed", backoff+1))
		pods, err := framework.GetJobPods(f.ClientSet, f.Namespace.Name, job.Name)
		Expect(err).NotTo(HaveOccurred())
		// Expect(pods.Items).To(HaveLen(backoff + 1))
		// due to NumRequeus not being stable enough, especially with failed status
		// updates we need to allow more than backoff+1
		// TODO revert this back to above when https://github.com/kubernetes/kubernetes/issues/64787 gets fixed
		if len(pods.Items) < backoff+1 {
			framework.Failf("Not enough pod created expected at least %d, got %#v", backoff+1, pods.Items)
		}
		for _, pod := range pods.Items {
			Expect(pod.Status.Phase).To(Equal(v1.PodFailed))
		}
	})
})
<commit_msg>annotate errors in apps/job e2e tests<commit_after>/*
Copyright 2017 The Kubernetes Authors.

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

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

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

package apps

import (
	"fmt"
	"time"

	"k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	batchinternal "k8s.io/kubernetes/pkg/apis/batch"
	"k8s.io/kubernetes/test/e2e/framework"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
)

var _ = SIGDescribe("Job", func() {
	f := framework.NewDefaultFramework("job")
	parallelism := int32(2)
	completions := int32(4)
	backoffLimit := int32(6) // default value

	// Simplest case: all pods succeed promptly
	It("should run a job to completion when tasks succeed", func() {
		By("Creating a job")
		job := framework.NewTestJob("succeed", "all-succeed", v1.RestartPolicyNever, parallelism, completions, nil, backoffLimit)
		job, err := framework.CreateJob(f.ClientSet, f.Namespace.Name, job)
		Expect(err).NotTo(HaveOccurred(), "failed to create Job in namespace: %s", f.Namespace.Name)

		By("Ensuring job reaches completions")
		err = framework.WaitForJobComplete(f.ClientSet, f.Namespace.Name, job.Name, completions)
		Expect(err).NotTo(HaveOccurred(), "failed to ensure job completion in namespace: %s", f.Namespace.Name)
	})

	// Pods sometimes fail, but eventually succeed.
	It("should run a job to completion when tasks sometimes fail and are locally restarted", func() {
		By("Creating a job")
		// One failure, then a success, local restarts.
		// We can't use the random failure approach used by the
		// non-local test below, because kubelet will throttle
		// frequently failing containers in a given pod, ramping
		// up to 5 minutes between restarts, making test timeouts
		// due to successive failures too likely with a reasonable
		// test timeout.
		job := framework.NewTestJob("failOnce", "fail-once-local", v1.RestartPolicyOnFailure, parallelism, completions, nil, backoffLimit)
		job, err := framework.CreateJob(f.ClientSet, f.Namespace.Name, job)
		Expect(err).NotTo(HaveOccurred(), "failed to create Job in namespace: %s", f.Namespace.Name)

		By("Ensuring job reaches completions")
		err = framework.WaitForJobComplete(f.ClientSet, f.Namespace.Name, job.Name, completions)
		Expect(err).NotTo(HaveOccurred(), "failed to ensure job completion in namespace: %s", f.Namespace.Name)
	})

	// Pods sometimes fail, but eventually succeed, after pod restarts
	It("should run a job to completion when tasks sometimes fail and are not locally restarted", func() {
		By("Creating a job")
		// 50% chance of container success, local restarts.
		// Can't use the failOnce approach because that relies
		// on an emptyDir, which is not preserved across new pods.
		// Worst case analysis: 15 failures, each taking 1 minute to
		// run due to some slowness, 1 in 2^15 chance of happening,
		// causing test flake.  Should be very rare.
		// With the introduction of backoff limit and high failure rate this
		// is hitting its timeout, the 3 is a reasonable that should make this
		// test less flaky, for now.
		job := framework.NewTestJob("randomlySucceedOrFail", "rand-non-local", v1.RestartPolicyNever, parallelism, 3, nil, 999)
		job, err := framework.CreateJob(f.ClientSet, f.Namespace.Name, job)
		Expect(err).NotTo(HaveOccurred(), "failed to create Job in namespace: %s", f.Namespace.Name)

		By("Ensuring job reaches completions")
		err = framework.WaitForJobComplete(f.ClientSet, f.Namespace.Name, job.Name, *job.Spec.Completions)
		Expect(err).NotTo(HaveOccurred(), "failed to ensure job completion in namespace: %s", f.Namespace.Name)
	})

	It("should exceed active deadline", func() {
		By("Creating a job")
		var activeDeadlineSeconds int64 = 1
		job := framework.NewTestJob("notTerminate", "exceed-active-deadline", v1.RestartPolicyNever, parallelism, completions, &activeDeadlineSeconds, backoffLimit)
		job, err := framework.CreateJob(f.ClientSet, f.Namespace.Name, job)
		Expect(err).NotTo(HaveOccurred(), "failed to create Job in namespace: %s", f.Namespace.Name)
		By("Ensuring job past active deadline")
		err = framework.WaitForJobFailure(f.ClientSet, f.Namespace.Name, job.Name, time.Duration(activeDeadlineSeconds+10)*time.Second, "DeadlineExceeded")
		Expect(err).NotTo(HaveOccurred(), "failed to ensure job past active deadline in namespace: %s", f.Namespace.Name)
	})

	It("should delete a job", func() {
		By("Creating a job")
		job := framework.NewTestJob("notTerminate", "foo", v1.RestartPolicyNever, parallelism, completions, nil, backoffLimit)
		job, err := framework.CreateJob(f.ClientSet, f.Namespace.Name, job)
		Expect(err).NotTo(HaveOccurred(), "failed to create Job in namespace: %s", f.Namespace.Name)

		By("Ensuring active pods == parallelism")
		err = framework.WaitForAllJobPodsRunning(f.ClientSet, f.Namespace.Name, job.Name, parallelism)
		Expect(err).NotTo(HaveOccurred(), "failed to ensure active pods == parallelism in namespace: %s", f.Namespace.Name)

		By("delete a job")
		framework.ExpectNoError(framework.DeleteResourceAndWaitForGC(f.ClientSet, batchinternal.Kind("Job"), f.Namespace.Name, job.Name))

		By("Ensuring job was deleted")
		_, err = framework.GetJob(f.ClientSet, f.Namespace.Name, job.Name)
		Expect(err).To(HaveOccurred(), "failed to get Job %s in namespace: %s", job.Name, f.Namespace.Name)
		Expect(errors.IsNotFound(err)).To(BeTrue())
	})

	It("should adopt matching orphans and release non-matching pods", func() {
		By("Creating a job")
		job := framework.NewTestJob("notTerminate", "adopt-release", v1.RestartPolicyNever, parallelism, completions, nil, backoffLimit)
		// Replace job with the one returned from Create() so it has the UID.
		// Save Kind since it won't be populated in the returned job.
		kind := job.Kind
		job, err := framework.CreateJob(f.ClientSet, f.Namespace.Name, job)
		Expect(err).NotTo(HaveOccurred(), "failed to create Job in namespace: %s", f.Namespace.Name)
		job.Kind = kind

		By("Ensuring active pods == parallelism")
		err = framework.WaitForAllJobPodsRunning(f.ClientSet, f.Namespace.Name, job.Name, parallelism)
		Expect(err).NotTo(HaveOccurred(), "failed to ensure active pods == parallelism in namespace: %s", f.Namespace.Name)

		By("Orphaning one of the Job's Pods")
		pods, err := framework.GetJobPods(f.ClientSet, f.Namespace.Name, job.Name)
		Expect(err).NotTo(HaveOccurred(), "failed to get PodList for job %s in namespace: %s", job.Name, f.Namespace.Name)
		Expect(pods.Items).To(HaveLen(int(parallelism)))
		pod := pods.Items[0]
		f.PodClient().Update(pod.Name, func(pod *v1.Pod) {
			pod.OwnerReferences = nil
		})

		By("Checking that the Job readopts the Pod")
		Expect(framework.WaitForPodCondition(f.ClientSet, pod.Namespace, pod.Name, "adopted", framework.JobTimeout,
			func(pod *v1.Pod) (bool, error) {
				controllerRef := metav1.GetControllerOf(pod)
				if controllerRef == nil {
					return false, nil
				}
				if controllerRef.Kind != job.Kind || controllerRef.Name != job.Name || controllerRef.UID != job.UID {
					return false, fmt.Errorf("pod has wrong controllerRef: got %v, want %v", controllerRef, job)
				}
				return true, nil
			},
		)).To(Succeed(), "wait for pod %q to be readopted", pod.Name)

		By("Removing the labels from the Job's Pod")
		f.PodClient().Update(pod.Name, func(pod *v1.Pod) {
			pod.Labels = nil
		})

		By("Checking that the Job releases the Pod")
		Expect(framework.WaitForPodCondition(f.ClientSet, pod.Namespace, pod.Name, "released", framework.JobTimeout,
			func(pod *v1.Pod) (bool, error) {
				controllerRef := metav1.GetControllerOf(pod)
				if controllerRef != nil {
					return false, nil
				}
				return true, nil
			},
		)).To(Succeed(), "wait for pod %q to be released", pod.Name)
	})

	It("should exceed backoffLimit", func() {
		By("Creating a job")
		backoff := 1
		job := framework.NewTestJob("fail", "backofflimit", v1.RestartPolicyNever, 1, 1, nil, int32(backoff))
		job, err := framework.CreateJob(f.ClientSet, f.Namespace.Name, job)
		Expect(err).NotTo(HaveOccurred(), "failed to create Job in namespace: %s", f.Namespace.Name)
		By("Ensuring job exceed backofflimit")

		err = framework.WaitForJobFailure(f.ClientSet, f.Namespace.Name, job.Name, framework.JobTimeout, "BackoffLimitExceeded")
		Expect(err).NotTo(HaveOccurred(), "failed to ensure job exceed backofflimit in namespace: %s", f.Namespace.Name)

		By(fmt.Sprintf("Checking that %d pod created and status is failed", backoff+1))
		pods, err := framework.GetJobPods(f.ClientSet, f.Namespace.Name, job.Name)
		Expect(err).NotTo(HaveOccurred(), "failed to get PodList for job %s in namespace: %s", job.Name, f.Namespace.Name)
		// Expect(pods.Items).To(HaveLen(backoff + 1))
		// due to NumRequeus not being stable enough, especially with failed status
		// updates we need to allow more than backoff+1
		// TODO revert this back to above when https://github.com/kubernetes/kubernetes/issues/64787 gets fixed
		if len(pods.Items) < backoff+1 {
			framework.Failf("Not enough pod created expected at least %d, got %#v", backoff+1, pods.Items)
		}
		for _, pod := range pods.Items {
			Expect(pod.Status.Phase).To(Equal(v1.PodFailed))
		}
	})
})
<|endoftext|>


<commit_before>//go:generate go run gen.go

package version

// Version tells us the app version string
const Version = "3.3.0"
<commit_msg>Bump to v3.3.1<commit_after>//go:generate go run gen.go

package version

// Version tells us the app version string
const Version = "3.3.1"
<|endoftext|>


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

// Unix environment variables.

package os

import (
	"sync"
)

// ENOENV is the Error indicating that an environment variable does not exist.
var ENOENV = NewError("no such environment variable")

var env map[string]string
var once sync.Once

func copyenv() {
	env = make(map[string]string)
	for _, s := range Envs {
		for j := 0; j < len(s); j++ {
			if s[j] == '=' {
				env[s[0:j]] = s[j+1:]
				break
			}
		}
	}
}

var envLock sync.RWMutex

// Getenverror retrieves the value of the environment variable named by the key.
// It returns the value and an error, if any.
func Getenverror(key string) (value string, err Error) {
	once.Do(copyenv)

	if len(key) == 0 {
		return "", EINVAL
	}

	envLock.RLock()
	defer envLock.RUnlock()

	v, ok := env[key]
	if !ok {
		return "", ENOENV
	}
	return v, nil
}

// Getenv retrieves the value of the environment variable named by the key.
// It returns the value, which will be empty if the variable is not present.
func Getenv(key string) string {
	v, _ := Getenverror(key)
	return v
}

// Setenv sets the value of the environment variable named by the key.
// It returns an Error, if any.
func Setenv(key, value string) Error {
	once.Do(copyenv)
	if len(key) == 0 {
		return EINVAL
	}

	envLock.Lock()
	defer envLock.Unlock()

	env[key] = value
	setenv_c(key, value) // is a no-op if cgo isn't loaded
	return nil
}

// Clearenv deletes all environment variables.
func Clearenv() {
	once.Do(copyenv) // prevent copyenv in Getenv/Setenv

	envLock.Lock()
	defer envLock.Unlock()

	env = make(map[string]string)

	// TODO(bradfitz): pass through to C
}

// Environ returns an array of strings representing the environment,
// in the form "key=value".
func Environ() []string {
	once.Do(copyenv)
	envLock.RLock()
	defer envLock.RUnlock()
	a := make([]string, len(env))
	i := 0
	for k, v := range env {
		// check i < len(a) for safety,
		// in case env is changing underfoot.
		if i < len(a) {
			a[i] = k + "=" + v
			i++
		}
	}
	return a[0:i]
}

// TempDir returns the default directory to use for temporary files.
func TempDir() string {
	dir := Getenv("TMPDIR")
	if dir == "" {
		dir = "/tmp"
	}
	return dir
}
<commit_msg>os: remove race paranoia from Environ(); it's guarded now<commit_after>// Copyright 2010 The Go Authors.  All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Unix environment variables.

package os

import (
	"sync"
)

// ENOENV is the Error indicating that an environment variable does not exist.
var ENOENV = NewError("no such environment variable")

var env map[string]string
var once sync.Once

func copyenv() {
	env = make(map[string]string)
	for _, s := range Envs {
		for j := 0; j < len(s); j++ {
			if s[j] == '=' {
				env[s[0:j]] = s[j+1:]
				break
			}
		}
	}
}

var envLock sync.RWMutex

// Getenverror retrieves the value of the environment variable named by the key.
// It returns the value and an error, if any.
func Getenverror(key string) (value string, err Error) {
	once.Do(copyenv)

	if len(key) == 0 {
		return "", EINVAL
	}

	envLock.RLock()
	defer envLock.RUnlock()

	v, ok := env[key]
	if !ok {
		return "", ENOENV
	}
	return v, nil
}

// Getenv retrieves the value of the environment variable named by the key.
// It returns the value, which will be empty if the variable is not present.
func Getenv(key string) string {
	v, _ := Getenverror(key)
	return v
}

// Setenv sets the value of the environment variable named by the key.
// It returns an Error, if any.
func Setenv(key, value string) Error {
	once.Do(copyenv)
	if len(key) == 0 {
		return EINVAL
	}

	envLock.Lock()
	defer envLock.Unlock()

	env[key] = value
	setenv_c(key, value) // is a no-op if cgo isn't loaded
	return nil
}

// Clearenv deletes all environment variables.
func Clearenv() {
	once.Do(copyenv) // prevent copyenv in Getenv/Setenv

	envLock.Lock()
	defer envLock.Unlock()

	env = make(map[string]string)

	// TODO(bradfitz): pass through to C
}

// Environ returns an array of strings representing the environment,
// in the form "key=value".
func Environ() []string {
	once.Do(copyenv)
	envLock.RLock()
	defer envLock.RUnlock()
	a := make([]string, len(env))
	i := 0
	for k, v := range env {
		a[i] = k + "=" + v
		i++
	}
	return a
}

// TempDir returns the default directory to use for temporary files.
func TempDir() string {
	dir := Getenv("TMPDIR")
	if dir == "" {
		dir = "/tmp"
	}
	return dir
}
<|endoftext|>


<commit_before>// HTTP router
//
// Basic example:
//
//  package main
//
//  import (
//     "fmt"
//     "github.com/nbari/violetear"
//     "net/http"
//  )
//
//  func catchAll(w http.ResponseWriter, r *http.Request) {
//      fmt.Fprintf(w, r.URL.Path[1:])
//  }
//
//  func helloWorld(w http.ResponseWriter, r *http.Request) {
//      fmt.Fprintf(w, r.URL.Path[1:])
//  }
//
//  func handleUUID(w http.ResponseWriter, r *http.Request) {
//      fmt.Fprintf(w, r.URL.Path[1:])
//  }
//
//  func main() {
//      router := violetear.New()
//      router.LogRequests = true
//      router.Request_ID = "REQUEST_LOG_ID"
//
//      router.AddRegex(":uuid", `[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}`)
//
//      router.HandleFunc("*", catchAll)
//      router.HandleFunc("/hello/", helloWorld, "GET,HEAD")
//      router.HandleFunc("/root/:uuid/item", handleUUID, "POST,PUT")
//
//      router.SetHeader("X-app-version", "1.1")
//
//      router.Run(":8080")
//  }
//
package violetear

import (
	"fmt"
	"log"
	"net/http"
	"regexp"
	"strings"
	"sync/atomic"
	"time"
)

type Router struct {
	// Routes to be matched
	routes *Trie

	// dynamicRoutes map of dynamic routes and regular expresions
	dynamicRoutes dynamicSet

	// logRequests yes or no
	LogRequests bool

	// NotFoundHandler configurable http.Handler which is called when no matching
	// route is found. If it is not set, http.NotFound is used.
	NotFoundHandler http.Handler

	// NotAllowedHandler configurable http.Handler which is called when method not allowed.
	NotAllowedHandler http.Handler

	// request-id to use
	Request_ID string

	// extraHeaders adds exta headers to the response
	extraHeaders map[string]string

	// count counter for hits
	count int64

	// Verbose
	Verbose bool
}

var split_path_rx = regexp.MustCompile(`[^/ ]+`)

// New returns a new initialized router.
func New() *Router {
	return &Router{
		routes:        NewTrie(),
		dynamicRoutes: make(dynamicSet),
		extraHeaders:  make(map[string]string),
		Verbose:       true,
	}
}

// Run violetear as an HTTP server.
// The addr string takes the same format as http.ListenAndServe.
func (v *Router) Run(addr string) {
	if v.Verbose {
		log.Printf("Router listening on %s", addr)
	}
	log.Fatal(http.ListenAndServe(addr, v))
}

// SetHeader adds extra headers to the response
func (v *Router) SetHeader(key, value string) {
	v.extraHeaders[key] = value
}

// HandleFunc add a route to the router (path, HandlerFunc, methods)
func (v *Router) HandleFunc(path string, handler http.HandlerFunc, http_methods ...string) error {
	path_parts := v.splitPath(path)

	// search for dynamic routes
	for _, p := range path_parts {
		if strings.HasPrefix(p, ":") {
			if _, ok := v.dynamicRoutes[p]; !ok {
				return fmt.Errorf("[%s] not found, need to add it using AddRegex(\"%s\", `your regex`)", p, p)
			}
		}
	}

	// if no methods, accept ALL
	methods := "ALL"
	if len(http_methods) > 0 {
		methods = http_methods[0]
	}

	if v.Verbose {
		log.Printf("Adding path: %s [%s]", path, methods)
	}
	if err := v.routes.Set(path_parts, handler, methods); err != nil {
		return err
	}
	return nil
}

// AddRegex adds a ":named" regular expression to the dynamicRoutes
func (v *Router) AddRegex(name string, regex string) error {
	return v.dynamicRoutes.Set(name, regex)
}

// MethodNotAllowed default handler for 405
func (v *Router) MethodNotAllowed() http.HandlerFunc {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		http.Error(w,
			http.StatusText(http.StatusMethodNotAllowed),
			http.StatusMethodNotAllowed,
		)
	})
}

// ServerHTTP dispatches the handler registered in the matched path
func (v *Router) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	start := time.Now()
	atomic.AddInt64(&v.count, 1)
	lw := NewResponseWriter(w)

	node, path, leaf, err := v.routes.Get(v.splitPath(r.URL.Path))

	if err != nil {
		log.Fatal(err)
	}

	// checkMethod check if method is allowed or not
	checkMethod := func(node *Trie, method string) http.Handler {
		if h, ok := node.Handler[method]; ok {
			return h
		} else if h, ok := node.Handler["ALL"]; ok {
			return h
		}
		if v.NotAllowedHandler != nil {
			return v.NotAllowedHandler
		} else {
			return v.MethodNotAllowed()
		}
	}

	var match func(node *Trie, path []string, leaf bool) http.Handler

	// match find a handler for the request
	match = func(node *Trie, path []string, leaf bool) http.Handler {
		if len(node.Handler) > 0 && leaf {
			return checkMethod(node, r.Method)
		} else if node.HasRegex {
			for k, _ := range node.Node {
				if strings.HasPrefix(k, ":") {
					rx := v.dynamicRoutes[k]
					if rx.MatchString(path[0]) {
						path[0] = k
						if leaf {
							return checkMethod(node.Node[k], r.Method)
						} else {
							node, path, leaf, _ := node.Get(path)
							return match(node, path, leaf)
						}
					}
				}
			}
			if node.HasCatchall {
				return checkMethod(node.Node["*"], r.Method)
			}
		} else if node.HasCatchall {
			return checkMethod(node.Node["*"], r.Method)
		}
		// NotFound
		if v.NotFoundHandler != nil {
			return v.NotFoundHandler
		}
		return http.NotFoundHandler()
	}

	// rid Set Request-ID
	rid := r.Header.Get(v.Request_ID)
	if rid != "" {
		w.Header().Set(v.Request_ID, rid)
	} else {
		rid = fmt.Sprintf("%s-%d-%d", r.Method, time.Now().UnixNano(), atomic.LoadInt64(&v.count))
		w.Header().Set("Request-ID", rid)
	}

	// set extra headers
	for k, v := range v.extraHeaders {
		w.Header().Set(k, v)
	}

	//h http.Handler
	h := match(node, path, leaf)

	// panicHandler
	defer func() {
		if err := recover(); err != nil {
			log.Printf("panic: %+v - %s [%s] %v %s",
				err,
				r.RemoteAddr,
				r.URL,
				time.Since(start),
				rid)
			http.Error(w, http.StatusText(500), http.StatusInternalServerError)
		}
	}()

	// dispatch request
	h.ServeHTTP(lw, r)

	if v.LogRequests {
		log.Printf("%s [%s] %d %d %v %s",
			r.RemoteAddr,
			r.URL,
			lw.Status(),
			lw.Size(),
			time.Since(start),
			rid)
	}
	return
}

// splitPath returns an slice of the path
func (v *Router) splitPath(p string) []string {
	path_parts := split_path_rx.FindAllString(p, -1)

	// root (empty slice)
	if len(path_parts) == 0 {
		path_parts = append(path_parts, "/")
	}

	return path_parts
}
<commit_msg>	modified:   violetear.go<commit_after>// HTTP router
//
// Basic example:
//
//  package main
//
//  import (
//     "fmt"
//     "github.com/nbari/violetear"
//     "net/http"
//  )
//
//  func catchAll(w http.ResponseWriter, r *http.Request) {
//      fmt.Fprintf(w, r.URL.Path[1:])
//  }
//
//  func helloWorld(w http.ResponseWriter, r *http.Request) {
//      fmt.Fprintf(w, r.URL.Path[1:])
//  }
//
//  func handleUUID(w http.ResponseWriter, r *http.Request) {
//      fmt.Fprintf(w, r.URL.Path[1:])
//  }
//
//  func main() {
//      router := violetear.New()
//      router.LogRequests = true
//      router.Request_ID = "REQUEST_LOG_ID"
//
//      router.AddRegex(":uuid", `[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}`)
//
//      router.HandleFunc("*", catchAll)
//      router.HandleFunc("/hello/", helloWorld, "GET,HEAD")
//      router.HandleFunc("/root/:uuid/item", handleUUID, "POST,PUT")
//
//      router.SetHeader("X-app-version", "1.1")
//
//		http.ListenAndServe(":8080", router)
//  }
//
package violetear

import (
	"fmt"
	"log"
	"net/http"
	"regexp"
	"strings"
	"sync/atomic"
	"time"
)

type Router struct {
	// Routes to be matched
	routes *Trie

	// dynamicRoutes map of dynamic routes and regular expresions
	dynamicRoutes dynamicSet

	// logRequests yes or no
	LogRequests bool

	// NotFoundHandler configurable http.Handler which is called when no matching
	// route is found. If it is not set, http.NotFound is used.
	NotFoundHandler http.Handler

	// NotAllowedHandler configurable http.Handler which is called when method not allowed.
	NotAllowedHandler http.Handler

	// request-id to use
	Request_ID string

	// extraHeaders adds exta headers to the response
	extraHeaders map[string]string

	// count counter for hits
	count int64

	// Verbose
	Verbose bool
}

var split_path_rx = regexp.MustCompile(`[^/ ]+`)

// New returns a new initialized router.
func New() *Router {
	return &Router{
		routes:        NewTrie(),
		dynamicRoutes: make(dynamicSet),
		extraHeaders:  make(map[string]string),
		Verbose:       true,
	}
}

// SetHeader adds extra headers to the response
func (v *Router) SetHeader(key, value string) {
	v.extraHeaders[key] = value
}

// HandleFunc add a route to the router (path, HandlerFunc, methods)
func (v *Router) HandleFunc(path string, handler http.HandlerFunc, http_methods ...string) error {
	path_parts := v.splitPath(path)

	// search for dynamic routes
	for _, p := range path_parts {
		if strings.HasPrefix(p, ":") {
			if _, ok := v.dynamicRoutes[p]; !ok {
				return fmt.Errorf("[%s] not found, need to add it using AddRegex(\"%s\", `your regex`)", p, p)
			}
		}
	}

	// if no methods, accept ALL
	methods := "ALL"
	if len(http_methods) > 0 {
		methods = http_methods[0]
	}

	if v.Verbose {
		log.Printf("Adding path: %s [%s]", path, methods)
	}
	if err := v.routes.Set(path_parts, handler, methods); err != nil {
		return err
	}
	return nil
}

// AddRegex adds a ":named" regular expression to the dynamicRoutes
func (v *Router) AddRegex(name string, regex string) error {
	return v.dynamicRoutes.Set(name, regex)
}

// MethodNotAllowed default handler for 405
func (v *Router) MethodNotAllowed() http.HandlerFunc {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		http.Error(w,
			http.StatusText(http.StatusMethodNotAllowed),
			http.StatusMethodNotAllowed,
		)
	})
}

// ServerHTTP dispatches the handler registered in the matched path
func (v *Router) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	start := time.Now()
	atomic.AddInt64(&v.count, 1)
	lw := NewResponseWriter(w)

	node, path, leaf, err := v.routes.Get(v.splitPath(r.URL.Path))

	if err != nil {
		log.Fatal(err)
	}

	// checkMethod check if method is allowed or not
	checkMethod := func(node *Trie, method string) http.Handler {
		if h, ok := node.Handler[method]; ok {
			return h
		} else if h, ok := node.Handler["ALL"]; ok {
			return h
		}
		if v.NotAllowedHandler != nil {
			return v.NotAllowedHandler
		} else {
			return v.MethodNotAllowed()
		}
	}

	var match func(node *Trie, path []string, leaf bool) http.Handler

	// match find a handler for the request
	match = func(node *Trie, path []string, leaf bool) http.Handler {
		if len(node.Handler) > 0 && leaf {
			return checkMethod(node, r.Method)
		} else if node.HasRegex {
			for k, _ := range node.Node {
				if strings.HasPrefix(k, ":") {
					rx := v.dynamicRoutes[k]
					if rx.MatchString(path[0]) {
						path[0] = k
						if leaf {
							return checkMethod(node.Node[k], r.Method)
						} else {
							node, path, leaf, _ := node.Get(path)
							return match(node, path, leaf)
						}
					}
				}
			}
			if node.HasCatchall {
				return checkMethod(node.Node["*"], r.Method)
			}
		} else if node.HasCatchall {
			return checkMethod(node.Node["*"], r.Method)
		}
		// NotFound
		if v.NotFoundHandler != nil {
			return v.NotFoundHandler
		}
		return http.NotFoundHandler()
	}

	// rid Set Request-ID
	rid := r.Header.Get(v.Request_ID)
	if rid != "" {
		w.Header().Set(v.Request_ID, rid)
	} else {
		rid = fmt.Sprintf("%s-%d-%d", r.Method, time.Now().UnixNano(), atomic.LoadInt64(&v.count))
		w.Header().Set("Request-ID", rid)
	}

	// set extra headers
	for k, v := range v.extraHeaders {
		w.Header().Set(k, v)
	}

	//h http.Handler
	h := match(node, path, leaf)

	// panicHandler
	defer func() {
		if err := recover(); err != nil {
			log.Printf("panic: %+v - %s [%s] %v %s",
				err,
				r.RemoteAddr,
				r.URL,
				time.Since(start),
				rid)
			http.Error(w, http.StatusText(500), http.StatusInternalServerError)
		}
	}()

	// dispatch request
	h.ServeHTTP(lw, r)

	if v.LogRequests {
		log.Printf("%s [%s] %d %d %v %s",
			r.RemoteAddr,
			r.URL,
			lw.Status(),
			lw.Size(),
			time.Since(start),
			rid)
	}
	return
}

// splitPath returns an slice of the path
func (v *Router) splitPath(p string) []string {
	path_parts := split_path_rx.FindAllString(p, -1)

	// root (empty slice)
	if len(path_parts) == 0 {
		path_parts = append(path_parts, "/")
	}

	return path_parts
}
<|endoftext|>


<commit_before>package store

import (
	"bytes"
	"gob"
	"doozer/util"
	"math"
	"os"
	"regexp"
	"strings"
)

const (
	Clobber = ""
	Missing = "0"
	Dir     = "dir"
)

// TODO revisit this when package regexp is more complete (e.g. do Unicode)
const (
	charPat = `([a-zA-Z0-9.]|-)`
	partPat = "/" + charPat + "+"
	pathPat = "^/$|^(" + partPat + ")+$"
)

var pathRe = regexp.MustCompile(pathPat)

var Any = MustCompileGlob("**")

var (
	ErrBadMutation = os.NewError("bad mutation")
	ErrBadSnapshot = os.NewError("bad snapshot")
	ErrTooLate     = os.NewError("too late")
	ErrCasMismatch = os.NewError("cas mismatch")
)

type BadPathError struct {
	Path string
}

func (e *BadPathError) String() string {
	return "bad path: " + e.Path
}

// Applies mutations sent on Ops in sequence according to field Seqn. Any
// errors that occur will be written to ErrorPath. Duplicate operations at a
// given position are sliently ignored.
type Store struct {
	Ops     chan<- Op
	Seqns   <-chan uint64
	Watches <-chan int
	watchCh chan *Watch
	watches []*Watch
	todo    map[uint64]Op
	state   *state
	log     map[uint64]Event
	cleanCh chan uint64
	notices []notice
}

// Represents an operation to apply to the store at position Seqn.
//
// If Mut is a snapshot, notifications will not be sent.
//
// If Mut is Nop, no change will be made, but a dummy event will still be sent.
type Op struct {
	Seqn uint64
	Mut  string
}

type state struct {
	ver  uint64
	root node
}

type Watch struct {
	C        <-chan Event
	c        chan<- Event
	glob     *Glob
	from, to uint64
	shutdown chan bool
}

func (wt *Watch) Stop() {
	_ = wt.shutdown <- true
}

type notice struct {
	ch chan<- Event
	ev Event
}

// Creates a new, empty data store. Mutations will be applied in order,
// starting at number 1 (number 0 can be thought of as the creation of the
// store).
func New() *Store {
	ops := make(chan Op)
	seqns := make(chan uint64)
	watches := make(chan int)

	st := &Store{
		Ops:     ops,
		Seqns:   seqns,
		Watches: watches,
		watchCh: make(chan *Watch),
		todo:    make(map[uint64]Op),
		watches: []*Watch{},
		state:   &state{0, emptyDir},
		log:     make(map[uint64]Event),
		cleanCh: make(chan uint64),
		notices: make([]notice, 1),
	}

	go st.process(ops, seqns, watches)
	return st
}

func split(path string) []string {
	if path == "/" {
		return []string{}
	}
	return strings.Split(path[1:], "/", -1)
}

func join(parts []string) string {
	return "/" + strings.Join(parts, "/")
}

func checkPath(k string) os.Error {
	if !pathRe.MatchString(k) {
		return &BadPathError{k}
	}
	return nil
}

// Returns a mutation that can be applied to a `Store`. The mutation will set
// the contents of the file at `path` to `body` iff the CAS token of that file
// matches `cas` at the time of application.
//
// If `path` is not valid, returns a `BadPathError`.
func EncodeSet(path, body string, cas string) (mutation string, err os.Error) {
	if err = checkPath(path); err != nil {
		return
	}
	return cas + ":" + path + "=" + body, nil
}

// Returns a mutation that can be applied to a `Store`. The mutation will cause
// the file at `path` to be deleted iff the CAS token of that file matches
// `cas` at the time of application.
//
// If `path` is not valid, returns a `BadPathError`.
func EncodeDel(path string, cas string) (mutation string, err os.Error) {
	if err := checkPath(path); err != nil {
		return
	}
	return cas + ":" + path, nil
}

// MustEncodeSet is like EncodeSet but panics if the mutation cannot be
// encoded. It simplifies safe initialization of global variables holding
// mutations.
func MustEncodeSet(path, body, cas string) (mutation string) {
	m, err := EncodeSet(path, body, cas)
	if err != nil {
		panic(err)
	}
	return m
}

// MustEncodeDel is like EncodeDel but panics if the mutation cannot be
// encoded. It simplifies safe initialization of global variables holding
// mutations.
func MustEncodeDel(path, cas string) (mutation string) {
	m, err := EncodeDel(path, cas)
	if err != nil {
		panic(err)
	}
	return m
}

func decode(mutation string) (path, v, cas string, keep bool, err os.Error) {
	cm := strings.Split(mutation, ":", 2)

	if len(cm) != 2 {
		err = ErrBadMutation
		return
	}

	kv := strings.Split(cm[1], "=", 2)

	if err = checkPath(kv[0]); err != nil {
		return
	}

	switch len(kv) {
	case 1:
		return kv[0], "", cm[0], false, nil
	case 2:
		return kv[0], kv[1], cm[0], true, nil
	}
	panic("unreachable")
}

func (st *Store) notify(e Event, ws []*Watch) []*Watch {
	nwatches := make([]*Watch, len(ws))

	i := 0
	for _, w := range ws {
		if _, ok := <-w.shutdown; ok {
			continue
		}

		if e.Seqn >= w.to {
			continue
		}

		last := w.to - 1
		if e.Seqn != last {
			nwatches[i] = w
			i++
		}

		if e.Seqn < w.from {
			continue
		}

		if w.glob.r.MatchString(e.Path) {
			st.notices = append(st.notices, notice{w.c, e})

			if st.notices[0].ch == nil {
				st.notices = st.notices[1:]
			}
		}
	}

	return nwatches[0:i]
}

func (st *Store) closeWatches() {
	for _, w := range st.watches {
		close(w.c)
	}
}

func (st *Store) process(ops <-chan Op, seqns chan<- uint64, watches chan<- int) {
	logger := util.NewLogger("store")
	defer st.closeWatches()

	var head uint64

	for {
		ver, values := st.state.ver, st.state.root

		// Take any incoming requests and queue them up.
		select {
		case a := <-ops:
			if closed(ops) {
				return
			}

			if a.Seqn > ver {
				st.todo[a.Seqn] = a
			}
		case w := <-st.watchCh:
			n, ws := w.from, []*Watch{w}
			for ; len(ws) > 0 && n < head; n++ {
				ws = st.notify(Event{Seqn: n, Err: ErrTooLate}, ws)
			}
			for ; len(ws) > 0 && n <= ver; n++ {
				ws = st.notify(st.log[n], ws)
			}

			st.watches = append(st.watches, ws...)
		case seqn := <-st.cleanCh:
			for ; head <= seqn; head++ {
				st.log[head] = Event{}, false
			}
		case seqns <- ver:
			// nothing to do here
		case watches <- len(st.watches):
			// nothing to do here
		case st.notices[0].ch <- st.notices[0].ev:
			st.notices = st.notices[1:]

			if len(st.notices) < 1 {
				st.notices = make([]notice, 1)
			}
		}

		// If we have any mutations that can be applied, do them.
		for t, ok := st.todo[ver+1]; ok; t, ok = st.todo[ver+1] {
			var ev Event
			var snap bool
			values, ev, snap = values.apply(t.Seqn, t.Mut)
			logger.Printf("apply %s %v %v %v %v %v", ev.Desc(), ev.Seqn, ev.Path, ev.Body, ev.Cas, ev.Err)
			st.state = &state{ev.Seqn, values}
			st.log[t.Seqn] = ev
			st.watches = st.notify(ev, st.watches)
			for ver < ev.Seqn {
				ver++
				st.todo[ver] = Op{}, false
			}
			if snap {
				head = ev.Seqn + 1
			}
		}
	}
}

// Returns a point-in-time snapshot of the contents of the store.
func (st *Store) Snap() (ver uint64, g Getter) {
	// WARNING: Be sure to read the pointer value of st.state only once. If you
	// need multiple accesses, copy the pointer first.
	p := st.state

	return p.ver, p.root
}

// Gets the value stored at `path`, if any.
//
// If no value is stored at `path`, `cas` will be `Missing` and `value` will be
// nil.
//
// if `path` is a directory, `cas` will be `Dir` and `value` will be a list of
// entries.
//
// Otherwise, `cas` is the CAS token and `value[0]` is the body.
func (st *Store) Get(path string) (value []string, cas string) {
	_, g := st.Snap()
	return g.Get(path)
}

// Encodes the entire storage state, including the current sequence number, as
// a mutation. This mutation can be applied to an empty store to reproduce the
// state of `s`.
//
// Returns the sequence number of the snapshot and the mutation itself.
//
// A snapshot must be applied at sequence number 1. Once a snapshot has been
// applied, the store's sequence number will be set to `seqn`.
//
// Note that applying a snapshot does not send notifications.
func (st *Store) Snapshot() (seqn uint64, mutation string) {
	w := new(bytes.Buffer)
	ver, g := st.Snap()

	err := gob.NewEncoder(w).Encode(ver)
	if err != nil {
		panic(err)
	}

	err = gob.NewEncoder(w).Encode(g)
	if err != nil {
		panic(err)
	}

	return ver, w.String()
}

// Returns a channel that will receive notifications when mutations are applied
// to paths in the store. One event will be sent for each mutation iff the
// event's path matches `pattern`, a Unix-style glob pattern.
//
// Glob notation:
//  - "?" matches a single char in a single path component
//  - "*" matches zero or more chars in a single path component
//  - "**" matches zero or more chars in zero or more components
//  - any other sequence matches itself
//
// Notifications will not be sent for changes made as the result of applying a
// snapshot.
func (st *Store) Watch(glob *Glob) <-chan Event {
	return NewWatch(st, glob).C
}

func NewWatch(st *Store, glob *Glob) *Watch {
	ch := make(chan Event)
	ver, _ := st.Snap()
	return st.watchOn(glob, ch, ver+1, math.MaxUint64)
}

func (st *Store) watchOn(glob *Glob, ch chan Event, from, to uint64) *Watch {
	wt := &Watch{
		C:        ch,
		c:        ch,
		glob:     glob,
		from:     from,
		to:       to,
		shutdown: make(chan bool, 1),
	}
	st.watchCh <- wt
	return wt
}

// Returns a read-only chan that will receive a single event representing the
// change made at position `seqn`.
//
// If `seqn` was applied before the call to `Wait`, a dummy event will be
// sent with its `Err` set to `ErrTooLate`.
func (st *Store) Wait(seqn uint64) <-chan Event {
	ch := make(chan Event, 1)
	st.watchOn(Any, ch, seqn, seqn+1)
	return ch
}

// Ensures that the application of mutation at `seqn` happens before the call
// to `Sync` returns.
//
// See http://golang.org/doc/go_mem.html for the meaning of "happens before" in
// Go.
func (st *Store) Sync(seqn uint64) {
	<-st.Wait(seqn)
}

// Returns an immutable copy of `st` in which `path` exists as a regular file
// (not a dir). Waits for `path` to be set, if necessary.
func (st *Store) SyncPath(path string) (Getter, os.Error) {
	glob, err := CompileGlob(path)
	if err != nil {
		return nil, err
	}

	wt := NewWatch(st, glob)
	defer wt.Stop()

	_, g := st.Snap()
	_, cas := g.Get(path)
	if cas != Dir && cas != Missing {
		return g, nil
	}

	for ev := range wt.C {
		if ev.IsSet() {
			return ev, nil
		}
	}

	panic("unreachable")
}

func (st *Store) Clean(seqn uint64) {
	st.cleanCh <- seqn
}
<commit_msg>refactor<commit_after>package store

import (
	"bytes"
	"gob"
	"doozer/util"
	"math"
	"os"
	"regexp"
	"strings"
)

const (
	Clobber = ""
	Missing = "0"
	Dir     = "dir"
)

// TODO revisit this when package regexp is more complete (e.g. do Unicode)
const (
	charPat = `([a-zA-Z0-9.]|-)`
	partPat = "/" + charPat + "+"
	pathPat = "^/$|^(" + partPat + ")+$"
)

var pathRe = regexp.MustCompile(pathPat)

var Any = MustCompileGlob("**")

var (
	ErrBadMutation = os.NewError("bad mutation")
	ErrBadSnapshot = os.NewError("bad snapshot")
	ErrTooLate     = os.NewError("too late")
	ErrCasMismatch = os.NewError("cas mismatch")
)

type BadPathError struct {
	Path string
}

func (e *BadPathError) String() string {
	return "bad path: " + e.Path
}

// Applies mutations sent on Ops in sequence according to field Seqn. Any
// errors that occur will be written to ErrorPath. Duplicate operations at a
// given position are sliently ignored.
type Store struct {
	Ops     chan<- Op
	Seqns   <-chan uint64
	Watches <-chan int
	watchCh chan *Watch
	watches []*Watch
	todo    map[uint64]Op
	state   *state
	log     map[uint64]Event
	cleanCh chan uint64
	notices []notice
}

// Represents an operation to apply to the store at position Seqn.
//
// If Mut is a snapshot, notifications will not be sent.
//
// If Mut is Nop, no change will be made, but a dummy event will still be sent.
type Op struct {
	Seqn uint64
	Mut  string
}

type state struct {
	ver  uint64
	root node
}

type Watch struct {
	C        <-chan Event
	c        chan<- Event
	glob     *Glob
	from, to uint64
	shutdown chan bool
}

func (wt *Watch) Stop() {
	_ = wt.shutdown <- true
}

type notice struct {
	ch chan<- Event
	ev Event
}

// Creates a new, empty data store. Mutations will be applied in order,
// starting at number 1 (number 0 can be thought of as the creation of the
// store).
func New() *Store {
	ops := make(chan Op)
	seqns := make(chan uint64)
	watches := make(chan int)

	st := &Store{
		Ops:     ops,
		Seqns:   seqns,
		Watches: watches,
		watchCh: make(chan *Watch),
		todo:    make(map[uint64]Op),
		watches: []*Watch{},
		state:   &state{0, emptyDir},
		log:     make(map[uint64]Event),
		cleanCh: make(chan uint64),
	}

	go st.process(ops, seqns, watches)
	return st
}

func split(path string) []string {
	if path == "/" {
		return []string{}
	}
	return strings.Split(path[1:], "/", -1)
}

func join(parts []string) string {
	return "/" + strings.Join(parts, "/")
}

func checkPath(k string) os.Error {
	if !pathRe.MatchString(k) {
		return &BadPathError{k}
	}
	return nil
}

// Returns a mutation that can be applied to a `Store`. The mutation will set
// the contents of the file at `path` to `body` iff the CAS token of that file
// matches `cas` at the time of application.
//
// If `path` is not valid, returns a `BadPathError`.
func EncodeSet(path, body string, cas string) (mutation string, err os.Error) {
	if err = checkPath(path); err != nil {
		return
	}
	return cas + ":" + path + "=" + body, nil
}

// Returns a mutation that can be applied to a `Store`. The mutation will cause
// the file at `path` to be deleted iff the CAS token of that file matches
// `cas` at the time of application.
//
// If `path` is not valid, returns a `BadPathError`.
func EncodeDel(path string, cas string) (mutation string, err os.Error) {
	if err := checkPath(path); err != nil {
		return
	}
	return cas + ":" + path, nil
}

// MustEncodeSet is like EncodeSet but panics if the mutation cannot be
// encoded. It simplifies safe initialization of global variables holding
// mutations.
func MustEncodeSet(path, body, cas string) (mutation string) {
	m, err := EncodeSet(path, body, cas)
	if err != nil {
		panic(err)
	}
	return m
}

// MustEncodeDel is like EncodeDel but panics if the mutation cannot be
// encoded. It simplifies safe initialization of global variables holding
// mutations.
func MustEncodeDel(path, cas string) (mutation string) {
	m, err := EncodeDel(path, cas)
	if err != nil {
		panic(err)
	}
	return m
}

func decode(mutation string) (path, v, cas string, keep bool, err os.Error) {
	cm := strings.Split(mutation, ":", 2)

	if len(cm) != 2 {
		err = ErrBadMutation
		return
	}

	kv := strings.Split(cm[1], "=", 2)

	if err = checkPath(kv[0]); err != nil {
		return
	}

	switch len(kv) {
	case 1:
		return kv[0], "", cm[0], false, nil
	case 2:
		return kv[0], kv[1], cm[0], true, nil
	}
	panic("unreachable")
}

func (st *Store) notify(e Event, ws []*Watch) []*Watch {
	nwatches := make([]*Watch, len(ws))

	i := 0
	for _, w := range ws {
		if _, ok := <-w.shutdown; ok {
			continue
		}

		if e.Seqn >= w.to {
			continue
		}

		last := w.to - 1
		if e.Seqn != last {
			nwatches[i] = w
			i++
		}

		if e.Seqn < w.from {
			continue
		}

		if w.glob.r.MatchString(e.Path) {
			st.notices = append(st.notices, notice{w.c, e})
		}
	}

	return nwatches[0:i]
}

func (st *Store) closeWatches() {
	for _, w := range st.watches {
		close(w.c)
	}
}

func (st *Store) process(ops <-chan Op, seqns chan<- uint64, watches chan<- int) {
	logger := util.NewLogger("store")
	defer st.closeWatches()

	var head uint64

	for {
		ver, values := st.state.ver, st.state.root

		var n notice
		if len(st.notices) > 0 {
			n = st.notices[0]
		}

		// Take any incoming requests and queue them up.
		select {
		case a := <-ops:
			if closed(ops) {
				return
			}

			if a.Seqn > ver {
				st.todo[a.Seqn] = a
			}
		case w := <-st.watchCh:
			n, ws := w.from, []*Watch{w}
			for ; len(ws) > 0 && n < head; n++ {
				ws = st.notify(Event{Seqn: n, Err: ErrTooLate}, ws)
			}
			for ; len(ws) > 0 && n <= ver; n++ {
				ws = st.notify(st.log[n], ws)
			}

			st.watches = append(st.watches, ws...)
		case seqn := <-st.cleanCh:
			for ; head <= seqn; head++ {
				st.log[head] = Event{}, false
			}
		case seqns <- ver:
			// nothing to do here
		case watches <- len(st.watches):
			// nothing to do here
		case n.ch <- n.ev:
			st.notices = st.notices[1:]
		}

		// If we have any mutations that can be applied, do them.
		for t, ok := st.todo[ver+1]; ok; t, ok = st.todo[ver+1] {
			var ev Event
			var snap bool
			values, ev, snap = values.apply(t.Seqn, t.Mut)
			logger.Printf("apply %s %v %v %v %v %v", ev.Desc(), ev.Seqn, ev.Path, ev.Body, ev.Cas, ev.Err)
			st.state = &state{ev.Seqn, values}
			st.log[t.Seqn] = ev
			st.watches = st.notify(ev, st.watches)
			for ver < ev.Seqn {
				ver++
				st.todo[ver] = Op{}, false
			}
			if snap {
				head = ev.Seqn + 1
			}
		}
	}
}

// Returns a point-in-time snapshot of the contents of the store.
func (st *Store) Snap() (ver uint64, g Getter) {
	// WARNING: Be sure to read the pointer value of st.state only once. If you
	// need multiple accesses, copy the pointer first.
	p := st.state

	return p.ver, p.root
}

// Gets the value stored at `path`, if any.
//
// If no value is stored at `path`, `cas` will be `Missing` and `value` will be
// nil.
//
// if `path` is a directory, `cas` will be `Dir` and `value` will be a list of
// entries.
//
// Otherwise, `cas` is the CAS token and `value[0]` is the body.
func (st *Store) Get(path string) (value []string, cas string) {
	_, g := st.Snap()
	return g.Get(path)
}

// Encodes the entire storage state, including the current sequence number, as
// a mutation. This mutation can be applied to an empty store to reproduce the
// state of `s`.
//
// Returns the sequence number of the snapshot and the mutation itself.
//
// A snapshot must be applied at sequence number 1. Once a snapshot has been
// applied, the store's sequence number will be set to `seqn`.
//
// Note that applying a snapshot does not send notifications.
func (st *Store) Snapshot() (seqn uint64, mutation string) {
	w := new(bytes.Buffer)
	ver, g := st.Snap()

	err := gob.NewEncoder(w).Encode(ver)
	if err != nil {
		panic(err)
	}

	err = gob.NewEncoder(w).Encode(g)
	if err != nil {
		panic(err)
	}

	return ver, w.String()
}

// Returns a channel that will receive notifications when mutations are applied
// to paths in the store. One event will be sent for each mutation iff the
// event's path matches `pattern`, a Unix-style glob pattern.
//
// Glob notation:
//  - "?" matches a single char in a single path component
//  - "*" matches zero or more chars in a single path component
//  - "**" matches zero or more chars in zero or more components
//  - any other sequence matches itself
//
// Notifications will not be sent for changes made as the result of applying a
// snapshot.
func (st *Store) Watch(glob *Glob) <-chan Event {
	return NewWatch(st, glob).C
}

func NewWatch(st *Store, glob *Glob) *Watch {
	ch := make(chan Event)
	ver, _ := st.Snap()
	return st.watchOn(glob, ch, ver+1, math.MaxUint64)
}

func (st *Store) watchOn(glob *Glob, ch chan Event, from, to uint64) *Watch {
	wt := &Watch{
		C:        ch,
		c:        ch,
		glob:     glob,
		from:     from,
		to:       to,
		shutdown: make(chan bool, 1),
	}
	st.watchCh <- wt
	return wt
}

// Returns a read-only chan that will receive a single event representing the
// change made at position `seqn`.
//
// If `seqn` was applied before the call to `Wait`, a dummy event will be
// sent with its `Err` set to `ErrTooLate`.
func (st *Store) Wait(seqn uint64) <-chan Event {
	ch := make(chan Event, 1)
	st.watchOn(Any, ch, seqn, seqn+1)
	return ch
}

// Ensures that the application of mutation at `seqn` happens before the call
// to `Sync` returns.
//
// See http://golang.org/doc/go_mem.html for the meaning of "happens before" in
// Go.
func (st *Store) Sync(seqn uint64) {
	<-st.Wait(seqn)
}

// Returns an immutable copy of `st` in which `path` exists as a regular file
// (not a dir). Waits for `path` to be set, if necessary.
func (st *Store) SyncPath(path string) (Getter, os.Error) {
	glob, err := CompileGlob(path)
	if err != nil {
		return nil, err
	}

	wt := NewWatch(st, glob)
	defer wt.Stop()

	_, g := st.Snap()
	_, cas := g.Get(path)
	if cas != Dir && cas != Missing {
		return g, nil
	}

	for ev := range wt.C {
		if ev.IsSet() {
			return ev, nil
		}
	}

	panic("unreachable")
}

func (st *Store) Clean(seqn uint64) {
	st.cleanCh <- seqn
}
<|endoftext|>


<commit_before>package store

import (
	"bytes"
	"gob"
	"doozer/util"
	"os"
	"regexp"
	"strings"
)

const (
	Clobber = ""
	Missing = "0"
	Dir     = "dir"
)

// TODO revisit this when package regexp is more complete (e.g. do Unicode)
const (
	charPat = `([a-zA-Z0-9.]|-)`
	partPat = "/" + charPat + "+"
	pathPat = "^/$|^(" + partPat + ")+$"
)

var pathRe = regexp.MustCompile(pathPat)

var (
	ErrBadMutation = os.NewError("bad mutation")
	ErrBadSnapshot = os.NewError("bad snapshot")
	ErrTooLate     = os.NewError("too late")
	ErrCasMismatch = os.NewError("cas mismatch")
)

type BadPathError struct {
	Path string
}

func (e *BadPathError) String() string {
	return "bad path: " + e.Path
}

// Applies mutations sent on Ops in sequence according to field Seqn. Any
// errors that occur will be written to ErrorPath. Duplicate operations at a
// given position are sliently ignored.
type Store struct {
	Ops     chan<- Op
	Seqns   <-chan uint64
	Watches <-chan int
	watchCh chan watch
	watches []watch
	todo    map[uint64]Op
	state   *state
	log     map[uint64]Event
	cleanCh chan uint64
	notices []notice
}

// Represents an operation to apply to the store at position Seqn.
//
// If Mut is a snapshot, notifications will not be sent.
//
// If Mut is Nop, no change will be made, but a dummy event will still be sent.
type Op struct {
	Seqn uint64
	Mut  string
}

type state struct {
	ver  uint64
	root node
}

type watch struct {
	out chan Event
	re  *regexp.Regexp
}

type notice struct {
	ch chan Event
	ev Event
}

// Creates a new, empty data store. Mutations will be applied in order,
// starting at number 1 (number 0 can be thought of as the creation of the
// store).
func New() *Store {
	ops := make(chan Op)
	seqns := make(chan uint64)
	watches := make(chan int)

	st := &Store{
		Ops:     ops,
		Seqns:   seqns,
		Watches: watches,
		watchCh: make(chan watch),
		todo:    make(map[uint64]Op),
		watches: []watch{},
		state:   &state{0, emptyDir},
		log:     make(map[uint64]Event),
		cleanCh: make(chan uint64),
		notices: make([]notice, 1),
	}

	go st.process(ops, seqns, watches)
	return st
}

func split(path string) []string {
	if path == "/" {
		return []string{}
	}
	return strings.Split(path[1:], "/", -1)
}

func join(parts []string) string {
	return "/" + strings.Join(parts, "/")
}

func checkPath(k string) os.Error {
	if !pathRe.MatchString(k) {
		return &BadPathError{k}
	}
	return nil
}

// Returns a mutation that can be applied to a `Store`. The mutation will set
// the contents of the file at `path` to `body` iff the CAS token of that file
// matches `cas` at the time of application.
//
// If `path` is not valid, returns a `BadPathError`.
func EncodeSet(path, body string, cas string) (mutation string, err os.Error) {
	if err = checkPath(path); err != nil {
		return
	}
	return cas + ":" + path + "=" + body, nil
}

// Returns a mutation that can be applied to a `Store`. The mutation will cause
// the file at `path` to be deleted iff the CAS token of that file matches
// `cas` at the time of application.
//
// If `path` is not valid, returns a `BadPathError`.
func EncodeDel(path string, cas string) (mutation string, err os.Error) {
	if err := checkPath(path); err != nil {
		return
	}
	return cas + ":" + path, nil
}

// MustEncodeSet is like EncodeSet but panics if the mutation cannot be
// encoded. It simplifies safe initialization of global variables holding
// mutations.
func MustEncodeSet(path, body, cas string) (mutation string) {
	m, err := EncodeSet(path, body, cas)
	if err != nil {
		panic(err)
	}
	return m
}

// MustEncodeDel is like EncodeDel but panics if the mutation cannot be
// encoded. It simplifies safe initialization of global variables holding
// mutations.
func MustEncodeDel(path, cas string) (mutation string) {
	m, err := EncodeDel(path, cas)
	if err != nil {
		panic(err)
	}
	return m
}

func decode(mutation string) (path, v, cas string, keep bool, err os.Error) {
	cm := strings.Split(mutation, ":", 2)

	if len(cm) != 2 {
		err = ErrBadMutation
		return
	}

	kv := strings.Split(cm[1], "=", 2)

	if err = checkPath(kv[0]); err != nil {
		return
	}

	switch len(kv) {
	case 1:
		return kv[0], "", cm[0], false, nil
	case 2:
		return kv[0], kv[1], cm[0], true, nil
	}
	panic("unreachable")
}

func (st *Store) notify(e Event) {
	nwatches := make([]watch, len(st.watches))

	i := 0
	for _, w := range st.watches {
		if closed(w.out) {
			continue
		}

		nwatches[i] = w
		i++

		if w.re.MatchString(e.Path) {
			st.notices = append(st.notices, notice{w.out, e})

			if st.notices[0].ch == nil {
				st.notices = st.notices[1:]
			}
		}
	}

	st.watches = nwatches[0:i]
}

func (st *Store) closeWatches() {
	for _, w := range st.watches {
		close(w.out)
	}
}

func (st *Store) process(ops <-chan Op, seqns chan<- uint64, watches chan<- int) {
	logger := util.NewLogger("store")
	defer st.closeWatches()

	var head uint64

	for {
		ver, values := st.state.ver, st.state.root

		// Take any incoming requests and queue them up.
		select {
		case a := <-ops:
			if closed(ops) {
				return
			}

			if a.Seqn > ver {
				st.todo[a.Seqn] = a
			}
		case w := <-st.watchCh:
			st.watches = append(st.watches, w)
		case seqn := <-st.cleanCh:
			for ; head <= seqn; head++ {
				st.log[head] = Event{}, false
			}
		case seqns <- ver:
			// nothing to do here
		case watches <- len(st.watches):
			// nothing to do here
		case st.notices[0].ch <- st.notices[0].ev:
			st.notices = st.notices[1:]

			if len(st.notices) < 1 {
				st.notices = make([]notice, 1)
			}
		}

		// If we have any mutations that can be applied, do them.
		for t, ok := st.todo[ver+1]; ok; t, ok = st.todo[ver+1] {
			var ev Event
			values, ev = values.apply(t.Seqn, t.Mut)
			logger.Printf("apply %s %v %v %v %v %v", ev.Desc(), ev.Seqn, ev.Path, ev.Body, ev.Cas, ev.Err)
			st.state = &state{ev.Seqn, values}
			st.log[t.Seqn] = ev
			st.notify(ev)
			for ver < ev.Seqn {
				ver++
				st.todo[ver] = Op{}, false
			}
		}
	}
}

// Returns a point-in-time snapshot of the contents of the store.
func (st *Store) Snap() Getter {
	// WARNING: Be sure to read the pointer value of st.state only once. If you
	// need multiple accesses, copy the pointer first.
	p := st.state

	return p.root
}

// Gets the value stored at `path`, if any.
//
// If no value is stored at `path`, `cas` will be `Missing` and `value` will be
// nil.
//
// if `path` is a directory, `cas` will be `Dir` and `value` will be a list of
// entries.
//
// Otherwise, `cas` is the CAS token and `value[0]` is the body.
func (st *Store) Get(path string) (value []string, cas string) {
	return st.Snap().Get(path)
}

// Encodes the entire storage state, including the current sequence number, as
// a mutation. This mutation can be applied to an empty store to reproduce the
// state of `s`.
//
// Returns the sequence number of the snapshot and the mutation itself.
//
// A snapshot must be applied at sequence number 1. Once a snapshot has been
// applied, the store's sequence number will be set to `seqn`.
//
// Note that applying a snapshot does not send notifications.
func (st *Store) Snapshot() (seqn uint64, mutation string) {
	w := new(bytes.Buffer)

	// WARNING: Be sure to read the pointer value of st.state only once. If you
	// need multiple accesses, copy the pointer first.
	ss := st.state

	err := gob.NewEncoder(w).Encode(ss.ver)
	if err != nil {
		panic(err)
	}

	err = gob.NewEncoder(w).Encode(ss.root)
	if err != nil {
		panic(err)
	}

	return ss.ver, w.String()
}

// Returns a channel that will receive notifications when mutations are applied
// to paths in the store. One event will be sent for each mutation iff the
// event's path matches `pattern`, a Unix-style glob pattern.
//
// Glob notation:
//  - "?" matches a single char in a single path component
//  - "*" matches zero or more chars in a single path component
//  - "**" matches zero or more chars in zero or more components
//  - any other sequence matches itself
//
// Notifications will not be sent for changes made as the result of applying a
// snapshot.
func (st *Store) Watch(pattern string) <-chan Event {
	ch := make(chan Event)
	st.watchOn(pattern, ch)
	return ch
}

func (st *Store) watchOn(pattern string, ch chan Event) {
	re, _ := compileGlob(pattern)
	st.watchCh <- watch{out: ch, re: re}
}

// Returns a read-only chan that will receive a single event representing the
// change made at position `seqn`.
//
// If `seqn` was applied before the call to `Wait`, a dummy event will be
// sent with its `Err` set to `ErrTooLate`.
func (st *Store) Wait(seqn uint64) <-chan Event {
	ch, all := make(chan Event, 1), st.Watch("**")

	// Reading shared state. This must happen after the call to st.Watch.
	if st.state.ver >= seqn {
		close(all)
		if ev, ok := st.log[seqn]; ok {
			ch <- ev
		} else {
			ch <- Event{Seqn: seqn, Err: ErrTooLate}
		}
	}

	go func() {
		for e := range all {
			if e.Seqn == seqn {
				close(all)
				ch <- e
			}
		}
	}()
	return ch
}

// Ensures that the application of mutation at `seqn` happens before the call
// to `Sync` returns.
//
// See http://golang.org/doc/go_mem.html for the meaning of "happens before" in
// Go.
func (st *Store) Sync(seqn uint64) {
	<-st.Wait(seqn)
}

// Returns an immutable copy of `st` in which `path` exists as a regular file
// (not a dir). Waits for `path` to be set, if necessary.
func (st *Store) SyncPath(path string) Getter {
	evs := st.Watch(path)
	defer func() {
		close(evs)
		<-evs
	}()

	g := st.state.root // TODO make this use a public method
	_, cas := g.Get(path)
	if cas != Dir && cas != Missing {
		return g
	}

	for ev := range evs {
		if ev.IsSet() {
			return ev
		}
	}

	panic("unreachable")
}

// Lists out the contents of `path` as dummy events on `ch`. Also subscribes
// `ch` to receive future events for changes to `path+"/*"`.
//
// The subscription is made before listing the directory entries. This
// guarantees no entry will be missed, but one or more of the dummy events may
// duplicate a true event.
func (st *Store) GetDirAndWatch(path string, ch chan Event) {
	st.watchOn(path+"/*", ch)
	go func() {
		for _, ent := range GetDir(st, path) {
			p := path + "/" + ent
			v, cas := st.Get(p)
			if cas != Missing && cas != Dir {
				ch <- Event{0, p, v[0], cas, "", nil, nil}
			}
		}
	}()
}

func (st *Store) Clean(seqn uint64) {
	st.cleanCh <- seqn
}
<commit_msg>store: use Seqns chan, not shared state<commit_after>package store

import (
	"bytes"
	"gob"
	"doozer/util"
	"os"
	"regexp"
	"strings"
)

const (
	Clobber = ""
	Missing = "0"
	Dir     = "dir"
)

// TODO revisit this when package regexp is more complete (e.g. do Unicode)
const (
	charPat = `([a-zA-Z0-9.]|-)`
	partPat = "/" + charPat + "+"
	pathPat = "^/$|^(" + partPat + ")+$"
)

var pathRe = regexp.MustCompile(pathPat)

var (
	ErrBadMutation = os.NewError("bad mutation")
	ErrBadSnapshot = os.NewError("bad snapshot")
	ErrTooLate     = os.NewError("too late")
	ErrCasMismatch = os.NewError("cas mismatch")
)

type BadPathError struct {
	Path string
}

func (e *BadPathError) String() string {
	return "bad path: " + e.Path
}

// Applies mutations sent on Ops in sequence according to field Seqn. Any
// errors that occur will be written to ErrorPath. Duplicate operations at a
// given position are sliently ignored.
type Store struct {
	Ops     chan<- Op
	Seqns   <-chan uint64
	Watches <-chan int
	watchCh chan watch
	watches []watch
	todo    map[uint64]Op
	state   *state
	log     map[uint64]Event
	cleanCh chan uint64
	notices []notice
}

// Represents an operation to apply to the store at position Seqn.
//
// If Mut is a snapshot, notifications will not be sent.
//
// If Mut is Nop, no change will be made, but a dummy event will still be sent.
type Op struct {
	Seqn uint64
	Mut  string
}

type state struct {
	ver  uint64
	root node
}

type watch struct {
	out chan Event
	re  *regexp.Regexp
}

type notice struct {
	ch chan Event
	ev Event
}

// Creates a new, empty data store. Mutations will be applied in order,
// starting at number 1 (number 0 can be thought of as the creation of the
// store).
func New() *Store {
	ops := make(chan Op)
	seqns := make(chan uint64)
	watches := make(chan int)

	st := &Store{
		Ops:     ops,
		Seqns:   seqns,
		Watches: watches,
		watchCh: make(chan watch),
		todo:    make(map[uint64]Op),
		watches: []watch{},
		state:   &state{0, emptyDir},
		log:     make(map[uint64]Event),
		cleanCh: make(chan uint64),
		notices: make([]notice, 1),
	}

	go st.process(ops, seqns, watches)
	return st
}

func split(path string) []string {
	if path == "/" {
		return []string{}
	}
	return strings.Split(path[1:], "/", -1)
}

func join(parts []string) string {
	return "/" + strings.Join(parts, "/")
}

func checkPath(k string) os.Error {
	if !pathRe.MatchString(k) {
		return &BadPathError{k}
	}
	return nil
}

// Returns a mutation that can be applied to a `Store`. The mutation will set
// the contents of the file at `path` to `body` iff the CAS token of that file
// matches `cas` at the time of application.
//
// If `path` is not valid, returns a `BadPathError`.
func EncodeSet(path, body string, cas string) (mutation string, err os.Error) {
	if err = checkPath(path); err != nil {
		return
	}
	return cas + ":" + path + "=" + body, nil
}

// Returns a mutation that can be applied to a `Store`. The mutation will cause
// the file at `path` to be deleted iff the CAS token of that file matches
// `cas` at the time of application.
//
// If `path` is not valid, returns a `BadPathError`.
func EncodeDel(path string, cas string) (mutation string, err os.Error) {
	if err := checkPath(path); err != nil {
		return
	}
	return cas + ":" + path, nil
}

// MustEncodeSet is like EncodeSet but panics if the mutation cannot be
// encoded. It simplifies safe initialization of global variables holding
// mutations.
func MustEncodeSet(path, body, cas string) (mutation string) {
	m, err := EncodeSet(path, body, cas)
	if err != nil {
		panic(err)
	}
	return m
}

// MustEncodeDel is like EncodeDel but panics if the mutation cannot be
// encoded. It simplifies safe initialization of global variables holding
// mutations.
func MustEncodeDel(path, cas string) (mutation string) {
	m, err := EncodeDel(path, cas)
	if err != nil {
		panic(err)
	}
	return m
}

func decode(mutation string) (path, v, cas string, keep bool, err os.Error) {
	cm := strings.Split(mutation, ":", 2)

	if len(cm) != 2 {
		err = ErrBadMutation
		return
	}

	kv := strings.Split(cm[1], "=", 2)

	if err = checkPath(kv[0]); err != nil {
		return
	}

	switch len(kv) {
	case 1:
		return kv[0], "", cm[0], false, nil
	case 2:
		return kv[0], kv[1], cm[0], true, nil
	}
	panic("unreachable")
}

func (st *Store) notify(e Event) {
	nwatches := make([]watch, len(st.watches))

	i := 0
	for _, w := range st.watches {
		if closed(w.out) {
			continue
		}

		nwatches[i] = w
		i++

		if w.re.MatchString(e.Path) {
			st.notices = append(st.notices, notice{w.out, e})

			if st.notices[0].ch == nil {
				st.notices = st.notices[1:]
			}
		}
	}

	st.watches = nwatches[0:i]
}

func (st *Store) closeWatches() {
	for _, w := range st.watches {
		close(w.out)
	}
}

func (st *Store) process(ops <-chan Op, seqns chan<- uint64, watches chan<- int) {
	logger := util.NewLogger("store")
	defer st.closeWatches()

	var head uint64

	for {
		ver, values := st.state.ver, st.state.root

		// Take any incoming requests and queue them up.
		select {
		case a := <-ops:
			if closed(ops) {
				return
			}

			if a.Seqn > ver {
				st.todo[a.Seqn] = a
			}
		case w := <-st.watchCh:
			st.watches = append(st.watches, w)
		case seqn := <-st.cleanCh:
			for ; head <= seqn; head++ {
				st.log[head] = Event{}, false
			}
		case seqns <- ver:
			// nothing to do here
		case watches <- len(st.watches):
			// nothing to do here
		case st.notices[0].ch <- st.notices[0].ev:
			st.notices = st.notices[1:]

			if len(st.notices) < 1 {
				st.notices = make([]notice, 1)
			}
		}

		// If we have any mutations that can be applied, do them.
		for t, ok := st.todo[ver+1]; ok; t, ok = st.todo[ver+1] {
			var ev Event
			values, ev = values.apply(t.Seqn, t.Mut)
			logger.Printf("apply %s %v %v %v %v %v", ev.Desc(), ev.Seqn, ev.Path, ev.Body, ev.Cas, ev.Err)
			st.state = &state{ev.Seqn, values}
			st.log[t.Seqn] = ev
			st.notify(ev)
			for ver < ev.Seqn {
				ver++
				st.todo[ver] = Op{}, false
			}
		}
	}
}

// Returns a point-in-time snapshot of the contents of the store.
func (st *Store) Snap() Getter {
	// WARNING: Be sure to read the pointer value of st.state only once. If you
	// need multiple accesses, copy the pointer first.
	p := st.state

	return p.root
}

// Gets the value stored at `path`, if any.
//
// If no value is stored at `path`, `cas` will be `Missing` and `value` will be
// nil.
//
// if `path` is a directory, `cas` will be `Dir` and `value` will be a list of
// entries.
//
// Otherwise, `cas` is the CAS token and `value[0]` is the body.
func (st *Store) Get(path string) (value []string, cas string) {
	return st.Snap().Get(path)
}

// Encodes the entire storage state, including the current sequence number, as
// a mutation. This mutation can be applied to an empty store to reproduce the
// state of `s`.
//
// Returns the sequence number of the snapshot and the mutation itself.
//
// A snapshot must be applied at sequence number 1. Once a snapshot has been
// applied, the store's sequence number will be set to `seqn`.
//
// Note that applying a snapshot does not send notifications.
func (st *Store) Snapshot() (seqn uint64, mutation string) {
	w := new(bytes.Buffer)

	// WARNING: Be sure to read the pointer value of st.state only once. If you
	// need multiple accesses, copy the pointer first.
	ss := st.state

	err := gob.NewEncoder(w).Encode(ss.ver)
	if err != nil {
		panic(err)
	}

	err = gob.NewEncoder(w).Encode(ss.root)
	if err != nil {
		panic(err)
	}

	return ss.ver, w.String()
}

// Returns a channel that will receive notifications when mutations are applied
// to paths in the store. One event will be sent for each mutation iff the
// event's path matches `pattern`, a Unix-style glob pattern.
//
// Glob notation:
//  - "?" matches a single char in a single path component
//  - "*" matches zero or more chars in a single path component
//  - "**" matches zero or more chars in zero or more components
//  - any other sequence matches itself
//
// Notifications will not be sent for changes made as the result of applying a
// snapshot.
func (st *Store) Watch(pattern string) <-chan Event {
	ch := make(chan Event)
	st.watchOn(pattern, ch)
	return ch
}

func (st *Store) watchOn(pattern string, ch chan Event) {
	re, _ := compileGlob(pattern)
	st.watchCh <- watch{out: ch, re: re}
}

// Returns a read-only chan that will receive a single event representing the
// change made at position `seqn`.
//
// If `seqn` was applied before the call to `Wait`, a dummy event will be
// sent with its `Err` set to `ErrTooLate`.
func (st *Store) Wait(seqn uint64) <-chan Event {
	ch, all := make(chan Event, 1), st.Watch("**")

	// Reading shared state. This must happen after the call to st.Watch.
	if <-st.Seqns >= seqn {
		close(all)
		if ev, ok := st.log[seqn]; ok {
			ch <- ev
		} else {
			ch <- Event{Seqn: seqn, Err: ErrTooLate}
		}
	}

	go func() {
		for e := range all {
			if e.Seqn == seqn {
				close(all)
				ch <- e
			}
		}
	}()
	return ch
}

// Ensures that the application of mutation at `seqn` happens before the call
// to `Sync` returns.
//
// See http://golang.org/doc/go_mem.html for the meaning of "happens before" in
// Go.
func (st *Store) Sync(seqn uint64) {
	<-st.Wait(seqn)
}

// Returns an immutable copy of `st` in which `path` exists as a regular file
// (not a dir). Waits for `path` to be set, if necessary.
func (st *Store) SyncPath(path string) Getter {
	evs := st.Watch(path)
	defer func() {
		close(evs)
		<-evs
	}()

	g := st.state.root // TODO make this use a public method
	_, cas := g.Get(path)
	if cas != Dir && cas != Missing {
		return g
	}

	for ev := range evs {
		if ev.IsSet() {
			return ev
		}
	}

	panic("unreachable")
}

// Lists out the contents of `path` as dummy events on `ch`. Also subscribes
// `ch` to receive future events for changes to `path+"/*"`.
//
// The subscription is made before listing the directory entries. This
// guarantees no entry will be missed, but one or more of the dummy events may
// duplicate a true event.
func (st *Store) GetDirAndWatch(path string, ch chan Event) {
	st.watchOn(path+"/*", ch)
	go func() {
		for _, ent := range GetDir(st, path) {
			p := path + "/" + ent
			v, cas := st.Get(p)
			if cas != Missing && cas != Dir {
				ch <- Event{0, p, v[0], cas, "", nil, nil}
			}
		}
	}()
}

func (st *Store) Clean(seqn uint64) {
	st.cleanCh <- seqn
}
<|endoftext|>


<commit_before>package testfixtures

const TomlConfigPath string = "testfixtures/testconfig.toml"
<commit_msg>Add path to test app<commit_after>package testfixtures

const TomlConfigPath string = "testfixtures/testconfig.toml"
const TestAppPath string = "testfixtures/postapp"
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"io/ioutil"
	"net"

	"golang.org/x/crypto/ssh"

	"github.com/tg123/sshpiper/sshpiperd/auditor"
	"github.com/tg123/sshpiper/sshpiperd/challenger"
	"github.com/tg123/sshpiper/sshpiperd/registry"
	"github.com/tg123/sshpiper/sshpiperd/upstream"
)

type piperdConfig struct {
	ListenAddr   string `short:"l" long:"listen" description:"Listening Address" default:"0.0.0.0" env:"SSHPIPERD_LISTENADDR" ini-name:"listen-address"`
	Port         uint   `short:"p" long:"port" description:"Listening Port" default:"2222" env:"SSHPIPERD_PORT" ini-name:"listen-port"`
	PiperKeyFile string `short:"i" long:"server-key" description:"Server key file for SSH Piper" default:"/etc/ssh/ssh_host_rsa_key" env:"SSHPIPERD_SERVER_KEY" ini-name:"server-key"`

	UpstreamDriver   string `short:"u" long:"upstream-driver" description:"Upstream provider driver" default:"workingdir" env:"SSHPIPERD_UPSTREAM_DRIVER" ini-name:"upstream-driver"`
	ChallengerDriver string `short:"c" long:"challenger-driver" description:"Additional challenger name, e.g. pam, empty for no additional challenge" env:"SSHPIPERD_CHALLENGER" ini-name:"challenger-driver"`
	AuditorDriver    string `long:"auditor-driver" description:"Auditor for ssh connections piped by SSH Piper " env:"SSHPIPERD_AUDITOR" ini-name:"auditor-driver"`
}

func getAndInstall(name string, get func(n string) registry.Plugin, install func(plugin registry.Plugin) error) error {
	if name == "" {
		return nil
	}

	p := get(name)

	if p == nil {
		return fmt.Errorf("driver %v not found", name)
	}

	err := p.Init(logger)
	if err != nil {
		return err
	}
	return install(p)
}

func installDrivers(piper *ssh.SSHPiperConfig, config *piperdConfig) (auditor.Provider, error) {

	// install upstreamProvider driver
	if config.UpstreamDriver == "" {
		return nil, fmt.Errorf("must provider upstream driver")
	}

	var bigbro auditor.Provider

	for _, d := range []struct {
		name    string
		get     func(n string) registry.Plugin
		install func(plugin registry.Plugin) error
	}{
		// upstream driver
		{
			config.UpstreamDriver,
			func(n string) registry.Plugin {
				return upstream.Get(n)
			},
			func(plugin registry.Plugin) error {
				handler := plugin.(upstream.Provider).GetHandler()

				if handler == nil {
					return fmt.Errorf("driver return nil handler")
				}

				piper.FindUpstream = handler
				return nil
			},
		},
		// challenger driver
		{
			config.ChallengerDriver,
			func(n string) registry.Plugin {
				return challenger.Get(n)
			},
			func(plugin registry.Plugin) error {
				handler := plugin.(challenger.Provider).GetHandler()

				if handler == nil {
					return fmt.Errorf("driver return nil handler")
				}

				piper.AdditionalChallenge = handler
				return nil
			},
		},
		// auditor driver
		{
			config.AuditorDriver,
			func(n string) registry.Plugin {
				return auditor.Get(n)
			},
			func(plugin registry.Plugin) error {
				bigbro = plugin.(auditor.Provider)
				return nil
			},
		},
	} {
		err := getAndInstall(d.name, d.get, d.install)
		if err != nil {
			return nil, err
		}
	}

	return bigbro, nil
}

func startPiper(config *piperdConfig) error {

	logger.Println("sshpiper is about to start")

	piper := &ssh.SSHPiperConfig{}

	bigbro, err := installDrivers(piper, config)

	privateBytes, err := ioutil.ReadFile(config.PiperKeyFile)
	if err != nil {
		return err
	}

	private, err := ssh.ParsePrivateKey(privateBytes)
	if err != nil {
		return err
	}

	piper.AddHostKey(private)

	listener, err := net.Listen("tcp", fmt.Sprintf("%s:%d", config.ListenAddr, config.Port))
	if err != nil {
		return fmt.Errorf("failed to listen for connection: %v", err)
	}
	defer listener.Close()

	logger.Printf("sshpiperd started")

	for {
		c, err := listener.Accept()
		if err != nil {
			logger.Printf("failed to accept connection: %v", err)
			continue
		}

		logger.Printf("connection accepted: %v", c.RemoteAddr())
		go func() {
			p, err := ssh.NewSSHPiperConn(c, piper)

			if err != nil {
				logger.Printf("connection from %v establishing failed reason: %v", c.RemoteAddr(), err)
				return
			}

			if bigbro != nil {
				a, err := bigbro.Create(p.DownstreamConnMeta())
				if err != nil {
					logger.Printf("connection from %v failed to create auditor reason: %v", c.RemoteAddr(), err)
					return
				}
				defer a.Close()

				p.HookUpstreamMsg = a.GetUpstreamHook()
				p.HookDownstreamMsg = a.GetDownstreamHook()
			}

			err = p.Wait()
			logger.Printf("connection from %v closed reason: %v", c.RemoteAddr(), err)
		}()
	}

	return nil
}
<commit_msg>fix unreachable code<commit_after>package main

import (
	"fmt"
	"io/ioutil"
	"net"

	"golang.org/x/crypto/ssh"

	"github.com/tg123/sshpiper/sshpiperd/auditor"
	"github.com/tg123/sshpiper/sshpiperd/challenger"
	"github.com/tg123/sshpiper/sshpiperd/registry"
	"github.com/tg123/sshpiper/sshpiperd/upstream"
)

type piperdConfig struct {
	ListenAddr   string `short:"l" long:"listen" description:"Listening Address" default:"0.0.0.0" env:"SSHPIPERD_LISTENADDR" ini-name:"listen-address"`
	Port         uint   `short:"p" long:"port" description:"Listening Port" default:"2222" env:"SSHPIPERD_PORT" ini-name:"listen-port"`
	PiperKeyFile string `short:"i" long:"server-key" description:"Server key file for SSH Piper" default:"/etc/ssh/ssh_host_rsa_key" env:"SSHPIPERD_SERVER_KEY" ini-name:"server-key"`

	UpstreamDriver   string `short:"u" long:"upstream-driver" description:"Upstream provider driver" default:"workingdir" env:"SSHPIPERD_UPSTREAM_DRIVER" ini-name:"upstream-driver"`
	ChallengerDriver string `short:"c" long:"challenger-driver" description:"Additional challenger name, e.g. pam, empty for no additional challenge" env:"SSHPIPERD_CHALLENGER" ini-name:"challenger-driver"`
	AuditorDriver    string `long:"auditor-driver" description:"Auditor for ssh connections piped by SSH Piper " env:"SSHPIPERD_AUDITOR" ini-name:"auditor-driver"`
}

func getAndInstall(name string, get func(n string) registry.Plugin, install func(plugin registry.Plugin) error) error {
	if name == "" {
		return nil
	}

	p := get(name)

	if p == nil {
		return fmt.Errorf("driver %v not found", name)
	}

	err := p.Init(logger)
	if err != nil {
		return err
	}
	return install(p)
}

func installDrivers(piper *ssh.SSHPiperConfig, config *piperdConfig) (auditor.Provider, error) {

	// install upstreamProvider driver
	if config.UpstreamDriver == "" {
		return nil, fmt.Errorf("must provider upstream driver")
	}

	var bigbro auditor.Provider

	for _, d := range []struct {
		name    string
		get     func(n string) registry.Plugin
		install func(plugin registry.Plugin) error
	}{
		// upstream driver
		{
			config.UpstreamDriver,
			func(n string) registry.Plugin {
				return upstream.Get(n)
			},
			func(plugin registry.Plugin) error {
				handler := plugin.(upstream.Provider).GetHandler()

				if handler == nil {
					return fmt.Errorf("driver return nil handler")
				}

				piper.FindUpstream = handler
				return nil
			},
		},
		// challenger driver
		{
			config.ChallengerDriver,
			func(n string) registry.Plugin {
				return challenger.Get(n)
			},
			func(plugin registry.Plugin) error {
				handler := plugin.(challenger.Provider).GetHandler()

				if handler == nil {
					return fmt.Errorf("driver return nil handler")
				}

				piper.AdditionalChallenge = handler
				return nil
			},
		},
		// auditor driver
		{
			config.AuditorDriver,
			func(n string) registry.Plugin {
				return auditor.Get(n)
			},
			func(plugin registry.Plugin) error {
				bigbro = plugin.(auditor.Provider)
				return nil
			},
		},
	} {
		err := getAndInstall(d.name, d.get, d.install)
		if err != nil {
			return nil, err
		}
	}

	return bigbro, nil
}

func startPiper(config *piperdConfig) error {

	logger.Println("sshpiper is about to start")

	piper := &ssh.SSHPiperConfig{}

	bigbro, err := installDrivers(piper, config)

	privateBytes, err := ioutil.ReadFile(config.PiperKeyFile)
	if err != nil {
		return err
	}

	private, err := ssh.ParsePrivateKey(privateBytes)
	if err != nil {
		return err
	}

	piper.AddHostKey(private)

	listener, err := net.Listen("tcp", fmt.Sprintf("%s:%d", config.ListenAddr, config.Port))
	if err != nil {
		return fmt.Errorf("failed to listen for connection: %v", err)
	}
	defer listener.Close()

	logger.Printf("sshpiperd started")

	for {
		c, err := listener.Accept()
		if err != nil {
			logger.Printf("failed to accept connection: %v", err)
			continue
		}

		logger.Printf("connection accepted: %v", c.RemoteAddr())
		go func() {
			p, err := ssh.NewSSHPiperConn(c, piper)

			if err != nil {
				logger.Printf("connection from %v establishing failed reason: %v", c.RemoteAddr(), err)
				return
			}

			if bigbro != nil {
				a, err := bigbro.Create(p.DownstreamConnMeta())
				if err != nil {
					logger.Printf("connection from %v failed to create auditor reason: %v", c.RemoteAddr(), err)
					return
				}
				defer a.Close()

				p.HookUpstreamMsg = a.GetUpstreamHook()
				p.HookDownstreamMsg = a.GetDownstreamHook()
			}

			err = p.Wait()
			logger.Printf("connection from %v closed reason: %v", c.RemoteAddr(), err)
		}()
	}
}
<|endoftext|>


<commit_before>package iris

import (
	"net/http"
	"sync"

	"github.com/kataras/go-websocket"
)

// conversionals
const (
	// All is the string which the Emitter use to send a message to all
	All = websocket.All
	// NotMe is the string which the Emitter use to send a message to all except this websocket.Connection
	NotMe = websocket.NotMe
	// Broadcast is the string which the Emitter use to send a message to all except this websocket.Connection, same as 'NotMe'
	Broadcast = websocket.Broadcast
)

// Note I keep this code only to no change the front-end API, we could only use the go-websocket and set our custom upgrader

type (
	// WebsocketServer is the iris websocket server, expose the websocket.Server
	// the below code is a wrapper and bridge between iris-contrib/websocket and kataras/go-websocket
	WebsocketServer struct {
		websocket.Server
		station *Framework
		once    sync.Once
		// Config:
		// if endpoint is not empty then this configuration is used instead of the station's
		// useful when the user/dev wants more than one websocket server inside one iris instance.
		Config WebsocketConfiguration
	}
)

// NewWebsocketServer returns a new empty unitialized websocket server
// it runs on first OnConnection
func NewWebsocketServer(station *Framework) *WebsocketServer {
	return &WebsocketServer{station: station, Server: websocket.New(), Config: DefaultWebsocketConfiguration()}
}

// NewWebsocketServer creates the client side source route and the route path Endpoint with the correct Handler
// receives the websocket configuration and  the iris station
// and returns the websocket server which can be attached to more than one iris station (if needed)
func (ws *WebsocketServer) init() {

	if ws.Config.Endpoint == "" {
		ws.Config = ws.station.Config.Websocket
	}

	c := ws.Config

	if c.Endpoint == "" {
		return
	}

	if c.CheckOrigin == nil {
		c.CheckOrigin = DefaultWebsocketCheckOrigin
	}

	if c.Error == nil {
		c.Error = DefaultWebsocketError
	}
	// set the underline websocket server's configuration
	ws.Server.Set(websocket.Config{
		WriteTimeout:    c.WriteTimeout,
		PongTimeout:     c.PongTimeout,
		PingPeriod:      c.PingPeriod,
		MaxMessageSize:  c.MaxMessageSize,
		BinaryMessages:  c.BinaryMessages,
		ReadBufferSize:  c.ReadBufferSize,
		WriteBufferSize: c.WriteBufferSize,
		Error: func(w http.ResponseWriter, r *http.Request, status int, reason error) {
			ws.station.Context.Run(w, r, func(ctx *Context) {
				c.Error(ctx, status, reason)
			})
		},
		CheckOrigin: c.CheckOrigin,
		IDGenerator: c.IDGenerator,
	})

	// set the routing for client-side source (javascript) (optional)
	clientSideLookupName := "iris-websocket-client-side"
	ws.station.Get(c.Endpoint, ToHandler(ws.Server.Handler()))
	// check if client side already exists
	if ws.station.Routes().Lookup(clientSideLookupName) == nil {
		// serve the client side on domain:port/iris-ws.js
		ws.station.StaticContent("/iris-ws.js", contentJavascript, websocket.ClientSource).ChangeName(clientSideLookupName)
	}
}

// WebsocketConnection is the front-end API that you will use to communicate with the client side
type WebsocketConnection interface {
	websocket.Connection
}

// OnConnection this is the main event you, as developer, will work with each of the websocket connections
func (ws *WebsocketServer) OnConnection(connectionListener func(WebsocketConnection)) {
	ws.once.Do(ws.init)

	ws.Server.OnConnection(func(c websocket.Connection) {
		connectionListener(c)
	})
}
<commit_msg>Revert "Silly fix for https://github.com/kataras/go-websocket/issues/27"<commit_after>package iris

import (
	"net/http"
	"sync"

	"github.com/kataras/go-websocket"
)

// conversionals
const (
	// All is the string which the Emitter use to send a message to all
	All = websocket.All
	// NotMe is the string which the Emitter use to send a message to all except this websocket.Connection
	NotMe = websocket.NotMe
	// Broadcast is the string which the Emitter use to send a message to all except this websocket.Connection, same as 'NotMe'
	Broadcast = websocket.Broadcast
)

// Note I keep this code only to no change the front-end API, we could only use the go-websocket and set our custom upgrader

type (
	// WebsocketServer is the iris websocket server, expose the websocket.Server
	// the below code is a wrapper and bridge between iris-contrib/websocket and kataras/go-websocket
	WebsocketServer struct {
		websocket.Server
		station *Framework
		once    sync.Once
		// Config:
		// if endpoint is not empty then this configuration is used instead of the station's
		// useful when the user/dev wants more than one websocket server inside one iris instance.
		Config WebsocketConfiguration
	}
)

// NewWebsocketServer returns a new empty unitialized websocket server
// it runs on first OnConnection
func NewWebsocketServer(station *Framework) *WebsocketServer {
	return &WebsocketServer{station: station, Server: websocket.New()}
}

// NewWebsocketServer creates the client side source route and the route path Endpoint with the correct Handler
// receives the websocket configuration and  the iris station
// and returns the websocket server which can be attached to more than one iris station (if needed)
func (ws *WebsocketServer) init() {
	if ws.Config.Endpoint == "" {
		ws.Config = ws.station.Config.Websocket
	}

	c := ws.Config

	if c.Endpoint == "" {
		return
	}
	// set the routing for client-side source (javascript) (optional)
	clientSideLookupName := "iris-websocket-client-side"
	ws.station.Get(c.Endpoint, ToHandler(ws.Server.Handler()))
	// check if client side already exists
	if ws.station.Lookup(clientSideLookupName) == nil {
		// serve the client side on domain:port/iris-ws.js
		ws.station.StaticContent("/iris-ws.js", contentJavascript, websocket.ClientSource)(clientSideLookupName)
	}

	if c.CheckOrigin == nil {
		c.CheckOrigin = DefaultWebsocketCheckOrigin
	}

	if c.Error == nil {
		c.Error = DefaultWebsocketError
	}
	// set the underline websocket server's configuration
	ws.Server.Set(websocket.Config{
		WriteTimeout:    c.WriteTimeout,
		PongTimeout:     c.PongTimeout,
		PingPeriod:      c.PingPeriod,
		MaxMessageSize:  c.MaxMessageSize,
		BinaryMessages:  c.BinaryMessages,
		ReadBufferSize:  c.ReadBufferSize,
		WriteBufferSize: c.WriteBufferSize,
		Error: func(w http.ResponseWriter, r *http.Request, status int, reason error) {
			ctx := ws.station.AcquireCtx(w, r)
			c.Error(ctx, status, reason)
			ws.station.ReleaseCtx(ctx)
		},
		CheckOrigin: c.CheckOrigin,
		IDGenerator: c.IDGenerator,
	})
}

// WebsocketConnection is the front-end API that you will use to communicate with the client side
type WebsocketConnection interface {
	websocket.Connection
}

// OnConnection this is the main event you, as developer, will work with each of the websocket connections
func (ws *WebsocketServer) OnConnection(connectionListener func(WebsocketConnection)) {
	ws.once.Do(ws.init)

	ws.Server.OnConnection(func(c websocket.Connection) {
		connectionListener(c)
	})
}
<|endoftext|>