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

import (
	"fmt"

	"github.com/golang/glog"

	v1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/types"
	"k8s.io/apimachinery/pkg/util/uuid"

	"github.com/kubernetes-sigs/kube-batch/pkg/apis/scheduling/v1alpha1"
	"github.com/kubernetes-sigs/kube-batch/pkg/scheduler/api"
	"github.com/kubernetes-sigs/kube-batch/pkg/scheduler/cache"
	"github.com/kubernetes-sigs/kube-batch/pkg/scheduler/conf"
	"github.com/kubernetes-sigs/kube-batch/pkg/scheduler/metrics"
)

type Session struct {
	UID types.UID

	cache cache.Cache

	Jobs    map[api.JobID]*api.JobInfo
	Nodes   map[string]*api.NodeInfo
	Queues  map[api.QueueID]*api.QueueInfo
	Backlog []*api.JobInfo
	Tiers   []conf.Tier

	plugins        map[string]Plugin
	eventHandlers  []*EventHandler
	jobOrderFns    map[string]api.CompareFn
	queueOrderFns  map[string]api.CompareFn
	taskOrderFns   map[string]api.CompareFn
	predicateFns   map[string]api.PredicateFn
	nodeOrderFns   map[string]api.NodeOrderFn
	preemptableFns map[string]api.EvictableFn
	reclaimableFns map[string]api.EvictableFn
	overusedFns    map[string]api.ValidateFn
	jobReadyFns    map[string]api.ValidateFn
	jobValidFns    map[string]api.ValidateExFn
}

func openSession(cache cache.Cache) *Session {
	ssn := &Session{
		UID:   uuid.NewUUID(),
		cache: cache,

		Jobs:   map[api.JobID]*api.JobInfo{},
		Nodes:  map[string]*api.NodeInfo{},
		Queues: map[api.QueueID]*api.QueueInfo{},

		plugins:        map[string]Plugin{},
		jobOrderFns:    map[string]api.CompareFn{},
		queueOrderFns:  map[string]api.CompareFn{},
		taskOrderFns:   map[string]api.CompareFn{},
		predicateFns:   map[string]api.PredicateFn{},
		nodeOrderFns:   map[string]api.NodeOrderFn{},
		preemptableFns: map[string]api.EvictableFn{},
		reclaimableFns: map[string]api.EvictableFn{},
		overusedFns:    map[string]api.ValidateFn{},
		jobReadyFns:    map[string]api.ValidateFn{},
		jobValidFns:    map[string]api.ValidateExFn{},
	}

	snapshot := cache.Snapshot()

	ssn.Jobs = snapshot.Jobs
	for _, job := range ssn.Jobs {
		if vjr := ssn.JobValid(job); vjr != nil {
			if !vjr.Pass {
				jc := &v1alpha1.PodGroupCondition{
					Type:               v1alpha1.PodGroupUnschedulableType,
					Status:             v1.ConditionTrue,
					LastTransitionTime: metav1.Now(),
					TransitionID:       string(ssn.UID),
					Reason:             vjr.Reason,
					Message:            vjr.Message,
				}

				if err := ssn.UpdateJobCondition(job, jc); err != nil {
					glog.Errorf("Failed to update job condition: %v", err)
				}
			}

			delete(ssn.Jobs, job.UID)
		}
	}

	ssn.Nodes = snapshot.Nodes
	ssn.Queues = snapshot.Queues

	glog.V(3).Infof("Open Session %v with <%d> Job and <%d> Queues",
		ssn.UID, len(ssn.Jobs), len(ssn.Queues))

	return ssn
}

func closeSession(ssn *Session) {
	for _, job := range ssn.Jobs {
		// If job is using PDB, ignore it.
		// TODO(k82cn): remove it when removing PDB support
		if job.PodGroup == nil {
			ssn.cache.RecordJobStatusEvent(job)
			continue
		}

		job.PodGroup.Status = jobStatus(ssn, job)
		if _, err := ssn.cache.UpdateJobStatus(job); err != nil {
			glog.Errorf("Failed to update job <%s/%s>: %v",
				job.Namespace, job.Name, err)
		}
	}

	ssn.Jobs = nil
	ssn.Nodes = nil
	ssn.Backlog = nil
	ssn.plugins = nil
	ssn.eventHandlers = nil
	ssn.jobOrderFns = nil
	ssn.queueOrderFns = nil

	glog.V(3).Infof("Close Session %v", ssn.UID)
}

func jobStatus(ssn *Session, jobInfo *api.JobInfo) v1alpha1.PodGroupStatus {
	status := jobInfo.PodGroup.Status

	unschedulable := false
	for _, c := range status.Conditions {
		if c.Type == v1alpha1.PodGroupUnschedulableType &&
			c.Status == v1.ConditionTrue &&
			c.TransitionID == string(ssn.UID) {

			unschedulable = true
			break
		}
	}

	// If running tasks && unschedulable, unknown phase
	if len(jobInfo.TaskStatusIndex[api.Running]) != 0 && unschedulable {
		status.Phase = v1alpha1.PodGroupUnknown
	} else {
		allocated := 0
		for status, tasks := range jobInfo.TaskStatusIndex {
			if api.AllocatedStatus(status) {
				allocated += len(tasks)
			}
		}

		// If there're enough allocated resource, it's running
		if int32(allocated) > jobInfo.PodGroup.Spec.MinMember {
			status.Phase = v1alpha1.PodGroupRunning
		} else {
			status.Phase = v1alpha1.PodGroupPending
		}
	}

	status.Running = int32(len(jobInfo.TaskStatusIndex[api.Running]))
	status.Failed = int32(len(jobInfo.TaskStatusIndex[api.Failed]))
	status.Succeeded = int32(len(jobInfo.TaskStatusIndex[api.Succeeded]))

	return status
}

func (ssn *Session) Statement() *Statement {
	return &Statement{
		ssn: ssn,
	}
}

func (ssn *Session) Pipeline(task *api.TaskInfo, hostname string) error {
	// Only update status in session
	job, found := ssn.Jobs[task.Job]
	if found {
		if err := job.UpdateTaskStatus(task, api.Pipelined); err != nil {
			glog.Errorf("Failed to update task <%v/%v> status to %v in Session <%v>: %v",
				task.Namespace, task.Name, api.Pipelined, ssn.UID, err)
		}
	} else {
		glog.Errorf("Failed to found Job <%s> in Session <%s> index when binding.",
			task.Job, ssn.UID)
	}

	task.NodeName = hostname

	if node, found := ssn.Nodes[hostname]; found {
		if err := node.AddTask(task); err != nil {
			glog.Errorf("Failed to add task <%v/%v> to node <%v> in Session <%v>: %v",
				task.Namespace, task.Name, hostname, ssn.UID, err)
		}
		glog.V(3).Infof("After added Task <%v/%v> to Node <%v>: idle <%v>, used <%v>, releasing <%v>",
			task.Namespace, task.Name, node.Name, node.Idle, node.Used, node.Releasing)
	} else {
		glog.Errorf("Failed to found Node <%s> in Session <%s> index when binding.",
			hostname, ssn.UID)
	}

	for _, eh := range ssn.eventHandlers {
		if eh.AllocateFunc != nil {
			eh.AllocateFunc(&Event{
				Task: task,
			})
		}
	}

	return nil
}

func (ssn *Session) Allocate(task *api.TaskInfo, hostname string) error {
	if err := ssn.cache.AllocateVolumes(task, hostname); err != nil {
		return err
	}

	// Only update status in session
	job, found := ssn.Jobs[task.Job]
	if found {
		if err := job.UpdateTaskStatus(task, api.Allocated); err != nil {
			glog.Errorf("Failed to update task <%v/%v> status to %v in Session <%v>: %v",
				task.Namespace, task.Name, api.Allocated, ssn.UID, err)
			return err
		}
	} else {
		glog.Errorf("Failed to found Job <%s> in Session <%s> index when binding.",
			task.Job, ssn.UID)
		return fmt.Errorf("failed to find job %s", task.Job)
	}

	task.NodeName = hostname

	if node, found := ssn.Nodes[hostname]; found {
		if err := node.AddTask(task); err != nil {
			glog.Errorf("Failed to add task <%v/%v> to node <%v> in Session <%v>: %v",
				task.Namespace, task.Name, hostname, ssn.UID, err)
			return err
		}
		glog.V(3).Infof("After allocated Task <%v/%v> to Node <%v>: idle <%v>, used <%v>, releasing <%v>",
			task.Namespace, task.Name, node.Name, node.Idle, node.Used, node.Releasing)
	} else {
		glog.Errorf("Failed to found Node <%s> in Session <%s> index when binding.",
			hostname, ssn.UID)
		return fmt.Errorf("failed to find node %s", hostname)
	}

	// Callbacks
	for _, eh := range ssn.eventHandlers {
		if eh.AllocateFunc != nil {
			eh.AllocateFunc(&Event{
				Task: task,
			})
		}
	}

	if ssn.JobReady(job) {
		for _, task := range job.TaskStatusIndex[api.Allocated] {
			if err := ssn.dispatch(task); err != nil {
				glog.Errorf("Failed to dispatch task <%v/%v>: %v",
					task.Namespace, task.Name, err)
				return err
			}
		}
	}

	return nil
}

func (ssn *Session) dispatch(task *api.TaskInfo) error {
	if err := ssn.cache.BindVolumes(task); err != nil {
		return err
	}

	if err := ssn.cache.Bind(task, task.NodeName); err != nil {
		return err
	}

	// Update status in session
	if job, found := ssn.Jobs[task.Job]; found {
		if err := job.UpdateTaskStatus(task, api.Binding); err != nil {
			glog.Errorf("Failed to update task <%v/%v> status to %v in Session <%v>: %v",
				task.Namespace, task.Name, api.Binding, ssn.UID, err)
		}
	} else {
		glog.Errorf("Failed to found Job <%s> in Session <%s> index when binding.",
			task.Job, ssn.UID)
	}

	metrics.UpdateTaskScheduleDuration(metrics.Duration(task.Pod.CreationTimestamp.Time))
	return nil
}

func (ssn *Session) Evict(reclaimee *api.TaskInfo, reason string) error {
	if err := ssn.cache.Evict(reclaimee, reason); err != nil {
		return err
	}

	// Update status in session
	job, found := ssn.Jobs[reclaimee.Job]
	if found {
		if err := job.UpdateTaskStatus(reclaimee, api.Releasing); err != nil {
			glog.Errorf("Failed to update task <%v/%v> status to %v in Session <%v>: %v",
				reclaimee.Namespace, reclaimee.Name, api.Releasing, ssn.UID, err)
		}
	} else {
		glog.Errorf("Failed to found Job <%s> in Session <%s> index when binding.",
			reclaimee.Job, ssn.UID)
	}

	// Update task in node.
	if node, found := ssn.Nodes[reclaimee.NodeName]; found {
		if err := node.UpdateTask(reclaimee); err != nil {
			glog.Errorf("Failed to update task <%v/%v> in Session <%v>: %v",
				reclaimee.Namespace, reclaimee.Name, ssn.UID, err)
		}
	}

	for _, eh := range ssn.eventHandlers {
		if eh.DeallocateFunc != nil {
			eh.DeallocateFunc(&Event{
				Task: reclaimee,
			})
		}
	}

	return nil
}

// UpdateJobStatus update job condition accordingly.
func (ssn *Session) UpdateJobCondition(jobInfo *api.JobInfo, cond *v1alpha1.PodGroupCondition) error {
	job, ok := ssn.Jobs[jobInfo.UID]
	if !ok {
		return fmt.Errorf("failed to find job <%s/%s>", jobInfo.Namespace, jobInfo.Name)
	}

	index := -1
	for i, c := range job.PodGroup.Status.Conditions {
		if c.Type == cond.Type {
			index = i
			break
		}
	}

	// Update condition to the new condition.
	if index < 0 {
		job.PodGroup.Status.Conditions = append(job.PodGroup.Status.Conditions, *cond)
	} else {
		job.PodGroup.Status.Conditions[index] = *cond
	}

	return nil
}

func (ssn *Session) AddEventHandler(eh *EventHandler) {
	ssn.eventHandlers = append(ssn.eventHandlers, eh)
}

