<commit_before>package internal

import (
	"bytes"
	"fmt"
	"net"
	"os"
	"os/signal"
	"time"

	"github.com/tendermint/tendermint/crypto/tmhash"

	"github.com/tendermint/tendermint/crypto/ed25519"
	"github.com/tendermint/tendermint/privval"
	"github.com/tendermint/tendermint/state"

	"github.com/tendermint/tendermint/libs/log"
	tmnet "github.com/tendermint/tendermint/libs/net"
	tmos "github.com/tendermint/tendermint/libs/os"
	tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
	"github.com/tendermint/tendermint/types"
)

// Test harness error codes (which act as exit codes when the test harness fails).
const (
	NoError                    int = iota // 0
	ErrInvalidParameters                  // 1
	ErrMaxAcceptRetriesReached            // 2
	ErrFailedToLoadGenesisFile            // 3
	ErrFailedToCreateListener             // 4
	ErrFailedToStartListener              // 5
	ErrInterrupted                        // 6
	ErrOther                              // 7
	ErrTestPublicKeyFailed                // 8
	ErrTestSignProposalFailed             // 9
	ErrTestSignVoteFailed                 // 10
)

var voteTypes = []tmproto.SignedMsgType{tmproto.PrevoteType, tmproto.PrecommitType}

// TestHarnessError allows us to keep track of which exit code should be used
// when exiting the main program.
type TestHarnessError struct {
	Code int    // The exit code to return
	Err  error  // The original error
	Info string // Any additional information
}

var _ error = (*TestHarnessError)(nil)

// TestHarness allows for testing of a remote signer to ensure compatibility
// with this version of Tendermint.
type TestHarness struct {
	addr             string
	signerClient     *privval.SignerClient
	fpv              *privval.FilePV
	chainID          string
	acceptRetries    int
	logger           log.Logger
	exitWhenComplete bool
	exitCode         int
}

// TestHarnessConfig provides configuration to set up a remote signer test
// harness.
type TestHarnessConfig struct {
	BindAddr string

	KeyFile     string
	StateFile   string
	GenesisFile string

	AcceptDeadline time.Duration
	ConnDeadline   time.Duration
	AcceptRetries  int

	SecretConnKey ed25519.PrivKey

	ExitWhenComplete bool // Whether or not to call os.Exit when the harness has completed.
}

// timeoutError can be used to check if an error returned from the netp package
// was due to a timeout.
type timeoutError interface {
	Timeout() bool
}

// NewTestHarness will load Tendermint data from the given files (including
// validator public/private keypairs and chain details) and create a new
// harness.
func NewTestHarness(logger log.Logger, cfg TestHarnessConfig) (*TestHarness, error) {
	keyFile := ExpandPath(cfg.KeyFile)
	stateFile := ExpandPath(cfg.StateFile)
	logger.Info("Loading private validator configuration", "keyFile", keyFile, "stateFile", stateFile)
	// NOTE: LoadFilePV ultimately calls os.Exit on failure. No error will be
	// returned if this call fails.
	fpv := privval.LoadFilePV(keyFile, stateFile)

	genesisFile := ExpandPath(cfg.GenesisFile)
	logger.Info("Loading chain ID from genesis file", "genesisFile", genesisFile)
	st, err := state.MakeGenesisDocFromFile(genesisFile)
	if err != nil {
		return nil, newTestHarnessError(ErrFailedToLoadGenesisFile, err, genesisFile)
	}
	logger.Info("Loaded genesis file", "chainID", st.ChainID)

	spv, err := newTestHarnessListener(logger, cfg)
	if err != nil {
		return nil, newTestHarnessError(ErrFailedToCreateListener, err, "")
	}

	signerClient, err := privval.NewSignerClient(spv, st.ChainID)
	if err != nil {
		return nil, newTestHarnessError(ErrFailedToCreateListener, err, "")
	}

	return &TestHarness{
		addr:             cfg.BindAddr,
		signerClient:     signerClient,
		fpv:              fpv,
		chainID:          st.ChainID,
		acceptRetries:    cfg.AcceptRetries,
		logger:           logger,
		exitWhenComplete: cfg.ExitWhenComplete,
		exitCode:         0,
	}, nil
}

// Run will execute the tests associated with this test harness. The intention
// here is to call this from one's `main` function, as the way it succeeds or
// fails at present is to call os.Exit() with an exit code related to the error
// that caused the tests to fail, or exit code 0 on success.
func (th *TestHarness) Run() {
	c := make(chan os.Signal, 1)
	signal.Notify(c, os.Interrupt)
	go func() {
		for sig := range c {
			th.logger.Info("Caught interrupt, terminating...", "sig", sig)
			th.Shutdown(newTestHarnessError(ErrInterrupted, nil, ""))
		}
	}()

	th.logger.Info("Starting test harness")
	accepted := false
	var startErr error

	for acceptRetries := th.acceptRetries; acceptRetries > 0; acceptRetries-- {
		th.logger.Info("Attempting to accept incoming connection", "acceptRetries", acceptRetries)

		if err := th.signerClient.WaitForConnection(10 * time.Millisecond); err != nil {
			// if it wasn't a timeout error
			if _, ok := err.(timeoutError); !ok {
				th.logger.Error("Failed to start listener", "err", err)
				th.Shutdown(newTestHarnessError(ErrFailedToStartListener, err, ""))
				// we need the return statements in case this is being run
				// from a unit test - otherwise this function will just die
				// when os.Exit is called
				return
			}
			startErr = err
		} else {
			th.logger.Info("Accepted external connection")
			accepted = true
			break
		}
	}
	if !accepted {
		th.logger.Error("Maximum accept retries reached", "acceptRetries", th.acceptRetries)
		th.Shutdown(newTestHarnessError(ErrMaxAcceptRetriesReached, startErr, ""))
		return
	}

	// Run the tests
	if err := th.TestPublicKey(); err != nil {
		th.Shutdown(err)
		return
	}
	if err := th.TestSignProposal(); err != nil {
		th.Shutdown(err)
		return
	}
	if err := th.TestSignVote(); err != nil {
		th.Shutdown(err)
		return
	}
	th.logger.Info("SUCCESS! All tests passed.")
	th.Shutdown(nil)
}

// TestPublicKey just validates that we can (1) fetch the public key from the
// remote signer, and (2) it matches the public key we've configured for our
// local Tendermint version.
func (th *TestHarness) TestPublicKey() error {
	th.logger.Info("TEST: Public key of remote signer")
	fpvk, err := th.fpv.GetPubKey()
	if err != nil {
		return err
	}
	th.logger.Info("Local", "pubKey", fpvk)
	sck, err := th.signerClient.GetPubKey()
	if err != nil {
		return err
	}
	th.logger.Info("Remote", "pubKey", sck)
	if !bytes.Equal(fpvk.Bytes(), sck.Bytes()) {
		th.logger.Error("FAILED: Local and remote public keys do not match")
		return newTestHarnessError(ErrTestPublicKeyFailed, nil, "")
	}
	return nil
}

// TestSignProposal makes sure the remote signer can successfully sign
// proposals.
func (th *TestHarness) TestSignProposal() error {
	th.logger.Info("TEST: Signing of proposals")
	// sha256 hash of "hash"
	hash := tmhash.Sum([]byte("hash"))
	prop := &types.Proposal{
		Type:     tmproto.ProposalType,
		Height:   100,
		Round:    0,
		POLRound: -1,
		BlockID: types.BlockID{
			Hash: hash,
			PartSetHeader: types.PartSetHeader{
				Hash:  hash,
				Total: 1000000,
			},
		},
		Timestamp: time.Now(),
	}
	p := prop.ToProto()
	propBytes := types.ProposalSignBytes(th.chainID, p)
	if err := th.signerClient.SignProposal(th.chainID, p); err != nil {
		th.logger.Error("FAILED: Signing of proposal", "err", err)
		return newTestHarnessError(ErrTestSignProposalFailed, err, "")
	}
	prop.Signature = p.Signature
	th.logger.Debug("Signed proposal", "prop", prop)
	// first check that it's a basically valid proposal
	if err := prop.ValidateBasic(); err != nil {
		th.logger.Error("FAILED: Signed proposal is invalid", "err", err)
		return newTestHarnessError(ErrTestSignProposalFailed, err, "")
	}
	sck, err := th.signerClient.GetPubKey()
	if err != nil {
		return err
	}
	// now validate the signature on the proposal
	if sck.VerifySignature(propBytes, prop.Signature) {
		th.logger.Info("Successfully validated proposal signature")
	} else {
		th.logger.Error("FAILED: Proposal signature validation failed")
		return newTestHarnessError(ErrTestSignProposalFailed, nil, "signature validation failed")
	}
	return nil
}

// TestSignVote makes sure the remote signer can successfully sign all kinds of
// votes.
func (th *TestHarness) TestSignVote() error {
	th.logger.Info("TEST: Signing of votes")
	for _, voteType := range voteTypes {
		th.logger.Info("Testing vote type", "type", voteType)
		hash := tmhash.Sum([]byte("hash"))
		vote := &types.Vote{
			Type:   voteType,
			Height: 101,
			Round:  0,
			BlockID: types.BlockID{
				Hash: hash,
				PartSetHeader: types.PartSetHeader{
					Hash:  hash,
					Total: 1000000,
				},
			},
			ValidatorIndex:   0,
			ValidatorAddress: tmhash.SumTruncated([]byte("addr")),
			Timestamp:        time.Now(),
		}
		v := vote.ToProto()
		voteBytes := types.VoteSignBytes(th.chainID, v)
		// sign the vote
		if err := th.signerClient.SignVote(th.chainID, v); err != nil {
			th.logger.Error("FAILED: Signing of vote", "err", err)
			return newTestHarnessError(ErrTestSignVoteFailed, err, fmt.Sprintf("voteType=%d", voteType))
		}
		vote.Signature = v.Signature
		th.logger.Debug("Signed vote", "vote", vote)
		// validate the contents of the vote
		if err := vote.ValidateBasic(); err != nil {
			th.logger.Error("FAILED: Signed vote is invalid", "err", err)
			return newTestHarnessError(ErrTestSignVoteFailed, err, fmt.Sprintf("voteType=%d", voteType))
		}
		sck, err := th.signerClient.GetPubKey()
		if err != nil {
			return err
		}

		// now validate the signature on the proposal
		if sck.VerifySignature(voteBytes, vote.Signature) {
			th.logger.Info("Successfully validated vote signature", "type", voteType)
		} else {
			th.logger.Error("FAILED: Vote signature validation failed", "type", voteType)
			return newTestHarnessError(ErrTestSignVoteFailed, nil, "signature validation failed")
		}
	}
	return nil
}

// Shutdown will kill the test harness and attempt to close all open sockets
// gracefully. If the supplied error is nil, it is assumed that the exit code
// should be 0. If err is not nil, it will exit with an exit code related to the
// error.
func (th *TestHarness) Shutdown(err error) {
	var exitCode int

	if err == nil {
		exitCode = NoError
	} else if therr, ok := err.(*TestHarnessError); ok {
		exitCode = therr.Code
	} else {
		exitCode = ErrOther
	}
	th.exitCode = exitCode

	// in case sc.Stop() takes too long
	if th.exitWhenComplete {
		go func() {
			time.Sleep(time.Duration(5) * time.Second)
			th.logger.Error("Forcibly exiting program after timeout")
			os.Exit(exitCode)
		}()
	}

	err = th.signerClient.Close()
	if err != nil {
		th.logger.Error("Failed to cleanly stop listener: %s", err.Error())
	}

	if th.exitWhenComplete {
		os.Exit(exitCode)
	}
}

