<commit_before>package parser

import (
	gotoken "go/token"
	"testing"

	"github.com/wellington/sass/token"
)

type elt struct {
	tok token.Token
	lit string
	// class int
}

const whitespace = "  \t  \n\n\n" // to separate tokens

var tokens = [...]elt{
	{token.CMT, "/* a comment */"},
	{token.CMT, "// single comment \n"},
	{token.INT, "0"},
	{token.INT, "314"},
	{token.FLOAT, "3.1415"},

	// Operators and delimiters
	{token.ADD, "+"},
	{token.SUB, "-"},
	{token.MUL, "*"},
	{token.QUO, "/"},
	{token.REM, "%"},

	// {token.LAND, "&&"},
	// {token.LOR, "||"},
	// {token.EQL, "=="},
	// {token.LSS, "<"},
	// {token.GTR, ">"},
	// {token.ASSIGN, "="},
	// {token.NOT, "!"},

	// {token.NEQ, "!="},
	// {token.LEQ, "<="},
	// {token.GEQ, ">="},
	// {token.DEFINE, ":="},

	// // Delimiters
	{token.LPAREN, "("},
	{token.LBRACK, "["},
	{token.LBRACE, "{"},
	{token.COMMA, ","},
	{token.PERIOD, "."},

	// {token.RPAREN, ")"},
	// {token.RBRACK, "]"},
	// {token.RBRACE, "}"},
	// {token.SEMICOLON, ";"},
	// {token.COLON, ":"},
}

var source = func() []byte {
	var src []byte
	for _, t := range tokens {
		src = append(src, t.lit...)
		src = append(src, whitespace...)
	}

	return src
}()

var fset = gotoken.NewFileSet()

func newlineCount(s string) int {
	n := 0
	for i := 0; i < len(s); i++ {
		if s[i] == '\n' {
			n++
		}
	}
	return n
}

func checkPos(t *testing.T, lit string, p gotoken.Pos, expected gotoken.Position) {
	pos := fset.Position(p)
	if pos.Filename != expected.Filename {
		t.Errorf("bad filename for %q: got %s, expected %s", lit, pos.Filename, expected.Filename)
	}
	if pos.Offset != expected.Offset {
		t.Errorf("bad position for %q: got %d, expected %d", lit, pos.Offset, expected.Offset)
	}
	if pos.Line != expected.Line {
		t.Errorf("bad line for %q: got %d, expected %d", lit, pos.Line, expected.Line)
	}
	if pos.Column != expected.Column {
		t.Errorf("bad column for %q: got %d, expected %d", lit, pos.Column, expected.Column)
	}
}

func TestScan(t *testing.T) {
	whitespaceLinecount := newlineCount(whitespace)

	// error handler
	eh := func(_ gotoken.Position, msg string) {
		t.Errorf("error handler called (msg = %s)", msg)
	}

	var s Scanner
	s.Init(fset.AddFile("", fset.Base(), len(source)), source, eh)

	epos := gotoken.Position{
		Filename: "",
		Offset:   0,
		Line:     1,
		Column:   1,
	}

	index := 0
	for {
		pos, tok, lit := s.Scan()
		if tok == token.EOF {
			epos.Line = newlineCount(string(source))
			epos.Column = 2
		}
		checkPos(t, lit, pos, epos)

		// check token
		e := elt{token.EOF, ""}
		if index < len(tokens) {
			e = tokens[index]
			index++
		}
		if tok != e.tok {
			t.Errorf("bad token for %q: got %s, expected %s", lit, tok, e.tok)
		}

		// check literal
		elit := ""
		switch e.tok {
		case token.CMT:
			// no CRs in comments
			elit = string(stripCR([]byte(e.lit)))
			//-style comment literal doesn't contain newline
			if elit[1] == '/' {
				elit = elit[0 : len(elit)-1]
			}
		case token.IDENT:
			elit = e.lit
		case token.SEMICOLON:
			elit = ";"
		default:
			if e.tok.IsLiteral() {
				// no CRs in raw string literals
				elit = e.lit
				if elit[0] == '`' {
					elit = string(stripCR([]byte(elit)))
				}
			} else if e.tok.IsKeyword() {
				elit = e.lit
			}
		}
		if lit != elit {
			t.Errorf("bad literal for %q: got %q, expected %q", lit, lit, elit)
		}

		if tok == token.EOF {
			break
		}

		// update position
		epos.Offset += len(e.lit) + len(whitespace)
		epos.Line += newlineCount(e.lit) + whitespaceLinecount

	}
}
<commit_msg>turn up the delims<commit_after>package parser

import (
	gotoken "go/token"
	"testing"

	"github.com/wellington/sass/token"
)

type elt struct {
	tok token.Token
	lit string
	// class int
}

const whitespace = "  \t  \n\n\n" // to separate tokens

var tokens = [...]elt{
	{token.CMT, "/* a comment */"},
	{token.CMT, "// single comment \n"},
	{token.INT, "0"},
	{token.INT, "314"},
	{token.FLOAT, "3.1415"},

	// Operators and delimiters
	{token.ADD, "+"},
	{token.SUB, "-"},
	{token.MUL, "*"},
	{token.QUO, "/"},
	{token.REM, "%"},

	// {token.LAND, "&&"},
	// {token.LOR, "||"},
	// {token.EQL, "=="},
	// {token.LSS, "<"},
	// {token.GTR, ">"},
	// {token.ASSIGN, "="},
	// {token.NOT, "!"},

	// {token.NEQ, "!="},
	// {token.LEQ, "<="},
	// {token.GEQ, ">="},

	// Delimiters
	{token.LPAREN, "("},
	{token.LBRACK, "["},
	{token.LBRACE, "{"},
	{token.COMMA, ","},
	{token.PERIOD, "."},

	{token.RPAREN, ")"},
	{token.RBRACK, "]"},
	{token.RBRACE, "}"},
	{token.SEMICOLON, ";"},
	{token.COLON, ":"},
}

var source = func() []byte {
	var src []byte
	for _, t := range tokens {
		src = append(src, t.lit...)
		src = append(src, whitespace...)
	}

	return src
}()

var fset = gotoken.NewFileSet()

func newlineCount(s string) int {
	n := 0
	for i := 0; i < len(s); i++ {
		if s[i] == '\n' {
			n++
		}
	}
	return n
}

func checkPos(t *testing.T, lit string, p gotoken.Pos, expected gotoken.Position) {
	pos := fset.Position(p)
	if pos.Filename != expected.Filename {
		t.Errorf("bad filename for %q: got %s, expected %s", lit, pos.Filename, expected.Filename)
	}
	if pos.Offset != expected.Offset {
		t.Errorf("bad position for %q: got %d, expected %d", lit, pos.Offset, expected.Offset)
	}
	if pos.Line != expected.Line {
		t.Errorf("bad line for %q: got %d, expected %d", lit, pos.Line, expected.Line)
	}
	if pos.Column != expected.Column {
		t.Errorf("bad column for %q: got %d, expected %d", lit, pos.Column, expected.Column)
	}
}

func TestScan(t *testing.T) {
	whitespaceLinecount := newlineCount(whitespace)

	// error handler
	eh := func(_ gotoken.Position, msg string) {
		t.Errorf("error handler called (msg = %s)", msg)
	}

	var s Scanner
	s.Init(fset.AddFile("", fset.Base(), len(source)), source, eh)

	epos := gotoken.Position{
		Filename: "",
		Offset:   0,
		Line:     1,
		Column:   1,
	}

	index := 0
	for {
		pos, tok, lit := s.Scan()
		if tok == token.EOF {
			epos.Line = newlineCount(string(source))
			epos.Column = 2
		}
		checkPos(t, lit, pos, epos)

		// check token
		e := elt{token.EOF, ""}
		if index < len(tokens) {
			e = tokens[index]
			index++
		}
		if tok != e.tok {
			t.Errorf("bad token for %q: got %s, expected %s", lit, tok, e.tok)
		}

		// check literal
		elit := ""
		switch e.tok {
		case token.CMT:
			// no CRs in comments
			elit = string(stripCR([]byte(e.lit)))
			//-style comment literal doesn't contain newline
			if elit[1] == '/' {
				elit = elit[0 : len(elit)-1]
			}
		case token.IDENT:
			elit = e.lit
		case token.SEMICOLON:
			elit = ";"
		default:
			if e.tok.IsLiteral() {
				// no CRs in raw string literals
				elit = e.lit
				if elit[0] == '`' {
					elit = string(stripCR([]byte(elit)))
				}
			} else if e.tok.IsKeyword() {
				elit = e.lit
			}
		}
		if lit != elit {
			t.Errorf("bad literal for %q: got %q, expected %q", lit, lit, elit)
		}

		if tok == token.EOF {
			break
		}

		// update position
		epos.Offset += len(e.lit) + len(whitespace)
		epos.Line += newlineCount(e.lit) + whitespaceLinecount

	}
}
<|endoftext|>


<commit_before>package process_manager

import (
	"fmt"
	"os"
	"os/signal"
	"syscall"
	"time"

	log "github.com/Sirupsen/logrus"
)

type Healthchecker func() error
type TeardownCallback func()

type ProcessManager struct {
	tdcb      TeardownCallback
	teardown  chan chan interface{}
	pid       int
	subscribe chan chan int
}

func NewProcessManager(tdcb TeardownCallback, executablePath string, args []string, healthcheck Healthchecker, chroot *string) (*ProcessManager, error) {
	retFuture := make(chan *ProcessManager)
	go startProcessManager(tdcb, executablePath, args, healthcheck, retFuture, chroot)
	retVal := <-retFuture
	log.Info("Retval: ", retVal)
	if retVal == nil {
		err := fmt.Errorf("Unknown Error")
		return retVal, err
	} else {
		return retVal, nil
	}
}

func (pm *ProcessManager) Listen() chan int {
	ret := make(chan int, 1)
	pm.subscribe <- ret
	return ret
}
func (pm *ProcessManager) TearDown() {
	replyChan := make(chan interface{})
	pm.teardown <- replyChan
	<-replyChan
	return
}
func startProcessManager(tdcb TeardownCallback, executablePath string, args []string, healthcheck Healthchecker, retChan chan *ProcessManager, chroot *string) {
	defer close(retChan)
	pm := &ProcessManager{
		teardown:  make(chan chan interface{}, 10),
		tdcb:      tdcb,
		subscribe: make(chan chan int, 10),
	}
	defer close(pm.teardown)
	defer close(pm.subscribe)
	signals := make(chan os.Signal, 3)
	signal.Notify(signals, syscall.SIGTERM, syscall.SIGINT, syscall.SIGKILL)
	defer signal.Stop(signals)
	defer close(signals)

	// Hopefully we don't get more than 1000 SIGCHLD in the before we come up
	sigchlds := make(chan os.Signal, 1000)
	signal.Notify(sigchlds, syscall.SIGCHLD)

	pm.start(executablePath, args, chroot)
	waitChan := subscribe(pm.pid)
	defer unsubscribe(pm.pid)
	defer close(waitChan)
	subscriptions := []chan int{}

	// Wait 60 seconds for the process to start, and pass its healthcheck.
	for i := 0; i < 60; i++ {
		select {
		case subscribe := <-pm.subscribe:
			{
				subscriptions = append(subscriptions, subscribe)
			}
		case <-signals:
			{
				log.Info("Tearing down at signal")
				pm.notify(-1, subscriptions)
				pm.killProcess(waitChan)
				pm.tdcb()
				return
			}
		case status := <-waitChan:
			{
				pm.notify(status.wstatus.ExitStatus(), subscriptions)
				pm.tdcb()
				pm.killProcess(waitChan)
				return
			}
		case tearDownChan := <-pm.teardown:
			{
				log.Info("Tearing down")
				pm.notify(-1, subscriptions)
				pm.killProcess(waitChan)
				pm.tdcb()
				tearDownChan <- nil
				return
			}
		case <-time.After(1000 * time.Millisecond):
			{
				// Try pinging Riak Explorer
				err := healthcheck()
				if err == nil {
					log.Info("Process status: ", err)
					retChan <- pm
					// re.background() should never return
					pm.background(waitChan, subscriptions, signals)
					return
				}
			}
		}
	}
	log.Info("Process manager failed to start process in time")
	pm.killProcess(waitChan)
}

func (pm *ProcessManager) notify(status int, subscriptions []chan int) {
	log.Info("Notify being called")
	for _, sub := range subscriptions {
		select {
		case sub <- status:
		default:
		}
	}
}
func (pm *ProcessManager) background(waitChan chan pidChangeNotification, subscriptions []chan int, signals chan os.Signal) {
	log.Info("Going into background mode")
	for {
		select {
		case status := <-waitChan:
			{
				pm.notify(status.wstatus.ExitStatus(), subscriptions)
				pm.tdcb()
				pm.killProcess(waitChan)
				return
			}
		case <-signals:
			{
				log.Info("Tearing down at signal")
				pm.notify(-1, subscriptions)
				pm.tdcb()
				pm.killProcess(waitChan)
				return
			}
		case tearDownChan := <-pm.teardown:
			{
				log.Info("Tearing down")
				pm.notify(-1, subscriptions)
				pm.killProcess(waitChan)
				pm.tdcb()
				tearDownChan <- nil
				return
			}
		case subscribe := <-pm.subscribe:
			{
				subscriptions = append(subscriptions, subscribe)
			}
		}
	}
}

