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

package rpc

import (
	"errors"
	"math"
	"net"
	"strings"
	"sync"
	"time"

	"github.com/rubyist/circuitbreaker"
	"golang.org/x/net/context"
	"google.golang.org/grpc"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/credentials"

	"github.com/cockroachdb/cockroach/pkg/base"
	"github.com/cockroachdb/cockroach/pkg/roachpb"
	"github.com/cockroachdb/cockroach/pkg/util/envutil"
	"github.com/cockroachdb/cockroach/pkg/util/grpcutil"
	"github.com/cockroachdb/cockroach/pkg/util/hlc"
	"github.com/cockroachdb/cockroach/pkg/util/log"
	"github.com/cockroachdb/cockroach/pkg/util/stop"
	"github.com/cockroachdb/cockroach/pkg/util/syncutil"
	"github.com/cockroachdb/cockroach/pkg/util/timeutil"
)

func init() {
	// Disable GRPC tracing. This retains a subset of messages for
	// display on /debug/requests, which is very expensive for
	// snapshots. Until we can be more selective about what is retained
	// in traces, we must disable tracing entirely.
	// https://github.com/grpc/grpc-go/issues/695
	grpc.EnableTracing = false
}

const (
	defaultHeartbeatInterval = 3 * time.Second
	// The coefficient by which the maximum offset is multiplied to determine the
	// maximum acceptable measurement latency.
	maximumPingDurationMult = 2
)

// SourceAddr provides a way to specify a source/local address for outgoing
// connections. It should only ever be set by testing code, and is not thread
// safe (so it must be initialized before the server starts).
var SourceAddr = envutil.EnvOrDefaultString("COCKROACH_SOURCE_IP_ADDRESS", "")

// NewServer is a thin wrapper around grpc.NewServer that registers a heartbeat
// service.
func NewServer(ctx *Context) *grpc.Server {
	opts := []grpc.ServerOption{
		// The limiting factor for lowering the max message size is the fact
		// that a single large kv can be sent over the network in one message.
		// Our maximum kv size is unlimited, so we need this to be very large.
		// TODO(peter,tamird): need tests before lowering
		grpc.MaxMsgSize(math.MaxInt32),
	}
	if !ctx.Insecure {
		tlsConfig, err := ctx.GetServerTLSConfig()
		if err != nil {
			panic(err)
		}
		opts = append(opts, grpc.Creds(credentials.NewTLS(tlsConfig)))
	}
	s := grpc.NewServer(opts...)
	RegisterHeartbeatServer(s, &HeartbeatService{
		clock:              ctx.LocalClock,
		remoteClockMonitor: ctx.RemoteClocks,
	})
	return s
}

type connMeta struct {
	sync.Once
	conn         *grpc.ClientConn
	dialErr      error
	heartbeatErr error
}

// Context contains the fields required by the rpc framework.
type Context struct {
	*base.Config

	LocalClock   *hlc.Clock
	breakerClock breakerClock
	Stopper      *stop.Stopper
	RemoteClocks *RemoteClockMonitor
	masterCtx    context.Context

	HeartbeatInterval time.Duration
	HeartbeatTimeout  time.Duration
	HeartbeatCB       func()

	localInternalServer roachpb.InternalServer

	conns struct {
		syncutil.Mutex
		cache map[string]*connMeta
	}

	// For unittesting.
	BreakerFactory func() *circuit.Breaker
}

// NewContext creates an rpc Context with the supplied values.
func NewContext(
	ambient log.AmbientContext, baseCtx *base.Config, hlcClock *hlc.Clock, stopper *stop.Stopper,
) *Context {
	if hlcClock == nil {
		panic("nil clock is forbidden")
	}
	ctx := &Context{
		Config:     baseCtx,
		LocalClock: hlcClock,
		breakerClock: breakerClock{
			clock: hlcClock,
		},
	}
	var cancel context.CancelFunc
	ctx.masterCtx, cancel = context.WithCancel(ambient.AnnotateCtx(context.Background()))
	ctx.Stopper = stopper
	ctx.RemoteClocks = newRemoteClockMonitor(
		ctx.LocalClock, 10*defaultHeartbeatInterval, baseCtx.HistogramWindowInterval)
	ctx.HeartbeatInterval = defaultHeartbeatInterval
	ctx.HeartbeatTimeout = 2 * defaultHeartbeatInterval
	ctx.conns.cache = make(map[string]*connMeta)

	stopper.RunWorker(func() {
		<-stopper.ShouldQuiesce()

		cancel()
		ctx.conns.Lock()
		for key, meta := range ctx.conns.cache {
			meta.Do(func() {
				// Make sure initialization is not in progress when we're removing the
				// conn. We need to set the error in case we win the race against the
				// real initialization code.
				if meta.dialErr == nil {
					meta.dialErr = &roachpb.NodeUnavailableError{}
				}
			})
			ctx.removeConnLocked(key, meta)
		}
		ctx.conns.Unlock()
	})

	return ctx
}

// GetLocalInternalServerForAddr returns the context's internal batch server
// for addr, if it exists.
func (ctx *Context) GetLocalInternalServerForAddr(addr string) roachpb.InternalServer {
	if addr == ctx.Addr {
		return ctx.localInternalServer
	}
	return nil
}

// SetLocalInternalServer sets the context's local internal batch server.
func (ctx *Context) SetLocalInternalServer(internalServer roachpb.InternalServer) {
	ctx.localInternalServer = internalServer
}

func (ctx *Context) removeConn(key string, meta *connMeta) {
	ctx.conns.Lock()
	defer ctx.conns.Unlock()
	ctx.removeConnLocked(key, meta)
}

func (ctx *Context) removeConnLocked(key string, meta *connMeta) {
	if log.V(1) {
		log.Infof(ctx.masterCtx, "closing %s", key)
	}
	if conn := meta.conn; conn != nil {
		if err := conn.Close(); err != nil && !grpcutil.IsClosedConnection(err) {
			if log.V(1) {
				log.Errorf(ctx.masterCtx, "failed to close client connection: %s", err)
			}
		}
	}
	delete(ctx.conns.cache, key)
}

// GRPCDial calls grpc.Dial with the options appropriate for the context.
func (ctx *Context) GRPCDial(target string, opts ...grpc.DialOption) (*grpc.ClientConn, error) {
	ctx.conns.Lock()
	meta, ok := ctx.conns.cache[target]
	if !ok {
		meta = &connMeta{
			heartbeatErr: errNotHeartbeated,
		}
		ctx.conns.cache[target] = meta
	}
	ctx.conns.Unlock()

	meta.Do(func() {
		var dialOpt grpc.DialOption
		if ctx.Insecure {
			dialOpt = grpc.WithInsecure()
		} else {
			tlsConfig, err := ctx.GetClientTLSConfig()
			if err != nil {
				meta.dialErr = err
				return
			}
			dialOpt = grpc.WithTransportCredentials(credentials.NewTLS(tlsConfig))
		}

		dialOpts := make([]grpc.DialOption, 0, 2+len(opts))
		dialOpts = append(dialOpts, dialOpt)
		dialOpts = append(dialOpts, grpc.WithBackoffMaxDelay(maxBackoff))
		dialOpts = append(dialOpts, opts...)

		if SourceAddr != "" {
			sourceIP := net.ParseIP(SourceAddr)
			if sourceIP == nil {
				log.Fatalf(ctx.masterCtx,
					"unable to parse COCKROACH_SOURCE_IP_ADDRESS %q as IP address", SourceAddr)
			}
			tcpAddr := &net.TCPAddr{
				IP: sourceIP,
			}
			dialOpts = append(dialOpts, grpc.WithDialer(
				func(addr string, timeout time.Duration) (net.Conn, error) {
					dialer := net.Dialer{
						Timeout:   timeout,
						LocalAddr: tcpAddr,
					}
					return dialer.Dial("tcp", addr)
				},
			))
		}

		if log.V(1) {
			log.Infof(ctx.masterCtx, "dialing %s", target)
		}
		meta.conn, meta.dialErr = grpc.DialContext(ctx.masterCtx, target, dialOpts...)
		if meta.dialErr == nil {
			if err := ctx.Stopper.RunTask(func() {
				ctx.Stopper.RunWorker(func() {
					err := ctx.runHeartbeat(meta, target)
					if err != nil && !grpcutil.IsClosedConnection(err) {
						log.Errorf(ctx.masterCtx, "removing connection to %s due to error: %s", target, err)
					}
					ctx.removeConn(target, meta)
				})
			}); err != nil {
				meta.dialErr = err
				// removeConn and ctx's cleanup worker both lock ctx.conns. However,
				// to avoid racing with meta's initialization, the cleanup worker
				// blocks on meta.Do while holding ctx.conns. Invoke removeConn
				// asynchronously to avoid deadlock.
				go ctx.removeConn(target, meta)
			}
		}
	})

	return meta.conn, meta.dialErr
}

// NewBreaker creates a new circuit breaker properly configured for RPC
// connections.
func (ctx *Context) NewBreaker() *circuit.Breaker {
	if ctx.BreakerFactory != nil {
		return ctx.BreakerFactory()
	}
	return newBreaker(&ctx.breakerClock)
}

var errNotConnected = errors.New("not connected")
var errNotHeartbeated = errors.New("not yet heartbeated")

// ConnHealth returns whether the most recent heartbeat succeeded or not.
// This should not be used as a definite status of a node's health and just used
// to prioritize healthy nodes over unhealthy ones.
func (ctx *Context) ConnHealth(remoteAddr string) error {
	ctx.conns.Lock()
	defer ctx.conns.Unlock()
	if meta, ok := ctx.conns.cache[remoteAddr]; ok {
		return meta.heartbeatErr
	}
	return errNotConnected
}

func (ctx *Context) runHeartbeat(meta *connMeta, remoteAddr string) error {
	request := PingRequest{
		Addr:           ctx.Addr,
		MaxOffsetNanos: ctx.LocalClock.MaxOffset().Nanoseconds(),
	}
	heartbeatClient := NewHeartbeatClient(meta.conn)

	var heartbeatTimer timeutil.Timer
	defer heartbeatTimer.Stop()

	// Give the first iteration a wait-free heartbeat attempt.
	heartbeatTimer.Reset(0)

	firstHeartbeat := true
	for {
		select {
		case <-ctx.Stopper.ShouldStop():
			return nil
		case <-heartbeatTimer.C:
			heartbeatTimer.Read = true
		}

		// Give the first heartbeat extra time to complete successfully, in case
		// the remote side is starting up.
		timeout := ctx.HeartbeatTimeout
		if firstHeartbeat {
			timeout *= 100

			firstHeartbeat = false
		}

		goCtx, cancel := context.WithTimeout(ctx.masterCtx, timeout)
		sendTime := ctx.LocalClock.PhysicalTime()
		// NB: We want the request to fail-fast (the default), otherwise we won't
		// be notified of transport failures.
		response, err := heartbeatClient.Ping(goCtx, &request)
		cancel()
		ctx.conns.Lock()
		meta.heartbeatErr = err
		ctx.conns.Unlock()

		// If we got a timeout, we might be experiencing a network partition. We
		// close the connection so that all other pending RPCs (which may not have
		// timeouts) fail eagerly. Any other error is likely to be noticed by
		// other RPCs, so it's OK to leave the connection open while grpc
		// internally reconnects if necessary.
		if grpc.Code(err) == codes.DeadlineExceeded {
			return err
		}

		// HACK: work around https://github.com/grpc/grpc-go/issues/1026
		// Getting a "connection refused" error from the "write" system call
		// has confused grpc's error handling and this connection is permanently
		// broken.
		// TODO(bdarnell): remove this when the upstream bug is fixed.
		if err != nil && strings.Contains(err.Error(), "write: connection refused") {
			return nil
		}

		if err == nil {
			receiveTime := ctx.LocalClock.PhysicalTime()

			// Only update the clock offset measurement if we actually got a
			// successful response from the server.
			pingDuration := receiveTime.Sub(sendTime)
			if pingDuration > maximumPingDurationMult*ctx.LocalClock.MaxOffset() {
				request.Offset.Reset()
			} else {
				// Offset and error are measured using the remote clock reading
				// technique described in
				// http://se.inf.tu-dresden.de/pubs/papers/SRDS1994.pdf, page 6.
				// However, we assume that drift and min message delay are 0, for
				// now.
				request.Offset.MeasuredAt = receiveTime.UnixNano()
				request.Offset.Uncertainty = (pingDuration / 2).Nanoseconds()
				remoteTimeNow := time.Unix(0, response.ServerTime).Add(pingDuration / 2)
				request.Offset.Offset = remoteTimeNow.Sub(receiveTime).Nanoseconds()
			}
			ctx.RemoteClocks.UpdateOffset(ctx.masterCtx, remoteAddr, request.Offset, pingDuration)

			if cb := ctx.HeartbeatCB; cb != nil {
				cb()
			}
		}

		heartbeatTimer.Reset(ctx.HeartbeatInterval)
	}
}
<commit_msg>rpc: avoid disconnecting on heartbeat timeouts for CLI clients<commit_after>// Copyright 2015 The Cockroach Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
// implied. See the License for the specific language governing
// permissions and limitations under the License.
//
// Author: Tamir Duberstein (tamird@gmail.com)

package rpc