func (ssn Session) String() string {
	msg := fmt.Sprintf("Session %v: \n", ssn.UID)

	for _, job := range ssn.Jobs {
		msg = fmt.Sprintf("%s%v\n", msg, job)
	}

	for _, node := range ssn.Nodes {
		msg = fmt.Sprintf("%s%v\n", msg, node)
	}

	return msg

}
<commit_msg>Return err in functions of session.go if any error occurs<commit_after>/*
Copyright 2018 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 framework

import (
	"fmt"

	"github.com/golang/glog"

	v1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/types"
	"k8s.io/apimachinery/pkg/util/uuid"

	"github.com/kubernetes-sigs/kube-batch/pkg/apis/scheduling/v1alpha1"
	"github.com/kubernetes-sigs/kube-batch/pkg/scheduler/api"
	"github.com/kubernetes-sigs/kube-batch/pkg/scheduler/cache"
	"github.com/kubernetes-sigs/kube-batch/pkg/scheduler/conf"
	"github.com/kubernetes-sigs/kube-batch/pkg/scheduler/metrics"
)

type Session struct {
	UID types.UID

	cache cache.Cache

	Jobs    map[api.JobID]*api.JobInfo
	Nodes   map[string]*api.NodeInfo
	Queues  map[api.QueueID]*api.QueueInfo
	Backlog []*api.JobInfo
	Tiers   []conf.Tier

	plugins        map[string]Plugin
	eventHandlers  []*EventHandler
	jobOrderFns    map[string]api.CompareFn
	queueOrderFns  map[string]api.CompareFn
	taskOrderFns   map[string]api.CompareFn
	predicateFns   map[string]api.PredicateFn
	nodeOrderFns   map[string]api.NodeOrderFn
	preemptableFns map[string]api.EvictableFn
	reclaimableFns map[string]api.EvictableFn
	overusedFns    map[string]api.ValidateFn
	jobReadyFns    map[string]api.ValidateFn
	jobValidFns    map[string]api.ValidateExFn
}

func openSession(cache cache.Cache) *Session {
	ssn := &Session{
		UID:   uuid.NewUUID(),
		cache: cache,

		Jobs:   map[api.JobID]*api.JobInfo{},
		Nodes:  map[string]*api.NodeInfo{},
		Queues: map[api.QueueID]*api.QueueInfo{},

		plugins:        map[string]Plugin{},
		jobOrderFns:    map[string]api.CompareFn{},
		queueOrderFns:  map[string]api.CompareFn{},
		taskOrderFns:   map[string]api.CompareFn{},
		predicateFns:   map[string]api.PredicateFn{},
		nodeOrderFns:   map[string]api.NodeOrderFn{},
		preemptableFns: map[string]api.EvictableFn{},
		reclaimableFns: map[string]api.EvictableFn{},
		overusedFns:    map[string]api.ValidateFn{},
		jobReadyFns:    map[string]api.ValidateFn{},
		jobValidFns:    map[string]api.ValidateExFn{},
	}

	snapshot := cache.Snapshot()

	ssn.Jobs = snapshot.Jobs
	for _, job := range ssn.Jobs {
		if vjr := ssn.JobValid(job); vjr != nil {
			if !vjr.Pass {
				jc := &v1alpha1.PodGroupCondition{
					Type:               v1alpha1.PodGroupUnschedulableType,
					Status:             v1.ConditionTrue,
					LastTransitionTime: metav1.Now(),
					TransitionID:       string(ssn.UID),
					Reason:             vjr.Reason,
					Message:            vjr.Message,
				}

				if err := ssn.UpdateJobCondition(job, jc); err != nil {
					glog.Errorf("Failed to update job condition: %v", err)
				}
			}

			delete(ssn.Jobs, job.UID)
		}
	}

	ssn.Nodes = snapshot.Nodes
	ssn.Queues = snapshot.Queues

	glog.V(3).Infof("Open Session %v with <%d> Job and <%d> Queues",
		ssn.UID, len(ssn.Jobs), len(ssn.Queues))

	return ssn
}

func closeSession(ssn *Session) {
	for _, job := range ssn.Jobs {
		// If job is using PDB, ignore it.
		// TODO(k82cn): remove it when removing PDB support
		if job.PodGroup == nil {
			ssn.cache.RecordJobStatusEvent(job)
			continue
		}

		job.PodGroup.Status = jobStatus(ssn, job)
		if _, err := ssn.cache.UpdateJobStatus(job); err != nil {
			glog.Errorf("Failed to update job <%s/%s>: %v",
				job.Namespace, job.Name, err)
		}
	}

	ssn.Jobs = nil
	ssn.Nodes = nil
	ssn.Backlog = nil
	ssn.plugins = nil
	ssn.eventHandlers = nil
	ssn.jobOrderFns = nil
	ssn.queueOrderFns = nil

	glog.V(3).Infof("Close Session %v", ssn.UID)
}

func jobStatus(ssn *Session, jobInfo *api.JobInfo) v1alpha1.PodGroupStatus {
	status := jobInfo.PodGroup.Status

	unschedulable := false
	for _, c := range status.Conditions {
		if c.Type == v1alpha1.PodGroupUnschedulableType &&
			c.Status == v1.ConditionTrue &&
			c.TransitionID == string(ssn.UID) {

			unschedulable = true
			break
		}
	}

	// If running tasks && unschedulable, unknown phase
	if len(jobInfo.TaskStatusIndex[api.Running]) != 0 && unschedulable {
		status.Phase = v1alpha1.PodGroupUnknown
	} else {
		allocated := 0
		for status, tasks := range jobInfo.TaskStatusIndex {
			if api.AllocatedStatus(status) {
				allocated += len(tasks)
			}
		}

		// If there're enough allocated resource, it's running
		if int32(allocated) > jobInfo.PodGroup.Spec.MinMember {
			status.Phase = v1alpha1.PodGroupRunning
		} else {
			status.Phase = v1alpha1.PodGroupPending
		}
	}

	status.Running = int32(len(jobInfo.TaskStatusIndex[api.Running]))
	status.Failed = int32(len(jobInfo.TaskStatusIndex[api.Failed]))
	status.Succeeded = int32(len(jobInfo.TaskStatusIndex[api.Succeeded]))

	return status
}

func (ssn *Session) Statement() *Statement {
	return &Statement{
		ssn: ssn,
	}
}

func (ssn *Session) Pipeline(task *api.TaskInfo, hostname string) error {
	// Only update status in session
	job, found := ssn.Jobs[task.Job]
	if found {
		if err := job.UpdateTaskStatus(task, api.Pipelined); err != nil {
			glog.Errorf("Failed to update task <%v/%v> status to %v in Session <%v>: %v",
				task.Namespace, task.Name, api.Pipelined, ssn.UID, err)
			return err
		}
	} else {
		glog.Errorf("Failed to found Job <%s> in Session <%s> index when binding.",
			task.Job, ssn.UID)
		return fmt.Errorf("failed to find job %s when binding", task.Job)
	}

	task.NodeName = hostname

	if node, found := ssn.Nodes[hostname]; found {
		if err := node.AddTask(task); err != nil {
			glog.Errorf("Failed to add task <%v/%v> to node <%v> in Session <%v>: %v",
				task.Namespace, task.Name, hostname, ssn.UID, err)
			return err
		}
		glog.V(3).Infof("After added Task <%v/%v> to Node <%v>: idle <%v>, used <%v>, releasing <%v>",
			task.Namespace, task.Name, node.Name, node.Idle, node.Used, node.Releasing)
	} else {
		glog.Errorf("Failed to found Node <%s> in Session <%s> index when binding.",
			hostname, ssn.UID)
		return fmt.Errorf("failed to find node %s", hostname)
	}

	for _, eh := range ssn.eventHandlers {
		if eh.AllocateFunc != nil {
			eh.AllocateFunc(&Event{
				Task: task,
			})
		}
	}

	return nil
}

func (ssn *Session) Allocate(task *api.TaskInfo, hostname string) error {
	if err := ssn.cache.AllocateVolumes(task, hostname); err != nil {
		return err
	}

	// Only update status in session
	job, found := ssn.Jobs[task.Job]
	if found {
		if err := job.UpdateTaskStatus(task, api.Allocated); err != nil {
			glog.Errorf("Failed to update task <%v/%v> status to %v in Session <%v>: %v",
				task.Namespace, task.Name, api.Allocated, ssn.UID, err)
			return err
		}
	} else {
		glog.Errorf("Failed to found Job <%s> in Session <%s> index when binding.",
			task.Job, ssn.UID)
		return fmt.Errorf("failed to find job %s", task.Job)
	}

	task.NodeName = hostname

	if node, found := ssn.Nodes[hostname]; found {
		if err := node.AddTask(task); err != nil {
			glog.Errorf("Failed to add task <%v/%v> to node <%v> in Session <%v>: %v",
				task.Namespace, task.Name, hostname, ssn.UID, err)
			return err
		}
		glog.V(3).Infof("After allocated Task <%v/%v> to Node <%v>: idle <%v>, used <%v>, releasing <%v>",
			task.Namespace, task.Name, node.Name, node.Idle, node.Used, node.Releasing)
	} else {
		glog.Errorf("Failed to found Node <%s> in Session <%s> index when binding.",
			hostname, ssn.UID)
		return fmt.Errorf("failed to find node %s", hostname)
	}

	// Callbacks
	for _, eh := range ssn.eventHandlers {
		if eh.AllocateFunc != nil {
			eh.AllocateFunc(&Event{
				Task: task,
			})
		}
	}

	if ssn.JobReady(job) {
		for _, task := range job.TaskStatusIndex[api.Allocated] {
			if err := ssn.dispatch(task); err != nil {
				glog.Errorf("Failed to dispatch task <%v/%v>: %v",
					task.Namespace, task.Name, err)
				return err
			}
		}
	}

	return nil
}

func (ssn *Session) dispatch(task *api.TaskInfo) error {
	if err := ssn.cache.BindVolumes(task); err != nil {
		return err
	}

	if err := ssn.cache.Bind(task, task.NodeName); err != nil {
		return err
	}

	// Update status in session
	if job, found := ssn.Jobs[task.Job]; found {
		if err := job.UpdateTaskStatus(task, api.Binding); err != nil {
			glog.Errorf("Failed to update task <%v/%v> status to %v in Session <%v>: %v",
				task.Namespace, task.Name, api.Binding, ssn.UID, err)
			return err
		}
	} else {
		glog.Errorf("Failed to found Job <%s> in Session <%s> index when binding.",
			task.Job, ssn.UID)
		return fmt.Errorf("failed to find job %s", task.Job)
	}

	metrics.UpdateTaskScheduleDuration(metrics.Duration(task.Pod.CreationTimestamp.Time))
	return nil
}

func (ssn *Session) Evict(reclaimee *api.TaskInfo, reason string) error {
	if err := ssn.cache.Evict(reclaimee, reason); err != nil {
		return err
	}

	// Update status in session
	job, found := ssn.Jobs[reclaimee.Job]
	if found {
		if err := job.UpdateTaskStatus(reclaimee, api.Releasing); err != nil {
			glog.Errorf("Failed to update task <%v/%v> status to %v in Session <%v>: %v",
				reclaimee.Namespace, reclaimee.Name, api.Releasing, ssn.UID, err)
			return err
		}
	} else {
		glog.Errorf("Failed to found Job <%s> in Session <%s> index when binding.",
			reclaimee.Job, ssn.UID)
		return fmt.Errorf("failed to find job %s", reclaimee.Job)
	}

	// Update task in node.
	if node, found := ssn.Nodes[reclaimee.NodeName]; found {
		if err := node.UpdateTask(reclaimee); err != nil {
			glog.Errorf("Failed to update task <%v/%v> in Session <%v>: %v",
				reclaimee.Namespace, reclaimee.Name, ssn.UID, err)
			return err
		}
	}

	for _, eh := range ssn.eventHandlers {
		if eh.DeallocateFunc != nil {
			eh.DeallocateFunc(&Event{
				Task: reclaimee,
			})
		}
	}

	return nil
}

// UpdateJobStatus update job condition accordingly.
func (ssn *Session) UpdateJobCondition(jobInfo *api.JobInfo, cond *v1alpha1.PodGroupCondition) error {
	job, ok := ssn.Jobs[jobInfo.UID]
	if !ok {
		return fmt.Errorf("failed to find job <%s/%s>", jobInfo.Namespace, jobInfo.Name)
	}

	index := -1
	for i, c := range job.PodGroup.Status.Conditions {
		if c.Type == cond.Type {
			index = i
			break
		}
	}

	// Update condition to the new condition.
	if index < 0 {
		job.PodGroup.Status.Conditions = append(job.PodGroup.Status.Conditions, *cond)
	} else {
		job.PodGroup.Status.Conditions[index] = *cond
	}

	return nil
}

func (ssn *Session) AddEventHandler(eh *EventHandler) {
	ssn.eventHandlers = append(ssn.eventHandlers, eh)
}

func (ssn Session) String() string {
	msg := fmt.Sprintf("Session %v: \n", ssn.UID)

	for _, job := range ssn.Jobs {
		msg = fmt.Sprintf("%s%v\n", msg, job)
	}

	for _, node := range ssn.Nodes {
		msg = fmt.Sprintf("%s%v\n", msg, node)
	}

	return msg

}
<|endoftext|>


<commit_before>package datasource

import (
	"bufio"
	"bytes"
	"encoding/json"
	"fmt"
	"strings"

	"github.com/coreos/coreos-cloudinit/pkg"
)

// metadataService retrieves metadata from either an OpenStack[1] (2012-08-10)
// or EC2[2] (2009-04-04) compatible endpoint. It will first attempt to
// directly retrieve a JSON blob from the OpenStack endpoint. If that fails
// with a 404, it then attempts to retrieve metadata bit-by-bit from the EC2
// endpoint, and populates that into an equivalent JSON blob. metadataService
// also checks for userdata from EC2 and, if that fails with a 404, OpenStack.
//
// [1] http://docs.openstack.org/grizzly/openstack-compute/admin/content/metadata-service.html
// [2] http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/AESDG-chapter-instancedata.html#instancedata-data-categories

const (
	BaseUrl              = "http://169.254.169.254/"
	Ec2ApiVersion        = "2009-04-04"
	Ec2UserdataUrl       = BaseUrl + Ec2ApiVersion + "/user-data"
	Ec2MetadataUrl       = BaseUrl + Ec2ApiVersion + "/meta-data"
	OpenstackApiVersion  = "openstack/2012-08-10"
	OpenstackUserdataUrl = BaseUrl + OpenstackApiVersion + "/user_data"
	OpenstackMetadataUrl = BaseUrl + OpenstackApiVersion + "/meta_data.json"
)

type metadataService struct{}

type getter interface {
	GetRetry(string) ([]byte, error)
}

func NewMetadataService() *metadataService {
	return &metadataService{}
}

func (ms *metadataService) IsAvailable() bool {
	client := pkg.NewHttpClient()
	_, err := client.Get(BaseUrl)
	return (err == nil)
}

func (ms *metadataService) AvailabilityChanges() bool {
	return true
}

func (ms *metadataService) ConfigRoot() string {
	return ""
}

func (ms *metadataService) FetchMetadata() ([]byte, error) {
	return fetchMetadata(pkg.NewHttpClient())
}

func (ms *metadataService) FetchUserdata() ([]byte, error) {
	client := pkg.NewHttpClient()
	if data, err := client.GetRetry(Ec2UserdataUrl); err == nil {
		return data, err
	} else if _, ok := err.(pkg.ErrTimeout); ok {
		return data, err
	}
	return client.GetRetry(OpenstackUserdataUrl)
}

func (ms *metadataService) Type() string {
	return "metadata-service"
}

func fetchMetadata(client getter) ([]byte, error) {
	if metadata, err := client.GetRetry(OpenstackMetadataUrl); err == nil {
		return metadata, nil
	} else if _, ok := err.(pkg.ErrTimeout); ok {
		return nil, err
	}

	attrs := make(map[string]interface{})
	if keynames, err := fetchAttributes(client, fmt.Sprintf("%s/public-keys", Ec2MetadataUrl)); err == nil {
		keyIDs := make(map[string]string)
		for _, keyname := range keynames {
			tokens := strings.SplitN(keyname, "=", 2)
			if len(tokens) != 2 {
				return nil, fmt.Errorf("malformed public key: %q\n", keyname)
			}
			keyIDs[tokens[1]] = tokens[0]
		}

		keys := make(map[string]string)
		for name, id := range keyIDs {
			sshkey, err := fetchAttribute(client, fmt.Sprintf("%s/public-keys/%s/openssh-key", Ec2MetadataUrl, id))
			if err != nil {
				return nil, err
			}
			keys[name] = sshkey
			fmt.Printf("Found SSH key for %q\n", name)
		}
		attrs["public_keys"] = keys
	} else if _, ok := err.(pkg.ErrNotFound); !ok {
		return nil, err
	}

	if hostname, err := fetchAttribute(client, fmt.Sprintf("%s/hostname", Ec2MetadataUrl)); err == nil {
		attrs["hostname"] = hostname
	} else if _, ok := err.(pkg.ErrNotFound); !ok {
		return nil, err
	}

	if content_path, err := fetchAttribute(client, fmt.Sprintf("%s/network_config/content_path", Ec2MetadataUrl)); err == nil {
		attrs["network_config"] = map[string]string{
			"content_path": content_path,
		}
	} else if _, ok := err.(pkg.ErrNotFound); !ok {
		return nil, err
	}

	return json.Marshal(attrs)
}

func fetchAttributes(client getter, url string) ([]string, error) {
	resp, err := client.GetRetry(url)
	if err != nil {
		return nil, err
	}
	scanner := bufio.NewScanner(bytes.NewBuffer(resp))
	data := make([]string, 0)
	for scanner.Scan() {
		data = append(data, scanner.Text())
	}
	return data, scanner.Err()
}

func fetchAttribute(client getter, url string) (string, error) {
	if attrs, err := fetchAttributes(client, url); err == nil && len(attrs) > 0 {
		return attrs[0], nil
	} else {
		return "", err
	}
}
<commit_msg>metadata: Fetch the public and private IP addresses<commit_after>package datasource

import (
	"bufio"
	"bytes"
	"encoding/json"
	"fmt"
	"strings"

	"github.com/coreos/coreos-cloudinit/pkg"
)

// metadataService retrieves metadata from either an OpenStack[1] (2012-08-10)
// or EC2[2] (2009-04-04) compatible endpoint. It will first attempt to
// directly retrieve a JSON blob from the OpenStack endpoint. If that fails
// with a 404, it then attempts to retrieve metadata bit-by-bit from the EC2
// endpoint, and populates that into an equivalent JSON blob. metadataService
// also checks for userdata from EC2 and, if that fails with a 404, OpenStack.
//
// [1] http://docs.openstack.org/grizzly/openstack-compute/admin/content/metadata-service.html
// [2] http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/AESDG-chapter-instancedata.html#instancedata-data-categories

const (
	BaseUrl              = "http://169.254.169.254/"
	Ec2ApiVersion        = "2009-04-04"
	Ec2UserdataUrl       = BaseUrl + Ec2ApiVersion + "/user-data"
	Ec2MetadataUrl       = BaseUrl + Ec2ApiVersion + "/meta-data"
	OpenstackApiVersion  = "openstack/2012-08-10"
	OpenstackUserdataUrl = BaseUrl + OpenstackApiVersion + "/user_data"
	OpenstackMetadataUrl = BaseUrl + OpenstackApiVersion + "/meta_data.json"
)

type metadataService struct{}

type getter interface {
	GetRetry(string) ([]byte, error)
}

func NewMetadataService() *metadataService {
	return &metadataService{}
}

func (ms *metadataService) IsAvailable() bool {
	client := pkg.NewHttpClient()
	_, err := client.Get(BaseUrl)
	return (err == nil)
}

func (ms *metadataService) AvailabilityChanges() bool {
	return true
}

func (ms *metadataService) ConfigRoot() string {
	return ""
}

func (ms *metadataService) FetchMetadata() ([]byte, error) {
	return fetchMetadata(pkg.NewHttpClient())
}

func (ms *metadataService) FetchUserdata() ([]byte, error) {
	client := pkg.NewHttpClient()
	if data, err := client.GetRetry(Ec2UserdataUrl); err == nil {
		return data, err
	} else if _, ok := err.(pkg.ErrTimeout); ok {
		return data, err
	}
	return client.GetRetry(OpenstackUserdataUrl)
}

func (ms *metadataService) Type() string {
	return "metadata-service"
}

func fetchMetadata(client getter) ([]byte, error) {
	if metadata, err := client.GetRetry(OpenstackMetadataUrl); err == nil {
		return metadata, nil
	} else if _, ok := err.(pkg.ErrTimeout); ok {
		return nil, err
	}

	attrs := make(map[string]interface{})
	if keynames, err := fetchAttributes(client, fmt.Sprintf("%s/public-keys", Ec2MetadataUrl)); err == nil {
		keyIDs := make(map[string]string)
		for _, keyname := range keynames {
			tokens := strings.SplitN(keyname, "=", 2)
			if len(tokens) != 2 {
				return nil, fmt.Errorf("malformed public key: %q\n", keyname)
			}
			keyIDs[tokens[1]] = tokens[0]
		}

		keys := make(map[string]string)
		for name, id := range keyIDs {
			sshkey, err := fetchAttribute(client, fmt.Sprintf("%s/public-keys/%s/openssh-key", Ec2MetadataUrl, id))
			if err != nil {
				return nil, err
			}
			keys[name] = sshkey
			fmt.Printf("Found SSH key for %q\n", name)
		}
		attrs["public_keys"] = keys
	} else if _, ok := err.(pkg.ErrNotFound); !ok {
		return nil, err
	}

	if hostname, err := fetchAttribute(client, fmt.Sprintf("%s/hostname", Ec2MetadataUrl)); err == nil {
		attrs["hostname"] = hostname
	} else if _, ok := err.(pkg.ErrNotFound); !ok {
		return nil, err
	}

	if localAddr, err := fetchAttribute(client, fmt.Sprintf("%s/local-ipv4", Ec2MetadataUrl)); err == nil {
		attrs["local-ipv4"] = localAddr
	} else if _, ok := err.(pkg.ErrNotFound); !ok {
		return nil, err
	}

	if publicAddr, err := fetchAttribute(client, fmt.Sprintf("%s/public-ipv4", Ec2MetadataUrl)); err == nil {
		attrs["public-ipv4"] = publicAddr
	} else if _, ok := err.(pkg.ErrNotFound); !ok {
		return nil, err
	}

	if content_path, err := fetchAttribute(client, fmt.Sprintf("%s/network_config/content_path", Ec2MetadataUrl)); err == nil {
		attrs["network_config"] = map[string]string{
			"content_path": content_path,
		}
	} else if _, ok := err.(pkg.ErrNotFound); !ok {
		return nil, err
	}

	return json.Marshal(attrs)
}

func fetchAttributes(client getter, url string) ([]string, error) {
	resp, err := client.GetRetry(url)
	if err != nil {
		return nil, err
	}
	scanner := bufio.NewScanner(bytes.NewBuffer(resp))
	data := make([]string, 0)
	for scanner.Scan() {
		data = append(data, scanner.Text())
	}
	return data, scanner.Err()
}

func fetchAttribute(client getter, url string) (string, error) {
	if attrs, err := fetchAttributes(client, url); err == nil && len(attrs) > 0 {
		return attrs[0], nil
	} else {
		return "", err
	}
}
<|endoftext|>


<commit_before>/*
Copyright 2015 The Kubernetes Authors.

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

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

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

package v1alpha1

import (
	"fmt"
	"strings"
	"time"

	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	kruntime "k8s.io/apimachinery/pkg/runtime"
	"k8s.io/kubernetes/pkg/api"
	kubeletapis "k8s.io/kubernetes/pkg/kubelet/apis"
	"k8s.io/kubernetes/pkg/kubelet/qos"
	"k8s.io/kubernetes/pkg/master/ports"
)

const (
	DefaultRootDir = "/var/lib/kubelet"

	AutoDetectCloudProvider = "auto-detect"

	defaultIPTablesMasqueradeBit = 14
	defaultIPTablesDropBit       = 15
)

var (
	zeroDuration = metav1.Duration{}
	// Refer to [Node Allocatable](https://git.k8s.io/community/contributors/design-proposals/node-allocatable.md) doc for more information.
	defaultNodeAllocatableEnforcement = []string{"pods"}
)

func addDefaultingFuncs(scheme *kruntime.Scheme) error {
	return RegisterDefaults(scheme)
}

func SetDefaults_KubeProxyConfiguration(obj *KubeProxyConfiguration) {
	if len(obj.BindAddress) == 0 {
		obj.BindAddress = "0.0.0.0"
	}
	if obj.HealthzBindAddress == "" {
		obj.HealthzBindAddress = fmt.Sprintf("0.0.0.0:%v", ports.ProxyHealthzPort)
	} else if !strings.Contains(obj.HealthzBindAddress, ":") {
		obj.HealthzBindAddress += fmt.Sprintf(":%v", ports.ProxyHealthzPort)
	}
	if obj.MetricsBindAddress == "" {
		obj.MetricsBindAddress = fmt.Sprintf("127.0.0.1:%v", ports.ProxyStatusPort)
	} else if !strings.Contains(obj.MetricsBindAddress, ":") {
		obj.MetricsBindAddress += fmt.Sprintf(":%v", ports.ProxyStatusPort)
	}
	if obj.OOMScoreAdj == nil {
		temp := int32(qos.KubeProxyOOMScoreAdj)
		obj.OOMScoreAdj = &temp
	}
	if obj.ResourceContainer == "" {
		obj.ResourceContainer = "/kube-proxy"
	}
	if obj.IPTables.SyncPeriod.Duration == 0 {
		obj.IPTables.SyncPeriod = metav1.Duration{Duration: 30 * time.Second}
	}
	zero := metav1.Duration{}
	if obj.UDPIdleTimeout == zero {
		obj.UDPIdleTimeout = metav1.Duration{Duration: 250 * time.Millisecond}
	}
	// If ConntrackMax is set, respect it.
	if obj.Conntrack.Max == 0 {
		// If ConntrackMax is *not* set, use per-core scaling.
		if obj.Conntrack.MaxPerCore == 0 {
			obj.Conntrack.MaxPerCore = 32 * 1024
		}
		if obj.Conntrack.Min == 0 {
			obj.Conntrack.Min = 128 * 1024
		}
	}
	if obj.IPTables.MasqueradeBit == nil {
		temp := int32(14)
		obj.IPTables.MasqueradeBit = &temp
	}
	if obj.Conntrack.TCPEstablishedTimeout == zero {
		obj.Conntrack.TCPEstablishedTimeout = metav1.Duration{Duration: 24 * time.Hour} // 1 day (1/5 default)
	}
	if obj.Conntrack.TCPCloseWaitTimeout == zero {
		// See https://github.com/kubernetes/kubernetes/issues/32551.
		//
		// CLOSE_WAIT conntrack state occurs when the the Linux kernel
		// sees a FIN from the remote server. Note: this is a half-close
		// condition that persists as long as the local side keeps the
		// socket open. The condition is rare as it is typical in most
		// protocols for both sides to issue a close; this typically
		// occurs when the local socket is lazily garbage collected.
		//
		// If the CLOSE_WAIT conntrack entry expires, then FINs from the
		// local socket will not be properly SNAT'd and will not reach the
		// remote server (if the connection was subject to SNAT). If the
		// remote timeouts for FIN_WAIT* states exceed the CLOSE_WAIT
		// timeout, then there will be an inconsistency in the state of
		// the connection and a new connection reusing the SNAT (src,
		// port) pair may be rejected by the remote side with RST. This
		// can cause new calls to connect(2) to return with ECONNREFUSED.
		//
		// We set CLOSE_WAIT to one hour by default to better match
		// typical server timeouts.
		obj.Conntrack.TCPCloseWaitTimeout = metav1.Duration{Duration: 1 * time.Hour}
	}
	if obj.ConfigSyncPeriod.Duration == 0 {
		obj.ConfigSyncPeriod.Duration = 15 * time.Minute
	}

	if len(obj.ClientConnection.ContentType) == 0 {
		obj.ClientConnection.ContentType = "application/vnd.kubernetes.protobuf"
	}
	if obj.ClientConnection.QPS == 0.0 {
		obj.ClientConnection.QPS = 5.0
	}
	if obj.ClientConnection.Burst == 0 {
		obj.ClientConnection.Burst = 10
	}
}

func SetDefaults_KubeSchedulerConfiguration(obj *KubeSchedulerConfiguration) {
	if obj.Port == 0 {
		obj.Port = ports.SchedulerPort
	}
	if obj.Address == "" {
		obj.Address = "0.0.0.0"
	}
	if obj.AlgorithmProvider == "" {
		obj.AlgorithmProvider = "DefaultProvider"
	}
	if obj.ContentType == "" {
		obj.ContentType = "application/vnd.kubernetes.protobuf"
	}
	if obj.KubeAPIQPS == 0 {
		obj.KubeAPIQPS = 50.0
	}
	if obj.KubeAPIBurst == 0 {
		obj.KubeAPIBurst = 100
	}
	if obj.SchedulerName == "" {
		obj.SchedulerName = api.DefaultSchedulerName
	}
	if obj.HardPodAffinitySymmetricWeight == 0 {
		obj.HardPodAffinitySymmetricWeight = api.DefaultHardPodAffinitySymmetricWeight
	}
	if obj.FailureDomains == "" {
		obj.FailureDomains = kubeletapis.DefaultFailureDomains
	}
	if obj.LockObjectNamespace == "" {
		obj.LockObjectNamespace = SchedulerDefaultLockObjectNamespace
	}
	if obj.LockObjectName == "" {
		obj.LockObjectName = SchedulerDefaultLockObjectName
	}
	if obj.PolicyConfigMapNamespace == "" {
		obj.PolicyConfigMapNamespace = api.NamespaceSystem
	}
}

func SetDefaults_LeaderElectionConfiguration(obj *LeaderElectionConfiguration) {
	zero := metav1.Duration{}
	if obj.LeaseDuration == zero {
		obj.LeaseDuration = metav1.Duration{Duration: 15 * time.Second}
	}
	if obj.RenewDeadline == zero {
		obj.RenewDeadline = metav1.Duration{Duration: 10 * time.Second}
	}
	if obj.RetryPeriod == zero {
		obj.RetryPeriod = metav1.Duration{Duration: 2 * time.Second}
	}
	if obj.ResourceLock == "" {
		// obj.ResourceLock = rl.EndpointsResourceLock
		obj.ResourceLock = "endpoints"
	}
}

func boolVar(b bool) *bool {
	return &b
}
<commit_msg>add ipvs default sync period<commit_after>/*
Copyright 2015 The Kubernetes Authors.

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

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

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

package v1alpha1

import (
	"fmt"
	"strings"
	"time"

	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	kruntime "k8s.io/apimachinery/pkg/runtime"
	"k8s.io/kubernetes/pkg/api"
	kubeletapis "k8s.io/kubernetes/pkg/kubelet/apis"
	"k8s.io/kubernetes/pkg/kubelet/qos"
	"k8s.io/kubernetes/pkg/master/ports"
)

const (
	DefaultRootDir = "/var/lib/kubelet"

	AutoDetectCloudProvider = "auto-detect"

	defaultIPTablesMasqueradeBit = 14
	defaultIPTablesDropBit       = 15
)

var (
	zeroDuration = metav1.Duration{}
	// Refer to [Node Allocatable](https://git.k8s.io/community/contributors/design-proposals/node-allocatable.md) doc for more information.
	defaultNodeAllocatableEnforcement = []string{"pods"}
)

func addDefaultingFuncs(scheme *kruntime.Scheme) error {
	return RegisterDefaults(scheme)
}

func SetDefaults_KubeProxyConfiguration(obj *KubeProxyConfiguration) {
	if len(obj.BindAddress) == 0 {
		obj.BindAddress = "0.0.0.0"
	}
	if obj.HealthzBindAddress == "" {
		obj.HealthzBindAddress = fmt.Sprintf("0.0.0.0:%v", ports.ProxyHealthzPort)
	} else if !strings.Contains(obj.HealthzBindAddress, ":") {
		obj.HealthzBindAddress += fmt.Sprintf(":%v", ports.ProxyHealthzPort)
	}
	if obj.MetricsBindAddress == "" {
		obj.MetricsBindAddress = fmt.Sprintf("127.0.0.1:%v", ports.ProxyStatusPort)
	} else if !strings.Contains(obj.MetricsBindAddress, ":") {
		obj.MetricsBindAddress += fmt.Sprintf(":%v", ports.ProxyStatusPort)
	}
	if obj.OOMScoreAdj == nil {
		temp := int32(qos.KubeProxyOOMScoreAdj)
		obj.OOMScoreAdj = &temp
	}
	if obj.ResourceContainer == "" {
		obj.ResourceContainer = "/kube-proxy"
	}
	if obj.IPTables.SyncPeriod.Duration == 0 {
		obj.IPTables.SyncPeriod = metav1.Duration{Duration: 30 * time.Second}
	}
	if obj.IPVS.SyncPeriod.Duration == 0 {
		obj.IPVS.SyncPeriod = metav1.Duration{Duration: 30 * time.Second}
	}
	zero := metav1.Duration{}
	if obj.UDPIdleTimeout == zero {
		obj.UDPIdleTimeout = metav1.Duration{Duration: 250 * time.Millisecond}
	}
	// If ConntrackMax is set, respect it.
	if obj.Conntrack.Max == 0 {
		// If ConntrackMax is *not* set, use per-core scaling.
		if obj.Conntrack.MaxPerCore == 0 {
			obj.Conntrack.MaxPerCore = 32 * 1024
		}
		if obj.Conntrack.Min == 0 {
			obj.Conntrack.Min = 128 * 1024
		}
	}
	if obj.IPTables.MasqueradeBit == nil {
		temp := int32(14)
		obj.IPTables.MasqueradeBit = &temp
	}
	if obj.Conntrack.TCPEstablishedTimeout == zero {
		obj.Conntrack.TCPEstablishedTimeout = metav1.Duration{Duration: 24 * time.Hour} // 1 day (1/5 default)
	}
	if obj.Conntrack.TCPCloseWaitTimeout == zero {
		// See https://github.com/kubernetes/kubernetes/issues/32551.
		//
		// CLOSE_WAIT conntrack state occurs when the the Linux kernel
		// sees a FIN from the remote server. Note: this is a half-close
		// condition that persists as long as the local side keeps the
		// socket open. The condition is rare as it is typical in most
		// protocols for both sides to issue a close; this typically
		// occurs when the local socket is lazily garbage collected.
		//
		// If the CLOSE_WAIT conntrack entry expires, then FINs from the
		// local socket will not be properly SNAT'd and will not reach the
		// remote server (if the connection was subject to SNAT). If the
		// remote timeouts for FIN_WAIT* states exceed the CLOSE_WAIT
		// timeout, then there will be an inconsistency in the state of
		// the connection and a new connection reusing the SNAT (src,
		// port) pair may be rejected by the remote side with RST. This
		// can cause new calls to connect(2) to return with ECONNREFUSED.
		//
		// We set CLOSE_WAIT to one hour by default to better match
		// typical server timeouts.
		obj.Conntrack.TCPCloseWaitTimeout = metav1.Duration{Duration: 1 * time.Hour}
	}
	if obj.ConfigSyncPeriod.Duration == 0 {
		obj.ConfigSyncPeriod.Duration = 15 * time.Minute
	}

	if len(obj.ClientConnection.ContentType) == 0 {
		obj.ClientConnection.ContentType = "application/vnd.kubernetes.protobuf"
	}
	if obj.ClientConnection.QPS == 0.0 {
		obj.ClientConnection.QPS = 5.0
	}
	if obj.ClientConnection.Burst == 0 {
		obj.ClientConnection.Burst = 10
	}
}

func SetDefaults_KubeSchedulerConfiguration(obj *KubeSchedulerConfiguration) {
	if obj.Port == 0 {
		obj.Port = ports.SchedulerPort
	}
	if obj.Address == "" {
		obj.Address = "0.0.0.0"
	}
	if obj.AlgorithmProvider == "" {
		obj.AlgorithmProvider = "DefaultProvider"
	}
	if obj.ContentType == "" {
		obj.ContentType = "application/vnd.kubernetes.protobuf"
	}
	if obj.KubeAPIQPS == 0 {
		obj.KubeAPIQPS = 50.0
	}
	if obj.KubeAPIBurst == 0 {
		obj.KubeAPIBurst = 100
	}
	if obj.SchedulerName == "" {
		obj.SchedulerName = api.DefaultSchedulerName
	}
	if obj.HardPodAffinitySymmetricWeight == 0 {
		obj.HardPodAffinitySymmetricWeight = api.DefaultHardPodAffinitySymmetricWeight
	}
	if obj.FailureDomains == "" {
		obj.FailureDomains = kubeletapis.DefaultFailureDomains
	}
	if obj.LockObjectNamespace == "" {
		obj.LockObjectNamespace = SchedulerDefaultLockObjectNamespace
	}
	if obj.LockObjectName == "" {
		obj.LockObjectName = SchedulerDefaultLockObjectName
	}
	if obj.PolicyConfigMapNamespace == "" {
		obj.PolicyConfigMapNamespace = api.NamespaceSystem
	}
}

func SetDefaults_LeaderElectionConfiguration(obj *LeaderElectionConfiguration) {
	zero := metav1.Duration{}
	if obj.LeaseDuration == zero {
		obj.LeaseDuration = metav1.Duration{Duration: 15 * time.Second}
	}
	if obj.RenewDeadline == zero {
		obj.RenewDeadline = metav1.Duration{Duration: 10 * time.Second}
	}
	if obj.RetryPeriod == zero {
		obj.RetryPeriod = metav1.Duration{Duration: 2 * time.Second}
	}
	if obj.ResourceLock == "" {
		// obj.ResourceLock = rl.EndpointsResourceLock
		obj.ResourceLock = "endpoints"
	}
}

func boolVar(b bool) *bool {
	return &b
}
<|endoftext|>


<commit_before>/*
Copyright 2019 The Tekton 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 v1beta1_test

import (
	"context"
	"encoding/json"
	"reflect"
	"testing"

	"github.com/google/go-cmp/cmp"
	"github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1"
	"github.com/tektoncd/pipeline/test/diff"
)

func TestParamSpec_SetDefaults(t *testing.T) {
	tests := []struct {
		name            string
		before          *v1beta1.ParamSpec
		defaultsApplied *v1beta1.ParamSpec
	}{{
		name: "inferred string type",
		before: &v1beta1.ParamSpec{
			Name: "parametername",
		},
		defaultsApplied: &v1beta1.ParamSpec{
			Name: "parametername",
			Type: v1beta1.ParamTypeString,
		},
	}, {
		name: "inferred type from default value - array",
		before: &v1beta1.ParamSpec{
			Name: "parametername",
			Default: &v1beta1.ArrayOrString{
				ArrayVal: []string{"array"},
			},
		},
		defaultsApplied: &v1beta1.ParamSpec{
			Name: "parametername",
			Type: v1beta1.ParamTypeArray,
			Default: &v1beta1.ArrayOrString{
				ArrayVal: []string{"array"},
			},
		},
	}, {
		name: "inferred type from default value - string",
		before: &v1beta1.ParamSpec{
			Name: "parametername",
			Default: &v1beta1.ArrayOrString{
				StringVal: "an",
			},
		},
		defaultsApplied: &v1beta1.ParamSpec{
			Name: "parametername",
			Type: v1beta1.ParamTypeString,
			Default: &v1beta1.ArrayOrString{
				StringVal: "an",
			},
		},
	}, {
		name: "fully defined ParamSpec",
		before: &v1beta1.ParamSpec{
			Name:        "parametername",
			Type:        v1beta1.ParamTypeArray,
			Description: "a description",
			Default: &v1beta1.ArrayOrString{
				ArrayVal: []string{"array"},
			},
		},
		defaultsApplied: &v1beta1.ParamSpec{
			Name:        "parametername",
			Type:        v1beta1.ParamTypeArray,
			Description: "a description",
			Default: &v1beta1.ArrayOrString{
				ArrayVal: []string{"array"},
			},
		},
	}}
	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			ctx := context.Background()
			tc.before.SetDefaults(ctx)
			if d := cmp.Diff(tc.before, tc.defaultsApplied); d != "" {
				t.Error(diff.PrintWantGot(d))
			}
		})
	}
}

func TestArrayOrString_ApplyReplacements(t *testing.T) {
	type args struct {
		input              *v1beta1.ArrayOrString
		stringReplacements map[string]string
		arrayReplacements  map[string][]string
	}
	tests := []struct {
		name           string
		args           args
		expectedOutput *v1beta1.ArrayOrString
	}{{
		name: "no replacements on array",
		args: args{
			input:              v1beta1.NewArrayOrString("an", "array"),
			stringReplacements: map[string]string{"some": "value", "anotherkey": "value"},
			arrayReplacements:  map[string][]string{"arraykey": {"array", "value"}, "sdfdf": {"sdf", "sdfsd"}},
		},
		expectedOutput: v1beta1.NewArrayOrString("an", "array"),
	}, {
		name: "string replacements on string",
		args: args{
			input:              v1beta1.NewArrayOrString("astring$(some) asdf $(anotherkey)"),
			stringReplacements: map[string]string{"some": "value", "anotherkey": "value"},
			arrayReplacements:  map[string][]string{"arraykey": {"array", "value"}, "sdfdf": {"asdf", "sdfsd"}},
		},
		expectedOutput: v1beta1.NewArrayOrString("astringvalue asdf value"),
	}, {
		name: "single array replacement",
		args: args{
			input:              v1beta1.NewArrayOrString("firstvalue", "$(arraykey)", "lastvalue"),
			stringReplacements: map[string]string{"some": "value", "anotherkey": "value"},
			arrayReplacements:  map[string][]string{"arraykey": {"array", "value"}, "sdfdf": {"asdf", "sdfsd"}},
		},
		expectedOutput: v1beta1.NewArrayOrString("firstvalue", "array", "value", "lastvalue"),
	}, {
		name: "multiple array replacement",
		args: args{
			input:              v1beta1.NewArrayOrString("firstvalue", "$(arraykey)", "lastvalue", "$(sdfdf)"),
			stringReplacements: map[string]string{"some": "value", "anotherkey": "value"},
			arrayReplacements:  map[string][]string{"arraykey": {"array", "value"}, "sdfdf": {"asdf", "sdfsd"}},
		},
		expectedOutput: v1beta1.NewArrayOrString("firstvalue", "array", "value", "lastvalue", "asdf", "sdfsd"),
	}, {
		name: "empty array replacement",
		args: args{
			input:              v1beta1.NewArrayOrString("firstvalue", "$(arraykey)", "lastvalue"),
			stringReplacements: map[string]string{"some": "value", "anotherkey": "value"},
			arrayReplacements:  map[string][]string{"arraykey": {}},
		},
		expectedOutput: v1beta1.NewArrayOrString("firstvalue", "lastvalue"),
	}}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			tt.args.input.ApplyReplacements(tt.args.stringReplacements, tt.args.arrayReplacements)
			if d := cmp.Diff(tt.expectedOutput, tt.args.input); d != "" {
				t.Errorf("ApplyReplacements() output did not match expected value %s", diff.PrintWantGot(d))
			}
		})
	}
}

type ArrayOrStringHolder struct {
	AOrS v1beta1.ArrayOrString `json:"val"`
}

func TestArrayOrString_UnmarshalJSON(t *testing.T) {
	cases := []struct {
		input  string
		result v1beta1.ArrayOrString
	}{
		{"{\"val\": \"123\"}", *v1beta1.NewArrayOrString("123")},
		{"{\"val\": \"\"}", *v1beta1.NewArrayOrString("")},
		{"{\"val\":[]}", v1beta1.ArrayOrString{Type: v1beta1.ParamTypeArray, ArrayVal: []string{}}},
		{"{\"val\":[\"oneelement\"]}", v1beta1.ArrayOrString{Type: v1beta1.ParamTypeArray, ArrayVal: []string{"oneelement"}}},
		{"{\"val\":[\"multiple\", \"elements\"]}", *v1beta1.NewArrayOrString("multiple", "elements")},
	}

	for _, c := range cases {
		var result ArrayOrStringHolder
		if err := json.Unmarshal([]byte(c.input), &result); err != nil {
			t.Errorf("Failed to unmarshal input '%v': %v", c.input, err)
		}
		if !reflect.DeepEqual(result.AOrS, c.result) {
			t.Errorf("Failed to unmarshal input '%v': expected %+v, got %+v", c.input, c.result, result)
		}
	}
}

func TestArrayOrString_MarshalJSON(t *testing.T) {
	cases := []struct {
		input  v1beta1.ArrayOrString
		result string
	}{
		{*v1beta1.NewArrayOrString("123"), "{\"val\":\"123\"}"},
		{*v1beta1.NewArrayOrString("123", "1234"), "{\"val\":[\"123\",\"1234\"]}"},
		{*v1beta1.NewArrayOrString("a", "a", "a"), "{\"val\":[\"a\",\"a\",\"a\"]}"},
	}

	for _, c := range cases {
		input := ArrayOrStringHolder{c.input}
		result, err := json.Marshal(&input)
		if err != nil {
			t.Errorf("Failed to marshal input '%v': %v", input, err)
		}
		if string(result) != c.result {
			t.Errorf("Failed to marshal input '%v': expected: %+v, got %q", input, c.result, string(result))
		}
	}
}

func TestArrayReference(t *testing.T) {
	tests := []struct {
		name, p, expectedResult string
	}{{
		name:           "valid array parameter expression with star notation returns param name",
		p:              "$(params.arrayParam[*])",
		expectedResult: "arrayParam",
	}, {
		name:           "invalid array parameter without dollar notation returns the input as is",
		p:              "params.arrayParam[*]",
		expectedResult: "params.arrayParam[*]",
	}}
	for _, tt := range tests {
		if d := cmp.Diff(tt.expectedResult, v1beta1.ArrayReference(tt.p)); d != "" {
			t.Errorf(diff.PrintWantGot(d))
		}
	}
}
<commit_msg>Params: sanity check unmarshalling works with multiline json.<commit_after>/*
Copyright 2019 The Tekton 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 v1beta1_test

import (
	"bytes"
	"context"
	"encoding/json"
	"reflect"
	"testing"

	"github.com/google/go-cmp/cmp"
	"github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1"
	"github.com/tektoncd/pipeline/test/diff"
)

func TestParamSpec_SetDefaults(t *testing.T) {
	tests := []struct {
		name            string
		before          *v1beta1.ParamSpec
		defaultsApplied *v1beta1.ParamSpec
	}{{
		name: "inferred string type",
		before: &v1beta1.ParamSpec{
			Name: "parametername",
		},
		defaultsApplied: &v1beta1.ParamSpec{
			Name: "parametername",
			Type: v1beta1.ParamTypeString,
		},
	}, {
		name: "inferred type from default value - array",
		before: &v1beta1.ParamSpec{
			Name: "parametername",
			Default: &v1beta1.ArrayOrString{
				ArrayVal: []string{"array"},
			},
		},
		defaultsApplied: &v1beta1.ParamSpec{
			Name: "parametername",
			Type: v1beta1.ParamTypeArray,
			Default: &v1beta1.ArrayOrString{
				ArrayVal: []string{"array"},
			},
		},
	}, {
		name: "inferred type from default value - string",
		before: &v1beta1.ParamSpec{
			Name: "parametername",
			Default: &v1beta1.ArrayOrString{
				StringVal: "an",
			},
		},
		defaultsApplied: &v1beta1.ParamSpec{
			Name: "parametername",
			Type: v1beta1.ParamTypeString,
			Default: &v1beta1.ArrayOrString{
				StringVal: "an",
			},
		},
	}, {
		name: "fully defined ParamSpec",
		before: &v1beta1.ParamSpec{
			Name:        "parametername",
			Type:        v1beta1.ParamTypeArray,
			Description: "a description",
			Default: &v1beta1.ArrayOrString{
				ArrayVal: []string{"array"},
			},
		},
		defaultsApplied: &v1beta1.ParamSpec{
			Name:        "parametername",
			Type:        v1beta1.ParamTypeArray,
			Description: "a description",
			Default: &v1beta1.ArrayOrString{
				ArrayVal: []string{"array"},
			},
		},
	}}
	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			ctx := context.Background()
			tc.before.SetDefaults(ctx)
			if d := cmp.Diff(tc.before, tc.defaultsApplied); d != "" {
				t.Error(diff.PrintWantGot(d))
			}
		})
	}
}

func TestArrayOrString_ApplyReplacements(t *testing.T) {
	type args struct {
		input              *v1beta1.ArrayOrString
		stringReplacements map[string]string
		arrayReplacements  map[string][]string
	}
	tests := []struct {
		name           string
		args           args
		expectedOutput *v1beta1.ArrayOrString
	}{{
		name: "no replacements on array",
		args: args{
			input:              v1beta1.NewArrayOrString("an", "array"),
			stringReplacements: map[string]string{"some": "value", "anotherkey": "value"},
			arrayReplacements:  map[string][]string{"arraykey": {"array", "value"}, "sdfdf": {"sdf", "sdfsd"}},
		},
		expectedOutput: v1beta1.NewArrayOrString("an", "array"),
	}, {
		name: "string replacements on string",
		args: args{
			input:              v1beta1.NewArrayOrString("astring$(some) asdf $(anotherkey)"),
			stringReplacements: map[string]string{"some": "value", "anotherkey": "value"},
			arrayReplacements:  map[string][]string{"arraykey": {"array", "value"}, "sdfdf": {"asdf", "sdfsd"}},
		},
		expectedOutput: v1beta1.NewArrayOrString("astringvalue asdf value"),
	}, {
		name: "single array replacement",
		args: args{
			input:              v1beta1.NewArrayOrString("firstvalue", "$(arraykey)", "lastvalue"),
			stringReplacements: map[string]string{"some": "value", "anotherkey": "value"},
			arrayReplacements:  map[string][]string{"arraykey": {"array", "value"}, "sdfdf": {"asdf", "sdfsd"}},
		},
		expectedOutput: v1beta1.NewArrayOrString("firstvalue", "array", "value", "lastvalue"),
	}, {
		name: "multiple array replacement",
		args: args{
			input:              v1beta1.NewArrayOrString("firstvalue", "$(arraykey)", "lastvalue", "$(sdfdf)"),
			stringReplacements: map[string]string{"some": "value", "anotherkey": "value"},
			arrayReplacements:  map[string][]string{"arraykey": {"array", "value"}, "sdfdf": {"asdf", "sdfsd"}},
		},
		expectedOutput: v1beta1.NewArrayOrString("firstvalue", "array", "value", "lastvalue", "asdf", "sdfsd"),
	}, {
		name: "empty array replacement",
		args: args{
			input:              v1beta1.NewArrayOrString("firstvalue", "$(arraykey)", "lastvalue"),
			stringReplacements: map[string]string{"some": "value", "anotherkey": "value"},
			arrayReplacements:  map[string][]string{"arraykey": {}},
		},
		expectedOutput: v1beta1.NewArrayOrString("firstvalue", "lastvalue"),
	}}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			tt.args.input.ApplyReplacements(tt.args.stringReplacements, tt.args.arrayReplacements)
			if d := cmp.Diff(tt.expectedOutput, tt.args.input); d != "" {
				t.Errorf("ApplyReplacements() output did not match expected value %s", diff.PrintWantGot(d))
			}
		})
	}
}

type ArrayOrStringHolder struct {
	AOrS v1beta1.ArrayOrString `json:"val"`
}

func TestArrayOrString_UnmarshalJSON(t *testing.T) {
	cases := []struct {
		input  map[string]interface{}
		result v1beta1.ArrayOrString
	}{
		{
			input:  map[string]interface{}{"val": "123"},
			result: *v1beta1.NewArrayOrString("123"),
		},
		{
			input:  map[string]interface{}{"val": ""},
			result: *v1beta1.NewArrayOrString(""),
		},
		{
			input:  map[string]interface{}{"val": nil},
			result: v1beta1.ArrayOrString{Type: v1beta1.ParamTypeArray, ArrayVal: nil},
		},
		{
			input:  map[string]interface{}{"val": []string{}},
			result: v1beta1.ArrayOrString{Type: v1beta1.ParamTypeArray, ArrayVal: []string{}},
		},
		{
			input:  map[string]interface{}{"val": []string{"oneelement"}},
			result: v1beta1.ArrayOrString{Type: v1beta1.ParamTypeArray, ArrayVal: []string{"oneelement"}},
		},
		{
			input:  map[string]interface{}{"val": []string{"multiple", "elements"}},
			result: v1beta1.ArrayOrString{Type: v1beta1.ParamTypeArray, ArrayVal: []string{"multiple", "elements"}},
		},
	}

	for _, c := range cases {
		for _, opts := range []func(enc *json.Encoder){
			// Default encoding
			func(enc *json.Encoder) {},
			// Multiline encoding
			func(enc *json.Encoder) { enc.SetIndent("", "  ") },
		} {
			b := new(bytes.Buffer)
			enc := json.NewEncoder(b)
			opts(enc)
			if err := enc.Encode(c.input); err != nil {
				t.Fatalf("error encoding json: %v", err)
			}

			var result ArrayOrStringHolder
			if err := json.Unmarshal(b.Bytes(), &result); err != nil {
				t.Errorf("Failed to unmarshal input '%v': %v", c.input, err)
			}
			if !reflect.DeepEqual(result.AOrS, c.result) {
				t.Errorf("expected %+v, got %+v", c.result, result)
			}
		}
	}
}

func TestArrayOrString_MarshalJSON(t *testing.T) {
	cases := []struct {
		input  v1beta1.ArrayOrString
		result string
	}{
		{*v1beta1.NewArrayOrString("123"), "{\"val\":\"123\"}"},
		{*v1beta1.NewArrayOrString("123", "1234"), "{\"val\":[\"123\",\"1234\"]}"},
		{*v1beta1.NewArrayOrString("a", "a", "a"), "{\"val\":[\"a\",\"a\",\"a\"]}"},
	}

	for _, c := range cases {
		input := ArrayOrStringHolder{c.input}
		result, err := json.Marshal(&input)
		if err != nil {
			t.Errorf("Failed to marshal input '%v': %v", input, err)
		}
		if string(result) != c.result {
			t.Errorf("Failed to marshal input '%v': expected: %+v, got %q", input, c.result, string(result))
		}
	}
}

func TestArrayReference(t *testing.T) {
	tests := []struct {
		name, p, expectedResult string
	}{{
		name:           "valid array parameter expression with star notation returns param name",
		p:              "$(params.arrayParam[*])",
		expectedResult: "arrayParam",
	}, {
		name:           "invalid array parameter without dollar notation returns the input as is",
		p:              "params.arrayParam[*]",
		expectedResult: "params.arrayParam[*]",
	}}
	for _, tt := range tests {
		if d := cmp.Diff(tt.expectedResult, v1beta1.ArrayReference(tt.p)); d != "" {
			t.Errorf(diff.PrintWantGot(d))
		}
	}
}
<|endoftext|>


<commit_before>package server

import (
	"crypto/tls"
	"errors"
	"net"

	"github.com/emersion/go-imap"
	"github.com/emersion/go-imap/commands"
	"github.com/emersion/go-imap/responses"
	"github.com/emersion/go-sasl"
)

// IMAP errors in Not Authenticated state.
var (
	ErrAlreadyAuthenticated = errors.New("Already authenticated")
	ErrAuthDisabled         = errors.New("Authentication disabled")
)

type StartTLS struct {
	commands.StartTLS
}

func (cmd *StartTLS) Handle(conn Conn) error {
	ctx := conn.Context()
	if ctx.State != imap.NotAuthenticatedState {
		return ErrAlreadyAuthenticated
	}
	if conn.IsTLS() {
		return errors.New("TLS is already enabled")
	}
	if conn.Server().TLSConfig == nil {
		return errors.New("TLS support not enabled")
	}

	// Send an OK status response to let the client know that the TLS handshake
	// can begin
	return ErrStatusResp(&imap.StatusResp{
		Type: imap.StatusRespOk,
		Info: "Begin TLS negotiation now",
	})
}

func (cmd *StartTLS) Upgrade(conn Conn) error {
	tlsConfig := conn.Server().TLSConfig

	var tlsConn *tls.Conn
	err := conn.Upgrade(func(sock net.Conn) (net.Conn, error) {
		conn.WaitReady()
		tlsConn = tls.Server(sock, tlsConfig)
		err := tlsConn.Handshake()
		return tlsConn, err
	})
	if err != nil {
		return err
	}

	conn.setTLSConn(tlsConn)

	res := &responses.Capability{Caps: conn.Capabilities()}
	return conn.WriteResp(res)
}

func afterAuthStatus(conn Conn) error {
	return ErrStatusResp(&imap.StatusResp{
		Type:      imap.StatusRespOk,
		Code:      imap.CodeCapability,
		Arguments: imap.FormatStringList(conn.Capabilities()),
	})
}

func canAuth(conn Conn) bool {
	for _, cap := range conn.Capabilities() {
		if cap == "AUTH=PLAIN" {
			return true
		}
	}
	return false
}

type Login struct {
	commands.Login
}

func (cmd *Login) Handle(conn Conn) error {
	ctx := conn.Context()
	if ctx.State != imap.NotAuthenticatedState {
		return ErrAlreadyAuthenticated
	}
	if !canAuth(conn) {
		return ErrAuthDisabled
	}

	user, err := conn.Server().Backend.Login(conn.Info(), cmd.Username, cmd.Password)
	if err != nil {
		return err
	}

	ctx.State = imap.AuthenticatedState
	ctx.User = user
	return afterAuthStatus(conn)
}

type Authenticate struct {
	commands.Authenticate
}

func (cmd *Authenticate) Handle(conn Conn) error {
	ctx := conn.Context()
	if ctx.State != imap.NotAuthenticatedState {
		return ErrAlreadyAuthenticated
	}
	if !canAuth(conn) {
		return ErrAuthDisabled
	}

	mechanisms := map[string]sasl.Server{}
	for name, newSasl := range conn.Server().auths {
		mechanisms[name] = newSasl(conn)
	}

	err := cmd.Authenticate.Handle(mechanisms, conn)
	if err != nil {
		return err
	}

	return afterAuthStatus(conn)
}
<commit_msg>server: Fix quoted capabilities in post-auth response<commit_after>package server

import (
	"crypto/tls"
	"errors"
	"net"

	"github.com/emersion/go-imap"
	"github.com/emersion/go-imap/commands"
	"github.com/emersion/go-imap/responses"
	"github.com/emersion/go-sasl"
)

// IMAP errors in Not Authenticated state.
var (
	ErrAlreadyAuthenticated = errors.New("Already authenticated")
	ErrAuthDisabled         = errors.New("Authentication disabled")
)

type StartTLS struct {
	commands.StartTLS
}

func (cmd *StartTLS) Handle(conn Conn) error {
	ctx := conn.Context()
	if ctx.State != imap.NotAuthenticatedState {
		return ErrAlreadyAuthenticated
	}
	if conn.IsTLS() {
		return errors.New("TLS is already enabled")
	}
	if conn.Server().TLSConfig == nil {
		return errors.New("TLS support not enabled")
	}

	// Send an OK status response to let the client know that the TLS handshake
	// can begin
	return ErrStatusResp(&imap.StatusResp{
		Type: imap.StatusRespOk,
		Info: "Begin TLS negotiation now",
	})
}

func (cmd *StartTLS) Upgrade(conn Conn) error {
	tlsConfig := conn.Server().TLSConfig

	var tlsConn *tls.Conn
	err := conn.Upgrade(func(sock net.Conn) (net.Conn, error) {
		conn.WaitReady()
		tlsConn = tls.Server(sock, tlsConfig)
		err := tlsConn.Handshake()
		return tlsConn, err
	})
	if err != nil {
		return err
	}

	conn.setTLSConn(tlsConn)

	res := &responses.Capability{Caps: conn.Capabilities()}
	return conn.WriteResp(res)
}

func afterAuthStatus(conn Conn) error {
	caps := conn.Capabilities()
	capAtoms := make([]interface{}, 0, len(caps))
	for _, cap := range caps {
		capAtoms = append(capAtoms, imap.RawString(cap))
	}

	return ErrStatusResp(&imap.StatusResp{
		Type:      imap.StatusRespOk,
		Code:      imap.CodeCapability,
		Arguments: capAtoms,
	})
}

func canAuth(conn Conn) bool {
	for _, cap := range conn.Capabilities() {
		if cap == "AUTH=PLAIN" {
			return true
		}
	}
	return false
}

type Login struct {
	commands.Login
}

func (cmd *Login) Handle(conn Conn) error {
	ctx := conn.Context()
	if ctx.State != imap.NotAuthenticatedState {
		return ErrAlreadyAuthenticated
	}
	if !canAuth(conn) {
		return ErrAuthDisabled
	}

	user, err := conn.Server().Backend.Login(conn.Info(), cmd.Username, cmd.Password)
	if err != nil {
		return err
	}

	ctx.State = imap.AuthenticatedState
	ctx.User = user
	return afterAuthStatus(conn)
}

type Authenticate struct {
	commands.Authenticate
}

func (cmd *Authenticate) Handle(conn Conn) error {
	ctx := conn.Context()
	if ctx.State != imap.NotAuthenticatedState {
		return ErrAlreadyAuthenticated
	}
	if !canAuth(conn) {
		return ErrAuthDisabled
	}

	mechanisms := map[string]sasl.Server{}
	for name, newSasl := range conn.Server().auths {
		mechanisms[name] = newSasl(conn)
	}

	err := cmd.Authenticate.Handle(mechanisms, conn)
	if err != nil {
		return err
	}

	return afterAuthStatus(conn)
}
<|endoftext|>


<commit_before>// nolint: govet
package main

import (
	"github.com/alecthomas/kong"

	"github.com/alecthomas/participle/v2"
	"github.com/alecthomas/participle/v2/lexer"
	"github.com/alecthomas/participle/v2/lexer/stateful"

	"github.com/alecthomas/repr"
)

type Boolean bool

func (b *Boolean) Capture(values []string) error {
	*b = values[0] == "TRUE"
	return nil
}

// Select based on http://www.h2database.com/html/grammar.html
type Select struct {
	Top        *Term             `"SELECT" ( "TOP" @@ )?`
	Distinct   bool              `(  @"DISTINCT"`
	All        bool              ` | @"ALL" )?`
	Expression *SelectExpression `@@`
	From       *From             `"FROM" @@`
	Limit      *Expression       `( "LIMIT" @@ )?`
	Offset     *Expression       `( "OFFSET" @@ )?`
	GroupBy    *Expression       `( "GROUP" "BY" @@ )?`
}

type From struct {
	TableExpressions []*TableExpression `@@ ( "," @@ )*`
	Where            *Expression        `( "WHERE" @@ )?`
}

type TableExpression struct {
	Table  string        `( @Ident ( "." @Ident )*`
	Select *Select       `  | "(" @@ ")"`
	Values []*Expression `  | "VALUES" "(" @@ ( "," @@ )* ")")`
	As     string        `( "AS" @Ident )?`
}

type SelectExpression struct {
	All         bool                 `  @"*"`
	Expressions []*AliasedExpression `| @@ ( "," @@ )*`
}

type AliasedExpression struct {
	Expression *Expression `@@`
	As         string      `( "AS" @Ident )?`
}

type Expression struct {
	Or []*OrCondition `@@ ( "OR" @@ )*`
}

type OrCondition struct {
	And []*Condition `@@ ( "AND" @@ )*`
}

type Condition struct {
	Operand *ConditionOperand `  @@`
	Not     *Condition        `| "NOT" @@`
	Exists  *Select           `| "EXISTS" "(" @@ ")"`
}

type ConditionOperand struct {
	Operand      *Operand      `@@`
	ConditionRHS *ConditionRHS `@@?`
}

type ConditionRHS struct {
	Compare *Compare `  @@`
	Is      *Is      `| "IS" @@`
	Between *Between `| "BETWEEN" @@`
	In      *In      `| "IN" "(" @@ ")"`
	Like    *Like    `| "LIKE" @@`
}

type Compare struct {
	Operator string         `@( "<>" | "<=" | ">=" | "=" | "<" | ">" | "!=" )`
	Operand  *Operand       `(  @@`
	Select   *CompareSelect ` | @@ )`
}

type CompareSelect struct {
	All    bool    `(  @"ALL"`
	Any    bool    ` | @"ANY"`
	Some   bool    ` | @"SOME" )`
	Select *Select `"(" @@ ")"`
}

type Like struct {
	Not     bool     `[ @"NOT" ]`
	Operand *Operand `@@`
}

type Is struct {
	Not          bool     `[ @"NOT" ]`
	Null         bool     `( @"NULL"`
	DistinctFrom *Operand `  | "DISTINCT" "FROM" @@ )`
}

type Between struct {
	Start *Operand `@@`
	End   *Operand `"AND" @@`
}

type In struct {
	Select      *Select       `  @@`
	Expressions []*Expression `| @@ ( "," @@ )*`
}

type Operand struct {
	Summand []*Summand `@@ ( "|" "|" @@ )*`
}

type Summand struct {
	LHS *Factor `@@`
	Op  string  `[ @("+" | "-")`
	RHS *Factor `  @@ ]`
}

type Factor struct {
	LHS *Term  `@@`
	Op  string `( @("*" | "/" | "%")`
	RHS *Term  `  @@ )?`
}

type Term struct {
	Select        *Select     `  @@`
	Value         *Value      `| @@`
	SymbolRef     *SymbolRef  `| @@`
	SubExpression *Expression `| "(" @@ ")"`
}

type SymbolRef struct {
	Symbol     string        `@Ident @( "." Ident )*`
	Parameters []*Expression `( "(" @@ ( "," @@ )* ")" )?`
}

type Value struct {
	Wildcard bool     `(  @"*"`
	Number   *float64 ` | @Number`
	String   *string  ` | @String`
	Boolean  *Boolean ` | @("TRUE" | "FALSE")`
	Null     bool     ` | @"NULL"`
	Array    *Array   ` | @@ )`
}

type Array struct {
	Expressions []*Expression `"(" @@ ( "," @@ )* ")"`
}

var (
	cli struct {
		SQL string `arg:"" required:"" help:"SQL to parse."`
	}

	sqlLexer = lexer.Must(stateful.NewSimple([]stateful.Rule{
		{`Keyword`, `(?i)SELECT|FROM|TOP|DISTINCT|ALL|WHERE|GROUP|BY|HAVING|UNION|MINUS|EXCEPT|INTERSECT|ORDER|LIMIT|OFFSET|TRUE|FALSE|NULL|IS|NOT|ANY|SOME|BETWEEN|AND|OR|LIKE|AS|IN`, nil},
		{`Ident`, `[a-zA-Z_][a-zA-Z0-9_]*`, nil},
		{`Number`, `[-+]?\d*\.?\d+([eE][-+]?\d+)?`, nil},
		{`String`, `'[^']*'|"[^"]*"`, nil},
		{`Operators`, `<>|!=|<=|>=|[-+*/%,.()=<>]`, nil},
		{"whitespace", `\s+`, nil},
	}))
	parser = participle.MustBuild(
		&Select{},
		participle.Lexer(sqlLexer),
		participle.Unquote("String"),
		participle.CaseInsensitive("Keyword"),
		// participle.Elide("Comment"),
		// Need to solve left recursion detection first, if possible.
		// participle.UseLookahead(),
	)
)

func main() {
	ctx := kong.Parse(&cli)
	sql := &Select{}
	err := parser.ParseString("", cli.SQL, sql)
	repr.Println(sql, repr.Indent("  "), repr.OmitEmpty(true))
	ctx.FatalIfErrorf(err)
}
<commit_msg>Update main.go<commit_after>// nolint: govet
package main

import (
	"github.com/alecthomas/kong"

	"github.com/alecthomas/participle/v2"
	"github.com/alecthomas/participle/v2/lexer"
	"github.com/alecthomas/participle/v2/lexer/stateful"

	"github.com/alecthomas/repr"
)

type Boolean bool

func (b *Boolean) Capture(values []string) error {
	*b = values[0] == "TRUE"
	return nil
}

// Select based on http://www.h2database.com/html/grammar.html
type Select struct {
	Top        *Term             `"SELECT" ( "TOP" @@ )?`
	Distinct   bool              `(  @"DISTINCT"`
	All        bool              ` | @"ALL" )?`
	Expression *SelectExpression `@@`
	From       *From             `"FROM" @@`
	Limit      *Expression       `( "LIMIT" @@ )?`
	Offset     *Expression       `( "OFFSET" @@ )?`
	GroupBy    *Expression       `( "GROUP" "BY" @@ )?`
}

type From struct {
	TableExpressions []*TableExpression `@@ ( "," @@ )*`
	Where            *Expression        `( "WHERE" @@ )?`
}

type TableExpression struct {
	Table  string        `( @Ident ( "." @Ident )*`
	Select *Select       `  | "(" @@ ")"`
	Values []*Expression `  | "VALUES" "(" @@ ( "," @@ )* ")")`
	As     string        `( "AS" @Ident )?`
}

type SelectExpression struct {
	All         bool                 `  @"*"`
	Expressions []*AliasedExpression `| @@ ( "," @@ )*`
}

type AliasedExpression struct {
	Expression *Expression `@@`
	As         string      `( "AS" @Ident )?`
}

type Expression struct {
	Or []*OrCondition `@@ ( "OR" @@ )*`
}

type OrCondition struct {
	And []*Condition `@@ ( "AND" @@ )*`
}

type Condition struct {
	Operand *ConditionOperand `  @@`
	Not     *Condition        `| "NOT" @@`
	Exists  *Select           `| "EXISTS" "(" @@ ")"`
}

type ConditionOperand struct {
	Operand      *Operand      `@@`
	ConditionRHS *ConditionRHS `@@?`
}

type ConditionRHS struct {
	Compare *Compare `  @@`
	Is      *Is      `| "IS" @@`
	Between *Between `| "BETWEEN" @@`
	In      *In      `| "IN" "(" @@ ")"`
	Like    *Like    `| "LIKE" @@`
}

type Compare struct {
	Operator string         `@( "<>" | "<=" | ">=" | "=" | "<" | ">" | "!=" )`
	Operand  *Operand       `(  @@`
	Select   *CompareSelect ` | @@ )`
}

type CompareSelect struct {
	All    bool    `(  @"ALL"`
	Any    bool    ` | @"ANY"`
	Some   bool    ` | @"SOME" )`
	Select *Select `"(" @@ ")"`
}

type Like struct {
	Not     bool     `[ @"NOT" ]`
	Operand *Operand `@@`
}

type Is struct {
	Not          bool     `[ @"NOT" ]`
	Null         bool     `( @"NULL"`
	DistinctFrom *Operand `  | "DISTINCT" "FROM" @@ )`
}

type Between struct {
	Start *Operand `@@`
	End   *Operand `"AND" @@`
}

type In struct {
	Select      *Select       `  @@`
	Expressions []*Expression `| @@ ( "," @@ )*`
}

type Operand struct {
	Summand []*Summand `@@ ( "|" "|" @@ )*`
}

type Summand struct {
	LHS *Factor `@@`
	Op  string  `[ @("+" | "-")`
	RHS *Factor `  @@ ]`
}

type Factor struct {
	LHS *Term  `@@`
	Op  string `( @("*" | "/" | "%")`
	RHS *Term  `  @@ )?`
}

type Term struct {
	Select        *Select     `  @@`
	Value         *Value      `| @@`
	SymbolRef     *SymbolRef  `| @@`
	SubExpression *Expression `| "(" @@ ")"`
}

type SymbolRef struct {
	Symbol     string        `@Ident @( "." Ident )*`
	Parameters []*Expression `( "(" @@ ( "," @@ )* ")" )?`
}

type Value struct {
	Wildcard bool     `(  @"*"`
	Number   *float64 ` | @Number`
	String   *string  ` | @String`
	Boolean  *Boolean ` | @("TRUE" | "FALSE")`
	Null     bool     ` | @"NULL"`
	Array    *Array   ` | @@ )`
}

type Array struct {
	Expressions []*Expression `"(" @@ ( "," @@ )* ")"`
}

var (
	cli struct {
		SQL string `arg:"" required:"" help:"SQL to parse."`
	}

	sqlLexer = lexer.Must(stateful.NewSimple([]stateful.Rule{
		{`Keyword`, `(?i)\b(SELECT|FROM|TOP|DISTINCT|ALL|WHERE|GROUP|BY|HAVING|UNION|MINUS|EXCEPT|INTERSECT|ORDER|LIMIT|OFFSET|TRUE|FALSE|NULL|IS|NOT|ANY|SOME|BETWEEN|AND|OR|LIKE|AS|IN)\b`, nil},
		{`Ident`, `[a-zA-Z_][a-zA-Z0-9_]*`, nil},
		{`Number`, `[-+]?\d*\.?\d+([eE][-+]?\d+)?`, nil},
		{`String`, `'[^']*'|"[^"]*"`, nil},
		{`Operators`, `<>|!=|<=|>=|[-+*/%,.()=<>]`, nil},
		{"whitespace", `\s+`, nil},
	}))
	parser = participle.MustBuild(
		&Select{},
		participle.Lexer(sqlLexer),
		participle.Unquote("String"),
		participle.CaseInsensitive("Keyword"),
		// participle.Elide("Comment"),
		// Need to solve left recursion detection first, if possible.
		// participle.UseLookahead(),
	)
)