func (pm *ProcessManager) killProcess(waitChan chan pidChangeNotification) {
	log.Info("Killing process")
	// Is it alive?
	if syscall.Kill(pm.pid, syscall.Signal(0)) != nil {
		return
	}

	// TODO: Work around some potential races here

	syscall.Kill(pm.pid, syscall.SIGTERM)
	select {
	case <-waitChan:
		{
			return
		}
	case <-time.After(time.Second * 5):
		{
			syscall.Kill(pm.pid, syscall.SIGKILL)
		}
	}
	<-waitChan
}

<commit_msg>Go FMT project<commit_after>package process_manager

import (
	"fmt"
	"os"
	"os/signal"
	"syscall"
	"time"

	log "github.com/Sirupsen/logrus"
)

type Healthchecker func() error
type TeardownCallback func()

type ProcessManager struct {
	tdcb      TeardownCallback
	teardown  chan chan interface{}
	pid       int
	subscribe chan chan int
}

func NewProcessManager(tdcb TeardownCallback, executablePath string, args []string, healthcheck Healthchecker, chroot *string) (*ProcessManager, error) {
	retFuture := make(chan *ProcessManager)
	go startProcessManager(tdcb, executablePath, args, healthcheck, retFuture, chroot)
	retVal := <-retFuture
	log.Info("Retval: ", retVal)
	if retVal == nil {
		err := fmt.Errorf("Unknown Error")
		return retVal, err
	} else {
		return retVal, nil
	}
}

func (pm *ProcessManager) Listen() chan int {
	ret := make(chan int, 1)
	pm.subscribe <- ret
	return ret
}
func (pm *ProcessManager) TearDown() {
	replyChan := make(chan interface{})
	pm.teardown <- replyChan
	<-replyChan
	return
}
func startProcessManager(tdcb TeardownCallback, executablePath string, args []string, healthcheck Healthchecker, retChan chan *ProcessManager, chroot *string) {
	defer close(retChan)
	pm := &ProcessManager{
		teardown:  make(chan chan interface{}, 10),
		tdcb:      tdcb,
		subscribe: make(chan chan int, 10),
	}
	defer close(pm.teardown)
	defer close(pm.subscribe)
	signals := make(chan os.Signal, 3)
	signal.Notify(signals, syscall.SIGTERM, syscall.SIGINT, syscall.SIGKILL)
	defer signal.Stop(signals)
	defer close(signals)

	// Hopefully we don't get more than 1000 SIGCHLD in the before we come up
	sigchlds := make(chan os.Signal, 1000)
	signal.Notify(sigchlds, syscall.SIGCHLD)

	pm.start(executablePath, args, chroot)
	waitChan := subscribe(pm.pid)
	defer unsubscribe(pm.pid)
	defer close(waitChan)
	subscriptions := []chan int{}

	// Wait 60 seconds for the process to start, and pass its healthcheck.
	for i := 0; i < 60; i++ {
		select {
		case subscribe := <-pm.subscribe:
			{
				subscriptions = append(subscriptions, subscribe)
			}
		case <-signals:
			{
				log.Info("Tearing down at signal")
				pm.notify(-1, subscriptions)
				pm.killProcess(waitChan)
				pm.tdcb()
				return
			}
		case status := <-waitChan:
			{
				pm.notify(status.wstatus.ExitStatus(), subscriptions)
				pm.tdcb()
				pm.killProcess(waitChan)
				return
			}
		case tearDownChan := <-pm.teardown:
			{
				log.Info("Tearing down")
				pm.notify(-1, subscriptions)
				pm.killProcess(waitChan)
				pm.tdcb()
				tearDownChan <- nil
				return
			}
		case <-time.After(1000 * time.Millisecond):
			{
				// Try pinging Riak Explorer
				err := healthcheck()
				if err == nil {
					log.Info("Process status: ", err)
					retChan <- pm
					// re.background() should never return
					pm.background(waitChan, subscriptions, signals)
					return
				}
			}
		}
	}
	log.Info("Process manager failed to start process in time")
	pm.killProcess(waitChan)
}

func (pm *ProcessManager) notify(status int, subscriptions []chan int) {
	log.Info("Notify being called")
	for _, sub := range subscriptions {
		select {
		case sub <- status:
		default:
		}
	}
}
func (pm *ProcessManager) background(waitChan chan pidChangeNotification, subscriptions []chan int, signals chan os.Signal) {
	log.Info("Going into background mode")
	for {
		select {
		case status := <-waitChan:
			{
				pm.notify(status.wstatus.ExitStatus(), subscriptions)
				pm.tdcb()
				pm.killProcess(waitChan)
				return
			}
		case <-signals:
			{
				log.Info("Tearing down at signal")
				pm.notify(-1, subscriptions)
				pm.tdcb()
				pm.killProcess(waitChan)
				return
			}
		case tearDownChan := <-pm.teardown:
			{
				log.Info("Tearing down")
				pm.notify(-1, subscriptions)
				pm.killProcess(waitChan)
				pm.tdcb()
				tearDownChan <- nil
				return
			}
		case subscribe := <-pm.subscribe:
			{
				subscriptions = append(subscriptions, subscribe)
			}
		}
	}
}

func (pm *ProcessManager) killProcess(waitChan chan pidChangeNotification) {
	log.Info("Killing process")
	// Is it alive?
	if syscall.Kill(pm.pid, syscall.Signal(0)) != nil {
		return
	}

	// TODO: Work around some potential races here

	syscall.Kill(pm.pid, syscall.SIGTERM)
	select {
	case <-waitChan:
		{
			return
		}
	case <-time.After(time.Second * 5):
		{
			syscall.Kill(pm.pid, syscall.SIGKILL)
		}
	}
	<-waitChan
}
<|endoftext|>


<commit_before>// Copyright 2020 Google LLC

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

//     https://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 cloudsql

import (
	"context"
	"crypto/rsa"
	"crypto/tls"
	"fmt"
	"regexp"
	"sync"
	"time"

	errtype "cloud.google.com/go/cloudsqlconn/errtype"
	"cloud.google.com/go/cloudsqlconn/internal/alloydb"
)

const (
	// refreshBuffer is the amount of time before a result expires to start a new refresh attempt.
	refreshBuffer = 5 * time.Minute
)

var (
	// Instance connection name is the format <PROJECT>:<REGION>:<CLUSTER>:<INSTANCE>
	// Additionally, we have to support legacy "domain-scoped" projects (e.g. "google.com:PROJECT")
	connNameRegex = regexp.MustCompile("([^:]+(:[^:]+)?):([^:]+):([^:]+):([^:]+)")
)

// connName represents the "instance connection name", in the format "project:region:name". Use the
// "parseConnName" method to initialize this struct.
type connName struct {
	project string
	region  string
	cluster string
	name    string
}

func (c *connName) String() string {
	return fmt.Sprintf("%s:%s:%s:%s", c.project, c.region, c.cluster, c.name)
}

// parseConnName initializes a new connName struct.
func parseConnName(cn string) (connName, error) {
	b := []byte(cn)
	m := connNameRegex.FindSubmatch(b)
	if m == nil {
		err := errtype.NewConfigError(
			"invalid instance connection name, expected PROJECT:REGION:CLUSTER:INSTANCE",
			cn,
		)
		return connName{}, err
	}

	c := connName{
		project: string(m[1]),
		region:  string(m[3]),
		cluster: string(m[4]),
		name:    string(m[5]),
	}
	return c, nil
}

type metadata struct {
	ipAddrs map[string]string
	version string
}

// refreshOperation is a pending result of a refresh operation of data used to connect securely. It should
// only be initialized by the Instance struct as part of a refresh cycle.
type refreshOperation struct {
	result refreshResult
	err    error

	// timer that triggers refresh, can be used to cancel.
	timer *time.Timer
	// indicates the struct is ready to read from
	ready chan struct{}
}

// Cancel prevents the instanceInfo from starting, if it hasn't already started. Returns true if timer
// was stopped successfully, or false if it has already started.
func (r *refreshOperation) Cancel() bool {
	return r.timer.Stop()
}

// Wait blocks until the refreshOperation attempt is completed.
func (r *refreshOperation) Wait(ctx context.Context) error {
	select {
	case <-r.ready:
		return r.err
	case <-ctx.Done():
		return ctx.Err()
	}
}

// IsValid returns true if this result is complete, successful, and is still valid.
func (r *refreshOperation) IsValid() bool {
	// verify the result has finished running
	select {
	default:
		return false
	case <-r.ready:
		if r.err != nil || time.Now().After(r.result.expiry) {
			return false
		}
		return true
	}
}

// Instance manages the information used to connect to the Cloud SQL instance by periodically calling
// the Cloud SQL Admin API. It automatically refreshes the required information approximately 5 minutes
// before the previous certificate expires (every 55 minutes).
type Instance struct {
	connName
	key *rsa.PrivateKey
	r   refresher

	resultGuard sync.RWMutex
	// cur represents the current refreshOperation that will be used to create connections. If a valid complete
	// refreshOperation isn't available it's possible for cur to be equal to next.
	cur *refreshOperation
	// next represents a future or ongoing refreshOperation. Once complete, it will replace cur and schedule a
	// replacement to occur.
	next *refreshOperation

	// OpenConns is the number of open connections to the instance.
	OpenConns uint64

	// ctx is the default ctx for refresh operations. Canceling it prevents new refresh
	// operations from being triggered.
	ctx    context.Context
	cancel context.CancelFunc
}

// NewInstance initializes a new Instance given an instance connection name
func NewInstance(
	instance string,
	client *alloydb.Client,
	key *rsa.PrivateKey,
	refreshTimeout time.Duration,
	dialerID string,
) (*Instance, error) {
	cn, err := parseConnName(instance)
	if err != nil {
		return nil, err
	}
	ctx, cancel := context.WithCancel(context.Background())
	i := &Instance{
		connName: cn,
		key:      key,
		r: newRefresher(
			client,
			refreshTimeout,
			30*time.Second,
			2,
			dialerID,
		),
		ctx:    ctx,
		cancel: cancel,
	}
	// For the initial refresh operation, set cur = next so that connection requests block
	// until the first refresh is complete.
	i.resultGuard.Lock()
	i.cur = i.scheduleRefresh(0)
	i.next = i.cur
	i.resultGuard.Unlock()
	return i, nil
}

// Close closes the instance; it stops the refresh cycle and prevents it from making
// additional calls to the Cloud SQL Admin API.
func (i *Instance) Close() {
	i.cancel()
}

// ConnectInfo returns an IP address specified by ipType (i.e., public or
// private) and a TLS config that can be used to connect to a Cloud SQL
// instance.
func (i *Instance) ConnectInfo(ctx context.Context) (string, *tls.Config, error) {
	res, err := i.result(ctx)
	if err != nil {
		return "", nil, err
	}
	return res.result.instanceIPAddr, res.result.conf, nil
}

// ForceRefresh triggers an immediate refresh operation to be scheduled and used for future connection attempts.
func (i *Instance) ForceRefresh() {
	i.resultGuard.Lock()
	defer i.resultGuard.Unlock()
	// If the next refresh hasn't started yet, we can cancel it and start an immediate one
	if i.next.Cancel() {
		i.next = i.scheduleRefresh(0)
	}
	// block all sequential connection attempts on the next refresh result
	i.cur = i.next
}

// result returns the most recent refresh result (waiting for it to complete if necessary)
func (i *Instance) result(ctx context.Context) (*refreshOperation, error) {
	i.resultGuard.RLock()
	res := i.cur
	i.resultGuard.RUnlock()
	err := res.Wait(ctx)
	if err != nil {
		return nil, err
	}
	return res, nil
}

// scheduleRefresh schedules a refresh operation to be triggered after a given
// duration. The returned refreshOperation can be used to either Cancel or Wait
// for the operations result.
func (i *Instance) scheduleRefresh(d time.Duration) *refreshOperation {
	res := &refreshOperation{}
	res.ready = make(chan struct{})
	res.timer = time.AfterFunc(d, func() {
		res.result, res.err = i.r.performRefresh(i.ctx, i.connName, i.key)
		close(res.ready)

		// Once the refresh is complete, update "current" with working result and schedule a new refresh
		i.resultGuard.Lock()
		defer i.resultGuard.Unlock()
		// if failed, scheduled the next refresh immediately
		if res.err != nil {
			i.next = i.scheduleRefresh(0)
			// If the latest result is bad, avoid replacing the used result while it's
			// still valid and potentially able to provide successful connections.
			// TODO: This means that errors while the current result is still valid are
			// surpressed. We should try to surface errors in a more meaningful way.
			if !i.cur.IsValid() {
				i.cur = res
			}
			return
		}
		// Update the current results, and schedule the next refresh in the future
		i.cur = res
		select {
		case <-i.ctx.Done():
			// instance has been closed, don't schedule anything
			return
		default:
		}
		nextRefresh := i.cur.result.expiry.Add(-refreshBuffer)
		i.next = i.scheduleRefresh(time.Until(nextRefresh))
	})
	return res
}

