<commit_before>/*
Copyright 2019 The Vitess 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 mysql

import (
	"fmt"

	replicationdatapb "vitess.io/vitess/go/vt/proto/replicationdata"
	"vitess.io/vitess/go/vt/vterrors"
)

// ReplicationStatus holds replication information from SHOW SLAVE STATUS.
type ReplicationStatus struct {
	Position Position
	// RelayLogPosition is the Position that the replica would be at if it
	// were to finish executing everything that's currently in its relay log.
	// However, some MySQL flavors don't expose this information,
	// in which case RelayLogPosition.IsZero() will be true.
	RelayLogPosition      Position
	FilePosition          Position
	FileRelayLogPosition  Position
	SourceServerID        uint
	IOThreadRunning       bool
	SQLThreadRunning      bool
	ReplicationLagSeconds uint
	SourceHost            string
	SourcePort            int
	ConnectRetry          int
	SourceUUID            SID
}

// ReplicationRunning returns true iff both the IO and SQL threads are
// running.
func (s *ReplicationStatus) ReplicationRunning() bool {
	return s.IOThreadRunning && s.SQLThreadRunning
}

// ReplicationStatusToProto translates a Status to proto3.
func ReplicationStatusToProto(s ReplicationStatus) *replicationdatapb.Status {
	return &replicationdatapb.Status{
		Position:              EncodePosition(s.Position),
		RelayLogPosition:      EncodePosition(s.RelayLogPosition),
		FilePosition:          EncodePosition(s.FilePosition),
		FileRelayLogPosition:  EncodePosition(s.FileRelayLogPosition),
		SourceServerId:        uint32(s.SourceServerID),
		IoThreadRunning:       s.IOThreadRunning,
		SqlThreadRunning:      s.SQLThreadRunning,
		ReplicationLagSeconds: uint32(s.ReplicationLagSeconds),
		SourceHost:            s.SourceHost,
		SourcePort:            int32(s.SourcePort),
		ConnectRetry:          int32(s.ConnectRetry),
		SourceUuid:            s.SourceUUID.String(),
	}
}

// ProtoToReplicationStatus translates a proto Status, or panics.
func ProtoToReplicationStatus(s *replicationdatapb.Status) ReplicationStatus {
	pos, err := DecodePosition(s.Position)
	if err != nil {
		panic(vterrors.Wrapf(err, "cannot decode Position"))
	}
	relayPos, err := DecodePosition(s.RelayLogPosition)
	if err != nil {
		panic(vterrors.Wrapf(err, "cannot decode RelayLogPosition"))
	}
	filePos, err := DecodePosition(s.FilePosition)
	if err != nil {
		panic(vterrors.Wrapf(err, "cannot decode FilePosition"))
	}
	fileRelayPos, err := DecodePosition(s.FileRelayLogPosition)
	if err != nil {
		panic(vterrors.Wrapf(err, "cannot decode FileRelayLogPosition"))
	}
	var sid SID
	if s.SourceUuid != "" {
		sid, err = ParseSID(s.SourceUuid)
		if err != nil {
			panic(vterrors.Wrapf(err, "cannot decode SourceUUID"))
		}
	}
	return ReplicationStatus{
		Position:              pos,
		RelayLogPosition:      relayPos,
		FilePosition:          filePos,
		FileRelayLogPosition:  fileRelayPos,
		SourceServerID:        uint(s.SourceServerId),
		IOThreadRunning:       s.IoThreadRunning,
		SQLThreadRunning:      s.SqlThreadRunning,
		ReplicationLagSeconds: uint(s.ReplicationLagSeconds),
		SourceHost:            s.SourceHost,
		SourcePort:            int(s.SourcePort),
		ConnectRetry:          int(s.ConnectRetry),
		SourceUUID:            sid,
	}
}

// FindErrantGTIDs can be used to find errant GTIDs in the receiver's relay log, by comparing it against all known replicas,
// provided as a list of ReplicationStatus's. This method only works if the flavor for all retrieved ReplicationStatus's is MySQL.
// The result is returned as a Mysql56GTIDSet, each of whose elements is a found errant GTID.
func (s *ReplicationStatus) FindErrantGTIDs(otherReplicaStatuses []*ReplicationStatus) (Mysql56GTIDSet, error) {
	set, ok := s.RelayLogPosition.GTIDSet.(Mysql56GTIDSet)
	if !ok {
		return nil, fmt.Errorf("errant GTIDs can only be computed on the MySQL flavor")
	}

	otherSets := make([]Mysql56GTIDSet, 0, len(otherReplicaStatuses))
	for _, status := range otherReplicaStatuses {
		otherSet, ok := status.RelayLogPosition.GTIDSet.(Mysql56GTIDSet)
		if !ok {
			panic("The receiver ReplicationStatus contained a Mysql56GTIDSet in its relay log, but a replica's ReplicationStatus is of another flavor. This should never happen.")
		}
		// Copy and throw out primary SID from consideration, so we don't mutate input.
		otherSetNoPrimarySID := make(Mysql56GTIDSet, len(otherSet))
		for sid, intervals := range otherSet {
			if sid == status.SourceUUID {
				continue
			}
			otherSetNoPrimarySID[sid] = intervals
		}

		otherSets = append(otherSets, otherSetNoPrimarySID)
	}

	// Copy set for final diffSet so we don't mutate receiver.
	diffSet := make(Mysql56GTIDSet, len(set))
	for sid, intervals := range set {
		if sid == s.SourceUUID {
			continue
		}
		diffSet[sid] = intervals
	}

	for _, otherSet := range otherSets {
		diffSet = diffSet.Difference(otherSet)
	}

	if len(diffSet) == 0 {
		// If diffSet is empty, then we have no errant GTIDs.
		return nil, nil
	}

	return diffSet, nil
}
<commit_msg>Make it clearer which GTIDSet we are using.<commit_after>/*
Copyright 2019 The Vitess 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 mysql

import (
	"fmt"

	replicationdatapb "vitess.io/vitess/go/vt/proto/replicationdata"
	"vitess.io/vitess/go/vt/vterrors"
)

// ReplicationStatus holds replication information from SHOW SLAVE STATUS.
type ReplicationStatus struct {
	Position Position
	// RelayLogPosition is the Position that the replica would be at if it
	// were to finish executing everything that's currently in its relay log.
	// However, some MySQL flavors don't expose this information,
	// in which case RelayLogPosition.IsZero() will be true.
	RelayLogPosition      Position
	FilePosition          Position
	FileRelayLogPosition  Position
	SourceServerID        uint
	IOThreadRunning       bool
	SQLThreadRunning      bool
	ReplicationLagSeconds uint
	SourceHost            string
	SourcePort            int
	ConnectRetry          int
	SourceUUID            SID
}

// ReplicationRunning returns true iff both the IO and SQL threads are
// running.
func (s *ReplicationStatus) ReplicationRunning() bool {
	return s.IOThreadRunning && s.SQLThreadRunning
}

// ReplicationStatusToProto translates a Status to proto3.
func ReplicationStatusToProto(s ReplicationStatus) *replicationdatapb.Status {
	return &replicationdatapb.Status{
		Position:              EncodePosition(s.Position),
		RelayLogPosition:      EncodePosition(s.RelayLogPosition),
		FilePosition:          EncodePosition(s.FilePosition),
		FileRelayLogPosition:  EncodePosition(s.FileRelayLogPosition),
		SourceServerId:        uint32(s.SourceServerID),
		IoThreadRunning:       s.IOThreadRunning,
		SqlThreadRunning:      s.SQLThreadRunning,
		ReplicationLagSeconds: uint32(s.ReplicationLagSeconds),
		SourceHost:            s.SourceHost,
		SourcePort:            int32(s.SourcePort),
		ConnectRetry:          int32(s.ConnectRetry),
		SourceUuid:            s.SourceUUID.String(),
	}
}

// ProtoToReplicationStatus translates a proto Status, or panics.
func ProtoToReplicationStatus(s *replicationdatapb.Status) ReplicationStatus {
	pos, err := DecodePosition(s.Position)
	if err != nil {
		panic(vterrors.Wrapf(err, "cannot decode Position"))
	}
	relayPos, err := DecodePosition(s.RelayLogPosition)
	if err != nil {
		panic(vterrors.Wrapf(err, "cannot decode RelayLogPosition"))
	}
	filePos, err := DecodePosition(s.FilePosition)
	if err != nil {
		panic(vterrors.Wrapf(err, "cannot decode FilePosition"))
	}
	fileRelayPos, err := DecodePosition(s.FileRelayLogPosition)
	if err != nil {
		panic(vterrors.Wrapf(err, "cannot decode FileRelayLogPosition"))
	}
	var sid SID
	if s.SourceUuid != "" {
		sid, err = ParseSID(s.SourceUuid)
		if err != nil {
			panic(vterrors.Wrapf(err, "cannot decode SourceUUID"))
		}
	}
	return ReplicationStatus{
		Position:              pos,
		RelayLogPosition:      relayPos,
		FilePosition:          filePos,
		FileRelayLogPosition:  fileRelayPos,
		SourceServerID:        uint(s.SourceServerId),
		IOThreadRunning:       s.IoThreadRunning,
		SQLThreadRunning:      s.SqlThreadRunning,
		ReplicationLagSeconds: uint(s.ReplicationLagSeconds),
		SourceHost:            s.SourceHost,
		SourcePort:            int(s.SourcePort),
		ConnectRetry:          int(s.ConnectRetry),
		SourceUUID:            sid,
	}
}

// FindErrantGTIDs can be used to find errant GTIDs in the receiver's relay log, by comparing it against all known replicas,
// provided as a list of ReplicationStatus's. This method only works if the flavor for all retrieved ReplicationStatus's is MySQL.
// The result is returned as a Mysql56GTIDSet, each of whose elements is a found errant GTID.
func (s *ReplicationStatus) FindErrantGTIDs(otherReplicaStatuses []*ReplicationStatus) (Mysql56GTIDSet, error) {
	relayLogSet, ok := s.RelayLogPosition.GTIDSet.(Mysql56GTIDSet)
	if !ok {
		return nil, fmt.Errorf("errant GTIDs can only be computed on the MySQL flavor")
	}

	otherSets := make([]Mysql56GTIDSet, 0, len(otherReplicaStatuses))
	for _, status := range otherReplicaStatuses {
		otherSet, ok := status.RelayLogPosition.GTIDSet.(Mysql56GTIDSet)
		if !ok {
			panic("The receiver ReplicationStatus contained a Mysql56GTIDSet in its relay log, but a replica's ReplicationStatus is of another flavor. This should never happen.")
		}
		// Copy and throw out primary SID from consideration, so we don't mutate input.
		otherSetNoPrimarySID := make(Mysql56GTIDSet, len(otherSet))
		for sid, intervals := range otherSet {
			if sid == status.SourceUUID {
				continue
			}
			otherSetNoPrimarySID[sid] = intervals
		}

		otherSets = append(otherSets, otherSetNoPrimarySID)
	}

	// Copy set for final diffSet so we don't mutate receiver.
	diffSet := make(Mysql56GTIDSet, len(relayLogSet))
	for sid, intervals := range relayLogSet {
		if sid == s.SourceUUID {
			continue
		}
		diffSet[sid] = intervals
	}

	for _, otherSet := range otherSets {
		diffSet = diffSet.Difference(otherSet)
	}

	if len(diffSet) == 0 {
		// If diffSet is empty, then we have no errant GTIDs.
		return nil, nil
	}

	return diffSet, nil
}
<|endoftext|>


<commit_before>/*
Copyright 2020 The Vitess 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 cluster

import (
	"regexp"
	"strings"

	"vitess.io/vitess/go/vt/log"
)

// FlagsByImpl groups a set of flags by discovery implementation. Its mapping is
// impl_name=>flag=>value.
type FlagsByImpl map[string]map[string]string

// Merge applies the flags in the parameter to the receiver, conflicts are
// resolved in favor of the parameter and not the receiver.
func (base *FlagsByImpl) Merge(override map[string]map[string]string) {
	if (*base) == nil {
		*base = map[string]map[string]string{}
	}

	for impl, flags := range override {
		_, ok := (*base)[impl]
		if !ok {
			(*base)[impl] = map[string]string{}
		}

		for k, v := range flags {
			(*base)[impl][k] = v
		}
	}
}

// ClustersFlag implements flag.Value allowing multiple occurrences of a flag to
// be accumulated into a map.
type ClustersFlag map[string]Config

// String is part of the flag.Value interface.
func (cf *ClustersFlag) String() string {
	buf := strings.Builder{}

	buf.WriteString("[")

	i := 0

	for _, cfg := range *cf {
		buf.WriteString(cfg.String())

		if i < len(*cf)-1 {
			buf.WriteString(" ")
		}

		i++
	}

	buf.WriteString("]")

	return buf.String()
}

// Type is part of the pflag.Value interface.
func (cf *ClustersFlag) Type() string {
	return "cluster.ClustersFlag"
}

// Set is part of the flag.Value interface. It merges the parsed config into the
// map, allowing ClustersFlag to power a repeated flag. See (*Config).Set for
// details on flag parsing.
func (cf *ClustersFlag) Set(value string) error {
	if (*cf) == nil {
		(*cf) = map[string]Config{}
	}

	cfg := Config{
		DiscoveryFlagsByImpl: map[string]map[string]string{},
	}

	if err := parseFlag(&cfg, value); err != nil {
		return err
	}

	// Merge a potentially existing config for the same cluster ID.
	c, ok := (*cf)[cfg.ID]
	if !ok {
		// If we don't have an existing config, create an empty one to "merge"
		// into.
		c = Config{}
	}

	(*cf)[cfg.ID] = cfg.Merge(c)

	return nil
}

// nolint:gochecknoglobals
var discoveryFlagRegexp = regexp.MustCompile(`^discovery-(?P<impl>\w+)-(?P<flag>.+)$`)

func parseFlag(cfg *Config, value string) error {
	args := strings.Split(value, ",")
	for _, arg := range args {
		var (
			name string
			val  string
		)

		if strings.Contains(arg, "=") {
			parts := strings.Split(arg, "=")
			name = parts[0]
			val = strings.Join(parts[1:], "=")
		} else {
			name = arg
			val = "true"
		}

		if err := parseOne(cfg, name, val); err != nil {
			return err
		}
	}

	return nil
}

func parseOne(cfg *Config, name string, val string) error {
	switch name {
	case "id":
		cfg.ID = val
	case "name":
		cfg.Name = val
	case "discovery":
		cfg.DiscoveryImpl = val
	case "tablet-fqdn-tmpl":
		cfg.TabletFQDNTmplStr = val
	default:
		if strings.HasPrefix(name, "vtsql-") {
			if cfg.VtSQLFlags == nil {
				cfg.VtSQLFlags = map[string]string{}
			}

			cfg.VtSQLFlags[strings.TrimPrefix(name, "vtsql-")] = val

			return nil
		} else if strings.HasPrefix(name, "vtctld-") {
			if cfg.VtctldFlags == nil {
				cfg.VtctldFlags = map[string]string{}
			}

			cfg.VtctldFlags[strings.TrimPrefix(name, "vtctld-")] = val

			return nil
		}

		match := discoveryFlagRegexp.FindStringSubmatch(name)
		if match == nil {
			// not a discovery flag
			log.Warningf("Attempted to parse %q as a discovery flag, ignoring ...", name)
			return nil
		}

		var impl, flag string

		for i, g := range discoveryFlagRegexp.SubexpNames() {
			switch g {
			case "impl":
				impl = match[i]
			case "flag":
				flag = match[i]
			}
		}

		if cfg.DiscoveryFlagsByImpl == nil {
			cfg.DiscoveryFlagsByImpl = map[string]map[string]string{}
		}

		if cfg.DiscoveryFlagsByImpl[impl] == nil {
			cfg.DiscoveryFlagsByImpl[impl] = map[string]string{}
		}

		cfg.DiscoveryFlagsByImpl[impl][flag] = val
	}

	return nil
}
<commit_msg>Restructure parseOne to remove the if/else chain<commit_after>/*
Copyright 2020 The Vitess 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 cluster

import (
	"regexp"
	"strings"

	"vitess.io/vitess/go/vt/log"
)

// FlagsByImpl groups a set of flags by discovery implementation. Its mapping is
// impl_name=>flag=>value.
type FlagsByImpl map[string]map[string]string

// Merge applies the flags in the parameter to the receiver, conflicts are
// resolved in favor of the parameter and not the receiver.
func (base *FlagsByImpl) Merge(override map[string]map[string]string) {
	if (*base) == nil {
		*base = map[string]map[string]string{}
	}

	for impl, flags := range override {
		_, ok := (*base)[impl]
		if !ok {
			(*base)[impl] = map[string]string{}
		}

		for k, v := range flags {
			(*base)[impl][k] = v
		}
	}
}

// ClustersFlag implements flag.Value allowing multiple occurrences of a flag to
// be accumulated into a map.
type ClustersFlag map[string]Config

// String is part of the flag.Value interface.
func (cf *ClustersFlag) String() string {
	buf := strings.Builder{}

	buf.WriteString("[")

	i := 0

	for _, cfg := range *cf {
		buf.WriteString(cfg.String())

		if i < len(*cf)-1 {
			buf.WriteString(" ")
		}

		i++
	}

	buf.WriteString("]")

	return buf.String()
}

// Type is part of the pflag.Value interface.
func (cf *ClustersFlag) Type() string {
	return "cluster.ClustersFlag"
}

// Set is part of the flag.Value interface. It merges the parsed config into the
// map, allowing ClustersFlag to power a repeated flag. See (*Config).Set for
// details on flag parsing.
func (cf *ClustersFlag) Set(value string) error {
	if (*cf) == nil {
		(*cf) = map[string]Config{}
	}

	cfg := Config{
		DiscoveryFlagsByImpl: map[string]map[string]string{},
	}

	if err := parseFlag(&cfg, value); err != nil {
		return err
	}

	// Merge a potentially existing config for the same cluster ID.
	c, ok := (*cf)[cfg.ID]
	if !ok {
		// If we don't have an existing config, create an empty one to "merge"
		// into.
		c = Config{}
	}

	(*cf)[cfg.ID] = cfg.Merge(c)

	return nil
}

// nolint:gochecknoglobals
var discoveryFlagRegexp = regexp.MustCompile(`^discovery-(?P<impl>\w+)-(?P<flag>.+)$`)

func parseFlag(cfg *Config, value string) error {
	args := strings.Split(value, ",")
	for _, arg := range args {
		var (
			name string
			val  string
		)

		if strings.Contains(arg, "=") {
			parts := strings.Split(arg, "=")
			name = parts[0]
			val = strings.Join(parts[1:], "=")
		} else {
			name = arg
			val = "true"
		}

		if err := parseOne(cfg, name, val); err != nil {
			return err
		}
	}

	return nil
}

func parseOne(cfg *Config, name string, val string) error {
	switch name {
	case "id":
		cfg.ID = val
	case "name":
		cfg.Name = val
	case "discovery":
		cfg.DiscoveryImpl = val
	case "tablet-fqdn-tmpl":
		cfg.TabletFQDNTmplStr = val
	default:
		switch {
		case strings.HasPrefix(name, "vtsql-"):
			if cfg.VtSQLFlags == nil {
				cfg.VtSQLFlags = map[string]string{}
			}

			cfg.VtSQLFlags[strings.TrimPrefix(name, "vtsql-")] = val
		case strings.HasPrefix(name, "vtctld-"):
			if cfg.VtctldFlags == nil {
				cfg.VtctldFlags = map[string]string{}
			}

			cfg.VtctldFlags[strings.TrimPrefix(name, "vtctld-")] = val
		default:
			match := discoveryFlagRegexp.FindStringSubmatch(name)
			if match == nil {
				// not a discovery flag
				log.Warningf("Attempted to parse %q as a discovery flag, ignoring ...", name)
				return nil
			}

			var impl, flag string

			for i, g := range discoveryFlagRegexp.SubexpNames() {
				switch g {
				case "impl":
					impl = match[i]
				case "flag":
					flag = match[i]
				}
			}

			if cfg.DiscoveryFlagsByImpl == nil {
				cfg.DiscoveryFlagsByImpl = map[string]map[string]string{}
			}

			if cfg.DiscoveryFlagsByImpl[impl] == nil {
				cfg.DiscoveryFlagsByImpl[impl] = map[string]string{}
			}

			cfg.DiscoveryFlagsByImpl[impl][flag] = val
		}
	}

	return nil
}
<|endoftext|>


<commit_before>package flake

import (
	"encoding/binary"
	"os"
	"testing"

	"github.com/stretchr/testify/assert"
)

var testHardwareID HardwareID

func TestMain(m *testing.M) {
	testHardwareID = make([]byte, 8)
	binary.LittleEndian.PutUint64(testHardwareID, 0x112233445566)

	retCode := m.Run()

	os.Exit(retCode)
}

func TestGenerateID(t *testing.T) {
	// Create a generator
	gen := NewOvertoneEpochGenerator(testHardwareID)

	assert.NotNil(t, gen, "Expecting generator to be allocated")
	//assert.Equal(t, OvertoneEpochMs, gen.Epoch(), "Expecting generator.Epoch() to == OvertoneEpochMS")
	//assert.Equal(t, os.Getpid(), gen.ProcessID(), "Expecting generator.ProcessID() to == os.Getpid()")
	//assert.Equal(t, testHardwareID, gen.HardwareID(), "Expecting generator.HardwareID() to == testHardwareID")
	// assert.Equal(t, OvertFlakeIDLength, gen.IDSize(), "Expecting generator.IDSize() to == OvertFlakeIDLength")

	// remember when we start the gen so we can compare the timestamp in the id for >=
	//startTime := time.Now().UTC().UnixNano() / 1e6

	id, err := gen.Generate(1)
	assert.NoError(t, err)
	assert.NotNil(t, id, "Expecting id to be non-nill if err == nil")
	assert.Equal(t, OvertFlakeIDLength, len(id), "Expecting id length to == %d, not %d", OvertFlakeIDLength, len(id))

	assert.Condition(t, func() bool {
		return gen.LastAllocatedTime() > 0
	})

	upper := binary.BigEndian.Uint64(id[0:8])
	//lower := binary.BigEndian.Uint64(id[8:16])

	/*assert.Equal(t,
	gen.(*generator).machineID,
	lower,
	"expecting lower 64-bits of id to == generator.machineID (%d), not %d",
	gen.(*generator).machineID,
	lower)*/

	// because we are generating 1, and we know that we are only requestor then
	// we also know none have been allocated so the interval should be 0
	assert.Condition(t, func() bool {
		return (upper & 0xFFFF) == 0
	})

	/*assert.Condition(t, func() bool {
		timestamp := upper >> 16
		beginDelta := uint64(startTime - gen.Epoch())
		return timestamp >= beginDelta
	}, "Expecting upper %d >= %d", upper>>16, startTime-gen.Epoch())*/
}

