<commit_before>/*
Copyright (c) 2014 VMware, Inc. All Rights Reserved.

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

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

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

package ipmi

type ChassisControl uint8
type BootDevice uint8

const (
	ControlPowerDown      = ChassisControl(0x0)
	ControlPowerUp        = ChassisControl(0x1)
	ControlPowerCycle     = ChassisControl(0x2)
	ControlPowerHardReset = ChassisControl(0x3)
	ControlPowerPulseDiag = ChassisControl(0x4)
	ControlPowerAcpiSoft  = ChassisControl(0x5)

	BootDeviceNone          = BootDevice(0x00)
	BootDevicePxe           = BootDevice(0x04)
	BootDeviceDisk          = BootDevice(0x08)
	BootDeviceSafe          = BootDevice(0x0c)
	BootDeviceDiag          = BootDevice(0x10)
	BootDeviceCdrom         = BootDevice(0x14)
	BootDeviceBios          = BootDevice(0x18)
	BootDeviceRemoteFloppy  = BootDevice(0x1c)
	BootDeviceRemotePrimary = BootDevice(0x24)
	BootDeviceRemoteCdrom   = BootDevice(0x20)
	BootDeviceRemoteDisk    = BootDevice(0x2c)
	BootDeviceFloppy        = BootDevice(0x3c)

	SystemPower       = 0x1
	PowerOverload     = 0x2
	PowerInterlock    = 0x4
	MainPowerFault    = 0x8
	PowerControlFault = 0x10

	PowerRestorePolicyAlwaysOff = 0x0
	PowerRestorePolicyPrevious  = 0x1
	PowerRestorePolicyAlwaysOn  = 0x2
	PowerRestorePolicyUnknown   = 0x3

	PowerEventUnknown   = 0x0
	PowerEventAcFailed  = 0x1
	PowerEventOverload  = 0x2
	PowerEventInterlock = 0x4
	PowerEventFault     = 0x8
	PowerEventCommand   = 0x10

	ChassisIntrusion  = 0x1
	FrontPanelLockout = 0x2
	DriveFault        = 0x4
	CoolingFanFault   = 0x8

	SleepButtonDisable  = 0x80
	DiagButtonDisable   = 0x40
	ResetButtonDisable  = 0x20
	PowerButtonDisable  = 0x10
	SleepButtonDisabled = 0x08
	DiagButtonDisabled  = 0x04
	ResetButtonDisabled = 0x02
	PowerButtonDisabled = 0x01

	BootParamSetInProgress = 0x0
	BootParamSvcPartSelect = 0x1
	BootParamSvcPartScan   = 0x2
	BootParamFlagValid     = 0x3
	BootParamInfoAck       = 0x4
	BootParamBootFlags     = 0x5
	BootParamInitInfo      = 0x6
	BootParamInitMbox      = 0x7
)

// ChassisStatusRequest per section 28.2
type ChassisStatusRequest struct{}

// ChassisStatusResponse per section 28.2
type ChassisStatusResponse struct {
	CompletionCode
	PowerState        uint8
	LastPowerEvent    uint8
	State             uint8
	FrontControlPanel uint8
}

// ChassisControlRequest per section 28.3
type ChassisControlRequest struct {
	ChassisControl
}

// ChassisControlResponse per section 28.3
type ChassisControlResponse struct {
	CompletionCode
}

// SetSystemBootOptionsRequest per section 28.12
type SetSystemBootOptionsRequest struct {
	Param uint8
	Data  []uint8
}

// SetSystemBootOptionsResponse per section 28.12
type SetSystemBootOptionsResponse struct {
	CompletionCode
}

// SystemBootOptionsRequest per section 28.13
type SystemBootOptionsRequest struct {
	Param uint8
	Set   uint8
	Block uint8
}

// SystemBootOptionsResponse per section 28.13
type SystemBootOptionsResponse struct {
	CompletionCode
	Version uint8
	Param   uint8
	Data    []uint8
}

// MarshalBinary implementation to handle variable length Data
func (r *SetSystemBootOptionsRequest) MarshalBinary() ([]byte, error) {
	buf := make([]byte, 1+len(r.Data))
	buf[0] = r.Param
	copy(buf[1:], r.Data)
	return buf, nil
}

var validSetBootOptionsDataLength = map[uint8]int{
	BootParamInfoAck:   2,
	BootParamBootFlags: 5,
}

// UnmarshalBinary implementation to handle variable length Data
func (r *SetSystemBootOptionsRequest) UnmarshalBinary(buf []byte) error {
	if len(buf) < 2 {
		return ErrShortPacket
	}
	r.Param = buf[0]
	r.Data = buf[1:]
	if l, ok := validSetBootOptionsDataLength[r.Param]; ok {
		if len(r.Data) < l {
			return ErrShortPacket
		}
	}
	return nil
}

// MarshalBinary implementation to handle variable length Data
func (r *SystemBootOptionsResponse) MarshalBinary() ([]byte, error) {
	buf := make([]byte, 3+len(r.Data))
	buf[0] = byte(r.CompletionCode)
	buf[1] = r.Version
	buf[2] = r.Param
	copy(buf[3:], r.Data)
	return buf, nil
}

// UnmarshalBinary implementation to handle variable length Data
func (r *SystemBootOptionsResponse) UnmarshalBinary(buf []byte) error {
	if len(buf) < 3 {
		return ErrShortPacket
	}
	r.CompletionCode = CompletionCode(buf[0])
	r.Version = buf[1]
	r.Param = buf[2]
	r.Data = buf[3:]
	return nil
}

func (r *SystemBootOptionsResponse) BootDeviceSelector() BootDevice {
	return BootDevice(((r.Data[1] >> 2) & 0x0f) << 2)
}

func (s *ChassisStatusResponse) IsSystemPowerOn() bool {
	return (s.PowerState & SystemPower) == SystemPower
}

func (s *ChassisStatusResponse) String() string {
	if s.IsSystemPowerOn() {
		return "on"
	}
	return "off"
}

func (s *ChassisStatusResponse) PowerRestorePolicy() uint8 {
	return (s.PowerState & 0x60) >> 5
}

var bootDeviceStrings = map[BootDevice]string{
	BootDeviceNone:          "none",
	BootDevicePxe:           "pxe",
	BootDeviceDisk:          "disk",
	BootDeviceSafe:          "safe",
	BootDeviceDiag:          "diag",
	BootDeviceCdrom:         "cdrom",
	BootDeviceBios:          "bios",
	BootDeviceRemoteFloppy:  "rfloppy",
	BootDeviceRemotePrimary: "rprimary",
	BootDeviceRemoteCdrom:   "rcdrom",
	BootDeviceRemoteDisk:    "rdisk",
	BootDeviceFloppy:        "floppy",
}

func (d BootDevice) String() string {
	if s, ok := bootDeviceStrings[d]; ok {
		return s
	}
	return "unknown"
}

func (c ChassisControl) String() string {
	switch c {
	case ControlPowerDown:
		return "down"
	case ControlPowerUp:
		return "up"
	case ControlPowerCycle:
		return "cycle"
	case ControlPowerHardReset:
		return "reset"
	case ControlPowerPulseDiag:
		return "diag"
	case ControlPowerAcpiSoft:
		return "acpi"
	}
	panic("unknown control")
}
<commit_msg>Handle optional byte in chassis status response.<commit_after>/*
Copyright (c) 2014 VMware, Inc. All Rights Reserved.

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

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

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

package ipmi

type ChassisControl uint8
type BootDevice uint8

const (
	ControlPowerDown      = ChassisControl(0x0)
	ControlPowerUp        = ChassisControl(0x1)
	ControlPowerCycle     = ChassisControl(0x2)
	ControlPowerHardReset = ChassisControl(0x3)
	ControlPowerPulseDiag = ChassisControl(0x4)
	ControlPowerAcpiSoft  = ChassisControl(0x5)

	BootDeviceNone          = BootDevice(0x00)
	BootDevicePxe           = BootDevice(0x04)
	BootDeviceDisk          = BootDevice(0x08)
	BootDeviceSafe          = BootDevice(0x0c)
	BootDeviceDiag          = BootDevice(0x10)
	BootDeviceCdrom         = BootDevice(0x14)
	BootDeviceBios          = BootDevice(0x18)
	BootDeviceRemoteFloppy  = BootDevice(0x1c)
	BootDeviceRemotePrimary = BootDevice(0x24)
	BootDeviceRemoteCdrom   = BootDevice(0x20)
	BootDeviceRemoteDisk    = BootDevice(0x2c)
	BootDeviceFloppy        = BootDevice(0x3c)

	SystemPower       = 0x1
	PowerOverload     = 0x2
	PowerInterlock    = 0x4
	MainPowerFault    = 0x8
	PowerControlFault = 0x10

	PowerRestorePolicyAlwaysOff = 0x0
	PowerRestorePolicyPrevious  = 0x1
	PowerRestorePolicyAlwaysOn  = 0x2
	PowerRestorePolicyUnknown   = 0x3

	PowerEventUnknown   = 0x0
	PowerEventAcFailed  = 0x1
	PowerEventOverload  = 0x2
	PowerEventInterlock = 0x4
	PowerEventFault     = 0x8
	PowerEventCommand   = 0x10

	ChassisIntrusion  = 0x1
	FrontPanelLockout = 0x2
	DriveFault        = 0x4
	CoolingFanFault   = 0x8

	SleepButtonDisable  = 0x80
	DiagButtonDisable   = 0x40
	ResetButtonDisable  = 0x20
	PowerButtonDisable  = 0x10
	SleepButtonDisabled = 0x08
	DiagButtonDisabled  = 0x04
	ResetButtonDisabled = 0x02
	PowerButtonDisabled = 0x01

	BootParamSetInProgress = 0x0
	BootParamSvcPartSelect = 0x1
	BootParamSvcPartScan   = 0x2
	BootParamFlagValid     = 0x3
	BootParamInfoAck       = 0x4
	BootParamBootFlags     = 0x5
	BootParamInitInfo      = 0x6
	BootParamInitMbox      = 0x7
)

// ChassisStatusRequest per section 28.2
type ChassisStatusRequest struct{}

// ChassisStatusResponse per section 28.2
type ChassisStatusResponse struct {
	CompletionCode
	PowerState        uint8
	LastPowerEvent    uint8
	State             uint8
	FrontControlPanel uint8
}

// ChassisControlRequest per section 28.3
type ChassisControlRequest struct {
	ChassisControl
}

// ChassisControlResponse per section 28.3
type ChassisControlResponse struct {
	CompletionCode
}

// SetSystemBootOptionsRequest per section 28.12
type SetSystemBootOptionsRequest struct {
	Param uint8
	Data  []uint8
}

// SetSystemBootOptionsResponse per section 28.12
type SetSystemBootOptionsResponse struct {
	CompletionCode
}

// SystemBootOptionsRequest per section 28.13
type SystemBootOptionsRequest struct {
	Param uint8
	Set   uint8
	Block uint8
}

// SystemBootOptionsResponse per section 28.13
type SystemBootOptionsResponse struct {
	CompletionCode
	Version uint8
	Param   uint8
	Data    []uint8
}

// MarshalBinary implementation to handle variable length Data
func (r *SetSystemBootOptionsRequest) MarshalBinary() ([]byte, error) {
	buf := make([]byte, 1+len(r.Data))
	buf[0] = r.Param
	copy(buf[1:], r.Data)
	return buf, nil
}

var validSetBootOptionsDataLength = map[uint8]int{
	BootParamInfoAck:   2,
	BootParamBootFlags: 5,
}

// UnmarshalBinary implementation to handle variable length Data
func (r *SetSystemBootOptionsRequest) UnmarshalBinary(buf []byte) error {
	if len(buf) < 2 {
		return ErrShortPacket
	}
	r.Param = buf[0]
	r.Data = buf[1:]
	if l, ok := validSetBootOptionsDataLength[r.Param]; ok {
		if len(r.Data) < l {
			return ErrShortPacket
		}
	}
	return nil
}

// MarshalBinary implementation to handle variable length Data
func (r *SystemBootOptionsResponse) MarshalBinary() ([]byte, error) {
	buf := make([]byte, 3+len(r.Data))
	buf[0] = byte(r.CompletionCode)
	buf[1] = r.Version
	buf[2] = r.Param
	copy(buf[3:], r.Data)
	return buf, nil
}

// UnmarshalBinary implementation to handle variable length Data
func (r *SystemBootOptionsResponse) UnmarshalBinary(buf []byte) error {
	if len(buf) < 3 {
		return ErrShortPacket
	}
	r.CompletionCode = CompletionCode(buf[0])
	r.Version = buf[1]
	r.Param = buf[2]
	r.Data = buf[3:]
	return nil
}

// UnmarshalBinary implementation to handle variable length Data
func (r *ChassisStatusResponse) UnmarshalBinary(buf []byte) error {
	if len(buf) < 4 {
		return ErrShortPacket
	}
	r.CompletionCode = CompletionCode(buf[0])
	r.PowerState = buf[1]
	r.LastPowerEvent =  buf[2]
	r.State = buf[3]
	if len(buf) > 4 {
		r.FrontControlPanel = buf[4]
	} else {
		r.FrontControlPanel = 0
	}
	return nil
}

func (r *SystemBootOptionsResponse) BootDeviceSelector() BootDevice {
	return BootDevice(((r.Data[1] >> 2) & 0x0f) << 2)
}

func (s *ChassisStatusResponse) IsSystemPowerOn() bool {
	return (s.PowerState & SystemPower) == SystemPower
}

func (s *ChassisStatusResponse) String() string {
	if s.IsSystemPowerOn() {
		return "on"
	}
	return "off"
}

func (s *ChassisStatusResponse) PowerRestorePolicy() uint8 {
	return (s.PowerState & 0x60) >> 5
}

var bootDeviceStrings = map[BootDevice]string{
	BootDeviceNone:          "none",
	BootDevicePxe:           "pxe",
	BootDeviceDisk:          "disk",
	BootDeviceSafe:          "safe",
	BootDeviceDiag:          "diag",
	BootDeviceCdrom:         "cdrom",
	BootDeviceBios:          "bios",
	BootDeviceRemoteFloppy:  "rfloppy",
	BootDeviceRemotePrimary: "rprimary",
	BootDeviceRemoteCdrom:   "rcdrom",
	BootDeviceRemoteDisk:    "rdisk",
	BootDeviceFloppy:        "floppy",
}

func (d BootDevice) String() string {
	if s, ok := bootDeviceStrings[d]; ok {
		return s
	}
	return "unknown"
}

func (c ChassisControl) String() string {
	switch c {
	case ControlPowerDown:
		return "down"
	case ControlPowerUp:
		return "up"
	case ControlPowerCycle:
		return "cycle"
	case ControlPowerHardReset:
		return "reset"
	case ControlPowerPulseDiag:
		return "diag"
	case ControlPowerAcpiSoft:
		return "acpi"
	}
	panic("unknown control")
}
<|endoftext|>


<commit_before>package HumorChecker // "cirello.io/HumorChecker"

import (
	"bufio"
	"regexp"
	"strings"
)

type sign int

const (
	positive sign = +1
	negative sign = -1
)

//Score is the result of sentiment calculation
type Score struct {
	// Score is the sum of the sentiment points of the analyzed text.
	// Negativity will render negative points only, and vice-versa.
	Score float64

	// Comparative establishes a ratio of sentiment per word
	Comparative float64

	// List of words for a given sentiment.
	Words []string
}

//FullScore is the difference between positive and negative sentiment
type FullScore struct {
	// Score is the difference between positive and negative sentiment
	// scores.
	Score float64

	// Comparative is the difference between positive and negative sentiment
	// comparative scores.
	Comparative float64

	// Positive score object
	Positive Score

	// Negative score object
	Negative Score
}

var lettersAndSpaceOnly = regexp.MustCompile(`[^a-zA-Z ]+`)

func scanPhrase(phrase string, c chan string) {
	var lettersAndSpaceOnly = regexp.MustCompile(`[^a-zA-Z ]+`)
	scanner := bufio.NewScanner(strings.NewReader(strings.ToLower(lettersAndSpaceOnly.ReplaceAllString(phrase, " "))))
	scanner.Split(bufio.ScanWords)
	for scanner.Scan() {
		word := scanner.Text()
		c <- word
	}
	close(c)
}

func calculateScore(phrase string, calcSign sign) Score {
	var hits float64
	var words []string
	var count int

	c := make(chan string)
	go scanPhrase(phrase, c)

	for word := range c {
		count++
		if v, ok := afinn[word]; ok {
			if (calcSign == positive && v > 0) || (calcSign == negative && v < 0) {
				hits += v * float64(calcSign)
				words = append(words, word)
			}
		}
	}

	return Score{
		Score:       hits,
		Comparative: hits / float64(count),
		Words:       words,
	}
}

// Negativity calculates the negative sentiment of a sentence
func Negativity(phrase string) Score {
	return calculateScore(phrase, negative)
}

// Positivity calculates the positive sentiment of a sentence
func Positivity(phrase string) Score {
	return calculateScore(phrase, positive)
}

// Analyze calculates overall sentiment
func Analyze(phrase string) FullScore {
	pos := Positivity(phrase)
	neg := Negativity(phrase)

	return FullScore{
		Score:       pos.Score - neg.Score,
		Comparative: pos.Comparative - neg.Comparative,
		Positive:    pos,
		Negative:    neg,
	}
}
<commit_msg>Revert back to no-channel<commit_after>package HumorChecker // "cirello.io/HumorChecker"

import (
	"bufio"
	"regexp"
	"strings"
)

type sign int

const (
	positive sign = +1
	negative sign = -1
)

//Score is the result of sentiment calculation
type Score struct {
	// Score is the sum of the sentiment points of the analyzed text.
	// Negativity will render negative points only, and vice-versa.
	Score float64

	// Comparative establishes a ratio of sentiment per word
	Comparative float64

	// List of words for a given sentiment.
	Words []string
}

//FullScore is the difference between positive and negative sentiment
type FullScore struct {
	// Score is the difference between positive and negative sentiment
	// scores.
	Score float64

	// Comparative is the difference between positive and negative sentiment
	// comparative scores.
	Comparative float64

	// Positive score object
	Positive Score

	// Negative score object
	Negative Score
}

var lettersAndSpaceOnly = regexp.MustCompile(`[^a-zA-Z ]+`)

func calculateScore(phrase string, calcSign sign) Score {
	var hits float64
	var words []string
	var count int

	var lettersAndSpaceOnly = regexp.MustCompile(`[^a-zA-Z ]+`)
	scanner := bufio.NewScanner(strings.NewReader(strings.ToLower(lettersAndSpaceOnly.ReplaceAllString(phrase, " "))))
	scanner.Split(bufio.ScanWords)
	for scanner.Scan() {
		count++
		word := scanner.Text()
		if v, ok := afinn[word]; ok {
			if (calcSign == positive && v > 0) || (calcSign == negative && v < 0) {
				hits += v * float64(calcSign)
				words = append(words, word)
			}
		}
	}

	return Score{
		Score:       hits,
		Comparative: hits / float64(count),
		Words:       words,
	}
}

// Negativity calculates the negative sentiment of a sentence
func Negativity(phrase string) Score {
	return calculateScore(phrase, negative)
}

// Positivity calculates the positive sentiment of a sentence
func Positivity(phrase string) Score {
	return calculateScore(phrase, positive)
}

// Analyze calculates overall sentiment
func Analyze(phrase string) FullScore {
	pos := Positivity(phrase)
	neg := Negativity(phrase)

	return FullScore{
		Score:       pos.Score - neg.Score,
		Comparative: pos.Comparative - neg.Comparative,
		Positive:    pos,
		Negative:    neg,
	}
}
<|endoftext|>


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

//go:generate binapi-generator --input-dir=bin_api --output-dir=bin_api

package core

import (
	"errors"
	"os"
	"sync"
	"sync/atomic"
	"time"

	logger "github.com/Sirupsen/logrus"

	"git.fd.io/govpp.git/adapter"
	"git.fd.io/govpp.git/api"
	"git.fd.io/govpp.git/core/bin_api/vpe"
	"github.com/fsnotify/fsnotify"
)

const (
	requestChannelBufSize      = 100 // default size of the request channel buffers
	replyChannelBufSize        = 100 // default size of the reply channel buffers
	notificationChannelBufSize = 100 // default size of the notification channel buffers
)

var (
	healthCheckProbeInterval = time.Second * 1         // default health check probe interval
	healthCheckReplyTimeout  = time.Millisecond * 1000 // timeout for reply to a health check probe
	healthCheckThreshold     = 3                       // number of failed healthProbe until the error is reported
)

// ConnectionState holds the current state of the connection to VPP.
type ConnectionState int

const (
	// Connected connection state means that the connection to VPP has been successfully established.
	Connected ConnectionState = iota

	// Disconnected connection state means that the connection to VPP has been lost.
	Disconnected = iota
)

const (
	// watchedFolder is a folder where vpp's shared memory is supposed to be created.
	// File system events are monitored in this folder.
	watchedFolder = "/dev/shm/"
	// watchedFile is a name of the file in the watchedFolder. Once the file is present
	// the vpp is ready to accept a new connection.
	watchedFile = watchedFolder + "vpe-api"
)

// ConnectionEvent is a notification about change in the VPP connection state.
type ConnectionEvent struct {
	// Timestamp holds the time when the event has been generated.
	Timestamp time.Time

	// State holds the new state of the connection to VPP at the time when the event has been generated.
	State ConnectionState
}

// Connection represents a shared memory connection to VPP via vppAdapter.
type Connection struct {
	vpp       adapter.VppAdapter // VPP adapter
	connected uint32             // non-zero if the adapter is connected to VPP
	codec     *MsgCodec          // message codec

	msgIDs     map[string]uint16 // map of message IDs indexed by message name + CRC
	msgIDsLock sync.RWMutex      // lock for the message IDs map

	channels     map[uint32]*api.Channel // map of all API channels indexed by the channel ID
	channelsLock sync.RWMutex            // lock for the channels map

	notifSubscriptions     map[uint16][]*api.NotifSubscription // map od all notification subscriptions indexed by message ID
	notifSubscriptionsLock sync.RWMutex                        // lock for the subscriptions map

	maxChannelID uint32 // maximum used client ID
	pingReqID    uint16 // ID if the ControlPing message
	pingReplyID  uint16 // ID of the ControlPingReply message
}

// channelMetadata contains core-local metadata of an API channel.
type channelMetadata struct {
	id        uint32 // channel ID
	multipart uint32 // 1 if multipart request is being processed, 0 otherwise
}

var (
	log      *logger.Logger // global logger
	conn     *Connection    // global handle to the Connection (used in the message receive callback)
	connLock sync.RWMutex   // lock for the global connection
)

// init initializes global logger, which logs debug level messages to stdout.
func init() {
	log = logger.New()
	log.Out = os.Stdout
	log.Level = logger.DebugLevel
}

// SetLogger sets global logger to provided one.
func SetLogger(l *logger.Logger) {
	log = l
}

// SetHealthCheckProbeInterval sets health check probe interval.
// Beware: Function is not thread-safe. It is recommended to setup this parameter
// before connecting to vpp.
func SetHealthCheckProbeInterval(interval time.Duration) {
	healthCheckProbeInterval = interval
}

// SetHealthCheckReplyTimeout sets timeout for reply to a health check probe.
// If reply arrives after the timeout, check is considered as failed.
// Beware: Function is not thread-safe. It is recommended to setup this parameter
// before connecting to vpp.
func SetHealthCheckReplyTimeout(timeout time.Duration) {
	healthCheckReplyTimeout = timeout
}

// SetHealthCheckThreshold sets the number of failed healthProbe checks until the error is reported.
// Beware: Function is not thread-safe. It is recommended to setup this parameter
// before connecting to vpp.
func SetHealthCheckThreshold(threshold int) {
	healthCheckThreshold = threshold
}

// Connect connects to VPP using specified VPP adapter and returns the connection handle.
// This call blocks until VPP is connected, or an error occurs. Only one connection attempt will be performed.
func Connect(vppAdapter adapter.VppAdapter) (*Connection, error) {
	// create new connection handle
	c, err := newConnection(vppAdapter)
	if err != nil {
		return nil, err
	}

	// blocking attempt to connect to VPP
	err = c.connectVPP()
	if err != nil {
		return nil, err
	}

	return conn, nil
}

// AsyncConnect asynchronously connects to VPP using specified VPP adapter and returns the connection handle
// and ConnectionState channel. This call does not block until connection is established, it
// returns immediately. The caller is supposed to watch the returned ConnectionState channel for
// Connected/Disconnected events. In case of disconnect, the library will asynchronously try to reconnect.
func AsyncConnect(vppAdapter adapter.VppAdapter) (*Connection, chan ConnectionEvent, error) {
	// create new connection handle
	c, err := newConnection(vppAdapter)
	if err != nil {
		return nil, nil, err
	}

	// asynchronously attempt to connect to VPP
	connChan := make(chan ConnectionEvent, notificationChannelBufSize)
	go c.connectLoop(connChan)

	return conn, connChan, nil
}

// Disconnect disconnects from VPP and releases all connection-related resources.
func (c *Connection) Disconnect() {
	if c == nil {
		return
	}
	connLock.Lock()
	defer connLock.Unlock()

	if c != nil && c.vpp != nil {
		c.disconnectVPP()
	}
	conn = nil
}

// newConnection returns new connection handle.
func newConnection(vppAdapter adapter.VppAdapter) (*Connection, error) {
	connLock.Lock()
	defer connLock.Unlock()

	if conn != nil {
		return nil, errors.New("only one connection per process is supported")
	}

	conn = &Connection{vpp: vppAdapter, codec: &MsgCodec{}}
	conn.channels = make(map[uint32]*api.Channel)
	conn.msgIDs = make(map[string]uint16)
	conn.notifSubscriptions = make(map[uint16][]*api.NotifSubscription)

	conn.vpp.SetMsgCallback(msgCallback)
	return conn, nil
}

// connectVPP performs one blocking attempt to connect to VPP.
func (c *Connection) connectVPP() error {
	log.Debug("Connecting to VPP...")

	// blocking connect
	err := c.vpp.Connect()
	if err != nil {
		log.Warn(err)
		return err
	}

	// store connected state
	atomic.StoreUint32(&c.connected, 1)

	// store control ping IDs
	c.pingReqID, _ = c.GetMessageID(&vpe.ControlPing{})
	c.pingReplyID, _ = c.GetMessageID(&vpe.ControlPingReply{})

	log.Info("Connected to VPP.")
	return nil
}

// disconnectVPP disconnects from VPP in case it is connected.
func (c *Connection) disconnectVPP() {
	if atomic.CompareAndSwapUint32(&c.connected, 1, 0) {
		c.vpp.Disconnect()
	}
}

func fileExists(name string) bool {
	if _, err := os.Stat(name); err != nil {
		if os.IsNotExist(err) {
			return false
		}
	}
	return true
}

// waitForVpp blocks until shared memory for sending bin api calls
// is present on the file system.
func waitForVpp() error {
	watcher, err := fsnotify.NewWatcher()
	if err != nil {
		return err
	}
	defer watcher.Close()

	err = watcher.Add(watchedFolder)
	if err != nil {
		return err
	}

	if fileExists(watchedFile) {
		return nil
	}

	for {
		ev := <-watcher.Events
		if ev.Name == watchedFile && (ev.Op&fsnotify.Create) == fsnotify.Create {
			break
		}
	}
	return nil
}

// connectLoop attempts to connect to VPP until it succeeds.
// Then it continues with healthCheckLoop.
func (c *Connection) connectLoop(connChan chan ConnectionEvent) {
	// loop until connected
	for {
		waitForVpp()
		err := c.connectVPP()
		if err == nil {
			// signal connected event
			connChan <- ConnectionEvent{Timestamp: time.Now(), State: Connected}
			break
		}
	}

	// we are now connected, continue with health check loop
	c.healthCheckLoop(connChan)
}

// healthCheckLoop checks whether connection to VPP is alive. In case of disconnect,
// it continues with connectLoop and tries to reconnect.
func (c *Connection) healthCheckLoop(connChan chan ConnectionEvent) {
	// create a separate API channel for health check probes
	ch, err := conn.NewAPIChannel()
	if err != nil {
		log.Error("Error by creating health check API channel, health check will be disabled:", err)
		return
	}

	failedChecks := 0
	// send health check probes until an error occurs
	for {
		// wait for healthCheckProbeInterval
		<-time.After(healthCheckProbeInterval)

		if atomic.LoadUint32(&c.connected) == 0 {
			// Disconnect has been called in the meantime, return the healthcheck - reconnect loop
			log.Debug("Disconnected on request, exiting health check loop.")
			return
		}

		// send the control ping
		ch.ReqChan <- &api.VppRequest{Message: &vpe.ControlPing{}}

		// expect response within timeout period
		select {
		case vppReply := <-ch.ReplyChan:
			err = vppReply.Error
		case <-time.After(healthCheckReplyTimeout):
			err = errors.New("probe reply not received within the timeout period")
		}

		if err != nil {
			failedChecks++
		} else {
			failedChecks = 0
		}

		if failedChecks >= healthCheckThreshold {
			// in case of error, break & disconnect
			log.Errorf("VPP health check failed: %v", err)
			// signal disconnected event via channel
			connChan <- ConnectionEvent{Timestamp: time.Now(), State: Disconnected}
			break
		}
	}

	// cleanup
	ch.Close()
	c.disconnectVPP()

	// we are now disconnected, start connect loop
	c.connectLoop(connChan)
}

// NewAPIChannel returns a new API channel for communication with VPP via govpp core.
// It uses default buffer sizes for the request and reply Go channels.
func (c *Connection) NewAPIChannel() (*api.Channel, error) {
	if c == nil {
		return nil, errors.New("nil connection passed in")
	}
	return c.NewAPIChannelBuffered(requestChannelBufSize, replyChannelBufSize)
}

// NewAPIChannelBuffered returns a new API channel for communication with VPP via govpp core.
// It allows to specify custom buffer sizes for the request and reply Go channels.
func (c *Connection) NewAPIChannelBuffered(reqChanBufSize, replyChanBufSize int) (*api.Channel, error) {
	if c == nil {
		return nil, errors.New("nil connection passed in")
	}
	chID := atomic.AddUint32(&c.maxChannelID, 1)
	chMeta := &channelMetadata{id: chID}

	ch := api.NewChannelInternal(chMeta)
	ch.MsgDecoder = c.codec
	ch.MsgIdentifier = c

	// create the communication channels
	ch.ReqChan = make(chan *api.VppRequest, reqChanBufSize)
	ch.ReplyChan = make(chan *api.VppReply, replyChanBufSize)
	ch.NotifSubsChan = make(chan *api.NotifSubscribeRequest, reqChanBufSize)
	ch.NotifSubsReplyChan = make(chan error, replyChanBufSize)

	// store API channel within the client
	c.channelsLock.Lock()
	c.channels[chID] = ch
	c.channelsLock.Unlock()

	// start watching on the request channel
	go c.watchRequests(ch, chMeta)

	return ch, nil
}

// releaseAPIChannel releases API channel that needs to be closed.
func (c *Connection) releaseAPIChannel(ch *api.Channel, chMeta *channelMetadata) {
	log.WithFields(logger.Fields{
		"context": chMeta.id,
	}).Debug("API channel closed.")

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

//go:generate binapi-generator --input-dir=bin_api --output-dir=bin_api

package core

import (
	"errors"
	"os"
	"sync"
	"sync/atomic"
	"time"

	logger "github.com/Sirupsen/logrus"

	"git.fd.io/govpp.git/adapter"
	"git.fd.io/govpp.git/api"
	"git.fd.io/govpp.git/core/bin_api/vpe"
	"github.com/fsnotify/fsnotify"
)

const (
	requestChannelBufSize      = 100 // default size of the request channel buffers
	replyChannelBufSize        = 100 // default size of the reply channel buffers
	notificationChannelBufSize = 100 // default size of the notification channel buffers
)

var (
	healthCheckProbeInterval = time.Second * 1        // default health check probe interval
	healthCheckReplyTimeout  = time.Millisecond * 100 // timeout for reply to a health check probe
	healthCheckThreshold     = 1                      // number of failed healthProbe until the error is reported
)

// ConnectionState holds the current state of the connection to VPP.
type ConnectionState int

const (
	// Connected connection state means that the connection to VPP has been successfully established.
	Connected ConnectionState = iota

	// Disconnected connection state means that the connection to VPP has been lost.
	Disconnected = iota
)

const (
	// watchedFolder is a folder where vpp's shared memory is supposed to be created.
	// File system events are monitored in this folder.
	watchedFolder = "/dev/shm/"
	// watchedFile is a name of the file in the watchedFolder. Once the file is present
	// the vpp is ready to accept a new connection.
	watchedFile = watchedFolder + "vpe-api"
)

// ConnectionEvent is a notification about change in the VPP connection state.
type ConnectionEvent struct {
	// Timestamp holds the time when the event has been generated.
	Timestamp time.Time

	// State holds the new state of the connection to VPP at the time when the event has been generated.
	State ConnectionState
}

// Connection represents a shared memory connection to VPP via vppAdapter.
type Connection struct {
	vpp       adapter.VppAdapter // VPP adapter
	connected uint32             // non-zero if the adapter is connected to VPP
	codec     *MsgCodec          // message codec

	msgIDs     map[string]uint16 // map of message IDs indexed by message name + CRC
	msgIDsLock sync.RWMutex      // lock for the message IDs map

	channels     map[uint32]*api.Channel // map of all API channels indexed by the channel ID
	channelsLock sync.RWMutex            // lock for the channels map

	notifSubscriptions     map[uint16][]*api.NotifSubscription // map od all notification subscriptions indexed by message ID
	notifSubscriptionsLock sync.RWMutex                        // lock for the subscriptions map

	maxChannelID uint32 // maximum used client ID
	pingReqID    uint16 // ID if the ControlPing message
	pingReplyID  uint16 // ID of the ControlPingReply message
}

// channelMetadata contains core-local metadata of an API channel.
type channelMetadata struct {
	id        uint32 // channel ID
	multipart uint32 // 1 if multipart request is being processed, 0 otherwise
}

var (
	log      *logger.Logger // global logger
	conn     *Connection    // global handle to the Connection (used in the message receive callback)
	connLock sync.RWMutex   // lock for the global connection
)

// init initializes global logger, which logs debug level messages to stdout.
func init() {
	log = logger.New()
	log.Out = os.Stdout
	log.Level = logger.DebugLevel
}

// SetLogger sets global logger to provided one.
func SetLogger(l *logger.Logger) {
	log = l
}

// SetHealthCheckProbeInterval sets health check probe interval.
// Beware: Function is not thread-safe. It is recommended to setup this parameter
// before connecting to vpp.
func SetHealthCheckProbeInterval(interval time.Duration) {
	healthCheckProbeInterval = interval
}

// SetHealthCheckReplyTimeout sets timeout for reply to a health check probe.
// If reply arrives after the timeout, check is considered as failed.
// Beware: Function is not thread-safe. It is recommended to setup this parameter
// before connecting to vpp.
func SetHealthCheckReplyTimeout(timeout time.Duration) {
	healthCheckReplyTimeout = timeout
}

// SetHealthCheckThreshold sets the number of failed healthProbe checks until the error is reported.
// Beware: Function is not thread-safe. It is recommended to setup this parameter
// before connecting to vpp.
func SetHealthCheckThreshold(threshold int) {
	healthCheckThreshold = threshold
}

// Connect connects to VPP using specified VPP adapter and returns the connection handle.
// This call blocks until VPP is connected, or an error occurs. Only one connection attempt will be performed.
func Connect(vppAdapter adapter.VppAdapter) (*Connection, error) {
	// create new connection handle
	c, err := newConnection(vppAdapter)
	if err != nil {
		return nil, err
	}

	// blocking attempt to connect to VPP
	err = c.connectVPP()
	if err != nil {
		return nil, err
	}

	return conn, nil
}

// AsyncConnect asynchronously connects to VPP using specified VPP adapter and returns the connection handle
// and ConnectionState channel. This call does not block until connection is established, it
// returns immediately. The caller is supposed to watch the returned ConnectionState channel for
// Connected/Disconnected events. In case of disconnect, the library will asynchronously try to reconnect.
func AsyncConnect(vppAdapter adapter.VppAdapter) (*Connection, chan ConnectionEvent, error) {
	// create new connection handle
	c, err := newConnection(vppAdapter)
	if err != nil {
		return nil, nil, err
	}

	// asynchronously attempt to connect to VPP
	connChan := make(chan ConnectionEvent, notificationChannelBufSize)
	go c.connectLoop(connChan)

	return conn, connChan, nil
}

// Disconnect disconnects from VPP and releases all connection-related resources.
func (c *Connection) Disconnect() {
	if c == nil {
		return
	}
	connLock.Lock()
	defer connLock.Unlock()

	if c != nil && c.vpp != nil {
		c.disconnectVPP()
	}
	conn = nil
}

// newConnection returns new connection handle.
func newConnection(vppAdapter adapter.VppAdapter) (*Connection, error) {
	connLock.Lock()
	defer connLock.Unlock()

	if conn != nil {
		return nil, errors.New("only one connection per process is supported")
	}

	conn = &Connection{vpp: vppAdapter, codec: &MsgCodec{}}
	conn.channels = make(map[uint32]*api.Channel)
	conn.msgIDs = make(map[string]uint16)
	conn.notifSubscriptions = make(map[uint16][]*api.NotifSubscription)

	conn.vpp.SetMsgCallback(msgCallback)
	return conn, nil
}

// connectVPP performs one blocking attempt to connect to VPP.
func (c *Connection) connectVPP() error {
	log.Debug("Connecting to VPP...")

	// blocking connect
	err := c.vpp.Connect()
	if err != nil {
		log.Warn(err)
		return err
	}

	// store connected state
	atomic.StoreUint32(&c.connected, 1)

	// store control ping IDs
	c.pingReqID, _ = c.GetMessageID(&vpe.ControlPing{})
	c.pingReplyID, _ = c.GetMessageID(&vpe.ControlPingReply{})

	log.Info("Connected to VPP.")
	return nil
}

// disconnectVPP disconnects from VPP in case it is connected.
func (c *Connection) disconnectVPP() {
	if atomic.CompareAndSwapUint32(&c.connected, 1, 0) {
		c.vpp.Disconnect()
	}
}

func fileExists(name string) bool {
	if _, err := os.Stat(name); err != nil {
		if os.IsNotExist(err) {
			return false
		}
	}
	return true
}

// waitForVpp blocks until shared memory for sending bin api calls
// is present on the file system.
func waitForVpp() error {
	watcher, err := fsnotify.NewWatcher()
	if err != nil {
		return err
	}
	defer watcher.Close()

	err = watcher.Add(watchedFolder)
	if err != nil {
		return err
	}

	if fileExists(watchedFile) {
		return nil
	}

	for {
		ev := <-watcher.Events
		if ev.Name == watchedFile && (ev.Op&fsnotify.Create) == fsnotify.Create {
			break
		}
	}
	return nil
}

// connectLoop attempts to connect to VPP until it succeeds.
// Then it continues with healthCheckLoop.
func (c *Connection) connectLoop(connChan chan ConnectionEvent) {
	// loop until connected
	for {
		waitForVpp()
		err := c.connectVPP()
		if err == nil {
			// signal connected event
			connChan <- ConnectionEvent{Timestamp: time.Now(), State: Connected}
			break
		}
	}

	// we are now connected, continue with health check loop
	c.healthCheckLoop(connChan)
}

// healthCheckLoop checks whether connection to VPP is alive. In case of disconnect,
// it continues with connectLoop and tries to reconnect.
func (c *Connection) healthCheckLoop(connChan chan ConnectionEvent) {
	// create a separate API channel for health check probes
	ch, err := conn.NewAPIChannel()
	if err != nil {
		log.Error("Error by creating health check API channel, health check will be disabled:", err)
		return
	}

	failedChecks := 0
	// send health check probes until an error occurs
	for {
		// wait for healthCheckProbeInterval
		<-time.After(healthCheckProbeInterval)

		if atomic.LoadUint32(&c.connected) == 0 {
			// Disconnect has been called in the meantime, return the healthcheck - reconnect loop
			log.Debug("Disconnected on request, exiting health check loop.")
			return
		}

		// send the control ping
		ch.ReqChan <- &api.VppRequest{Message: &vpe.ControlPing{}}

		// expect response within timeout period
		select {
		case vppReply := <-ch.ReplyChan:
			err = vppReply.Error
		case <-time.After(healthCheckReplyTimeout):
			err = errors.New("probe reply not received within the timeout period")
		}

		if err != nil {
			failedChecks++
		} else {
			failedChecks = 0
		}

		if failedChecks >= healthCheckThreshold {
			// in case of error, break & disconnect
			log.Errorf("VPP health check failed: %v", err)
			// signal disconnected event via channel
			connChan <- ConnectionEvent{Timestamp: time.Now(), State: Disconnected}
			break
		}
	}

	// cleanup
	ch.Close()
	c.disconnectVPP()

	// we are now disconnected, start connect loop
	c.connectLoop(connChan)
}

// NewAPIChannel returns a new API channel for communication with VPP via govpp core.
// It uses default buffer sizes for the request and reply Go channels.
func (c *Connection) NewAPIChannel() (*api.Channel, error) {
	if c == nil {
		return nil, errors.New("nil connection passed in")
	}
	return c.NewAPIChannelBuffered(requestChannelBufSize, replyChannelBufSize)
}

// NewAPIChannelBuffered returns a new API channel for communication with VPP via govpp core.
// It allows to specify custom buffer sizes for the request and reply Go channels.
func (c *Connection) NewAPIChannelBuffered(reqChanBufSize, replyChanBufSize int) (*api.Channel, error) {
	if c == nil {
		return nil, errors.New("nil connection passed in")
	}
	chID := atomic.AddUint32(&c.maxChannelID, 1)
	chMeta := &channelMetadata{id: chID}

	ch := api.NewChannelInternal(chMeta)
	ch.MsgDecoder = c.codec
	ch.MsgIdentifier = c

	// create the communication channels
	ch.ReqChan = make(chan *api.VppRequest, reqChanBufSize)
	ch.ReplyChan = make(chan *api.VppReply, replyChanBufSize)
	ch.NotifSubsChan = make(chan *api.NotifSubscribeRequest, reqChanBufSize)
	ch.NotifSubsReplyChan = make(chan error, replyChanBufSize)

	// store API channel within the client
	c.channelsLock.Lock()
	c.channels[chID] = ch
	c.channelsLock.Unlock()

	// start watching on the request channel
	go c.watchRequests(ch, chMeta)

	return ch, nil
}

// releaseAPIChannel releases API channel that needs to be closed.
func (c *Connection) releaseAPIChannel(ch *api.Channel, chMeta *channelMetadata) {
	log.WithFields(logger.Fields{
		"context": chMeta.id,
	}).Debug("API channel closed.")

	// delete the channel from channels map
	c.channelsLock.Lock()
	delete(c.channels, chMeta.id)
	c.channelsLock.Unlock()
}
<|endoftext|>


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

// Package oauth2 contains Martini handlers to provide
// user login via an OAuth 2.0 backend.
package oauth2

import (
	"encoding/json"
	"fmt"
	"net/http"
	"net/url"
	"time"

	"github.com/go-martini/martini"
	"github.com/golang/oauth2"
	"github.com/martini-contrib/sessions"
)

const (
	codeRedirect = 302
	keyToken     = "oauth2_token"
	keyNextPage  = "next"
)

var (
	// Path to handle OAuth 2.0 logins.
	PathLogin = "/login"
	// Path to handle OAuth 2.0 logouts.
	PathLogout = "/logout"
	// Path to handle callback from OAuth 2.0 backend
	// to exchange credentials.
	PathCallback = "/oauth2callback"
	// Path to handle error cases.
	PathError = "/oauth2error"
)

// Represents a container that contains
// user's OAuth 2.0 access and refresh tokens.
type Tokens interface {
	Access() string
	Refresh() string
	IsExpired() bool
	ExpiryTime() time.Time
	ExtraData() map[string]string
}

type token struct {
	oauth2.Token
}

func (t *token) ExtraData() map[string]string {
	return t.Extra
}

// Returns the access token.
func (t *token) Access() string {
	return t.AccessToken
}

// Returns the refresh token.
func (t *token) Refresh() string {
	return t.RefreshToken
}

// Returns whether the access token is
// expired or not.
func (t *token) IsExpired() bool {
	if t == nil {
		return true
	}
	return t.Expired()
}

// Returns the expiry time of the user's
// access token.
func (t *token) ExpiryTime() time.Time {
	return t.Expiry
}

// Formats tokens into string.
func (t *token) String() string {
	return fmt.Sprintf("tokens: %v", t)
}

// Returns a new Google OAuth 2.0 backend endpoint.
func Google(opts *oauth2.Options) martini.Handler {
	authUrl := "https://accounts.google.com/o/oauth2/auth"
	tokenUrl := "https://accounts.google.com/o/oauth2/token"
	return NewOAuth2Provider(opts, authUrl, tokenUrl)
}

// Returns a new Github OAuth 2.0 backend endpoint.
func Github(opts *oauth2.Options) martini.Handler {
	authUrl := "https://github.com/login/oauth/authorize"
	tokenUrl := "https://github.com/login/oauth/access_token"
	return NewOAuth2Provider(opts, authUrl, tokenUrl)
}

func Facebook(opts *oauth2.Options) martini.Handler {
	authUrl := "https://www.facebook.com/dialog/oauth"
	tokenUrl := "https://graph.facebook.com/oauth/access_token"
	return NewOAuth2Provider(opts, authUrl, tokenUrl)
}

func LinkedIn(opts *oauth2.Options) martini.Handler {
	authUrl := "https://www.linkedin.com/uas/oauth2/authorization"
	tokenUrl := "https://www.linkedin.com/uas/oauth2/accessToken"
	return NewOAuth2Provider(opts, authUrl, tokenUrl)
}

// Returns a generic OAuth 2.0 backend endpoint.
func NewOAuth2Provider(opts *oauth2.Options, authUrl, tokenUrl string) martini.Handler {

	config, err := oauth2.NewConfig(opts, authUrl, tokenUrl)
	if err != nil {
		panic(fmt.Sprintf("oauth2: %s", err))
	}

	return func(s sessions.Session, c martini.Context, w http.ResponseWriter, r *http.Request) {
		if r.Method == "GET" {
			switch r.URL.Path {
			case PathLogin:
				login(config, s, w, r)
			case PathLogout:
				logout(s, w, r)
			case PathCallback:
				handleOAuth2Callback(config, s, w, r)
			}
		}

		tk := unmarshallToken(s)
		if tk != nil {
			// check if the access token is expired
			if tk.IsExpired() && tk.Refresh() == "" {
				s.Delete(keyToken)
				tk = nil
			}
		}
		// Inject tokens.
		c.MapTo(tk, (*Tokens)(nil))
	}
}

// Handler that redirects user to the login page
// if user is not logged in.
// Sample usage:
// m.Get("/login-required", oauth2.LoginRequired, func() ... {})
var LoginRequired martini.Handler = func() martini.Handler {
	return func(s sessions.Session, c martini.Context, w http.ResponseWriter, r *http.Request) {
		token := unmarshallToken(s)
		if token == nil || token.IsExpired() {
			next := url.QueryEscape(r.URL.RequestURI())
			http.Redirect(w, r, PathLogin+"?next="+next, codeRedirect)
		}
	}
}()

func login(c *oauth2.Config, s sessions.Session, w http.ResponseWriter, r *http.Request) {
	next := extractPath(r.URL.Query().Get(keyNextPage))
	if s.Get(keyToken) == nil {
		// User is not logged in.
		if next == "" {
			next = "/"
		}

		// TODO (ahmetalpbalkan) error is only a URL parsing error and should be validated at
		// config initialization time: https://github.com/golang/oauth2/issues/13
		authUrl, err := c.AuthCodeURL(next)
		if err != nil {
			http.Redirect(w, r, PathError, codeRedirect)
		}
		http.Redirect(w, r, authUrl, codeRedirect)
		return
	}
	// No need to login, redirect to the next page.
	http.Redirect(w, r, next, codeRedirect)
}

func logout(s sessions.Session, w http.ResponseWriter, r *http.Request) {
	next := extractPath(r.URL.Query().Get(keyNextPage))
	s.Delete(keyToken)
	http.Redirect(w, r, next, codeRedirect)
}

func handleOAuth2Callback(c *oauth2.Config, s sessions.Session, w http.ResponseWriter, r *http.Request) {
	next := extractPath(r.URL.Query().Get("state"))
	code := r.URL.Query().Get("code")
	t, err := c.NewTransportWithCode(code)
	if err != nil {
		// Pass the error message, or allow dev to provide its own
		// error handler.
		http.Redirect(w, r, PathError, codeRedirect)
		return
	}
	// Store the credentials in the session.
	val, _ := json.Marshal(t.Token())
	s.Set(keyToken, val)
	http.Redirect(w, r, next, codeRedirect)
}

func unmarshallToken(s sessions.Session) (t *token) {
	if s.Get(keyToken) == nil {
		return
	}
	data := s.Get(keyToken).([]byte)
	var tk oauth2.Token
	json.Unmarshal(data, &tk)
	return &token{tk}
}

func extractPath(next string) string {
	n, err := url.Parse(next)
	if err != nil {
		return "/"
	}
	return n.Path
}
<commit_msg>AuthCodeURL no longer returns error<commit_after>// Copyright 2014 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Package oauth2 contains Martini handlers to provide
// user login via an OAuth 2.0 backend.
package oauth2

import (
	"encoding/json"
	"fmt"
	"net/http"
	"net/url"
	"time"

	"github.com/go-martini/martini"
	"github.com/golang/oauth2"
	"github.com/martini-contrib/sessions"
)

const (
	codeRedirect = 302
	keyToken     = "oauth2_token"
	keyNextPage  = "next"
)

var (
	// Path to handle OAuth 2.0 logins.
	PathLogin = "/login"
	// Path to handle OAuth 2.0 logouts.
	PathLogout = "/logout"
	// Path to handle callback from OAuth 2.0 backend
	// to exchange credentials.
	PathCallback = "/oauth2callback"
	// Path to handle error cases.
	PathError = "/oauth2error"
)

// Represents a container that contains
// user's OAuth 2.0 access and refresh tokens.
type Tokens interface {
	Access() string
	Refresh() string
	IsExpired() bool
	ExpiryTime() time.Time
	ExtraData() map[string]string
}

type token struct {
	oauth2.Token
}

func (t *token) ExtraData() map[string]string {
	return t.Extra
}

// Returns the access token.
func (t *token) Access() string {
	return t.AccessToken
}

// Returns the refresh token.
func (t *token) Refresh() string {
	return t.RefreshToken
}

// Returns whether the access token is
// expired or not.
func (t *token) IsExpired() bool {
	if t == nil {
		return true
	}
	return t.Expired()
}

// Returns the expiry time of the user's
// access token.
func (t *token) ExpiryTime() time.Time {
	return t.Expiry
}

// Formats tokens into string.
func (t *token) String() string {
	return fmt.Sprintf("tokens: %v", t)
}

// Returns a new Google OAuth 2.0 backend endpoint.
func Google(opts *oauth2.Options) martini.Handler {
	authUrl := "https://accounts.google.com/o/oauth2/auth"
	tokenUrl := "https://accounts.google.com/o/oauth2/token"
	return NewOAuth2Provider(opts, authUrl, tokenUrl)
}

// Returns a new Github OAuth 2.0 backend endpoint.
func Github(opts *oauth2.Options) martini.Handler {
	authUrl := "https://github.com/login/oauth/authorize"
	tokenUrl := "https://github.com/login/oauth/access_token"
	return NewOAuth2Provider(opts, authUrl, tokenUrl)
}

func Facebook(opts *oauth2.Options) martini.Handler {
	authUrl := "https://www.facebook.com/dialog/oauth"
	tokenUrl := "https://graph.facebook.com/oauth/access_token"
	return NewOAuth2Provider(opts, authUrl, tokenUrl)
}

func LinkedIn(opts *oauth2.Options) martini.Handler {
	authUrl := "https://www.linkedin.com/uas/oauth2/authorization"
	tokenUrl := "https://www.linkedin.com/uas/oauth2/accessToken"
	return NewOAuth2Provider(opts, authUrl, tokenUrl)
}

// Returns a generic OAuth 2.0 backend endpoint.
func NewOAuth2Provider(opts *oauth2.Options, authUrl, tokenUrl string) martini.Handler {

	config, err := oauth2.NewConfig(opts, authUrl, tokenUrl)
	if err != nil {
		panic(fmt.Sprintf("oauth2: %s", err))
	}

	return func(s sessions.Session, c martini.Context, w http.ResponseWriter, r *http.Request) {
		if r.Method == "GET" {
			switch r.URL.Path {
			case PathLogin:
				login(config, s, w, r)
			case PathLogout:
				logout(s, w, r)
			case PathCallback:
				handleOAuth2Callback(config, s, w, r)
			}
		}

		tk := unmarshallToken(s)
		if tk != nil {
			// check if the access token is expired
			if tk.IsExpired() && tk.Refresh() == "" {
				s.Delete(keyToken)
				tk = nil
			}
		}
		// Inject tokens.
		c.MapTo(tk, (*Tokens)(nil))
	}
}

// Handler that redirects user to the login page
// if user is not logged in.
// Sample usage:
// m.Get("/login-required", oauth2.LoginRequired, func() ... {})
var LoginRequired martini.Handler = func() martini.Handler {
	return func(s sessions.Session, c martini.Context, w http.ResponseWriter, r *http.Request) {
		token := unmarshallToken(s)
		if token == nil || token.IsExpired() {
			next := url.QueryEscape(r.URL.RequestURI())
			http.Redirect(w, r, PathLogin+"?next="+next, codeRedirect)
		}
	}
}()

func login(c *oauth2.Config, s sessions.Session, w http.ResponseWriter, r *http.Request) {
	next := extractPath(r.URL.Query().Get(keyNextPage))
	if s.Get(keyToken) == nil {
		// User is not logged in.
		if next == "" {
			next = "/"
		}

		// TODO (ahmetalpbalkan) error is only a URL parsing error and should be validated at
		// config initialization time: https://github.com/golang/oauth2/issues/13
		http.Redirect(w, r, c.AuthCodeURL(next), codeRedirect)
		return
	}
	// No need to login, redirect to the next page.
	http.Redirect(w, r, next, codeRedirect)
}

func logout(s sessions.Session, w http.ResponseWriter, r *http.Request) {
	next := extractPath(r.URL.Query().Get(keyNextPage))
	s.Delete(keyToken)
	http.Redirect(w, r, next, codeRedirect)
}

func handleOAuth2Callback(c *oauth2.Config, s sessions.Session, w http.ResponseWriter, r *http.Request) {
	next := extractPath(r.URL.Query().Get("state"))
	code := r.URL.Query().Get("code")
	t, err := c.NewTransportWithCode(code)
	if err != nil {
		// Pass the error message, or allow dev to provide its own
		// error handler.
		http.Redirect(w, r, PathError, codeRedirect)
		return
	}
	// Store the credentials in the session.
	val, _ := json.Marshal(t.Token())
	s.Set(keyToken, val)
	http.Redirect(w, r, next, codeRedirect)
}

func unmarshallToken(s sessions.Session) (t *token) {
	if s.Get(keyToken) == nil {
		return
	}
	data := s.Get(keyToken).([]byte)
	var tk oauth2.Token
	json.Unmarshal(data, &tk)
	return &token{tk}
}

func extractPath(next string) string {
	n, err := url.Parse(next)
	if err != nil {
		return "/"
	}
	return n.Path
}
<|endoftext|>


<commit_before>package sej

import (
	"io"
	"os"
	"path"
)

// Offset is used to manage a disk-persisted offset
type Offset struct {
	dir      *os.File
	file     string
	fileLock *fileLock
	value    uint64

	Syncing bool
}

type DefaultOffset int

const (
	FirstOffset DefaultOffset = iota
	LastOffset
)

// NewOffset creates a new Offset object persisted to dir/ofs/name.ofs
func NewOffset(dir, name string, defaultOffset DefaultOffset) (*Offset, error) {
	jnlDir, dir := JournalDirPath(dir), OffsetDirPath(dir)
	_ = os.MkdirAll(dir, 0755)
	filePrefix := path.Join(dir, name)
	fileLock, err := openFileLock(filePrefix + ".lck")
	if err != nil {
		return nil, err
	}
	d, err := os.Open(dir)
	if err != nil {
		return nil, err
	}
	o := &Offset{dir: d, file: filePrefix + ".ofs", fileLock: fileLock}
	f, err := openOrCreate(o.file)
	if err != nil {
		return nil, err
	}
	defer f.Close()
	o.value, err = ReadOffset(f)
	if err == io.EOF {
		jd, err := OpenJournalDir(jnlDir)
		if err != nil {
			return nil, err
		}
		if defaultOffset == LastOffset {
			o.value, err = jd.Last().LastReadableOffset()
			if err != nil {
				return nil, err
			}
		} else {
			o.value = jd.First().FirstOffset
		}
	} else if err != nil {
		return nil, err
	}
	return o, nil
}

func OpenReadonlyOffset(dir, name string) (*Offset, error) {
	filePrefix := path.Join(dir, name)
	d, err := os.Open(dir)
	if err != nil {
		return nil, err
	}
	o := &Offset{dir: d, file: filePrefix + ".ofs"}
	f, err := os.Open(o.file)
	if err != nil {
		return nil, err
	}
	defer f.Close()
	o.value, err = ReadOffset(f)
	if err != nil {
		return nil, err
	}
	return o, nil
}

// Value gets the current offset value
func (o *Offset) Value() uint64 {
	return o.value
}

// Commit saves and syncs the offset to disk
func (o *Offset) Commit(offset uint64) error {
	if o.fileLock == nil {
		panic("read only offset cannot be used for committing offset")
	}
	if offset == o.value {
		return nil
	}
	file := o.file + ".tmp"
	f, err := os.Create(file)
	if err != nil {
		return err
	}
	if _, err := writeUint64(f, make([]byte, 8), offset); err != nil {
		f.Close()
		return err
	}
	if o.Syncing {
		if err := f.Sync(); err != nil {
			return err
		}
	}
	if err := f.Close(); err != nil {
		return err
	}
	if err := os.Rename(file, o.file); err != nil {
		os.Remove(file)
		return err
	}
	if o.Syncing {
		if err := o.dir.Sync(); err != nil {
			return err
		}
	}
	o.value = offset
	return nil
}

// Close closes opened resources
func (o *Offset) Close() error {
	o.fileLock.Close()
	return o.dir.Close()
}

func OffsetDirPath(dir string) string {
	return path.Join(dir, "ofs")
}

// ReadOffset reads the offset stored in an ofs file (r)
func ReadOffset(r io.ReadSeeker) (offset uint64, err error) {
	_, err = readUint64(r, &offset)
	return offset, err
}
<commit_msg>fix offset dir<commit_after>package sej

import (
	"io"
	"os"
	"path"
)

// Offset is used to manage a disk-persisted offset
type Offset struct {
	dir      *os.File
	file     string
	fileLock *fileLock
	value    uint64

	Syncing bool
}

type DefaultOffset int

const (
	FirstOffset DefaultOffset = iota
	LastOffset
)

// NewOffset creates a new Offset object persisted to dir/ofs/name.ofs
func NewOffset(dir, name string, defaultOffset DefaultOffset) (*Offset, error) {
	jnlDir, dir := JournalDirPath(dir), OffsetDirPath(dir)
	_ = os.MkdirAll(dir, 0755)
	filePrefix := path.Join(dir, name)
	fileLock, err := openFileLock(filePrefix + ".lck")
	if err != nil {
		return nil, err
	}
	d, err := os.Open(dir)
	if err != nil {
		return nil, err
	}
	o := &Offset{dir: d, file: filePrefix + ".ofs", fileLock: fileLock}
	f, err := openOrCreate(o.file)
	if err != nil {
		return nil, err
	}
	defer f.Close()
	o.value, err = ReadOffset(f)
	if err == io.EOF {
		jd, err := OpenJournalDir(jnlDir)
		if err != nil {
			return nil, err
		}
		if defaultOffset == LastOffset {
			o.value, err = jd.Last().LastReadableOffset()
			if err != nil {
				return nil, err
			}
		} else {
			o.value = jd.First().FirstOffset
		}
	} else if err != nil {
		return nil, err
	}
	return o, nil
}

func OpenReadonlyOffset(dir, name string) (*Offset, error) {
	dir = OffsetDirPath(dir)
	filePrefix := path.Join(dir, name)
	d, err := os.Open(dir)
	if err != nil {
		return nil, err
	}
	o := &Offset{dir: d, file: filePrefix + ".ofs"}
	f, err := os.Open(o.file)
	if err != nil {
		return nil, err
	}
	defer f.Close()
	o.value, err = ReadOffset(f)
	if err != nil {
		return nil, err
	}
	return o, nil
}

// Value gets the current offset value
func (o *Offset) Value() uint64 {
	return o.value
}

// Commit saves and syncs the offset to disk
func (o *Offset) Commit(offset uint64) error {
	if o.fileLock == nil {
		panic("read only offset cannot be used for committing offset")
	}
	if offset == o.value {
		return nil
	}
	file := o.file + ".tmp"
	f, err := os.Create(file)
	if err != nil {
		return err
	}
	if _, err := writeUint64(f, make([]byte, 8), offset); err != nil {
		f.Close()
		return err
	}
	if o.Syncing {
		if err := f.Sync(); err != nil {
			return err
		}
	}
	if err := f.Close(); err != nil {
		return err
	}
	if err := os.Rename(file, o.file); err != nil {
		os.Remove(file)
		return err
	}
	if o.Syncing {
		if err := o.dir.Sync(); err != nil {
			return err
		}
	}
	o.value = offset
	return nil
}

// Close closes opened resources
func (o *Offset) Close() error {
	o.fileLock.Close()
	return o.dir.Close()
}

func OffsetDirPath(dir string) string {
	return path.Join(dir, "ofs")
}

// ReadOffset reads the offset stored in an ofs file (r)
func ReadOffset(r io.ReadSeeker) (offset uint64, err error) {
	_, err = readUint64(r, &offset)
	return offset, err
}
<|endoftext|>


<commit_before>package integration_test

import (
	"fmt"
	"io/ioutil"
	"net/http"
	"policy-server/config"
	"policy-server/integration/helpers"
	"strings"

	"code.cloudfoundry.org/cf-networking-helpers/db"
	"code.cloudfoundry.org/cf-networking-helpers/testsupport"
	"code.cloudfoundry.org/cf-networking-helpers/testsupport/metrics"
	"code.cloudfoundry.org/cf-networking-helpers/testsupport/ports"

	"bytes"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	"github.com/onsi/gomega/gexec"
)

var _ = Describe("External Destination API", func() {
	var (
		sessions          []*gexec.Session
		conf              config.Config
		policyServerConfs []config.Config
		dbConf            db.Config

		fakeMetron metrics.FakeMetron
	)

	BeforeEach(func() {
		fakeMetron = metrics.NewFakeMetron()

		dbConf = testsupport.GetDBConfig()
		dbConf.DatabaseName = fmt.Sprintf("external_api_destination_test_node_%d", ports.PickAPort())

		template, _ := helpers.DefaultTestConfig(dbConf, fakeMetron.Address(), "fixtures")
		policyServerConfs = configurePolicyServers(template, 2)
		sessions = startPolicyServers(policyServerConfs)
		conf = policyServerConfs[0]
	})

	AfterEach(func() {
		stopPolicyServers(sessions, policyServerConfs)

		Expect(fakeMetron.Close()).To(Succeed())
	})

	Describe("destinations", func() {
		addPolicy := func(body string) {
			resp := helpers.MakeAndDoRequest(
				"POST",
				fmt.Sprintf("http://%s:%d/networking/v1/external/policies", conf.ListenHost, conf.ListenPort),
				nil,
				strings.NewReader(body),
			)

			Expect(resp.StatusCode).To(Equal(http.StatusOK))
			responseString, err := ioutil.ReadAll(resp.Body)
			Expect(err).NotTo(HaveOccurred())
			Expect(responseString).To(MatchJSON("{}"))
		}
		BeforeEach(func() {
			addPolicy(`{ "egress_policies": [ {"source": { "id": "live-app-1-guid" }, "destination": { "protocol": "tcp", "ips": [ {"start": "23.96.32.148", "end": "23.96.32.149" } ] } } ] }`)
			addPolicy(`{ "egress_policies": [ {"source": { "id": "live-app-1-guid" }, "destination": { "protocol": "tcp", "ports": [{"start": 8080, "end": 8081}], "ips": [ {"start": "23.96.32.150", "end": "23.96.32.151" } ] } } ] }`)
			addPolicy(`{ "egress_policies": [ {"source": { "id": "live-app-1-guid" }, "destination": { "protocol": "icmp", "icmp_type": 1, "icmp_code": 2, "ips": [ {"start": "23.96.32.150", "end": "23.96.32.151" } ] } } ] }`)
		})

		Describe("create and listing all destinations", func() {
			It("returns all created destinations", func() {

				createRequestBody := bytes.NewBufferString(`{
					"destinations": [
						{  "name": "my service",
						    "description": "my service is a great service",
						    "ips": [{"start": "7211.30.35.9", "end": "72.30.35.9"}],
						     "ports": [{"start": 8080, "end": 8080}],
						     "protocol":"tcp"
						},
						{  "name": "cloud infra",
						    "description": "this is where my apps go",
						    "ips": [{"start": "7211.30.35.9", "end": "72.30.35.9"}],
						     "ports": [{"start": 8080, "end": 8080}],
						     "protocol":"tcp"
						}
					]
				}`)
				resp := helpers.MakeAndDoRequest(
					"POST",
					fmt.Sprintf("http://%s:%d/networking/v1/external/destinations", conf.ListenHost, conf.ListenPort),
					nil,
					createRequestBody,
				)

				Expect(resp.StatusCode).To(Equal(http.StatusCreated))
				responseBytes, err := ioutil.ReadAll(resp.Body)
				Expect(err).NotTo(HaveOccurred())

				Expect(string(responseBytes)).To(MatchJSON(`{
					"total_destinations": 2,
					"destinations": [
						{
							"guid": "5",
							"name": "my service",
						    "description": "my service is a great service",
						    "ips": [{"start": "7211.30.35.9", "end": "72.30.35.9"}],
						    "ports": [{"start": 8080, "end": 8080}],
						    "protocol":"tcp"
						},
						{
							"guid": "6",
							"name": "cloud infra",
						    "description": "this is where my apps go",
						    "ips": [{"start": "7211.30.35.9", "end": "72.30.35.9"}],
						    "ports": [{"start": 8080, "end": 8080}],
						    "protocol":"tcp"
						}
					]
				}`))

				resp = helpers.MakeAndDoRequest(
					"GET",
					fmt.Sprintf("http://%s:%d/networking/v1/external/destinations", conf.ListenHost, conf.ListenPort),
					nil,
					nil,
				)

				Expect(resp.StatusCode).To(Equal(http.StatusOK))
				responseBytes, err = ioutil.ReadAll(resp.Body)
				Expect(err).NotTo(HaveOccurred())

				Expect(string(responseBytes)).To(MatchJSON(`{
					"total_destinations": 5,
					"destinations": [
						{ "guid": "2", "protocol": "tcp", "ips": [ {"start": "23.96.32.148", "end": "23.96.32.149" } ] },
						{ "guid": "3", "protocol": "tcp", "ports": [{"start": 8080, "end": 8081}], "ips": [ {"start": "23.96.32.150", "end": "23.96.32.151" } ] },
						{ "guid": "4", "protocol": "icmp", "icmp_type": 1, "icmp_code": 2, "ips": [ {"start": "23.96.32.150", "end": "23.96.32.151" } ] },
						{ "guid": "5", "name": "my service", "description": "my service is a great service",	"ips": [{"start": "7211.30.35.9", "end": "72.30.35.9"}], "ports": [{"start": 8080, "end": 8080}], "protocol":"tcp" },
						{ "guid": "6", "name": "cloud infra", "description": "this is where my apps go", "ips": [{"start": "7211.30.35.9", "end": "72.30.35.9"}], "ports": [{"start": 8080, "end": 8080}], "protocol":"tcp" }
					]
				}`))

				Eventually(fakeMetron.AllEvents, "5s").Should(ContainElement(
					HaveName("DestinationsIndexRequestTime"),
				))
			})
		})
	})
})
<commit_msg>Format external api destination test<commit_after>package integration_test

import (
	"fmt"
	"io/ioutil"
	"net/http"
	"policy-server/config"
	"policy-server/integration/helpers"
	"strings"

	"code.cloudfoundry.org/cf-networking-helpers/db"
	"code.cloudfoundry.org/cf-networking-helpers/testsupport"
	"code.cloudfoundry.org/cf-networking-helpers/testsupport/metrics"
	"code.cloudfoundry.org/cf-networking-helpers/testsupport/ports"

	"bytes"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	"github.com/onsi/gomega/gexec"
)

var _ = Describe("External Destination API", func() {
	var (
		sessions          []*gexec.Session
		conf              config.Config
		policyServerConfs []config.Config
		dbConf            db.Config

		fakeMetron metrics.FakeMetron
	)

	BeforeEach(func() {
		fakeMetron = metrics.NewFakeMetron()

		dbConf = testsupport.GetDBConfig()
		dbConf.DatabaseName = fmt.Sprintf("external_api_destination_test_node_%d", ports.PickAPort())

		template, _ := helpers.DefaultTestConfig(dbConf, fakeMetron.Address(), "fixtures")
		policyServerConfs = configurePolicyServers(template, 2)
		sessions = startPolicyServers(policyServerConfs)
		conf = policyServerConfs[0]
	})

	AfterEach(func() {
		stopPolicyServers(sessions, policyServerConfs)

		Expect(fakeMetron.Close()).To(Succeed())
	})

	Describe("create and listing all destinations", func() {
		addPolicy := func(body string) {
			resp := helpers.MakeAndDoRequest(
				"POST",
				fmt.Sprintf("http://%s:%d/networking/v1/external/policies", conf.ListenHost, conf.ListenPort),
				nil,
				strings.NewReader(body),
			)

			Expect(resp.StatusCode).To(Equal(http.StatusOK))
			responseString, err := ioutil.ReadAll(resp.Body)
			Expect(err).NotTo(HaveOccurred())
			Expect(responseString).To(MatchJSON("{}"))
		}
		BeforeEach(func() {
			addPolicy(`{ "egress_policies": [ {"source": { "id": "live-app-1-guid" }, "destination": { "protocol": "tcp", "ips": [ {"start": "23.96.32.148", "end": "23.96.32.149" } ] } } ] }`)
			addPolicy(`{ "egress_policies": [ {"source": { "id": "live-app-1-guid" }, "destination": { "protocol": "tcp", "ports": [{"start": 8080, "end": 8081}], "ips": [ {"start": "23.96.32.150", "end": "23.96.32.151" } ] } } ] }`)
			addPolicy(`{ "egress_policies": [ {"source": { "id": "live-app-1-guid" }, "destination": { "protocol": "icmp", "icmp_type": 1, "icmp_code": 2, "ips": [ {"start": "23.96.32.150", "end": "23.96.32.151" } ] } } ] }`)
		})

		It("returns all created destinations", func() {

			createRequestBody := bytes.NewBufferString(`{
				"destinations": [
					{
						"name": "my service",
						"description": "my service is a great service",
						"ips": [{"start": "7211.30.35.9", "end": "72.30.35.9"}],
						"ports": [{"start": 8080, "end": 8080}],
						"protocol":"tcp"
					},
					{
						"name": "cloud infra",
						"description": "this is where my apps go",
						"ips": [{"start": "7211.30.35.9", "end": "72.30.35.9"}],
						"ports": [{"start": 8080, "end": 8080}],
						"protocol":"tcp"
					}
				]
			}`)
			resp := helpers.MakeAndDoRequest(
				"POST",
				fmt.Sprintf("http://%s:%d/networking/v1/external/destinations", conf.ListenHost, conf.ListenPort),
				nil,
				createRequestBody,
			)

			Expect(resp.StatusCode).To(Equal(http.StatusCreated))
			responseBytes, err := ioutil.ReadAll(resp.Body)
			Expect(err).NotTo(HaveOccurred())

			Expect(string(responseBytes)).To(MatchJSON(`{
				"total_destinations": 2,
				"destinations": [
					{
						"guid": "5",
						"name": "my service",
						"description": "my service is a great service",
						"ips": [{"start": "7211.30.35.9", "end": "72.30.35.9"}],
						"ports": [{"start": 8080, "end": 8080}],
						"protocol":"tcp"
					},
					{
						"guid": "6",
						"name": "cloud infra",
						"description": "this is where my apps go",
						"ips": [{"start": "7211.30.35.9", "end": "72.30.35.9"}],
						"ports": [{"start": 8080, "end": 8080}],
						"protocol":"tcp"
					}
				]
			}`))

			resp = helpers.MakeAndDoRequest(
				"GET",
				fmt.Sprintf("http://%s:%d/networking/v1/external/destinations", conf.ListenHost, conf.ListenPort),
				nil,
				nil,
			)

			Expect(resp.StatusCode).To(Equal(http.StatusOK))
			responseBytes, err = ioutil.ReadAll(resp.Body)
			Expect(err).NotTo(HaveOccurred())

			Expect(string(responseBytes)).To(MatchJSON(`{
				"total_destinations": 5,
				"destinations": [
					{ "guid": "2", "protocol": "tcp", "ips": [ {"start": "23.96.32.148", "end": "23.96.32.149" } ] },
					{ "guid": "3", "protocol": "tcp", "ports": [{"start": 8080, "end": 8081}], "ips": [ {"start": "23.96.32.150", "end": "23.96.32.151" } ] },
					{ "guid": "4", "protocol": "icmp", "icmp_type": 1, "icmp_code": 2, "ips": [ {"start": "23.96.32.150", "end": "23.96.32.151" } ] },
					{ "guid": "5", "name": "my service", "description": "my service is a great service",	"ips": [{"start": "7211.30.35.9", "end": "72.30.35.9"}], "ports": [{"start": 8080, "end": 8080}], "protocol":"tcp" },
					{ "guid": "6", "name": "cloud infra", "description": "this is where my apps go", "ips": [{"start": "7211.30.35.9", "end": "72.30.35.9"}], "ports": [{"start": 8080, "end": 8080}], "protocol":"tcp" }
				]
			}`))

			Eventually(fakeMetron.AllEvents, "5s").Should(ContainElement(
				HaveName("DestinationsIndexRequestTime"),
			))
		})
	})
})
<|endoftext|>


<commit_before>// Ogo

package ogo

import (
	"fmt"
	"sync"
	"time"
)

type OnlineMap struct {
	lock *sync.RWMutex
	olm  map[string]*Online
}

type Online struct {
	Key string
	Ex  time.Time
}

var (
	OnlineCache *OnlineMap
)

func init() {
	OnlineCache = &OnlineMap{
		lock: new(sync.RWMutex),
		olm:  make(map[string]*Online),
	}
}

/* {{{ func GetOnlineKey(id string) (*Online,error)
 * 获取在线的key
 */