// newTestHarnessListener creates our client instance which we will use for testing.
func newTestHarnessListener(logger log.Logger, cfg TestHarnessConfig) (*privval.SignerListenerEndpoint, error) {
	proto, addr := tmnet.ProtocolAndAddress(cfg.BindAddr)
	if proto == "unix" {
		// make sure the socket doesn't exist - if so, try to delete it
		if tmos.FileExists(addr) {
			if err := os.Remove(addr); err != nil {
				logger.Error("Failed to remove existing Unix domain socket", "addr", addr)
				return nil, err
			}
		}
	}
	ln, err := net.Listen(proto, addr)
	if err != nil {
		return nil, err
	}
	logger.Info("Listening", "proto", proto, "addr", addr)
	var svln net.Listener
	switch proto {
	case "unix":
		unixLn := privval.NewUnixListener(ln)
		privval.UnixListenerTimeoutAccept(cfg.AcceptDeadline)(unixLn)
		privval.UnixListenerTimeoutReadWrite(cfg.ConnDeadline)(unixLn)
		svln = unixLn
	case "tcp":
		tcpLn := privval.NewTCPListener(ln, cfg.SecretConnKey)
		privval.TCPListenerTimeoutAccept(cfg.AcceptDeadline)(tcpLn)
		privval.TCPListenerTimeoutReadWrite(cfg.ConnDeadline)(tcpLn)
		logger.Info("Resolved TCP address for listener", "addr", tcpLn.Addr())
		svln = tcpLn
	default:
		logger.Error("Unsupported protocol (must be unix:// or tcp://)", "proto", proto)
		return nil, newTestHarnessError(ErrInvalidParameters, nil, fmt.Sprintf("Unsupported protocol: %s", proto))
	}
	return privval.NewSignerListenerEndpoint(logger, svln), nil
}

func newTestHarnessError(code int, err error, info string) *TestHarnessError {
	return &TestHarnessError{
		Code: code,
		Err:  err,
		Info: info,
	}
}

