<commit_before>// package peer implements an object used to represent peers in the ipfs network.
package peer

import (
	"encoding/hex"
	"encoding/json"
	"fmt"
	"strings"

	ma "gx/ipfs/QmR3JkmZBKYXgNMNsNZawm914455Qof3PEopwuVSeXG7aV/go-multiaddr"
	b58 "gx/ipfs/QmT8rehPR3F6bmwL6zjUN8XpiDBFFpMP2myPdC6ApsWfJf/go-base58"
	mh "gx/ipfs/QmYf7ng2hG5XBtJA3tN34DQ2GUN5HNksEw1rLDkmr6vGku/go-multihash"

	ic "github.com/ipfs/go-libp2p/p2p/crypto"
	u "gx/ipfs/QmZNVWh8LLjAavuQ2JXuFmuYH3C11xo988vSgp7UQrTRj1/go-ipfs-util"
	logging "gx/ipfs/Qmazh5oNUVsDZTs2g59rq8aYQqwpss8tcUWQzor5sCCEuH/go-log"
)

var log = logging.Logger("peer")

// ID represents the identity of a peer.
type ID string

// Pretty returns a b58-encoded string of the ID
func (id ID) Pretty() string {
	return IDB58Encode(id)
}

func (id ID) Loggable() map[string]interface{} {
	return map[string]interface{}{
		"peerID": id.Pretty(),
	}
}

// String prints out the peer.
//
// TODO(brian): ensure correctness at ID generation and
// enforce this by only exposing functions that generate
// IDs safely. Then any peer.ID type found in the
// codebase is known to be correct.
func (id ID) String() string {
	pid := id.Pretty()

	//All sha256 nodes start with Qm
	//We can skip the Qm to make the peer.ID more useful
	if strings.HasPrefix(pid, "gx/Qm") {
		pid = pid[2:]
	}

	maxRunes := 6
	if len(pid) < maxRunes {
		maxRunes = len(pid)
	}
	return fmt.Sprintf("<peer.ID %s>", pid[:maxRunes])
}

// MatchesPrivateKey tests whether this ID was derived from sk
func (id ID) MatchesPrivateKey(sk ic.PrivKey) bool {
	return id.MatchesPublicKey(sk.GetPublic())
}

// MatchesPublicKey tests whether this ID was derived from pk
func (id ID) MatchesPublicKey(pk ic.PubKey) bool {
	oid, err := IDFromPublicKey(pk)
	if err != nil {
		return false
	}
	return oid == id
}

// IDFromString cast a string to ID type, and validate
// the id to make sure it is a multihash.
func IDFromString(s string) (ID, error) {
	if _, err := mh.Cast([]byte(s)); err != nil {
		return ID(""), err
	}
	return ID(s), nil
}

// IDFromBytes cast a string to ID type, and validate
// the id to make sure it is a multihash.
func IDFromBytes(b []byte) (ID, error) {
	if _, err := mh.Cast(b); err != nil {
		return ID(""), err
	}
	return ID(b), nil
}

// IDB58Decode returns a b58-decoded Peer
func IDB58Decode(s string) (ID, error) {
	m, err := mh.FromB58String(s)
	if err != nil {
		return "", err
	}
	return ID(m), err
}

// IDB58Encode returns b58-encoded string
func IDB58Encode(id ID) string {
	return b58.Encode([]byte(id))
}

// IDHexDecode returns a b58-decoded Peer
func IDHexDecode(s string) (ID, error) {
	m, err := mh.FromHexString(s)
	if err != nil {
		return "", err
	}
	return ID(m), err
}

// IDHexEncode returns b58-encoded string
func IDHexEncode(id ID) string {
	return hex.EncodeToString([]byte(id))
}

// IDFromPublicKey returns the Peer ID corresponding to pk
func IDFromPublicKey(pk ic.PubKey) (ID, error) {
	b, err := pk.Bytes()
	if err != nil {
		return "", err
	}
	hash := u.Hash(b)
	return ID(hash), nil
}

// IDFromPrivateKey returns the Peer ID corresponding to sk
func IDFromPrivateKey(sk ic.PrivKey) (ID, error) {
	return IDFromPublicKey(sk.GetPublic())
}

// Map maps a Peer ID to a struct.
type Set map[ID]struct{}

// PeerInfo is a small struct used to pass around a peer with
// a set of addresses (and later, keys?). This is not meant to be
// a complete view of the system, but rather to model updates to
// the peerstore. It is used by things like the routing system.
type PeerInfo struct {
	ID    ID
	Addrs []ma.Multiaddr
}

func (pi *PeerInfo) MarshalJSON() ([]byte, error) {
	out := make(map[string]interface{})
	out["ID"] = IDB58Encode(pi.ID)
	var addrs []string
	for _, a := range pi.Addrs {
		addrs = append(addrs, a.String())
	}
	out["Addrs"] = addrs
	return json.Marshal(out)
}

func (pi *PeerInfo) UnmarshalJSON(b []byte) error {
	var data map[string]interface{}
	err := json.Unmarshal(b, &data)
	if err != nil {
		return err
	}
	pid, err := IDB58Decode(data["ID"].(string))
	if err != nil {
		return err
	}
	pi.ID = pid
	addrs, ok := data["Addrs"].([]interface{})
	if ok {
		for _, a := range addrs {
			pi.Addrs = append(pi.Addrs, ma.StringCast(a.(string)))
		}
	}
	return nil
}

// IDSlice for sorting peers
type IDSlice []ID

func (es IDSlice) Len() int           { return len(es) }
func (es IDSlice) Swap(i, j int)      { es[i], es[j] = es[j], es[i] }
func (es IDSlice) Less(i, j int) bool { return string(es[i]) < string(es[j]) }
<commit_msg>make PeerInfo loggable<commit_after>// package peer implements an object used to represent peers in the ipfs network.
package peer

import (
	"encoding/hex"
	"encoding/json"
	"fmt"
	"strings"

	ma "gx/ipfs/QmR3JkmZBKYXgNMNsNZawm914455Qof3PEopwuVSeXG7aV/go-multiaddr"
	b58 "gx/ipfs/QmT8rehPR3F6bmwL6zjUN8XpiDBFFpMP2myPdC6ApsWfJf/go-base58"
	mh "gx/ipfs/QmYf7ng2hG5XBtJA3tN34DQ2GUN5HNksEw1rLDkmr6vGku/go-multihash"

	ic "github.com/ipfs/go-libp2p/p2p/crypto"
	u "gx/ipfs/QmZNVWh8LLjAavuQ2JXuFmuYH3C11xo988vSgp7UQrTRj1/go-ipfs-util"
	logging "gx/ipfs/Qmazh5oNUVsDZTs2g59rq8aYQqwpss8tcUWQzor5sCCEuH/go-log"
)

var log = logging.Logger("peer")

// ID represents the identity of a peer.
type ID string

// Pretty returns a b58-encoded string of the ID
func (id ID) Pretty() string {
	return IDB58Encode(id)
}

func (id ID) Loggable() map[string]interface{} {
	return map[string]interface{}{
		"peerID": id.Pretty(),
	}
}

// String prints out the peer.
//
// TODO(brian): ensure correctness at ID generation and
// enforce this by only exposing functions that generate
// IDs safely. Then any peer.ID type found in the
// codebase is known to be correct.
func (id ID) String() string {
	pid := id.Pretty()

	//All sha256 nodes start with Qm
	//We can skip the Qm to make the peer.ID more useful
	if strings.HasPrefix(pid, "gx/Qm") {
		pid = pid[2:]
	}

	maxRunes := 6
	if len(pid) < maxRunes {
		maxRunes = len(pid)
	}
	return fmt.Sprintf("<peer.ID %s>", pid[:maxRunes])
}

// MatchesPrivateKey tests whether this ID was derived from sk
func (id ID) MatchesPrivateKey(sk ic.PrivKey) bool {
	return id.MatchesPublicKey(sk.GetPublic())
}

// MatchesPublicKey tests whether this ID was derived from pk
func (id ID) MatchesPublicKey(pk ic.PubKey) bool {
	oid, err := IDFromPublicKey(pk)
	if err != nil {
		return false
	}
	return oid == id
}

// IDFromString cast a string to ID type, and validate
// the id to make sure it is a multihash.
func IDFromString(s string) (ID, error) {
	if _, err := mh.Cast([]byte(s)); err != nil {
		return ID(""), err
	}
	return ID(s), nil
}

// IDFromBytes cast a string to ID type, and validate
// the id to make sure it is a multihash.
func IDFromBytes(b []byte) (ID, error) {
	if _, err := mh.Cast(b); err != nil {
		return ID(""), err
	}
	return ID(b), nil
}

// IDB58Decode returns a b58-decoded Peer
func IDB58Decode(s string) (ID, error) {
	m, err := mh.FromB58String(s)
	if err != nil {
		return "", err
	}
	return ID(m), err
}

// IDB58Encode returns b58-encoded string
func IDB58Encode(id ID) string {
	return b58.Encode([]byte(id))
}

// IDHexDecode returns a b58-decoded Peer
func IDHexDecode(s string) (ID, error) {
	m, err := mh.FromHexString(s)
	if err != nil {
		return "", err
	}
	return ID(m), err
}

// IDHexEncode returns b58-encoded string
func IDHexEncode(id ID) string {
	return hex.EncodeToString([]byte(id))
}

// IDFromPublicKey returns the Peer ID corresponding to pk
func IDFromPublicKey(pk ic.PubKey) (ID, error) {
	b, err := pk.Bytes()
	if err != nil {
		return "", err
	}
	hash := u.Hash(b)
	return ID(hash), nil
}

// IDFromPrivateKey returns the Peer ID corresponding to sk
func IDFromPrivateKey(sk ic.PrivKey) (ID, error) {
	return IDFromPublicKey(sk.GetPublic())
}

// Map maps a Peer ID to a struct.
type Set map[ID]struct{}

// PeerInfo is a small struct used to pass around a peer with
// a set of addresses (and later, keys?). This is not meant to be
// a complete view of the system, but rather to model updates to
// the peerstore. It is used by things like the routing system.
type PeerInfo struct {
	ID    ID
	Addrs []ma.Multiaddr
}

func (pi *PeerInfo) Loggable() map[string]interface{} {
	return map[string]interface{}{
		"peerID": pi.ID.Pretty(),
		"addrs":  pi.Addrs,
	}
}

func (pi *PeerInfo) MarshalJSON() ([]byte, error) {
	out := make(map[string]interface{})
	out["ID"] = IDB58Encode(pi.ID)
	var addrs []string
	for _, a := range pi.Addrs {
		addrs = append(addrs, a.String())
	}
	out["Addrs"] = addrs
	return json.Marshal(out)
}

func (pi *PeerInfo) UnmarshalJSON(b []byte) error {
	var data map[string]interface{}
	err := json.Unmarshal(b, &data)
	if err != nil {
		return err
	}
	pid, err := IDB58Decode(data["ID"].(string))
	if err != nil {
		return err
	}
	pi.ID = pid
	addrs, ok := data["Addrs"].([]interface{})
	if ok {
		for _, a := range addrs {
			pi.Addrs = append(pi.Addrs, ma.StringCast(a.(string)))
		}
	}
	return nil
}

// IDSlice for sorting peers
type IDSlice []ID

func (es IDSlice) Len() int           { return len(es) }
func (es IDSlice) Swap(i, j int)      { es[i], es[j] = es[j], es[i] }
func (es IDSlice) Less(i, j int) bool { return string(es[i]) < string(es[j]) }
<|endoftext|>


<commit_before>package sous

import (
	"fmt"
	"strings"
)