func GetOnlineKey(id string) (ol *Online, err error) {
	if ol = OnlineCache.Get(id); ol == nil {
		err = fmt.Errorf("not found")
	}
	return
}

/* }}} */

/* {{{ func SetOnlineKey(id string, ol *Online) bool
 *
 */
func SetOnlineKey(id, key string) bool {
	ol := &Online{
		Key: key,
		Ex:  time.Now().Add(300 * time.Second), //缓存5分钟
	}
	return OnlineCache.Set(id, ol)
}

/* }}} */

// Maps the given key and value. Returns false
// if the key is already in the map and changes nothing.
func (m *OnlineMap) Set(k string, v *Online) bool {
	m.lock.Lock()
	defer m.lock.Unlock()
	if val, ok := m.olm[k]; !ok {
		m.olm[k] = v
	} else if val != v {
		m.olm[k] = v
	} else {
		return false
	}
	return true
}

// Get from maps return the k's value
func (m *OnlineMap) Get(k string) (ol *Online) {
	m.lock.RLock()
	defer m.lock.RUnlock()
	if val, ok := m.olm[k]; ok {
		if time.Now().Before(val.Ex) { //还没过期
			return val
		} else { //过期,删除
			m.Delete(k)
		}
	}
	return nil
}

// Delete the given key and value.
func (m *OnlineMap) Delete(k string) {
	m.lock.Lock()
	defer m.lock.Unlock()
	delete(m.olm, k)
}
<commit_msg>update<commit_after>// Ogo