func TestGenerateStreamIDs(t *testing.T) {
	// Create a generator
	gen := NewOvertFlakeGenerator(OvertoneEpochMs, testHardwareID, 42, 0)

	// Create a buffer which forces the stream to provide them 1 at a time
	buffer := make([]byte, OvertFlakeIDLength)
	var called int
	totalAllocated, err := gen.GenerateAsStream(3, buffer, func(allocated int, ids []byte) error {
		called++
		return nil
	})
	assert.NoError(t, err)
	assert.Equal(t, 3, totalAllocated, "Expecting total # of ids generated to == %d, not %d", 3, totalAllocated)
	assert.Equal(t, 3, called, "Expecting total # of callbacks to be %d, not %d", 3, called)

	// Create a buffer which forces the stream to provide them 2 at a time
	buffer = make([]byte, OvertFlakeIDLength*2)
	called = 0

	// We are requesting 3 with a buffer that can hold 2, so two callbacks are expected with
	totalAllocated, err = gen.GenerateAsStream(3, buffer, func(allocated int, ids []byte) error {
		switch called {
		case 0:
			assert.Equal(t, 2, allocated, "Expecting 1st callback to have % ids, not %d", 2, allocated)
		case 1:
			assert.Equal(t, 1, allocated, "Expecting last callback to have % ids, not %d", 1, allocated)
		}
		called++
		return nil
	})
	assert.NoError(t, err)
	assert.Equal(t, 3, totalAllocated, "Expecting total # of ids generated to == %d, not %d", 3, totalAllocated)
	assert.Equal(t, 2, called, "Expecting total # of callbacks to be %d, not %d", 3, called)

}
<commit_msg>add a test of a larger buffer<commit_after>package flake

import (
	"encoding/binary"
	"os"
	"testing"

	"github.com/stretchr/testify/assert"
)

var testHardwareID HardwareID

func TestMain(m *testing.M) {
	testHardwareID = make([]byte, 8)
	binary.LittleEndian.PutUint64(testHardwareID, 0x112233445566)

	retCode := m.Run()

	os.Exit(retCode)
}

func TestGenerateID(t *testing.T) {
	// Create a generator
	gen := NewOvertoneEpochGenerator(testHardwareID)

	assert.NotNil(t, gen, "Expecting generator to be allocated")
	//assert.Equal(t, OvertoneEpochMs, gen.Epoch(), "Expecting generator.Epoch() to == OvertoneEpochMS")
	//assert.Equal(t, os.Getpid(), gen.ProcessID(), "Expecting generator.ProcessID() to == os.Getpid()")
	//assert.Equal(t, testHardwareID, gen.HardwareID(), "Expecting generator.HardwareID() to == testHardwareID")
	// assert.Equal(t, OvertFlakeIDLength, gen.IDSize(), "Expecting generator.IDSize() to == OvertFlakeIDLength")

	// remember when we start the gen so we can compare the timestamp in the id for >=
	//startTime := time.Now().UTC().UnixNano() / 1e6

	id, err := gen.Generate(1)
	assert.NoError(t, err)
	assert.NotNil(t, id, "Expecting id to be non-nill if err == nil")
	assert.Equal(t, OvertFlakeIDLength, len(id), "Expecting id length to == %d, not %d", OvertFlakeIDLength, len(id))

	assert.Condition(t, func() bool {
		return gen.LastAllocatedTime() > 0
	})

	upper := binary.BigEndian.Uint64(id[0:8])
	//lower := binary.BigEndian.Uint64(id[8:16])

	/*assert.Equal(t,
	gen.(*generator).machineID,
	lower,
	"expecting lower 64-bits of id to == generator.machineID (%d), not %d",
	gen.(*generator).machineID,
	lower)*/

	// because we are generating 1, and we know that we are only requestor then
	// we also know none have been allocated so the interval should be 0
	assert.Condition(t, func() bool {
		return (upper & 0xFFFF) == 0
	})

	/*assert.Condition(t, func() bool {
		timestamp := upper >> 16
		beginDelta := uint64(startTime - gen.Epoch())
		return timestamp >= beginDelta
	}, "Expecting upper %d >= %d", upper>>16, startTime-gen.Epoch())*/
}