import (
	"errors"
	"math"
	"net"
	"strings"
	"sync"
	"time"

	"github.com/rubyist/circuitbreaker"
	"golang.org/x/net/context"
	"google.golang.org/grpc"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/credentials"

	"github.com/cockroachdb/cockroach/pkg/base"
	"github.com/cockroachdb/cockroach/pkg/roachpb"
	"github.com/cockroachdb/cockroach/pkg/util/envutil"
	"github.com/cockroachdb/cockroach/pkg/util/grpcutil"
	"github.com/cockroachdb/cockroach/pkg/util/hlc"
	"github.com/cockroachdb/cockroach/pkg/util/log"
	"github.com/cockroachdb/cockroach/pkg/util/stop"
	"github.com/cockroachdb/cockroach/pkg/util/syncutil"
	"github.com/cockroachdb/cockroach/pkg/util/timeutil"
)

func init() {
	// Disable GRPC tracing. This retains a subset of messages for
	// display on /debug/requests, which is very expensive for
	// snapshots. Until we can be more selective about what is retained
	// in traces, we must disable tracing entirely.
	// https://github.com/grpc/grpc-go/issues/695
	grpc.EnableTracing = false
}

const (
	defaultHeartbeatInterval = 3 * time.Second
	// The coefficient by which the maximum offset is multiplied to determine the
	// maximum acceptable measurement latency.
	maximumPingDurationMult = 2
)

// SourceAddr provides a way to specify a source/local address for outgoing
// connections. It should only ever be set by testing code, and is not thread
// safe (so it must be initialized before the server starts).
var SourceAddr = envutil.EnvOrDefaultString("COCKROACH_SOURCE_IP_ADDRESS", "")

// NewServer is a thin wrapper around grpc.NewServer that registers a heartbeat
// service.
func NewServer(ctx *Context) *grpc.Server {
	opts := []grpc.ServerOption{
		// The limiting factor for lowering the max message size is the fact
		// that a single large kv can be sent over the network in one message.
		// Our maximum kv size is unlimited, so we need this to be very large.
		// TODO(peter,tamird): need tests before lowering
		grpc.MaxMsgSize(math.MaxInt32),
	}
	if !ctx.Insecure {
		tlsConfig, err := ctx.GetServerTLSConfig()
		if err != nil {
			panic(err)
		}
		opts = append(opts, grpc.Creds(credentials.NewTLS(tlsConfig)))
	}
	s := grpc.NewServer(opts...)
	RegisterHeartbeatServer(s, &HeartbeatService{
		clock:              ctx.LocalClock,
		remoteClockMonitor: ctx.RemoteClocks,
	})
	return s
}

type connMeta struct {
	sync.Once
	conn         *grpc.ClientConn
	dialErr      error
	heartbeatErr error
}

// Context contains the fields required by the rpc framework.
type Context struct {
	*base.Config

	LocalClock   *hlc.Clock
	breakerClock breakerClock
	Stopper      *stop.Stopper
	RemoteClocks *RemoteClockMonitor
	masterCtx    context.Context

	HeartbeatInterval time.Duration
	HeartbeatTimeout  time.Duration
	HeartbeatCB       func()

	localInternalServer roachpb.InternalServer

	conns struct {
		syncutil.Mutex
		cache map[string]*connMeta
	}

	// For unittesting.
	BreakerFactory func() *circuit.Breaker
}

// NewContext creates an rpc Context with the supplied values.
func NewContext(
	ambient log.AmbientContext, baseCtx *base.Config, hlcClock *hlc.Clock, stopper *stop.Stopper,
) *Context {
	if hlcClock == nil {
		panic("nil clock is forbidden")
	}
	ctx := &Context{
		Config:     baseCtx,
		LocalClock: hlcClock,
		breakerClock: breakerClock{
			clock: hlcClock,
		},
	}
	var cancel context.CancelFunc
	ctx.masterCtx, cancel = context.WithCancel(ambient.AnnotateCtx(context.Background()))
	ctx.Stopper = stopper
	ctx.RemoteClocks = newRemoteClockMonitor(
		ctx.LocalClock, 10*defaultHeartbeatInterval, baseCtx.HistogramWindowInterval)
	ctx.HeartbeatInterval = defaultHeartbeatInterval
	ctx.HeartbeatTimeout = 2 * defaultHeartbeatInterval
	ctx.conns.cache = make(map[string]*connMeta)

	stopper.RunWorker(func() {
		<-stopper.ShouldQuiesce()

		cancel()
		ctx.conns.Lock()
		for key, meta := range ctx.conns.cache {
			meta.Do(func() {
				// Make sure initialization is not in progress when we're removing the
				// conn. We need to set the error in case we win the race against the
				// real initialization code.
				if meta.dialErr == nil {
					meta.dialErr = &roachpb.NodeUnavailableError{}
				}
			})
			ctx.removeConnLocked(key, meta)
		}
		ctx.conns.Unlock()
	})

	return ctx
}

// GetLocalInternalServerForAddr returns the context's internal batch server
// for addr, if it exists.
func (ctx *Context) GetLocalInternalServerForAddr(addr string) roachpb.InternalServer {
	if addr == ctx.Addr {
		return ctx.localInternalServer
	}
	return nil
}

// SetLocalInternalServer sets the context's local internal batch server.
func (ctx *Context) SetLocalInternalServer(internalServer roachpb.InternalServer) {
	ctx.localInternalServer = internalServer
}

func (ctx *Context) removeConn(key string, meta *connMeta) {
	ctx.conns.Lock()
	defer ctx.conns.Unlock()
	ctx.removeConnLocked(key, meta)
}

func (ctx *Context) removeConnLocked(key string, meta *connMeta) {
	if log.V(1) {
		log.Infof(ctx.masterCtx, "closing %s", key)
	}
	if conn := meta.conn; conn != nil {
		if err := conn.Close(); err != nil && !grpcutil.IsClosedConnection(err) {
			if log.V(1) {
				log.Errorf(ctx.masterCtx, "failed to close client connection: %s", err)
			}
		}
	}
	delete(ctx.conns.cache, key)
}

// GRPCDial calls grpc.Dial with the options appropriate for the context.
func (ctx *Context) GRPCDial(target string, opts ...grpc.DialOption) (*grpc.ClientConn, error) {
	ctx.conns.Lock()
	meta, ok := ctx.conns.cache[target]
	if !ok {
		meta = &connMeta{
			heartbeatErr: errNotHeartbeated,
		}
		ctx.conns.cache[target] = meta
	}
	ctx.conns.Unlock()

	meta.Do(func() {
		var dialOpt grpc.DialOption
		if ctx.Insecure {
			dialOpt = grpc.WithInsecure()
		} else {
			tlsConfig, err := ctx.GetClientTLSConfig()
			if err != nil {
				meta.dialErr = err
				return
			}
			dialOpt = grpc.WithTransportCredentials(credentials.NewTLS(tlsConfig))
		}

		dialOpts := make([]grpc.DialOption, 0, 2+len(opts))
		dialOpts = append(dialOpts, dialOpt)
		dialOpts = append(dialOpts, grpc.WithBackoffMaxDelay(maxBackoff))
		dialOpts = append(dialOpts, opts...)

		if SourceAddr != "" {
			sourceIP := net.ParseIP(SourceAddr)
			if sourceIP == nil {
				log.Fatalf(ctx.masterCtx,
					"unable to parse COCKROACH_SOURCE_IP_ADDRESS %q as IP address", SourceAddr)
			}
			tcpAddr := &net.TCPAddr{
				IP: sourceIP,
			}
			dialOpts = append(dialOpts, grpc.WithDialer(
				func(addr string, timeout time.Duration) (net.Conn, error) {
					dialer := net.Dialer{
						Timeout:   timeout,
						LocalAddr: tcpAddr,
					}
					return dialer.Dial("tcp", addr)
				},
			))
		}

		if log.V(1) {
			log.Infof(ctx.masterCtx, "dialing %s", target)
		}
		meta.conn, meta.dialErr = grpc.DialContext(ctx.masterCtx, target, dialOpts...)
		if meta.dialErr == nil {
			if err := ctx.Stopper.RunTask(func() {
				ctx.Stopper.RunWorker(func() {
					err := ctx.runHeartbeat(meta, target)
					if err != nil && !grpcutil.IsClosedConnection(err) {
						log.Errorf(ctx.masterCtx, "removing connection to %s due to error: %s", target, err)
					}
					ctx.removeConn(target, meta)
				})
			}); err != nil {
				meta.dialErr = err
				// removeConn and ctx's cleanup worker both lock ctx.conns. However,
				// to avoid racing with meta's initialization, the cleanup worker
				// blocks on meta.Do while holding ctx.conns. Invoke removeConn
				// asynchronously to avoid deadlock.
				go ctx.removeConn(target, meta)
			}
		}
	})

	return meta.conn, meta.dialErr
}

// NewBreaker creates a new circuit breaker properly configured for RPC
// connections.
func (ctx *Context) NewBreaker() *circuit.Breaker {
	if ctx.BreakerFactory != nil {
		return ctx.BreakerFactory()
	}
	return newBreaker(&ctx.breakerClock)
}

var errNotConnected = errors.New("not connected")
var errNotHeartbeated = errors.New("not yet heartbeated")

// ConnHealth returns whether the most recent heartbeat succeeded or not.
// This should not be used as a definite status of a node's health and just used
// to prioritize healthy nodes over unhealthy ones.
func (ctx *Context) ConnHealth(remoteAddr string) error {
	ctx.conns.Lock()
	defer ctx.conns.Unlock()
	if meta, ok := ctx.conns.cache[remoteAddr]; ok {
		return meta.heartbeatErr
	}
	return errNotConnected
}

func (ctx *Context) runHeartbeat(meta *connMeta, remoteAddr string) error {
	maxOffset := ctx.LocalClock.MaxOffset()

	request := PingRequest{
		Addr:           ctx.Addr,
		MaxOffsetNanos: maxOffset.Nanoseconds(),
	}
	heartbeatClient := NewHeartbeatClient(meta.conn)

	var heartbeatTimer timeutil.Timer
	defer heartbeatTimer.Stop()

	// Give the first iteration a wait-free heartbeat attempt.
	heartbeatTimer.Reset(0)
	for {
		select {
		case <-ctx.Stopper.ShouldStop():
			return nil
		case <-heartbeatTimer.C:
			heartbeatTimer.Read = true
		}

		goCtx, cancel := context.WithTimeout(ctx.masterCtx, ctx.HeartbeatTimeout)
		sendTime := ctx.LocalClock.PhysicalTime()
		// NB: We want the request to fail-fast (the default), otherwise we won't
		// be notified of transport failures.
		response, err := heartbeatClient.Ping(goCtx, &request)
		cancel()
		ctx.conns.Lock()
		meta.heartbeatErr = err
		ctx.conns.Unlock()

		// If we got a timeout, we might be experiencing a network partition. We
		// close the connection so that all other pending RPCs (which may not have
		// timeouts) fail eagerly. Any other error is likely to be noticed by
		// other RPCs, so it's OK to leave the connection open while grpc
		// internally reconnects if necessary.
		//
		// NB: This check is skipped when the connection is initiated from a CLI
		// client since those clients aren't sensitive to partitions, are likely
		// to be invoked while the server is starting (particularly in tests), and
		// are not equipped with the retry logic necessary to deal with this
		// connection termination.
		//
		// TODO(tamird): That we rely on the zero maxOffset to indicate a CLI
		// client is a hack; we should do something more explicit.
		if maxOffset != 0 && grpc.Code(err) == codes.DeadlineExceeded {
			return err
		}

		// HACK: work around https://github.com/grpc/grpc-go/issues/1026
		// Getting a "connection refused" error from the "write" system call
		// has confused grpc's error handling and this connection is permanently
		// broken.
		// TODO(bdarnell): remove this when the upstream bug is fixed.
		if err != nil && strings.Contains(err.Error(), "write: connection refused") {
			return nil
		}

		if err == nil {
			receiveTime := ctx.LocalClock.PhysicalTime()

			// Only update the clock offset measurement if we actually got a
			// successful response from the server.
			pingDuration := receiveTime.Sub(sendTime)
			if pingDuration > maximumPingDurationMult*ctx.LocalClock.MaxOffset() {
				request.Offset.Reset()
			} else {
				// Offset and error are measured using the remote clock reading
				// technique described in
				// http://se.inf.tu-dresden.de/pubs/papers/SRDS1994.pdf, page 6.
				// However, we assume that drift and min message delay are 0, for
				// now.
				request.Offset.MeasuredAt = receiveTime.UnixNano()
				request.Offset.Uncertainty = (pingDuration / 2).Nanoseconds()
				remoteTimeNow := time.Unix(0, response.ServerTime).Add(pingDuration / 2)
				request.Offset.Offset = remoteTimeNow.Sub(receiveTime).Nanoseconds()
			}
			ctx.RemoteClocks.UpdateOffset(ctx.masterCtx, remoteAddr, request.Offset, pingDuration)

			if cb := ctx.HeartbeatCB; cb != nil {
				cb()
			}
		}

		heartbeatTimer.Reset(ctx.HeartbeatInterval)
	}
}
<|endoftext|>