package ogo

import (
	"fmt"
	"sync"
	"time"
)

type OnlineMap struct {
	lock *sync.RWMutex
	olm  map[string]*Online
}

type Online struct {
	Key  string
	Ex   time.Time
	Perm interface{}
}

var (
	OnlineCache *OnlineMap
)

func init() {
	OnlineCache = &OnlineMap{
		lock: new(sync.RWMutex),
		olm:  make(map[string]*Online),
	}
}

/* {{{ func GetOnlineKey(id string) (*Online,error)
 * 获取在线的key
 */
func GetOnlineKey(id string) (ol *Online, err error) {
	if ol = OnlineCache.Get(id); ol == nil {
		err = fmt.Errorf("not found")
	}
	return
}

/* }}} */

/* {{{ func SetOnlineKey(id string, ol *Online,p interface{}) bool
 *
 */
func SetOnlineKey(id, key string, p interface{}) bool {
	ol := &Online{
		Key:  key,
		Ex:   time.Now().Add(300 * time.Second), //缓存5分钟
		Perm: p,
	}
	return OnlineCache.Set(id, ol)
}

/* }}} */

// Maps the given key and value. Returns false
// if the key is already in the map and changes nothing.
func (m *OnlineMap) Set(k string, v *Online) bool {
	m.lock.Lock()
	defer m.lock.Unlock()
	if val, ok := m.olm[k]; !ok {
		m.olm[k] = v
	} else if val != v {
		m.olm[k] = v
	} else {
		return false
	}
	return true
}