func main() {
	ctx := kong.Parse(&cli)
	sql := &Select{}
	err := parser.ParseString("", cli.SQL, sql)
	repr.Println(sql, repr.Indent("  "), repr.OmitEmpty(true))
	ctx.FatalIfErrorf(err)
}
<|endoftext|>


<commit_before>package gogadgets

import (
	"errors"
	"fmt"
	"strconv"
	"strings"
	"time"
)

var (
	units = map[string]string{
		"liters":     "volume",
		"gallons":    "volume",
		"liter":      "volume",
		"gallon":     "volume",
		"c":          "temperature",
		"f":          "temperature",
		"celcius":    "temperature",
		"fahrenheit": "temperature",
		"seconds":    "time",
		"minutes":    "time",
		"hours":      "time",
		"second":     "time",
		"minute":     "time",
		"hour":       "time",
	}
)

type Comparitor func(msg *Message) bool

//Each part of a Gadgets system that controls a single
//piece of hardware (for example: a gpio pin) is represented
//by Gadget.  A Gadget must have either an InputDevice or
//an OutputDevice.  Gadget fulfills the GoGaget interface.
type Gadget struct {
	GoGadget
	Location   string       `json:"location"`
	Name       string       `json:"name"`
	Output     OutputDevice `json:"-"`
	Input      InputDevice  `json:"-"`
	Direction  string       `json:"direction"`
	OnCommand  string       `json:"on"`
	OffCommand string       `json:"off"`
	UID        string       `json:"uid"`
	status     bool
	compare    Comparitor
	shutdown   bool
	units      string
	Operator   string
	out        chan<- Message
	devIn      chan Message
	timerIn    chan bool
	timerOut   chan bool
}