type (
	// Deployments is a collection of Deployment.
	Deployments []*Deployment
	// Deployment is a completely configured deployment of a piece of software.
	// It contains all the data necessary for Sous to create a single
	// deployment, which is a single version of a piece of software, running in
	// a single cluster.
	Deployment struct {
		// DeployConfig contains configuration info for this deployment,
		// including environment variables, resources, suggested instance count.
		DeployConfig `yaml:"inline"`
		// Cluster is the name of the cluster this deployment belongs to. Upon
		// parsing the Manifest, this will be set to the key in
		// Manifests.Deployments which points at this Deployment.
		Cluster string
		// SourceVersion is the precise version of the software to be deployed.
		SourceVersion SourceVersion
		// Owners is a map of named owners of this repository. The type of this
		// field is subject to change.
		Owners OwnerSet
		// Kind is the kind of software that SourceRepo represents.
		Kind ManifestKind

		// Notes collected from the deployment's source
		Annotation
	}

	// DeploymentState is used in a DeploymentIntention to describe the state of
	// the deployment: e.g. whether it's been acheived or not
	DeploymentState uint

	// LogicalSequence is used to order DeploymentIntentions and keep track of a
	// canonical order in which they should be satisfied
	LogicalSequence uint

	// An Annotation stores notes about data available from the source of
	// of a Deployment. For instance, the Id field from the source
	// SingularityRequest for a Deployment can be stored to refer to the source post-diff.
	// They don't participate in equality checks on the deployment
	Annotation struct {
		// RequestID stores the Singularity Request ID that was used for this deployment
		RequestID string
	}

	// DeploymentIntentions represents deployments commanded by a user.
	DeploymentIntentions []DeploymentIntention

	// A DeploymentIntention represents a deployment commanded by a user, possibly not yet acheived
	DeploymentIntention struct {
		Deployment
		// State is the relative state of this intention.
		State DeploymentState

		// The sequence this intention was resolved in - might be e.g. synthesized while walking
		// a git history. This might be left as implicit on the sequence of DIs in a []DI,
		// but if there's a change in storage (i.e. not git), or two single DIs need to be compared,
		// the sequence is useful
		Sequence LogicalSequence
	}

	// A DepName is the name of a deployment
	DepName struct {
		cluster string
		source  SourceLocation
	}

	// OwnerSet collects the names of the owners of a deployment
	OwnerSet map[string]struct{}
)

const (
	// Current means the the deployment is the one currently running
	Current DeploymentState = iota

	// Acheived means that the deployment was realized in infrastructure at some point
	Acheived = iota

	// Waiting means the deployment hasn't yet been acheived
	Waiting = iota

	// PassedOver means that the deployment was received but a different deployment was received before this one could be deployed
	PassedOver = iota
)

// Add adds an owner to an ownerset
func (os OwnerSet) Add(owner string) {
	os[owner] = struct{}{}
}

// Remove removes an owner from an ownerset
func (os OwnerSet) Remove(owner string) {
	delete(os, owner)
}

// Equal returns true if two ownersets contain the same owner names
func (os OwnerSet) Equal(o OwnerSet) bool {
	if len(os) != len(o) {
		return false
	}
	for ownr := range os {
		if _, has := o[ownr]; !has {
			return false
		}
	}

	return true
}

// Add adds a deployment to a Deployments
func (ds *Deployments) Add(d *Deployment) {
	*ds = append(*ds, d)
}

// BuildDeployment constructs a deployment out of a Manifest
func BuildDeployment(m *Manifest, spec PartialDeploySpec, inherit DeploymentSpecs) (*Deployment, error) {
	ownMap := OwnerSet{}
	for i := range m.Owners {
		ownMap.Add(m.Owners[i])
	}
	return &Deployment{
		Cluster: spec.clusterName,
		DeployConfig: DeployConfig{
			Resources:    spec.Resources,
			Env:          spec.Env,
			NumInstances: spec.NumInstances,
		},
		Owners:        ownMap,
		Kind:          m.Kind,
		SourceVersion: m.Source.SourceVersion(spec.Version),
	}, nil
}

func (d *Deployment) String() string {
	return fmt.Sprintf("%s @ %s %s", d.SourceVersion, d.Cluster, d.DeployConfig.String())
}

/*
	Deployment struct {
		DeployConfig `yaml:"inline"`
			Args []string `yaml:",omitempty" validate:"values=nonempty"`
			Env  `yaml:",omitempty" validate:"keys=nonempty,values=nonempty"`
			NumInstances int
		Kind ManifestKind
	}
*/

// TabbedDeploymentHeaders returns the names of the fields for Tabbed, suitable for use with text/tabwriter
func TabbedDeploymentHeaders() string {
	return "Cluster\t" +
		"Repo\t" +
		"Version\t" +
		"Offset\t" +
		"NumInstances\t" +
		"Owner\t" +
		"Resources\t" +
		"Env"
}

// Tabbed returns the fields of a deployment formatted in a tab delimited list
func (d *Deployment) Tabbed() string {
	o := "<?>"
	for onr := range d.Owners {
		o = onr
		break
	}

	rs := []string{}
	for k, v := range d.DeployConfig.Resources {
		rs = append(rs, fmt.Sprintf("%s: %s", k, v))
	}
	es := []string{}
	for k, v := range d.DeployConfig.Env {
		es = append(es, fmt.Sprintf("%s: %s", k, v))
	}

	return fmt.Sprintf(
		"%s\t"+ //"Cluster\t" +
			"%s\t"+ //"Repo\t" +
			"%s\t"+ //"Version\t" +
			"%s\t"+ //"Offset\t" +
			"%d\t"+ //"NumInstances\t" +
			"%s\t"+ //"Owner\t" +
			"%s\t"+ //"Resources\t" +
			"%s", //"Env"
		d.Cluster,
		string(d.SourceVersion.RepoURL),
		d.SourceVersion.Version.String(),
		string(d.SourceVersion.RepoOffset),
		d.NumInstances,
		o,
		strings.Join(rs, ", "),
		strings.Join(es, ", "),
	)
}

// Name returns the DepName for a Deployment
func (d *Deployment) Name() DepName {
	return DepName{
		cluster: d.Cluster,
		source:  d.SourceVersion.CanonicalName(),
	}
}

// Equal returns true if two Deployments are equal
func (d *Deployment) Equal(o *Deployment) bool {
	Log.Debug.Printf("%+ v ?= %+ v", d, o)
	if !(d.Cluster == o.Cluster && d.SourceVersion.Equal(o.SourceVersion) && d.Kind == o.Kind) { // && len(d.Owners) == len(o.Owners)) {
		Log.Debug.Printf("C: %t V: %t, K: %t, #O: %t", d.Cluster == o.Cluster, d.SourceVersion.Equal(o.SourceVersion), d.Kind == o.Kind, len(d.Owners) == len(o.Owners))
		return false
	}

	for ownr := range d.Owners {
		if _, has := o.Owners[ownr]; !has {
			return false
		}
	}
	return d.DeployConfig.Equal(o.DeployConfig)
}
<commit_msg>Adds sous.Deployments.Filter method<commit_after>package sous

import (
	"fmt"
	"strings"
)

type (
	// Deployments is a collection of Deployment.
	Deployments []*Deployment
	// Deployment is a completely configured deployment of a piece of software.
	// It contains all the data necessary for Sous to create a single
	// deployment, which is a single version of a piece of software, running in
	// a single cluster.
	Deployment struct {
		// DeployConfig contains configuration info for this deployment,
		// including environment variables, resources, suggested instance count.
		DeployConfig `yaml:"inline"`
		// Cluster is the name of the cluster this deployment belongs to. Upon
		// parsing the Manifest, this will be set to the key in
		// Manifests.Deployments which points at this Deployment.
		Cluster string
		// SourceVersion is the precise version of the software to be deployed.
		SourceVersion SourceVersion
		// Owners is a map of named owners of this repository. The type of this
		// field is subject to change.
		Owners OwnerSet
		// Kind is the kind of software that SourceRepo represents.
		Kind ManifestKind

		// Notes collected from the deployment's source
		Annotation
	}

	// DeploymentState is used in a DeploymentIntention to describe the state of
	// the deployment: e.g. whether it's been acheived or not
	DeploymentState uint

	// LogicalSequence is used to order DeploymentIntentions and keep track of a
	// canonical order in which they should be satisfied
	LogicalSequence uint

	// An Annotation stores notes about data available from the source of
	// of a Deployment. For instance, the Id field from the source
	// SingularityRequest for a Deployment can be stored to refer to the source post-diff.
	// They don't participate in equality checks on the deployment
	Annotation struct {
		// RequestID stores the Singularity Request ID that was used for this deployment
		RequestID string
	}

	// DeploymentIntentions represents deployments commanded by a user.
	DeploymentIntentions []DeploymentIntention

	// A DeploymentIntention represents a deployment commanded by a user, possibly not yet acheived
	DeploymentIntention struct {
		Deployment
		// State is the relative state of this intention.
		State DeploymentState

		// The sequence this intention was resolved in - might be e.g. synthesized while walking
		// a git history. This might be left as implicit on the sequence of DIs in a []DI,
		// but if there's a change in storage (i.e. not git), or two single DIs need to be compared,
		// the sequence is useful
		Sequence LogicalSequence
	}

	// A DepName is the name of a deployment
	DepName struct {
		cluster string
		source  SourceLocation
	}

	// OwnerSet collects the names of the owners of a deployment
	OwnerSet map[string]struct{}
)

const (
	// Current means the the deployment is the one currently running
	Current DeploymentState = iota

	// Acheived means that the deployment was realized in infrastructure at some point
	Acheived = iota

	// Waiting means the deployment hasn't yet been acheived
	Waiting = iota

	// PassedOver means that the deployment was received but a different deployment was received before this one could be deployed
	PassedOver = iota
)

// Add adds an owner to an ownerset
func (os OwnerSet) Add(owner string) {
	os[owner] = struct{}{}
}

// Remove removes an owner from an ownerset
func (os OwnerSet) Remove(owner string) {
	delete(os, owner)
}

// Equal returns true if two ownersets contain the same owner names
func (os OwnerSet) Equal(o OwnerSet) bool {
	if len(os) != len(o) {
		return false
	}
	for ownr := range os {
		if _, has := o[ownr]; !has {
			return false
		}
	}

	return true
}

// Add adds a deployment to a Deployments
func (ds *Deployments) Add(d *Deployment) {
	*ds = append(*ds, d)
}

// Filter returns a new Deployments, filtered based on a predicate. A predicate
// value of nil returns an unfiltered copy of ds.
func (ds *Deployments) Filter(predicate func(*Deployment) bool) Deployments {
	out := Deployments{}
	for _, d := range *ds {
		if predicate == nil || predicate(d) {
			out = append(out, d)
		}
	}
	return out
}

// BuildDeployment constructs a deployment out of a Manifest
func BuildDeployment(m *Manifest, spec PartialDeploySpec, inherit DeploymentSpecs) (*Deployment, error) {
	ownMap := OwnerSet{}
	for i := range m.Owners {
		ownMap.Add(m.Owners[i])
	}
	return &Deployment{
		Cluster: spec.clusterName,
		DeployConfig: DeployConfig{
			Resources:    spec.Resources,
			Env:          spec.Env,
			NumInstances: spec.NumInstances,
		},
		Owners:        ownMap,
		Kind:          m.Kind,
		SourceVersion: m.Source.SourceVersion(spec.Version),
	}, nil
}

func (d *Deployment) String() string {
	return fmt.Sprintf("%s @ %s %s", d.SourceVersion, d.Cluster, d.DeployConfig.String())
}

/*
	Deployment struct {
		DeployConfig `yaml:"inline"`
			Args []string `yaml:",omitempty" validate:"values=nonempty"`
			Env  `yaml:",omitempty" validate:"keys=nonempty,values=nonempty"`
			NumInstances int
		Kind ManifestKind
	}
*/

// TabbedDeploymentHeaders returns the names of the fields for Tabbed, suitable for use with text/tabwriter
func TabbedDeploymentHeaders() string {
	return "Cluster\t" +
		"Repo\t" +
		"Version\t" +
		"Offset\t" +
		"NumInstances\t" +
		"Owner\t" +
		"Resources\t" +
		"Env"
}

// Tabbed returns the fields of a deployment formatted in a tab delimited list
func (d *Deployment) Tabbed() string {
	o := "<?>"
	for onr := range d.Owners {
		o = onr
		break
	}

	rs := []string{}
	for k, v := range d.DeployConfig.Resources {
		rs = append(rs, fmt.Sprintf("%s: %s", k, v))
	}
	es := []string{}
	for k, v := range d.DeployConfig.Env {
		es = append(es, fmt.Sprintf("%s: %s", k, v))
	}

	return fmt.Sprintf(
		"%s\t"+ //"Cluster\t" +
			"%s\t"+ //"Repo\t" +
			"%s\t"+ //"Version\t" +
			"%s\t"+ //"Offset\t" +
			"%d\t"+ //"NumInstances\t" +
			"%s\t"+ //"Owner\t" +
			"%s\t"+ //"Resources\t" +
			"%s", //"Env"
		d.Cluster,
		string(d.SourceVersion.RepoURL),
		d.SourceVersion.Version.String(),
		string(d.SourceVersion.RepoOffset),
		d.NumInstances,
		o,
		strings.Join(rs, ", "),
		strings.Join(es, ", "),
	)
}

