<commit_before>// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

package router

import (
	"sync"
	"time"

	"github.com/ava-labs/gecko/ids"
	"github.com/ava-labs/gecko/snow/networking/handler"
	"github.com/ava-labs/gecko/snow/networking/timeout"
	"github.com/ava-labs/gecko/utils/logging"
	"github.com/ava-labs/gecko/utils/timer"
)

// ChainRouter routes incoming messages from the validator network
// to the consensus engines that the messages are intended for.
// Note that consensus engines are uniquely identified by the ID of the chain
// that they are working on.
type ChainRouter struct {
	log      logging.Logger
	lock     sync.RWMutex
	chains   map[[32]byte]*handler.Handler
	timeouts *timeout.Manager
	gossiper *timer.Repeater
}

// Initialize the router
// When this router receives an incoming message, it cancels the timeout in [timeouts]
// associated with the request that caused the incoming message, if applicable
func (sr *ChainRouter) Initialize(log logging.Logger, timeouts *timeout.Manager, gossipFrequency time.Duration) {
	sr.log = log
	sr.chains = make(map[[32]byte]*handler.Handler)
	sr.timeouts = timeouts
	sr.gossiper = timer.NewRepeater(sr.Gossip, gossipFrequency)

	go log.RecoverAndPanic(sr.gossiper.Dispatch)
}

// AddChain registers the specified chain so that incoming
// messages can be routed to it
func (sr *ChainRouter) AddChain(chain *handler.Handler) {
	sr.lock.Lock()
	defer sr.lock.Unlock()

	chainID := chain.Context().ChainID
	sr.log.Debug("Adding %s to the routing table", chainID)
	sr.chains[chainID.Key()] = chain
}

// RemoveChain removes the specified chain so that incoming
// messages can't be routed to it
func (sr *ChainRouter) RemoveChain(chainID ids.ID) {
	sr.lock.Lock()
	defer sr.lock.Unlock()

	if chain, exists := sr.chains[chainID.Key()]; exists {
		chain.Shutdown()
		delete(sr.chains, chainID.Key())
	} else {
		sr.log.Warn("Message referenced a chain, %s, this validator is not validating", chainID)
	}
}

// GetAcceptedFrontier routes an incoming GetAcceptedFrontier request from the
// validator with ID [validatorID]  to the consensus engine working on the
// chain with ID [chainID]
func (sr *ChainRouter) GetAcceptedFrontier(validatorID ids.ShortID, chainID ids.ID, requestID uint32) {
	sr.lock.RLock()
	defer sr.lock.RUnlock()

	if chain, exists := sr.chains[chainID.Key()]; exists {
		chain.GetAcceptedFrontier(validatorID, requestID)
	} else {
		sr.log.Warn("Message referenced a chain, %s, this validator is not validating", chainID)
	}
}

// AcceptedFrontier routes an incoming AcceptedFrontier request from the
// validator with ID [validatorID]  to the consensus engine working on the
// chain with ID [chainID]
func (sr *ChainRouter) AcceptedFrontier(validatorID ids.ShortID, chainID ids.ID, requestID uint32, containerIDs ids.Set) {
	sr.lock.RLock()
	defer sr.lock.RUnlock()

	sr.timeouts.Cancel(validatorID, chainID, requestID)
	if chain, exists := sr.chains[chainID.Key()]; exists {
		chain.AcceptedFrontier(validatorID, requestID, containerIDs)
	} else {
		sr.log.Warn("Message referenced a chain, %s, this validator is not validating", chainID)
	}
}

// GetAcceptedFrontierFailed routes an incoming GetAcceptedFrontierFailed
// request from the validator with ID [validatorID]  to the consensus engine
// working on the chain with ID [chainID]
func (sr *ChainRouter) GetAcceptedFrontierFailed(validatorID ids.ShortID, chainID ids.ID, requestID uint32) {
	sr.lock.RLock()
	defer sr.lock.RUnlock()

	sr.timeouts.Cancel(validatorID, chainID, requestID)
	if chain, exists := sr.chains[chainID.Key()]; exists {
		chain.GetAcceptedFrontierFailed(validatorID, requestID)
	} else {
		sr.log.Warn("Message referenced a chain, %s, this validator is not validating", chainID)
	}
}

// GetAccepted routes an incoming GetAccepted request from the
// validator with ID [validatorID]  to the consensus engine working on the
// chain with ID [chainID]
func (sr *ChainRouter) GetAccepted(validatorID ids.ShortID, chainID ids.ID, requestID uint32, containerIDs ids.Set) {
	sr.lock.RLock()
	defer sr.lock.RUnlock()

	if chain, exists := sr.chains[chainID.Key()]; exists {
		chain.GetAccepted(validatorID, requestID, containerIDs)
	} else {
		sr.log.Warn("Message referenced a chain, %s, this validator is not validating", chainID)
	}
}

// Accepted routes an incoming Accepted request from the validator with ID
// [validatorID]  to the consensus engine working on the chain with ID
// [chainID]
func (sr *ChainRouter) Accepted(validatorID ids.ShortID, chainID ids.ID, requestID uint32, containerIDs ids.Set) {
	sr.lock.RLock()
	defer sr.lock.RUnlock()

	sr.timeouts.Cancel(validatorID, chainID, requestID)
	if chain, exists := sr.chains[chainID.Key()]; exists {
		chain.Accepted(validatorID, requestID, containerIDs)
	} else {
		sr.log.Warn("Message referenced a chain, %s, this validator is not validating", chainID)
	}
}

// GetAcceptedFailed routes an incoming GetAcceptedFailed request from the
// validator with ID [validatorID]  to the consensus engine working on the
// chain with ID [chainID]
func (sr *ChainRouter) GetAcceptedFailed(validatorID ids.ShortID, chainID ids.ID, requestID uint32) {
	sr.lock.RLock()
	defer sr.lock.RUnlock()

	sr.timeouts.Cancel(validatorID, chainID, requestID)
	if chain, exists := sr.chains[chainID.Key()]; exists {
		chain.GetAcceptedFailed(validatorID, requestID)
	} else {
		sr.log.Warn("Message referenced a chain, %s, this validator is not validating", chainID)
	}
}

// Get routes an incoming Get request from the validator with ID [validatorID]
// to the consensus engine working on the chain with ID [chainID]
func (sr *ChainRouter) Get(validatorID ids.ShortID, chainID ids.ID, requestID uint32, containerID ids.ID) {
	sr.lock.RLock()
	defer sr.lock.RUnlock()

	if chain, exists := sr.chains[chainID.Key()]; exists {
		chain.Get(validatorID, requestID, containerID)
	} else {
		sr.log.Warn("Message referenced a chain, %s, this validator is not validating", chainID)
	}
}

// Put routes an incoming Put request from the validator with ID [validatorID]
// to the consensus engine working on the chain with ID [chainID]
func (sr *ChainRouter) Put(validatorID ids.ShortID, chainID ids.ID, requestID uint32, containerID ids.ID, container []byte) {
	sr.lock.RLock()
	defer sr.lock.RUnlock()

	// This message came in response to a Get message from this node, and when we sent that Get
	// message we set a timeout. Since we got a response, cancel the timeout.
	sr.timeouts.Cancel(validatorID, chainID, requestID)
	if chain, exists := sr.chains[chainID.Key()]; exists {
		chain.Put(validatorID, requestID, containerID, container)
	} else {
		sr.log.Warn("Message referenced a chain, %s, this validator is not validating", chainID)
	}
}

// GetFailed routes an incoming GetFailed message from the validator with ID [validatorID]
// to the consensus engine working on the chain with ID [chainID]
func (sr *ChainRouter) GetFailed(validatorID ids.ShortID, chainID ids.ID, requestID uint32, containerID ids.ID) {
	sr.lock.RLock()
	defer sr.lock.RUnlock()

	sr.timeouts.Cancel(validatorID, chainID, requestID)
	if chain, exists := sr.chains[chainID.Key()]; exists {
		chain.GetFailed(validatorID, requestID, containerID)
	} else {
		sr.log.Warn("Message referenced a chain, %s, this validator is not validating", chainID)
	}
}

// PushQuery routes an incoming PushQuery request from the validator with ID [validatorID]
// to the consensus engine working on the chain with ID [chainID]
func (sr *ChainRouter) PushQuery(validatorID ids.ShortID, chainID ids.ID, requestID uint32, containerID ids.ID, container []byte) {
	sr.lock.RLock()
	defer sr.lock.RUnlock()

	if chain, exists := sr.chains[chainID.Key()]; exists {
		chain.PushQuery(validatorID, requestID, containerID, container)
	} else {
		sr.log.Warn("Message referenced a chain, %s, this validator is not validating", chainID)
	}
}

// PullQuery routes an incoming PullQuery request from the validator with ID [validatorID]
// to the consensus engine working on the chain with ID [chainID]
func (sr *ChainRouter) PullQuery(validatorID ids.ShortID, chainID ids.ID, requestID uint32, containerID ids.ID) {
	sr.lock.RLock()
	defer sr.lock.RUnlock()

	if chain, exists := sr.chains[chainID.Key()]; exists {
		chain.PullQuery(validatorID, requestID, containerID)
	} else {
		sr.log.Warn("Message referenced a chain, %s, this validator is not validating", chainID)
	}
}

// Chits routes an incoming Chits message from the validator with ID [validatorID]
// to the consensus engine working on the chain with ID [chainID]
func (sr *ChainRouter) Chits(validatorID ids.ShortID, chainID ids.ID, requestID uint32, votes ids.Set) {
	sr.lock.RLock()
	defer sr.lock.RUnlock()

	// Cancel timeout we set when sent the message asking for these Chits
	sr.timeouts.Cancel(validatorID, chainID, requestID)
	if chain, exists := sr.chains[chainID.Key()]; exists {
		chain.Chits(validatorID, requestID, votes)
	} else {
		sr.log.Warn("Message referenced a chain, %s, this validator is not validating", chainID)
	}
}

// QueryFailed routes an incoming QueryFailed message from the validator with ID [validatorID]
// to the consensus engine working on the chain with ID [chainID]
func (sr *ChainRouter) QueryFailed(validatorID ids.ShortID, chainID ids.ID, requestID uint32) {
	sr.lock.RLock()
	defer sr.lock.RUnlock()

	sr.timeouts.Cancel(validatorID, chainID, requestID)
	if chain, exists := sr.chains[chainID.Key()]; exists {
		chain.QueryFailed(validatorID, requestID)
	} else {
		sr.log.Warn("Message referenced a chain, %s, this validator is not validating", chainID)
	}
}

// Shutdown shuts down this router
func (sr *ChainRouter) Shutdown() {
	sr.lock.RLock()
	defer sr.lock.RUnlock()

	sr.shutdown()
}

func (sr *ChainRouter) shutdown() {
	for _, chain := range sr.chains {
		chain.Shutdown()
	}
	sr.gossiper.Stop()
}

// Gossip accepted containers
func (sr *ChainRouter) Gossip() {
	sr.lock.RLock()
	defer sr.lock.RUnlock()

	sr.gossip()
}

func (sr *ChainRouter) gossip() {
	for _, chain := range sr.chains {
		chain.Gossip()
	}
}
<commit_msg>Added gossip frequency docs<commit_after>// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

package router

import (
	"sync"
	"time"

	"github.com/ava-labs/gecko/ids"
	"github.com/ava-labs/gecko/snow/networking/handler"
	"github.com/ava-labs/gecko/snow/networking/timeout"
	"github.com/ava-labs/gecko/utils/logging"
	"github.com/ava-labs/gecko/utils/timer"
)

