<commit_before>package net

import (
	"io"

	conn "github.com/jbenet/go-ipfs/net/conn"
	// swarm "github.com/jbenet/go-ipfs/net/swarm2"
	peer "github.com/jbenet/go-ipfs/peer"

	context "github.com/jbenet/go-ipfs/Godeps/_workspace/src/code.google.com/p/go.net/context"
	ctxgroup "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-ctxgroup"
	ma "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multiaddr"
)

// ProtocolID is an identifier used to write protocol headers in streams.
type ProtocolID string

// These are the ProtocolIDs of the protocols running. It is useful
// to keep them in one place.
const (
	ProtocolBitswap ProtocolID = "/ipfs/bitswap"
	ProtocolDHT     ProtocolID = "/ipfs/dht"
	ProtocolDiag    ProtocolID = "/ipfs/diagnostics"
)

// MessageSizeMax is a soft (recommended) maximum for network messages.
// One can write more, as the interface is a stream. But it is useful
// to bunch it up into multiple read/writes when the whole message is
// a single, large serialized object.
const MessageSizeMax = 2 << 22 // 4MB

// Stream represents a bidirectional channel between two agents in
// the IPFS network. "agent" is as granular as desired, potentially
// being a "request -> reply" pair, or whole protocols.
// Streams are backed by SPDY streams underneath the hood.
type Stream interface {
	io.Reader
	io.Writer
	io.Closer

	// Conn returns the connection this stream is part of.
	Conn() Conn
}

// StreamHandler is the function protocols who wish to listen to
// incoming streams must implement.
type StreamHandler func(Stream)

type StreamHandlerMap map[ProtocolID]StreamHandler

// Conn is a connection to a remote peer. It multiplexes streams.
// Usually there is no need to use a Conn directly, but it may
// be useful to get information about the peer on the other side:
//  stream.Conn().RemotePeer()
type Conn interface {
	conn.PeerConn

	// NewStreamWithProtocol constructs a new Stream directly connected to p.
	NewStreamWithProtocol(pr ProtocolID, p peer.Peer) (Stream, error)
}

// Network is the interface IPFS uses for connecting to the world.
// It dials and listens for connections. it uses a Swarm to pool
// connnections (see swarm pkg, and peerstream.Swarm). Connections
// are encrypted with a TLS-like protocol.
type Network interface {
	Dialer
	io.Closer

	// SetHandler sets the protocol handler on the Network's Muxer.
	// This operation is threadsafe.
	SetHandler(ProtocolID, StreamHandler)

	// NewStream returns a new stream to given peer p.
	// If there is no connection to p, attempts to create one.
	// If ProtocolID is "", writes no header.
	NewStream(ProtocolID, peer.Peer) (Stream, error)

	// Peers returns the peers connected
	Peers() []peer.Peer

	// Conns returns the connections in this Netowrk
	Conns() []Conn

	// BandwidthTotals returns the total number of bytes passed through
	// the network since it was instantiated
	BandwidthTotals() (uint64, uint64)

	// ListenAddresses returns a list of addresses at which this network listens.
	ListenAddresses() []ma.Multiaddr

	// InterfaceListenAddresses returns a list of addresses at which this network
	// listens. It expands "any interface" addresses (/ip4/0.0.0.0, /ip6/::) to
	// use the known local interfaces.
	InterfaceListenAddresses() ([]ma.Multiaddr, error)

	// CtxGroup returns the network's contextGroup
	CtxGroup() ctxgroup.ContextGroup
}

// Dialer represents a service that can dial out to peers
// (this is usually just a Network, but other services may not need the whole
// stack, and thus it becomes easier to mock)
type Dialer interface {
	// LocalPeer returns the local peer associated with this network
	LocalPeer() peer.Peer

	// DialPeer attempts to establish a connection to a given peer
	DialPeer(context.Context, peer.Peer) error

	// ClosePeer closes the connection to a given peer
	ClosePeer(peer.Peer) error

	// Connectedness returns a state signaling connection capabilities
	Connectedness(peer.Peer) Connectedness
}

// Connectedness signals the capacity for a connection with a given node.
// It is used to signal to services and other peers whether a node is reachable.
type Connectedness int

const (
	// NotConnected means no connection to peer, and no extra information (default)
	NotConnected Connectedness = iota

	// Connected means has an open, live connection to peer
	Connected

	// CanConnect means recently connected to peer, terminated gracefully
	CanConnect

	// CannotConnect means recently attempted connecting but failed to connect.
	// (should signal "made effort, failed")
	CannotConnect
)
<commit_msg>net: testing protocol<commit_after>package net

import (
	"io"

	conn "github.com/jbenet/go-ipfs/net/conn"
	// swarm "github.com/jbenet/go-ipfs/net/swarm2"
	peer "github.com/jbenet/go-ipfs/peer"

	context "github.com/jbenet/go-ipfs/Godeps/_workspace/src/code.google.com/p/go.net/context"
	ctxgroup "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-ctxgroup"
	ma "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multiaddr"
)

// ProtocolID is an identifier used to write protocol headers in streams.
type ProtocolID string

// These are the ProtocolIDs of the protocols running. It is useful
// to keep them in one place.
const (
	ProtocolTesting ProtocolID = "/ipfs/testing"
	ProtocolBitswap ProtocolID = "/ipfs/bitswap"
	ProtocolDHT     ProtocolID = "/ipfs/dht"
	ProtocolDiag    ProtocolID = "/ipfs/diagnostics"
)

// MessageSizeMax is a soft (recommended) maximum for network messages.
// One can write more, as the interface is a stream. But it is useful
// to bunch it up into multiple read/writes when the whole message is
// a single, large serialized object.
const MessageSizeMax = 2 << 22 // 4MB

// Stream represents a bidirectional channel between two agents in
// the IPFS network. "agent" is as granular as desired, potentially
// being a "request -> reply" pair, or whole protocols.
// Streams are backed by SPDY streams underneath the hood.
type Stream interface {
	io.Reader
	io.Writer
	io.Closer

	// Conn returns the connection this stream is part of.
	Conn() Conn
}

// StreamHandler is the function protocols who wish to listen to
// incoming streams must implement.
type StreamHandler func(Stream)

type StreamHandlerMap map[ProtocolID]StreamHandler

// Conn is a connection to a remote peer. It multiplexes streams.
// Usually there is no need to use a Conn directly, but it may
// be useful to get information about the peer on the other side:
//  stream.Conn().RemotePeer()
type Conn interface {
	conn.PeerConn

	// NewStreamWithProtocol constructs a new Stream directly connected to p.
	NewStreamWithProtocol(pr ProtocolID, p peer.Peer) (Stream, error)
}

// Network is the interface IPFS uses for connecting to the world.
// It dials and listens for connections. it uses a Swarm to pool
// connnections (see swarm pkg, and peerstream.Swarm). Connections
// are encrypted with a TLS-like protocol.
type Network interface {
	Dialer
	io.Closer

	// SetHandler sets the protocol handler on the Network's Muxer.
	// This operation is threadsafe.
	SetHandler(ProtocolID, StreamHandler)

	// NewStream returns a new stream to given peer p.
	// If there is no connection to p, attempts to create one.
	// If ProtocolID is "", writes no header.
	NewStream(ProtocolID, peer.Peer) (Stream, error)

	// Peers returns the peers connected
	Peers() []peer.Peer

	// Conns returns the connections in this Netowrk
	Conns() []Conn

	// BandwidthTotals returns the total number of bytes passed through
	// the network since it was instantiated
	BandwidthTotals() (uint64, uint64)

	// ListenAddresses returns a list of addresses at which this network listens.
	ListenAddresses() []ma.Multiaddr

	// InterfaceListenAddresses returns a list of addresses at which this network
	// listens. It expands "any interface" addresses (/ip4/0.0.0.0, /ip6/::) to
	// use the known local interfaces.
	InterfaceListenAddresses() ([]ma.Multiaddr, error)

	// CtxGroup returns the network's contextGroup
	CtxGroup() ctxgroup.ContextGroup
}

// Dialer represents a service that can dial out to peers
// (this is usually just a Network, but other services may not need the whole
// stack, and thus it becomes easier to mock)
type Dialer interface {
	// LocalPeer returns the local peer associated with this network
	LocalPeer() peer.Peer

	// DialPeer attempts to establish a connection to a given peer
	DialPeer(context.Context, peer.Peer) error

	// ClosePeer closes the connection to a given peer
	ClosePeer(peer.Peer) error

	// Connectedness returns a state signaling connection capabilities
	Connectedness(peer.Peer) Connectedness
}

// Connectedness signals the capacity for a connection with a given node.
// It is used to signal to services and other peers whether a node is reachable.
type Connectedness int

const (
	// NotConnected means no connection to peer, and no extra information (default)
	NotConnected Connectedness = iota

	// Connected means has an open, live connection to peer
	Connected

	// CanConnect means recently connected to peer, terminated gracefully
	CanConnect

	// CannotConnect means recently attempted connecting but failed to connect.
	// (should signal "made effort, failed")
	CannotConnect
)
<|endoftext|>


<commit_before>// Copyright (c) 2014 Datacratic. All rights reserved.

package nfork

import (
	"github.com/nativetouch/goklog/klog"

	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"net"
	"net/http"
	"net/url"
	"strings"
	"sync"
	"syscall"
	"time"
)

// OutboundProperties contains information on the Outbound host
// as well as the outbound path to override the HTTP path.
type OutboundProperties struct {
	// Host is the address where HTTP requests
	// should be redirected to. Addresses should be of the for
	// <scheme>://<host>:<port>.
	Host string `json:"host"`
	// Path will be appended to the route of the host.
	// It will also override  the HTTP path.
	Path string `json:"path,omitempty"`
}

// DefaultInboundTimeout is used if no timeout is set for an inbound.
const DefaultInboundTimeout = 1 * time.Second

// Inbound duplicates HTTP requests to a set of outbounds and only forwards the
// HTTP response of an active outbound. All other responses are dropped.
//
// Stats are also gathered for each outbound and each outbounds can also be
// configured to timeout.
type Inbound struct {
	// Name is the name associated with this inbound.
	Name string

	// Listen defines which interface and port this inbound should listen on.
	Listen string

	// Outbound maps a set of outbound names to the address where HTTP requests
	// should be redirected to. Addresses should be of the for
	// <scheme>://<host>:<port>.
	// Optionally a path can be added which will be appended to the route of the
	// host.
	Outbound map[string]OutboundProperties

	// Active defines the name of the outbound whose response will be forwarded
	// back upstream. All other outbound responses are dropped.
	Active string

	// Timeout defines the timeout allowed for all outbounds. If the timeout
	// expires for the active outbound, TimeoutCode is sent back upstream.
	Timeout time.Duration

	// TimeoutCode is HTTP status code sent back upstream if a timeout occurs on
	// the active outbbound.
	TimeoutCode int

	// Client is the http.Client which will be used to forward HTTP requests to
	// all outbounds.
	Client *http.Client

	// IdleConnections defines the maximum size of the connection pool which
	// will be used by the http client for this inbound. Setting this will
	// overwrite the transport of the Client if it is set.
	IdleConnections int

	// Rate at which stats are updated.
	StatsUpdateRate time.Duration

	// ResponseHandler must be a function which handles the responses
	// from the different outbounds.
	ResponseHandler func(http.ResponseWriter, *http.Response, []byte, error, int)

	initialize sync.Once

	inboundStats StatsRecorder
	stats        map[string]*StatsRecorder
}

// Copy returns a copy of the inbound object.
func (inbound *Inbound) Copy() *Inbound {
	newInbound := &Inbound{
		Name: inbound.Name,

		Listen:   inbound.Listen,
		Active:   inbound.Active,
		Outbound: make(map[string]OutboundProperties),

		Timeout:         inbound.Timeout,
		TimeoutCode:     inbound.TimeoutCode,
		IdleConnections: inbound.IdleConnections,
		StatsUpdateRate: inbound.StatsUpdateRate,

		Client:       inbound.Client,
		stats:        make(map[string]*StatsRecorder),
		inboundStats: *inbound.inboundStats.Copy(),
	}

	for outbound, addr := range inbound.Outbound {
		newInbound.Outbound[outbound] = addr
	}

	for outbound, stats := range inbound.stats {
		newInbound.stats[outbound] = stats
	}

	return newInbound
}

// Validate returns an error if one of the Inbound invariants are not satisfied.
func (inbound *Inbound) Validate() error {
	if len(inbound.Name) == 0 {
		inbound.Name = inbound.Listen
	}

	if len(inbound.Listen) == 0 {
		return fmt.Errorf("missing listen host")
	}

	if len(inbound.Outbound) == 0 {
		return fmt.Errorf("no outbound in '%s'", inbound.Name)
	}

	if len(inbound.Active) == 0 {
		return fmt.Errorf("no active outbound in '%s'", inbound.Name)
	}

	if _, ok := inbound.Outbound[inbound.Active]; !ok {
		return fmt.Errorf("active outbound '%s' doesn't exist in '%s'", inbound.Active, inbound.Name)
	}

	return nil
}

// Init initializes the object. Inbounds are lazily initialized so calling this
// is optional.
func (inbound *Inbound) Init() {
	inbound.initialize.Do(inbound.init)
}