// String returns the instance's connection name.
func (i *Instance) String() string {
	return i.connName.String()
}
<commit_msg>Update refresh buffer to 12 hours<commit_after>// Copyright 2020 Google LLC

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

//     https://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 cloudsql

import (
	"context"
	"crypto/rsa"
	"crypto/tls"
	"fmt"
	"regexp"
	"sync"
	"time"

	errtype "cloud.google.com/go/cloudsqlconn/errtype"
	"cloud.google.com/go/cloudsqlconn/internal/alloydb"
)

const (
	// refreshBuffer is the amount of time before a result expires to start a
	// new refresh attempt.
	refreshBuffer = 12 * time.Hour
)

var (
	// Instance connection name is the format <PROJECT>:<REGION>:<CLUSTER>:<INSTANCE>
	// Additionally, we have to support legacy "domain-scoped" projects (e.g. "google.com:PROJECT")
	connNameRegex = regexp.MustCompile("([^:]+(:[^:]+)?):([^:]+):([^:]+):([^:]+)")
)

// connName represents the "instance connection name", in the format "project:region:name". Use the
// "parseConnName" method to initialize this struct.
type connName struct {
	project string
	region  string
	cluster string
	name    string
}

func (c *connName) String() string {
	return fmt.Sprintf("%s:%s:%s:%s", c.project, c.region, c.cluster, c.name)
}

// parseConnName initializes a new connName struct.
func parseConnName(cn string) (connName, error) {
	b := []byte(cn)
	m := connNameRegex.FindSubmatch(b)
	if m == nil {
		err := errtype.NewConfigError(
			"invalid instance connection name, expected PROJECT:REGION:CLUSTER:INSTANCE",
			cn,
		)
		return connName{}, err
	}

	c := connName{
		project: string(m[1]),
		region:  string(m[3]),
		cluster: string(m[4]),
		name:    string(m[5]),
	}
	return c, nil
}

type metadata struct {
	ipAddrs map[string]string
	version string
}

// refreshOperation is a pending result of a refresh operation of data used to connect securely. It should
// only be initialized by the Instance struct as part of a refresh cycle.
type refreshOperation struct {
	result refreshResult
	err    error

	// timer that triggers refresh, can be used to cancel.
	timer *time.Timer
	// indicates the struct is ready to read from
	ready chan struct{}
}

// Cancel prevents the instanceInfo from starting, if it hasn't already started. Returns true if timer
// was stopped successfully, or false if it has already started.
func (r *refreshOperation) Cancel() bool {
	return r.timer.Stop()
}

// Wait blocks until the refreshOperation attempt is completed.
func (r *refreshOperation) Wait(ctx context.Context) error {
	select {
	case <-r.ready:
		return r.err
	case <-ctx.Done():
		return ctx.Err()
	}
}

// IsValid returns true if this result is complete, successful, and is still valid.
func (r *refreshOperation) IsValid() bool {
	// verify the result has finished running
	select {
	default:
		return false
	case <-r.ready:
		if r.err != nil || time.Now().After(r.result.expiry) {
			return false
		}
		return true
	}
}

// Instance manages the information used to connect to the Cloud SQL instance by periodically calling
// the Cloud SQL Admin API. It automatically refreshes the required information approximately 5 minutes
// before the previous certificate expires (every 55 minutes).
type Instance struct {
	connName
	key *rsa.PrivateKey
	r   refresher

	resultGuard sync.RWMutex
	// cur represents the current refreshOperation that will be used to create connections. If a valid complete
	// refreshOperation isn't available it's possible for cur to be equal to next.
	cur *refreshOperation
	// next represents a future or ongoing refreshOperation. Once complete, it will replace cur and schedule a
	// replacement to occur.
	next *refreshOperation

	// OpenConns is the number of open connections to the instance.
	OpenConns uint64

	// ctx is the default ctx for refresh operations. Canceling it prevents new refresh
	// operations from being triggered.
	ctx    context.Context
	cancel context.CancelFunc
}

// NewInstance initializes a new Instance given an instance connection name
func NewInstance(
	instance string,
	client *alloydb.Client,
	key *rsa.PrivateKey,
	refreshTimeout time.Duration,
	dialerID string,
) (*Instance, error) {
	cn, err := parseConnName(instance)
	if err != nil {
		return nil, err
	}
	ctx, cancel := context.WithCancel(context.Background())
	i := &Instance{
		connName: cn,
		key:      key,
		r: newRefresher(
			client,
			refreshTimeout,
			30*time.Second,
			2,
			dialerID,
		),
		ctx:    ctx,
		cancel: cancel,
	}
	// For the initial refresh operation, set cur = next so that connection requests block
	// until the first refresh is complete.
	i.resultGuard.Lock()
	i.cur = i.scheduleRefresh(0)
	i.next = i.cur
	i.resultGuard.Unlock()
	return i, nil
}

// Close closes the instance; it stops the refresh cycle and prevents it from making
// additional calls to the Cloud SQL Admin API.
func (i *Instance) Close() {
	i.cancel()
}

// ConnectInfo returns an IP address specified by ipType (i.e., public or
// private) and a TLS config that can be used to connect to a Cloud SQL
// instance.
func (i *Instance) ConnectInfo(ctx context.Context) (string, *tls.Config, error) {
	res, err := i.result(ctx)
	if err != nil {
		return "", nil, err
	}
	return res.result.instanceIPAddr, res.result.conf, nil
}

// ForceRefresh triggers an immediate refresh operation to be scheduled and used for future connection attempts.
func (i *Instance) ForceRefresh() {
	i.resultGuard.Lock()
	defer i.resultGuard.Unlock()
	// If the next refresh hasn't started yet, we can cancel it and start an immediate one
	if i.next.Cancel() {
		i.next = i.scheduleRefresh(0)
	}
	// block all sequential connection attempts on the next refresh result
	i.cur = i.next
}

// result returns the most recent refresh result (waiting for it to complete if necessary)
func (i *Instance) result(ctx context.Context) (*refreshOperation, error) {
	i.resultGuard.RLock()
	res := i.cur
	i.resultGuard.RUnlock()
	err := res.Wait(ctx)
	if err != nil {
		return nil, err
	}
	return res, nil
}

// scheduleRefresh schedules a refresh operation to be triggered after a given
// duration. The returned refreshOperation can be used to either Cancel or Wait
// for the operations result.
func (i *Instance) scheduleRefresh(d time.Duration) *refreshOperation {
	res := &refreshOperation{}
	res.ready = make(chan struct{})
	res.timer = time.AfterFunc(d, func() {
		res.result, res.err = i.r.performRefresh(i.ctx, i.connName, i.key)
		close(res.ready)

		// Once the refresh is complete, update "current" with working result and schedule a new refresh
		i.resultGuard.Lock()
		defer i.resultGuard.Unlock()
		// if failed, scheduled the next refresh immediately
		if res.err != nil {
			i.next = i.scheduleRefresh(0)
			// If the latest result is bad, avoid replacing the used result while it's
			// still valid and potentially able to provide successful connections.
			// TODO: This means that errors while the current result is still valid are
			// surpressed. We should try to surface errors in a more meaningful way.
			if !i.cur.IsValid() {
				i.cur = res
			}
			return
		}
		// Update the current results, and schedule the next refresh in the future
		i.cur = res
		select {
		case <-i.ctx.Done():
			// instance has been closed, don't schedule anything
			return
		default:
		}
		nextRefresh := i.cur.result.expiry.Add(-refreshBuffer)
		i.next = i.scheduleRefresh(time.Until(nextRefresh))
	})
	return res
}

// String returns the instance's connection name.
func (i *Instance) String() string {
	return i.connName.String()
}
<|endoftext|>


<commit_before>// SPDX-License-Identifier: MIT

// Package handlers 用于处理节点下与处理函数相关的逻辑
package handlers

import (
	"fmt"
	"net/http"
	"sort"
)

type optionsState int8

// 表示对 OPTIONAL 请求中 Allow 报头中输出内容的处理方式。
const (
	optionsStateDefault     optionsState = iota // 默认情况
	optionsStateFixedString                     // 设置为固定的字符串
)

// Methods 所有支持请求方法
var Methods = []string{
	http.MethodGet,
	http.MethodPost,
	http.MethodDelete,
	http.MethodPut,
	http.MethodPatch,
	http.MethodConnect,
	http.MethodTrace,
	http.MethodOptions,
	http.MethodHead,
}

// 除 OPTIONS 和 HEAD 之外的所有支持的元素
//
// 在 Add 方法中用到。
var addAny = Methods[:len(Methods)-2]

// Handlers 用于表示某节点下各个请求方法对应的处理函数
type Handlers struct {
	handlers map[string]http.Handler // 请求方法及其对应的 http.Handler

	optionsAllow string       // 缓存的 OPTIONS 请求的 allow 报头内容
	optionsState optionsState // OPTIONS 请求的处理方式
	disableHead  bool
}

// New 声明一个新的 Handlers 实例
//
// disableHead 是否自动添加 HEAD 请求内容。
func New(disableHead bool) *Handlers {
	return &Handlers{
		handlers:     make(map[string]http.Handler, 4), // 大部分不会超过 4 条数据
		optionsState: optionsStateDefault,
		disableHead:  disableHead,
	}
}

// Add 添加一个处理函数
func (hs *Handlers) Add(h http.Handler, methods ...string) error {
	if len(methods) == 0 {
		methods = addAny
	}

	for _, m := range methods {
		if err := hs.addSingle(h, m); err != nil {
			return err
		}
	}

	return nil
}

func (hs *Handlers) addSingle(h http.Handler, m string) error {
	switch m {
	case http.MethodOptions:
		return fmt.Errorf("无法手动添加 OPTIONS 请求方法")
	case http.MethodHead:
		return fmt.Errorf("无法手动添加 HEAD 请求方法")
	default:
		if !methodExists(m) {
			return fmt.Errorf("该请求方法 %s 不被支持", m)
		}

		if _, found := hs.handlers[m]; found {
			return fmt.Errorf("该请求方法 %s 已经存在", m)
		}
		hs.handlers[m] = h

		// GET
		if m == http.MethodGet && !hs.disableHead {
			hs.handlers[http.MethodHead] = hs.headServeHTTP(h)
		}

		if _, found := hs.handlers[http.MethodOptions]; !found {
			hs.handlers[http.MethodOptions] = http.HandlerFunc(hs.optionsServeHTTP)
		}

		// 重新生成 optionsAllow 字符串
		if hs.optionsState == optionsStateDefault {
			hs.optionsAllow = hs.getOptionsAllow()
		}
	}
	return nil
}

func methodExists(m string) bool {
	for _, mm := range Methods {
		if mm == m {
			return true
		}
	}
	return false
}

func (hs *Handlers) optionsServeHTTP(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Allow", hs.optionsAllow)
	if r.Header.Get("Origin") != "" { // preflight request
		w.Header().Set("Access-Control-Allow-Methods", hs.optionsAllow)
	}
}

func (hs *Handlers) headServeHTTP(h http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		h.ServeHTTP(&response{ResponseWriter: w}, r)
	})
}

func (hs *Handlers) getOptionsAllow() string {
	var index int
	for method := range hs.handlers {
		index += methodMap[method]
	}
	return optionsStrings[index]
}

// Remove 移除某个请求方法对应的处理函数
//
// 返回值表示是否已经被清空。
func (hs *Handlers) Remove(methods ...string) bool {
	if len(methods) == 0 {
		hs.handlers = make(map[string]http.Handler, 4)
		hs.optionsAllow = ""
		return true
	}

	for _, m := range methods {
		delete(hs.handlers, m)

		if m == http.MethodOptions { // 明确要删除 OPTIONS，则将其 optionsAllow 设置为空
			hs.optionsAllow = ""
		} else if m == http.MethodGet { // HEAD 跟随 GET 一起删除
			delete(hs.handlers, http.MethodHead)
		}
	}

	// 删完了
	if hs.Len() == 0 {
		hs.optionsAllow = ""
		return true
	}

	// 只有一个 OPTIONS 了，且未经外界强制修改，则将其也一并删除。
	if hs.Len() == 1 && hs.handlers[http.MethodOptions] != nil {
		delete(hs.handlers, http.MethodOptions)
		hs.optionsAllow = ""
		return true
	}

	if hs.optionsAllow != "" && hs.optionsState != optionsStateFixedString { // 为空，表示是主动删除 options
		hs.optionsAllow = hs.getOptionsAllow()
	}

	return false
}

// SetAllow 设置 Options 请求头的 Allow 报头
func (hs *Handlers) SetAllow(optionsAllow string) {
	hs.optionsAllow = optionsAllow
	hs.optionsState = optionsStateFixedString

	if _, found := hs.handlers[http.MethodOptions]; !found {
		hs.handlers[http.MethodOptions] = http.HandlerFunc(hs.optionsServeHTTP)
	}
}

// Handler 获取指定方法对应的处理函数
func (hs *Handlers) Handler(method string) http.Handler {
	return hs.handlers[method]
}

// Options 获取当前支持的请求方法列表字符串
func (hs *Handlers) Options() string {
	return hs.optionsAllow
}

