<commit_before>/*
Copyright 2021 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 notes

import (
	"fmt"
	"os"
	"strings"
	"sync"
	"time"

	"github.com/pkg/errors"

	"k8s.io/release/pkg/notes/options"

	"github.com/cheggaaa/pb/v3"
	"github.com/go-git/go-git/v5"
	"github.com/go-git/go-git/v5/plumbing"
	gitobject "github.com/go-git/go-git/v5/plumbing/object"
	"github.com/mattn/go-isatty"
	"github.com/nozzle/throttler"
	"github.com/sirupsen/logrus"
)

type commitPrPair struct {
	Commit *gitobject.Commit
	PrNum  int
}

type releaseNotesAggregator struct {
	releaseNotes *ReleaseNotes
	sync.RWMutex
}

func (g *Gatherer) ListReleaseNotesV2() (*ReleaseNotes, error) {
	// left parent of Git commits is always the main branch parent
	pairs, err := g.listLeftParentCommits(g.options)
	if err != nil {
		return nil, errors.Wrap(err, "listing offline commits")
	}

	// load map providers specified in options
	mapProviders := []MapProvider{}
	for _, initString := range g.options.MapProviderStrings {
		provider, err := NewProviderFromInitString(initString)
		if err != nil {
			return nil, errors.Wrap(err, "while getting release notes map providers")
		}
		mapProviders = append(mapProviders, provider)
	}

	t := throttler.New(maxParallelRequests, len(pairs))

	aggregator := releaseNotesAggregator{
		releaseNotes: NewReleaseNotes(),
	}

	pairsCount := len(pairs)
	logrus.Infof("processing release notes for %d commits", pairsCount)

	// display progress bar in stdout, since stderr is used by logger
	bar := pb.New(pairsCount).SetWriter(os.Stdout)

	// only display progress bar in user TTY
	if isatty.IsTerminal(os.Stdout.Fd()) {
		bar.Start()
	}

	for _, pair := range pairs {
		// pair needs to be scoped in parameter so that the specific variable read
		// happens when the goroutine is declared, not when referenced inside
		go func(pair *commitPrPair) {
			noteMaps := []*ReleaseNotesMap{}
			for _, provider := range mapProviders {
				noteMaps, err = provider.GetMapsForPR(pair.PrNum)
				if err != nil {
					logrus.WithFields(logrus.Fields{
						"pr": pair.PrNum,
					}).Errorf("ignore err: %v", err)
					noteMaps = []*ReleaseNotesMap{}
				}
			}

			releaseNote, err := g.buildReleaseNote(pair)
			if err == nil {
				if releaseNote != nil {
					for _, noteMap := range noteMaps {
						if err := releaseNote.ApplyMap(noteMap, g.options.AddMarkdownLinks); err != nil {
							logrus.WithFields(logrus.Fields{
								"pr": pair.PrNum,
							}).Errorf("ignore err: %v", err)
						}
					}
					logrus.WithFields(logrus.Fields{
						"pr":   pair.PrNum,
						"note": releaseNote.Text,
					}).Debugf("finalized release note")
					aggregator.Lock()
					aggregator.releaseNotes.Set(pair.PrNum, releaseNote)
					aggregator.Unlock()
				} else {
					logrus.WithFields(logrus.Fields{
						"pr": pair.PrNum,
					}).Debugf("skip: empty release note")
				}
			} else {
				logrus.WithFields(logrus.Fields{
					"sha": pair.Commit.Hash.String(),
					"pr":  pair.PrNum,
				}).Errorf("err: %v", err)
			}
			bar.Increment()
			t.Done(nil)
		}(pair)

		if t.Throttle() > 0 {
			break
		}
	}

	if err := t.Err(); err != nil {
		return nil, err
	}

	bar.Finish()

	return aggregator.releaseNotes, nil
}

func (g *Gatherer) buildReleaseNote(pair *commitPrPair) (*ReleaseNote, error) {
	pr, _, err := g.client.GetPullRequest(g.context, g.options.GithubOrg, g.options.GithubRepo, pair.PrNum)
	if err != nil {
		return nil, err
	}

	prBody := pr.GetBody()

	if MatchesExcludeFilter(prBody) {
		return nil, nil
	}

	text, err := noteTextFromString(prBody)
	if err != nil {
		logrus.WithFields(logrus.Fields{
			"sha": pair.Commit.Hash.String(),
			"pr":  pair.PrNum,
		}).Debugf("ignore err: %v", err)
		return nil, nil
	}

	documentation := DocumentationFromString(prBody)

	author := pr.GetUser().GetLogin()
	authorURL := pr.GetUser().GetHTMLURL()
	prURL := pr.GetHTMLURL()
	isFeature := hasString(labelsWithPrefix(pr, "kind"), "feature")
	noteSuffix := prettifySIGList(labelsWithPrefix(pr, "sig"))

	isDuplicateSIG := false
	if len(labelsWithPrefix(pr, "sig")) > 1 {
		isDuplicateSIG = true
	}

	isDuplicateKind := false
	if len(labelsWithPrefix(pr, "kind")) > 1 {
		isDuplicateKind = true
	}

	// TODO(wilsonehusin): extract / follow original in ReleasenoteFromCommit
	indented := strings.ReplaceAll(text, "\n", "\n  ")
	markdown := fmt.Sprintf("%s (#%d, @%s)",
		indented, pr.GetNumber(), author)
	if g.options.AddMarkdownLinks {
		markdown = fmt.Sprintf("%s ([#%d](%s), [@%s](%s))",
			indented, pr.GetNumber(), prURL, author, authorURL)
	}

	if noteSuffix != "" {
		markdown = fmt.Sprintf("%s [%s]", markdown, noteSuffix)
	}

	// Uppercase the first character of the markdown to make it look uniform
	markdown = capitalizeString(markdown)

	return &ReleaseNote{
		Commit:         pair.Commit.Hash.String(),
		Text:           text,
		Markdown:       markdown,
		Documentation:  documentation,
		Author:         author,
		AuthorURL:      authorURL,
		PrURL:          prURL,
		PrNumber:       pr.GetNumber(),
		SIGs:           labelsWithPrefix(pr, "sig"),
		Kinds:          labelsWithPrefix(pr, "kind"),
		Areas:          labelsWithPrefix(pr, "area"),
		Feature:        isFeature,
		Duplicate:      isDuplicateSIG,
		DuplicateKind:  isDuplicateKind,
		ActionRequired: labelExactMatch(pr, "release-note-action-required"),
		DoNotPublish:   labelExactMatch(pr, "release-note-none"),
	}, nil
}

func (g *Gatherer) listLeftParentCommits(opts *options.Options) ([]*commitPrPair, error) {
	localRepository, err := git.PlainOpen(opts.RepoPath)
	if err != nil {
		return nil, err
	}

	// opts.StartSHA points to a tag (e.g. 1.20.0) which is on a release branch (e.g. release-1.20)
	// this means traveling through commit history from opts.EndSHA will never reach opts.StartSHA

	// the stopping point to be set should be the last shared commit between release branch and primary (master) branch
	// usually, following the left / first parents, it would be

	// ^ master
	// |
	// * tag: 1.21.0-alpha.x / 1.21.0-beta.y
	// |
	// : :
	// | |
	// | * tag: v1.20.0, some merge commit pointed by opts.StartSHA
	// | |
	// | * Anago GCB release commit (begin branch out of release-1.20)
	// |/
	// x last shared commit

	// merge base would resolve to last shared commit, marked by (x)

	endCommit, err := localRepository.CommitObject(plumbing.NewHash(opts.EndSHA))
	if err != nil {
		return nil, errors.Wrap(err, "finding commit of EndSHA")
	}

	startCommit, err := localRepository.CommitObject(plumbing.NewHash(opts.StartSHA))
	if err != nil {
		return nil, errors.Wrap(err, "finding commit of StartSHA")
	}

	logrus.Debugf("finding merge base (last shared commit) between the two SHAs")
	startTime := time.Now()
	lastSharedCommits, err := endCommit.MergeBase(startCommit)
	if err != nil {
		return nil, errors.Wrap(err, "finding shared commits")
	}
	if len(lastSharedCommits) == 0 {
		return nil, fmt.Errorf("no shared commits between the provided SHAs")
	}
	logrus.Debugf("found merge base in %v", time.Since(startTime))

	stopHash := lastSharedCommits[0].Hash
	logrus.Infof("will stop at %s", stopHash.String())

	currentTagHash := plumbing.NewHash(opts.EndSHA)

	pairs := []*commitPrPair{}
	hashPointer := currentTagHash
	for hashPointer != stopHash {
		hashString := hashPointer.String()

		// Find and collect commit objects
		commitPointer, err := localRepository.CommitObject(hashPointer)
		if err != nil {
			return nil, errors.Wrap(err, "finding CommitObject")
		}

		// Find and collect PR number from commit message
		prNums, err := prsNumForCommitFromMessage(commitPointer.Message)
		if err == errNoPRIDFoundInCommitMessage {
			logrus.WithFields(logrus.Fields{
				"sha": hashString,
			}).Debug("no associated PR found")

			// Advance pointer based on left parent
			hashPointer = commitPointer.ParentHashes[0]
			continue
		}
		if err != nil {
			logrus.WithFields(logrus.Fields{
				"sha": hashString,
			}).Warnf("ignore err: %v", err)

			// Advance pointer based on left parent
			hashPointer = commitPointer.ParentHashes[0]
			continue
		}
		logrus.WithFields(logrus.Fields{
			"sha": hashString,
			"prs": prNums,
		}).Debug("found PR from commit")

		// Only taking the first one, assuming they are merged by Prow
		pairs = append(pairs, &commitPrPair{Commit: commitPointer, PrNum: prNums[0]})

		// Advance pointer based on left parent
		hashPointer = commitPointer.ParentHashes[0]
	}

	return pairs, nil
}
<commit_msg>replace pkg/errors<commit_after>/*
Copyright 2021 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 notes

import (
	"fmt"
	"os"
	"strings"
	"sync"
	"time"

	"k8s.io/release/pkg/notes/options"

	"github.com/cheggaaa/pb/v3"
	"github.com/go-git/go-git/v5"
	"github.com/go-git/go-git/v5/plumbing"
	gitobject "github.com/go-git/go-git/v5/plumbing/object"
	"github.com/mattn/go-isatty"
	"github.com/nozzle/throttler"
	"github.com/sirupsen/logrus"
)

type commitPrPair struct {
	Commit *gitobject.Commit
	PrNum  int
}

type releaseNotesAggregator struct {
	releaseNotes *ReleaseNotes
	sync.RWMutex
}

func (g *Gatherer) ListReleaseNotesV2() (*ReleaseNotes, error) {
	// left parent of Git commits is always the main branch parent
	pairs, err := g.listLeftParentCommits(g.options)
	if err != nil {
		return nil, fmt.Errorf("listing offline commits: %w", err)
	}

	// load map providers specified in options
	mapProviders := []MapProvider{}
	for _, initString := range g.options.MapProviderStrings {
		provider, err := NewProviderFromInitString(initString)
		if err != nil {
			return nil, fmt.Errorf("while getting release notes map providers: %w", err)
		}
		mapProviders = append(mapProviders, provider)
	}

	t := throttler.New(maxParallelRequests, len(pairs))

	aggregator := releaseNotesAggregator{
		releaseNotes: NewReleaseNotes(),
	}

	pairsCount := len(pairs)
	logrus.Infof("processing release notes for %d commits", pairsCount)

	// display progress bar in stdout, since stderr is used by logger
	bar := pb.New(pairsCount).SetWriter(os.Stdout)

	// only display progress bar in user TTY
	if isatty.IsTerminal(os.Stdout.Fd()) {
		bar.Start()
	}

	for _, pair := range pairs {
		// pair needs to be scoped in parameter so that the specific variable read
		// happens when the goroutine is declared, not when referenced inside
		go func(pair *commitPrPair) {
			noteMaps := []*ReleaseNotesMap{}
			for _, provider := range mapProviders {
				noteMaps, err = provider.GetMapsForPR(pair.PrNum)
				if err != nil {
					logrus.WithFields(logrus.Fields{
						"pr": pair.PrNum,
					}).Errorf("ignore err: %v", err)
					noteMaps = []*ReleaseNotesMap{}
				}
			}

			releaseNote, err := g.buildReleaseNote(pair)
			if err == nil {
				if releaseNote != nil {
					for _, noteMap := range noteMaps {
						if err := releaseNote.ApplyMap(noteMap, g.options.AddMarkdownLinks); err != nil {
							logrus.WithFields(logrus.Fields{
								"pr": pair.PrNum,
							}).Errorf("ignore err: %v", err)
						}
					}
					logrus.WithFields(logrus.Fields{
						"pr":   pair.PrNum,
						"note": releaseNote.Text,
					}).Debugf("finalized release note")
					aggregator.Lock()
					aggregator.releaseNotes.Set(pair.PrNum, releaseNote)
					aggregator.Unlock()
				} else {
					logrus.WithFields(logrus.Fields{
						"pr": pair.PrNum,
					}).Debugf("skip: empty release note")
				}
			} else {
				logrus.WithFields(logrus.Fields{
					"sha": pair.Commit.Hash.String(),
					"pr":  pair.PrNum,
				}).Errorf("err: %v", err)
			}
			bar.Increment()
			t.Done(nil)
		}(pair)

		if t.Throttle() > 0 {
			break
		}
	}

	if err := t.Err(); err != nil {
		return nil, err
	}

	bar.Finish()

	return aggregator.releaseNotes, nil
}

func (g *Gatherer) buildReleaseNote(pair *commitPrPair) (*ReleaseNote, error) {
	pr, _, err := g.client.GetPullRequest(g.context, g.options.GithubOrg, g.options.GithubRepo, pair.PrNum)
	if err != nil {
		return nil, err
	}

	prBody := pr.GetBody()

	if MatchesExcludeFilter(prBody) {
		return nil, nil
	}

	text, err := noteTextFromString(prBody)
	if err != nil {
		logrus.WithFields(logrus.Fields{
			"sha": pair.Commit.Hash.String(),
			"pr":  pair.PrNum,
		}).Debugf("ignore err: %v", err)
		return nil, nil
	}

	documentation := DocumentationFromString(prBody)

	author := pr.GetUser().GetLogin()
	authorURL := pr.GetUser().GetHTMLURL()
	prURL := pr.GetHTMLURL()
	isFeature := hasString(labelsWithPrefix(pr, "kind"), "feature")
	noteSuffix := prettifySIGList(labelsWithPrefix(pr, "sig"))

	isDuplicateSIG := false
	if len(labelsWithPrefix(pr, "sig")) > 1 {
		isDuplicateSIG = true
	}

	isDuplicateKind := false
	if len(labelsWithPrefix(pr, "kind")) > 1 {
		isDuplicateKind = true
	}

	// TODO(wilsonehusin): extract / follow original in ReleasenoteFromCommit
	indented := strings.ReplaceAll(text, "\n", "\n  ")
	markdown := fmt.Sprintf("%s (#%d, @%s)",
		indented, pr.GetNumber(), author)
	if g.options.AddMarkdownLinks {
		markdown = fmt.Sprintf("%s ([#%d](%s), [@%s](%s))",
			indented, pr.GetNumber(), prURL, author, authorURL)
	}

	if noteSuffix != "" {
		markdown = fmt.Sprintf("%s [%s]", markdown, noteSuffix)
	}

	// Uppercase the first character of the markdown to make it look uniform
	markdown = capitalizeString(markdown)

	return &ReleaseNote{
		Commit:         pair.Commit.Hash.String(),
		Text:           text,
		Markdown:       markdown,
		Documentation:  documentation,
		Author:         author,
		AuthorURL:      authorURL,
		PrURL:          prURL,
		PrNumber:       pr.GetNumber(),
		SIGs:           labelsWithPrefix(pr, "sig"),
		Kinds:          labelsWithPrefix(pr, "kind"),
		Areas:          labelsWithPrefix(pr, "area"),
		Feature:        isFeature,
		Duplicate:      isDuplicateSIG,
		DuplicateKind:  isDuplicateKind,
		ActionRequired: labelExactMatch(pr, "release-note-action-required"),
		DoNotPublish:   labelExactMatch(pr, "release-note-none"),
	}, nil
}

func (g *Gatherer) listLeftParentCommits(opts *options.Options) ([]*commitPrPair, error) {
	localRepository, err := git.PlainOpen(opts.RepoPath)
	if err != nil {
		return nil, err
	}

	// opts.StartSHA points to a tag (e.g. 1.20.0) which is on a release branch (e.g. release-1.20)
	// this means traveling through commit history from opts.EndSHA will never reach opts.StartSHA

	// the stopping point to be set should be the last shared commit between release branch and primary (master) branch
	// usually, following the left / first parents, it would be

	// ^ master
	// |
	// * tag: 1.21.0-alpha.x / 1.21.0-beta.y
	// |
	// : :
	// | |
	// | * tag: v1.20.0, some merge commit pointed by opts.StartSHA
	// | |
	// | * Anago GCB release commit (begin branch out of release-1.20)
	// |/
	// x last shared commit

	// merge base would resolve to last shared commit, marked by (x)

	endCommit, err := localRepository.CommitObject(plumbing.NewHash(opts.EndSHA))
	if err != nil {
		return nil, fmt.Errorf("finding commit of EndSHA: %w", err)
	}

	startCommit, err := localRepository.CommitObject(plumbing.NewHash(opts.StartSHA))
	if err != nil {
		return nil, fmt.Errorf("finding commit of StartSHA: %w", err)
	}

	logrus.Debugf("finding merge base (last shared commit) between the two SHAs")
	startTime := time.Now()
	lastSharedCommits, err := endCommit.MergeBase(startCommit)
	if err != nil {
		return nil, fmt.Errorf("finding shared commits: %w", err)
	}
	if len(lastSharedCommits) == 0 {
		return nil, fmt.Errorf("no shared commits between the provided SHAs")
	}
	logrus.Debugf("found merge base in %v", time.Since(startTime))

	stopHash := lastSharedCommits[0].Hash
	logrus.Infof("will stop at %s", stopHash.String())

	currentTagHash := plumbing.NewHash(opts.EndSHA)

	pairs := []*commitPrPair{}
	hashPointer := currentTagHash
	for hashPointer != stopHash {
		hashString := hashPointer.String()

		// Find and collect commit objects
		commitPointer, err := localRepository.CommitObject(hashPointer)
		if err != nil {
			return nil, fmt.Errorf("finding CommitObject: %w", err)
		}

		// Find and collect PR number from commit message
		prNums, err := prsNumForCommitFromMessage(commitPointer.Message)
		if err == errNoPRIDFoundInCommitMessage {
			logrus.WithFields(logrus.Fields{
				"sha": hashString,
			}).Debug("no associated PR found")

			// Advance pointer based on left parent
			hashPointer = commitPointer.ParentHashes[0]
			continue
		}
		if err != nil {
			logrus.WithFields(logrus.Fields{
				"sha": hashString,
			}).Warnf("ignore err: %v", err)

			// Advance pointer based on left parent
			hashPointer = commitPointer.ParentHashes[0]
			continue
		}
		logrus.WithFields(logrus.Fields{
			"sha": hashString,
			"prs": prNums,
		}).Debug("found PR from commit")

		// Only taking the first one, assuming they are merged by Prow
		pairs = append(pairs, &commitPrPair{Commit: commitPointer, PrNum: prNums[0]})

		// Advance pointer based on left parent
		hashPointer = commitPointer.ParentHashes[0]
	}

	return pairs, nil
}
<|endoftext|>


<commit_before>/*
Copyright 2020 The Knative Authors

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

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

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

package resources

import (
	"sort"

	corev1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/labels"
	corev1listers "k8s.io/client-go/listers/core/v1"
	"knative.dev/serving/pkg/apis/serving"
)

// PodAccessor provides access to various dimensions of pods listing
// and querying for a given bound revision.
type PodAccessor struct {
	podsLister corev1listers.PodNamespaceLister
	selector   labels.Selector
}

// NewPodAccessor creates a PodAccessor implementation that counts
// pods for a namespace/revision.
func NewPodAccessor(lister corev1listers.PodLister, namespace, revisionName string) PodAccessor {
	return PodAccessor{
		podsLister: lister.Pods(namespace),
		selector: labels.SelectorFromSet(labels.Set{
			serving.RevisionLabelKey: revisionName,
		}),
	}
}

// PendingTerminatingCount returns the number of pods in a Pending or
// Terminating state
func (pa PodAccessor) PendingTerminatingCount() (int, int, error) {
	_, _, p, t, err := pa.PodCountsByState()
	return p, t, err
}

// PodCountsByState returns number of pods for the revision grouped by their state, that is
// of interest to knative (e.g. ignoring failed or terminated pods).
func (pa PodAccessor) PodCountsByState() (ready, notReady, pending, terminating int, err error) {
	pods, err := pa.podsLister.List(pa.selector)
	if err != nil {
		return 0, 0, 0, 0, err
	}
	for _, p := range pods {
		switch p.Status.Phase {
		case corev1.PodPending:
			pending++
			notReady++
		case corev1.PodRunning:
			if p.DeletionTimestamp != nil {
				terminating++
				notReady++
				continue
			}
			isReady := false
			for _, cond := range p.Status.Conditions {
				if cond.Type == corev1.PodReady {
					if cond.Status == corev1.ConditionTrue {
						isReady = true
					}
					break
				}
			}
			if isReady {
				ready++
			} else {
				notReady++
			}
		}
	}
	return
}

// ReadyCount implements EndpointsCounter.
func (pa PodAccessor) ReadyCount() (int, error) {
	r, _, _, _, err := pa.PodCountsByState()
	return r, err
}

// NotReadyCount implements EndpointsCounter.
func (pa PodAccessor) NotReadyCount() (int, error) {
	_, nr, _, _, err := pa.PodCountsByState()
	return nr, err
}

// PodIPsByAge returns the list of running pod (terminating
// and non-running are excluded) IP addresses, sorted descending by pod age.
func (pa PodAccessor) PodIPsByAge() ([]string, error) {
	pods, err := pa.podsLister.List(pa.selector)
	if err != nil {
		return nil, err
	}
	// Keep only running ones.
	write := 0
	for _, p := range pods {
		if p.Status.Phase == corev1.PodRunning && p.DeletionTimestamp == nil {
			pods[write] = p
			write++
		}
	}
	pods = pods[:write]

	if len(pods) > 1 {
		// This results in a few reflection calls, which we can easily avoid.
		sort.SliceStable(pods, func(i, j int) bool {
			return pods[i].Status.StartTime.Before(pods[j].Status.StartTime)
		})
	}
	ret := make([]string, 0, len(pods))
	for _, p := range pods {
		ret = append(ret, p.Status.PodIP)
	}

	return ret, nil
}
<commit_msg>Pod random shuffle for scraping, pt I (#8859)<commit_after>/*
Copyright 2020 The Knative Authors

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

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

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

package resources

import (
	"sort"

	corev1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/labels"
	corev1listers "k8s.io/client-go/listers/core/v1"
	"knative.dev/serving/pkg/apis/serving"
)

// PodAccessor provides access to various dimensions of pods listing
// and querying for a given bound revision.
type PodAccessor struct {
	podsLister corev1listers.PodNamespaceLister
	selector   labels.Selector
}

// NewPodAccessor creates a PodAccessor implementation that counts
// pods for a namespace/revision.
func NewPodAccessor(lister corev1listers.PodLister, namespace, revisionName string) PodAccessor {
	return PodAccessor{
		podsLister: lister.Pods(namespace),
		selector: labels.SelectorFromSet(labels.Set{
			serving.RevisionLabelKey: revisionName,
		}),
	}
}

// PendingTerminatingCount returns the number of pods in a Pending or
// Terminating state
func (pa PodAccessor) PendingTerminatingCount() (int, int, error) {
	_, _, p, t, err := pa.PodCountsByState()
	return p, t, err
}

// PodCountsByState returns number of pods for the revision grouped by their state, that is
// of interest to knative (e.g. ignoring failed or terminated pods).
func (pa PodAccessor) PodCountsByState() (ready, notReady, pending, terminating int, err error) {
	pods, err := pa.podsLister.List(pa.selector)
	if err != nil {
		return 0, 0, 0, 0, err
	}
	for _, p := range pods {
		switch p.Status.Phase {
		case corev1.PodPending:
			pending++
			notReady++
		case corev1.PodRunning:
			if p.DeletionTimestamp != nil {
				terminating++
				notReady++
				continue
			}
			isReady := false
			for _, cond := range p.Status.Conditions {
				if cond.Type == corev1.PodReady {
					if cond.Status == corev1.ConditionTrue {
						isReady = true
					}
					break
				}
			}
			if isReady {
				ready++
			} else {
				notReady++
			}
		}
	}
	return
}

// ReadyCount implements EndpointsCounter.
func (pa PodAccessor) ReadyCount() (int, error) {
	r, _, _, _, err := pa.PodCountsByState()
	return r, err
}

// NotReadyCount implements EndpointsCounter.
func (pa PodAccessor) NotReadyCount() (int, error) {
	_, nr, _, _, err := pa.PodCountsByState()
	return nr, err
}

// PodFilter provides a way to filter pods for a revision.
// Returning true, means that pod should be kept.
type PodFilter func(p *corev1.Pod) bool

// PodTransformer provides a way to do something with the pod
// that has been selected by all the filters.
// For example pod transformer may extract a field and store it in
// internal state.
type PodTransformer func(p *corev1.Pod)

// ProcessPods filters all the pods using provided pod filters and then if the pod
// is selected, applies the transformer to it.
func (pa PodAccessor) ProcessPods(pt PodTransformer, pfs ...PodFilter) error {
	pods, err := pa.podsLister.List(pa.selector)
	if err != nil {
		return err
	}
	for _, p := range pods {
		if applyFilters(p, pfs...) {
			pt(p)
		}
	}
	return nil
}

func applyFilters(p *corev1.Pod, pfs ...PodFilter) bool {
	for _, pf := range pfs {
		if !pf(p) {
			return false
		}
	}
	return true
}

func podRunning(p *corev1.Pod) bool {
	return p.Status.Phase == corev1.PodRunning && p.DeletionTimestamp == nil
}

type podIPByAgeSorter struct {
	pods []*corev1.Pod
}

func (s *podIPByAgeSorter) process(p *corev1.Pod) {
	s.pods = append(s.pods, p)
}

func (s *podIPByAgeSorter) get() []string {
	if len(s.pods) > 1 {
		// This results in a few reflection calls, which we can easily avoid.
		sort.SliceStable(s.pods, func(i, j int) bool {
			return s.pods[i].Status.StartTime.Before(s.pods[j].Status.StartTime)
		})
	}
	ret := make([]string, 0, len(s.pods))
	for _, p := range s.pods {
		ret = append(ret, p.Status.PodIP)
	}
	return ret
}

// PodIPsByAge returns the list of running pods (terminating
// and non-running are excluded) IP addresses, sorted descending by pod age.
func (pa PodAccessor) PodIPsByAge() ([]string, error) {
	ps := podIPByAgeSorter{}
	if err := pa.ProcessPods(ps.process, podRunning); err != nil {
		return nil, err
	}
	return ps.get(), nil
}
<|endoftext|>


<commit_before>package server

import (
	"github.com/pkg/errors"
	"github.com/xiezhenye/servant/pkg/conf"
	"io"
	"io/ioutil"
	"net/http"
	"os/exec"
	"os/user"
	"regexp"
	"strconv"
	"strings"
	"syscall"
	"time"
)

var argRe, _ = regexp.Compile(`("[^"]*"|'[^']*'|[^\s"']+)`)

type CommandServer struct {
	*Session
}

func NewCommandServer(sess *Session) Handler {
	return CommandServer{
		Session: sess,
	}
}

func (self CommandServer) findCommandConfig() *conf.Command {
	cmdsConf, ok := self.config.Commands[self.group]
	if !ok {
		return nil
	}
	cmdConf, ok := cmdsConf.Commands[self.item]
	if !ok {
		return nil
	}
	return cmdConf
}

func getCmdBashArgs(code string, query ParamFunc) (string, []string) {
	return "bash", []string{"-c", code}
}

func replaceCmdParams(arg string, query ParamFunc) (string, bool) {
	return VarExpand(arg, query, func(s string) string { return s })
}

func getCmdExecArgs(code string, query ParamFunc) (string, []string, bool) {
	argsMatches := argRe.FindAllStringSubmatch(code, -1)
	args := make([]string, 0, 4)
	var exists bool
	for i := 0; i < len(argsMatches); i++ {
		arg := argsMatches[i][1]
		if arg[0] == '\'' || arg[0] == '"' {
			arg = arg[1 : len(arg)-1]
		}
		arg, exists = replaceCmdParams(arg, query)
		if !exists {
			return "", nil, false
		}
		args = append(args, arg)
	}
	return args[0], args[1:], true
}

func (self CommandServer) serve() {
	urlPath := self.req.URL.Path
	method := self.req.Method

	if method != "GET" && method != "POST" {
		self.ErrorEnd(http.StatusMethodNotAllowed, "not allow method: %s", method)
		return
	}
	cmdConf := self.findCommandConfig()
	if cmdConf == nil {
		self.ErrorEnd(http.StatusNotFound, "command %s not found", urlPath)
		self.resp.WriteHeader(http.StatusNotFound)
		return
	}
	if cmdConf.Lock.Name == "" {
		self.serveCommand(cmdConf)
	} else {
		if cmdConf.Lock.Wait {
			GetLock(cmdConf.Lock.Name).TimeoutWith(time.Duration(cmdConf.Lock.Timeout)*time.Second, func() {
				self.serveCommand(cmdConf)
			})
		} else {
			GetLock(cmdConf.Lock.Name).TryWith(func() {
				self.serveCommand(cmdConf)
			})
		}
	}
}

func setCmdUser(cmd *exec.Cmd, username string) error {
	sysUser, err := user.Lookup(username)
	if err != nil {
		return err
	}
	uid, err := strconv.Atoi(sysUser.Uid)
	if err != nil {
		return err
	}
	gid, err := strconv.Atoi(sysUser.Gid)
	if err != nil {
		return err
	}
	cred := syscall.Credential{Uid: uint32(uid), Gid: uint32(gid)}

	cmd.SysProcAttr.Credential = &cred
	return nil
}

func (self CommandServer) serveCommand(cmdConf *conf.Command) {
	outBuf, err := self.execCommand(cmdConf)
	if err != nil {
		self.ErrorEnd(err.(ServantError).HttpCode, err.(ServantError).Message)
		return
	}
	_, err = self.resp.Write(outBuf) // may log errors
	if err != nil {
		self.BadEnd("io error: %s", err)
	} else {
		self.GoodEnd("execution done")
	}
}

func cmdFromConf(cmdConf *conf.Command, params ParamFunc, input io.ReadCloser) (cmd *exec.Cmd, out io.ReadCloser, err error) {
	var name string
	var args []string
	if !ValidateParams(cmdConf.Validators, params) {
		return nil, nil, NewServantError(http.StatusBadRequest, "validate params failed")
	}
	code := strings.TrimSpace(cmdConf.Code)
	if code == "" {
		return nil, nil, NewServantError(http.StatusInternalServerError, "command code is empty")
	}
	switch cmdConf.Lang {
	case "exec":
		var exists bool
		name, args, exists = getCmdExecArgs(code, params)
		if !exists {
			err = NewServantError(http.StatusBadRequest, "some params missing")
			return
		}
	case "bash", "":
		name, args = getCmdBashArgs(code, params)
	default:
		err = NewServantError(http.StatusInternalServerError, "unknown language")
		return
	}
	cmd = exec.Command(name, args...)
	cmd.SysProcAttr = &syscall.SysProcAttr{}
	cmd.Dir = "/"
	if cmdConf.User != "" {
		err = setCmdUser(cmd, cmdConf.User)
		if err != nil {
			err = NewServantError(http.StatusInternalServerError, "set user failed: %s", err.Error())
			return
		}
	}
	cmd.Stdin = input
	cmd.Stderr = nil
	if cmdConf.Background {
		cmd.SysProcAttr.Setsid = true
		cmd.SysProcAttr.Foreground = false
		cmd.Stdout = nil
		cmd.Stdin = nil
	} else {
		cmd.SysProcAttr.Setpgid = true
		cmd.SysProcAttr.Pgid = 0
		out, err = cmd.StdoutPipe()
		if err != nil {
			err = NewServantError(http.StatusInternalServerError, "pipe stdout failed: %s", err.Error())
			return
		}
	}
	return cmd, out, nil
}

func (self CommandServer) execCommand(cmdConf *conf.Command) (outBuf []byte, err error) {
	var input io.ReadCloser = nil
	if self.req.Method == "POST" {
		input = self.req.Body
	}
	params := requestParams(self.req)
	cmd, out, err := cmdFromConf(cmdConf, params, input)
	if err != nil {
		return
	}
	self.info("command: %v", cmd.Args)
	if out != nil {
		defer out.Close()
	}
	err = cmd.Start()
	if err != nil {
		err = NewServantError(http.StatusBadGateway, "execution error: %s", err)
		return
	}
	self.info("process started. pid: %d", cmd.Process.Pid)
	if cmdConf.Background {
		go func() {
			err = cmd.Wait()
			if err != nil {
				self.warn("background process %d ended with error: %s", cmd.Process.Pid, err.Error())
			} else {
				self.info("background process %d ended", cmd.Process.Pid)
			}
		}()
	} else {
		ch := make(chan error, 1)
		go func() {
			if out != nil {
				if outBuf, err = ioutil.ReadAll(out); err != nil {
					ch <- err
					_ = cmd.Wait()
					return
				}
				if err = cmd.Wait(); err != nil {
					ch <- errors.WithMessage(err, string(outBuf))
					return
				}
				ch <- nil
				return
			}
			ch <- cmd.Wait()
		}()
		timeout := time.Duration(cmdConf.Timeout)
		select {
		case err = <-ch:
			if err != nil {
				err = NewServantError(http.StatusBadGateway, "execution error: %s", err)
			}
		case <-time.After(timeout * time.Second):
			_ = cmd.Process.Kill()
			err = NewServantError(http.StatusGatewayTimeout, "command execution timeout: %d", timeout)
		}
	}
	return
}
<commit_msg>fix non-ServantError handle<commit_after>package server

import (
	"github.com/pkg/errors"
	"github.com/xiezhenye/servant/pkg/conf"
	"io"
	"io/ioutil"
	"net/http"
	"os/exec"
	"os/user"
	"regexp"
	"strconv"
	"strings"
	"syscall"
	"time"
)

var argRe, _ = regexp.Compile(`("[^"]*"|'[^']*'|[^\s"']+)`)

type CommandServer struct {
	*Session
}

func NewCommandServer(sess *Session) Handler {
	return CommandServer{
		Session: sess,
	}
}

func (self CommandServer) findCommandConfig() *conf.Command {
	cmdsConf, ok := self.config.Commands[self.group]
	if !ok {
		return nil
	}
	cmdConf, ok := cmdsConf.Commands[self.item]
	if !ok {
		return nil
	}
	return cmdConf
}

func getCmdBashArgs(code string, query ParamFunc) (string, []string) {
	return "bash", []string{"-c", code}
}

func replaceCmdParams(arg string, query ParamFunc) (string, bool) {
	return VarExpand(arg, query, func(s string) string { return s })
}

func getCmdExecArgs(code string, query ParamFunc) (string, []string, bool) {
	argsMatches := argRe.FindAllStringSubmatch(code, -1)
	args := make([]string, 0, 4)
	var exists bool
	for i := 0; i < len(argsMatches); i++ {
		arg := argsMatches[i][1]
		if arg[0] == '\'' || arg[0] == '"' {
			arg = arg[1 : len(arg)-1]
		}
		arg, exists = replaceCmdParams(arg, query)
		if !exists {
			return "", nil, false
		}
		args = append(args, arg)
	}
	return args[0], args[1:], true
}

func (self CommandServer) serve() {
	urlPath := self.req.URL.Path
	method := self.req.Method

	if method != "GET" && method != "POST" {
		self.ErrorEnd(http.StatusMethodNotAllowed, "not allow method: %s", method)
		return
	}
	cmdConf := self.findCommandConfig()
	if cmdConf == nil {
		self.ErrorEnd(http.StatusNotFound, "command %s not found", urlPath)
		self.resp.WriteHeader(http.StatusNotFound)
		return
	}
	if cmdConf.Lock.Name == "" {
		self.serveCommand(cmdConf)
	} else {
		if cmdConf.Lock.Wait {
			GetLock(cmdConf.Lock.Name).TimeoutWith(time.Duration(cmdConf.Lock.Timeout)*time.Second, func() {
				self.serveCommand(cmdConf)
			})
		} else {
			GetLock(cmdConf.Lock.Name).TryWith(func() {
				self.serveCommand(cmdConf)
			})
		}
	}
}

func setCmdUser(cmd *exec.Cmd, username string) error {
	sysUser, err := user.Lookup(username)
	if err != nil {
		return err
	}
	uid, err := strconv.Atoi(sysUser.Uid)
	if err != nil {
		return err
	}
	gid, err := strconv.Atoi(sysUser.Gid)
	if err != nil {
		return err
	}
	cred := syscall.Credential{Uid: uint32(uid), Gid: uint32(gid)}

	cmd.SysProcAttr.Credential = &cred
	return nil
}

func (self CommandServer) serveCommand(cmdConf *conf.Command) {
	outBuf, err := self.execCommand(cmdConf)
	if err != nil {
		if sErr, ok := err.(ServantError); ok {
			self.ErrorEnd(sErr.HttpCode, sErr.Message)
		} else {
			self.ErrorEnd(500, err.Error())
		}
		return
	}
	_, err = self.resp.Write(outBuf) // may log errors
	if err != nil {
		self.BadEnd("io error: %s", err)
	} else {
		self.GoodEnd("execution done")
	}
}

func cmdFromConf(cmdConf *conf.Command, params ParamFunc, input io.ReadCloser) (cmd *exec.Cmd, out io.ReadCloser, err error) {
	var name string
	var args []string
	if !ValidateParams(cmdConf.Validators, params) {
		return nil, nil, NewServantError(http.StatusBadRequest, "validate params failed")
	}
	code := strings.TrimSpace(cmdConf.Code)
	if code == "" {
		return nil, nil, NewServantError(http.StatusInternalServerError, "command code is empty")
	}
	switch cmdConf.Lang {
	case "exec":
		var exists bool
		name, args, exists = getCmdExecArgs(code, params)
		if !exists {
			err = NewServantError(http.StatusBadRequest, "some params missing")
			return
		}
	case "bash", "":
		name, args = getCmdBashArgs(code, params)
	default:
		err = NewServantError(http.StatusInternalServerError, "unknown language")
		return
	}
	cmd = exec.Command(name, args...)
	cmd.SysProcAttr = &syscall.SysProcAttr{}
	cmd.Dir = "/"
	if cmdConf.User != "" {
		err = setCmdUser(cmd, cmdConf.User)
		if err != nil {
			err = NewServantError(http.StatusInternalServerError, "set user failed: %s", err.Error())
			return
		}
	}
	cmd.Stdin = input
	cmd.Stderr = nil
	if cmdConf.Background {
		cmd.SysProcAttr.Setsid = true
		cmd.SysProcAttr.Foreground = false
		cmd.Stdout = nil
		cmd.Stdin = nil
	} else {
		cmd.SysProcAttr.Setpgid = true
		cmd.SysProcAttr.Pgid = 0
		out, err = cmd.StdoutPipe()
		if err != nil {
			err = NewServantError(http.StatusInternalServerError, "pipe stdout failed: %s", err.Error())
			return
		}
	}
	return cmd, out, nil
}

func (self CommandServer) execCommand(cmdConf *conf.Command) (outBuf []byte, err error) {
	var input io.ReadCloser = nil
	if self.req.Method == "POST" {
		input = self.req.Body
	}
	params := requestParams(self.req)
	cmd, out, err := cmdFromConf(cmdConf, params, input)
	if err != nil {
		return
	}
	self.info("command: %v", cmd.Args)
	if out != nil {
		defer out.Close()
	}
	err = cmd.Start()
	if err != nil {
		err = NewServantError(http.StatusBadGateway, "execution error: %s", err)
		return
	}
	self.info("process started. pid: %d", cmd.Process.Pid)
	if cmdConf.Background {
		go func() {
			err = cmd.Wait()
			if err != nil {
				self.warn("background process %d ended with error: %s", cmd.Process.Pid, err.Error())
			} else {
				self.info("background process %d ended", cmd.Process.Pid)
			}
		}()
	} else {
		ch := make(chan error, 1)
		go func() {
			if out != nil {
				if outBuf, err = ioutil.ReadAll(out); err != nil {
					ch <- err
					_ = cmd.Wait()
					return
				}
				if err = cmd.Wait(); err != nil {
					ch <- errors.WithMessage(err, string(outBuf))
					return
				}
				ch <- nil
				return
			}
			ch <- cmd.Wait()
		}()
		timeout := time.Duration(cmdConf.Timeout)
		select {
		case err = <-ch:
			if err != nil {
				err = NewServantError(http.StatusBadGateway, "execution error: %s", err)
			}
		case <-time.After(timeout * time.Second):
			_ = cmd.Process.Kill()
			err = NewServantError(http.StatusGatewayTimeout, "command execution timeout: %d", timeout)
		}
	}
	return
}
<|endoftext|>


<commit_before>/*
Copyright © 2014–5 Brad Ackerman.

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 server

import (
	"net/http"
	"time"

	"github.com/backerman/eveindy/pkg/db"
	"github.com/zenazn/goji/web"
)

const cookieName = "EVEINDY_SESSION"

type sessionizer struct {
	cookieDomain, cookiePath string
	isProduction             bool
	db                       db.LocalDB
}

// GetSessionizer returns a Sessionizer to be passed to handlers.
func GetSessionizer(cookieDomain, cookiePath string, isProduction bool, db db.LocalDB) Sessionizer {
	return &sessionizer{
		cookieDomain: cookieDomain,
		cookiePath:   cookiePath,
		isProduction: isProduction,
		db:           db,
	}
}

func (s *sessionizer) setCookie(w http.ResponseWriter, cookie string) {
	sessionCookie := &http.Cookie{
		Name:    cookieName,
		Value:   cookie,
		Domain:  s.cookieDomain,
		Path:    s.cookiePath,
		Expires: time.Now().Add(168 * time.Hour), // 1 week
		Secure:  s.isProduction,                  // HTTPS-only iff production system
	}
	http.SetCookie(w, sessionCookie)
}

func (s *sessionizer) GetSession(c *web.C, w http.ResponseWriter, r *http.Request) *db.Session {
	// Get my session cookie.
	var session db.Session
	var newSession bool
	sessionCookie, err := r.Cookie(cookieName)
	if err == nil {
		// Got a cookie; check to see if the corresponding session is available.
		oldCookie := sessionCookie.Value
		session, err = s.db.FindSession(oldCookie)
		if err == nil && session.Cookie != oldCookie {
			// This session didn't exist, so a new session has been created.
			newSession = true
		}
	} else {
		// The session cookie did not exist.
		session, err = s.db.NewSession()
		newSession = true
	}
	if err != nil {
		panic("OMG! " + err.Error())
	}
	if newSession {
		// Store a cookie.
		s.setCookie(w, session.Cookie)
	}
	c.Env["session"] = session

	return &session
}
<commit_msg>Increase cookie lifetime to 90 days.<commit_after>/*
Copyright © 2014–5 Brad Ackerman.

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 server

import (
	"net/http"
	"time"

	"github.com/backerman/eveindy/pkg/db"
	"github.com/zenazn/goji/web"
)

const cookieName = "EVEINDY_SESSION"

type sessionizer struct {
	cookieDomain, cookiePath string
	isProduction             bool
	db                       db.LocalDB
}

// GetSessionizer returns a Sessionizer to be passed to handlers.
func GetSessionizer(cookieDomain, cookiePath string, isProduction bool, db db.LocalDB) Sessionizer {
	return &sessionizer{
		cookieDomain: cookieDomain,
		cookiePath:   cookiePath,
		isProduction: isProduction,
		db:           db,
	}
}

func (s *sessionizer) setCookie(w http.ResponseWriter, cookie string) {
	sessionCookie := &http.Cookie{
		Name:    cookieName,
		Value:   cookie,
		Domain:  s.cookieDomain,
		Path:    s.cookiePath,
		Expires: time.Now().Add(24 * 30 * 3 * time.Hour), // 3 months
		Secure:  s.isProduction,                          // HTTPS-only iff production system
	}
	http.SetCookie(w, sessionCookie)
}

func (s *sessionizer) GetSession(c *web.C, w http.ResponseWriter, r *http.Request) *db.Session {
	// Get my session cookie.
	var session db.Session
	var newSession bool
	sessionCookie, err := r.Cookie(cookieName)
	if err == nil {
		// Got a cookie; check to see if the corresponding session is available.
		oldCookie := sessionCookie.Value
		session, err = s.db.FindSession(oldCookie)
		if err == nil && session.Cookie != oldCookie {
			// This session didn't exist, so a new session has been created.
			newSession = true
		}
	} else {
		// The session cookie did not exist.
		session, err = s.db.NewSession()
		newSession = true
	}
	if err != nil {
		panic("OMG! " + err.Error())
	}
	if newSession {
		// Store a cookie.
		s.setCookie(w, session.Cookie)
	}
	c.Env["session"] = session

	return &session
}
<|endoftext|>


<commit_before>package cmd

import (
	"errors"
	"fmt"
	"net/url"
	"strings"
	"syscall"

	"github.com/deis/deis/client-go/controller/client"
	"github.com/deis/deis/client-go/controller/models/auth"
	"golang.org/x/crypto/ssh/terminal"
)

// Register creates a account on a Deis controller.
func Register(controller string, username string, password string, email string,
	sslVerify bool) error {

	u, err := url.Parse(controller)
	httpClient := client.CreateHTTPClient(sslVerify)

	if err != nil {
		return err
	}

	controllerURL, err := chooseScheme(*u)

	if err != nil {
		return err
	}

	if err = client.CheckConection(httpClient, controllerURL); err != nil {
		return err
	}

	if username == "" {
		fmt.Print("username: ")
		fmt.Scanln(&username)
	}

	if password == "" {
		fmt.Print("password: ")
		password, err = readPassword()
		fmt.Printf("\npassword (confirm): ")
		passwordConfirm, err := readPassword()
		fmt.Println()

		if err != nil {
			return err
		}

		if password != passwordConfirm {
			return errors.New("Password mismatch, aborting registration.")
		}
	}

	if email == "" {
		fmt.Print("email: ")
		fmt.Scanln(&email)
	}

	c := &client.Client{ControllerURL: controllerURL, SSLVerify: sslVerify, HTTPClient: httpClient}

	tempClient, err := client.New()

	if err == nil {
		c.Token = tempClient.Token
	}

	err = auth.Register(c, username, password, email)

	if err != nil {
		return err
	}

	fmt.Printf("Registered %s\n", username)
	return doLogin(c, username, password)
}

func doLogin(c *client.Client, username, password string) error {
	token, err := auth.Login(c, username, password)

	if err != nil {
		return err
	}

	c.Token = token
	c.Username = username

	err = c.Save()

	if err != nil {
		return nil
	}

	fmt.Printf("Logged in as %s\n", username)
	return nil
}

// Login to a Deis controller.
func Login(controller string, username string, password string, sslVerify bool) error {
	u, err := url.Parse(controller)

	if err != nil {
		return err
	}

	controllerURL, err := chooseScheme(*u)
	httpClient := client.CreateHTTPClient(sslVerify)

	if err != nil {
		return err
	}

	if err = client.CheckConection(httpClient, controllerURL); err != nil {
		return err
	}

	if username == "" {
		fmt.Print("username: ")
		fmt.Scanln(&username)
	}

	if password == "" {
		fmt.Print("password: ")
		password, err = readPassword()
		fmt.Println()

		if err != nil {
			return err
		}
	}

	c := &client.Client{ControllerURL: controllerURL, SSLVerify: sslVerify, HTTPClient: httpClient}

	return doLogin(c, username, password)
}

// Logout from a Deis controller.
func Logout() error {
	if err := client.Delete(); err != nil {
		return err
	}

	fmt.Println("Logged out")
	return nil
}

// Passwd changes a user's password.
func Passwd(username string, password string, newPassword string) error {
	c, err := client.New()

	if err != nil {
		return err
	}

	if password == "" && username == "" {
		fmt.Print("current password: ")
		password, err = readPassword()
		fmt.Println()

		if err != nil {
			return err
		}
	}

	if newPassword == "" {
		fmt.Print("new password: ")
		newPassword, err = readPassword()
		fmt.Printf("\nnew password (confirm): ")
		passwordConfirm, err := readPassword()

		fmt.Println()

		if err != nil {
			return err
		}

		if newPassword != passwordConfirm {
			return errors.New("Password mismatch, not changing.")
		}
	}

	err = auth.Passwd(c, username, password, newPassword)

	if err != nil {
		return err
	}

	fmt.Println("Password change succeeded.")
	return nil
}

// Cancel deletes a user's account.
func Cancel(username string, password string, yes bool) error {
	c, err := client.New()

	if err != nil {
		return err
	}

	fmt.Println("Please log in again in order to cancel this account")

	if err = Login(c.ControllerURL.String(), username, password, c.SSLVerify); err != nil {
		return err
	}

	if yes == false {
		confirm := ""

		c, err = client.New()

		if err != nil {
			return err
		}

		fmt.Printf("cancel account %s at %s? (y/N): ", c.Username, c.ControllerURL.String())
		fmt.Scanln(&confirm)

		if strings.ToLower(confirm) == "y" {
			yes = true
		}
	}

	if yes == false {
		fmt.Println("Account not changed")
		return nil
	}

	err = auth.Delete(c)

	if err != nil {
		return err
	}

	if err := client.Delete(); err != nil {
		return err
	}

	fmt.Println("Account cancelled")
	return nil
}

// Whoami prints the logged in user.
func Whoami() error {
	c, err := client.New()

	if err != nil {
		return err
	}

	fmt.Printf("You are %s at %s\n", c.Username, c.ControllerURL.String())
	return nil
}

// Regenerate regenenerates a user's token.
func Regenerate(username string, all bool) error {
	c, err := client.New()

	if err != nil {
		return err
	}

	token, err := auth.Regenerate(c, username, all)

	if err != nil {
		return err
	}

	if username == "" && all == false {
		c.Token = token

		err = c.Save()

		if err != nil {
			return err
		}
	}

	fmt.Println("Token Regenerated")
	return nil
}

func readPassword() (string, error) {
	password, err := terminal.ReadPassword(int(syscall.Stdin))

	return string(password), err
}

func chooseScheme(u url.URL) (url.URL, error) {
	if u.Scheme == "" {
		u.Scheme = "http"
		u, err := url.Parse(u.String())

		if err != nil {
			return url.URL{}, err
		}

		return *u, nil
	}

	return u, nil
}
<commit_msg>fix(client-go): unset token before logging in<commit_after>package cmd

import (
	"errors"
	"fmt"
	"net/url"
	"strings"
	"syscall"

	"github.com/deis/deis/client-go/controller/client"
	"github.com/deis/deis/client-go/controller/models/auth"
	"golang.org/x/crypto/ssh/terminal"
)

// Register creates a account on a Deis controller.
func Register(controller string, username string, password string, email string,
	sslVerify bool) error {

	u, err := url.Parse(controller)
	httpClient := client.CreateHTTPClient(sslVerify)

	if err != nil {
		return err
	}

	controllerURL, err := chooseScheme(*u)

	if err != nil {
		return err
	}

	if err = client.CheckConection(httpClient, controllerURL); err != nil {
		return err
	}

	if username == "" {
		fmt.Print("username: ")
		fmt.Scanln(&username)
	}

	if password == "" {
		fmt.Print("password: ")
		password, err = readPassword()
		fmt.Printf("\npassword (confirm): ")
		passwordConfirm, err := readPassword()
		fmt.Println()

		if err != nil {
			return err
		}

		if password != passwordConfirm {
			return errors.New("Password mismatch, aborting registration.")
		}
	}

	if email == "" {
		fmt.Print("email: ")
		fmt.Scanln(&email)
	}

	c := &client.Client{ControllerURL: controllerURL, SSLVerify: sslVerify, HTTPClient: httpClient}

	tempClient, err := client.New()

	if err == nil {
		c.Token = tempClient.Token
	}

	err = auth.Register(c, username, password, email)

	c.Token = ""

	if err != nil {
		return err
	}

	fmt.Printf("Registered %s\n", username)
	return doLogin(c, username, password)
}

func doLogin(c *client.Client, username, password string) error {
	token, err := auth.Login(c, username, password)

	if err != nil {
		return err
	}

	c.Token = token
	c.Username = username

	err = c.Save()

	if err != nil {
		return nil
	}

	fmt.Printf("Logged in as %s\n", username)
	return nil
}

// Login to a Deis controller.
func Login(controller string, username string, password string, sslVerify bool) error {
	u, err := url.Parse(controller)

	if err != nil {
		return err
	}

	controllerURL, err := chooseScheme(*u)
	httpClient := client.CreateHTTPClient(sslVerify)

	if err != nil {
		return err
	}

	if err = client.CheckConection(httpClient, controllerURL); err != nil {
		return err
	}

	if username == "" {
		fmt.Print("username: ")
		fmt.Scanln(&username)
	}

	if password == "" {
		fmt.Print("password: ")
		password, err = readPassword()
		fmt.Println()

		if err != nil {
			return err
		}
	}

	c := &client.Client{ControllerURL: controllerURL, SSLVerify: sslVerify, HTTPClient: httpClient}

	return doLogin(c, username, password)
}

// Logout from a Deis controller.
func Logout() error {
	if err := client.Delete(); err != nil {
		return err
	}

	fmt.Println("Logged out")
	return nil
}

// Passwd changes a user's password.
func Passwd(username string, password string, newPassword string) error {
	c, err := client.New()

	if err != nil {
		return err
	}

	if password == "" && username == "" {
		fmt.Print("current password: ")
		password, err = readPassword()
		fmt.Println()

		if err != nil {
			return err
		}
	}

	if newPassword == "" {
		fmt.Print("new password: ")
		newPassword, err = readPassword()
		fmt.Printf("\nnew password (confirm): ")
		passwordConfirm, err := readPassword()

		fmt.Println()

		if err != nil {
			return err
		}

		if newPassword != passwordConfirm {
			return errors.New("Password mismatch, not changing.")
		}
	}

	err = auth.Passwd(c, username, password, newPassword)

	if err != nil {
		return err
	}

	fmt.Println("Password change succeeded.")
	return nil
}

// Cancel deletes a user's account.
func Cancel(username string, password string, yes bool) error {
	c, err := client.New()

	if err != nil {
		return err
	}

	fmt.Println("Please log in again in order to cancel this account")

	if err = Login(c.ControllerURL.String(), username, password, c.SSLVerify); err != nil {
		return err
	}

	if yes == false {
		confirm := ""

		c, err = client.New()

		if err != nil {
			return err
		}

		fmt.Printf("cancel account %s at %s? (y/N): ", c.Username, c.ControllerURL.String())
		fmt.Scanln(&confirm)

		if strings.ToLower(confirm) == "y" {
			yes = true
		}
	}

	if yes == false {
		fmt.Println("Account not changed")
		return nil
	}

	err = auth.Delete(c)

	if err != nil {
		return err
	}

	if err := client.Delete(); err != nil {
		return err
	}

	fmt.Println("Account cancelled")
	return nil
}

// Whoami prints the logged in user.
func Whoami() error {
	c, err := client.New()

	if err != nil {
		return err
	}

	fmt.Printf("You are %s at %s\n", c.Username, c.ControllerURL.String())
	return nil
}

// Regenerate regenenerates a user's token.
func Regenerate(username string, all bool) error {
	c, err := client.New()

	if err != nil {
		return err
	}

	token, err := auth.Regenerate(c, username, all)

	if err != nil {
		return err
	}

	if username == "" && all == false {
		c.Token = token

		err = c.Save()

		if err != nil {
			return err
		}
	}

	fmt.Println("Token Regenerated")
	return nil
}

func readPassword() (string, error) {
	password, err := terminal.ReadPassword(int(syscall.Stdin))

	return string(password), err
}

func chooseScheme(u url.URL) (url.URL, error) {
	if u.Scheme == "" {
		u.Scheme = "http"
		u, err := url.Parse(u.String())

		if err != nil {
			return url.URL{}, err
		}

		return *u, nil
	}

	return u, nil
}
<|endoftext|>


<commit_before>/*
Copyright 2014 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 nfs

import (
	"fmt"
	"os"
	"runtime"

	"k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/types"
	"k8s.io/klog"
	"k8s.io/kubernetes/pkg/util/mount"
	"k8s.io/kubernetes/pkg/volume"
	"k8s.io/kubernetes/pkg/volume/util"
	"k8s.io/kubernetes/pkg/volume/util/recyclerclient"
	utilstrings "k8s.io/utils/strings"
)

// ProbeVolumePlugins is the primary entrypoint for volume plugins.
// The volumeConfig arg provides the ability to configure recycler behavior.  It is implemented as a pointer to allow nils.
// The nfsPlugin is used to store the volumeConfig and give it, when needed, to the func that creates NFS Recyclers.
// Tests that exercise recycling should not use this func but instead use ProbeRecyclablePlugins() to override default behavior.
func ProbeVolumePlugins(volumeConfig volume.VolumeConfig) []volume.VolumePlugin {
	return []volume.VolumePlugin{
		&nfsPlugin{
			host:   nil,
			config: volumeConfig,
		},
	}
}

type nfsPlugin struct {
	host   volume.VolumeHost
	config volume.VolumeConfig
}

var _ volume.VolumePlugin = &nfsPlugin{}
var _ volume.PersistentVolumePlugin = &nfsPlugin{}
var _ volume.RecyclableVolumePlugin = &nfsPlugin{}

const (
	nfsPluginName = "kubernetes.io/nfs"
)

func (plugin *nfsPlugin) Init(host volume.VolumeHost) error {
	plugin.host = host
	return nil
}

func (plugin *nfsPlugin) GetPluginName() string {
	return nfsPluginName
}

func (plugin *nfsPlugin) GetVolumeName(spec *volume.Spec) (string, error) {
	volumeSource, _, err := getVolumeSource(spec)
	if err != nil {
		return "", err
	}

	return fmt.Sprintf(
		"%v/%v",
		volumeSource.Server,
		volumeSource.Path), nil
}

func (plugin *nfsPlugin) CanSupport(spec *volume.Spec) bool {
	return (spec.PersistentVolume != nil && spec.PersistentVolume.Spec.NFS != nil) ||
		(spec.Volume != nil && spec.Volume.NFS != nil)
}

func (plugin *nfsPlugin) IsMigratedToCSI() bool {
	return false
}

func (plugin *nfsPlugin) RequiresRemount() bool {
	return false
}

func (plugin *nfsPlugin) SupportsMountOption() bool {
	return true
}

func (plugin *nfsPlugin) SupportsBulkVolumeVerification() bool {
	return false
}

func (plugin *nfsPlugin) GetAccessModes() []v1.PersistentVolumeAccessMode {
	return []v1.PersistentVolumeAccessMode{
		v1.ReadWriteOnce,
		v1.ReadOnlyMany,
		v1.ReadWriteMany,
	}
}

func (plugin *nfsPlugin) NewMounter(spec *volume.Spec, pod *v1.Pod, _ volume.VolumeOptions) (volume.Mounter, error) {
	return plugin.newMounterInternal(spec, pod, plugin.host.GetMounter(plugin.GetPluginName()))
}

func (plugin *nfsPlugin) newMounterInternal(spec *volume.Spec, pod *v1.Pod, mounter mount.Interface) (volume.Mounter, error) {
	source, readOnly, err := getVolumeSource(spec)
	if err != nil {
		return nil, err
	}

	return &nfsMounter{
		nfs: &nfs{
			volName: spec.Name(),
			mounter: mounter,
			pod:     pod,
			plugin:  plugin,
		},
		server:       source.Server,
		exportPath:   source.Path,
		readOnly:     readOnly,
		mountOptions: util.MountOptionFromSpec(spec),
	}, nil
}

func (plugin *nfsPlugin) NewUnmounter(volName string, podUID types.UID) (volume.Unmounter, error) {
	return plugin.newUnmounterInternal(volName, podUID, plugin.host.GetMounter(plugin.GetPluginName()))
}

func (plugin *nfsPlugin) newUnmounterInternal(volName string, podUID types.UID, mounter mount.Interface) (volume.Unmounter, error) {
	return &nfsUnmounter{&nfs{
		volName: volName,
		mounter: mounter,
		pod:     &v1.Pod{ObjectMeta: metav1.ObjectMeta{UID: podUID}},
		plugin:  plugin,
	}}, nil
}

// Recycle recycles/scrubs clean an NFS volume.
// Recycle blocks until the pod has completed or any error occurs.
func (plugin *nfsPlugin) Recycle(pvName string, spec *volume.Spec, eventRecorder recyclerclient.RecycleEventRecorder) error {
	if spec.PersistentVolume == nil || spec.PersistentVolume.Spec.NFS == nil {
		return fmt.Errorf("spec.PersistentVolumeSource.NFS is nil")
	}

	pod := plugin.config.RecyclerPodTemplate
	timeout := util.CalculateTimeoutForVolume(plugin.config.RecyclerMinimumTimeout, plugin.config.RecyclerTimeoutIncrement, spec.PersistentVolume)
	// overrides
	pod.Spec.ActiveDeadlineSeconds = &timeout
	pod.GenerateName = "pv-recycler-nfs-"
	pod.Spec.Volumes[0].VolumeSource = v1.VolumeSource{
		NFS: &v1.NFSVolumeSource{
			Server: spec.PersistentVolume.Spec.NFS.Server,
			Path:   spec.PersistentVolume.Spec.NFS.Path,
		},
	}
	return recyclerclient.RecycleVolumeByWatchingPodUntilCompletion(pvName, pod, plugin.host.GetKubeClient(), eventRecorder)
}

func (plugin *nfsPlugin) ConstructVolumeSpec(volumeName, mountPath string) (*volume.Spec, error) {
	nfsVolume := &v1.Volume{
		Name: volumeName,
		VolumeSource: v1.VolumeSource{
			NFS: &v1.NFSVolumeSource{
				Path: volumeName,
			},
		},
	}
	return volume.NewSpecFromVolume(nfsVolume), nil
}

// NFS volumes represent a bare host file or directory mount of an NFS export.
type nfs struct {
	volName string
	pod     *v1.Pod
	mounter mount.Interface
	plugin  *nfsPlugin
	volume.MetricsNil
}

func (nfsVolume *nfs) GetPath() string {
	name := nfsPluginName
	return nfsVolume.plugin.host.GetPodVolumeDir(nfsVolume.pod.UID, utilstrings.EscapeQualifiedName(name), nfsVolume.volName)
}

// Checks prior to mount operations to verify that the required components (binaries, etc.)
// to mount the volume are available on the underlying node.
// If not, it returns an error
func (nfsMounter *nfsMounter) CanMount() error {
	exec := nfsMounter.plugin.host.GetExec(nfsMounter.plugin.GetPluginName())
	switch runtime.GOOS {
	case "linux":
		if _, err := exec.Run("test", "-x", "/sbin/mount.nfs"); err != nil {
			return fmt.Errorf("Required binary /sbin/mount.nfs is missing")
		}
		if _, err := exec.Run("test", "-x", "/sbin/mount.nfs4"); err != nil {
			return fmt.Errorf("Required binary /sbin/mount.nfs4 is missing")
		}
		return nil
	case "darwin":
		if _, err := exec.Run("test", "-x", "/sbin/mount_nfs"); err != nil {
			return fmt.Errorf("Required binary /sbin/mount_nfs is missing")
		}
	}
	return nil
}

type nfsMounter struct {
	*nfs
	server       string
	exportPath   string
	readOnly     bool
	mountOptions []string
}

var _ volume.Mounter = &nfsMounter{}

func (nfsMounter *nfsMounter) GetAttributes() volume.Attributes {
	return volume.Attributes{
		ReadOnly:        nfsMounter.readOnly,
		Managed:         false,
		SupportsSELinux: false,
	}
}

// SetUp attaches the disk and bind mounts to the volume path.
func (nfsMounter *nfsMounter) SetUp(mounterArgs volume.MounterArgs) error {
	return nfsMounter.SetUpAt(nfsMounter.GetPath(), mounterArgs)
}

func (nfsMounter *nfsMounter) SetUpAt(dir string, mounterArgs volume.MounterArgs) error {
	notMnt, err := mount.IsNotMountPoint(nfsMounter.mounter, dir)
	klog.V(4).Infof("NFS mount set up: %s %v %v", dir, !notMnt, err)
	if err != nil && !os.IsNotExist(err) {
		return err
	}
	if !notMnt {
		return nil
	}
	if err := os.MkdirAll(dir, 0750); err != nil {
		return err
	}
	source := fmt.Sprintf("%s:%s", nfsMounter.server, nfsMounter.exportPath)
	options := []string{}
	if nfsMounter.readOnly {
		options = append(options, "ro")
	}
	mountOptions := util.JoinMountOptions(nfsMounter.mountOptions, options)
	err = nfsMounter.mounter.Mount(source, dir, "nfs", mountOptions)
	if err != nil {
		notMnt, mntErr := mount.IsNotMountPoint(nfsMounter.mounter, dir)
		if mntErr != nil {
			klog.Errorf("IsNotMountPoint check failed: %v", mntErr)
			return err
		}
		if !notMnt {
			if mntErr = nfsMounter.mounter.Unmount(dir); mntErr != nil {
				klog.Errorf("Failed to unmount: %v", mntErr)
				return err
			}
			notMnt, mntErr := mount.IsNotMountPoint(nfsMounter.mounter, dir)
			if mntErr != nil {
				klog.Errorf("IsNotMountPoint check failed: %v", mntErr)
				return err
			}
			if !notMnt {
				// This is very odd, we don't expect it.  We'll try again next sync loop.
				klog.Errorf("%s is still mounted, despite call to unmount().  Will try again next sync loop.", dir)
				return err
			}
		}
		os.Remove(dir)
		return err
	}
	return nil
}

var _ volume.Unmounter = &nfsUnmounter{}

type nfsUnmounter struct {
	*nfs
}

func (c *nfsUnmounter) TearDown() error {
	return c.TearDownAt(c.GetPath())
}

func (c *nfsUnmounter) TearDownAt(dir string) error {
	// Use extensiveMountPointCheck to consult /proc/mounts. We can't use faster
	// IsLikelyNotMountPoint (lstat()), since there may be root_squash on the
	// NFS server and kubelet may not be able to do lstat/stat() there.
	return mount.CleanupMountPoint(dir, c.mounter, true /* extensiveMountPointCheck */)
}