//There are 5 types of Input/Output devices build into
//GoGadgets (header, cooler, gpio, thermometer and switch)
//NewGadget reads a GadgetConfig and creates the correct
//type of Gadget.
func NewGadget(config *GadgetConfig) (*Gadget, error) {
	t := config.Pin.Type
	if t == "heater" || t == "cooler" || t == "gpio" {
		return NewOutputGadget(config)
	} else if t == "thermometer" || t == "switch" {
		return NewInputGadget(config)
	}
	err := errors.New(
		fmt.Sprintf(
			"couldn't build a gadget based on config: %s %s",
			config.Location,
			config.Name))
	return nil, err
}

//Input Gadgets read from input devices and report their values (thermometer
//is an example).
func NewInputGadget(config *GadgetConfig) (gadget *Gadget, err error) {
	dev, err := NewInputDevice(&config.Pin)
	if err == nil {
		gadget = &Gadget{
			Location:   config.Location,
			Name:       config.Name,
			Input:      dev,
			Direction:  "input",
			OnCommand:  "n/a",
			OffCommand: "n/a",
			UID:        fmt.Sprintf("%s %s", config.Location, config.Name),
		}
	}
	return gadget, err
}

//Output Gadgets turn devices on and off.
func NewOutputGadget(config *GadgetConfig) (gadget *Gadget, err error) {
	dev, err := NewOutputDevice(&config.Pin)
	if config.OnCommand == "" {
		config.OnCommand = fmt.Sprintf("turn on %s %s", config.Location, config.Name)
	}
	if config.OffCommand == "" {
		config.OffCommand = fmt.Sprintf("turn off %s %s", config.Location, config.Name)
	}
	if err == nil {
		gadget = &Gadget{
			Location:   config.Location,
			Name:       config.Name,
			Direction:  "output",
			OnCommand:  config.OnCommand,
			OffCommand: config.OffCommand,
			Output:     dev,
			Operator:   ">=",
			UID:        fmt.Sprintf("%s %s", config.Location, config.Name),
		}
	} else {
		panic(err)
	}
	return gadget, err
}