// Len 获取当前支持请求方法数量
func (hs *Handlers) Len() int {
	return len(hs.handlers)
}

// Methods 当前节点支持的请求方法
func (hs *Handlers) Methods(ignoreHead, ignoreOptions bool) []string {
	methods := make([]string, 0, len(hs.handlers))

	for key := range hs.handlers {
		if (key == http.MethodOptions && ignoreOptions) || key == http.MethodHead && ignoreHead {
			continue
		}

		methods = append(methods, key)
	}

	sort.Strings(methods)
	return methods
}
<commit_msg>perf(internal/handlers): 优化代码<commit_after>// SPDX-License-Identifier: MIT

// Package handlers 用于处理节点下与处理函数相关的逻辑
package handlers

import (
	"fmt"
	"net/http"
	"sort"
)

// Methods 所有支持请求方法
var Methods = []string{
	http.MethodGet,
	http.MethodPost,
	http.MethodDelete,
	http.MethodPut,
	http.MethodPatch,
	http.MethodConnect,
	http.MethodTrace,
	http.MethodOptions,
	http.MethodHead,
}

// 除 OPTIONS 和 HEAD 之外的所有支持的元素
//
// 在 Add 方法中用到。
var addAny = Methods[:len(Methods)-2]

// Handlers 用于表示某节点下各个请求方法对应的处理函数
type Handlers struct {
	handlers map[string]http.Handler // 请求方法及其对应的 http.Handler

	optionsAllow      string // 缓存的 OPTIONS 请求的 allow 报头内容
	fixedOptionsAllow bool

	disableHead bool
}

// New 声明一个新的 Handlers 实例
//
// disableHead 是否自动添加 HEAD 请求内容。
func New(disableHead bool) *Handlers {
	return &Handlers{
		handlers:    make(map[string]http.Handler, 4), // 大部分不会超过 4 条数据
		disableHead: disableHead,
	}
}

// Add 添加一个处理函数
func (hs *Handlers) Add(h http.Handler, methods ...string) error {
	if len(methods) == 0 {
		methods = addAny
	}

	for _, m := range methods {
		if err := hs.addSingle(h, m); err != nil {
			return err
		}
	}

	return nil
}

func (hs *Handlers) addSingle(h http.Handler, m string) error {
	switch m {
	case http.MethodOptions:
		return fmt.Errorf("无法手动添加 OPTIONS 请求方法")
	case http.MethodHead:
		return fmt.Errorf("无法手动添加 HEAD 请求方法")
	default:
		if !methodExists(m) {
			return fmt.Errorf("该请求方法 %s 不被支持", m)
		}

		if _, found := hs.handlers[m]; found {
			return fmt.Errorf("该请求方法 %s 已经存在", m)
		}
		hs.handlers[m] = h

		// GET
		if m == http.MethodGet && !hs.disableHead {
			hs.handlers[http.MethodHead] = hs.headServeHTTP(h)
		}

		if _, found := hs.handlers[http.MethodOptions]; !found {
			hs.handlers[http.MethodOptions] = http.HandlerFunc(hs.optionsServeHTTP)
		}

		if !hs.fixedOptionsAllow { // 重新生成 optionsAllow 字符串
			hs.optionsAllow = hs.getOptionsAllow()
		}
	}
	return nil
}

func methodExists(m string) bool {
	for _, mm := range Methods {
		if mm == m {
			return true
		}
	}
	return false
}

func (hs *Handlers) optionsServeHTTP(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Allow", hs.optionsAllow)
	if r.Header.Get("Origin") != "" { // preflight request
		w.Header().Set("Access-Control-Allow-Methods", hs.optionsAllow)
	}
}

func (hs *Handlers) headServeHTTP(h http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		h.ServeHTTP(&response{ResponseWriter: w}, r)
	})
}

func (hs *Handlers) getOptionsAllow() string {
	var index int
	for method := range hs.handlers {
		index += methodMap[method]
	}
	return optionsStrings[index]
}

// Remove 移除某个请求方法对应的处理函数
//
// 返回值表示是否已经被清空。
func (hs *Handlers) Remove(methods ...string) bool {
	if len(methods) == 0 {
		hs.handlers = make(map[string]http.Handler, 4)
		hs.optionsAllow = ""
		return true
	}

	for _, m := range methods {
		delete(hs.handlers, m)

		if m == http.MethodOptions { // 明确要删除 OPTIONS，则将其 optionsAllow 设置为空
			hs.optionsAllow = ""
		} else if m == http.MethodGet { // HEAD 跟随 GET 一起删除
			delete(hs.handlers, http.MethodHead)
		}
	}

	// 删完了
	if hs.Len() == 0 {
		hs.optionsAllow = ""
		return true
	}

	// 只有一个 OPTIONS 了，且未经外界强制修改，则将其也一并删除。
	if hs.Len() == 1 && hs.handlers[http.MethodOptions] != nil {
		delete(hs.handlers, http.MethodOptions)
		hs.optionsAllow = ""
		return true
	}

	if hs.optionsAllow != "" && !hs.fixedOptionsAllow { // 为空，表示是主动删除 options
		hs.optionsAllow = hs.getOptionsAllow()
	}

	return false
}

// SetAllow 设置 Options 请求头的 Allow 报头
func (hs *Handlers) SetAllow(optionsAllow string) {
	hs.optionsAllow = optionsAllow
	hs.fixedOptionsAllow = true

	if _, found := hs.handlers[http.MethodOptions]; !found {
		hs.handlers[http.MethodOptions] = http.HandlerFunc(hs.optionsServeHTTP)
	}
}

// Handler 获取指定方法对应的处理函数
func (hs *Handlers) Handler(method string) http.Handler {
	return hs.handlers[method]
}

// Options 获取当前支持的请求方法列表字符串
func (hs *Handlers) Options() string {
	return hs.optionsAllow
}

// Len 获取当前支持请求方法数量
func (hs *Handlers) Len() int {
	return len(hs.handlers)
}

// Methods 当前节点支持的请求方法
func (hs *Handlers) Methods(ignoreHead, ignoreOptions bool) []string {
	methods := make([]string, 0, len(hs.handlers))

	for key := range hs.handlers {
		if (key == http.MethodOptions && ignoreOptions) || key == http.MethodHead && ignoreHead {
			continue
		}

		methods = append(methods, key)
	}

	sort.Strings(methods)
	return methods
}
<|endoftext|>


<commit_before>package integration

import (
	"reflect"
	"testing"

	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/cli-runtime/pkg/genericclioptions"
	"k8s.io/cli-runtime/pkg/genericclioptions/printers"
	kapi "k8s.io/kubernetes/pkg/apis/core"

	userv1typedclient "github.com/openshift/client-go/user/clientset/versioned/typed/user/v1"
	authorizationapi "github.com/openshift/origin/pkg/authorization/apis/authorization"
	authorizationclient "github.com/openshift/origin/pkg/authorization/generated/internalclientset"
	groupsnewcmd "github.com/openshift/origin/pkg/oc/cli/admin/groups/new"
	groupsuserscmd "github.com/openshift/origin/pkg/oc/cli/admin/groups/users"
	projectapi "github.com/openshift/origin/pkg/project/apis/project"
	projectclient "github.com/openshift/origin/pkg/project/generated/internalclientset"
	userapi "github.com/openshift/origin/pkg/user/apis/user"
	userclient "github.com/openshift/origin/pkg/user/generated/internalclientset/typed/user/internalversion"
	testutil "github.com/openshift/origin/test/util"
	testserver "github.com/openshift/origin/test/util/server"
)

func TestBasicUserBasedGroupManipulation(t *testing.T) {
	masterConfig, clusterAdminKubeConfig, err := testserver.StartTestMasterAPI()
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	defer testserver.CleanupMasterEtcd(t, masterConfig)

	clusterAdminClientConfig, err := testutil.GetClusterAdminClientConfig(clusterAdminKubeConfig)
	if err != nil {
		t.Errorf("unexpected error: %v", err)
	}
	clusterAdminUserClient := userclient.NewForConfigOrDie(clusterAdminClientConfig)

	valerieKubeClient, valerieConfig, err := testutil.GetClientForUser(clusterAdminClientConfig, "valerie")
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	valerieProjectClient := projectclient.NewForConfigOrDie(valerieConfig).Project()

	// make sure we don't get back system groups
	userValerie, err := clusterAdminUserClient.Users().Get("valerie", metav1.GetOptions{})
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if len(userValerie.Groups) != 0 {
		t.Errorf("unexpected groups: %v", userValerie.Groups)
	}

	// make sure that user/~ returns groups for unbacked users
	expectedClusterAdminGroups := []string{"system:authenticated", "system:cluster-admins", "system:masters"}
	clusterAdminUser, err := clusterAdminUserClient.Users().Get("~", metav1.GetOptions{})
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if !reflect.DeepEqual(clusterAdminUser.Groups, expectedClusterAdminGroups) {
		t.Errorf("expected %v, got %v", expectedClusterAdminGroups, clusterAdminUser.Groups)
	}

	theGroup := &userapi.Group{}
	theGroup.Name = "theGroup"
	theGroup.Users = append(theGroup.Users, "valerie")
	_, err = clusterAdminUserClient.Groups().Create(theGroup)
	if err != nil {
		t.Errorf("unexpected error: %v", err)
	}

	_, err = valerieProjectClient.Projects().Get("empty", metav1.GetOptions{})
	if err == nil {
		t.Fatalf("expected error")
	}

	emptyProject := &projectapi.Project{}
	emptyProject.Name = "empty"
	_, err = projectclient.NewForConfigOrDie(clusterAdminClientConfig).Project().Projects().Create(emptyProject)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	roleBinding := &authorizationapi.RoleBinding{}
	roleBinding.Name = "admins"
	roleBinding.RoleRef.Name = "admin"
	roleBinding.Subjects = authorizationapi.BuildSubjects([]string{}, []string{theGroup.Name})
	_, err = authorizationclient.NewForConfigOrDie(clusterAdminClientConfig).Authorization().RoleBindings("empty").Create(roleBinding)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if err := testutil.WaitForPolicyUpdate(valerieKubeClient.AuthorizationV1(), "empty", "get", kapi.Resource("pods"), true); err != nil {
		t.Error(err)
	}

	// make sure that user groups are respected for policy
	_, err = valerieProjectClient.Projects().Get("empty", metav1.GetOptions{})
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

}

func TestBasicGroupManipulation(t *testing.T) {
	masterConfig, clusterAdminKubeConfig, err := testserver.StartTestMasterAPI()
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	defer testserver.CleanupMasterEtcd(t, masterConfig)

	clusterAdminClientConfig, err := testutil.GetClusterAdminClientConfig(clusterAdminKubeConfig)
	if err != nil {
		t.Errorf("unexpected error: %v", err)
	}

	valerieKubeClient, valerieConfig, err := testutil.GetClientForUser(clusterAdminClientConfig, "valerie")
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	valerieProjectClient := projectclient.NewForConfigOrDie(valerieConfig).Project()

	theGroup := &userapi.Group{}
	theGroup.Name = "thegroup"
	theGroup.Users = append(theGroup.Users, "valerie", "victor")
	_, err = userclient.NewForConfigOrDie(clusterAdminClientConfig).Groups().Create(theGroup)
	if err != nil {
		t.Errorf("unexpected error: %v", err)
	}

	_, err = valerieProjectClient.Projects().Get("empty", metav1.GetOptions{})
	if err == nil {
		t.Fatalf("expected error")
	}

	emptyProject := &projectapi.Project{}
	emptyProject.Name = "empty"
	_, err = projectclient.NewForConfigOrDie(clusterAdminClientConfig).Project().Projects().Create(emptyProject)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	roleBinding := &authorizationapi.RoleBinding{}
	roleBinding.Name = "admins"
	roleBinding.RoleRef.Name = "admin"
	roleBinding.Subjects = authorizationapi.BuildSubjects([]string{}, []string{theGroup.Name})
	_, err = authorizationclient.NewForConfigOrDie(clusterAdminClientConfig).Authorization().RoleBindings("empty").Create(roleBinding)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if err := testutil.WaitForPolicyUpdate(valerieKubeClient.AuthorizationV1(), "empty", "get", kapi.Resource("pods"), true); err != nil {
		t.Error(err)
	}

	// make sure that user groups are respected for policy
	_, err = valerieProjectClient.Projects().Get("empty", metav1.GetOptions{})
	if err != nil {
		t.Errorf("unexpected error: %v", err)
	}

	_, victorConfig, err := testutil.GetClientForUser(clusterAdminClientConfig, "victor")
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	_, err = projectclient.NewForConfigOrDie(victorConfig).Project().Projects().Get("empty", metav1.GetOptions{})
	if err != nil {
		t.Errorf("unexpected error: %v", err)
	}
}