// Get from maps return the k's value
func (m *OnlineMap) Get(k string) (ol *Online) {
	m.lock.RLock()
	defer m.lock.RUnlock()
	if val, ok := m.olm[k]; ok {
		if time.Now().Before(val.Ex) { //还没过期
			return val
		} else { //过期,删除
			m.Delete(k)
		}
	}
	return nil
}

// Delete the given key and value.
func (m *OnlineMap) Delete(k string) {
	m.lock.Lock()
	defer m.lock.Unlock()
	delete(m.olm, k)
}
<|endoftext|>


<commit_before>// Copyright 2019 The GCR Cleaner 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 gcrcleaner cleans up stale images from a container registry.
package gcrcleaner

import (
	"context"
	"fmt"
	"sort"
	"strings"
	"sync"
	"time"

	"github.com/GoogleCloudPlatform/gcr-cleaner/internal/version"
	gcrauthn "github.com/google/go-containerregistry/pkg/authn"
	gcrname "github.com/google/go-containerregistry/pkg/name"
	gcrgoogle "github.com/google/go-containerregistry/pkg/v1/google"
	gcrremote "github.com/google/go-containerregistry/pkg/v1/remote"
	"golang.org/x/sync/semaphore"
)

// dockerExistence is date of the first release of Docker[1] (then dotCloud) and
// marks the first possible date in which Docker containers could feasibly have
// been created. We need this because some tools set the container's CreatedDate
// to a very old value[2] and thus sorting by creation date fails.
//
// [1]: https://en.wikipedia.org/wiki/Docker_(software)
// [2]: https://buildpacks.io/docs/features/reproducibility/
var dockerExistence = time.Date(2013, time.March, 20, 0, 0, 0, 0, time.UTC)

