<commit_before>// Copyright 2016 PingCAP, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// See the License for the specific language governing permissions and
// limitations under the License.

package executor

import (
	"context"
	"sync"

	"github.com/pingcap/errors"
	"github.com/pingcap/parser/model"
	"github.com/pingcap/tidb/expression"
	"github.com/pingcap/tidb/sessionctx"
	"github.com/pingcap/tidb/table"
	"github.com/pingcap/tidb/types"
	"github.com/pingcap/tidb/util/chunk"
)

// DirtyDB stores uncommitted write operations for a transaction.
// It is stored and retrieved by context.Value and context.SetValue method.
type DirtyDB struct {
	sync.Mutex

	// tables is a map whose key is tableID.
	tables map[int64]*DirtyTable
}

// GetDirtyTable gets the DirtyTable by id from the DirtyDB.
func (udb *DirtyDB) GetDirtyTable(tid int64) *DirtyTable {
	// The index join access the tables map parallelly.
	// But the map throws panic in this case. So it's locked.
	udb.Lock()
	dt, ok := udb.tables[tid]
	if !ok {
		dt = &DirtyTable{
			tid:         tid,
			addedRows:   make(map[int64]struct{}),
			deletedRows: make(map[int64]struct{}),
		}
		udb.tables[tid] = dt
	}
	udb.Unlock()
	return dt
}

// DirtyTable stores uncommitted write operation for a transaction.
type DirtyTable struct {
	tid int64
	// addedRows ...
	// the key is handle.
	addedRows   map[int64]struct{}
	deletedRows map[int64]struct{}
}

// AddRow adds a row to the DirtyDB.
func (dt *DirtyTable) AddRow(handle int64) {
	dt.addedRows[handle] = struct{}{}
}

// DeleteRow deletes a row from the DirtyDB.
func (dt *DirtyTable) DeleteRow(handle int64) {
	delete(dt.addedRows, handle)
	dt.deletedRows[handle] = struct{}{}
}

// IsEmpty checks whether the table is empty.
func (dt *DirtyTable) IsEmpty() bool {
	return len(dt.addedRows)+len(dt.deletedRows) == 0
}

// GetDirtyDB returns the DirtyDB bind to the context.
func GetDirtyDB(ctx sessionctx.Context) *DirtyDB {
	var udb *DirtyDB
	x := ctx.GetSessionVars().TxnCtx.DirtyDB
	if x == nil {
		udb = &DirtyDB{tables: make(map[int64]*DirtyTable)}
		ctx.GetSessionVars().TxnCtx.DirtyDB = udb
	} else {
		udb = x.(*DirtyDB)
	}
	return udb
}

// UnionScanExec merges the rows from dirty table and the rows from distsql request.
type UnionScanExec struct {
	baseExecutor

	dirty *DirtyTable
	// usedIndex is the column offsets of the index which Src executor has used.
	usedIndex            []int
	desc                 bool
	conditions           []expression.Expression
	conditionsWithVirCol []expression.Expression
	columns              []*model.ColumnInfo
	table                table.Table
	// belowHandleIndex is the handle's position of the below scan plan.
	belowHandleIndex int

	addedRows           [][]types.Datum
	cursor4AddRows      int
	sortErr             error
	snapshotRows        [][]types.Datum
	cursor4SnapshotRows int
	snapshotChunkBuffer *chunk.Chunk
	mutableRow          chunk.MutRow
	// virtualColumnIndex records all the indices of virtual columns and sort them in definition
	// to make sure we can compute the virtual column in right order.
	virtualColumnIndex []int
}

// Open implements the Executor Open interface.
func (us *UnionScanExec) Open(ctx context.Context) error {
	if err := us.baseExecutor.Open(ctx); err != nil {
		return err
	}
	return us.open(ctx)
}

func (us *UnionScanExec) open(ctx context.Context) error {
	var err error
	reader := us.children[0]

	// If the push-downed condition contains virtual column, we may build a selection upon reader. Since unionScanExec
	// has already contained condition, we can ignore the selection.
	if sel, ok := reader.(*SelectionExec); ok {
		reader = sel.children[0]
	}

	// 1. select without virtual columns
	// 2. build virtual columns and select with virtual columns
	switch x := reader.(type) {
	case *TableReaderExecutor:
		us.addedRows, err = buildMemTableReader(us, x).getMemRows()
	case *IndexReaderExecutor:
		us.addedRows, err = buildMemIndexReader(us, x).getMemRows()
	case *IndexLookUpExecutor:
		us.addedRows, err = buildMemIndexLookUpReader(us, x).getMemRows()
	}
	if err != nil {
		return err
	}
	us.snapshotChunkBuffer = newFirstChunk(us)
	return nil
}

// Next implements the Executor Next interface.
func (us *UnionScanExec) Next(ctx context.Context, req *chunk.Chunk) error {
	req.GrowAndReset(us.maxChunkSize)
	mutableRow := chunk.MutRowFromTypes(retTypes(us))
	for i, batchSize := 0, req.Capacity(); i < batchSize; i++ {
		row, err := us.getOneRow(ctx)
		if err != nil {
			return err
		}
		// no more data.
		if row == nil {
			return nil
		}
		mutableRow.SetDatums(row...)

		for _, idx := range us.virtualColumnIndex {
			datum, err := us.schema.Columns[idx].EvalVirtualColumn(mutableRow.ToRow())
			if err != nil {
				return err
			}
			// Because the expression might return different type from
			// the generated column, we should wrap a CAST on the result.
			castDatum, err := table.CastValue(us.ctx, datum, us.columns[idx])
			if err != nil {
				return err
			}
			mutableRow.SetDatum(idx, castDatum)
		}

		matched, _, err := expression.EvalBool(us.ctx, us.conditionsWithVirCol, mutableRow.ToRow())
		if err != nil {
			return err
		}
		if matched {
			req.AppendRow(mutableRow.ToRow())
		}
	}
	return nil
}

// getOneRow gets one result row from dirty table or child.
func (us *UnionScanExec) getOneRow(ctx context.Context) ([]types.Datum, error) {
	snapshotRow, err := us.getSnapshotRow(ctx)
	if err != nil {
		return nil, err
	}
	addedRow := us.getAddedRow()
	var row []types.Datum
	var isSnapshotRow bool
	if addedRow == nil {
		row = snapshotRow
		isSnapshotRow = true
	} else if snapshotRow == nil {
		row = addedRow
	} else {
		isSnapshotRow, err = us.shouldPickFirstRow(snapshotRow, addedRow)
		if err != nil {
			return nil, err
		}
		if isSnapshotRow {
			row = snapshotRow
		} else {
			row = addedRow
		}
	}
	if row == nil {
		return nil, nil
	}

	if isSnapshotRow {
		us.cursor4SnapshotRows++
	} else {
		us.cursor4AddRows++
	}
	return row, nil
}

func (us *UnionScanExec) getSnapshotRow(ctx context.Context) ([]types.Datum, error) {
	if us.cursor4SnapshotRows < len(us.snapshotRows) {
		return us.snapshotRows[us.cursor4SnapshotRows], nil
	}
	var err error
	us.cursor4SnapshotRows = 0
	us.snapshotRows = us.snapshotRows[:0]
	for len(us.snapshotRows) == 0 {
		err = Next(ctx, us.children[0], us.snapshotChunkBuffer)
		if err != nil || us.snapshotChunkBuffer.NumRows() == 0 {
			return nil, err
		}
		iter := chunk.NewIterator4Chunk(us.snapshotChunkBuffer)
		for row := iter.Begin(); row != iter.End(); row = iter.Next() {
			snapshotHandle := row.GetInt64(us.belowHandleIndex)
			if _, ok := us.dirty.deletedRows[snapshotHandle]; ok {
				continue
			}
			if _, ok := us.dirty.addedRows[snapshotHandle]; ok {
				// If src handle appears in added rows, it means there is conflict and the transaction will fail to
				// commit, but for simplicity, we don't handle it here.
				continue
			}
			us.snapshotRows = append(us.snapshotRows, row.GetDatumRow(retTypes(us.children[0])))
		}
	}
	return us.snapshotRows[0], nil
}

func (us *UnionScanExec) getAddedRow() []types.Datum {
	var addedRow []types.Datum
	if us.cursor4AddRows < len(us.addedRows) {
		addedRow = us.addedRows[us.cursor4AddRows]
	}
	return addedRow
}

// shouldPickFirstRow picks the suitable row in order.
// The value returned is used to determine whether to pick the first input row.
func (us *UnionScanExec) shouldPickFirstRow(a, b []types.Datum) (bool, error) {
	var isFirstRow bool
	addedCmpSrc, err := us.compare(a, b)
	if err != nil {
		return isFirstRow, err
	}
	// Compare result will never be 0.
	if us.desc {
		if addedCmpSrc > 0 {
			isFirstRow = true
		}
	} else {
		if addedCmpSrc < 0 {
			isFirstRow = true
		}
	}
	return isFirstRow, nil
}

func (us *UnionScanExec) compare(a, b []types.Datum) (int, error) {
	sc := us.ctx.GetSessionVars().StmtCtx
	for _, colOff := range us.usedIndex {
		aColumn := a[colOff]
		bColumn := b[colOff]
		cmp, err := aColumn.CompareDatum(sc, &bColumn)
		if err != nil {
			return 0, err
		}
		if cmp != 0 {
			return cmp, nil
		}
	}
	aHandle := a[us.belowHandleIndex].GetInt64()
	bHandle := b[us.belowHandleIndex].GetInt64()
	var cmp int
	if aHandle == bHandle {
		cmp = 0
	} else if aHandle > bHandle {
		cmp = 1
	} else {
		cmp = -1
	}
	return cmp, nil
}

// Len implements sort.Interface interface.
func (us *UnionScanExec) Len() int {
	return len(us.addedRows)
}

// Less implements sort.Interface interface.
func (us *UnionScanExec) Less(i, j int) bool {
	cmp, err := us.compare(us.addedRows[i], us.addedRows[j])
	if err != nil {
		us.sortErr = errors.Trace(err)
		return true
	}
	return cmp < 0
}