func TestGroupCommands(t *testing.T) {
	masterConfig, clusterAdminKubeConfig, err := testserver.StartTestMasterAPI()
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	defer testserver.CleanupMasterEtcd(t, masterConfig)

	clusterAdminClientConfig, err := testutil.GetClusterAdminClientConfig(clusterAdminKubeConfig)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	userClient := userv1typedclient.NewForConfigOrDie(clusterAdminClientConfig)

	newGroup := &groupsnewcmd.NewGroupOptions{
		GroupClient: userClient,
		Group:       "group1",
		Users:       []string{"first", "second", "third", "first"},
		Printer:     printers.NewDiscardingPrinter(),
	}
	if err := newGroup.Run(); err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	group1, err := userClient.Groups().Get("group1", metav1.GetOptions{})
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if e, a := []string{"first", "second", "third"}, []string(group1.Users); !reflect.DeepEqual(e, a) {
		t.Errorf("expected %v, actual %v", e, a)
	}

	addUsers := &groupsuserscmd.AddUsersOptions{
		GroupModificationOptions: groupsuserscmd.NewGroupModificationOptions(genericclioptions.NewTestIOStreamsDiscard()),
	}
	addUsers.GroupModificationOptions.GroupClient = userClient
	addUsers.GroupModificationOptions.Group = "group1"
	addUsers.GroupModificationOptions.Users = []string{"second", "fourth", "fifth"}
	addUsers.GroupModificationOptions.ToPrinter = func(string) (printers.ResourcePrinter, error) {
		return printers.NewDiscardingPrinter(), nil
	}
	if err := addUsers.Run(); err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	group1, err = userClient.Groups().Get("group1", metav1.GetOptions{})
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if e, a := []string{"first", "second", "third", "fourth", "fifth"}, []string(group1.Users); !reflect.DeepEqual(e, a) {
		t.Errorf("expected %v, actual %v", e, a)
	}

	removeUsers := &groupsuserscmd.RemoveUsersOptions{
		GroupModificationOptions: groupsuserscmd.NewGroupModificationOptions(genericclioptions.NewTestIOStreamsDiscard()),
	}
	removeUsers.GroupModificationOptions.ToPrinter = func(string) (printers.ResourcePrinter, error) {
		return printers.NewDiscardingPrinter(), nil
	}
	removeUsers.GroupModificationOptions.GroupClient = userClient
	removeUsers.GroupModificationOptions.Group = "group1"
	removeUsers.GroupModificationOptions.Users = []string{"second", "fourth", "fifth"}
	if err := removeUsers.Run(); err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	group1, err = userClient.Groups().Get("group1", metav1.GetOptions{})
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if e, a := []string{"first", "third"}, []string(group1.Users); !reflect.DeepEqual(e, a) {
		t.Errorf("expected %v, actual %v", e, a)
	}
}
<commit_msg>TestBasicUserBasedGroupManipulation: test that the user ~ endpoint gives correct groups<commit_after>package integration

import (
	"reflect"
	"testing"

	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/cli-runtime/pkg/genericclioptions"
	"k8s.io/cli-runtime/pkg/genericclioptions/printers"
	kapi "k8s.io/kubernetes/pkg/apis/core"

	userv1typedclient "github.com/openshift/client-go/user/clientset/versioned/typed/user/v1"
	authorizationapi "github.com/openshift/origin/pkg/authorization/apis/authorization"
	authorizationclient "github.com/openshift/origin/pkg/authorization/generated/internalclientset"
	groupsnewcmd "github.com/openshift/origin/pkg/oc/cli/admin/groups/new"
	groupsuserscmd "github.com/openshift/origin/pkg/oc/cli/admin/groups/users"
	projectapi "github.com/openshift/origin/pkg/project/apis/project"
	projectclient "github.com/openshift/origin/pkg/project/generated/internalclientset"
	userapi "github.com/openshift/origin/pkg/user/apis/user"
	userclient "github.com/openshift/origin/pkg/user/generated/internalclientset/typed/user/internalversion"
	testutil "github.com/openshift/origin/test/util"
	testserver "github.com/openshift/origin/test/util/server"
)

func TestBasicUserBasedGroupManipulation(t *testing.T) {
	masterConfig, clusterAdminKubeConfig, err := testserver.StartTestMasterAPI()
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	defer testserver.CleanupMasterEtcd(t, masterConfig)

	clusterAdminClientConfig, err := testutil.GetClusterAdminClientConfig(clusterAdminKubeConfig)
	if err != nil {
		t.Errorf("unexpected error: %v", err)
	}
	clusterAdminUserClient := userclient.NewForConfigOrDie(clusterAdminClientConfig)

	valerieKubeClient, valerieConfig, err := testutil.GetClientForUser(clusterAdminClientConfig, "valerie")
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	valerieProjectClient := projectclient.NewForConfigOrDie(valerieConfig).Project()

	// make sure we don't get back system groups
	userValerie, err := clusterAdminUserClient.Users().Get("valerie", metav1.GetOptions{})
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if len(userValerie.Groups) != 0 {
		t.Errorf("unexpected groups: %v", userValerie.Groups)
	}

	// make sure that user/~ returns groups for unbacked users
	expectedClusterAdminGroups := []string{"system:authenticated", "system:cluster-admins", "system:masters"}
	clusterAdminUser, err := clusterAdminUserClient.Users().Get("~", metav1.GetOptions{})
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if !reflect.DeepEqual(clusterAdminUser.Groups, expectedClusterAdminGroups) {
		t.Errorf("expected %v, got %v", expectedClusterAdminGroups, clusterAdminUser.Groups)
	}

	theGroup := &userapi.Group{}
	theGroup.Name = "theGroup"
	theGroup.Users = append(theGroup.Users, "valerie")
	_, err = clusterAdminUserClient.Groups().Create(theGroup)
	if err != nil {
		t.Errorf("unexpected error: %v", err)
	}

	// make sure that user/~ returns system groups for backed users when it merges
	expectedValerieGroups := []string{"system:authenticated", "system:authenticated:oauth"}
	secondValerie, err := userclient.NewForConfigOrDie(valerieConfig).Users().Get("~", metav1.GetOptions{})
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if !reflect.DeepEqual(secondValerie.Groups, expectedValerieGroups) {
		t.Errorf("expected %v, got %v", expectedValerieGroups, secondValerie.Groups)
	}

	_, err = valerieProjectClient.Projects().Get("empty", metav1.GetOptions{})
	if err == nil {
		t.Fatalf("expected error")
	}

	emptyProject := &projectapi.Project{}
	emptyProject.Name = "empty"
	_, err = projectclient.NewForConfigOrDie(clusterAdminClientConfig).Project().Projects().Create(emptyProject)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	roleBinding := &authorizationapi.RoleBinding{}
	roleBinding.Name = "admins"
	roleBinding.RoleRef.Name = "admin"
	roleBinding.Subjects = authorizationapi.BuildSubjects([]string{}, []string{theGroup.Name})
	_, err = authorizationclient.NewForConfigOrDie(clusterAdminClientConfig).Authorization().RoleBindings("empty").Create(roleBinding)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if err := testutil.WaitForPolicyUpdate(valerieKubeClient.AuthorizationV1(), "empty", "get", kapi.Resource("pods"), true); err != nil {
		t.Error(err)
	}

	// make sure that user groups are respected for policy
	_, err = valerieProjectClient.Projects().Get("empty", metav1.GetOptions{})
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

}

func TestBasicGroupManipulation(t *testing.T) {
	masterConfig, clusterAdminKubeConfig, err := testserver.StartTestMasterAPI()
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	defer testserver.CleanupMasterEtcd(t, masterConfig)

	clusterAdminClientConfig, err := testutil.GetClusterAdminClientConfig(clusterAdminKubeConfig)
	if err != nil {
		t.Errorf("unexpected error: %v", err)
	}

	valerieKubeClient, valerieConfig, err := testutil.GetClientForUser(clusterAdminClientConfig, "valerie")
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	valerieProjectClient := projectclient.NewForConfigOrDie(valerieConfig).Project()

	theGroup := &userapi.Group{}
	theGroup.Name = "thegroup"
	theGroup.Users = append(theGroup.Users, "valerie", "victor")
	_, err = userclient.NewForConfigOrDie(clusterAdminClientConfig).Groups().Create(theGroup)
	if err != nil {
		t.Errorf("unexpected error: %v", err)
	}

	_, err = valerieProjectClient.Projects().Get("empty", metav1.GetOptions{})
	if err == nil {
		t.Fatalf("expected error")
	}

	emptyProject := &projectapi.Project{}
	emptyProject.Name = "empty"
	_, err = projectclient.NewForConfigOrDie(clusterAdminClientConfig).Project().Projects().Create(emptyProject)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	roleBinding := &authorizationapi.RoleBinding{}
	roleBinding.Name = "admins"
	roleBinding.RoleRef.Name = "admin"
	roleBinding.Subjects = authorizationapi.BuildSubjects([]string{}, []string{theGroup.Name})
	_, err = authorizationclient.NewForConfigOrDie(clusterAdminClientConfig).Authorization().RoleBindings("empty").Create(roleBinding)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if err := testutil.WaitForPolicyUpdate(valerieKubeClient.AuthorizationV1(), "empty", "get", kapi.Resource("pods"), true); err != nil {
		t.Error(err)
	}

	// make sure that user groups are respected for policy
	_, err = valerieProjectClient.Projects().Get("empty", metav1.GetOptions{})
	if err != nil {
		t.Errorf("unexpected error: %v", err)
	}

	_, victorConfig, err := testutil.GetClientForUser(clusterAdminClientConfig, "victor")
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	_, err = projectclient.NewForConfigOrDie(victorConfig).Project().Projects().Get("empty", metav1.GetOptions{})
	if err != nil {
		t.Errorf("unexpected error: %v", err)
	}
}

func TestGroupCommands(t *testing.T) {
	masterConfig, clusterAdminKubeConfig, err := testserver.StartTestMasterAPI()
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	defer testserver.CleanupMasterEtcd(t, masterConfig)

	clusterAdminClientConfig, err := testutil.GetClusterAdminClientConfig(clusterAdminKubeConfig)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	userClient := userv1typedclient.NewForConfigOrDie(clusterAdminClientConfig)

	newGroup := &groupsnewcmd.NewGroupOptions{
		GroupClient: userClient,
		Group:       "group1",
		Users:       []string{"first", "second", "third", "first"},
		Printer:     printers.NewDiscardingPrinter(),
	}
	if err := newGroup.Run(); err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	group1, err := userClient.Groups().Get("group1", metav1.GetOptions{})
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if e, a := []string{"first", "second", "third"}, []string(group1.Users); !reflect.DeepEqual(e, a) {
		t.Errorf("expected %v, actual %v", e, a)
	}

	addUsers := &groupsuserscmd.AddUsersOptions{
		GroupModificationOptions: groupsuserscmd.NewGroupModificationOptions(genericclioptions.NewTestIOStreamsDiscard()),
	}
	addUsers.GroupModificationOptions.GroupClient = userClient
	addUsers.GroupModificationOptions.Group = "group1"
	addUsers.GroupModificationOptions.Users = []string{"second", "fourth", "fifth"}
	addUsers.GroupModificationOptions.ToPrinter = func(string) (printers.ResourcePrinter, error) {
		return printers.NewDiscardingPrinter(), nil
	}
	if err := addUsers.Run(); err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	group1, err = userClient.Groups().Get("group1", metav1.GetOptions{})
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if e, a := []string{"first", "second", "third", "fourth", "fifth"}, []string(group1.Users); !reflect.DeepEqual(e, a) {
		t.Errorf("expected %v, actual %v", e, a)
	}

	removeUsers := &groupsuserscmd.RemoveUsersOptions{
		GroupModificationOptions: groupsuserscmd.NewGroupModificationOptions(genericclioptions.NewTestIOStreamsDiscard()),
	}
	removeUsers.GroupModificationOptions.ToPrinter = func(string) (printers.ResourcePrinter, error) {
		return printers.NewDiscardingPrinter(), nil
	}
	removeUsers.GroupModificationOptions.GroupClient = userClient
	removeUsers.GroupModificationOptions.Group = "group1"
	removeUsers.GroupModificationOptions.Users = []string{"second", "fourth", "fifth"}
	if err := removeUsers.Run(); err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	group1, err = userClient.Groups().Get("group1", metav1.GetOptions{})
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if e, a := []string{"first", "third"}, []string(group1.Users); !reflect.DeepEqual(e, a) {
		t.Errorf("expected %v, actual %v", e, a)
	}
}
<|endoftext|>


<commit_before>// Package mongo is a REST Layer resource storage handler for MongoDB using mgo
package mongo

import (
	"context"
	"time"

	"github.com/rs/rest-layer/resource"
	"gopkg.in/mgo.v2"
	"gopkg.in/mgo.v2/bson"
)

// mongoItem is a bson representation of a resource.Item
type mongoItem struct {
	ID      interface{}            `bson:"_id"`
	ETag    string                 `bson:"_etag"`
	Updated time.Time              `bson:"_updated"`
	Payload map[string]interface{} `bson:",inline"`
}

// newMongoItem converts a resource.Item into a mongoItem
func newMongoItem(i *resource.Item) *mongoItem {
	// Filter out id from the payload so we don't store it twice
	p := map[string]interface{}{}
	for k, v := range i.Payload {
		if k != "id" {
			p[k] = v
		}
	}
	return &mongoItem{
		ID:      i.ID,
		ETag:    i.ETag,
		Updated: i.Updated,
		Payload: p,
	}
}