//All gadgets respond to Robot Command Language (RCL) messages.  isMyCommand
//reads an RCL message and decides if it was meant for this instance
//of Gadget.
func (g *Gadget) isMyCommand(msg *Message) bool {
	return msg.Type == COMMAND &&
		(strings.Index(msg.Body, g.OnCommand) == 0 ||
			strings.Index(msg.Body, g.OffCommand) == 0 ||
			msg.Body == "update" ||
			msg.Body == "shutdown")
}

//Start is one of the two interface methods of GoGadget.  Start takes
//in in and out chan and is meant to be called as a goroutine.
func (g *Gadget) Start(in <-chan Message, out chan<- Message) {
	g.out = out
	g.timerIn = make(chan bool)
	g.timerOut = make(chan bool)
	if g.Output != nil {
		g.off()
		g.doOutputLoop(in)
	} else if g.Input != nil {
		g.doInputLoop(in)
	}
}

//Once a gadget is started as a goroutine, this loop collects
//all the messages that are sent to this particular Gadget and
//responds accordingly.  This is the loop that is executed if
//this Gadget is an input Gadget
func (g *Gadget) doInputLoop(in <-chan Message) {
	devOut := make(chan Value, 10)
	g.devIn = make(chan Message, 10)
	go g.Input.Start(g.devIn, devOut)
	for !g.shutdown {
		select {
		case msg := <-in:
			g.readMessage(&msg)
		case val := <-devOut:
			g.out <- Message{
				Sender:   g.UID,
				Type:     "update",
				Location: g.Location,
				Name:     g.Name,
				Value:    val,
				Timestamp: time.Now(),
			}
		}
	}
}