// userAgent is the HTTP user agent.
var userAgent = fmt.Sprintf("%s/%s (+https://github.com/GoogleCloudPlatform/gcr-cleaner)",
	version.Name, version.Version)

// Cleaner is a gcr cleaner.
type Cleaner struct {
	keychain    gcrauthn.Keychain
	logger      *Logger
	concurrency int
}

// NewCleaner creates a new GCR cleaner with the given token provider and
// concurrency.
func NewCleaner(keychain gcrauthn.Keychain, logger *Logger, c int) (*Cleaner, error) {
	if c < 1 {
		return nil, fmt.Errorf("concurrency must be at least 1, got %d", c)
	}

	return &Cleaner{
		keychain:    keychain,
		concurrency: c,
		logger:      logger,
	}, nil
}

// Clean deletes old images from GCR that are (un)tagged and older than "since"
// and higher than the "keep" amount.
func (c *Cleaner) Clean(ctx context.Context, repo string, since time.Time, keep int, tagFilter TagFilter, dryRun bool) ([]string, error) {
	gcrrepo, err := gcrname.NewRepository(repo)
	if err != nil {
		return nil, fmt.Errorf("failed to get repo %s: %w", repo, err)
	}
	c.logger.Debug("computed repo", "repo", gcrrepo.Name())

	tags, err := gcrgoogle.List(gcrrepo,
		gcrgoogle.WithContext(ctx),
		gcrgoogle.WithUserAgent(userAgent),
		gcrgoogle.WithAuthFromKeychain(c.keychain))
	if err != nil {
		return nil, fmt.Errorf("failed to list tags for repo %s: %w", repo, err)
	}

	workers := int64(c.concurrency)
	sem := semaphore.NewWeighted(workers)

	var keepCount = 0
	var deleted = make([]string, 0, len(tags.Manifests))
	var deletedLock sync.Mutex
	var errs = make([]error, 0)
	var errsLock sync.RWMutex

	var manifests = make([]*manifest, 0, len(tags.Manifests))
	for k, m := range tags.Manifests {
		manifests = append(manifests, &manifest{repo, k, m})
	}

	// Sort manifests. If either of the containers were created before Docker even
	// existed, we fall back to the upload date. This can happen with some
	// community build tools. If two containers were created at the same time, we
	// fall back to the upload date. Otherwise, we sort by the container creation
	// date.
	sort.Slice(manifests, func(i, j int) bool {
		jCreated, jUploaded := manifests[j].Info.Created, manifests[j].Info.Uploaded
		iCreated, iUploaded := manifests[i].Info.Created, manifests[i].Info.Uploaded

		// If either container has a CreateTime that predates Docker's existence, or
		// the contains have the same creation time, fallback to the uploaded time.
		if jCreated.Before(dockerExistence) || iCreated.Before(dockerExistence) || jCreated.Equal(iCreated) {
			return jUploaded.Before(iUploaded)
		}

		return jCreated.Before(iCreated)
	})

	// Generate an ordered map
	manifestListForLog := make([]map[string]any, len(manifests))
	for _, m := range manifests {
		manifestListForLog = append(manifestListForLog, map[string]any{
			"repo":     m.Repo,
			"digest":   m.Digest,
			"tags":     m.Info.Tags,
			"created":  m.Info.Created.Format(time.RFC3339),
			"uploaded": m.Info.Uploaded.Format(time.RFC3339),
		})
	}
	c.logger.Debug("computed all manifests",
		"keep", keep,
		"manifests", manifestListForLog)

	for _, m := range manifests {
		// Store copy of manifest for thread safety in delete job pool
		m := m

		c.logger.Debug("processing manifest",
			"repo", repo,
			"digest", m.Digest,
			"tags", m.Info.Tags,
			"created", m.Info.Created.Format(time.RFC3339),
			"uploaded", m.Info.Uploaded.Format(time.RFC3339))

		if c.shouldDelete(m, since, tagFilter) {
			// Keep a certain amount of images
			if keepCount < keep {
				c.logger.Debug("skipping deletion because of keep count",
					"repo", repo,
					"digest", m.Digest,
					"keep", keep,
					"keep_count", keepCount,
					"created", m.Info.Created.Format(time.RFC3339),
					"uploaded", m.Info.Uploaded.Format(time.RFC3339))

				keepCount++
				continue
			}

			// Deletes all tags before deleting the image
			for _, tag := range m.Info.Tags {
				c.logger.Debug("deleting tag",
					"repo", repo,
					"digest", m.Digest,
					"tag", tag)

				tagged := gcrrepo.Tag(tag)
				if !dryRun {
					if err := c.deleteOne(ctx, tagged); err != nil {
						return nil, fmt.Errorf("failed to delete tag %s: %w", tagged, err)
					}
				}

				deletedLock.Lock()
				deleted = append(deleted, tagged.Identifier())
				deletedLock.Unlock()
			}

			digest := m.Digest
			ref := gcrrepo.Digest(digest)

			if err := sem.Acquire(ctx, 1); err != nil {
				return nil, fmt.Errorf("failed to acquire semaphore: %w", err)
			}

			go func() {
				defer sem.Release(1)

				c.logger.Debug("deleting digest",
					"repo", repo,
					"digest", m.Digest)

				if !dryRun {
					if err := c.deleteOne(ctx, ref); err != nil {
						errsLock.Lock()
						errs = append(errs, fmt.Errorf("failed to delete digest %s: %w", ref, err))
						errsLock.Unlock()
					}
				}

				deletedLock.Lock()
				deleted = append(deleted, digest)
				deletedLock.Unlock()
			}()
		}
	}

	// Wait for everything to finish
	if err := sem.Acquire(ctx, workers); err != nil {
		return nil, fmt.Errorf("failed to wait for semaphore: %w", err)
	}

	// Aggregate any errors
	if err := ErrsToError(errs); err != nil {
		return nil, err
	}

	// Return the list of deleted entries
	sort.Strings(deleted)
	return deleted, nil
}