func (inbound *Inbound) init() {
	if inbound.Timeout == 0 {
		inbound.Timeout = DefaultInboundTimeout
	}

	if inbound.TimeoutCode == 0 {
		inbound.TimeoutCode = http.StatusServiceUnavailable
	}

	if inbound.Client == nil {
		inbound.Client = new(http.Client)
	}

	if inbound.IdleConnections > 0 {
		inbound.Client.Transport = httpTransport(inbound.IdleConnections)
	} else {
		inbound.IdleConnections = http.DefaultMaxIdleConnsPerHost
	}

	if inbound.Client.Timeout == 0 {
		inbound.Client.Timeout = inbound.Timeout
	}

	if inbound.StatsUpdateRate == 0 {
		inbound.StatsUpdateRate = DefaultSampleRate
	}

	if inbound.stats == nil {
		inbound.stats = make(map[string]*StatsRecorder)
	}

	for outbound := range inbound.Outbound {
		inbound.stats[outbound] = new(StatsRecorder)
		inbound.stats[outbound].Rate = inbound.StatsUpdateRate
	}

	if inbound.ResponseHandler == nil {
		inbound.ResponseHandler = func(writer http.ResponseWriter, respHead *http.Response, respBody []byte, err error, errorCode int) {
			if err != nil {
				http.Error(writer, err.Error(), errorCode)
				return
			}

			writerHeader := writer.Header()
			for key, val := range respHead.Header {
				writerHeader[key] = val
			}

			writer.WriteHeader(respHead.StatusCode)
			writer.Write(respBody)
		}
	}

	inbound.inboundStats.Rate = inbound.StatsUpdateRate
}

// ReadStats returns the stats associated with each outbounds.
func (inbound *Inbound) ReadStats() map[string]*Stats {
	stats := make(map[string]*Stats)

	for outbound, recorder := range inbound.stats {
		stats[outbound] = recorder.Read()
	}

	return stats
}

// ReadOutboundStats returns the stats associated with a given outbound.
func (inbound *Inbound) ReadOutboundStats(outbound string) (*Stats, error) {
	if _, ok := inbound.Outbound[outbound]; !ok {
		return nil, fmt.Errorf("unknown outbound '%s' for inbound '%s'", outbound, inbound.Name)
	}

	return inbound.stats[outbound].Read(), nil
}

// AddOutbound adds a new outbound associated with the given address. If the
// outbound already exists, it is overridden.
func (inbound *Inbound) AddOutbound(outbound, addr, path string) error {
	inbound.Outbound[outbound] = OutboundProperties{Host: addr, Path: path}
	inbound.stats[outbound] = new(StatsRecorder)
	inbound.stats[outbound].Rate = inbound.StatsUpdateRate
	return nil
}

// RemoveOutbound removes the given outbound. An error is returned if the
// outbound doesn't exist.
func (inbound *Inbound) RemoveOutbound(outbound string) error {
	if _, ok := inbound.Outbound[outbound]; !ok {
		return fmt.Errorf("unknown outbound '%s' for inbound '%s'", outbound, inbound.Name)
	}

	if outbound == inbound.Active {
		return fmt.Errorf("can't remove active outbound '%s' for inbound '%s'", outbound, inbound.Name)
	}

	delete(inbound.Outbound, outbound)
	delete(inbound.stats, outbound)

	return nil
}

// ActivateOutbound activates the given outbound.
func (inbound *Inbound) ActivateOutbound(outbound string) error {
	if _, ok := inbound.Outbound[outbound]; !ok {
		return fmt.Errorf("unknown outbound '%s' for inbound '%s'", outbound, inbound.Name)
	}

	inbound.Active = outbound

	return nil
}

// ServeHTTP forwards the given HTTP request to all the outbounds and forwards
// the response of the active outbound back upstream. All other responses are
// dropped.
func (inbound *Inbound) ServeHTTP(writer http.ResponseWriter, httpReq *http.Request) {
	inbound.Init()

	body, err := ioutil.ReadAll(httpReq.Body)
	if err != nil {
		inbound.ResponseHandler(writer, nil, []byte{}, err, http.StatusBadRequest)
		return
	}

	httpReq.Header.Set("X-Nfork", "true")

	inbound.recordInbound()

	var activeHost string
	var activePath string

	for outbound, host := range inbound.Outbound {
		if outbound != inbound.Active {
			go inbound.forward(outbound, httpReq, host.Host, host.Path, body)
		} else {
			activeHost = host.Host
			activePath = host.Path
		}
	}

	if len(activeHost) == 0 {
		log.Panicf("no active outbound '%s'", inbound.Active)
	}

	respHead, respBody, err := inbound.forward(inbound.Active, httpReq, activeHost, activePath, body)
	inbound.ResponseHandler(writer, respHead, respBody, err, inbound.TimeoutCode)
}

func (inbound *Inbound) record(outbound string, event Event) {
	stats, ok := inbound.stats[outbound]
	if !ok {
		log.Panicf("no stats for outbound '%s'", outbound)
	}
	stats.Record(event)
}

func (inbound *Inbound) recordInbound() {
	inbound.inboundStats.Record(Event{})
}

func addScheme(addr string) (schemedAddr string) {
	if i := strings.Index(addr, "://"); i >= 0 {
		return addr
	}
	return "http://" + addr
}

func addPath(addr, path string) (pathdAddr string) {
	if len(path) > 0 {
		return addr + "/" + path
	}
	return addr
}

func (inbound *Inbound) forward(
	outbound string, oldReq *http.Request, addr, path string, body []byte) (*http.Response, []byte, error) {

	t0 := time.Now()

	addr = addScheme(addr)
	addr = addPath(addr, path)
	parsedURL, err := url.ParseRequestURI(addr)
	if err != nil {
		return nil, nil, inbound.error("parse", addr, err, t0)
	}

	newReq := new(http.Request)
	*newReq = *oldReq

	newReq.URL = new(url.URL)
	newReq.Host = parsedURL.Host

	hasEnforcedPath := len(path) > 0
	if hasEnforcedPath {
		// Take the path added as a parameter as the new path where to forward
		// the messages
		*newReq.URL = *parsedURL
	} else {
		// Forward the messages to the same path as the incoming message but
		// make sure to send to the correct `Scheme` and `Host`.
		*newReq.URL = *oldReq.URL
		newReq.URL.Host = parsedURL.Host
		newReq.URL.Scheme = parsedURL.Scheme
	}

	newReq.RequestURI = ""
	newReq.Body = ioutil.NopCloser(bytes.NewReader(body))

	resp, err := inbound.Client.Do(newReq)
	if err != nil {
		return nil, nil, inbound.error("send", outbound, err, t0)
	}

	respBody, err := ioutil.ReadAll(resp.Body)
	resp.Body.Close()
	if err != nil {
		return nil, nil, inbound.error("recv", outbound, err, t0)
	}

	inbound.record(outbound, Event{Response: resp.StatusCode, Latency: time.Since(t0)})
	return resp, respBody, nil
}

func (inbound *Inbound) error(title, outbound string, err error, t0 time.Time) error {

	if urlErr, ok := err.(*url.Error); ok {
		return inbound.error(title, outbound, urlErr.Err, t0)

	} else if netErr, ok := err.(*net.OpError); ok {
		if errno, ok := netErr.Err.(syscall.Errno); ok && errno == syscall.ECONNREFUSED {
			klog.KPrintf(klog.Keyf("%s.%s.%s.timeout", inbound.Name, outbound, title), "%T -> %v", err, err)
			inbound.record(outbound, Event{Timeout: true, Latency: time.Since(t0)})
			return err
		}

		return inbound.error(title, outbound, netErr.Err, t0)
	}

	switch err.Error() {

	// Prevents spamming the logs with closed connections even though they were
	// not properly closed.
	case "EOF":
		inbound.record(outbound, Event{Error: true, Latency: time.Since(t0)})
		return err

	// I hate this but net and net/http provides no useful errors or indicators
	// that a request ended up in a timeout. Furthermore, most of the errors are
	// either not exported or are just randomly created as string. In other
	// words, this is a crappy interface that needs to be fixed bad.
	case "use of closed network connection": // net.errClosing
		fallthrough
	case "net/http: transport closed before response was received":
		fallthrough
	case "net/http: request canceled while waiting for connection":
		klog.KPrintf(klog.Keyf("%s.%s.%s.timeout", inbound.Name, outbound, title), "%T -> %v", err, err)
		inbound.record(outbound, Event{Timeout: true, Latency: time.Since(t0)})
		return err
	}

	klog.KPrintf(klog.Keyf("%s.%s.%s.error", inbound.Name, outbound, title), "%T -> %v", err, err)
	inbound.record(outbound, Event{Error: true, Latency: time.Since(t0)})
	return err
}

// UnmarshalJSON defines a custom JSON format for the encoding/json package.
func (inbound *Inbound) UnmarshalJSON(body []byte) (err error) {
	var inboundJSON struct {
		Name string `json:"name"`

		Listen   string                        `json:"listen"`
		Outbound map[string]OutboundProperties `json:"out"`
		Active   string                        `json:"active"`

		Timeout     string `json:"timeout,omitempty"`
		TimeoutCode int    `json:"timeoutCode,omitempty"`

		IdleConnections int `json:"idleConn"`
	}

	if err = json.Unmarshal(body, &inboundJSON); err != nil {
		return
	}

	inbound.Name = inboundJSON.Name

	inbound.Listen = inboundJSON.Listen
	inbound.Outbound = inboundJSON.Outbound
	inbound.Active = inboundJSON.Active

	if inbound.Timeout, err = time.ParseDuration(inboundJSON.Timeout); err != nil {
		return
	}
	inbound.TimeoutCode = inboundJSON.TimeoutCode

	inbound.IdleConnections = inboundJSON.IdleConnections

	return
}

// MarshalJSON defines a custom JSON format for the encoding/json package.
func (inbound *Inbound) MarshalJSON() ([]byte, error) {
	var inboundJSON struct {
		Name string `json:"name"`

		Listen   string                        `json:"listen"`
		Active   string                        `json:"active"`
		Outbound map[string]OutboundProperties `json:"out"`

		Timeout     string `json:"timeout,omitempty"`
		TimeoutCode int    `json:"timeoutCode,omitempty"`

		IdleConnections int `json:"idleConn"`
	}

	inboundJSON.Name = inbound.Name

	inboundJSON.Listen = inbound.Listen
	inboundJSON.Outbound = inbound.Outbound
	inboundJSON.Active = inbound.Active

	inboundJSON.Timeout = inbound.Timeout.String()
	inboundJSON.TimeoutCode = inbound.TimeoutCode

	inboundJSON.IdleConnections = inbound.IdleConnections

	return json.Marshal(&inboundJSON)
}
<commit_msg>Add context with inbound timeout to forwarded http request<commit_after>// Copyright (c) 2014 Datacratic. All rights reserved.

package nfork

import (
	"github.com/nativetouch/goklog/klog"

	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"net"
	"net/http"
	"net/url"
	"strings"
	"sync"
	"syscall"
	"time"
)

// OutboundProperties contains information on the Outbound host
// as well as the outbound path to override the HTTP path.
type OutboundProperties struct {
	// Host is the address where HTTP requests
	// should be redirected to. Addresses should be of the for
	// <scheme>://<host>:<port>.
	Host string `json:"host"`
	// Path will be appended to the route of the host.
	// It will also override  the HTTP path.
	Path string `json:"path,omitempty"`
}

// DefaultInboundTimeout is used if no timeout is set for an inbound.
const DefaultInboundTimeout = 1 * time.Second

// Inbound duplicates HTTP requests to a set of outbounds and only forwards the
// HTTP response of an active outbound. All other responses are dropped.
//
// Stats are also gathered for each outbound and each outbounds can also be
// configured to timeout.
type Inbound struct {
	// Name is the name associated with this inbound.
	Name string

	// Listen defines which interface and port this inbound should listen on.
	Listen string

	// Outbound maps a set of outbound names to the address where HTTP requests
	// should be redirected to. Addresses should be of the for
	// <scheme>://<host>:<port>.
	// Optionally a path can be added which will be appended to the route of the
	// host.
	Outbound map[string]OutboundProperties

	// Active defines the name of the outbound whose response will be forwarded
	// back upstream. All other outbound responses are dropped.
	Active string

	// Timeout defines the timeout allowed for all outbounds. If the timeout
	// expires for the active outbound, TimeoutCode is sent back upstream.
	Timeout time.Duration

	// TimeoutCode is HTTP status code sent back upstream if a timeout occurs on
	// the active outbbound.
	TimeoutCode int

	// Client is the http.Client which will be used to forward HTTP requests to
	// all outbounds.
	Client *http.Client

	// IdleConnections defines the maximum size of the connection pool which
	// will be used by the http client for this inbound. Setting this will
	// overwrite the transport of the Client if it is set.
	IdleConnections int

	// Rate at which stats are updated.
	StatsUpdateRate time.Duration

	// ResponseHandler must be a function which handles the responses
	// from the different outbounds.
	ResponseHandler func(http.ResponseWriter, *http.Response, []byte, error, int)

	initialize sync.Once

	inboundStats StatsRecorder
	stats        map[string]*StatsRecorder
}

// Copy returns a copy of the inbound object.
func (inbound *Inbound) Copy() *Inbound {
	newInbound := &Inbound{
		Name: inbound.Name,

		Listen:   inbound.Listen,
		Active:   inbound.Active,
		Outbound: make(map[string]OutboundProperties),

		Timeout:         inbound.Timeout,
		TimeoutCode:     inbound.TimeoutCode,
		IdleConnections: inbound.IdleConnections,
		StatsUpdateRate: inbound.StatsUpdateRate,

		Client:       inbound.Client,
		stats:        make(map[string]*StatsRecorder),
		inboundStats: *inbound.inboundStats.Copy(),
	}

	for outbound, addr := range inbound.Outbound {
		newInbound.Outbound[outbound] = addr
	}

	for outbound, stats := range inbound.stats {
		newInbound.stats[outbound] = stats
	}

	return newInbound
}