<commit_before>/*
Copyright 2011 Google Inc.

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

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

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

package server

import (
	"fmt"
	"html"
	"log"
	"net/http"
	"strings"
	"sync"
	"time"

	"camlistore.org/pkg/blob"
	"camlistore.org/pkg/blobserver"
	"camlistore.org/pkg/camerrors"
	"camlistore.org/pkg/jsonconfig"
	"camlistore.org/pkg/readerutil"
)

var queueSyncInterval = 5 * time.Second

const (
	maxErrors    = 20
	maxCopyTries = 17 // ~36 hours with retryCopyLoop(time.Second ...)
)

// TODO: rate control + tunable
// TODO: expose copierPoolSize as tunable
type SyncHandler struct {
	fromName, fromqName, toName string
	from, fromq, to             blobserver.Storage

	idle bool // if true, the handler does nothing other than providing the discovery.

	copierPoolSize int

	lk             sync.Mutex // protects following
	status         string
	blobStatus     map[string]fmt.Stringer // stringer called with lk held
	recentErrors   []timestampedError
	recentCopyTime time.Time
	totalCopies    int64
	totalCopyBytes int64
	totalErrors    int64
}

func init() {
	blobserver.RegisterHandlerConstructor("sync", newSyncFromConfig)
}

func newSyncFromConfig(ld blobserver.Loader, conf jsonconfig.Obj) (h http.Handler, err error) {
	from := conf.RequiredString("from")
	to := conf.RequiredString("to")
	fullSync := conf.OptionalBool("fullSyncOnStart", false)
	blockFullSync := conf.OptionalBool("blockingFullSyncOnStart", false)
	idle := conf.OptionalBool("idle", false)
	if err = conf.Validate(); err != nil {
		return
	}
	if idle {
		synch, err := createIdleSyncHandler(from, to)
		if err != nil {
			return nil, err
		}
		return synch, nil
	}
	fromBs, err := ld.GetStorage(from)
	if err != nil {
		return
	}
	toBs, err := ld.GetStorage(to)
	if err != nil {
		return
	}
	fromQsc, ok := fromBs.(blobserver.StorageQueueCreator)
	if !ok {
		return nil, fmt.Errorf("Prefix %s (type %T) does not support being efficient replication source (queueing)", from, fromBs)
	}
	synch, err := createSyncHandler(from, to, fromQsc, toBs)
	if err != nil {
		return
	}

	if fullSync || blockFullSync {
		didFullSync := make(chan bool, 1)
		go func() {
			n := synch.runSync("queue", fromQsc, 0)
			log.Printf("Queue sync copied %d blobs", n)
			n = synch.runSync("full", fromBs, 0)
			log.Printf("Full sync copied %d blobs", n)
			didFullSync <- true
			synch.syncQueueLoop()
		}()
		if blockFullSync {
			log.Printf("Blocking startup, waiting for full sync from %q to %q", from, to)
			<-didFullSync
			log.Printf("Full sync complete.")
		}
	} else {
		go synch.syncQueueLoop()
	}

	rootPrefix, _, err := ld.FindHandlerByType("root")
	switch err {
	case blobserver.ErrHandlerTypeNotFound:
		// ignore; okay to not have a root handler.
	case nil:
		h, err := ld.GetHandler(rootPrefix)
		if err != nil {
			return nil, err
		}
		h.(*RootHandler).registerSyncHandler(synch)
	default:
		return nil, fmt.Errorf("Error looking for root handler: %v", err)
	}
	return synch, nil
}

type timestampedError struct {
	t   time.Time
	err error
}

func createSyncHandler(fromName, toName string, from blobserver.StorageQueueCreator, to blobserver.Storage) (*SyncHandler, error) {
	h := &SyncHandler{
		copierPoolSize: 3,
		from:           from,
		to:             to,
		fromName:       fromName,
		toName:         toName,
		status:         "not started",
		blobStatus:     make(map[string]fmt.Stringer),
	}
	h.fromqName = strings.Replace(strings.Trim(toName, "/"), "/", "-", -1)
	var err error
	h.fromq, err = from.CreateQueue(h.fromqName)
	if err != nil {
		return nil, fmt.Errorf("Prefix %s (type %T) failed to create queue %q: %v",
			fromName, from, h.fromqName, err)
	}
	return h, nil
}

func createIdleSyncHandler(fromName, toName string) (*SyncHandler, error) {
	h := &SyncHandler{
		fromName: fromName,
		toName:   toName,
		idle:     true,
		status:   "disabled",
	}
	return h, nil
}

func (sh *SyncHandler) discoveryMap() map[string]interface{} {
	// TODO(mpl): more status info
	return map[string]interface{}{
		"from": sh.fromName,
		"to":   sh.toName,
	}
}

func (sh *SyncHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
	sh.lk.Lock()
	defer sh.lk.Unlock()

	fmt.Fprintf(rw, "
%s to %s Sync Status

Current status: %s
",
		sh.fromName, sh.toName, html.EscapeString(sh.status))
	if sh.idle {
		return
	}

	fmt.Fprintf(rw, "
Stats:

")
	fmt.Fprintf(rw, "
Blobs copied: %d
", sh.totalCopies)
	fmt.Fprintf(rw, "
Bytes copied: %d
", sh.totalCopyBytes)
	if !sh.recentCopyTime.IsZero() {
		fmt.Fprintf(rw, "
Most recent copy: %s
", sh.recentCopyTime.Format(time.RFC3339))
	}
	fmt.Fprintf(rw, "
Copy errors: %d
", sh.totalErrors)
	fmt.Fprintf(rw, "
")

	if len(sh.blobStatus) > 0 {
		fmt.Fprintf(rw, "
Current Copies:

")
		for blobstr, sfn := range sh.blobStatus {
			fmt.Fprintf(rw, "
%s: %s
\n",
				blobstr, html.EscapeString(sfn.String()))
		}
		fmt.Fprintf(rw, "
")
	}

	if len(sh.recentErrors) > 0 {
		fmt.Fprintf(rw, "
Recent Errors:

")
		for _, te := range sh.recentErrors {
			fmt.Fprintf(rw, "
%s: %s
\n",
				te.t.Format(time.RFC3339),
				html.EscapeString(te.err.Error()))
		}
		fmt.Fprintf(rw, "
")
	}
}

func (sh *SyncHandler) setStatus(s string, args ...interface{}) {
	s = time.Now().UTC().Format(time.RFC3339) + ": " + fmt.Sprintf(s, args...)
	sh.lk.Lock()
	defer sh.lk.Unlock()
	sh.status = s
}

func (sh *SyncHandler) setBlobStatus(blobref string, s fmt.Stringer) {
	sh.lk.Lock()
	defer sh.lk.Unlock()
	if s != nil {
		sh.blobStatus[blobref] = s
	} else {
		delete(sh.blobStatus, blobref)
	}
}

func (sh *SyncHandler) addErrorToLog(err error) {
	log.Printf(err.Error())
	sh.lk.Lock()
	defer sh.lk.Unlock()
	sh.recentErrors = append(sh.recentErrors, timestampedError{time.Now().UTC(), err})
	if len(sh.recentErrors) > maxErrors {
		// Kinda lame, but whatever. Only for errors, rare.
		copy(sh.recentErrors[:maxErrors], sh.recentErrors[1:maxErrors+1])
		sh.recentErrors = sh.recentErrors[:maxErrors]
	}
}

type copyResult struct {
	sb  blob.SizedRef
	err error
}

func (sh *SyncHandler) runSync(srcName string, enumSrc blobserver.Storage, longPollWait time.Duration) int {
	if longPollWait != 0 {
		sh.setStatus("Idle; waiting for new blobs")
		// TODO: use longPollWait somehow.
	}
	enumch := make(chan blob.SizedRef)
	errch := make(chan error, 1)
	go func() {
		errch <- enumSrc.EnumerateBlobs(enumch, "", 1000)
	}()

	nCopied := 0
	toCopy := 0

	workch := make(chan blob.SizedRef, 1000)
	resch := make(chan copyResult, 8)
	for sb := range enumch {
		toCopy++
		workch <- sb
		if toCopy <= sh.copierPoolSize {
			go sh.copyWorker(resch, workch)
		}
		sh.setStatus("Enumerating queued blobs: %d", toCopy)
	}
	close(workch)
	for i := 0; i < toCopy; i++ {
		sh.setStatus("Copied %d/%d of batch of queued blobs", nCopied, toCopy)
		res := <-resch
		// TODO(mpl): why is nCopied incremented while res.err hasn't been checked
		// yet? Maybe it should be renamed to nTried?
		nCopied++
		sh.lk.Lock()
		if res.err == nil {
			sh.totalCopies++
			sh.totalCopyBytes += res.sb.Size
			sh.recentCopyTime = time.Now().UTC()
		} else {
			sh.totalErrors++
		}
		sh.lk.Unlock()
	}

	if err := <-errch; err != nil {
		sh.addErrorToLog(fmt.Errorf("replication error for source %q, enumerate from source: %v", srcName, err))
		return nCopied
	}
	return nCopied
}

func (sh *SyncHandler) syncQueueLoop() {
	every(queueSyncInterval, func() {
		for sh.runSync(sh.fromqName, sh.fromq, queueSyncInterval) > 0 {
			// Loop, before sleeping.
		}
		sh.setStatus("Sleeping briefly before next long poll.")
	})
}

func (sh *SyncHandler) copyWorker(res chan<- copyResult, work <-chan blob.SizedRef) {
	for sb := range work {
		res <- copyResult{sb, sh.copyBlob(sb, 0)}
	}
}

func (sh *SyncHandler) retryCopyLoop(initialInterval time.Duration, sb blob.SizedRef) {
	interval := initialInterval
	tryCount := 1
	for {
		if tryCount >= maxCopyTries {
			break
		}
		t1 := time.Now()
		err := sh.copyBlob(sb, tryCount)
		sh.lk.Lock()
		if err == nil {
			sh.totalCopies++
			sh.totalCopyBytes += sb.Size
			sh.recentCopyTime = time.Now().UTC()
			sh.lk.Unlock()
			break
		} else {
			sh.totalErrors++
		}
		sh.lk.Unlock()
		time.Sleep(t1.Add(interval).Sub(time.Now()))
		interval = interval * 2
		tryCount++
	}
}

type statusFunc func() string

func (sf statusFunc) String() string { return sf() }

type status string

func (s status) String() string { return string(s) }

func (sh *SyncHandler) copyBlob(sb blob.SizedRef, tryCount int) error {
	key := sb.Ref.String()
	set := func(s fmt.Stringer) {
		sh.setBlobStatus(key, s)
	}
	defer set(nil)

	errorf := func(s string, args ...interface{}) error {
		// TODO: increment error stats
		pargs := []interface{}{sh.fromqName, sb.Ref}
		pargs = append(pargs, args...)
		err := fmt.Errorf("replication error for queue %q, blob %s: "+s, pargs...)
		sh.addErrorToLog(err)
		return err
	}

	set(status("sending GET to source"))
	rc, fromSize, err := sh.from.FetchStreaming(sb.Ref)
	if err != nil {
		return errorf("source fetch: %v", err)
	}
	defer rc.Close()
	if fromSize != sb.Size {
		return errorf("source fetch size mismatch: get=%d, enumerate=%d", fromSize, sb.Size)
	}

	bytesCopied := int64(0) // TODO: data race, accessed without locking in statusFunc below.
	set(statusFunc(func() string {
		return fmt.Sprintf("copying: %d/%d bytes", bytesCopied, sb.Size)
	}))
	newsb, err := sh.to.ReceiveBlob(sb.Ref, readerutil.CountingReader{rc, &bytesCopied})
	if err != nil {
		if err == camerrors.MissingKeyBlob && tryCount == 0 {
			err := sh.fromq.RemoveBlobs([]blob.Ref{sb.Ref})
			if err != nil {
				return errorf("source queue delete: %v", err)
			}
			// TODO(mpl): instead of doing one goroutine per blob, maybe transfer
			// the "faulty" blobs in a retry queue, and do one goroutine per queue?
			// Also we probably will want to deal with part of this problem in the
			// index layer anyway: http://camlistore.org/issue/102
			go sh.retryCopyLoop(time.Second, sb)
		}
		return errorf("dest write: %v", err)
	}
	if newsb.Size != sb.Size {
		return errorf("write size mismatch: source_read=%d but dest_write=%d", sb.Size, newsb.Size)
	}
	set(status("copied; removing from queue"))
	err = sh.fromq.RemoveBlobs([]blob.Ref{sb.Ref})
	if err != nil {
		return errorf("source queue delete: %v", err)
	}
	return nil
}

func every(interval time.Duration, f func()) {
	for {
		t1 := time.Now()
		f()
		time.Sleep(t1.Add(interval).Sub(time.Now()))
	}
}
<commit_msg>Start of making QueueCreator optional for replication sources.<commit_after>/*
Copyright 2011 Google Inc.

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

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

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

package server

import (
	"fmt"
	"html"
	"log"
	"net/http"
	"strings"
	"sync"
	"time"

	"camlistore.org/pkg/blob"
	"camlistore.org/pkg/blobserver"
	"camlistore.org/pkg/camerrors"
	"camlistore.org/pkg/jsonconfig"
	"camlistore.org/pkg/readerutil"
)

var queueSyncInterval = 5 * time.Second

const (
	maxErrors    = 20
	maxCopyTries = 17 // ~36 hours with retryCopyLoop(time.Second ...)
)

// TODO: rate control + tunable
// TODO: expose copierPoolSize as tunable
type SyncHandler struct {
	fromName, fromqName, toName string
	from, fromq                 blobserver.Storage
	to                          blobserver.BlobReceiver

	idle bool // if true, the handler does nothing other than providing the discovery.

	copierPoolSize int

	lk             sync.Mutex // protects following
	status         string
	blobStatus     map[string]fmt.Stringer // stringer called with lk held
	recentErrors   []timestampedError
	recentCopyTime time.Time
	totalCopies    int64
	totalCopyBytes int64
	totalErrors    int64
}

func init() {
	blobserver.RegisterHandlerConstructor("sync", newSyncFromConfig)
}

func newSyncFromConfig(ld blobserver.Loader, conf jsonconfig.Obj) (http.Handler, error) {
	from := conf.RequiredString("from")
	to := conf.RequiredString("to")
	fullSync := conf.OptionalBool("fullSyncOnStart", false)
	blockFullSync := conf.OptionalBool("blockingFullSyncOnStart", false)
	idle := conf.OptionalBool("idle", false)
	queueDir := conf.OptionalString("queueDir", "")
	if err := conf.Validate(); err != nil {
		return nil, err
	}
	if idle {
		synch, err := createIdleSyncHandler(from, to)
		if err != nil {
			return nil, err
		}
		return synch, nil
	}
	fromBs, err := ld.GetStorage(from)
	if err != nil {
		return nil, err
	}
	toBs, err := ld.GetStorage(to)
	if err != nil {
		return nil, err
	}
	fromQsc, ok := fromBs.(blobserver.StorageQueueCreator)
	if !ok {
		if queueDir != "" {
			// TODO(bradfitz): finish implementing.
			// Should create a a localdisk target on the
			// queueDir and setup sync from that.  Be sure
			// to remove item from top-level TODO too.
		}
		return nil, fmt.Errorf("Prefix %s (type %T) does not support being efficient replication source (queueing)", from, fromBs)
	}

	synch, err := createSyncHandler(from, to, fromQsc, toBs)
	if err != nil {
		return nil, err
	}

	if fullSync || blockFullSync {
		didFullSync := make(chan bool, 1)
		go func() {
			n := synch.runSync("queue", fromQsc, 0)
			log.Printf("Queue sync copied %d blobs", n)
			n = synch.runSync("full", fromBs, 0)
			log.Printf("Full sync copied %d blobs", n)
			didFullSync <- true
			synch.syncQueueLoop()
		}()
		if blockFullSync {
			log.Printf("Blocking startup, waiting for full sync from %q to %q", from, to)
			<-didFullSync
			log.Printf("Full sync complete.")
		}
	} else {
		go synch.syncQueueLoop()
	}

	rootPrefix, _, err := ld.FindHandlerByType("root")
	switch err {
	case blobserver.ErrHandlerTypeNotFound:
		// ignore; okay to not have a root handler.
	case nil:
		h, err := ld.GetHandler(rootPrefix)
		if err != nil {
			return nil, err
		}
		h.(*RootHandler).registerSyncHandler(synch)
	default:
		return nil, fmt.Errorf("Error looking for root handler: %v", err)
	}
	return synch, nil
}

type timestampedError struct {
	t   time.Time
	err error
}

func createSyncHandler(fromName, toName string, from blobserver.StorageQueueCreator, to blobserver.BlobReceiver) (*SyncHandler, error) {
	h := &SyncHandler{
		copierPoolSize: 3,
		from:           from,
		to:             to,
		fromName:       fromName,
		toName:         toName,
		status:         "not started",
		blobStatus:     make(map[string]fmt.Stringer),
	}
	h.fromqName = strings.Replace(strings.Trim(toName, "/"), "/", "-", -1)
	var err error
	h.fromq, err = from.CreateQueue(h.fromqName)
	if err != nil {
		return nil, fmt.Errorf("Prefix %s (type %T) failed to create queue %q: %v",
			fromName, from, h.fromqName, err)
	}
	return h, nil
}

func createIdleSyncHandler(fromName, toName string) (*SyncHandler, error) {
	h := &SyncHandler{
		fromName: fromName,
		toName:   toName,
		idle:     true,
		status:   "disabled",
	}
	return h, nil
}

func (sh *SyncHandler) discoveryMap() map[string]interface{} {
	// TODO(mpl): more status info
	return map[string]interface{}{
		"from": sh.fromName,
		"to":   sh.toName,
	}
}

func (sh *SyncHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
	sh.lk.Lock()
	defer sh.lk.Unlock()

	fmt.Fprintf(rw, "
%s to %s Sync Status

Current status: %s
",
		sh.fromName, sh.toName, html.EscapeString(sh.status))
	if sh.idle {
		return
	}

	fmt.Fprintf(rw, "
Stats:

")
	fmt.Fprintf(rw, "
Blobs copied: %d
", sh.totalCopies)
	fmt.Fprintf(rw, "
Bytes copied: %d
", sh.totalCopyBytes)
	if !sh.recentCopyTime.IsZero() {
		fmt.Fprintf(rw, "
Most recent copy: %s
", sh.recentCopyTime.Format(time.RFC3339))
	}
	fmt.Fprintf(rw, "
Copy errors: %d
", sh.totalErrors)
	fmt.Fprintf(rw, "
")

	if len(sh.blobStatus) > 0 {
		fmt.Fprintf(rw, "
Current Copies:

")
		for blobstr, sfn := range sh.blobStatus {
			fmt.Fprintf(rw, "
%s: %s
\n",
				blobstr, html.EscapeString(sfn.String()))
		}
		fmt.Fprintf(rw, "
")
	}

	if len(sh.recentErrors) > 0 {
		fmt.Fprintf(rw, "
Recent Errors:

")
		for _, te := range sh.recentErrors {
			fmt.Fprintf(rw, "
%s: %s
\n",
				te.t.Format(time.RFC3339),
				html.EscapeString(te.err.Error()))
		}
		fmt.Fprintf(rw, "
")
	}
}

func (sh *SyncHandler) setStatus(s string, args ...interface{}) {
	s = time.Now().UTC().Format(time.RFC3339) + ": " + fmt.Sprintf(s, args...)
	sh.lk.Lock()
	defer sh.lk.Unlock()
	sh.status = s
}

func (sh *SyncHandler) setBlobStatus(blobref string, s fmt.Stringer) {
	sh.lk.Lock()
	defer sh.lk.Unlock()
	if s != nil {
		sh.blobStatus[blobref] = s
	} else {
		delete(sh.blobStatus, blobref)
	}
}

func (sh *SyncHandler) addErrorToLog(err error) {
	log.Printf(err.Error())
	sh.lk.Lock()
	defer sh.lk.Unlock()
	sh.recentErrors = append(sh.recentErrors, timestampedError{time.Now().UTC(), err})
	if len(sh.recentErrors) > maxErrors {
		// Kinda lame, but whatever. Only for errors, rare.
		copy(sh.recentErrors[:maxErrors], sh.recentErrors[1:maxErrors+1])
		sh.recentErrors = sh.recentErrors[:maxErrors]
	}
}

type copyResult struct {
	sb  blob.SizedRef
	err error
}

func (sh *SyncHandler) runSync(srcName string, enumSrc blobserver.Storage, longPollWait time.Duration) int {
	if longPollWait != 0 {
		sh.setStatus("Idle; waiting for new blobs")
		// TODO: use longPollWait somehow.
	}
	enumch := make(chan blob.SizedRef)
	errch := make(chan error, 1)
	go func() {
		errch <- enumSrc.EnumerateBlobs(enumch, "", 1000)
	}()

	nCopied := 0
	toCopy := 0

	workch := make(chan blob.SizedRef, 1000)
	resch := make(chan copyResult, 8)
	for sb := range enumch {
		toCopy++
		workch <- sb
		if toCopy <= sh.copierPoolSize {
			go sh.copyWorker(resch, workch)
		}
		sh.setStatus("Enumerating queued blobs: %d", toCopy)
	}
	close(workch)
	for i := 0; i < toCopy; i++ {
		sh.setStatus("Copied %d/%d of batch of queued blobs", nCopied, toCopy)
		res := <-resch
		// TODO(mpl): why is nCopied incremented while res.err hasn't been checked
		// yet? Maybe it should be renamed to nTried?
		nCopied++
		sh.lk.Lock()
		if res.err == nil {
			sh.totalCopies++
			sh.totalCopyBytes += res.sb.Size
			sh.recentCopyTime = time.Now().UTC()
		} else {
			sh.totalErrors++
		}
		sh.lk.Unlock()
	}

	if err := <-errch; err != nil {
		sh.addErrorToLog(fmt.Errorf("replication error for source %q, enumerate from source: %v", srcName, err))
		return nCopied
	}
	return nCopied
}

func (sh *SyncHandler) syncQueueLoop() {
	every(queueSyncInterval, func() {
		for sh.runSync(sh.fromqName, sh.fromq, queueSyncInterval) > 0 {
			// Loop, before sleeping.
		}
		sh.setStatus("Sleeping briefly before next long poll.")
	})
}

func (sh *SyncHandler) copyWorker(res chan<- copyResult, work <-chan blob.SizedRef) {
	for sb := range work {
		res <- copyResult{sb, sh.copyBlob(sb, 0)}
	}
}

func (sh *SyncHandler) retryCopyLoop(initialInterval time.Duration, sb blob.SizedRef) {
	interval := initialInterval
	tryCount := 1
	for {
		if tryCount >= maxCopyTries {
			break
		}
		t1 := time.Now()
		err := sh.copyBlob(sb, tryCount)
		sh.lk.Lock()
		if err == nil {
			sh.totalCopies++
			sh.totalCopyBytes += sb.Size
			sh.recentCopyTime = time.Now().UTC()
			sh.lk.Unlock()
			break
		} else {
			sh.totalErrors++
		}
		sh.lk.Unlock()
		time.Sleep(t1.Add(interval).Sub(time.Now()))
		interval = interval * 2
		tryCount++
	}
}

type statusFunc func() string

func (sf statusFunc) String() string { return sf() }

type status string

func (s status) String() string { return string(s) }

func (sh *SyncHandler) copyBlob(sb blob.SizedRef, tryCount int) error {
	key := sb.Ref.String()
	set := func(s fmt.Stringer) {
		sh.setBlobStatus(key, s)
	}
	defer set(nil)

	errorf := func(s string, args ...interface{}) error {
		// TODO: increment error stats
		pargs := []interface{}{sh.fromqName, sb.Ref}
		pargs = append(pargs, args...)
		err := fmt.Errorf("replication error for queue %q, blob %s: "+s, pargs...)
		sh.addErrorToLog(err)
		return err
	}

	set(status("sending GET to source"))
	rc, fromSize, err := sh.from.FetchStreaming(sb.Ref)
	if err != nil {
		return errorf("source fetch: %v", err)
	}
	defer rc.Close()
	if fromSize != sb.Size {
		return errorf("source fetch size mismatch: get=%d, enumerate=%d", fromSize, sb.Size)
	}

	bytesCopied := int64(0) // TODO: data race, accessed without locking in statusFunc below.
	set(statusFunc(func() string {
		return fmt.Sprintf("copying: %d/%d bytes", bytesCopied, sb.Size)
	}))
	newsb, err := sh.to.ReceiveBlob(sb.Ref, readerutil.CountingReader{rc, &bytesCopied})
	if err != nil {
		if err == camerrors.MissingKeyBlob && tryCount == 0 {
			err := sh.fromq.RemoveBlobs([]blob.Ref{sb.Ref})
			if err != nil {
				return errorf("source queue delete: %v", err)
			}
			// TODO(mpl): instead of doing one goroutine per blob, maybe transfer
			// the "faulty" blobs in a retry queue, and do one goroutine per queue?
			// Also we probably will want to deal with part of this problem in the
			// index layer anyway: http://camlistore.org/issue/102
			go sh.retryCopyLoop(time.Second, sb)
		}
		return errorf("dest write: %v", err)
	}
	if newsb.Size != sb.Size {
		return errorf("write size mismatch: source_read=%d but dest_write=%d", sb.Size, newsb.Size)
	}
	set(status("copied; removing from queue"))
	err = sh.fromq.RemoveBlobs([]blob.Ref{sb.Ref})
	if err != nil {
		return errorf("source queue delete: %v", err)
	}
	return nil
}

func every(interval time.Duration, f func()) {
	for {
		t1 := time.Now()
		f()
		time.Sleep(t1.Add(interval).Sub(time.Now()))
	}
}
<|endoftext|>


<commit_before>package topom

import (
	"net"
	"net/http"
	"os"
	"sync"
	"time"

	"github.com/wandoulabs/codis/pkg/models"
	"github.com/wandoulabs/codis/pkg/proxy"
	"github.com/wandoulabs/codis/pkg/utils"
	"github.com/wandoulabs/codis/pkg/utils/atomic2"
	"github.com/wandoulabs/codis/pkg/utils/errors"
	"github.com/wandoulabs/codis/pkg/utils/log"
	"github.com/wandoulabs/codis/pkg/utils/rpc"
)

type Topom struct {
	mu sync.RWMutex

	xauth string
	model *models.Topom
	store models.Store

	exit struct {
		C chan struct{}
	}

	online bool
	closed bool

	ladmin net.Listener
	redisp *RedisPool

	config *Config

	mappings [models.MaxSlotNum]*models.SlotMapping

	groups map[int]*models.Group
	glocks map[int]*atomic2.Int64

	proxies map[string]*models.Proxy
	clients map[string]*proxy.ApiClient

	stats struct {
		servers map[string]*ServerStats
		proxies map[string]*ProxyStats
	}
	start sync.Once

	action struct {
		interval atomic2.Int64
		disabled atomic2.Bool
	}
}

var (
	ErrClosedTopom = errors.New("use of closed topom")
	ErrUpdateStore = errors.New("update store failed")
)

func NewWithConfig(store models.Store, config *Config) (*Topom, error) {
	s := &Topom{config: config, store: store}
	s.xauth = rpc.NewXAuth(config.ProductName, config.ProductAuth)
	s.model = &models.Topom{
		StartTime: time.Now().String(),
	}
	s.model.ProductName = config.ProductName
	s.model.Pid = os.Getpid()
	s.model.Pwd, _ = os.Getwd()

	s.action.interval.Set(1000)

	s.redisp = NewRedisPool(config.ProductAuth, time.Minute)

	s.exit.C = make(chan struct{})

	s.groups = make(map[int]*models.Group)
	s.glocks = make(map[int]*atomic2.Int64)

	s.proxies = make(map[string]*models.Proxy)
	s.clients = make(map[string]*proxy.ApiClient)

	s.stats.servers = make(map[string]*ServerStats)
	s.stats.proxies = make(map[string]*ProxyStats)

	if err := s.setup(); err != nil {
		s.Close()
		return nil, err
	}

	log.Infof("[%p] create new topom:\n%s", s, s.model.Encode())

	go s.serveAdmin()

	return s, nil
}

func (s *Topom) setup() error {
	if !utils.IsValidName(s.config.ProductName) {
		return errors.New("invalid product name, empty or using invalid character")
	}

	if l, err := net.Listen("tcp", s.config.AdminAddr); err != nil {
		return errors.Trace(err)
	} else {
		s.ladmin = l
	}

	if addr, err := utils.ResolveAddr("tcp", s.ladmin.Addr().String()); err != nil {
		return err
	} else {
		s.model.AdminAddr = addr
	}

	if err := s.store.Acquire(s.config.ProductName, s.model); err != nil {
		return err
	} else {
		s.online = true
	}

	for i := 0; i < len(s.mappings); i++ {
		if m, err := s.store.LoadSlotMapping(i); err != nil {
			return err
		} else {
			s.mappings[i] = m
		}
	}

	if glist, err := s.store.ListGroup(); err != nil {
		return err
	} else {
		for _, g := range glist {
			s.groups[g.Id] = g
		}
		for _, g := range glist {
			for _, addr := range g.Servers {
				s.stats.servers[addr] = nil
			}
		}
	}

	if plist, err := s.store.ListProxy(); err != nil {
		return err
	} else {
		for _, p := range plist {
			c := proxy.NewApiClient(p.AdminAddr)
			c.SetXAuth(s.config.ProductName, s.config.ProductAuth, p.Token)
			s.proxies[p.Token] = p
			s.clients[p.Token] = c
		}
		for _, p := range plist {
			s.stats.servers[p.Token] = nil
		}
	}
	return nil
}

func (s *Topom) Close() error {
	s.mu.Lock()
	defer s.mu.Unlock()
	if s.closed {
		return nil
	}
	s.closed = true
	close(s.exit.C)

	s.ladmin.Close()
	s.redisp.Close()

	defer s.store.Close()

	if !s.online {
		return nil
	} else {
		return s.store.Release()
	}
}

func (s *Topom) GetXAuth() string {
	return s.xauth
}

func (s *Topom) GetModel() *models.Topom {
	return s.model
}

func (s *Topom) GetConfig() *Config {
	return s.config
}

func (s *Topom) IsOnline() bool {
	s.mu.RLock()
	defer s.mu.RUnlock()
	return s.online && !s.closed
}

func (s *Topom) IsClosed() bool {
	s.mu.RLock()
	defer s.mu.RUnlock()
	return s.closed
}

func (s *Topom) GetActionInterval() int {
	return int(s.action.interval.Get())
}

func (s *Topom) SetActionInterval(ms int) {
	ms = utils.MaxInt(ms, 0)
	ms = utils.MinInt(ms, 1000)
	s.action.interval.Set(int64(ms))
	log.Infof("[%p] set action interval = %d", s, ms)
}

func (s *Topom) GetActionDisabled() bool {
	return s.action.disabled.Get()
}

func (s *Topom) SetActionDisabled(value bool) {
	s.action.disabled.Set(value)
	log.Infof("[%p] set action disabled = %d", s, value)
}

func (s *Topom) serveAdmin() {
	if s.IsClosed() {
		return
	}
	defer s.Close()

	log.Infof("[%p] admin start service on %s", s, s.ladmin.Addr())

	eh := make(chan error, 1)
	go func(l net.Listener) {
		h := http.NewServeMux()
		h.Handle("/", newApiServer(s))
		hs := &http.Server{Handler: h}
		eh <- hs.Serve(l)
	}(s.ladmin)

	select {
	case <-s.exit.C:
		log.Infof("[%p] admin shutdown", s)
	case err := <-eh:
		log.ErrorErrorf(err, "[%p] admin exit on error", s)
	}
}

func (s *Topom) StartDaemonRoutines() {
	s.start.Do(func() {
		go func() {
			for {
				wg := s.RefreshServerStats(time.Second)
				if wg != nil {
					wg.Wait()
				} else {
					return
				}
				time.Sleep(time.Second)
			}
		}()

		go func() {
			for {
				wg := s.RefreshProxyStats(time.Second)
				if wg != nil {
					wg.Wait()
				} else {
					return
				}
				time.Sleep(time.Second)
			}
		}()

		go func() {
			for !s.IsClosed() {
				var slotId int = -1
				if !s.GetActionDisabled() {
					slotId = s.NextActionSlotId()
				}
				if slotId >= 0 {
					if err := s.ProcessAction(slotId); err != nil {
						log.WarnErrorf(err, "[%p] action on slot-[%d] failed", s, slotId)
						time.Sleep(time.Second * 3)
					}
				} else {
					time.Sleep(time.Millisecond * 200)
				}
			}
		}()
	})
}
<commit_msg>Update, cleanup<commit_after>package topom

import (
	"net"
	"net/http"
	"os"
	"sync"
	"time"

	"github.com/wandoulabs/codis/pkg/models"
	"github.com/wandoulabs/codis/pkg/proxy"
	"github.com/wandoulabs/codis/pkg/utils"
	"github.com/wandoulabs/codis/pkg/utils/atomic2"
	"github.com/wandoulabs/codis/pkg/utils/errors"
	"github.com/wandoulabs/codis/pkg/utils/log"
	"github.com/wandoulabs/codis/pkg/utils/rpc"
)

type Topom struct {
	mu sync.RWMutex

	xauth string
	model *models.Topom
	store models.Store

	exit struct {
		C chan struct{}
	}

	online bool
	closed bool

	ladmin net.Listener
	redisp *RedisPool

	config *Config

	mappings [models.MaxSlotNum]*models.SlotMapping

	groups map[int]*models.Group
	glocks map[int]*atomic2.Int64

	proxies map[string]*models.Proxy
	clients map[string]*proxy.ApiClient

	stats struct {
		servers map[string]*ServerStats
		proxies map[string]*ProxyStats
	}
	start sync.Once

	action struct {
		interval atomic2.Int64
		disabled atomic2.Bool
	}
}

var (
	ErrClosedTopom = errors.New("use of closed topom")
	ErrUpdateStore = errors.New("update store failed")
)

func NewWithConfig(store models.Store, config *Config) (*Topom, error) {
	s := &Topom{config: config, store: store}
	s.xauth = rpc.NewXAuth(config.ProductName, config.ProductAuth)
	s.model = &models.Topom{
		StartTime: time.Now().String(),
	}
	s.model.ProductName = config.ProductName
	s.model.Pid = os.Getpid()
	s.model.Pwd, _ = os.Getwd()

	s.action.interval.Set(1000)

	s.redisp = NewRedisPool(config.ProductAuth, time.Minute)

	s.exit.C = make(chan struct{})

	s.groups = make(map[int]*models.Group)
	s.glocks = make(map[int]*atomic2.Int64)

	s.proxies = make(map[string]*models.Proxy)
	s.clients = make(map[string]*proxy.ApiClient)

	s.stats.servers = make(map[string]*ServerStats)
	s.stats.proxies = make(map[string]*ProxyStats)

	if err := s.setup(); err != nil {
		s.Close()
		return nil, err
	}

	log.Infof("[%p] create new topom:\n%s", s, s.model.Encode())

	go s.serveAdmin()

	return s, nil
}

func (s *Topom) setup() error {
	if !utils.IsValidName(s.config.ProductName) {
		return errors.New("invalid product name, empty or using invalid character")
	}

	if l, err := net.Listen("tcp", s.config.AdminAddr); err != nil {
		return errors.Trace(err)
	} else {
		s.ladmin = l
	}

	if addr, err := utils.ResolveAddr("tcp", s.ladmin.Addr().String()); err != nil {
		return err
	} else {
		s.model.AdminAddr = addr
	}

	if err := s.store.Acquire(s.config.ProductName, s.model); err != nil {
		return err
	} else {
		s.online = true
	}

	for i := 0; i < len(s.mappings); i++ {
		if m, err := s.store.LoadSlotMapping(i); err != nil {
			return err
		} else {
			s.mappings[i] = m
		}
	}

	if glist, err := s.store.ListGroup(); err != nil {
		return err
	} else {
		for _, g := range glist {
			s.groups[g.Id] = g
		}
		for _, g := range glist {
			for _, addr := range g.Servers {
				s.stats.servers[addr] = nil
			}
		}
	}

	if plist, err := s.store.ListProxy(); err != nil {
		return err
	} else {
		for _, p := range plist {
			c := proxy.NewApiClient(p.AdminAddr)
			c.SetXAuth(s.config.ProductName, s.config.ProductAuth, p.Token)
			s.proxies[p.Token] = p
			s.clients[p.Token] = c
		}
		for _, p := range plist {
			s.stats.servers[p.Token] = nil
		}
	}
	return nil
}

func (s *Topom) Close() error {
	s.mu.Lock()
	defer s.mu.Unlock()
	if s.closed {
		return nil
	}
	s.closed = true
	close(s.exit.C)

	s.ladmin.Close()
	s.redisp.Close()

	defer s.store.Close()

	if !s.online {
		return nil
	} else {
		return s.store.Release()
	}
}

func (s *Topom) GetXAuth() string {
	return s.xauth
}

func (s *Topom) GetModel() *models.Topom {
	return s.model
}

func (s *Topom) GetConfig() *Config {
	return s.config
}

func (s *Topom) IsOnline() bool {
	s.mu.RLock()
	defer s.mu.RUnlock()
	return s.online && !s.closed
}

func (s *Topom) IsClosed() bool {
	s.mu.RLock()
	defer s.mu.RUnlock()
	return s.closed
}

func (s *Topom) GetActionInterval() int {
	return int(s.action.interval.Get())
}

func (s *Topom) SetActionInterval(ms int) {
	ms = utils.MaxInt(ms, 0)
	ms = utils.MinInt(ms, 1000)
	s.action.interval.Set(int64(ms))
	log.Infof("[%p] set action interval = %d", s, ms)
}

func (s *Topom) GetActionDisabled() bool {
	return s.action.disabled.Get()
}

func (s *Topom) SetActionDisabled(value bool) {
	s.action.disabled.Set(value)
	log.Infof("[%p] set action disabled = %d", s, value)
}

func (s *Topom) serveAdmin() {
	if s.IsClosed() {
		return
	}
	defer s.Close()

	log.Infof("[%p] admin start service on %s", s, s.ladmin.Addr())

	eh := make(chan error, 1)
	go func(l net.Listener) {
		h := http.NewServeMux()
		h.Handle("/", newApiServer(s))
		hs := &http.Server{Handler: h}
		eh <- hs.Serve(l)
	}(s.ladmin)

	select {
	case <-s.exit.C:
		log.Infof("[%p] admin shutdown", s)
	case err := <-eh:
		log.ErrorErrorf(err, "[%p] admin exit on error", s)
	}
}

func (s *Topom) StartDaemonRoutines() {
	s.start.Do(func() {
		go func() {
			for !s.IsClosed() {
				if wg := s.RefreshServerStats(time.Second); wg != nil {
					wg.Wait()
				}
				time.Sleep(time.Second)
			}
		}()

		go func() {
			for !s.IsClosed() {
				if wg := s.RefreshProxyStats(time.Second); wg != nil {
					wg.Wait()
				}
				time.Sleep(time.Second)
			}
		}()

		go func() {
			for !s.IsClosed() {
				var slotId int = -1
				if !s.GetActionDisabled() {
					slotId = s.NextActionSlotId()
				}
				if slotId >= 0 {
					if err := s.ProcessAction(slotId); err != nil {
						log.WarnErrorf(err, "[%p] action on slot-[%d] failed", s, slotId)
						time.Sleep(time.Second * 3)
					}
				} else {
					time.Sleep(time.Millisecond * 200)
				}
			}
		}()
	})
}
<|endoftext|>


<commit_before>// Copyright (C) 2015 Scaleway. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE.md file.

// scw helpers

// Package utils contains helpers
package utils

import (
	"crypto/md5"
	"errors"
	"fmt"
	"io"
	"net"
	"os"
	"os/exec"
	"path"
	"path/filepath"
	"reflect"
	"regexp"
	"strings"
	"time"

	"golang.org/x/crypto/ssh"

	"github.com/Sirupsen/logrus"
	log "github.com/Sirupsen/logrus"
	"github.com/mattn/go-isatty"
	"github.com/moul/gotty-client"
	"github.com/scaleway/scaleway-cli/pkg/sshcommand"
)

// SpawnRedirection is used to redirects the fluxes
type SpawnRedirection struct {
	Stdin  io.Reader
	Stdout io.Writer
	Stderr io.Writer
}

// SSHExec executes a command over SSH and redirects file-descriptors
func SSHExec(publicIPAddress string, privateIPAddress string, command []string, checkConnection bool, gateway string) error {
	gatewayUser := "root"
	gatewayIPAddress := gateway
	if strings.Contains(gateway, "@") {
		parts := strings.Split(gatewayIPAddress, "@")
		gatewayUser = parts[0]
		gatewayIPAddress = parts[1]
		gateway = gatewayUser + "@" + gatewayIPAddress
	}

	if publicIPAddress == "" && gatewayIPAddress == "" {
		return errors.New("server does not have public IP")
	}
	if privateIPAddress == "" && gatewayIPAddress != "" {
		return errors.New("server does not have private IP")
	}

	if checkConnection {
		useGateway := gatewayIPAddress != ""
		if useGateway && !IsTCPPortOpen(fmt.Sprintf("%s:22", gatewayIPAddress)) {
			return errors.New("gateway is not available, try again later")
		}
		if !useGateway && !IsTCPPortOpen(fmt.Sprintf("%s:22", publicIPAddress)) {
			return errors.New("server is not ready, try again later")
		}
	}

	sshCommand := NewSSHExecCmd(publicIPAddress, privateIPAddress, isatty.IsTerminal(os.Stdin.Fd()), command, gateway)

	log.Debugf("Executing: %s", sshCommand)

	spawn := exec.Command("ssh", sshCommand.Slice()[1:]...)
	spawn.Stdout = os.Stdout
	spawn.Stdin = os.Stdin
	spawn.Stderr = os.Stderr
	return spawn.Run()
}

// NewSSHExecCmd computes execve compatible arguments to run a command via ssh
func NewSSHExecCmd(publicIPAddress string, privateIPAddress string, allocateTTY bool, command []string, gatewayIPAddress string) *sshcommand.Command {
	quiet := os.Getenv("DEBUG") != "1"
	secureExec := os.Getenv("SCW_SECURE_EXEC") == "1"
	sshCommand := &sshcommand.Command{
		AllocateTTY:         allocateTTY,
		Command:             command,
		Host:                publicIPAddress,
		Quiet:               quiet,
		SkipHostKeyChecking: !secureExec,
		User:                "root",
		NoEscapeCommand:     true,
	}
	if gatewayIPAddress != "" {
		sshCommand.Host = privateIPAddress
		sshCommand.Gateway = &sshcommand.Command{
			Host:                gatewayIPAddress,
			SkipHostKeyChecking: !secureExec,
			AllocateTTY:         allocateTTY,
			Quiet:               quiet,
			User:                "root",
		}
	}

	return sshCommand
}

// GeneratingAnSSHKey generates an SSH key
func GeneratingAnSSHKey(cfg SpawnRedirection, path string, name string) (string, error) {
	args := []string{
		"-t",
		"rsa",
		"-b",
		"4096",
		"-f",
		filepath.Join(path, name),
		"-N",
		"",
		"-C",
		"",
	}
	log.Infof("Executing commands %v", args)
	spawn := exec.Command("ssh-keygen", args...)
	spawn.Stdout = cfg.Stdout
	spawn.Stdin = cfg.Stdin
	spawn.Stderr = cfg.Stderr
	return args[5], spawn.Run()
}

// WaitForTCPPortOpen calls IsTCPPortOpen in a loop
func WaitForTCPPortOpen(dest string) error {
	for {
		if IsTCPPortOpen(dest) {
			break
		}
		time.Sleep(1 * time.Second)
	}
	return nil
}

// IsTCPPortOpen returns true if a TCP communication with "host:port" can be initialized
func IsTCPPortOpen(dest string) bool {
	conn, err := net.DialTimeout("tcp", dest, time.Duration(2000)*time.Millisecond)
	if err == nil {
		defer conn.Close()
	}
	return err == nil
}

// TruncIf ensures the input string does not exceed max size if cond is met
func TruncIf(str string, max int, cond bool) string {
	if cond && len(str) > max {
		return str[:max]
	}
	return str
}

// Wordify convert complex name to a single word without special shell characters
func Wordify(str string) string {
	str = regexp.MustCompile(`[^a-zA-Z0-9-]`).ReplaceAllString(str, "_")
	str = regexp.MustCompile(`__+`).ReplaceAllString(str, "_")
	str = strings.Trim(str, "_")
	return str
}

// PathToTARPathparts returns the two parts of a unix path
func PathToTARPathparts(fullPath string) (string, string) {
	fullPath = strings.TrimRight(fullPath, "/")
	return path.Dir(fullPath), path.Base(fullPath)
}

// RemoveDuplicates transforms an array into a unique array
func RemoveDuplicates(elements []string) []string {
	encountered := map[string]bool{}

	// Create a map of all unique elements.
	for v := range elements {
		encountered[elements[v]] = true
	}

	// Place all keys from the map into a slice.
	result := []string{}
	for key := range encountered {
		result = append(result, key)
	}
	return result
}

// AttachToSerial tries to connect to server serial using 'gotty-client' and fallback with a help message
func AttachToSerial(serverID string, apiToken string) (*gottyclient.Client, chan bool, error) {
	gottyURL := os.Getenv("SCW_GOTTY_URL")
	if gottyURL == "" {
		gottyURL = "https://tty.scaleway.com/v2/"
	}
	URL := fmt.Sprintf("%s?arg=%s&arg=%s", gottyURL, apiToken, serverID)

	logrus.Debug("Connection to ", URL)
	gottycli, err := gottyclient.NewClient(URL)
	if err != nil {
		return nil, nil, err
	}

	if os.Getenv("SCW_TLSVERIFY") == "0" {
		gottycli.SkipTLSVerify = true
	}

	if err = gottycli.Connect(); err != nil {
		return nil, nil, err
	}
	done := make(chan bool)

	fmt.Println("You are connected, type 'Ctrl+q' to quit.")
	go func() {
		gottycli.Loop()
		gottycli.Close()
		done <- true
	}()
	return gottycli, done, nil
}

func rfc4716hex(data []byte) string {
	fingerprint := ""

	for i := 0; i < len(data); i++ {
		fingerprint = fmt.Sprintf("%s%0.2x", fingerprint, data[i])
		if i != len(data)-1 {
			fingerprint = fingerprint + ":"
		}
	}
	return fingerprint
}

// SSHGetFingerprint returns the fingerprint of an SSH key
func SSHGetFingerprint(key []byte) (string, error) {
	publicKey, comment, _, _, err := ssh.ParseAuthorizedKey(key)
	if err != nil {
		return "", err
	}
	switch reflect.TypeOf(publicKey).String() {
	case "*ssh.rsaPublicKey", "*ssh.dsaPublicKey", "*ssh.ecdsaPublicKey":
		md5sum := md5.Sum(publicKey.Marshal())
		return publicKey.Type() + " " + rfc4716hex(md5sum[:]) + " " + comment, nil
	default:
		return "", errors.New("Can't handle this key")
	}
}
<commit_msg>Use proxy configuration from environment in 'scw attach' command<commit_after>// Copyright (C) 2015 Scaleway. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE.md file.

// scw helpers

// Package utils contains helpers
package utils

import (
	"crypto/md5"
	"errors"
	"fmt"
	"io"
	"net"
	"os"
	"os/exec"
	"path"
	"path/filepath"
	"reflect"
	"regexp"
	"strings"
	"time"

	"golang.org/x/crypto/ssh"

	"github.com/Sirupsen/logrus"
	log "github.com/Sirupsen/logrus"
	"github.com/mattn/go-isatty"
	"github.com/moul/gotty-client"
	"github.com/scaleway/scaleway-cli/pkg/sshcommand"
)

// SpawnRedirection is used to redirects the fluxes
type SpawnRedirection struct {
	Stdin  io.Reader
	Stdout io.Writer
	Stderr io.Writer
}

// SSHExec executes a command over SSH and redirects file-descriptors
func SSHExec(publicIPAddress string, privateIPAddress string, command []string, checkConnection bool, gateway string) error {
	gatewayUser := "root"
	gatewayIPAddress := gateway
	if strings.Contains(gateway, "@") {
		parts := strings.Split(gatewayIPAddress, "@")
		gatewayUser = parts[0]
		gatewayIPAddress = parts[1]
		gateway = gatewayUser + "@" + gatewayIPAddress
	}

	if publicIPAddress == "" && gatewayIPAddress == "" {
		return errors.New("server does not have public IP")
	}
	if privateIPAddress == "" && gatewayIPAddress != "" {
		return errors.New("server does not have private IP")
	}

	if checkConnection {
		useGateway := gatewayIPAddress != ""
		if useGateway && !IsTCPPortOpen(fmt.Sprintf("%s:22", gatewayIPAddress)) {
			return errors.New("gateway is not available, try again later")
		}
		if !useGateway && !IsTCPPortOpen(fmt.Sprintf("%s:22", publicIPAddress)) {
			return errors.New("server is not ready, try again later")
		}
	}

	sshCommand := NewSSHExecCmd(publicIPAddress, privateIPAddress, isatty.IsTerminal(os.Stdin.Fd()), command, gateway)

	log.Debugf("Executing: %s", sshCommand)

	spawn := exec.Command("ssh", sshCommand.Slice()[1:]...)
	spawn.Stdout = os.Stdout
	spawn.Stdin = os.Stdin
	spawn.Stderr = os.Stderr
	return spawn.Run()
}

// NewSSHExecCmd computes execve compatible arguments to run a command via ssh
func NewSSHExecCmd(publicIPAddress string, privateIPAddress string, allocateTTY bool, command []string, gatewayIPAddress string) *sshcommand.Command {
	quiet := os.Getenv("DEBUG") != "1"
	secureExec := os.Getenv("SCW_SECURE_EXEC") == "1"
	sshCommand := &sshcommand.Command{
		AllocateTTY:         allocateTTY,
		Command:             command,
		Host:                publicIPAddress,
		Quiet:               quiet,
		SkipHostKeyChecking: !secureExec,
		User:                "root",
		NoEscapeCommand:     true,
	}
	if gatewayIPAddress != "" {
		sshCommand.Host = privateIPAddress
		sshCommand.Gateway = &sshcommand.Command{
			Host:                gatewayIPAddress,
			SkipHostKeyChecking: !secureExec,
			AllocateTTY:         allocateTTY,
			Quiet:               quiet,
			User:                "root",
		}
	}

	return sshCommand
}

// GeneratingAnSSHKey generates an SSH key
func GeneratingAnSSHKey(cfg SpawnRedirection, path string, name string) (string, error) {
	args := []string{
		"-t",
		"rsa",
		"-b",
		"4096",
		"-f",
		filepath.Join(path, name),
		"-N",
		"",
		"-C",
		"",
	}
	log.Infof("Executing commands %v", args)
	spawn := exec.Command("ssh-keygen", args...)
	spawn.Stdout = cfg.Stdout
	spawn.Stdin = cfg.Stdin
	spawn.Stderr = cfg.Stderr
	return args[5], spawn.Run()
}

// WaitForTCPPortOpen calls IsTCPPortOpen in a loop
func WaitForTCPPortOpen(dest string) error {
	for {
		if IsTCPPortOpen(dest) {
			break
		}
		time.Sleep(1 * time.Second)
	}
	return nil
}

// IsTCPPortOpen returns true if a TCP communication with "host:port" can be initialized
func IsTCPPortOpen(dest string) bool {
	conn, err := net.DialTimeout("tcp", dest, time.Duration(2000)*time.Millisecond)
	if err == nil {
		defer conn.Close()
	}
	return err == nil
}

// TruncIf ensures the input string does not exceed max size if cond is met
func TruncIf(str string, max int, cond bool) string {
	if cond && len(str) > max {
		return str[:max]
	}
	return str
}

// Wordify convert complex name to a single word without special shell characters
func Wordify(str string) string {
	str = regexp.MustCompile(`[^a-zA-Z0-9-]`).ReplaceAllString(str, "_")
	str = regexp.MustCompile(`__+`).ReplaceAllString(str, "_")
	str = strings.Trim(str, "_")
	return str
}

// PathToTARPathparts returns the two parts of a unix path
func PathToTARPathparts(fullPath string) (string, string) {
	fullPath = strings.TrimRight(fullPath, "/")
	return path.Dir(fullPath), path.Base(fullPath)
}

// RemoveDuplicates transforms an array into a unique array
func RemoveDuplicates(elements []string) []string {
	encountered := map[string]bool{}

	// Create a map of all unique elements.
	for v := range elements {
		encountered[elements[v]] = true
	}

	// Place all keys from the map into a slice.
	result := []string{}
	for key := range encountered {
		result = append(result, key)
	}
	return result
}

// AttachToSerial tries to connect to server serial using 'gotty-client' and fallback with a help message
func AttachToSerial(serverID string, apiToken string) (*gottyclient.Client, chan bool, error) {
	gottyURL := os.Getenv("SCW_GOTTY_URL")
	if gottyURL == "" {
		gottyURL = "https://tty.scaleway.com/v2/"
	}
	URL := fmt.Sprintf("%s?arg=%s&arg=%s", gottyURL, apiToken, serverID)

	logrus.Debug("Connection to ", URL)
	gottycli, err := gottyclient.NewClient(URL)
	if err != nil {
		return nil, nil, err
	}

	if os.Getenv("SCW_TLSVERIFY") == "0" {
		gottycli.SkipTLSVerify = true
	}

	gottycli.UseProxyFromEnv = true

	if err = gottycli.Connect(); err != nil {
		return nil, nil, err
	}
	done := make(chan bool)

	fmt.Println("You are connected, type 'Ctrl+q' to quit.")
	go func() {
		gottycli.Loop()
		gottycli.Close()
		done <- true
	}()
	return gottycli, done, nil
}

func rfc4716hex(data []byte) string {
	fingerprint := ""

	for i := 0; i < len(data); i++ {
		fingerprint = fmt.Sprintf("%s%0.2x", fingerprint, data[i])
		if i != len(data)-1 {
			fingerprint = fingerprint + ":"
		}
	}
	return fingerprint
}

// SSHGetFingerprint returns the fingerprint of an SSH key
func SSHGetFingerprint(key []byte) (string, error) {
	publicKey, comment, _, _, err := ssh.ParseAuthorizedKey(key)
	if err != nil {
		return "", err
	}
	switch reflect.TypeOf(publicKey).String() {
	case "*ssh.rsaPublicKey", "*ssh.dsaPublicKey", "*ssh.ecdsaPublicKey":
		md5sum := md5.Sum(publicKey.Marshal())
		return publicKey.Type() + " " + rfc4716hex(md5sum[:]) + " " + comment, nil
	default:
		return "", errors.New("Can't handle this key")
	}
}
<|endoftext|>


<commit_before>package epos

import (
	"errors"
	"fmt"
	"github.com/feyeleanor/atomiser"
	"github.com/feyeleanor/chain"
	"strconv"
	"strings"
)

func Expression(s string) (Condition, error) {
	expr := atomiser.NewAtomiser(strings.NewReader(s)).ReadList()

	if expr == nil {
		return nil, fmt.Errorf("parsing '%s' failed", s)
	}

	return parseExpressionToCondition(expr)
}

func parseExpressionToCondition(expr *chain.Cell) (Condition, error) {
	sym, ok := expr.Car().(atomiser.Symbol)
	if !ok {
		return nil, fmt.Errorf("expected symbol, got '%v' instead", expr.Car())
	}

	sym = atomiser.Symbol(strings.ToLower(string(sym)))

	switch sym {
	case "and":
		return parseAnd(expr.Cdr())
	case "or":
		return parseOr(expr.Cdr())
	case "eq":
		return parseEqual(expr.Cdr())
	case "id":
		return parseId(expr.Cdr())
	}
	return nil, fmt.Errorf("unknown symbol '%s'", sym)
}

func parseAnd(expr *chain.Cell) (Condition, error) {
	cond := &And{}

	cur := expr
	for cur != nil {
		if subexpr, ok := cur.Car().(*chain.Cell); ok {
			if subcond, err := parseExpressionToCondition(subexpr); err != nil {
				return nil, err
			} else {
				*cond = append(*cond, subcond)
			}
		}
		cur = cur.Cdr()
	}

	if len(*cond) == 0 {
		return nil, errors.New("empty and expression")
	}

	return cond, nil
}

func parseOr(expr *chain.Cell) (Condition, error) {
	cond := &Or{}

	cur := expr
	for cur != nil {
		if subexpr, ok := cur.Car().(*chain.Cell); ok {
			if subcond, err := parseExpressionToCondition(subexpr); err != nil {
				return nil, err
			} else {
				*cond = append(*cond, subcond)
			}
		}
		cur = cur.Cdr()
	}

	if len(*cond) == 0 {
		return nil, errors.New("empty or expression")
	}

	return cond, nil
}

func parseEqual(expr *chain.Cell) (Condition, error) {
	cond := &Equals{}

	if expr == nil {
		return nil, errors.New("missing arguments in eq")
	}

	if field, ok := expr.Car().(atomiser.Symbol); !ok {
		return nil, fmt.Errorf("expected field name, got '%#v' instead", expr.Car())
	} else {
		cond.Field = string(field)
	}

	expr = expr.Cdr()
	if expr == nil {
		return nil, fmt.Errorf("missing value in (eq %s) expression", cond.Field)
	}

	cond.Value = fmt.Sprintf("%v", expr.Car())

	return cond, nil
}

func parseId(expr *chain.Cell) (Condition, error) {
	if expr == nil {
		return nil, fmt.Errorf("missing ID value in (id) expression")
	}

	id := new(Id)

	if id_str, ok := expr.Car().(atomiser.Symbol); !ok {
		return nil, fmt.Errorf("expected ID, got '%#v' instead", expr.Car())
	} else {
		parsed_id, err := strconv.ParseInt(string(id_str), 10, 64)
		if err != nil {
			return nil, fmt.Errorf("couldn't parse numeric ID in id expression")
		}
		*id = Id(parsed_id)
	}

	return id, nil
}
<commit_msg>document Expression.<commit_after>package epos

import (
	"errors"
	"fmt"
	"github.com/feyeleanor/atomiser"
	"github.com/feyeleanor/chain"
	"strconv"
	"strings"
)

// Expression converts a S-Expr-based query to a structure of Condition objects.
// The following symbols are available for queries:
//
//    (id 1)					query entry with ID 1
//    (eq field-name value)		query all entries where field-name equals value
//    (or expr...)              OR all query sub-expressions
//    (and expr...)             AND all query sub-expressions
func Expression(s string) (Condition, error) {
	expr := atomiser.NewAtomiser(strings.NewReader(s)).ReadList()

	if expr == nil {
		return nil, fmt.Errorf("parsing '%s' failed", s)
	}

	return parseExpressionToCondition(expr)
}

func parseExpressionToCondition(expr *chain.Cell) (Condition, error) {
	sym, ok := expr.Car().(atomiser.Symbol)
	if !ok {
		return nil, fmt.Errorf("expected symbol, got '%v' instead", expr.Car())
	}

	sym = atomiser.Symbol(strings.ToLower(string(sym)))

	switch sym {
	case "and":
		return parseAnd(expr.Cdr())
	case "or":
		return parseOr(expr.Cdr())
	case "eq":
		return parseEqual(expr.Cdr())
	case "id":
		return parseId(expr.Cdr())
	}
	return nil, fmt.Errorf("unknown symbol '%s'", sym)
}

func parseAnd(expr *chain.Cell) (Condition, error) {
	cond := &And{}

	cur := expr
	for cur != nil {
		if subexpr, ok := cur.Car().(*chain.Cell); ok {
			if subcond, err := parseExpressionToCondition(subexpr); err != nil {
				return nil, err
			} else {
				*cond = append(*cond, subcond)
			}
		}
		cur = cur.Cdr()
	}

	if len(*cond) == 0 {
		return nil, errors.New("empty and expression")
	}

	return cond, nil
}

func parseOr(expr *chain.Cell) (Condition, error) {
	cond := &Or{}

	cur := expr
	for cur != nil {
		if subexpr, ok := cur.Car().(*chain.Cell); ok {
			if subcond, err := parseExpressionToCondition(subexpr); err != nil {
				return nil, err
			} else {
				*cond = append(*cond, subcond)
			}
		}
		cur = cur.Cdr()
	}

	if len(*cond) == 0 {
		return nil, errors.New("empty or expression")
	}

	return cond, nil
}

func parseEqual(expr *chain.Cell) (Condition, error) {
	cond := &Equals{}

	if expr == nil {
		return nil, errors.New("missing arguments in eq")
	}

	if field, ok := expr.Car().(atomiser.Symbol); !ok {
		return nil, fmt.Errorf("expected field name, got '%#v' instead", expr.Car())
	} else {
		cond.Field = string(field)
	}

	expr = expr.Cdr()
	if expr == nil {
		return nil, fmt.Errorf("missing value in (eq %s) expression", cond.Field)
	}

	cond.Value = fmt.Sprintf("%v", expr.Car())

	return cond, nil
}

func parseId(expr *chain.Cell) (Condition, error) {
	if expr == nil {
		return nil, fmt.Errorf("missing ID value in (id) expression")
	}

	id := new(Id)

	if id_str, ok := expr.Car().(atomiser.Symbol); !ok {
		return nil, fmt.Errorf("expected ID, got '%#v' instead", expr.Car())
	} else {
		parsed_id, err := strconv.ParseInt(string(id_str), 10, 64)
		if err != nil {
			return nil, fmt.Errorf("couldn't parse numeric ID in id expression")
		}
		*id = Id(parsed_id)
	}

	return id, nil
}
<|endoftext|>


<commit_before>// Copyright 2011 The Go Authors.  All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

/*
Generating random text: a Markov chain algorithm

Based on the program presented in the "Design and Implementation" chapter
of The Practice of Programming (Kernighan and Pike, Addison-Wesley 1999).
See also Computer Recreations, Scientific American 260, 122 - 125 (1989).

A Markov chain algorithm generates text by creating a statistical model of
potential textual suffixes for a given prefix. Consider this text:

    I am not a number! I am a free man!

Our Markov chain algorithm would arrange this text into this set of prefixes
and suffixes, or "chain": (This table assumes a prefix length of two words.)

    Prefix       Suffix

    "" ""        I
    "" I         am
    I am         a
    I am         not
    a free       man!
    am a         free
    am not       a
    a number!    I
    number! I    am
    not a        number!

To generate text using this table we select an initial prefix ("I am", for
example), choose one of the suffixes associated with that prefix at random
with probability determined by the input statistics ("a"),
and then create a new prefix by removing the first word from the prefix
and appending the suffix (making the new prefix is "am a"). Repeat this process
until we can't find any suffixes for the current prefix or we exceed the word
limit. (The word limit is necessary as the chain table may contain cycles.)

Our version of this program reads text from standard input, parsing it into a
Markov chain, and writes generated text to standard output.
The prefix and output lengths can be specified using the -prefix and -words
flags on the command-line.
*/
package main