func (e *TestHarnessError) Error() string {
	var msg string
	switch e.Code {
	case ErrInvalidParameters:
		msg = "Invalid parameters supplied to application"
	case ErrMaxAcceptRetriesReached:
		msg = "Maximum accept retries reached"
	case ErrFailedToLoadGenesisFile:
		msg = "Failed to load genesis file"
	case ErrFailedToCreateListener:
		msg = "Failed to create listener"
	case ErrFailedToStartListener:
		msg = "Failed to start listener"
	case ErrInterrupted:
		msg = "Interrupted"
	case ErrTestPublicKeyFailed:
		msg = "Public key validation test failed"
	case ErrTestSignProposalFailed:
		msg = "Proposal signing validation test failed"
	case ErrTestSignVoteFailed:
		msg = "Vote signing validation test failed"
	default:
		msg = "Unknown error"
	}
	if len(e.Info) > 0 {
		msg = fmt.Sprintf("%s: %s", msg, e.Info)
	}
	if e.Err != nil {
		msg = fmt.Sprintf("%s (original error: %s)", msg, e.Err.Error())
	}
	return msg
}
<commit_msg>tools/tm-signer-harness: fix listener leak in newTestHarnessListener() (#5850)<commit_after>package internal

import (
	"bytes"
	"fmt"
	"net"
	"os"
	"os/signal"
	"time"

	"github.com/tendermint/tendermint/crypto/tmhash"

	"github.com/tendermint/tendermint/crypto/ed25519"
	"github.com/tendermint/tendermint/privval"
	"github.com/tendermint/tendermint/state"

	"github.com/tendermint/tendermint/libs/log"
	tmnet "github.com/tendermint/tendermint/libs/net"
	tmos "github.com/tendermint/tendermint/libs/os"
	tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
	"github.com/tendermint/tendermint/types"
)

// Test harness error codes (which act as exit codes when the test harness fails).
const (
	NoError                    int = iota // 0
	ErrInvalidParameters                  // 1
	ErrMaxAcceptRetriesReached            // 2
	ErrFailedToLoadGenesisFile            // 3
	ErrFailedToCreateListener             // 4
	ErrFailedToStartListener              // 5
	ErrInterrupted                        // 6
	ErrOther                              // 7
	ErrTestPublicKeyFailed                // 8
	ErrTestSignProposalFailed             // 9
	ErrTestSignVoteFailed                 // 10
)

var voteTypes = []tmproto.SignedMsgType{tmproto.PrevoteType, tmproto.PrecommitType}

// TestHarnessError allows us to keep track of which exit code should be used
// when exiting the main program.
type TestHarnessError struct {
	Code int    // The exit code to return
	Err  error  // The original error
	Info string // Any additional information
}

var _ error = (*TestHarnessError)(nil)

// TestHarness allows for testing of a remote signer to ensure compatibility
// with this version of Tendermint.
type TestHarness struct {
	addr             string
	signerClient     *privval.SignerClient
	fpv              *privval.FilePV
	chainID          string
	acceptRetries    int
	logger           log.Logger
	exitWhenComplete bool
	exitCode         int
}

// TestHarnessConfig provides configuration to set up a remote signer test
// harness.
type TestHarnessConfig struct {
	BindAddr string

	KeyFile     string
	StateFile   string
	GenesisFile string

	AcceptDeadline time.Duration
	ConnDeadline   time.Duration
	AcceptRetries  int

	SecretConnKey ed25519.PrivKey

	ExitWhenComplete bool // Whether or not to call os.Exit when the harness has completed.
}

// timeoutError can be used to check if an error returned from the netp package
// was due to a timeout.
type timeoutError interface {
	Timeout() bool
}

// NewTestHarness will load Tendermint data from the given files (including
// validator public/private keypairs and chain details) and create a new
// harness.
func NewTestHarness(logger log.Logger, cfg TestHarnessConfig) (*TestHarness, error) {
	keyFile := ExpandPath(cfg.KeyFile)
	stateFile := ExpandPath(cfg.StateFile)
	logger.Info("Loading private validator configuration", "keyFile", keyFile, "stateFile", stateFile)
	// NOTE: LoadFilePV ultimately calls os.Exit on failure. No error will be
	// returned if this call fails.
	fpv := privval.LoadFilePV(keyFile, stateFile)

	genesisFile := ExpandPath(cfg.GenesisFile)
	logger.Info("Loading chain ID from genesis file", "genesisFile", genesisFile)
	st, err := state.MakeGenesisDocFromFile(genesisFile)
	if err != nil {
		return nil, newTestHarnessError(ErrFailedToLoadGenesisFile, err, genesisFile)
	}
	logger.Info("Loaded genesis file", "chainID", st.ChainID)

	spv, err := newTestHarnessListener(logger, cfg)
	if err != nil {
		return nil, newTestHarnessError(ErrFailedToCreateListener, err, "")
	}

	signerClient, err := privval.NewSignerClient(spv, st.ChainID)
	if err != nil {
		return nil, newTestHarnessError(ErrFailedToCreateListener, err, "")
	}

	return &TestHarness{
		addr:             cfg.BindAddr,
		signerClient:     signerClient,
		fpv:              fpv,
		chainID:          st.ChainID,
		acceptRetries:    cfg.AcceptRetries,
		logger:           logger,
		exitWhenComplete: cfg.ExitWhenComplete,
		exitCode:         0,
	}, nil
}

// Run will execute the tests associated with this test harness. The intention
// here is to call this from one's `main` function, as the way it succeeds or
// fails at present is to call os.Exit() with an exit code related to the error
// that caused the tests to fail, or exit code 0 on success.
func (th *TestHarness) Run() {
	c := make(chan os.Signal, 1)
	signal.Notify(c, os.Interrupt)
	go func() {
		for sig := range c {
			th.logger.Info("Caught interrupt, terminating...", "sig", sig)
			th.Shutdown(newTestHarnessError(ErrInterrupted, nil, ""))
		}
	}()

	th.logger.Info("Starting test harness")
	accepted := false
	var startErr error

	for acceptRetries := th.acceptRetries; acceptRetries > 0; acceptRetries-- {
		th.logger.Info("Attempting to accept incoming connection", "acceptRetries", acceptRetries)

		if err := th.signerClient.WaitForConnection(10 * time.Millisecond); err != nil {
			// if it wasn't a timeout error
			if _, ok := err.(timeoutError); !ok {
				th.logger.Error("Failed to start listener", "err", err)
				th.Shutdown(newTestHarnessError(ErrFailedToStartListener, err, ""))
				// we need the return statements in case this is being run
				// from a unit test - otherwise this function will just die
				// when os.Exit is called
				return
			}
			startErr = err
		} else {
			th.logger.Info("Accepted external connection")
			accepted = true
			break
		}
	}
	if !accepted {
		th.logger.Error("Maximum accept retries reached", "acceptRetries", th.acceptRetries)
		th.Shutdown(newTestHarnessError(ErrMaxAcceptRetriesReached, startErr, ""))
		return
	}

	// Run the tests
	if err := th.TestPublicKey(); err != nil {
		th.Shutdown(err)
		return
	}
	if err := th.TestSignProposal(); err != nil {
		th.Shutdown(err)
		return
	}
	if err := th.TestSignVote(); err != nil {
		th.Shutdown(err)
		return
	}
	th.logger.Info("SUCCESS! All tests passed.")
	th.Shutdown(nil)
}

// TestPublicKey just validates that we can (1) fetch the public key from the
// remote signer, and (2) it matches the public key we've configured for our
// local Tendermint version.
func (th *TestHarness) TestPublicKey() error {
	th.logger.Info("TEST: Public key of remote signer")
	fpvk, err := th.fpv.GetPubKey()
	if err != nil {
		return err
	}
	th.logger.Info("Local", "pubKey", fpvk)
	sck, err := th.signerClient.GetPubKey()
	if err != nil {
		return err
	}
	th.logger.Info("Remote", "pubKey", sck)
	if !bytes.Equal(fpvk.Bytes(), sck.Bytes()) {
		th.logger.Error("FAILED: Local and remote public keys do not match")
		return newTestHarnessError(ErrTestPublicKeyFailed, nil, "")
	}
	return nil
}

// TestSignProposal makes sure the remote signer can successfully sign
// proposals.
func (th *TestHarness) TestSignProposal() error {
	th.logger.Info("TEST: Signing of proposals")
	// sha256 hash of "hash"
	hash := tmhash.Sum([]byte("hash"))
	prop := &types.Proposal{
		Type:     tmproto.ProposalType,
		Height:   100,
		Round:    0,
		POLRound: -1,
		BlockID: types.BlockID{
			Hash: hash,
			PartSetHeader: types.PartSetHeader{
				Hash:  hash,
				Total: 1000000,
			},
		},
		Timestamp: time.Now(),
	}
	p := prop.ToProto()
	propBytes := types.ProposalSignBytes(th.chainID, p)
	if err := th.signerClient.SignProposal(th.chainID, p); err != nil {
		th.logger.Error("FAILED: Signing of proposal", "err", err)
		return newTestHarnessError(ErrTestSignProposalFailed, err, "")
	}
	prop.Signature = p.Signature
	th.logger.Debug("Signed proposal", "prop", prop)
	// first check that it's a basically valid proposal
	if err := prop.ValidateBasic(); err != nil {
		th.logger.Error("FAILED: Signed proposal is invalid", "err", err)
		return newTestHarnessError(ErrTestSignProposalFailed, err, "")
	}
	sck, err := th.signerClient.GetPubKey()
	if err != nil {
		return err
	}
	// now validate the signature on the proposal
	if sck.VerifySignature(propBytes, prop.Signature) {
		th.logger.Info("Successfully validated proposal signature")
	} else {
		th.logger.Error("FAILED: Proposal signature validation failed")
		return newTestHarnessError(ErrTestSignProposalFailed, nil, "signature validation failed")
	}
	return nil
}

// TestSignVote makes sure the remote signer can successfully sign all kinds of
// votes.
func (th *TestHarness) TestSignVote() error {
	th.logger.Info("TEST: Signing of votes")
	for _, voteType := range voteTypes {
		th.logger.Info("Testing vote type", "type", voteType)
		hash := tmhash.Sum([]byte("hash"))
		vote := &types.Vote{
			Type:   voteType,
			Height: 101,
			Round:  0,
			BlockID: types.BlockID{
				Hash: hash,
				PartSetHeader: types.PartSetHeader{
					Hash:  hash,
					Total: 1000000,
				},
			},
			ValidatorIndex:   0,
			ValidatorAddress: tmhash.SumTruncated([]byte("addr")),
			Timestamp:        time.Now(),
		}
		v := vote.ToProto()
		voteBytes := types.VoteSignBytes(th.chainID, v)
		// sign the vote
		if err := th.signerClient.SignVote(th.chainID, v); err != nil {
			th.logger.Error("FAILED: Signing of vote", "err", err)
			return newTestHarnessError(ErrTestSignVoteFailed, err, fmt.Sprintf("voteType=%d", voteType))
		}
		vote.Signature = v.Signature
		th.logger.Debug("Signed vote", "vote", vote)
		// validate the contents of the vote
		if err := vote.ValidateBasic(); err != nil {
			th.logger.Error("FAILED: Signed vote is invalid", "err", err)
			return newTestHarnessError(ErrTestSignVoteFailed, err, fmt.Sprintf("voteType=%d", voteType))
		}
		sck, err := th.signerClient.GetPubKey()
		if err != nil {
			return err
		}

		// now validate the signature on the proposal
		if sck.VerifySignature(voteBytes, vote.Signature) {
			th.logger.Info("Successfully validated vote signature", "type", voteType)
		} else {
			th.logger.Error("FAILED: Vote signature validation failed", "type", voteType)
			return newTestHarnessError(ErrTestSignVoteFailed, nil, "signature validation failed")
		}
	}
	return nil
}

// Shutdown will kill the test harness and attempt to close all open sockets
// gracefully. If the supplied error is nil, it is assumed that the exit code
// should be 0. If err is not nil, it will exit with an exit code related to the
// error.
func (th *TestHarness) Shutdown(err error) {
	var exitCode int

	if err == nil {
		exitCode = NoError
	} else if therr, ok := err.(*TestHarnessError); ok {
		exitCode = therr.Code
	} else {
		exitCode = ErrOther
	}
	th.exitCode = exitCode

	// in case sc.Stop() takes too long
	if th.exitWhenComplete {
		go func() {
			time.Sleep(time.Duration(5) * time.Second)
			th.logger.Error("Forcibly exiting program after timeout")
			os.Exit(exitCode)
		}()
	}

	err = th.signerClient.Close()
	if err != nil {
		th.logger.Error("Failed to cleanly stop listener: %s", err.Error())
	}

	if th.exitWhenComplete {
		os.Exit(exitCode)
	}
}

// newTestHarnessListener creates our client instance which we will use for testing.
func newTestHarnessListener(logger log.Logger, cfg TestHarnessConfig) (*privval.SignerListenerEndpoint, error) {
	proto, addr := tmnet.ProtocolAndAddress(cfg.BindAddr)
	if proto == "unix" {
		// make sure the socket doesn't exist - if so, try to delete it
		if tmos.FileExists(addr) {
			if err := os.Remove(addr); err != nil {
				logger.Error("Failed to remove existing Unix domain socket", "addr", addr)
				return nil, err
			}
		}
	}
	ln, err := net.Listen(proto, addr)
	if err != nil {
		return nil, err
	}
	logger.Info("Listening", "proto", proto, "addr", addr)
	var svln net.Listener
	switch proto {
	case "unix":
		unixLn := privval.NewUnixListener(ln)
		privval.UnixListenerTimeoutAccept(cfg.AcceptDeadline)(unixLn)
		privval.UnixListenerTimeoutReadWrite(cfg.ConnDeadline)(unixLn)
		svln = unixLn
	case "tcp":
		tcpLn := privval.NewTCPListener(ln, cfg.SecretConnKey)
		privval.TCPListenerTimeoutAccept(cfg.AcceptDeadline)(tcpLn)
		privval.TCPListenerTimeoutReadWrite(cfg.ConnDeadline)(tcpLn)
		logger.Info("Resolved TCP address for listener", "addr", tcpLn.Addr())
		svln = tcpLn
	default:
		_ = ln.Close()
		logger.Error("Unsupported protocol (must be unix:// or tcp://)", "proto", proto)
		return nil, newTestHarnessError(ErrInvalidParameters, nil, fmt.Sprintf("Unsupported protocol: %s", proto))
	}
	return privval.NewSignerListenerEndpoint(logger, svln), nil
}

func newTestHarnessError(code int, err error, info string) *TestHarnessError {
	return &TestHarnessError{
		Code: code,
		Err:  err,
		Info: info,
	}
}

func (e *TestHarnessError) Error() string {
	var msg string
	switch e.Code {
	case ErrInvalidParameters:
		msg = "Invalid parameters supplied to application"
	case ErrMaxAcceptRetriesReached:
		msg = "Maximum accept retries reached"
	case ErrFailedToLoadGenesisFile:
		msg = "Failed to load genesis file"
	case ErrFailedToCreateListener:
		msg = "Failed to create listener"
	case ErrFailedToStartListener:
		msg = "Failed to start listener"
	case ErrInterrupted:
		msg = "Interrupted"
	case ErrTestPublicKeyFailed:
		msg = "Public key validation test failed"
	case ErrTestSignProposalFailed:
		msg = "Proposal signing validation test failed"
	case ErrTestSignVoteFailed:
		msg = "Vote signing validation test failed"
	default:
		msg = "Unknown error"
	}
	if len(e.Info) > 0 {
		msg = fmt.Sprintf("%s: %s", msg, e.Info)
	}
	if e.Err != nil {
		msg = fmt.Sprintf("%s (original error: %s)", msg, e.Err.Error())
	}
	return msg
}
<|endoftext|>


<commit_before>package app

import (
	"context"
	"crypto/tls"
	"encoding/json"
	"fmt"
	"net"
	"os"
	"strings"
	"sync"
	"time"

	pb "github.com/bleenco/abstruse/pb"
	"github.com/bleenco/abstruse/pkg/fs"
	"github.com/bleenco/abstruse/pkg/stats"
	"github.com/bleenco/abstruse/worker/config"
	"github.com/bleenco/abstruse/worker/docker"
	"github.com/bleenco/abstruse/worker/git"
	"github.com/golang/protobuf/ptypes/empty"
	"go.uber.org/zap"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials"
)

// Server represents gRPC server.
type Server struct {
	mu       sync.Mutex
	config   *config.Config
	id       string
	addr     string
	listener net.Listener
	server   *grpc.Server
	app      *App
	logger   *zap.SugaredLogger
	jobs     map[uint64]*pb.Job
	errch    chan error
}

// NewServer returns new gRPC server.
func NewServer(config *config.Config, logger *zap.Logger, app *App) *Server {
	return &Server{
		config: config,
		id:     config.ID,
		addr:   config.GRPC.Addr,
		app:    app,
		logger: logger.With(zap.String("type", "server")).Sugar(),
		jobs:   make(map[uint64]*pb.Job),
		errch:  make(chan error),
	}
}

// Run starts the gRPC server.
func (s *Server) Run() error {
	var err error
	grpcOpts := []grpc.ServerOption{}
	certificate, err := tls.LoadX509KeyPair(s.config.TLS.Cert, s.config.TLS.Key)
	if err != nil {
		return err
	}
	s.listener, err = net.Listen("tcp", s.config.GRPC.Addr)
	if err != nil {
		return err
	}
	creds := credentials.NewTLS(&tls.Config{
		Certificates:       []tls.Certificate{certificate},
		InsecureSkipVerify: true,
	})

	grpcOpts = append(grpcOpts, grpc.Creds(creds))
	grpcOpts = append(grpcOpts, grpc.UnaryInterceptor(s.unaryInterceptor))
	grpcOpts = append(grpcOpts, grpc.StreamInterceptor(s.streamInterceptor))
	s.server = grpc.NewServer(grpcOpts...)
	pb.RegisterAPIServer(s.server, s)
	s.logger.Infof("grpc server listening on %s", s.config.GRPC.Addr)

	return s.server.Serve(s.listener)
}

// Connect returns worker host information.
func (s *Server) Connect(ctx context.Context, in *empty.Empty) (*pb.HostInfo, error) {
	info, err := stats.GetHostStats()
	if err != nil {
		return nil, err
	}

	return &pb.HostInfo{
		Id:                   s.id,
		Addr:                 s.addr,
		Hostname:             info.Hostname,
		Uptime:               info.Uptime,
		BootTime:             info.BootTime,
		Procs:                info.Procs,
		Os:                   info.OS,
		Platform:             info.Platform,
		PlatformFamily:       info.PlatformFamily,
		PlatformVersion:      info.PlatformVersion,
		KernelVersion:        info.KernelVersion,
		KernelArch:           info.KernelArch,
		VirtualizationSystem: info.VirtualizationRole,
		VirtualizationRole:   info.VirtualizationRole,
		HostID:               info.HostID,
		MaxParallel:          uint64(s.config.Scheduler.MaxParallel),
	}, nil
}

// Usage returns stream of health data.
func (s *Server) Usage(stream pb.API_UsageServer) error {
	errch := make(chan error)
	s.logger.Infof("connection with server successfully initialized")

	send := func(stream pb.API_UsageServer) error {
		cpu, mem := stats.GetUsageStats()
		if err := stream.Send(&pb.UsageStats{
			Cpu: cpu,
			Mem: mem,
		}); err != nil {
			return err
		}
		return nil
	}

	go func() {
		_, err := stream.Recv()
		if err != nil {
			errch <- err
		}
	}()

	go func() {
		if err := send(stream); err != nil {
			errch <- err
		}
		ticker := time.NewTicker(5 * time.Second)
		for range ticker.C {
			if err := send(stream); err != nil {
				ticker.Stop()
				errch <- err
			}
		}
	}()

	err := <-errch
	s.logger.Errorf("lost connection with server: %s", err.Error())
	s.errch <- err
	return err
}

// StartJob gRPC method.
func (s *Server) StartJob(job *pb.Job, stream pb.API_StartJobServer) error {
	name := fmt.Sprintf("abstruse-job-%d", job.GetId())

	s.mu.Lock()
	if _, ok := s.jobs[job.Id]; ok {
		docker.StopContainer(name)
		delete(s.jobs, job.Id)
	}
	s.jobs[job.Id] = job
	s.mu.Unlock()

	defer func() {
		s.mu.Lock()
		if _, ok := s.jobs[job.Id]; ok {
			docker.StopContainer(name)
			delete(s.jobs, job.Id)
		}
		s.mu.Unlock()
	}()

	logch := make(chan []byte, 1024)
	image := job.Image
	env := strings.Split(job.Env, " ")
	var cmds []string
	if err := json.Unmarshal([]byte(job.Commands), &cmds); err != nil {
		return err
	}
	var commands [][]string
	for _, c := range cmds {
		commands = append(commands, strings.Split(c, " "))
	}

	dir, err := fs.TempDir()
	if err != nil {
		return err
	}
	defer os.RemoveAll(dir)
	if err := git.CloneRepository(job.GetUrl(), job.GetRef(), job.GetCommitSHA(), job.GetProviderToken(), dir); err != nil {
		return err
	}

	go func() {
		for output := range logch {
			log := &pb.JobResp{Id: job.GetId(), Content: output, Type: pb.JobResp_Log}
			if err := stream.Send(log); err != nil {
				break
			}
		}
	}()

	if err := docker.PullImage(image); err != nil {
		logch <- []byte(err.Error())
	}

	ok := true
	s.mu.Lock()
	_, ok = s.jobs[job.Id]
	s.mu.Unlock()

	if !ok {
		return nil
	}

	if err := docker.RunContainer(name, image, commands, env, dir, logch); err != nil {
		stream.Send(&pb.JobResp{Id: job.GetId(), Type: pb.JobResp_Done, Status: pb.JobResp_StatusFailing})
		return err
	}

	stream.Send(&pb.JobResp{Id: job.GetId(), Type: pb.JobResp_Done, Status: pb.JobResp_StatusPassing})

	return nil
}

// StopJob gRPC method.
func (s *Server) StopJob(ctx context.Context, job *pb.Job) (*pb.JobStopResp, error) {
	defer func() {
		s.mu.Lock()
		delete(s.jobs, job.Id)
		s.mu.Unlock()
	}()

	name := fmt.Sprintf("abstruse-job-%d", job.GetId())
	if _, exists := docker.ContainerExists(name); exists {
		if err := docker.StopContainer(name); err != nil {
			return &pb.JobStopResp{Stopped: false}, nil
		}
		return &pb.JobStopResp{Stopped: true}, nil
	}

	return &pb.JobStopResp{Stopped: false}, nil
}

func (s *Server) Error() chan error {
	return s.errch
}
<commit_msg>feat(worker): improve worker logging<commit_after>package app

import (
	"context"
	"crypto/tls"
	"encoding/json"
	"fmt"
	"net"
	"os"
	"strings"
	"sync"
	"time"

	pb "github.com/bleenco/abstruse/pb"
	"github.com/bleenco/abstruse/pkg/fs"
	"github.com/bleenco/abstruse/pkg/stats"
	"github.com/bleenco/abstruse/worker/config"
	"github.com/bleenco/abstruse/worker/docker"
	"github.com/bleenco/abstruse/worker/git"
	"github.com/golang/protobuf/ptypes/empty"
	"go.uber.org/zap"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials"
)

// Server represents gRPC server.
type Server struct {
	mu       sync.Mutex
	config   *config.Config
	id       string
	addr     string
	listener net.Listener
	server   *grpc.Server
	app      *App
	logger   *zap.SugaredLogger
	jobs     map[uint64]*pb.Job
	errch    chan error
}

// NewServer returns new gRPC server.
func NewServer(config *config.Config, logger *zap.Logger, app *App) *Server {
	return &Server{
		config: config,
		id:     config.ID,
		addr:   config.GRPC.Addr,
		app:    app,
		logger: logger.With(zap.String("type", "server")).Sugar(),
		jobs:   make(map[uint64]*pb.Job),
		errch:  make(chan error),
	}
}

// Run starts the gRPC server.
func (s *Server) Run() error {
	var err error
	grpcOpts := []grpc.ServerOption{}
	certificate, err := tls.LoadX509KeyPair(s.config.TLS.Cert, s.config.TLS.Key)
	if err != nil {
		return err
	}
	s.listener, err = net.Listen("tcp", s.config.GRPC.Addr)
	if err != nil {
		return err
	}
	creds := credentials.NewTLS(&tls.Config{
		Certificates:       []tls.Certificate{certificate},
		InsecureSkipVerify: true,
	})

	grpcOpts = append(grpcOpts, grpc.Creds(creds))
	grpcOpts = append(grpcOpts, grpc.UnaryInterceptor(s.unaryInterceptor))
	grpcOpts = append(grpcOpts, grpc.StreamInterceptor(s.streamInterceptor))
	s.server = grpc.NewServer(grpcOpts...)
	pb.RegisterAPIServer(s.server, s)
	s.logger.Infof("grpc server listening on %s", s.config.GRPC.Addr)

	return s.server.Serve(s.listener)
}

// Connect returns worker host information.
func (s *Server) Connect(ctx context.Context, in *empty.Empty) (*pb.HostInfo, error) {
	info, err := stats.GetHostStats()
	if err != nil {
		return nil, err
	}

	return &pb.HostInfo{
		Id:                   s.id,
		Addr:                 s.addr,
		Hostname:             info.Hostname,
		Uptime:               info.Uptime,
		BootTime:             info.BootTime,
		Procs:                info.Procs,
		Os:                   info.OS,
		Platform:             info.Platform,
		PlatformFamily:       info.PlatformFamily,
		PlatformVersion:      info.PlatformVersion,
		KernelVersion:        info.KernelVersion,
		KernelArch:           info.KernelArch,
		VirtualizationSystem: info.VirtualizationRole,
		VirtualizationRole:   info.VirtualizationRole,
		HostID:               info.HostID,
		MaxParallel:          uint64(s.config.Scheduler.MaxParallel),
	}, nil
}

// Usage returns stream of health data.
func (s *Server) Usage(stream pb.API_UsageServer) error {
	errch := make(chan error)
	s.logger.Infof("connection with server successfully initialized")

	send := func(stream pb.API_UsageServer) error {
		cpu, mem := stats.GetUsageStats()
		if err := stream.Send(&pb.UsageStats{
			Cpu: cpu,
			Mem: mem,
		}); err != nil {
			return err
		}
		return nil
	}

	go func() {
		_, err := stream.Recv()
		if err != nil {
			errch <- err
		}
	}()

	go func() {
		if err := send(stream); err != nil {
			errch <- err
		}
		ticker := time.NewTicker(5 * time.Second)
		for range ticker.C {
			if err := send(stream); err != nil {
				ticker.Stop()
				errch <- err
			}
		}
	}()

	err := <-errch
	s.logger.Errorf("lost connection with server: %s", err.Error())
	s.errch <- err
	return err
}

// StartJob gRPC method.
func (s *Server) StartJob(job *pb.Job, stream pb.API_StartJobServer) error {
	name := fmt.Sprintf("abstruse-job-%d", job.GetId())
	s.logger.Infof("starting job %d with name %s", job.Id, name)

	s.mu.Lock()
	if _, ok := s.jobs[job.Id]; ok {
		docker.StopContainer(name)
		delete(s.jobs, job.Id)
	}
	s.jobs[job.Id] = job
	s.mu.Unlock()

	defer func() {
		s.mu.Lock()
		if _, ok := s.jobs[job.Id]; ok {
			docker.StopContainer(name)
			delete(s.jobs, job.Id)
		}
		s.mu.Unlock()
	}()

	logch := make(chan []byte, 1024)
	image := job.Image
	env := strings.Split(job.Env, " ")
	var cmds []string
	if err := json.Unmarshal([]byte(job.Commands), &cmds); err != nil {
		return err
	}
	var commands [][]string
	for _, c := range cmds {
		commands = append(commands, strings.Split(c, " "))
	}

	dir, err := fs.TempDir()
	if err != nil {
		return err
	}
	defer os.RemoveAll(dir)
	if err := git.CloneRepository(job.GetUrl(), job.GetRef(), job.GetCommitSHA(), job.GetProviderToken(), dir); err != nil {
		return err
	}

	go func() {
		for output := range logch {
			log := &pb.JobResp{Id: job.GetId(), Content: output, Type: pb.JobResp_Log}
			if err := stream.Send(log); err != nil {
				break
			}
		}
	}()

	if err := docker.PullImage(image); err != nil {
		logch <- []byte(err.Error())
	}

	ok := true
	s.mu.Lock()
	_, ok = s.jobs[job.Id]
	s.mu.Unlock()

	if !ok {
		return nil
	}

	if err := docker.RunContainer(name, image, commands, env, dir, logch); err != nil {
		stream.Send(&pb.JobResp{Id: job.GetId(), Type: pb.JobResp_Done, Status: pb.JobResp_StatusFailing})
		s.logger.Infof("job %d with name %s done with status failing", job.Id, name)
		return err
	}

	stream.Send(&pb.JobResp{Id: job.GetId(), Type: pb.JobResp_Done, Status: pb.JobResp_StatusPassing})
	s.logger.Infof("job %d with name %s done with status success", job.Id, name)

	return nil
}

// StopJob gRPC method.
func (s *Server) StopJob(ctx context.Context, job *pb.Job) (*pb.JobStopResp, error) {
	name := fmt.Sprintf("abstruse-job-%d", job.GetId())
	s.logger.Infof("stopping job %d (%s)...", job.Id, name)
	defer s.logger.Infof("job %d (%s) stopped", job.Id, name)

	defer func() {
		s.mu.Lock()
		delete(s.jobs, job.Id)
		s.mu.Unlock()
	}()

	if _, exists := docker.ContainerExists(name); exists {
		if err := docker.StopContainer(name); err != nil {
			return &pb.JobStopResp{Stopped: false}, nil
		}
		return &pb.JobStopResp{Stopped: true}, nil
	}

	return &pb.JobStopResp{Stopped: false}, nil
}

func (s *Server) Error() chan error {
	return s.errch
}
<|endoftext|>


<commit_before>// Copyright 2016 Palantir Technologies, 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,
// 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 dist

import (
	"bytes"
	"fmt"
	"io"
	"io/ioutil"
	"path"
	"text/template"

	"github.com/palantir/pkg/specdir"
	"github.com/pkg/errors"

	"github.com/palantir/godel/apps/distgo/params"
	"github.com/palantir/godel/apps/distgo/pkg/binspec"
	"github.com/palantir/godel/apps/distgo/templating"
)

const binDistInitSh = `#!/bin/bash
set -euo pipefail

BIN_DIR="$(cd "$(dirname "$0")" && pwd)"

# determine OS
OS=""
case "$(uname)" in
  Darwin*)
    OS=darwin
    ;;
  Linux*)
    OS=linux
    ;;
  *)
    echo "Unsupported operating system: $(uname)"
    exit 1
    ;;
esac

# determine executable location based on OS
CMD=$BIN_DIR/$OS-amd64/{{.ProductName}}

# verify that executable exists
if [ ! -e "$CMD" ]; then
    echo "Executable $CMD does not exist"
    exit 1
fi

# invoke appropriate executable
$CMD "$@"
`

type binDister params.BinDistInfo

func (b *binDister) NumArtifacts() int {
	return 1
}

func (b *binDister) ArtifactPathsInOutputDir(buildSpec params.ProductBuildSpec) []string {
	return []string{fmt.Sprintf("%v-%v.tgz", buildSpec.ProductName, buildSpec.ProductVersion)}
}

func (b *binDister) Dist(buildSpecWithDeps params.ProductBuildSpecWithDeps, distCfg params.Dist, outputProductDir string, spec specdir.LayoutSpec, values specdir.TemplateValues, stdout io.Writer) (Packager, error) {
	buildSpec := buildSpecWithDeps.Spec
	binSpec := binspec.New(buildSpec.Build.OSArchs, buildSpec.ProductName)
	binDir := path.Join(outputProductDir, "bin")
	binSpecDir, err := specdir.New(binDir, binSpec, nil, specdir.Create)
	if err != nil {
		return nil, errors.Wrapf(err, "failed to create directory structure for %v", binDir)
	}
	if err := copyBuildArtifactsToBinDir(buildSpecWithDeps, binSpecDir); err != nil {
		return nil, errors.Wrapf(err, "failed to copy artifacts to bin dir")
	}

	if !b.OmitInitSh {
		var initShTemplateBytes []byte
		if b.InitShTemplateFile != "" {
			initShTemplateFilePath := path.Join(buildSpec.ProjectDir, b.InitShTemplateFile)
			var err error
			initShTemplateBytes, err = ioutil.ReadFile(initShTemplateFilePath)
			if err != nil {
				return nil, errors.Wrapf(err, "failed to read init.sh template file %v", initShTemplateFilePath)
			}
		} else {
			initShTemplateBytes = []byte(binDistInitSh)
		}
		initShBuf := bytes.Buffer{}
		t := template.Must(template.New("init.sh").Parse(string(initShTemplateBytes)))
		if err := t.Execute(&initShBuf, templating.ConvertSpec(buildSpec, distCfg)); err != nil {
			return nil, errors.Wrapf(err, "failed to execute template %v on template %v", t, buildSpec)
		}
		if err := ioutil.WriteFile(path.Join(binDir, buildSpec.ProductName+".sh"), initShBuf.Bytes(), 0755); err != nil {
			return nil, errors.Wrapf(err, "failed to write init.sh")
		}
	}

	// known to only have 1 output path
	dstPath := FullArtifactsPaths(b, buildSpec, distCfg)[0]
	return singlePathTGZPackager(dstPath, outputProductDir), nil
}

func (b *binDister) DistPackageType() string {
	return "tgz"
}
<commit_msg>Update some %v format strings to be %s (#176)<commit_after>// Copyright 2016 Palantir Technologies, 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,
// 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 dist

import (
	"bytes"
	"fmt"
	"io"
	"io/ioutil"
	"path"
	"text/template"

	"github.com/palantir/pkg/specdir"
	"github.com/pkg/errors"

	"github.com/palantir/godel/apps/distgo/params"
	"github.com/palantir/godel/apps/distgo/pkg/binspec"
	"github.com/palantir/godel/apps/distgo/templating"
)

const binDistInitSh = `#!/bin/bash
set -euo pipefail

BIN_DIR="$(cd "$(dirname "$0")" && pwd)"

# determine OS
OS=""
case "$(uname)" in
  Darwin*)
    OS=darwin
    ;;
  Linux*)
    OS=linux
    ;;
  *)
    echo "Unsupported operating system: $(uname)"
    exit 1
    ;;
esac

# determine executable location based on OS
CMD=$BIN_DIR/$OS-amd64/{{.ProductName}}

# verify that executable exists
if [ ! -e "$CMD" ]; then
    echo "Executable $CMD does not exist"
    exit 1
fi

# invoke appropriate executable
$CMD "$@"
`

type binDister params.BinDistInfo

func (b *binDister) NumArtifacts() int {
	return 1
}

func (b *binDister) ArtifactPathsInOutputDir(buildSpec params.ProductBuildSpec) []string {
	return []string{fmt.Sprintf("%s-%s.tgz", buildSpec.ProductName, buildSpec.ProductVersion)}
}

func (b *binDister) Dist(buildSpecWithDeps params.ProductBuildSpecWithDeps, distCfg params.Dist, outputProductDir string, spec specdir.LayoutSpec, values specdir.TemplateValues, stdout io.Writer) (Packager, error) {
	buildSpec := buildSpecWithDeps.Spec
	binSpec := binspec.New(buildSpec.Build.OSArchs, buildSpec.ProductName)
	binDir := path.Join(outputProductDir, "bin")
	binSpecDir, err := specdir.New(binDir, binSpec, nil, specdir.Create)
	if err != nil {
		return nil, errors.Wrapf(err, "failed to create directory structure for %s", binDir)
	}
	if err := copyBuildArtifactsToBinDir(buildSpecWithDeps, binSpecDir); err != nil {
		return nil, errors.Wrapf(err, "failed to copy artifacts to bin dir")
	}

	if !b.OmitInitSh {
		var initShTemplateBytes []byte
		if b.InitShTemplateFile != "" {
			initShTemplateFilePath := path.Join(buildSpec.ProjectDir, b.InitShTemplateFile)
			var err error
			initShTemplateBytes, err = ioutil.ReadFile(initShTemplateFilePath)
			if err != nil {
				return nil, errors.Wrapf(err, "failed to read init.sh template file %s", initShTemplateFilePath)
			}
		} else {
			initShTemplateBytes = []byte(binDistInitSh)
		}
		initShBuf := bytes.Buffer{}
		t := template.Must(template.New("init.sh").Parse(string(initShTemplateBytes)))
		if err := t.Execute(&initShBuf, templating.ConvertSpec(buildSpec, distCfg)); err != nil {
			return nil, errors.Wrapf(err, "failed to execute template %v on build spec %+v", t, buildSpec)
		}
		if err := ioutil.WriteFile(path.Join(binDir, buildSpec.ProductName+".sh"), initShBuf.Bytes(), 0755); err != nil {
			return nil, errors.Wrapf(err, "failed to write init.sh")
		}
	}

	// known to only have 1 output path
	dstPath := FullArtifactsPaths(b, buildSpec, distCfg)[0]
	return singlePathTGZPackager(dstPath, outputProductDir), nil
}

func (b *binDister) DistPackageType() string {
	return "tgz"
}
<|endoftext|>


<commit_before>package rabbitsmpp

import (
	"errors"
	"sync"

	"golang.org/x/net/context"
)

var (
	errConsumerDuplicate = errors.New("consumer already exist")
	errConsumerMissing   = errors.New("consumer missing")
)

// ConsumerContainer is a container that collects the data from all the consumer channels into 1
// channel it's a fan-in pattern. You can add an remove consumers dynamically
type ConsumerContainer struct {
	consumers  map[string]Consumer
	m          *sync.Mutex
	wg         *sync.WaitGroup
	Ctx        context.Context
	stopper    context.CancelFunc
	outJobChan chan Job
	outErrChan chan error
}

func NewContainer() *ConsumerContainer {
	consumers := make(map[string]Consumer)
	var m sync.Mutex
	var wg sync.WaitGroup

	outJobChan := make(chan Job)
	outErrChan := make(chan error)

	ctx, cancel := context.WithCancel(context.Background())

	return &ConsumerContainer{
		consumers:  consumers,
		m:          &m,
		wg:         &wg,
		Ctx:        ctx,
		stopper:    cancel,
		outJobChan: outJobChan,
		outErrChan: outErrChan,
	}
}

func (container *ConsumerContainer) getNoLock(consumerID string) (Consumer, error) {
	consumer, ok := container.consumers[consumerID]
	if !ok {
		return nil, errConsumerMissing
	}

	return consumer, nil
}

func (container *ConsumerContainer) removeNoLock(consumerID string) error {

	if _, ok := container.consumers[consumerID]; !ok {
		return errConsumerMissing
	}

	delete(container.consumers, consumerID)
	return nil

}

func (container *ConsumerContainer) add(consumer Consumer) error {
	c, _ := container.getNoLock(consumer.ID())
	if c != nil {
		return errConsumerDuplicate
	}

	container.consumers[consumer.ID()] = consumer
	return nil
}

func (container *ConsumerContainer) RemoveStop(consumerID string) error {
	container.m.Lock()
	defer container.m.Unlock()

	consumer, err := container.getNoLock(consumerID)
	if err != nil {
		return err
	}

	err = consumer.Stop()
	if err != nil {
		return err
	}

	err = container.removeNoLock(consumerID)
	if err != nil {
		return nil
	}

	return nil
}

func (container *ConsumerContainer) AddFromConfig(conf Config) (string, error) {
	consumer, err := newConsumerWithContext(container.Ctx, conf)
	if err != nil {
		return "", err
	}
	err = container.AddRun(consumer)
	if err != nil {
		return "", err
	}
	return consumer.ID(), err
}

func (container *ConsumerContainer) AddRun(consumer Consumer) error {
	container.m.Lock()
	defer container.m.Unlock()

	err := container.add(consumer)
	if err != nil {
		return err
	}

	container.wg.Add(1)
	go container.runConsumer(consumer)

	return nil
}

func (container *ConsumerContainer) runConsumer(consumer Consumer) {
	defer container.wg.Done()

	jChan, eChan, err := consumer.Consume()
	if err != nil {
		return
	}
	// If there is error we might remove the consumer ?
	for {
		select {
		case job, ok := <-jChan:
			if !ok {
				return
			}
			container.outJobChan <- job

		case err, ok := <-eChan:
			if !ok {
				// We disable that bit of the select statement
				eChan = nil
			} else {
				container.outErrChan <- err
			}

		}
	}
}

func (container *ConsumerContainer) Consume() (<-chan Job, <-chan error, error) {
	return container.outJobChan, container.outErrChan, nil
}

func (container *ConsumerContainer) ID() string {
	// We might want to return all of the queues so far ?
	return "Container"
}

// A Blocking operation waiting for all of the consumers to stop
func (container *ConsumerContainer) Stop() error {
	container.m.Lock()
	defer container.m.Unlock()

	container.stopper()
	container.wg.Wait()

	container.consumers = nil

	// Close the channels
	close(container.outJobChan)
	close(container.outErrChan)

	return nil
}
<commit_msg>Make ConsumerContainer an interface for easier testing in dependencies<commit_after>package rabbitsmpp

import (
	"errors"
	"sync"

	"golang.org/x/net/context"
)

var (
	errConsumerDuplicate = errors.New("consumer already exist")
	errConsumerMissing   = errors.New("consumer missing")
)

type ConsumerContainer interface {
	Consumer
	AddFromConfig(Config) (string, error)
	AddRun(Consumer) error
	RemoveStop(string) error
}

// consumerContainer is a container that collects the data from all the consumer channels into 1
// channel it's a fan-in pattern. You can add an remove consumers dynamically
type consumerContainer struct {
	consumers  map[string]Consumer
	m          *sync.Mutex
	wg         *sync.WaitGroup
	Ctx        context.Context
	stopper    context.CancelFunc
	outJobChan chan Job
	outErrChan chan error
}

func NewContainer() ConsumerContainer {
	consumers := make(map[string]Consumer)
	var m sync.Mutex
	var wg sync.WaitGroup

	outJobChan := make(chan Job)
	outErrChan := make(chan error)

	ctx, cancel := context.WithCancel(context.Background())

	return &consumerContainer{
		consumers:  consumers,
		m:          &m,
		wg:         &wg,
		Ctx:        ctx,
		stopper:    cancel,
		outJobChan: outJobChan,
		outErrChan: outErrChan,
	}
}

func (container *consumerContainer) getNoLock(consumerID string) (Consumer, error) {
	consumer, ok := container.consumers[consumerID]
	if !ok {
		return nil, errConsumerMissing
	}

	return consumer, nil
}

func (container *consumerContainer) removeNoLock(consumerID string) error {

	if _, ok := container.consumers[consumerID]; !ok {
		return errConsumerMissing
	}

	delete(container.consumers, consumerID)
	return nil

}

func (container *consumerContainer) add(consumer Consumer) error {
	c, _ := container.getNoLock(consumer.ID())
	if c != nil {
		return errConsumerDuplicate
	}

	container.consumers[consumer.ID()] = consumer
	return nil
}

func (container *consumerContainer) RemoveStop(consumerID string) error {
	container.m.Lock()
	defer container.m.Unlock()

	consumer, err := container.getNoLock(consumerID)
	if err != nil {
		return err
	}

	err = consumer.Stop()
	if err != nil {
		return err
	}

	err = container.removeNoLock(consumerID)
	if err != nil {
		return nil
	}

	return nil
}

func (container *consumerContainer) AddFromConfig(conf Config) (string, error) {
	consumer, err := newConsumerWithContext(container.Ctx, conf)
	if err != nil {
		return "", err
	}
	err = container.AddRun(consumer)
	if err != nil {
		return "", err
	}
	return consumer.ID(), err
}

func (container *consumerContainer) AddRun(consumer Consumer) error {
	container.m.Lock()
	defer container.m.Unlock()

	err := container.add(consumer)
	if err != nil {
		return err
	}

	container.wg.Add(1)
	go container.runConsumer(consumer)

	return nil
}

func (container *consumerContainer) runConsumer(consumer Consumer) {
	defer container.wg.Done()

	jChan, eChan, err := consumer.Consume()
	if err != nil {
		return
	}
	// If there is error we might remove the consumer ?
	for {
		select {
		case job, ok := <-jChan:
			if !ok {
				return
			}
			container.outJobChan <- job

		case err, ok := <-eChan:
			if !ok {
				// We disable that bit of the select statement
				eChan = nil
			} else {
				container.outErrChan <- err
			}

		}
	}
}

func (container *consumerContainer) Consume() (<-chan Job, <-chan error, error) {
	return container.outJobChan, container.outErrChan, nil
}

func (container *consumerContainer) ID() string {
	// We might want to return all of the queues so far ?
	return "Container"
}

// A Blocking operation waiting for all of the consumers to stop
func (container *consumerContainer) Stop() error {
	container.m.Lock()
	defer container.m.Unlock()

	container.stopper()
	container.wg.Wait()

	container.consumers = nil

	// Close the channels
	close(container.outJobChan)
	close(container.outErrChan)

	return nil
}
<|endoftext|>


<commit_before>package main

import (
    "fmt"
//    "net/ipv4"
    "golang.org/x/net/ipv4"
)

func main() {
    fmt.Println("Hello, World!")
}

type UDP struct {
	conn *ipv4.RawConn
}
<commit_msg>Beginning of the methods framework.<commit_after>package main

import (
    "fmt"
//    "net/ipv4"
    "golang.org/x/net/ipv4"
    "os"
)

func main() {
    fmt.Println("Hello, World!")

    c, err := openUDP(5049, 5050)
    if err != nil {
        fmt.Println("Failed")
        os.Exit(1)
    }
    read(c, 1024)
}

//type netConn interface {
//    open(src, dest int) (S UDP, error)
//}

type UDP struct {
	conn *ipv4.RawConn
}
func openUDP(src, dest int) (*UDP, error) {
    return nil, nil
}
func read(S *UDP, size int) ([]byte, error) {
    return make([]byte, 0), nil
}<|endoftext|>


<commit_before>package main

import (
	"crypto"
	"crypto/x509"
	"encoding/json"
	"flag"
	"fmt"
	"io/ioutil"
	"os"

	"github.com/beeker1121/goque"
	cfsslConfig "github.com/cloudflare/cfssl/config"
	"github.com/cloudflare/cfssl/helpers"
	pkcs11key "github.com/letsencrypt/pkcs11key/v4"
	"google.golang.org/grpc/health"
	healthpb "google.golang.org/grpc/health/grpc_health_v1"

	"github.com/letsencrypt/boulder/ca"
	capb "github.com/letsencrypt/boulder/ca/proto"
	"github.com/letsencrypt/boulder/cmd"
	"github.com/letsencrypt/boulder/core"
	"github.com/letsencrypt/boulder/features"
	"github.com/letsencrypt/boulder/goodkey"
	bgrpc "github.com/letsencrypt/boulder/grpc"
	"github.com/letsencrypt/boulder/issuance"
	"github.com/letsencrypt/boulder/lint"
	"github.com/letsencrypt/boulder/policy"
	sapb "github.com/letsencrypt/boulder/sa/proto"
)

type config struct {
	CA struct {
		cmd.ServiceConfig
		cmd.DBConfig
		cmd.HostnamePolicyConfig

		GRPCCA            *cmd.GRPCServerConfig
		GRPCOCSPGenerator *cmd.GRPCServerConfig

		SAService *cmd.GRPCClientConfig

		// CFSSL contains CFSSL-specific configs as specified by that library.
		CFSSL cfsslConfig.Config
		// RSAProfile and ECDSAProfile name which of the profiles specified in the
		// CFSSL config should be used when issuing RSA and ECDSA certs, respectively.
		RSAProfile   string
		ECDSAProfile string
		// Issuers contains configuration information for each issuer cert and key
		// this CA knows about. The first in the list is used as the default.
		// Only used by CFSSL.
		Issuers []IssuerConfig

		// Issuance contains all information necessary to load and initialize non-CFSSL issuers.
		Issuance struct {
			Profile      issuance.ProfileConfig
			Issuers      []issuance.IssuerConfig
			IgnoredLints []string
		}

		// How long issued certificates are valid for, should match expiry field
		// in cfssl config.
		Expiry cmd.ConfigDuration

		// How far back certificates should be backdated, should match backdate
		// field in cfssl config.
		Backdate cmd.ConfigDuration

		// What digits we should prepend to serials after randomly generating them.
		SerialPrefix int

		// The maximum number of subjectAltNames in a single certificate
		MaxNames int

		// LifespanOCSP is how long OCSP responses are valid for; It should be longer
		// than the minTimeToExpiry field for the OCSP Updater.
		LifespanOCSP cmd.ConfigDuration

		// WeakKeyFile is the path to a JSON file containing truncated RSA modulus
		// hashes of known easily enumerable keys.
		WeakKeyFile string

		// BlockedKeyFile is the path to a YAML file containing Base64 encoded
		// SHA256 hashes of SubjectPublicKeyInfo's that should be considered
		// administratively blocked.
		BlockedKeyFile string

		// Path to directory holding orphan queue files, if not provided an orphan queue
		// is not used.
		OrphanQueueDir string

		// Maximum length (in bytes) of a line accumulating OCSP audit log entries.
		// Recommended to be around 4000. If this is 0, do not perform OCSP audit
		// logging.
		OCSPLogMaxLength int

		// Maximum period (in Go duration format) to wait to accumulate a max-length
		// OCSP audit log line. We will emit a log line at least once per period,
		// if there is anything to be logged. Keeping this low minimizes the risk
		// of losing logs during a catastrophic failure. Making it too high
		// means logging more often than necessary, which is inefficient in terms
		// of bytes and log system resources.
		// Recommended to be around 500ms.
		OCSPLogPeriod cmd.ConfigDuration

		// List of Registration IDs for which ECDSA issuance is allowed. If an
		// account is in this allowlist *and* requests issuance for an ECDSA key
		// *and* an ECDSA issuer is configured in the CA, then the certificate
		// will be issued from that ECDSA issuer. If this list is empty, then
		// ECDSA issuance is allowed for all accounts.
		// This is temporary, and will be used for testing and slow roll-out of
		// ECDSA issuance, but will then be removed.
		ECDSAAllowedAccounts []int64

		Features map[string]bool
	}

	PA cmd.PAConfig

	Syslog cmd.SyslogConfig
}

// IssuerConfig contains info about an issuer: private key and issuer cert.
// It should contain either a File path to a PEM-format private key,
// or a PKCS11Config defining how to load a module for an HSM. Used by CFSSL.
type IssuerConfig struct {
	// A file from which a pkcs11key.Config will be read and parsed, if present
	ConfigFile string
	File       string
	PKCS11     *pkcs11key.Config
	CertFile   string
	// Number of sessions to open with the HSM. For maximum performance,
	// this should be equal to the number of cores in the HSM. Defaults to 1.
	NumSessions int
}

func loadCFSSLIssuers(configs []IssuerConfig) ([]ca.Issuer, error) {
	var issuers []ca.Issuer
	for _, issuerConfig := range configs {
		signer, cert, err := loadCFSSLIssuer(issuerConfig)
		cmd.FailOnError(err, "Couldn't load private key")
		issuers = append(issuers, ca.Issuer{
			Signer: signer,
			Cert:   cert,
		})
	}
	return issuers, nil
}

func loadCFSSLIssuer(issuerConfig IssuerConfig) (crypto.Signer, *issuance.Certificate, error) {
	cert, err := issuance.LoadCertificate(issuerConfig.CertFile)
	if err != nil {
		return nil, nil, err
	}

	signer, err := loadCFSSLSigner(issuerConfig, cert.Certificate)
	if err != nil {
		return nil, nil, err
	}

	if !core.KeyDigestEquals(signer.Public(), cert.PublicKey) {
		return nil, nil, fmt.Errorf("Issuer key did not match issuer cert %s", issuerConfig.CertFile)
	}
	return signer, cert, err
}

func loadCFSSLSigner(issuerConfig IssuerConfig, cert *x509.Certificate) (crypto.Signer, error) {
	if issuerConfig.File != "" {
		keyBytes, err := ioutil.ReadFile(issuerConfig.File)
		if err != nil {
			return nil, fmt.Errorf("Could not read key file %s", issuerConfig.File)
		}

		signer, err := helpers.ParsePrivateKeyPEM(keyBytes)
		if err != nil {
			return nil, err
		}
		return signer, nil
	}

	var pkcs11Config *pkcs11key.Config
	if issuerConfig.ConfigFile != "" {
		contents, err := ioutil.ReadFile(issuerConfig.ConfigFile)
		if err != nil {
			return nil, err
		}
		pkcs11Config = new(pkcs11key.Config)
		err = json.Unmarshal(contents, pkcs11Config)
		if err != nil {
			return nil, err
		}
	} else {
		pkcs11Config = issuerConfig.PKCS11
	}
	if pkcs11Config.Module == "" ||
		pkcs11Config.TokenLabel == "" ||
		pkcs11Config.PIN == "" {
		return nil, fmt.Errorf("Missing a field in pkcs11Config %#v", pkcs11Config)
	}
	numSessions := issuerConfig.NumSessions
	if numSessions <= 0 {
		numSessions = 1
	}
	return pkcs11key.NewPool(numSessions, pkcs11Config.Module,
		pkcs11Config.TokenLabel, pkcs11Config.PIN, cert.PublicKey)
}

func loadBoulderIssuers(profileConfig issuance.ProfileConfig, issuerConfigs []issuance.IssuerConfig, ignoredLints []string) ([]*issuance.Issuer, error) {
	issuers := make([]*issuance.Issuer, 0, len(issuerConfigs))
	for _, issuerConfig := range issuerConfigs {
		profile, err := issuance.NewProfile(profileConfig, issuerConfig)
		if err != nil {
			return nil, err
		}

		cert, signer, err := issuance.LoadIssuer(issuerConfig.Location)
		if err != nil {
			return nil, err
		}

		linter, err := lint.NewLinter(signer, ignoredLints)
		if err != nil {
			return nil, err
		}

		issuer, err := issuance.NewIssuer(cert, signer, profile, linter, cmd.Clock())
		if err != nil {
			return nil, err
		}

		issuers = append(issuers, issuer)
	}
	return issuers, nil
}

func main() {
	caAddr := flag.String("ca-addr", "", "CA gRPC listen address override")
	ocspAddr := flag.String("ocsp-addr", "", "OCSP gRPC listen address override")
	debugAddr := flag.String("debug-addr", "", "Debug server address override")
	configFile := flag.String("config", "", "File path to the configuration file for this service")
	flag.Parse()
	if *configFile == "" {
		flag.Usage()
		os.Exit(1)
	}

	var c config
	err := cmd.ReadConfigFile(*configFile, &c)
	cmd.FailOnError(err, "Reading JSON config file into config structure")

	err = features.Set(c.CA.Features)
	cmd.FailOnError(err, "Failed to set feature flags")

	if *caAddr != "" {
		c.CA.GRPCCA.Address = *caAddr
	}
	if *ocspAddr != "" {
		c.CA.GRPCOCSPGenerator.Address = *ocspAddr
	}
	if *debugAddr != "" {
		c.CA.DebugAddr = *debugAddr
	}

	if c.CA.MaxNames == 0 {
		cmd.Fail("Error in CA config: MaxNames must not be 0")
	}

	scope, logger := cmd.StatsAndLogging(c.Syslog, c.CA.DebugAddr)
	defer logger.AuditPanic()
	logger.Info(cmd.VersionString())

	cmd.FailOnError(c.PA.CheckChallenges(), "Invalid PA configuration")

	pa, err := policy.New(c.PA.Challenges)
	cmd.FailOnError(err, "Couldn't create PA")

	if c.CA.HostnamePolicyFile == "" {
		cmd.FailOnError(fmt.Errorf("HostnamePolicyFile was empty."), "")
	}
	err = pa.SetHostnamePolicyFile(c.CA.HostnamePolicyFile)
	cmd.FailOnError(err, "Couldn't load hostname policy file")

	var cfsslIssuers []ca.Issuer
	var boulderIssuers []*issuance.Issuer
	if features.Enabled(features.NonCFSSLSigner) {
		boulderIssuers, err = loadBoulderIssuers(c.CA.Issuance.Profile, c.CA.Issuance.Issuers, c.CA.Issuance.IgnoredLints)
		cmd.FailOnError(err, "Couldn't load issuers")
	} else {
		cfsslIssuers, err = loadCFSSLIssuers(c.CA.Issuers)
		cmd.FailOnError(err, "Couldn't load issuers")
	}

	tlsConfig, err := c.CA.TLS.Load()
	cmd.FailOnError(err, "TLS config")

	clk := cmd.Clock()

	clientMetrics := bgrpc.NewClientMetrics(scope)
	conn, err := bgrpc.ClientSetup(c.CA.SAService, tlsConfig, clientMetrics, clk)
	cmd.FailOnError(err, "Failed to load credentials and create gRPC connection to SA")
	sa := bgrpc.NewStorageAuthorityClient(sapb.NewStorageAuthorityClient(conn))

	kp, err := goodkey.NewKeyPolicy(c.CA.WeakKeyFile, c.CA.BlockedKeyFile, sa.KeyBlocked)
	cmd.FailOnError(err, "Unable to create key policy")

	var orphanQueue *goque.Queue
	if c.CA.OrphanQueueDir != "" {
		orphanQueue, err = goque.OpenQueue(c.CA.OrphanQueueDir)
		cmd.FailOnError(err, "Failed to open orphaned certificate queue")
		defer func() { _ = orphanQueue.Close() }()
	}

	cai, err := ca.NewCertificateAuthorityImpl(
		sa,
		pa,
		c.CA.CFSSL,
		c.CA.RSAProfile,
		c.CA.ECDSAProfile,
		cfsslIssuers,
		boulderIssuers,
		c.CA.ECDSAAllowedAccounts,
		c.CA.Expiry.Duration,
		c.CA.Backdate.Duration,
		c.CA.SerialPrefix,
		c.CA.MaxNames,
		c.CA.LifespanOCSP.Duration,
		kp,
		orphanQueue,
		c.CA.OCSPLogMaxLength,
		c.CA.OCSPLogPeriod.Duration,
		logger,
		scope,
		clk)
	cmd.FailOnError(err, "Failed to create CA impl")

	if orphanQueue != nil {
		go cai.OrphanIntegrationLoop()
	}
	go cai.LogOCSPLoop()

	serverMetrics := bgrpc.NewServerMetrics(scope)

	caSrv, caListener, err := bgrpc.NewServer(c.CA.GRPCCA, tlsConfig, serverMetrics, clk)
	cmd.FailOnError(err, "Unable to setup CA gRPC server")
	caWrapper := bgrpc.NewCertificateAuthorityServer(cai)
	capb.RegisterCertificateAuthorityServer(caSrv, caWrapper)
	caHealth := health.NewServer()
	healthpb.RegisterHealthServer(caSrv, caHealth)
	go func() {
		cmd.FailOnError(cmd.FilterShutdownErrors(caSrv.Serve(caListener)), "CA gRPC service failed")
	}()

	ocspSrv, ocspListener, err := bgrpc.NewServer(c.CA.GRPCOCSPGenerator, tlsConfig, serverMetrics, clk)
	cmd.FailOnError(err, "Unable to setup CA gRPC server")
	ocspWrapper := bgrpc.NewCertificateAuthorityServer(cai)
	capb.RegisterOCSPGeneratorServer(ocspSrv, ocspWrapper)
	ocspHealth := health.NewServer()
	healthpb.RegisterHealthServer(ocspSrv, ocspHealth)
	go func() {
		cmd.FailOnError(cmd.FilterShutdownErrors(ocspSrv.Serve(ocspListener)),
			"OCSPGenerator gRPC service failed")
	}()

	go cmd.CatchSignals(logger, func() {
		caHealth.Shutdown()
		ocspHealth.Shutdown()
		caSrv.GracefulStop()
		ocspSrv.GracefulStop()
		cai.Stop()
	})

	select {}
}
<commit_msg>Fix potential race condition in boulder-ca shutdown (#5277)<commit_after>package main

import (
	"crypto"
	"crypto/x509"
	"encoding/json"
	"flag"
	"fmt"
	"io/ioutil"
	"os"
	"sync"

	"github.com/beeker1121/goque"
	cfsslConfig "github.com/cloudflare/cfssl/config"
	"github.com/cloudflare/cfssl/helpers"
	pkcs11key "github.com/letsencrypt/pkcs11key/v4"
	"google.golang.org/grpc/health"
	healthpb "google.golang.org/grpc/health/grpc_health_v1"

	"github.com/letsencrypt/boulder/ca"
	capb "github.com/letsencrypt/boulder/ca/proto"
	"github.com/letsencrypt/boulder/cmd"
	"github.com/letsencrypt/boulder/core"
	"github.com/letsencrypt/boulder/features"
	"github.com/letsencrypt/boulder/goodkey"
	bgrpc "github.com/letsencrypt/boulder/grpc"
	"github.com/letsencrypt/boulder/issuance"
	"github.com/letsencrypt/boulder/lint"
	"github.com/letsencrypt/boulder/policy"
	sapb "github.com/letsencrypt/boulder/sa/proto"
)

type config struct {
	CA struct {
		cmd.ServiceConfig
		cmd.DBConfig
		cmd.HostnamePolicyConfig

		GRPCCA            *cmd.GRPCServerConfig
		GRPCOCSPGenerator *cmd.GRPCServerConfig

		SAService *cmd.GRPCClientConfig

		// CFSSL contains CFSSL-specific configs as specified by that library.
		CFSSL cfsslConfig.Config
		// RSAProfile and ECDSAProfile name which of the profiles specified in the
		// CFSSL config should be used when issuing RSA and ECDSA certs, respectively.
		RSAProfile   string
		ECDSAProfile string
		// Issuers contains configuration information for each issuer cert and key
		// this CA knows about. The first in the list is used as the default.
		// Only used by CFSSL.
		Issuers []IssuerConfig

		// Issuance contains all information necessary to load and initialize non-CFSSL issuers.
		Issuance struct {
			Profile      issuance.ProfileConfig
			Issuers      []issuance.IssuerConfig
			IgnoredLints []string
		}

		// How long issued certificates are valid for, should match expiry field
		// in cfssl config.
		Expiry cmd.ConfigDuration

		// How far back certificates should be backdated, should match backdate
		// field in cfssl config.
		Backdate cmd.ConfigDuration

		// What digits we should prepend to serials after randomly generating them.
		SerialPrefix int

		// The maximum number of subjectAltNames in a single certificate
		MaxNames int

		// LifespanOCSP is how long OCSP responses are valid for; It should be longer
		// than the minTimeToExpiry field for the OCSP Updater.
		LifespanOCSP cmd.ConfigDuration

		// WeakKeyFile is the path to a JSON file containing truncated RSA modulus
		// hashes of known easily enumerable keys.
		WeakKeyFile string

		// BlockedKeyFile is the path to a YAML file containing Base64 encoded
		// SHA256 hashes of SubjectPublicKeyInfo's that should be considered
		// administratively blocked.
		BlockedKeyFile string

		// Path to directory holding orphan queue files, if not provided an orphan queue
		// is not used.
		OrphanQueueDir string

		// Maximum length (in bytes) of a line accumulating OCSP audit log entries.
		// Recommended to be around 4000. If this is 0, do not perform OCSP audit
		// logging.
		OCSPLogMaxLength int

		// Maximum period (in Go duration format) to wait to accumulate a max-length
		// OCSP audit log line. We will emit a log line at least once per period,
		// if there is anything to be logged. Keeping this low minimizes the risk
		// of losing logs during a catastrophic failure. Making it too high
		// means logging more often than necessary, which is inefficient in terms
		// of bytes and log system resources.
		// Recommended to be around 500ms.
		OCSPLogPeriod cmd.ConfigDuration

		// List of Registration IDs for which ECDSA issuance is allowed. If an
		// account is in this allowlist *and* requests issuance for an ECDSA key
		// *and* an ECDSA issuer is configured in the CA, then the certificate
		// will be issued from that ECDSA issuer. If this list is empty, then
		// ECDSA issuance is allowed for all accounts.
		// This is temporary, and will be used for testing and slow roll-out of
		// ECDSA issuance, but will then be removed.
		ECDSAAllowedAccounts []int64

		Features map[string]bool
	}

	PA cmd.PAConfig

	Syslog cmd.SyslogConfig
}

// IssuerConfig contains info about an issuer: private key and issuer cert.
// It should contain either a File path to a PEM-format private key,
// or a PKCS11Config defining how to load a module for an HSM. Used by CFSSL.
type IssuerConfig struct {
	// A file from which a pkcs11key.Config will be read and parsed, if present
	ConfigFile string
	File       string
	PKCS11     *pkcs11key.Config
	CertFile   string
	// Number of sessions to open with the HSM. For maximum performance,
	// this should be equal to the number of cores in the HSM. Defaults to 1.
	NumSessions int
}

func loadCFSSLIssuers(configs []IssuerConfig) ([]ca.Issuer, error) {
	var issuers []ca.Issuer
	for _, issuerConfig := range configs {
		signer, cert, err := loadCFSSLIssuer(issuerConfig)
		cmd.FailOnError(err, "Couldn't load private key")
		issuers = append(issuers, ca.Issuer{
			Signer: signer,
			Cert:   cert,
		})
	}
	return issuers, nil
}

func loadCFSSLIssuer(issuerConfig IssuerConfig) (crypto.Signer, *issuance.Certificate, error) {
	cert, err := issuance.LoadCertificate(issuerConfig.CertFile)
	if err != nil {
		return nil, nil, err
	}

	signer, err := loadCFSSLSigner(issuerConfig, cert.Certificate)
	if err != nil {
		return nil, nil, err
	}

	if !core.KeyDigestEquals(signer.Public(), cert.PublicKey) {
		return nil, nil, fmt.Errorf("Issuer key did not match issuer cert %s", issuerConfig.CertFile)
	}
	return signer, cert, err
}

func loadCFSSLSigner(issuerConfig IssuerConfig, cert *x509.Certificate) (crypto.Signer, error) {
	if issuerConfig.File != "" {
		keyBytes, err := ioutil.ReadFile(issuerConfig.File)
		if err != nil {
			return nil, fmt.Errorf("Could not read key file %s", issuerConfig.File)
		}

		signer, err := helpers.ParsePrivateKeyPEM(keyBytes)
		if err != nil {
			return nil, err
		}
		return signer, nil
	}

	var pkcs11Config *pkcs11key.Config
	if issuerConfig.ConfigFile != "" {
		contents, err := ioutil.ReadFile(issuerConfig.ConfigFile)
		if err != nil {
			return nil, err
		}
		pkcs11Config = new(pkcs11key.Config)
		err = json.Unmarshal(contents, pkcs11Config)
		if err != nil {
			return nil, err
		}
	} else {
		pkcs11Config = issuerConfig.PKCS11
	}
	if pkcs11Config.Module == "" ||
		pkcs11Config.TokenLabel == "" ||
		pkcs11Config.PIN == "" {
		return nil, fmt.Errorf("Missing a field in pkcs11Config %#v", pkcs11Config)
	}
	numSessions := issuerConfig.NumSessions
	if numSessions <= 0 {
		numSessions = 1
	}
	return pkcs11key.NewPool(numSessions, pkcs11Config.Module,
		pkcs11Config.TokenLabel, pkcs11Config.PIN, cert.PublicKey)
}

func loadBoulderIssuers(profileConfig issuance.ProfileConfig, issuerConfigs []issuance.IssuerConfig, ignoredLints []string) ([]*issuance.Issuer, error) {
	issuers := make([]*issuance.Issuer, 0, len(issuerConfigs))
	for _, issuerConfig := range issuerConfigs {
		profile, err := issuance.NewProfile(profileConfig, issuerConfig)
		if err != nil {
			return nil, err
		}

		cert, signer, err := issuance.LoadIssuer(issuerConfig.Location)
		if err != nil {
			return nil, err
		}

		linter, err := lint.NewLinter(signer, ignoredLints)
		if err != nil {
			return nil, err
		}

		issuer, err := issuance.NewIssuer(cert, signer, profile, linter, cmd.Clock())
		if err != nil {
			return nil, err
		}

		issuers = append(issuers, issuer)
	}
	return issuers, nil
}

func main() {
	caAddr := flag.String("ca-addr", "", "CA gRPC listen address override")
	ocspAddr := flag.String("ocsp-addr", "", "OCSP gRPC listen address override")
	debugAddr := flag.String("debug-addr", "", "Debug server address override")
	configFile := flag.String("config", "", "File path to the configuration file for this service")
	flag.Parse()
	if *configFile == "" {
		flag.Usage()
		os.Exit(1)
	}

	var c config
	err := cmd.ReadConfigFile(*configFile, &c)
	cmd.FailOnError(err, "Reading JSON config file into config structure")

	err = features.Set(c.CA.Features)
	cmd.FailOnError(err, "Failed to set feature flags")

	if *caAddr != "" {
		c.CA.GRPCCA.Address = *caAddr
	}
	if *ocspAddr != "" {
		c.CA.GRPCOCSPGenerator.Address = *ocspAddr
	}
	if *debugAddr != "" {
		c.CA.DebugAddr = *debugAddr
	}

	if c.CA.MaxNames == 0 {
		cmd.Fail("Error in CA config: MaxNames must not be 0")
	}

	scope, logger := cmd.StatsAndLogging(c.Syslog, c.CA.DebugAddr)
	defer logger.AuditPanic()
	logger.Info(cmd.VersionString())

	cmd.FailOnError(c.PA.CheckChallenges(), "Invalid PA configuration")

	pa, err := policy.New(c.PA.Challenges)
	cmd.FailOnError(err, "Couldn't create PA")

	if c.CA.HostnamePolicyFile == "" {
		cmd.FailOnError(fmt.Errorf("HostnamePolicyFile was empty."), "")
	}
	err = pa.SetHostnamePolicyFile(c.CA.HostnamePolicyFile)
	cmd.FailOnError(err, "Couldn't load hostname policy file")

	var cfsslIssuers []ca.Issuer
	var boulderIssuers []*issuance.Issuer
	if features.Enabled(features.NonCFSSLSigner) {
		boulderIssuers, err = loadBoulderIssuers(c.CA.Issuance.Profile, c.CA.Issuance.Issuers, c.CA.Issuance.IgnoredLints)
		cmd.FailOnError(err, "Couldn't load issuers")
	} else {
		cfsslIssuers, err = loadCFSSLIssuers(c.CA.Issuers)
		cmd.FailOnError(err, "Couldn't load issuers")
	}

	tlsConfig, err := c.CA.TLS.Load()
	cmd.FailOnError(err, "TLS config")

	clk := cmd.Clock()

	clientMetrics := bgrpc.NewClientMetrics(scope)
	conn, err := bgrpc.ClientSetup(c.CA.SAService, tlsConfig, clientMetrics, clk)
	cmd.FailOnError(err, "Failed to load credentials and create gRPC connection to SA")
	sa := bgrpc.NewStorageAuthorityClient(sapb.NewStorageAuthorityClient(conn))

	kp, err := goodkey.NewKeyPolicy(c.CA.WeakKeyFile, c.CA.BlockedKeyFile, sa.KeyBlocked)
	cmd.FailOnError(err, "Unable to create key policy")

	var orphanQueue *goque.Queue
	if c.CA.OrphanQueueDir != "" {
		orphanQueue, err = goque.OpenQueue(c.CA.OrphanQueueDir)
		cmd.FailOnError(err, "Failed to open orphaned certificate queue")
		defer func() { _ = orphanQueue.Close() }()
	}

	cai, err := ca.NewCertificateAuthorityImpl(
		sa,
		pa,
		c.CA.CFSSL,
		c.CA.RSAProfile,
		c.CA.ECDSAProfile,
		cfsslIssuers,
		boulderIssuers,
		c.CA.ECDSAAllowedAccounts,
		c.CA.Expiry.Duration,
		c.CA.Backdate.Duration,
		c.CA.SerialPrefix,
		c.CA.MaxNames,
		c.CA.LifespanOCSP.Duration,
		kp,
		orphanQueue,
		c.CA.OCSPLogMaxLength,
		c.CA.OCSPLogPeriod.Duration,
		logger,
		scope,
		clk)
	cmd.FailOnError(err, "Failed to create CA impl")

	if orphanQueue != nil {
		go cai.OrphanIntegrationLoop()
	}
	go cai.LogOCSPLoop()

	serverMetrics := bgrpc.NewServerMetrics(scope)
	var wg sync.WaitGroup

	caSrv, caListener, err := bgrpc.NewServer(c.CA.GRPCCA, tlsConfig, serverMetrics, clk)
	cmd.FailOnError(err, "Unable to setup CA gRPC server")
	caWrapper := bgrpc.NewCertificateAuthorityServer(cai)
	capb.RegisterCertificateAuthorityServer(caSrv, caWrapper)
	caHealth := health.NewServer()
	healthpb.RegisterHealthServer(caSrv, caHealth)
	wg.Add(1)
	go func() {
		cmd.FailOnError(cmd.FilterShutdownErrors(caSrv.Serve(caListener)), "CA gRPC service failed")
		wg.Done()
	}()

	ocspSrv, ocspListener, err := bgrpc.NewServer(c.CA.GRPCOCSPGenerator, tlsConfig, serverMetrics, clk)
	cmd.FailOnError(err, "Unable to setup CA gRPC server")
	ocspWrapper := bgrpc.NewCertificateAuthorityServer(cai)
	capb.RegisterOCSPGeneratorServer(ocspSrv, ocspWrapper)
	ocspHealth := health.NewServer()
	healthpb.RegisterHealthServer(ocspSrv, ocspHealth)
	wg.Add(1)
	go func() {
		cmd.FailOnError(cmd.FilterShutdownErrors(ocspSrv.Serve(ocspListener)),
			"OCSPGenerator gRPC service failed")
		wg.Done()
	}()

	go cmd.CatchSignals(logger, func() {
		caHealth.Shutdown()
		ocspHealth.Shutdown()
		caSrv.GracefulStop()
		ocspSrv.GracefulStop()
		wg.Wait()
		cai.Stop()
	})

	select {}
}
<|endoftext|>