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

package kubernetes

import (
	"bufio"
	"context"
	"fmt"
	"sort"
	"strings"
	"sync"
	"time"

	uuid "github.com/nu7hatch/gouuid"
	"github.com/pkg/errors"
	"github.com/tsuru/tsuru/provision"
	"github.com/tsuru/tsuru/set"
	appTypes "github.com/tsuru/tsuru/types/app"
	apiv1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/labels"
	knet "k8s.io/apimachinery/pkg/util/net"
)

const (
	logLineTimeSeparator = " "
	logWatchBufferSize   = 1000
)

var (
	watchTimeout = time.Hour
)

func (p *kubernetesProvisioner) ListLogs(app appTypes.App, args appTypes.ListLogArgs) ([]appTypes.Applog, error) {
	clusterClient, err := clusterForPool(app.GetPool())
	if err != nil {
		return nil, err
	}
	if !clusterClient.LogsFromAPIServerEnabled() {
		return nil, provision.ErrLogsUnavailable
	}
	clusterController, err := getClusterController(p, clusterClient)
	if err != nil {
		return nil, err
	}

	ns, err := clusterClient.AppNamespace(app)
	if err != nil {
		return nil, err
	}

	podInformer, err := clusterController.getPodInformer()
	if err != nil {
		return nil, err
	}

	pods, err := podInformer.Lister().Pods(ns).List(listPodsSelectorForLog(args))
	if err != nil {
		return nil, err
	}
	if len(args.Units) > 0 {
		pods = filterPods(pods, args.Units)
	}
	return listLogsFromPods(clusterClient, ns, pods, args)
}

func (p *kubernetesProvisioner) WatchLogs(app appTypes.App, args appTypes.ListLogArgs) (appTypes.LogWatcher, error) {
	clusterClient, err := clusterForPool(app.GetPool())
	if err != nil {
		return nil, err
	}
	if !clusterClient.LogsFromAPIServerEnabled() {
		return nil, provision.ErrLogsUnavailable
	}
	clusterClient.SetTimeout(watchTimeout)

	clusterController, err := getClusterController(p, clusterClient)
	if err != nil {
		return nil, err
	}

	ns, err := clusterClient.AppNamespace(app)
	if err != nil {
		return nil, err
	}

	podInformer, err := clusterController.getPodInformer()
	if err != nil {
		return nil, err
	}

	selector := listPodsSelectorForLog(args)
	pods, err := podInformer.Lister().Pods(ns).List(selector)
	if err != nil {
		return nil, err
	}
	if len(args.Units) > 0 {
		pods = filterPods(pods, args.Units)
	}
	uuidV4, err := uuid.NewV4()
	if err != nil {
		return nil, errors.WithMessage(err, "failed to generate uuid v4")
	}
	ctx, done := context.WithCancel(context.Background())
	watcher := &k8sLogsWatcher{
		id:           uuidV4.String(),
		logArgs:      args,
		ctx:          ctx,
		ch:           make(chan appTypes.Applog, logWatchBufferSize),
		ns:           ns,
		done:         done,
		watchingPods: map[string]bool{},

		clusterClient:     clusterClient,
		clusterController: clusterController,
	}
	for _, pod := range pods {
		if pod.Status.Phase == apiv1.PodPending {
			continue
		}
		watcher.watchingPods[pod.ObjectMeta.Name] = true
		watcher.wg.Add(1)
		go watcher.watchPod(pod, false)
	}

	clusterController.addPodListener(args.AppName, watcher.id, watcher)

	return watcher, nil
}

func listLogsFromPods(clusterClient *ClusterClient, ns string, pods []*apiv1.Pod, args appTypes.ListLogArgs) ([]appTypes.Applog, error) {
	var wg sync.WaitGroup
	wg.Add(len(pods))

	errs := make([]error, len(pods))
	logs := make([][]appTypes.Applog, len(pods))
	tailLimit := tailLines(args.Limit)
	if args.Limit == 0 {
		tailLimit = tailLines(100)
	}

	for index, pod := range pods {
		go func(index int, pod *apiv1.Pod) {
			defer wg.Done()

			request := clusterClient.CoreV1().Pods(ns).GetLogs(pod.ObjectMeta.Name, &apiv1.PodLogOptions{
				TailLines:  tailLimit,
				Timestamps: true,
			})
			stream, err := request.Stream()
			if err != nil {
				errs[index] = err
				return
			}

			appName := pod.ObjectMeta.Labels["tsuru.io/app-name"]
			appProcess := pod.ObjectMeta.Labels["tsuru.io/app-process"]

			scanner := bufio.NewScanner(stream)
			tsuruLogs := make([]appTypes.Applog, 0)
			for scanner.Scan() {
				line := scanner.Text()

				if len(line) == 0 {
					continue
				}
				tsuruLog := parsek8sLogLine(line)
				tsuruLog.Unit = pod.ObjectMeta.Name
				tsuruLog.AppName = appName
				tsuruLog.Source = appProcess
				tsuruLogs = append(tsuruLogs, tsuruLog)
			}

			if err := scanner.Err(); err != nil && !knet.IsProbableEOF(err) {
				errs[index] = err
			}

			logs[index] = tsuruLogs
		}(index, pod)
	}

	wg.Wait()

	unifiedLog := []appTypes.Applog{}
	for _, podLogs := range logs {
		unifiedLog = append(unifiedLog, podLogs...)
	}

	sort.Slice(unifiedLog, func(i, j int) bool { return unifiedLog[i].Date.Before(unifiedLog[j].Date) })

	for index, err := range errs {
		if err == nil {
			continue
		}

		pod := pods[index]
		appName := pod.ObjectMeta.Labels["tsuru.io/app-name"]

		unifiedLog = append(unifiedLog, errToLog(pod.ObjectMeta.Name, appName, err))
	}

	return unifiedLog, nil
}

func listPodsSelectorForLog(args appTypes.ListLogArgs) labels.Selector {
	m := map[string]string{
		"tsuru.io/is-build":  "false",
		"tsuru.io/is-deploy": "false",
		"tsuru.io/app-name":  args.AppName,
	}
	if args.Source != "" {
		m["tsuru.io/app-process"] = args.Source
	}
	return labels.SelectorFromSet(labels.Set(m))
}

func parsek8sLogLine(line string) (appLog appTypes.Applog) {
	parts := strings.SplitN(line, logLineTimeSeparator, 2)

	if len(parts) < 2 {
		appLog.Message = string(line)
		return
	}
	appLog.Date, _ = parseRFC3339(parts[0])
	appLog.Message = parts[1]

	return
}

func parseRFC3339(s string) (time.Time, error) {
	if t, timeErr := time.Parse(time.RFC3339Nano, s); timeErr == nil {
		return t, nil
	}
	return time.Parse(time.RFC3339, s)
}

func tailLines(i int) *int64 {
	b := int64(i)
	return &b
}

func errToLog(podName, appName string, err error) appTypes.Applog {
	return appTypes.Applog{
		Date:    time.Now().UTC(),
		Message: fmt.Sprintf("Could not get logs from unit: %s, error: %s", podName, err.Error()),
		Unit:    "apiserver",
		AppName: appName,
		Source:  "kubernetes",
	}
}

func infoToLog(appName string, message string) appTypes.Applog {
	return appTypes.Applog{
		Date:    time.Now().UTC(),
		Message: message,
		Unit:    "apiserver",
		AppName: appName,
		Source:  "kubernetes",
	}
}

type k8sLogsWatcher struct {
	id   string
	ctx  context.Context
	ch   chan appTypes.Applog
	ns   string
	wg   sync.WaitGroup
	once sync.Once
	done context.CancelFunc

	logArgs           appTypes.ListLogArgs
	clusterClient     *ClusterClient
	clusterController *clusterController
	watchingPods      map[string]bool
}

func (k *k8sLogsWatcher) watchPod(pod *apiv1.Pod, notify bool) {

	defer k.wg.Done()
	appName := pod.ObjectMeta.Labels["tsuru.io/app-name"]
	appProcess := pod.ObjectMeta.Labels["tsuru.io/app-process"]

	if notify {
		k.ch <- infoToLog(appName, "Starting to watch new unit: "+pod.ObjectMeta.Name)
	}

	var tailLines int64
	request := k.clusterClient.CoreV1().Pods(k.ns).GetLogs(pod.ObjectMeta.Name, &apiv1.PodLogOptions{
		Follow:     true,
		TailLines:  &tailLines,
		Timestamps: true,
	})
	stream, err := request.Stream()
	if err != nil {
		k.ch <- errToLog(pod.ObjectMeta.Name, appName, err)
		return
	}

	go func() {
		// TODO: after update k8s library we can put context inside the request
		<-k.ctx.Done()
		stream.Close()
	}()

	scanner := bufio.NewScanner(stream)

	for scanner.Scan() {
		line := scanner.Text()
		tsuruLog := parsek8sLogLine(line)
		tsuruLog.Unit = pod.ObjectMeta.Name
		tsuruLog.AppName = appName
		tsuruLog.Source = appProcess

		k.ch <- tsuruLog
	}

	if err := scanner.Err(); err != nil && !knet.IsProbableEOF(err) {
		k.ch <- errToLog(pod.ObjectMeta.Name, appName, err)
	}
}

func (k *k8sLogsWatcher) Chan() <-chan appTypes.Applog {
	return k.ch
}

func (k *k8sLogsWatcher) Close() {
	k.once.Do(func() {
		k.clusterController.removePodListener(k.logArgs.AppName, k.id)
		k.done()
		k.wg.Wait()
		close(k.ch)
	})
}

func (k *k8sLogsWatcher) OnPodEvent(pod *apiv1.Pod) {
	_, alreadyWatching := k.watchingPods[pod.ObjectMeta.Name]
	podMatches := matchPod(pod, k.logArgs)
	if !alreadyWatching && podMatches && pod.Status.Phase != apiv1.PodPending {
		k.watchingPods[pod.ObjectMeta.Name] = true
		k.wg.Add(1)
		go k.watchPod(pod, true)
	}
}

func filterPods(pods []*apiv1.Pod, names []string) []*apiv1.Pod {
	nameSet := set.FromSlice(names)
	result := []*apiv1.Pod{}
	for _, pod := range pods {
		if nameSet.Includes(pod.ObjectMeta.Name) {
			result = append(result, pod)
		}
	}

	return result
}

func matchPod(pod *apiv1.Pod, args appTypes.ListLogArgs) bool {
	if pod.ObjectMeta.Labels["tsuru.io/is-build"] == "true" {
		return false
	}
	if pod.ObjectMeta.Labels["tsuru.io/is-deploy"] == "true" {
		return false
	}
	if args.Source != "" && pod.ObjectMeta.Labels["tsuru.io/app-process"] != args.Source {
		return false
	}

	if len(args.Units) > 0 {
		nameSet := set.FromSlice(args.Units)
		if !nameSet.Includes(pod.ObjectMeta.Name) {
			return false
		}
	}

	return true
}
<commit_msg>provision/kubernetes/logs: shun that startup logs of recently new pods be forgotten<commit_after>// Copyright 2020 tsuru authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package kubernetes

import (
	"bufio"
	"context"
	"fmt"
	"sort"
	"strings"
	"sync"
	"time"

	uuid "github.com/nu7hatch/gouuid"
	"github.com/pkg/errors"
	"github.com/tsuru/tsuru/provision"
	"github.com/tsuru/tsuru/set"
	appTypes "github.com/tsuru/tsuru/types/app"
	apiv1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/labels"
	knet "k8s.io/apimachinery/pkg/util/net"
)

const (
	logLineTimeSeparator = " "
	logWatchBufferSize   = 1000
)

var (
	watchTimeout = time.Hour
)

func (p *kubernetesProvisioner) ListLogs(app appTypes.App, args appTypes.ListLogArgs) ([]appTypes.Applog, error) {
	clusterClient, err := clusterForPool(app.GetPool())
	if err != nil {
		return nil, err
	}
	if !clusterClient.LogsFromAPIServerEnabled() {
		return nil, provision.ErrLogsUnavailable
	}
	clusterController, err := getClusterController(p, clusterClient)
	if err != nil {
		return nil, err
	}

	ns, err := clusterClient.AppNamespace(app)
	if err != nil {
		return nil, err
	}

	podInformer, err := clusterController.getPodInformer()
	if err != nil {
		return nil, err
	}

	pods, err := podInformer.Lister().Pods(ns).List(listPodsSelectorForLog(args))
	if err != nil {
		return nil, err
	}
	if len(args.Units) > 0 {
		pods = filterPods(pods, args.Units)
	}
	return listLogsFromPods(clusterClient, ns, pods, args)
}

func (p *kubernetesProvisioner) WatchLogs(app appTypes.App, args appTypes.ListLogArgs) (appTypes.LogWatcher, error) {
	clusterClient, err := clusterForPool(app.GetPool())
	if err != nil {
		return nil, err
	}
	if !clusterClient.LogsFromAPIServerEnabled() {
		return nil, provision.ErrLogsUnavailable
	}
	clusterClient.SetTimeout(watchTimeout)

	clusterController, err := getClusterController(p, clusterClient)
	if err != nil {
		return nil, err
	}

	ns, err := clusterClient.AppNamespace(app)
	if err != nil {
		return nil, err
	}

	podInformer, err := clusterController.getPodInformer()
	if err != nil {
		return nil, err
	}

	selector := listPodsSelectorForLog(args)
	pods, err := podInformer.Lister().Pods(ns).List(selector)
	if err != nil {
		return nil, err
	}
	if len(args.Units) > 0 {
		pods = filterPods(pods, args.Units)
	}
	uuidV4, err := uuid.NewV4()
	if err != nil {
		return nil, errors.WithMessage(err, "failed to generate uuid v4")
	}
	ctx, done := context.WithCancel(context.Background())
	watcher := &k8sLogsWatcher{
		id:           uuidV4.String(),
		logArgs:      args,
		ctx:          ctx,
		ch:           make(chan appTypes.Applog, logWatchBufferSize),
		ns:           ns,
		done:         done,
		watchingPods: map[string]bool{},

		clusterClient:     clusterClient,
		clusterController: clusterController,
	}
	for _, pod := range pods {
		if pod.Status.Phase == apiv1.PodPending {
			continue
		}
		watcher.watchingPods[pod.ObjectMeta.Name] = true
		watcher.wg.Add(1)
		go watcher.watchPod(pod, false)
	}

	clusterController.addPodListener(args.AppName, watcher.id, watcher)

	return watcher, nil
}