func TestGenerateStreamIDs(t *testing.T) {
	// Create a generator
	gen := NewOvertFlakeGenerator(OvertoneEpochMs, testHardwareID, 42, 0)

	// Create a buffer which forces the stream to provide them 1 at a time
	buffer := make([]byte, OvertFlakeIDLength)
	var called int
	totalAllocated, err := gen.GenerateAsStream(3, buffer, func(allocated int, ids []byte) error {
		called++
		return nil
	})
	assert.NoError(t, err)
	assert.Equal(t, 3, totalAllocated, "Expecting total # of ids generated to == %d, not %d", 3, totalAllocated)
	assert.Equal(t, 3, called, "Expecting total # of callbacks to be %d, not %d", 3, called)

	// Create a buffer which forces the stream to provide them 2 at a time
	buffer = make([]byte, OvertFlakeIDLength*2)
	called = 0

	// We are requesting 3 with a buffer that can hold 2, so two callbacks are expected with
	totalAllocated, err = gen.GenerateAsStream(3, buffer, func(allocated int, ids []byte) error {
		switch called {
		case 0:
			assert.Equal(t, 2, allocated, "Expecting 1st callback to have % ids, not %d", 2, allocated)
		case 1:
			assert.Equal(t, 1, allocated, "Expecting last callback to have % ids, not %d", 1, allocated)
		}
		called++
		return nil
	})
	assert.NoError(t, err)
	assert.Equal(t, 3, totalAllocated, "Expecting total # of ids generated to == %d, not %d", 3, totalAllocated)
	assert.Equal(t, 2, called, "Expecting total # of callbacks to be %d, not %d", 3, called)

	buffer = make([]byte, OvertFlakeIDLength*64)
	called = 0

	// We are requesting 3 with a buffer that can hold 2, so two callbacks are expected with
	totalAllocated, err = gen.GenerateAsStream(64, buffer, func(allocated int, ids []byte) error {
		switch called {
		case 0:
			assert.Equal(t, 64, allocated, "Expecting 1st callback to have % ids, not %d", 64, allocated)
		case 1:
			assert.Fail(t, "Not expecting 2nd callback")
		}
		called++
		return nil
	})
	assert.NoError(t, err)
	assert.Equal(t, 64, totalAllocated, "Expecting total # of ids generated to == %d, not %d", 64, totalAllocated)
	assert.Equal(t, 1, called, "Expecting total # of callbacks to be %d, not %d", 1, called)
}
<|endoftext|>


<commit_before>package mailp

import (
	"errors"
	"fmt"
	"github.com/HouzuoGuo/laitos/bridge"
	"github.com/HouzuoGuo/laitos/email"
	"github.com/HouzuoGuo/laitos/feature"
	"github.com/HouzuoGuo/laitos/frontend/common"
	"github.com/HouzuoGuo/laitos/global"
	"strconv"
	"strings"
	"sync"
)

/*
Look for feature commands from an incoming mail, run them and reply the sender with command results.
Usually used in combination of laitos' own SMTP daemon, but it can also work independently in something like
postfix's "forward-mail-to-program" mechanism.
*/
type MailProcessor struct {
	CommandTimeoutSec int                      `json:"CommandTimeoutSec"` // Commands get time out error after this number of seconds
	Undocumented1     Undocumented1            `json:"Undocumented1"`     // Intentionally undocumented he he he he
	Processor         *common.CommandProcessor `json:"-"`                 // Feature configuration
	ReplyMailer       email.Mailer             `json:"-"`                 // To deliver Email replies
	Logger            global.Logger            `json:"-"`                 // Logger
}

// Run a health check on mailer and "undocumented" things.
func (mailproc *MailProcessor) SelfTest() error {
	ret := make([]error, 0, 0)
	retMutex := &sync.Mutex{}
	wait := &sync.WaitGroup{}
	// One mailer and one undocumented
	wait.Add(2)
	go func() {
		err := mailproc.ReplyMailer.SelfTest()
		if err != nil {
			retMutex.Lock()
			ret = append(ret, err)
			retMutex.Unlock()
		}
	}()
	go func() {
		err := mailproc.Undocumented1.SelfTest()
		if err != nil {
			retMutex.Lock()
			ret = append(ret, err)
			retMutex.Unlock()
		}
	}()
	wait.Done()
	if len(ret) == 0 {
		return nil
	}
	return fmt.Errorf("%v", ret)
}

/*
Make sure mail processor is sane before processing the incoming mail.
Process only one command (if found) in the incoming mail. If reply addresses are specified, send command result
to the specified addresses. If they are not specified, use the incoming mail sender's address as reply address.
*/
func (mailproc *MailProcessor) Process(mailContent []byte, replyAddresses ...string) error {
	mailproc.Logger = global.Logger{ComponentName: "MailProcessor", ComponentID: strconv.Itoa(mailproc.CommandTimeoutSec)}
	mailproc.Processor.SetLogger(mailproc.Logger)
	if global.EmergencyLockDown {
		return global.ErrEmergencyLockDown
	}
	if errs := mailproc.Processor.IsSaneForInternet(); len(errs) > 0 {
		return fmt.Errorf("MailProcessor.Process: %+v", errs)
	}
	var commandIsProcessed bool
	walkErr := email.WalkMessage(mailContent, func(prop email.BasicProperties, body []byte) (bool, error) {
		// Avoid recursive processing
		if strings.Contains(prop.Subject, email.OutgoingMailSubjectKeyword) {
			return false, errors.New("Ignore email sent by this program itself")
		}
		mailproc.Logger.Printf("Process", prop.FromAddress, nil, "process message of type %s, subject \"%s\"", prop.ContentType, prop.Subject)
		// By contract, PIN processor finds command among input lines.
		result := mailproc.Processor.Process(feature.Command{
			Content:    string(body),
			TimeoutSec: mailproc.CommandTimeoutSec,
		})
		// If this part does not have a PIN/shortcut match, simply move on to the next part.
		if result.Error == bridge.ErrPINAndShortcutNotFound {
			// Move on, do not return error.
			return true, nil
		} else if result.Error != nil {
			// In case of command processing error, do not move on, return the error.
			return false, result.Error
		}
		// A command has been processed, now work on the reply.
		commandIsProcessed = true
		// Normally the result should be sent as Email reply, but there are undocumented scenarios.
		if mailproc.Undocumented1.IsConfigured() {
			// The undocumented scenario is triggered by an Email address suffix
			if mailproc.Undocumented1.Addr1 != "" && strings.HasSuffix(prop.ReplyAddress, mailproc.Undocumented1.Addr1) {
				// Let the undocumented scenario take care of delivering the result
				if undoc1Err := mailproc.Undocumented1.SendMessage(result.CombinedOutput); undoc1Err == nil {
					return false, nil
				} else {
					return false, undoc1Err
				}
			}
		}
		// The Email address suffix did not satisfy undocumented scenario, so send the result as a normal Email reply.
		if !mailproc.ReplyMailer.IsConfigured() {
			return false, errors.New("The reply has to be sent via Email but configuration is missing")
		}
		recipients := replyAddresses
		if recipients == nil || len(recipients) == 0 {
			recipients = []string{prop.ReplyAddress}
		}
		return false, mailproc.ReplyMailer.Send(email.OutgoingMailSubjectKeyword+"-reply-"+result.Command.Content, result.CombinedOutput, recipients...)
	})
	if walkErr != nil {
		return walkErr
	}
	// If all parts have been visited but no command is found, return the PIN mismatch error.
	if !commandIsProcessed {
		return bridge.ErrPINAndShortcutNotFound
	}
	return nil
}

var TestUndocumented1Message = ""                     // Content is set by init_mail_test.go
var TestUndocumented1Wolfram = feature.WolframAlpha{} // Details are set by init_mail_test.go
<commit_msg>fix malfunctioned self test of mail processor<commit_after>package mailp

import (
	"errors"
	"fmt"
	"github.com/HouzuoGuo/laitos/bridge"
	"github.com/HouzuoGuo/laitos/email"
	"github.com/HouzuoGuo/laitos/feature"
	"github.com/HouzuoGuo/laitos/frontend/common"
	"github.com/HouzuoGuo/laitos/global"
	"strconv"
	"strings"
	"sync"
)

/*
Look for feature commands from an incoming mail, run them and reply the sender with command results.
Usually used in combination of laitos' own SMTP daemon, but it can also work independently in something like
postfix's "forward-mail-to-program" mechanism.
*/
type MailProcessor struct {
	CommandTimeoutSec int                      `json:"CommandTimeoutSec"` // Commands get time out error after this number of seconds
	Undocumented1     Undocumented1            `json:"Undocumented1"`     // Intentionally undocumented he he he he
	Processor         *common.CommandProcessor `json:"-"`                 // Feature configuration
	ReplyMailer       email.Mailer             `json:"-"`                 // To deliver Email replies
	Logger            global.Logger            `json:"-"`                 // Logger
}

// Run a health check on mailer and "undocumented" things.
func (mailproc *MailProcessor) SelfTest() error {
	ret := make([]error, 0, 0)
	retMutex := &sync.Mutex{}
	wait := &sync.WaitGroup{}
	// One mailer and one undocumented
	wait.Add(2)
	go func() {
		err := mailproc.ReplyMailer.SelfTest()
		if err != nil {
			retMutex.Lock()
			ret = append(ret, err)
			retMutex.Unlock()
		}
		wait.Done()
	}()
	go func() {
		err := mailproc.Undocumented1.SelfTest()
		if err != nil {
			retMutex.Lock()
			ret = append(ret, err)
			retMutex.Unlock()
		}
		wait.Done()
	}()
	wait.Wait()
	if len(ret) == 0 {
		return nil
	}
	return fmt.Errorf("%v", ret)
}

/*
Make sure mail processor is sane before processing the incoming mail.
Process only one command (if found) in the incoming mail. If reply addresses are specified, send command result
to the specified addresses. If they are not specified, use the incoming mail sender's address as reply address.
*/
func (mailproc *MailProcessor) Process(mailContent []byte, replyAddresses ...string) error {
	mailproc.Logger = global.Logger{ComponentName: "MailProcessor", ComponentID: strconv.Itoa(mailproc.CommandTimeoutSec)}
	mailproc.Processor.SetLogger(mailproc.Logger)
	if global.EmergencyLockDown {
		return global.ErrEmergencyLockDown
	}
	if errs := mailproc.Processor.IsSaneForInternet(); len(errs) > 0 {
		return fmt.Errorf("MailProcessor.Process: %+v", errs)
	}
	var commandIsProcessed bool
	walkErr := email.WalkMessage(mailContent, func(prop email.BasicProperties, body []byte) (bool, error) {
		// Avoid recursive processing
		if strings.Contains(prop.Subject, email.OutgoingMailSubjectKeyword) {
			return false, errors.New("Ignore email sent by this program itself")
		}
		mailproc.Logger.Printf("Process", prop.FromAddress, nil, "process message of type %s, subject \"%s\"", prop.ContentType, prop.Subject)
		// By contract, PIN processor finds command among input lines.
		result := mailproc.Processor.Process(feature.Command{
			Content:    string(body),
			TimeoutSec: mailproc.CommandTimeoutSec,
		})
		// If this part does not have a PIN/shortcut match, simply move on to the next part.
		if result.Error == bridge.ErrPINAndShortcutNotFound {
			// Move on, do not return error.
			return true, nil
		} else if result.Error != nil {
			// In case of command processing error, do not move on, return the error.
			return false, result.Error
		}
		// A command has been processed, now work on the reply.
		commandIsProcessed = true
		// Normally the result should be sent as Email reply, but there are undocumented scenarios.
		if mailproc.Undocumented1.IsConfigured() {
			// The undocumented scenario is triggered by an Email address suffix
			if mailproc.Undocumented1.Addr1 != "" && strings.HasSuffix(prop.ReplyAddress, mailproc.Undocumented1.Addr1) {
				// Let the undocumented scenario take care of delivering the result
				if undoc1Err := mailproc.Undocumented1.SendMessage(result.CombinedOutput); undoc1Err == nil {
					return false, nil
				} else {
					return false, undoc1Err
				}
			}
		}
		// The Email address suffix did not satisfy undocumented scenario, so send the result as a normal Email reply.
		if !mailproc.ReplyMailer.IsConfigured() {
			return false, errors.New("The reply has to be sent via Email but configuration is missing")
		}
		recipients := replyAddresses
		if recipients == nil || len(recipients) == 0 {
			recipients = []string{prop.ReplyAddress}
		}
		return false, mailproc.ReplyMailer.Send(email.OutgoingMailSubjectKeyword+"-reply-"+result.Command.Content, result.CombinedOutput, recipients...)
	})
	if walkErr != nil {
		return walkErr
	}
	// If all parts have been visited but no command is found, return the PIN mismatch error.
	if !commandIsProcessed {
		return bridge.ErrPINAndShortcutNotFound
	}
	return nil
}

var TestUndocumented1Message = ""                     // Content is set by init_mail_test.go
var TestUndocumented1Wolfram = feature.WolframAlpha{} // Details are set by init_mail_test.go
<|endoftext|>