type manifest struct {
	Repo   string
	Digest string
	Info   gcrgoogle.ManifestInfo
}

// deleteOne deletes a single repo ref using the supplied auth.
func (c *Cleaner) deleteOne(ctx context.Context, ref gcrname.Reference) error {
	if err := gcrremote.Delete(ref,
		gcrremote.WithUserAgent(userAgent),
		gcrremote.WithAuthFromKeychain(c.keychain),
		gcrremote.WithContext(ctx)); err != nil {
		return err
	}

	return nil
}

// shouldDelete returns true if the manifest was created before the given
// timestamp and either has no tags or has tags that match the given filter.
func (c *Cleaner) shouldDelete(m *manifest, since time.Time, tagFilter TagFilter) bool {
	// Immediately exclude images that have been uploaded after the given time.
	if uploaded := m.Info.Uploaded.UTC(); uploaded.After(since) {
		c.logger.Debug("should not delete",
			"repo", m.Repo,
			"digest", m.Digest,
			"reason", "too new",
			"since", since.Format(time.RFC3339),
			"created", m.Info.Created.Format(time.RFC3339),
			"uploaded", uploaded.Format(time.RFC3339),
			"delta", uploaded.Sub(since).String())
		return false
	}

	// If there are no tags, it should be deleted.
	if len(m.Info.Tags) == 0 {
		c.logger.Debug("should delete",
			"repo", m.Repo,
			"digest", m.Digest,
			"reason", "no tags")
		return true
	}

	// If tagged images are allowed and the given filter matches the list of tags,
	// this is a deletion candidate. The default tag filter is to reject all
	// strings.
	if tagFilter.Matches(m.Info.Tags) {
		c.logger.Debug("should delete",
			"repo", m.Repo,
			"digest", m.Digest,
			"reason", "matches tag filter",
			"tags", m.Info.Tags,
			"tag_filter", tagFilter.Name())
		return true
	}

	// If we got this far, it'ts not a viable deletion candidate.
	c.logger.Debug("should not delete",
		"repo", m.Repo,
		"digest", m.Digest,
		"reason", "no filter matches")
	return false
}