import (
    "bufio"
    "flag"
    "fmt"
    "io"
    "math/rand"
    "os"
    "strings"
    "time"
)

// Prefix is a Markov chain prefix of one or more words.
type Prefix []string

// String returns the Prefix as a string (for use as a map key).
func (p Prefix) String() string {
    return strings.Join(p, " ")
}

// Shift removes the first word from the Prefix and appends the given word.
func (p Prefix) Shift(word string) {
    copy(p, p[1:])
    p[len(p)-1] = word
}

// Chain contains a map ("chain") of prefixes to a list of suffixes.
// A prefix is a string of prefixLen words joined with spaces.
// A suffix is a single word. A prefix can have multiple suffixes.
type Chain struct {
    chain     map[string][]string
    prefixLen int
}

// NewChain returns a new Chain with prefixes of prefixLen words.
func NewChain(prefixLen int) *Chain {
    return &Chain{make(map[string][]string), prefixLen}
}

// Build reads text from the provided Reader and
// parses it into prefixes and suffixes that are stored in Chain.
func (c *Chain) Build(r io.Reader) {
    br := bufio.NewReader(r)
    p := make(Prefix, c.prefixLen)
    for {
        var s string
        if _, err := fmt.Fscan(br, &s); err != nil {
            break
        }
        key := p.String()
        c.chain[key] = append(c.chain[key], s)
        p.Shift(s)
    }
}