func listLogsFromPods(clusterClient *ClusterClient, ns string, pods []*apiv1.Pod, args appTypes.ListLogArgs) ([]appTypes.Applog, error) {
	var wg sync.WaitGroup
	wg.Add(len(pods))

	errs := make([]error, len(pods))
	logs := make([][]appTypes.Applog, len(pods))
	tailLimit := tailLines(args.Limit)
	if args.Limit == 0 {
		tailLimit = tailLines(100)
	}

	for index, pod := range pods {
		go func(index int, pod *apiv1.Pod) {
			defer wg.Done()

			request := clusterClient.CoreV1().Pods(ns).GetLogs(pod.ObjectMeta.Name, &apiv1.PodLogOptions{
				TailLines:  tailLimit,
				Timestamps: true,
			})
			stream, err := request.Stream()
			if err != nil {
				errs[index] = err
				return
			}

			appName := pod.ObjectMeta.Labels["tsuru.io/app-name"]
			appProcess := pod.ObjectMeta.Labels["tsuru.io/app-process"]

			scanner := bufio.NewScanner(stream)
			tsuruLogs := make([]appTypes.Applog, 0)
			for scanner.Scan() {
				line := scanner.Text()

				if len(line) == 0 {
					continue
				}
				tsuruLog := parsek8sLogLine(line)
				tsuruLog.Unit = pod.ObjectMeta.Name
				tsuruLog.AppName = appName
				tsuruLog.Source = appProcess
				tsuruLogs = append(tsuruLogs, tsuruLog)
			}

			if err := scanner.Err(); err != nil && !knet.IsProbableEOF(err) {
				errs[index] = err
			}

			logs[index] = tsuruLogs
		}(index, pod)
	}

	wg.Wait()

	unifiedLog := []appTypes.Applog{}
	for _, podLogs := range logs {
		unifiedLog = append(unifiedLog, podLogs...)
	}

	sort.Slice(unifiedLog, func(i, j int) bool { return unifiedLog[i].Date.Before(unifiedLog[j].Date) })

	for index, err := range errs {
		if err == nil {
			continue
		}

		pod := pods[index]
		appName := pod.ObjectMeta.Labels["tsuru.io/app-name"]

		unifiedLog = append(unifiedLog, errToLog(pod.ObjectMeta.Name, appName, err))
	}

	return unifiedLog, nil
}

func listPodsSelectorForLog(args appTypes.ListLogArgs) labels.Selector {
	m := map[string]string{
		"tsuru.io/is-build":  "false",
		"tsuru.io/is-deploy": "false",
		"tsuru.io/app-name":  args.AppName,
	}
	if args.Source != "" {
		m["tsuru.io/app-process"] = args.Source
	}
	return labels.SelectorFromSet(labels.Set(m))
}

func parsek8sLogLine(line string) (appLog appTypes.Applog) {
	parts := strings.SplitN(line, logLineTimeSeparator, 2)

	if len(parts) < 2 {
		appLog.Message = string(line)
		return
	}
	appLog.Date, _ = parseRFC3339(parts[0])
	appLog.Message = parts[1]

	return
}

func parseRFC3339(s string) (time.Time, error) {
	if t, timeErr := time.Parse(time.RFC3339Nano, s); timeErr == nil {
		return t, nil
	}
	return time.Parse(time.RFC3339, s)
}

func tailLines(i int) *int64 {
	b := int64(i)
	return &b
}

func errToLog(podName, appName string, err error) appTypes.Applog {
	return appTypes.Applog{
		Date:    time.Now().UTC(),
		Message: fmt.Sprintf("Could not get logs from unit: %s, error: %s", podName, err.Error()),
		Unit:    "apiserver",
		AppName: appName,
		Source:  "kubernetes",
	}
}

func infoToLog(appName string, message string) appTypes.Applog {
	return appTypes.Applog{
		Date:    time.Now().UTC(),
		Message: message,
		Unit:    "apiserver",
		AppName: appName,
		Source:  "kubernetes",
	}
}

type k8sLogsWatcher struct {
	id   string
	ctx  context.Context
	ch   chan appTypes.Applog
	ns   string
	wg   sync.WaitGroup
	once sync.Once
	done context.CancelFunc

	logArgs           appTypes.ListLogArgs
	clusterClient     *ClusterClient
	clusterController *clusterController
	watchingPods      map[string]bool
}

func (k *k8sLogsWatcher) watchPod(pod *apiv1.Pod, addedLater bool) {

	defer k.wg.Done()
	appName := pod.ObjectMeta.Labels["tsuru.io/app-name"]
	appProcess := pod.ObjectMeta.Labels["tsuru.io/app-process"]
	var tailLines int64

	if addedLater {
		k.ch <- infoToLog(appName, "Starting to watch new unit: "+pod.ObjectMeta.Name)
		tailLines = int64(k.logArgs.Limit) // shun that startup logs be forgotten
	}

	request := k.clusterClient.CoreV1().Pods(k.ns).GetLogs(pod.ObjectMeta.Name, &apiv1.PodLogOptions{
		Follow:     true,
		TailLines:  &tailLines,
		Timestamps: true,
	})
	stream, err := request.Stream()
	if err != nil {
		k.ch <- errToLog(pod.ObjectMeta.Name, appName, err)
		return
	}

	go func() {
		// TODO: after update k8s library we can put context inside the request
		<-k.ctx.Done()
		stream.Close()
	}()

	scanner := bufio.NewScanner(stream)

	for scanner.Scan() {
		line := scanner.Text()
		tsuruLog := parsek8sLogLine(line)
		tsuruLog.Unit = pod.ObjectMeta.Name
		tsuruLog.AppName = appName
		tsuruLog.Source = appProcess

		k.ch <- tsuruLog
	}

	if err := scanner.Err(); err != nil && !knet.IsProbableEOF(err) {
		k.ch <- errToLog(pod.ObjectMeta.Name, appName, err)
	}
}

func (k *k8sLogsWatcher) Chan() <-chan appTypes.Applog {
	return k.ch
}

func (k *k8sLogsWatcher) Close() {
	k.once.Do(func() {
		k.clusterController.removePodListener(k.logArgs.AppName, k.id)
		k.done()
		k.wg.Wait()
		close(k.ch)
	})
}

func (k *k8sLogsWatcher) OnPodEvent(pod *apiv1.Pod) {
	_, alreadyWatching := k.watchingPods[pod.ObjectMeta.Name]
	podMatches := matchPod(pod, k.logArgs)
	if !alreadyWatching && podMatches && pod.Status.Phase != apiv1.PodPending {
		k.watchingPods[pod.ObjectMeta.Name] = true
		k.wg.Add(1)
		go k.watchPod(pod, true)
	}
}

func filterPods(pods []*apiv1.Pod, names []string) []*apiv1.Pod {
	nameSet := set.FromSlice(names)
	result := []*apiv1.Pod{}
	for _, pod := range pods {
		if nameSet.Includes(pod.ObjectMeta.Name) {
			result = append(result, pod)
		}
	}

	return result
}

func matchPod(pod *apiv1.Pod, args appTypes.ListLogArgs) bool {
	if pod.ObjectMeta.Labels["tsuru.io/is-build"] == "true" {
		return false
	}
	if pod.ObjectMeta.Labels["tsuru.io/is-deploy"] == "true" {
		return false
	}
	if args.Source != "" && pod.ObjectMeta.Labels["tsuru.io/app-process"] != args.Source {
		return false
	}

	if len(args.Units) > 0 {
		nameSet := set.FromSlice(args.Units)
		if !nameSet.Includes(pod.ObjectMeta.Name) {
			return false
		}
	}

	return true
}
<|endoftext|>


<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 client implements a client that can handle multiple gerrit instances
// derived from https://github.com/andygrunwald/go-gerrit
package client

import (
	"fmt"
	"io/ioutil"
	"strings"
	"time"

	"github.com/andygrunwald/go-gerrit"
	"github.com/sirupsen/logrus"
)

const (
	// LGTM means all presubmits passed, but need someone else to approve before merge.
	LGTM = "+1"
	// LBTM means some presubmits failed, perfer not merge.
	LBTM = "-1"
	// CodeReview is the default gerrit code review label
	CodeReview = "Code-Review"

	// GerritID identifies a gerrit change
	GerritID = "gerrit-id"
	// GerritInstance is the gerrit host url
	GerritInstance = "gerrit-instance"
	// GerritRevision is the SHA of current patchset from a gerrit change
	GerritRevision = "gerrit-revision"
	// GerritReportLabel is the gerrit label prow will cast vote on, fallback to CodeReview label if unset
	GerritReportLabel = "gerrit-report-label"
)

// ProjectsFlag is the flag type for gerrit projects when initializing a gerrit client
type ProjectsFlag map[string][]string

func (p ProjectsFlag) String() string {
	var hosts []string
	for host, repos := range p {
		hosts = append(hosts, host+"="+strings.Join(repos, ","))
	}
	return strings.Join(hosts, " ")
}

// Set populates ProjectsFlag upon flag.Parse()
func (p ProjectsFlag) Set(value string) error {
	parts := strings.SplitN(value, "=", 2)
	if len(parts) != 2 {
		return fmt.Errorf("%s not in the form of host=repo-a,repo-b,etc", value)
	}
	host := parts[0]
	if _, ok := p[host]; ok {
		return fmt.Errorf("duplicate host: %s", host)
	}
	repos := strings.Split(parts[1], ",")
	p[host] = repos
	return nil
}

type gerritAuthentication interface {
	SetCookieAuth(name, value string)
}

type gerritAccount interface {
	GetAccount(name string) (*gerrit.AccountInfo, *gerrit.Response, error)
	SetUsername(accountID string, input *gerrit.UsernameInput) (*string, *gerrit.Response, error)
}

type gerritChange interface {
	QueryChanges(opt *gerrit.QueryChangeOptions) (*[]gerrit.ChangeInfo, *gerrit.Response, error)
	SetReview(changeID, revisionID string, input *gerrit.ReviewInput) (*gerrit.ReviewResult, *gerrit.Response, error)
}

type gerritProjects interface {
	GetBranch(projectName, branchID string) (*gerrit.BranchInfo, *gerrit.Response, error)
}

// gerritInstanceHandler holds all actual gerrit handlers
type gerritInstanceHandler struct {
	instance string
	projects []string

	authService    gerritAuthentication
	accountService gerritAccount
	changeService  gerritChange
	projectService gerritProjects
}

// Client holds a instance:handler map
type Client struct {
	handlers map[string]*gerritInstanceHandler
}

// ChangeInfo is a gerrit.ChangeInfo
type ChangeInfo = gerrit.ChangeInfo

// RevisionInfo is a gerrit.RevisionInfo
type RevisionInfo = gerrit.RevisionInfo

// NewClient returns a new gerrit client
func NewClient(instances map[string][]string) (*Client, error) {
	c := &Client{
		handlers: map[string]*gerritInstanceHandler{},
	}
	for instance := range instances {
		gc, err := gerrit.NewClient(instance, nil)
		if err != nil {
			return nil, err
		}

		c.handlers[instance] = &gerritInstanceHandler{
			instance:       instance,
			projects:       instances[instance],
			authService:    gc.Authentication,
			accountService: gc.Accounts,
			changeService:  gc.Changes,
			projectService: gc.Projects,
		}
	}

	return c, nil
}

func auth(c *Client, cookiefilePath string) {
	logrus.Info("Starting auth loop...")
	var previousToken string
	wait := 10 * time.Minute
	for {
		raw, err := ioutil.ReadFile(cookiefilePath)
		if err != nil {
			logrus.WithError(err).Error("Failed to read auth cookie")
		}
		fields := strings.Fields(string(raw))
		token := fields[len(fields)-1]

		if token == previousToken {
			time.Sleep(wait)
			continue
		}

		logrus.Info("New token, updating handlers...")

		// update auth token for each instance
		for _, handler := range c.handlers {
			handler.authService.SetCookieAuth("o", token)

			self, _, err := handler.accountService.GetAccount("self")
			if err != nil {
				logrus.WithError(err).Error("Failed to auth with token")
			}

			logrus.Infof("Authentication to %s successful, Username: %s", handler.instance, self.Name)
		}
		previousToken = token
		time.Sleep(wait)
	}
}

// Start will authenticate the client with gerrit periodically
// Start must be called before user calls any client functions.
func (c *Client) Start(cookiefilePath string) {
	go auth(c, cookiefilePath)
}

// QueryChanges queries for all changes from all projects after lastUpdate time
// returns an instance:changes map
func (c *Client) QueryChanges(lastUpdate time.Time, rateLimit int) map[string][]ChangeInfo {
	result := map[string][]ChangeInfo{}
	for _, h := range c.handlers {
		changes := h.queryAllChanges(lastUpdate, rateLimit)
		if len(changes) > 0 {
			result[h.instance] = []ChangeInfo{}
			for _, change := range changes {
				result[h.instance] = append(result[h.instance], change)
			}
		}
	}
	return result
}