// ListChildRepositories lists all child repositores for the given roots. Roots
// can be entire registries (e.g. us-docker.pkg.dev) or a subpath within a
// registry (e.g. gcr.io/my-project/my-container).
func (c *Cleaner) ListChildRepositories(ctx context.Context, roots []string) ([]string, error) {
	c.logger.Debug("finding all child repositories", "roots", roots)

	// registriesMap is a cache of registries to all the repos in that registry.
	// Since multiple repos might use the same registry, the result is cached to
	// limit upstream API calls.
	registriesMap := make(map[string]*gcrname.Registry, len(roots))

	// Iterate over each root and attempt to extract the registry component. Some
	// roots will be registries themselves whereas other roots could be a subpath
	// in a registry and we need to extract just the registry part.
	for _, root := range roots {
		registryName := ""

		parts := strings.Split(root, "/")
		switch len(parts) {
		case 0:
			panic("got 0 parts from string split (impossible)")
		case 1:
			// Most likely this is a registry, since it contains no slashes.
			registryName = parts[0]
		default:
			repo, err := gcrname.NewRepository(root, gcrname.StrictValidation)
			if err != nil {
				return nil, fmt.Errorf("failed to parse root repository %q: %w", root, err)
			}

			registryName = repo.RegistryStr()
		}

		registry, err := gcrname.NewRegistry(registryName)
		if err != nil {
			return nil, fmt.Errorf("failed to parse registry name %q: %w", registryName, err)
		}
		registriesMap[registryName] = &registry
	}

	// candidateRepos is the list of full repository names that match any of the
	// given root repositories. This list is appended to so the range function
	// below is psuedo-recursive.
	candidateRepos := make([]string, 0, len(roots))

	// Iterate through each registry, query the entire registry (yea, that's how
	// you "search"), and collect a list of candidate repos.
	for _, registry := range registriesMap {
		c.logger.Debug("listing child repositories for registry",
			"registry", registry.Name())

		// List all repos in the registry.
		allRepos, err := gcrremote.Catalog(ctx, *registry,
			gcrremote.WithUserAgent(userAgent),
			gcrremote.WithAuthFromKeychain(c.keychain),
			gcrremote.WithContext(ctx))
		if err != nil {
			return nil, fmt.Errorf("failed to fetch catalog for registry %q: %w", registry.Name(), err)
		}

		c.logger.Debug("found child repositories for registry",
			"registry", registry.Name(),
			"repos", allRepos)

		// Search through each repository and append any repository that matches any
		// of the prefixes defined by roots.
		for _, repo := range allRepos {
			// Compute the full repo name by appending the repo to the registry
			// identifier.
			fullRepoName := registry.Name() + "/" + repo

			hasPrefix := false
			for _, root := range roots {
				if strings.HasPrefix(fullRepoName, root) {
					hasPrefix = true
					break
				}
			}
			if hasPrefix {
				c.logger.Debug("appending new repository candidate",
					"registry", registry.Name(),
					"repo", repo)
				candidateRepos = append(candidateRepos, fullRepoName)
			} else {
				c.logger.Debug("skipping repository candidate (does not match any roots)",
					"registry", registry.Name(),
					"repo", repo)
			}
		}
	}

	// De-duplicate and sort the list.
	sort.Strings(candidateRepos)
	return candidateRepos, nil
}