// ChainRouter routes incoming messages from the validator network
// to the consensus engines that the messages are intended for.
// Note that consensus engines are uniquely identified by the ID of the chain
// that they are working on.
type ChainRouter struct {
	log      logging.Logger
	lock     sync.RWMutex
	chains   map[[32]byte]*handler.Handler
	timeouts *timeout.Manager
	gossiper *timer.Repeater
}

// Initialize the router.
//
// When this router receives an incoming message, it cancels the timeout in
// [timeouts] associated with the request that caused the incoming message, if
// applicable.
//
// This router also fires a gossip event every [gossipFrequency] to the engine,
// notifying the engine it should gossip it's accepted set.
func (sr *ChainRouter) Initialize(log logging.Logger, timeouts *timeout.Manager, gossipFrequency time.Duration) {
	sr.log = log
	sr.chains = make(map[[32]byte]*handler.Handler)
	sr.timeouts = timeouts
	sr.gossiper = timer.NewRepeater(sr.Gossip, gossipFrequency)

	go log.RecoverAndPanic(sr.gossiper.Dispatch)
}

// AddChain registers the specified chain so that incoming
// messages can be routed to it
func (sr *ChainRouter) AddChain(chain *handler.Handler) {
	sr.lock.Lock()
	defer sr.lock.Unlock()

	chainID := chain.Context().ChainID
	sr.log.Debug("Adding %s to the routing table", chainID)
	sr.chains[chainID.Key()] = chain
}

// RemoveChain removes the specified chain so that incoming
// messages can't be routed to it
func (sr *ChainRouter) RemoveChain(chainID ids.ID) {
	sr.lock.Lock()
	defer sr.lock.Unlock()

	if chain, exists := sr.chains[chainID.Key()]; exists {
		chain.Shutdown()
		delete(sr.chains, chainID.Key())
	} else {
		sr.log.Warn("Message referenced a chain, %s, this validator is not validating", chainID)
	}
}

// GetAcceptedFrontier routes an incoming GetAcceptedFrontier request from the
// validator with ID [validatorID]  to the consensus engine working on the
// chain with ID [chainID]
func (sr *ChainRouter) GetAcceptedFrontier(validatorID ids.ShortID, chainID ids.ID, requestID uint32) {
	sr.lock.RLock()
	defer sr.lock.RUnlock()

	if chain, exists := sr.chains[chainID.Key()]; exists {
		chain.GetAcceptedFrontier(validatorID, requestID)
	} else {
		sr.log.Warn("Message referenced a chain, %s, this validator is not validating", chainID)
	}
}

// AcceptedFrontier routes an incoming AcceptedFrontier request from the
// validator with ID [validatorID]  to the consensus engine working on the
// chain with ID [chainID]
func (sr *ChainRouter) AcceptedFrontier(validatorID ids.ShortID, chainID ids.ID, requestID uint32, containerIDs ids.Set) {
	sr.lock.RLock()
	defer sr.lock.RUnlock()

	sr.timeouts.Cancel(validatorID, chainID, requestID)
	if chain, exists := sr.chains[chainID.Key()]; exists {
		chain.AcceptedFrontier(validatorID, requestID, containerIDs)
	} else {
		sr.log.Warn("Message referenced a chain, %s, this validator is not validating", chainID)
	}
}

// GetAcceptedFrontierFailed routes an incoming GetAcceptedFrontierFailed
// request from the validator with ID [validatorID]  to the consensus engine
// working on the chain with ID [chainID]
func (sr *ChainRouter) GetAcceptedFrontierFailed(validatorID ids.ShortID, chainID ids.ID, requestID uint32) {
	sr.lock.RLock()
	defer sr.lock.RUnlock()

	sr.timeouts.Cancel(validatorID, chainID, requestID)
	if chain, exists := sr.chains[chainID.Key()]; exists {
		chain.GetAcceptedFrontierFailed(validatorID, requestID)
	} else {
		sr.log.Warn("Message referenced a chain, %s, this validator is not validating", chainID)
	}
}

// GetAccepted routes an incoming GetAccepted request from the
// validator with ID [validatorID]  to the consensus engine working on the
// chain with ID [chainID]
func (sr *ChainRouter) GetAccepted(validatorID ids.ShortID, chainID ids.ID, requestID uint32, containerIDs ids.Set) {
	sr.lock.RLock()
	defer sr.lock.RUnlock()

	if chain, exists := sr.chains[chainID.Key()]; exists {
		chain.GetAccepted(validatorID, requestID, containerIDs)
	} else {
		sr.log.Warn("Message referenced a chain, %s, this validator is not validating", chainID)
	}
}

// Accepted routes an incoming Accepted request from the validator with ID
// [validatorID]  to the consensus engine working on the chain with ID
// [chainID]
func (sr *ChainRouter) Accepted(validatorID ids.ShortID, chainID ids.ID, requestID uint32, containerIDs ids.Set) {
	sr.lock.RLock()
	defer sr.lock.RUnlock()

	sr.timeouts.Cancel(validatorID, chainID, requestID)
	if chain, exists := sr.chains[chainID.Key()]; exists {
		chain.Accepted(validatorID, requestID, containerIDs)
	} else {
		sr.log.Warn("Message referenced a chain, %s, this validator is not validating", chainID)
	}
}

// GetAcceptedFailed routes an incoming GetAcceptedFailed request from the
// validator with ID [validatorID]  to the consensus engine working on the
// chain with ID [chainID]
func (sr *ChainRouter) GetAcceptedFailed(validatorID ids.ShortID, chainID ids.ID, requestID uint32) {
	sr.lock.RLock()
	defer sr.lock.RUnlock()

	sr.timeouts.Cancel(validatorID, chainID, requestID)
	if chain, exists := sr.chains[chainID.Key()]; exists {
		chain.GetAcceptedFailed(validatorID, requestID)
	} else {
		sr.log.Warn("Message referenced a chain, %s, this validator is not validating", chainID)
	}
}

// Get routes an incoming Get request from the validator with ID [validatorID]
// to the consensus engine working on the chain with ID [chainID]
func (sr *ChainRouter) Get(validatorID ids.ShortID, chainID ids.ID, requestID uint32, containerID ids.ID) {
	sr.lock.RLock()
	defer sr.lock.RUnlock()

	if chain, exists := sr.chains[chainID.Key()]; exists {
		chain.Get(validatorID, requestID, containerID)
	} else {
		sr.log.Warn("Message referenced a chain, %s, this validator is not validating", chainID)
	}
}

// Put routes an incoming Put request from the validator with ID [validatorID]
// to the consensus engine working on the chain with ID [chainID]
func (sr *ChainRouter) Put(validatorID ids.ShortID, chainID ids.ID, requestID uint32, containerID ids.ID, container []byte) {
	sr.lock.RLock()
	defer sr.lock.RUnlock()

	// This message came in response to a Get message from this node, and when we sent that Get
	// message we set a timeout. Since we got a response, cancel the timeout.
	sr.timeouts.Cancel(validatorID, chainID, requestID)
	if chain, exists := sr.chains[chainID.Key()]; exists {
		chain.Put(validatorID, requestID, containerID, container)
	} else {
		sr.log.Warn("Message referenced a chain, %s, this validator is not validating", chainID)
	}
}

// GetFailed routes an incoming GetFailed message from the validator with ID [validatorID]
// to the consensus engine working on the chain with ID [chainID]
func (sr *ChainRouter) GetFailed(validatorID ids.ShortID, chainID ids.ID, requestID uint32, containerID ids.ID) {
	sr.lock.RLock()
	defer sr.lock.RUnlock()

	sr.timeouts.Cancel(validatorID, chainID, requestID)
	if chain, exists := sr.chains[chainID.Key()]; exists {
		chain.GetFailed(validatorID, requestID, containerID)
	} else {
		sr.log.Warn("Message referenced a chain, %s, this validator is not validating", chainID)
	}
}

// PushQuery routes an incoming PushQuery request from the validator with ID [validatorID]
// to the consensus engine working on the chain with ID [chainID]
func (sr *ChainRouter) PushQuery(validatorID ids.ShortID, chainID ids.ID, requestID uint32, containerID ids.ID, container []byte) {
	sr.lock.RLock()
	defer sr.lock.RUnlock()

	if chain, exists := sr.chains[chainID.Key()]; exists {
		chain.PushQuery(validatorID, requestID, containerID, container)
	} else {
		sr.log.Warn("Message referenced a chain, %s, this validator is not validating", chainID)
	}
}

// PullQuery routes an incoming PullQuery request from the validator with ID [validatorID]
// to the consensus engine working on the chain with ID [chainID]
func (sr *ChainRouter) PullQuery(validatorID ids.ShortID, chainID ids.ID, requestID uint32, containerID ids.ID) {
	sr.lock.RLock()
	defer sr.lock.RUnlock()

	if chain, exists := sr.chains[chainID.Key()]; exists {
		chain.PullQuery(validatorID, requestID, containerID)
	} else {
		sr.log.Warn("Message referenced a chain, %s, this validator is not validating", chainID)
	}
}

// Chits routes an incoming Chits message from the validator with ID [validatorID]
// to the consensus engine working on the chain with ID [chainID]
func (sr *ChainRouter) Chits(validatorID ids.ShortID, chainID ids.ID, requestID uint32, votes ids.Set) {
	sr.lock.RLock()
	defer sr.lock.RUnlock()

	// Cancel timeout we set when sent the message asking for these Chits
	sr.timeouts.Cancel(validatorID, chainID, requestID)
	if chain, exists := sr.chains[chainID.Key()]; exists {
		chain.Chits(validatorID, requestID, votes)
	} else {
		sr.log.Warn("Message referenced a chain, %s, this validator is not validating", chainID)
	}
}

// QueryFailed routes an incoming QueryFailed message from the validator with ID [validatorID]
// to the consensus engine working on the chain with ID [chainID]
func (sr *ChainRouter) QueryFailed(validatorID ids.ShortID, chainID ids.ID, requestID uint32) {
	sr.lock.RLock()
	defer sr.lock.RUnlock()

	sr.timeouts.Cancel(validatorID, chainID, requestID)
	if chain, exists := sr.chains[chainID.Key()]; exists {
		chain.QueryFailed(validatorID, requestID)
	} else {
		sr.log.Warn("Message referenced a chain, %s, this validator is not validating", chainID)
	}
}

// Shutdown shuts down this router
func (sr *ChainRouter) Shutdown() {
	sr.lock.RLock()
	defer sr.lock.RUnlock()

	sr.shutdown()
}

func (sr *ChainRouter) shutdown() {
	for _, chain := range sr.chains {
		chain.Shutdown()
	}
	sr.gossiper.Stop()
}

// Gossip accepted containers
func (sr *ChainRouter) Gossip() {
	sr.lock.RLock()
	defer sr.lock.RUnlock()

	sr.gossip()
}

func (sr *ChainRouter) gossip() {
	for _, chain := range sr.chains {
		chain.Gossip()
	}
}
<|endoftext|>


<commit_before>package main

import (
	"errors"
	"fmt"
	"github.com/vlifesystems/rulehuntersrv/logger"
	"io/ioutil"
	"os"
	"path/filepath"
	"reflect"
	"syscall"
	"testing"
	"time"
)