<commit_before>// Copyright 2014 CoreOS, 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 util

import (
	"bytes"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"strings"
	"time"

	"github.com/coreos/fleet/Godeps/_workspace/src/github.com/pborman/uuid"
)

var fleetctlBinPath string

func init() {
	fleetctlBinPath = os.Getenv("FLEETCTL_BIN")
	if fleetctlBinPath == "" {
		fmt.Println("FLEETCTL_BIN environment variable must be set")
		os.Exit(1)
	} else if _, err := os.Stat(fleetctlBinPath); err != nil {
		fmt.Printf("%v\n", err)
		os.Exit(1)
	}

	if os.Getenv("SSH_AUTH_SOCK") == "" {
		fmt.Println("SSH_AUTH_SOCK environment variable must be set")
		os.Exit(1)
	}
}

type fleetfunc func(args ...string) (string, string, error)

func RunFleetctl(args ...string) (string, string, error) {
	log.Printf("%s %s", fleetctlBinPath, strings.Join(args, " "))
	var stdoutBytes, stderrBytes bytes.Buffer
	cmd := exec.Command(fleetctlBinPath, args...)
	cmd.Stdout = &stdoutBytes
	cmd.Stderr = &stderrBytes
	err := cmd.Run()
	return stdoutBytes.String(), stderrBytes.String(), err
}

func RunFleetctlWithInput(input string, args ...string) (string, string, error) {
	log.Printf("%s %s", fleetctlBinPath, strings.Join(args, " "))
	var stdoutBytes, stderrBytes bytes.Buffer
	cmd := exec.Command(fleetctlBinPath, args...)
	cmd.Stdout = &stdoutBytes
	cmd.Stderr = &stderrBytes
	stdin, err := cmd.StdinPipe()
	if err != nil {
		return "", "", err
	}

	if err = cmd.Start(); err != nil {
		return "", "", err
	}

	stdin.Write([]byte(input))
	stdin.Close()
	err = cmd.Wait()

	return stdoutBytes.String(), stderrBytes.String(), err
}

// ActiveToSingleStates takes a map of active states (such as that returned by
// WaitForNActiveUnits) and ensures that each unit has at most a single active
// state. It returns a mapping of unit name to a single UnitState.
func ActiveToSingleStates(active map[string][]UnitState) (map[string]UnitState, error) {
	states := make(map[string]UnitState)
	for name, us := range active {
		if len(us) != 1 {
			return nil, fmt.Errorf("unit %s running in multiple locations: %v", name, us)
		}
		states[name] = us[0]
	}
	return states, nil
}

type UnitState struct {
	Name        string
	ActiveState string
	Machine     string
}

type UnitFileState struct {
	Name         string
	DesiredState string
	State        string
}

func ParseUnitStates(units []string) (states []UnitState) {
	for _, unit := range units {
		cols := strings.Fields(unit)
		if len(cols) == 3 {
			machine := strings.SplitN(cols[2], "/", 2)[0]
			states = append(states, UnitState{cols[0], cols[1], machine})
		}
	}
	return states
}

func ParseUnitFileStates(units []string) (states []UnitFileState) {
	for _, unit := range units {
		cols := strings.Fields(unit)
		if len(cols) == 3 {
			states = append(states, UnitFileState{cols[0], cols[1], cols[2]})
		}
	}
	return states
}

func FilterActiveUnits(states []UnitState) (filtered []UnitState) {
	for _, state := range states {
		if state.ActiveState == "active" {
			filtered = append(filtered, state)
		}
	}
	return
}

// tempUnit creates a local unit file with the given contents, returning
// the name of the file
func TempUnit(contents string) (string, error) {
	tmp, err := ioutil.TempFile(os.TempDir(), "fleet-test-unit-")
	if err != nil {
		return "", err
	}

	tmp.Write([]byte(contents))
	tmp.Close()

	svc := fmt.Sprintf("%s.service", tmp.Name())
	err = os.Rename(tmp.Name(), svc)
	if err != nil {
		os.Remove(tmp.Name())
		return "", err
	}

	return svc, nil
}

func WaitForState(stateCheckFunc func() bool) (time.Duration, error) {
	timeout := 15 * time.Second
	alarm := time.After(timeout)
	ticker := time.Tick(250 * time.Millisecond)
	for {
		select {
		case <-alarm:
			// Generic message. Callers can build more specific ones using the returned timeout value.
			return timeout, fmt.Errorf("Failed to reach expected state within %v.", timeout)
		case <-ticker:
			if stateCheckFunc() {
				return timeout, nil
			}
		}
	}
}

func NewMachineID() string {
	// drop the standard separators to match systemd
	return strings.Replace(uuid.New(), "-", "", -1)
}
<commit_msg>functional: introduce new helpers for testing the replace option<commit_after>// Copyright 2014 CoreOS, 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 util

import (
	"bytes"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"strings"
	"time"

	"github.com/coreos/fleet/Godeps/_workspace/src/github.com/pborman/uuid"
)

var fleetctlBinPath string

func init() {
	fleetctlBinPath = os.Getenv("FLEETCTL_BIN")
	if fleetctlBinPath == "" {
		fmt.Println("FLEETCTL_BIN environment variable must be set")
		os.Exit(1)
	} else if _, err := os.Stat(fleetctlBinPath); err != nil {
		fmt.Printf("%v\n", err)
		os.Exit(1)
	}

	if os.Getenv("SSH_AUTH_SOCK") == "" {
		fmt.Println("SSH_AUTH_SOCK environment variable must be set")
		os.Exit(1)
	}
}

type fleetfunc func(args ...string) (string, string, error)

func RunFleetctl(args ...string) (string, string, error) {
	log.Printf("%s %s", fleetctlBinPath, strings.Join(args, " "))
	var stdoutBytes, stderrBytes bytes.Buffer
	cmd := exec.Command(fleetctlBinPath, args...)
	cmd.Stdout = &stdoutBytes
	cmd.Stderr = &stderrBytes
	err := cmd.Run()
	return stdoutBytes.String(), stderrBytes.String(), err
}

func RunFleetctlWithInput(input string, args ...string) (string, string, error) {
	log.Printf("%s %s", fleetctlBinPath, strings.Join(args, " "))
	var stdoutBytes, stderrBytes bytes.Buffer
	cmd := exec.Command(fleetctlBinPath, args...)
	cmd.Stdout = &stdoutBytes
	cmd.Stderr = &stderrBytes
	stdin, err := cmd.StdinPipe()
	if err != nil {
		return "", "", err
	}

	if err = cmd.Start(); err != nil {
		return "", "", err
	}

	stdin.Write([]byte(input))
	stdin.Close()
	err = cmd.Wait()

	return stdoutBytes.String(), stderrBytes.String(), err
}

// ActiveToSingleStates takes a map of active states (such as that returned by
// WaitForNActiveUnits) and ensures that each unit has at most a single active
// state. It returns a mapping of unit name to a single UnitState.
func ActiveToSingleStates(active map[string][]UnitState) (map[string]UnitState, error) {
	states := make(map[string]UnitState)
	for name, us := range active {
		if len(us) != 1 {
			return nil, fmt.Errorf("unit %s running in multiple locations: %v", name, us)
		}
		states[name] = us[0]
	}
	return states, nil
}

type UnitState struct {
	Name        string
	ActiveState string
	Machine     string
}

type UnitFileState struct {
	Name         string
	DesiredState string
	State        string
}

func ParseUnitStates(units []string) (states []UnitState) {
	for _, unit := range units {
		cols := strings.Fields(unit)
		if len(cols) == 3 {
			machine := strings.SplitN(cols[2], "/", 2)[0]
			states = append(states, UnitState{cols[0], cols[1], machine})
		}
	}
	return states
}

func ParseUnitFileStates(units []string) (states []UnitFileState) {
	for _, unit := range units {
		cols := strings.Fields(unit)
		if len(cols) == 3 {
			states = append(states, UnitFileState{cols[0], cols[1], cols[2]})
		}
	}
	return states
}

func FilterActiveUnits(states []UnitState) (filtered []UnitState) {
	for _, state := range states {
		if state.ActiveState == "active" {
			filtered = append(filtered, state)
		}
	}
	return
}

// tempUnit creates a local unit file with the given contents, returning
// the name of the file
func TempUnit(contents string) (string, error) {
	tmp, err := ioutil.TempFile(os.TempDir(), "fleet-test-unit-")
	if err != nil {
		return "", err
	}

	tmp.Write([]byte(contents))
	tmp.Close()

	svc := fmt.Sprintf("%s.service", tmp.Name())
	err = os.Rename(tmp.Name(), svc)
	if err != nil {
		os.Remove(tmp.Name())
		return "", err
	}

	return svc, nil
}

func WaitForState(stateCheckFunc func() bool) (time.Duration, error) {
	timeout := 15 * time.Second
	alarm := time.After(timeout)
	ticker := time.Tick(250 * time.Millisecond)
	for {
		select {
		case <-alarm:
			// Generic message. Callers can build more specific ones using the returned timeout value.
			return timeout, fmt.Errorf("Failed to reach expected state within %v.", timeout)
		case <-ticker:
			if stateCheckFunc() {
				return timeout, nil
			}
		}
	}
}

func NewMachineID() string {
	// drop the standard separators to match systemd
	return strings.Replace(uuid.New(), "-", "", -1)
}

// CopyFile()
func CopyFile(newFile, oldFile string) error {
	input, err := ioutil.ReadFile(oldFile)
	if err != nil {
		return err
	}
	err = ioutil.WriteFile(newFile, []byte(input), 0644)
	if err != nil {
		return err
	}
	return nil
}

// GenNewFleetService() is a helper for generating a temporary fleet service
// that reads from oldFile, replaces oldVal with newVal, and stores the result
// to newFile.
func GenNewFleetService(newFile, oldFile, newVal, oldVal string) error {
	input, err := ioutil.ReadFile(oldFile)
	if err != nil {
		return err
	}
	lines := strings.Split(string(input), "\n")

	for i, line := range lines {
		if strings.Contains(line, oldVal) {
			lines[i] = strings.Replace(line, oldVal, newVal, len(oldVal))
		}
	}
	output := strings.Join(lines, "\n")
	err = ioutil.WriteFile(newFile, []byte(output), 0644)
	if err != nil {
		return err
	}
	return nil
}
<|endoftext|>


<commit_before>// Copyright (C) 2017 Google 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 client

import (
	"fmt"

	"github.com/google/gapid/core/app/auth"
	"github.com/google/gapid/core/fault/cause"
	"github.com/google/gapid/core/log"
	"github.com/google/gapid/core/net/grpcutil"
	"github.com/google/gapid/core/os/file"
	"github.com/google/gapid/core/os/process"
	"github.com/google/gapid/framework/binary/registry"
	"github.com/google/gapid/framework/binary/schema"
	"google.golang.org/grpc"
)

const (
	BinaryName = "gapis"
)

var (
	// GapisPath is the full filepath to the gapir executable.
	GapisPath file.Path
)

func init() {
	// Search directory that this executable is in.
	if path, err := file.FindExecutable(file.ExecutablePath().Parent().Join(BinaryName).System()); err == nil {
		GapisPath = path
		return
	}
	// Search $PATH.
	if path, err := file.FindExecutable(BinaryName); err == nil {
		GapisPath = path
		return
	}
}

type Config struct {
	Path  *file.Path
	Port  int
	Args  []string
	Token auth.Token
}

// Connect attempts to connect to a GAPIS process.
// If port is zero, a new GAPIS server will be started, otherwise a connection
// will be made to the specified port.
func Connect(ctx log.Context, cfg Config) (Client, *schema.Message, error) {
	if cfg.Path == nil {
		cfg.Path = &GapisPath
	}

	var err error
	if cfg.Port == 0 || len(cfg.Args) > 0 {
		if ll := logLevel(ctx); ll != "" {
			cfg.Args = append(cfg.Args, "--log-level", ll)
		}
		if cfg.Token != auth.NoAuth {
			cfg.Args = append(cfg.Args, "--gapis-auth-token", string(cfg.Token))
		}
		cfg.Port, err = process.Start(ctx, cfg.Path.System(), nil, cfg.Args...)
		if err != nil {
			return nil, nil, err
		}
	}

	target := fmt.Sprintf("localhost:%d", cfg.Port)

	conn, err := grpcutil.Dial(ctx, target,
		grpc.WithInsecure(),
		grpc.WithUnaryInterceptor(auth.ClientInterceptor(cfg.Token)))
	if err != nil {
		return nil, nil, cause.Explain(ctx, err, "Dialing GAPIS")
	}
	client := Bind(conn)

	message, err := client.GetSchema(ctx)
	if err != nil {
		return nil, nil, fmt.Errorf("Error resolving schema: %v", err)
	}

	for _, entity := range message.Entities {
		registry.Global.Add((*schema.ObjectClass)(entity))
	}

	return client, message, nil
}

func logLevel(ctx log.Context) string {
	switch {
	case ctx.Debug().Active():
		return "Debug"
	case ctx.Info().Active():
		return "Info"
	case ctx.Notice().Active():
		return "Notice"
	case ctx.Warning().Active():
		return "Warning"
	case ctx.Error().Active():
		return "Error"
	case ctx.Critical().Active():
		return "Critical"
	case ctx.Alert().Active():
		return "Alert"
	case ctx.Emergency().Active():
		return "Emergency"
	default:
		return ""
	}
}
<commit_msg>Add package style path searching to gapit looking for gapis<commit_after>// Copyright (C) 2017 Google 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 client