// newItem converts a back mongoItem into a resource.Item
func newItem(i *mongoItem) *resource.Item {
	// Add the id back (we use the same map hoping the mongoItem won't be stored back)
	i.Payload["id"] = i.ID
	return &resource.Item{
		ID:      i.ID,
		ETag:    i.ETag,
		Updated: i.Updated,
		Payload: i.Payload,
	}
}

// Handler handles resource storage in a MongoDB collection.
type Handler func(ctx context.Context) (*mgo.Collection, error)

// NewHandler creates an new mongo handler
func NewHandler(s *mgo.Session, db, collection string) Handler {
	return func(ctx context.Context) (*mgo.Collection, error) {
		return s.DB(db).C(collection), nil
	}
}

// C returns the mongo collection managed by this storage handler
// from a Copy() of the mgo session.
func (m Handler) c(ctx context.Context) (*mgo.Collection, error) {
	if err := ctx.Err(); err != nil {
		return nil, err
	}
	c, err := m(ctx)
	if err != nil {
		return nil, err
	}
	// With mgo, session.Copy() pulls a connection from the connection pool
	s := c.Database.Session.Copy()
	// Ensure safe mode is enabled in order to get errors
	s.EnsureSafe(&mgo.Safe{})
	// Set a timeout to match the context deadline if any
	if deadline, ok := ctx.Deadline(); ok {
		timeout := deadline.Sub(time.Now())
		if timeout <= 0 {
			timeout = 0
		}
		s.SetSocketTimeout(timeout)
		s.SetSyncTimeout(timeout)
	}
	c.Database.Session = s
	return c, nil
}

// close returns a mgo.Collection's session to the connection pool.
func (m Handler) close(c *mgo.Collection) {
	c.Database.Session.Close()
}

// Insert inserts new items in the mongo collection
func (m Handler) Insert(ctx context.Context, items []*resource.Item) error {
	mItems := make([]interface{}, len(items))
	for i, item := range items {
		mItems[i] = newMongoItem(item)
	}
	c, err := m.c(ctx)
	if err != nil {
		return err
	}
	defer m.close(c)
	err = c.Insert(mItems...)
	if mgo.IsDup(err) {
		// Duplicate ID key
		err = resource.ErrConflict
	}
	if ctx.Err() != nil {
		return ctx.Err()
	}
	return err
}

// Update replace an item by a new one in the mongo collection
func (m Handler) Update(ctx context.Context, item *resource.Item, original *resource.Item) error {
	mItem := newMongoItem(item)
	c, err := m.c(ctx)
	if err != nil {
		return err
	}
	defer m.close(c)
	err = c.Update(bson.M{"_id": original.ID, "_etag": original.ETag}, mItem)
	if err == mgo.ErrNotFound {
		// Determine if the item is not found or if the item is found but etag missmatch
		var count int
		count, err = c.FindId(original.ID).Count()
		if err != nil {
			// The find returned an unexpected err, just forward it with no mapping
		} else if count == 0 {
			err = resource.ErrNotFound
		} else if ctx.Err() != nil {
			err = ctx.Err()
		} else {
			// If the item were found, it means that its etag didn't match
			err = resource.ErrConflict
		}
	}
	return err
}

// Delete deletes an item from the mongo collection
func (m Handler) Delete(ctx context.Context, item *resource.Item) error {
	c, err := m.c(ctx)
	if err != nil {
		return err
	}
	defer m.close(c)
	err = c.Remove(bson.M{"_id": item.ID, "_etag": item.ETag})
	if err == mgo.ErrNotFound {
		// Determine if the item is not found or if the item is found but etag missmatch
		var count int
		count, err = c.FindId(item.ID).Count()
		if err != nil {
			// The find returned an unexpected err, just forward it with no mapping
		} else if count == 0 {
			err = resource.ErrNotFound
		} else if ctx.Err() != nil {
			err = ctx.Err()
		} else {
			// If the item were found, it means that its etag didn't match
			err = resource.ErrConflict
		}
	}
	return err
}

// Clear clears all items from the mongo collection matching the lookup
func (m Handler) Clear(ctx context.Context, lookup *resource.Lookup) (int, error) {
	q, err := getQuery(lookup)
	if err != nil {
		return 0, err
	}
	c, err := m.c(ctx)
	if err != nil {
		return 0, err
	}
	defer m.close(c)
	info, err := c.RemoveAll(q)
	if err != nil {
		return 0, err
	}
	if ctx.Err() != nil {
		return 0, ctx.Err()
	}
	return info.Removed, nil
}

// Find items from the mongo collection matching the provided lookup
func (m Handler) Find(ctx context.Context, lookup *resource.Lookup, offset, limit int) (*resource.ItemList, error) {
	q, err := getQuery(lookup)
	if err != nil {
		return nil, err
	}
	s := getSort(lookup)
	c, err := m.c(ctx)
	if err != nil {
		return nil, err
	}
	defer m.close(c)
	var mItem mongoItem
	query := c.Find(q).Sort(s...)

	if offset > 0 {
		query.Skip(offset)
	}
	if limit >= 0 {
		query.Limit(limit)
	}
	// Apply context deadline if any
	if dl, ok := ctx.Deadline(); ok {
		dur := dl.Sub(time.Now())
		if dur < 0 {
			dur = 0
		}
		query.SetMaxTime(dur)
	}
	// Perform request
	iter := query.Iter()
	// Total is set to -1 because we have no easy way with Mongodb to to compute this value
	// without performing two requests.
	list := &resource.ItemList{Total: -1, Items: []*resource.Item{}}
	for iter.Next(&mItem) {
		// Check if context is still ok before to continue
		if err = ctx.Err(); err != nil {
			// TODO bench this as net/context is using mutex under the hood
			iter.Close()
			return nil, err
		}
		list.Items = append(list.Items, newItem(&mItem))
	}
	if err := iter.Close(); err != nil {
		return nil, err
	}
	// If the number of returned elements is lower than requested limit, or not
	// limit is requested, we can deduce the total number of element for free.
	if limit == -1 || len(list.Items) < limit {
		list.Total = offset + len(list.Items)
	}
	return list, err
}

// Count counts the number items matching the lookup filter
func (m Handler) Count(ctx context.Context, lookup *resource.Lookup) (int, error) {
	q, err := getQuery(lookup)
	if err != nil {
		return -1, err
	}
	c, err := m.c(ctx)
	if err != nil {
		return -1, err
	}
	defer m.close(c)
	query := c.Find(q)
	// Apply context deadline if any
	if dl, ok := ctx.Deadline(); ok {
		dur := dl.Sub(time.Now())
		if dur < 0 {
			dur = 0
		}
		query.SetMaxTime(dur)
	}
	return query.Count()
}
<commit_msg>return c in handler func<commit_after>// Package mongo is a REST Layer resource storage handler for MongoDB using mgo
package mongo

import (
	"context"
	"time"

	"github.com/rs/rest-layer/resource"
	"gopkg.in/mgo.v2"
	"gopkg.in/mgo.v2/bson"
)

// mongoItem is a bson representation of a resource.Item
type mongoItem struct {
	ID      interface{}            `bson:"_id"`
	ETag    string                 `bson:"_etag"`
	Updated time.Time              `bson:"_updated"`
	Payload map[string]interface{} `bson:",inline"`
}

// newMongoItem converts a resource.Item into a mongoItem
func newMongoItem(i *resource.Item) *mongoItem {
	// Filter out id from the payload so we don't store it twice
	p := map[string]interface{}{}
	for k, v := range i.Payload {
		if k != "id" {
			p[k] = v
		}
	}
	return &mongoItem{
		ID:      i.ID,
		ETag:    i.ETag,
		Updated: i.Updated,
		Payload: p,
	}
}

// newItem converts a back mongoItem into a resource.Item
func newItem(i *mongoItem) *resource.Item {
	// Add the id back (we use the same map hoping the mongoItem won't be stored back)
	i.Payload["id"] = i.ID
	return &resource.Item{
		ID:      i.ID,
		ETag:    i.ETag,
		Updated: i.Updated,
		Payload: i.Payload,
	}
}

// Handler handles resource storage in a MongoDB collection.
type Handler func(ctx context.Context) (*mgo.Collection, error)

// NewHandler creates an new mongo handler
func NewHandler(s *mgo.Session, db, collection string) Handler {
	c := s.DB(db).C(collection)
	return func(ctx context.Context) (*mgo.Collection, error) {
		return c, nil
	}
}

// C returns the mongo collection managed by this storage handler
// from a Copy() of the mgo session.
func (m Handler) c(ctx context.Context) (*mgo.Collection, error) {
	if err := ctx.Err(); err != nil {
		return nil, err
	}
	c, err := m(ctx)
	if err != nil {
		return nil, err
	}
	// With mgo, session.Copy() pulls a connection from the connection pool
	s := c.Database.Session.Copy()
	// Ensure safe mode is enabled in order to get errors
	s.EnsureSafe(&mgo.Safe{})
	// Set a timeout to match the context deadline if any
	if deadline, ok := ctx.Deadline(); ok {
		timeout := deadline.Sub(time.Now())
		if timeout <= 0 {
			timeout = 0
		}
		s.SetSocketTimeout(timeout)
		s.SetSyncTimeout(timeout)
	}
	c.Database.Session = s
	return c, nil
}

// close returns a mgo.Collection's session to the connection pool.
func (m Handler) close(c *mgo.Collection) {
	c.Database.Session.Close()
}

// Insert inserts new items in the mongo collection
func (m Handler) Insert(ctx context.Context, items []*resource.Item) error {
	mItems := make([]interface{}, len(items))
	for i, item := range items {
		mItems[i] = newMongoItem(item)
	}
	c, err := m.c(ctx)
	if err != nil {
		return err
	}
	defer m.close(c)
	err = c.Insert(mItems...)
	if mgo.IsDup(err) {
		// Duplicate ID key
		err = resource.ErrConflict
	}
	if ctx.Err() != nil {
		return ctx.Err()
	}
	return err
}

// Update replace an item by a new one in the mongo collection
func (m Handler) Update(ctx context.Context, item *resource.Item, original *resource.Item) error {
	mItem := newMongoItem(item)
	c, err := m.c(ctx)
	if err != nil {
		return err
	}
	defer m.close(c)
	err = c.Update(bson.M{"_id": original.ID, "_etag": original.ETag}, mItem)
	if err == mgo.ErrNotFound {
		// Determine if the item is not found or if the item is found but etag missmatch
		var count int
		count, err = c.FindId(original.ID).Count()
		if err != nil {
			// The find returned an unexpected err, just forward it with no mapping
		} else if count == 0 {
			err = resource.ErrNotFound
		} else if ctx.Err() != nil {
			err = ctx.Err()
		} else {
			// If the item were found, it means that its etag didn't match
			err = resource.ErrConflict
		}
	}
	return err
}

// Delete deletes an item from the mongo collection
func (m Handler) Delete(ctx context.Context, item *resource.Item) error {
	c, err := m.c(ctx)
	if err != nil {
		return err
	}
	defer m.close(c)
	err = c.Remove(bson.M{"_id": item.ID, "_etag": item.ETag})
	if err == mgo.ErrNotFound {
		// Determine if the item is not found or if the item is found but etag missmatch
		var count int
		count, err = c.FindId(item.ID).Count()
		if err != nil {
			// The find returned an unexpected err, just forward it with no mapping
		} else if count == 0 {
			err = resource.ErrNotFound
		} else if ctx.Err() != nil {
			err = ctx.Err()
		} else {
			// If the item were found, it means that its etag didn't match
			err = resource.ErrConflict
		}
	}
	return err
}

// Clear clears all items from the mongo collection matching the lookup
func (m Handler) Clear(ctx context.Context, lookup *resource.Lookup) (int, error) {
	q, err := getQuery(lookup)
	if err != nil {
		return 0, err
	}
	c, err := m.c(ctx)
	if err != nil {
		return 0, err
	}
	defer m.close(c)
	info, err := c.RemoveAll(q)
	if err != nil {
		return 0, err
	}
	if ctx.Err() != nil {
		return 0, ctx.Err()
	}
	return info.Removed, nil
}

// Find items from the mongo collection matching the provided lookup
func (m Handler) Find(ctx context.Context, lookup *resource.Lookup, offset, limit int) (*resource.ItemList, error) {
	q, err := getQuery(lookup)
	if err != nil {
		return nil, err
	}
	s := getSort(lookup)
	c, err := m.c(ctx)
	if err != nil {
		return nil, err
	}
	defer m.close(c)
	var mItem mongoItem
	query := c.Find(q).Sort(s...)

	if offset > 0 {
		query.Skip(offset)
	}
	if limit >= 0 {
		query.Limit(limit)
	}
	// Apply context deadline if any
	if dl, ok := ctx.Deadline(); ok {
		dur := dl.Sub(time.Now())
		if dur < 0 {
			dur = 0
		}
		query.SetMaxTime(dur)
	}
	// Perform request
	iter := query.Iter()
	// Total is set to -1 because we have no easy way with Mongodb to to compute this value
	// without performing two requests.
	list := &resource.ItemList{Total: -1, Items: []*resource.Item{}}
	for iter.Next(&mItem) {
		// Check if context is still ok before to continue
		if err = ctx.Err(); err != nil {
			// TODO bench this as net/context is using mutex under the hood
			iter.Close()
			return nil, err
		}
		list.Items = append(list.Items, newItem(&mItem))
	}
	if err := iter.Close(); err != nil {
		return nil, err
	}
	// If the number of returned elements is lower than requested limit, or not
	// limit is requested, we can deduce the total number of element for free.
	if limit == -1 || len(list.Items) < limit {
		list.Total = offset + len(list.Items)
	}
	return list, err
}