func TestSubMain_errors(t *testing.T) {
	tmpDir, err := ioutil.TempDir("", "rulehuntersrv")
	if err != nil {
		t.Fatal("TempDir() couldn't create dir")
	}
	defer os.RemoveAll(tmpDir)

	cases := []struct {
		flags        *cmdFlags
		wantErr      error
		wantExitCode int
	}{
		{
			flags: &cmdFlags{
				user:      "fred",
				configDir: "",
				install:   true,
			},
			wantErr:      errNoConfigDirArg,
			wantExitCode: 1,
		},
		{
			flags: &cmdFlags{
				user:      "fred",
				configDir: tmpDir,
				install:   true,
			},
			wantErr: errConfigLoad{
				filename: filepath.Join(tmpDir, "config.json"),
				err: &os.PathError{
					"open",
					filepath.Join(tmpDir, "config.json"),
					syscall.ENOENT,
				},
			},
			wantExitCode: 1,
		},
	}

	for _, c := range cases {
		testLogger := logger.NewTestLogger()
		quitter := newQuitter()
		exitCode, err := subMain(c.flags, testLogger.MakeRun(), quitter)
		if exitCode != c.wantExitCode {
			t.Errorf("subMain(%q) exitCode: %d, want: %d",
				c.flags, exitCode, c.wantExitCode)
		}
		if err := checkErrorMatch(err, c.wantErr); err != nil {
			t.Errorf("subMain(%q) %s", c.flags, err)
		}
		if len(testLogger.GetEntries()) != 0 {
			t.Errorf("GetEntries() got: %s, want: {}", testLogger.GetEntries())
		}
	}
}

func TestSubMain(t *testing.T) {
	cases := []struct {
		flags        *cmdFlags
		wantErr      error
		wantExitCode int
		wantEntries  []logger.Entry
	}{
		{
			flags: &cmdFlags{
				user:    "fred",
				install: false,
			},
			wantErr:      nil,
			wantExitCode: 0,
			wantEntries: []logger.Entry{
				{logger.Info, "Waiting for experiments to process"},
			},
		},
	}

	for _, c := range cases {
		configDir, err := buildConfigDirs()
		if err != nil {
			t.Fatalf("buildConfigDirs() err: %s", err)
		}
		defer os.RemoveAll(c.flags.configDir)
		c.flags.configDir = configDir

		testLogger := logger.NewTestLogger()
		quitter := newQuitter()
		go func() {
			tryInSeconds := 10
			for i := 0; i < tryInSeconds*5; i++ {
				if reflect.DeepEqual(testLogger.GetEntries(), c.wantEntries) {
					quitter.Quit()
					return
				}
				time.Sleep(200 * time.Millisecond)
			}
			quitter.Quit()
		}()
		exitCode, err := subMain(c.flags, testLogger.MakeRun(), quitter)
		if exitCode != c.wantExitCode {
			t.Errorf("subMain(%q) exitCode: %d, want: %d",
				c.flags, exitCode, c.wantExitCode)
		}
		if err := checkErrorMatch(err, c.wantErr); err != nil {
			t.Errorf("subMain(%q) %s", c.flags, err)
		}
		if !reflect.DeepEqual(testLogger.GetEntries(), c.wantEntries) {
			t.Errorf("GetEntries() got: %s, want: %s",
				testLogger.GetEntries(), c.wantEntries)
		}
	}
}

/*************************************
 *  Helper functions
 *************************************/

func buildConfigDirs() (string, error) {
	// File mode permission:
	// No special permission bits
	// User: Read, Write Execute
	// Group: None
	// Other: None
	const modePerm = 0700

	tmpDir, err := ioutil.TempDir("", "rulehuntersrv")
	if err != nil {
		return "", errors.New("TempDir() couldn't create dir")
	}

	subDirs := []string{"experiments", "www", "build"}
	for _, subDir := range subDirs {
		if err := os.MkdirAll(subDir, modePerm); err != nil {
			return "", fmt.Errorf("can't make directory: %s", subDir)
		}
	}

	err = copyFile(filepath.Join("fixtures", "config.json"), tmpDir)
	return tmpDir, err
}

func checkErrorMatch(got, want error) error {
	if got == nil && want == nil {
		return nil
	}
	if got == nil || want == nil {
		return fmt.Errorf("got err: %s, want err: %s", got, want)
	}
	switch x := want.(type) {
	case *os.PathError:
		return checkPathErrorMatch(got, x)
	case errConfigLoad:
		return checkErrConfigLoadMatch(got, x)
	}
	if got.Error() != want.Error() {
		return fmt.Errorf("got err: %s, want err: %s", got, want)
	}
	return nil
}

func checkPathErrorMatch(checkErr error, wantErr error) error {
	cerr, ok := checkErr.(*os.PathError)
	if !ok {
		return fmt.Errorf("got err type: %T, want error type: os.PathError",
			checkErr)
	}
	werr, ok := wantErr.(*os.PathError)
	if !ok {
		panic("wantErr isn't type *os.PathError")
	}
	if cerr.Op != werr.Op {
		return fmt.Errorf("got cerr.Op: %s, want: %s", cerr.Op, werr.Op)
	}
	if filepath.Clean(cerr.Path) != filepath.Clean(werr.Path) {
		return fmt.Errorf("got cerr.Path: %s, want: %s", cerr.Path, werr.Path)
	}
	if cerr.Err != werr.Err {
		return fmt.Errorf("got cerr.Err: %s, want: %s", cerr.Err, werr.Err)
	}
	return nil
}

func checkErrConfigLoadMatch(checkErr error, wantErr errConfigLoad) error {
	cerr, ok := checkErr.(errConfigLoad)
	if !ok {
		return fmt.Errorf("got err type: %T, want error type: errConfigLoad",
			checkErr)
	}
	if filepath.Clean(cerr.filename) != filepath.Clean(wantErr.filename) {
		return fmt.Errorf("got cerr.Path: %s, want: %s",
			cerr.filename, wantErr.filename)
	}
	return checkPathErrorMatch(cerr.err, wantErr.err)
}

func copyFile(srcFilename, dstDir string) error {
	contents, err := ioutil.ReadFile(srcFilename)
	if err != nil {
		return err
	}
	info, err := os.Stat(srcFilename)
	if err != nil {
		return err
	}
	mode := info.Mode()
	dstFilename := filepath.Join(dstDir, filepath.Base(srcFilename))
	return ioutil.WriteFile(dstFilename, contents, mode)
}
<commit_msg>Create www/* and build/* subdirs in test<commit_after>package main

import (
	"errors"
	"fmt"
	"github.com/vlifesystems/rulehuntersrv/logger"
	"io/ioutil"
	"os"
	"path/filepath"
	"reflect"
	"syscall"
	"testing"
	"time"
)

func TestSubMain_errors(t *testing.T) {
	tmpDir, err := ioutil.TempDir("", "rulehuntersrv")
	if err != nil {
		t.Fatal("TempDir() couldn't create dir")
	}
	defer os.RemoveAll(tmpDir)

	cases := []struct {
		flags        *cmdFlags
		wantErr      error
		wantExitCode int
	}{
		{
			flags: &cmdFlags{
				user:      "fred",
				configDir: "",
				install:   true,
			},
			wantErr:      errNoConfigDirArg,
			wantExitCode: 1,
		},
		{
			flags: &cmdFlags{
				user:      "fred",
				configDir: tmpDir,
				install:   true,
			},
			wantErr: errConfigLoad{
				filename: filepath.Join(tmpDir, "config.json"),
				err: &os.PathError{
					"open",
					filepath.Join(tmpDir, "config.json"),
					syscall.ENOENT,
				},
			},
			wantExitCode: 1,
		},
	}

	for _, c := range cases {
		testLogger := logger.NewTestLogger()
		quitter := newQuitter()
		exitCode, err := subMain(c.flags, testLogger.MakeRun(), quitter)
		if exitCode != c.wantExitCode {
			t.Errorf("subMain(%q) exitCode: %d, want: %d",
				c.flags, exitCode, c.wantExitCode)
		}
		if err := checkErrorMatch(err, c.wantErr); err != nil {
			t.Errorf("subMain(%q) %s", c.flags, err)
		}
		if len(testLogger.GetEntries()) != 0 {
			t.Errorf("GetEntries() got: %s, want: {}", testLogger.GetEntries())
		}
	}
}

func TestSubMain(t *testing.T) {
	cases := []struct {
		flags        *cmdFlags
		wantErr      error
		wantExitCode int
		wantEntries  []logger.Entry
	}{
		{
			flags: &cmdFlags{
				user:    "fred",
				install: false,
			},
			wantErr:      nil,
			wantExitCode: 0,
			wantEntries: []logger.Entry{
				{logger.Info, "Waiting for experiments to process"},
			},
		},
	}

	wd, err := os.Getwd()
	if err != nil {
		t.Fatalf("Getwd() err: ", err)
	}
	defer os.Chdir(wd)

	for _, c := range cases {
		configDir, err := buildConfigDirs()
		if err != nil {
			t.Fatalf("buildConfigDirs() err: %s", err)
		}
		defer os.RemoveAll(c.flags.configDir)
		c.flags.configDir = configDir

		testLogger := logger.NewTestLogger()
		quitter := newQuitter()
		go func() {
			tryInSeconds := 5
			for i := 0; i < tryInSeconds*5; i++ {
				if reflect.DeepEqual(testLogger.GetEntries(), c.wantEntries) {
					quitter.Quit()
					return
				}
				time.Sleep(200 * time.Millisecond)
			}
			quitter.Quit()
		}()
		if err := os.Chdir(configDir); err != nil {
			t.Fatalf("Chdir() err: %s", err)
		}
		exitCode, err := subMain(c.flags, testLogger.MakeRun(), quitter)
		if exitCode != c.wantExitCode {
			t.Errorf("subMain(%q) exitCode: %d, want: %d",
				c.flags, exitCode, c.wantExitCode)
		}
		if err := checkErrorMatch(err, c.wantErr); err != nil {
			t.Errorf("subMain(%q) %s", c.flags, err)
		}
		if !reflect.DeepEqual(testLogger.GetEntries(), c.wantEntries) {
			t.Errorf("GetEntries() got: %s, want: %s",
				testLogger.GetEntries(), c.wantEntries)
		}
	}
}

/*************************************
 *  Helper functions
 *************************************/

func buildConfigDirs() (string, error) {
	// File mode permission:
	// No special permission bits
	// User: Read, Write Execute
	// Group: None
	// Other: None
	const modePerm = 0700

	tmpDir, err := ioutil.TempDir("", "rulehuntersrv")
	if err != nil {
		return "", errors.New("TempDir() couldn't create dir")
	}

	// TODO: Create the www/* and build/* subdirectories from rulehuntersrv code
	subDirs := []string{
		"experiments",
		filepath.Join("www", "reports"),
		filepath.Join("www", "progress"),
		filepath.Join("build", "reports")}
	for _, subDir := range subDirs {
		fullSubDir := filepath.Join(tmpDir, subDir)
		if err := os.MkdirAll(fullSubDir, modePerm); err != nil {
			return "", fmt.Errorf("can't make directory: %s", subDir)
		}
	}

	err = copyFile(filepath.Join("fixtures", "config.json"), tmpDir)
	return tmpDir, err
}

func checkErrorMatch(got, want error) error {
	if got == nil && want == nil {
		return nil
	}
	if got == nil || want == nil {
		return fmt.Errorf("got err: %s, want err: %s", got, want)
	}
	switch x := want.(type) {
	case *os.PathError:
		return checkPathErrorMatch(got, x)
	case errConfigLoad:
		return checkErrConfigLoadMatch(got, x)
	}
	if got.Error() != want.Error() {
		return fmt.Errorf("got err: %s, want err: %s", got, want)
	}
	return nil
}