// Validate returns an error if one of the Inbound invariants are not satisfied.
func (inbound *Inbound) Validate() error {
	if len(inbound.Name) == 0 {
		inbound.Name = inbound.Listen
	}

	if len(inbound.Listen) == 0 {
		return fmt.Errorf("missing listen host")
	}

	if len(inbound.Outbound) == 0 {
		return fmt.Errorf("no outbound in '%s'", inbound.Name)
	}

	if len(inbound.Active) == 0 {
		return fmt.Errorf("no active outbound in '%s'", inbound.Name)
	}

	if _, ok := inbound.Outbound[inbound.Active]; !ok {
		return fmt.Errorf("active outbound '%s' doesn't exist in '%s'", inbound.Active, inbound.Name)
	}

	return nil
}

// Init initializes the object. Inbounds are lazily initialized so calling this
// is optional.
func (inbound *Inbound) Init() {
	inbound.initialize.Do(inbound.init)
}

func (inbound *Inbound) init() {
	if inbound.Timeout == 0 {
		inbound.Timeout = DefaultInboundTimeout
	}

	if inbound.TimeoutCode == 0 {
		inbound.TimeoutCode = http.StatusServiceUnavailable
	}

	if inbound.Client == nil {
		inbound.Client = new(http.Client)
	}

	if inbound.IdleConnections > 0 {
		inbound.Client.Transport = httpTransport(inbound.IdleConnections)
	} else {
		inbound.IdleConnections = http.DefaultMaxIdleConnsPerHost
	}

	if inbound.Client.Timeout == 0 {
		inbound.Client.Timeout = inbound.Timeout
	}

	if inbound.StatsUpdateRate == 0 {
		inbound.StatsUpdateRate = DefaultSampleRate
	}

	if inbound.stats == nil {
		inbound.stats = make(map[string]*StatsRecorder)
	}

	for outbound := range inbound.Outbound {
		inbound.stats[outbound] = new(StatsRecorder)
		inbound.stats[outbound].Rate = inbound.StatsUpdateRate
	}

	if inbound.ResponseHandler == nil {
		inbound.ResponseHandler = func(writer http.ResponseWriter, respHead *http.Response, respBody []byte, err error, errorCode int) {
			if err != nil {
				http.Error(writer, err.Error(), errorCode)
				return
			}

			writerHeader := writer.Header()
			for key, val := range respHead.Header {
				writerHeader[key] = val
			}

			writer.WriteHeader(respHead.StatusCode)
			writer.Write(respBody)
		}
	}

	inbound.inboundStats.Rate = inbound.StatsUpdateRate
}

// ReadStats returns the stats associated with each outbounds.
func (inbound *Inbound) ReadStats() map[string]*Stats {
	stats := make(map[string]*Stats)

	for outbound, recorder := range inbound.stats {
		stats[outbound] = recorder.Read()
	}

	return stats
}

// ReadOutboundStats returns the stats associated with a given outbound.
func (inbound *Inbound) ReadOutboundStats(outbound string) (*Stats, error) {
	if _, ok := inbound.Outbound[outbound]; !ok {
		return nil, fmt.Errorf("unknown outbound '%s' for inbound '%s'", outbound, inbound.Name)
	}

	return inbound.stats[outbound].Read(), nil
}

// AddOutbound adds a new outbound associated with the given address. If the
// outbound already exists, it is overridden.
func (inbound *Inbound) AddOutbound(outbound, addr, path string) error {
	inbound.Outbound[outbound] = OutboundProperties{Host: addr, Path: path}
	inbound.stats[outbound] = new(StatsRecorder)
	inbound.stats[outbound].Rate = inbound.StatsUpdateRate
	return nil
}

// RemoveOutbound removes the given outbound. An error is returned if the
// outbound doesn't exist.
func (inbound *Inbound) RemoveOutbound(outbound string) error {
	if _, ok := inbound.Outbound[outbound]; !ok {
		return fmt.Errorf("unknown outbound '%s' for inbound '%s'", outbound, inbound.Name)
	}

	if outbound == inbound.Active {
		return fmt.Errorf("can't remove active outbound '%s' for inbound '%s'", outbound, inbound.Name)
	}

	delete(inbound.Outbound, outbound)
	delete(inbound.stats, outbound)

	return nil
}

// ActivateOutbound activates the given outbound.
func (inbound *Inbound) ActivateOutbound(outbound string) error {
	if _, ok := inbound.Outbound[outbound]; !ok {
		return fmt.Errorf("unknown outbound '%s' for inbound '%s'", outbound, inbound.Name)
	}

	inbound.Active = outbound

	return nil
}

// ServeHTTP forwards the given HTTP request to all the outbounds and forwards
// the response of the active outbound back upstream. All other responses are
// dropped.
func (inbound *Inbound) ServeHTTP(writer http.ResponseWriter, httpReq *http.Request) {
	inbound.Init()

	body, err := ioutil.ReadAll(httpReq.Body)
	if err != nil {
		inbound.ResponseHandler(writer, nil, []byte{}, err, http.StatusBadRequest)
		return
	}

	httpReq.Header.Set("X-Nfork", "true")

	inbound.recordInbound()

	var activeHost string
	var activePath string

	for outbound, host := range inbound.Outbound {
		if outbound != inbound.Active {
			go inbound.forward(outbound, httpReq, host.Host, host.Path, body)
		} else {
			activeHost = host.Host
			activePath = host.Path
		}
	}

	if len(activeHost) == 0 {
		log.Panicf("no active outbound '%s'", inbound.Active)
	}

	respHead, respBody, err := inbound.forward(inbound.Active, httpReq, activeHost, activePath, body)
	inbound.ResponseHandler(writer, respHead, respBody, err, inbound.TimeoutCode)
}

func (inbound *Inbound) record(outbound string, event Event) {
	stats, ok := inbound.stats[outbound]
	if !ok {
		log.Panicf("no stats for outbound '%s'", outbound)
	}
	stats.Record(event)
}

func (inbound *Inbound) recordInbound() {
	inbound.inboundStats.Record(Event{})
}

func addScheme(addr string) (schemedAddr string) {
	if i := strings.Index(addr, "://"); i >= 0 {
		return addr
	}
	return "http://" + addr
}

func addPath(addr, path string) (pathdAddr string) {
	if len(path) > 0 {
		return addr + "/" + path
	}
	return addr
}

func (inbound *Inbound) forward(
	outbound string, oldReq *http.Request, addr, path string, body []byte) (*http.Response, []byte, error) {

	t0 := time.Now()

	addr = addScheme(addr)
	addr = addPath(addr, path)
	parsedURL, err := url.ParseRequestURI(addr)
	if err != nil {
		return nil, nil, inbound.error("parse", addr, err, t0)
	}

	ctx, cancelFunc := context.WithTimeout(context.Background(), inbound.Timeout)
	defer cancelFunc()

	newReq := oldReq.WithContext(ctx)

	newReq.URL = new(url.URL)
	newReq.Host = parsedURL.Host

	hasEnforcedPath := len(path) > 0
	if hasEnforcedPath {
		// Take the path added as a parameter as the new path where to forward
		// the messages
		*newReq.URL = *parsedURL
	} else {
		// Forward the messages to the same path as the incoming message but
		// make sure to send to the correct `Scheme` and `Host`.
		*newReq.URL = *oldReq.URL
		newReq.URL.Host = parsedURL.Host
		newReq.URL.Scheme = parsedURL.Scheme
	}

	newReq.RequestURI = ""
	newReq.Body = ioutil.NopCloser(bytes.NewReader(body))

	resp, err := inbound.Client.Do(newReq)
	if err != nil {
		return nil, nil, inbound.error("send", outbound, err, t0)
	}

	respBody, err := ioutil.ReadAll(resp.Body)
	resp.Body.Close()
	if err != nil {
		return nil, nil, inbound.error("recv", outbound, err, t0)
	}

	inbound.record(outbound, Event{Response: resp.StatusCode, Latency: time.Since(t0)})
	return resp, respBody, nil
}

func (inbound *Inbound) error(title, outbound string, err error, t0 time.Time) error {

	if urlErr, ok := err.(*url.Error); ok {
		return inbound.error(title, outbound, urlErr.Err, t0)

	} else if netErr, ok := err.(*net.OpError); ok {
		if errno, ok := netErr.Err.(syscall.Errno); ok && errno == syscall.ECONNREFUSED {
			klog.KPrintf(klog.Keyf("%s.%s.%s.timeout", inbound.Name, outbound, title), "%T -> %v", err, err)
			inbound.record(outbound, Event{Timeout: true, Latency: time.Since(t0)})
			return err
		}

		return inbound.error(title, outbound, netErr.Err, t0)
	}

	switch err.Error() {

	// Prevents spamming the logs with closed connections even though they were
	// not properly closed.
	case "EOF":
		inbound.record(outbound, Event{Error: true, Latency: time.Since(t0)})
		return err

	// I hate this but net and net/http provides no useful errors or indicators
	// that a request ended up in a timeout. Furthermore, most of the errors are
	// either not exported or are just randomly created as string. In other
	// words, this is a crappy interface that needs to be fixed bad.
	case "use of closed network connection": // net.errClosing
		fallthrough
	case "net/http: transport closed before response was received":
		fallthrough
	case "net/http: request canceled while waiting for connection":
		klog.KPrintf(klog.Keyf("%s.%s.%s.timeout", inbound.Name, outbound, title), "%T -> %v", err, err)
		inbound.record(outbound, Event{Timeout: true, Latency: time.Since(t0)})
		return err
	}

	klog.KPrintf(klog.Keyf("%s.%s.%s.error", inbound.Name, outbound, title), "%T -> %v", err, err)
	inbound.record(outbound, Event{Error: true, Latency: time.Since(t0)})
	return err
}

// UnmarshalJSON defines a custom JSON format for the encoding/json package.
func (inbound *Inbound) UnmarshalJSON(body []byte) (err error) {
	var inboundJSON struct {
		Name string `json:"name"`

		Listen   string                        `json:"listen"`
		Outbound map[string]OutboundProperties `json:"out"`
		Active   string                        `json:"active"`

		Timeout     string `json:"timeout,omitempty"`
		TimeoutCode int    `json:"timeoutCode,omitempty"`

		IdleConnections int `json:"idleConn"`
	}

	if err = json.Unmarshal(body, &inboundJSON); err != nil {
		return
	}

	inbound.Name = inboundJSON.Name

	inbound.Listen = inboundJSON.Listen
	inbound.Outbound = inboundJSON.Outbound
	inbound.Active = inboundJSON.Active

	if inbound.Timeout, err = time.ParseDuration(inboundJSON.Timeout); err != nil {
		return
	}
	inbound.TimeoutCode = inboundJSON.TimeoutCode

	inbound.IdleConnections = inboundJSON.IdleConnections

	return
}

// MarshalJSON defines a custom JSON format for the encoding/json package.
func (inbound *Inbound) MarshalJSON() ([]byte, error) {
	var inboundJSON struct {
		Name string `json:"name"`

		Listen   string                        `json:"listen"`
		Active   string                        `json:"active"`
		Outbound map[string]OutboundProperties `json:"out"`

		Timeout     string `json:"timeout,omitempty"`
		TimeoutCode int    `json:"timeoutCode,omitempty"`

		IdleConnections int `json:"idleConn"`
	}

	inboundJSON.Name = inbound.Name

	inboundJSON.Listen = inbound.Listen
	inboundJSON.Outbound = inbound.Outbound
	inboundJSON.Active = inbound.Active

	inboundJSON.Timeout = inbound.Timeout.String()
	inboundJSON.TimeoutCode = inbound.TimeoutCode

	inboundJSON.IdleConnections = inbound.IdleConnections

	return json.Marshal(&inboundJSON)
}
<|endoftext|>


<commit_before>package npc

import (
	"encoding/json"

	"k8s.io/client-go/pkg/api/unversioned"
	coreapi "k8s.io/client-go/pkg/api/v1"
	extnapi "k8s.io/client-go/pkg/apis/extensions/v1beta1"
	"k8s.io/client-go/pkg/types"
	"k8s.io/client-go/pkg/util/uuid"

	"github.com/weaveworks/weave/common"
	"github.com/weaveworks/weave/npc/ipset"
	"github.com/weaveworks/weave/npc/iptables"
)

type ns struct {
	ipt iptables.Interface // interface to iptables
	ips ipset.Interface    // interface to ipset

	name      string                               // k8s Namespace name
	nodeName  string                               // my node name
	namespace *coreapi.Namespace                   // k8s Namespace object
	pods      map[types.UID]*coreapi.Pod           // k8s Pod objects by UID
	policies  map[types.UID]*extnapi.NetworkPolicy // k8s NetworkPolicy objects by UID

	uid     types.UID     // surrogate UID to own allPods selector
	allPods *selectorSpec // hash:ip ipset of all pod IPs in this namespace

	nsSelectors  *selectorSet
	podSelectors *selectorSet
	rules        *ruleSet
}