import (
	"fmt"

	"github.com/google/gapid/core/app/auth"
	"github.com/google/gapid/core/fault/cause"
	"github.com/google/gapid/core/log"
	"github.com/google/gapid/core/net/grpcutil"
	"github.com/google/gapid/core/os/device"
	"github.com/google/gapid/core/os/file"
	"github.com/google/gapid/core/os/process"
	"github.com/google/gapid/framework/binary/registry"
	"github.com/google/gapid/framework/binary/schema"
	"google.golang.org/grpc"
)

const (
	BinaryName = "gapis"
)

var (
	// GapisPath is the full filepath to the gapir executable.
	GapisPath file.Path
)

func init() {
	// Search directory that this executable is in.
	if path, err := file.FindExecutable(file.ExecutablePath().Parent().Join(BinaryName).System()); err == nil {
		GapisPath = path
		return
	}
	// Search standard package structure
	packagePath := file.Abs(".")
	switch device.Host(log.Background()).Configuration.OS.Kind {
	case device.Windows:
		packagePath = packagePath.Join("windows")
	case device.OSX:
		packagePath = packagePath.Join("osx")
	case device.Linux:
		packagePath = packagePath.Join("linux")
	}
	packagePath = packagePath.Join("x86_64")
	if path, err := file.FindExecutable(packagePath.Join(BinaryName).System()); err == nil {
		GapisPath = path
		return
	}
	// Search $PATH.
	if path, err := file.FindExecutable(BinaryName); err == nil {
		GapisPath = path
		return
	}
}

type Config struct {
	Path  *file.Path
	Port  int
	Args  []string
	Token auth.Token
}

// Connect attempts to connect to a GAPIS process.
// If port is zero, a new GAPIS server will be started, otherwise a connection
// will be made to the specified port.
func Connect(ctx log.Context, cfg Config) (Client, *schema.Message, error) {
	if cfg.Path == nil {
		cfg.Path = &GapisPath
	}

	var err error
	if cfg.Port == 0 || len(cfg.Args) > 0 {
		if ll := logLevel(ctx); ll != "" {
			cfg.Args = append(cfg.Args, "--log-level", ll)
		}
		if cfg.Token != auth.NoAuth {
			cfg.Args = append(cfg.Args, "--gapis-auth-token", string(cfg.Token))
		}
		cfg.Port, err = process.Start(ctx, cfg.Path.System(), nil, cfg.Args...)
		if err != nil {
			return nil, nil, err
		}
	}

	target := fmt.Sprintf("localhost:%d", cfg.Port)

	conn, err := grpcutil.Dial(ctx, target,
		grpc.WithInsecure(),
		grpc.WithUnaryInterceptor(auth.ClientInterceptor(cfg.Token)))
	if err != nil {
		return nil, nil, cause.Explain(ctx, err, "Dialing GAPIS")
	}
	client := Bind(conn)

	message, err := client.GetSchema(ctx)
	if err != nil {
		return nil, nil, fmt.Errorf("Error resolving schema: %v", err)
	}

	for _, entity := range message.Entities {
		registry.Global.Add((*schema.ObjectClass)(entity))
	}

	return client, message, nil
}

func logLevel(ctx log.Context) string {
	switch {
	case ctx.Debug().Active():
		return "Debug"
	case ctx.Info().Active():
		return "Info"
	case ctx.Notice().Active():
		return "Notice"
	case ctx.Warning().Active():
		return "Warning"
	case ctx.Error().Active():
		return "Error"
	case ctx.Critical().Active():
		return "Critical"
	case ctx.Alert().Active():
		return "Alert"
	case ctx.Emergency().Active():
		return "Emergency"
	default:
		return ""
	}
}
<|endoftext|>


<commit_before>package gateway

import (
	"context"
	"encoding/json"
	"net/http"
	"sync"
	"sync/atomic"
	"time"

	"github.com/TykTechnologies/tyk/config"
	"github.com/TykTechnologies/tyk/storage"
	"github.com/sirupsen/logrus"
)

type (
	HealthCheckStatus string

	HealthCheckComponentType string
)

const (
	Pass HealthCheckStatus = "pass"
	Fail                   = "fail"
	Warn                   = "warn"

	Component HealthCheckComponentType = "component"
	Datastore                          = "datastore"
	System                             = "system"
)

var (
	healthCheckInfo atomic.Value
	healthCheckLock sync.Mutex
)

func setCurrentHealthCheckInfo(h map[string]HealthCheckItem) {
	healthCheckLock.Lock()
	healthCheckInfo.Store(h)
	healthCheckLock.Unlock()
}

func getHealthCheckInfo() map[string]HealthCheckItem {
	healthCheckLock.Lock()
	ret := healthCheckInfo.Load().(map[string]HealthCheckItem)
	healthCheckLock.Unlock()
	return ret
}

type HealthCheckResponse struct {
	Status      HealthCheckStatus          `json:"status"`
	Version     string                     `json:"version,omitempty"`
	Output      string                     `json:"output,omitempty"`
	Description string                     `json:"description,omitempty"`
	Details     map[string]HealthCheckItem `json:"details,omitempty"`
}

type HealthCheckItem struct {
	Status        HealthCheckStatus `json:"status"`
	Output        string            `json:"output,omitempty"`
	ComponentType string            `json:"componentType,omitempty"`
	ComponentID   string            `json:"componentId,omitempty"`
	Time          string            `json:"time"`
}

func initHealthCheck(ctx context.Context) {
	setCurrentHealthCheckInfo(make(map[string]HealthCheckItem, 3))

	go func(ctx context.Context) {
		var n = config.Global().LivenessCheck.CheckDuration

		if n == 0 {
			n = 10
		}

		ticker := time.NewTicker(time.Second * n)

		for {
			select {
			case <-ctx.Done():

				ticker.Stop()
				mainLog.WithFields(logrus.Fields{
					"prefix": "health-check",
				}).Debug("Stopping Health checks for all components")
				return

			case <-ticker.C:
				gatherHealthChecks()
			}
		}
	}(ctx)
}

func gatherHealthChecks() {

	allInfos := make(map[string]HealthCheckItem, 3)

	redisStore := storage.RedisCluster{KeyPrefix: "livenesscheck-"}

	key := "tyk-liveness-probe"

	var wg sync.WaitGroup

	go func() {

		wg.Add(1)

		defer wg.Done()

		var checkItem = HealthCheckItem{
			Status:        Pass,
			ComponentType: Datastore,
			Time:          time.Now().Format(time.RFC3339),
		}

		err := redisStore.SetRawKey(key, key, 10)
		if err != nil {
			mainLog.WithField("liveness-check", true).WithError(err).Error("Redis health check failed")
			checkItem.Output = err.Error()
			checkItem.Status = Fail
		}

		allInfos["redis"] = checkItem
	}()

	if config.Global().UseDBAppConfigs {

		wg.Add(1)

		go func() {
			defer wg.Done()

			var checkItem = HealthCheckItem{
				Status:        Pass,
				ComponentType: Datastore,
				Time:          time.Now().Format(time.RFC3339),
			}

			if err := DashService.Ping(); err != nil {
				mainLog.WithField("liveness-check", true).Error(err)
				checkItem.Output = err.Error()
				checkItem.Status = Fail
			}

			checkItem.ComponentType = System
			allInfos["dashboard"] = checkItem
		}()
	}

	if config.Global().Policies.PolicySource == "rpc" {

		wg.Add(1)

		go func() {
			defer wg.Done()

			var checkItem = HealthCheckItem{
				Status:        Pass,
				ComponentType: Datastore,
				Time:          time.Now().Format(time.RFC3339),
			}

			rpcStore := RPCStorageHandler{KeyPrefix: "livenesscheck-"}

			if !rpcStore.Connect() {
				checkItem.Output = "Could not connect to RPC"
				checkItem.Status = Fail
			}

			checkItem.ComponentType = System

			allInfos["rpc"] = checkItem
		}()
	}

	wg.Wait()

	setCurrentHealthCheckInfo(allInfos)
}