func checkPathErrorMatch(checkErr error, wantErr error) error {
	cerr, ok := checkErr.(*os.PathError)
	if !ok {
		return fmt.Errorf("got err type: %T, want error type: os.PathError",
			checkErr)
	}
	werr, ok := wantErr.(*os.PathError)
	if !ok {
		panic("wantErr isn't type *os.PathError")
	}
	if cerr.Op != werr.Op {
		return fmt.Errorf("got cerr.Op: %s, want: %s", cerr.Op, werr.Op)
	}
	if filepath.Clean(cerr.Path) != filepath.Clean(werr.Path) {
		return fmt.Errorf("got cerr.Path: %s, want: %s", cerr.Path, werr.Path)
	}
	if cerr.Err != werr.Err {
		return fmt.Errorf("got cerr.Err: %s, want: %s", cerr.Err, werr.Err)
	}
	return nil
}

func checkErrConfigLoadMatch(checkErr error, wantErr errConfigLoad) error {
	cerr, ok := checkErr.(errConfigLoad)
	if !ok {
		return fmt.Errorf("got err type: %T, want error type: errConfigLoad",
			checkErr)
	}
	if filepath.Clean(cerr.filename) != filepath.Clean(wantErr.filename) {
		return fmt.Errorf("got cerr.Path: %s, want: %s",
			cerr.filename, wantErr.filename)
	}
	return checkPathErrorMatch(cerr.err, wantErr.err)
}

func copyFile(srcFilename, dstDir string) error {
	contents, err := ioutil.ReadFile(srcFilename)
	if err != nil {
		return err
	}
	info, err := os.Stat(srcFilename)
	if err != nil {
		return err
	}
	mode := info.Mode()
	dstFilename := filepath.Join(dstDir, filepath.Base(srcFilename))
	return ioutil.WriteFile(dstFilename, contents, mode)
}
<|endoftext|>


<commit_before>// Copyright 2018 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package main

import (
	"bytes"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"path/filepath"
	"regexp"
	"strconv"
	"strings"

	"github.com/googleapis/gapic-showcase/util"
	"github.com/spf13/cobra"
)

const currentAPIVersion = "v1alpha3"
const currentReleaseVersion = "0.0.16"

// This script updates the release version or API version of files in gapic-showcase.
// This script is used on API and release version bumps. This script must be ran in
// the root directory of gapic-showcase.
//
// Usage: go run ./util/cmd/bump_version/main.go -h
func main() {
	var bumpMajor, bumpMinor, bumpPatch bool
	var newAPI string

	cmd := &cobra.Command{
		Short: "Utility script to bump the API and realease versions in all relevant files.",
		Run: func(cmd *cobra.Command, args []string) {
			if newAPI == "" && !bumpMajor && !bumpMinor && !bumpPatch {
				return
			}

			if bumpMajor || bumpMinor || bumpPatch {
				if !oneof(bumpMajor, bumpMinor, bumpPatch) {
					log.Fatalf("Expected only one of --major, --minor, and --patch.")
				}
				versions := strings.Split(currentReleaseVersion, ".")

				atoi := func(s string) int {
					i, err := strconv.Atoi(s)
					if err != nil {
						log.Fatal(err)
					}
					return i
				}

				major := atoi(versions[0])
				minor := atoi(versions[1])
				patch := atoi(versions[2])

				if bumpMajor {
					major++
					minor = 0
					patch = 0
				}
				if bumpMinor {
					minor++
					patch = 0
				}
				if bumpPatch {
					patch++
				}
				replace(currentReleaseVersion, fmt.Sprintf("%d.%d.%d", major, minor, patch))
			}

			if newAPI != "" && currentAPIVersion != newAPI {
				versionRegexStr := "^([v]\\d+)([p_]\\d+)?((alpha|beta)\\d*)?"
				versionRegex, err := regexp.Compile(versionRegexStr)
				if err != nil {
					log.Fatalf("Unexpected Error compiling version regex: %+v", err)
				}
				if !versionRegex.Match([]byte(newAPI)) {
					log.Fatalf("The API version must conform to the regex: %s", versionRegexStr)
				}

				schemaDir := filepath.Join("schema", "google", "showcase")
				err = os.Rename(filepath.Join(schemaDir, currentAPIVersion), filepath.Join(schemaDir, newAPI))
				if err != nil {
					log.Fatalf("Failed to change proto directory: %+v", err)
				}

				replace(currentAPIVersion, newAPI)
				util.CompileProtos(newAPI)
			}
		},
	}

	cmd.Flags().BoolVarP(
		&bumpMajor,
		"major",
		"",
		false,
		"Pass this flag to bump the major version")
	cmd.Flags().BoolVarP(
		&bumpMinor,
		"minor",
		"",
		false,
		"Pass this flag to bump the minor version")
	cmd.Flags().BoolVarP(
		&bumpPatch,
		"patch",
		"",
		false,
		"Pass this flag to bump the patch version")
	cmd.Flags().StringVarP(
		&newAPI,
		"api",
		"a",
		"",
		"The new API version to set.")

	if err := cmd.Execute(); err != nil {
		fmt.Println(err)
		os.Exit(1)
	}
}

func replace(old, new string) {
	pwd, err := os.Getwd()
	if err != nil {
		log.Fatalf("Error: unable to get working dir: %+v", err)
	}

	filetypes := []string{".go", ".md", ".yml"}
	err = filepath.Walk(pwd, replacer(filetypes, old, new))
	if err != nil {
		log.Fatalf("%v", err)
	}
}

func replacer(filetypes []string, old, new string) filepath.WalkFunc {
	return func(path string, fi os.FileInfo, err error) error {
		if err != nil {
			return err
		}
		if fi.IsDir() {
			return nil
		}
		if strings.HasSuffix(fi.Name(), "CHANGELOG.md") {
			return nil
		}

		matched := false
		for _, t := range filetypes {
			matched = matched || strings.HasSuffix(path, t)
		}
		if !matched {
			return nil
		}

		oldBytes, err := ioutil.ReadFile(path)
		if err != nil {
			log.Fatalf("%v", err)
		}

		newBytes := bytes.Replace(oldBytes, []byte(old), []byte(new), -1)
		if !bytes.Equal(oldBytes, newBytes) {
			if err = ioutil.WriteFile(path, newBytes, 0); err != nil {
				log.Fatalf("%v", err)
			}
		}
		return nil
	}
}