// Generate returns a string of at most n words generated from Chain.
func (c *Chain) Generate(n int) string {
    p := make(Prefix, c.prefixLen)
    var words []string
    for i := 0; i < n; i++ {
        choices := c.chain[p.String()]
        if len(choices) == 0 {
            break
        }
        next := choices[rand.Intn(len(choices))]
        words = append(words, next)
        p.Shift(next)
    }
    return strings.Join(words, " ")
}

func main() {
    // Register command-line flags.
    numWords := flag.Int("words", 100, "maximum number of words to print")
    prefixLen := flag.Int("prefix", 2, "prefix length in words")

    flag.Parse()                     // Parse command-line flags.
    rand.Seed(time.Now().UnixNano()) // Seed the random number generator.

    c := NewChain(*prefixLen)     // Initialize a new Chain.
    c.Build(os.Stdin)             // Build chains from standard input.
    text := c.Generate(*numWords) // Generate text.
    fmt.Println(text)             // Write text to standard output.
}<commit_msg>remove markov chains<commit_after><|endoftext|>


<commit_before>package vecty

import (
	"reflect"

	"github.com/gopherjs/gopherjs/js"
)

// EventListener is markup that specifies a callback function to be invoked when
// the named DOM event is fired.
type EventListener struct {
	Name                string
	Listener            func(*Event)
	callPreventDefault  bool
	callStopPropagation bool
	wrapper             func(jsEvent *js.Object)
}