func liveCheckHandler(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodGet {
		doJSONWrite(w, http.StatusMethodNotAllowed, apiError(http.StatusText(http.StatusMethodNotAllowed)))
		return
	}

	checks := getHealthCheckInfo()

	res := HealthCheckResponse{
		Status:      Pass,
		Version:     VERSION,
		Description: "Tyk GW",
		Details:     checks,
	}

	var failCount int

	for _, v := range checks {
		if v.Status == Fail {
			failCount++
		}
	}

	var status HealthCheckStatus

	switch failCount {
	case 0:
		status = Pass

	case len(checks):
		status = Fail

	default:
		status = Warn
	}

	res.Status = status

	w.WriteHeader(http.StatusOK)
	json.NewEncoder(w).Encode(res)
}
<commit_msg>Fix race (#2881)<commit_after>package gateway

import (
	"context"
	"encoding/json"
	"net/http"
	"sync"
	"sync/atomic"
	"time"

	"github.com/TykTechnologies/tyk/config"
	"github.com/TykTechnologies/tyk/storage"
	"github.com/sirupsen/logrus"
)

type (
	HealthCheckStatus string

	HealthCheckComponentType string
)

const (
	Pass HealthCheckStatus = "pass"
	Fail                   = "fail"
	Warn                   = "warn"

	Component HealthCheckComponentType = "component"
	Datastore                          = "datastore"
	System                             = "system"
)

var (
	healthCheckInfo atomic.Value
	healthCheckLock sync.Mutex
)

func setCurrentHealthCheckInfo(h map[string]HealthCheckItem) {
	healthCheckLock.Lock()
	healthCheckInfo.Store(h)
	healthCheckLock.Unlock()
}

func getHealthCheckInfo() map[string]HealthCheckItem {
	healthCheckLock.Lock()
	ret := healthCheckInfo.Load().(map[string]HealthCheckItem)
	healthCheckLock.Unlock()
	return ret
}

type HealthCheckResponse struct {
	Status      HealthCheckStatus          `json:"status"`
	Version     string                     `json:"version,omitempty"`
	Output      string                     `json:"output,omitempty"`
	Description string                     `json:"description,omitempty"`
	Details     map[string]HealthCheckItem `json:"details,omitempty"`
}

type HealthCheckItem struct {
	Status        HealthCheckStatus `json:"status"`
	Output        string            `json:"output,omitempty"`
	ComponentType string            `json:"componentType,omitempty"`
	ComponentID   string            `json:"componentId,omitempty"`
	Time          string            `json:"time"`
}

func initHealthCheck(ctx context.Context) {
	setCurrentHealthCheckInfo(make(map[string]HealthCheckItem, 3))

	go func(ctx context.Context) {
		var n = config.Global().LivenessCheck.CheckDuration

		if n == 0 {
			n = 10
		}

		ticker := time.NewTicker(time.Second * n)

		for {
			select {
			case <-ctx.Done():

				ticker.Stop()
				mainLog.WithFields(logrus.Fields{
					"prefix": "health-check",
				}).Debug("Stopping Health checks for all components")
				return

			case <-ticker.C:
				gatherHealthChecks()
			}
		}
	}(ctx)
}

func gatherHealthChecks() {

	allInfos := make(map[string]HealthCheckItem, 3)

	redisStore := storage.RedisCluster{KeyPrefix: "livenesscheck-"}

	key := "tyk-liveness-probe"

	var wg sync.WaitGroup

	wg.Add(1)
	go func() {
		defer wg.Done()

		var checkItem = HealthCheckItem{
			Status:        Pass,
			ComponentType: Datastore,
			Time:          time.Now().Format(time.RFC3339),
		}

		err := redisStore.SetRawKey(key, key, 10)
		if err != nil {
			mainLog.WithField("liveness-check", true).WithError(err).Error("Redis health check failed")
			checkItem.Output = err.Error()
			checkItem.Status = Fail
		}

		allInfos["redis"] = checkItem
	}()

	if config.Global().UseDBAppConfigs {
		wg.Add(1)

		go func() {
			defer wg.Done()

			var checkItem = HealthCheckItem{
				Status:        Pass,
				ComponentType: Datastore,
				Time:          time.Now().Format(time.RFC3339),
			}

			if err := DashService.Ping(); err != nil {
				mainLog.WithField("liveness-check", true).Error(err)
				checkItem.Output = err.Error()
				checkItem.Status = Fail
			}

			checkItem.ComponentType = System
			allInfos["dashboard"] = checkItem
		}()
	}

	if config.Global().Policies.PolicySource == "rpc" {

		wg.Add(1)

		go func() {
			defer wg.Done()

			var checkItem = HealthCheckItem{
				Status:        Pass,
				ComponentType: Datastore,
				Time:          time.Now().Format(time.RFC3339),
			}

			rpcStore := RPCStorageHandler{KeyPrefix: "livenesscheck-"}

			if !rpcStore.Connect() {
				checkItem.Output = "Could not connect to RPC"
				checkItem.Status = Fail
			}

			checkItem.ComponentType = System

			allInfos["rpc"] = checkItem
		}()
	}

	wg.Wait()

	setCurrentHealthCheckInfo(allInfos)
}

func liveCheckHandler(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodGet {
		doJSONWrite(w, http.StatusMethodNotAllowed, apiError(http.StatusText(http.StatusMethodNotAllowed)))
		return
	}

	checks := getHealthCheckInfo()

	res := HealthCheckResponse{
		Status:      Pass,
		Version:     VERSION,
		Description: "Tyk GW",
		Details:     checks,
	}

	var failCount int

	for _, v := range checks {
		if v.Status == Fail {
			failCount++
		}
	}

	var status HealthCheckStatus

	switch failCount {
	case 0:
		status = Pass

	case len(checks):
		status = Fail

	default:
		status = Warn
	}

	res.Status = status

	w.WriteHeader(http.StatusOK)
	json.NewEncoder(w).Encode(res)
}
<|endoftext|>


<commit_before>package server

import (
	"io"

	"github.com/juju/errors"
	"github.com/peer-calls/peer-calls/server/identifiers"
	"github.com/peer-calls/peer-calls/server/logger"
	"github.com/peer-calls/peer-calls/server/sfu"
	"github.com/pion/webrtc/v3"
)

type DataTransceiver struct {
	log logger.Logger

	clientID       identifiers.ClientID
	peerConnection *webrtc.PeerConnection

	// dataChannelChan will receive DataChannels from peer connection
	// OnDataChannel handler.
	dataChannelChan chan *webrtc.DataChannel

	// privateRecvMessagesChan is never closed and it is there to prevent panics
	// when a message is received, but the recvMessagesChan has already been
	// closed.
	privateRecvMessagesChan chan webrtc.DataChannelMessage

	// recvMessagesChan contains received messages. It will be closed on
	// teardown.
	recvMessagesChan chan webrtc.DataChannelMessage

	// sendMessagesChan contains messages to be sent. It is never closed.
	sendMessagesChan chan dataTransceiverMessageSend

	// teardownChan will initiate a teardown as soon as it receives a message.
	teardownChan chan struct{}

	// torndownChan will be closed as soon as teardown is complete.
	torndownChan chan struct{}
}

func NewDataTransceiver(
	log logger.Logger,
	clientID identifiers.ClientID,
	dataChannel *webrtc.DataChannel,
	peerConnection *webrtc.PeerConnection,
) *DataTransceiver {
	d := &DataTransceiver{
		log: log.WithNamespaceAppended("datatransceiver").WithCtx(logger.Ctx{
			"client_id": clientID,
		}),
		clientID:       clientID,
		peerConnection: peerConnection,

		dataChannelChan:         make(chan *webrtc.DataChannel),
		privateRecvMessagesChan: make(chan webrtc.DataChannelMessage),
		recvMessagesChan:        make(chan webrtc.DataChannelMessage),
		sendMessagesChan:        make(chan dataTransceiverMessageSend),
		teardownChan:            make(chan struct{}),
		torndownChan:            make(chan struct{}),
	}

	go d.start()

	if dataChannel != nil {
		d.handleDataChannel(dataChannel)
	}

	peerConnection.OnDataChannel(d.handleDataChannel)

	return d
}

func (d *DataTransceiver) handleDataChannel(dataChannel *webrtc.DataChannel) {
	if dataChannel.Label() == sfu.DataChannelName {
		d.dataChannelChan <- dataChannel

		dataChannel.OnMessage(func(message webrtc.DataChannelMessage) {
			d.log.Info("DataTransceiver.handleMessage", nil)

			select {
			case <-d.torndownChan:
				return
			default:
			}

			select {
			case d.privateRecvMessagesChan <- message:
				// Successfully sent.
			case <-d.torndownChan:
				// DataTransceiver has been torn down.
			}
		})
	}
}

func (d *DataTransceiver) start() {
	defer func() {
		close(d.recvMessagesChan)
		close(d.torndownChan)
	}()

	var dataChannel *webrtc.DataChannel

	handleSendMessage := func(message webrtc.DataChannelMessage) error {
		if dataChannel == nil {
			return errors.Errorf("data channel is nil")
		}

		if message.IsString {
			return errors.Annotate(dataChannel.SendText(string(message.Data)), "send text")
		}

		return errors.Annotate(dataChannel.Send(message.Data), "send bytes")
	}

	for {
		select {
		case dc := <-d.dataChannelChan:
			dataChannel = dc
		case msg := <-d.privateRecvMessagesChan:
			d.recvMessagesChan <- msg
		case msgFuture := <-d.sendMessagesChan:
			err := handleSendMessage(msgFuture.message)
			if err != nil {
				d.log.Error("Send error", errors.Trace(err), nil)

				msgFuture.errCh <- errors.Trace(err)
			}
		case <-d.teardownChan:
			return
		}
	}
}

func (d *DataTransceiver) MessagesChannel() <-chan webrtc.DataChannelMessage {
	return d.recvMessagesChan
}

func (d *DataTransceiver) Close() {
	d.log.Trace("DataTransceiver.Close", nil)

	select {
	case d.teardownChan <- struct{}{}:
	case <-d.torndownChan:
	}

	<-d.torndownChan
}

func (d *DataTransceiver) Send(message webrtc.DataChannelMessage) <-chan error {
	errCh := make(chan error, 1)

	select {
	case d.sendMessagesChan <- dataTransceiverMessageSend{
		errCh:   errCh,
		message: message,
	}:
	case <-d.torndownChan:
		errCh <- errors.Trace(io.ErrClosedPipe)
	}

	close(errCh)

	return errCh
}

type dataTransceiverMessageSend struct {
	// errCh will have error written to it if it occurrs. It will be closed once
	// the message sending has finished.
	errCh chan<- error
	// message to send.
	message webrtc.DataChannelMessage
}
<commit_msg>Fix panic in DataTransceiver (#218)<commit_after>package server

import (
	"io"

	"github.com/juju/errors"
	"github.com/peer-calls/peer-calls/server/identifiers"
	"github.com/peer-calls/peer-calls/server/logger"
	"github.com/peer-calls/peer-calls/server/sfu"
	"github.com/pion/webrtc/v3"
)

type DataTransceiver struct {
	log logger.Logger

	clientID       identifiers.ClientID
	peerConnection *webrtc.PeerConnection

	// dataChannelChan will receive DataChannels from peer connection
	// OnDataChannel handler.
	dataChannelChan chan *webrtc.DataChannel

	// privateRecvMessagesChan is never closed and it is there to prevent panics
	// when a message is received, but the recvMessagesChan has already been
	// closed.
	privateRecvMessagesChan chan webrtc.DataChannelMessage

	// recvMessagesChan contains received messages. It will be closed on
	// teardown.
	recvMessagesChan chan webrtc.DataChannelMessage

	// sendMessagesChan contains messages to be sent. It is never closed.
	sendMessagesChan chan dataTransceiverMessageSend

	// teardownChan will initiate a teardown as soon as it receives a message.
	teardownChan chan struct{}

	// torndownChan will be closed as soon as teardown is complete.
	torndownChan chan struct{}
}

func NewDataTransceiver(
	log logger.Logger,
	clientID identifiers.ClientID,
	dataChannel *webrtc.DataChannel,
	peerConnection *webrtc.PeerConnection,
) *DataTransceiver {
	d := &DataTransceiver{
		log: log.WithNamespaceAppended("datatransceiver").WithCtx(logger.Ctx{
			"client_id": clientID,
		}),
		clientID:       clientID,
		peerConnection: peerConnection,

		dataChannelChan:         make(chan *webrtc.DataChannel),
		privateRecvMessagesChan: make(chan webrtc.DataChannelMessage),
		recvMessagesChan:        make(chan webrtc.DataChannelMessage),
		sendMessagesChan:        make(chan dataTransceiverMessageSend),
		teardownChan:            make(chan struct{}),
		torndownChan:            make(chan struct{}),
	}

	go d.start()

	if dataChannel != nil {
		d.handleDataChannel(dataChannel)
	}

	peerConnection.OnDataChannel(d.handleDataChannel)

	return d
}

func (d *DataTransceiver) handleDataChannel(dataChannel *webrtc.DataChannel) {
	if dataChannel.Label() == sfu.DataChannelName {
		d.dataChannelChan <- dataChannel

		dataChannel.OnMessage(func(message webrtc.DataChannelMessage) {
			d.log.Info("DataTransceiver.handleMessage", nil)

			select {
			case <-d.torndownChan:
				return
			default:
			}

			select {
			case d.privateRecvMessagesChan <- message:
				// Successfully sent.
			case <-d.torndownChan:
				// DataTransceiver has been torn down.
			}
		})
	}
}

func (d *DataTransceiver) start() {
	defer func() {
		close(d.recvMessagesChan)
		close(d.torndownChan)
	}()

	var dataChannel *webrtc.DataChannel

	handleSendMessage := func(message webrtc.DataChannelMessage) error {
		if dataChannel == nil {
			return errors.Errorf("data channel is nil")
		}

		if message.IsString {
			return errors.Annotate(dataChannel.SendText(string(message.Data)), "send text")
		}

		return errors.Annotate(dataChannel.Send(message.Data), "send bytes")
	}

	for {
		select {
		case dc := <-d.dataChannelChan:
			dataChannel = dc
		case msg := <-d.privateRecvMessagesChan:
			d.recvMessagesChan <- msg
		case msgFuture := <-d.sendMessagesChan:
			err := handleSendMessage(msgFuture.message)
			if err != nil {
				d.log.Error("Send error", errors.Trace(err), nil)

				msgFuture.errCh <- errors.Trace(err)
			}

			close(msgFuture.errCh)
		case <-d.teardownChan:
			return
		}
	}
}

func (d *DataTransceiver) MessagesChannel() <-chan webrtc.DataChannelMessage {
	return d.recvMessagesChan
}

func (d *DataTransceiver) Close() {
	d.log.Trace("DataTransceiver.Close", nil)

	select {
	case d.teardownChan <- struct{}{}:
	case <-d.torndownChan:
	}

	<-d.torndownChan
}

func (d *DataTransceiver) Send(message webrtc.DataChannelMessage) <-chan error {
	errCh := make(chan error, 1)

	select {
	case d.sendMessagesChan <- dataTransceiverMessageSend{
		errCh:   errCh,
		message: message,
	}:
	case <-d.torndownChan:
		errCh <- errors.Trace(io.ErrClosedPipe)
		close(errCh)
	}

	return errCh
}

type dataTransceiverMessageSend struct {
	// errCh will have error written to it if it occurrs. It will be closed once
	// the message sending has finished.
	errCh chan<- error
	// message to send.
	message webrtc.DataChannelMessage
}
<|endoftext|>


<commit_before>package handlers

import (
	"fmt"
	"github.com/APTrust/easy-store/db/models"
	"github.com/APTrust/easy-store/util/testutil"
	"github.com/APTrust/go-form-it"
	"github.com/APTrust/go-form-it/fields"
	"github.com/gorilla/schema"
	"github.com/jinzhu/gorm"
	_ "github.com/jinzhu/gorm/dialects/sqlite"
	_ "github.com/mattn/go-sqlite3"
	"html/template"
	"log"
	"net/http"
	"path/filepath"
	"strconv"
)

// The following vars are shared among all files in this package.
var templates *template.Template
var decoder = schema.NewDecoder()
var db *gorm.DB

func HandleRootRequest(w http.ResponseWriter, r *http.Request) {
	data := make(map[string]interface{})
	err := templates.ExecuteTemplate(w, "index", data)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
}

func CompileTemplates(pathToServerRoot string) {
	templateDir, _ := filepath.Abs(filepath.Join(pathToServerRoot, "templates", "*.html"))
	log.Println("Loading templates:", templateDir)
	templates = template.Must(template.ParseGlob(templateDir))
}

// TODO: This is also used by the easy_store_setup app.
// Put it on one place, and don't rely on testutil.GetPathToSchema()
// as that file and directory exist in dev mode only, and users
// won't have them.
func InitDBConnection() {
	schemaPath, err := testutil.GetPathToSchema()
	if err != nil {
		panic(err.Error())
	}
	dbFilePath := filepath.Join(filepath.Dir(schemaPath), "..", "..", "easy-store.db")
	// This sets the main global var db.
	db, err = gorm.Open("sqlite3", dbFilePath)
	if err != nil {
		panic(err.Error())
	}
	db.LogMode(true)
}

func GetOptions(modelName string) map[string][]fields.InputChoice {
	// BagItProfile, StorageService
	choices := make([]fields.InputChoice, 1)
	choices[0] = fields.InputChoice{Id: "", Val: ""}
	if modelName == "BagItProfile" {
		profiles := make([]models.BagItProfile, 0)
		db.Select("id, name").Find(&profiles).Order("name")
		for _, profile := range profiles {
			choices = append(choices, fields.InputChoice{
				Id:  strconv.FormatUint(uint64(profile.ID), 10),
				Val: profile.Name})
		}
	} else if modelName == "StorageService" {
		services := make([]models.StorageService, 0)
		db.Select("id, name").Find(&services).Order("name")
		for _, service := range services {
			choices = append(choices, fields.InputChoice{
				Id:  strconv.FormatUint(uint64(service.ID), 10),
				Val: service.Name})
		}
	} else if modelName == "Workflow" {
		workflows := make([]models.Workflow, 0)
		db.Select("id, name").Find(&workflows).Order("name")
		for _, workflow := range workflows {
			choices = append(choices, fields.InputChoice{
				Id:  strconv.FormatUint(uint64(workflow.ID), 10),
				Val: workflow.Name})
		}
	} else if modelName == "SerializationFormat" {
		choices = append(choices, fields.InputChoice{Id: "gzip", Val: "gzip"})
		choices = append(choices, fields.InputChoice{Id: "tar", Val: "tar"})
		choices = append(choices, fields.InputChoice{Id: "zip", Val: "zip"})
	} else if modelName == "Protocol" {
		choices = append(choices, fields.InputChoice{Id: "ftp", Val: "ftp"})
		choices = append(choices, fields.InputChoice{Id: "rsync", Val: "rsync"})
		choices = append(choices, fields.InputChoice{Id: "s3", Val: "s3"})
		choices = append(choices, fields.InputChoice{Id: "scp", Val: "scp"})
	}
	options := make(map[string][]fields.InputChoice)
	options[""] = choices
	return options
}

// AddTagValueFields adds tag value form fields for a BagItProfile to
// the given form. Set the last param, hideNonEmpty, to true if you want
// to hide the fields that have non-empty values. Do this on the job form,
// for example, where the user fills in only those tags that don't already
// have default values.
func AddTagValueFields(profile models.BagItProfile, form *forms.Form, hideNonEmpty bool) error {
	profileDef, err := profile.Profile()
	if err != nil {
		return err
	}
	for _, relFilePath := range profileDef.SortedTagFilesRequired() {
		hasVisibleFields := false
		fieldsInSet := make([]fields.FieldInterface, 0)
		mapOfRequiredTags := profileDef.TagFilesRequired[relFilePath]
		for _, tagname := range profileDef.SortedTagNames(relFilePath) {
			// This tag is a basic part of the BagIt spec and will
			// always be set by the system, not the user.
			if tagname == "Payload-Oxum" {
				continue
			}
			tagdef := mapOfRequiredTags[tagname]
			defaultTags := profile.GetDefaultTagValues(relFilePath, tagname)
			defaultValue := ""
			defaultTagId := uint(0)
			if len(defaultTags) > 0 {
				defaultValue = defaultTags[0].TagValue
				defaultTagId = defaultTags[0].ID
			}
			fieldName := fmt.Sprintf("%s|%s|%d", relFilePath, tagname, defaultTagId)
			fieldLabel := tagname

			formField := fields.TextField(fieldName)
			if len(tagdef.Values) > 0 {
				options := make(map[string][]fields.InputChoice)
				options[""] = make([]fields.InputChoice, len(tagdef.Values)+1)
				options[""][0] = fields.InputChoice{Id: "", Val: ""}
				for i, val := range tagdef.Values {
					options[""][i+1] = fields.InputChoice{Id: val, Val: val}
				}
				formField = fields.SelectField(fieldName, options)
			}
			formField.SetLabel(fieldLabel)
			formField.SetValue(defaultValue)
			if hideNonEmpty && defaultValue != "" {
				formField.AddClass("hidden")
				formField.AddClass("show-hide")
			} else {
				hasVisibleFields = true
			}
			fieldsInSet = append(fieldsInSet, formField)
		}
		legend := fmt.Sprintf("Default values for %s", relFilePath)
		fieldSet := forms.FieldSet(relFilePath, legend, fieldsInSet...)
		form.Elements(fieldSet)
		if !hasVisibleFields {
			fieldSet.AddClass("hidden")
			fieldSet.AddClass("show-hide")
		}
	}
	return nil
}
<commit_msg>Don't let user set bag size. System will do it.<commit_after>package handlers

import (
	"fmt"
	"github.com/APTrust/easy-store/db/models"
	"github.com/APTrust/easy-store/util/testutil"
	"github.com/APTrust/go-form-it"
	"github.com/APTrust/go-form-it/fields"
	"github.com/gorilla/schema"
	"github.com/jinzhu/gorm"
	_ "github.com/jinzhu/gorm/dialects/sqlite"
	_ "github.com/mattn/go-sqlite3"
	"html/template"
	"log"
	"net/http"
	"path/filepath"
	"strconv"
)

// The following vars are shared among all files in this package.
var templates *template.Template
var decoder = schema.NewDecoder()
var db *gorm.DB

func HandleRootRequest(w http.ResponseWriter, r *http.Request) {
	data := make(map[string]interface{})
	err := templates.ExecuteTemplate(w, "index", data)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
}

func CompileTemplates(pathToServerRoot string) {
	templateDir, _ := filepath.Abs(filepath.Join(pathToServerRoot, "templates", "*.html"))
	log.Println("Loading templates:", templateDir)
	templates = template.Must(template.ParseGlob(templateDir))
}

// TODO: This is also used by the easy_store_setup app.
// Put it on one place, and don't rely on testutil.GetPathToSchema()
// as that file and directory exist in dev mode only, and users
// won't have them.
func InitDBConnection() {
	schemaPath, err := testutil.GetPathToSchema()
	if err != nil {
		panic(err.Error())
	}
	dbFilePath := filepath.Join(filepath.Dir(schemaPath), "..", "..", "easy-store.db")
	// This sets the main global var db.
	db, err = gorm.Open("sqlite3", dbFilePath)
	if err != nil {
		panic(err.Error())
	}
	db.LogMode(true)
}

func GetOptions(modelName string) map[string][]fields.InputChoice {
	// BagItProfile, StorageService
	choices := make([]fields.InputChoice, 1)
	choices[0] = fields.InputChoice{Id: "", Val: ""}
	if modelName == "BagItProfile" {
		profiles := make([]models.BagItProfile, 0)
		db.Select("id, name").Find(&profiles).Order("name")
		for _, profile := range profiles {
			choices = append(choices, fields.InputChoice{
				Id:  strconv.FormatUint(uint64(profile.ID), 10),
				Val: profile.Name})
		}
	} else if modelName == "StorageService" {
		services := make([]models.StorageService, 0)
		db.Select("id, name").Find(&services).Order("name")
		for _, service := range services {
			choices = append(choices, fields.InputChoice{
				Id:  strconv.FormatUint(uint64(service.ID), 10),
				Val: service.Name})
		}
	} else if modelName == "Workflow" {
		workflows := make([]models.Workflow, 0)
		db.Select("id, name").Find(&workflows).Order("name")
		for _, workflow := range workflows {
			choices = append(choices, fields.InputChoice{
				Id:  strconv.FormatUint(uint64(workflow.ID), 10),
				Val: workflow.Name})
		}
	} else if modelName == "SerializationFormat" {
		choices = append(choices, fields.InputChoice{Id: "gzip", Val: "gzip"})
		choices = append(choices, fields.InputChoice{Id: "tar", Val: "tar"})
		choices = append(choices, fields.InputChoice{Id: "zip", Val: "zip"})
	} else if modelName == "Protocol" {
		choices = append(choices, fields.InputChoice{Id: "ftp", Val: "ftp"})
		choices = append(choices, fields.InputChoice{Id: "rsync", Val: "rsync"})
		choices = append(choices, fields.InputChoice{Id: "s3", Val: "s3"})
		choices = append(choices, fields.InputChoice{Id: "scp", Val: "scp"})
	}
	options := make(map[string][]fields.InputChoice)
	options[""] = choices
	return options
}

// AddTagValueFields adds tag value form fields for a BagItProfile to
// the given form. Set the last param, hideNonEmpty, to true if you want
// to hide the fields that have non-empty values. Do this on the job form,
// for example, where the user fills in only those tags that don't already
// have default values.
func AddTagValueFields(profile models.BagItProfile, form *forms.Form, hideNonEmpty bool) error {
	profileDef, err := profile.Profile()
	if err != nil {
		return err
	}
	for _, relFilePath := range profileDef.SortedTagFilesRequired() {
		hasVisibleFields := false
		fieldsInSet := make([]fields.FieldInterface, 0)
		mapOfRequiredTags := profileDef.TagFilesRequired[relFilePath]
		for _, tagname := range profileDef.SortedTagNames(relFilePath) {
			// Payload-Oxum is a basic part of the BagIt spec and will
			// always be set by the system, not the user. Bag-Size is
			// a DPN tag that should also be set by the system.
			if tagname == "Payload-Oxum" || tagname == "Bag-Size" {
				continue
			}
			tagdef := mapOfRequiredTags[tagname]
			defaultTags := profile.GetDefaultTagValues(relFilePath, tagname)
			defaultValue := ""
			defaultTagId := uint(0)
			if len(defaultTags) > 0 {
				defaultValue = defaultTags[0].TagValue
				defaultTagId = defaultTags[0].ID
			}
			fieldName := fmt.Sprintf("%s|%s|%d", relFilePath, tagname, defaultTagId)
			fieldLabel := tagname

			formField := fields.TextField(fieldName)
			if len(tagdef.Values) > 0 {
				options := make(map[string][]fields.InputChoice)
				options[""] = make([]fields.InputChoice, len(tagdef.Values)+1)
				options[""][0] = fields.InputChoice{Id: "", Val: ""}
				for i, val := range tagdef.Values {
					options[""][i+1] = fields.InputChoice{Id: val, Val: val}
				}
				formField = fields.SelectField(fieldName, options)
			}
			formField.SetLabel(fieldLabel)
			formField.SetValue(defaultValue)
			if hideNonEmpty && defaultValue != "" {
				formField.AddClass("hidden")
				formField.AddClass("show-hide")
			} else {
				hasVisibleFields = true
			}
			fieldsInSet = append(fieldsInSet, formField)
		}
		legend := fmt.Sprintf("Default values for %s", relFilePath)
		fieldSet := forms.FieldSet(relFilePath, legend, fieldsInSet...)
		form.Elements(fieldSet)
		if !hasVisibleFields {
			fieldSet.AddClass("hidden")
			fieldSet.AddClass("show-hide")
		}
	}
	return nil
}
<|endoftext|>


<commit_before>package snakes

import (
    "math/big"
)

func MontgomeryLadderExp(a, b, c *big.Int) *big.Int {
    zero := big.NewInt(0)
    if a.Cmp(zero) == 0 && b.Cmp(zero) == 0 {
        return big.NewInt(1)
    }
    a1 := a
    a2 := new(big.Int).Mul(a, a)
    for pos := b.BitLen() - 1; pos >= 0; pos -- {
        if b.Bit(pos) == 0 {
            a2 = new(big.Int).Mul(a2, a1)
            a1 = new(big.Int).Mul(a1, a1)
        } else {
            a1 = new(big.Int).Mul(a1, a2)
            a2 = new(big.Int).Mul(a2, a2)
        }

        a1 = new(big.Int).Mod(a1, c)
        a2 = new(big.Int).Mod(a2, c)
    }
    return a1
}
<commit_msg>Document<commit_after>package snakes

import (
    "math/big"
)

// Compute a ** b % c, just like big.Int.Exp, but do it in a way that is more
// constant time. This is useful for crypto stuff.
func MontgomeryLadderExp(a, b, c *big.Int) *big.Int {
    zero := big.NewInt(0)
    if a.Cmp(zero) == 0 && b.Cmp(zero) == 0 {
        return big.NewInt(1)
    }
    a1 := a
    a2 := new(big.Int).Mul(a, a)
    for pos := b.BitLen() - 1; pos >= 0; pos -- {
        if b.Bit(pos) == 0 {
            a2 = new(big.Int).Mul(a2, a1)
            a1 = new(big.Int).Mul(a1, a1)
        } else {
            a1 = new(big.Int).Mul(a1, a2)
            a2 = new(big.Int).Mul(a2, a2)
        }

        a1 = new(big.Int).Mod(a1, c)
        a2 = new(big.Int).Mod(a2, c)
    }
    return a1
}
<|endoftext|>


<commit_before>package server

import (
	"context"
	"fmt"
	"net/http"
	"net/http/httptest"
	"os"
	"testing"
	"time"

	qt "github.com/frankban/quicktest"
	"github.com/google/go-cmp/cmp"
	"github.com/google/uuid"
	"github.com/rs/zerolog"
	"golang.org/x/oauth2"

	"github.com/gilcrest/go-api-basic/domain/app"
	"github.com/gilcrest/go-api-basic/domain/auth"
	"github.com/gilcrest/go-api-basic/domain/errs"
	"github.com/gilcrest/go-api-basic/domain/logger"
	"github.com/gilcrest/go-api-basic/domain/org"
)

type mockFindAppService struct{}

func (m mockFindAppService) FindAppByAPIKey(ctx context.Context, realm string, appExtlID string, apiKey string) (app.App, error) {
	return app.App{
		ID:           uuid.UUID{},
		ExternalID:   []byte("so random"),
		Org:          org.Org{},
		Name:         "",
		Description:  "",
		CreateAppID:  uuid.UUID{},
		CreateUserID: uuid.UUID{},
		CreateTime:   time.Time{},
		UpdateAppID:  uuid.UUID{},
		UpdateUserID: uuid.UUID{},
		UpdateTime:   time.Time{},
		APIKeys:      nil,
	}, nil
}

func TestJSONContentTypeResponseHandler(t *testing.T) {

	s := Server{}

	req, err := http.NewRequest("GET", "/ping", nil)
	if err != nil {
		t.Fatalf("http.NewRequest() error = %v", err)
	}

	testJSONContentTypeResponseHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		contentType := w.Header().Get("Content-Type")
		if contentType != "application/json" {
			t.Fatalf("Content-Type %s is invalid", contentType)
		}
	})

	rr := httptest.NewRecorder()

	handlers := s.jsonContentTypeResponseHandler(testJSONContentTypeResponseHandler)
	handlers.ServeHTTP(rr, req)
}