func (g *Gadget) doOutputLoop(in <-chan Message) {
	for !g.shutdown {
		select {
		case msg := <-in:
			g.readMessage(&msg)
		case <-g.timerOut:
			g.off()
		}
	}
}

func (g *Gadget) on(val *Value) {
	g.Output.On(val)
	if !g.status {
		g.status = true
		g.sendUpdate(val)
	}
}

func (g *Gadget) off() {
	g.status = false
	g.Output.Off()
	g.compare = nil
	g.sendUpdate(nil)
}

func (g *Gadget) readMessage(msg *Message) {
	if g.devIn != nil {
		g.devIn <- *msg
	}
	if msg.Type == COMMAND && g.isMyCommand(msg) {
		g.readCommand(msg)
	} else if g.status && msg.Type == UPDATE {
		g.readUpdate(msg)
	}
}

func (g *Gadget) readUpdate(msg *Message) {
	if g.status && g.compare != nil && g.compare(msg) {
		g.off()
	} else if g.status && msg.Location == g.Location {
		g.Output.Update(msg)
	}
}

func (g *Gadget) readCommand(msg *Message) {
	if msg.Body == "shutdown" {
		g.shutdown = true
		g.off()
	} else if msg.Body == "update" {
		g.sendUpdate(nil)
	} else if strings.Index(msg.Body, g.OnCommand) == 0 {
		g.readOnCommand(msg)
	} else if strings.Index(msg.Body, g.OffCommand) == 0 {
		g.readOffCommand(msg)
	}
}