// Name returns the DepName for a Deployment
func (d *Deployment) Name() DepName {
	return DepName{
		cluster: d.Cluster,
		source:  d.SourceVersion.CanonicalName(),
	}
}

// Equal returns true if two Deployments are equal
func (d *Deployment) Equal(o *Deployment) bool {
	Log.Debug.Printf("%+ v ?= %+ v", d, o)
	if !(d.Cluster == o.Cluster && d.SourceVersion.Equal(o.SourceVersion) && d.Kind == o.Kind) { // && len(d.Owners) == len(o.Owners)) {
		Log.Debug.Printf("C: %t V: %t, K: %t, #O: %t", d.Cluster == o.Cluster, d.SourceVersion.Equal(o.SourceVersion), d.Kind == o.Kind, len(d.Owners) == len(o.Owners))
		return false
	}

	for ownr := range d.Owners {
		if _, has := o.Owners[ownr]; !has {
			return false
		}
	}
	return d.DeployConfig.Equal(o.DeployConfig)
}
<|endoftext|>


<commit_before>// Licensed to Elasticsearch B.V. under one or more agreements.
// Elasticsearch B.V. licenses this file to you under the Apache 2.0 License.
// See the LICENSE file in the project root for more information.

// +build !integration

package esapi

import (
	"errors"
	"io/ioutil"
	"strings"
	"testing"
)

type errReader struct{}

func (errReader) Read(p []byte) (n int, err error) { return 1, errors.New("MOCK ERROR") }

func TestAPIResponse(t *testing.T) {
	var (
		body string
		res  *Response
	)

	t.Run("String", func(t *testing.T) {
		body = `{"foo":"bar"}`

		res = &Response{StatusCode: 200, Body: ioutil.NopCloser(strings.NewReader(body))}

		expected := `[200 OK]` + ` ` + body
		if res.String() != expected {
			t.Errorf("Unexpected response: %s, want: %s", res.String(), expected)
		}
	})

	t.Run("String with empty response", func(t *testing.T) {
		res = &Response{}

		if res.String() != "[0 ]" {
			t.Errorf("Unexpected response: %s", res.String())
		}
	})

	t.Run("String with nil response", func(t *testing.T) {
		res = nil

		if res.String() != "[0 <nil>]" {
			t.Errorf("Unexpected response: %s", res.String())
		}
	})

	t.Run("String Error", func(t *testing.T) {
		res = &Response{StatusCode: 200, Body: ioutil.NopCloser(errReader{})}

		t.Log(res.String())
		t.Log(res.String())

		if !strings.Contains(res.String(), `error reading response`) {
			t.Errorf("Expected response string to contain 'error reading response', got: %s", res.String())
		}
	})

	t.Run("Status", func(t *testing.T) {
		res = &Response{StatusCode: 404}

		if res.Status() != `404 Not Found` {
			t.Errorf("Unexpected response status text: %s, want: 404 Not Found", res.Status())
		}
	})

	t.Run("IsError", func(t *testing.T) {
		res = &Response{StatusCode: 201}

		if res.IsError() {
			t.Errorf("Unexpected error for response: %s", res.Status())
		}

		res = &Response{StatusCode: 403}

		if !res.IsError() {
			t.Errorf("Expected error for response: %s", res.Status())
		}
	})
}
<commit_msg>API: Remove logging from TestAPIResponse<commit_after>// Licensed to Elasticsearch B.V. under one or more agreements.
// Elasticsearch B.V. licenses this file to you under the Apache 2.0 License.
// See the LICENSE file in the project root for more information.

// +build !integration

package esapi

import (
	"errors"
	"io/ioutil"
	"strings"
	"testing"
)

type errReader struct{}

func (errReader) Read(p []byte) (n int, err error) { return 1, errors.New("MOCK ERROR") }

func TestAPIResponse(t *testing.T) {
	var (
		body string
		res  *Response
	)

	t.Run("String", func(t *testing.T) {
		body = `{"foo":"bar"}`

		res = &Response{StatusCode: 200, Body: ioutil.NopCloser(strings.NewReader(body))}

		expected := `[200 OK]` + ` ` + body
		if res.String() != expected {
			t.Errorf("Unexpected response: %s, want: %s", res.String(), expected)
		}
	})

	t.Run("String with empty response", func(t *testing.T) {
		res = &Response{}

		if res.String() != "[0 ]" {
			t.Errorf("Unexpected response: %s", res.String())
		}
	})

	t.Run("String with nil response", func(t *testing.T) {
		res = nil

		if res.String() != "[0 <nil>]" {
			t.Errorf("Unexpected response: %s", res.String())
		}
	})

	t.Run("String Error", func(t *testing.T) {
		res = &Response{StatusCode: 200, Body: ioutil.NopCloser(errReader{})}

		if !strings.Contains(res.String(), `error reading response`) {
			t.Errorf("Expected response string to contain 'error reading response', got: %s", res.String())
		}
	})

	t.Run("Status", func(t *testing.T) {
		res = &Response{StatusCode: 404}

		if res.Status() != `404 Not Found` {
			t.Errorf("Unexpected response status text: %s, want: 404 Not Found", res.Status())
		}
	})

	t.Run("IsError", func(t *testing.T) {
		res = &Response{StatusCode: 201}

		if res.IsError() {
			t.Errorf("Unexpected error for response: %s", res.Status())
		}

		res = &Response{StatusCode: 403}

		if !res.IsError() {
			t.Errorf("Expected error for response: %s", res.Status())
		}
	})
}
<|endoftext|>


<commit_before>package awsup

import (
	"fmt"
	"github.com/golang/glog"
)

// I believe one vCPU ~ 3 ECUS, and 60 CPU credits would be needed to use one vCPU for an hour
const BurstableCreditsToECUS float32 = 3.0 / 60.0

type AWSMachineTypeInfo struct {
	Name           string
	MemoryGB       float32
	ECU            float32
	Cores          int
	EphemeralDisks []int
	Burstable      bool
}

type EphemeralDevice struct {
	DeviceName  string
	VirtualName string
	SizeGB      int
}

func (m *AWSMachineTypeInfo) EphemeralDevices() []*EphemeralDevice {
	var disks []*EphemeralDevice
	for i, sizeGB := range m.EphemeralDisks {
		d := &EphemeralDevice{
			SizeGB: sizeGB,
		}

		if i >= 20 {
			// TODO: What drive letters do we use?
			glog.Fatalf("ephemeral devices for > 20 not yet implemented")
		}
		d.DeviceName = "/dev/sd" + string('c'+i)
		d.VirtualName = fmt.Sprintf("ephemeral%d", i)

		disks = append(disks, d)
	}
	return disks
}

func GetMachineTypeInfo(machineType string) (*AWSMachineTypeInfo, error) {
	for i := range MachineTypes {
		m := &MachineTypes[i]
		if m.Name == machineType {
			return m, nil
		}
	}

	return nil, fmt.Errorf("instance type not handled: %q", machineType)
}

var MachineTypes []AWSMachineTypeInfo = []AWSMachineTypeInfo{
	// This is tedious, but seems simpler than trying to have some logic and then a lot of exceptions

	// t2 family
	{
		Name:           "t2.nano",
		MemoryGB:       0.5,
		ECU:            3 * BurstableCreditsToECUS,
		Cores:          1,
		EphemeralDisks: nil,
		Burstable:      true,
	},
	{
		Name:           "t2.micro",
		MemoryGB:       1,
		ECU:            6 * BurstableCreditsToECUS,
		Cores:          1,
		EphemeralDisks: nil,
		Burstable:      true,
	},
	{
		Name:           "t2.small",
		MemoryGB:       2,
		ECU:            12 * BurstableCreditsToECUS,
		Cores:          1,
		EphemeralDisks: nil,
		Burstable:      true,
	},
	{
		Name:           "t2.medium",
		MemoryGB:       4,
		ECU:            24 * BurstableCreditsToECUS,
		Cores:          2,
		EphemeralDisks: nil,
		Burstable:      true,
	},
	{
		Name:           "t2.large",
		MemoryGB:       8,
		ECU:            36 * BurstableCreditsToECUS,
		Cores:          2,
		EphemeralDisks: nil,
		Burstable:      true,
	},

	// m3 family
	{
		Name:           "m3.medium",
		MemoryGB:       3.75,
		ECU:            3,
		Cores:          1,
		EphemeralDisks: []int{4},
	},
	{
		Name:           "m3.large",
		MemoryGB:       7.5,
		ECU:            6.5,
		Cores:          2,
		EphemeralDisks: []int{32},
	},
	{
		Name:           "m3.xlarge",
		MemoryGB:       15,
		ECU:            13,
		Cores:          4,
		EphemeralDisks: []int{40, 40},
	},
	{
		Name:           "m3.2xlarge",
		MemoryGB:       30,
		ECU:            26,
		Cores:          8,
		EphemeralDisks: []int{80, 80},
	},

	// m4 family
	{
		Name:           "m4.large",
		MemoryGB:       8,
		ECU:            6.5,
		Cores:          2,
		EphemeralDisks: nil,
	},
	{
		Name:           "m4.xlarge",
		MemoryGB:       16,
		ECU:            13,
		Cores:          4,
		EphemeralDisks: nil,
	},
	{
		Name:           "m4.2xlarge",
		MemoryGB:       32,
		ECU:            26,
		Cores:          8,
		EphemeralDisks: nil,
	},
	{
		Name:           "m4.4xlarge",
		MemoryGB:       64,
		ECU:            53.5,
		Cores:          16,
		EphemeralDisks: nil,
	},
	{
		Name:           "m4.10xlarge",
		MemoryGB:       160,
		ECU:            124.5,
		Cores:          40,
		EphemeralDisks: nil,
	},

	// c3 family
	{
		Name:           "c3.large",
		MemoryGB:       3.75,
		ECU:            7,
		Cores:          2,
		EphemeralDisks: []int{16, 16},
	},
	{
		Name:           "c3.xlarge",
		MemoryGB:       7.5,
		ECU:            14,
		Cores:          4,
		EphemeralDisks: []int{40, 40},
	},
	{
		Name:           "c3.2xlarge",
		MemoryGB:       15,
		ECU:            28,
		Cores:          8,
		EphemeralDisks: []int{80, 80},
	},
	{
		Name:           "c3.4xlarge",
		MemoryGB:       30,
		ECU:            55,
		Cores:          16,
		EphemeralDisks: []int{160, 160},
	},
	{
		Name:           "c3.8xlarge",
		MemoryGB:       60,
		ECU:            108,
		Cores:          32,
		EphemeralDisks: []int{320, 320},
	},

	// c4 family
	{
		Name:           "c4.large",
		MemoryGB:       3.75,
		ECU:            8,
		Cores:          2,
		EphemeralDisks: nil,
	},
	{
		Name:           "c4.xlarge",
		MemoryGB:       7.5,
		ECU:            16,
		Cores:          4,
		EphemeralDisks: nil,
	},
	{
		Name:           "c4.2xlarge",
		MemoryGB:       15,
		ECU:            31,
		Cores:          8,
		EphemeralDisks: nil,
	},
	{
		Name:           "c4.4xlarge",
		MemoryGB:       30,
		ECU:            62,
		Cores:          16,
		EphemeralDisks: nil,
	},
	{
		Name:           "c4.8xlarge",
		MemoryGB:       60,
		ECU:            132,
		Cores:          32,
		EphemeralDisks: nil,
	},
}
<commit_msg>Add support for r3 family<commit_after>package awsup

import (
	"fmt"
	"github.com/golang/glog"
)

// I believe one vCPU ~ 3 ECUS, and 60 CPU credits would be needed to use one vCPU for an hour
const BurstableCreditsToECUS float32 = 3.0 / 60.0

type AWSMachineTypeInfo struct {
	Name           string
	MemoryGB       float32
	ECU            float32
	Cores          int
	EphemeralDisks []int
	Burstable      bool
}

type EphemeralDevice struct {
	DeviceName  string
	VirtualName string
	SizeGB      int
}

func (m *AWSMachineTypeInfo) EphemeralDevices() []*EphemeralDevice {
	var disks []*EphemeralDevice
	for i, sizeGB := range m.EphemeralDisks {
		d := &EphemeralDevice{
			SizeGB: sizeGB,
		}

		if i >= 20 {
			// TODO: What drive letters do we use?
			glog.Fatalf("ephemeral devices for > 20 not yet implemented")
		}
		d.DeviceName = "/dev/sd" + string('c'+i)
		d.VirtualName = fmt.Sprintf("ephemeral%d", i)

		disks = append(disks, d)
	}
	return disks
}