func newNS(name, nodeName string, ipt iptables.Interface, ips ipset.Interface, nsSelectors *selectorSet) (*ns, error) {
	allPods, err := newSelectorSpec(&unversioned.LabelSelector{}, name, ipset.HashIP)
	if err != nil {
		return nil, err
	}

	ns := &ns{
		ipt:         ipt,
		ips:         ips,
		name:        name,
		nodeName:    nodeName,
		pods:        make(map[types.UID]*coreapi.Pod),
		policies:    make(map[types.UID]*extnapi.NetworkPolicy),
		uid:         uuid.NewUUID(),
		allPods:     allPods,
		nsSelectors: nsSelectors,
		rules:       newRuleSet(ipt)}

	ns.podSelectors = newSelectorSet(ips, ns.onNewPodSelector)

	if err := ns.podSelectors.provision(ns.uid, nil, map[string]*selectorSpec{ns.allPods.key: ns.allPods}); err != nil {
		return nil, err
	}

	return ns, nil
}

func (ns *ns) empty() bool {
	return len(ns.pods) == 0 && len(ns.policies) == 0 && ns.namespace == nil
}

func (ns *ns) destroy() error {
	if err := ns.podSelectors.deprovision(ns.uid, map[string]*selectorSpec{ns.allPods.key: ns.allPods}, nil); err != nil {
		return err
	}
	return nil
}

func (ns *ns) onNewPodSelector(selector *selector) error {
	for _, pod := range ns.pods {
		if hasIP(pod) {
			if selector.matches(pod.ObjectMeta.Labels) {
				if err := selector.addEntry(pod.Status.PodIP); err != nil {
					return err
				}
			}
		}
	}
	return nil
}

func (ns *ns) checkLocalPod(obj *coreapi.Pod) bool {
	if obj.Spec.NodeName != ns.nodeName {
		return false
	}
	return true
}

func (ns *ns) addPod(obj *coreapi.Pod) error {
	ns.pods[obj.ObjectMeta.UID] = obj

	if !hasIP(obj) {
		return nil
	}

	if ns.checkLocalPod(obj) {
		ns.ips.AddEntry(LocalIpset, obj.Status.PodIP)
	}
	return ns.podSelectors.addToMatching(obj.ObjectMeta.Labels, obj.Status.PodIP)
}

func (ns *ns) updatePod(oldObj, newObj *coreapi.Pod) error {
	delete(ns.pods, oldObj.ObjectMeta.UID)
	ns.pods[newObj.ObjectMeta.UID] = newObj

	if !hasIP(oldObj) && !hasIP(newObj) {
		return nil
	}

	if hasIP(oldObj) && !hasIP(newObj) {
		if ns.checkLocalPod(oldObj) {
			ns.ips.DelEntry(LocalIpset, oldObj.Status.PodIP)
		}
		return ns.podSelectors.delFromMatching(oldObj.ObjectMeta.Labels, oldObj.Status.PodIP)
	}

	if !hasIP(oldObj) && hasIP(newObj) {
		if ns.checkLocalPod(newObj) {
			ns.ips.AddEntry(LocalIpset, newObj.Status.PodIP)
		}
		return ns.podSelectors.addToMatching(newObj.ObjectMeta.Labels, newObj.Status.PodIP)
	}

	if !equals(oldObj.ObjectMeta.Labels, newObj.ObjectMeta.Labels) ||
		oldObj.Status.PodIP != newObj.Status.PodIP {

		for _, ps := range ns.podSelectors.entries {
			oldMatch := ps.matches(oldObj.ObjectMeta.Labels)
			newMatch := ps.matches(newObj.ObjectMeta.Labels)
			if oldMatch == newMatch && oldObj.Status.PodIP == newObj.Status.PodIP {
				continue
			}
			if oldMatch {
				if err := ps.delEntry(oldObj.Status.PodIP); err != nil {
					return err
				}
			}
			if newMatch {
				if err := ps.addEntry(newObj.Status.PodIP); err != nil {
					return err
				}
			}
		}
	}

	return nil
}

func (ns *ns) deletePod(obj *coreapi.Pod) error {
	delete(ns.pods, obj.ObjectMeta.UID)

	if !hasIP(obj) {
		return nil
	}

	if ns.checkLocalPod(obj) {
		ns.ips.DelEntry(LocalIpset, obj.Status.PodIP)
	}
	return ns.podSelectors.delFromMatching(obj.ObjectMeta.Labels, obj.Status.PodIP)
}

func (ns *ns) addNetworkPolicy(obj *extnapi.NetworkPolicy) error {
	ns.policies[obj.ObjectMeta.UID] = obj

	// Analyse policy, determine which rules and ipsets are required
	rules, nsSelectors, podSelectors, err := ns.analysePolicy(obj)
	if err != nil {
		return err
	}

	// Provision required resources in dependency order
	if err := ns.nsSelectors.provision(obj.ObjectMeta.UID, nil, nsSelectors); err != nil {
		return err
	}
	if err := ns.podSelectors.provision(obj.ObjectMeta.UID, nil, podSelectors); err != nil {
		return err
	}
	if err := ns.rules.provision(obj.ObjectMeta.UID, nil, rules); err != nil {
		return err
	}

	return nil
}

func (ns *ns) updateNetworkPolicy(oldObj, newObj *extnapi.NetworkPolicy) error {
	delete(ns.policies, oldObj.ObjectMeta.UID)
	ns.policies[newObj.ObjectMeta.UID] = newObj

	// Analyse the old and the new policy so we can determine differences
	oldRules, oldNsSelectors, oldPodSelectors, err := ns.analysePolicy(oldObj)
	if err != nil {
		return err
	}
	newRules, newNsSelectors, newPodSelectors, err := ns.analysePolicy(newObj)
	if err != nil {
		return err
	}

	// Deprovision unused and provision newly required resources in dependency order
	if err := ns.rules.deprovision(oldObj.ObjectMeta.UID, oldRules, newRules); err != nil {
		return err
	}
	if err := ns.nsSelectors.deprovision(oldObj.ObjectMeta.UID, oldNsSelectors, newNsSelectors); err != nil {
		return err
	}
	if err := ns.podSelectors.deprovision(oldObj.ObjectMeta.UID, oldPodSelectors, newPodSelectors); err != nil {
		return err
	}
	if err := ns.nsSelectors.provision(oldObj.ObjectMeta.UID, oldNsSelectors, newNsSelectors); err != nil {
		return err
	}
	if err := ns.podSelectors.provision(oldObj.ObjectMeta.UID, oldPodSelectors, newPodSelectors); err != nil {
		return err
	}
	if err := ns.rules.provision(oldObj.ObjectMeta.UID, oldRules, newRules); err != nil {
		return err
	}

	return nil
}

func (ns *ns) deleteNetworkPolicy(obj *extnapi.NetworkPolicy) error {
	delete(ns.policies, obj.ObjectMeta.UID)

	// Analyse network policy to free resources
	rules, nsSelectors, podSelectors, err := ns.analysePolicy(obj)
	if err != nil {
		return err
	}

	// Deprovision unused resources in dependency order
	if err := ns.rules.deprovision(obj.ObjectMeta.UID, rules, nil); err != nil {
		return err
	}
	if err := ns.nsSelectors.deprovision(obj.ObjectMeta.UID, nsSelectors, nil); err != nil {
		return err
	}
	if err := ns.podSelectors.deprovision(obj.ObjectMeta.UID, podSelectors, nil); err != nil {
		return err
	}

	return nil
}

func bypassRule(nsIpsetName ipset.Name) []string {
	return []string{"-m", "set", "--match-set", string(nsIpsetName), "dst", "-j", "ACCEPT"}
}

func (ns *ns) ensureBypassRule(nsIpsetName ipset.Name) error {
	common.Log.Debugf("ensuring rule for DefaultAllow in namespace: %s, set %s", ns.name, nsIpsetName)
	return ns.ipt.Append(TableFilter, DefaultChain, bypassRule(ns.allPods.ipsetName)...)
}

func (ns *ns) deleteBypassRule(nsIpsetName ipset.Name) error {
	common.Log.Debugf("removing default rule in namespace: %s, set %s", ns.name, nsIpsetName)
	return ns.ipt.Delete(TableFilter, DefaultChain, bypassRule(ns.allPods.ipsetName)...)
}

func (ns *ns) addNamespace(obj *coreapi.Namespace) error {
	ns.namespace = obj

	// Insert a rule to bypass policies if namespace is DefaultAllow
	if !isDefaultDeny(obj) {
		return ns.ensureBypassRule(ns.allPods.ipsetName)
	}

	// Add namespace ipset to matching namespace selectors
	return ns.nsSelectors.addToMatching(obj.ObjectMeta.Labels, string(ns.allPods.ipsetName))
}

func (ns *ns) updateNamespace(oldObj, newObj *coreapi.Namespace) error {
	ns.namespace = newObj

	// Update bypass rule if ingress default has changed
	oldDefaultDeny := isDefaultDeny(oldObj)
	newDefaultDeny := isDefaultDeny(newObj)

	if oldDefaultDeny != newDefaultDeny {
		common.Log.Infof("namespace DefaultDeny changed from %t to %t", oldDefaultDeny, newDefaultDeny)
		if oldDefaultDeny {
			return ns.ensureBypassRule(ns.allPods.ipsetName)
		}
		if newDefaultDeny {
			return ns.deleteBypassRule(ns.allPods.ipsetName)
		}
	}

	// Re-evaluate namespace selector membership if labels have changed
	if !equals(oldObj.ObjectMeta.Labels, newObj.ObjectMeta.Labels) {
		for _, selector := range ns.nsSelectors.entries {
			oldMatch := selector.matches(oldObj.ObjectMeta.Labels)
			newMatch := selector.matches(newObj.ObjectMeta.Labels)
			if oldMatch == newMatch {
				continue
			}
			if oldMatch {
				if err := selector.delEntry(string(ns.allPods.ipsetName)); err != nil {
					return err
				}
			}
			if newMatch {
				if err := selector.addEntry(string(ns.allPods.ipsetName)); err != nil {
					return err
				}
			}
		}
	}

	return nil
}

func (ns *ns) deleteNamespace(obj *coreapi.Namespace) error {
	ns.namespace = nil

	// Remove bypass rule
	if !isDefaultDeny(obj) {
		return ns.deleteBypassRule(ns.allPods.ipsetName)
	}

	// Remove namespace ipset from any matching namespace selectors
	return ns.nsSelectors.delFromMatching(obj.ObjectMeta.Labels, string(ns.allPods.ipsetName))
}

func hasIP(pod *coreapi.Pod) bool {
	// Ensure pod isn't dead, has an IP address and isn't sharing the host network namespace
	return pod.Status.Phase != "Succeeded" && pod.Status.Phase != "Failed" &&
		len(pod.Status.PodIP) > 0 && !pod.Spec.HostNetwork
}

func equals(a, b map[string]string) bool {
	if len(a) != len(b) {
		return false
	}
	for ak, av := range a {
		if b[ak] != av {
			return false
		}
	}
	return true
}

func isDefaultDeny(namespace *coreapi.Namespace) bool {
	nnpJSON, found := namespace.ObjectMeta.Annotations["net.beta.kubernetes.io/network-policy"]
	if !found {
		return false
	}

	var nnp NamespaceNetworkPolicy
	if err := json.Unmarshal([]byte(nnpJSON), &nnp); err != nil {
		common.Log.Warn("Ignoring network policy annotation: unmarshal failed:", err)
		// If we can't understand the annotation, behave as if it isn't present
		return false
	}

	return nnp.Ingress != nil &&
		nnp.Ingress.Isolation != nil &&
		*(nnp.Ingress.Isolation) == DefaultDeny
}
<commit_msg>Don't bypass selector provisioning when not default deny<commit_after>package npc

import (
	"encoding/json"

	"k8s.io/client-go/pkg/api/unversioned"
	coreapi "k8s.io/client-go/pkg/api/v1"
	extnapi "k8s.io/client-go/pkg/apis/extensions/v1beta1"
	"k8s.io/client-go/pkg/types"
	"k8s.io/client-go/pkg/util/uuid"

	"github.com/weaveworks/weave/common"
	"github.com/weaveworks/weave/npc/ipset"
	"github.com/weaveworks/weave/npc/iptables"
)

type ns struct {
	ipt iptables.Interface // interface to iptables
	ips ipset.Interface    // interface to ipset

	name      string                               // k8s Namespace name
	nodeName  string                               // my node name
	namespace *coreapi.Namespace                   // k8s Namespace object
	pods      map[types.UID]*coreapi.Pod           // k8s Pod objects by UID
	policies  map[types.UID]*extnapi.NetworkPolicy // k8s NetworkPolicy objects by UID

	uid     types.UID     // surrogate UID to own allPods selector
	allPods *selectorSpec // hash:ip ipset of all pod IPs in this namespace

	nsSelectors  *selectorSet
	podSelectors *selectorSet
	rules        *ruleSet
}

func newNS(name, nodeName string, ipt iptables.Interface, ips ipset.Interface, nsSelectors *selectorSet) (*ns, error) {
	allPods, err := newSelectorSpec(&unversioned.LabelSelector{}, name, ipset.HashIP)
	if err != nil {
		return nil, err
	}

	ns := &ns{
		ipt:         ipt,
		ips:         ips,
		name:        name,
		nodeName:    nodeName,
		pods:        make(map[types.UID]*coreapi.Pod),
		policies:    make(map[types.UID]*extnapi.NetworkPolicy),
		uid:         uuid.NewUUID(),
		allPods:     allPods,
		nsSelectors: nsSelectors,
		rules:       newRuleSet(ipt)}

	ns.podSelectors = newSelectorSet(ips, ns.onNewPodSelector)

	if err := ns.podSelectors.provision(ns.uid, nil, map[string]*selectorSpec{ns.allPods.key: ns.allPods}); err != nil {
		return nil, err
	}

	return ns, nil
}