// Swap implements sort.Interface interface.
func (us *UnionScanExec) Swap(i, j int) {
	us.addedRows[i], us.addedRows[j] = us.addedRows[j], us.addedRows[i]
}
<commit_msg>executor: remove useless union scan codes (#16683)<commit_after>// Copyright 2016 PingCAP, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// See the License for the specific language governing permissions and
// limitations under the License.

package executor

import (
	"context"
	"sync"

	"github.com/pingcap/parser/model"
	"github.com/pingcap/tidb/expression"
	"github.com/pingcap/tidb/sessionctx"
	"github.com/pingcap/tidb/table"
	"github.com/pingcap/tidb/types"
	"github.com/pingcap/tidb/util/chunk"
)

// DirtyDB stores uncommitted write operations for a transaction.
// It is stored and retrieved by context.Value and context.SetValue method.
type DirtyDB struct {
	sync.Mutex

	// tables is a map whose key is tableID.
	tables map[int64]*DirtyTable
}

// GetDirtyTable gets the DirtyTable by id from the DirtyDB.
func (udb *DirtyDB) GetDirtyTable(tid int64) *DirtyTable {
	// The index join access the tables map parallelly.
	// But the map throws panic in this case. So it's locked.
	udb.Lock()
	dt, ok := udb.tables[tid]
	if !ok {
		dt = &DirtyTable{
			tid:         tid,
			addedRows:   make(map[int64]struct{}),
			deletedRows: make(map[int64]struct{}),
		}
		udb.tables[tid] = dt
	}
	udb.Unlock()
	return dt
}

// DirtyTable stores uncommitted write operation for a transaction.
type DirtyTable struct {
	tid int64
	// addedRows ...
	// the key is handle.
	addedRows   map[int64]struct{}
	deletedRows map[int64]struct{}
}

// AddRow adds a row to the DirtyDB.
func (dt *DirtyTable) AddRow(handle int64) {
	dt.addedRows[handle] = struct{}{}
}

// DeleteRow deletes a row from the DirtyDB.
func (dt *DirtyTable) DeleteRow(handle int64) {
	delete(dt.addedRows, handle)
	dt.deletedRows[handle] = struct{}{}
}

// IsEmpty checks whether the table is empty.
func (dt *DirtyTable) IsEmpty() bool {
	return len(dt.addedRows)+len(dt.deletedRows) == 0
}

// GetDirtyDB returns the DirtyDB bind to the context.
func GetDirtyDB(ctx sessionctx.Context) *DirtyDB {
	var udb *DirtyDB
	x := ctx.GetSessionVars().TxnCtx.DirtyDB
	if x == nil {
		udb = &DirtyDB{tables: make(map[int64]*DirtyTable)}
		ctx.GetSessionVars().TxnCtx.DirtyDB = udb
	} else {
		udb = x.(*DirtyDB)
	}
	return udb
}

// UnionScanExec merges the rows from dirty table and the rows from distsql request.
type UnionScanExec struct {
	baseExecutor

	dirty *DirtyTable
	// usedIndex is the column offsets of the index which Src executor has used.
	usedIndex            []int
	desc                 bool
	conditions           []expression.Expression
	conditionsWithVirCol []expression.Expression
	columns              []*model.ColumnInfo
	table                table.Table
	// belowHandleIndex is the handle's position of the below scan plan.
	belowHandleIndex int

	addedRows           [][]types.Datum
	cursor4AddRows      int
	sortErr             error
	snapshotRows        [][]types.Datum
	cursor4SnapshotRows int
	snapshotChunkBuffer *chunk.Chunk
	mutableRow          chunk.MutRow
	// virtualColumnIndex records all the indices of virtual columns and sort them in definition
	// to make sure we can compute the virtual column in right order.
	virtualColumnIndex []int
}

// Open implements the Executor Open interface.
func (us *UnionScanExec) Open(ctx context.Context) error {
	if err := us.baseExecutor.Open(ctx); err != nil {
		return err
	}
	return us.open(ctx)
}

func (us *UnionScanExec) open(ctx context.Context) error {
	var err error
	reader := us.children[0]

	// If the push-downed condition contains virtual column, we may build a selection upon reader. Since unionScanExec
	// has already contained condition, we can ignore the selection.
	if sel, ok := reader.(*SelectionExec); ok {
		reader = sel.children[0]
	}

	// 1. select without virtual columns
	// 2. build virtual columns and select with virtual columns
	switch x := reader.(type) {
	case *TableReaderExecutor:
		us.addedRows, err = buildMemTableReader(us, x).getMemRows()
	case *IndexReaderExecutor:
		us.addedRows, err = buildMemIndexReader(us, x).getMemRows()
	case *IndexLookUpExecutor:
		us.addedRows, err = buildMemIndexLookUpReader(us, x).getMemRows()
	}
	if err != nil {
		return err
	}
	us.snapshotChunkBuffer = newFirstChunk(us)
	return nil
}

// Next implements the Executor Next interface.
func (us *UnionScanExec) Next(ctx context.Context, req *chunk.Chunk) error {
	req.GrowAndReset(us.maxChunkSize)
	mutableRow := chunk.MutRowFromTypes(retTypes(us))
	for i, batchSize := 0, req.Capacity(); i < batchSize; i++ {
		row, err := us.getOneRow(ctx)
		if err != nil {
			return err
		}
		// no more data.
		if row == nil {
			return nil
		}
		mutableRow.SetDatums(row...)

		for _, idx := range us.virtualColumnIndex {
			datum, err := us.schema.Columns[idx].EvalVirtualColumn(mutableRow.ToRow())
			if err != nil {
				return err
			}
			// Because the expression might return different type from
			// the generated column, we should wrap a CAST on the result.
			castDatum, err := table.CastValue(us.ctx, datum, us.columns[idx])
			if err != nil {
				return err
			}
			mutableRow.SetDatum(idx, castDatum)
		}

		matched, _, err := expression.EvalBool(us.ctx, us.conditionsWithVirCol, mutableRow.ToRow())
		if err != nil {
			return err
		}
		if matched {
			req.AppendRow(mutableRow.ToRow())
		}
	}
	return nil
}

// getOneRow gets one result row from dirty table or child.
func (us *UnionScanExec) getOneRow(ctx context.Context) ([]types.Datum, error) {
	snapshotRow, err := us.getSnapshotRow(ctx)
	if err != nil {
		return nil, err
	}
	addedRow := us.getAddedRow()
	var row []types.Datum
	var isSnapshotRow bool
	if addedRow == nil {
		row = snapshotRow
		isSnapshotRow = true
	} else if snapshotRow == nil {
		row = addedRow
	} else {
		isSnapshotRow, err = us.shouldPickFirstRow(snapshotRow, addedRow)
		if err != nil {
			return nil, err
		}
		if isSnapshotRow {
			row = snapshotRow
		} else {
			row = addedRow
		}
	}
	if row == nil {
		return nil, nil
	}

	if isSnapshotRow {
		us.cursor4SnapshotRows++
	} else {
		us.cursor4AddRows++
	}
	return row, nil
}

func (us *UnionScanExec) getSnapshotRow(ctx context.Context) ([]types.Datum, error) {
	if us.cursor4SnapshotRows < len(us.snapshotRows) {
		return us.snapshotRows[us.cursor4SnapshotRows], nil
	}
	var err error
	us.cursor4SnapshotRows = 0
	us.snapshotRows = us.snapshotRows[:0]
	for len(us.snapshotRows) == 0 {
		err = Next(ctx, us.children[0], us.snapshotChunkBuffer)
		if err != nil || us.snapshotChunkBuffer.NumRows() == 0 {
			return nil, err
		}
		iter := chunk.NewIterator4Chunk(us.snapshotChunkBuffer)
		for row := iter.Begin(); row != iter.End(); row = iter.Next() {
			snapshotHandle := row.GetInt64(us.belowHandleIndex)
			if _, ok := us.dirty.deletedRows[snapshotHandle]; ok {
				continue
			}
			if _, ok := us.dirty.addedRows[snapshotHandle]; ok {
				// If src handle appears in added rows, it means there is conflict and the transaction will fail to
				// commit, but for simplicity, we don't handle it here.
				continue
			}
			us.snapshotRows = append(us.snapshotRows, row.GetDatumRow(retTypes(us.children[0])))
		}
	}
	return us.snapshotRows[0], nil
}

func (us *UnionScanExec) getAddedRow() []types.Datum {
	var addedRow []types.Datum
	if us.cursor4AddRows < len(us.addedRows) {
		addedRow = us.addedRows[us.cursor4AddRows]
	}
	return addedRow
}

// shouldPickFirstRow picks the suitable row in order.
// The value returned is used to determine whether to pick the first input row.
func (us *UnionScanExec) shouldPickFirstRow(a, b []types.Datum) (bool, error) {
	var isFirstRow bool
	addedCmpSrc, err := us.compare(a, b)
	if err != nil {
		return isFirstRow, err
	}
	// Compare result will never be 0.
	if us.desc {
		if addedCmpSrc > 0 {
			isFirstRow = true
		}
	} else {
		if addedCmpSrc < 0 {
			isFirstRow = true
		}
	}
	return isFirstRow, nil
}

func (us *UnionScanExec) compare(a, b []types.Datum) (int, error) {
	sc := us.ctx.GetSessionVars().StmtCtx
	for _, colOff := range us.usedIndex {
		aColumn := a[colOff]
		bColumn := b[colOff]
		cmp, err := aColumn.CompareDatum(sc, &bColumn)
		if err != nil {
			return 0, err
		}
		if cmp != 0 {
			return cmp, nil
		}
	}
	aHandle := a[us.belowHandleIndex].GetInt64()
	bHandle := b[us.belowHandleIndex].GetInt64()
	var cmp int
	if aHandle == bHandle {
		cmp = 0
	} else if aHandle > bHandle {
		cmp = 1
	} else {
		cmp = -1
	}
	return cmp, nil
}
<|endoftext|>


<commit_before>package messages

import "fmt"
import "github.com/shopspring/decimal"

type TargetPortRange struct {
	_struct bool `codec:",uint"`        //encode struct with "unsigned integer" keys
	LowerPort int `json:"lower-port" codec:"8"`
	UpperPort int `json:"upper-port" codec:"9,omitempty"`
}

type MitigationScope struct {
	_struct bool `codec:",uint"`        //encode struct with "unsigned integer" keys
	Scopes            []Scope  `json:"scope"             codec:"2"`
	// only used in response
	ClientDomainIdentifier string `json:"cdid" codec:"3,omitempty"`
}

type Scope struct {
	_struct bool `codec:",uint"`        //encode struct with "unsigned integer" keys
	// A unique identifier that is randomly generated by a DOTS client to prevent request collisions. (used only server side in BODY)
	ClientIdentifier string `json:"cuid" codec:"4,omitempty"`
	// Identifier for the mitigation request
	MitigationId int `json:"mid" codec:"5,omitempty"`
	// prefix
	TargetPrefix []string `json:"target-prefix" codec:"6,omitempty"`
	// lower-port upper-port
	TargetPortRange []TargetPortRange `json:"target-port-range" codec:"7"`
	// Internet Protocol number
	TargetProtocol []int `json:"target-protocol" codec:"10"`
	// FQDN
	FQDN []string `json:"target-fqdn" codec:"11,omitempty"`
	// URI
	URI []string `json:"target-uri" codec:"12,omitempty"`
	// alias name
	AliasName []string `json:"alias-name" codec:"13,omitempty"`
	// lifetime
	Lifetime int `json:"lifetime" codec:"14,omitempty"`
	// attack-status
	AttackStatus int `json:"attack-status" codec:"29,omitempty"`
}

type MitigationRequest struct {
	_struct bool `codec:",uint"`        //encode struct with "unsigned integer" keys
	MitigationScope MitigationScope `json:"ietf-dots-signal-channel:mitigation-scope" codec:"1"`
}

/*
 * get last client-identifier
 */
func (m *MitigationRequest) EffectiveClientIdentifier() string {
	return m.MitigationScope.Scopes[0].ClientIdentifier
}

/*
 * get last client-domain-identifier
 */
func (m *MitigationRequest) EffectiveClientDomainIdentifier() string {
	return m.MitigationScope.ClientDomainIdentifier
}

/*
 * get last client-domain-identifier
 */
 func (m *MitigationRequest) EffectiveMitigationId() int {
	return m.MitigationScope.Scopes[0].MitigationId
}

/*
 * set client-identifier
 */
func (m *MitigationRequest) UpdateClientIdentifier(cuid string) {
	m.MitigationScope.Scopes[0].ClientIdentifier = cuid
}

/*
 * set client-domain-identifier
 */
func (m *MitigationRequest) UpdateClientDomainIdentifier(cdid string) {
	m.MitigationScope.ClientDomainIdentifier = cdid
}

/*
 * set mitigation-id
 */
func (m *MitigationRequest) UpdateMitigationId(mid int) {
	m.MitigationScope.Scopes[0].MitigationId = mid
}

/*
 * Convert MitigationRequests to strings
 */
func (m *MitigationRequest) String() (result string) {
	result = "\n \"ietf-dots-signal-channel:mitigation-scope\":\n"
	for key, scope := range m.MitigationScope.Scopes {
		result += fmt.Sprintf("   \"%s[%d]\":\n", "scope", key+1)
		result += fmt.Sprintf("     \"%s\": %d\n", "mid", scope.MitigationId)
		if scope.TargetPrefix != nil {
			for k, v := range scope.TargetPrefix {
				result += fmt.Sprintf("     \"%s[%d]\": %s\n", "target-prefix", k+1, v)
			}
		}
		if scope.TargetPortRange != nil {
			for k, v := range scope.TargetPortRange {
				result += fmt.Sprintf("     \"%s[%d]\":\n", "target-port-range", k+1)
				result += fmt.Sprintf("       \"%s\": %d\n", "lower-port", v.LowerPort)
				if (v.UpperPort > 0) {
					result += fmt.Sprintf("       \"%s\": %d\n", "upper-port", v.UpperPort)
				}
			}
		}
		if scope.TargetProtocol != nil {
			for k, v := range scope.TargetProtocol {
				result += fmt.Sprintf("     \"%s[%d]\": %d\n", "target-protocol", k+1, v)
			}
		}
		if scope.FQDN != nil {
			for k, v := range scope.FQDN {
				result += fmt.Sprintf("     \"%s[%d]\": %s\n", "target-fqdn", k+1, v)
			}
		}
		if scope.URI != nil {
			for k, v := range scope.URI {
				result += fmt.Sprintf("     \"%s[%d]\": %s\n", "target-uri", k+1, v)
			}
		}
		if scope.AliasName != nil {
			for k, v := range scope.AliasName {
				result += fmt.Sprintf("     \"%s[%d]\": %s\n", "alias-name", k+1, v)
			}
		}
		if scope.Lifetime != 0 {
			result += fmt.Sprintf("     \"%s\": %d\n", "lifetime", scope.Lifetime)
		}
		if scope.AttackStatus != 0 {
			result += fmt.Sprintf("     \"%s\": %d\n", "attack-status", scope.AttackStatus)
		}
	}
	return
}

type SignalConfigRequest struct {
	_struct bool `codec:",uint"`        //encode struct with "unsigned integer" keys
	SignalConfigs SignalConfigs `json:"ietf-dots-signal-channel:signal-config" codec:"30"`
}

type SignalConfigs struct {
	_struct bool `codec:",uint"`        //encode struct with "unsigned integer" keys
	MitigatingConfig SignalConfig `json:"mitigating-config" codec:"32"`
	IdleConfig SignalConfig       `json:"idle-config"       codec:"44"`
	// If false, mitigation is triggered only if the signal channel is lost. This is an optional attribute.
	TriggerMitigation bool `json:"trigger-mitigation" codec:"45"`
}

type IntCurrent struct {
	_struct bool `codec:",uint"`        //encode struct with "unsigned integer" keys
	CurrentValue int `json:"current-value" codec:"36"`
}

type DecimalCurrent struct {
	_struct bool `codec:",uint"`        //encode struct with "unsigned integer" keys
	CurrentValue decimal.Decimal `json:"current-value-decimal" codec:"43"`
}

type SignalConfig struct {
	_struct bool `codec:",uint"`        //encode struct with "unsigned integer" keys
	// Identifier for the DOTS signal channel session configuration data represented as an integer.
	// This identifier MUST be generated by the DOTS client.  This document does not make any assumption about how this
	// identifier is generated. This is a mandatory attribute.
	SessionId int `json:"sid" codec:"31,omitempty"`
	// Heartbeat interval to check the DOTS peer health.  This is an optional attribute.
	HeartbeatInterval IntCurrent `json:"heartbeat-interval" codec:"33"`
	// Maximum number of missing heartbeat response allowed. This is an optional attribute.
	MissingHbAllowed IntCurrent `json:"missing-hb-allowed" codec:"37"`
	// Maximum number of retransmissions for a message (referred to as MAX_RETRANSMIT parameter in CoAP).
	// This is an optional attribute.
	MaxRetransmit IntCurrent `json:"max-retransmit" codec:"38"`
	// Timeout value in seconds used to calculate the initial retransmission timeout value (referred to as ACK_TIMEOUT
	// parameter in CoAP). This is an optional attribute.
	AckTimeout DecimalCurrent `json:"ack-timeout" codec:"39"`
	// Random factor used to influence the timing of retransmissions (referred to as ACK_RANDOM_FACTOR parameter in
	// CoAP).  This is an optional attribute.
	AckRandomFactor DecimalCurrent `json:"ack-random-factor" codec:"40"`
}

type HelloRequest struct {
	Message string `json:"message" cbor:"message"`
}

type HelloResponse struct {
	Message string `json:"message" cbor:"message"`
}

type SignalChannelRequest struct {
	_struct bool `codec:",uint"`        //encode struct with "unsigned integer" keys
}
<commit_msg>Fix dev#89 Not send 0 value when POST SessionConfig<commit_after>package messages

import "fmt"
import "github.com/shopspring/decimal"

type TargetPortRange struct {
	_struct bool `codec:",uint"`        //encode struct with "unsigned integer" keys
	LowerPort int `json:"lower-port" codec:"8"`
	UpperPort int `json:"upper-port" codec:"9,omitempty"`
}

type MitigationScope struct {
	_struct bool `codec:",uint"`        //encode struct with "unsigned integer" keys
	Scopes            []Scope  `json:"scope"             codec:"2"`
	// only used in response
	ClientDomainIdentifier string `json:"cdid" codec:"3,omitempty"`
}

type Scope struct {
	_struct bool `codec:",uint"`        //encode struct with "unsigned integer" keys
	// A unique identifier that is randomly generated by a DOTS client to prevent request collisions. (used only server side in BODY)
	ClientIdentifier string `json:"cuid" codec:"4,omitempty"`
	// Identifier for the mitigation request
	MitigationId int `json:"mid" codec:"5,omitempty"`
	// prefix
	TargetPrefix []string `json:"target-prefix" codec:"6,omitempty"`
	// lower-port upper-port
	TargetPortRange []TargetPortRange `json:"target-port-range" codec:"7"`
	// Internet Protocol number
	TargetProtocol []int `json:"target-protocol" codec:"10"`
	// FQDN
	FQDN []string `json:"target-fqdn" codec:"11,omitempty"`
	// URI
	URI []string `json:"target-uri" codec:"12,omitempty"`
	// alias name
	AliasName []string `json:"alias-name" codec:"13,omitempty"`
	// lifetime
	Lifetime int `json:"lifetime" codec:"14,omitempty"`
	// attack-status
	AttackStatus int `json:"attack-status" codec:"29,omitempty"`
}

type MitigationRequest struct {
	_struct bool `codec:",uint"`        //encode struct with "unsigned integer" keys
	MitigationScope MitigationScope `json:"ietf-dots-signal-channel:mitigation-scope" codec:"1"`
}

/*
 * get last client-identifier
 */
func (m *MitigationRequest) EffectiveClientIdentifier() string {
	return m.MitigationScope.Scopes[0].ClientIdentifier
}

/*
 * get last client-domain-identifier
 */
func (m *MitigationRequest) EffectiveClientDomainIdentifier() string {
	return m.MitigationScope.ClientDomainIdentifier
}

/*
 * get last client-domain-identifier
 */
 func (m *MitigationRequest) EffectiveMitigationId() int {
	return m.MitigationScope.Scopes[0].MitigationId
}

/*
 * set client-identifier
 */
func (m *MitigationRequest) UpdateClientIdentifier(cuid string) {
	m.MitigationScope.Scopes[0].ClientIdentifier = cuid
}

/*
 * set client-domain-identifier
 */
func (m *MitigationRequest) UpdateClientDomainIdentifier(cdid string) {
	m.MitigationScope.ClientDomainIdentifier = cdid
}

/*
 * set mitigation-id
 */
func (m *MitigationRequest) UpdateMitigationId(mid int) {
	m.MitigationScope.Scopes[0].MitigationId = mid
}

/*
 * Convert MitigationRequests to strings
 */
func (m *MitigationRequest) String() (result string) {
	result = "\n \"ietf-dots-signal-channel:mitigation-scope\":\n"
	for key, scope := range m.MitigationScope.Scopes {
		result += fmt.Sprintf("   \"%s[%d]\":\n", "scope", key+1)
		result += fmt.Sprintf("     \"%s\": %d\n", "mid", scope.MitigationId)
		if scope.TargetPrefix != nil {
			for k, v := range scope.TargetPrefix {
				result += fmt.Sprintf("     \"%s[%d]\": %s\n", "target-prefix", k+1, v)
			}
		}
		if scope.TargetPortRange != nil {
			for k, v := range scope.TargetPortRange {
				result += fmt.Sprintf("     \"%s[%d]\":\n", "target-port-range", k+1)
				result += fmt.Sprintf("       \"%s\": %d\n", "lower-port", v.LowerPort)
				if (v.UpperPort > 0) {
					result += fmt.Sprintf("       \"%s\": %d\n", "upper-port", v.UpperPort)
				}
			}
		}
		if scope.TargetProtocol != nil {
			for k, v := range scope.TargetProtocol {
				result += fmt.Sprintf("     \"%s[%d]\": %d\n", "target-protocol", k+1, v)
			}
		}
		if scope.FQDN != nil {
			for k, v := range scope.FQDN {
				result += fmt.Sprintf("     \"%s[%d]\": %s\n", "target-fqdn", k+1, v)
			}
		}
		if scope.URI != nil {
			for k, v := range scope.URI {
				result += fmt.Sprintf("     \"%s[%d]\": %s\n", "target-uri", k+1, v)
			}
		}
		if scope.AliasName != nil {
			for k, v := range scope.AliasName {
				result += fmt.Sprintf("     \"%s[%d]\": %s\n", "alias-name", k+1, v)
			}
		}
		if scope.Lifetime != 0 {
			result += fmt.Sprintf("     \"%s\": %d\n", "lifetime", scope.Lifetime)
		}
		if scope.AttackStatus != 0 {
			result += fmt.Sprintf("     \"%s\": %d\n", "attack-status", scope.AttackStatus)
		}
	}
	return
}

type SignalConfigRequest struct {
	_struct bool `codec:",uint"`        //encode struct with "unsigned integer" keys
	SignalConfigs SignalConfigs `json:"ietf-dots-signal-channel:signal-config" codec:"30"`
}

type SignalConfigs struct {
	_struct bool `codec:",uint"`        //encode struct with "unsigned integer" keys
	MitigatingConfig SignalConfig `json:"mitigating-config" codec:"32"`
	IdleConfig SignalConfig       `json:"idle-config"       codec:"44"`
	// If false, mitigation is triggered only if the signal channel is lost. This is an optional attribute.
	TriggerMitigation bool `json:"trigger-mitigation" codec:"45"`
}

type IntCurrent struct {
	_struct bool `codec:",uint"`        //encode struct with "unsigned integer" keys
	CurrentValue int `json:"current-value" codec:"36,omitempty"`
}

type DecimalCurrent struct {
	_struct bool `codec:",uint"`        //encode struct with "unsigned integer" keys
	CurrentValue decimal.Decimal `json:"current-value-decimal" codec:"43,omitempty"`
}

type SignalConfig struct {
	_struct bool `codec:",uint"`        //encode struct with "unsigned integer" keys
	// Identifier for the DOTS signal channel session configuration data represented as an integer.
	// This identifier MUST be generated by the DOTS client.  This document does not make any assumption about how this
	// identifier is generated. This is a mandatory attribute.
	SessionId int `json:"sid" codec:"31,omitempty"`
	// Heartbeat interval to check the DOTS peer health.  This is an optional attribute.
	HeartbeatInterval IntCurrent `json:"heartbeat-interval" codec:"33,omitempty"`
	// Maximum number of missing heartbeat response allowed. This is an optional attribute.
	MissingHbAllowed IntCurrent `json:"missing-hb-allowed" codec:"37,omitempty"`
	// Maximum number of retransmissions for a message (referred to as MAX_RETRANSMIT parameter in CoAP).
	// This is an optional attribute.
	MaxRetransmit IntCurrent `json:"max-retransmit" codec:"38,omitempty"`
	// Timeout value in seconds used to calculate the initial retransmission timeout value (referred to as ACK_TIMEOUT
	// parameter in CoAP). This is an optional attribute.
	AckTimeout DecimalCurrent `json:"ack-timeout" codec:"39,omitempty"`
	// Random factor used to influence the timing of retransmissions (referred to as ACK_RANDOM_FACTOR parameter in
	// CoAP).  This is an optional attribute.
	AckRandomFactor DecimalCurrent `json:"ack-random-factor" codec:"40,omitempty"`
}

type HelloRequest struct {
	Message string `json:"message" cbor:"message"`
}

type HelloResponse struct {
	Message string `json:"message" cbor:"message"`
}

type SignalChannelRequest struct {
	_struct bool `codec:",uint"`        //encode struct with "unsigned integer" keys
}
<|endoftext|>


<commit_before>package metrics

import (
	"runtime"
	"time"
)

// RuntimeSample represents a sampling of the runtime stats.
type RuntimeSample struct {
	*runtime.MemStats
	NumGoroutine int
}

// NewRuntimeSample samples the current runtime and returns a RuntimeSample.
func NewRuntimeSample() *RuntimeSample {
	r := &RuntimeSample{MemStats: &runtime.MemStats{}}
	runtime.ReadMemStats(r.MemStats)
	r.NumGoroutine = runtime.NumGoroutine()
	return r
}

// Drain drains all of the metrics.
func (r *RuntimeSample) Drain() {
	Sample("goroutine", r.NumGoroutine, "")
	Sample("runtime.MemStats.Alloc", r.MemStats.Alloc, "")
	Sample("runtime.MemStats.Frees", r.MemStats.Frees, "")
	Sample("runtime.MemStats.HeapAlloc", r.MemStats.HeapAlloc, "")
	Sample("runtime.MemStats.HeapIdle", r.MemStats.HeapIdle, "")
	Sample("runtime.MemStats.HeapObjects", r.MemStats.HeapObjects, "")
	Sample("runtime.MemStats.HeapReleased", r.MemStats.HeapReleased, "")
	Sample("runtime.MemStats.HeapSys", r.MemStats.HeapSys, "")
	Sample("runtime.MemStats.LastGC", r.MemStats.LastGC, "")
	Sample("runtime.MemStats.Lookups", r.MemStats.Lookups, "")
	Sample("runtime.MemStats.Mallocs", r.MemStats.Mallocs, "")
	Sample("runtime.MemStats.MCacheInuse", r.MemStats.MCacheInuse, "")
	Sample("runtime.MemStats.MCacheSys", r.MemStats.MCacheSys, "")
	Sample("runtime.MemStats.MSpanInuse", r.MemStats.MSpanInuse, "")
	Sample("runtime.MemStats.MSpanSys", r.MemStats.MSpanSys, "")
	Sample("runtime.MemStats.NextGC", r.MemStats.NextGC, "")
	Sample("runtime.MemStats.NumGC", r.MemStats.NumGC, "")
	Sample("runtime.MemStats.StackInuse", r.MemStats.StackInuse, "")
}

// Runtime enters into a loop, sampling and outputing the runtime stats periodically.
func Runtime() {
	SampleEvery(5 * time.Second)
}

// SampleEvery enters a loop, sampling at the specified interval
func SampleEvery(t time.Duration) {
	c := time.Tick(t)
	for _ = range c {
		r := NewRuntimeSample()
		r.Drain()
	}
}
<commit_msg>Sample every 30 seconds by default.<commit_after>package metrics

import (
	"runtime"
	"time"
)

// RuntimeSample represents a sampling of the runtime stats.
type RuntimeSample struct {
	*runtime.MemStats
	NumGoroutine int
}

// NewRuntimeSample samples the current runtime and returns a RuntimeSample.
func NewRuntimeSample() *RuntimeSample {
	r := &RuntimeSample{MemStats: &runtime.MemStats{}}
	runtime.ReadMemStats(r.MemStats)
	r.NumGoroutine = runtime.NumGoroutine()
	return r
}

// Drain drains all of the metrics.
func (r *RuntimeSample) Drain() {
	Sample("goroutine", r.NumGoroutine, "")
	Sample("runtime.MemStats.Alloc", r.MemStats.Alloc, "")
	Sample("runtime.MemStats.Frees", r.MemStats.Frees, "")
	Sample("runtime.MemStats.HeapAlloc", r.MemStats.HeapAlloc, "")
	Sample("runtime.MemStats.HeapIdle", r.MemStats.HeapIdle, "")
	Sample("runtime.MemStats.HeapObjects", r.MemStats.HeapObjects, "")
	Sample("runtime.MemStats.HeapReleased", r.MemStats.HeapReleased, "")
	Sample("runtime.MemStats.HeapSys", r.MemStats.HeapSys, "")
	Sample("runtime.MemStats.LastGC", r.MemStats.LastGC, "")
	Sample("runtime.MemStats.Lookups", r.MemStats.Lookups, "")
	Sample("runtime.MemStats.Mallocs", r.MemStats.Mallocs, "")
	Sample("runtime.MemStats.MCacheInuse", r.MemStats.MCacheInuse, "")
	Sample("runtime.MemStats.MCacheSys", r.MemStats.MCacheSys, "")
	Sample("runtime.MemStats.MSpanInuse", r.MemStats.MSpanInuse, "")
	Sample("runtime.MemStats.MSpanSys", r.MemStats.MSpanSys, "")
	Sample("runtime.MemStats.NextGC", r.MemStats.NextGC, "")
	Sample("runtime.MemStats.NumGC", r.MemStats.NumGC, "")
	Sample("runtime.MemStats.StackInuse", r.MemStats.StackInuse, "")
}

// DefaultInterval sets how often the metrics Runtime will sample and
// emit stats.
var DefaultInterval time.Duration = 30 * time.Second

// Runtime enters into a loop, sampling and outputing the runtime stats periodically.
func Runtime() {
	SampleEvery(DefaultInterval)
}

// SampleEvery enters a loop, sampling at the specified interval
func SampleEvery(t time.Duration) {
	c := time.Tick(t)
	for _ = range c {
		r := NewRuntimeSample()
		r.Drain()
	}
}
<|endoftext|>


<commit_before>// Package internal holds asset templates used by bootkube.
package internal

var (
	KubeConfigTemplate = []byte(`apiVersion: v1
kind: Config
clusters:
- name: local
  cluster:
    server: {{ .Server }}
    certificate-authority-data: {{ .CACert }}
users:
- name: kubelet
  user:
    client-certificate-data: {{ .KubeletCert}}
    client-key-data: {{ .KubeletKey }}
contexts:
- context:
    cluster: local
    user: kubelet
`)
	KubeletTemplate = []byte(`apiVersion: extensions/v1beta1
kind: DaemonSet
metadata:
  name: kubelet
  namespace: kube-system
  labels:
    k8s-app: kubelet
    version: v1.3.6_coreos.0
spec:
  template:
    metadata:
      labels:
        k8s-app: kubelet
        version: v1.3.6_coreos.0
    spec:
      containers:
      - name: kubelet
        image: quay.io/coreos/hyperkube:v1.3.6_coreos.0
        command:
        - /nsenter
        - --target=1
        - --mount
        - --wd=.
        - --
        - ./hyperkube
        - kubelet
        - --api-servers={{ index .APIServers 0 }}
        - --config=/etc/kubernetes/manifests
        - --allow-privileged
        - --hostname-override=$(MY_POD_IP)
        - --cluster-dns=10.3.0.10
        - --cluster-domain=cluster.local
        - --kubeconfig=/etc/kubernetes/kubeconfig
        - --lock-file=/var/run/lock/kubelet.lock
        - --minimum-container-ttl-duration=3m0s
        env:
          - name: MY_POD_IP
            valueFrom:
              fieldRef:
                fieldPath: status.podIP
        securityContext:
          privileged: true
        volumeMounts:
        - name: dev
          mountPath: /dev
        - name: run
          mountPath: /run
        - name: sys
          mountPath: /sys
          readOnly: true
        - name: etc-kubernetes
          mountPath: /etc/kubernetes
          readOnly: true
        - name: etc-ssl-certs
          mountPath: /etc/ssl/certs
          readOnly: true
        - name: var-lib-docker
          mountPath: /var/lib/docker
        - name: var-lib-kubelet
          mountPath: /var/lib/kubelet
        - name: var-lib-rkt
          mountPath: /var/lib/rkt
      hostNetwork: true
      hostPID: true
      volumes:
      - name: dev
        hostPath:
          path: /dev
      - name: run
        hostPath:
          path: /run
      - name: sys
        hostPath:
          path: /sys
      - name: etc-kubernetes
        hostPath:
          path: /etc/kubernetes
      - name: etc-ssl-certs
        hostPath:
          path: /usr/share/ca-certificates
      - name: var-lib-docker
        hostPath:
          path: /var/lib/docker
      - name: var-lib-kubelet
        hostPath:
          path: /var/lib/kubelet
      - name: var-lib-rkt
        hostPath:
          path: /var/lib/rkt
`)
	APIServerTemplate = []byte(`apiVersion: "extensions/v1beta1"
kind: DaemonSet
metadata:
  name: kube-apiserver
  namespace: kube-system
  labels:
    k8s-app: kube-apiserver
    version: v1.3.6_coreos.0
spec:
  template:
    metadata:
      labels:
        k8s-app: kube-apiserver
        version: v1.3.6_coreos.0
    spec:
      nodeSelector:
        master: "true"
      hostNetwork: true
      containers:
      - name: checkpoint-installer
        image: quay.io/coreos/pod-checkpointer:91d5a311eee40d579a8f0549c10eeed57979d6c4
        command:
        - /checkpoint-installer.sh
        volumeMounts:
        - mountPath: /etc/kubernetes/manifests
          name: etc-k8s-manifests
      - name: kube-apiserver
        image: quay.io/coreos/hyperkube:v1.3.6_coreos.0
        command:
        - /hyperkube
        - apiserver
        - --bind-address=0.0.0.0
        - --secure-port=443
        - --insecure-port=8080
        - --advertise-address=$(MY_POD_IP)
        - --etcd-servers={{ range $i, $e := .EtcdServers }}{{ if $i }},{{end}}{{ $e }}{{end}}
        - --allow-privileged=true
        - --service-cluster-ip-range=10.3.0.0/24
        - --admission-control=NamespaceLifecycle,LimitRanger,ServiceAccount,ResourceQuota
        - --tls-cert-file=/etc/kubernetes/secrets/apiserver.crt
        - --tls-private-key-file=/etc/kubernetes/secrets/apiserver.key
        - --service-account-key-file=/etc/kubernetes/secrets/service-account.pub
        - --client-ca-file=/etc/kubernetes/secrets/ca.crt
        env:
          - name: MY_POD_IP
            valueFrom:
              fieldRef:
                fieldPath: status.podIP
        volumeMounts:
        - mountPath: /etc/ssl/certs
          name: ssl-certs-host
          readOnly: true
        - mountPath: /etc/kubernetes/secrets
          name: secrets
          readOnly: true
      volumes:
      - name: ssl-certs-host
        hostPath:
          path: /usr/share/ca-certificates
      - name: etc-k8s-manifests
        hostPath:
          path: /etc/kubernetes/manifests
      - name: secrets
        secret:
          secretName: kube-apiserver
`)
	ControllerManagerTemplate = []byte(`apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: kube-controller-manager
  namespace: kube-system
  labels:
    k8s-app: kube-controller-manager
    version: v1.3.6_coreos.0
spec:
  template:
    metadata:
      labels:
        k8s-app: kube-controller-manager
        version: v1.3.6_coreos.0
    spec:
      containers:
      - name: kube-controller-manager
        image: quay.io/coreos/hyperkube:v1.3.6_coreos.0
        command:
        - ./hyperkube
        - controller-manager
        - --root-ca-file=/etc/kubernetes/secrets/ca.crt
        - --service-account-private-key-file=/etc/kubernetes/secrets/service-account.key
        - --leader-elect=true
        volumeMounts:
        - name: secrets
          mountPath: /etc/kubernetes/secrets
          readOnly: true
        - name: ssl-host
          mountPath: /etc/ssl/certs
          readOnly: true
      volumes:
      - name: secrets
        secret:
          secretName: kube-controller-manager
      - name: ssl-host
        hostPath:
          path: /usr/share/ca-certificates
`)
	SchedulerTemplate = []byte(`apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: kube-scheduler
  namespace: kube-system
  labels:
    k8s-app: kube-scheduler
    version: v1.3.6_coreos.0
spec:
  template:
    metadata:
      labels:
        k8s-app: kube-scheduler
        version: v1.3.6_coreos.0
    spec:
      containers:
      - name: kube-scheduler
        image: quay.io/coreos/hyperkube:v1.3.6_coreos.0
        command:
        - ./hyperkube
        - scheduler
        - --leader-elect=true
`)
	ProxyTemplate = []byte(`apiVersion: "extensions/v1beta1"
kind: DaemonSet
metadata:
  name: kube-proxy
  namespace: kube-system
  labels:
    k8s_app: kube-proxy
    version: v1.3.6_coreos.0
spec:
  template:
    metadata:
      labels:
        k8s_app: kube-proxy
        version: v1.3.6_coreos.0
    spec:
      hostNetwork: true
      containers:
      - name: kube-proxy
        image: quay.io/coreos/hyperkube:v1.3.6_coreos.0
        command:
        - /hyperkube
        - proxy
        - --master={{ index .APIServers 0 }}
        - --kubeconfig=/etc/kubernetes/kubeconfig
        - --proxy-mode=iptables
        securityContext:
          privileged: true
        volumeMounts:
        - mountPath: /etc/ssl/certs
          name: ssl-certs-host
          readOnly: true
        - name: etc-kubernetes
          mountPath: /etc/kubernetes
          readOnly: true
      volumes:
      - hostPath:
          path: /usr/share/ca-certificates
        name: ssl-certs-host
      - name: etc-kubernetes
        hostPath:
          path: /etc/kubernetes
`)
	DNSDeploymentTemplate = []byte(`apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: kube-dns-v17.1
  namespace: kube-system
  labels:
    k8s-app: kube-dns
    version: v17.1
    kubernetes.io/cluster-service: "true"
spec:
  replicas: 1
  template:
    metadata:
      labels:
        k8s-app: kube-dns
        version: v17.1
        kubernetes.io/cluster-service: "true"
    spec:
      containers:
      - name: kubedns
        image: gcr.io/google_containers/kubedns-amd64:1.5
        resources:
          limits:
            cpu: 100m
            memory: 170Mi
          requests:
            cpu: 100m
            memory: 70Mi
        livenessProbe:
          httpGet:
            path: /healthz
            port: 8080
            scheme: HTTP
          initialDelaySeconds: 60
          timeoutSeconds: 5
          successThreshold: 1
          failureThreshold: 5
        readinessProbe:
          httpGet:
            path: /readiness
            port: 8081
            scheme: HTTP
          initialDelaySeconds: 30
          timeoutSeconds: 5
        args:
          - --domain=cluster.local.
          - --dns-port=10053
          ports:
          - containerPort: 10053
            name: dns-local
            protocol: UDP
          - containerPort: 10053
            name: dns-tcp-local
            protocol: TCP
        - name: dnsmasq
          image: gcr.io/google_containers/kube-dnsmasq-amd64:1.3
          args:
          - --cache-size=1000
          - --no-resolv
          - --server=127.0.0.1#10053
        ports:
        - containerPort: 53
          name: dns
          protocol: UDP
        - containerPort: 53
          name: dns-tcp
          protocol: TCP
      - name: healthz
        image: gcr.io/google_containers/exechealthz-amd64:1.1
        resources:
          limits:
            cpu: 10m
            memory: 50Mi
          requests:
            cpu: 10m
            memory: 50Mi
        args:
        - -cmd=nslookup kubernetes.default.svc.cluster.local 127.0.0.1 >/dev/null
        - -port=8080
        - -quiet
        ports:
        - containerPort: 8080
          protocol: TCP
      dnsPolicy: Default
`)
	DNSSvcTemplate = []byte(`apiVersion: v1
kind: Service
metadata:
  name: kube-dns
  namespace: kube-system
  labels:
    k8s-app: kube-dns
    kubernetes.io/cluster-service: "true"
    kubernetes.io/name: "KubeDNS"
spec:
  selector:
    k8s-app: kube-dns
  clusterIP:  10.3.0.10
  ports:
  - name: dns
    port: 53
    protocol: UDP
  - name: dns-tcp
    port: 53
    protocol: TCP
`)
	SystemNSTemplate = []byte(`apiVersion: v1
kind: Namespace
metadata:
  name: kube-system
`)
)
<commit_msg>pkg/assets/internal/template.go: Fix kube-dns indentation<commit_after>// Package internal holds asset templates used by bootkube.
package internal

var (
	KubeConfigTemplate = []byte(`apiVersion: v1
kind: Config
clusters:
- name: local
  cluster:
    server: {{ .Server }}
    certificate-authority-data: {{ .CACert }}
users:
- name: kubelet
  user:
    client-certificate-data: {{ .KubeletCert}}
    client-key-data: {{ .KubeletKey }}
contexts:
- context:
    cluster: local
    user: kubelet
`)
	KubeletTemplate = []byte(`apiVersion: extensions/v1beta1
kind: DaemonSet
metadata:
  name: kubelet
  namespace: kube-system
  labels:
    k8s-app: kubelet
    version: v1.3.6_coreos.0
spec:
  template:
    metadata:
      labels:
        k8s-app: kubelet
        version: v1.3.6_coreos.0
    spec:
      containers:
      - name: kubelet
        image: quay.io/coreos/hyperkube:v1.3.6_coreos.0
        command:
        - /nsenter
        - --target=1
        - --mount
        - --wd=.
        - --
        - ./hyperkube
        - kubelet
        - --api-servers={{ index .APIServers 0 }}
        - --config=/etc/kubernetes/manifests
        - --allow-privileged
        - --hostname-override=$(MY_POD_IP)
        - --cluster-dns=10.3.0.10
        - --cluster-domain=cluster.local
        - --kubeconfig=/etc/kubernetes/kubeconfig
        - --lock-file=/var/run/lock/kubelet.lock
        - --minimum-container-ttl-duration=3m0s
        env:
          - name: MY_POD_IP
            valueFrom:
              fieldRef:
                fieldPath: status.podIP
        securityContext:
          privileged: true
        volumeMounts:
        - name: dev
          mountPath: /dev
        - name: run
          mountPath: /run
        - name: sys
          mountPath: /sys
          readOnly: true
        - name: etc-kubernetes
          mountPath: /etc/kubernetes
          readOnly: true
        - name: etc-ssl-certs
          mountPath: /etc/ssl/certs
          readOnly: true
        - name: var-lib-docker
          mountPath: /var/lib/docker
        - name: var-lib-kubelet
          mountPath: /var/lib/kubelet
        - name: var-lib-rkt
          mountPath: /var/lib/rkt
      hostNetwork: true
      hostPID: true
      volumes:
      - name: dev
        hostPath:
          path: /dev
      - name: run
        hostPath:
          path: /run
      - name: sys
        hostPath:
          path: /sys
      - name: etc-kubernetes
        hostPath:
          path: /etc/kubernetes
      - name: etc-ssl-certs
        hostPath:
          path: /usr/share/ca-certificates
      - name: var-lib-docker
        hostPath:
          path: /var/lib/docker
      - name: var-lib-kubelet
        hostPath:
          path: /var/lib/kubelet
      - name: var-lib-rkt
        hostPath:
          path: /var/lib/rkt
`)
	APIServerTemplate = []byte(`apiVersion: "extensions/v1beta1"
kind: DaemonSet
metadata:
  name: kube-apiserver
  namespace: kube-system
  labels:
    k8s-app: kube-apiserver
    version: v1.3.6_coreos.0
spec:
  template:
    metadata:
      labels:
        k8s-app: kube-apiserver
        version: v1.3.6_coreos.0
    spec:
      nodeSelector:
        master: "true"
      hostNetwork: true
      containers:
      - name: checkpoint-installer
        image: quay.io/coreos/pod-checkpointer:91d5a311eee40d579a8f0549c10eeed57979d6c4
        command:
        - /checkpoint-installer.sh
        volumeMounts:
        - mountPath: /etc/kubernetes/manifests
          name: etc-k8s-manifests
      - name: kube-apiserver
        image: quay.io/coreos/hyperkube:v1.3.6_coreos.0
        command:
        - /hyperkube
        - apiserver
        - --bind-address=0.0.0.0
        - --secure-port=443
        - --insecure-port=8080
        - --advertise-address=$(MY_POD_IP)
        - --etcd-servers={{ range $i, $e := .EtcdServers }}{{ if $i }},{{end}}{{ $e }}{{end}}
        - --allow-privileged=true
        - --service-cluster-ip-range=10.3.0.0/24
        - --admission-control=NamespaceLifecycle,LimitRanger,ServiceAccount,ResourceQuota
        - --tls-cert-file=/etc/kubernetes/secrets/apiserver.crt
        - --tls-private-key-file=/etc/kubernetes/secrets/apiserver.key
        - --service-account-key-file=/etc/kubernetes/secrets/service-account.pub
        - --client-ca-file=/etc/kubernetes/secrets/ca.crt
        env:
          - name: MY_POD_IP
            valueFrom:
              fieldRef:
                fieldPath: status.podIP
        volumeMounts:
        - mountPath: /etc/ssl/certs
          name: ssl-certs-host
          readOnly: true
        - mountPath: /etc/kubernetes/secrets
          name: secrets
          readOnly: true
      volumes:
      - name: ssl-certs-host
        hostPath:
          path: /usr/share/ca-certificates
      - name: etc-k8s-manifests
        hostPath:
          path: /etc/kubernetes/manifests
      - name: secrets
        secret:
          secretName: kube-apiserver
`)
	ControllerManagerTemplate = []byte(`apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: kube-controller-manager
  namespace: kube-system
  labels:
    k8s-app: kube-controller-manager
    version: v1.3.6_coreos.0
spec:
  template:
    metadata:
      labels:
        k8s-app: kube-controller-manager
        version: v1.3.6_coreos.0
    spec:
      containers:
      - name: kube-controller-manager
        image: quay.io/coreos/hyperkube:v1.3.6_coreos.0
        command:
        - ./hyperkube
        - controller-manager
        - --root-ca-file=/etc/kubernetes/secrets/ca.crt
        - --service-account-private-key-file=/etc/kubernetes/secrets/service-account.key
        - --leader-elect=true
        volumeMounts:
        - name: secrets
          mountPath: /etc/kubernetes/secrets
          readOnly: true
        - name: ssl-host
          mountPath: /etc/ssl/certs
          readOnly: true
      volumes:
      - name: secrets
        secret:
          secretName: kube-controller-manager
      - name: ssl-host
        hostPath:
          path: /usr/share/ca-certificates
`)
	SchedulerTemplate = []byte(`apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: kube-scheduler
  namespace: kube-system
  labels:
    k8s-app: kube-scheduler
    version: v1.3.6_coreos.0
spec:
  template:
    metadata:
      labels:
        k8s-app: kube-scheduler
        version: v1.3.6_coreos.0
    spec:
      containers:
      - name: kube-scheduler
        image: quay.io/coreos/hyperkube:v1.3.6_coreos.0
        command:
        - ./hyperkube
        - scheduler
        - --leader-elect=true
`)
	ProxyTemplate = []byte(`apiVersion: "extensions/v1beta1"
kind: DaemonSet
metadata:
  name: kube-proxy
  namespace: kube-system
  labels:
    k8s_app: kube-proxy
    version: v1.3.6_coreos.0
spec:
  template:
    metadata:
      labels:
        k8s_app: kube-proxy
        version: v1.3.6_coreos.0
    spec:
      hostNetwork: true
      containers:
      - name: kube-proxy
        image: quay.io/coreos/hyperkube:v1.3.6_coreos.0
        command:
        - /hyperkube
        - proxy
        - --master={{ index .APIServers 0 }}
        - --kubeconfig=/etc/kubernetes/kubeconfig
        - --proxy-mode=iptables
        securityContext:
          privileged: true
        volumeMounts:
        - mountPath: /etc/ssl/certs
          name: ssl-certs-host
          readOnly: true
        - name: etc-kubernetes
          mountPath: /etc/kubernetes
          readOnly: true
      volumes:
      - hostPath:
          path: /usr/share/ca-certificates
        name: ssl-certs-host
      - name: etc-kubernetes
        hostPath:
          path: /etc/kubernetes
`)
	DNSDeploymentTemplate = []byte(`apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: kube-dns-v17.1
  namespace: kube-system
  labels:
    k8s-app: kube-dns
    version: v17.1
    kubernetes.io/cluster-service: "true"
spec:
  replicas: 1
  template:
    metadata:
      labels:
        k8s-app: kube-dns
        version: v17.1
        kubernetes.io/cluster-service: "true"
    spec:
      containers:
      - name: kubedns
        image: gcr.io/google_containers/kubedns-amd64:1.5
        resources:
          limits:
            cpu: 100m
            memory: 170Mi
          requests:
            cpu: 100m
            memory: 70Mi
        livenessProbe:
          httpGet:
            path: /healthz
            port: 8080
            scheme: HTTP
          initialDelaySeconds: 60
          timeoutSeconds: 5
          successThreshold: 1
          failureThreshold: 5
        readinessProbe:
          httpGet:
            path: /readiness
            port: 8081
            scheme: HTTP
          initialDelaySeconds: 30
          timeoutSeconds: 5
        args:
          - --domain=cluster.local.
          - --dns-port=10053
        ports:
        - containerPort: 10053
          name: dns-local
          protocol: UDP
        - containerPort: 10053
          name: dns-tcp-local
          protocol: TCP
      - name: dnsmasq
        image: gcr.io/google_containers/kube-dnsmasq-amd64:1.3
        args:
        - --cache-size=1000
        - --no-resolv
        - --server=127.0.0.1#10053
        ports:
        - containerPort: 53
          name: dns
          protocol: UDP
        - containerPort: 53
          name: dns-tcp
          protocol: TCP
      - name: healthz
        image: gcr.io/google_containers/exechealthz-amd64:1.1
        resources:
          limits:
            cpu: 10m
            memory: 50Mi
          requests:
            cpu: 10m
            memory: 50Mi
        args:
        - -cmd=nslookup kubernetes.default.svc.cluster.local 127.0.0.1 >/dev/null
        - -port=8080
        - -quiet
        ports:
        - containerPort: 8080
          protocol: TCP
      dnsPolicy: Default
`)
	DNSSvcTemplate = []byte(`apiVersion: v1
kind: Service
metadata:
  name: kube-dns
  namespace: kube-system
  labels:
    k8s-app: kube-dns
    kubernetes.io/cluster-service: "true"
    kubernetes.io/name: "KubeDNS"
spec:
  selector:
    k8s-app: kube-dns
  clusterIP:  10.3.0.10
  ports:
  - name: dns
    port: 53
    protocol: UDP
  - name: dns-tcp
    port: 53
    protocol: TCP
`)
	SystemNSTemplate = []byte(`apiVersion: v1
kind: Namespace
metadata:
  name: kube-system
`)
)
<|endoftext|>


<commit_before>package openstack

import (
	"fmt"
	"sync"

	"github.com/go-kit/kit/log"
	"github.com/gophercloud/gophercloud"
	"github.com/gophercloud/gophercloud/openstack"
	"github.com/gophercloud/gophercloud/openstack/identity/v3/tokens"
	meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	core_v1 "k8s.io/client-go/kubernetes/typed/core/v1"
	"k8s.io/client-go/tools/cache"

	kubernikus_v1 "github.com/sapcc/kubernikus/pkg/apis/kubernikus/v1"
	"github.com/sapcc/kubernikus/pkg/client/openstack/admin"
	openstack_kluster "github.com/sapcc/kubernikus/pkg/client/openstack/kluster"
	openstack_project "github.com/sapcc/kubernikus/pkg/client/openstack/project"
	utillog "github.com/sapcc/kubernikus/pkg/util/log"
)

type SharedOpenstackClientFactory interface {
	KlusterClientFor(*kubernikus_v1.Kluster) (openstack_kluster.KlusterClient, error)
	ProjectClientFor(authOptions *tokens.AuthOptions) (openstack_project.ProjectClient, error)
	ProjectAdminClientFor(string) (openstack_project.ProjectClient, error)
	ProviderClientFor(authOptions *tokens.AuthOptions, logger log.Logger) (*gophercloud.ProviderClient, error)
	ProviderClientForKluster(kluster *kubernikus_v1.Kluster, logger log.Logger) (*gophercloud.ProviderClient, error)
	AdminClient() (admin.AdminClient, error)
}

type factory struct {
	klusterClients sync.Map
	projectClients sync.Map
	adminClient    admin.AdminClient

	secrets          core_v1.SecretInterface
	klusters         cache.SharedIndexInformer
	adminAuthOptions *tokens.AuthOptions
	logger           log.Logger
}

func NewSharedOpenstackClientFactory(secrets core_v1.SecretInterface, klusters cache.SharedIndexInformer, adminAuthOptions *tokens.AuthOptions, logger log.Logger) SharedOpenstackClientFactory {
	factory := &factory{
		secrets:          secrets,
		klusters:         klusters,
		adminAuthOptions: adminAuthOptions,
		logger:           logger,
	}

	if klusters != nil {
		klusters.AddEventHandler(cache.ResourceEventHandlerFuncs{
			DeleteFunc: func(obj interface{}) {
				if kluster, ok := obj.(*kubernikus_v1.Kluster); ok {
					factory.logger.Log(
						"msg", "deleting shared openstack client",
						"kluster", kluster.Name,
						"v", 5)
					factory.klusterClients.Delete(kluster.GetUID())
				}
			},
		})
	}

	return factory
}

func (f *factory) AdminClient() (admin.AdminClient, error) {
	if f.adminClient != nil {
		return f.adminClient, nil
	}

	identity, compute, network, err := f.serviceClientsFor(f.adminAuthOptions, f.logger)
	if err != nil {
		return nil, err
	}

	var client admin.AdminClient
	client = admin.NewAdminClient(network, compute, identity)
	client = admin.LoggingClient{client, f.logger}

	f.adminClient = client

	return f.adminClient, nil
}

func (f *factory) KlusterClientFor(kluster *kubernikus_v1.Kluster) (openstack_kluster.KlusterClient, error) {
	if obj, found := f.klusterClients.Load(kluster.GetUID()); found {
		return obj.(openstack_kluster.KlusterClient), nil
	}

	authOptions, err := f.authOptionsForKluster(kluster)
	if err != nil {
		return nil, err
	}

	identity, compute, network, err := f.serviceClientsFor(authOptions, f.logger)
	if err != nil {
		return nil, err
	}

	var client openstack_kluster.KlusterClient
	client = openstack_kluster.NewKlusterClient(identity, compute, network, kluster)
	client = &openstack_kluster.LoggingClient{client, log.With(f.logger, "kluster", kluster.GetName(), "project", kluster.Account())}

	f.klusterClients.Store(kluster.GetUID(), client)

	return client, nil
}

func (f *factory) ProjectClientFor(authOptions *tokens.AuthOptions) (openstack_project.ProjectClient, error) {
	if authOptions.Scope.ProjectID == "" {
		return nil, fmt.Errorf("AuthOptions must be scoped to a projectID")
	}
	return f.projectClient(authOptions.Scope.ProjectID, authOptions)
}

func (f *factory) ProjectAdminClientFor(projectID string) (openstack_project.ProjectClient, error) {
	return f.projectClient(projectID, f.adminAuthOptions)
}

func (f *factory) projectClient(projectID string, authOptions *tokens.AuthOptions) (openstack_project.ProjectClient, error) {
	if obj, found := f.projectClients.Load(projectID); found {
		return obj.(openstack_project.ProjectClient), nil
	}

	identity, compute, network, err := f.serviceClientsFor(authOptions, f.logger)
	if err != nil {
		return nil, err
	}

	var client openstack_project.ProjectClient
	client = openstack_project.NewProjectClient(projectID, network, compute, identity)
	client = &openstack_project.LoggingClient{client, log.With(f.logger, "project_id", projectID)}

	f.projectClients.Store(projectID, client)
	return client, nil
}

func (f *factory) authOptionsForKluster(kluster *kubernikus_v1.Kluster) (*tokens.AuthOptions, error) {
	secret, err := f.secrets.Get(kluster.Name, meta_v1.GetOptions{})
	if err != nil {
		return nil, fmt.Errorf("Couldn't retrieve secret %s/%s: %v", kluster.GetNamespace(), kluster.Name, err)
	}

	authOptions := &tokens.AuthOptions{
		IdentityEndpoint: string(secret.Data["openstack-auth-url"]),
		Username:         string(secret.Data["openstack-username"]),
		Password:         string(secret.Data["openstack-password"]),
		DomainName:       string(secret.Data["openstack-domain-name"]),
		AllowReauth:      true,
		Scope: tokens.Scope{
			ProjectID: string(secret.Data["openstack-project-id"]),
		},
	}

	f.logger.Log(
		"msg", "using authOptions from secret",
		"identity_endpoint", authOptions.IdentityEndpoint,
		"username", authOptions.Username,
		"domain_name", authOptions.DomainName,
		"project_id", authOptions.Scope.ProjectID,
		"v", 5)

	return authOptions, nil
}

func (f *factory) ProviderClientForKluster(kluster *kubernikus_v1.Kluster, logger log.Logger) (*gophercloud.ProviderClient, error) {
	authOptions, err := f.authOptionsForKluster(kluster)
	if err != nil {
		return nil, err
	}
	return f.ProviderClientFor(authOptions, logger)
}

func (f *factory) ProviderClientFor(authOptions *tokens.AuthOptions, logger log.Logger) (*gophercloud.ProviderClient, error) {
	provider, err := utillog.NewLoggingProviderClient(authOptions.IdentityEndpoint, logger)
	if err != nil {
		return nil, err
	}

	provider.UseTokenLock()

	err = openstack.AuthenticateV3(provider, authOptions, gophercloud.EndpointOpts{})
	if err != nil {
		return nil, err
	}

	return provider, nil
}

func (f *factory) serviceClientsFor(authOptions *tokens.AuthOptions, logger log.Logger) (*gophercloud.ServiceClient, *gophercloud.ServiceClient, *gophercloud.ServiceClient, error) {
	providerClient, err := f.ProviderClientFor(authOptions, logger)
	if err != nil {
		return nil, nil, nil, err
	}

	identity, err := openstack.NewIdentityV3(providerClient, gophercloud.EndpointOpts{})
	if err != nil {
		return nil, nil, nil, err
	}

	compute, err := openstack.NewComputeV2(providerClient, gophercloud.EndpointOpts{})
	if err != nil {
		return nil, nil, nil, err
	}

	network, err := openstack.NewNetworkV2(providerClient, gophercloud.EndpointOpts{})
	if err != nil {
		return nil, nil, nil, err
	}

	return identity, compute, network, nil
}
<commit_msg>fixes service client foo<commit_after>package openstack

import (
	"fmt"
	"sync"

	"github.com/go-kit/kit/log"
	"github.com/gophercloud/gophercloud"
	"github.com/gophercloud/gophercloud/openstack"
	"github.com/gophercloud/gophercloud/openstack/identity/v3/tokens"
	meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	core_v1 "k8s.io/client-go/kubernetes/typed/core/v1"
	"k8s.io/client-go/tools/cache"

	kubernikus_v1 "github.com/sapcc/kubernikus/pkg/apis/kubernikus/v1"
	"github.com/sapcc/kubernikus/pkg/client/openstack/admin"
	openstack_kluster "github.com/sapcc/kubernikus/pkg/client/openstack/kluster"
	openstack_project "github.com/sapcc/kubernikus/pkg/client/openstack/project"
	utillog "github.com/sapcc/kubernikus/pkg/util/log"
)

type SharedOpenstackClientFactory interface {
	KlusterClientFor(*kubernikus_v1.Kluster) (openstack_kluster.KlusterClient, error)
	ProjectClientFor(authOptions *tokens.AuthOptions) (openstack_project.ProjectClient, error)
	ProjectAdminClientFor(string) (openstack_project.ProjectClient, error)
	ProviderClientFor(authOptions *tokens.AuthOptions, logger log.Logger) (*gophercloud.ProviderClient, error)
	ProviderClientForKluster(kluster *kubernikus_v1.Kluster, logger log.Logger) (*gophercloud.ProviderClient, error)
	AdminClient() (admin.AdminClient, error)
}

type factory struct {
	klusterClients sync.Map
	projectClients sync.Map
	adminClient    admin.AdminClient

	secrets          core_v1.SecretInterface
	klusters         cache.SharedIndexInformer
	adminAuthOptions *tokens.AuthOptions
	logger           log.Logger
}

func NewSharedOpenstackClientFactory(secrets core_v1.SecretInterface, klusters cache.SharedIndexInformer, adminAuthOptions *tokens.AuthOptions, logger log.Logger) SharedOpenstackClientFactory {
	factory := &factory{
		secrets:          secrets,
		klusters:         klusters,
		adminAuthOptions: adminAuthOptions,
		logger:           logger,
	}

	if klusters != nil {
		klusters.AddEventHandler(cache.ResourceEventHandlerFuncs{
			DeleteFunc: func(obj interface{}) {
				if kluster, ok := obj.(*kubernikus_v1.Kluster); ok {
					factory.logger.Log(
						"msg", "deleting shared openstack client",
						"kluster", kluster.Name,
						"v", 5)
					factory.klusterClients.Delete(kluster.GetUID())
				}
			},
		})
	}

	return factory
}

func (f *factory) AdminClient() (admin.AdminClient, error) {
	if f.adminClient != nil {
		return f.adminClient, nil
	}

	identity, compute, network, err := f.serviceClientsFor(f.adminAuthOptions, f.logger)
	if err != nil {
		return nil, err
	}

	var client admin.AdminClient
	client = admin.NewAdminClient(network, compute, identity)
	client = admin.LoggingClient{client, f.logger}

	f.adminClient = client

	return f.adminClient, nil
}

func (f *factory) KlusterClientFor(kluster *kubernikus_v1.Kluster) (openstack_kluster.KlusterClient, error) {
	if obj, found := f.klusterClients.Load(kluster.GetUID()); found {
		return obj.(openstack_kluster.KlusterClient), nil
	}

	authOptions, err := f.authOptionsForKluster(kluster)
	if err != nil {
		return nil, err
	}

	identity, compute, network, err := f.serviceClientsFor(authOptions, f.logger)
	if err != nil {
		return nil, err
	}

	var client openstack_kluster.KlusterClient
	client = openstack_kluster.NewKlusterClient(network, compute, identity, kluster)
	client = &openstack_kluster.LoggingClient{client, log.With(f.logger, "kluster", kluster.GetName(), "project", kluster.Account())}

	f.klusterClients.Store(kluster.GetUID(), client)

	return client, nil
}

func (f *factory) ProjectClientFor(authOptions *tokens.AuthOptions) (openstack_project.ProjectClient, error) {
	if authOptions.Scope.ProjectID == "" {
		return nil, fmt.Errorf("AuthOptions must be scoped to a projectID")
	}
	return f.projectClient(authOptions.Scope.ProjectID, authOptions)
}

func (f *factory) ProjectAdminClientFor(projectID string) (openstack_project.ProjectClient, error) {
	return f.projectClient(projectID, f.adminAuthOptions)
}

func (f *factory) projectClient(projectID string, authOptions *tokens.AuthOptions) (openstack_project.ProjectClient, error) {
	if obj, found := f.projectClients.Load(projectID); found {
		return obj.(openstack_project.ProjectClient), nil
	}

	identity, compute, network, err := f.serviceClientsFor(authOptions, f.logger)
	if err != nil {
		return nil, err
	}

	var client openstack_project.ProjectClient
	client = openstack_project.NewProjectClient(projectID, network, compute, identity)
	client = &openstack_project.LoggingClient{client, log.With(f.logger, "project_id", projectID)}

	f.projectClients.Store(projectID, client)
	return client, nil
}

func (f *factory) authOptionsForKluster(kluster *kubernikus_v1.Kluster) (*tokens.AuthOptions, error) {
	secret, err := f.secrets.Get(kluster.Name, meta_v1.GetOptions{})
	if err != nil {
		return nil, fmt.Errorf("Couldn't retrieve secret %s/%s: %v", kluster.GetNamespace(), kluster.Name, err)
	}

	authOptions := &tokens.AuthOptions{
		IdentityEndpoint: string(secret.Data["openstack-auth-url"]),
		Username:         string(secret.Data["openstack-username"]),
		Password:         string(secret.Data["openstack-password"]),
		DomainName:       string(secret.Data["openstack-domain-name"]),
		AllowReauth:      true,
		Scope: tokens.Scope{
			ProjectID: string(secret.Data["openstack-project-id"]),
		},
	}

	f.logger.Log(
		"msg", "using authOptions from secret",
		"identity_endpoint", authOptions.IdentityEndpoint,
		"username", authOptions.Username,
		"domain_name", authOptions.DomainName,
		"project_id", authOptions.Scope.ProjectID,
		"v", 5)

	return authOptions, nil
}

func (f *factory) ProviderClientForKluster(kluster *kubernikus_v1.Kluster, logger log.Logger) (*gophercloud.ProviderClient, error) {
	authOptions, err := f.authOptionsForKluster(kluster)
	if err != nil {
		return nil, err
	}
	return f.ProviderClientFor(authOptions, logger)
}

func (f *factory) ProviderClientFor(authOptions *tokens.AuthOptions, logger log.Logger) (*gophercloud.ProviderClient, error) {
	provider, err := utillog.NewLoggingProviderClient(authOptions.IdentityEndpoint, logger)
	if err != nil {
		return nil, err
	}

	provider.UseTokenLock()

	err = openstack.AuthenticateV3(provider, authOptions, gophercloud.EndpointOpts{})
	if err != nil {
		return nil, err
	}

	return provider, nil
}

func (f *factory) serviceClientsFor(authOptions *tokens.AuthOptions, logger log.Logger) (*gophercloud.ServiceClient, *gophercloud.ServiceClient, *gophercloud.ServiceClient, error) {
	providerClient, err := f.ProviderClientFor(authOptions, logger)
	if err != nil {
		return nil, nil, nil, err
	}

	identity, err := openstack.NewIdentityV3(providerClient, gophercloud.EndpointOpts{})
	if err != nil {
		return nil, nil, nil, err
	}

	compute, err := openstack.NewComputeV2(providerClient, gophercloud.EndpointOpts{})
	if err != nil {
		return nil, nil, nil, err
	}

	network, err := openstack.NewNetworkV2(providerClient, gophercloud.EndpointOpts{})
	if err != nil {
		return nil, nil, nil, err
	}

	return identity, compute, network, nil
}
<|endoftext|>


<commit_before>package integration

import (
	"io/ioutil"
	"os"
	"path/filepath"
	"runtime"
	"testing"

	"github.com/pkg/errors"

	"github.com/havoc-io/mutagen/pkg/agent"
	"github.com/havoc-io/mutagen/pkg/daemon"
	"github.com/havoc-io/mutagen/pkg/filesystem"
	"github.com/havoc-io/mutagen/pkg/session"
	"github.com/havoc-io/mutagen/pkg/url"
)

// daemonLock is the daemon lock manager.
var daemonLock *daemon.Lock

// sessionManager is the session manager.
var sessionManager *session.Manager

func init() {
	// Copy the agent bundle for testing.
	// HACK: We're relying on the fact that Go will clean this up when it
	// removes the testing temporary directory.
	if err := agent.CopyBundleForTesting(); err != nil {
		panic(errors.Wrap(err, "unable to copy agent bundle for testing"))
	}

	// Acquire the daemon lock.
	if l, err := daemon.AcquireLock(); err != nil {
		panic(errors.Wrap(err, "unable to acquire daemon lock"))
	} else {
		daemonLock = l
	}

	// Create a session manager.
	if m, err := session.NewManager(); err != nil {
		panic(errors.Wrap(err, "unable to create session manager"))
	} else {
		sessionManager = m
	}

	// Perform housekeeping.
	agent.Housekeep()
	session.HousekeepCaches()
	session.HousekeepStaging()
}

func waitForSuccessfulSynchronizationCycle(sessionId string, allowConflicts, allowProblems bool) error {
	// Create a session specification.
	specification := []string{sessionId}

	// Perform waiting.
	var previousStateIndex uint64
	var states []*session.State
	var err error
	for {
		previousStateIndex, states, err = sessionManager.List(previousStateIndex, specification)
		if err != nil {
			return errors.Wrap(err, "unable to list session states")
		} else if len(states) != 1 {
			return errors.New("invalid number of session states returned")
		} else if states[0].SuccessfulSynchronizationCycles > 0 {
			if !allowProblems && (len(states[0].AlphaProblems) > 0 || len(states[0].BetaProblems) > 0) {
				return errors.New("problems detected (and disallowed)")
			} else if !allowConflicts && len(states[0].Conflicts) > 0 {
				return errors.New("conflicts detected (and disallowed)")
			}
			return nil
		}
	}
}

func testSessionLifecycle(alpha, beta *url.URL, configuration *session.Configuration, allowConflicts, allowProblems bool) error {
	// Create a session.
	sessionId, err := sessionManager.Create(alpha, beta, configuration, "")
	if err != nil {
		return errors.Wrap(err, "unable to create session")
	}

	// Create a session specification.
	specification := []string{sessionId}

	// Wait for the session to have at least one successful synchronization
	// cycle.
	// TODO: Should we add a timeout on this?
	if err := waitForSuccessfulSynchronizationCycle(sessionId, allowConflicts, allowProblems); err != nil {
		return errors.Wrap(err, "unable to wait for successful synchronization")
	}

	// TODO: Add hook for verifying file contents.

	// TODO: Add hook for verifying presence/absence of particular
	// conflicts/problems and remove that monitoring from
	// waitForSuccessfulSynchronizationCycle (maybe have it pass back the
	// relevant state).

	// Pause the session.
	if err := sessionManager.Pause(specification); err != nil {
		return errors.Wrap(err, "unable to pause session")
	}

	// Resume the session.
	if err := sessionManager.Resume(specification, ""); err != nil {
		return errors.Wrap(err, "unable to resume session")
	}

	// Wait for the session to have at least one additional synchronization
	// cycle.
	// TODO: Should we add a timeout on this?
	if err := waitForSuccessfulSynchronizationCycle(sessionId, allowConflicts, allowProblems); err != nil {
		return errors.Wrap(err, "unable to wait for additional synchronization")
	}

	// Attempt an additional resume (this should be a no-op).
	if err := sessionManager.Resume(specification, ""); err != nil {
		return errors.Wrap(err, "unable to perform additional resume")
	}

	// Terminate the session.
	if err := sessionManager.Terminate(specification); err != nil {
		return errors.Wrap(err, "unable to terminate session")
	}

	// TODO: Verify that cleanup took place.

	// Success.
	return nil
}

func TestSessionBothRootsNil(t *testing.T) {
	// If end-to-end tests haven't been enabled, then skip this test.
	if os.Getenv("MUTAGEN_TEST_END_TO_END") != "true" {
		t.Skip()
	}

	// Create a temporary directory and defer its cleanup.
	directory, err := ioutil.TempDir("", "mutagen_end_to_end")
	if err != nil {
		t.Fatal("unable to create temporary directory:", err)
	}
	defer os.RemoveAll(directory)

	// Calculate alpha and beta paths.
	alphaRoot := filepath.Join(directory, "alpha")
	betaRoot := filepath.Join(directory, "beta")

	// Compute alpha and beta URLs.
	alphaURL := &url.URL{Path: alphaRoot}
	betaURL := &url.URL{Path: betaRoot}

	// Compute configuration.
	// HACK: The notify package has a race condition on Windows that the race
	// detector catches, so force polling there for now during tests.
	configuration := &session.Configuration{}
	if runtime.GOOS == "windows" {
		configuration.WatchMode = filesystem.WatchMode_WatchForcePoll
	}

	// Test the session lifecycle.
	if err := testSessionLifecycle(alphaURL, betaURL, configuration, false, false); err != nil {
		t.Fatal("session lifecycle test failed:", err)
	}
}

func TestSessionGOROOTSrcToBeta(t *testing.T) {
	// If end-to-end tests haven't been enabled, then skip this test.
	if os.Getenv("MUTAGEN_TEST_END_TO_END") != "true" {
		t.Skip()
	}

	// Create a temporary directory and defer its cleanup.
	directory, err := ioutil.TempDir("", "mutagen_end_to_end")
	if err != nil {
		t.Fatal("unable to create temporary directory:", err)
	}
	defer os.RemoveAll(directory)

	// Calculate alpha and beta paths.
	alphaRoot := filepath.Join(runtime.GOROOT(), "src")
	betaRoot := filepath.Join(directory, "beta")

	// Compute alpha and beta URLs.
	alphaURL := &url.URL{Path: alphaRoot}
	betaURL := &url.URL{Path: betaRoot}

	// Compute configuration.
	// HACK: The notify package has a race condition on Windows that the race
	// detector catches, so force polling there for now during tests.
	configuration := &session.Configuration{}
	if runtime.GOOS == "windows" {
		configuration.WatchMode = filesystem.WatchMode_WatchForcePoll
	}

	// Test the session lifecycle.
	if err := testSessionLifecycle(alphaURL, betaURL, configuration, false, false); err != nil {
		t.Fatal("session lifecycle test failed:", err)
	}
}

func TestSessionGOROOTSrcToAlpha(t *testing.T) {
	// If end-to-end tests haven't been enabled, then skip this test.
	if os.Getenv("MUTAGEN_TEST_END_TO_END") != "true" {
		t.Skip()
	}

	// Create a temporary directory and defer its cleanup.
	directory, err := ioutil.TempDir("", "mutagen_end_to_end")
	if err != nil {
		t.Fatal("unable to create temporary directory:", err)
	}
	defer os.RemoveAll(directory)

	// Calculate alpha and beta paths.
	alphaRoot := filepath.Join(directory, "alpha")
	betaRoot := filepath.Join(runtime.GOROOT(), "src")

	// Compute alpha and beta URLs.
	alphaURL := &url.URL{Path: alphaRoot}
	betaURL := &url.URL{Path: betaRoot}

	// Compute configuration.
	// HACK: The notify package has a race condition on Windows that the race
	// detector catches, so force polling there for now during tests.
	configuration := &session.Configuration{}
	if runtime.GOOS == "windows" {
		configuration.WatchMode = filesystem.WatchMode_WatchForcePoll
	}

	// Test the session lifecycle.
	if err := testSessionLifecycle(alphaURL, betaURL, configuration, false, false); err != nil {
		t.Fatal("session lifecycle test failed:", err)
	}
}

func TestSessionGOROOTSrcToBetaOverSSH(t *testing.T) {
	// If end-to-end tests haven't been enabled, then skip this test.
	if os.Getenv("MUTAGEN_TEST_END_TO_END") != "true" {
		t.Skip()
	}

	// If localhost SSH support isn't available, then skip this test.
	if os.Getenv("MUTAGEN_TEST_SSH") != "true" {
		t.Skip()
	}

	// Create a temporary directory and defer its cleanup.
	directory, err := ioutil.TempDir("", "mutagen_end_to_end")
	if err != nil {
		t.Fatal("unable to create temporary directory:", err)
	}
	defer os.RemoveAll(directory)

	// Calculate alpha and beta paths.
	alphaRoot := filepath.Join(runtime.GOROOT(), "src")
	betaRoot := filepath.Join(directory, "beta")

	// Compute alpha and beta URLs.
	alphaURL := &url.URL{Path: alphaRoot}
	betaURL := &url.URL{
		Protocol: url.Protocol_SSH,
		Hostname: "localhost",
		Path:     betaRoot,
	}

	// Compute configuration.
	// HACK: The notify package has a race condition on Windows that the race
	// detector catches, so force polling there for now during tests.
	configuration := &session.Configuration{}
	if runtime.GOOS == "windows" {
		configuration.WatchMode = filesystem.WatchMode_WatchForcePoll
	}

	// Test the session lifecycle.
	if err := testSessionLifecycle(alphaURL, betaURL, configuration, false, false); err != nil {
		t.Fatal("session lifecycle test failed:", err)
	}
}

func TestSessionGOROOTSrcToAlphaOverSSH(t *testing.T) {
	// If end-to-end tests haven't been enabled, then skip this test.
	if os.Getenv("MUTAGEN_TEST_END_TO_END") != "true" {
		t.Skip()
	}

	// If localhost SSH support isn't available, then skip this test.
	if os.Getenv("MUTAGEN_TEST_SSH") != "true" {
		t.Skip()
	}

	// Create a temporary directory and defer its cleanup.
	directory, err := ioutil.TempDir("", "mutagen_end_to_end")
	if err != nil {
		t.Fatal("unable to create temporary directory:", err)
	}
	defer os.RemoveAll(directory)

	// Calculate alpha and beta paths.
	alphaRoot := filepath.Join(directory, "alpha")
	betaRoot := filepath.Join(runtime.GOROOT(), "src")

	// Compute alpha and beta URLs.
	alphaURL := &url.URL{
		Protocol: url.Protocol_SSH,
		Hostname: "localhost",
		Path:     alphaRoot,
	}
	betaURL := &url.URL{Path: betaRoot}

	// Compute configuration.
	// HACK: The notify package has a race condition on Windows that the race
	// detector catches, so force polling there for now during tests.
	configuration := &session.Configuration{}
	if runtime.GOOS == "windows" {
		configuration.WatchMode = filesystem.WatchMode_WatchForcePoll
	}

	// Test the session lifecycle.
	if err := testSessionLifecycle(alphaURL, betaURL, configuration, false, false); err != nil {
		t.Fatal("session lifecycle test failed:", err)
	}
}

func TestSessionGOROOTSrcToBetaOverSSHInMemory(t *testing.T) {
	// If end-to-end tests haven't been enabled, then skip this test.
	if os.Getenv("MUTAGEN_TEST_END_TO_END") != "true" {
		t.Skip()
	}

	// Create a temporary directory and defer its cleanup.
	directory, err := ioutil.TempDir("", "mutagen_end_to_end")
	if err != nil {
		t.Fatal("unable to create temporary directory:", err)
	}
	defer os.RemoveAll(directory)

	// Calculate alpha and beta paths.
	alphaRoot := filepath.Join(runtime.GOROOT(), "src")
	betaRoot := filepath.Join(directory, "beta")

	// Compute alpha and beta URLs. We use a special (and invalid) SSH URL (with
	// an empty hostname) to indicate an in-memory connection.
	alphaURL := &url.URL{Path: alphaRoot}
	betaURL := &url.URL{
		Protocol: url.Protocol_SSH,
		Path:     betaRoot,
	}

	// Compute configuration.
	// HACK: The notify package has a race condition on Windows that the race
	// detector catches, so force polling there for now during tests.
	configuration := &session.Configuration{}
	if runtime.GOOS == "windows" {
		configuration.WatchMode = filesystem.WatchMode_WatchForcePoll
	}

	// Test the session lifecycle.
	if err := testSessionLifecycle(alphaURL, betaURL, configuration, false, false); err != nil {
		t.Fatal("session lifecycle test failed:", err)
	}
}

func TestSessionGOROOTSrcFromAlphaOverSSHInMemory(t *testing.T) {
	// If end-to-end tests haven't been enabled, then skip this test.
	if os.Getenv("MUTAGEN_TEST_END_TO_END") != "true" {
		t.Skip()
	}

	// Create a temporary directory and defer its cleanup.
	directory, err := ioutil.TempDir("", "mutagen_end_to_end")
	if err != nil {
		t.Fatal("unable to create temporary directory:", err)
	}
	defer os.RemoveAll(directory)

	// Calculate alpha and beta paths.
	alphaRoot := filepath.Join(runtime.GOROOT(), "src")
	betaRoot := filepath.Join(directory, "beta")

	// Compute alpha and beta URLs. We use a special (and invalid) SSH URL (with
	// an empty hostname) to indicate an in-memory connection.
	alphaURL := &url.URL{
		Protocol: url.Protocol_SSH,
		Path:     alphaRoot,
	}
	betaURL := &url.URL{Path: betaRoot}

	// Compute configuration.
	// HACK: The notify package has a race condition on Windows that the race
	// detector catches, so force polling there for now during tests.
	configuration := &session.Configuration{}
	if runtime.GOOS == "windows" {
		configuration.WatchMode = filesystem.WatchMode_WatchForcePoll
	}

	// Test the session lifecycle.
	if err := testSessionLifecycle(alphaURL, betaURL, configuration, false, false); err != nil {
		t.Fatal("session lifecycle test failed:", err)
	}
}

// TODO: Implement end-to-end tests that work via the gRPC service endpoints.
// This will obviously require setting up the whole service architecture. Maybe
// we can modify the session service to take a session manager as an argument
// (instead of creating it internally) so that we don't need to tear down the
// other manager before trying the gRPC version? Manager is already safe for
// concurrent usage.
<commit_msg>Disabled all but one notify package test.<commit_after>package integration

import (
	"io/ioutil"
	"os"
	"path/filepath"
	"runtime"
	"testing"

	"github.com/pkg/errors"

	"github.com/havoc-io/mutagen/pkg/agent"
	"github.com/havoc-io/mutagen/pkg/daemon"
	"github.com/havoc-io/mutagen/pkg/filesystem"
	"github.com/havoc-io/mutagen/pkg/session"
	"github.com/havoc-io/mutagen/pkg/url"
)

// daemonLock is the daemon lock manager.
var daemonLock *daemon.Lock

// sessionManager is the session manager.
var sessionManager *session.Manager

func init() {
	// Copy the agent bundle for testing.
	// HACK: We're relying on the fact that Go will clean this up when it
	// removes the testing temporary directory.
	if err := agent.CopyBundleForTesting(); err != nil {
		panic(errors.Wrap(err, "unable to copy agent bundle for testing"))
	}

	// Acquire the daemon lock.
	if l, err := daemon.AcquireLock(); err != nil {
		panic(errors.Wrap(err, "unable to acquire daemon lock"))
	} else {
		daemonLock = l
	}

	// Create a session manager.
	if m, err := session.NewManager(); err != nil {
		panic(errors.Wrap(err, "unable to create session manager"))
	} else {
		sessionManager = m
	}

	// Perform housekeeping.
	agent.Housekeep()
	session.HousekeepCaches()
	session.HousekeepStaging()
}

func waitForSuccessfulSynchronizationCycle(sessionId string, allowConflicts, allowProblems bool) error {
	// Create a session specification.
	specification := []string{sessionId}

	// Perform waiting.
	var previousStateIndex uint64
	var states []*session.State
	var err error
	for {
		previousStateIndex, states, err = sessionManager.List(previousStateIndex, specification)
		if err != nil {
			return errors.Wrap(err, "unable to list session states")
		} else if len(states) != 1 {
			return errors.New("invalid number of session states returned")
		} else if states[0].SuccessfulSynchronizationCycles > 0 {
			if !allowProblems && (len(states[0].AlphaProblems) > 0 || len(states[0].BetaProblems) > 0) {
				return errors.New("problems detected (and disallowed)")
			} else if !allowConflicts && len(states[0].Conflicts) > 0 {
				return errors.New("conflicts detected (and disallowed)")
			}
			return nil
		}
	}
}

func testSessionLifecycle(alpha, beta *url.URL, configuration *session.Configuration, allowConflicts, allowProblems bool) error {
	// Create a session.
	sessionId, err := sessionManager.Create(alpha, beta, configuration, "")
	if err != nil {
		return errors.Wrap(err, "unable to create session")
	}

	// Create a session specification.
	specification := []string{sessionId}

	// Wait for the session to have at least one successful synchronization
	// cycle.
	// TODO: Should we add a timeout on this?
	if err := waitForSuccessfulSynchronizationCycle(sessionId, allowConflicts, allowProblems); err != nil {
		return errors.Wrap(err, "unable to wait for successful synchronization")
	}

	// TODO: Add hook for verifying file contents.

	// TODO: Add hook for verifying presence/absence of particular
	// conflicts/problems and remove that monitoring from
	// waitForSuccessfulSynchronizationCycle (maybe have it pass back the
	// relevant state).

	// Pause the session.
	if err := sessionManager.Pause(specification); err != nil {
		return errors.Wrap(err, "unable to pause session")
	}

	// Resume the session.
	if err := sessionManager.Resume(specification, ""); err != nil {
		return errors.Wrap(err, "unable to resume session")
	}

	// Wait for the session to have at least one additional synchronization
	// cycle.
	// TODO: Should we add a timeout on this?
	if err := waitForSuccessfulSynchronizationCycle(sessionId, allowConflicts, allowProblems); err != nil {
		return errors.Wrap(err, "unable to wait for additional synchronization")
	}

	// Attempt an additional resume (this should be a no-op).
	if err := sessionManager.Resume(specification, ""); err != nil {
		return errors.Wrap(err, "unable to perform additional resume")
	}

	// Terminate the session.
	if err := sessionManager.Terminate(specification); err != nil {
		return errors.Wrap(err, "unable to terminate session")
	}

	// TODO: Verify that cleanup took place.

	// Success.
	return nil
}

func TestSessionBothRootsNil(t *testing.T) {
	// If end-to-end tests haven't been enabled, then skip this test.
	if os.Getenv("MUTAGEN_TEST_END_TO_END") != "true" {
		t.Skip()
	}

	// Create a temporary directory and defer its cleanup.
	directory, err := ioutil.TempDir("", "mutagen_end_to_end")
	if err != nil {
		t.Fatal("unable to create temporary directory:", err)
	}
	defer os.RemoveAll(directory)

	// Calculate alpha and beta paths.
	alphaRoot := filepath.Join(directory, "alpha")
	betaRoot := filepath.Join(directory, "beta")

	// Compute alpha and beta URLs.
	alphaURL := &url.URL{Path: alphaRoot}
	betaURL := &url.URL{Path: betaRoot}

	// Compute configuration.
	// HACK: The notify package has a race condition on Windows that the race
	// detector catches, so force polling there for now during tests. Force
	// polling on macOS as well since notify seems flaky in tests there as well.
	configuration := &session.Configuration{}
	if runtime.GOOS == "windows" || runtime.GOOS == "darwin" {
		configuration.WatchMode = filesystem.WatchMode_WatchForcePoll
	}

	// Test the session lifecycle.
	if err := testSessionLifecycle(alphaURL, betaURL, configuration, false, false); err != nil {
		t.Fatal("session lifecycle test failed:", err)
	}
}

func TestSessionGOROOTSrcToBeta(t *testing.T) {
	// If end-to-end tests haven't been enabled, then skip this test.
	if os.Getenv("MUTAGEN_TEST_END_TO_END") != "true" {
		t.Skip()
	}

	// Create a temporary directory and defer its cleanup.
	directory, err := ioutil.TempDir("", "mutagen_end_to_end")
	if err != nil {
		t.Fatal("unable to create temporary directory:", err)
	}
	defer os.RemoveAll(directory)

	// Calculate alpha and beta paths.
	alphaRoot := filepath.Join(runtime.GOROOT(), "src")
	betaRoot := filepath.Join(directory, "beta")

	// Compute alpha and beta URLs.
	alphaURL := &url.URL{Path: alphaRoot}
	betaURL := &url.URL{Path: betaRoot}

	// Compute configuration.
	// HACK: The notify package has a race condition on Windows that the race
	// detector catches, so force polling there for now during tests. Force
	// polling on macOS as well since notify seems flaky in tests there as well.
	configuration := &session.Configuration{}
	if runtime.GOOS == "windows" || runtime.GOOS == "darwin" {
		configuration.WatchMode = filesystem.WatchMode_WatchForcePoll
	}

	// Test the session lifecycle.
	if err := testSessionLifecycle(alphaURL, betaURL, configuration, false, false); err != nil {
		t.Fatal("session lifecycle test failed:", err)
	}
}

func TestSessionGOROOTSrcToAlpha(t *testing.T) {
	// If end-to-end tests haven't been enabled, then skip this test.
	if os.Getenv("MUTAGEN_TEST_END_TO_END") != "true" {
		t.Skip()
	}

	// Create a temporary directory and defer its cleanup.
	directory, err := ioutil.TempDir("", "mutagen_end_to_end")
	if err != nil {
		t.Fatal("unable to create temporary directory:", err)
	}
	defer os.RemoveAll(directory)

	// Calculate alpha and beta paths.
	alphaRoot := filepath.Join(directory, "alpha")
	betaRoot := filepath.Join(runtime.GOROOT(), "src")

	// Compute alpha and beta URLs.
	alphaURL := &url.URL{Path: alphaRoot}
	betaURL := &url.URL{Path: betaRoot}

	// Compute configuration.
	// HACK: The notify package has a race condition on Windows that the race
	// detector catches, so force polling there for now during tests. Force
	// polling on macOS as well since notify seems flaky in tests there as well.
	configuration := &session.Configuration{}
	if runtime.GOOS == "windows" || runtime.GOOS == "darwin" {
		configuration.WatchMode = filesystem.WatchMode_WatchForcePoll
	}

	// Test the session lifecycle.
	if err := testSessionLifecycle(alphaURL, betaURL, configuration, false, false); err != nil {
		t.Fatal("session lifecycle test failed:", err)
	}
}

func TestSessionGOROOTSrcToBetaOverSSH(t *testing.T) {
	// If end-to-end tests haven't been enabled, then skip this test.
	if os.Getenv("MUTAGEN_TEST_END_TO_END") != "true" {
		t.Skip()
	}

	// If localhost SSH support isn't available, then skip this test.
	if os.Getenv("MUTAGEN_TEST_SSH") != "true" {
		t.Skip()
	}

	// Create a temporary directory and defer its cleanup.
	directory, err := ioutil.TempDir("", "mutagen_end_to_end")
	if err != nil {
		t.Fatal("unable to create temporary directory:", err)
	}
	defer os.RemoveAll(directory)

	// Calculate alpha and beta paths.
	alphaRoot := filepath.Join(runtime.GOROOT(), "src")
	betaRoot := filepath.Join(directory, "beta")

	// Compute alpha and beta URLs.
	alphaURL := &url.URL{Path: alphaRoot}
	betaURL := &url.URL{
		Protocol: url.Protocol_SSH,
		Hostname: "localhost",
		Path:     betaRoot,
	}

	// Compute configuration.
	// HACK: The notify package has a race condition on Windows that the race
	// detector catches, so force polling there for now during tests. Force
	// polling on macOS as well since notify seems flaky in tests there as well.
	configuration := &session.Configuration{}
	if runtime.GOOS == "windows" || runtime.GOOS == "darwin" {
		configuration.WatchMode = filesystem.WatchMode_WatchForcePoll
	}

	// Test the session lifecycle.
	if err := testSessionLifecycle(alphaURL, betaURL, configuration, false, false); err != nil {
		t.Fatal("session lifecycle test failed:", err)
	}
}

func TestSessionGOROOTSrcToAlphaOverSSH(t *testing.T) {
	// If end-to-end tests haven't been enabled, then skip this test.
	if os.Getenv("MUTAGEN_TEST_END_TO_END") != "true" {
		t.Skip()
	}

	// If localhost SSH support isn't available, then skip this test.
	if os.Getenv("MUTAGEN_TEST_SSH") != "true" {
		t.Skip()
	}

	// Create a temporary directory and defer its cleanup.
	directory, err := ioutil.TempDir("", "mutagen_end_to_end")
	if err != nil {
		t.Fatal("unable to create temporary directory:", err)
	}
	defer os.RemoveAll(directory)

	// Calculate alpha and beta paths.
	alphaRoot := filepath.Join(directory, "alpha")
	betaRoot := filepath.Join(runtime.GOROOT(), "src")

	// Compute alpha and beta URLs.
	alphaURL := &url.URL{
		Protocol: url.Protocol_SSH,
		Hostname: "localhost",
		Path:     alphaRoot,
	}
	betaURL := &url.URL{Path: betaRoot}

	// Compute configuration.
	// HACK: The notify package has a race condition on Windows that the race
	// detector catches, so force polling there for now during tests. Force
	// polling on macOS as well since notify seems flaky in tests there as well.
	configuration := &session.Configuration{}
	if runtime.GOOS == "windows" || runtime.GOOS == "darwin" {
		configuration.WatchMode = filesystem.WatchMode_WatchForcePoll
	}

	// Test the session lifecycle.
	if err := testSessionLifecycle(alphaURL, betaURL, configuration, false, false); err != nil {
		t.Fatal("session lifecycle test failed:", err)
	}
}

func TestSessionGOROOTSrcToBetaOverSSHInMemory(t *testing.T) {
	// If end-to-end tests haven't been enabled, then skip this test.
	if os.Getenv("MUTAGEN_TEST_END_TO_END") != "true" {
		t.Skip()
	}

	// Create a temporary directory and defer its cleanup.
	directory, err := ioutil.TempDir("", "mutagen_end_to_end")
	if err != nil {
		t.Fatal("unable to create temporary directory:", err)
	}
	defer os.RemoveAll(directory)

	// Calculate alpha and beta paths.
	alphaRoot := filepath.Join(runtime.GOROOT(), "src")
	betaRoot := filepath.Join(directory, "beta")

	// Compute alpha and beta URLs. We use a special (and invalid) SSH URL (with
	// an empty hostname) to indicate an in-memory connection.
	alphaURL := &url.URL{Path: alphaRoot}
	betaURL := &url.URL{
		Protocol: url.Protocol_SSH,
		Path:     betaRoot,
	}

	// Compute configuration.
	// HACK: The notify package has a race condition on Windows that the race
	// detector catches, so force polling there for now during tests. Force
	// polling on macOS as well since notify seems flaky in tests there as well.
	configuration := &session.Configuration{}
	if runtime.GOOS == "windows" || runtime.GOOS == "darwin" {
		configuration.WatchMode = filesystem.WatchMode_WatchForcePoll
	}

	// Test the session lifecycle.
	if err := testSessionLifecycle(alphaURL, betaURL, configuration, false, false); err != nil {
		t.Fatal("session lifecycle test failed:", err)
	}
}

func TestSessionGOROOTSrcFromAlphaOverSSHInMemory(t *testing.T) {
	// If end-to-end tests haven't been enabled, then skip this test.
	if os.Getenv("MUTAGEN_TEST_END_TO_END") != "true" {
		t.Skip()
	}

	// Create a temporary directory and defer its cleanup.
	directory, err := ioutil.TempDir("", "mutagen_end_to_end")
	if err != nil {
		t.Fatal("unable to create temporary directory:", err)
	}
	defer os.RemoveAll(directory)

	// Calculate alpha and beta paths.
	alphaRoot := filepath.Join(runtime.GOROOT(), "src")
	betaRoot := filepath.Join(directory, "beta")

	// Compute alpha and beta URLs. We use a special (and invalid) SSH URL (with
	// an empty hostname) to indicate an in-memory connection.
	alphaURL := &url.URL{
		Protocol: url.Protocol_SSH,
		Path:     alphaRoot,
	}
	betaURL := &url.URL{Path: betaRoot}

	// Compute configuration.
	// HACK: The notify package has a race condition on Windows that the race
	// detector catches, so force polling there for now during tests. Force
	// polling on macOS as well since notify seems flaky in tests there as well.
	configuration := &session.Configuration{}
	if runtime.GOOS == "windows" || runtime.GOOS == "darwin" {
		configuration.WatchMode = filesystem.WatchMode_WatchForcePoll
	}

	// Test the session lifecycle.
	if err := testSessionLifecycle(alphaURL, betaURL, configuration, false, false); err != nil {
		t.Fatal("session lifecycle test failed:", err)
	}
}

// TODO: Implement end-to-end tests that work via the gRPC service endpoints.
// This will obviously require setting up the whole service architecture. Maybe
// we can modify the session service to take a session manager as an argument
// (instead of creating it internally) so that we don't need to tear down the
// other manager before trying the gRPC version? Manager is already safe for
// concurrent usage.
<|endoftext|>