func GetMachineTypeInfo(machineType string) (*AWSMachineTypeInfo, error) {
	for i := range MachineTypes {
		m := &MachineTypes[i]
		if m.Name == machineType {
			return m, nil
		}
	}

	return nil, fmt.Errorf("instance type not handled: %q", machineType)
}

var MachineTypes []AWSMachineTypeInfo = []AWSMachineTypeInfo{
	// This is tedious, but seems simpler than trying to have some logic and then a lot of exceptions

	// t2 family
	{
		Name:           "t2.nano",
		MemoryGB:       0.5,
		ECU:            3 * BurstableCreditsToECUS,
		Cores:          1,
		EphemeralDisks: nil,
		Burstable:      true,
	},
	{
		Name:           "t2.micro",
		MemoryGB:       1,
		ECU:            6 * BurstableCreditsToECUS,
		Cores:          1,
		EphemeralDisks: nil,
		Burstable:      true,
	},
	{
		Name:           "t2.small",
		MemoryGB:       2,
		ECU:            12 * BurstableCreditsToECUS,
		Cores:          1,
		EphemeralDisks: nil,
		Burstable:      true,
	},
	{
		Name:           "t2.medium",
		MemoryGB:       4,
		ECU:            24 * BurstableCreditsToECUS,
		Cores:          2,
		EphemeralDisks: nil,
		Burstable:      true,
	},
	{
		Name:           "t2.large",
		MemoryGB:       8,
		ECU:            36 * BurstableCreditsToECUS,
		Cores:          2,
		EphemeralDisks: nil,
		Burstable:      true,
	},

	// m3 family
	{
		Name:           "m3.medium",
		MemoryGB:       3.75,
		ECU:            3,
		Cores:          1,
		EphemeralDisks: []int{4},
	},
	{
		Name:           "m3.large",
		MemoryGB:       7.5,
		ECU:            6.5,
		Cores:          2,
		EphemeralDisks: []int{32},
	},
	{
		Name:           "m3.xlarge",
		MemoryGB:       15,
		ECU:            13,
		Cores:          4,
		EphemeralDisks: []int{40, 40},
	},
	{
		Name:           "m3.2xlarge",
		MemoryGB:       30,
		ECU:            26,
		Cores:          8,
		EphemeralDisks: []int{80, 80},
	},

	// m4 family
	{
		Name:           "m4.large",
		MemoryGB:       8,
		ECU:            6.5,
		Cores:          2,
		EphemeralDisks: nil,
	},
	{
		Name:           "m4.xlarge",
		MemoryGB:       16,
		ECU:            13,
		Cores:          4,
		EphemeralDisks: nil,
	},
	{
		Name:           "m4.2xlarge",
		MemoryGB:       32,
		ECU:            26,
		Cores:          8,
		EphemeralDisks: nil,
	},
	{
		Name:           "m4.4xlarge",
		MemoryGB:       64,
		ECU:            53.5,
		Cores:          16,
		EphemeralDisks: nil,
	},
	{
		Name:           "m4.10xlarge",
		MemoryGB:       160,
		ECU:            124.5,
		Cores:          40,
		EphemeralDisks: nil,
	},

	// c3 family
	{
		Name:           "c3.large",
		MemoryGB:       3.75,
		ECU:            7,
		Cores:          2,
		EphemeralDisks: []int{16, 16},
	},
	{
		Name:           "c3.xlarge",
		MemoryGB:       7.5,
		ECU:            14,
		Cores:          4,
		EphemeralDisks: []int{40, 40},
	},
	{
		Name:           "c3.2xlarge",
		MemoryGB:       15,
		ECU:            28,
		Cores:          8,
		EphemeralDisks: []int{80, 80},
	},
	{
		Name:           "c3.4xlarge",
		MemoryGB:       30,
		ECU:            55,
		Cores:          16,
		EphemeralDisks: []int{160, 160},
	},
	{
		Name:           "c3.8xlarge",
		MemoryGB:       60,
		ECU:            108,
		Cores:          32,
		EphemeralDisks: []int{320, 320},
	},

	// c4 family
	{
		Name:           "c4.large",
		MemoryGB:       3.75,
		ECU:            8,
		Cores:          2,
		EphemeralDisks: nil,
	},
	{
		Name:           "c4.xlarge",
		MemoryGB:       7.5,
		ECU:            16,
		Cores:          4,
		EphemeralDisks: nil,
	},
	{
		Name:           "c4.2xlarge",
		MemoryGB:       15,
		ECU:            31,
		Cores:          8,
		EphemeralDisks: nil,
	},
	{
		Name:           "c4.4xlarge",
		MemoryGB:       30,
		ECU:            62,
		Cores:          16,
		EphemeralDisks: nil,
	},
	{
		Name:           "c4.8xlarge",
		MemoryGB:       60,
		ECU:            132,
		Cores:          32,
		EphemeralDisks: nil,
	},

	// r3 family
	{
		Name:           "r3.large",
		MemoryGB:       15.25,
		ECU:            6.5,
		Cores:          2,
		EphemeralDisks: []int{32},
	},
	{
		Name:           "r3.xlarge",
		MemoryGB:       30.5,
		ECU:            13,
		Cores:          4,
		EphemeralDisks: []int{80},
	},
	{
		Name:           "r3.2xlarge",
		MemoryGB:       61,
		ECU:            26,
		Cores:          8,
		EphemeralDisks: []int{160},
	},
	{
		Name:           "r3.4xlarge",
		MemoryGB:       122,
		ECU:            52,
		Cores:          16,
		EphemeralDisks: []int{320},
	},
	{
		Name:           "r3.8xlarge",
		MemoryGB:       244,
		ECU:            104,
		Cores:          32,
		EphemeralDisks: []int{320, 320},
	},
}
<|endoftext|>


<commit_before>package integration

import (
	"fmt"
	"io/ioutil"
	"net/http"
	"net/http/httptest"
	"net/http/httputil"
	"os/exec"
	"strings"
	"testing"

	"github.com/sclevine/spec"
	"github.com/stretchr/testify/require"
)

var _ = suite("compute/firewall/create", func(t *testing.T, when spec.G, it spec.S) {
	var (
		expect *require.Assertions
		server *httptest.Server
	)

	it.Before(func() {
		expect = require.New(t)

		server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
			switch req.URL.Path {
			case "/v2/firewalls":
				if req.Method != http.MethodPost {
					w.WriteHeader(http.StatusMethodNotAllowed)
					return
				}

				auth := req.Header.Get("Authorization")
				if auth != "Bearer some-magic-token" {
					w.WriteHeader(http.StatusUnauthorized)
					return
				}

				body, err := ioutil.ReadAll(req.Body)
				expect.NoError(err)
				expect.JSONEq(firewallCreateRequestBody, string(body))

				w.Write([]byte(firewallCreateResponse))
			default:
				dump, err := httputil.DumpRequest(req, true)
				if err != nil {
					t.Fatal("failed to dump request")
				}

				t.Fatalf("received unknown request: %s", dump)
			}
		}))
	})

	when("the minimum required flags are provided", func() {
		it("creates a firewall", func() {
			cmd := exec.Command(builtBinaryPath,
				"-t", "some-magic-token",
				"-u", server.URL,
				"compute",
				"firewall",
				"create",
				"--name", "test-firewall",
				"--inbound-rules", `protocol:tcp,ports:443`,
			)

			output, err := cmd.CombinedOutput()
			expect.NoError(err, fmt.Sprintf("received error output: %s", output))
			expect.Equal(strings.TrimSpace(firewallCreateOutput), strings.TrimSpace(string(output)))
		})
	})
})

const (
	firewallCreateOutput = `
ID                                      Name             Status       Created At              Inbound Rules              Outbound Rules    Droplet IDs    Tags    Pending Changes
e4b9c960-d385-4950-84f3-d102162e6be5    test-firewall    succeeded    2019-10-24T20:30:26Z    protocol:tcp,ports:443,`

	firewallCreateRequestBody = `{
  "name":"test-firewall",
  "inbound_rules":[{
	"protocol":"tcp",
	"ports":"443",
	"sources":{}
  }],
  "outbound_rules":null,
  "droplet_ids":[],
  "tags":[]
}`

	firewallCreateResponse = `{
  "firewall": {
	"id":"e4b9c960-d385-4950-84f3-d102162e6be5",
	"name":"test-firewall",
	"status":"succeeded",
	"inbound_rules":[{
	  "protocol":"tcp",
	  "ports":"443",
	  "sources":{}
	}],
	"outbound_rules":[],
	"created_at":"2019-10-24T20:30:26Z",
	"droplet_ids":[],
	"tags":[],
	"pending_changes":[]
  }
}`
)
<commit_msg>Add command aliases<commit_after>package integration

import (
	"fmt"
	"io/ioutil"
	"net/http"
	"net/http/httptest"
	"net/http/httputil"
	"os/exec"
	"strings"
	"testing"

	"github.com/sclevine/spec"
	"github.com/stretchr/testify/require"
)

var _ = suite("compute/firewall/create", func(t *testing.T, when spec.G, it spec.S) {
	var (
		expect *require.Assertions
		server *httptest.Server
	)

	it.Before(func() {
		expect = require.New(t)

		server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
			switch req.URL.Path {
			case "/v2/firewalls":
				if req.Method != http.MethodPost {
					w.WriteHeader(http.StatusMethodNotAllowed)
					return
				}

				auth := req.Header.Get("Authorization")
				if auth != "Bearer some-magic-token" {
					w.WriteHeader(http.StatusUnauthorized)
					return
				}

				body, err := ioutil.ReadAll(req.Body)
				expect.NoError(err)
				expect.JSONEq(firewallCreateRequestBody, string(body))

				w.Write([]byte(firewallCreateResponse))
			default:
				dump, err := httputil.DumpRequest(req, true)
				if err != nil {
					t.Fatal("failed to dump request")
				}

				t.Fatalf("received unknown request: %s", dump)
			}
		}))
	})

	when("the minimum required flags are provided", func() {
		it("creates a firewall", func() {
			aliases := []string{"create", "c"}

			for _, alias := range aliases {
				cmd := exec.Command(builtBinaryPath,
					"-t", "some-magic-token",
					"-u", server.URL,
					"compute",
					"firewall",
					alias,
					"--name", "test-firewall",
					"--inbound-rules", `protocol:tcp,ports:443`,
				)

				output, err := cmd.CombinedOutput()
				expect.NoError(err, fmt.Sprintf("received error output: %s", output))
				expect.Equal(strings.TrimSpace(firewallCreateOutput), strings.TrimSpace(string(output)))
			}
		})
	})
})

const (
	firewallCreateOutput = `
ID                                      Name             Status       Created At              Inbound Rules              Outbound Rules    Droplet IDs    Tags    Pending Changes
e4b9c960-d385-4950-84f3-d102162e6be5    test-firewall    succeeded    2019-10-24T20:30:26Z    protocol:tcp,ports:443,`

	firewallCreateRequestBody = `{
  "name":"test-firewall",
  "inbound_rules":[{
	"protocol":"tcp",
	"ports":"443",
	"sources":{}
  }],
  "outbound_rules":null,
  "droplet_ids":[],
  "tags":[]
}`

	firewallCreateResponse = `{
  "firewall": {
	"id":"e4b9c960-d385-4950-84f3-d102162e6be5",
	"name":"test-firewall",
	"status":"succeeded",
	"inbound_rules":[{
	  "protocol":"tcp",
	  "ports":"443",
	  "sources":{}
	}],
	"outbound_rules":[],
	"created_at":"2019-10-24T20:30:26Z",
	"droplet_ids":[],
	"tags":[],
	"pending_changes":[]
  }
}`
)
<|endoftext|>


<commit_before>// Copyright (c) 2014-2016 The btcsuite developers
// Copyright (c) 2015-2017 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.

package bloom_test

import (
	"fmt"
	"math/rand"
	"time"

	"github.com/decred/dcrd/bloom"
	"github.com/decred/dcrd/chaincfg/chainhash"
	"github.com/decred/dcrd/wire"
)