func (g *Gadget) readOnCommand(msg *Message) {
	var val *Value
	if len(strings.Trim(msg.Body, " ")) > len(g.OnCommand) {
		val, err := g.readOnArguments(msg.Body)
		if err == nil {
			g.on(val)
		}
	} else {
		g.compare = nil
		g.on(val)
	}
}

func (g *Gadget) readOnArguments(cmd string) (*Value, error) {
	var val *Value
	value, unit, err := ParseCommand(cmd)
	if err != nil {
		return val, errors.New(fmt.Sprintf("could not parse %s", cmd))
	}
	gadget, ok := units[unit]
	if ok {
		if gadget == "time" {
			go g.startTimer(value, unit, g.timerIn, g.timerOut)
		} else if gadget == "volume" || gadget == "temperature" {
			g.setCompare(value, unit, gadget)
			val = &Value{
				Value: value,
				Units: unit,
			}
		}
	}
	return val, nil
}

func (g *Gadget) setCompare(value float64, unit string, gadget string) {
	if g.Operator == "<=" {
		g.compare = func(msg *Message) bool {
			val, ok := msg.Value.Value.(float64)
			return msg.Location == g.Location &&
				ok &&
				msg.Name == gadget &&
				val <= value
		}
	} else if g.Operator == ">=" {
		g.compare = func(msg *Message) bool {
			val, ok := msg.Value.Value.(float64)
			return msg.Location == g.Location &&
				ok &&
				msg.Name == gadget &&
				val >= value
		}
	}
}

func (g *Gadget) startTimer(value float64, unit string, in <-chan bool, out chan<- bool) {
	d := time.Duration(value * float64(time.Second))
	keepGoing := true
	for keepGoing {
		select {
		case <-in:
			keepGoing = false
		case <-time.After(d):
			keepGoing = false
			out <- true
		}
	}
}

func (g *Gadget) readOffCommand(msg *Message) {
	if g.status {
		g.off()
	}
}

func (g *Gadget) GetUID() string {
	if g.UID == "" {
		g.UID = fmt.Sprintf("%s %s", g.Location, g.Name)
	}
	return g.UID
}

func (g *Gadget) sendUpdate(val *Value) {
	var value *Value
	if g.Input != nil {
		value = g.Input.GetValue()
	} else {
		value = &Value{
			Units: g.units,
			Value: g.status,
		}
	}
	msg := Message{
		Sender:      g.UID,
		Type:        UPDATE,
		Location:    g.Location,
		Name:        g.Name,
		Value:       *value,
		TargetValue: val,
		Info: Info{
			Direction: g.Direction,
			On:        g.OnCommand,
			Off:       g.OffCommand,
		},
	}
	g.out <- msg
}

func ParseCommand(cmd string) (float64, string, error) {
	cmd = stripCommand(cmd)
	value, unit, err := splitCommand(cmd)
	var v float64
	if err == nil {
		v, err = strconv.ParseFloat(value, 64)
	}
	return v, unit, err
}

func splitCommand(cmd string) (string, string, error) {
	parts := strings.Split(cmd, " ")
	return parts[0], parts[1], nil
}

func stripCommand(cmd string) string {
	cmd = strings.Trim(cmd, " ")
	i := strings.Index(cmd, " for ")
	if i != -1 {
		return cmd[i+5:]
	}
	i = strings.Index(cmd, " to ")
	if i != -1 {
		return cmd[i+4:]
	}
	return ""
}
<commit_msg>made the time utc<commit_after>package gogadgets

import (
	"errors"
	"fmt"
	"strconv"
	"strings"
	"time"
)

var (
	units = map[string]string{
		"liters":     "volume",
		"gallons":    "volume",
		"liter":      "volume",
		"gallon":     "volume",
		"c":          "temperature",
		"f":          "temperature",
		"celcius":    "temperature",
		"fahrenheit": "temperature",
		"seconds":    "time",
		"minutes":    "time",
		"hours":      "time",
		"second":     "time",
		"minute":     "time",
		"hour":       "time",
	}
)

type Comparitor func(msg *Message) bool

//Each part of a Gadgets system that controls a single
//piece of hardware (for example: a gpio pin) is represented
//by Gadget.  A Gadget must have either an InputDevice or
//an OutputDevice.  Gadget fulfills the GoGaget interface.
type Gadget struct {
	GoGadget
	Location   string       `json:"location"`
	Name       string       `json:"name"`
	Output     OutputDevice `json:"-"`
	Input      InputDevice  `json:"-"`
	Direction  string       `json:"direction"`
	OnCommand  string       `json:"on"`
	OffCommand string       `json:"off"`
	UID        string       `json:"uid"`
	status     bool
	compare    Comparitor
	shutdown   bool
	units      string
	Operator   string
	out        chan<- Message
	devIn      chan Message
	timerIn    chan bool
	timerOut   chan bool
}

//There are 5 types of Input/Output devices build into
//GoGadgets (header, cooler, gpio, thermometer and switch)
//NewGadget reads a GadgetConfig and creates the correct
//type of Gadget.
func NewGadget(config *GadgetConfig) (*Gadget, error) {
	t := config.Pin.Type
	if t == "heater" || t == "cooler" || t == "gpio" {
		return NewOutputGadget(config)
	} else if t == "thermometer" || t == "switch" {
		return NewInputGadget(config)
	}
	err := errors.New(
		fmt.Sprintf(
			"couldn't build a gadget based on config: %s %s",
			config.Location,
			config.Name))
	return nil, err
}

//Input Gadgets read from input devices and report their values (thermometer
//is an example).
func NewInputGadget(config *GadgetConfig) (gadget *Gadget, err error) {
	dev, err := NewInputDevice(&config.Pin)
	if err == nil {
		gadget = &Gadget{
			Location:   config.Location,
			Name:       config.Name,
			Input:      dev,
			Direction:  "input",
			OnCommand:  "n/a",
			OffCommand: "n/a",
			UID:        fmt.Sprintf("%s %s", config.Location, config.Name),
		}
	}
	return gadget, err
}

//Output Gadgets turn devices on and off.
func NewOutputGadget(config *GadgetConfig) (gadget *Gadget, err error) {
	dev, err := NewOutputDevice(&config.Pin)
	if config.OnCommand == "" {
		config.OnCommand = fmt.Sprintf("turn on %s %s", config.Location, config.Name)
	}
	if config.OffCommand == "" {
		config.OffCommand = fmt.Sprintf("turn off %s %s", config.Location, config.Name)
	}
	if err == nil {
		gadget = &Gadget{
			Location:   config.Location,
			Name:       config.Name,
			Direction:  "output",
			OnCommand:  config.OnCommand,
			OffCommand: config.OffCommand,
			Output:     dev,
			Operator:   ">=",
			UID:        fmt.Sprintf("%s %s", config.Location, config.Name),
		}
	} else {
		panic(err)
	}
	return gadget, err
}

//All gadgets respond to Robot Command Language (RCL) messages.  isMyCommand
//reads an RCL message and decides if it was meant for this instance
//of Gadget.
func (g *Gadget) isMyCommand(msg *Message) bool {
	return msg.Type == COMMAND &&
		(strings.Index(msg.Body, g.OnCommand) == 0 ||
			strings.Index(msg.Body, g.OffCommand) == 0 ||
			msg.Body == "update" ||
			msg.Body == "shutdown")
}

//Start is one of the two interface methods of GoGadget.  Start takes
//in in and out chan and is meant to be called as a goroutine.
func (g *Gadget) Start(in <-chan Message, out chan<- Message) {
	g.out = out
	g.timerIn = make(chan bool)
	g.timerOut = make(chan bool)
	if g.Output != nil {
		g.off()
		g.doOutputLoop(in)
	} else if g.Input != nil {
		g.doInputLoop(in)
	}
}