func (ns *ns) empty() bool {
	return len(ns.pods) == 0 && len(ns.policies) == 0 && ns.namespace == nil
}

func (ns *ns) destroy() error {
	if err := ns.podSelectors.deprovision(ns.uid, map[string]*selectorSpec{ns.allPods.key: ns.allPods}, nil); err != nil {
		return err
	}
	return nil
}

func (ns *ns) onNewPodSelector(selector *selector) error {
	for _, pod := range ns.pods {
		if hasIP(pod) {
			if selector.matches(pod.ObjectMeta.Labels) {
				if err := selector.addEntry(pod.Status.PodIP); err != nil {
					return err
				}
			}
		}
	}
	return nil
}

func (ns *ns) checkLocalPod(obj *coreapi.Pod) bool {
	if obj.Spec.NodeName != ns.nodeName {
		return false
	}
	return true
}

func (ns *ns) addPod(obj *coreapi.Pod) error {
	ns.pods[obj.ObjectMeta.UID] = obj

	if !hasIP(obj) {
		return nil
	}

	if ns.checkLocalPod(obj) {
		ns.ips.AddEntry(LocalIpset, obj.Status.PodIP)
	}
	return ns.podSelectors.addToMatching(obj.ObjectMeta.Labels, obj.Status.PodIP)
}

func (ns *ns) updatePod(oldObj, newObj *coreapi.Pod) error {
	delete(ns.pods, oldObj.ObjectMeta.UID)
	ns.pods[newObj.ObjectMeta.UID] = newObj

	if !hasIP(oldObj) && !hasIP(newObj) {
		return nil
	}

	if hasIP(oldObj) && !hasIP(newObj) {
		if ns.checkLocalPod(oldObj) {
			ns.ips.DelEntry(LocalIpset, oldObj.Status.PodIP)
		}
		return ns.podSelectors.delFromMatching(oldObj.ObjectMeta.Labels, oldObj.Status.PodIP)
	}

	if !hasIP(oldObj) && hasIP(newObj) {
		if ns.checkLocalPod(newObj) {
			ns.ips.AddEntry(LocalIpset, newObj.Status.PodIP)
		}
		return ns.podSelectors.addToMatching(newObj.ObjectMeta.Labels, newObj.Status.PodIP)
	}

	if !equals(oldObj.ObjectMeta.Labels, newObj.ObjectMeta.Labels) ||
		oldObj.Status.PodIP != newObj.Status.PodIP {

		for _, ps := range ns.podSelectors.entries {
			oldMatch := ps.matches(oldObj.ObjectMeta.Labels)
			newMatch := ps.matches(newObj.ObjectMeta.Labels)
			if oldMatch == newMatch && oldObj.Status.PodIP == newObj.Status.PodIP {
				continue
			}
			if oldMatch {
				if err := ps.delEntry(oldObj.Status.PodIP); err != nil {
					return err
				}
			}
			if newMatch {
				if err := ps.addEntry(newObj.Status.PodIP); err != nil {
					return err
				}
			}
		}
	}

	return nil
}

func (ns *ns) deletePod(obj *coreapi.Pod) error {
	delete(ns.pods, obj.ObjectMeta.UID)

	if !hasIP(obj) {
		return nil
	}

	if ns.checkLocalPod(obj) {
		ns.ips.DelEntry(LocalIpset, obj.Status.PodIP)
	}
	return ns.podSelectors.delFromMatching(obj.ObjectMeta.Labels, obj.Status.PodIP)
}

func (ns *ns) addNetworkPolicy(obj *extnapi.NetworkPolicy) error {
	ns.policies[obj.ObjectMeta.UID] = obj

	// Analyse policy, determine which rules and ipsets are required
	rules, nsSelectors, podSelectors, err := ns.analysePolicy(obj)
	if err != nil {
		return err
	}

	// Provision required resources in dependency order
	if err := ns.nsSelectors.provision(obj.ObjectMeta.UID, nil, nsSelectors); err != nil {
		return err
	}
	if err := ns.podSelectors.provision(obj.ObjectMeta.UID, nil, podSelectors); err != nil {
		return err
	}
	if err := ns.rules.provision(obj.ObjectMeta.UID, nil, rules); err != nil {
		return err
	}

	return nil
}

func (ns *ns) updateNetworkPolicy(oldObj, newObj *extnapi.NetworkPolicy) error {
	delete(ns.policies, oldObj.ObjectMeta.UID)
	ns.policies[newObj.ObjectMeta.UID] = newObj

	// Analyse the old and the new policy so we can determine differences
	oldRules, oldNsSelectors, oldPodSelectors, err := ns.analysePolicy(oldObj)
	if err != nil {
		return err
	}
	newRules, newNsSelectors, newPodSelectors, err := ns.analysePolicy(newObj)
	if err != nil {
		return err
	}

	// Deprovision unused and provision newly required resources in dependency order
	if err := ns.rules.deprovision(oldObj.ObjectMeta.UID, oldRules, newRules); err != nil {
		return err
	}
	if err := ns.nsSelectors.deprovision(oldObj.ObjectMeta.UID, oldNsSelectors, newNsSelectors); err != nil {
		return err
	}
	if err := ns.podSelectors.deprovision(oldObj.ObjectMeta.UID, oldPodSelectors, newPodSelectors); err != nil {
		return err
	}
	if err := ns.nsSelectors.provision(oldObj.ObjectMeta.UID, oldNsSelectors, newNsSelectors); err != nil {
		return err
	}
	if err := ns.podSelectors.provision(oldObj.ObjectMeta.UID, oldPodSelectors, newPodSelectors); err != nil {
		return err
	}
	if err := ns.rules.provision(oldObj.ObjectMeta.UID, oldRules, newRules); err != nil {
		return err
	}

	return nil
}

func (ns *ns) deleteNetworkPolicy(obj *extnapi.NetworkPolicy) error {
	delete(ns.policies, obj.ObjectMeta.UID)

	// Analyse network policy to free resources
	rules, nsSelectors, podSelectors, err := ns.analysePolicy(obj)
	if err != nil {
		return err
	}

	// Deprovision unused resources in dependency order
	if err := ns.rules.deprovision(obj.ObjectMeta.UID, rules, nil); err != nil {
		return err
	}
	if err := ns.nsSelectors.deprovision(obj.ObjectMeta.UID, nsSelectors, nil); err != nil {
		return err
	}
	if err := ns.podSelectors.deprovision(obj.ObjectMeta.UID, podSelectors, nil); err != nil {
		return err
	}

	return nil
}

func bypassRule(nsIpsetName ipset.Name) []string {
	return []string{"-m", "set", "--match-set", string(nsIpsetName), "dst", "-j", "ACCEPT"}
}

func (ns *ns) ensureBypassRule(nsIpsetName ipset.Name) error {
	common.Log.Debugf("ensuring rule for DefaultAllow in namespace: %s, set %s", ns.name, nsIpsetName)
	return ns.ipt.Append(TableFilter, DefaultChain, bypassRule(ns.allPods.ipsetName)...)
}

func (ns *ns) deleteBypassRule(nsIpsetName ipset.Name) error {
	common.Log.Debugf("removing default rule in namespace: %s, set %s", ns.name, nsIpsetName)
	return ns.ipt.Delete(TableFilter, DefaultChain, bypassRule(ns.allPods.ipsetName)...)
}

func (ns *ns) addNamespace(obj *coreapi.Namespace) error {
	ns.namespace = obj

	// Insert a rule to bypass policies if namespace is DefaultAllow
	if !isDefaultDeny(obj) {
		if err := ns.ensureBypassRule(ns.allPods.ipsetName); err != nil {
			return err
		}
	}

	// Add namespace ipset to matching namespace selectors
	return ns.nsSelectors.addToMatching(obj.ObjectMeta.Labels, string(ns.allPods.ipsetName))
}

func (ns *ns) updateNamespace(oldObj, newObj *coreapi.Namespace) error {
	ns.namespace = newObj

	// Update bypass rule if ingress default has changed
	oldDefaultDeny := isDefaultDeny(oldObj)
	newDefaultDeny := isDefaultDeny(newObj)

	if oldDefaultDeny != newDefaultDeny {
		common.Log.Infof("namespace DefaultDeny changed from %t to %t", oldDefaultDeny, newDefaultDeny)
		if oldDefaultDeny {
			if err := ns.ensureBypassRule(ns.allPods.ipsetName); err != nil {
				return err
			}
		}
		if newDefaultDeny {
			if err := ns.deleteBypassRule(ns.allPods.ipsetName); err != nil {
				return err
			}
		}
	}

	// Re-evaluate namespace selector membership if labels have changed
	if !equals(oldObj.ObjectMeta.Labels, newObj.ObjectMeta.Labels) {
		for _, selector := range ns.nsSelectors.entries {
			oldMatch := selector.matches(oldObj.ObjectMeta.Labels)
			newMatch := selector.matches(newObj.ObjectMeta.Labels)
			if oldMatch == newMatch {
				continue
			}
			if oldMatch {
				if err := selector.delEntry(string(ns.allPods.ipsetName)); err != nil {
					return err
				}
			}
			if newMatch {
				if err := selector.addEntry(string(ns.allPods.ipsetName)); err != nil {
					return err
				}
			}
		}
	}

	return nil
}

func (ns *ns) deleteNamespace(obj *coreapi.Namespace) error {
	ns.namespace = nil

	// Remove bypass rule
	if !isDefaultDeny(obj) {
		if err := ns.deleteBypassRule(ns.allPods.ipsetName); err != nil {
			return err
		}
	}

	// Remove namespace ipset from any matching namespace selectors
	return ns.nsSelectors.delFromMatching(obj.ObjectMeta.Labels, string(ns.allPods.ipsetName))
}

func hasIP(pod *coreapi.Pod) bool {
	// Ensure pod isn't dead, has an IP address and isn't sharing the host network namespace
	return pod.Status.Phase != "Succeeded" && pod.Status.Phase != "Failed" &&
		len(pod.Status.PodIP) > 0 && !pod.Spec.HostNetwork
}

func equals(a, b map[string]string) bool {
	if len(a) != len(b) {
		return false
	}
	for ak, av := range a {
		if b[ak] != av {
			return false
		}
	}
	return true
}

func isDefaultDeny(namespace *coreapi.Namespace) bool {
	nnpJSON, found := namespace.ObjectMeta.Annotations["net.beta.kubernetes.io/network-policy"]
	if !found {
		return false
	}

	var nnp NamespaceNetworkPolicy
	if err := json.Unmarshal([]byte(nnpJSON), &nnp); err != nil {
		common.Log.Warn("Ignoring network policy annotation: unmarshal failed:", err)
		// If we can't understand the annotation, behave as if it isn't present
		return false
	}

	return nnp.Ingress != nil &&
		nnp.Ingress.Isolation != nil &&
		*(nnp.Ingress.Isolation) == DefaultDeny
}
<|endoftext|>


<commit_before>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

package main

import (
	"bytes"
	"crypto/tls"
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"os"
	"runtime"
	"strconv"
	"strings"

	"github.com/fatih/color"
)

// Manager manages all api communications
type Manager struct {
	URL string `json:"url"`
}

// Token holds the JWT token that is received when authenticating
type Token struct {
	Token string `json:"token"`
}

// Service ...
type Service struct {
	ID         string `json:"service_id"`
	Name       string `json:"service_name"`
	Datacenter string `json:"datacenter_id"`
	Version    string `json:"service_version"`
	Status     string `json:"service_status"`
	Definition string `json:"service_definition"`
	Result     string `json:"service_result"`
	Endpoint   string `json:"service_endpoint"`
}

// Datacenter ...
type Datacenter struct {
	ID   string `json:"datacenter_id"`
	Name string `json:"datacenter_name"`
}

// User ...
type User struct {
	ID       int    `json:"id"`
	Username string `json:"username"`
	Email    string `json:"email"`
	GroupID  int    `json:"group_id"`
	IsAdmin  bool   `json:"admin"`
}

// Group ...
type Group struct {
	ID   int    `json:"id"`
	Name string `json:"name"`
}

// Session ...
type Session struct {
	UserID    string `json:"user_id"`
	ClientID  string `json:"client_id"`
	UserName  string `json:"user_name"`
	UserEmail string `json:"user_email"`
}

func (m *Manager) client() *http.Client {
	tr := &http.Transport{
		TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
	}
	client := &http.Client{Transport: tr}

	return client
}

func (m *Manager) doRequest(url, method string, payload []byte, token string, contentType string) (string, *http.Response, error) {
	url = m.URL + url
	req, err := http.NewRequest(method, url, bytes.NewBuffer(payload))
	if token != "" {
		req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", token))
	}
	if contentType != "" {
		req.Header.Set("Content-Type", contentType)
	}
	resp, err := m.client().Do(req)

	if err != nil {
		return err.Error(), resp, err
	}
	defer resp.Body.Close()
	responseBody, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		color.Red(err.Error())
	}
	body := string(responseBody)

	if resp.StatusCode != 200 {
		return string(body), resp, errors.New(resp.Status)
	}
	return string(body), resp, nil
}