// This example demonstrates how to create a new bloom filter, add a transaction
// hash to it, and check if the filter matches the transaction.
func ExampleNewFilter() {
	rand.Seed(time.Now().UnixNano())
	tweak := rand.Uint32()

	// Create a new bloom filter intended to hold 10 elements with a 0.01%
	// false positive rate and does not include any automatic update
	// functionality when transactions are matched.
	filter := bloom.NewFilter(10, tweak, 0.0001, wire.BloomUpdateNone)

	// Create a transaction hash and add it to the filter.  This particular
	// trasaction is the first transaction in block 310,000 of the main
	// bitcoin block chain.
	txHashStr := "fd611c56ca0d378cdcd16244b45c2ba9588da3adac367c4ef43e808b280b8a45"
	txHash, err := chainhash.NewHashFromStr(txHashStr)
	if err != nil {
		fmt.Println(err)
		return
	}
	filter.AddHash(txHash)

	// Show that the filter matches.
	matches := filter.Matches(txHash[:])
	fmt.Println("Filter Matches?:", matches)

	// Output:
	// Filter Matches?: true
}
<commit_msg>bloom: workaround go vet issue in example (#895)<commit_after>// Copyright (c) 2014-2016 The btcsuite developers
// Copyright (c) 2015-2017 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.

package bloom_test

import (
	"fmt"
	"math/rand"
	"time"

	"github.com/decred/dcrd/bloom"
	"github.com/decred/dcrd/chaincfg/chainhash"
	"github.com/decred/dcrd/wire"
)

// This example demonstrates how to create a new bloom filter, add a transaction
// hash to it, and check if the filter matches the transaction.
func Example_newFilter() {
	rand.Seed(time.Now().UnixNano())
	tweak := rand.Uint32()

	// Create a new bloom filter intended to hold 10 elements with a 0.01%
	// false positive rate and does not include any automatic update
	// functionality when transactions are matched.
	filter := bloom.NewFilter(10, tweak, 0.0001, wire.BloomUpdateNone)

	// Create a transaction hash and add it to the filter.  This particular
	// trasaction is the first transaction in block 310,000 of the main
	// bitcoin block chain.
	txHashStr := "fd611c56ca0d378cdcd16244b45c2ba9588da3adac367c4ef43e808b280b8a45"
	txHash, err := chainhash.NewHashFromStr(txHashStr)
	if err != nil {
		fmt.Println(err)
		return
	}
	filter.AddHash(txHash)

	// Show that the filter matches.
	matches := filter.Matches(txHash[:])
	fmt.Println("Filter Matches?:", matches)

	// Output:
	// Filter Matches?: true
}
<|endoftext|>


<commit_before>package bot

import (
	"log"
	"net"

	"github.com/belak/irc"
	"github.com/khades/servbot/ircClient"
	"github.com/khades/servbot/repos"
)

type chatClient struct {
	Client *irc.Client
	Ready  bool
}

// Start function dials up connection for chathandler
func Start() {
	conn, err := net.Dial("tcp", "irc.chat.twitch.tv:6667")
	if err != nil {
		log.Fatalln(err)
	}

	config := irc.ClientConfig{
		Nick:    repos.Config.BotUserName,
		Pass:    repos.Config.OauthKey,
		User:    repos.Config.BotUserName,
		Name:    repos.Config.BotUserName,
		Handler: chatHandler}
	chatClient := irc.NewClient(conn, config)
	log.Println("Bot is starting...")

	clientError := chatClient.Run()
	log.Fatalln(clientError)
	IrcClientInstance = ircClient.IrcClient{Ready: false}
	conn.Close()
}
<commit_msg>adding log message if bot dies<commit_after>package bot

import (
	"log"
	"net"

	"github.com/belak/irc"
	"github.com/khades/servbot/ircClient"
	"github.com/khades/servbot/repos"
)

type chatClient struct {
	Client *irc.Client
	Ready  bool
}

// Start function dials up connection for chathandler
func Start() {
	conn, err := net.Dial("tcp", "irc.chat.twitch.tv:6667")
	if err != nil {
		log.Fatalln(err)
	}

	config := irc.ClientConfig{
		Nick:    repos.Config.BotUserName,
		Pass:    repos.Config.OauthKey,
		User:    repos.Config.BotUserName,
		Name:    repos.Config.BotUserName,
		Handler: chatHandler}
	chatClient := irc.NewClient(conn, config)
	log.Println("Bot is starting...")

	clientError := chatClient.Run()
	log.Fatalln(clientError)
	log.Println("Bot died...")
	IrcClientInstance = ircClient.IrcClient{Ready: false}
	conn.Close()
}
<|endoftext|>


<commit_before>package shorty

import (
	"errors"
	"github.com/golang/glog"
	"github.com/qorio/omni/http"
	"net/url"
	"regexp"
	"strings"
	"time"
)

func (this *ShortUrl) MatchRule(service Shorty, userAgent *http.UserAgent,
	origin *http.RequestOrigin, cookies http.Cookies) (matchedRule *RoutingRule, err error) {

	glog.Infoln("matching userAgent=", userAgent, "origin=", origin, "referrer=", origin.Referrer)

	for _, rule := range this.Rules {
		if match := rule.Match(this.service, userAgent, origin, cookies); match {
			matchedRule = &rule
			break
		}
	}
	if matchedRule == nil || matchedRule.Destination == "" {
		err = errors.New("not found")
	} else {
		for _, sub := range matchedRule.Special {
			matchSub := sub.Match(this.service, userAgent, origin, cookies)
			glog.Infoln("Checking subrule:", sub, "matched=", matchSub)
			if matchSub {
				matchedRule = &sub
				return
			}
		}
	}
	return
}

func (this OnOff) IsOn() bool {
	return strings.ToLower(string(this)) == "on"
}

func (this *RoutingRule) Validate() (err error) {
	if len(this.Destination) > 0 {
		if _, err = url.Parse(this.Destination); err != nil {
			return
		}
	}
	matching := 0
	if c, err := regexp.Compile(string(this.MatchPlatform)); err != nil {
		return errors.New("Bad platform regex " + string(this.MatchPlatform))
	} else if c != nil {
		matching++
	}
	if c, err := regexp.Compile(string(this.MatchOS)); err != nil {
		return errors.New("Bad os regex " + string(this.MatchOS))
	} else if c != nil {
		matching++
	}
	if c, err := regexp.Compile(string(this.MatchMake)); err != nil {
		return errors.New("Bad make regex " + string(this.MatchMake))
	} else if c != nil {
		matching++
	}
	if c, err := regexp.Compile(string(this.MatchBrowser)); err != nil {
		return errors.New("Bad browser regex " + string(this.MatchBrowser))
	} else if c != nil {
		matching++
	}
	// Must have 1 or more matching regexp
	if matching == 0 {
		err = errors.New("bad-routing-rule:no matching regexp")
		return
	}
	return
}

// TODO - precompile the regexs and store them in the Routing rule
func (this *RoutingRule) Match(service Shorty, ua *http.UserAgent, origin *http.RequestOrigin, cookies http.Cookies) bool {
	// use bit mask to match
	var actual, expect int = 0, 0

	if len(string(this.MatchPlatform)) > 0 {
		expect |= 1 << 0
		if matches, _ := regexp.MatchString(string(this.MatchPlatform), ua.Platform); matches {
			actual |= 1 << 0
		}
	}
	if len(string(this.MatchOS)) > 0 {
		expect |= 1 << 1
		if matches, _ := regexp.MatchString(string(this.MatchOS), ua.OS); matches {
			actual |= 1 << 1
		}
	}
	if len(string(this.MatchMake)) > 0 {
		expect |= 1 << 2
		if matches, _ := regexp.MatchString(string(this.MatchMake), ua.Make); matches {
			actual |= 1 << 2
		}
	}
	if len(string(this.MatchBrowser)) > 0 {
		expect |= 1 << 3
		if matches, _ := regexp.MatchString(string(this.MatchBrowser), ua.Browser); matches {
			actual |= 1 << 3
		}
	}
	if this.MatchMobile.IsOn() {
		expect |= 1 << 4
		if ua.Mobile {
			actual |= 1 << 4
		}
	}
	if len(string(this.MatchReferrer)) > 0 {
		expect |= 1 << 5
		if matches, _ := regexp.MatchString(string(this.MatchReferrer), origin.Referrer); matches {
			actual |= 1 << 5
		}
	}
	if this.MatchNoAppOpenInXDays.IsOn() && this.AppUrlScheme != "" {
		expect |= 1 << 6
		uuid, _ := cookies.GetPlainString(uuidCookieKey)
		appOpen, found, _ := service.FindAppOpen(UrlScheme(this.AppUrlScheme), UUID(uuid))
		if !found || float64(time.Now().Unix()-appOpen.Timestamp) >= this.AppOpenTTLDays*24.*60.*60. {
			actual |= 1 << 6
		}
	}
	// By the time we get here, we have done a match all
	return actual == expect && expect > 0
}
<commit_msg>Log level<commit_after>package shorty

import (
	"errors"
	"github.com/golang/glog"
	"github.com/qorio/omni/http"
	"net/url"
	"regexp"
	"strings"
	"time"
)

func (this *ShortUrl) MatchRule(service Shorty, userAgent *http.UserAgent,
	origin *http.RequestOrigin, cookies http.Cookies) (matchedRule *RoutingRule, err error) {

	defer glog.V(10).Infoln("matched-rule", "id=", matchedRule.Id, "comment=", matchedRule.Comment,
		"origin=", origin, "referrer=", origin.Referrer, "userAgent=", userAgent)

	for _, rule := range this.Rules {
		if match := rule.Match(this.service, userAgent, origin, cookies); match {
			matchedRule = &rule
			break
		}
	}
	if matchedRule == nil || matchedRule.Destination == "" {
		err = errors.New("not found")
	} else {
		for _, sub := range matchedRule.Special {
			matchSub := sub.Match(this.service, userAgent, origin, cookies)
			if matchSub {
				matchedRule = &sub
				return
			}
		}
	}
	return
}

func (this OnOff) IsOn() bool {
	return strings.ToLower(string(this)) == "on"
}

func (this *RoutingRule) Validate() (err error) {
	if len(this.Destination) > 0 {
		if _, err = url.Parse(this.Destination); err != nil {
			return
		}
	}
	matching := 0
	if c, err := regexp.Compile(string(this.MatchPlatform)); err != nil {
		return errors.New("Bad platform regex " + string(this.MatchPlatform))
	} else if c != nil {
		matching++
	}
	if c, err := regexp.Compile(string(this.MatchOS)); err != nil {
		return errors.New("Bad os regex " + string(this.MatchOS))
	} else if c != nil {
		matching++
	}
	if c, err := regexp.Compile(string(this.MatchMake)); err != nil {
		return errors.New("Bad make regex " + string(this.MatchMake))
	} else if c != nil {
		matching++
	}
	if c, err := regexp.Compile(string(this.MatchBrowser)); err != nil {
		return errors.New("Bad browser regex " + string(this.MatchBrowser))
	} else if c != nil {
		matching++
	}
	// Must have 1 or more matching regexp
	if matching == 0 {
		err = errors.New("bad-routing-rule:no matching regexp")
		return
	}
	return
}

// TODO - precompile the regexs and store them in the Routing rule
func (this *RoutingRule) Match(service Shorty, ua *http.UserAgent, origin *http.RequestOrigin, cookies http.Cookies) bool {
	// use bit mask to match
	var actual, expect int = 0, 0

	if len(string(this.MatchPlatform)) > 0 {
		expect |= 1 << 0
		if matches, _ := regexp.MatchString(string(this.MatchPlatform), ua.Platform); matches {
			actual |= 1 << 0
		}
	}
	if len(string(this.MatchOS)) > 0 {
		expect |= 1 << 1
		if matches, _ := regexp.MatchString(string(this.MatchOS), ua.OS); matches {
			actual |= 1 << 1
		}
	}
	if len(string(this.MatchMake)) > 0 {
		expect |= 1 << 2
		if matches, _ := regexp.MatchString(string(this.MatchMake), ua.Make); matches {
			actual |= 1 << 2
		}
	}
	if len(string(this.MatchBrowser)) > 0 {
		expect |= 1 << 3
		if matches, _ := regexp.MatchString(string(this.MatchBrowser), ua.Browser); matches {
			actual |= 1 << 3
		}
	}
	if this.MatchMobile.IsOn() {
		expect |= 1 << 4
		if ua.Mobile {
			actual |= 1 << 4
		}
	}
	if len(string(this.MatchReferrer)) > 0 {
		expect |= 1 << 5
		if matches, _ := regexp.MatchString(string(this.MatchReferrer), origin.Referrer); matches {
			actual |= 1 << 5
		}
	}
	if this.MatchNoAppOpenInXDays.IsOn() && this.AppUrlScheme != "" {
		expect |= 1 << 6
		uuid, _ := cookies.GetPlainString(uuidCookieKey)
		appOpen, found, _ := service.FindAppOpen(UrlScheme(this.AppUrlScheme), UUID(uuid))
		if !found || float64(time.Now().Unix()-appOpen.Timestamp) >= this.AppOpenTTLDays*24.*60.*60. {
			actual |= 1 << 6
		}
	}
	// By the time we get here, we have done a match all
	return actual == expect && expect > 0
}
<|endoftext|>


<commit_before>/*
Copyright 2016 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 options

import (
	"fmt"
	"net"
	"strings"
	"time"

	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime/serializer"
	"k8s.io/apimachinery/pkg/util/errors"
	"k8s.io/apiserver/pkg/server"
	utilfeature "k8s.io/apiserver/pkg/util/feature"

	"github.com/spf13/pflag"
)

// ServerRunOptions contains the options while running a generic api server.
type ServerRunOptions struct {
	AdvertiseAddress net.IP

	CorsAllowedOriginList       []string
	HSTSDirectives              []string
	ExternalHost                string
	MaxRequestsInFlight         int
	MaxMutatingRequestsInFlight int
	RequestTimeout              time.Duration
	GoawayChance                float64
	LivezGracePeriod            time.Duration
	MinRequestTimeout           int
	ShutdownDelayDuration       time.Duration
	// We intentionally did not add a flag for this option. Users of the
	// apiserver library can wire it to a flag.
	JSONPatchMaxCopyBytes int64
	// The limit on the request body size that would be accepted and
	// decoded in a write request. 0 means no limit.
	// We intentionally did not add a flag for this option. Users of the
	// apiserver library can wire it to a flag.
	MaxRequestBodyBytes       int64
	EnablePriorityAndFairness bool

	// ShutdownSendRetryAfter dictates when to initiate shutdown of the HTTP
	// Server during the graceful termination of the apiserver. If true, we wait
	// for non longrunning requests in flight to be drained and then initiate a
	// shutdown of the HTTP Server. If false, we initiate a shutdown of the HTTP
	// Server as soon as ShutdownDelayDuration has elapsed.
	// If enabled, after ShutdownDelayDuration elapses, any incoming request is
	// rejected with a 429 status code and a 'Retry-After' response.
	ShutdownSendRetryAfter bool

	// StartupSendRetryAfterUntilReady once set will reject incoming requests with
	// a 429 status code and a 'Retry-After' response header until the apiserver
	// hasn't fully initialized.
	// This option ensures that the system stays consistent even when requests
	// are received before the server has been initialized.
	// In particular, it prevents child deletion in case of GC or/and orphaned
	// content in case of the namespaces controller.
	StartupSendRetryAfterUntilReady bool
}

func NewServerRunOptions() *ServerRunOptions {
	defaults := server.NewConfig(serializer.CodecFactory{})
	return &ServerRunOptions{
		MaxRequestsInFlight:             defaults.MaxRequestsInFlight,
		MaxMutatingRequestsInFlight:     defaults.MaxMutatingRequestsInFlight,
		RequestTimeout:                  defaults.RequestTimeout,
		LivezGracePeriod:                defaults.LivezGracePeriod,
		MinRequestTimeout:               defaults.MinRequestTimeout,
		ShutdownDelayDuration:           defaults.ShutdownDelayDuration,
		JSONPatchMaxCopyBytes:           defaults.JSONPatchMaxCopyBytes,
		MaxRequestBodyBytes:             defaults.MaxRequestBodyBytes,
		EnablePriorityAndFairness:       true,
		ShutdownSendRetryAfter:          false,
		StartupSendRetryAfterUntilReady: false,
	}
}

// ApplyTo applies the run options to the method receiver and returns self
func (s *ServerRunOptions) ApplyTo(c *server.Config) error {
	c.CorsAllowedOriginList = s.CorsAllowedOriginList
	c.HSTSDirectives = s.HSTSDirectives
	c.ExternalAddress = s.ExternalHost
	c.MaxRequestsInFlight = s.MaxRequestsInFlight
	c.MaxMutatingRequestsInFlight = s.MaxMutatingRequestsInFlight
	c.LivezGracePeriod = s.LivezGracePeriod
	c.RequestTimeout = s.RequestTimeout
	c.GoawayChance = s.GoawayChance
	c.MinRequestTimeout = s.MinRequestTimeout
	c.ShutdownDelayDuration = s.ShutdownDelayDuration
	c.JSONPatchMaxCopyBytes = s.JSONPatchMaxCopyBytes
	c.MaxRequestBodyBytes = s.MaxRequestBodyBytes
	c.PublicAddress = s.AdvertiseAddress
	c.ShutdownSendRetryAfter = s.ShutdownSendRetryAfter
	c.StartupSendRetryAfterUntilReady = s.StartupSendRetryAfterUntilReady

	return nil
}

// DefaultAdvertiseAddress sets the field AdvertiseAddress if unset. The field will be set based on the SecureServingOptions.
func (s *ServerRunOptions) DefaultAdvertiseAddress(secure *SecureServingOptions) error {
	if secure == nil {
		return nil
	}

	if s.AdvertiseAddress == nil || s.AdvertiseAddress.IsUnspecified() {
		hostIP, err := secure.DefaultExternalAddress()
		if err != nil {
			return fmt.Errorf("Unable to find suitable network address.error='%v'. "+
				"Try to set the AdvertiseAddress directly or provide a valid BindAddress to fix this.", err)
		}
		s.AdvertiseAddress = hostIP
	}

	return nil
}

// Validate checks validation of ServerRunOptions
func (s *ServerRunOptions) Validate() []error {
	errors := []error{}

	if s.LivezGracePeriod < 0 {
		errors = append(errors, fmt.Errorf("--livez-grace-period can not be a negative value"))
	}

	if s.MaxRequestsInFlight < 0 {
		errors = append(errors, fmt.Errorf("--max-requests-inflight can not be negative value"))
	}
	if s.MaxMutatingRequestsInFlight < 0 {
		errors = append(errors, fmt.Errorf("--max-mutating-requests-inflight can not be negative value"))
	}

	if s.RequestTimeout.Nanoseconds() < 0 {
		errors = append(errors, fmt.Errorf("--request-timeout can not be negative value"))
	}

	if s.GoawayChance < 0 || s.GoawayChance > 0.02 {
		errors = append(errors, fmt.Errorf("--goaway-chance can not be less than 0 or greater than 0.02"))
	}

	if s.MinRequestTimeout < 0 {
		errors = append(errors, fmt.Errorf("--min-request-timeout can not be negative value"))
	}

	if s.ShutdownDelayDuration < 0 {
		errors = append(errors, fmt.Errorf("--shutdown-delay-duration can not be negative value"))
	}

	if s.JSONPatchMaxCopyBytes < 0 {
		errors = append(errors, fmt.Errorf("--json-patch-max-copy-bytes can not be negative value"))
	}

	if s.MaxRequestBodyBytes < 0 {
		errors = append(errors, fmt.Errorf("--max-resource-write-bytes can not be negative value"))
	}

	if err := validateHSTSDirectives(s.HSTSDirectives); err != nil {
		errors = append(errors, err)
	}
	return errors
}

func validateHSTSDirectives(hstsDirectives []string) error {
	// HSTS Headers format: Strict-Transport-Security:max-age=expireTime [;includeSubDomains] [;preload]
	// See https://tools.ietf.org/html/rfc6797#section-6.1 for more information
	allErrors := []error{}
	for _, hstsDirective := range hstsDirectives {
		if len(strings.TrimSpace(hstsDirective)) == 0 {
			allErrors = append(allErrors, fmt.Errorf("empty value in strict-transport-security-directives"))
			continue
		}
		if hstsDirective != "includeSubDomains" && hstsDirective != "preload" {
			maxAgeDirective := strings.Split(hstsDirective, "=")
			if len(maxAgeDirective) != 2 || maxAgeDirective[0] != "max-age" {
				allErrors = append(allErrors, fmt.Errorf("--strict-transport-security-directives invalid, allowed values: max-age=expireTime, includeSubDomains, preload. see https://tools.ietf.org/html/rfc6797#section-6.1 for more information"))
			}
		}
	}
	return errors.NewAggregate(allErrors)
}

// AddUniversalFlags adds flags for a specific APIServer to the specified FlagSet
func (s *ServerRunOptions) AddUniversalFlags(fs *pflag.FlagSet) {
	// Note: the weird ""+ in below lines seems to be the only way to get gofmt to
	// arrange these text blocks sensibly. Grrr.

	fs.IPVar(&s.AdvertiseAddress, "advertise-address", s.AdvertiseAddress, ""+
		"The IP address on which to advertise the apiserver to members of the cluster. This "+
		"address must be reachable by the rest of the cluster. If blank, the --bind-address "+
		"will be used. If --bind-address is unspecified, the host's default interface will "+
		"be used.")

	fs.StringSliceVar(&s.CorsAllowedOriginList, "cors-allowed-origins", s.CorsAllowedOriginList, ""+
		"List of allowed origins for CORS, comma separated.  An allowed origin can be a regular "+
		"expression to support subdomain matching. If this list is empty CORS will not be enabled.")

	fs.StringSliceVar(&s.HSTSDirectives, "strict-transport-security-directives", s.HSTSDirectives, ""+
		"List of directives for HSTS, comma separated. If this list is empty, then HSTS directives will not "+
		"be added. Example: 'max-age=31536000,includeSubDomains,preload'")

	deprecatedTargetRAMMB := 0
	fs.IntVar(&deprecatedTargetRAMMB, "target-ram-mb", deprecatedTargetRAMMB,
		"DEPRECATED: Memory limit for apiserver in MB (used to configure sizes of caches, etc.)")
	fs.MarkDeprecated("target-ram-mb", "This flag will be removed in v1.23")

	fs.StringVar(&s.ExternalHost, "external-hostname", s.ExternalHost,
		"The hostname to use when generating externalized URLs for this master (e.g. Swagger API Docs or OpenID Discovery).")

	deprecatedMasterServiceNamespace := metav1.NamespaceDefault
	fs.StringVar(&deprecatedMasterServiceNamespace, "master-service-namespace", deprecatedMasterServiceNamespace, ""+
		"DEPRECATED: the namespace from which the Kubernetes master services should be injected into pods.")

	fs.IntVar(&s.MaxRequestsInFlight, "max-requests-inflight", s.MaxRequestsInFlight, ""+
		"This and --max-mutating-requests-inflight are summed to determine the server's total concurrency limit "+
		"(which must be positive) if --enable-priority-and-fairness is true. "+
		"Otherwise, this flag limits the maximum number of non-mutating requests in flight, "+
		"or a zero value disables the limit completely.")

	fs.IntVar(&s.MaxMutatingRequestsInFlight, "max-mutating-requests-inflight", s.MaxMutatingRequestsInFlight, ""+
		"This and --max-requests-inflight are summed to determine the server's total concurrency limit "+
		"(which must be positive) if --enable-priority-and-fairness is true. "+
		"Otherwise, this flag limits the maximum number of mutating requests in flight, "+
		"or a zero value disables the limit completely.")

	fs.DurationVar(&s.RequestTimeout, "request-timeout", s.RequestTimeout, ""+
		"An optional field indicating the duration a handler must keep a request open before timing "+
		"it out. This is the default request timeout for requests but may be overridden by flags such as "+
		"--min-request-timeout for specific types of requests.")

	fs.Float64Var(&s.GoawayChance, "goaway-chance", s.GoawayChance, ""+
		"To prevent HTTP/2 clients from getting stuck on a single apiserver, randomly close a connection (GOAWAY). "+
		"The client's other in-flight requests won't be affected, and the client will reconnect, likely landing on a different apiserver after going through the load balancer again. "+
		"This argument sets the fraction of requests that will be sent a GOAWAY. Clusters with single apiservers, or which don't use a load balancer, should NOT enable this. "+
		"Min is 0 (off), Max is .02 (1/50 requests); .001 (1/1000) is a recommended starting point.")

	fs.DurationVar(&s.LivezGracePeriod, "livez-grace-period", s.LivezGracePeriod, ""+
		"This option represents the maximum amount of time it should take for apiserver to complete its startup sequence "+
		"and become live. From apiserver's start time to when this amount of time has elapsed, /livez will assume "+
		"that unfinished post-start hooks will complete successfully and therefore return true.")

	fs.IntVar(&s.MinRequestTimeout, "min-request-timeout", s.MinRequestTimeout, ""+
		"An optional field indicating the minimum number of seconds a handler must keep "+
		"a request open before timing it out. Currently only honored by the watch request "+
		"handler, which picks a randomized value above this number as the connection timeout, "+
		"to spread out load.")

	fs.BoolVar(&s.EnablePriorityAndFairness, "enable-priority-and-fairness", s.EnablePriorityAndFairness, ""+
		"If true and the APIPriorityAndFairness feature gate is enabled, replace the max-in-flight handler with an enhanced one that queues and dispatches with priority and fairness")

	fs.DurationVar(&s.ShutdownDelayDuration, "shutdown-delay-duration", s.ShutdownDelayDuration, ""+
		"Time to delay the termination. During that time the server keeps serving requests normally. The endpoints /healthz and /livez "+
		"will return success, but /readyz immediately returns failure. Graceful termination starts after this delay "+
		"has elapsed. This can be used to allow load balancer to stop sending traffic to this server.")

	fs.BoolVar(&s.ShutdownSendRetryAfter, "shutdown-send-retry-after", s.ShutdownSendRetryAfter, ""+
		"If true the HTTP Server will continue listening until all non long running request(s) in flight have been drained, "+
		"during this window all incoming requests will be rejected with a status code 429 and a 'Retry-After' response header, "+
		"in addition 'Connection: close' response header is set in order to tear down the TCP connection when idle.")

	fs.BoolVar(&s.StartupSendRetryAfterUntilReady, "startup-send-retry-after-until-ready", s.ShutdownSendRetryAfter, ""+
		"If true, incoming request(s) will be rejected with a '429' status code and a 'Retry-After' response header "+
		"until the apiserver has initialized. This option ensures that the system stays consistent even when requests "+
		"arrive at the server before it has been initialized.")

	utilfeature.DefaultMutableFeatureGate.AddFlag(fs)
}
<commit_msg>UPSTREAM: 104630: remove server option startup-send-retry-after-until-ready<commit_after>/*
Copyright 2016 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 options

import (
	"fmt"
	"net"
	"strings"
	"time"

	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime/serializer"
	"k8s.io/apimachinery/pkg/util/errors"
	"k8s.io/apiserver/pkg/server"
	utilfeature "k8s.io/apiserver/pkg/util/feature"

	"github.com/spf13/pflag"
)

// ServerRunOptions contains the options while running a generic api server.
type ServerRunOptions struct {
	AdvertiseAddress net.IP

	CorsAllowedOriginList       []string
	HSTSDirectives              []string
	ExternalHost                string
	MaxRequestsInFlight         int
	MaxMutatingRequestsInFlight int
	RequestTimeout              time.Duration
	GoawayChance                float64
	LivezGracePeriod            time.Duration
	MinRequestTimeout           int
	ShutdownDelayDuration       time.Duration
	// We intentionally did not add a flag for this option. Users of the
	// apiserver library can wire it to a flag.
	JSONPatchMaxCopyBytes int64
	// The limit on the request body size that would be accepted and
	// decoded in a write request. 0 means no limit.
	// We intentionally did not add a flag for this option. Users of the
	// apiserver library can wire it to a flag.
	MaxRequestBodyBytes       int64
	EnablePriorityAndFairness bool

	// ShutdownSendRetryAfter dictates when to initiate shutdown of the HTTP
	// Server during the graceful termination of the apiserver. If true, we wait
	// for non longrunning requests in flight to be drained and then initiate a
	// shutdown of the HTTP Server. If false, we initiate a shutdown of the HTTP
	// Server as soon as ShutdownDelayDuration has elapsed.
	// If enabled, after ShutdownDelayDuration elapses, any incoming request is
	// rejected with a 429 status code and a 'Retry-After' response.
	ShutdownSendRetryAfter bool
}

func NewServerRunOptions() *ServerRunOptions {
	defaults := server.NewConfig(serializer.CodecFactory{})
	return &ServerRunOptions{
		MaxRequestsInFlight:         defaults.MaxRequestsInFlight,
		MaxMutatingRequestsInFlight: defaults.MaxMutatingRequestsInFlight,
		RequestTimeout:              defaults.RequestTimeout,
		LivezGracePeriod:            defaults.LivezGracePeriod,
		MinRequestTimeout:           defaults.MinRequestTimeout,
		ShutdownDelayDuration:       defaults.ShutdownDelayDuration,
		JSONPatchMaxCopyBytes:       defaults.JSONPatchMaxCopyBytes,
		MaxRequestBodyBytes:         defaults.MaxRequestBodyBytes,
		EnablePriorityAndFairness:   true,
		ShutdownSendRetryAfter:      false,
	}
}

// ApplyTo applies the run options to the method receiver and returns self
func (s *ServerRunOptions) ApplyTo(c *server.Config) error {
	c.CorsAllowedOriginList = s.CorsAllowedOriginList
	c.HSTSDirectives = s.HSTSDirectives
	c.ExternalAddress = s.ExternalHost
	c.MaxRequestsInFlight = s.MaxRequestsInFlight
	c.MaxMutatingRequestsInFlight = s.MaxMutatingRequestsInFlight
	c.LivezGracePeriod = s.LivezGracePeriod
	c.RequestTimeout = s.RequestTimeout
	c.GoawayChance = s.GoawayChance
	c.MinRequestTimeout = s.MinRequestTimeout
	c.ShutdownDelayDuration = s.ShutdownDelayDuration
	c.JSONPatchMaxCopyBytes = s.JSONPatchMaxCopyBytes
	c.MaxRequestBodyBytes = s.MaxRequestBodyBytes
	c.PublicAddress = s.AdvertiseAddress
	c.ShutdownSendRetryAfter = s.ShutdownSendRetryAfter

	return nil
}

// DefaultAdvertiseAddress sets the field AdvertiseAddress if unset. The field will be set based on the SecureServingOptions.
func (s *ServerRunOptions) DefaultAdvertiseAddress(secure *SecureServingOptions) error {
	if secure == nil {
		return nil
	}

	if s.AdvertiseAddress == nil || s.AdvertiseAddress.IsUnspecified() {
		hostIP, err := secure.DefaultExternalAddress()
		if err != nil {
			return fmt.Errorf("Unable to find suitable network address.error='%v'. "+
				"Try to set the AdvertiseAddress directly or provide a valid BindAddress to fix this.", err)
		}
		s.AdvertiseAddress = hostIP
	}

	return nil
}

// Validate checks validation of ServerRunOptions
func (s *ServerRunOptions) Validate() []error {
	errors := []error{}

	if s.LivezGracePeriod < 0 {
		errors = append(errors, fmt.Errorf("--livez-grace-period can not be a negative value"))
	}

	if s.MaxRequestsInFlight < 0 {
		errors = append(errors, fmt.Errorf("--max-requests-inflight can not be negative value"))
	}
	if s.MaxMutatingRequestsInFlight < 0 {
		errors = append(errors, fmt.Errorf("--max-mutating-requests-inflight can not be negative value"))
	}

	if s.RequestTimeout.Nanoseconds() < 0 {
		errors = append(errors, fmt.Errorf("--request-timeout can not be negative value"))
	}

	if s.GoawayChance < 0 || s.GoawayChance > 0.02 {
		errors = append(errors, fmt.Errorf("--goaway-chance can not be less than 0 or greater than 0.02"))
	}

	if s.MinRequestTimeout < 0 {
		errors = append(errors, fmt.Errorf("--min-request-timeout can not be negative value"))
	}

	if s.ShutdownDelayDuration < 0 {
		errors = append(errors, fmt.Errorf("--shutdown-delay-duration can not be negative value"))
	}

	if s.JSONPatchMaxCopyBytes < 0 {
		errors = append(errors, fmt.Errorf("--json-patch-max-copy-bytes can not be negative value"))
	}

	if s.MaxRequestBodyBytes < 0 {
		errors = append(errors, fmt.Errorf("--max-resource-write-bytes can not be negative value"))
	}

	if err := validateHSTSDirectives(s.HSTSDirectives); err != nil {
		errors = append(errors, err)
	}
	return errors
}

func validateHSTSDirectives(hstsDirectives []string) error {
	// HSTS Headers format: Strict-Transport-Security:max-age=expireTime [;includeSubDomains] [;preload]
	// See https://tools.ietf.org/html/rfc6797#section-6.1 for more information
	allErrors := []error{}
	for _, hstsDirective := range hstsDirectives {
		if len(strings.TrimSpace(hstsDirective)) == 0 {
			allErrors = append(allErrors, fmt.Errorf("empty value in strict-transport-security-directives"))
			continue
		}
		if hstsDirective != "includeSubDomains" && hstsDirective != "preload" {
			maxAgeDirective := strings.Split(hstsDirective, "=")
			if len(maxAgeDirective) != 2 || maxAgeDirective[0] != "max-age" {
				allErrors = append(allErrors, fmt.Errorf("--strict-transport-security-directives invalid, allowed values: max-age=expireTime, includeSubDomains, preload. see https://tools.ietf.org/html/rfc6797#section-6.1 for more information"))
			}
		}
	}
	return errors.NewAggregate(allErrors)
}

// AddUniversalFlags adds flags for a specific APIServer to the specified FlagSet
func (s *ServerRunOptions) AddUniversalFlags(fs *pflag.FlagSet) {
	// Note: the weird ""+ in below lines seems to be the only way to get gofmt to
	// arrange these text blocks sensibly. Grrr.

	fs.IPVar(&s.AdvertiseAddress, "advertise-address", s.AdvertiseAddress, ""+
		"The IP address on which to advertise the apiserver to members of the cluster. This "+
		"address must be reachable by the rest of the cluster. If blank, the --bind-address "+
		"will be used. If --bind-address is unspecified, the host's default interface will "+
		"be used.")

	fs.StringSliceVar(&s.CorsAllowedOriginList, "cors-allowed-origins", s.CorsAllowedOriginList, ""+
		"List of allowed origins for CORS, comma separated.  An allowed origin can be a regular "+
		"expression to support subdomain matching. If this list is empty CORS will not be enabled.")

	fs.StringSliceVar(&s.HSTSDirectives, "strict-transport-security-directives", s.HSTSDirectives, ""+
		"List of directives for HSTS, comma separated. If this list is empty, then HSTS directives will not "+
		"be added. Example: 'max-age=31536000,includeSubDomains,preload'")

	deprecatedTargetRAMMB := 0
	fs.IntVar(&deprecatedTargetRAMMB, "target-ram-mb", deprecatedTargetRAMMB,
		"DEPRECATED: Memory limit for apiserver in MB (used to configure sizes of caches, etc.)")
	fs.MarkDeprecated("target-ram-mb", "This flag will be removed in v1.23")

	fs.StringVar(&s.ExternalHost, "external-hostname", s.ExternalHost,
		"The hostname to use when generating externalized URLs for this master (e.g. Swagger API Docs or OpenID Discovery).")

	deprecatedMasterServiceNamespace := metav1.NamespaceDefault
	fs.StringVar(&deprecatedMasterServiceNamespace, "master-service-namespace", deprecatedMasterServiceNamespace, ""+
		"DEPRECATED: the namespace from which the Kubernetes master services should be injected into pods.")

	fs.IntVar(&s.MaxRequestsInFlight, "max-requests-inflight", s.MaxRequestsInFlight, ""+
		"This and --max-mutating-requests-inflight are summed to determine the server's total concurrency limit "+
		"(which must be positive) if --enable-priority-and-fairness is true. "+
		"Otherwise, this flag limits the maximum number of non-mutating requests in flight, "+
		"or a zero value disables the limit completely.")

	fs.IntVar(&s.MaxMutatingRequestsInFlight, "max-mutating-requests-inflight", s.MaxMutatingRequestsInFlight, ""+
		"This and --max-requests-inflight are summed to determine the server's total concurrency limit "+
		"(which must be positive) if --enable-priority-and-fairness is true. "+
		"Otherwise, this flag limits the maximum number of mutating requests in flight, "+
		"or a zero value disables the limit completely.")

	fs.DurationVar(&s.RequestTimeout, "request-timeout", s.RequestTimeout, ""+
		"An optional field indicating the duration a handler must keep a request open before timing "+
		"it out. This is the default request timeout for requests but may be overridden by flags such as "+
		"--min-request-timeout for specific types of requests.")

	fs.Float64Var(&s.GoawayChance, "goaway-chance", s.GoawayChance, ""+
		"To prevent HTTP/2 clients from getting stuck on a single apiserver, randomly close a connection (GOAWAY). "+
		"The client's other in-flight requests won't be affected, and the client will reconnect, likely landing on a different apiserver after going through the load balancer again. "+
		"This argument sets the fraction of requests that will be sent a GOAWAY. Clusters with single apiservers, or which don't use a load balancer, should NOT enable this. "+
		"Min is 0 (off), Max is .02 (1/50 requests); .001 (1/1000) is a recommended starting point.")

	fs.DurationVar(&s.LivezGracePeriod, "livez-grace-period", s.LivezGracePeriod, ""+
		"This option represents the maximum amount of time it should take for apiserver to complete its startup sequence "+
		"and become live. From apiserver's start time to when this amount of time has elapsed, /livez will assume "+
		"that unfinished post-start hooks will complete successfully and therefore return true.")

	fs.IntVar(&s.MinRequestTimeout, "min-request-timeout", s.MinRequestTimeout, ""+
		"An optional field indicating the minimum number of seconds a handler must keep "+
		"a request open before timing it out. Currently only honored by the watch request "+
		"handler, which picks a randomized value above this number as the connection timeout, "+
		"to spread out load.")

	fs.BoolVar(&s.EnablePriorityAndFairness, "enable-priority-and-fairness", s.EnablePriorityAndFairness, ""+
		"If true and the APIPriorityAndFairness feature gate is enabled, replace the max-in-flight handler with an enhanced one that queues and dispatches with priority and fairness")

	fs.DurationVar(&s.ShutdownDelayDuration, "shutdown-delay-duration", s.ShutdownDelayDuration, ""+
		"Time to delay the termination. During that time the server keeps serving requests normally. The endpoints /healthz and /livez "+
		"will return success, but /readyz immediately returns failure. Graceful termination starts after this delay "+
		"has elapsed. This can be used to allow load balancer to stop sending traffic to this server.")

	fs.BoolVar(&s.ShutdownSendRetryAfter, "shutdown-send-retry-after", s.ShutdownSendRetryAfter, ""+
		"If true the HTTP Server will continue listening until all non long running request(s) in flight have been drained, "+
		"during this window all incoming requests will be rejected with a status code 429 and a 'Retry-After' response header, "+
		"in addition 'Connection: close' response header is set in order to tear down the TCP connection when idle.")

	utilfeature.DefaultMutableFeatureGate.AddFlag(fs)
}
<|endoftext|>


<commit_before>// +build e2e

/*
Copyright 2018 Google Inc. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

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

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

package e2e

import (
	"errors"
	"fmt"
	"github.com/golang/glog"
	"github.com/google/go-containerregistry/v1/remote"
	"github.com/knative/serving/pkg/apis/serving/v1alpha1"
	"github.com/knative/serving/test"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"strings"
	"testing"
)

const (
	containerMissing = "ContainerMissing"
)

// TestContainerErrorMsg is to validate the error condition defined at
// https://github.com/knative/serving/blob/master/docs/spec/errors.md
// for the container image missing scenario.
func TestContainerErrorMsg(t *testing.T) {
	t.Skip("Skipping until https://github.com/knative/serving/issues/1240 is closed")
	clients := Setup(t)

	// Specify an invalid image path
	// A valid DockerRepo is still needed, otherwise will get UNAUTHORIZED instead of container missing error
	imagePath := strings.Join([]string{test.Flags.DockerRepo, "invalidhelloworld"}, "/")

	glog.Infof("Creating a new Route and Configuration %s", imagePath)
	names, err := CreateRouteAndConfig(clients, imagePath)
	if err != nil {
		t.Fatalf("Failed to create Route and Configuration: %v", err)
	}
	defer TearDown(clients, names)
	test.CleanupOnInterrupt(func() { TearDown(clients, names) })

	manifestUnknown := string(remote.ManifestUnknownErrorCode)
	glog.Infof("When the imagepath is invalid, the Configuration should have error status.")

	// Checking for "Container image not present in repository" scenario defined in error condition spec
	err = test.WaitForConfigurationState(clients.Configs, names.Config, func(r *v1alpha1.Configuration) (bool, error) {
		cond := r.Status.GetCondition(v1alpha1.ConfigurationConditionLatestRevisionReady)
		if cond != nil {
			if cond.Reason == containerMissing && strings.HasPrefix(cond.Message, manifestUnknown) && cond.Status == "False" {
				return true, nil
			}
			s := fmt.Sprintf("The configuration %s was not marked with expected error condition (Reason=\"%s\", Message=\"%s\", Status=\"%s\"), but with (Reason=\"%s\", Message=\"%s\", Status=\"%s\")", names.Config, containerMissing, manifestUnknown, "False", cond.Reason, cond.Message, cond.Status)
			return true, errors.New(s)
		}
		return false, nil
	})

	if err != nil {
		t.Fatalf("Failed to validate configuration state: %s", err)
	}

	revisionName, err := getRevisionFromConfiguration(clients, names.Config)
	if err != nil {
		t.Fatalf("Failed to get revision from configuration %s: %v", names.Config, err)
	}

	glog.Infof("When the imagepath is invalid, the revision should have error status.")
	err = test.WaitForRevisionState(clients.Revisions, revisionName, func(r *v1alpha1.Revision) (bool, error) {
		cond := r.Status.GetCondition(v1alpha1.RevisionConditionReady)
		if cond != nil {
			if cond.Reason == containerMissing && strings.HasPrefix(cond.Message, manifestUnknown) {
				return true, nil
			}
			s := fmt.Sprintf("The revision %s was not marked with expected error condition (Reason=\"%s\", Message=\"%s\"), but with (Reason=\"%s\", Message=\"%s\")", revisionName, containerMissing, manifestUnknown, cond.Reason, cond.Message)
			return true, errors.New(s)
		}
		return false, nil
	})

	if err != nil {
		t.Fatalf("Failed to validate revision state: %s", err)
	}

	glog.Infof("When the revision has error condition, logUrl should be populated.")
	logURL, err := getLogURLFromRevision(clients, revisionName)
	if err != nil {
		t.Fatalf("Failed to get logUrl from revision %s: %v", revisionName, err)
	}

	// TODO(jessiezcc): actually validate the logURL, but requires kibana setup
	test.Verbose("LogURL: %s", logURL)

	// TODO(jessiezcc): add the check to validate that Route is not marked as ready once https://github.com/elafros/elafros/issues/990 is fixed
}

// Get revision name from configuration.
func getRevisionFromConfiguration(clients *test.Clients, configName string) (string, error) {
	config, err := clients.Configs.Get(configName, metav1.GetOptions{})
	if err != nil {
		return "", err
	}
	if config.Status.LatestCreatedRevisionName != "" {
		return config.Status.LatestCreatedRevisionName, nil
	}
	s := fmt.Sprintf("No valid revision name found in configuration %s", configName)
	return "", errors.New(s)
}

// Get LogURL from revision.
func getLogURLFromRevision(clients *test.Clients, revisionName string) (string, error) {
	revision, err := clients.Revisions.Get(revisionName, metav1.GetOptions{})
	if err != nil {
		return "", err
	}
	if revision.Status.LogURL != "" && strings.Contains(revision.Status.LogURL, string(revision.GetUID())) {
		return revision.Status.LogURL, nil
	}
	s := fmt.Sprintf("The revision %s does't have valid logUrl: %s", revisionName, revision.Status.LogURL)
	return "", errors.New(s)
}
<commit_msg>Re-enable ContainerMissing testing. (#1264)<commit_after>// +build e2e

/*
Copyright 2018 Google Inc. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

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

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

package e2e

import (
	"errors"
	"fmt"
	"github.com/golang/glog"
	"github.com/google/go-containerregistry/v1/remote"
	"github.com/knative/serving/pkg/apis/serving/v1alpha1"
	"github.com/knative/serving/test"
	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"strings"
	"testing"
)

const (
	containerMissing = "ContainerMissing"
)

// TestContainerErrorMsg is to validate the error condition defined at
// https://github.com/knative/serving/blob/master/docs/spec/errors.md
// for the container image missing scenario.
func TestContainerErrorMsg(t *testing.T) {
	clients := Setup(t)

	// Specify an invalid image path
	// A valid DockerRepo is still needed, otherwise will get UNAUTHORIZED instead of container missing error
	imagePath := strings.Join([]string{test.Flags.DockerRepo, "invalidhelloworld"}, "/")

	glog.Infof("Creating a new Route and Configuration %s", imagePath)
	names, err := CreateRouteAndConfig(clients, imagePath)
	if err != nil {
		t.Fatalf("Failed to create Route and Configuration: %v", err)
	}
	defer TearDown(clients, names)
	test.CleanupOnInterrupt(func() { TearDown(clients, names) })

	manifestUnknown := string(remote.ManifestUnknownErrorCode)
	glog.Infof("When the imagepath is invalid, the Configuration should have error status.")

	// Checking for "Container image not present in repository" scenario defined in error condition spec
	err = test.WaitForConfigurationState(clients.Configs, names.Config, func(r *v1alpha1.Configuration) (bool, error) {
		cond := r.Status.GetCondition(v1alpha1.ConfigurationConditionLatestRevisionReady)
		if cond != nil && cond.Status != corev1.ConditionUnknown {
			if strings.Contains(cond.Message, manifestUnknown) && cond.Status == corev1.ConditionFalse {
				return true, nil
			}
			return true, fmt.Errorf("The configuration %s was not marked with expected error condition (Reason=%q, Message=%q, Status=%q), but with (Reason=%q, Message=%q, Status=%q)",
				names.Config, containerMissing, manifestUnknown, "False", cond.Reason, cond.Message, cond.Status)
		}
		return false, nil
	})

	if err != nil {
		t.Fatalf("Failed to validate configuration state: %s", err)
	}

	revisionName, err := getRevisionFromConfiguration(clients, names.Config)
	if err != nil {
		t.Fatalf("Failed to get revision from configuration %s: %v", names.Config, err)
	}

	glog.Infof("When the imagepath is invalid, the revision should have error status.")
	err = test.WaitForRevisionState(clients.Revisions, revisionName, func(r *v1alpha1.Revision) (bool, error) {
		cond := r.Status.GetCondition(v1alpha1.RevisionConditionReady)
		if cond != nil {
			if cond.Reason == containerMissing && strings.HasPrefix(cond.Message, manifestUnknown) {
				return true, nil
			}
			return true, fmt.Errorf("The revision %s was not marked with expected error condition (Reason=%q, Message=%q), but with (Reason=%q, Message=%q)",
				revisionName, containerMissing, manifestUnknown, cond.Reason, cond.Message)
		}
		return false, nil
	})

	if err != nil {
		t.Fatalf("Failed to validate revision state: %s", err)
	}

	glog.Infof("When the revision has error condition, logUrl should be populated.")
	logURL, err := getLogURLFromRevision(clients, revisionName)
	if err != nil {
		t.Fatalf("Failed to get logUrl from revision %s: %v", revisionName, err)
	}

	// TODO(jessiezcc): actually validate the logURL, but requires kibana setup
	test.Verbose("LogURL: %s", logURL)

	// TODO(jessiezcc): add the check to validate that Route is not marked as ready once https://github.com/elafros/elafros/issues/990 is fixed
}

// Get revision name from configuration.
func getRevisionFromConfiguration(clients *test.Clients, configName string) (string, error) {
	config, err := clients.Configs.Get(configName, metav1.GetOptions{})
	if err != nil {
		return "", err
	}
	if config.Status.LatestCreatedRevisionName != "" {
		return config.Status.LatestCreatedRevisionName, nil
	}
	s := fmt.Sprintf("No valid revision name found in configuration %s", configName)
	return "", errors.New(s)
}

// Get LogURL from revision.
func getLogURLFromRevision(clients *test.Clients, revisionName string) (string, error) {
	revision, err := clients.Revisions.Get(revisionName, metav1.GetOptions{})
	if err != nil {
		return "", err
	}
	if revision.Status.LogURL != "" && strings.Contains(revision.Status.LogURL, string(revision.GetUID())) {
		return revision.Status.LogURL, nil
	}
	s := fmt.Sprintf("The revision %s does't have valid logUrl: %s", revisionName, revision.Status.LogURL)
	return "", errors.New(s)
}
<|endoftext|>