// ErrsToError converts a list of errors into a single error. If the list is
// empty, it returns nil. If the list contains exactly one error, it returns
// that error. Otherwise it returns a bulleted list of the sorted errors, but
// the original error contexts are discarded.
func ErrsToError(errs []error) error {
	switch len(errs) {
	case 0:
		return nil
	case 1:
		return errs[0]
	default:
		errMessages := make([]string, len(errs))
		for i, err := range errs {
			errMessages[i] = err.Error()
		}
		sort.Strings(errMessages)

		var b strings.Builder
		fmt.Fprintf(&b, "%d errors occurred:\n", len(errs))
		for _, msg := range errMessages {
			fmt.Fprintf(&b, "  * %s\n", msg)
		}
		return fmt.Errorf(b.String())
	}
}
<commit_msg>Fix misallocated slice (#120)<commit_after>// Copyright 2019 The GCR Cleaner 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 gcrcleaner cleans up stale images from a container registry.
package gcrcleaner

import (
	"context"
	"fmt"
	"sort"
	"strings"
	"sync"
	"time"

	"github.com/GoogleCloudPlatform/gcr-cleaner/internal/version"
	gcrauthn "github.com/google/go-containerregistry/pkg/authn"
	gcrname "github.com/google/go-containerregistry/pkg/name"
	gcrgoogle "github.com/google/go-containerregistry/pkg/v1/google"
	gcrremote "github.com/google/go-containerregistry/pkg/v1/remote"
	"golang.org/x/sync/semaphore"
)

// dockerExistence is date of the first release of Docker[1] (then dotCloud) and
// marks the first possible date in which Docker containers could feasibly have
// been created. We need this because some tools set the container's CreatedDate
// to a very old value[2] and thus sorting by creation date fails.
//
// [1]: https://en.wikipedia.org/wiki/Docker_(software)
// [2]: https://buildpacks.io/docs/features/reproducibility/
var dockerExistence = time.Date(2013, time.March, 20, 0, 0, 0, 0, time.UTC)

// userAgent is the HTTP user agent.
var userAgent = fmt.Sprintf("%s/%s (+https://github.com/GoogleCloudPlatform/gcr-cleaner)",
	version.Name, version.Version)

// Cleaner is a gcr cleaner.
type Cleaner struct {
	keychain    gcrauthn.Keychain
	logger      *Logger
	concurrency int
}

// NewCleaner creates a new GCR cleaner with the given token provider and
// concurrency.
func NewCleaner(keychain gcrauthn.Keychain, logger *Logger, c int) (*Cleaner, error) {
	if c < 1 {
		return nil, fmt.Errorf("concurrency must be at least 1, got %d", c)
	}

	return &Cleaner{
		keychain:    keychain,
		concurrency: c,
		logger:      logger,
	}, nil
}

// Clean deletes old images from GCR that are (un)tagged and older than "since"
// and higher than the "keep" amount.
func (c *Cleaner) Clean(ctx context.Context, repo string, since time.Time, keep int, tagFilter TagFilter, dryRun bool) ([]string, error) {
	gcrrepo, err := gcrname.NewRepository(repo)
	if err != nil {
		return nil, fmt.Errorf("failed to get repo %s: %w", repo, err)
	}
	c.logger.Debug("computed repo", "repo", gcrrepo.Name())

	tags, err := gcrgoogle.List(gcrrepo,
		gcrgoogle.WithContext(ctx),
		gcrgoogle.WithUserAgent(userAgent),
		gcrgoogle.WithAuthFromKeychain(c.keychain))
	if err != nil {
		return nil, fmt.Errorf("failed to list tags for repo %s: %w", repo, err)
	}

	workers := int64(c.concurrency)
	sem := semaphore.NewWeighted(workers)

	var keepCount = 0
	var deleted = make([]string, 0, len(tags.Manifests))
	var deletedLock sync.Mutex
	var errs = make([]error, 0)
	var errsLock sync.RWMutex

	var manifests = make([]*manifest, 0, len(tags.Manifests))
	for k, m := range tags.Manifests {
		manifests = append(manifests, &manifest{repo, k, m})
	}

	// Sort manifests. If either of the containers were created before Docker even
	// existed, we fall back to the upload date. This can happen with some
	// community build tools. If two containers were created at the same time, we
	// fall back to the upload date. Otherwise, we sort by the container creation
	// date.
	sort.Slice(manifests, func(i, j int) bool {
		jCreated, jUploaded := manifests[j].Info.Created, manifests[j].Info.Uploaded
		iCreated, iUploaded := manifests[i].Info.Created, manifests[i].Info.Uploaded

		// If either container has a CreateTime that predates Docker's existence, or
		// the contains have the same creation time, fallback to the uploaded time.
		if jCreated.Before(dockerExistence) || iCreated.Before(dockerExistence) || jCreated.Equal(iCreated) {
			return jUploaded.Before(iUploaded)
		}

		return jCreated.Before(iCreated)
	})

	// Generate an ordered map
	manifestListForLog := make([]map[string]any, 0, len(manifests))
	for _, m := range manifests {
		manifestListForLog = append(manifestListForLog, map[string]any{
			"repo":     m.Repo,
			"digest":   m.Digest,
			"tags":     m.Info.Tags,
			"created":  m.Info.Created.Format(time.RFC3339),
			"uploaded": m.Info.Uploaded.Format(time.RFC3339),
		})
	}
	c.logger.Debug("computed all manifests",
		"keep", keep,
		"manifests", manifestListForLog)

	for _, m := range manifests {
		// Store copy of manifest for thread safety in delete job pool
		m := m

		c.logger.Debug("processing manifest",
			"repo", repo,
			"digest", m.Digest,
			"tags", m.Info.Tags,
			"created", m.Info.Created.Format(time.RFC3339),
			"uploaded", m.Info.Uploaded.Format(time.RFC3339))

		if c.shouldDelete(m, since, tagFilter) {
			// Keep a certain amount of images
			if keepCount < keep {
				c.logger.Debug("skipping deletion because of keep count",
					"repo", repo,
					"digest", m.Digest,
					"keep", keep,
					"keep_count", keepCount,
					"created", m.Info.Created.Format(time.RFC3339),
					"uploaded", m.Info.Uploaded.Format(time.RFC3339))

				keepCount++
				continue
			}

			// Deletes all tags before deleting the image
			for _, tag := range m.Info.Tags {
				c.logger.Debug("deleting tag",
					"repo", repo,
					"digest", m.Digest,
					"tag", tag)

				tagged := gcrrepo.Tag(tag)
				if !dryRun {
					if err := c.deleteOne(ctx, tagged); err != nil {
						return nil, fmt.Errorf("failed to delete tag %s: %w", tagged, err)
					}
				}

				deletedLock.Lock()
				deleted = append(deleted, tagged.Identifier())
				deletedLock.Unlock()
			}

			digest := m.Digest
			ref := gcrrepo.Digest(digest)

			if err := sem.Acquire(ctx, 1); err != nil {
				return nil, fmt.Errorf("failed to acquire semaphore: %w", err)
			}

			go func() {
				defer sem.Release(1)

				c.logger.Debug("deleting digest",
					"repo", repo,
					"digest", m.Digest)

				if !dryRun {
					if err := c.deleteOne(ctx, ref); err != nil {
						errsLock.Lock()
						errs = append(errs, fmt.Errorf("failed to delete digest %s: %w", ref, err))
						errsLock.Unlock()
					}
				}

				deletedLock.Lock()
				deleted = append(deleted, digest)
				deletedLock.Unlock()
			}()
		}
	}

	// Wait for everything to finish
	if err := sem.Acquire(ctx, workers); err != nil {
		return nil, fmt.Errorf("failed to wait for semaphore: %w", err)
	}

	// Aggregate any errors
	if err := ErrsToError(errs); err != nil {
		return nil, err
	}

	// Return the list of deleted entries
	sort.Strings(deleted)
	return deleted, nil
}

type manifest struct {
	Repo   string
	Digest string
	Info   gcrgoogle.ManifestInfo
}

// deleteOne deletes a single repo ref using the supplied auth.
func (c *Cleaner) deleteOne(ctx context.Context, ref gcrname.Reference) error {
	if err := gcrremote.Delete(ref,
		gcrremote.WithUserAgent(userAgent),
		gcrremote.WithAuthFromKeychain(c.keychain),
		gcrremote.WithContext(ctx)); err != nil {
		return err
	}

	return nil
}

// shouldDelete returns true if the manifest was created before the given
// timestamp and either has no tags or has tags that match the given filter.
func (c *Cleaner) shouldDelete(m *manifest, since time.Time, tagFilter TagFilter) bool {
	// Immediately exclude images that have been uploaded after the given time.
	if uploaded := m.Info.Uploaded.UTC(); uploaded.After(since) {
		c.logger.Debug("should not delete",
			"repo", m.Repo,
			"digest", m.Digest,
			"reason", "too new",
			"since", since.Format(time.RFC3339),
			"created", m.Info.Created.Format(time.RFC3339),
			"uploaded", uploaded.Format(time.RFC3339),
			"delta", uploaded.Sub(since).String())
		return false
	}

	// If there are no tags, it should be deleted.
	if len(m.Info.Tags) == 0 {
		c.logger.Debug("should delete",
			"repo", m.Repo,
			"digest", m.Digest,
			"reason", "no tags")
		return true
	}

	// If tagged images are allowed and the given filter matches the list of tags,
	// this is a deletion candidate. The default tag filter is to reject all
	// strings.
	if tagFilter.Matches(m.Info.Tags) {
		c.logger.Debug("should delete",
			"repo", m.Repo,
			"digest", m.Digest,
			"reason", "matches tag filter",
			"tags", m.Info.Tags,
			"tag_filter", tagFilter.Name())
		return true
	}

	// If we got this far, it'ts not a viable deletion candidate.
	c.logger.Debug("should not delete",
		"repo", m.Repo,
		"digest", m.Digest,
		"reason", "no filter matches")
	return false
}

// ListChildRepositories lists all child repositores for the given roots. Roots
// can be entire registries (e.g. us-docker.pkg.dev) or a subpath within a
// registry (e.g. gcr.io/my-project/my-container).
func (c *Cleaner) ListChildRepositories(ctx context.Context, roots []string) ([]string, error) {
	c.logger.Debug("finding all child repositories", "roots", roots)

	// registriesMap is a cache of registries to all the repos in that registry.
	// Since multiple repos might use the same registry, the result is cached to
	// limit upstream API calls.
	registriesMap := make(map[string]*gcrname.Registry, len(roots))

	// Iterate over each root and attempt to extract the registry component. Some
	// roots will be registries themselves whereas other roots could be a subpath
	// in a registry and we need to extract just the registry part.
	for _, root := range roots {
		registryName := ""

		parts := strings.Split(root, "/")
		switch len(parts) {
		case 0:
			panic("got 0 parts from string split (impossible)")
		case 1:
			// Most likely this is a registry, since it contains no slashes.
			registryName = parts[0]
		default:
			repo, err := gcrname.NewRepository(root, gcrname.StrictValidation)
			if err != nil {
				return nil, fmt.Errorf("failed to parse root repository %q: %w", root, err)
			}

			registryName = repo.RegistryStr()
		}

		registry, err := gcrname.NewRegistry(registryName)
		if err != nil {
			return nil, fmt.Errorf("failed to parse registry name %q: %w", registryName, err)
		}
		registriesMap[registryName] = &registry
	}

	// candidateRepos is the list of full repository names that match any of the
	// given root repositories. This list is appended to so the range function
	// below is psuedo-recursive.
	candidateRepos := make([]string, 0, len(roots))

	// Iterate through each registry, query the entire registry (yea, that's how
	// you "search"), and collect a list of candidate repos.
	for _, registry := range registriesMap {
		c.logger.Debug("listing child repositories for registry",
			"registry", registry.Name())

		// List all repos in the registry.
		allRepos, err := gcrremote.Catalog(ctx, *registry,
			gcrremote.WithUserAgent(userAgent),
			gcrremote.WithAuthFromKeychain(c.keychain),
			gcrremote.WithContext(ctx))
		if err != nil {
			return nil, fmt.Errorf("failed to fetch catalog for registry %q: %w", registry.Name(), err)
		}

		c.logger.Debug("found child repositories for registry",
			"registry", registry.Name(),
			"repos", allRepos)

		// Search through each repository and append any repository that matches any
		// of the prefixes defined by roots.
		for _, repo := range allRepos {
			// Compute the full repo name by appending the repo to the registry
			// identifier.
			fullRepoName := registry.Name() + "/" + repo

			hasPrefix := false
			for _, root := range roots {
				if strings.HasPrefix(fullRepoName, root) {
					hasPrefix = true
					break
				}
			}
			if hasPrefix {
				c.logger.Debug("appending new repository candidate",
					"registry", registry.Name(),
					"repo", repo)
				candidateRepos = append(candidateRepos, fullRepoName)
			} else {
				c.logger.Debug("skipping repository candidate (does not match any roots)",
					"registry", registry.Name(),
					"repo", repo)
			}
		}
	}

	// De-duplicate and sort the list.
	sort.Strings(candidateRepos)
	return candidateRepos, nil
}

// ErrsToError converts a list of errors into a single error. If the list is
// empty, it returns nil. If the list contains exactly one error, it returns
// that error. Otherwise it returns a bulleted list of the sorted errors, but
// the original error contexts are discarded.
func ErrsToError(errs []error) error {
	switch len(errs) {
	case 0:
		return nil
	case 1:
		return errs[0]
	default:
		errMessages := make([]string, len(errs))
		for i, err := range errs {
			errMessages[i] = err.Error()
		}
		sort.Strings(errMessages)

		var b strings.Builder
		fmt.Fprintf(&b, "%d errors occurred:\n", len(errs))
		for _, msg := range errMessages {
			fmt.Fprintf(&b, "  * %s\n", msg)
		}
		return fmt.Errorf(b.String())
	}
}
<|endoftext|>