// SetReview writes a review comment base on the change id + revision
func (c *Client) SetReview(instance, id, revision, message string, labels map[string]string) error {
	h, ok := c.handlers[instance]
	if !ok {
		return fmt.Errorf("not activated gerrit instance: %s", instance)
	}

	if _, _, err := h.changeService.SetReview(id, revision, &gerrit.ReviewInput{
		Message: message,
		Labels:  labels,
	}); err != nil {
		return fmt.Errorf("cannot comment to gerrit: %v", err)
	}

	return nil
}

// GetBranchRevision returns SHA of HEAD of a branch
func (c *Client) GetBranchRevision(instance, project, branch string) (string, error) {
	h, ok := c.handlers[instance]
	if !ok {
		return "", fmt.Errorf("not activated gerrit instance: %s", instance)
	}

	res, _, err := h.projectService.GetBranch(project, branch)
	if err != nil {
		return "", err
	}

	return res.Revision, nil
}

// private handler implementation details

func (h *gerritInstanceHandler) queryAllChanges(lastUpdate time.Time, rateLimit int) []gerrit.ChangeInfo {
	result := []gerrit.ChangeInfo{}
	for _, project := range h.projects {
		changes, err := h.queryChangesForProject(project, lastUpdate, rateLimit)
		if err != nil {
			// don't halt on error from one project, log & continue
			logrus.WithError(err).Errorf("fail to query changes for project %s", project)
			continue
		}
		result = append(result, changes...)
	}

	return result
}

func (h *gerritInstanceHandler) queryChangesForProject(project string, lastUpdate time.Time, rateLimit int) ([]gerrit.ChangeInfo, error) {
	pending := []gerrit.ChangeInfo{}

	opt := &gerrit.QueryChangeOptions{}
	opt.Query = append(opt.Query, "project:"+project+"+status:open")
	opt.AdditionalFields = []string{"CURRENT_REVISION", "CURRENT_COMMIT"}

	start := 0

	for {
		opt.Limit = rateLimit
		opt.Start = start

		// The change output is sorted by the last update time, most recently updated to oldest updated.
		// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#list-changes
		changes, _, err := h.changeService.QueryChanges(opt)
		if err != nil {
			// should not happen? Let next sync loop catch up
			return nil, fmt.Errorf("failed to query gerrit changes: %v", err)
		}

		if changes == nil || len(*changes) == 0 {
			logrus.Infof("no more changes from query, returning...")
			return pending, nil
		}

		logrus.Infof("Find %d changes from query %v", len(*changes), opt.Query)

		start += len(*changes)

		for _, change := range *changes {
			// if we already processed this change, then we stop the current sync loop
			const layout = "2006-01-02 15:04:05"
			updated, err := time.Parse(layout, change.Updated)
			if err != nil {
				logrus.WithError(err).Errorf("Parse time %v failed", change.Updated)
				continue
			}

			logrus.Infof("Change %d, last updated %s", change.Number, change.Updated)

			// process if updated later than last updated
			// stop if update was stale
			if !updated.Before(lastUpdate) {
				// we need to make sure the change update is from a new commit change
				rev, ok := change.Revisions[change.CurrentRevision]
				if !ok {
					logrus.WithError(err).Errorf("(should not happen?)cannot find current revision for change %v", change.ID)
					continue
				}

				created, err := time.Parse(layout, rev.Created)
				if err != nil {
					logrus.WithError(err).Errorf("Parse time %v failed", rev.Created)
					continue
				}

				if created.Before(lastUpdate) {
					// stale commit
					logrus.Infof("Change %d, latest revision updated %s before lastUpdate %s, skipping this patchset", change.Number, created, lastUpdate)
					continue
				}

				pending = append(pending, change)
			} else {
				logrus.Infof("Change %d, updated %s before lastUpdate %s, return", change.Number, change.Updated, lastUpdate)
				return pending, nil
			}
		}
	}
}
<commit_msg>prevent seg fault if auth fails<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 client implements a client that can handle multiple gerrit instances
// derived from https://github.com/andygrunwald/go-gerrit
package client

import (
	"fmt"
	"io/ioutil"
	"strings"
	"time"

	"github.com/andygrunwald/go-gerrit"
	"github.com/sirupsen/logrus"
)

const (
	// LGTM means all presubmits passed, but need someone else to approve before merge.
	LGTM = "+1"
	// LBTM means some presubmits failed, perfer not merge.
	LBTM = "-1"
	// CodeReview is the default gerrit code review label
	CodeReview = "Code-Review"

	// GerritID identifies a gerrit change
	GerritID = "gerrit-id"
	// GerritInstance is the gerrit host url
	GerritInstance = "gerrit-instance"
	// GerritRevision is the SHA of current patchset from a gerrit change
	GerritRevision = "gerrit-revision"
	// GerritReportLabel is the gerrit label prow will cast vote on, fallback to CodeReview label if unset
	GerritReportLabel = "gerrit-report-label"
)

// ProjectsFlag is the flag type for gerrit projects when initializing a gerrit client
type ProjectsFlag map[string][]string

func (p ProjectsFlag) String() string {
	var hosts []string
	for host, repos := range p {
		hosts = append(hosts, host+"="+strings.Join(repos, ","))
	}
	return strings.Join(hosts, " ")
}

// Set populates ProjectsFlag upon flag.Parse()
func (p ProjectsFlag) Set(value string) error {
	parts := strings.SplitN(value, "=", 2)
	if len(parts) != 2 {
		return fmt.Errorf("%s not in the form of host=repo-a,repo-b,etc", value)
	}
	host := parts[0]
	if _, ok := p[host]; ok {
		return fmt.Errorf("duplicate host: %s", host)
	}
	repos := strings.Split(parts[1], ",")
	p[host] = repos
	return nil
}

type gerritAuthentication interface {
	SetCookieAuth(name, value string)
}

type gerritAccount interface {
	GetAccount(name string) (*gerrit.AccountInfo, *gerrit.Response, error)
	SetUsername(accountID string, input *gerrit.UsernameInput) (*string, *gerrit.Response, error)
}

type gerritChange interface {
	QueryChanges(opt *gerrit.QueryChangeOptions) (*[]gerrit.ChangeInfo, *gerrit.Response, error)
	SetReview(changeID, revisionID string, input *gerrit.ReviewInput) (*gerrit.ReviewResult, *gerrit.Response, error)
}

type gerritProjects interface {
	GetBranch(projectName, branchID string) (*gerrit.BranchInfo, *gerrit.Response, error)
}

// gerritInstanceHandler holds all actual gerrit handlers
type gerritInstanceHandler struct {
	instance string
	projects []string

	authService    gerritAuthentication
	accountService gerritAccount
	changeService  gerritChange
	projectService gerritProjects
}

// Client holds a instance:handler map
type Client struct {
	handlers map[string]*gerritInstanceHandler
}

// ChangeInfo is a gerrit.ChangeInfo
type ChangeInfo = gerrit.ChangeInfo

// RevisionInfo is a gerrit.RevisionInfo
type RevisionInfo = gerrit.RevisionInfo

// NewClient returns a new gerrit client
func NewClient(instances map[string][]string) (*Client, error) {
	c := &Client{
		handlers: map[string]*gerritInstanceHandler{},
	}
	for instance := range instances {
		gc, err := gerrit.NewClient(instance, nil)
		if err != nil {
			return nil, err
		}

		c.handlers[instance] = &gerritInstanceHandler{
			instance:       instance,
			projects:       instances[instance],
			authService:    gc.Authentication,
			accountService: gc.Accounts,
			changeService:  gc.Changes,
			projectService: gc.Projects,
		}
	}

	return c, nil
}

func auth(c *Client, cookiefilePath string) {
	logrus.Info("Starting auth loop...")
	var previousToken string
	wait := 10 * time.Minute
	for {
		raw, err := ioutil.ReadFile(cookiefilePath)
		if err != nil {
			logrus.WithError(err).Error("Failed to read auth cookie")
		}
		fields := strings.Fields(string(raw))
		token := fields[len(fields)-1]

		if token == previousToken {
			time.Sleep(wait)
			continue
		}

		logrus.Info("New token, updating handlers...")

		// update auth token for each instance
		for _, handler := range c.handlers {
			handler.authService.SetCookieAuth("o", token)

			self, _, err := handler.accountService.GetAccount("self")
			if err != nil {
				logrus.WithError(err).Error("Failed to auth with token")
				continue
			}

			logrus.Infof("Authentication to %s successful, Username: %s", handler.instance, self.Name)
		}
		previousToken = token
		time.Sleep(wait)
	}
}

// Start will authenticate the client with gerrit periodically
// Start must be called before user calls any client functions.
func (c *Client) Start(cookiefilePath string) {
	go auth(c, cookiefilePath)
}

// QueryChanges queries for all changes from all projects after lastUpdate time
// returns an instance:changes map
func (c *Client) QueryChanges(lastUpdate time.Time, rateLimit int) map[string][]ChangeInfo {
	result := map[string][]ChangeInfo{}
	for _, h := range c.handlers {
		changes := h.queryAllChanges(lastUpdate, rateLimit)
		if len(changes) > 0 {
			result[h.instance] = []ChangeInfo{}
			for _, change := range changes {
				result[h.instance] = append(result[h.instance], change)
			}
		}
	}
	return result
}

// SetReview writes a review comment base on the change id + revision
func (c *Client) SetReview(instance, id, revision, message string, labels map[string]string) error {
	h, ok := c.handlers[instance]
	if !ok {
		return fmt.Errorf("not activated gerrit instance: %s", instance)
	}

	if _, _, err := h.changeService.SetReview(id, revision, &gerrit.ReviewInput{
		Message: message,
		Labels:  labels,
	}); err != nil {
		return fmt.Errorf("cannot comment to gerrit: %v", err)
	}

	return nil
}

// GetBranchRevision returns SHA of HEAD of a branch
func (c *Client) GetBranchRevision(instance, project, branch string) (string, error) {
	h, ok := c.handlers[instance]
	if !ok {
		return "", fmt.Errorf("not activated gerrit instance: %s", instance)
	}

	res, _, err := h.projectService.GetBranch(project, branch)
	if err != nil {
		return "", err
	}

	return res.Revision, nil
}

// private handler implementation details

func (h *gerritInstanceHandler) queryAllChanges(lastUpdate time.Time, rateLimit int) []gerrit.ChangeInfo {
	result := []gerrit.ChangeInfo{}
	for _, project := range h.projects {
		changes, err := h.queryChangesForProject(project, lastUpdate, rateLimit)
		if err != nil {
			// don't halt on error from one project, log & continue
			logrus.WithError(err).Errorf("fail to query changes for project %s", project)
			continue
		}
		result = append(result, changes...)
	}

	return result
}

func (h *gerritInstanceHandler) queryChangesForProject(project string, lastUpdate time.Time, rateLimit int) ([]gerrit.ChangeInfo, error) {
	pending := []gerrit.ChangeInfo{}

	opt := &gerrit.QueryChangeOptions{}
	opt.Query = append(opt.Query, "project:"+project+"+status:open")
	opt.AdditionalFields = []string{"CURRENT_REVISION", "CURRENT_COMMIT"}

	start := 0

	for {
		opt.Limit = rateLimit
		opt.Start = start

		// The change output is sorted by the last update time, most recently updated to oldest updated.
		// Gerrit API docs: https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#list-changes
		changes, _, err := h.changeService.QueryChanges(opt)
		if err != nil {
			// should not happen? Let next sync loop catch up
			return nil, fmt.Errorf("failed to query gerrit changes: %v", err)
		}

		if changes == nil || len(*changes) == 0 {
			logrus.Infof("no more changes from query, returning...")
			return pending, nil
		}

		logrus.Infof("Find %d changes from query %v", len(*changes), opt.Query)

		start += len(*changes)

		for _, change := range *changes {
			// if we already processed this change, then we stop the current sync loop
			const layout = "2006-01-02 15:04:05"
			updated, err := time.Parse(layout, change.Updated)
			if err != nil {
				logrus.WithError(err).Errorf("Parse time %v failed", change.Updated)
				continue
			}

			logrus.Infof("Change %d, last updated %s", change.Number, change.Updated)

			// process if updated later than last updated
			// stop if update was stale
			if !updated.Before(lastUpdate) {
				// we need to make sure the change update is from a new commit change
				rev, ok := change.Revisions[change.CurrentRevision]
				if !ok {
					logrus.WithError(err).Errorf("(should not happen?)cannot find current revision for change %v", change.ID)
					continue
				}

				created, err := time.Parse(layout, rev.Created)
				if err != nil {
					logrus.WithError(err).Errorf("Parse time %v failed", rev.Created)
					continue
				}

				if created.Before(lastUpdate) {
					// stale commit
					logrus.Infof("Change %d, latest revision updated %s before lastUpdate %s, skipping this patchset", change.Number, created, lastUpdate)
					continue
				}

				pending = append(pending, change)
			} else {
				logrus.Infof("Change %d, updated %s before lastUpdate %s, return", change.Number, change.Updated, lastUpdate)
				return pending, nil
			}
		}
	}
}
<|endoftext|>


<commit_before>package main

import (
	"os"
	"testing"

	"github.com/prebid/prebid-server/config"

	"github.com/spf13/viper"
)

func compareStrings(t *testing.T, message string, expect string, actual string) {
	if expect != actual {
		t.Errorf(message, expect, actual)
	}
}

// forceEnv sets an environment variable to a certain value, and return a deferable function to reset it to the original value.
func forceEnv(t *testing.T, key string, val string) func() {
	orig, set := os.LookupEnv(key)
	err := os.Setenv(key, val)
	if err != nil {
		t.Fatalf("Error setting evnvironment %s", key)
	}
	if set {
		return func() {
			if os.Setenv(key, orig) != nil {
				t.Fatalf("Error unsetting evnvironment %s", key)
			}
		}
	}
	return func() {
		if os.Unsetenv(key) != nil {
			t.Fatalf("Error unsetting evnvironment %s", key)
		}
	}
}