// PreventDefault prevents the default behavior of the event from occurring.
//
// See https://developer.mozilla.org/en-US/docs/Web/API/Event/preventDefault.
func (l *EventListener) PreventDefault() *EventListener {
	l.callPreventDefault = true
	return l
}

// StopPropagation prevents further propagation of the current event in the
// capturing and bubbling phases.
//
// See https://developer.mozilla.org/en-US/docs/Web/API/Event/stopPropagation.
func (l *EventListener) StopPropagation() *EventListener {
	l.callStopPropagation = true
	return l
}

// Apply implements the Applyer interface.
func (l *EventListener) Apply(h *HTML) {
	h.eventListeners = append(h.eventListeners, l)
}

// Event represents a DOM event.
type Event struct {
	*js.Object
	Target *js.Object
}

// MarkupOrChild represents one of:
//
//  Component
//  *HTML
//  List
//  nil
//  MarkupList
//
// If the underlying value is not one of these types, the code handling the
// value is expected to panic.
type MarkupOrChild interface{}

func apply(m MarkupOrChild, h *HTML) {
	switch m := m.(type) {
	case MarkupList:
		m.Apply(h)
	case *HTML:
		if m == nil {
			h.children = append(h.children, nil)
			return
		}
		h.children = append(h.children, m)
	case Component, List, nil:
		h.children = append(h.children, m)
	default:
		panic("vecty: invalid type " + reflect.TypeOf(m).String() + " does not match MarkupOrChild interface")
	}
}