func (m *Manager) createClient(token string, name string) (string, error) {
	payload := []byte(`{"name":"` + name + `"}`)
	body, _, err := m.doRequest("/api/groups/", "POST", payload, token, "")
	if err != nil {
		return body, err
	}

	color.Green("SUCCESS: Group " + name + " created")

	var group struct {
		ID int `json:"id"`
	}
	err = json.Unmarshal([]byte(body), &group)
	if err != nil {
		return "", errors.New("ERROR: Couldn't read response from server")
	}
	return strconv.Itoa(group.ID), nil
}

func (m *Manager) createUser(token string, client string, user string, password string, email string) error {
	payload := []byte(`{"group_id": ` + client + `, "username": "` + user + `", "email": "` + email + `", "password": "` + password + `"}`)
	_, _, err := m.doRequest("/api/users/", "POST", payload, token, "")
	if err != nil {
		return err
	}
	color.Green("SUCCESS: User " + user + " created")
	return nil
}

func (m *Manager) getUser(token string, userid string) (user User, err error) {
	res, _, err := m.doRequest("/api/users/"+userid, "GET", nil, token, "application/yaml")
	json.Unmarshal([]byte(res), &user)
	return user, err
}

func (m *Manager) deleteUser(token string, user string) error {
	_, _, err := m.doRequest("/api/users/"+user, "DELETE", nil, token, "application/yaml")
	return err
}

func (m *Manager) getSession(token string) (session Session, err error) {
	res, _, err := m.doRequest("/api/session/", "GET", nil, token, "application/yaml")
	json.Unmarshal([]byte(res), &session)
	return session, err
}

// ********************* Login *******************

// Login does a login action against the api
func (m *Manager) Login(username string, password string) (token string, err error) {
	var t Token

	f := url.Values{}
	f.Add("username", username)
	f.Add("password", password)

	url := m.URL + "/auth"
	req, err := http.NewRequest("POST", url, strings.NewReader(f.Encode()))
	req.Form = f
	req.PostForm = f
	req.Header.Add("Content-Type", "application/x-www-form-urlencoded")

	resp, err := m.client().Do(req)
	if err != nil {
		return "", err
	}

	if resp.StatusCode != 200 {
		return "", errors.New("Unauthorized")
	}
	defer resp.Body.Close()

	responseBody, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		color.Red(err.Error())
	}

	err = json.Unmarshal(responseBody, &t)
	if err != nil {
		color.Red(err.Error())
	}

	token = t.Token

	return token, nil
}

// ********************* Update *******************

// ChangePassword ...
func (m *Manager) ChangePassword(token string, userid int, oldpassword string, newpassword string) error {
	payload := []byte(`{"old_password":"` + oldpassword + `", "new_password": "` + newpassword + `"}`)
	_, _, err := m.doRequest("/api/users/"+string(userid), "PUT", payload, token, "application/yaml")
	if err != nil {
		return err
	}
	return nil
}

// ChangePasswordByAdmin ...
func (m *Manager) ChangePasswordByAdmin(token string, userid int, newpassword string) error {
	payload := []byte(`{"new_password": "` + newpassword + `"}`)
	_, _, err := m.doRequest("/api/users/"+string(userid), "PUT", payload, token, "application/yaml")
	if err != nil {
		return err
	}
	return nil
}

// ********************* Create *******************

// CreateDatacenter ...
func (m *Manager) CreateDatacenter(token string, name string, user string, password string, url string, network string, vseURL string) (string, error) {
	payload := []byte(`{"datacenter_name": "` + name + `", "datacenter_type": "vcloud", "datacenter_region": "LON-001", "datacenter_username":"` + user + `", "datacenter_password":"` + password + `", "external_network":"` + network + `", "vcloud_url":"` + url + `", "vse_url":"` + vseURL + `"}`)
	body, _, err := m.doRequest("/api/datacenters/", "POST", payload, token, "")
	if err != nil {
		return body, err
	}
	color.Green("SUCCESS: Datacenter " + name + " created")
	return body, err
}

// CreateUser ...
func (m *Manager) CreateUser(name string, email string, user string, password string, adminuser string, adminpassword string) error {
	token, err := m.Login(adminuser, adminpassword)
	if err != nil {
		color.Red(err.Error())
		os.Exit(1)
	}
	c, err := m.createClient(token, name)
	if err != nil {
		color.Red(err.Error() + ": Group " + name + " already exists")
		os.Exit(1)
	}
	res := m.createUser(token, c, user, password, email)
	return res
}

// ********************* Get *******************

// GetUser ...
func (m *Manager) GetUser(token string, userid string) (user User, err error) {
	res, _, err := m.doRequest("/api/users/"+userid, "GET", nil, token, "application/yaml")
	json.Unmarshal([]byte(res), &user)
	return user, err
}

// GetUUID ...
func (m *Manager) GetUUID(token string, payload []byte) string {
	id, err := buildServiceUUID(payload)
	if err != nil {
		log.Fatal(err)
	}
	body, _, _ := m.doRequest("/api/services/uuid/", "POST", []byte(`{"id":"`+id+`"}`), token, "")
	var dat map[string]interface{}
	json.Unmarshal([]byte(body), &dat)

	if str, ok := dat["uuid"].(string); ok {
		return str
	}
	return ""
}

// ********************* Apply *******************

// Apply ...
func (m *Manager) Apply(token string, path string, monit bool) (string, error) {
	payload, err := ioutil.ReadFile(path)
	if err != nil {
		color.Red(err.Error())
		return "", nil
	}

	color.Green("Environment creation requested")
	println("Ernest will show you all output from your requested service creation")
	println("You can cancel at any moment with Ctrl+C, even the service is still being created, you won't have any output")

	streamID := m.GetUUID(token, payload)
	if streamID == "" {
		color.Red("Please log in")
		return "", nil
	}

	if monit == true {
		go Monitorize(m.URL, token, streamID)
	} else {
		println("Additionally you can trace your service on ernest monitor tool with id: " + streamID)
	}

	if body, _, err := m.doRequest("/api/services/", "POST", payload, token, "application/yaml"); err != nil {
		return "", errors.New(body)
	}
	if monit == true {
		runtime.Goexit()
	}
	return streamID, nil
}

// ********************* Destroy *******************

// Destroy ...
func (m *Manager) Destroy(token string, name string, monit bool) error {
	body, _, err := m.doRequest("/api/services/"+name, "DELETE", nil, token, "application/yaml")
	if err != nil {
		return err
	}

	var res map[string]interface{}
	json.Unmarshal([]byte(body), &res)

	if monit == true {
		if str, ok := res["stream_id"].(string); ok {
			Monitorize(m.URL, token, str)
			runtime.Goexit()
		}
	}

	return err
}

// ********************* Reset *******************

// ResetService ...
func (m *Manager) ResetService(name string, token string) error {
	_, _, err := m.doRequest("/api/services/"+name+"/reset/", "POST", nil, token, "application/yaml")
	return err
}

// ********************* Status *******************

// ServiceStatus ...
func (m *Manager) ServiceStatus(token string, serviceName string) (service Service, err error) {
	body, _, err := m.doRequest("/api/services/"+serviceName, "GET", []byte(""), token, "")
	if err != nil {
		return service, err
	}
	json.Unmarshal([]byte(body), &service)
	return service, err
}

// ServiceBuildStatus ...
func (m *Manager) ServiceBuildStatus(token string, serviceName string, serviceID string) (service Service, err error) {
	body, _, err := m.doRequest("/api/services/"+serviceName+"/builds/"+serviceID, "GET", []byte(""), token, "")
	if err != nil {
		return service, err
	}
	json.Unmarshal([]byte(body), &service)
	return service, err
}

// ********************* List *********************

// ListDatacenters ...
func (m *Manager) ListDatacenters(token string) (datacenters []Datacenter, err error) {
	body, _, err := m.doRequest("/api/datacenters/", "GET", []byte(""), token, "")
	if err != nil {
		return nil, err
	}
	json.Unmarshal([]byte(body), &datacenters)
	return datacenters, err
}

// ListServices ...
func (m *Manager) ListServices(token string) (services []Service, err error) {
	body, _, err := m.doRequest("/api/services/", "GET", []byte(""), token, "")
	if err != nil {
		return nil, err
	}
	json.Unmarshal([]byte(body), &services)
	return services, err
}

// ListBuilds ...
func (m *Manager) ListBuilds(name string, token string) (builds []Service, err error) {
	body, _, err := m.doRequest("/api/services/"+name+"/builds/", "GET", []byte(""), token, "")
	if err != nil {
		return nil, err
	}
	json.Unmarshal([]byte(body), &builds)
	return builds, err
}

// ListUsers ...
func (m *Manager) ListUsers(token string) (users []User, err error) {
	body, _, err := m.doRequest("/api/users/", "GET", []byte(""), token, "")
	if err != nil {
		return nil, err
	}
	json.Unmarshal([]byte(body), &users)
	return users, err
}

// ListGroups ...
func (m *Manager) ListGroups(token string) (groups []Group, err error) {
	body, _, err := m.doRequest("/api/groups/", "GET", []byte(""), token, "")
	if err != nil {
		return nil, err
	}
	json.Unmarshal([]byte(body), &groups)
	return groups, err
}
<commit_msg>Create datacenters<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

package main

import (
	"bytes"
	"crypto/tls"
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"os"
	"runtime"
	"strconv"
	"strings"

	"github.com/fatih/color"
)

// Manager manages all api communications
type Manager struct {
	URL string `json:"url"`
}

// Token holds the JWT token that is received when authenticating
type Token struct {
	Token string `json:"token"`
}

// Service ...
type Service struct {
	ID         string `json:"service_id"`
	Name       string `json:"service_name"`
	Datacenter string `json:"datacenter_id"`
	Version    string `json:"service_version"`
	Status     string `json:"service_status"`
	Definition string `json:"service_definition"`
	Result     string `json:"service_result"`
	Endpoint   string `json:"service_endpoint"`
}

// Datacenter ...
type Datacenter struct {
	ID   int    `json:"id"`
	Name string `json:"datacenter_name"`
}

// User ...
type User struct {
	ID       int    `json:"id"`
	Username string `json:"username"`
	Email    string `json:"email"`
	GroupID  int    `json:"group_id"`
	IsAdmin  bool   `json:"admin"`
}

// Group ...
type Group struct {
	ID   int    `json:"id"`
	Name string `json:"name"`
}

// Session ...
type Session struct {
	UserID    string `json:"user_id"`
	ClientID  string `json:"client_id"`
	UserName  string `json:"user_name"`
	UserEmail string `json:"user_email"`
}

func (m *Manager) client() *http.Client {
	tr := &http.Transport{
		TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
	}
	client := &http.Client{Transport: tr}

	return client
}

func (m *Manager) doRequest(url, method string, payload []byte, token string, contentType string) (string, *http.Response, error) {
	url = m.URL + url
	req, err := http.NewRequest(method, url, bytes.NewBuffer(payload))
	if token != "" {
		req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", token))
	}
	if contentType != "" {
		req.Header.Set("Content-Type", contentType)
	}
	resp, err := m.client().Do(req)

	if err != nil {
		return err.Error(), resp, err
	}
	defer resp.Body.Close()
	responseBody, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		color.Red(err.Error())
	}
	body := string(responseBody)

	if resp.StatusCode != 200 {
		return string(body), resp, errors.New(resp.Status)
	}
	return string(body), resp, nil
}

func (m *Manager) createClient(token string, name string) (string, error) {
	payload := []byte(`{"name":"` + name + `"}`)
	body, _, err := m.doRequest("/api/groups/", "POST", payload, token, "")
	if err != nil {
		return body, err
	}

	color.Green("SUCCESS: Group " + name + " created")

	var group struct {
		ID int `json:"id"`
	}
	err = json.Unmarshal([]byte(body), &group)
	if err != nil {
		return "", errors.New("ERROR: Couldn't read response from server")
	}
	return strconv.Itoa(group.ID), nil
}

func (m *Manager) createUser(token string, client string, user string, password string, email string) error {
	payload := []byte(`{"group_id": ` + client + `, "username": "` + user + `", "email": "` + email + `", "password": "` + password + `"}`)
	_, _, err := m.doRequest("/api/users/", "POST", payload, token, "")
	if err != nil {
		return err
	}
	color.Green("SUCCESS: User " + user + " created")
	return nil
}

func (m *Manager) getUser(token string, userid string) (user User, err error) {
	res, _, err := m.doRequest("/api/users/"+userid, "GET", nil, token, "application/yaml")
	json.Unmarshal([]byte(res), &user)
	return user, err
}

func (m *Manager) deleteUser(token string, user string) error {
	_, _, err := m.doRequest("/api/users/"+user, "DELETE", nil, token, "application/yaml")
	return err
}

func (m *Manager) getSession(token string) (session Session, err error) {
	res, _, err := m.doRequest("/api/session/", "GET", nil, token, "application/yaml")
	json.Unmarshal([]byte(res), &session)
	return session, err
}

// ********************* Login *******************

// Login does a login action against the api
func (m *Manager) Login(username string, password string) (token string, err error) {
	var t Token

	f := url.Values{}
	f.Add("username", username)
	f.Add("password", password)

	url := m.URL + "/auth"
	req, err := http.NewRequest("POST", url, strings.NewReader(f.Encode()))
	req.Form = f
	req.PostForm = f
	req.Header.Add("Content-Type", "application/x-www-form-urlencoded")

	resp, err := m.client().Do(req)
	if err != nil {
		return "", err
	}

	if resp.StatusCode != 200 {
		return "", errors.New("Unauthorized")
	}
	defer resp.Body.Close()

	responseBody, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		color.Red(err.Error())
	}

	err = json.Unmarshal(responseBody, &t)
	if err != nil {
		color.Red(err.Error())
	}

	token = t.Token

	return token, nil
}