// Test the viper setup
func TestViperInit(t *testing.T) {
	v := viper.New()
	config.SetupViper(v, "")
	compareStrings(t, "Viper error: external_url expected to be %s, found %s", "http://localhost:8000", v.Get("external_url").(string))
	compareStrings(t, "Viper error: adapters.pulsepoint.endpoint expected to be %s, found %s", "http://bid.contextweb.com/header/s/ortb/prebid-s2s", v.Get("adapters.pulsepoint.endpoint").(string))
}

func TestViperEnv(t *testing.T) {
	v := viper.New()
	config.SetupViper(v, "")
	port := forceEnv(t, "PBS_PORT", "7777")
	defer port()

	endpt := forceEnv(t, "PBS_ADAPTERS_PUBMATIC_ENDPOINT", "not_an_endpoint")
	defer endpt()

	ttl := forceEnv(t, "PBS_HOST_COOKIE_TTL_DAYS", "60")
	defer ttl()

	// Basic config set
	compareStrings(t, "Viper error: port expected to be %s, found %s", "7777", v.Get("port").(string))
	// Nested config set
	compareStrings(t, "Viper error: adapters.pubmatic.endpoint expected to be %s, found %s", "not_an_endpoint", v.Get("adapters.pubmatic.endpoint").(string))
	// Config set with underscores
	compareStrings(t, "Viper error: host_cookie.ttl_days expected to be %s, found %s", "60", v.Get("host_cookie.ttl_days").(string))
}
<commit_msg>[fix][mispelling] fix mispelling in test log (#996)<commit_after>package main

import (
	"os"
	"testing"

	"github.com/prebid/prebid-server/config"

	"github.com/spf13/viper"
)

func compareStrings(t *testing.T, message string, expect string, actual string) {
	if expect != actual {
		t.Errorf(message, expect, actual)
	}
}

// forceEnv sets an environment variable to a certain value, and return a deferable function to reset it to the original value.
func forceEnv(t *testing.T, key string, val string) func() {
	orig, set := os.LookupEnv(key)
	err := os.Setenv(key, val)
	if err != nil {
		t.Fatalf("Error setting environment %s", key)
	}
	if set {
		return func() {
			if os.Setenv(key, orig) != nil {
				t.Fatalf("Error unsetting environment %s", key)
			}
		}
	}
	return func() {
		if os.Unsetenv(key) != nil {
			t.Fatalf("Error unsetting environment %s", key)
		}
	}
}

// Test the viper setup
func TestViperInit(t *testing.T) {
	v := viper.New()
	config.SetupViper(v, "")
	compareStrings(t, "Viper error: external_url expected to be %s, found %s", "http://localhost:8000", v.Get("external_url").(string))
	compareStrings(t, "Viper error: adapters.pulsepoint.endpoint expected to be %s, found %s", "http://bid.contextweb.com/header/s/ortb/prebid-s2s", v.Get("adapters.pulsepoint.endpoint").(string))
}

func TestViperEnv(t *testing.T) {
	v := viper.New()
	config.SetupViper(v, "")
	port := forceEnv(t, "PBS_PORT", "7777")
	defer port()

	endpt := forceEnv(t, "PBS_ADAPTERS_PUBMATIC_ENDPOINT", "not_an_endpoint")
	defer endpt()

	ttl := forceEnv(t, "PBS_HOST_COOKIE_TTL_DAYS", "60")
	defer ttl()

	// Basic config set
	compareStrings(t, "Viper error: port expected to be %s, found %s", "7777", v.Get("port").(string))
	// Nested config set
	compareStrings(t, "Viper error: adapters.pubmatic.endpoint expected to be %s, found %s", "not_an_endpoint", v.Get("adapters.pubmatic.endpoint").(string))
	// Config set with underscores
	compareStrings(t, "Viper error: host_cookie.ttl_days expected to be %s, found %s", "60", v.Get("host_cookie.ttl_days").(string))
}
<|endoftext|>


<commit_before>package peer

import (
	"errors"
	"fmt"
	"net"
	"os"
	"testing"
	"time"

	log "github.com/Sirupsen/logrus"
	"github.com/ubclaunchpad/cumulus/conn"
	"github.com/ubclaunchpad/cumulus/msg"
)

const (
	FailConnect = "Failed to create connection and perform handshake"
	FailGetPeer = "Failed to get peer from peer store after connection was established"
)

var (
	addrs1 = []string{
		"50.74.89.63", "80.74.170.128", "252.206.163.100", "106.237.154.62",
		"23.37.91.218", "141.205.198.174", "243.116.40.121", "219.202.178.157",
		"208.213.114.64", "99.130.197.2", "215.99.177.252", "253.191.123.93",
		"118.98.13.210", "217.41.101.17", "94.39.137.0", "8.26.57.127",
		"2.121.24.48", "45.166.60.59", "69.14.4.201", "73.11.112.209",
		"119.235.160.135", "158.60.36.47", "173.52.51.91", "160.76.117.247",
		"99.3.196.77", "26.37.188.143", "252.52.197.42", "189.10.80.173",
		"32.15.5.182", "73.178.78.95", "166.109.113.195", "39.137.84.170",
		"82.249.125.238", "154.66.246.230", "53.195.164.196", "79.7.11.105",
		"98.209.56.64", "74.25.239.123", "211.0.166.153", "54.32.155.245",
		"49.37.93.82", "141.54.42.188", "202.15.222.2", "39.251.90.236",
		"227.14.254.34", "163.89.37.232", "232.46.225.13", "125.66.77.152",
		"134.182.19.76", "4.220.173.35",
	}

	addrs2 = []string{
		"7.226.78.25", "90.9.211.160", "201.35.42.214", "71.203.173.18",
		"80.33.238.63", "115.122.81.134", "213.234.92.168", "139.190.9.146",
		"161.86.170.251", "95.126.157.42", "160.198.2.231", "146.174.248.226",
		"206.232.35.67", "99.116.200.57", "95.37.225.234", "227.234.125.133",
		"66.40.130.160", "166.32.202.71", "229.203.48.121", "122.41.93.73",
		"19.127.139.118", "242.85.174.83", "121.145.63.93", "125.187.226.190",
		"227.102.96.138", "133.43.209.108", "245.2.228.113", "43.67.186.61",
		"194.70.178.182", "155.98.10.21", "157.150.51.175", "222.1.20.83",
		"19.253.228.59", "195.118.45.237", "159.78.10.205", "206.31.54.66",
		"31.191.153.165", "130.235.208.32", "130.5.207.98", "5.226.180.24",
	}
)

// fakeConn implements net.Conn
type fakeConn struct {
	Addr net.Addr
}

func (fc fakeConn) Read(b []byte) (n int, err error)   { return 0, nil }
func (fc fakeConn) Write(b []byte) (n int, err error)  { return 0, nil }
func (fc fakeConn) Close() error                       { return nil }
func (fc fakeConn) LocalAddr() net.Addr                { return fc.Addr }
func (fc fakeConn) RemoteAddr() net.Addr               { return fc.Addr }
func (fc fakeConn) SetDeadline(t time.Time) error      { return nil }
func (fc fakeConn) SetReadDeadline(t time.Time) error  { return nil }
func (fc fakeConn) SetWriteDeadline(t time.Time) error { return nil }

// fakeAddr implementes net.Addr
type fakeAddr struct {
	Addr string
}

func (fa fakeAddr) Network() string { return fa.Addr }
func (fa fakeAddr) String() string  { return fa.Addr }

func inList(item string, list []string) bool {
	for _, listItem := range list {
		if item == listItem {
			return true
		}
	}
	return false
}

func newConnectionAndHandshake(addr string) (net.Conn, error) {
	go conn.Listen(ListenAddr, ConnectionHandler)

	// Allow 3 retries as the socket might not be open by the time we dial
	for i := 0; i < 3; i++ {
		c, err := conn.Dial(ListenAddr)
		if err == nil {
			// Success, continue as usual
			_, err = exchangeListenAddrs(c, time.Second*5)
			return c, err
		}
	}
	return nil, errors.New("Failed to connect to" + ListenAddr + "after 3 dial attempts")
}

func TestMain(m *testing.M) {
	log.SetLevel(log.DebugLevel)
	fmt.Println("NOTE: Some errors will be logged during tests. Note that these",
		"errors do NOT necessarily mean the tests are failing.")
	os.Exit(m.Run())
}

// This will error if there are concurrent accesses to the PeerStore, or error
// if an atomic operation returns un unexpected result.
func TestConcurrentPeerStore(t *testing.T) {
	ps := NewPeerStore()

	resChan1 := make(chan bool)
	resChan2 := make(chan bool)

	// Asynchronously add and find peers
	go func() {
		var fa net.Addr
		var fc net.Conn
		for _, addr := range addrs2 {
			fa = fakeAddr{Addr: addr}
			fc = fakeConn{Addr: fa}
			ps.Add(New(fc, ps, addr))
			p := ps.Get(addr)
			if p.ListenAddr != addr {
				resChan1 <- false
			}
		}
		resChan1 <- true
	}()

	// Asynchronously add and remove peers
	go func() {
		var fa net.Addr
		var fc net.Conn
		for _, addr := range addrs1 {
			fa = fakeAddr{Addr: addr}
			fc = fakeConn{Addr: fa}
			ps.Add(New(fc, ps, addr))
			ps.Remove(addr)
		}
		resChan2 <- true
	}()

	returnCount := 0
	for returnCount != 2 {
		select {
		case res1 := <-resChan1:
			if !res1 {
				t.FailNow()
			}
			returnCount++
		case res2 := <-resChan2:
			if !res2 {
				t.FailNow()
			}
			returnCount++
		}
	}

	if ps.Size() != len(addrs2) {
		t.FailNow()
	}

	for i := 0; i < len(addrs2); i++ {
		p := ps.Get(addrs2[i])
		if p == nil {
			t.FailNow()
		}
		ps.Remove(addrs2[i])
	}

	if ps.Size() != 0 {
		t.FailNow()
	}
}

func TestRemoveRandom(t *testing.T) {
	var fa fakeAddr
	var fc fakeConn
	ps := NewPeerStore()
	for _, addr := range addrs1 {
		fa = fakeAddr{Addr: addr}
		fc = fakeConn{Addr: fa}
		ps.Add(New(fc, ps, addr))
	}

	for i := ps.Size(); i > 0; i-- {
		ps.RemoveRandom()
		if ps.Size() != i-1 {
			t.FailNow()
		}
	}
}

func TestAddrs(t *testing.T) {
	var fa fakeAddr
	var fc fakeConn
	ps := NewPeerStore()
	for _, addr := range addrs1 {
		fa = fakeAddr{Addr: addr}
		fc = fakeConn{Addr: fa}
		ps.Add(New(fc, ps, addr))
	}

	addrs := ps.Addrs()
	for _, addr := range addrs {
		if !inList(addr, addrs1) {
			t.FailNow()
		}
	}
}

func TestSetRequestHandler(t *testing.T) {
	var fa net.Addr
	var fc net.Conn
	fa = fakeAddr{Addr: "127.0.0.1"}
	fc = fakeConn{Addr: fa}
	p := New(fc, PStore, fa.String())
	if p.requestHandler != nil {
		t.FailNow()
	}

	rh := func(req *msg.Request) msg.Response {
		return msg.Response{
			ID:       "heyyou",
			Resource: "i can see you",
		}
	}

	p.SetRequestHandler(rh)

	if p.requestHandler == nil {
		t.FailNow()
	}

	p2 := New(fc, PStore, fa.String())
	if p2.requestHandler != nil {
		t.FailNow()
	}
}

func TestSetPushHandler(t *testing.T) {
	var fa net.Addr
	var fc net.Conn
	fa = fakeAddr{Addr: "127.0.0.1"}
	fc = fakeConn{Addr: fa}
	p := New(fc, PStore, fa.String())
	if p.pushHandler != nil {
		t.FailNow()
	}

	ph := func(req *msg.Push) {}

	p.SetPushHandler(ph)

	if p.pushHandler == nil {
		t.FailNow()
	}

	p2 := New(fc, PStore, fa.String())
	if p2.pushHandler != nil {
		t.FailNow()
	}
}

func TestSetDefaultRequestHandler(t *testing.T) {
	var fa net.Addr
	var fc net.Conn
	fa = fakeAddr{Addr: "127.0.0.1"}
	fc = fakeConn{Addr: fa}
	p := New(fc, PStore, fa.String())
	if p.requestHandler != nil {
		t.FailNow()
	}

	rh := func(req *msg.Request) msg.Response {
		return msg.Response{
			ID:       "heyyou",
			Resource: "i can see you",
		}
	}

	SetDefaultRequestHandler(rh)

	if p.requestHandler != nil {
		t.FailNow()
	}

	p2 := New(fc, PStore, fa.String())
	if p2.requestHandler == nil {
		t.FailNow()
	}
}

func TestSetDefaultPushHandler(t *testing.T) {
	var fa net.Addr
	var fc net.Conn
	fa = fakeAddr{Addr: "127.0.0.1"}
	fc = fakeConn{Addr: fa}
	p := New(fc, PStore, fa.String())
	if p.pushHandler != nil {
		t.FailNow()
	}

	ph := func(req *msg.Push) {}

	SetDefaultPushHandler(ph)

	if p.pushHandler != nil {
		t.FailNow()
	}

	p2 := New(fc, PStore, fa.String())
	if p2.pushHandler == nil {
		t.FailNow()
	}
}
<commit_msg>Remove unused functions from peer_test<commit_after>package peer

import (
	"fmt"
	"net"
	"os"
	"testing"
	"time"

	log "github.com/Sirupsen/logrus"
	"github.com/ubclaunchpad/cumulus/msg"
)

const (
	FailConnect = "Failed to create connection and perform handshake"
	FailGetPeer = "Failed to get peer from peer store after connection was established"
)

var (
	addrs1 = []string{
		"50.74.89.63", "80.74.170.128", "252.206.163.100", "106.237.154.62",
		"23.37.91.218", "141.205.198.174", "243.116.40.121", "219.202.178.157",
		"208.213.114.64", "99.130.197.2", "215.99.177.252", "253.191.123.93",
		"118.98.13.210", "217.41.101.17", "94.39.137.0", "8.26.57.127",
		"2.121.24.48", "45.166.60.59", "69.14.4.201", "73.11.112.209",
		"119.235.160.135", "158.60.36.47", "173.52.51.91", "160.76.117.247",
		"99.3.196.77", "26.37.188.143", "252.52.197.42", "189.10.80.173",
		"32.15.5.182", "73.178.78.95", "166.109.113.195", "39.137.84.170",
		"82.249.125.238", "154.66.246.230", "53.195.164.196", "79.7.11.105",
		"98.209.56.64", "74.25.239.123", "211.0.166.153", "54.32.155.245",
		"49.37.93.82", "141.54.42.188", "202.15.222.2", "39.251.90.236",
		"227.14.254.34", "163.89.37.232", "232.46.225.13", "125.66.77.152",
		"134.182.19.76", "4.220.173.35",
	}

	addrs2 = []string{
		"7.226.78.25", "90.9.211.160", "201.35.42.214", "71.203.173.18",
		"80.33.238.63", "115.122.81.134", "213.234.92.168", "139.190.9.146",
		"161.86.170.251", "95.126.157.42", "160.198.2.231", "146.174.248.226",
		"206.232.35.67", "99.116.200.57", "95.37.225.234", "227.234.125.133",
		"66.40.130.160", "166.32.202.71", "229.203.48.121", "122.41.93.73",
		"19.127.139.118", "242.85.174.83", "121.145.63.93", "125.187.226.190",
		"227.102.96.138", "133.43.209.108", "245.2.228.113", "43.67.186.61",
		"194.70.178.182", "155.98.10.21", "157.150.51.175", "222.1.20.83",
		"19.253.228.59", "195.118.45.237", "159.78.10.205", "206.31.54.66",
		"31.191.153.165", "130.235.208.32", "130.5.207.98", "5.226.180.24",
	}
)

// fakeConn implements net.Conn
type fakeConn struct {
	Addr net.Addr
}

func (fc fakeConn) Read(b []byte) (n int, err error)   { return 0, nil }
func (fc fakeConn) Write(b []byte) (n int, err error)  { return 0, nil }
func (fc fakeConn) Close() error                       { return nil }
func (fc fakeConn) LocalAddr() net.Addr                { return fc.Addr }
func (fc fakeConn) RemoteAddr() net.Addr               { return fc.Addr }
func (fc fakeConn) SetDeadline(t time.Time) error      { return nil }
func (fc fakeConn) SetReadDeadline(t time.Time) error  { return nil }
func (fc fakeConn) SetWriteDeadline(t time.Time) error { return nil }

// fakeAddr implementes net.Addr
type fakeAddr struct {
	Addr string
}

func (fa fakeAddr) Network() string { return fa.Addr }
func (fa fakeAddr) String() string  { return fa.Addr }

func inList(item string, list []string) bool {
	for _, listItem := range list {
		if item == listItem {
			return true
		}
	}
	return false
}

func TestMain(m *testing.M) {
	log.SetLevel(log.DebugLevel)
	fmt.Println("NOTE: Some errors will be logged during tests. Note that these",
		"errors do NOT necessarily mean the tests are failing.")
	os.Exit(m.Run())
}

// This will error if there are concurrent accesses to the PeerStore, or error
// if an atomic operation returns un unexpected result.
func TestConcurrentPeerStore(t *testing.T) {
	ps := NewPeerStore()

	resChan1 := make(chan bool)
	resChan2 := make(chan bool)

	// Asynchronously add and find peers
	go func() {
		var fa net.Addr
		var fc net.Conn
		for _, addr := range addrs2 {
			fa = fakeAddr{Addr: addr}
			fc = fakeConn{Addr: fa}
			ps.Add(New(fc, ps, addr))
			p := ps.Get(addr)
			if p.ListenAddr != addr {
				resChan1 <- false
			}
		}
		resChan1 <- true
	}()

	// Asynchronously add and remove peers
	go func() {
		var fa net.Addr
		var fc net.Conn
		for _, addr := range addrs1 {
			fa = fakeAddr{Addr: addr}
			fc = fakeConn{Addr: fa}
			ps.Add(New(fc, ps, addr))
			ps.Remove(addr)
		}
		resChan2 <- true
	}()

	returnCount := 0
	for returnCount != 2 {
		select {
		case res1 := <-resChan1:
			if !res1 {
				t.FailNow()
			}
			returnCount++
		case res2 := <-resChan2:
			if !res2 {
				t.FailNow()
			}
			returnCount++
		}
	}

	if ps.Size() != len(addrs2) {
		t.FailNow()
	}

	for i := 0; i < len(addrs2); i++ {
		p := ps.Get(addrs2[i])
		if p == nil {
			t.FailNow()
		}
		ps.Remove(addrs2[i])
	}

	if ps.Size() != 0 {
		t.FailNow()
	}
}

func TestRemoveRandom(t *testing.T) {
	var fa fakeAddr
	var fc fakeConn
	ps := NewPeerStore()
	for _, addr := range addrs1 {
		fa = fakeAddr{Addr: addr}
		fc = fakeConn{Addr: fa}
		ps.Add(New(fc, ps, addr))
	}

	for i := ps.Size(); i > 0; i-- {
		ps.RemoveRandom()
		if ps.Size() != i-1 {
			t.FailNow()
		}
	}
}

func TestAddrs(t *testing.T) {
	var fa fakeAddr
	var fc fakeConn
	ps := NewPeerStore()
	for _, addr := range addrs1 {
		fa = fakeAddr{Addr: addr}
		fc = fakeConn{Addr: fa}
		ps.Add(New(fc, ps, addr))
	}

	addrs := ps.Addrs()
	for _, addr := range addrs {
		if !inList(addr, addrs1) {
			t.FailNow()
		}
	}
}

func TestSetRequestHandler(t *testing.T) {
	var fa net.Addr
	var fc net.Conn
	fa = fakeAddr{Addr: "127.0.0.1"}
	fc = fakeConn{Addr: fa}
	p := New(fc, PStore, fa.String())
	if p.requestHandler != nil {
		t.FailNow()
	}

	rh := func(req *msg.Request) msg.Response {
		return msg.Response{
			ID:       "heyyou",
			Resource: "i can see you",
		}
	}

	p.SetRequestHandler(rh)

	if p.requestHandler == nil {
		t.FailNow()
	}

	p2 := New(fc, PStore, fa.String())
	if p2.requestHandler != nil {
		t.FailNow()
	}
}

func TestSetPushHandler(t *testing.T) {
	var fa net.Addr
	var fc net.Conn
	fa = fakeAddr{Addr: "127.0.0.1"}
	fc = fakeConn{Addr: fa}
	p := New(fc, PStore, fa.String())
	if p.pushHandler != nil {
		t.FailNow()
	}

	ph := func(req *msg.Push) {}

	p.SetPushHandler(ph)

	if p.pushHandler == nil {
		t.FailNow()
	}

	p2 := New(fc, PStore, fa.String())
	if p2.pushHandler != nil {
		t.FailNow()
	}
}

func TestSetDefaultRequestHandler(t *testing.T) {
	var fa net.Addr
	var fc net.Conn
	fa = fakeAddr{Addr: "127.0.0.1"}
	fc = fakeConn{Addr: fa}
	p := New(fc, PStore, fa.String())
	if p.requestHandler != nil {
		t.FailNow()
	}

	rh := func(req *msg.Request) msg.Response {
		return msg.Response{
			ID:       "heyyou",
			Resource: "i can see you",
		}
	}

	SetDefaultRequestHandler(rh)

	if p.requestHandler != nil {
		t.FailNow()
	}

	p2 := New(fc, PStore, fa.String())
	if p2.requestHandler == nil {
		t.FailNow()
	}
}

func TestSetDefaultPushHandler(t *testing.T) {
	var fa net.Addr
	var fc net.Conn
	fa = fakeAddr{Addr: "127.0.0.1"}
	fc = fakeConn{Addr: fa}
	p := New(fc, PStore, fa.String())
	if p.pushHandler != nil {
		t.FailNow()
	}

	ph := func(req *msg.Push) {}

	SetDefaultPushHandler(ph)

	if p.pushHandler != nil {
		t.FailNow()
	}

	p2 := New(fc, PStore, fa.String())
	if p2.pushHandler == nil {
		t.FailNow()
	}
}
<|endoftext|>


<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 auth

import (
	"context"
	"fmt"
	"net"
	"strconv"

	v1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/kubernetes/pkg/cluster/ports"
	"k8s.io/kubernetes/test/e2e/framework"

	"github.com/onsi/ginkgo"
	"github.com/onsi/gomega"
	e2enode "k8s.io/kubernetes/test/e2e/framework/node"
	e2epod "k8s.io/kubernetes/test/e2e/framework/pod"
)

var _ = SIGDescribe("[Feature:NodeAuthenticator]", func() {

	f := framework.NewDefaultFramework("node-authn")
	var ns string
	var nodeIPs []string
	ginkgo.BeforeEach(func() {
		ns = f.Namespace.Name

		nodeList, err := f.ClientSet.CoreV1().Nodes().List(context.TODO(), metav1.ListOptions{})
		framework.ExpectNoError(err, "failed to list nodes in namespace: %s", ns)
		framework.ExpectNotEqual(len(nodeList.Items), 0)

		pickedNode := nodeList.Items[0]
		nodeIPs = e2enode.GetAddresses(&pickedNode, v1.NodeExternalIP)
		// The pods running in the cluster can see the internal addresses.
		nodeIPs = append(nodeIPs, e2enode.GetAddresses(&pickedNode, v1.NodeInternalIP)...)

		// make sure ServiceAccount admission controller is enabled, so secret generation on SA creation works
		saName := "default"
		sa, err := f.ClientSet.CoreV1().ServiceAccounts(ns).Get(context.TODO(), saName, metav1.GetOptions{})
		framework.ExpectNoError(err, "failed to retrieve service account (%s:%s)", ns, saName)
		framework.ExpectNotEqual(len(sa.Secrets), 0)

	})

	ginkgo.It("The kubelet's main port 10250 should reject requests with no credentials", func() {
		pod := createNodeAuthTestPod(f)
		for _, nodeIP := range nodeIPs {
			// Anonymous authentication is disabled by default
			host := net.JoinHostPort(nodeIP, strconv.Itoa(ports.KubeletPort))
			result := framework.RunHostCmdOrDie(ns, pod.Name, fmt.Sprintf("curl -sIk -o /dev/null -w '%s' https://%s/metrics", "%{http_code}", host))
			gomega.Expect(result).To(gomega.Or(gomega.Equal("401"), gomega.Equal("403")), "the kubelet's main port 10250 should reject requests with no credentials")
		}
	})

	ginkgo.It("The kubelet can delegate ServiceAccount tokens to the API server", func() {
		ginkgo.By("create a new ServiceAccount for authentication")
		trueValue := true
		newSA := &v1.ServiceAccount{
			ObjectMeta: metav1.ObjectMeta{
				Namespace: ns,
				Name:      "node-auth-newsa",
			},
			AutomountServiceAccountToken: &trueValue,
		}
		_, err := f.ClientSet.CoreV1().ServiceAccounts(ns).Create(context.TODO(), newSA, metav1.CreateOptions{})
		framework.ExpectNoError(err, "failed to create service account (%s:%s)", ns, newSA.Name)

		pod := createNodeAuthTestPod(f)

		for _, nodeIP := range nodeIPs {
			host := net.JoinHostPort(nodeIP, strconv.Itoa(ports.KubeletPort))
			result := framework.RunHostCmdOrDie(ns,
				pod.Name,
				fmt.Sprintf("curl -sIk -o /dev/null -w '%s' --header \"Authorization: Bearer `%s`\" https://%s/metrics",
					"%{http_code}",
					"cat /var/run/secrets/kubernetes.io/serviceaccount/token",
					host))
			gomega.Expect(result).To(gomega.Or(gomega.Equal("401"), gomega.Equal("403")), "the kubelet can delegate ServiceAccount tokens to the API server")
		}
	})
})

func createNodeAuthTestPod(f *framework.Framework) *v1.Pod {
	pod := e2epod.NewAgnhostPod(f.Namespace.Name, "agnhost-pod", nil, nil, nil)
	pod.ObjectMeta.GenerateName = "test-node-authn-"
	return f.PodClient().CreateSync(pod)
}
<commit_msg>UPSTREAM: 103050: Fix NodeAuthenticator tests in dual stack<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 auth

import (
	"context"
	"fmt"
	"net"
	"strconv"

	v1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/kubernetes/pkg/cluster/ports"
	"k8s.io/kubernetes/test/e2e/framework"

	"github.com/onsi/ginkgo"
	"github.com/onsi/gomega"
	e2enode "k8s.io/kubernetes/test/e2e/framework/node"
	e2epod "k8s.io/kubernetes/test/e2e/framework/pod"
)

var _ = SIGDescribe("[Feature:NodeAuthenticator]", func() {

	f := framework.NewDefaultFramework("node-authn")
	var ns string
	var nodeIPs []string
	ginkgo.BeforeEach(func() {
		ns = f.Namespace.Name

		nodes, err := e2enode.GetBoundedReadySchedulableNodes(f.ClientSet, 1)
		framework.ExpectNoError(err)

		family := v1.IPv4Protocol
		if framework.TestContext.ClusterIsIPv6() {
			family = v1.IPv6Protocol
		}

		nodeIPs := e2enode.GetAddressesByTypeAndFamily(&nodes.Items[0], v1.NodeInternalIP, family)
		framework.ExpectNotEqual(len(nodeIPs), 0)

		// make sure ServiceAccount admission controller is enabled, so secret generation on SA creation works
		saName := "default"
		sa, err := f.ClientSet.CoreV1().ServiceAccounts(ns).Get(context.TODO(), saName, metav1.GetOptions{})
		framework.ExpectNoError(err, "failed to retrieve service account (%s:%s)", ns, saName)
		framework.ExpectNotEqual(len(sa.Secrets), 0)

	})

	ginkgo.It("The kubelet's main port 10250 should reject requests with no credentials", func() {
		pod := createNodeAuthTestPod(f)
		for _, nodeIP := range nodeIPs {
			// Anonymous authentication is disabled by default
			host := net.JoinHostPort(nodeIP, strconv.Itoa(ports.KubeletPort))
			result := framework.RunHostCmdOrDie(ns, pod.Name, fmt.Sprintf("curl -sIk -o /dev/null -w '%s' https://%s/metrics", "%{http_code}", host))
			gomega.Expect(result).To(gomega.Or(gomega.Equal("401"), gomega.Equal("403")), "the kubelet's main port 10250 should reject requests with no credentials")
		}
	})

	ginkgo.It("The kubelet can delegate ServiceAccount tokens to the API server", func() {
		ginkgo.By("create a new ServiceAccount for authentication")
		trueValue := true
		newSA := &v1.ServiceAccount{
			ObjectMeta: metav1.ObjectMeta{
				Namespace: ns,
				Name:      "node-auth-newsa",
			},
			AutomountServiceAccountToken: &trueValue,
		}
		_, err := f.ClientSet.CoreV1().ServiceAccounts(ns).Create(context.TODO(), newSA, metav1.CreateOptions{})
		framework.ExpectNoError(err, "failed to create service account (%s:%s)", ns, newSA.Name)

		pod := createNodeAuthTestPod(f)

		for _, nodeIP := range nodeIPs {
			host := net.JoinHostPort(nodeIP, strconv.Itoa(ports.KubeletPort))
			result := framework.RunHostCmdOrDie(ns,
				pod.Name,
				fmt.Sprintf("curl -sIk -o /dev/null -w '%s' --header \"Authorization: Bearer `%s`\" https://%s/metrics",
					"%{http_code}",
					"cat /var/run/secrets/kubernetes.io/serviceaccount/token",
					host))
			gomega.Expect(result).To(gomega.Or(gomega.Equal("401"), gomega.Equal("403")), "the kubelet can delegate ServiceAccount tokens to the API server")
		}
	})
})

func createNodeAuthTestPod(f *framework.Framework) *v1.Pod {
	pod := e2epod.NewAgnhostPod(f.Namespace.Name, "agnhost-pod", nil, nil, nil)
	pod.ObjectMeta.GenerateName = "test-node-authn-"
	return f.PodClient().CreateSync(pod)
}
<|endoftext|>


<commit_before>package e2e

import (
	"context"
	_ "embed"
	"encoding/json"

	"github.com/ghodss/yaml"
	apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
	k8serrors "k8s.io/apimachinery/pkg/api/errors"
	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime/schema"
	"k8s.io/client-go/dynamic"

	"github.com/operator-framework/api/pkg/operators/v1alpha1"
	"github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/clientset/versioned"
	"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/operatorclient"
	"github.com/operator-framework/operator-lifecycle-manager/test/e2e/ctx"
)

//go:embed testdata/vpa/crd.yaml
var vpaCRDRaw []byte

var _ = Describe("Installing bundles with new object types", func() {
	var (
		kubeClient     operatorclient.ClientInterface
		operatorClient versioned.Interface
		dynamicClient  dynamic.Interface
	)

	BeforeEach(func() {
		kubeClient = ctx.Ctx().KubeClient()
		operatorClient = ctx.Ctx().OperatorClient()
		dynamicClient = ctx.Ctx().DynamicClient()
	})

	AfterEach(func() {
		TearDown(testNamespace)
	})

	When("a bundle with a pdb, priorityclass, and VPA object is installed", func() {
		const (
			packageName = "busybox"
			channelName = "alpha"
			subName     = "test-subscription"
		)
		var vpaCRD unstructured.Unstructured

		BeforeEach(func() {
			By("first installing the VPA CRD on cluster")
			const (
				sourceName = "test-catalog"
				imageName  = "quay.io/olmtest/single-bundle-index:pdb"
			)

			// create VPA CRD on cluster
			Expect(vpaCRDRaw).ToNot(BeEmpty(), "could not read vpa bindata")
			data, err := yaml.YAMLToJSON(vpaCRDRaw)
			Expect(err).ToNot(HaveOccurred(), "could not convert vpa crd to json")

			err = json.Unmarshal(data, &vpaCRD)
			Expect(err).ToNot(HaveOccurred(), "could not convert vpa crd to unstructured")

			Eventually(func() error {
				err := ctx.Ctx().Client().Create(context.TODO(), &vpaCRD)
				if err != nil {
					if !k8serrors.IsAlreadyExists(err) {
						return err
					}
				}
				return nil
			}).Should(Succeed())

			// ensure vpa crd is established and accepted on the cluster before continuing
			Eventually(func() (bool, error) {
				crd, err := kubeClient.ApiextensionsInterface().ApiextensionsV1().CustomResourceDefinitions().Get(context.TODO(), vpaCRD.GetName(), metav1.GetOptions{})
				if err != nil {
					return false, err
				}
				return crdReady(&crd.Status), nil
			}).Should(BeTrue())

			var installPlanRef string
			source := &v1alpha1.CatalogSource{
				TypeMeta: metav1.TypeMeta{
					Kind:       v1alpha1.CatalogSourceKind,
					APIVersion: v1alpha1.CatalogSourceCRDAPIVersion,
				},
				ObjectMeta: metav1.ObjectMeta{
					Name:      sourceName,
					Namespace: testNamespace,
					Labels:    map[string]string{"olm.catalogSource": sourceName},
				},
				Spec: v1alpha1.CatalogSourceSpec{
					SourceType: v1alpha1.SourceTypeGrpc,
					Image:      imageName,
				},
			}

			Eventually(func() error {
				source, err = operatorClient.OperatorsV1alpha1().CatalogSources(source.GetNamespace()).Create(context.TODO(), source, metav1.CreateOptions{})
				return err
			}).Should(Succeed())

			// Create a Subscription for package
			_ = createSubscriptionForCatalog(operatorClient, source.GetNamespace(), subName, source.GetName(), packageName, channelName, "", v1alpha1.ApprovalAutomatic)

			// Wait for the Subscription to succeed
			sub, err := fetchSubscription(operatorClient, testNamespace, subName, subscriptionStateAtLatestChecker)
			Expect(err).ToNot(HaveOccurred(), "could not get subscription at latest status")

			installPlanRef = sub.Status.InstallPlanRef.Name

			// Wait for the installplan to complete (5 minute timeout)
			_, err = fetchInstallPlan(GinkgoT(), operatorClient, installPlanRef, buildInstallPlanPhaseCheckFunc(v1alpha1.InstallPlanPhaseComplete))
			Expect(err).ToNot(HaveOccurred(), "could not get installplan at complete phase")

			ctx.Ctx().Logf("install plan %s completed", installPlanRef)
		})

		It("should create the additional bundle objects", func() {
			const (
				vpaGroup          = "autoscaling.k8s.io"
				vpaVersion        = "v1"
				vpaResource       = "verticalpodautoscalers"
				pdbName           = "busybox-pdb"
				priorityClassName = "super-priority"
				vpaName           = "busybox-vpa"
			)

			var resource = schema.GroupVersionResource{
				Group:    vpaGroup,
				Version:  vpaVersion,
				Resource: vpaResource,
			}

			// confirm extra bundle objects are installed
			Eventually(func() error {
				_, err := kubeClient.KubernetesInterface().PolicyV1beta1().PodDisruptionBudgets(testNamespace).Get(context.TODO(), pdbName, metav1.GetOptions{})
				return err
			}).Should(Succeed(), "expected no error getting pdb object associated with CSV")

			Eventually(func() error {
				_, err := kubeClient.KubernetesInterface().SchedulingV1().PriorityClasses().Get(context.TODO(), priorityClassName, metav1.GetOptions{})
				return err
			}).Should(Succeed(), "expected no error getting priorityclass object associated with CSV")

			Eventually(func() error {
				_, err := dynamicClient.Resource(resource).Namespace(testNamespace).Get(context.TODO(), vpaName, metav1.GetOptions{})
				return err
			}).Should(Succeed(), "expected no error finding vpa object associated with csv")
		})

		AfterEach(func() {
			By("Deleting the VPA CRD")
			Eventually(func() error {
				err := ctx.Ctx().Client().Delete(context.TODO(), &vpaCRD)
				if k8serrors.IsNotFound(err) {
					return nil
				}
				return err
			}).Should(Succeed())
		})
	})
})

func crdReady(status *apiextensionsv1.CustomResourceDefinitionStatus) bool {
	if status == nil {
		return false
	}
	established, namesAccepted := false, false
	for _, cdt := range status.Conditions {
		switch cdt.Type {
		case apiextensionsv1.Established:
			if cdt.Status == apiextensionsv1.ConditionTrue {
				established = true
			}
		case apiextensionsv1.NamesAccepted:
			if cdt.Status == apiextensionsv1.ConditionTrue {
				namesAccepted = true
			}
		}
	}
	return established && namesAccepted
}
<commit_msg>fix: use v1 pdb object under e2e bundle test (#2398)<commit_after>package e2e

import (
	"context"
	_ "embed"
	"encoding/json"

	"github.com/ghodss/yaml"
	apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
	k8serrors "k8s.io/apimachinery/pkg/api/errors"
	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime/schema"
	"k8s.io/client-go/dynamic"

	"github.com/operator-framework/api/pkg/operators/v1alpha1"
	"github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/clientset/versioned"
	"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/operatorclient"
	"github.com/operator-framework/operator-lifecycle-manager/test/e2e/ctx"
)

//go:embed testdata/vpa/crd.yaml
var vpaCRDRaw []byte

var _ = Describe("Installing bundles with new object types", func() {
	var (
		kubeClient     operatorclient.ClientInterface
		operatorClient versioned.Interface
		dynamicClient  dynamic.Interface
	)

	BeforeEach(func() {
		kubeClient = ctx.Ctx().KubeClient()
		operatorClient = ctx.Ctx().OperatorClient()
		dynamicClient = ctx.Ctx().DynamicClient()
	})

	AfterEach(func() {
		TearDown(testNamespace)
	})

	When("a bundle with a pdb, priorityclass, and VPA object is installed", func() {
		const (
			packageName = "busybox"
			channelName = "alpha"
			subName     = "test-subscription"
		)
		var vpaCRD unstructured.Unstructured

		BeforeEach(func() {
			By("first installing the VPA CRD on cluster")
			const (
				sourceName = "test-catalog"
				imageName  = "quay.io/olmtest/single-bundle-index:pdb-v1"
			)

			// create VPA CRD on cluster
			Expect(vpaCRDRaw).ToNot(BeEmpty(), "could not read vpa bindata")
			data, err := yaml.YAMLToJSON(vpaCRDRaw)
			Expect(err).ToNot(HaveOccurred(), "could not convert vpa crd to json")

			err = json.Unmarshal(data, &vpaCRD)
			Expect(err).ToNot(HaveOccurred(), "could not convert vpa crd to unstructured")

			Eventually(func() error {
				err := ctx.Ctx().Client().Create(context.TODO(), &vpaCRD)
				if err != nil {
					if !k8serrors.IsAlreadyExists(err) {
						return err
					}
				}
				return nil
			}).Should(Succeed())

			// ensure vpa crd is established and accepted on the cluster before continuing
			Eventually(func() (bool, error) {
				crd, err := kubeClient.ApiextensionsInterface().ApiextensionsV1().CustomResourceDefinitions().Get(context.TODO(), vpaCRD.GetName(), metav1.GetOptions{})
				if err != nil {
					return false, err
				}
				return crdReady(&crd.Status), nil
			}).Should(BeTrue())

			var installPlanRef string
			source := &v1alpha1.CatalogSource{
				TypeMeta: metav1.TypeMeta{
					Kind:       v1alpha1.CatalogSourceKind,
					APIVersion: v1alpha1.CatalogSourceCRDAPIVersion,
				},
				ObjectMeta: metav1.ObjectMeta{
					Name:      sourceName,
					Namespace: testNamespace,
					Labels:    map[string]string{"olm.catalogSource": sourceName},
				},
				Spec: v1alpha1.CatalogSourceSpec{
					SourceType: v1alpha1.SourceTypeGrpc,
					Image:      imageName,
				},
			}

			Eventually(func() error {
				source, err = operatorClient.OperatorsV1alpha1().CatalogSources(source.GetNamespace()).Create(context.TODO(), source, metav1.CreateOptions{})
				return err
			}).Should(Succeed())

			// Create a Subscription for package
			_ = createSubscriptionForCatalog(operatorClient, source.GetNamespace(), subName, source.GetName(), packageName, channelName, "", v1alpha1.ApprovalAutomatic)

			// Wait for the Subscription to succeed
			sub, err := fetchSubscription(operatorClient, testNamespace, subName, subscriptionStateAtLatestChecker)
			Expect(err).ToNot(HaveOccurred(), "could not get subscription at latest status")

			installPlanRef = sub.Status.InstallPlanRef.Name

			// Wait for the installplan to complete (5 minute timeout)
			_, err = fetchInstallPlan(GinkgoT(), operatorClient, installPlanRef, buildInstallPlanPhaseCheckFunc(v1alpha1.InstallPlanPhaseComplete))
			Expect(err).ToNot(HaveOccurred(), "could not get installplan at complete phase")

			ctx.Ctx().Logf("install plan %s completed", installPlanRef)
		})

		It("should create the additional bundle objects", func() {
			const (
				vpaGroup          = "autoscaling.k8s.io"
				vpaVersion        = "v1"
				vpaResource       = "verticalpodautoscalers"
				pdbName           = "busybox-pdb"
				priorityClassName = "super-priority"
				vpaName           = "busybox-vpa"
			)

			var resource = schema.GroupVersionResource{
				Group:    vpaGroup,
				Version:  vpaVersion,
				Resource: vpaResource,
			}

			// confirm extra bundle objects are installed
			Eventually(func() error {
				_, err := kubeClient.KubernetesInterface().SchedulingV1().PriorityClasses().Get(context.TODO(), priorityClassName, metav1.GetOptions{})
				return err
			}).Should(Succeed(), "expected no error getting priorityclass object associated with CSV")

			Eventually(func() error {
				_, err := dynamicClient.Resource(resource).Namespace(testNamespace).Get(context.TODO(), vpaName, metav1.GetOptions{})
				return err
			}).Should(Succeed(), "expected no error finding vpa object associated with csv")

			Eventually(func() error {
				_, err := kubeClient.KubernetesInterface().PolicyV1().PodDisruptionBudgets(testNamespace).Get(context.TODO(), pdbName, metav1.GetOptions{})
				return err
			}).Should(Succeed(), "expected no error getting pdb object associated with CSV")
		})

		AfterEach(func() {
			By("Deleting the VPA CRD")
			Eventually(func() error {
				err := ctx.Ctx().Client().Delete(context.TODO(), &vpaCRD)
				if k8serrors.IsNotFound(err) {
					return nil
				}
				return err
			}).Should(Succeed())
		})
	})
})

func crdReady(status *apiextensionsv1.CustomResourceDefinitionStatus) bool {
	if status == nil {
		return false
	}
	established, namesAccepted := false, false
	for _, cdt := range status.Conditions {
		switch cdt.Type {
		case apiextensionsv1.Established:
			if cdt.Status == apiextensionsv1.ConditionTrue {
				established = true
			}
		case apiextensionsv1.NamesAccepted:
			if cdt.Status == apiextensionsv1.ConditionTrue {
				namesAccepted = true
			}
		}
	}
	return established && namesAccepted
}
<|endoftext|>


<commit_before>// Package igc implements an IGC parser.
package igc

import (
	"bufio"
	"errors"
	"fmt"
	"io"
	"strings"
	"time"

	"github.com/twpayne/go-geom"
)

var (
	ErrInvalidCharacter               = errors.New("invalid character")
	ErrInvalidCharactersBeforeARecord = errors.New("invalid characters before A record")
	ErrInvalidBRecord                 = errors.New("invalid B record")
	ErrInvalidHRecord                 = errors.New("invalid H record")
	ErrInvalidIRecord                 = errors.New("invalid I record")
	ErrEmptyLine                      = errors.New("empty line")
	ErrMissingARecord                 = errors.New("missing A record")
	ErrNoBRecords                     = errors.New("no B records")
	ErrOutOfRange                     = errors.New("out of range")
)

type Errors map[int]error

func (es Errors) Error() string {
	var ss []string
	for lineno, e := range es {
		ss = append(ss, fmt.Sprintf("%d: %s", lineno, e.Error()))
	}
	return strings.Join(ss, "\n")
}

// parseDec parses a decimal value in s[start:stop].
func parseDec(s string, start, stop int) (int, error) {
	result := 0
	for i := start; i < stop; i++ {
		if c := s[i]; '0' <= c && c <= '9' {
			result = 10*result + int(c) - '0'
		} else {
			return 0, ErrInvalidCharacter
		}
	}
	return result, nil
}

// parseDecInRange parsers a decimal value in s[start:stop], and returns an
// error if it is outside the range [min, max).
func parseDecInRange(s string, start, stop, min, max int) (int, error) {
	if result, err := parseDec(s, start, stop); err != nil {
		return result, err
	} else if result < min || max <= result {
		return result, ErrOutOfRange
	} else {
		return result, nil
	}
}

// parser contains the state of a parser.
type parser struct {
	state             int
	coords            []float64
	year, month, day  int
	startAt           time.Time
	lastDate          time.Time
	ladStart, ladStop int
	lodStart, lodStop int
	tdsStart, tdsStop int
	bRecordLen        int
}

// newParser creates a new parser.
func newParser() *parser {
	return &parser{bRecordLen: 35}
}

// parseB parses a B record from line and updates the state of p.
func (p *parser) parseB(line string) error {

	if len(line) != p.bRecordLen {
		return ErrInvalidBRecord
	}

	var err error

	var hour, minute, second, nsec int
	if hour, err = parseDecInRange(line, 1, 3, 0, 24); err != nil {
		return err
	}
	if minute, err = parseDecInRange(line, 3, 5, 0, 60); err != nil {
		return err
	}
	if second, err = parseDecInRange(line, 5, 7, 0, 60); err != nil {
		return err
	}
	if p.tdsStart != 0 {
		if decisecond, err := parseDecInRange(line, p.tdsStart, p.tdsStop, 0, 10); err != nil {
			return err
		} else {
			nsec = decisecond * 1e8
		}
	}
	date := time.Date(p.year, time.Month(p.month), p.day, hour, minute, second, nsec, time.UTC)
	if date.Before(p.lastDate) {
		p.day++
		date = time.Date(p.year, time.Month(p.month), p.day, hour, minute, second, nsec, time.UTC)
	}

	if p.startAt.IsZero() {
		p.startAt = date
	}

	var latDeg, latMilliMin int
	if latDeg, err = parseDecInRange(line, 7, 9, 0, 90); err != nil {
		return err
	}
	// special case: latMilliMin should be in the range [0, 60000) but a number of flight recorders generate latMilliMins of 60000
	// FIXME check what happens in negative (S, W) hemispheres
	if latMilliMin, err = parseDecInRange(line, 9, 14, 0, 60000+1); err != nil {
		return err
	}
	lat := float64(60000*latDeg+latMilliMin) / 60000.
	if p.ladStart != 0 {
		if lad, err := parseDec(line, p.ladStart, p.ladStop); err == nil {
			lat += float64(lad) / 6000000.
		} else {
			return err
		}
	}
	switch c := line[14]; c {
	case 'N':
	case 'S':
		lat = -lat
	default:
		return ErrInvalidCharacter
	}

	var lngDeg, lngMilliMin int
	if lngDeg, err = parseDecInRange(line, 15, 18, 0, 180); err != nil {
		return err
	}
	if lngMilliMin, err = parseDecInRange(line, 18, 23, 0, 60000+1); err != nil {
		return err
	}
	lng := float64(60000*lngDeg+lngMilliMin) / 60000.
	if p.lodStart != 0 {
		if lod, err := parseDec(line, p.lodStart, p.lodStop); err == nil {
			lng += float64(lod) / 6000000.
		} else {
			return err
		}
	}
	switch c := line[23]; c {
	case 'E':
	case 'W':
		lng = -lng
	default:
		return ErrInvalidCharacter
	}

	var pressureAlt, ellipsoidAlt int
	if pressureAlt, err = parseDec(line, 25, 30); err != nil {
		return err
	}
	if ellipsoidAlt, err = parseDec(line, 30, 35); err != nil {
		return err
	}
	_ = pressureAlt

	p.coords = append(p.coords, lng, lat, float64(ellipsoidAlt), float64(date.UnixNano())/1e9)
	p.lastDate = date

	return nil

}

// parseB parses an H record from line and updates the state of p.
func (p *parser) parseH(line string) error {
	switch {
	case strings.HasPrefix(line, "HFDTE"):
		return p.parseHFDTE(line)
	default:
		return nil
	}
}

// parseB parses an HFDTE record from line and updates the state of p.
func (p *parser) parseHFDTE(line string) error {
	var err error
	var day, month, year int
	if len(line) != 11 {
		return ErrInvalidHRecord
	}
	if day, err = parseDecInRange(line, 5, 7, 1, 31+1); err != nil {
		return err
	}
	if month, err = parseDecInRange(line, 7, 9, 1, 12+1); err != nil {
		return err
	}
	if year, err = parseDec(line, 9, 11); err != nil {
		return err
	}
	// FIXME check for invalid dates
	p.day = day
	p.month = month
	if year < 70 {
		p.year = 2000 + year
	} else {
		p.year = 1970 + year
	}
	return nil
}

// parseB parses an I record from line and updates the state of p.
func (p *parser) parseI(line string) error {
	var err error
	var n int
	if len(line) < 3 {
		return ErrInvalidIRecord
	}
	if n, err = parseDec(line, 1, 3); err != nil {
		return err
	}
	if len(line) < 7*n+3 {
		return ErrInvalidIRecord
	}
	for i := 0; i < n; i++ {
		var start, stop int
		if start, err = parseDec(line, 7*i+3, 7*i+5); err != nil {
			return err
		}
		if stop, err = parseDec(line, 7*i+5, 7*i+7); err != nil {
			return err
		}
		if start != p.bRecordLen+1 || stop < start {
			return ErrInvalidIRecord
		}
		p.bRecordLen = stop
		switch line[7*i+7 : 7*i+10] {
		case "LAD":
			p.ladStart, p.ladStop = start-1, stop
		case "LOD":
			p.lodStart, p.lodStop = start-1, stop
		case "TDS":
			p.tdsStart, p.tdsStop = start-1, stop
		}
	}
	return nil
}

// parseLine parses a single record from line and updates the state of p.
func (p *parser) parseLine(line string) error {
	switch line[0] {
	case 'B':
		return p.parseB(line)
	case 'H':
		return p.parseH(line)
	case 'I':
		return p.parseI(line)
	default:
		return nil
	}
}

// doParse reads r, parsers all the records it finds, updating the state of p.
func doParse(r io.Reader) (*parser, Errors) {
	errors := make(Errors)
	p := newParser()
	s := bufio.NewScanner(r)
	foundA := false
	leadingNoise := false
	for lineno := 1; s.Scan(); lineno++ {
		line := s.Text()
		if len(line) == 0 {
			// errors[lineno] = ErrEmptyLine
		} else if foundA {
			if err := p.parseLine(line); err != nil {
				errors[lineno] = err
			}
		} else {
			if c := line[0]; c == 'A' {
				foundA = true
			} else if 'A' <= c && c <= 'Z' {
				// All records that start with an uppercase character must be valid.
				leadingNoise = true
				continue
			} else if i := strings.IndexRune(line, 'A'); i != -1 {
				// Strip any leading noise.
				// The noise must include at least one unprintable character (like XOFF or a fragment of a Unicode BOM).
				for _, c := range line[:i] {
					if !(c == ' ' || ('A' <= c && c <= 'Z')) {
						foundA = true
						leadingNoise = true
						line = line[i:]
						break
					}
				}
			}
		}
	}
	if !foundA {
		errors[1] = ErrMissingARecord
	} else if leadingNoise {
		errors[1] = ErrInvalidCharactersBeforeARecord
	}
	return p, errors
}

// Read reads a igc.T from r, which should contain IGC records.
func Read(r io.Reader) (*geom.LineString, error) {
	p, errors := doParse(r)
	if len(errors) != 0 {
		return nil, errors
	}
	if len(p.coords) == 0 {
		return nil, ErrNoBRecords
	}
	return geom.NewLineStringFlat(geom.XYZM, p.coords), nil
}
<commit_msg>Store pressure altitude in 5th dimension<commit_after>// Package igc implements an IGC parser.
package igc

import (
	"bufio"
	"errors"
	"fmt"
	"io"
	"strings"
	"time"

	"github.com/twpayne/go-geom"
)

var (
	ErrInvalidCharacter               = errors.New("invalid character")
	ErrInvalidCharactersBeforeARecord = errors.New("invalid characters before A record")
	ErrInvalidBRecord                 = errors.New("invalid B record")
	ErrInvalidHRecord                 = errors.New("invalid H record")
	ErrInvalidIRecord                 = errors.New("invalid I record")
	ErrEmptyLine                      = errors.New("empty line")
	ErrMissingARecord                 = errors.New("missing A record")
	ErrNoBRecords                     = errors.New("no B records")
	ErrOutOfRange                     = errors.New("out of range")
)

type Errors map[int]error

func (es Errors) Error() string {
	var ss []string
	for lineno, e := range es {
		ss = append(ss, fmt.Sprintf("%d: %s", lineno, e.Error()))
	}
	return strings.Join(ss, "\n")
}

// parseDec parses a decimal value in s[start:stop].
func parseDec(s string, start, stop int) (int, error) {
	result := 0
	for i := start; i < stop; i++ {
		if c := s[i]; '0' <= c && c <= '9' {
			result = 10*result + int(c) - '0'
		} else {
			return 0, ErrInvalidCharacter
		}
	}
	return result, nil
}

// parseDecInRange parsers a decimal value in s[start:stop], and returns an
// error if it is outside the range [min, max).
func parseDecInRange(s string, start, stop, min, max int) (int, error) {
	if result, err := parseDec(s, start, stop); err != nil {
		return result, err
	} else if result < min || max <= result {
		return result, ErrOutOfRange
	} else {
		return result, nil
	}
}

// parser contains the state of a parser.
type parser struct {
	state             int
	coords            []float64
	year, month, day  int
	startAt           time.Time
	lastDate          time.Time
	ladStart, ladStop int
	lodStart, lodStop int
	tdsStart, tdsStop int
	bRecordLen        int
}

// newParser creates a new parser.
func newParser() *parser {
	return &parser{bRecordLen: 35}
}

// parseB parses a B record from line and updates the state of p.
func (p *parser) parseB(line string) error {

	if len(line) != p.bRecordLen {
		return ErrInvalidBRecord
	}

	var err error

	var hour, minute, second, nsec int
	if hour, err = parseDecInRange(line, 1, 3, 0, 24); err != nil {
		return err
	}
	if minute, err = parseDecInRange(line, 3, 5, 0, 60); err != nil {
		return err
	}
	if second, err = parseDecInRange(line, 5, 7, 0, 60); err != nil {
		return err
	}
	if p.tdsStart != 0 {
		if decisecond, err := parseDecInRange(line, p.tdsStart, p.tdsStop, 0, 10); err != nil {
			return err
		} else {
			nsec = decisecond * 1e8
		}
	}
	date := time.Date(p.year, time.Month(p.month), p.day, hour, minute, second, nsec, time.UTC)
	if date.Before(p.lastDate) {
		p.day++
		date = time.Date(p.year, time.Month(p.month), p.day, hour, minute, second, nsec, time.UTC)
	}

	if p.startAt.IsZero() {
		p.startAt = date
	}

	var latDeg, latMilliMin int
	if latDeg, err = parseDecInRange(line, 7, 9, 0, 90); err != nil {
		return err
	}
	// special case: latMilliMin should be in the range [0, 60000) but a number of flight recorders generate latMilliMins of 60000
	// FIXME check what happens in negative (S, W) hemispheres
	if latMilliMin, err = parseDecInRange(line, 9, 14, 0, 60000+1); err != nil {
		return err
	}
	lat := float64(60000*latDeg+latMilliMin) / 60000.
	if p.ladStart != 0 {
		if lad, err := parseDec(line, p.ladStart, p.ladStop); err == nil {
			lat += float64(lad) / 6000000.
		} else {
			return err
		}
	}
	switch c := line[14]; c {
	case 'N':
	case 'S':
		lat = -lat
	default:
		return ErrInvalidCharacter
	}

	var lngDeg, lngMilliMin int
	if lngDeg, err = parseDecInRange(line, 15, 18, 0, 180); err != nil {
		return err
	}
	if lngMilliMin, err = parseDecInRange(line, 18, 23, 0, 60000+1); err != nil {
		return err
	}
	lng := float64(60000*lngDeg+lngMilliMin) / 60000.
	if p.lodStart != 0 {
		if lod, err := parseDec(line, p.lodStart, p.lodStop); err == nil {
			lng += float64(lod) / 6000000.
		} else {
			return err
		}
	}
	switch c := line[23]; c {
	case 'E':
	case 'W':
		lng = -lng
	default:
		return ErrInvalidCharacter
	}

	var pressureAlt, ellipsoidAlt int
	if pressureAlt, err = parseDec(line, 25, 30); err != nil {
		return err
	}
	if ellipsoidAlt, err = parseDec(line, 30, 35); err != nil {
		return err
	}

	p.coords = append(p.coords, lng, lat, float64(ellipsoidAlt), float64(date.UnixNano())/1e9, float64(pressureAlt))
	p.lastDate = date

	return nil

}

// parseB parses an H record from line and updates the state of p.
func (p *parser) parseH(line string) error {
	switch {
	case strings.HasPrefix(line, "HFDTE"):
		return p.parseHFDTE(line)
	default:
		return nil
	}
}

// parseB parses an HFDTE record from line and updates the state of p.
func (p *parser) parseHFDTE(line string) error {
	var err error
	var day, month, year int
	if len(line) != 11 {
		return ErrInvalidHRecord
	}
	if day, err = parseDecInRange(line, 5, 7, 1, 31+1); err != nil {
		return err
	}
	if month, err = parseDecInRange(line, 7, 9, 1, 12+1); err != nil {
		return err
	}
	if year, err = parseDec(line, 9, 11); err != nil {
		return err
	}
	// FIXME check for invalid dates
	p.day = day
	p.month = month
	if year < 70 {
		p.year = 2000 + year
	} else {
		p.year = 1970 + year
	}
	return nil
}

// parseB parses an I record from line and updates the state of p.
func (p *parser) parseI(line string) error {
	var err error
	var n int
	if len(line) < 3 {
		return ErrInvalidIRecord
	}
	if n, err = parseDec(line, 1, 3); err != nil {
		return err
	}
	if len(line) < 7*n+3 {
		return ErrInvalidIRecord
	}
	for i := 0; i < n; i++ {
		var start, stop int
		if start, err = parseDec(line, 7*i+3, 7*i+5); err != nil {
			return err
		}
		if stop, err = parseDec(line, 7*i+5, 7*i+7); err != nil {
			return err
		}
		if start != p.bRecordLen+1 || stop < start {
			return ErrInvalidIRecord
		}
		p.bRecordLen = stop
		switch line[7*i+7 : 7*i+10] {
		case "LAD":
			p.ladStart, p.ladStop = start-1, stop
		case "LOD":
			p.lodStart, p.lodStop = start-1, stop
		case "TDS":
			p.tdsStart, p.tdsStop = start-1, stop
		}
	}
	return nil
}

// parseLine parses a single record from line and updates the state of p.
func (p *parser) parseLine(line string) error {
	switch line[0] {
	case 'B':
		return p.parseB(line)
	case 'H':
		return p.parseH(line)
	case 'I':
		return p.parseI(line)
	default:
		return nil
	}
}

// doParse reads r, parsers all the records it finds, updating the state of p.
func doParse(r io.Reader) (*parser, Errors) {
	errors := make(Errors)
	p := newParser()
	s := bufio.NewScanner(r)
	foundA := false
	leadingNoise := false
	for lineno := 1; s.Scan(); lineno++ {
		line := s.Text()
		if len(line) == 0 {
			// errors[lineno] = ErrEmptyLine
		} else if foundA {
			if err := p.parseLine(line); err != nil {
				errors[lineno] = err
			}
		} else {
			if c := line[0]; c == 'A' {
				foundA = true
			} else if 'A' <= c && c <= 'Z' {
				// All records that start with an uppercase character must be valid.
				leadingNoise = true
				continue
			} else if i := strings.IndexRune(line, 'A'); i != -1 {
				// Strip any leading noise.
				// The noise must include at least one unprintable character (like XOFF or a fragment of a Unicode BOM).
				for _, c := range line[:i] {
					if !(c == ' ' || ('A' <= c && c <= 'Z')) {
						foundA = true
						leadingNoise = true
						line = line[i:]
						break
					}
				}
			}
		}
	}
	if !foundA {
		errors[1] = ErrMissingARecord
	} else if leadingNoise {
		errors[1] = ErrInvalidCharactersBeforeARecord
	}
	return p, errors
}

// Read reads a igc.T from r, which should contain IGC records.
func Read(r io.Reader) (*geom.LineString, error) {
	p, errors := doParse(r)
	if len(errors) != 0 {
		return nil, errors
	}
	if len(p.coords) == 0 {
		return nil, ErrNoBRecords
	}
	return geom.NewLineStringFlat(geom.Layout(5), p.coords), nil
}
<|endoftext|>


<commit_before>package api

// TokenAuth is used to perform token backend operations on Vault.
type TokenAuth struct {
	c *Client
}

// Token is used to return the client for logical-backend API calls.
func (a *Auth) Token() *TokenAuth {
	return &TokenAuth{c: a.c}
}

func (c *TokenAuth) Create(opts *TokenCreateRequest) (*Secret, error) {
	r := c.c.NewRequest("POST", "/v1/auth/token/create")
	if err := r.SetJSONBody(opts); err != nil {
		return nil, err
	}

	resp, err := c.c.RawRequest(r)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	return ParseSecret(resp.Body)
}

func (c *TokenAuth) Lookup(token string) (*Secret, error) {
	r := c.c.NewRequest("GET", "/v1/auth/token/lookup/"+token)

	resp, err := c.c.RawRequest(r)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	return ParseSecret(resp.Body)
}

func (c *TokenAuth) LookupSelf() (*Secret, error) {
	r := c.c.NewRequest("GET", "/v1/auth/token/lookup-self")

	resp, err := c.c.RawRequest(r)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	return ParseSecret(resp.Body)
}

func (c *TokenAuth) Renew(token string, increment int) (*Secret, error) {
	r := c.c.NewRequest("PUT", "/v1/auth/token/renew/"+token)

	body := map[string]interface{}{"increment": increment}
	if err := r.SetJSONBody(body); err != nil {
		return nil, err
	}

	resp, err := c.c.RawRequest(r)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	return ParseSecret(resp.Body)
}

func (c *TokenAuth) RenewSelf(increment int) (*Secret, error) {
	r := c.c.NewRequest("PUT", "/v1/auth/token/renew-self")

	body := map[string]interface{}{"increment": increment}
	if err := r.SetJSONBody(body); err != nil {
		return nil, err
	}

	resp, err := c.c.RawRequest(r)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	return ParseSecret(resp.Body)
}

func (c *TokenAuth) RevokeOrphan(token string) error {
	r := c.c.NewRequest("PUT", "/v1/auth/token/revoke-orphan/"+token)
	resp, err := c.c.RawRequest(r)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	return nil
}

func (c *TokenAuth) RevokePrefix(token string) error {
	r := c.c.NewRequest("PUT", "/v1/auth/token/revoke-prefix/"+token)
	resp, err := c.c.RawRequest(r)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	return nil
}

func (c *TokenAuth) RevokeSelf() error {
	r := c.c.NewRequest("PUT", "/v1/auth/token/revoke-self")
	resp, err := c.c.RawRequest(r)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	return nil
}

func (c *TokenAuth) RevokeTree(token string) error {
	r := c.c.NewRequest("PUT", "/v1/auth/token/revoke/"+token)
	resp, err := c.c.RawRequest(r)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	return nil
}

// TokenCreateRequest is the options structure for creating a token.
type TokenCreateRequest struct {
	ID              string            `json:"id,omitempty"`
	Policies        []string          `json:"policies,omitempty"`
	Metadata        map[string]string `json:"meta,omitempty"`
	Lease           string            `json:"lease,omitempty"`
	TTL             string            `json:"ttl,omitempty"`
	NoParent        bool              `json:"no_parent,omitempty"`
	NoDefaultPolicy bool              `json:"no_default_policy,omitempty"`
	DisplayName     string            `json:"display_name"`
	NumUses         int               `json:"num_uses"`
}
<commit_msg>Add some documentation to the API revoke functions<commit_after>package api

// TokenAuth is used to perform token backend operations on Vault.
type TokenAuth struct {
	c *Client
}

// Token is used to return the client for logical-backend API calls.
func (a *Auth) Token() *TokenAuth {
	return &TokenAuth{c: a.c}
}

func (c *TokenAuth) Create(opts *TokenCreateRequest) (*Secret, error) {
	r := c.c.NewRequest("POST", "/v1/auth/token/create")
	if err := r.SetJSONBody(opts); err != nil {
		return nil, err
	}

	resp, err := c.c.RawRequest(r)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	return ParseSecret(resp.Body)
}

func (c *TokenAuth) Lookup(token string) (*Secret, error) {
	r := c.c.NewRequest("GET", "/v1/auth/token/lookup/"+token)

	resp, err := c.c.RawRequest(r)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	return ParseSecret(resp.Body)
}

func (c *TokenAuth) LookupSelf() (*Secret, error) {
	r := c.c.NewRequest("GET", "/v1/auth/token/lookup-self")

	resp, err := c.c.RawRequest(r)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	return ParseSecret(resp.Body)
}

func (c *TokenAuth) Renew(token string, increment int) (*Secret, error) {
	r := c.c.NewRequest("PUT", "/v1/auth/token/renew/"+token)

	body := map[string]interface{}{"increment": increment}
	if err := r.SetJSONBody(body); err != nil {
		return nil, err
	}

	resp, err := c.c.RawRequest(r)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	return ParseSecret(resp.Body)
}

func (c *TokenAuth) RenewSelf(increment int) (*Secret, error) {
	r := c.c.NewRequest("PUT", "/v1/auth/token/renew-self")

	body := map[string]interface{}{"increment": increment}
	if err := r.SetJSONBody(body); err != nil {
		return nil, err
	}

	resp, err := c.c.RawRequest(r)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	return ParseSecret(resp.Body)
}

// RevokeOrphan revokes a token without revoking the tree underneath it (so
// child tokens are orphaned rather than revoked)
func (c *TokenAuth) RevokeOrphan(token string) error {
	r := c.c.NewRequest("PUT", "/v1/auth/token/revoke-orphan/"+token)
	resp, err := c.c.RawRequest(r)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	return nil
}

// RevokePrefix revokes a token based on a prefix, which can be used to revoke
// e.g. all tokens issued by a certain credential mount
func (c *TokenAuth) RevokePrefix(token string) error {
	r := c.c.NewRequest("PUT", "/v1/auth/token/revoke-prefix/"+token)
	resp, err := c.c.RawRequest(r)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	return nil
}

// RevokeSelf revokes the token making the call
func (c *TokenAuth) RevokeSelf() error {
	r := c.c.NewRequest("PUT", "/v1/auth/token/revoke-self")
	resp, err := c.c.RawRequest(r)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	return nil
}

// RevokeTree is the "normal" revoke operation that revokes the given token and
// the entire tree underneath -- all of its child tokens, their child tokens,
// etc.
func (c *TokenAuth) RevokeTree(token string) error {
	r := c.c.NewRequest("PUT", "/v1/auth/token/revoke/"+token)
	resp, err := c.c.RawRequest(r)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	return nil
}

// TokenCreateRequest is the options structure for creating a token.
type TokenCreateRequest struct {
	ID              string            `json:"id,omitempty"`
	Policies        []string          `json:"policies,omitempty"`
	Metadata        map[string]string `json:"meta,omitempty"`
	Lease           string            `json:"lease,omitempty"`
	TTL             string            `json:"ttl,omitempty"`
	NoParent        bool              `json:"no_parent,omitempty"`
	NoDefaultPolicy bool              `json:"no_default_policy,omitempty"`
	DisplayName     string            `json:"display_name"`
	NumUses         int               `json:"num_uses"`
}
<|endoftext|>