// Applyer represents some type of markup (a style, property, data, etc) which
// can be applied to a given HTML element or text node.
type Applyer interface {
	// Apply applies the markup to the given HTML element or text node.
	Apply(h *HTML)
}

type markupFunc func(h *HTML)

func (m markupFunc) Apply(h *HTML) { m(h) }

// Style returns Applyer which applies the given CSS style. Generally, this
// function is not used directly but rather the style subpackage (which is type
// safe) should be used instead.
func Style(key, value string) Applyer {
	return markupFunc(func(h *HTML) {
		if h.styles == nil {
			h.styles = make(map[string]string)
		}
		h.styles[key] = value
	})
}

// Property returns Applyer which applies the given JavaScript property to an
// HTML element or text node. Generally, this function is not used directly but
// rather the prop and style subpackages (which are type safe) should be used instead.
//
// To set style, use style package or Style. Property panics if key is "style".
func Property(key string, value interface{}) Applyer {
	if key == "style" {
		panic(`vecty: Property called with key "style"; style package or Style should be used instead`)
	}
	return markupFunc(func(h *HTML) {
		if h.properties == nil {
			h.properties = make(map[string]interface{})
		}
		h.properties[key] = value
	})
}

// Attribute returns Applyer which applies the given attribute to an element.
//
// In most situations, you should use Property function, or the prop subpackage
// (which is type-safe) instead. There are only a few attributes (aria-*, role,
// etc) which do not have equivalent properties. Always opt for the property
// first, before relying on an attribute.
func Attribute(key string, value interface{}) Applyer {
	return markupFunc(func(h *HTML) {
		if h.attributes == nil {
			h.attributes = make(map[string]interface{})
		}
		h.attributes[key] = value
	})
}