func getVolumeSource(spec *volume.Spec) (*v1.NFSVolumeSource, bool, error) {
	if spec.Volume != nil && spec.Volume.NFS != nil {
		return spec.Volume.NFS, spec.Volume.NFS.ReadOnly, nil
	} else if spec.PersistentVolume != nil &&
		spec.PersistentVolume.Spec.NFS != nil {
		return spec.PersistentVolume.Spec.NFS, spec.ReadOnly, nil
	}

	return nil, false, fmt.Errorf("Spec does not reference a NFS volume type")
}
<commit_msg>Adding metrics to nfs driver<commit_after>/*
Copyright 2014 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 nfs

import (
	"fmt"
	"os"
	"runtime"

	"k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/types"
	"k8s.io/klog"
	"k8s.io/kubernetes/pkg/util/mount"
	"k8s.io/kubernetes/pkg/volume"
	"k8s.io/kubernetes/pkg/volume/util"
	"k8s.io/kubernetes/pkg/volume/util/recyclerclient"
	utilstrings "k8s.io/utils/strings"
)

func getPath(uid types.UID, volName string, host volume.VolumeHost) string {
	return host.GetPodVolumeDir(uid, utilstrings.EscapeQualifiedName(nfsPluginName), volName)
}

// ProbeVolumePlugins is the primary entrypoint for volume plugins.
// This is the primary entrypoint for volume plugins.
// The volumeConfig arg provides the ability to configure recycler behavior.  It is implemented as a pointer to allow nils.
// The nfsPlugin is used to store the volumeConfig and give it, when needed, to the func that creates NFS Recyclers.
// Tests that exercise recycling should not use this func but instead use ProbeRecyclablePlugins() to override default behavior.
func ProbeVolumePlugins(volumeConfig volume.VolumeConfig) []volume.VolumePlugin {
	return []volume.VolumePlugin{
		&nfsPlugin{
			host:   nil,
			config: volumeConfig,
		},
	}
}

type nfsPlugin struct {
	host   volume.VolumeHost
	config volume.VolumeConfig
}

var _ volume.VolumePlugin = &nfsPlugin{}
var _ volume.PersistentVolumePlugin = &nfsPlugin{}
var _ volume.RecyclableVolumePlugin = &nfsPlugin{}

const (
	nfsPluginName = "kubernetes.io/nfs"
)

func (plugin *nfsPlugin) Init(host volume.VolumeHost) error {
	plugin.host = host
	return nil
}

func (plugin *nfsPlugin) GetPluginName() string {
	return nfsPluginName
}

func (plugin *nfsPlugin) GetVolumeName(spec *volume.Spec) (string, error) {
	volumeSource, _, err := getVolumeSource(spec)
	if err != nil {
		return "", err
	}

	return fmt.Sprintf(
		"%v/%v",
		volumeSource.Server,
		volumeSource.Path), nil
}

func (plugin *nfsPlugin) CanSupport(spec *volume.Spec) bool {
	return (spec.PersistentVolume != nil && spec.PersistentVolume.Spec.NFS != nil) ||
		(spec.Volume != nil && spec.Volume.NFS != nil)
}

func (plugin *nfsPlugin) IsMigratedToCSI() bool {
	return false
}

func (plugin *nfsPlugin) RequiresRemount() bool {
	return false
}

func (plugin *nfsPlugin) SupportsMountOption() bool {
	return true
}

func (plugin *nfsPlugin) SupportsBulkVolumeVerification() bool {
	return false
}

func (plugin *nfsPlugin) GetAccessModes() []v1.PersistentVolumeAccessMode {
	return []v1.PersistentVolumeAccessMode{
		v1.ReadWriteOnce,
		v1.ReadOnlyMany,
		v1.ReadWriteMany,
	}
}

func (plugin *nfsPlugin) NewMounter(spec *volume.Spec, pod *v1.Pod, _ volume.VolumeOptions) (volume.Mounter, error) {
	return plugin.newMounterInternal(spec, pod, plugin.host.GetMounter(plugin.GetPluginName()))
}

func (plugin *nfsPlugin) newMounterInternal(spec *volume.Spec, pod *v1.Pod, mounter mount.Interface) (volume.Mounter, error) {
	source, readOnly, err := getVolumeSource(spec)
	if err != nil {
		return nil, err
	}

	return &nfsMounter{
		nfs: &nfs{
			volName:         spec.Name(),
			mounter:         mounter,
			pod:             pod,
			plugin:          plugin,
			MetricsProvider: volume.NewMetricsStatFS(getPath(pod.UID, spec.Name(), plugin.host)),
		},
		server:       source.Server,
		exportPath:   source.Path,
		readOnly:     readOnly,
		mountOptions: util.MountOptionFromSpec(spec),
	}, nil
}

func (plugin *nfsPlugin) NewUnmounter(volName string, podUID types.UID) (volume.Unmounter, error) {
	return plugin.newUnmounterInternal(volName, podUID, plugin.host.GetMounter(plugin.GetPluginName()))
}

func (plugin *nfsPlugin) newUnmounterInternal(volName string, podUID types.UID, mounter mount.Interface) (volume.Unmounter, error) {
	return &nfsUnmounter{&nfs{
		volName:         volName,
		mounter:         mounter,
		pod:             &v1.Pod{ObjectMeta: metav1.ObjectMeta{UID: podUID}},
		plugin:          plugin,
		MetricsProvider: volume.NewMetricsStatFS(getPath(podUID, volName, plugin.host)),
	}}, nil
}

// Recycle recycles/scrubs clean an NFS volume.
// Recycle blocks until the pod has completed or any error occurs.
func (plugin *nfsPlugin) Recycle(pvName string, spec *volume.Spec, eventRecorder recyclerclient.RecycleEventRecorder) error {
	if spec.PersistentVolume == nil || spec.PersistentVolume.Spec.NFS == nil {
		return fmt.Errorf("spec.PersistentVolumeSource.NFS is nil")
	}

	pod := plugin.config.RecyclerPodTemplate
	timeout := util.CalculateTimeoutForVolume(plugin.config.RecyclerMinimumTimeout, plugin.config.RecyclerTimeoutIncrement, spec.PersistentVolume)
	// overrides
	pod.Spec.ActiveDeadlineSeconds = &timeout
	pod.GenerateName = "pv-recycler-nfs-"
	pod.Spec.Volumes[0].VolumeSource = v1.VolumeSource{
		NFS: &v1.NFSVolumeSource{
			Server: spec.PersistentVolume.Spec.NFS.Server,
			Path:   spec.PersistentVolume.Spec.NFS.Path,
		},
	}
	return recyclerclient.RecycleVolumeByWatchingPodUntilCompletion(pvName, pod, plugin.host.GetKubeClient(), eventRecorder)
}

func (plugin *nfsPlugin) ConstructVolumeSpec(volumeName, mountPath string) (*volume.Spec, error) {
	nfsVolume := &v1.Volume{
		Name: volumeName,
		VolumeSource: v1.VolumeSource{
			NFS: &v1.NFSVolumeSource{
				Path: volumeName,
			},
		},
	}
	return volume.NewSpecFromVolume(nfsVolume), nil
}

// NFS volumes represent a bare host file or directory mount of an NFS export.
type nfs struct {
	volName string
	pod     *v1.Pod
	mounter mount.Interface
	plugin  *nfsPlugin
	volume.MetricsProvider
}

func (nfsVolume *nfs) GetPath() string {
	name := nfsPluginName
	return nfsVolume.plugin.host.GetPodVolumeDir(nfsVolume.pod.UID, utilstrings.EscapeQualifiedName(name), nfsVolume.volName)
}

// Checks prior to mount operations to verify that the required components (binaries, etc.)
// to mount the volume are available on the underlying node.
// If not, it returns an error
func (nfsMounter *nfsMounter) CanMount() error {
	exec := nfsMounter.plugin.host.GetExec(nfsMounter.plugin.GetPluginName())
	switch runtime.GOOS {
	case "linux":
		if _, err := exec.Run("test", "-x", "/sbin/mount.nfs"); err != nil {
			return fmt.Errorf("Required binary /sbin/mount.nfs is missing")
		}
		if _, err := exec.Run("test", "-x", "/sbin/mount.nfs4"); err != nil {
			return fmt.Errorf("Required binary /sbin/mount.nfs4 is missing")
		}
		return nil
	case "darwin":
		if _, err := exec.Run("test", "-x", "/sbin/mount_nfs"); err != nil {
			return fmt.Errorf("Required binary /sbin/mount_nfs is missing")
		}
	}
	return nil
}

type nfsMounter struct {
	*nfs
	server       string
	exportPath   string
	readOnly     bool
	mountOptions []string
}

var _ volume.Mounter = &nfsMounter{}

func (nfsMounter *nfsMounter) GetAttributes() volume.Attributes {
	return volume.Attributes{
		ReadOnly:        nfsMounter.readOnly,
		Managed:         false,
		SupportsSELinux: false,
	}
}

// SetUp attaches the disk and bind mounts to the volume path.
func (nfsMounter *nfsMounter) SetUp(mounterArgs volume.MounterArgs) error {
	return nfsMounter.SetUpAt(nfsMounter.GetPath(), mounterArgs)
}

func (nfsMounter *nfsMounter) SetUpAt(dir string, mounterArgs volume.MounterArgs) error {
	notMnt, err := mount.IsNotMountPoint(nfsMounter.mounter, dir)
	klog.V(4).Infof("NFS mount set up: %s %v %v", dir, !notMnt, err)
	if err != nil && !os.IsNotExist(err) {
		return err
	}
	if !notMnt {
		return nil
	}
	if err := os.MkdirAll(dir, 0750); err != nil {
		return err
	}
	source := fmt.Sprintf("%s:%s", nfsMounter.server, nfsMounter.exportPath)
	options := []string{}
	if nfsMounter.readOnly {
		options = append(options, "ro")
	}
	mountOptions := util.JoinMountOptions(nfsMounter.mountOptions, options)
	err = nfsMounter.mounter.Mount(source, dir, "nfs", mountOptions)
	if err != nil {
		notMnt, mntErr := mount.IsNotMountPoint(nfsMounter.mounter, dir)
		if mntErr != nil {
			klog.Errorf("IsNotMountPoint check failed: %v", mntErr)
			return err
		}
		if !notMnt {
			if mntErr = nfsMounter.mounter.Unmount(dir); mntErr != nil {
				klog.Errorf("Failed to unmount: %v", mntErr)
				return err
			}
			notMnt, mntErr := mount.IsNotMountPoint(nfsMounter.mounter, dir)
			if mntErr != nil {
				klog.Errorf("IsNotMountPoint check failed: %v", mntErr)
				return err
			}
			if !notMnt {
				// This is very odd, we don't expect it.  We'll try again next sync loop.
				klog.Errorf("%s is still mounted, despite call to unmount().  Will try again next sync loop.", dir)
				return err
			}
		}
		os.Remove(dir)
		return err
	}
	return nil
}

var _ volume.Unmounter = &nfsUnmounter{}

type nfsUnmounter struct {
	*nfs
}

func (c *nfsUnmounter) TearDown() error {
	return c.TearDownAt(c.GetPath())
}

func (c *nfsUnmounter) TearDownAt(dir string) error {
	// Use extensiveMountPointCheck to consult /proc/mounts. We can't use faster
	// IsLikelyNotMountPoint (lstat()), since there may be root_squash on the
	// NFS server and kubelet may not be able to do lstat/stat() there.
	return mount.CleanupMountPoint(dir, c.mounter, true /* extensiveMountPointCheck */)
}