//Once a gadget is started as a goroutine, this loop collects
//all the messages that are sent to this particular Gadget and
//responds accordingly.  This is the loop that is executed if
//this Gadget is an input Gadget
func (g *Gadget) doInputLoop(in <-chan Message) {
	devOut := make(chan Value, 10)
	g.devIn = make(chan Message, 10)
	go g.Input.Start(g.devIn, devOut)
	for !g.shutdown {
		select {
		case msg := <-in:
			g.readMessage(&msg)
		case val := <-devOut:
			g.out <- Message{
				Sender:   g.UID,
				Type:     "update",
				Location: g.Location,
				Name:     g.Name,
				Value:    val,
				Timestamp: time.Now().UTC(),
			}
		}
	}
}

func (g *Gadget) doOutputLoop(in <-chan Message) {
	for !g.shutdown {
		select {
		case msg := <-in:
			g.readMessage(&msg)
		case <-g.timerOut:
			g.off()
		}
	}
}

func (g *Gadget) on(val *Value) {
	g.Output.On(val)
	if !g.status {
		g.status = true
		g.sendUpdate(val)
	}
}

func (g *Gadget) off() {
	g.status = false
	g.Output.Off()
	g.compare = nil
	g.sendUpdate(nil)
}

func (g *Gadget) readMessage(msg *Message) {
	if g.devIn != nil {
		g.devIn <- *msg
	}
	if msg.Type == COMMAND && g.isMyCommand(msg) {
		g.readCommand(msg)
	} else if g.status && msg.Type == UPDATE {
		g.readUpdate(msg)
	}
}

func (g *Gadget) readUpdate(msg *Message) {
	if g.status && g.compare != nil && g.compare(msg) {
		g.off()
	} else if g.status && msg.Location == g.Location {
		g.Output.Update(msg)
	}
}

func (g *Gadget) readCommand(msg *Message) {
	if msg.Body == "shutdown" {
		g.shutdown = true
		g.off()
	} else if msg.Body == "update" {
		g.sendUpdate(nil)
	} else if strings.Index(msg.Body, g.OnCommand) == 0 {
		g.readOnCommand(msg)
	} else if strings.Index(msg.Body, g.OffCommand) == 0 {
		g.readOffCommand(msg)
	}
}

func (g *Gadget) readOnCommand(msg *Message) {
	var val *Value
	if len(strings.Trim(msg.Body, " ")) > len(g.OnCommand) {
		val, err := g.readOnArguments(msg.Body)
		if err == nil {
			g.on(val)
		}
	} else {
		g.compare = nil
		g.on(val)
	}
}

func (g *Gadget) readOnArguments(cmd string) (*Value, error) {
	var val *Value
	value, unit, err := ParseCommand(cmd)
	if err != nil {
		return val, errors.New(fmt.Sprintf("could not parse %s", cmd))
	}
	gadget, ok := units[unit]
	if ok {
		if gadget == "time" {
			go g.startTimer(value, unit, g.timerIn, g.timerOut)
		} else if gadget == "volume" || gadget == "temperature" {
			g.setCompare(value, unit, gadget)
			val = &Value{
				Value: value,
				Units: unit,
			}
		}
	}
	return val, nil
}

func (g *Gadget) setCompare(value float64, unit string, gadget string) {
	if g.Operator == "<=" {
		g.compare = func(msg *Message) bool {
			val, ok := msg.Value.Value.(float64)
			return msg.Location == g.Location &&
				ok &&
				msg.Name == gadget &&
				val <= value
		}
	} else if g.Operator == ">=" {
		g.compare = func(msg *Message) bool {
			val, ok := msg.Value.Value.(float64)
			return msg.Location == g.Location &&
				ok &&
				msg.Name == gadget &&
				val >= value
		}
	}
}

func (g *Gadget) startTimer(value float64, unit string, in <-chan bool, out chan<- bool) {
	d := time.Duration(value * float64(time.Second))
	keepGoing := true
	for keepGoing {
		select {
		case <-in:
			keepGoing = false
		case <-time.After(d):
			keepGoing = false
			out <- true
		}
	}
}

func (g *Gadget) readOffCommand(msg *Message) {
	if g.status {
		g.off()
	}
}

func (g *Gadget) GetUID() string {
	if g.UID == "" {
		g.UID = fmt.Sprintf("%s %s", g.Location, g.Name)
	}
	return g.UID
}

func (g *Gadget) sendUpdate(val *Value) {
	var value *Value
	if g.Input != nil {
		value = g.Input.GetValue()
	} else {
		value = &Value{
			Units: g.units,
			Value: g.status,
		}
	}
	msg := Message{
		Sender:      g.UID,
		Type:        UPDATE,
		Location:    g.Location,
		Name:        g.Name,
		Value:       *value,
		TargetValue: val,
		Info: Info{
			Direction: g.Direction,
			On:        g.OnCommand,
			Off:       g.OffCommand,
		},
	}
	g.out <- msg
}

func ParseCommand(cmd string) (float64, string, error) {
	cmd = stripCommand(cmd)
	value, unit, err := splitCommand(cmd)
	var v float64
	if err == nil {
		v, err = strconv.ParseFloat(value, 64)
	}
	return v, unit, err
}

func splitCommand(cmd string) (string, string, error) {
	parts := strings.Split(cmd, " ")
	return parts[0], parts[1], nil
}

func stripCommand(cmd string) string {
	cmd = strings.Trim(cmd, " ")
	i := strings.Index(cmd, " for ")
	if i != -1 {
		return cmd[i+5:]
	}
	i = strings.Index(cmd, " to ")
	if i != -1 {
		return cmd[i+4:]
	}
	return ""
}
<|endoftext|>


<commit_before>package deprecated

type Deprecation struct {
	DeprecatedSince           int
	AlternativeAvailableSince int
}

var Stdlib = map[string]Deprecation{
	"image/jpeg.Reader": {4, 0},
	// FIXME(dh): AllowBinary isn't being detected as deprecated
	// because the comment has a newline right after "Deprecated:"
	"go/build.AllowBinary":                      {7, 7},
	"(archive/zip.FileHeader).CompressedSize":   {1, 1},
	"(archive/zip.FileHeader).UncompressedSize": {1, 1},
	"(go/doc.Package).Bugs":                     {1, 1},
	"os.SEEK_SET":                               {7, 7},
	"os.SEEK_CUR":                               {7, 7},
	"os.SEEK_END":                               {7, 7},
	"(net.Dialer).Cancel":                       {7, 7},
	"runtime.CPUProfile":                        {9, 0},
	"compress/flate.ReadError":                  {6, 6},
	"compress/flate.WriteError":                 {6, 6},
	"path/filepath.HasPrefix":                   {0, 0},
	"(net/http.Transport).Dial":                 {7, 7},
	"(*net/http.Transport).CancelRequest":       {6, 5},
	"net/http.ErrWriteAfterFlush":               {7, 0},
	"net/http.ErrHeaderTooLong":                 {8, 0},
	"net/http.ErrShortBody":                     {8, 0},
	"net/http.ErrMissingContentLength":          {8, 0},
	"net/http/httputil.ErrPersistEOF":           {0, 0},
	"net/http/httputil.ErrClosed":               {0, 0},
	"net/http/httputil.ErrPipeline":             {0, 0},
	"net/http/httputil.ServerConn":              {0, 0},
	"net/http/httputil.NewServerConn":           {0, 0},
	"net/http/httputil.ClientConn":              {0, 0},
	"net/http/httputil.NewClientConn":           {0, 0},
	"net/http/httputil.NewProxyClientConn":      {0, 0},
	"(net/http.Request).Cancel":                 {7, 7},
	"(text/template/parse.PipeNode).Line":       {1, 1},
	"(text/template/parse.ActionNode).Line":     {1, 1},
	"(text/template/parse.BranchNode).Line":     {1, 1},
	"(text/template/parse.TemplateNode).Line":   {1, 1},
	"database/sql/driver.ColumnConverter":       {9, 9},
	"database/sql/driver.Execer":                {8, 8},
	"database/sql/driver.Queryer":               {8, 8},
	"(database/sql/driver.Conn).Begin":          {8, 8},
	"(database/sql/driver.Stmt).Exec":           {8, 8},
	"(database/sql/driver.Stmt).Query":          {8, 8},
	"syscall.StringByteSlice":                   {1, 1},
	"syscall.StringBytePtr":                     {1, 1},
	"syscall.StringSlicePtr":                    {1, 1},
	"syscall.StringToUTF16":                     {1, 1},
	"syscall.StringToUTF16Ptr":                  {1, 1},
}
<commit_msg>deprecated: update list<commit_after>package deprecated

type Deprecation struct {
	DeprecatedSince           int
	AlternativeAvailableSince int
}

var Stdlib = map[string]Deprecation{
	"image/jpeg.Reader": {4, 0},
	// FIXME(dh): AllowBinary isn't being detected as deprecated
	// because the comment has a newline right after "Deprecated:"
	"go/build.AllowBinary":                        {7, 7},
	"(archive/zip.FileHeader).CompressedSize":     {1, 1},
	"(archive/zip.FileHeader).UncompressedSize":   {1, 1},
	"(archive/zip.FileHeader).ModifiedTime":       {10, 10},
	"(archive/zip.FileHeader).ModifiedDate":       {10, 10},
	"(*archive/zip.FileHeader).ModTime":           {10, 10},
	"(*archive/zip.FileHeader).SetModTime":        {10, 10},
	"(go/doc.Package).Bugs":                       {1, 1},
	"os.SEEK_SET":                                 {7, 7},
	"os.SEEK_CUR":                                 {7, 7},
	"os.SEEK_END":                                 {7, 7},
	"(net.Dialer).Cancel":                         {7, 7},
	"runtime.CPUProfile":                          {9, 0},
	"compress/flate.ReadError":                    {6, 6},
	"compress/flate.WriteError":                   {6, 6},
	"path/filepath.HasPrefix":                     {0, 0},
	"(net/http.Transport).Dial":                   {7, 7},
	"(*net/http.Transport).CancelRequest":         {6, 5},
	"net/http.ErrWriteAfterFlush":                 {7, 0},
	"net/http.ErrHeaderTooLong":                   {8, 0},
	"net/http.ErrShortBody":                       {8, 0},
	"net/http.ErrMissingContentLength":            {8, 0},
	"net/http/httputil.ErrPersistEOF":             {0, 0},
	"net/http/httputil.ErrClosed":                 {0, 0},
	"net/http/httputil.ErrPipeline":               {0, 0},
	"net/http/httputil.ServerConn":                {0, 0},
	"net/http/httputil.NewServerConn":             {0, 0},
	"net/http/httputil.ClientConn":                {0, 0},
	"net/http/httputil.NewClientConn":             {0, 0},
	"net/http/httputil.NewProxyClientConn":        {0, 0},
	"(net/http.Request).Cancel":                   {7, 7},
	"(text/template/parse.PipeNode).Line":         {1, 1},
	"(text/template/parse.ActionNode).Line":       {1, 1},
	"(text/template/parse.BranchNode).Line":       {1, 1},
	"(text/template/parse.TemplateNode).Line":     {1, 1},
	"database/sql/driver.ColumnConverter":         {9, 9},
	"database/sql/driver.Execer":                  {8, 8},
	"database/sql/driver.Queryer":                 {8, 8},
	"(database/sql/driver.Conn).Begin":            {8, 8},
	"(database/sql/driver.Stmt).Exec":             {8, 8},
	"(database/sql/driver.Stmt).Query":            {8, 8},
	"syscall.StringByteSlice":                     {1, 1},
	"syscall.StringBytePtr":                       {1, 1},
	"syscall.StringSlicePtr":                      {1, 1},
	"syscall.StringToUTF16":                       {1, 1},
	"syscall.StringToUTF16Ptr":                    {1, 1},
	"(*regexp.Regexp).Copy":                       {12, 12},
	"(archive/tar.Header).Xattrs":                 {10, 10},
	"archive/tar.TypeRegA":                        {11, 1},
	"go/types.NewInterface":                       {11, 11},
	"(*go/types.Interface).Embedded":              {11, 11},
	"go/importer.For":                             {12, 12},
	"encoding/json.InvalidUTF8Error":              {2, 2},
	"encoding/json.UnmarshalFieldError":           {2, 2},
	"encoding/csv.ErrTrailingComma":               {2, 2},
	"(encoding/csv.Reader).TrailingComma":         {2, 2},
	"(net.Dialer).DualStack":                      {12, 12},
	"net/http.ErrUnexpectedTrailer":               {12, 12},
	"net/http.CloseNotifier":                      {11, 7},
	"net/http.ProtocolError":                      {8, 8},
	"(crypto/x509.CertificateRequest).Attributes": {5, 3},
	// This function has no alternative, but also no purpose.
	"(*crypto/rc4.Cipher).Reset":                     {12, 0},
	"(net/http/httptest.ResponseRecorder).HeaderMap": {11, 7},

	// All of these have been deprecated in favour of external libraries
	"syscall.AttachLsf":             {7, 0},
	"syscall.DetachLsf":             {7, 0},
	"syscall.LsfSocket":             {7, 0},
	"syscall.SetLsfPromisc":         {7, 0},
	"syscall.LsfJump":               {7, 0},
	"syscall.LsfStmt":               {7, 0},
	"syscall.BpfStmt":               {7, 0},
	"syscall.BpfJump":               {7, 0},
	"syscall.BpfBuflen":             {7, 0},
	"syscall.SetBpfBuflen":          {7, 0},
	"syscall.BpfDatalink":           {7, 0},
	"syscall.SetBpfDatalink":        {7, 0},
	"syscall.SetBpfPromisc":         {7, 0},
	"syscall.FlushBpf":              {7, 0},
	"syscall.BpfInterface":          {7, 0},
	"syscall.SetBpfInterface":       {7, 0},
	"syscall.BpfTimeout":            {7, 0},
	"syscall.SetBpfTimeout":         {7, 0},
	"syscall.BpfStats":              {7, 0},
	"syscall.SetBpfImmediate":       {7, 0},
	"syscall.SetBpf":                {7, 0},
	"syscall.CheckBpfVersion":       {7, 0},
	"syscall.BpfHeadercmpl":         {7, 0},
	"syscall.SetBpfHeadercmpl":      {7, 0},
	"syscall.RouteRIB":              {8, 0},
	"syscall.RoutingMessage":        {8, 0},
	"syscall.RouteMessage":          {8, 0},
	"syscall.InterfaceMessage":      {8, 0},
	"syscall.InterfaceAddrMessage":  {8, 0},
	"syscall.ParseRoutingMessage":   {8, 0},
	"syscall.ParseRoutingSockaddr":  {8, 0},
	"InterfaceAnnounceMessage":      {7, 0},
	"InterfaceMulticastAddrMessage": {7, 0},
	"syscall.FormatMessage":         {5, 0},
}
<|endoftext|>