// Data returns Applyer which applies the given data attribute.
func Data(key, value string) Applyer {
	return markupFunc(func(h *HTML) {
		if h.dataset == nil {
			h.dataset = make(map[string]string)
		}
		h.dataset[key] = value
	})
}

// ClassMap is markup that specifies classes to be applied to an element if
// their boolean value are true.
type ClassMap map[string]bool

// Apply implements the Applyer interface.
func (m ClassMap) Apply(h *HTML) {
	var classes []string
	for name, active := range m {
		if active {
			classes = append(classes, name)
		}
	}
	Property("className", join(classes, " ")).Apply(h)
}

// MarkupList represents a list of Applyer which is individually
// applied to an HTML element or text node.
//
// It may only be created through the Markup function.
type MarkupList struct {
	list []Applyer
}

// Apply implements the Applyer interface.
func (m MarkupList) Apply(h *HTML) {
	for _, a := range m.list {
		if a == nil {
			continue
		}
		a.Apply(h)
	}
}

// Markup wraps a list of Applyer which is individually
// applied to an HTML element or text node.
func Markup(m ...Applyer) MarkupList {
	// returns public non-pointer struct value with private field so that users
	// must acquire a MarkupList only from this function, and so that it can
	// never be nil (which would make it indistinguishable from (*HTML)(nil) in
	// a call to e.g. Tag).
	return MarkupList{list: m}
}

// If returns nil if cond is false, otherwise it returns the given markup.
func If(cond bool, markup ...ComponentOrHTML) MarkupOrChild {
	if cond {
		return List(markup)
	}
	return nil
}

// MarkupIf returns nil if cond is false, otherwise it returns the given markup.
func MarkupIf(cond bool, markup ...Applyer) Applyer {
	if cond {
		return Markup(markup...)
	}
	return nil
}

// UnsafeHTML is Applyer which unsafely sets the inner HTML of an HTML element.
//
// It is entirely up to the caller to ensure the input HTML is properly
// sanitized.
//
// It is akin to innerHTML in standard JavaScript and dangerouslySetInnerHTML
// in React, and is said to be unsafe because Vecty makes no effort to validate
// or ensure the HTML is safe for insertion in the DOM. If the HTML came from a
// user, for example, it would create a cross-site-scripting (XSS) exploit in
// the application.
//
// The returned Applyer can only be applied to HTML, not vecty.Text, or else a
// panic will occur.
func UnsafeHTML(html string) Applyer {
	return markupFunc(func(h *HTML) {
		h.innerHTML = html
	})
}

// Namespace is Applyer which sets the namespace URI to associate with the
// created element. This is primarily used when working with, e.g., SVG.
//
// See https://developer.mozilla.org/en-US/docs/Web/API/Document/createElementNS#Valid Namespace URIs
func Namespace(uri string) Applyer {
	return markupFunc(func(h *HTML) {
		h.namespace = uri
	})
}

// join is extracted from the stdlib `strings` package
func join(a []string, sep string) string {
	switch len(a) {
	case 0:
		return ""
	case 1:
		return a[0]
	case 2:
		// Special case for common small values.
		// Remove if golang.org/issue/6714 is fixed
		return a[0] + sep + a[1]
	case 3:
		// Special case for common small values.
		// Remove if golang.org/issue/6714 is fixed
		return a[0] + sep + a[1] + sep + a[2]
	}
	n := len(sep) * (len(a) - 1)
	for i := 0; i < len(a); i++ {
		n += len(a[i])
	}

	b := make([]byte, n)
	bp := copy(b, a[0])
	for _, s := range a[1:] {
		bp += copy(b[bp:], sep)
		bp += copy(b[bp:], s)
	}
	return string(b)
}
<commit_msg>Revert "handle nil *HTML in render (#138)"<commit_after>package vecty

import (
	"reflect"

	"github.com/gopherjs/gopherjs/js"
)

// EventListener is markup that specifies a callback function to be invoked when
// the named DOM event is fired.
type EventListener struct {
	Name                string
	Listener            func(*Event)
	callPreventDefault  bool
	callStopPropagation bool
	wrapper             func(jsEvent *js.Object)
}

// PreventDefault prevents the default behavior of the event from occurring.
//
// See https://developer.mozilla.org/en-US/docs/Web/API/Event/preventDefault.
func (l *EventListener) PreventDefault() *EventListener {
	l.callPreventDefault = true
	return l
}

// StopPropagation prevents further propagation of the current event in the
// capturing and bubbling phases.
//
// See https://developer.mozilla.org/en-US/docs/Web/API/Event/stopPropagation.
func (l *EventListener) StopPropagation() *EventListener {
	l.callStopPropagation = true
	return l
}

// Apply implements the Applyer interface.
func (l *EventListener) Apply(h *HTML) {
	h.eventListeners = append(h.eventListeners, l)
}

// Event represents a DOM event.
type Event struct {
	*js.Object
	Target *js.Object
}

// MarkupOrChild represents one of:
//
//  Component
//  *HTML
//  List
//  nil
//  MarkupList
//
// If the underlying value is not one of these types, the code handling the
// value is expected to panic.
type MarkupOrChild interface{}

func apply(m MarkupOrChild, h *HTML) {
	switch m := m.(type) {
	case MarkupList:
		m.Apply(h)
	case Component, *HTML, List, nil:
		h.children = append(h.children, m)
	default:
		panic("vecty: invalid type " + reflect.TypeOf(m).String() + " does not match MarkupOrChild interface")
	}
}

// Applyer represents some type of markup (a style, property, data, etc) which
// can be applied to a given HTML element or text node.
type Applyer interface {
	// Apply applies the markup to the given HTML element or text node.
	Apply(h *HTML)
}

type markupFunc func(h *HTML)

func (m markupFunc) Apply(h *HTML) { m(h) }

// Style returns Applyer which applies the given CSS style. Generally, this
// function is not used directly but rather the style subpackage (which is type
// safe) should be used instead.
func Style(key, value string) Applyer {
	return markupFunc(func(h *HTML) {
		if h.styles == nil {
			h.styles = make(map[string]string)
		}
		h.styles[key] = value
	})
}

// Property returns Applyer which applies the given JavaScript property to an
// HTML element or text node. Generally, this function is not used directly but
// rather the prop and style subpackages (which are type safe) should be used instead.
//
// To set style, use style package or Style. Property panics if key is "style".
func Property(key string, value interface{}) Applyer {
	if key == "style" {
		panic(`vecty: Property called with key "style"; style package or Style should be used instead`)
	}
	return markupFunc(func(h *HTML) {
		if h.properties == nil {
			h.properties = make(map[string]interface{})
		}
		h.properties[key] = value
	})
}

// Attribute returns Applyer which applies the given attribute to an element.
//
// In most situations, you should use Property function, or the prop subpackage
// (which is type-safe) instead. There are only a few attributes (aria-*, role,
// etc) which do not have equivalent properties. Always opt for the property
// first, before relying on an attribute.
func Attribute(key string, value interface{}) Applyer {
	return markupFunc(func(h *HTML) {
		if h.attributes == nil {
			h.attributes = make(map[string]interface{})
		}
		h.attributes[key] = value
	})
}

// Data returns Applyer which applies the given data attribute.
func Data(key, value string) Applyer {
	return markupFunc(func(h *HTML) {
		if h.dataset == nil {
			h.dataset = make(map[string]string)
		}
		h.dataset[key] = value
	})
}

// ClassMap is markup that specifies classes to be applied to an element if
// their boolean value are true.
type ClassMap map[string]bool

// Apply implements the Applyer interface.
func (m ClassMap) Apply(h *HTML) {
	var classes []string
	for name, active := range m {
		if active {
			classes = append(classes, name)
		}
	}
	Property("className", join(classes, " ")).Apply(h)
}

// MarkupList represents a list of Applyer which is individually
// applied to an HTML element or text node.
//
// It may only be created through the Markup function.
type MarkupList struct {
	list []Applyer
}

// Apply implements the Applyer interface.
func (m MarkupList) Apply(h *HTML) {
	for _, a := range m.list {
		if a == nil {
			continue
		}
		a.Apply(h)
	}
}

// Markup wraps a list of Applyer which is individually
// applied to an HTML element or text node.
func Markup(m ...Applyer) MarkupList {
	// returns public non-pointer struct value with private field so that users
	// must acquire a MarkupList only from this function, and so that it can
	// never be nil (which would make it indistinguishable from (*HTML)(nil) in
	// a call to e.g. Tag).
	return MarkupList{list: m}
}

// If returns nil if cond is false, otherwise it returns the given markup.
func If(cond bool, markup ...ComponentOrHTML) MarkupOrChild {
	if cond {
		return List(markup)
	}
	return nil
}

// MarkupIf returns nil if cond is false, otherwise it returns the given markup.
func MarkupIf(cond bool, markup ...Applyer) Applyer {
	if cond {
		return Markup(markup...)
	}
	return nil
}

// UnsafeHTML is Applyer which unsafely sets the inner HTML of an HTML element.
//
// It is entirely up to the caller to ensure the input HTML is properly
// sanitized.
//
// It is akin to innerHTML in standard JavaScript and dangerouslySetInnerHTML
// in React, and is said to be unsafe because Vecty makes no effort to validate
// or ensure the HTML is safe for insertion in the DOM. If the HTML came from a
// user, for example, it would create a cross-site-scripting (XSS) exploit in
// the application.
//
// The returned Applyer can only be applied to HTML, not vecty.Text, or else a
// panic will occur.
func UnsafeHTML(html string) Applyer {
	return markupFunc(func(h *HTML) {
		h.innerHTML = html
	})
}

// Namespace is Applyer which sets the namespace URI to associate with the
// created element. This is primarily used when working with, e.g., SVG.
//
// See https://developer.mozilla.org/en-US/docs/Web/API/Document/createElementNS#Valid Namespace URIs
func Namespace(uri string) Applyer {
	return markupFunc(func(h *HTML) {
		h.namespace = uri
	})
}

// join is extracted from the stdlib `strings` package
func join(a []string, sep string) string {
	switch len(a) {
	case 0:
		return ""
	case 1:
		return a[0]
	case 2:
		// Special case for common small values.
		// Remove if golang.org/issue/6714 is fixed
		return a[0] + sep + a[1]
	case 3:
		// Special case for common small values.
		// Remove if golang.org/issue/6714 is fixed
		return a[0] + sep + a[1] + sep + a[2]
	}
	n := len(sep) * (len(a) - 1)
	for i := 0; i < len(a); i++ {
		n += len(a[i])
	}

	b := make([]byte, n)
	bp := copy(b, a[0])
	for _, s := range a[1:] {
		bp += copy(b[bp:], sep)
		bp += copy(b[bp:], s)
	}
	return string(b)
}
<|endoftext|>