func oneof(bs ...bool) bool {
	t := 0
	for _, b := range bs {
		if b {
			t++
		}
	}
	return t == 1
}
<commit_msg>Small bump script fix (#119)<commit_after>// Copyright 2018 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package main

import (
	"bytes"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"path/filepath"
	"regexp"
	"strconv"
	"strings"

	"github.com/googleapis/gapic-showcase/util"
	"github.com/spf13/cobra"
)

const currentAPIVersion = "v1alpha3"
const currentReleaseVersion = "0.0.16"

// This script updates the release version or API version of files in gapic-showcase.
// This script is used on API and release version bumps. This script must be ran in
// the root directory of gapic-showcase.
//
// Usage: go run ./util/cmd/bump_version/main.go -h
func main() {
	var bumpMajor, bumpMinor, bumpPatch bool
	var newAPI string

	cmd := &cobra.Command{
		Short: "Utility script to bump the API and realease versions in all relevant files.",
		Run: func(cmd *cobra.Command, args []string) {
			if newAPI == "" && !bumpMajor && !bumpMinor && !bumpPatch {
				return
			}

			if bumpMajor || bumpMinor || bumpPatch {
				if !oneof(bumpMajor, bumpMinor, bumpPatch) {
					log.Fatalf("Expected only one of --major, --minor, and --patch.")
				}
				versions := strings.Split(currentReleaseVersion, ".")

				atoi := func(s string) int {
					i, err := strconv.Atoi(s)
					if err != nil {
						log.Fatal(err)
					}
					return i
				}

				major := atoi(versions[0])
				minor := atoi(versions[1])
				patch := atoi(versions[2])

				if bumpMajor {
					major++
					minor = 0
					patch = 0
				}
				if bumpMinor {
					minor++
					patch = 0
				}
				if bumpPatch {
					patch++
				}
				replace(currentReleaseVersion, fmt.Sprintf("%d.%d.%d", major, minor, patch))
			}

			if newAPI != "" && currentAPIVersion != newAPI {
				versionRegexStr := "^([v]\\d+)([p_]\\d+)?((alpha|beta)\\d*)?"
				versionRegex, err := regexp.Compile(versionRegexStr)
				if err != nil {
					log.Fatalf("Unexpected Error compiling version regex: %+v", err)
				}
				if !versionRegex.Match([]byte(newAPI)) {
					log.Fatalf("The API version must conform to the regex: %s", versionRegexStr)
				}

				schemaDir := filepath.Join("schema", "google", "showcase")
				err = os.Rename(filepath.Join(schemaDir, currentAPIVersion), filepath.Join(schemaDir, newAPI))
				if err != nil {
					log.Fatalf("Failed to change proto directory: %+v", err)
				}

				replace(currentAPIVersion, newAPI)
				util.CompileProtos(newAPI)
			}
		},
	}

	cmd.Flags().BoolVarP(
		&bumpMajor,
		"major",
		"",
		false,
		"Pass this flag to bump the major version")
	cmd.Flags().BoolVarP(
		&bumpMinor,
		"minor",
		"",
		false,
		"Pass this flag to bump the minor version")
	cmd.Flags().BoolVarP(
		&bumpPatch,
		"patch",
		"",
		false,
		"Pass this flag to bump the patch version")
	cmd.Flags().StringVarP(
		&newAPI,
		"api",
		"a",
		"",
		"The new API version to set.")

	if err := cmd.Execute(); err != nil {
		fmt.Println(err)
		os.Exit(1)
	}
}

func replace(old, new string) {
	pwd, err := os.Getwd()
	if err != nil {
		log.Fatalf("Error: unable to get working dir: %+v", err)
	}

	filetypes := []string{".go", ".md", ".yml", ".proto"}
	err = filepath.Walk(pwd, replacer(filetypes, old, new))
	if err != nil {
		log.Fatalf("%v", err)
	}
}

func replacer(filetypes []string, old, new string) filepath.WalkFunc {
	return func(path string, fi os.FileInfo, err error) error {
		if err != nil {
			return err
		}
		if fi.IsDir() {
			return nil
		}
		if strings.HasSuffix(fi.Name(), "CHANGELOG.md") {
			return nil
		}

		matched := false
		for _, t := range filetypes {
			matched = matched || strings.HasSuffix(path, t)
		}
		if !matched {
			return nil
		}

		oldBytes, err := ioutil.ReadFile(path)
		if err != nil {
			log.Fatalf("%v", err)
		}

		newBytes := bytes.Replace(oldBytes, []byte(old), []byte(new), -1)
		if !bytes.Equal(oldBytes, newBytes) {
			if err = ioutil.WriteFile(path, newBytes, 0); err != nil {
				log.Fatalf("%v", err)
			}
		}
		return nil
	}
}

func oneof(bs ...bool) bool {
	t := 0
	for _, b := range bs {
		if b {
			t++
		}
	}
	return t == 1
}
<|endoftext|>


<commit_before>/*
Copyright 2017 The Kubernetes Authors.

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

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

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

package gce

import compute "google.golang.org/api/compute/v1"

func newTargetPoolMetricContext(request, region string) *metricContext {
	return newGenericMetricContext("targetpool", request, region, unusedMetricLabel, computeV1Version)
}

// GetTargetPool returns the TargetPool by name.
func (gce *GCECloud) GetTargetPool(name, region string) (*compute.TargetPool, error) {
	mc := newTargetPoolMetricContext("get", region)
	v, err := gce.service.TargetPools.Get(gce.projectID, region, name).Do()
	return v, mc.Observe(err)
}

// CreateTargetPool creates the passed TargetPool
func (gce *GCECloud) CreateTargetPool(tp *compute.TargetPool, region string) error {
	mc := newTargetPoolMetricContext("create", region)
	op, err := gce.service.TargetPools.Insert(gce.projectID, region, tp).Do()
	if err != nil {
		return mc.Observe(err)
	}

	return gce.waitForRegionOp(op, region, mc)
}

// DeleteTargetPool deletes TargetPool by name.
func (gce *GCECloud) DeleteTargetPool(name, region string) error {
	mc := newTargetPoolMetricContext("delete", region)
	op, err := gce.service.TargetPools.Delete(gce.projectID, region, name).Do()
	if err != nil {
		return mc.Observe(err)
	}
	return gce.waitForRegionOp(op, region, mc)
}

// AddInstancesToTargetPool adds instances by link to the TargetPool
func (gce *GCECloud) AddInstancesToTargetPool(name, region string, instanceRefs []*compute.InstanceReference) error {
	add := &compute.TargetPoolsAddInstanceRequest{Instances: instanceRefs}
	mc := newTargetPoolMetricContext("add_instances", region)
	op, err := gce.service.TargetPools.AddInstance(gce.projectID, region, name, add).Do()
	if err != nil {
		return mc.Observe(err)
	}
	return gce.waitForRegionOp(op, region, mc)
}

// RemoveInstancesToTargetPool removes instances by link to the TargetPool
func (gce *GCECloud) RemoveInstancesFromTargetPool(name, region string, instanceRefs []*compute.InstanceReference) error {
	remove := &compute.TargetPoolsRemoveInstanceRequest{Instances: instanceRefs}
	mc := newTargetPoolMetricContext("remove_instances", region)
	op, err := gce.service.TargetPools.RemoveInstance(gce.projectID, region, name, remove).Do()
	if err != nil {
		return mc.Observe(err)
	}
	return gce.waitForRegionOp(op, region, mc)
}
<commit_msg>Update TargetPool to use generated code<commit_after>/*
Copyright 2017 The Kubernetes Authors.

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

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

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

package gce

import (
	"context"

	compute "google.golang.org/api/compute/v1"

	"k8s.io/kubernetes/pkg/cloudprovider/providers/gce/cloud/meta"
)

func newTargetPoolMetricContext(request, region string) *metricContext {
	return newGenericMetricContext("targetpool", request, region, unusedMetricLabel, computeV1Version)
}

// GetTargetPool returns the TargetPool by name.
func (gce *GCECloud) GetTargetPool(name, region string) (*compute.TargetPool, error) {
	mc := newTargetPoolMetricContext("get", region)
	v, err := gce.c.TargetPools().Get(context.Background(), meta.RegionalKey(name, region))
	return v, mc.Observe(err)
}

// CreateTargetPool creates the passed TargetPool
func (gce *GCECloud) CreateTargetPool(tp *compute.TargetPool, region string) error {
	mc := newTargetPoolMetricContext("create", region)
	return mc.Observe(gce.c.TargetPools().Insert(context.Background(), meta.RegionalKey(tp.Name, region), tp))
}

// DeleteTargetPool deletes TargetPool by name.
func (gce *GCECloud) DeleteTargetPool(name, region string) error {
	mc := newTargetPoolMetricContext("delete", region)
	return mc.Observe(gce.c.TargetPools().Delete(context.Background(), meta.RegionalKey(name, region)))
}

// AddInstancesToTargetPool adds instances by link to the TargetPool
func (gce *GCECloud) AddInstancesToTargetPool(name, region string, instanceRefs []*compute.InstanceReference) error {
	req := &compute.TargetPoolsAddInstanceRequest{
		Instances: instanceRefs,
	}
	mc := newTargetPoolMetricContext("add_instances", region)
	return mc.Observe(gce.c.TargetPools().AddInstance(context.Background(), meta.RegionalKey(name, region), req))
}

// RemoveInstancesFromTargetPool removes instances by link to the TargetPool
func (gce *GCECloud) RemoveInstancesFromTargetPool(name, region string, instanceRefs []*compute.InstanceReference) error {
	req := &compute.TargetPoolsRemoveInstanceRequest{
		Instances: instanceRefs,
	}
	mc := newTargetPoolMetricContext("remove_instances", region)
	return mc.Observe(gce.c.TargetPools().RemoveInstance(context.Background(), meta.RegionalKey(name, region), req))
}
<|endoftext|>


<commit_before>/*
Copyright 2019 The Skaffold Authors

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

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

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

package validation

import (
	"fmt"
	"testing"

	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest"
	"github.com/GoogleContainerTools/skaffold/testutil"
)

var (
	cfgWithErrors = &latest.SkaffoldConfig{
		Pipeline: latest.Pipeline{
			Build: latest.BuildConfig{
				Artifacts: []*latest.Artifact{
					{
						ArtifactType: latest.ArtifactType{
							DockerArtifact: &latest.DockerArtifact{},
							BazelArtifact:  &latest.BazelArtifact{},
						},
					},
					{
						ArtifactType: latest.ArtifactType{
							BazelArtifact:  &latest.BazelArtifact{},
							KanikoArtifact: &latest.KanikoArtifact{},
						},
					},
				},
			},
			Deploy: latest.DeployConfig{
				DeployType: latest.DeployType{
					HelmDeploy:    &latest.HelmDeploy{},
					KubectlDeploy: &latest.KubectlDeploy{},
				},
			},
		},
	}
)

func TestValidateSchema(t *testing.T) {
	tests := []struct {
		name      string
		cfg       *latest.SkaffoldConfig
		shouldErr bool
	}{
		{
			name:      "config with errors",
			cfg:       cfgWithErrors,
			shouldErr: true,
		},
		{
			name:      "empty config",
			cfg:       &latest.SkaffoldConfig{},
			shouldErr: true,
		},
		{
			name: "minimal config",
			cfg: &latest.SkaffoldConfig{
				APIVersion: "foo",
				Kind:       "bar",
			},
			shouldErr: false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			err := Process(tt.cfg)
			testutil.CheckError(t, tt.shouldErr, err)
		})
	}
}

func alwaysErr(_ interface{}) error {
	return fmt.Errorf("always fail")
}

type emptyStruct struct{}
type nestedEmptyStruct struct {
	N emptyStruct
}

func TestVisitStructs(t *testing.T) {
	tests := []struct {
		name         string
		input        interface{}
		expectedErrs int
	}{
		{
			name:         "single struct to validate",
			input:        emptyStruct{},
			expectedErrs: 1,
		},
		{
			name:         "recurse into nested struct",
			input:        nestedEmptyStruct{},
			expectedErrs: 2,
		},
		{
			name: "check all slice items",
			input: struct {
				A []emptyStruct
			}{
				A: []emptyStruct{{}, {}},
			},
			expectedErrs: 3,
		},
		{
			name: "recurse into slices",
			input: struct {
				A []nestedEmptyStruct
			}{
				A: []nestedEmptyStruct{
					{
						N: emptyStruct{},
					},
				},
			},
			expectedErrs: 3,
		},
		{
			name: "recurse into ptr slices",
			input: struct {
				A []*nestedEmptyStruct
			}{
				A: []*nestedEmptyStruct{
					{
						N: emptyStruct{},
					},
				},
			},
			expectedErrs: 3,
		},
		{
			name: "ignore empty slices",
			input: struct {
				A []emptyStruct
			}{},
			expectedErrs: 1,
		},
		{
			name: "ignore nil pointers",
			input: struct {
				A *struct{}
			}{},
			expectedErrs: 1,
		},
		{
			name: "recurse into members",
			input: struct {
				A, B emptyStruct
			}{
				A: emptyStruct{},
				B: emptyStruct{},
			},
			expectedErrs: 3,
		},
		{
			name: "recurse into ptr members",
			input: struct {
				A, B *emptyStruct
			}{
				A: &emptyStruct{},
				B: &emptyStruct{},
			},
			expectedErrs: 3,
		},
		{
			name: "ignore other fields",
			input: struct {
				A emptyStruct
				C int
			}{
				A: emptyStruct{},
				C: 2,
			},
			expectedErrs: 2,
		},
		{
			name: "unexported fields",
			input: struct {
				a emptyStruct
			}{},
			expectedErrs: 1,
		},
		{
			name: "exported and unexported fields",
			input: struct {
				a, A, b emptyStruct
			}{},
			expectedErrs: 2,
		},
		{
			name: "unexported nil ptr fields",
			input: struct {
				a *emptyStruct
			}{},
			expectedErrs: 1,
		},
		{
			name: "unexported ptr fields",
			input: struct {
				a *emptyStruct
			}{
				a: &emptyStruct{},
			},
			expectedErrs: 1,
		},
		{
			name: "unexported and exported ptr fields",
			input: struct {
				a, A, b *emptyStruct
			}{
				a: &emptyStruct{},
				A: &emptyStruct{},
				b: &emptyStruct{},
			},
			expectedErrs: 2,
		},
	}

	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			actual := visitStructs(test.input, alwaysErr)

			testutil.CheckDeepEqual(t, test.expectedErrs, len(actual))
		})
	}
}
<commit_msg>Make linter happy<commit_after>/*
Copyright 2019 The Skaffold Authors

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

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

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

package validation

import (
	"fmt"
	"testing"

	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest"
	"github.com/GoogleContainerTools/skaffold/testutil"
)

var (
	cfgWithErrors = &latest.SkaffoldConfig{
		Pipeline: latest.Pipeline{
			Build: latest.BuildConfig{
				Artifacts: []*latest.Artifact{
					{
						ArtifactType: latest.ArtifactType{
							DockerArtifact: &latest.DockerArtifact{},
							BazelArtifact:  &latest.BazelArtifact{},
						},
					},
					{
						ArtifactType: latest.ArtifactType{
							BazelArtifact:  &latest.BazelArtifact{},
							KanikoArtifact: &latest.KanikoArtifact{},
						},
					},
				},
			},
			Deploy: latest.DeployConfig{
				DeployType: latest.DeployType{
					HelmDeploy:    &latest.HelmDeploy{},
					KubectlDeploy: &latest.KubectlDeploy{},
				},
			},
		},
	}
)

func TestValidateSchema(t *testing.T) {
	tests := []struct {
		name      string
		cfg       *latest.SkaffoldConfig
		shouldErr bool
	}{
		{
			name:      "config with errors",
			cfg:       cfgWithErrors,
			shouldErr: true,
		},
		{
			name:      "empty config",
			cfg:       &latest.SkaffoldConfig{},
			shouldErr: true,
		},
		{
			name: "minimal config",
			cfg: &latest.SkaffoldConfig{
				APIVersion: "foo",
				Kind:       "bar",
			},
			shouldErr: false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			err := Process(tt.cfg)
			testutil.CheckError(t, tt.shouldErr, err)
		})
	}
}

func alwaysErr(_ interface{}) error {
	return fmt.Errorf("always fail")
}

type emptyStruct struct{}
type nestedEmptyStruct struct {
	N emptyStruct
}

func TestVisitStructs(t *testing.T) {
	tests := []struct {
		name         string
		input        interface{}
		expectedErrs int
	}{
		{
			name:         "single struct to validate",
			input:        emptyStruct{},
			expectedErrs: 1,
		},
		{
			name:         "recurse into nested struct",
			input:        nestedEmptyStruct{},
			expectedErrs: 2,
		},
		{
			name: "check all slice items",
			input: struct {
				A []emptyStruct
			}{
				A: []emptyStruct{{}, {}},
			},
			expectedErrs: 3,
		},
		{
			name: "recurse into slices",
			input: struct {
				A []nestedEmptyStruct
			}{
				A: []nestedEmptyStruct{
					{
						N: emptyStruct{},
					},
				},
			},
			expectedErrs: 3,
		},
		{
			name: "recurse into ptr slices",
			input: struct {
				A []*nestedEmptyStruct
			}{
				A: []*nestedEmptyStruct{
					{
						N: emptyStruct{},
					},
				},
			},
			expectedErrs: 3,
		},
		{
			name: "ignore empty slices",
			input: struct {
				A []emptyStruct
			}{},
			expectedErrs: 1,
		},
		{
			name: "ignore nil pointers",
			input: struct {
				A *struct{}
			}{},
			expectedErrs: 1,
		},
		{
			name: "recurse into members",
			input: struct {
				A, B emptyStruct
			}{
				A: emptyStruct{},
				B: emptyStruct{},
			},
			expectedErrs: 3,
		},
		{
			name: "recurse into ptr members",
			input: struct {
				A, B *emptyStruct
			}{
				A: &emptyStruct{},
				B: &emptyStruct{},
			},
			expectedErrs: 3,
		},
		{
			name: "ignore other fields",
			input: struct {
				A emptyStruct
				C int
			}{
				A: emptyStruct{},
				C: 2,
			},
			expectedErrs: 2,
		},
		{
			name: "unexported fields",
			input: struct {
				a emptyStruct
			}{
				a: emptyStruct{},
			},
			expectedErrs: 1,
		},
		{
			name: "exported and unexported fields",
			input: struct {
				a, A, b emptyStruct
			}{
				a: emptyStruct{},
				A: emptyStruct{},
				b: emptyStruct{},
			},
			expectedErrs: 2,
		},
		{
			name: "unexported nil ptr fields",
			input: struct {
				a *emptyStruct
			}{
				a: nil,
			},
			expectedErrs: 1,
		},
		{
			name: "unexported ptr fields",
			input: struct {
				a *emptyStruct
			}{
				a: &emptyStruct{},
			},
			expectedErrs: 1,
		},
		{
			name: "unexported and exported ptr fields",
			input: struct {
				a, A, b *emptyStruct
			}{
				a: &emptyStruct{},
				A: &emptyStruct{},
				b: &emptyStruct{},
			},
			expectedErrs: 2,
		},
	}

	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			actual := visitStructs(test.input, alwaysErr)

			testutil.CheckDeepEqual(t, test.expectedErrs, len(actual))
		})
	}
}
<|endoftext|>


<commit_before>// Copyright (c) 2017 Cisco and/or its affiliates.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at:
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//go:generate binapi-generator --input-file=/usr/share/vpp/api/session.api.json --output-dir=bin_api

package vpptcp

import (
	"bytes"
	"errors"
	"net"
	"strings"

	govpp "git.fd.io/govpp.git/api"
	"github.com/ligato/cn-infra/logging"

	"github.com/contiv/vpp/plugins/contiv"
	podmodel "github.com/contiv/vpp/plugins/ksr/model/pod"
	"github.com/contiv/vpp/plugins/policy/renderer"
	"github.com/contiv/vpp/plugins/policy/renderer/vpptcp/bin_api/session"
	"github.com/contiv/vpp/plugins/policy/renderer/vpptcp/cache"
)

// SessionRuleTagPrefix is used to tag session rules created for the implementation
// of K8s policies.
const SessionRuleTagPrefix = "contiv/vpp-policy-"

// Renderer renders Contiv Rules into VPP Session rules.
// Session rules are configured into VPP directly via binary API using govpp.
type Renderer struct {
	Deps

	cache *cache.SessionRuleCache
}

// Deps lists dependencies of Renderer.
type Deps struct {
	Log        logging.Logger
	LogFactory logging.LogFactory /* optional */
	Contiv     contiv.API         /* for GetNsIndex() */
	GoVPPChan  *govpp.Channel
}