func getVolumeSource(spec *volume.Spec) (*v1.NFSVolumeSource, bool, error) {
	if spec.Volume != nil && spec.Volume.NFS != nil {
		return spec.Volume.NFS, spec.Volume.NFS.ReadOnly, nil
	} else if spec.PersistentVolume != nil &&
		spec.PersistentVolume.Spec.NFS != nil {
		return spec.PersistentVolume.Spec.NFS, spec.ReadOnly, nil
	}

	return nil, false, fmt.Errorf("Spec does not reference a NFS volume type")
}
<|endoftext|>


<commit_before>/*
Copyright 2014 The Kubernetes Authors All rights reserved.

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 nfs

import (
	"fmt"
	"os"

	"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
	"github.com/GoogleCloudPlatform/kubernetes/pkg/types"
	"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
	"github.com/GoogleCloudPlatform/kubernetes/pkg/util/mount"
	"github.com/GoogleCloudPlatform/kubernetes/pkg/volume"
	"github.com/golang/glog"
)

// This is the primary entrypoint for volume plugins.
func ProbeVolumePlugins() []volume.VolumePlugin {
	return []volume.VolumePlugin{&nfsPlugin{nil, mount.New()}}
}

type nfsPlugin struct {
	host    volume.VolumeHost
	mounter mount.Interface
}

var _ volume.VolumePlugin = &nfsPlugin{}

const (
	nfsPluginName = "kubernetes.io/nfs"
)

func (plugin *nfsPlugin) Init(host volume.VolumeHost) {
	plugin.host = host
}

func (plugin *nfsPlugin) Name() string {
	return nfsPluginName
}

func (plugin *nfsPlugin) CanSupport(spec *volume.Spec) bool {
	return spec.VolumeSource.NFS != nil
}

func (plugin *nfsPlugin) GetAccessModes() []api.AccessModeType {
	return []api.AccessModeType{
		api.ReadWriteOnce,
		api.ReadOnlyMany,
		api.ReadWriteMany,
	}
}

func (plugin *nfsPlugin) NewBuilder(spec *volume.Spec, pod *api.Pod, _ volume.VolumeOptions, _ mount.Interface) (volume.Builder, error) {
	return plugin.newBuilderInternal(spec, pod, plugin.mounter)
}

func (plugin *nfsPlugin) newBuilderInternal(spec *volume.Spec, pod *api.Pod, mounter mount.Interface) (volume.Builder, error) {
	return &nfs{
		volName:    spec.Name,
		server:     spec.VolumeSource.NFS.Server,
		exportPath: spec.VolumeSource.NFS.Path,
		readOnly:   spec.VolumeSource.NFS.ReadOnly,
		mounter:    mounter,
		pod:        pod,
		plugin:     plugin,
	}, nil
}

func (plugin *nfsPlugin) NewCleaner(volName string, podUID types.UID, _ mount.Interface) (volume.Cleaner, error) {
	return plugin.newCleanerInternal(volName, podUID, plugin.mounter)
}

func (plugin *nfsPlugin) newCleanerInternal(volName string, podUID types.UID, mounter mount.Interface) (volume.Cleaner, error) {
	return &nfs{
		volName:    volName,
		server:     "",
		exportPath: "",
		readOnly:   false,
		mounter:    mounter,
		pod:        &api.Pod{ObjectMeta: api.ObjectMeta{UID: podUID}},
		plugin:     plugin,
	}, nil
}

// NFS volumes represent a bare host file or directory mount of an NFS export.
type nfs struct {
	volName    string
	pod        *api.Pod
	server     string
	exportPath string
	readOnly   bool
	mounter    mount.Interface
	plugin     *nfsPlugin
}

// SetUp attaches the disk and bind mounts to the volume path.
func (nfsVolume *nfs) SetUp() error {
	return nfsVolume.SetUpAt(nfsVolume.GetPath())
}

func (nfsVolume *nfs) SetUpAt(dir string) error {
	mountpoint, err := nfsVolume.mounter.IsMountPoint(dir)
	glog.V(4).Infof("NFS mount set up: %s %v %v", dir, mountpoint, err)
	if err != nil && !os.IsNotExist(err) {
		return err
	}
	if mountpoint {
		return nil
	}
	os.MkdirAll(dir, 0750)
	source := fmt.Sprintf("%s:%s", nfsVolume.server, nfsVolume.exportPath)
	options := []string{}
	if nfsVolume.readOnly {
		options = append(options, "ro")
	}
	err = nfsVolume.mounter.Mount(source, dir, "nfs", options)
	if err != nil {
		mountpoint, mntErr := nfsVolume.mounter.IsMountPoint(dir)
		if mntErr != nil {
			glog.Errorf("IsMountpoint check failed: %v", mntErr)
			return err
		}
		if mountpoint {
			if mntErr = nfsVolume.mounter.Unmount(dir); mntErr != nil {
				glog.Errorf("Failed to unmount: %v", mntErr)
				return err
			}
			mountpoint, mntErr := nfsVolume.mounter.IsMountPoint(dir)
			if mntErr != nil {
				glog.Errorf("IsMountpoint check failed: %v", mntErr)
				return err
			}
			if mountpoint {
				// This is very odd, we don't expect it.  We'll try again next sync loop.
				glog.Errorf("%s is still mounted, despite call to unmount().  Will try again next sync loop.", dir)
				return err
			}
		}
		os.Remove(dir)
		return err
	}
	return nil
}

func (nfsVolume *nfs) GetPath() string {
	name := nfsPluginName
	return nfsVolume.plugin.host.GetPodVolumeDir(nfsVolume.pod.UID, util.EscapeQualifiedNameForDisk(name), nfsVolume.volName)
}

func (nfsVolume *nfs) TearDown() error {
	return nfsVolume.TearDownAt(nfsVolume.GetPath())
}

func (nfsVolume *nfs) TearDownAt(dir string) error {
	mountpoint, err := nfsVolume.mounter.IsMountPoint(dir)
	if err != nil {
		glog.Errorf("Error checking IsMountPoint: %v", err)
		return err
	}
	if !mountpoint {
		return os.Remove(dir)
	}

	if err := nfsVolume.mounter.Unmount(dir); err != nil {
		glog.Errorf("Unmounting failed: %v", err)
		return err
	}
	mountpoint, mntErr := nfsVolume.mounter.IsMountPoint(dir)
	if mntErr != nil {
		glog.Errorf("IsMountpoint check failed: %v", mntErr)
		return mntErr
	}
	if !mountpoint {
		if err := os.Remove(dir); err != nil {
			return err
		}
	}

	return nil
}
<commit_msg>Make nfs volume plugin use injected mounter<commit_after>/*
Copyright 2014 The Kubernetes Authors All rights reserved.

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 nfs

import (
	"fmt"
	"os"

	"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
	"github.com/GoogleCloudPlatform/kubernetes/pkg/types"
	"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
	"github.com/GoogleCloudPlatform/kubernetes/pkg/util/mount"
	"github.com/GoogleCloudPlatform/kubernetes/pkg/volume"
	"github.com/golang/glog"
)

// This is the primary entrypoint for volume plugins.
func ProbeVolumePlugins() []volume.VolumePlugin {
	return []volume.VolumePlugin{&nfsPlugin{nil}}
}

type nfsPlugin struct {
	host volume.VolumeHost
}

var _ volume.VolumePlugin = &nfsPlugin{}

const (
	nfsPluginName = "kubernetes.io/nfs"
)

func (plugin *nfsPlugin) Init(host volume.VolumeHost) {
	plugin.host = host
}

func (plugin *nfsPlugin) Name() string {
	return nfsPluginName
}

func (plugin *nfsPlugin) CanSupport(spec *volume.Spec) bool {
	return spec.VolumeSource.NFS != nil
}

func (plugin *nfsPlugin) GetAccessModes() []api.AccessModeType {
	return []api.AccessModeType{
		api.ReadWriteOnce,
		api.ReadOnlyMany,
		api.ReadWriteMany,
	}
}

func (plugin *nfsPlugin) NewBuilder(spec *volume.Spec, pod *api.Pod, _ volume.VolumeOptions, mounter mount.Interface) (volume.Builder, error) {
	return plugin.newBuilderInternal(spec, pod, mounter)
}

func (plugin *nfsPlugin) newBuilderInternal(spec *volume.Spec, pod *api.Pod, mounter mount.Interface) (volume.Builder, error) {
	return &nfs{
		volName:    spec.Name,
		server:     spec.VolumeSource.NFS.Server,
		exportPath: spec.VolumeSource.NFS.Path,
		readOnly:   spec.VolumeSource.NFS.ReadOnly,
		mounter:    mounter,
		pod:        pod,
		plugin:     plugin,
	}, nil
}

func (plugin *nfsPlugin) NewCleaner(volName string, podUID types.UID, mounter mount.Interface) (volume.Cleaner, error) {
	return plugin.newCleanerInternal(volName, podUID, mounter)
}

func (plugin *nfsPlugin) newCleanerInternal(volName string, podUID types.UID, mounter mount.Interface) (volume.Cleaner, error) {
	return &nfs{
		volName:    volName,
		server:     "",
		exportPath: "",
		readOnly:   false,
		mounter:    mounter,
		pod:        &api.Pod{ObjectMeta: api.ObjectMeta{UID: podUID}},
		plugin:     plugin,
	}, nil
}

// NFS volumes represent a bare host file or directory mount of an NFS export.
type nfs struct {
	volName    string
	pod        *api.Pod
	server     string
	exportPath string
	readOnly   bool
	mounter    mount.Interface
	plugin     *nfsPlugin
}

// SetUp attaches the disk and bind mounts to the volume path.
func (nfsVolume *nfs) SetUp() error {
	return nfsVolume.SetUpAt(nfsVolume.GetPath())
}

func (nfsVolume *nfs) SetUpAt(dir string) error {
	mountpoint, err := nfsVolume.mounter.IsMountPoint(dir)
	glog.V(4).Infof("NFS mount set up: %s %v %v", dir, mountpoint, err)
	if err != nil && !os.IsNotExist(err) {
		return err
	}
	if mountpoint {
		return nil
	}
	os.MkdirAll(dir, 0750)
	source := fmt.Sprintf("%s:%s", nfsVolume.server, nfsVolume.exportPath)
	options := []string{}
	if nfsVolume.readOnly {
		options = append(options, "ro")
	}
	err = nfsVolume.mounter.Mount(source, dir, "nfs", options)
	if err != nil {
		mountpoint, mntErr := nfsVolume.mounter.IsMountPoint(dir)
		if mntErr != nil {
			glog.Errorf("IsMountpoint check failed: %v", mntErr)
			return err
		}
		if mountpoint {
			if mntErr = nfsVolume.mounter.Unmount(dir); mntErr != nil {
				glog.Errorf("Failed to unmount: %v", mntErr)
				return err
			}
			mountpoint, mntErr := nfsVolume.mounter.IsMountPoint(dir)
			if mntErr != nil {
				glog.Errorf("IsMountpoint check failed: %v", mntErr)
				return err
			}
			if mountpoint {
				// This is very odd, we don't expect it.  We'll try again next sync loop.
				glog.Errorf("%s is still mounted, despite call to unmount().  Will try again next sync loop.", dir)
				return err
			}
		}
		os.Remove(dir)
		return err
	}
	return nil
}

func (nfsVolume *nfs) GetPath() string {
	name := nfsPluginName
	return nfsVolume.plugin.host.GetPodVolumeDir(nfsVolume.pod.UID, util.EscapeQualifiedNameForDisk(name), nfsVolume.volName)
}

func (nfsVolume *nfs) TearDown() error {
	return nfsVolume.TearDownAt(nfsVolume.GetPath())
}

func (nfsVolume *nfs) TearDownAt(dir string) error {
	mountpoint, err := nfsVolume.mounter.IsMountPoint(dir)
	if err != nil {
		glog.Errorf("Error checking IsMountPoint: %v", err)
		return err
	}
	if !mountpoint {
		return os.Remove(dir)
	}

	if err := nfsVolume.mounter.Unmount(dir); err != nil {
		glog.Errorf("Unmounting failed: %v", err)
		return err
	}
	mountpoint, mntErr := nfsVolume.mounter.IsMountPoint(dir)
	if mntErr != nil {
		glog.Errorf("IsMountpoint check failed: %v", mntErr)
		return mntErr
	}
	if !mountpoint {
		if err := os.Remove(dir); err != nil {
			return err
		}
	}

	return nil
}
<|endoftext|>


<commit_before>package assert

import (
	_ "fmt"
	"reflect"
	"testing"
)

// Usage:
// assert := assert.Assert(t)
// ...
// assert.Equal(actual, expected)
// assert.Nil(err, "X threw error")
func Assert(t *testing.T) *Assertion {
	return &Assertion{t}
}

type Assertion struct {
	t *testing.T
}

func (a *Assertion) True(b bool, message string, messageParams ...interface{}) {
	if !b {
		a.t.Fatalf(message, messageParams...)
	}
}

func (a *Assertion) False(b bool, message string, messageParams ...interface{}) {
	a.True(!b, message, messageParams...)
}

func (a *Assertion) Nil(val interface{}, message string, messageParams ...interface{}) {
	eq := reflect.DeepEqual(val, nil)
	a.True(eq, message, messageParams...)
}

func (a *Assertion) NotNil(val interface{}, message string, messageParams ...interface{}) {
	eq := reflect.DeepEqual(val, nil)
	a.True(!eq, message, messageParams...)
}

func (a *Assertion) Equal(actual, expected interface{}) {
	eq := reflect.DeepEqual(actual, expected)
	a.True(eq, "\nExpected: %v\nReceived: %v", expected, actual)
}

func (a *Assertion) NotEqual(actual, expected interface{}) {
	eq := reflect.DeepEqual(actual, expected)
	a.True(!eq, "Expected %v to not equal %v, but it did", expected, actual)
}
<commit_msg>shows file and line number of test on failure<commit_after>package assert

import (
	"fmt"
	"reflect"
	"testing"
	"runtime"
	"strings"
)

// Usage:
// assert := assert.Assert(t)
// ...
// assert.Equal(actual, expected)
// assert.Nil(err, "X threw error")
func Assert(t *testing.T) *Assertion {
	return &Assertion{t}
}

type Assertion struct {
	t *testing.T
}

func caller() string {
	fname, line, caller := "assert.go", 0, 1
	for ; strings.HasSuffix(fname, "assert.go") && caller<4; caller++ {
		_, fname, line, _ = runtime.Caller(caller)
	}
	fname = fname[strings.LastIndex(fname, "/")+1:]
	return fmt.Sprintf("\n%v:%v ", fname, line)
}

func (a *Assertion) True(b bool, message string, messageParams ...interface{}) {
	if !b {
		a.t.Fatalf(caller() + message, messageParams...)
	}
}

func (a *Assertion) False(b bool, message string, messageParams ...interface{}) {
	a.True(!b, message, messageParams...)
}

func (a *Assertion) Nil(val interface{}, message string, messageParams ...interface{}) {
	eq := reflect.DeepEqual(val, nil)
	a.True(eq, message, messageParams...)
}

func (a *Assertion) NotNil(val interface{}, message string, messageParams ...interface{}) {
	eq := reflect.DeepEqual(val, nil)
	a.True(!eq, message, messageParams...)
}

func (a *Assertion) Equal(actual, expected interface{}) {
	eq := reflect.DeepEqual(actual, expected)
	a.True(eq, "\nExpected: %v\nReceived: %v", expected, actual)
}

func (a *Assertion) NotEqual(actual, expected interface{}) {
	eq := reflect.DeepEqual(actual, expected)
	a.True(!eq, "Expected %v to not equal %v, but it did", expected, actual)
}
<|endoftext|>