// ********************* Update *******************

// ChangePassword ...
func (m *Manager) ChangePassword(token string, userid int, oldpassword string, newpassword string) error {
	payload := []byte(`{"old_password":"` + oldpassword + `", "new_password": "` + newpassword + `"}`)
	_, _, err := m.doRequest("/api/users/"+string(userid), "PUT", payload, token, "application/yaml")
	if err != nil {
		return err
	}
	return nil
}

// ChangePasswordByAdmin ...
func (m *Manager) ChangePasswordByAdmin(token string, userid int, newpassword string) error {
	payload := []byte(`{"new_password": "` + newpassword + `"}`)
	_, _, err := m.doRequest("/api/users/"+string(userid), "PUT", payload, token, "application/yaml")
	if err != nil {
		return err
	}
	return nil
}

// ********************* Create *******************

// CreateDatacenter ...
func (m *Manager) CreateDatacenter(token string, name string, user string, password string, url string, network string, vseURL string) (string, error) {
	payload := []byte(`{"name": "` + name + `", "type": "vcloud", "region": "LON-001", "username":"` + user + `", "password":"` + password + `", "external_network":"` + network + `", "vcloud_url":"` + url + `", "vse_url":"` + vseURL + `"}`)
	body, _, err := m.doRequest("/api/datacenters/", "POST", payload, token, "")
	if err != nil {
		return body, err
	}
	color.Green("SUCCESS: Datacenter " + name + " created")
	return body, err
}

// CreateUser ...
func (m *Manager) CreateUser(name string, email string, user string, password string, adminuser string, adminpassword string) error {
	token, err := m.Login(adminuser, adminpassword)
	if err != nil {
		color.Red(err.Error())
		os.Exit(1)
	}
	c, err := m.createClient(token, name)
	if err != nil {
		color.Red(err.Error() + ": Group " + name + " already exists")
		os.Exit(1)
	}
	res := m.createUser(token, c, user, password, email)
	return res
}

// ********************* Get *******************

// GetUser ...
func (m *Manager) GetUser(token string, userid string) (user User, err error) {
	res, _, err := m.doRequest("/api/users/"+userid, "GET", nil, token, "application/yaml")
	json.Unmarshal([]byte(res), &user)
	return user, err
}

// GetUUID ...
func (m *Manager) GetUUID(token string, payload []byte) string {
	id, err := buildServiceUUID(payload)
	if err != nil {
		log.Fatal(err)
	}
	body, _, _ := m.doRequest("/api/services/uuid/", "POST", []byte(`{"id":"`+id+`"}`), token, "")
	var dat map[string]interface{}
	json.Unmarshal([]byte(body), &dat)

	if str, ok := dat["uuid"].(string); ok {
		return str
	}
	return ""
}

// ********************* Apply *******************

// Apply ...
func (m *Manager) Apply(token string, path string, monit bool) (string, error) {
	payload, err := ioutil.ReadFile(path)
	if err != nil {
		color.Red(err.Error())
		return "", nil
	}

	color.Green("Environment creation requested")
	println("Ernest will show you all output from your requested service creation")
	println("You can cancel at any moment with Ctrl+C, even the service is still being created, you won't have any output")

	streamID := m.GetUUID(token, payload)
	if streamID == "" {
		color.Red("Please log in")
		return "", nil
	}

	if monit == true {
		go Monitorize(m.URL, token, streamID)
	} else {
		println("Additionally you can trace your service on ernest monitor tool with id: " + streamID)
	}

	if body, _, err := m.doRequest("/api/services/", "POST", payload, token, "application/yaml"); err != nil {
		return "", errors.New(body)
	}
	if monit == true {
		runtime.Goexit()
	}
	return streamID, nil
}

// ********************* Destroy *******************

// Destroy ...
func (m *Manager) Destroy(token string, name string, monit bool) error {
	body, _, err := m.doRequest("/api/services/"+name, "DELETE", nil, token, "application/yaml")
	if err != nil {
		return err
	}

	var res map[string]interface{}
	json.Unmarshal([]byte(body), &res)

	if monit == true {
		if str, ok := res["stream_id"].(string); ok {
			Monitorize(m.URL, token, str)
			runtime.Goexit()
		}
	}

	return err
}

// ********************* Reset *******************

// ResetService ...
func (m *Manager) ResetService(name string, token string) error {
	_, _, err := m.doRequest("/api/services/"+name+"/reset/", "POST", nil, token, "application/yaml")
	return err
}

// ********************* Status *******************

// ServiceStatus ...
func (m *Manager) ServiceStatus(token string, serviceName string) (service Service, err error) {
	body, _, err := m.doRequest("/api/services/"+serviceName, "GET", []byte(""), token, "")
	if err != nil {
		return service, err
	}
	json.Unmarshal([]byte(body), &service)
	return service, err
}

// ServiceBuildStatus ...
func (m *Manager) ServiceBuildStatus(token string, serviceName string, serviceID string) (service Service, err error) {
	body, _, err := m.doRequest("/api/services/"+serviceName+"/builds/"+serviceID, "GET", []byte(""), token, "")
	if err != nil {
		return service, err
	}
	json.Unmarshal([]byte(body), &service)
	return service, err
}

// ********************* List *********************

// ListDatacenters ...
func (m *Manager) ListDatacenters(token string) (datacenters []Datacenter, err error) {
	body, _, err := m.doRequest("/api/datacenters/", "GET", []byte(""), token, "")
	if err != nil {
		return nil, err
	}
	json.Unmarshal([]byte(body), &datacenters)
	return datacenters, err
}

// ListServices ...
func (m *Manager) ListServices(token string) (services []Service, err error) {
	body, _, err := m.doRequest("/api/services/", "GET", []byte(""), token, "")
	if err != nil {
		return nil, err
	}
	json.Unmarshal([]byte(body), &services)
	return services, err
}

// ListBuilds ...
func (m *Manager) ListBuilds(name string, token string) (builds []Service, err error) {
	body, _, err := m.doRequest("/api/services/"+name+"/builds/", "GET", []byte(""), token, "")
	if err != nil {
		return nil, err
	}
	json.Unmarshal([]byte(body), &builds)
	return builds, err
}

// ListUsers ...
func (m *Manager) ListUsers(token string) (users []User, err error) {
	body, _, err := m.doRequest("/api/users/", "GET", []byte(""), token, "")
	if err != nil {
		return nil, err
	}
	json.Unmarshal([]byte(body), &users)
	return users, err
}

// ListGroups ...
func (m *Manager) ListGroups(token string) (groups []Group, err error) {
	body, _, err := m.doRequest("/api/groups/", "GET", []byte(""), token, "")
	if err != nil {
		return nil, err
	}
	json.Unmarshal([]byte(body), &groups)
	return groups, err
}
<|endoftext|>


<commit_before>package manikyr

import (
	"errors"
	"image"
	"os"
	"path"

	"github.com/go-fsnotify/fsnotify"
	"github.com/disintegration/imaging"
)

var (
	ErrRootNotExist 	= errors.New("root does not exist")
	ErrRootExist 		= errors.New("root already exists")

	NearestNeighbor		= imaging.NearestNeighbor
	Box					= imaging.Box
	Linear				= imaging.Linear
	Hermite				= imaging.Hermite
	MitchellNetravali	= imaging.MitchellNetravali
	CatmullRom			= imaging.CatmullRom
	BSpline				= imaging.BSpline
	Gaussian			= imaging.Gaussian
	Bartlett			= imaging.Bartlett
	Lanczos				= imaging.Lanczos
	Hann				= imaging.Hann
	Hamming				= imaging.Hamming
	Blackman			= imaging.Blackman
	Welch				= imaging.Welch
	Cosine				= imaging.Cosine
)

type Manikyr struct {
	roots				map[string]*fsnotify.Watcher
	thumbDirPerms		os.FileMode
	thumbWidth			int
	thumbHeight			int
	thumbAlgo			imaging.ResampleFilter
	ThumbDirGetter		func(string) string
	ThumbNameGetter		func(string) string
	ShouldCreateThumb	func(string, string) bool
	ShouldWatchSubdir	func(string, string) bool
}
func (m *Manikyr) Roots() []string {
	keys := make([]string, len(m.roots))
	i := 0
	for k := range m.roots {
		keys[i] = k
		i = i + 1
	}
	return keys
}
func (m *Manikyr) AddRoot(root string, errChan chan error) error {
	if _, ok := m.roots[root]; ok {
		return ErrRootExist
	}

	w, err := fsnotify.NewWatcher()
	if err != nil {
		return err
	}

	m.roots[root] = w
	go m.watch(root, errChan)

	err = m.roots[root].Add(root)
	if err != nil {
		return err
	}
	return nil
}
func (m *Manikyr) RemoveRoot(root string) error {
	if _, ok := m.roots[root]; !ok {
		return ErrRootNotExist
	}
	m.roots[root].Close()
	delete(m.roots, root)
	return nil
}
func (m *Manikyr) watch(root string, errChan chan error) {
	w, ok := m.roots[root]
	if !ok {
		errChan <-ErrRootNotExist
	}

	defer w.Close()
	for {
		select {
		case evt := <- w.Events:
			if evt.Op == fsnotify.Create {
				// If a file was created

				// Get some info about the file
				info, err := os.Stat(evt.Name)
				if os.IsNotExist(err) {
					errChan <-err
					continue
				}

				switch mode := info.Mode(); {
				case mode.IsDir():
					if m.ShouldWatchSubdir(root, evt.Name) {
						w.Add(evt.Name)
					}
				case mode.IsRegular():
					if m.ShouldCreateThumb(root, evt.Name) {
						go m.createThumb(evt.Name, errChan)
					}
				}
			} else {
				// Something else happened to the file
				_, err := os.Stat(evt.Name)
				if os.IsNotExist(err) {
					// Try to delete thumb.
					// Error is useless here because the file could
					// have been a directory or a non-image file.
					m.removeThumb(evt.Name)
				} else if err != nil {
					errChan <-err
					continue
				}
			}
		case err := <- w.Errors:
			errChan <-err
		}
	}
}
func (m *Manikyr) removeThumb(parentFile string) error {
	thumbPath := path.Join(m.ThumbDirGetter(parentFile), m.ThumbNameGetter(parentFile))
	return os.Remove(thumbPath)
}
func (m *Manikyr) createThumb(parentFile string, errChan chan error) {
	img, err := imaging.Open(parentFile)
	if err == image.ErrFormat {
		// There is a chance that the file is not yet completely created.
		// We need some sort of retry/wait functionality in here for production use.
		errChan <-err
		return
	} else if err != nil {
		errChan <-err
		return
	}

	localThumbs := m.ThumbDirGetter(parentFile)
	_, err = os.Stat(localThumbs)
	// If thumbDir does not exist...
	if os.IsNotExist(err) {
		// ..create it
		err := os.Mkdir(localThumbs, m.thumbDirPerms)
		if err != nil {
			errChan <-err
			return
		}
	} else if err != nil {
		errChan <-err
		return
	}

	// Save the thumbnail
	thumb := imaging.Thumbnail(img, m.thumbWidth, m.thumbHeight, m.thumbAlgo)
	thumbPath := path.Join(localThumbs, m.ThumbNameGetter(parentFile))
	if err = imaging.Save(thumb, thumbPath); err != nil {
		errChan <-err
		return
	}
}
func (m *Manikyr) ThumbSize() (int, int) {
	return m.thumbWidth, m.thumbHeight
}
func (m *Manikyr) SetThumbSize(w, h int) {
	// Dimensions must be positive

	if w < 1 {
		w = 1
	}
	m.thumbWidth = w

	if h < 1 {
		h = 1
	}
	m.thumbHeight = h
}
func (m *Manikyr) SetThumbDirFileMode(fm uint32) {
	m.thumbDirPerms = os.FileMode(fm)
}

func New() *Manikyr {
	// Sensible defaults
	return &Manikyr{
		roots:			make(map[string]*fsnotify.Watcher),
		thumbWidth: 		100,
		thumbHeight: 		100,
		thumbAlgo:		NearestNeighbor,
		thumbDirPerms:		0777,
		ThumbDirGetter: func(parentFile string) string {
			return path.Join(path.Dir(parentFile), ".thumbs")
		},
		ThumbNameGetter: func(parentFile string) string {
			return path.Base(parentFile)
		},
		ShouldCreateThumb: func(root, parentFile string) bool {
			if NthSubdir(root, parentFile, 1) {
				return true
			}
			return false
		},
		ShouldWatchSubdir: func(root, parentFile string) bool {
			if NthSubdir(root, parentFile, 0) && parentFile[0] != '.' {
				return true
			}
			return false
		},
	}
}
<commit_msg>Fix formatting, add setters&getters<commit_after>package manikyr

import (
	"errors"
	"image"
	"os"
	"path"

	"github.com/go-fsnotify/fsnotify"
	"github.com/disintegration/imaging"
)

var (
	ErrRootNotExist 	= errors.New("root does not exist")
	ErrRootExist 		= errors.New("root already exists")

	NearestNeighbor		= imaging.NearestNeighbor
	Box			= imaging.Box
	Linear			= imaging.Linear
	Hermite			= imaging.Hermite
	MitchellNetravali	= imaging.MitchellNetravali
	CatmullRom		= imaging.CatmullRom
	BSpline			= imaging.BSpline
	Gaussian		= imaging.Gaussian
	Bartlett		= imaging.Bartlett
	Lanczos			= imaging.Lanczos
	Hann			= imaging.Hann
	Hamming			= imaging.Hamming
	Blackman		= imaging.Blackman
	Welch			= imaging.Welch
	Cosine			= imaging.Cosine
)