// Count counts the number items matching the lookup filter
func (m Handler) Count(ctx context.Context, lookup *resource.Lookup) (int, error) {
	q, err := getQuery(lookup)
	if err != nil {
		return -1, err
	}
	c, err := m.c(ctx)
	if err != nil {
		return -1, err
	}
	defer m.close(c)
	query := c.Find(q)
	// Apply context deadline if any
	if dl, ok := ctx.Deadline(); ok {
		dur := dl.Sub(time.Now())
		if dur < 0 {
			dur = 0
		}
		query.SetMaxTime(dur)
	}
	return query.Count()
}
<|endoftext|>


<commit_before>// +build integration,!no-docker,docker

package integration

import (
	"crypto/tls"
	"encoding/json"
	"errors"
	"net/http"
	"os"
	"testing"
	"time"

	"golang.org/x/net/websocket"

	kapi "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
	"github.com/GoogleCloudPlatform/kubernetes/pkg/watch"
	dockerClient "github.com/fsouza/go-dockerclient"
	routeapi "github.com/openshift/origin/pkg/route/api"
	tr "github.com/openshift/origin/test/integration/router"
)

const (
	defaultRouterImage = "openshift/origin-haproxy-router"

	tcWaitSeconds = 1
	tcRetries     = 3

	dockerWaitSeconds = 1
	dockerRetries     = 3
)

// init ensures docker exists for this test
func init() {
	requireDocker()
}

// TestRouter is the table based test for routers.  It will initialize a fake master/client and expect to deploy
// a router image in docker.  It then sends watch events through the simulator and makes http client requests that
// should go through the deployed router and return data from the client simulator.
func TestRouter(t *testing.T) {
	//create a server which will act as a user deployed application that
	//serves http and https as well as act as a master to simulate watches
	fakeMasterAndPod := tr.NewTestHttpService()
	defer fakeMasterAndPod.Stop()

	err := fakeMasterAndPod.Start()
	validateServer(fakeMasterAndPod, t)

	if err != nil {
		t.Fatalf("Unable to start http server: %v", err)
	}

	//deploy router docker container
	dockerCli, err := newDockerClient()

	if err != nil {
		t.Fatalf("Unable to get docker client: %v", err)
	}

	routerId, err := createAndStartRouterContainer(dockerCli, fakeMasterAndPod.MasterHttpAddr)

	if err != nil {
		t.Fatalf("Error starting container %s : %v", getRouterImage(), err)
	}

	defer cleanUp(dockerCli, routerId)

	//run through test cases now that environment is set up
	testCases := []struct {
		name              string
		serviceName       string
		endpoints         []string
		routeAlias        string
		endpointEventType watch.EventType
		routeEventType    watch.EventType
		protocol          string
		expectedResponse  string
		routeTLS          *routeapi.TLSConfig
		routerUrl         string
	}{
		{
			name:              "non-secure",
			serviceName:       "example",
			endpoints:         []string{fakeMasterAndPod.PodHttpAddr},
			routeAlias:        "www.example-unsecure.com",
			endpointEventType: watch.Added,
			routeEventType:    watch.Added,
			protocol:          "http",
			expectedResponse:  tr.HelloPod,
			routeTLS:          nil,
			routerUrl:         "0.0.0.0",
		},
		{
			name:              "edge termination",
			serviceName:       "example-edge",
			endpoints:         []string{fakeMasterAndPod.PodHttpAddr},
			routeAlias:        "www.example.com",
			endpointEventType: watch.Added,
			routeEventType:    watch.Added,
			protocol:          "https",
			expectedResponse:  tr.HelloPod,
			routeTLS: &routeapi.TLSConfig{
				Termination:   routeapi.TLSTerminationEdge,
				Certificate:   tr.ExampleCert,
				Key:           tr.ExampleKey,
				CACertificate: tr.ExampleCACert,
			},
			routerUrl: "0.0.0.0",
		},
		{
			name:              "passthrough termination",
			serviceName:       "example-passthrough",
			endpoints:         []string{fakeMasterAndPod.PodHttpsAddr},
			routeAlias:        "www.example2.com",
			endpointEventType: watch.Added,
			routeEventType:    watch.Added,
			protocol:          "https",
			expectedResponse:  tr.HelloPodSecure,
			routeTLS: &routeapi.TLSConfig{
				Termination: routeapi.TLSTerminationPassthrough,
			},
			routerUrl: "0.0.0.0",
		},
		{
			name:              "websocket unsecure",
			serviceName:       "websocket-unsecure",
			endpoints:         []string{fakeMasterAndPod.PodHttpAddr},
			routeAlias:        "0.0.0.0:80",
			endpointEventType: watch.Added,
			routeEventType:    watch.Added,
			protocol:          "ws",
			expectedResponse:  "hello-websocket-unsecure",
			routerUrl:         "0.0.0.0:80/echo",
		},
		{
			name:              "ws edge termination",
			serviceName:       "websocket-edge",
			endpoints:         []string{fakeMasterAndPod.PodHttpAddr},
			routeAlias:        "0.0.0.0:443",
			endpointEventType: watch.Added,
			routeEventType:    watch.Added,
			protocol:          "wss",
			expectedResponse:  "hello-websocket-edge",
			routeTLS: &routeapi.TLSConfig{
				Termination:   routeapi.TLSTerminationEdge,
				Certificate:   tr.ExampleCert,
				Key:           tr.ExampleKey,
				CACertificate: tr.ExampleCACert,
			},
			routerUrl: "0.0.0.0:443/echo",
		},
		{
			name:              "ws passthrough termination",
			serviceName:       "websocket-passthrough",
			endpoints:         []string{fakeMasterAndPod.PodHttpsAddr},
			routeAlias:        "0.0.0.0:443",
			endpointEventType: watch.Added,
			routeEventType:    watch.Added,
			protocol:          "wss",
			expectedResponse:  "hello-websocket-passthrough",
			routeTLS: &routeapi.TLSConfig{
				Termination: routeapi.TLSTerminationPassthrough,
			},
			routerUrl: "0.0.0.0:443/echo",
		},
	}

	for _, tc := range testCases {
		//simulate the events
		endpointEvent := &watch.Event{
			Type: tc.endpointEventType,

			Object: &kapi.Endpoints{
				ObjectMeta: kapi.ObjectMeta{
					Name: tc.serviceName,
				},
				TypeMeta: kapi.TypeMeta{
					Kind:       "Endpoints",
					APIVersion: "v1beta3",
				},
				Endpoints: tc.endpoints,
			},
		}

		routeEvent := &watch.Event{
			Type: tc.routeEventType,
			Object: &routeapi.Route{
				TypeMeta: kapi.TypeMeta{
					Kind:       "Route",
					APIVersion: "v1beta1",
				},
				Host:        tc.routeAlias,
				ServiceName: tc.serviceName,
				TLS:         tc.routeTLS,
			},
		}

		fakeMasterAndPod.EndpointChannel <- eventString(endpointEvent)
		fakeMasterAndPod.RouteChannel <- eventString(routeEvent)

		for i := 0; i < tcRetries; i++ {
			//wait for router to pick up configs
			time.Sleep(time.Second * tcWaitSeconds)
			//now verify the route with an http client
			resp, err := getRoute(tc.routerUrl, tc.routeAlias, tc.protocol, tc.expectedResponse)

			if err != nil {
				if i != 2 {
					continue
				}
				t.Errorf("Unable to verify response: %v", err)
			}

			if resp != tc.expectedResponse {
				t.Errorf("TC %s failed! Response body %v did not match expected %v", tc.name, resp, tc.expectedResponse)
			} else {
				//good to go, stop trying
				break
			}
		}

		//clean up
		routeEvent.Type = watch.Deleted
		endpointEvent.Type = watch.Deleted

		fakeMasterAndPod.EndpointChannel <- eventString(endpointEvent)
		fakeMasterAndPod.RouteChannel <- eventString(routeEvent)
	}
}

// getRoute is a utility function for making the web request to a route.  Protocol is either http or https.  If the
// protocol is https then getRoute will make a secure transport client with InsecureSkipVerify: true.  Http does a plain
// http client request.
func getRoute(routerUrl string, hostName string, protocol string, expectedResponse string) (response string, err error) {
	url := protocol + "://" + routerUrl
	var tlsConfig *tls.Config

	if protocol == "https" || protocol == "wss" {
		tlsConfig = &tls.Config{
			InsecureSkipVerify: true,
			ServerName:         hostName,
		}
	}

	switch protocol {
	case "http", "https":
		httpClient := &http.Client{Transport: &http.Transport{
			TLSClientConfig: tlsConfig,
		},
		}
		req, err := http.NewRequest("GET", url, nil)

		if err != nil {
			return "", err
		}

		req.Host = hostName
		resp, err := httpClient.Do(req)

		if err != nil {
			return "", err
		}

		var respBody = make([]byte, len([]byte(expectedResponse)))
		resp.Body.Read(respBody)

		return string(respBody), nil
	case "ws", "wss":
		wsConfig, err := websocket.NewConfig(url, "http://localhost/")
		if err != nil {
			return "", err
		}

		wsConfig.Header.Set("Host", hostName)
		wsConfig.TlsConfig = tlsConfig

		ws, err := websocket.DialConfig(wsConfig)
		if err != nil {
			return "", err
		}

		_, err = ws.Write([]byte(expectedResponse))
		if err != nil {
			return "", err
		}

		var msg = make([]byte, len(expectedResponse))
		_, err = ws.Read(msg)
		if err != nil {
			return "", err
		}

		return string(msg), nil
	}

	return "", errors.New("Unrecognized protocol in getRoute")
}

// eventString marshals the event into a string
func eventString(e *watch.Event) string {
	s, _ := json.Marshal(e)
	return string(s)
}

// createAndStartRouterContainer is responsible for deploying the router image in docker.  It assumes that all router images
// will use a command line flag that can take --master which points to the master url
func createAndStartRouterContainer(dockerCli *dockerClient.Client, masterIp string) (containerId string, err error) {
	ports := []string{"80", "443"}
	portBindings := make(map[dockerClient.Port][]dockerClient.PortBinding)
	exposedPorts := map[dockerClient.Port]struct{}{}

	for _, p := range ports {
		dockerPort := dockerClient.Port(p + "/tcp")

		portBindings[dockerPort] = []dockerClient.PortBinding{
			{
				HostPort: p,
			},
		}

		exposedPorts[dockerPort] = struct{}{}
	}

	containerOpts := dockerClient.CreateContainerOptions{
		Config: &dockerClient.Config{
			Image:        getRouterImage(),
			Cmd:          []string{"--master=" + masterIp, "--loglevel=4"},
			ExposedPorts: exposedPorts,
		},
	}

	container, err := dockerCli.CreateContainer(containerOpts)

	if err != nil {
		return "", err
	}

	dockerHostCfg := &dockerClient.HostConfig{NetworkMode: "host", PortBindings: portBindings}
	err = dockerCli.StartContainer(container.ID, dockerHostCfg)

	if err != nil {
		return "", err
	}

	running := false

	//wait for it to start
	for i := 0; i < dockerRetries; i++ {
		c, err := dockerCli.InspectContainer(container.ID)

		if err != nil {
			return "", err
		}

		if c.State.Running {
			running = true
			break
		}
		time.Sleep(time.Second * dockerWaitSeconds)
	}

	if !running {
		return "", errors.New("Container did not start after 3 tries!")
	}

	return container.ID, nil
}

// validateServer performs a basic run through by validating each of the configured urls for the simulator to
// ensure they are responding
func validateServer(server *tr.TestHttpService, t *testing.T) {
	_, err := http.Get("http://" + server.MasterHttpAddr)

	if err != nil {
		t.Errorf("Error validating master addr %s : %v", server.MasterHttpAddr, err)
	}

	_, err = http.Get("http://" + server.PodHttpAddr)

	if err != nil {
		t.Errorf("Error validating master addr %s : %v", server.MasterHttpAddr, err)
	}

	secureTransport := &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}}
	secureClient := &http.Client{Transport: secureTransport}
	_, err = secureClient.Get("https://" + server.PodHttpsAddr)

	if err != nil {
		t.Errorf("Error validating master addr %s : %v", server.MasterHttpAddr, err)
	}
}

// cleanUp stops and removes the deployed router
func cleanUp(dockerCli *dockerClient.Client, routerId string) {
	dockerCli.StopContainer(routerId, 5)

	dockerCli.RemoveContainer(dockerClient.RemoveContainerOptions{
		ID:    routerId,
		Force: true,
	})
}