// TODO - add typical - with database test to actually query db. Requires quite a bit of data setup, but is appropriate and will get to this.
func TestServer_appHandler(t *testing.T) {
	t.Run("typical - mock database", func(t *testing.T) {
		c := qt.New(t)

		req, err := http.NewRequest("GET", "/ping", nil)
		if err != nil {
			t.Fatalf("http.NewRequest() error = %v", err)
		}
		req.Header.Add(appIDHeaderKey, "test_app_extl_id")
		req.Header.Add(apiKeyHeaderKey, "test_app_api_key")

		testAppHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			a, err := app.FromRequest(r)
			if err != nil {
				t.Fatal("app.FromRequest() error", err)
			}
			wantApp := app.App{
				ID:           uuid.UUID{},
				ExternalID:   []byte("so random"),
				Org:          org.Org{},
				Name:         "",
				Description:  "",
				CreateAppID:  uuid.UUID{},
				CreateUserID: uuid.UUID{},
				CreateTime:   time.Time{},
				UpdateAppID:  uuid.UUID{},
				UpdateUserID: uuid.UUID{},
				UpdateTime:   time.Time{},
				APIKeys:      nil,
			}
			c.Assert(a, qt.DeepEquals, wantApp)
		})

		rr := httptest.NewRecorder()

		lgr := logger.NewLogger(os.Stdout, zerolog.DebugLevel, true)

		s := New(NewMuxRouter(), NewDriver(), lgr)
		s.FindAppService = mockFindAppService{}

		handlers := s.appHandler(testAppHandler)
		handlers.ServeHTTP(rr, req)

		// If there is any issues with the Access Token, the body
		// should be empty and the status code should be 401
		c.Assert(rr.Code, qt.Equals, http.StatusOK)
	})
}