// RendererTxn represents a single transaction of Renderer.
type RendererTxn struct {
	cacheTxn cache.Txn
	renderer *Renderer
	resync   bool
}

// Init initializes the VPPTCP Renderer.
func (r *Renderer) Init() error {
	// Init the cache
	r.cache = &cache.SessionRuleCache{}
	if r.LogFactory != nil {
		r.cache.Log = r.LogFactory.NewLogger("-vpptcpCache")
		r.cache.Log.SetLevel(logging.DebugLevel)
	} else {
		r.cache.Log = r.Log
	}
	r.cache.Init(r.dumpRules, SessionRuleTagPrefix)
	return nil
}

// NewTxn starts a new transaction. The rendering executes only after Commit()
// is called. Rollback is not yet supported however.
// If <resync> is enabled, the supplied configuration will completely
// replace the existing one. Otherwise, the change is performed incrementally,
// i.e. interfaces not mentioned in the transaction are left unaffected.
func (r *Renderer) NewTxn(resync bool) renderer.Txn {
	return &RendererTxn{cacheTxn: r.cache.NewTxn(resync), renderer: r, resync: resync}
}

// dumpRules queries VPP to get the currently installed set of rules.
func (r *Renderer) dumpRules() ([]*cache.SessionRule, error) {
	rules := []*cache.SessionRule{}
	// Send request to dump all installed rules.
	req := &session.SessionRulesDump{}
	reqContext := r.GoVPPChan.SendMultiRequest(req)
	// Receive details about each installed rule.
	for {
		msg := &session.SessionRulesDetails{}
		stop, err := reqContext.ReceiveReply(msg)
		if err != nil {
			r.Log.WithField("err", err).Error("Failed to get a session rule details")
			return rules, err
		}
		if stop {
			break
		}
		tagLen := bytes.IndexByte(msg.Tag, 0)
		tag := string(msg.Tag[:tagLen])
		if !strings.HasPrefix(tag, SessionRuleTagPrefix) {
			// Skip rules not installed by this renderer.
			continue
		}
		sessionRule := &cache.SessionRule{
			TransportProto: msg.TransportProto,
			IsIP4:          msg.IsIP4,
			LclPlen:        msg.LclPlen,
			RmtPlen:        msg.RmtPlen,
			LclPort:        msg.LclPort,
			RmtPort:        msg.RmtPort,
			ActionIndex:    msg.ActionIndex,
			AppnsIndex:     msg.AppnsIndex,
			Scope:          msg.Scope,
		}
		copy(sessionRule.LclIP[:], msg.LclIP)
		copy(sessionRule.RmtIP[:], msg.RmtIP)
		copy(sessionRule.Tag[:], msg.Tag)
		rules = append(rules, sessionRule)
	}

	r.Log.WithFields(logging.Fields{
		"rules": rules,
	}).Debug("VPPTCP Renderer dumpRules()")
	return rules, nil
}

// makeSessionRuleAddDelReq creates an instance of SessionRuleAddDel bin API
// request.
func (r *Renderer) makeSessionRuleAddDelReq(rule *cache.SessionRule, add bool) *govpp.VppRequest {
	isAdd := uint8(0)
	if add {
		isAdd = uint8(1)
	}
	msg := &session.SessionRuleAddDel{
		TransportProto: rule.TransportProto,
		IsIP4:          rule.IsIP4,
		LclIP:          rule.LclIP[:],
		LclPlen:        rule.LclPlen,
		RmtIP:          rule.RmtIP[:],
		RmtPlen:        rule.RmtPlen,
		LclPort:        rule.LclPort,
		RmtPort:        rule.RmtPort,
		ActionIndex:    rule.ActionIndex,
		IsAdd:          isAdd,
		AppnsIndex:     rule.AppnsIndex,
		Scope:          rule.Scope,
		Tag:            rule.Tag[:],
	}
	r.Log.WithField("msg:", *msg).Debug("Sending BIN API Request to VPP.")
	req := &govpp.VppRequest{
		Message: msg,
	}
	return req
}

// updateRules adds/removes selected rules to/from VPP Session rule tables.
func (r *Renderer) updateRules(add, remove []*cache.SessionRule) error {
	const errMsg = "failed to update VPPTCP session rule"

	// Prepare VPP requests.
	requests := []*govpp.VppRequest{}
	for _, delRule := range remove {
		requests = append(requests, r.makeSessionRuleAddDelReq(delRule, false))
	}
	for _, addRule := range add {
		requests = append(requests, r.makeSessionRuleAddDelReq(addRule, true))
	}

	// Send all VPP requests at once.
	for _, req := range requests {
		r.GoVPPChan.ReqChan <- req
	}

	// Wait for all VPP responses.
	var wasError error
	for i := 0; i < len(requests); i++ {
		reply := <-r.GoVPPChan.ReplyChan
		if reply.Error != nil {
			r.Log.WithField("err", reply.Error).Error(errMsg)
			wasError = reply.Error
			break
		}
		msg := &session.SessionRuleAddDelReply{}
		err := r.GoVPPChan.MsgDecoder.DecodeMsg(reply.Data, msg)
		if err != nil {
			r.Log.WithField("err", err).Error(errMsg)
			wasError = err
			break
		}
		if msg.Retval != 0 {
			r.Log.WithField("retval", msg.Retval).Error(errMsg)
			wasError = errors.New(errMsg)
			break
		}
	}
	return wasError
}

// Render applies the set of ingress & egress rules for a given pod.
// The existing rules are replaced.
// Te actual change is performed only after the commit.
func (art *RendererTxn) Render(pod podmodel.ID, podIP *net.IPNet, ingress []*renderer.ContivRule, egress []*renderer.ContivRule) renderer.Txn {
	// Get the target namespace index.
	nsIndex, found := art.renderer.Contiv.GetNsIndex(pod.Namespace, pod.Name)
	if !found {
		art.renderer.Log.WithField("pod", pod).Warn("Unable to get the namespace index of the Pod")
		return art
	}

	art.renderer.Log.WithFields(logging.Fields{
		"pod":     pod,
		"nsIndex": nsIndex,
		"ingress": ingress,
		"egress":  egress,
	}).Debug("VPPTCP RendererTxn Render()")

	// Add the rules into the transaction.
	art.cacheTxn.Update(nsIndex, podIP, ingress, egress)
	return art
}

// Commit proceeds with the rendering. A minimalistic set of changes is
// calculated using ContivRuleCache and applied via binary API using govpp.
func (art *RendererTxn) Commit() error {
	added, removed, err := art.cacheTxn.Changes()
	if err != nil {
		return err
	}
	if len(added) == 0 && len(removed) == 0 {
		art.renderer.Log.Debug("No changes to be rendered in the transaction")
		return nil
	}
	err = art.renderer.updateRules(added, removed)
	if err != nil {
		return err
	}
	art.cacheTxn.Commit()
	return nil
}<commit_msg>SNK 292: Fix formatting.<commit_after>// Copyright (c) 2017 Cisco and/or its affiliates.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at:
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//go:generate binapi-generator --input-file=/usr/share/vpp/api/session.api.json --output-dir=bin_api

package vpptcp

import (
	"bytes"
	"errors"
	"net"
	"strings"

	govpp "git.fd.io/govpp.git/api"
	"github.com/ligato/cn-infra/logging"

	"github.com/contiv/vpp/plugins/contiv"
	podmodel "github.com/contiv/vpp/plugins/ksr/model/pod"
	"github.com/contiv/vpp/plugins/policy/renderer"
	"github.com/contiv/vpp/plugins/policy/renderer/vpptcp/bin_api/session"
	"github.com/contiv/vpp/plugins/policy/renderer/vpptcp/cache"
)

// SessionRuleTagPrefix is used to tag session rules created for the implementation
// of K8s policies.
const SessionRuleTagPrefix = "contiv/vpp-policy-"

// Renderer renders Contiv Rules into VPP Session rules.
// Session rules are configured into VPP directly via binary API using govpp.
type Renderer struct {
	Deps

	cache *cache.SessionRuleCache
}

// Deps lists dependencies of Renderer.
type Deps struct {
	Log        logging.Logger
	LogFactory logging.LogFactory /* optional */
	Contiv     contiv.API         /* for GetNsIndex() */
	GoVPPChan  *govpp.Channel
}