// getRouterImage is a utility that provides the router image to use by checking to see if OPENSHIFT_ROUTER_IMAGE is set
// or by using the default image
func getRouterImage() string {
	i := os.Getenv("OPENSHIFT_ROUTER_IMAGE")

	if len(i) == 0 {
		i = defaultRouterImage
	}

	return i
}
<commit_msg>Fix router integration test<commit_after>// +build integration,!no-docker,docker

package integration

import (
	"crypto/tls"
	"encoding/json"
	"errors"
	"net"
	"net/http"
	"os"
	"strconv"
	"testing"
	"time"

	"golang.org/x/net/websocket"

	kapi "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
	"github.com/GoogleCloudPlatform/kubernetes/pkg/watch"
	dockerClient "github.com/fsouza/go-dockerclient"
	routeapi "github.com/openshift/origin/pkg/route/api"
	tr "github.com/openshift/origin/test/integration/router"
)

const (
	defaultRouterImage = "openshift/origin-haproxy-router"

	tcWaitSeconds = 1
	tcRetries     = 3

	dockerWaitSeconds = 1
	dockerRetries     = 3
)

// init ensures docker exists for this test
func init() {
	requireDocker()
}

// TestRouter is the table based test for routers.  It will initialize a fake master/client and expect to deploy
// a router image in docker.  It then sends watch events through the simulator and makes http client requests that
// should go through the deployed router and return data from the client simulator.
func TestRouter(t *testing.T) {
	//create a server which will act as a user deployed application that
	//serves http and https as well as act as a master to simulate watches
	fakeMasterAndPod := tr.NewTestHttpService()
	defer fakeMasterAndPod.Stop()

	err := fakeMasterAndPod.Start()
	validateServer(fakeMasterAndPod, t)

	if err != nil {
		t.Fatalf("Unable to start http server: %v", err)
	}

	//deploy router docker container
	dockerCli, err := newDockerClient()

	if err != nil {
		t.Fatalf("Unable to get docker client: %v", err)
	}

	routerId, err := createAndStartRouterContainer(dockerCli, fakeMasterAndPod.MasterHttpAddr)

	if err != nil {
		t.Fatalf("Error starting container %s : %v", getRouterImage(), err)
	}

	defer cleanUp(dockerCli, routerId)

	httpEndpoint, err := getEndpoint(fakeMasterAndPod.PodHttpAddr)
	if err != nil {
		t.Fatalf("Couldn't get http endpoint: %v", err)
	}
	httpsEndpoint, err := getEndpoint(fakeMasterAndPod.PodHttpsAddr)
	if err != nil {
		t.Fatalf("Couldn't get https endpoint: %v", err)
	}

	//run through test cases now that environment is set up
	testCases := []struct {
		name              string
		serviceName       string
		endpoints         []kapi.Endpoint
		routeAlias        string
		endpointEventType watch.EventType
		routeEventType    watch.EventType
		protocol          string
		expectedResponse  string
		routeTLS          *routeapi.TLSConfig
		routerUrl         string
	}{
		{
			name:              "non-secure",
			serviceName:       "example",
			endpoints:         []kapi.Endpoint{httpEndpoint},
			routeAlias:        "www.example-unsecure.com",
			endpointEventType: watch.Added,
			routeEventType:    watch.Added,
			protocol:          "http",
			expectedResponse:  tr.HelloPod,
			routeTLS:          nil,
			routerUrl:         "0.0.0.0",
		},
		{
			name:              "edge termination",
			serviceName:       "example-edge",
			endpoints:         []kapi.Endpoint{httpEndpoint},
			routeAlias:        "www.example.com",
			endpointEventType: watch.Added,
			routeEventType:    watch.Added,
			protocol:          "https",
			expectedResponse:  tr.HelloPod,
			routeTLS: &routeapi.TLSConfig{
				Termination:   routeapi.TLSTerminationEdge,
				Certificate:   tr.ExampleCert,
				Key:           tr.ExampleKey,
				CACertificate: tr.ExampleCACert,
			},
			routerUrl: "0.0.0.0",
		},
		{
			name:              "passthrough termination",
			serviceName:       "example-passthrough",
			endpoints:         []kapi.Endpoint{httpsEndpoint},
			routeAlias:        "www.example2.com",
			endpointEventType: watch.Added,
			routeEventType:    watch.Added,
			protocol:          "https",
			expectedResponse:  tr.HelloPodSecure,
			routeTLS: &routeapi.TLSConfig{
				Termination: routeapi.TLSTerminationPassthrough,
			},
			routerUrl: "0.0.0.0",
		},
		{
			name:              "websocket unsecure",
			serviceName:       "websocket-unsecure",
			endpoints:         []kapi.Endpoint{httpEndpoint},
			routeAlias:        "0.0.0.0:80",
			endpointEventType: watch.Added,
			routeEventType:    watch.Added,
			protocol:          "ws",
			expectedResponse:  "hello-websocket-unsecure",
			routerUrl:         "0.0.0.0:80/echo",
		},
		{
			name:              "ws edge termination",
			serviceName:       "websocket-edge",
			endpoints:         []kapi.Endpoint{httpEndpoint},
			routeAlias:        "0.0.0.0:443",
			endpointEventType: watch.Added,
			routeEventType:    watch.Added,
			protocol:          "wss",
			expectedResponse:  "hello-websocket-edge",
			routeTLS: &routeapi.TLSConfig{
				Termination:   routeapi.TLSTerminationEdge,
				Certificate:   tr.ExampleCert,
				Key:           tr.ExampleKey,
				CACertificate: tr.ExampleCACert,
			},
			routerUrl: "0.0.0.0:443/echo",
		},
		{
			name:              "ws passthrough termination",
			serviceName:       "websocket-passthrough",
			endpoints:         []kapi.Endpoint{httpsEndpoint},
			routeAlias:        "0.0.0.0:443",
			endpointEventType: watch.Added,
			routeEventType:    watch.Added,
			protocol:          "wss",
			expectedResponse:  "hello-websocket-passthrough",
			routeTLS: &routeapi.TLSConfig{
				Termination: routeapi.TLSTerminationPassthrough,
			},
			routerUrl: "0.0.0.0:443/echo",
		},
	}

	for _, tc := range testCases {
		//simulate the events
		endpointEvent := &watch.Event{
			Type: tc.endpointEventType,

			Object: &kapi.Endpoints{
				ObjectMeta: kapi.ObjectMeta{
					Name: tc.serviceName,
				},
				TypeMeta: kapi.TypeMeta{
					Kind:       "Endpoints",
					APIVersion: "v1beta3",
				},
				Endpoints: tc.endpoints,
			},
		}

		routeEvent := &watch.Event{
			Type: tc.routeEventType,
			Object: &routeapi.Route{
				TypeMeta: kapi.TypeMeta{
					Kind:       "Route",
					APIVersion: "v1beta1",
				},
				Host:        tc.routeAlias,
				ServiceName: tc.serviceName,
				TLS:         tc.routeTLS,
			},
		}

		fakeMasterAndPod.EndpointChannel <- eventString(endpointEvent)
		fakeMasterAndPod.RouteChannel <- eventString(routeEvent)

		for i := 0; i < tcRetries; i++ {
			//wait for router to pick up configs
			time.Sleep(time.Second * tcWaitSeconds)
			//now verify the route with an http client
			resp, err := getRoute(tc.routerUrl, tc.routeAlias, tc.protocol, tc.expectedResponse)

			if err != nil {
				if i != 2 {
					continue
				}
				t.Errorf("Unable to verify response: %v", err)
			}

			if resp != tc.expectedResponse {
				t.Errorf("TC %s failed! Response body %v did not match expected %v", tc.name, resp, tc.expectedResponse)
			} else {
				//good to go, stop trying
				break
			}
		}

		//clean up
		routeEvent.Type = watch.Deleted
		endpointEvent.Type = watch.Deleted

		fakeMasterAndPod.EndpointChannel <- eventString(endpointEvent)
		fakeMasterAndPod.RouteChannel <- eventString(routeEvent)
	}
}

func getEndpoint(hostport string) (kapi.Endpoint, error) {
	host, port, err := net.SplitHostPort(hostport)
	if err != nil {
		return kapi.Endpoint{}, err
	}
	portNum, err := strconv.Atoi(port)
	if err != nil {
		return kapi.Endpoint{}, err
	}
	return kapi.Endpoint{IP: host, Port: portNum}, nil
}

// getRoute is a utility function for making the web request to a route.  Protocol is either http or https.  If the
// protocol is https then getRoute will make a secure transport client with InsecureSkipVerify: true.  Http does a plain
// http client request.
func getRoute(routerUrl string, hostName string, protocol string, expectedResponse string) (response string, err error) {
	url := protocol + "://" + routerUrl
	var tlsConfig *tls.Config

	if protocol == "https" || protocol == "wss" {
		tlsConfig = &tls.Config{
			InsecureSkipVerify: true,
			ServerName:         hostName,
		}
	}

	switch protocol {
	case "http", "https":
		httpClient := &http.Client{Transport: &http.Transport{
			TLSClientConfig: tlsConfig,
		},
		}
		req, err := http.NewRequest("GET", url, nil)

		if err != nil {
			return "", err
		}

		req.Host = hostName
		resp, err := httpClient.Do(req)

		if err != nil {
			return "", err
		}

		var respBody = make([]byte, len([]byte(expectedResponse)))
		resp.Body.Read(respBody)

		return string(respBody), nil
	case "ws", "wss":
		wsConfig, err := websocket.NewConfig(url, "http://localhost/")
		if err != nil {
			return "", err
		}

		wsConfig.Header.Set("Host", hostName)
		wsConfig.TlsConfig = tlsConfig

		ws, err := websocket.DialConfig(wsConfig)
		if err != nil {
			return "", err
		}

		_, err = ws.Write([]byte(expectedResponse))
		if err != nil {
			return "", err
		}

		var msg = make([]byte, len(expectedResponse))
		_, err = ws.Read(msg)
		if err != nil {
			return "", err
		}

		return string(msg), nil
	}

	return "", errors.New("Unrecognized protocol in getRoute")
}

// eventString marshals the event into a string
func eventString(e *watch.Event) string {
	s, _ := json.Marshal(e)
	return string(s)
}

// createAndStartRouterContainer is responsible for deploying the router image in docker.  It assumes that all router images
// will use a command line flag that can take --master which points to the master url
func createAndStartRouterContainer(dockerCli *dockerClient.Client, masterIp string) (containerId string, err error) {
	ports := []string{"80", "443"}
	portBindings := make(map[dockerClient.Port][]dockerClient.PortBinding)
	exposedPorts := map[dockerClient.Port]struct{}{}

	for _, p := range ports {
		dockerPort := dockerClient.Port(p + "/tcp")

		portBindings[dockerPort] = []dockerClient.PortBinding{
			{
				HostPort: p,
			},
		}

		exposedPorts[dockerPort] = struct{}{}
	}

	containerOpts := dockerClient.CreateContainerOptions{
		Config: &dockerClient.Config{
			Image:        getRouterImage(),
			Cmd:          []string{"--master=" + masterIp, "--loglevel=4"},
			ExposedPorts: exposedPorts,
		},
	}

	container, err := dockerCli.CreateContainer(containerOpts)

	if err != nil {
		return "", err
	}

	dockerHostCfg := &dockerClient.HostConfig{NetworkMode: "host", PortBindings: portBindings}
	err = dockerCli.StartContainer(container.ID, dockerHostCfg)

	if err != nil {
		return "", err
	}

	running := false

	//wait for it to start
	for i := 0; i < dockerRetries; i++ {
		c, err := dockerCli.InspectContainer(container.ID)

		if err != nil {
			return "", err
		}

		if c.State.Running {
			running = true
			break
		}
		time.Sleep(time.Second * dockerWaitSeconds)
	}

	if !running {
		return "", errors.New("Container did not start after 3 tries!")
	}

	return container.ID, nil
}

// validateServer performs a basic run through by validating each of the configured urls for the simulator to
// ensure they are responding
func validateServer(server *tr.TestHttpService, t *testing.T) {
	_, err := http.Get("http://" + server.MasterHttpAddr)

	if err != nil {
		t.Errorf("Error validating master addr %s : %v", server.MasterHttpAddr, err)
	}

	_, err = http.Get("http://" + server.PodHttpAddr)

	if err != nil {
		t.Errorf("Error validating master addr %s : %v", server.MasterHttpAddr, err)
	}

	secureTransport := &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}}
	secureClient := &http.Client{Transport: secureTransport}
	_, err = secureClient.Get("https://" + server.PodHttpsAddr)

	if err != nil {
		t.Errorf("Error validating master addr %s : %v", server.MasterHttpAddr, err)
	}
}

// cleanUp stops and removes the deployed router
func cleanUp(dockerCli *dockerClient.Client, routerId string) {
	dockerCli.StopContainer(routerId, 5)

	dockerCli.RemoveContainer(dockerClient.RemoveContainerOptions{
		ID:    routerId,
		Force: true,
	})
}

// getRouterImage is a utility that provides the router image to use by checking to see if OPENSHIFT_ROUTER_IMAGE is set
// or by using the default image
func getRouterImage() string {
	i := os.Getenv("OPENSHIFT_ROUTER_IMAGE")

	if len(i) == 0 {
		i = defaultRouterImage
	}

	return i
}
<|endoftext|>