func TestXHeader(t *testing.T) {
	t.Run("x-app-id", func(t *testing.T) {
		c := qt.New(t)
		hdr := http.Header{}
		hdr.Add(appIDHeaderKey, "appologies")

		appID, err := xHeader(defaultRealm, hdr, appIDHeaderKey)
		c.Assert(err, qt.IsNil)
		c.Assert(appID, qt.Equals, "appologies")
	})
	t.Run("no header error", func(t *testing.T) {
		c := qt.New(t)
		hdr := http.Header{}

		_, err := xHeader(defaultRealm, hdr, appIDHeaderKey)
		c.Assert(err, qt.CmpEquals(cmp.Comparer(errs.Match)), errs.E(errs.Unauthenticated, errs.Realm(defaultRealm), fmt.Sprintf("unauthenticated: no %s header sent", appIDHeaderKey)))
	})
	t.Run("too many values error", func(t *testing.T) {
		c := qt.New(t)
		hdr := http.Header{}
		hdr.Add(appIDHeaderKey, "value1")
		hdr.Add(appIDHeaderKey, "value2")

		_, err := xHeader(defaultRealm, hdr, appIDHeaderKey)
		c.Assert(err, qt.CmpEquals(cmp.Comparer(errs.Match)), errs.E(errs.Unauthenticated, errs.Realm(defaultRealm), fmt.Sprintf("%s header value > 1", appIDHeaderKey)))
	})
	t.Run("empty value error", func(t *testing.T) {
		c := qt.New(t)
		hdr := http.Header{}
		hdr.Add(appIDHeaderKey, "")

		_, err := xHeader(defaultRealm, hdr, appIDHeaderKey)
		c.Assert(err, qt.CmpEquals(cmp.Comparer(errs.Match)), errs.E(errs.Unauthenticated, errs.Realm(defaultRealm), fmt.Sprintf("unauthenticated: %s header value not found", appIDHeaderKey)))
	})
}

func Test_authHeader(t *testing.T) {
	c := qt.New(t)

	const reqHeader string = "Authorization"

	type args struct {
		realm  string
		header http.Header
	}

	hdr := http.Header{}
	hdr.Add(reqHeader, "Bearer foobarbbq")

	emptyHdr := http.Header{}
	emptyHdrErr := errs.E(errs.Unauthenticated, errs.Realm(defaultRealm), "unauthenticated: no Authorization header sent")

	tooManyValues := http.Header{}
	tooManyValues.Add(reqHeader, "value1")
	tooManyValues.Add(reqHeader, "value2")
	tooManyValuesErr := errs.E(errs.Unauthenticated, errs.Realm(defaultRealm), "header value > 1")

	noBearer := http.Header{}
	noBearer.Add(reqHeader, "xyz")
	noBearerErr := errs.E(errs.Unauthenticated, errs.Realm(defaultRealm), "unauthenticated: Bearer authentication scheme not found")

	hdrSpacesBearer := http.Header{}
	hdrSpacesBearer.Add("Authorization", "Bearer  ")
	spacesHdrErr := errs.E(errs.Unauthenticated, errs.Realm(defaultRealm), "unauthenticated: Authorization header sent with Bearer scheme, but no token found")

	tests := []struct {
		name      string
		args      args
		wantToken oauth2.Token
		wantErr   error
	}{
		{"typical", args{realm: defaultRealm, header: hdr}, oauth2.Token{AccessToken: "foobarbbq", TokenType: auth.BearerTokenType}, nil},
		{"no authorization header error", args{realm: defaultRealm, header: emptyHdr}, oauth2.Token{}, emptyHdrErr},
		{"too many values error", args{realm: defaultRealm, header: tooManyValues}, oauth2.Token{}, tooManyValuesErr},
		{"no bearer scheme error", args{realm: defaultRealm, header: noBearer}, oauth2.Token{}, noBearerErr},
		{"spaces as token error", args{realm: defaultRealm, header: hdrSpacesBearer}, oauth2.Token{}, spacesHdrErr},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			gotToken, err := authHeader(tt.args.realm, tt.args.header)
			if (err != nil) && (tt.wantErr == nil) {
				t.Errorf("authHeader() error = %v, nil expected", err)
				return
			}
			c.Assert(err, qt.CmpEquals(cmp.Comparer(errs.Match)), tt.wantErr)
			c.Assert(gotToken, qt.Equals, tt.wantToken)
		})
	}
}
<commit_msg>updated tests<commit_after>package server

import (
	"context"
	"fmt"
	"net/http"
	"net/http/httptest"
	"os"
	"testing"

	qt "github.com/frankban/quicktest"
	"github.com/google/go-cmp/cmp"
	"github.com/google/uuid"
	"github.com/rs/zerolog"
	"golang.org/x/oauth2"

	"github.com/gilcrest/go-api-basic/domain/app"
	"github.com/gilcrest/go-api-basic/domain/auth"
	"github.com/gilcrest/go-api-basic/domain/errs"
	"github.com/gilcrest/go-api-basic/domain/logger"
	"github.com/gilcrest/go-api-basic/domain/org"
)

type mockFindAppService struct{}

func (m mockFindAppService) FindAppByAPIKey(ctx context.Context, realm string, appExtlID string, apiKey string) (app.App, error) {
	return app.App{
		ID:          uuid.UUID{},
		ExternalID:  []byte("so random"),
		Org:         org.Org{},
		Name:        "",
		Description: "",
		APIKeys:     nil,
	}, nil
}

func TestJSONContentTypeResponseHandler(t *testing.T) {

	s := Server{}

	req, err := http.NewRequest("GET", "/ping", nil)
	if err != nil {
		t.Fatalf("http.NewRequest() error = %v", err)
	}

	testJSONContentTypeResponseHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		contentType := w.Header().Get("Content-Type")
		if contentType != "application/json" {
			t.Fatalf("Content-Type %s is invalid", contentType)
		}
	})

	rr := httptest.NewRecorder()

	handlers := s.jsonContentTypeResponseHandler(testJSONContentTypeResponseHandler)
	handlers.ServeHTTP(rr, req)
}

// TODO - add typical - with database test to actually query db. Requires quite a bit of data setup, but is appropriate and will get to this.
func TestServer_appHandler(t *testing.T) {
	t.Run("typical - mock database", func(t *testing.T) {
		c := qt.New(t)

		req, err := http.NewRequest("GET", "/ping", nil)
		if err != nil {
			t.Fatalf("http.NewRequest() error = %v", err)
		}
		req.Header.Add(appIDHeaderKey, "test_app_extl_id")
		req.Header.Add(apiKeyHeaderKey, "test_app_api_key")

		testAppHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			a, err := app.FromRequest(r)
			if err != nil {
				t.Fatal("app.FromRequest() error", err)
			}
			wantApp := app.App{
				ID:          uuid.UUID{},
				ExternalID:  []byte("so random"),
				Org:         org.Org{},
				Name:        "",
				Description: "",
				APIKeys:     nil,
			}
			c.Assert(a, qt.DeepEquals, wantApp)
		})

		rr := httptest.NewRecorder()

		lgr := logger.NewLogger(os.Stdout, zerolog.DebugLevel, true)

		s := New(NewMuxRouter(), NewDriver(), lgr)
		s.FindAppService = mockFindAppService{}

		handlers := s.appHandler(testAppHandler)
		handlers.ServeHTTP(rr, req)

		// If there is any issues with the Access Token, the body
		// should be empty and the status code should be 401
		c.Assert(rr.Code, qt.Equals, http.StatusOK)
	})
}

func TestXHeader(t *testing.T) {
	t.Run("x-app-id", func(t *testing.T) {
		c := qt.New(t)
		hdr := http.Header{}
		hdr.Add(appIDHeaderKey, "appologies")

		appID, err := xHeader(defaultRealm, hdr, appIDHeaderKey)
		c.Assert(err, qt.IsNil)
		c.Assert(appID, qt.Equals, "appologies")
	})
	t.Run("no header error", func(t *testing.T) {
		c := qt.New(t)
		hdr := http.Header{}

		_, err := xHeader(defaultRealm, hdr, appIDHeaderKey)
		c.Assert(err, qt.CmpEquals(cmp.Comparer(errs.Match)), errs.E(errs.Unauthenticated, errs.Realm(defaultRealm), fmt.Sprintf("unauthenticated: no %s header sent", appIDHeaderKey)))
	})
	t.Run("too many values error", func(t *testing.T) {
		c := qt.New(t)
		hdr := http.Header{}
		hdr.Add(appIDHeaderKey, "value1")
		hdr.Add(appIDHeaderKey, "value2")

		_, err := xHeader(defaultRealm, hdr, appIDHeaderKey)
		c.Assert(err, qt.CmpEquals(cmp.Comparer(errs.Match)), errs.E(errs.Unauthenticated, errs.Realm(defaultRealm), fmt.Sprintf("%s header value > 1", appIDHeaderKey)))
	})
	t.Run("empty value error", func(t *testing.T) {
		c := qt.New(t)
		hdr := http.Header{}
		hdr.Add(appIDHeaderKey, "")

		_, err := xHeader(defaultRealm, hdr, appIDHeaderKey)
		c.Assert(err, qt.CmpEquals(cmp.Comparer(errs.Match)), errs.E(errs.Unauthenticated, errs.Realm(defaultRealm), fmt.Sprintf("unauthenticated: %s header value not found", appIDHeaderKey)))
	})
}

func Test_authHeader(t *testing.T) {
	c := qt.New(t)

	const reqHeader string = "Authorization"

	type args struct {
		realm  string
		header http.Header
	}

	hdr := http.Header{}
	hdr.Add(reqHeader, "Bearer foobarbbq")

	emptyHdr := http.Header{}
	emptyHdrErr := errs.E(errs.Unauthenticated, errs.Realm(defaultRealm), "unauthenticated: no Authorization header sent")

	tooManyValues := http.Header{}
	tooManyValues.Add(reqHeader, "value1")
	tooManyValues.Add(reqHeader, "value2")
	tooManyValuesErr := errs.E(errs.Unauthenticated, errs.Realm(defaultRealm), "header value > 1")

	noBearer := http.Header{}
	noBearer.Add(reqHeader, "xyz")
	noBearerErr := errs.E(errs.Unauthenticated, errs.Realm(defaultRealm), "unauthenticated: Bearer authentication scheme not found")

	hdrSpacesBearer := http.Header{}
	hdrSpacesBearer.Add("Authorization", "Bearer  ")
	spacesHdrErr := errs.E(errs.Unauthenticated, errs.Realm(defaultRealm), "unauthenticated: Authorization header sent with Bearer scheme, but no token found")

	tests := []struct {
		name      string
		args      args
		wantToken oauth2.Token
		wantErr   error
	}{
		{"typical", args{realm: defaultRealm, header: hdr}, oauth2.Token{AccessToken: "foobarbbq", TokenType: auth.BearerTokenType}, nil},
		{"no authorization header error", args{realm: defaultRealm, header: emptyHdr}, oauth2.Token{}, emptyHdrErr},
		{"too many values error", args{realm: defaultRealm, header: tooManyValues}, oauth2.Token{}, tooManyValuesErr},
		{"no bearer scheme error", args{realm: defaultRealm, header: noBearer}, oauth2.Token{}, noBearerErr},
		{"spaces as token error", args{realm: defaultRealm, header: hdrSpacesBearer}, oauth2.Token{}, spacesHdrErr},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			gotToken, err := authHeader(tt.args.realm, tt.args.header)
			if (err != nil) && (tt.wantErr == nil) {
				t.Errorf("authHeader() error = %v, nil expected", err)
				return
			}
			c.Assert(err, qt.CmpEquals(cmp.Comparer(errs.Match)), tt.wantErr)
			c.Assert(gotToken, qt.Equals, tt.wantToken)
		})
	}
}
<|endoftext|>