// RendererTxn represents a single transaction of Renderer.
type RendererTxn struct {
	cacheTxn cache.Txn
	renderer *Renderer
	resync   bool
}

// Init initializes the VPPTCP Renderer.
func (r *Renderer) Init() error {
	// Init the cache
	r.cache = &cache.SessionRuleCache{}
	if r.LogFactory != nil {
		r.cache.Log = r.LogFactory.NewLogger("-vpptcpCache")
		r.cache.Log.SetLevel(logging.DebugLevel)
	} else {
		r.cache.Log = r.Log
	}
	r.cache.Init(r.dumpRules, SessionRuleTagPrefix)
	return nil
}

// NewTxn starts a new transaction. The rendering executes only after Commit()
// is called. Rollback is not yet supported however.
// If <resync> is enabled, the supplied configuration will completely
// replace the existing one. Otherwise, the change is performed incrementally,
// i.e. interfaces not mentioned in the transaction are left unaffected.
func (r *Renderer) NewTxn(resync bool) renderer.Txn {
	return &RendererTxn{cacheTxn: r.cache.NewTxn(resync), renderer: r, resync: resync}
}

// dumpRules queries VPP to get the currently installed set of rules.
func (r *Renderer) dumpRules() ([]*cache.SessionRule, error) {
	rules := []*cache.SessionRule{}
	// Send request to dump all installed rules.
	req := &session.SessionRulesDump{}
	reqContext := r.GoVPPChan.SendMultiRequest(req)
	// Receive details about each installed rule.
	for {
		msg := &session.SessionRulesDetails{}
		stop, err := reqContext.ReceiveReply(msg)
		if err != nil {
			r.Log.WithField("err", err).Error("Failed to get a session rule details")
			return rules, err
		}
		if stop {
			break
		}
		tagLen := bytes.IndexByte(msg.Tag, 0)
		tag := string(msg.Tag[:tagLen])
		if !strings.HasPrefix(tag, SessionRuleTagPrefix) {
			// Skip rules not installed by this renderer.
			continue
		}
		sessionRule := &cache.SessionRule{
			TransportProto: msg.TransportProto,
			IsIP4:          msg.IsIP4,
			LclPlen:        msg.LclPlen,
			RmtPlen:        msg.RmtPlen,
			LclPort:        msg.LclPort,
			RmtPort:        msg.RmtPort,
			ActionIndex:    msg.ActionIndex,
			AppnsIndex:     msg.AppnsIndex,
			Scope:          msg.Scope,
		}
		copy(sessionRule.LclIP[:], msg.LclIP)
		copy(sessionRule.RmtIP[:], msg.RmtIP)
		copy(sessionRule.Tag[:], msg.Tag)
		rules = append(rules, sessionRule)
	}

	r.Log.WithFields(logging.Fields{
		"rules": rules,
	}).Debug("VPPTCP Renderer dumpRules()")
	return rules, nil
}

// makeSessionRuleAddDelReq creates an instance of SessionRuleAddDel bin API
// request.
func (r *Renderer) makeSessionRuleAddDelReq(rule *cache.SessionRule, add bool) *govpp.VppRequest {
	isAdd := uint8(0)
	if add {
		isAdd = uint8(1)
	}
	msg := &session.SessionRuleAddDel{
		TransportProto: rule.TransportProto,
		IsIP4:          rule.IsIP4,
		LclIP:          rule.LclIP[:],
		LclPlen:        rule.LclPlen,
		RmtIP:          rule.RmtIP[:],
		RmtPlen:        rule.RmtPlen,
		LclPort:        rule.LclPort,
		RmtPort:        rule.RmtPort,
		ActionIndex:    rule.ActionIndex,
		IsAdd:          isAdd,
		AppnsIndex:     rule.AppnsIndex,
		Scope:          rule.Scope,
		Tag:            rule.Tag[:],
	}
	r.Log.WithField("msg:", *msg).Debug("Sending BIN API Request to VPP.")
	req := &govpp.VppRequest{
		Message: msg,
	}
	return req
}

// updateRules adds/removes selected rules to/from VPP Session rule tables.
func (r *Renderer) updateRules(add, remove []*cache.SessionRule) error {
	const errMsg = "failed to update VPPTCP session rule"

	// Prepare VPP requests.
	requests := []*govpp.VppRequest{}
	for _, delRule := range remove {
		requests = append(requests, r.makeSessionRuleAddDelReq(delRule, false))
	}
	for _, addRule := range add {
		requests = append(requests, r.makeSessionRuleAddDelReq(addRule, true))
	}

	// Send all VPP requests at once.
	for _, req := range requests {
		r.GoVPPChan.ReqChan <- req
	}

	// Wait for all VPP responses.
	var wasError error
	for i := 0; i < len(requests); i++ {
		reply := <-r.GoVPPChan.ReplyChan
		if reply.Error != nil {
			r.Log.WithField("err", reply.Error).Error(errMsg)
			wasError = reply.Error
			break
		}
		msg := &session.SessionRuleAddDelReply{}
		err := r.GoVPPChan.MsgDecoder.DecodeMsg(reply.Data, msg)
		if err != nil {
			r.Log.WithField("err", err).Error(errMsg)
			wasError = err
			break
		}
		if msg.Retval != 0 {
			r.Log.WithField("retval", msg.Retval).Error(errMsg)
			wasError = errors.New(errMsg)
			break
		}
	}
	return wasError
}

// Render applies the set of ingress & egress rules for a given pod.
// The existing rules are replaced.
// Te actual change is performed only after the commit.
func (art *RendererTxn) Render(pod podmodel.ID, podIP *net.IPNet, ingress []*renderer.ContivRule, egress []*renderer.ContivRule) renderer.Txn {
	// Get the target namespace index.
	nsIndex, found := art.renderer.Contiv.GetNsIndex(pod.Namespace, pod.Name)
	if !found {
		art.renderer.Log.WithField("pod", pod).Warn("Unable to get the namespace index of the Pod")
		return art
	}

	art.renderer.Log.WithFields(logging.Fields{
		"pod":     pod,
		"nsIndex": nsIndex,
		"ingress": ingress,
		"egress":  egress,
	}).Debug("VPPTCP RendererTxn Render()")

	// Add the rules into the transaction.
	art.cacheTxn.Update(nsIndex, podIP, ingress, egress)
	return art
}

// Commit proceeds with the rendering. A minimalistic set of changes is
// calculated using ContivRuleCache and applied via binary API using govpp.
func (art *RendererTxn) Commit() error {
	added, removed, err := art.cacheTxn.Changes()
	if err != nil {
		return err
	}
	if len(added) == 0 && len(removed) == 0 {
		art.renderer.Log.Debug("No changes to be rendered in the transaction")
		return nil
	}
	err = art.renderer.updateRules(added, removed)
	if err != nil {
		return err
	}
	art.cacheTxn.Commit()
	return nil
}
<|endoftext|>


<commit_before>// Package google implements logging in through Google's OpenID Connect provider.
package google

import (
	"context"
	"errors"
	"fmt"
	"io/ioutil"
	"net/http"
	"time"

	"github.com/coreos/go-oidc"
	"golang.org/x/oauth2"

	"github.com/dexidp/dex/connector"
	"github.com/dexidp/dex/pkg/log"
	"golang.org/x/oauth2/google"
	"google.golang.org/api/admin/directory/v1"
)

const (
	issuerURL = "https://accounts.google.com"
)

// Config holds configuration options for OpenID Connect logins.
type Config struct {
	ClientID     string `json:"clientID"`
	ClientSecret string `json:"clientSecret"`
	RedirectURI  string `json:"redirectURI"`

	Scopes []string `json:"scopes"` // defaults to "profile" and "email"

	// Optional list of whitelisted domains
	// If this field is nonempty, only users from a listed domain will be allowed to log in
	HostedDomains []string `json:"hostedDomains"`

	// Optional path to service account json
	// If nonempty, and groups claim is made, will use authentication from file to
	// check groups with the admin directory api
	ServiceAccountFilePath string `json:"serviceAccountFilePath"`

	// Required if ServiceAccountFilePath
	// The email of a GSuite super user which the service account will impersonate
	// when listing groups
	AdminEmail string
}

// Open returns a connector which can be used to login users through an upstream
// OpenID Connect provider.
func (c *Config) Open(id string, logger log.Logger) (conn connector.Connector, err error) {
	ctx, cancel := context.WithCancel(context.Background())

	provider, err := oidc.NewProvider(ctx, issuerURL)
	if err != nil {
		cancel()
		return nil, fmt.Errorf("failed to get provider: %v", err)
	}

	scopes := []string{oidc.ScopeOpenID}
	if len(c.Scopes) > 0 {
		scopes = append(scopes, c.Scopes...)
	} else {
		scopes = append(scopes, "profile", "email")
	}

	clientID := c.ClientID
	return &googleConnector{
		redirectURI: c.RedirectURI,
		oauth2Config: &oauth2.Config{
			ClientID:     clientID,
			ClientSecret: c.ClientSecret,
			Endpoint:     provider.Endpoint(),
			Scopes:       scopes,
			RedirectURL:  c.RedirectURI,
		},
		verifier: provider.Verifier(
			&oidc.Config{ClientID: clientID},
		),
		logger:                 logger,
		cancel:                 cancel,
		hostedDomains:          c.HostedDomains,
		serviceAccountFilePath: c.ServiceAccountFilePath,
		adminEmail:             c.AdminEmail,
	}, nil
}

var (
	_ connector.CallbackConnector = (*googleConnector)(nil)
	_ connector.RefreshConnector  = (*googleConnector)(nil)
)

type googleConnector struct {
	redirectURI            string
	oauth2Config           *oauth2.Config
	verifier               *oidc.IDTokenVerifier
	ctx                    context.Context
	cancel                 context.CancelFunc
	logger                 log.Logger
	hostedDomains          []string
	serviceAccountFilePath string
	adminEmail             string
}

func (c *googleConnector) Close() error {
	c.cancel()
	return nil
}

func (c *googleConnector) LoginURL(s connector.Scopes, callbackURL, state string) (string, error) {
	if c.redirectURI != callbackURL {
		return "", fmt.Errorf("expected callback URL %q did not match the URL in the config %q", callbackURL, c.redirectURI)
	}

	var opts []oauth2.AuthCodeOption
	if len(c.hostedDomains) > 0 {
		preferredDomain := c.hostedDomains[0]
		if len(c.hostedDomains) > 1 {
			preferredDomain = "*"
		}
		opts = append(opts, oauth2.SetAuthURLParam("hd", preferredDomain))
	}

	if s.OfflineAccess {
		opts = append(opts, oauth2.AccessTypeOffline, oauth2.SetAuthURLParam("prompt", "consent"))
	}
	return c.oauth2Config.AuthCodeURL(state, opts...), nil
}

type oauth2Error struct {
	error            string
	errorDescription string
}

func (e *oauth2Error) Error() string {
	if e.errorDescription == "" {
		return e.error
	}
	return e.error + ": " + e.errorDescription
}

func (c *googleConnector) HandleCallback(s connector.Scopes, r *http.Request) (identity connector.Identity, err error) {
	q := r.URL.Query()
	if errType := q.Get("error"); errType != "" {
		return identity, &oauth2Error{errType, q.Get("error_description")}
	}
	token, err := c.oauth2Config.Exchange(r.Context(), q.Get("code"))
	if err != nil {
		return identity, fmt.Errorf("google: failed to get token: %v", err)
	}

	return c.createIdentity(r.Context(), identity, s, token)
}