type Manikyr struct {
	roots			map[string]*fsnotify.Watcher
	thumbDirPerms		os.FileMode
	thumbWidth		int
	thumbHeight		int
	thumbAlgo		imaging.ResampleFilter
	ThumbDirGetter		func(string) string
	ThumbNameGetter		func(string) string
	ShouldCreateThumb	func(string, string) bool
	ShouldWatchSubdir	func(string, string) bool
}
func (m *Manikyr) Roots() []string {
	keys := make([]string, len(m.roots))
	i := 0
	for k := range m.roots {
		keys[i] = k
		i = i + 1
	}
	return keys
}
func (m *Manikyr) AddRoot(root string, errChan chan error) error {
	if _, ok := m.roots[root]; ok {
		return ErrRootExist
	}

	w, err := fsnotify.NewWatcher()
	if err != nil {
		return err
	}

	m.roots[root] = w
	go m.watch(root, errChan)

	err = m.roots[root].Add(root)
	if err != nil {
		return err
	}
	return nil
}
func (m *Manikyr) RemoveRoot(root string) error {
	if _, ok := m.roots[root]; !ok {
		return ErrRootNotExist
	}
	m.roots[root].Close()
	delete(m.roots, root)
	return nil
}
func (m *Manikyr) watch(root string, errChan chan error) {
	w, ok := m.roots[root]
	if !ok {
		errChan <-ErrRootNotExist
	}

	defer w.Close()
	for {
		select {
		case evt := <- w.Events:
			if evt.Op == fsnotify.Create {
				// If a file was created

				// Get some info about the file
				info, err := os.Stat(evt.Name)
				if os.IsNotExist(err) {
					errChan <-err
					continue
				}

				switch mode := info.Mode(); {
				case mode.IsDir():
					if m.ShouldWatchSubdir(root, evt.Name) {
						w.Add(evt.Name)
					}
				case mode.IsRegular():
					if m.ShouldCreateThumb(root, evt.Name) {
						go m.createThumb(evt.Name, errChan)
					}
				}
			} else {
				// Something else happened to the file
				_, err := os.Stat(evt.Name)
				if os.IsNotExist(err) {
					// Try to delete thumb.
					// Error is useless here because the file could
					// have been a directory or a non-image file.
					m.removeThumb(evt.Name)
				} else if err != nil {
					errChan <-err
					continue
				}
			}
		case err := <- w.Errors:
			errChan <-err
		}
	}
}
func (m *Manikyr) removeThumb(parentFile string) error {
	thumbPath := path.Join(m.ThumbDirGetter(parentFile), m.ThumbNameGetter(parentFile))
	return os.Remove(thumbPath)
}
func (m *Manikyr) createThumb(parentFile string, errChan chan error) {
	img, err := imaging.Open(parentFile)
	if err == image.ErrFormat {
		// There is a chance that the file is not yet completely created.
		// We need some sort of retry/wait functionality in here for production use.
		errChan <-err
		return
	} else if err != nil {
		errChan <-err
		return
	}

	localThumbs := m.ThumbDirGetter(parentFile)
	_, err = os.Stat(localThumbs)
	// If thumbDir does not exist...
	if os.IsNotExist(err) {
		// ..create it
		err := os.Mkdir(localThumbs, m.thumbDirPerms)
		if err != nil {
			errChan <-err
			return
		}
	} else if err != nil {
		errChan <-err
		return
	}

	// Save the thumbnail
	thumb := imaging.Thumbnail(img, m.thumbWidth, m.thumbHeight, m.thumbAlgo)
	thumbPath := path.Join(localThumbs, m.ThumbNameGetter(parentFile))
	if err = imaging.Save(thumb, thumbPath); err != nil {
		errChan <-err
		return
	}
}
func (m *Manikyr) ThumbSize() (int, int) {
	return m.thumbWidth, m.thumbHeight
}
func (m *Manikyr) SetThumbSize(w, h int) {
	// Dimensions must be positive

	if w < 1 {
		w = 1
	}
	m.thumbWidth = w

	if h < 1 {
		h = 1
	}
	m.thumbHeight = h
}
func (m *Manikyr) ThumbDirFileMode() uint32 {
	return uint32(m.thumbDirPerms)
}
func (m *Manikyr) SetThumbDirFileMode(fm uint32) {
	m.thumbDirPerms = os.FileMode(fm)
}
func (m *Manikyr) ThumbAlgorithm() imaging.ResampleFilter {
	return m.thumbAlgo
}
func (m *Manikyr) SetThumbAlgorithm(filter imaging.ResampleFilter) {
	m.thumbAlgo = filter
}

func New() *Manikyr {
	// Sensible defaults
	return &Manikyr{
		roots:			make(map[string]*fsnotify.Watcher),
		thumbWidth: 		100,
		thumbHeight: 		100,
		thumbAlgo:		NearestNeighbor,
		thumbDirPerms:		0777,
		ThumbDirGetter: func(parentFile string) string {
			return path.Join(path.Dir(parentFile), ".thumbs")
		},
		ThumbNameGetter: func(parentFile string) string {
			return path.Base(parentFile)
		},
		ShouldCreateThumb: func(root, parentFile string) bool {
			if NthSubdir(root, parentFile, 1) {
				return true
			}
			return false
		},
		ShouldWatchSubdir: func(root, parentFile string) bool {
			if NthSubdir(root, parentFile, 0) && parentFile[0] != '.' {
				return true
			}
			return false
		},
	}
}
<|endoftext|>


<commit_before>/*
Copyright (c) 2012, Jan Schlicht <jan.schlicht@gmail.com>

Permission to use, copy, modify, and/or distribute this software for any purpose
with or without fee is hereby granted, provided that the above copyright notice
and this permission notice appear in all copies.

THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
THIS SOFTWARE.
*/

package resize

import (
	"image"
	"image/color"
)

type colorArray [4]float32

// converter allows to retrieve
// a colorArray for points of an image
type converter interface {
	at(x, y int) colorArray
}

type genericConverter struct {
	src image.Image
}

func (c *genericConverter) at(x, y int) colorArray {
	r, g, b, a := c.src.At(x, y).RGBA()
	return colorArray{
		float32(r),
		float32(g),
		float32(b),
		float32(a),
	}
}

type rgbaConverter struct {
	src *image.RGBA
}

func (c *rgbaConverter) at(x, y int) colorArray {
	if !(image.Point{x, y}.In(c.src.Rect)) {
		return colorArray{0, 0, 0, 0}
	}
	i := c.src.PixOffset(x, y)
	return colorArray{
		float32(uint16(c.src.Pix[i+0])<<8 | uint16(c.src.Pix[i+0])),
		float32(uint16(c.src.Pix[i+1])<<8 | uint16(c.src.Pix[i+1])),
		float32(uint16(c.src.Pix[i+2])<<8 | uint16(c.src.Pix[i+2])),
		float32(uint16(c.src.Pix[i+3])<<8 | uint16(c.src.Pix[i+3])),
	}
}

type rgba64Converter struct {
	src *image.RGBA64
}

func (c *rgba64Converter) at(x, y int) colorArray {
	if !(image.Point{x, y}.In(c.src.Rect)) {
		return colorArray{0, 0, 0, 0}
	}
	i := c.src.PixOffset(x, y)
	return colorArray{
		float32(uint16(c.src.Pix[i+0])<<8 | uint16(c.src.Pix[i+1])),
		float32(uint16(c.src.Pix[i+2])<<8 | uint16(c.src.Pix[i+3])),
		float32(uint16(c.src.Pix[i+4])<<8 | uint16(c.src.Pix[i+5])),
		float32(uint16(c.src.Pix[i+6])<<8 | uint16(c.src.Pix[i+7])),
	}
}

type grayConverter struct {
	src *image.Gray
}

func (c *grayConverter) at(x, y int) colorArray {
	if !(image.Point{x, y}.In(c.src.Rect)) {
		return colorArray{0, 0, 0, 0}
	}
	i := c.src.PixOffset(x, y)
	g := float32(uint16(c.src.Pix[i])<<8 | uint16(c.src.Pix[i]))
	return colorArray{
		g,
		g,
		g,
		float32(0xffff),
	}
}

type gray16Converter struct {
	src *image.Gray16
}

func (c *gray16Converter) at(x, y int) colorArray {
	if !(image.Point{x, y}.In(c.src.Rect)) {
		return colorArray{0, 0, 0, 0}
	}
	i := c.src.PixOffset(x, y)
	g := float32(uint16(c.src.Pix[i+0])<<8 | uint16(c.src.Pix[i+1]))
	return colorArray{
		g,
		g,
		g,
		float32(0xffff),
	}
}

type ycbcrConverter struct {
	src *image.YCbCr
}

func (c *ycbcrConverter) at(x, y int) colorArray {
	if !(image.Point{x, y}.In(c.src.Rect)) {
		return colorArray{0, 0, 0, 0}
	}
	yi := c.src.YOffset(x, y)
	ci := c.src.COffset(x, y)
	r, g, b := color.YCbCrToRGB(c.src.Y[yi], c.src.Cb[ci], c.src.Cr[ci])
	return colorArray{
		float32(uint16(r) * 0x101),
		float32(uint16(g) * 0x101),
		float32(uint16(b) * 0x101),
		float32(0xffff),
	}
}
<commit_msg>Replicate border instead of using a constant<commit_after>/*
Copyright (c) 2012, Jan Schlicht <jan.schlicht@gmail.com>

Permission to use, copy, modify, and/or distribute this software for any purpose
with or without fee is hereby granted, provided that the above copyright notice
and this permission notice appear in all copies.

THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
THIS SOFTWARE.
*/

package resize

import (
	"image"
	"image/color"
)

type colorArray [4]float32

// converter allows to retrieve
// a colorArray for points of an image
type converter interface {
	at(x, y int) colorArray
}

type genericConverter struct {
	src image.Image
}

func replicateBorder1d(x, min, max int) int {
	if (x < min) {
		x = min
	} else if (x >= max) {
		x = max-1
	}

	return x
}

func replicateBorder(x, y int, rect image.Rectangle) (xx, yy int) {
	xx = replicateBorder1d(x, rect.Min.X, rect.Max.X)
	yy = replicateBorder1d(y, rect.Min.Y, rect.Max.Y)
	return
}

func (c *genericConverter) at(x, y int) colorArray {
	r, g, b, a := c.src.At(x, y).RGBA()
	return colorArray{
		float32(r),
		float32(g),
		float32(b),
		float32(a),
	}
}

type rgbaConverter struct {
	src *image.RGBA
}

func (c *rgbaConverter) at(x, y int) colorArray {
	i := c.src.PixOffset(replicateBorder(x, y, c.src.Rect))
	return colorArray{
		float32(uint16(c.src.Pix[i+0])<<8 | uint16(c.src.Pix[i+0])),
		float32(uint16(c.src.Pix[i+1])<<8 | uint16(c.src.Pix[i+1])),
		float32(uint16(c.src.Pix[i+2])<<8 | uint16(c.src.Pix[i+2])),
		float32(uint16(c.src.Pix[i+3])<<8 | uint16(c.src.Pix[i+3])),
	}
}

type rgba64Converter struct {
	src *image.RGBA64
}

func (c *rgba64Converter) at(x, y int) colorArray {
	i := c.src.PixOffset(replicateBorder(x, y, c.src.Rect))
	return colorArray{
		float32(uint16(c.src.Pix[i+0])<<8 | uint16(c.src.Pix[i+1])),
		float32(uint16(c.src.Pix[i+2])<<8 | uint16(c.src.Pix[i+3])),
		float32(uint16(c.src.Pix[i+4])<<8 | uint16(c.src.Pix[i+5])),
		float32(uint16(c.src.Pix[i+6])<<8 | uint16(c.src.Pix[i+7])),
	}
}

type grayConverter struct {
	src *image.Gray
}

func (c *grayConverter) at(x, y int) colorArray {
	i := c.src.PixOffset(replicateBorder(x, y, c.src.Rect))
	g := float32(uint16(c.src.Pix[i])<<8 | uint16(c.src.Pix[i]))
	return colorArray{
		g,
		g,
		g,
		float32(0xffff),
	}
}

type gray16Converter struct {
	src *image.Gray16
}

func (c *gray16Converter) at(x, y int) colorArray {
	i := c.src.PixOffset(replicateBorder(x, y, c.src.Rect))
	g := float32(uint16(c.src.Pix[i+0])<<8 | uint16(c.src.Pix[i+1]))
	return colorArray{
		g,
		g,
		g,
		float32(0xffff),
	}
}

type ycbcrConverter struct {
	src *image.YCbCr
}

func (c *ycbcrConverter) at(x, y int) colorArray {
	xx, yy := replicateBorder(x, y, c.src.Rect)
	yi := c.src.YOffset(xx, yy)
	ci := c.src.COffset(xx, yy)
	r, g, b := color.YCbCrToRGB(c.src.Y[yi], c.src.Cb[ci], c.src.Cr[ci])
	return colorArray{
		float32(uint16(r) * 0x101),
		float32(uint16(g) * 0x101),
		float32(uint16(b) * 0x101),
		float32(0xffff),
	}
}
<|endoftext|>