// Refresh is implemented for backwards compatibility, even though it's a no-op.
func (c *googleConnector) Refresh(ctx context.Context, s connector.Scopes, identity connector.Identity) (connector.Identity, error) {
	t := &oauth2.Token{
		RefreshToken: string(identity.ConnectorData),
		Expiry:       time.Now().Add(-time.Hour),
	}
	token, err := c.oauth2Config.TokenSource(ctx, t).Token()
	if err != nil {
		return identity, fmt.Errorf("google: failed to get token: %v", err)
	}

	return c.createIdentity(ctx, identity, s, token)
}

func (c *googleConnector) createIdentity(ctx context.Context, identity connector.Identity, s connector.Scopes, token *oauth2.Token) (connector.Identity, error) {
	rawIDToken, ok := token.Extra("id_token").(string)
	if !ok {
		return identity, errors.New("google: no id_token in token response")
	}
	idToken, err := c.verifier.Verify(ctx, rawIDToken)
	if err != nil {
		return identity, fmt.Errorf("google: failed to verify ID Token: %v", err)
	}

	var claims struct {
		Username      string `json:"name"`
		Email         string `json:"email"`
		EmailVerified bool   `json:"email_verified"`
		HostedDomain  string `json:"hd"`
	}
	if err := idToken.Claims(&claims); err != nil {
		return identity, fmt.Errorf("oidc: failed to decode claims: %v", err)
	}

	if len(c.hostedDomains) > 0 {
		found := false
		for _, domain := range c.hostedDomains {
			if claims.HostedDomain == domain {
				found = true
				break
			}
		}

		if !found {
			return identity, fmt.Errorf("oidc: unexpected hd claim %v", claims.HostedDomain)
		}
	}

	var groups []string
	if s.Groups {
		groups, err = c.getGroups(claims.Email)
		if err != nil {
			return identity, fmt.Errorf("google: could not retrieve groups: %v", err)
		}
	}

	identity = connector.Identity{
		UserID:        idToken.Subject,
		Username:      claims.Username,
		Email:         claims.Email,
		EmailVerified: claims.EmailVerified,
		ConnectorData: []byte(token.RefreshToken),
		Groups:        groups,
	}
	return identity, nil
}

func (c *googleConnector) getGroups(email string) ([]string, error) {
	srv, err := createDirectoryService(c.serviceAccountFilePath, c.adminEmail)
	if err != nil {
		return nil, fmt.Errorf("could not create directory service: %v", err)
	}

	groupsList, err := srv.Groups.List().UserKey(email).Do()
	if err != nil {
		return nil, fmt.Errorf("could not list groups: %v", err)
	}

	var userGroups []string
	for _, group := range groupsList.Groups {
		userGroups = append(userGroups, group.Email)
	}

	return userGroups, nil
}

func createDirectoryService(serviceAccountFilePath string, email string) (*admin.Service, error) {
	jsonCredentials, err := ioutil.ReadFile(serviceAccountFilePath)
	if err != nil {
		return nil, fmt.Errorf("error reading credentials from file: %v", err)
	}

	config, err := google.JWTConfigFromJSON(jsonCredentials, admin.AdminDirectoryGroupReadonlyScope)
	if err != nil {
		return nil, fmt.Errorf("unable to parse client secret file to config: %v", err)
	}

	config.Subject = email

	ctx := context.Background()
	client := config.Client(ctx)

	srv, err := admin.New(client)
	if err != nil {
		return nil, fmt.Errorf("unable to create directory service %v", err)
	}
	return srv, nil
}
<commit_msg>Check config before getting groups<commit_after>// Package google implements logging in through Google's OpenID Connect provider.
package google

import (
	"context"
	"errors"
	"fmt"
	"io/ioutil"
	"net/http"
	"time"

	"github.com/coreos/go-oidc"
	"golang.org/x/oauth2"

	"github.com/dexidp/dex/connector"
	"github.com/dexidp/dex/pkg/log"
	"golang.org/x/oauth2/google"
	"google.golang.org/api/admin/directory/v1"
)

const (
	issuerURL = "https://accounts.google.com"
)

// Config holds configuration options for OpenID Connect logins.
type Config struct {
	ClientID     string `json:"clientID"`
	ClientSecret string `json:"clientSecret"`
	RedirectURI  string `json:"redirectURI"`

	Scopes []string `json:"scopes"` // defaults to "profile" and "email"

	// Optional list of whitelisted domains
	// If this field is nonempty, only users from a listed domain will be allowed to log in
	HostedDomains []string `json:"hostedDomains"`

	// Optional path to service account json
	// If nonempty, and groups claim is made, will use authentication from file to
	// check groups with the admin directory api
	ServiceAccountFilePath string `json:"serviceAccountFilePath"`

	// Required if ServiceAccountFilePath
	// The email of a GSuite super user which the service account will impersonate
	// when listing groups
	AdminEmail string
}

// Open returns a connector which can be used to login users through an upstream
// OpenID Connect provider.
func (c *Config) Open(id string, logger log.Logger) (conn connector.Connector, err error) {
	ctx, cancel := context.WithCancel(context.Background())

	provider, err := oidc.NewProvider(ctx, issuerURL)
	if err != nil {
		cancel()
		return nil, fmt.Errorf("failed to get provider: %v", err)
	}

	scopes := []string{oidc.ScopeOpenID}
	if len(c.Scopes) > 0 {
		scopes = append(scopes, c.Scopes...)
	} else {
		scopes = append(scopes, "profile", "email")
	}

	clientID := c.ClientID
	return &googleConnector{
		redirectURI: c.RedirectURI,
		oauth2Config: &oauth2.Config{
			ClientID:     clientID,
			ClientSecret: c.ClientSecret,
			Endpoint:     provider.Endpoint(),
			Scopes:       scopes,
			RedirectURL:  c.RedirectURI,
		},
		verifier: provider.Verifier(
			&oidc.Config{ClientID: clientID},
		),
		logger:                 logger,
		cancel:                 cancel,
		hostedDomains:          c.HostedDomains,
		serviceAccountFilePath: c.ServiceAccountFilePath,
		adminEmail:             c.AdminEmail,
	}, nil
}

var (
	_ connector.CallbackConnector = (*googleConnector)(nil)
	_ connector.RefreshConnector  = (*googleConnector)(nil)
)

type googleConnector struct {
	redirectURI            string
	oauth2Config           *oauth2.Config
	verifier               *oidc.IDTokenVerifier
	ctx                    context.Context
	cancel                 context.CancelFunc
	logger                 log.Logger
	hostedDomains          []string
	serviceAccountFilePath string
	adminEmail             string
}

func (c *googleConnector) Close() error {
	c.cancel()
	return nil
}

func (c *googleConnector) LoginURL(s connector.Scopes, callbackURL, state string) (string, error) {
	if c.redirectURI != callbackURL {
		return "", fmt.Errorf("expected callback URL %q did not match the URL in the config %q", callbackURL, c.redirectURI)
	}

	var opts []oauth2.AuthCodeOption
	if len(c.hostedDomains) > 0 {
		preferredDomain := c.hostedDomains[0]
		if len(c.hostedDomains) > 1 {
			preferredDomain = "*"
		}
		opts = append(opts, oauth2.SetAuthURLParam("hd", preferredDomain))
	}

	if s.OfflineAccess {
		opts = append(opts, oauth2.AccessTypeOffline, oauth2.SetAuthURLParam("prompt", "consent"))
	}
	return c.oauth2Config.AuthCodeURL(state, opts...), nil
}

type oauth2Error struct {
	error            string
	errorDescription string
}

func (e *oauth2Error) Error() string {
	if e.errorDescription == "" {
		return e.error
	}
	return e.error + ": " + e.errorDescription
}

func (c *googleConnector) HandleCallback(s connector.Scopes, r *http.Request) (identity connector.Identity, err error) {
	q := r.URL.Query()
	if errType := q.Get("error"); errType != "" {
		return identity, &oauth2Error{errType, q.Get("error_description")}
	}
	token, err := c.oauth2Config.Exchange(r.Context(), q.Get("code"))
	if err != nil {
		return identity, fmt.Errorf("google: failed to get token: %v", err)
	}

	return c.createIdentity(r.Context(), identity, s, token)
}

// Refresh is implemented for backwards compatibility, even though it's a no-op.
func (c *googleConnector) Refresh(ctx context.Context, s connector.Scopes, identity connector.Identity) (connector.Identity, error) {
	t := &oauth2.Token{
		RefreshToken: string(identity.ConnectorData),
		Expiry:       time.Now().Add(-time.Hour),
	}
	token, err := c.oauth2Config.TokenSource(ctx, t).Token()
	if err != nil {
		return identity, fmt.Errorf("google: failed to get token: %v", err)
	}

	return c.createIdentity(ctx, identity, s, token)
}

func (c *googleConnector) createIdentity(ctx context.Context, identity connector.Identity, s connector.Scopes, token *oauth2.Token) (connector.Identity, error) {
	rawIDToken, ok := token.Extra("id_token").(string)
	if !ok {
		return identity, errors.New("google: no id_token in token response")
	}
	idToken, err := c.verifier.Verify(ctx, rawIDToken)
	if err != nil {
		return identity, fmt.Errorf("google: failed to verify ID Token: %v", err)
	}

	var claims struct {
		Username      string `json:"name"`
		Email         string `json:"email"`
		EmailVerified bool   `json:"email_verified"`
		HostedDomain  string `json:"hd"`
	}
	if err := idToken.Claims(&claims); err != nil {
		return identity, fmt.Errorf("oidc: failed to decode claims: %v", err)
	}

	if len(c.hostedDomains) > 0 {
		found := false
		for _, domain := range c.hostedDomains {
			if claims.HostedDomain == domain {
				found = true
				break
			}
		}

		if !found {
			return identity, fmt.Errorf("oidc: unexpected hd claim %v", claims.HostedDomain)
		}
	}

	var groups []string
	if s.Groups && c.adminEmail != "" && c.serviceAccountFilePath != "" {
		groups, err = c.getGroups(claims.Email)
		if err != nil {
			return identity, fmt.Errorf("google: could not retrieve groups: %v", err)
		}
	}

	identity = connector.Identity{
		UserID:        idToken.Subject,
		Username:      claims.Username,
		Email:         claims.Email,
		EmailVerified: claims.EmailVerified,
		ConnectorData: []byte(token.RefreshToken),
		Groups:        groups,
	}
	return identity, nil
}

func (c *googleConnector) getGroups(email string) ([]string, error) {
	srv, err := createDirectoryService(c.serviceAccountFilePath, c.adminEmail)
	if err != nil {
		return nil, fmt.Errorf("could not create directory service: %v", err)
	}

	groupsList, err := srv.Groups.List().UserKey(email).Do()
	if err != nil {
		return nil, fmt.Errorf("could not list groups: %v", err)
	}

	var userGroups []string
	for _, group := range groupsList.Groups {
		userGroups = append(userGroups, group.Email)
	}

	return userGroups, nil
}

func createDirectoryService(serviceAccountFilePath string, email string) (*admin.Service, error) {
	jsonCredentials, err := ioutil.ReadFile(serviceAccountFilePath)
	if err != nil {
		return nil, fmt.Errorf("error reading credentials from file: %v", err)
	}

	config, err := google.JWTConfigFromJSON(jsonCredentials, admin.AdminDirectoryGroupReadonlyScope)
	if err != nil {
		return nil, fmt.Errorf("unable to parse client secret file to config: %v", err)
	}

	config.Subject = email

	ctx := context.Background()
	client := config.Client(ctx)

	srv, err := admin.New(client)
	if err != nil {
		return nil, fmt.Errorf("unable to create directory service %v", err)
	}
	return srv, nil
}
<|endoftext|>