<commit_before>// +build linux darwin

// Package roaming provides a network-aware Profile that provides appropriate
// options and configuration for a variety of network configurations, including
// being behind 1-1 NATs, using dhcp and auto-configuration for being on
// Google Compute Engine.
//
// The config.Publisher mechanism is used for communicating networking
// settings to the ipc.Server implementation of the runtime and publishes
// the Settings it expects.
package roaming

import (
	"flag"
	"fmt"
	"net"

	"veyron.io/veyron/veyron2"
	"veyron.io/veyron/veyron2/config"
	"veyron.io/veyron/veyron2/ipc"
	"veyron.io/veyron/veyron2/rt"

	"veyron.io/veyron/veyron/lib/flags"
	"veyron.io/veyron/veyron/lib/netconfig"
	"veyron.io/veyron/veyron/lib/netstate"
	"veyron.io/veyron/veyron/profiles"
)

const (
	SettingsStreamName = "roaming"
)

var (
	listenProtocolFlag = flags.TCPProtocolFlag{"tcp"}
	listenAddressFlag  = flags.IPHostPortFlag{Port: "0"}
	listenProxyFlag    string
	ListenSpec         *ipc.ListenSpec
)

func init() {
	flag.Var(&listenProtocolFlag, "veyron.tcp.protocol", "protocol to listen with")
	flag.Var(&listenAddressFlag, "veyron.tcp.address", "address to listen on")
	flag.StringVar(&listenProxyFlag, "veyron.proxy", "", "proxy to use")

	rt.RegisterProfile(New())
	ListenSpec = &ipc.ListenSpec{
		Protocol: listenProtocolFlag.Protocol,
		Address:  listenAddressFlag.String(),
		Proxy:    listenProxyFlag,
	}
}

type profile struct {
	gce string
}

func preferredIPAddress(network string, addrs []net.Addr) (net.Addr, error) {
	if !netstate.IsIPProtocol(network) {
		return nil, fmt.Errorf("can't support network protocol %q", network)
	}
	al := netstate.AddrList(addrs).Map(netstate.ConvertToIPHost)
	for _, predicate := range []netstate.Predicate{netstate.IsPublicUnicastIPv4,
		netstate.IsUnicastIPv4, netstate.IsPublicUnicastIPv6} {
		if a := al.First(predicate); a != nil {
			return a, nil
		}
	}
	return nil, fmt.Errorf("failed to find any usable address for %q", network)
}

func New() veyron2.Profile {
	return &profile{}
}

func (p *profile) Platform() *veyron2.Platform {
	platform, _ := profiles.Platform()
	return platform
}

func (p *profile) Name() string {
	return "dhcp" + p.gce
}

func (p *profile) Runtime() string {
	return ""
}

func (p *profile) String() string {
	return p.Name() + " " + p.Platform().String()
}

func (p *profile) Init(rt veyron2.Runtime, publisher *config.Publisher) {
	log := rt.Logger()

	state, err := netstate.GetAccessibleIPs()
	if err != nil {
		log.Infof("failed to determine network state")
		// TODO(cnicolaou): in a subsequent CL, change Init to return an error.
		return
		//return err
	}
	first := state.First(netstate.IsUnicastIP)
	if first == nil {
		log.Infof("failed to find any usable IP addresses at startup")
	}
	public := netstate.IsPublicUnicastIPv4(first)

	// We now know that there is an IP address to listen on, and whether
	// it's public or private.

	// Our address is private, so we test for running on GCE and for its
	// 1:1 NAT configuration. handleGCE returns a non-nil addr
	// if we are indeed running on GCE.
	if !public {
		if addr := handleGCE(rt, publisher); addr != nil {
			ListenSpec.AddressChooser = func(string, []net.Addr) (net.Addr, error) {
				return addr, nil
			}
			p.gce = "+gce"
			return
		}
	}

	// Create stream in Init function to avoid a race between any
	// goroutines started here and consumers started after Init returns.
	ch := make(chan config.Setting)
	stop, err := publisher.CreateStream(SettingsStreamName, "dhcp", ch)
	if err != nil {
		log.Errorf("failed to create publisher: %s", err)
		return
	}

	protocol := listenProtocolFlag.Protocol
	ListenSpec.StreamPublisher = publisher
	ListenSpec.StreamName = "dhcp"
	ListenSpec.AddressChooser = preferredIPAddress
	log.VI(2).Infof("Initial Network Settings: %s %s available: %s", protocol, listenAddressFlag, state)
	go monitorNetworkSettings(rt, stop, ch, state, ListenSpec)
}

// monitorNetworkSettings will monitor network configuration changes and
// publish subsequent Settings to reflect any changes detected.
func monitorNetworkSettings(rt veyron2.Runtime, stop <-chan struct{},
	ch chan<- config.Setting, prev netstate.AddrList, listenSpec *ipc.ListenSpec) {
	defer close(ch)

	log := rt.Logger()

	// Start the dhcp watcher.
	watcher, err := netconfig.NewNetConfigWatcher()
	if err != nil {
		log.VI(2).Infof("Failed to get new config watcher: %s", err)
		// TODO(cnicolaou): add support for shutting down profiles
		//<-stop
		return
	}

	for {
		select {
		case <-watcher.Channel():
			cur, err := netstate.GetAccessibleIPs()
			if err != nil {
				log.Errorf("failed to read network state: %s", err)
				continue
			}
			removed := netstate.FindRemoved(prev, cur)
			added := netstate.FindAdded(prev, cur)
			log.VI(2).Infof("Previous: %d: %s", len(prev), prev)
			log.VI(2).Infof("Current : %d: %s", len(cur), cur)
			log.VI(2).Infof("Added   : %d: %s", len(added), added)
			log.VI(2).Infof("Removed : %d: %s", len(removed), removed)
			if len(removed) == 0 && len(added) == 0 {
				log.VI(2).Infof("Network event that lead to no address changes since our last 'baseline'")
				continue
			}
			if len(removed) > 0 {
				log.VI(2).Infof("Sending removed: %s", removed)
				ch <- ipc.NewRmAddrsSetting(removed)
			}
			// We will always send the best currently available address
			if chosen, err := listenSpec.AddressChooser(listenSpec.Protocol, cur); err == nil && chosen != nil {
				ch <- ipc.NewAddAddrsSetting([]net.Addr{chosen})
			}
			prev = cur
			// TODO(cnicolaou): add support for shutting down profiles.
			//case <-stop:
			//	return
		}
	}
}
<commit_msg>veyron/profiles/roaming: Move ListenSpec initialization<commit_after>// +build linux darwin

// Package roaming provides a network-aware Profile that provides appropriate
// options and configuration for a variety of network configurations, including
// being behind 1-1 NATs, using dhcp and auto-configuration for being on
// Google Compute Engine.
//
// The config.Publisher mechanism is used for communicating networking
// settings to the ipc.Server implementation of the runtime and publishes
// the Settings it expects.
package roaming

import (
	"flag"
	"fmt"
	"net"

	"veyron.io/veyron/veyron2"
	"veyron.io/veyron/veyron2/config"
	"veyron.io/veyron/veyron2/ipc"
	"veyron.io/veyron/veyron2/rt"

	"veyron.io/veyron/veyron/lib/flags"
	"veyron.io/veyron/veyron/lib/netconfig"
	"veyron.io/veyron/veyron/lib/netstate"
	"veyron.io/veyron/veyron/profiles"
)

const (
	SettingsStreamName = "roaming"
)

var (
	listenProtocolFlag = flags.TCPProtocolFlag{"tcp"}
	listenAddressFlag  = flags.IPHostPortFlag{Port: "0"}
	listenProxyFlag    string
	ListenSpec         *ipc.ListenSpec
)

func init() {
	flag.Var(&listenProtocolFlag, "veyron.tcp.protocol", "protocol to listen with")
	flag.Var(&listenAddressFlag, "veyron.tcp.address", "address to listen on")
	flag.StringVar(&listenProxyFlag, "veyron.proxy", "", "proxy to use")

	rt.RegisterProfile(New())
}

type profile struct {
	gce string
}

func preferredIPAddress(network string, addrs []net.Addr) (net.Addr, error) {
	if !netstate.IsIPProtocol(network) {
		return nil, fmt.Errorf("can't support network protocol %q", network)
	}
	al := netstate.AddrList(addrs).Map(netstate.ConvertToIPHost)
	for _, predicate := range []netstate.Predicate{netstate.IsPublicUnicastIPv4,
		netstate.IsUnicastIPv4, netstate.IsPublicUnicastIPv6} {
		if a := al.First(predicate); a != nil {
			return a, nil
		}
	}
	return nil, fmt.Errorf("failed to find any usable address for %q", network)
}

func New() veyron2.Profile {
	return &profile{}
}

func (p *profile) Platform() *veyron2.Platform {
	platform, _ := profiles.Platform()
	return platform
}

func (p *profile) Name() string {
	return "dhcp" + p.gce
}

func (p *profile) Runtime() string {
	return ""
}

func (p *profile) String() string {
	return p.Name() + " " + p.Platform().String()
}

func (p *profile) Init(rt veyron2.Runtime, publisher *config.Publisher) {
	log := rt.Logger()

	ListenSpec = &ipc.ListenSpec{
		Protocol: listenProtocolFlag.Protocol,
		Address:  listenAddressFlag.String(),
		Proxy:    listenProxyFlag,
	}

	state, err := netstate.GetAccessibleIPs()
	if err != nil {
		log.Infof("failed to determine network state")
		// TODO(cnicolaou): in a subsequent CL, change Init to return an error.
		return
		//return err
	}
	first := state.First(netstate.IsUnicastIP)
	if first == nil {
		log.Infof("failed to find any usable IP addresses at startup")
	}
	public := netstate.IsPublicUnicastIPv4(first)

	// We now know that there is an IP address to listen on, and whether
	// it's public or private.

	// Our address is private, so we test for running on GCE and for its
	// 1:1 NAT configuration. handleGCE returns a non-nil addr
	// if we are indeed running on GCE.
	if !public {
		if addr := handleGCE(rt, publisher); addr != nil {
			ListenSpec.AddressChooser = func(string, []net.Addr) (net.Addr, error) {
				return addr, nil
			}
			p.gce = "+gce"
			return
		}
	}

	// Create stream in Init function to avoid a race between any
	// goroutines started here and consumers started after Init returns.
	ch := make(chan config.Setting)
	stop, err := publisher.CreateStream(SettingsStreamName, "dhcp", ch)
	if err != nil {
		log.Errorf("failed to create publisher: %s", err)
		return
	}

	protocol := listenProtocolFlag.Protocol
	ListenSpec.StreamPublisher = publisher
	ListenSpec.StreamName = "dhcp"
	ListenSpec.AddressChooser = preferredIPAddress
	log.VI(2).Infof("Initial Network Settings: %s %s available: %s", protocol, listenAddressFlag, state)
	go monitorNetworkSettings(rt, stop, ch, state, ListenSpec)
}

// monitorNetworkSettings will monitor network configuration changes and
// publish subsequent Settings to reflect any changes detected.
func monitorNetworkSettings(rt veyron2.Runtime, stop <-chan struct{},
	ch chan<- config.Setting, prev netstate.AddrList, listenSpec *ipc.ListenSpec) {
	defer close(ch)

	log := rt.Logger()

	// Start the dhcp watcher.
	watcher, err := netconfig.NewNetConfigWatcher()
	if err != nil {
		log.VI(2).Infof("Failed to get new config watcher: %s", err)
		// TODO(cnicolaou): add support for shutting down profiles
		//<-stop
		return
	}

	for {
		select {
		case <-watcher.Channel():
			cur, err := netstate.GetAccessibleIPs()
			if err != nil {
				log.Errorf("failed to read network state: %s", err)
				continue
			}
			removed := netstate.FindRemoved(prev, cur)
			added := netstate.FindAdded(prev, cur)
			log.VI(2).Infof("Previous: %d: %s", len(prev), prev)
			log.VI(2).Infof("Current : %d: %s", len(cur), cur)
			log.VI(2).Infof("Added   : %d: %s", len(added), added)
			log.VI(2).Infof("Removed : %d: %s", len(removed), removed)
			if len(removed) == 0 && len(added) == 0 {
				log.VI(2).Infof("Network event that lead to no address changes since our last 'baseline'")
				continue
			}
			if len(removed) > 0 {
				log.VI(2).Infof("Sending removed: %s", removed)
				ch <- ipc.NewRmAddrsSetting(removed)
			}
			// We will always send the best currently available address
			if chosen, err := listenSpec.AddressChooser(listenSpec.Protocol, cur); err == nil && chosen != nil {
				ch <- ipc.NewAddAddrsSetting([]net.Addr{chosen})
			}
			prev = cur
			// TODO(cnicolaou): add support for shutting down profiles.
			//case <-stop:
			//	return
		}
	}
}
<|endoftext|>


<commit_before>// Package flight_test
package flight_test

import (
	"log"
	"net/http/httptest"
	"testing"

	"github.com/blue-jay/blueprint/lib/env"
	"github.com/blue-jay/blueprint/lib/flight"
)

// TestRace tests for race conditions.
func TestRace(t *testing.T) {
	for i := 0; i < 100; i++ {
		go func() {
			// Load the configuration file
			config, err := env.LoadConfig("../../env.json")
			if err != nil {
				t.Fatal(err)
			}

			// Set up the session cookie store
			config.Session.SetupConfig()

			// Set up the views
			config.View.SetTemplates(config.Template.Root, config.Template.Children)

			// Store the view in flight
			flight.StoreConfig(*config)

			// Test the context retrieval
			w := httptest.NewRecorder()
			r := httptest.NewRequest("GET", "http://localhost/foo", nil)
			c := flight.Context(w, r)

			c.Config.Asset.Folder = "foo"
			log.Println(c.Config.Asset.Folder)

			c.View.BaseURI = "bar"
			log.Println(c.View.BaseURI)
		}()
	}
}
<commit_msg>Update test to use example json<commit_after>// Package flight_test
package flight_test

import (
	"log"
	"net/http/httptest"
	"testing"

	"github.com/blue-jay/blueprint/lib/env"
	"github.com/blue-jay/blueprint/lib/flight"
)

// TestRace tests for race conditions.
func TestRace(t *testing.T) {
	for i := 0; i < 100; i++ {
		go func() {
			// Load the configuration file
			config, err := env.LoadConfig("../../env.json.example")
			if err != nil {
				t.Fatal(err)
			}

			// Set up the session cookie store
			config.Session.SetupConfig()

			// Set up the views
			config.View.SetTemplates(config.Template.Root, config.Template.Children)

			// Store the view in flight
			flight.StoreConfig(*config)

			// Test the context retrieval
			w := httptest.NewRecorder()
			r := httptest.NewRequest("GET", "http://localhost/foo", nil)
			c := flight.Context(w, r)

			c.Config.Asset.Folder = "foo"
			log.Println(c.Config.Asset.Folder)

			c.View.BaseURI = "bar"
			log.Println(c.View.BaseURI)
		}()
	}
}
<|endoftext|>


<commit_before>package ns1

//-----------------------------------------------------------------------------
// Package factored import statement:
//-----------------------------------------------------------------------------

import (

	// Stdlib:
	"strings"

	// Community:
	log "github.com/Sirupsen/logrus"
	"github.com/bobtfish/go-nsone-api"
)

//-----------------------------------------------------------------------------
// Typedefs:
//-----------------------------------------------------------------------------

// Data struct for NS1 information.
type Data struct {
	command string
	Link    string   // zone:add |
	Zones   []string // zone:add |
	APIKey  string   // zone:add | record:add
	Zone    string   //          | record:add
	Records []string //          | record:add
}

//-----------------------------------------------------------------------------
// func: AddZones
//-----------------------------------------------------------------------------

// AddZones adds one or more zones to NS1.
func (d *Data) AddZones() {

	// Set the current command:
	d.command = "zone:add"

	// Create an NS1 API client:
	api := nsone.New(d.APIKey)

	// Retrieve the current zone list:
	zones, err := api.GetZones()
	if err != nil {
		log.WithField("cmd", "ec2:"+d.command).Fatal(err)
	}

Zone:
	// For each requested zone:
	for _, e := range d.Zones {

		// New zone handler:
		z := nsone.NewZone(e)

		// Setup link if defined:
		if d.Link != "" {
			z.LinkTo(d.Link)
		}

		// Continue if already exists:
		for _, v := range zones {
			if v.Zone == z.Zone {
				log.WithFields(log.Fields{"cmd": "ns1:" + d.command, "id": e}).
					Info("Using existing DNS zone")
				continue Zone
			}
		}

		// Send the new zone request:
		if err := api.CreateZone(z); err != nil {
			log.WithFields(log.Fields{"cmd": "ns1:" + d.command, "id": e}).Fatal(err)
		}

		// Log zone creation:
		log.WithFields(log.Fields{"cmd": "ns1:" + d.command, "id": e}).
			Info("New DNS zone created")
	}
}

//-----------------------------------------------------------------------------
// func: AddRecords
//-----------------------------------------------------------------------------

// AddRecords adds one or more records to an NS1 zone.
func (d *Data) AddRecords() {

	// Set the current command:
	d.command = "record:add"

	// Create an NS1 API client:
	api := nsone.New(d.APIKey)

Record:
	// For each requested record:
	for _, e := range d.Records {

		// New record handler:
		s := strings.Split(e, ":")
		r1 := nsone.NewRecord(d.Zone, s[2]+"."+d.Zone, s[1])
		r1.Answers = make([]nsone.Answer, 1)
		r1.Answers[0] = nsone.NewAnswer()
		r1.Answers[0].Answer = []string{s[0]}

		// Attempt to retrieve an existing record:
		r2, err := api.GetRecord(d.Zone, s[2]+"."+d.Zone, s[1])
		if err != nil {
			log.WithFields(log.Fields{"cmd": "ns1:" + d.command, "id": e}).Fatal(err)
		}

		// Compare and continue:
		if r1.Domain == r2.Domain {
			log.WithFields(log.Fields{"cmd": "ns1:" + d.command, "id": e}).
				Info("Using existing DNS record")
			continue Record
		}

		// Send the new record request:
		err = api.CreateRecord(r1)
		if err != nil {
			log.WithFields(log.Fields{"cmd": "ns1:" + d.command, "id": e}).Fatal(err)
		}

		// Log record creation:
		log.WithFields(log.Fields{"cmd": "ns1:" + d.command, "id": e}).
			Info("New DNS record created")
	}
}
<commit_msg>#34 update katoctl::ns1::zone::add to ns1-go.v2<commit_after>package ns1

//-----------------------------------------------------------------------------
// Package factored import statement:
//-----------------------------------------------------------------------------

import (

	// Stdlib:
	"net/http"
	"time"

	// Community:
	log "github.com/Sirupsen/logrus"
	api "gopkg.in/ns1/ns1-go.v2/rest"
	"gopkg.in/ns1/ns1-go.v2/rest/model/dns"
)

//-----------------------------------------------------------------------------
// Typedefs:
//-----------------------------------------------------------------------------

// Data struct for NS1 information.
type Data struct {
	command string
	Link    string   // zone:add |
	Zones   []string // zone:add |
	APIKey  string   // zone:add | record:add
	Zone    string   //          | record:add
	Records []string //          | record:add
}

//-----------------------------------------------------------------------------
// func: AddZones
//-----------------------------------------------------------------------------

// AddZones adds one or more zones to NS1.
func (d *Data) AddZones() {

	// Set the current command:
	d.command = "zone:add"

	// Create an NS1 API client:
	httpClient := &http.Client{Timeout: time.Second * 10}
	client := api.NewClient(httpClient, api.SetAPIKey(d.APIKey))

	// For each requested zone:
	for _, zone := range d.Zones {

		// New zone handler:
		z := dns.NewZone(zone)
		if d.Link != "" {
			z.LinkTo(d.Link)
		}

		// Send the new zone request:
		if _, err := client.Zones.Create(z); err != nil {
			if err != api.ErrZoneExists {
				log.WithFields(log.Fields{"cmd": "ns1:" + d.command, "id": zone}).Fatal(err)
			}
		}

		// Log zone creation:
		log.WithFields(log.Fields{"cmd": "ns1:" + d.command, "id": zone}).
			Info("New DNS zone created")
	}
}

//-----------------------------------------------------------------------------
// func: AddRecords
//-----------------------------------------------------------------------------

// AddRecords adds one or more records to an NS1 zone.
func (d *Data) AddRecords() {

	// Set the current command:
	d.command = "record:add"
}
<|endoftext|>


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

package envoy

// MeshConfig defines proxy mesh variables
type MeshConfig struct {
	// DiscoveryAddress is the DNS address for Envoy discovery service
	DiscoveryAddress string
	// MixerAddress is the DNS address for Istio Mixer service
	MixerAddress string
	// ProxyPort is the Envoy proxy port
	ProxyPort int
	// AdminPort is the administrative interface port
	AdminPort int
	// Envoy binary path
	BinaryPath string
	// Envoy config root path
	ConfigPath string
	// Envoy runtime config path
	RuntimePath string
	// Envoy access log path
	AccessLogPath string
}

// Config defines the schema for Envoy JSON configuration format
// See: https://lyft.github.io/envoy/docs/configuration/overview/overview.html
type Config struct {
	RootRuntime    RootRuntime    `json:"runtime"`
	Listeners      []Listener     `json:"listeners"`
	Admin          Admin          `json:"admin"`
	ClusterManager ClusterManager `json:"cluster_manager"`
}

// RootRuntime definition.
// See: https://lyft.github.io/envoy/docs/configuration/overview/overview.html
type RootRuntime struct {
	SymlinkRoot          string `json:"symlink_root"`
	Subdirectory         string `json:"subdirectory"`
	OverrideSubdirectory string `json:"override_subdirectory,omitempty"`
}

// Listener definition
type Listener struct {
	Port           int             `json:"port"`
	Filters        []NetworkFilter `json:"filters"`
	BindToPort     bool            `json:"bind_to_port"`
	UseOriginalDst bool            `json:"use_original_dst,omitempty"`
}

// Admin definition
type Admin struct {
	AccessLogPath string `json:"access_log_path"`
	Port          int    `json:"port"`
}

// ClusterManager definition
type ClusterManager struct {
	Clusters []Cluster `json:"clusters"`
	SDS      SDS       `json:"sds"`
}

// SDS is a service discovery service definition
type SDS struct {
	Cluster        Cluster `json:"cluster"`
	RefreshDelayMs int     `json:"refresh_delay_ms"`
}

// Cluster definition
type Cluster struct {
	Name                     string            `json:"name"`
	ServiceName              string            `json:"service_name,omitempty"`
	ConnectTimeoutMs         int               `json:"connect_timeout_ms"`
	Type                     string            `json:"type"`
	LbType                   string            `json:"lb_type"`
	MaxRequestsPerConnection int               `json:"max_requests_per_connection,omitempty"`
	Hosts                    []Host            `json:"hosts,omitempty"`
	Features                 string            `json:"features,omitempty"`
	CircuitBreaker           *CircuitBreaker   `json:"circuit_breaker,omitempty"`
	OutlierDetection         *OutlierDetection `json:"outlier_detection,omitempty"`
}

// CircuitBreaker definition
// See: https://lyft.github.io/envoy/docs/configuration/cluster_manager/cluster_circuit_breakers.html#circuit-breakers
type CircuitBreaker struct {
	MaxConnections    int `json:"max_connections,omitempty"`
	MaxPendingRequest int `json:"max_pending_requests,omitempty"`
	MaxRequests       int `json:"max_requests,omitempty"`
	MaxRetries        int `json:"max_retries,omitempty"`
}

// OutlierDetection definition
// See: https://lyft.github.io/envoy/docs/configuration/cluster_manager/cluster_runtime.html#outlier-detection
type OutlierDetection struct {
	ConsecutiveError   int `json:"consecutive_5xx,omitempty"`
	IntervalMS         int `json:"interval_ms,omitempty"`
	BaseEjectionTimeMS int `json:"base_ejection_time_ms,omitempty"`
	MaxEjectionPercent int `json:"max_ejection_percent,omitempty"`
}

// Filter definition
type Filter struct {
	Type   string      `json:"type"`
	Name   string      `json:"name"`
	Config interface{} `json:"config"`
}

// FilterRouterConfig definition
type FilterRouterConfig struct {
	// DynamicStats defaults to true
	DynamicStats bool `json:"dynamic_stats,omitempty"`
}

// FilterEndpointsConfig definition
type FilterEndpointsConfig struct {
	ServiceConfig string `json:"service_config,omitempty"`
	ServerConfig  string `json:"server_config,omitempty"`
}

// FilterFaultConfig definition
type FilterFaultConfig struct {
	Abort           *AbortFilter `json:"abort,omitempty"`
	Delay           *DelayFilter `json:"delay,omitempty"`
	Headers         []Header     `json:"headers,omitempty"`
	UpstreamCluster string       `json:"upstream_cluster,omitempty"`
}

// AbortFilter definition
type AbortFilter struct {
	Percent    int `json:"abort_percent,omitempty"`
	HTTPStatus int `json:"http_status,omitempty"`
}

// DelayFilter definition
type DelayFilter struct {
	Type     string `json:"type,omitempty"`
	Percent  int    `json:"fixed_delay_percent,omitempty"`
	Duration int    `json:"fixed_duration_ms,omitempty"`
}

// NetworkFilter definition
type NetworkFilter struct {
	Type   string              `json:"type"`
	Name   string              `json:"name"`
	Config NetworkFilterConfig `json:"config"`
}

// NetworkFilterConfig definition
type NetworkFilterConfig struct {
	CodecType         string      `json:"codec_type"`
	StatPrefix        string      `json:"stat_prefix"`
	GenerateRequestID bool        `json:"generate_request_id,omitempty"`
	RouteConfig       RouteConfig `json:"route_config"`
	Filters           []Filter    `json:"filters"`
	AccessLog         []AccessLog `json:"access_log"`
	Cluster           string      `json:"cluster,omitempty"`
}

// AccessLog definition.
type AccessLog struct {
	Path   string `json:"path"`
	Format string `json:"format,omitempty"`
	Filter string `json:"filter,omitempty"`
}

// RouteConfig definition
type RouteConfig struct {
	VirtualHosts []VirtualHost `json:"virtual_hosts"`
}

// VirtualHost definition
type VirtualHost struct {
	Name    string   `json:"name"`
	Domains []string `json:"domains"`
	Routes  []Route  `json:"routes"`
}

// Route definition
type Route struct {
	Runtime          *Runtime         `json:"runtime,omitempty"`
	Prefix           string           `json:"prefix"`
	PrefixRewrite    string           `json:"prefix_rewrite,omitempty"`
	Cluster          string           `json:"cluster"`
	WeightedClusters *WeightedCluster `json:"weighted_clusters,omitempty"`
	Headers          []Header         `json:"headers,omitempty"`
	TimeoutMS        int              `json:"timeout_ms,omitempty"`
	RetryPolicy      RetryPolicy      `json:"retry_policy"`
}

// RetryPolicy definition
// See: https://lyft.github.io/envoy/docs/configuration/http_conn_man/route_config/route.html#retry-policy
type RetryPolicy struct {
	Policy     string `json:"retry_on"` //5xx,connect-failure,refused-stream
	NumRetries int    `json:"num_retries,omitempty"`
}

// Runtime definition
type Runtime struct {
	Key     string `json:"key"`
	Default int    `json:"default"`
}

// WeightedClusters definition
// See https://lyft.github.io/envoy/docs/configuration/http_conn_man/route_config/route.html
type WeightedCluster struct {
	Clusters         []WeightedClusterEntry `json:"clusters"`
	RuntimeKeyPrefix string                 `json:"runtime_key_prefix,omitempty"`
}

// WeightedClusterEntry definition. Describes the format of each entry in the WeightedCluster
type WeightedClusterEntry struct {
	Name   string `json:"name"`
	Weight int    `json:"weight"`
}

// Header definition
type Header struct {
	Name  string `json:"name"`
	Value string `json:"value"`
	Regex bool   `json:"regex,omitempty"`
}

// Host definition
type Host struct {
	URL string `json:"url"`
}

// Constant values
const (
	LbTypeRoundRobin = "round_robin"
)

// ListenersByPort sorts listeners by port
type ListenersByPort []Listener

func (l ListenersByPort) Len() int {
	return len(l)
}

func (l ListenersByPort) Swap(i, j int) {
	l[i], l[j] = l[j], l[i]
}

func (l ListenersByPort) Less(i, j int) bool {
	return l[i].Port < l[j].Port
}

// ClustersByName sorts clusters by name
type ClustersByName []Cluster

func (s ClustersByName) Len() int {
	return len(s)
}

func (s ClustersByName) Swap(i, j int) {
	s[i], s[j] = s[j], s[i]
}

func (s ClustersByName) Less(i, j int) bool {
	return s[i].Name < s[j].Name
}

// HostsByName sorts clusters by name
type HostsByName []VirtualHost

func (s HostsByName) Len() int {
	return len(s)
}

func (s HostsByName) Swap(i, j int) {
	s[i], s[j] = s[j], s[i]
}

func (s HostsByName) Less(i, j int) bool {
	return s[i].Name < s[j].Name
}

// RoutesByCluster sorts routes by name
type RoutesByCluster []Route

func (r RoutesByCluster) Len() int {
	return len(r)
}

func (r RoutesByCluster) Swap(i, j int) {
	r[i], r[j] = r[j], r[i]
}

func (r RoutesByCluster) Less(i, j int) bool {
	return r[i].Cluster < r[j].Cluster
}

// ByName implements sort
type ByName []Cluster

// Len length
func (a ByName) Len() int {
	return len(a)
}

// Swap elements
func (a ByName) Swap(i, j int) {
	a[i], a[j] = a[j], a[i]
}

// Less compare
func (a ByName) Less(i, j int) bool {
	return a[i].Name < a[j].Name
}

//ByHost implement sort
type ByHost []Host

// Len length
func (a ByHost) Len() int {
	return len(a)
}

// Swap elements
func (a ByHost) Swap(i, j int) {
	a[i], a[j] = a[j], a[i]
}

// Less compare
func (a ByHost) Less(i, j int) bool {
	return a[i].URL < a[j].URL
}
<commit_msg>satisfy lint<commit_after>// Copyright 2017 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package envoy

// MeshConfig defines proxy mesh variables
type MeshConfig struct {
	// DiscoveryAddress is the DNS address for Envoy discovery service
	DiscoveryAddress string
	// MixerAddress is the DNS address for Istio Mixer service
	MixerAddress string
	// ProxyPort is the Envoy proxy port
	ProxyPort int
	// AdminPort is the administrative interface port
	AdminPort int
	// Envoy binary path
	BinaryPath string
	// Envoy config root path
	ConfigPath string
	// Envoy runtime config path
	RuntimePath string
	// Envoy access log path
	AccessLogPath string
}

// Config defines the schema for Envoy JSON configuration format
// See: https://lyft.github.io/envoy/docs/configuration/overview/overview.html
type Config struct {
	RootRuntime    RootRuntime    `json:"runtime"`
	Listeners      []Listener     `json:"listeners"`
	Admin          Admin          `json:"admin"`
	ClusterManager ClusterManager `json:"cluster_manager"`
}

// RootRuntime definition.
// See: https://lyft.github.io/envoy/docs/configuration/overview/overview.html
type RootRuntime struct {
	SymlinkRoot          string `json:"symlink_root"`
	Subdirectory         string `json:"subdirectory"`
	OverrideSubdirectory string `json:"override_subdirectory,omitempty"`
}

// Listener definition
type Listener struct {
	Port           int             `json:"port"`
	Filters        []NetworkFilter `json:"filters"`
	BindToPort     bool            `json:"bind_to_port"`
	UseOriginalDst bool            `json:"use_original_dst,omitempty"`
}

// Admin definition
type Admin struct {
	AccessLogPath string `json:"access_log_path"`
	Port          int    `json:"port"`
}

// ClusterManager definition
type ClusterManager struct {
	Clusters []Cluster `json:"clusters"`
	SDS      SDS       `json:"sds"`
}

// SDS is a service discovery service definition
type SDS struct {
	Cluster        Cluster `json:"cluster"`
	RefreshDelayMs int     `json:"refresh_delay_ms"`
}

// Cluster definition
type Cluster struct {
	Name                     string            `json:"name"`
	ServiceName              string            `json:"service_name,omitempty"`
	ConnectTimeoutMs         int               `json:"connect_timeout_ms"`
	Type                     string            `json:"type"`
	LbType                   string            `json:"lb_type"`
	MaxRequestsPerConnection int               `json:"max_requests_per_connection,omitempty"`
	Hosts                    []Host            `json:"hosts,omitempty"`
	Features                 string            `json:"features,omitempty"`
	CircuitBreaker           *CircuitBreaker   `json:"circuit_breaker,omitempty"`
	OutlierDetection         *OutlierDetection `json:"outlier_detection,omitempty"`
}

// CircuitBreaker definition
// See: https://lyft.github.io/envoy/docs/configuration/cluster_manager/cluster_circuit_breakers.html#circuit-breakers
type CircuitBreaker struct {
	MaxConnections    int `json:"max_connections,omitempty"`
	MaxPendingRequest int `json:"max_pending_requests,omitempty"`
	MaxRequests       int `json:"max_requests,omitempty"`
	MaxRetries        int `json:"max_retries,omitempty"`
}

// OutlierDetection definition
// See: https://lyft.github.io/envoy/docs/configuration/cluster_manager/cluster_runtime.html#outlier-detection
type OutlierDetection struct {
	ConsecutiveError   int `json:"consecutive_5xx,omitempty"`
	IntervalMS         int `json:"interval_ms,omitempty"`
	BaseEjectionTimeMS int `json:"base_ejection_time_ms,omitempty"`
	MaxEjectionPercent int `json:"max_ejection_percent,omitempty"`
}

// Filter definition
type Filter struct {
	Type   string      `json:"type"`
	Name   string      `json:"name"`
	Config interface{} `json:"config"`
}

// FilterRouterConfig definition
type FilterRouterConfig struct {
	// DynamicStats defaults to true
	DynamicStats bool `json:"dynamic_stats,omitempty"`
}

// FilterEndpointsConfig definition
type FilterEndpointsConfig struct {
	ServiceConfig string `json:"service_config,omitempty"`
	ServerConfig  string `json:"server_config,omitempty"`
}

// FilterFaultConfig definition
type FilterFaultConfig struct {
	Abort           *AbortFilter `json:"abort,omitempty"`
	Delay           *DelayFilter `json:"delay,omitempty"`
	Headers         []Header     `json:"headers,omitempty"`
	UpstreamCluster string       `json:"upstream_cluster,omitempty"`
}

// AbortFilter definition
type AbortFilter struct {
	Percent    int `json:"abort_percent,omitempty"`
	HTTPStatus int `json:"http_status,omitempty"`
}

// DelayFilter definition
type DelayFilter struct {
	Type     string `json:"type,omitempty"`
	Percent  int    `json:"fixed_delay_percent,omitempty"`
	Duration int    `json:"fixed_duration_ms,omitempty"`
}

// NetworkFilter definition
type NetworkFilter struct {
	Type   string              `json:"type"`
	Name   string              `json:"name"`
	Config NetworkFilterConfig `json:"config"`
}

// NetworkFilterConfig definition
type NetworkFilterConfig struct {
	CodecType         string      `json:"codec_type"`
	StatPrefix        string      `json:"stat_prefix"`
	GenerateRequestID bool        `json:"generate_request_id,omitempty"`
	RouteConfig       RouteConfig `json:"route_config"`
	Filters           []Filter    `json:"filters"`
	AccessLog         []AccessLog `json:"access_log"`
	Cluster           string      `json:"cluster,omitempty"`
}

// AccessLog definition.
type AccessLog struct {
	Path   string `json:"path"`
	Format string `json:"format,omitempty"`
	Filter string `json:"filter,omitempty"`
}

// RouteConfig definition
type RouteConfig struct {
	VirtualHosts []VirtualHost `json:"virtual_hosts"`
}

// VirtualHost definition
type VirtualHost struct {
	Name    string   `json:"name"`
	Domains []string `json:"domains"`
	Routes  []Route  `json:"routes"`
}

// Route definition
type Route struct {
	Runtime          *Runtime         `json:"runtime,omitempty"`
	Prefix           string           `json:"prefix"`
	PrefixRewrite    string           `json:"prefix_rewrite,omitempty"`
	Cluster          string           `json:"cluster"`
	WeightedClusters *WeightedCluster `json:"weighted_clusters,omitempty"`
	Headers          []Header         `json:"headers,omitempty"`
	TimeoutMS        int              `json:"timeout_ms,omitempty"`
	RetryPolicy      RetryPolicy      `json:"retry_policy"`
}

// RetryPolicy definition
// See: https://lyft.github.io/envoy/docs/configuration/http_conn_man/route_config/route.html#retry-policy
type RetryPolicy struct {
	Policy     string `json:"retry_on"` //5xx,connect-failure,refused-stream
	NumRetries int    `json:"num_retries,omitempty"`
}

// Runtime definition
type Runtime struct {
	Key     string `json:"key"`
	Default int    `json:"default"`
}

// WeightedCluster definition
// See https://lyft.github.io/envoy/docs/configuration/http_conn_man/route_config/route.html
type WeightedCluster struct {
	Clusters         []WeightedClusterEntry `json:"clusters"`
	RuntimeKeyPrefix string                 `json:"runtime_key_prefix,omitempty"`
}

// WeightedClusterEntry definition. Describes the format of each entry in the WeightedCluster
type WeightedClusterEntry struct {
	Name   string `json:"name"`
	Weight int    `json:"weight"`
}

// Header definition
type Header struct {
	Name  string `json:"name"`
	Value string `json:"value"`
	Regex bool   `json:"regex,omitempty"`
}

// Host definition
type Host struct {
	URL string `json:"url"`
}

// Constant values
const (
	LbTypeRoundRobin = "round_robin"
)

// ListenersByPort sorts listeners by port
type ListenersByPort []Listener

func (l ListenersByPort) Len() int {
	return len(l)
}

func (l ListenersByPort) Swap(i, j int) {
	l[i], l[j] = l[j], l[i]
}

func (l ListenersByPort) Less(i, j int) bool {
	return l[i].Port < l[j].Port
}

// ClustersByName sorts clusters by name
type ClustersByName []Cluster

func (s ClustersByName) Len() int {
	return len(s)
}

func (s ClustersByName) Swap(i, j int) {
	s[i], s[j] = s[j], s[i]
}

func (s ClustersByName) Less(i, j int) bool {
	return s[i].Name < s[j].Name
}

// HostsByName sorts clusters by name
type HostsByName []VirtualHost

func (s HostsByName) Len() int {
	return len(s)
}

func (s HostsByName) Swap(i, j int) {
	s[i], s[j] = s[j], s[i]
}

func (s HostsByName) Less(i, j int) bool {
	return s[i].Name < s[j].Name
}

// RoutesByCluster sorts routes by name
type RoutesByCluster []Route

func (r RoutesByCluster) Len() int {
	return len(r)
}

func (r RoutesByCluster) Swap(i, j int) {
	r[i], r[j] = r[j], r[i]
}

func (r RoutesByCluster) Less(i, j int) bool {
	return r[i].Cluster < r[j].Cluster
}

// ByName implements sort
type ByName []Cluster

// Len length
func (a ByName) Len() int {
	return len(a)
}

// Swap elements
func (a ByName) Swap(i, j int) {
	a[i], a[j] = a[j], a[i]
}

// Less compare
func (a ByName) Less(i, j int) bool {
	return a[i].Name < a[j].Name
}

//ByHost implement sort
type ByHost []Host

// Len length
func (a ByHost) Len() int {
	return len(a)
}

// Swap elements
func (a ByHost) Swap(i, j int) {
	a[i], a[j] = a[j], a[i]
}

// Less compare
func (a ByHost) Less(i, j int) bool {
	return a[i].URL < a[j].URL
}
<|endoftext|>


<commit_before>package freedom

import (
	"io"

	"errors"
	"v2ray.com/core/app"
	"v2ray.com/core/app/dns"
	"v2ray.com/core/common/alloc"
	"v2ray.com/core/common/dice"
	v2io "v2ray.com/core/common/io"
	"v2ray.com/core/common/loader"
	"v2ray.com/core/common/log"
	v2net "v2ray.com/core/common/net"
	"v2ray.com/core/common/retry"
	"v2ray.com/core/proxy"
	"v2ray.com/core/proxy/registry"
	"v2ray.com/core/transport/internet"
	"v2ray.com/core/transport/internet/tcp"
	"v2ray.com/core/transport/ray"
)

type FreedomConnection struct {
	domainStrategy Config_DomainStrategy
	timeout        uint32
	dns            dns.Server
	meta           *proxy.OutboundHandlerMeta
}

func NewFreedomConnection(config *Config, space app.Space, meta *proxy.OutboundHandlerMeta) *FreedomConnection {
	f := &FreedomConnection{
		domainStrategy: config.DomainStrategy,
		timeout:        config.Timeout,
		meta:           meta,
	}
	space.InitializeApplication(func() error {
		if config.DomainStrategy == Config_USE_IP {
			if !space.HasApp(dns.APP_ID) {
				return errors.New("Freedom: DNS server is not found in the space.")
			}
			f.dns = space.GetApp(dns.APP_ID).(dns.Server)
		}
		return nil
	})
	return f
}

// Private: Visible for testing.
func (this *FreedomConnection) ResolveIP(destination v2net.Destination) v2net.Destination {
	if !destination.Address.Family().IsDomain() {
		return destination
	}

	ips := this.dns.Get(destination.Address.Domain())
	if len(ips) == 0 {
		log.Info("Freedom: DNS returns nil answer. Keep domain as is.")
		return destination
	}

	ip := ips[dice.Roll(len(ips))]
	var newDest v2net.Destination
	if destination.Network == v2net.Network_TCP {
		newDest = v2net.TCPDestination(v2net.IPAddress(ip), destination.Port)
	} else {
		newDest = v2net.UDPDestination(v2net.IPAddress(ip), destination.Port)
	}
	log.Info("Freedom: Changing destination from ", destination, " to ", newDest)
	return newDest
}

func (this *FreedomConnection) Dispatch(destination v2net.Destination, payload *alloc.Buffer, ray ray.OutboundRay) error {
	log.Info("Freedom: Opening connection to ", destination)

	defer payload.Release()
	defer ray.OutboundInput().Release()
	defer ray.OutboundOutput().Close()

	var conn internet.Connection
	if this.domainStrategy == Config_USE_IP && destination.Address.Family().IsDomain() {
		destination = this.ResolveIP(destination)
	}
	err := retry.ExponentialBackoff(5, 100).On(func() error {
		rawConn, err := internet.Dial(this.meta.Address, destination, this.meta.GetDialerOptions())
		if err != nil {
			return err
		}
		conn = rawConn
		return nil
	})
	if err != nil {
		log.Warning("Freedom: Failed to open connection to ", destination, ": ", err)
		return err
	}
	defer conn.Close()

	input := ray.OutboundInput()
	output := ray.OutboundOutput()

	if !payload.IsEmpty() {
		conn.Write(payload.Value)
	}

	go func() {
		v2writer := v2io.NewAdaptiveWriter(conn)
		defer v2writer.Release()

		if err := v2io.PipeUntilEOF(input, v2writer); err != nil {
			log.Info("Freedom: Failed to transport all TCP request: ", err)
		}
		if tcpConn, ok := conn.(*tcp.RawConnection); ok {
			tcpConn.CloseWrite()
		}
	}()

	var reader io.Reader = conn

	timeout := this.timeout
	if destination.Network == v2net.Network_UDP {
		timeout = 16
	}
	if timeout > 0 {
		reader = v2net.NewTimeOutReader(timeout /* seconds */, conn)
	}

	v2reader := v2io.NewAdaptiveReader(reader)
	if err := v2io.PipeUntilEOF(v2reader, output); err != nil {
		log.Info("Freedom: Failed to transport all TCP response: ", err)
	}
	v2reader.Release()
	ray.OutboundOutput().Close()

	return nil
}

type FreedomFactory struct{}

func (this *FreedomFactory) StreamCapability() v2net.NetworkList {
	return v2net.NetworkList{
		Network: []v2net.Network{v2net.Network_RawTCP},
	}
}

func (this *FreedomFactory) Create(space app.Space, config interface{}, meta *proxy.OutboundHandlerMeta) (proxy.OutboundHandler, error) {
	return NewFreedomConnection(config.(*Config), space, meta), nil
}

func init() {
	registry.MustRegisterOutboundHandlerCreator(loader.GetType(new(Config)), new(FreedomFactory))
}
<commit_msg>fix import<commit_after>package freedom

import (
	"errors"
	"io"
	"v2ray.com/core/app"
	"v2ray.com/core/app/dns"
	"v2ray.com/core/common/alloc"
	"v2ray.com/core/common/dice"
	v2io "v2ray.com/core/common/io"
	"v2ray.com/core/common/loader"
	"v2ray.com/core/common/log"
	v2net "v2ray.com/core/common/net"
	"v2ray.com/core/common/retry"
	"v2ray.com/core/proxy"
	"v2ray.com/core/proxy/registry"
	"v2ray.com/core/transport/internet"
	"v2ray.com/core/transport/internet/tcp"
	"v2ray.com/core/transport/ray"
)

type FreedomConnection struct {
	domainStrategy Config_DomainStrategy
	timeout        uint32
	dns            dns.Server
	meta           *proxy.OutboundHandlerMeta
}

func NewFreedomConnection(config *Config, space app.Space, meta *proxy.OutboundHandlerMeta) *FreedomConnection {
	f := &FreedomConnection{
		domainStrategy: config.DomainStrategy,
		timeout:        config.Timeout,
		meta:           meta,
	}
	space.InitializeApplication(func() error {
		if config.DomainStrategy == Config_USE_IP {
			if !space.HasApp(dns.APP_ID) {
				return errors.New("Freedom: DNS server is not found in the space.")
			}
			f.dns = space.GetApp(dns.APP_ID).(dns.Server)
		}
		return nil
	})
	return f
}

// Private: Visible for testing.
func (this *FreedomConnection) ResolveIP(destination v2net.Destination) v2net.Destination {
	if !destination.Address.Family().IsDomain() {
		return destination
	}

	ips := this.dns.Get(destination.Address.Domain())
	if len(ips) == 0 {
		log.Info("Freedom: DNS returns nil answer. Keep domain as is.")
		return destination
	}

	ip := ips[dice.Roll(len(ips))]
	var newDest v2net.Destination
	if destination.Network == v2net.Network_TCP {
		newDest = v2net.TCPDestination(v2net.IPAddress(ip), destination.Port)
	} else {
		newDest = v2net.UDPDestination(v2net.IPAddress(ip), destination.Port)
	}
	log.Info("Freedom: Changing destination from ", destination, " to ", newDest)
	return newDest
}

func (this *FreedomConnection) Dispatch(destination v2net.Destination, payload *alloc.Buffer, ray ray.OutboundRay) error {
	log.Info("Freedom: Opening connection to ", destination)

	defer payload.Release()
	defer ray.OutboundInput().Release()
	defer ray.OutboundOutput().Close()

	var conn internet.Connection
	if this.domainStrategy == Config_USE_IP && destination.Address.Family().IsDomain() {
		destination = this.ResolveIP(destination)
	}
	err := retry.ExponentialBackoff(5, 100).On(func() error {
		rawConn, err := internet.Dial(this.meta.Address, destination, this.meta.GetDialerOptions())
		if err != nil {
			return err
		}
		conn = rawConn
		return nil
	})
	if err != nil {
		log.Warning("Freedom: Failed to open connection to ", destination, ": ", err)
		return err
	}
	defer conn.Close()

	input := ray.OutboundInput()
	output := ray.OutboundOutput()

	if !payload.IsEmpty() {
		conn.Write(payload.Value)
	}

	go func() {
		v2writer := v2io.NewAdaptiveWriter(conn)
		defer v2writer.Release()

		if err := v2io.PipeUntilEOF(input, v2writer); err != nil {
			log.Info("Freedom: Failed to transport all TCP request: ", err)
		}
		if tcpConn, ok := conn.(*tcp.RawConnection); ok {
			tcpConn.CloseWrite()
		}
	}()

	var reader io.Reader = conn

	timeout := this.timeout
	if destination.Network == v2net.Network_UDP {
		timeout = 16
	}
	if timeout > 0 {
		reader = v2net.NewTimeOutReader(timeout /* seconds */, conn)
	}

	v2reader := v2io.NewAdaptiveReader(reader)
	if err := v2io.PipeUntilEOF(v2reader, output); err != nil {
		log.Info("Freedom: Failed to transport all TCP response: ", err)
	}
	v2reader.Release()
	ray.OutboundOutput().Close()

	return nil
}

type FreedomFactory struct{}

func (this *FreedomFactory) StreamCapability() v2net.NetworkList {
	return v2net.NetworkList{
		Network: []v2net.Network{v2net.Network_RawTCP},
	}
}

func (this *FreedomFactory) Create(space app.Space, config interface{}, meta *proxy.OutboundHandlerMeta) (proxy.OutboundHandler, error) {
	return NewFreedomConnection(config.(*Config), space, meta), nil
}

func init() {
	registry.MustRegisterOutboundHandlerCreator(loader.GetType(new(Config)), new(FreedomFactory))
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"github.com/gopherjs/gopherjs/js"
	"github.com/rusco/qunit"
	"github.com/soroushjp/humble/router"
	"strings"
	"time"
)

// browserSupportsPushState will be true if the current browser
// supports history.pushState and the onpopstate event.
var browserSupportsPushState = (js.Global.Get("onpopstate") != js.Undefined) &&
	(js.Global.Get("history") != js.Undefined) &&
	(js.Global.Get("history").Get("pushState") != js.Undefined)

// route is an internal representation of a route that has been triggered.
type route struct {
	path   string
	params map[string]string
}

func main() {
	qunit.Test("Navigate", func(assert qunit.QUnitAssert) {
		qunit.Expect(3)
		r := router.New()
		// For this test and other similar tests in this file, we're going to
		// create a channel called routeChan. Any router.Handlers that we set
		// up are just going to send a route object through the routeChan. That
		// way, we can recieve from the routeChan to detect when certain routes
		// were triggered. And by extension, we can check that the expected route
		// is triggered within a certain amount of time.
		routeChan := make(chan route)
		r.HandleFunc("/foo", newChanHandlerFunc("/foo", routeChan))
		r.Start()
		defer r.Stop()
		done := assert.Async()
		go r.Navigate("/foo")
		expectedRoute := route{
			path:   "/foo",
			params: map[string]string{},
		}
		go expectTriggeredRoute(assert, routeChan, "/foo", expectedRoute, done)
	})

	qunit.Test("Navigate with Params", func(assert qunit.QUnitAssert) {
		qunit.Expect(3)
		r := router.New()
		routeChan := make(chan route)
		r.HandleFunc("/foo/{param1}/{param2}", newChanHandlerFunc("/foo/{param1}/{param2}", routeChan))
		r.Start()
		defer r.Stop()
		done := assert.Async()
		go r.Navigate("/foo/bar/baz")
		expectedRoute := route{
			path: "/foo/{param1}/{param2}",
			params: map[string]string{
				"param1": "bar",
				"param2": "baz",
			},
		}
		go expectTriggeredRoute(assert, routeChan, "/foo/bar/baz", expectedRoute, done)
	})

	qunit.Test("Navigate Back", func(assert qunit.QUnitAssert) {
		qunit.Expect(3)
		r := router.New()
		routeChan := make(chan route)
		r.HandleFunc("/foo", newChanHandlerFunc("/foo", routeChan))
		r.HandleFunc("/bar", newChanHandlerFunc("/bar", routeChan))
		r.Start()
		defer r.Stop()
		done := assert.Async()
		go func() {
			// Navigate to "/foo"
			r.Navigate("/foo")
			// Wait for the "/foo" handler to be triggered
			// once before continuing.
			<-routeChan
			// Navigate to "/bar"
			r.Navigate("/bar")
			// Wait for the "/bar" handler to be triggered
			// once before continuing.
			<-routeChan
			// Navigate back to "/foo", which should trigger the onpopstate listener
			// or the onhashchange listener, depending on browser support, and in turn
			// trigger the corresponding router.Handler again.
			r.Back()
		}()
		expectedRoute := route{
			path:   "/foo",
			params: map[string]string{},
		}
		go expectTriggeredRoute(assert, routeChan, "/foo", expectedRoute, done)
	})
}

// newChanHandlerFunc will create and return a router.Handler which, when run,
// will send the route that was triggered (along with its params) through the
// given routeChan. This serves as an easy way for us to test which routes were
// triggered and what params were provided.
func newChanHandlerFunc(path string, routeChan chan route) router.Handler {
	return func(params map[string]string) {
		routeChan <- route{
			path:   path,
			params: params,
		}
	}
}

// checkPath checks that the browser is currently at the expected path. It
// knows whether or not to check the hash or the pathname based on browser
// support.
func checkPath(assert qunit.QUnitAssert, expected string) {
	gotPath := ""
	if browserSupportsPushState {
		gotPath = js.Global.Get("location").Get("pathname").String()
	} else {
		hash := js.Global.Get("location").Get("hash").String()
		gotPath = strings.SplitN(hash, "#", 2)[1]
	}
	assert.Equal(gotPath, expected, "Path was not set correctly.")
}

// expectTriggeredRoute will first wait to receive from routeChan. When it receives, it checks the route
// object to make sure it matches expectedRoute. It also checks window.location to make sure that the
// browser actually navigated to expectedPath. If the route is not triggered within 200 milliseconds, the
// check will fail. The function will call done (which should be from assert.Async()) when it is done.
func expectTriggeredRoute(assert qunit.QUnitAssert, routeChan chan route, expectedPath string, expectedRoute route, done func()) {
	select {
	case gotRoute := <-routeChan:
		checkPath(assert, expectedPath)
		assert.Equal(gotRoute.path, expectedRoute.path, "Triggered route had incorrect path.")
		assert.DeepEqual(gotRoute.params, expectedRoute.params, "Triggered route had incorrect params.")
		done()
	case <-time.After(200 * time.Millisecond):
		// This is admittedly very akward. But AFIAK there is no equivalent of t.Fail or t.Error
		// in qunit.
		assert.Ok(false, fmt.Sprintf("Route %s was not triggered within 200 milliseconds", expectedRoute.path))
		assert.Ok(true, "")
		assert.Ok(true, "")
		done()
	}
}
<commit_msg>Implement and test an InterceptLinks method which calls router.Navigate instead of doing a full page reload on relative links.<commit_after>package main

import (
	"fmt"
	"github.com/gopherjs/gopherjs/js"
	"github.com/rusco/qunit"
	"github.com/soroushjp/humble/router"
	"honnef.co/go/js/dom"
	"strings"
	"time"
)

// browserSupportsPushState will be true if the current browser
// supports history.pushState and the onpopstate event.
var (
	browserSupportsPushState = (js.Global.Get("onpopstate") != js.Undefined) &&
		(js.Global.Get("history") != js.Undefined) &&
		(js.Global.Get("history").Get("pushState") != js.Undefined)
	document = dom.GetWindow().Document()
)

// route is an internal representation of a route that has been triggered.
type route struct {
	path   string
	params map[string]string
}

func main() {
	qunit.Test("Navigate", func(assert qunit.QUnitAssert) {
		qunit.Expect(3)
		r := router.New()
		// For this test and other similar tests in this file, we're going to
		// create a channel called routeChan. Any router.Handlers that we set
		// up are just going to send a route object through the routeChan. That
		// way, we can recieve from the routeChan to detect when certain routes
		// were triggered. And by extension, we can check that the expected route
		// is triggered within a certain amount of time.
		routeChan := make(chan route)
		r.HandleFunc("/foo", newChanHandlerFunc("/foo", routeChan))
		r.Start()
		defer r.Stop()
		done := assert.Async()
		go r.Navigate("/foo")
		expectedRoute := route{
			path:   "/foo",
			params: map[string]string{},
		}
		go expectTriggeredRoute(assert, routeChan, "/foo", expectedRoute, done)
	})

	qunit.Test("Navigate with Params", func(assert qunit.QUnitAssert) {
		qunit.Expect(3)
		r := router.New()
		routeChan := make(chan route)
		r.HandleFunc("/foo/{param1}/{param2}", newChanHandlerFunc("/foo/{param1}/{param2}", routeChan))
		r.Start()
		defer r.Stop()
		done := assert.Async()
		go r.Navigate("/foo/bar/baz")
		expectedRoute := route{
			path: "/foo/{param1}/{param2}",
			params: map[string]string{
				"param1": "bar",
				"param2": "baz",
			},
		}
		go expectTriggeredRoute(assert, routeChan, "/foo/bar/baz", expectedRoute, done)
	})

	qunit.Test("Navigate Back", func(assert qunit.QUnitAssert) {
		qunit.Expect(3)
		r := router.New()
		routeChan := make(chan route)
		r.HandleFunc("/foo", newChanHandlerFunc("/foo", routeChan))
		r.HandleFunc("/bar", newChanHandlerFunc("/bar", routeChan))
		r.Start()
		defer r.Stop()
		done := assert.Async()
		go func() {
			// Navigate to "/foo"
			r.Navigate("/foo")
			// Wait for the "/foo" handler to be triggered
			// once before continuing.
			<-routeChan
			// Navigate to "/bar"
			r.Navigate("/bar")
			// Wait for the "/bar" handler to be triggered
			// once before continuing.
			<-routeChan
			// Navigate back to "/foo", which should trigger the onpopstate listener
			// or the onhashchange listener, depending on browser support, and in turn
			// trigger the corresponding router.Handler again.
			r.Back()
		}()
		expectedRoute := route{
			path:   "/foo",
			params: map[string]string{},
		}
		go expectTriggeredRoute(assert, routeChan, "/foo", expectedRoute, done)
	})

	qunit.Test("Intercept Links", func(assert qunit.QUnitAssert) {
		qunit.Expect(3)
		done := assert.Async()

		go func() {
			// Set up and start the router
			r := router.New()
			routeChan := make(chan route)
			r.HandleFunc("/biz", newChanHandlerFunc("/biz", routeChan))
			r.Start()
			defer r.Stop()

			// Create a link element, call InterceptLinks, and then simulate clicking it
			link := createLinkElement("/biz", "biz-link")
			r.InterceptLinks()
			link.Click()

			// Make sure that we navigated to the /biz route
			expectedRoute := route{
				path:   "/biz",
				params: map[string]string{},
			}
			expectTriggeredRoute(assert, routeChan, "/biz", expectedRoute, done)
		}()
	})
}

// newChanHandlerFunc will create and return a router.Handler which, when run,
// will send the route that was triggered (along with its params) through the
// given routeChan. This serves as an easy way for us to test which routes were
// triggered and what params were provided.
func newChanHandlerFunc(path string, routeChan chan route) router.Handler {
	return func(params map[string]string) {
		routeChan <- route{
			path:   path,
			params: params,
		}
	}
}

// checkPath checks that the browser is currently at the expected path. It
// knows whether or not to check the hash or the pathname based on browser
// support.
func checkPath(assert qunit.QUnitAssert, expected string) {
	gotPath := ""
	if browserSupportsPushState {
		gotPath = js.Global.Get("location").Get("pathname").String()
	} else {
		hash := js.Global.Get("location").Get("hash").String()
		gotPath = strings.SplitN(hash, "#", 2)[1]
	}
	assert.Equal(gotPath, expected, "Path was not set correctly.")
}

// expectTriggeredRoute will first wait to receive from routeChan. When it receives, it checks the route
// object to make sure it matches expectedRoute. It also checks window.location to make sure that the
// browser actually navigated to expectedPath. If the route is not triggered within 200 milliseconds, the
// check will fail. The function will call done (which should be from assert.Async()) when it is done.
func expectTriggeredRoute(assert qunit.QUnitAssert, routeChan chan route, expectedPath string, expectedRoute route, done func()) {
	select {
	case gotRoute := <-routeChan:
		checkPath(assert, expectedPath)
		assert.Equal(gotRoute.path, expectedRoute.path, "Triggered route had incorrect path.")
		assert.DeepEqual(gotRoute.params, expectedRoute.params, "Triggered route had incorrect params.")
		done()
	case <-time.After(200 * time.Millisecond):
		// This is admittedly very akward. But AFIAK there is no equivalent of t.Fail or t.Error
		// in qunit.
		assert.Ok(false, fmt.Sprintf("Route %s was not triggered within 200 milliseconds", expectedRoute.path))
		assert.Ok(true, "")
		assert.Ok(true, "")
		done()
	}
}

// createLinkElement will add an <a> element to the DOM with an href property equal to path
// and an id equal to id. It returns the created element.
func createLinkElement(path string, id string) *dom.HTMLAnchorElement {
	el := document.CreateElement("a")
	el.SetAttribute("href", path)
	el.SetID(id)
	document.QuerySelector("body").AppendChild(el)
	return el.(*dom.HTMLAnchorElement)
}
<|endoftext|>


<commit_before>package worker

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"github.com/MG-RAST/AWE/lib/conf"
	"github.com/MG-RAST/AWE/lib/core"
	e "github.com/MG-RAST/AWE/lib/errors"
	"github.com/MG-RAST/AWE/lib/httpclient"
	"github.com/MG-RAST/AWE/lib/logger"
	"github.com/MG-RAST/AWE/lib/logger/event"
	"io"
	"io/ioutil"
	"mime/multipart"
	"net"
	"net/http"
	"os"
	"runtime"
	"strconv"
	"strings"
	"time"
)

type HeartbeatResponse struct {
	Code int        `bson:"status" json:"status"`
	Data core.HBmsg `bson:"data" json:"data"`
	Errs []string   `bson:"error" json:"error"`
}

type ClientResponse struct {
	Code int         `bson:"status" json:"status"`
	Data core.Client `bson:"data" json:"data"`
	Errs []string    `bson:"error" json:"error"`
}

func heartBeater(control chan int) {
	for {
		time.Sleep(10 * time.Second)
		SendHeartBeat()
	}
	control <- 2 //we are ending
}

//client sends heartbeat to server to maintain active status and re-register when needed
func SendHeartBeat() {
	hbmsg, err := heartbeating(conf.SERVER_URL, core.Self.Id)
	if err != nil {
		if err.Error() == e.ClientNotFound {
			ReRegisterWithSelf(conf.SERVER_URL)
		}
	}
	//handle requested ops from the server
	for op, objs := range hbmsg {
		if op == "discard" { //discard suspended workunits
			suspendedworks := strings.Split(objs, ",")
			for _, work := range suspendedworks {
				DiscardWorkunit(work)
			}
		} else if op == "restart" {
			RestartClient()
		} else if op == "stop" {
			StopClient()
		} else if op == "clean" {
			CleanDisk()
		}
	}
}

func heartbeating(host string, clientid string) (msg core.HBmsg, err error) {
	response := new(HeartbeatResponse)
	targeturl := fmt.Sprintf("%s/client/%s?heartbeat", host, clientid)
	//res, err := http.Get(targeturl)
	res, err := httpclient.Get(targeturl, httpclient.Header{}, nil, nil)
	logger.Debug(3, fmt.Sprintf("client %s sent a heartbeat to %s", host, clientid))
	if err != nil {
		return
	}
	defer res.Body.Close()
	jsonstream, err := ioutil.ReadAll(res.Body)
	if err != nil {
		return
	}
	if err = json.Unmarshal(jsonstream, response); err == nil {
		if len(response.Errs) > 0 {
			return msg, errors.New(strings.Join(response.Errs, ","))
		}
		return response.Data, nil
	}
	return
}

func RegisterWithProfile(host string, profile *core.Client) (client *core.Client, err error) {
	profile_jsonstream, err := json.Marshal(profile)
	profile_path := conf.DATA_PATH + "/clientprofile.json"
	ioutil.WriteFile(profile_path, []byte(profile_jsonstream), 0644)

	bodyBuf := &bytes.Buffer{}
	bodyWriter := multipart.NewWriter(bodyBuf)
	fileWriter, err := bodyWriter.CreateFormFile("profile", profile_path)
	if err != nil {
		return nil, err
	}
	fh, err := os.Open(profile_path)
	if err != nil {
		return nil, err
	}
	_, err = io.Copy(fileWriter, fh)
	if err != nil {
		return nil, err
	}
	contentType := bodyWriter.FormDataContentType()
	bodyWriter.Close()
	targetUrl := host + "/client"

	resp, err := http.Post(targetUrl, contentType, bodyBuf)

	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	jsonstream, err := ioutil.ReadAll(resp.Body)

	response := new(ClientResponse)
	if err = json.Unmarshal(jsonstream, response); err != nil {
		return nil, errors.New("fail to unmashal response:" + string(jsonstream))
	}
	if len(response.Errs) > 0 {
		return nil, errors.New(strings.Join(response.Errs, ","))
	}
	client = &response.Data
	return
}

func RegisterWithAuth(host string, profile *core.Client) (client *core.Client, err error) {
	if conf.CLIENT_USERNAME == "public" {
		fmt.Println("client username and password not configured, register as a public user (can only access public data)")
	}

	profile_jsonstream, err := json.Marshal(profile)
	profile_path := conf.DATA_PATH + "/clientprofile.json"
	ioutil.WriteFile(profile_path, []byte(profile_jsonstream), 0644)

	form := httpclient.NewForm()
	form.AddFile("profile", profile_path)
	if err := form.Create(); err != nil {
		return nil, err
	}
	headers := httpclient.Header{
		"Content-Type":   []string{form.ContentType},
		"Content-Length": []string{strconv.FormatInt(form.Length, 10)},
	}
	user := httpclient.GetUserByBasicAuth(conf.CLIENT_USERNAME, conf.CLIENT_PASSWORD)
	targetUrl := host + "/client"

	resp, err := httpclient.Post(targetUrl, headers, form.Reader, user)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	jsonstream, err := ioutil.ReadAll(resp.Body)
	response := new(ClientResponse)
	if err = json.Unmarshal(jsonstream, response); err != nil {
		return nil, errors.New("fail to unmashal response:" + string(jsonstream))
	}
	if len(response.Errs) > 0 {
		return nil, errors.New(strings.Join(response.Errs, ","))
	}
	client = &response.Data
	return
}

func ReRegisterWithSelf(host string) (client *core.Client, err error) {
	fmt.Printf("lost contact with server, try to re-register\n")
	client, err = RegisterWithAuth(host, core.Self)
	if err != nil {
		logger.Error("Error: fail to re-register, clientid=" + core.Self.Id)
		fmt.Printf("failed to re-register\n")
	} else {
		logger.Event(event.CLIENT_AUTO_REREGI, "clientid="+core.Self.Id)
		fmt.Printf("re-register successfully\n")
	}
	return
}

func ComposeProfile() (profile *core.Client, err error) {
	profile = new(core.Client)
	profile.Name = conf.CLIENT_NAME
	profile.Group = conf.CLIENT_GROUP
	profile.CPUs = runtime.NumCPU()
	profile.User = conf.CLIENT_USERNAME
	profile.Domain = conf.CLIENT_DOMAIN
	profile.Version = conf.VERSION

	//app list
	profile.Apps = []string{}
	if conf.SUPPORTED_APPS != "" { //apps configured in .cfg
		apps := strings.Split(conf.SUPPORTED_APPS, ",")
		for _, item := range apps {
			profile.Apps = append(profile.Apps, item)
		}
	} else { //apps not configured in .cfg, check the executables in APP_PATH)
		if files, err := ioutil.ReadDir(conf.APP_PATH); err == nil {
			for _, item := range files {
				profile.Apps = append(profile.Apps, item.Name())
			}
		}
	}

	if addrs, err := net.InterfaceAddrs(); err == nil {
		for _, a := range addrs {
			if ipnet, ok := a.(*net.IPNet); ok && !ipnet.IP.IsLoopback() && len(strings.Split(ipnet.IP.String(), ".")) == 4 {
				profile.Host = ipnet.IP.String()
				break
			}
		}
	}

	if len(conf.OPENSTACK_METADATA_URL) > 7 { // longer than "http://"

		fmt.Printf("openstack_metadata_url=%s, getting instance_id and instance_type...\n", conf.OPENSTACK_METADATA_URL)

		// read all values: for i in `curl http://169.254.169.254/1.0/meta-data/` ; do echo ${i}: `curl -s http://169.254.169.254/1.0/meta-data/${i}` ; done
		instance_hostname, err := getMetaDataField("hostname")
		if err == nil {
			profile.Name = instance_hostname
		}
		instance_id, err := getMetaDataField("instance-id")
		if err == nil {
			instance_id = strings.TrimSuffix(instance_id, ".novalocal")
			profile.InstanceId = instance_id
		}
		instance_type, err := getMetaDataField("instance-type")
		if err == nil {
			profile.InstanceType = instance_type
		}
		//for i := 0; i < 3; i++ {
		//	profile.InstanceId, _ = getInstanceId()
		//	if profile.InstanceId != "" {
		//		break
		//	}
		//}
		//for i := 0; i < 3; i++ {
		//	profile.InstanceType, _ = getInstanceType()
		//	if profile.InstanceType != "" {
		//		break
		//	}
		//}
	}

	if core.Service == "proxy" {
		profile.Proxy = true
	}
	return
}

func DiscardWorkunit(id string) (err error) {
	//fmt.Printf("try to discard workunit %s\n", id)
	if stage, ok := workmap[id]; ok {
		if stage == ID_WORKER {
			chankill <- true
		}
		workmap[id] = ID_DISCARDED
	}
	return
}

func RestartClient() (err error) {
	//fmt.Printf("try to restart client\n")
	//to-do: implementation here
	return
}

func StopClient() (err error) {
	fmt.Printf("client deleted, exiting...\n")
	os.Exit(0)
	return
}

func CleanDisk() (err error) {
	//fmt.Printf("try to clean disk space\n")
	//to-do: implementation here
	return
}

func getMetaDataField(field string) (result string, err error) {
	var url = fmt.Sprintf("%s/%s", conf.OPENSTACK_METADATA_URL, field) // TODO this is not OPENSTACK, this is EC2
	fmt.Printf("url=%s\n", url)

	for i := 0; i < 3; i++ {
		var res *http.Response
		c := make(chan bool, 1)
		go func() {
			res, err = http.Get(url)
			c <- true //we are ending
		}()
		select {
		case <-c:
			//go ahead
		case <-time.After(conf.INSTANCE_METADATA_TIMEOUT): //GET timeout
			return "", errors.New("timeout")
		}
		if err != nil {
			return "", err
		}
		defer res.Body.Close()
		bodybytes, err := ioutil.ReadAll(res.Body)
		result = string(bodybytes[:])
		if err != nil {
			fmt.Printf("error: (iteration=%d) %s \"%s\"\n", i, url, err.Error())
		} else if result == "" {
			fmt.Printf("error: (iteration=%d) %s , empty result\n", i, url)
		} else {
			break
		}
	}

	if err != nil {
		return "", err
	}

	if result == "" {
		return "", errors.New(fmt.Sprintf("metadata result empty, %s", url))
	}
	fmt.Printf("Intance Metadata %s => \"%s\"", url, result)
	//logger.Debug(1, fmt.Sprintf("Intance Metadata %s => \"%s\"", url, result))
	return
}

// TODO deprecated by getMetaDataField !?
func getInstanceId() (instance_id string, err error) {
	var instance_id_url = fmt.Sprintf("%s/instance-id", conf.OPENSTACK_METADATA_URL)
	var res *http.Response
	c := make(chan bool, 1)
	go func() {
		res, err = http.Get(instance_id_url)
		c <- true //we are ending
	}()
	select {
	case <-c:
		//go ahead
	case <-time.After(conf.INSTANCE_METADATA_TIMEOUT): //GET timeout
		return "", errors.New("timeout")
	}
	if err != nil {
		return "", err
	}
	defer res.Body.Close()
	bodybytes, err := ioutil.ReadAll(res.Body)
	if err != nil {
		return "", err
	}
	instance_id = string(bodybytes[:])
	fmt.Printf("instance_id=%s\n", instance_id)
	return
}

// TODO deprecated by getMetaDataField !?
func getInstanceType() (instance_type string, err error) {
	var instance_type_url = fmt.Sprintf("%s/instance-type", conf.OPENSTACK_METADATA_URL)
	var res *http.Response
	c := make(chan bool, 1)
	go func() {
		res, err = http.Get(instance_type_url)
		c <- true //we are ending
	}()
	select {
	case <-c:
		//go ahead
	case <-time.After(conf.INSTANCE_METADATA_TIMEOUT): //GET timeout
		return "", errors.New("timeout")
	}
	if err != nil {
		return "", err
	}
	defer res.Body.Close()
	bodybytes, err := ioutil.ReadAll(res.Body)
	if err != nil {
		return "", err
	}
	instance_type = string(bodybytes[:])
	fmt.Printf("instance_type=%s\n", instance_type)
	return
}
<commit_msg>debug<commit_after>package worker

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"github.com/MG-RAST/AWE/lib/conf"
	"github.com/MG-RAST/AWE/lib/core"
	e "github.com/MG-RAST/AWE/lib/errors"
	"github.com/MG-RAST/AWE/lib/httpclient"
	"github.com/MG-RAST/AWE/lib/logger"
	"github.com/MG-RAST/AWE/lib/logger/event"
	"io"
	"io/ioutil"
	"mime/multipart"
	"net"
	"net/http"
	"os"
	"runtime"
	"strconv"
	"strings"
	"time"
)

type HeartbeatResponse struct {
	Code int        `bson:"status" json:"status"`
	Data core.HBmsg `bson:"data" json:"data"`
	Errs []string   `bson:"error" json:"error"`
}

type ClientResponse struct {
	Code int         `bson:"status" json:"status"`
	Data core.Client `bson:"data" json:"data"`
	Errs []string    `bson:"error" json:"error"`
}

func heartBeater(control chan int) {
	for {
		time.Sleep(10 * time.Second)
		SendHeartBeat()
	}
	control <- 2 //we are ending
}

//client sends heartbeat to server to maintain active status and re-register when needed
func SendHeartBeat() {
	hbmsg, err := heartbeating(conf.SERVER_URL, core.Self.Id)
	if err != nil {
		if err.Error() == e.ClientNotFound {
			ReRegisterWithSelf(conf.SERVER_URL)
		}
	}
	//handle requested ops from the server
	for op, objs := range hbmsg {
		if op == "discard" { //discard suspended workunits
			suspendedworks := strings.Split(objs, ",")
			for _, work := range suspendedworks {
				DiscardWorkunit(work)
			}
		} else if op == "restart" {
			RestartClient()
		} else if op == "stop" {
			StopClient()
		} else if op == "clean" {
			CleanDisk()
		}
	}
}

func heartbeating(host string, clientid string) (msg core.HBmsg, err error) {
	response := new(HeartbeatResponse)
	targeturl := fmt.Sprintf("%s/client/%s?heartbeat", host, clientid)
	//res, err := http.Get(targeturl)
	res, err := httpclient.Get(targeturl, httpclient.Header{}, nil, nil)
	logger.Debug(3, fmt.Sprintf("client %s sent a heartbeat to %s", host, clientid))
	if err != nil {
		return
	}
	defer res.Body.Close()
	jsonstream, err := ioutil.ReadAll(res.Body)
	if err != nil {
		return
	}
	if err = json.Unmarshal(jsonstream, response); err == nil {
		if len(response.Errs) > 0 {
			return msg, errors.New(strings.Join(response.Errs, ","))
		}
		return response.Data, nil
	}
	return
}

func RegisterWithProfile(host string, profile *core.Client) (client *core.Client, err error) {
	profile_jsonstream, err := json.Marshal(profile)
	profile_path := conf.DATA_PATH + "/clientprofile.json"
	ioutil.WriteFile(profile_path, []byte(profile_jsonstream), 0644)

	bodyBuf := &bytes.Buffer{}
	bodyWriter := multipart.NewWriter(bodyBuf)
	fileWriter, err := bodyWriter.CreateFormFile("profile", profile_path)
	if err != nil {
		return nil, err
	}
	fh, err := os.Open(profile_path)
	if err != nil {
		return nil, err
	}
	_, err = io.Copy(fileWriter, fh)
	if err != nil {
		return nil, err
	}
	contentType := bodyWriter.FormDataContentType()
	bodyWriter.Close()
	targetUrl := host + "/client"

	resp, err := http.Post(targetUrl, contentType, bodyBuf)

	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	jsonstream, err := ioutil.ReadAll(resp.Body)

	response := new(ClientResponse)
	if err = json.Unmarshal(jsonstream, response); err != nil {
		return nil, errors.New("fail to unmashal response:" + string(jsonstream))
	}
	if len(response.Errs) > 0 {
		return nil, errors.New(strings.Join(response.Errs, ","))
	}
	client = &response.Data
	return
}

func RegisterWithAuth(host string, profile *core.Client) (client *core.Client, err error) {
	if conf.CLIENT_USERNAME == "public" {
		fmt.Println("client username and password not configured, register as a public user (can only access public data)")
	}

	profile_jsonstream, err := json.Marshal(profile)
	profile_path := conf.DATA_PATH + "/clientprofile.json"
	ioutil.WriteFile(profile_path, []byte(profile_jsonstream), 0644)

	form := httpclient.NewForm()
	form.AddFile("profile", profile_path)
	if err := form.Create(); err != nil {
		return nil, err
	}
	headers := httpclient.Header{
		"Content-Type":   []string{form.ContentType},
		"Content-Length": []string{strconv.FormatInt(form.Length, 10)},
	}
	user := httpclient.GetUserByBasicAuth(conf.CLIENT_USERNAME, conf.CLIENT_PASSWORD)
	targetUrl := host + "/client"

	resp, err := httpclient.Post(targetUrl, headers, form.Reader, user)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	jsonstream, err := ioutil.ReadAll(resp.Body)
	response := new(ClientResponse)
	if err = json.Unmarshal(jsonstream, response); err != nil {
		return nil, errors.New("fail to unmashal response:" + string(jsonstream))
	}
	if len(response.Errs) > 0 {
		return nil, errors.New(strings.Join(response.Errs, ","))
	}
	client = &response.Data
	return
}

func ReRegisterWithSelf(host string) (client *core.Client, err error) {
	fmt.Printf("lost contact with server, try to re-register\n")
	client, err = RegisterWithAuth(host, core.Self)
	if err != nil {
		logger.Error("Error: fail to re-register, clientid=" + core.Self.Id)
		fmt.Printf("failed to re-register\n")
	} else {
		logger.Event(event.CLIENT_AUTO_REREGI, "clientid="+core.Self.Id)
		fmt.Printf("re-register successfully\n")
	}
	return
}

func ComposeProfile() (profile *core.Client, err error) {
	profile = new(core.Client)
	profile.Name = conf.CLIENT_NAME
	profile.Group = conf.CLIENT_GROUP
	profile.CPUs = runtime.NumCPU()
	profile.User = conf.CLIENT_USERNAME
	profile.Domain = conf.CLIENT_DOMAIN
	profile.Version = conf.VERSION

	//app list
	profile.Apps = []string{}
	if conf.SUPPORTED_APPS != "" { //apps configured in .cfg
		apps := strings.Split(conf.SUPPORTED_APPS, ",")
		for _, item := range apps {
			profile.Apps = append(profile.Apps, item)
		}
	} else { //apps not configured in .cfg, check the executables in APP_PATH)
		if files, err := ioutil.ReadDir(conf.APP_PATH); err == nil {
			for _, item := range files {
				profile.Apps = append(profile.Apps, item.Name())
			}
		}
	}

	if addrs, err := net.InterfaceAddrs(); err == nil {
		for _, a := range addrs {
			if ipnet, ok := a.(*net.IPNet); ok && !ipnet.IP.IsLoopback() && len(strings.Split(ipnet.IP.String(), ".")) == 4 {
				profile.Host = ipnet.IP.String()
				break
			}
		}
	}

	if len(conf.OPENSTACK_METADATA_URL) > 7 { // longer than "http://"

		fmt.Printf("openstack_metadata_url=%s, getting instance_id and instance_type...\n", conf.OPENSTACK_METADATA_URL)

		// read all values: for i in `curl http://169.254.169.254/1.0/meta-data/` ; do echo ${i}: `curl -s http://169.254.169.254/1.0/meta-data/${i}` ; done
		instance_hostname, err := getMetaDataField("hostname")
		if err == nil {
			profile.Name = instance_hostname
		}
		instance_id, err := getMetaDataField("instance-id")
		if err == nil {
			fmt.Printf("instance_id: "+instance_id)
			instance_id = strings.TrimSuffix(instance_id, ".novalocal")
			fmt.Printf("instance_id, trimmed: "+instance_id)
			profile.InstanceId = instance_id
		}
		instance_type, err := getMetaDataField("instance-type")
		if err == nil {
			profile.InstanceType = instance_type
		}
		//for i := 0; i < 3; i++ {
		//	profile.InstanceId, _ = getInstanceId()
		//	if profile.InstanceId != "" {
		//		break
		//	}
		//}
		//for i := 0; i < 3; i++ {
		//	profile.InstanceType, _ = getInstanceType()
		//	if profile.InstanceType != "" {
		//		break
		//	}
		//}
	}

	if core.Service == "proxy" {
		profile.Proxy = true
	}
	return
}

func DiscardWorkunit(id string) (err error) {
	//fmt.Printf("try to discard workunit %s\n", id)
	if stage, ok := workmap[id]; ok {
		if stage == ID_WORKER {
			chankill <- true
		}
		workmap[id] = ID_DISCARDED
	}
	return
}

func RestartClient() (err error) {
	//fmt.Printf("try to restart client\n")
	//to-do: implementation here
	return
}

func StopClient() (err error) {
	fmt.Printf("client deleted, exiting...\n")
	os.Exit(0)
	return
}

func CleanDisk() (err error) {
	//fmt.Printf("try to clean disk space\n")
	//to-do: implementation here
	return
}

func getMetaDataField(field string) (result string, err error) {
	var url = fmt.Sprintf("%s/%s", conf.OPENSTACK_METADATA_URL, field) // TODO this is not OPENSTACK, this is EC2
	fmt.Printf("url=%s\n", url)

	for i := 0; i < 3; i++ {
		var res *http.Response
		c := make(chan bool, 1)
		go func() {
			res, err = http.Get(url)
			c <- true //we are ending
		}()
		select {
		case <-c:
			//go ahead
		case <-time.After(conf.INSTANCE_METADATA_TIMEOUT): //GET timeout
			return "", errors.New("timeout")
		}
		if err != nil {
			return "", err
		}
		defer res.Body.Close()
		bodybytes, err := ioutil.ReadAll(res.Body)
		result = string(bodybytes[:])
		if err != nil {
			fmt.Printf("error: (iteration=%d) %s \"%s\"\n", i, url, err.Error())
		} else if result == "" {
			fmt.Printf("error: (iteration=%d) %s , empty result\n", i, url)
		} else {
			break
		}
	}

	if err != nil {
		return "", err
	}

	if result == "" {
		return "", errors.New(fmt.Sprintf("metadata result empty, %s", url))
	}
	fmt.Printf("Intance Metadata %s => \"%s\"", url, result)
	//logger.Debug(1, fmt.Sprintf("Intance Metadata %s => \"%s\"", url, result))
	return
}

// TODO deprecated by getMetaDataField !?
func getInstanceId() (instance_id string, err error) {
	var instance_id_url = fmt.Sprintf("%s/instance-id", conf.OPENSTACK_METADATA_URL)
	var res *http.Response
	c := make(chan bool, 1)
	go func() {
		res, err = http.Get(instance_id_url)
		c <- true //we are ending
	}()
	select {
	case <-c:
		//go ahead
	case <-time.After(conf.INSTANCE_METADATA_TIMEOUT): //GET timeout
		return "", errors.New("timeout")
	}
	if err != nil {
		return "", err
	}
	defer res.Body.Close()
	bodybytes, err := ioutil.ReadAll(res.Body)
	if err != nil {
		return "", err
	}
	instance_id = string(bodybytes[:])
	fmt.Printf("instance_id=%s\n", instance_id)
	return
}

// TODO deprecated by getMetaDataField !?
func getInstanceType() (instance_type string, err error) {
	var instance_type_url = fmt.Sprintf("%s/instance-type", conf.OPENSTACK_METADATA_URL)
	var res *http.Response
	c := make(chan bool, 1)
	go func() {
		res, err = http.Get(instance_type_url)
		c <- true //we are ending
	}()
	select {
	case <-c:
		//go ahead
	case <-time.After(conf.INSTANCE_METADATA_TIMEOUT): //GET timeout
		return "", errors.New("timeout")
	}
	if err != nil {
		return "", err
	}
	defer res.Body.Close()
	bodybytes, err := ioutil.ReadAll(res.Body)
	if err != nil {
		return "", err
	}
	instance_type = string(bodybytes[:])
	fmt.Printf("instance_type=%s\n", instance_type)
	return
}
<|endoftext|>


<commit_before>package libkbfs

import (
	"crypto/tls"
	"crypto/x509"
	"encoding/hex"
	"errors"
	"net"
	"sync"
	"time"

	"github.com/keybase/client/go/libkb"
	keybase1 "github.com/keybase/client/protocol/go"
	"github.com/maxtaco/go-framed-msgpack-rpc/rpc2"
	"golang.org/x/net/context"
)

var (
	// ErrNoActiveConn is an error returned when this component
	// is not yet connected to the block server.
	ErrNoActiveConn = errors.New("Not connected to block server")
	// BServerTimeout is the timeout for communications with block server.
	BServerTimeout = 60 * time.Second
)

// BlockServerRemote implements the BlockServer interface and
// represents a remote KBFS block server.
type BlockServerRemote struct {
	config Config

	srvAddr string

	// connMu protects both conn and shutdown
	connMu   sync.Mutex
	conn     net.Conn
	shutdown bool

	clt keybase1.GenericClient

	// connectedChan is closed and set to nil when the connection
	// succeeds.  We'd need to create a new connectedChan if we ever
	// want to deal with reconnecting after a disconnect.
	connectedMu   sync.Mutex
	connectedChan chan struct{}

	lastTried time.Time
}

// NewBlockServerRemote constructs a new BlockServerRemote for the
// given address.
func NewBlockServerRemote(ctx context.Context, config Config,
	blkSrvAddr string) *BlockServerRemote {
	b := &BlockServerRemote{
		config:        config,
		srvAddr:       blkSrvAddr,
		connectedChan: make(chan struct{}),
	}

	// Start connecting in the background
	go b.Reconnect(ctx)
	return b
}

// newBlockServerRemoteWithClient should only be used for testing.
func newBlockServerRemoteWithClient(ctx context.Context, config Config,
	client keybase1.GenericClient) *BlockServerRemote {
	b := &BlockServerRemote{
		config:        config,
		connectedChan: make(chan struct{}),
		clt:           client,
	}

	if err := b.ConnectOnce(ctx); err != nil {
		panic("Failed to connect to a provided client.")
	}

	return b
}

// TLSConnect connects over TLS to the given server, expecting the
// connection to be authenticated with the given certificate.
func TLSConnect(cert []byte, Addr string) (conn net.Conn, err error) {
	CAPool := x509.NewCertPool()
	CAPool.AppendCertsFromPEM(cert)

	config := tls.Config{RootCAs: CAPool}
	conn, err = tls.Dial("tcp", Addr, &config)
	if err != nil {
		return
	}
	return
}

func (b *BlockServerRemote) initClient() error {
	b.connMu.Lock()
	defer b.connMu.Unlock()
	if b.shutdown {
		// Pretend everything is fine.  That will cause the goroutine
		// calling us to exit eventually.
		return nil
	}

	var err error
	if b.conn, err = TLSConnect(b.config.CACert(), b.srvAddr); err != nil {
		libkb.G.Log.Warning("NewBlockServerRemote: cannot connect to backend "+
			"err : %v", err)
		return err
	}

	b.clt = rpc2.NewClient(rpc2.NewTransport(b.conn, libkb.NewRPCLogFactory(),
		libkb.WrapError), libkb.UnwrapError)

	return nil
}

// Config returns the configuration object
func (b *BlockServerRemote) Config() Config {
	return b.config
}

// ConnectOnce tries once to connect to the remote block server.
func (b *BlockServerRemote) ConnectOnce(ctx context.Context) error {
	shutdown := func() bool {
		b.connMu.Lock()
		defer b.connMu.Unlock()
		return b.shutdown
	}()
	if shutdown {
		// Pretend everything is fine.  That will cause the goroutine
		// calling us to exit.
		return nil
	}

	var token string
	var session *libkb.Session
	var err error
	if session, err = b.config.KBPKI().GetSession(ctx); err != nil {
		libkb.G.Log.Warning("BlockServerRemote: error getting session %q", err)
		return err
	} else if session != nil {
		token = session.GetToken()
	}

	var user keybase1.UID
	user, err = b.config.KBPKI().GetLoggedInUser(ctx)
	if err != nil {
		return err
	}

	arg := keybase1.EstablishSessionArg{
		User: user,
		Sid:  token,
	}
	clt := keybase1.BlockClient{Cli: b.clt}
	if err = clt.EstablishSession(arg); err != nil {
		libkb.G.Log.Warning("BlockServerRemote: error getting session token %q", err)
		return err
	}

	b.connectedMu.Lock()
	defer b.connectedMu.Unlock()
	close(b.connectedChan)
	b.connectedChan = nil
	return nil
}

func (b *BlockServerRemote) isConnected() bool {
	return b.connectedChan == nil
}

// WaitForReconnect waits for the timeout period to reconnect to the
// server.
func (b *BlockServerRemote) WaitForReconnect(parent context.Context) error {
	c := func() chan struct{} {
		b.connectedMu.Lock()
		defer b.connectedMu.Unlock()
		return b.connectedChan
	}()

	if c == nil {
		// we're already connected
		return nil
	}

	ctx, cancel := context.WithTimeout(parent, BServerTimeout)
	defer cancel()

	// Wait either for the timeout, or for the connection to come up
	// (c will be closed).
	select {
	case <-ctx.Done():
		return ctx.Err()
	case <-c:
		// Note: if we ever want to recover from lost connectivity, we
		// should probably put this whole method in a loop so that we
		// check connectedChan again after the channel closes.
		return nil
	}
}

// Reconnect reconnects to block server.
func (b *BlockServerRemote) Reconnect(ctx context.Context) {
	for b.initClient() != nil {
		time.Sleep(1 * time.Second)
	}

	for b.ConnectOnce(ctx) != nil {
		time.Sleep(1 * time.Second)
	}
}

// Shutdown closes the connection to this remote block server.
func (b *BlockServerRemote) Shutdown() {
	b.connMu.Lock()
	defer b.connMu.Unlock()
	if b.conn != nil {
		b.conn.Close()
	}
	b.shutdown = true
}

// Get implements the BlockServer interface for BlockServerRemote.
func (b *BlockServerRemote) Get(ctx context.Context, id BlockID,
	context BlockContext) ([]byte, BlockCryptKeyServerHalf, error) {
	libkb.G.Log.Debug("BlockServerRemote.Get id=%s uid=%s\n",
		id.String(), context.GetWriter().String())
	if !b.isConnected() {
		if err := b.WaitForReconnect(ctx); err != nil {
			return nil, BlockCryptKeyServerHalf{}, err
		}
	}
	bid := keybase1.BlockIdCombo{
		BlockHash: id.String(),
		ChargedTo: context.GetWriter(),
	}

	var res keybase1.GetBlockRes
	f := func() error {
		var err error
		//XXX: if fails due to connection problem, should reconnect
		clt := keybase1.BlockClient{Cli: b.clt}
		res, err = clt.GetBlock(bid)
		return err
	}

	err := runUnlessCanceled(ctx, f)
	if err != nil {
		libkb.G.Log.Debug("BlockServerRemote.Get id=%s err=%v\n",
			id.String(), err)
		return nil, BlockCryptKeyServerHalf{}, err
	}

	bk := BlockCryptKeyServerHalf{}
	kbuf, err := hex.DecodeString(res.BlockKey)
	if err != nil {
		return nil, BlockCryptKeyServerHalf{}, err
	}

	copy(bk.ServerHalf[:], kbuf)
	return res.Buf, bk, nil
}

// Put implements the BlockServer interface for BlockServerRemote.
// TODO: store the server-half of the block key
func (b *BlockServerRemote) Put(ctx context.Context, id BlockID, tlfID TlfID,
	context BlockContext, buf []byte,
	serverHalf BlockCryptKeyServerHalf) error {
	libkb.G.Log.Debug("BlockServerRemote.Put id=%s uid=%s\n",
		id.String(), context.GetWriter().String())
	if !b.isConnected() {
		if err := b.WaitForReconnect(ctx); err != nil {
			return err
		}
	}
	arg := keybase1.PutBlockArg{
		Bid: keybase1.BlockIdCombo{
			ChargedTo: context.GetWriter(),
			BlockHash: id.String(),
		},
		BlockKey: hex.EncodeToString(serverHalf.ServerHalf[:]),
		Folder:   tlfID.String(),
		Buf:      buf,
	}

	f := func() error {
		clt := keybase1.BlockClient{Cli: b.clt}
		return clt.PutBlock(arg)
	}
	err := runUnlessCanceled(ctx, f)
	if err != nil {
		libkb.G.Log.Debug("BlockServerRemote.Put id=%s err=%v\n",
			id.String(), err)
		return err
	}

	return nil
}

// AddBlockReference implements the BlockServer interface for BlockServerRemote
func (b *BlockServerRemote) AddBlockReference(ctx context.Context, id BlockID,
	tlfID TlfID, context BlockContext) error {
	libkb.G.Log.Debug("BlockServerRemote.AddBlockReference id=%s uid=%s\n",
		id.String(), context.GetWriter().String())
	nonce := context.GetRefNonce()
	arg := keybase1.IncBlockReferenceArg{
		Bid: keybase1.BlockIdCombo{
			ChargedTo: context.GetCreator(),
			BlockHash: id.String(),
		},
		Folder:    tlfID.String(),
		ChargedTo: context.GetWriter(), //the actual writer to decrement quota from
	}
	copy(arg.Nonce[:], nonce[:])

	f := func() error {
		clt := keybase1.BlockClient{Cli: b.clt}
		return clt.IncBlockReference(arg)
	}
	err := runUnlessCanceled(ctx, f)
	if err != nil {
		// TODO: translate a particular RPC error into
		// IncrementMissingBlockError?
		libkb.G.Log.Debug("AddBlockReference to backend err : %q", err)
		return err
	}

	return nil
}

// RemoveBlockReference implements the BlockServer interface for
// BlockServerRemote
func (b *BlockServerRemote) RemoveBlockReference(ctx context.Context, id BlockID,
	tlfID TlfID, context BlockContext) error {
	libkb.G.Log.Debug("BlockServerRemote.RemoveBlockReference id=%s uid=%s\n",
		id.String(), context.GetWriter().String())
	nonce := context.GetRefNonce()
	arg := keybase1.DecBlockReferenceArg{
		Bid: keybase1.BlockIdCombo{
			ChargedTo: context.GetCreator(),
			BlockHash: id.String(),
		},
		Folder:    tlfID[:].String(),
		ChargedTo: context.GetWriter(), //the actual writer to decrement quota from
	}
	copy(arg.Nonce[:], nonce[:])

	f := func() error {
		clt := keybase1.BlockClient{Cli: b.clt}
		return clt.DecBlockReference(arg)
	}
	err := runUnlessCanceled(ctx, f)
	if err != nil {
		libkb.G.Log.Debug("RemoveBlockReference to backend err : %q", err)
		return err
	}

	return nil
}
<commit_msg>fixed nits to pass old tests<commit_after>package libkbfs

import (
	"crypto/tls"
	"crypto/x509"
	"encoding/hex"
	"errors"
	"net"
	"sync"
	"time"

	"github.com/keybase/client/go/libkb"
	keybase1 "github.com/keybase/client/protocol/go"
	"github.com/maxtaco/go-framed-msgpack-rpc/rpc2"
	"golang.org/x/net/context"
)

var (
	// ErrNoActiveConn is an error returned when this component
	// is not yet connected to the block server.
	ErrNoActiveConn = errors.New("Not connected to block server")
	// BServerTimeout is the timeout for communications with block server.
	BServerTimeout = 60 * time.Second
)

// BlockServerRemote implements the BlockServer interface and
// represents a remote KBFS block server.
type BlockServerRemote struct {
	config Config

	srvAddr string

	// connMu protects both conn and shutdown
	connMu   sync.Mutex
	conn     net.Conn
	shutdown bool

	clt keybase1.GenericClient

	// connectedChan is closed and set to nil when the connection
	// succeeds.  We'd need to create a new connectedChan if we ever
	// want to deal with reconnecting after a disconnect.
	connectedMu   sync.Mutex
	connectedChan chan struct{}

	lastTried time.Time
}

// NewBlockServerRemote constructs a new BlockServerRemote for the
// given address.
func NewBlockServerRemote(ctx context.Context, config Config,
	blkSrvAddr string) *BlockServerRemote {
	b := &BlockServerRemote{
		config:        config,
		srvAddr:       blkSrvAddr,
		connectedChan: make(chan struct{}),
	}

	// Start connecting in the background
	go b.Reconnect(ctx)
	return b
}

// newBlockServerRemoteWithClient should only be used for testing.
func newBlockServerRemoteWithClient(ctx context.Context, config Config,
	client keybase1.GenericClient) *BlockServerRemote {
	b := &BlockServerRemote{
		config:        config,
		connectedChan: make(chan struct{}),
		clt:           client,
	}

	if err := b.ConnectOnce(ctx); err != nil {
		panic("Failed to connect to a provided client.")
	}

	return b
}

// TLSConnect connects over TLS to the given server, expecting the
// connection to be authenticated with the given certificate.
func TLSConnect(cert []byte, Addr string) (conn net.Conn, err error) {
	CAPool := x509.NewCertPool()
	CAPool.AppendCertsFromPEM(cert)

	config := tls.Config{RootCAs: CAPool}
	conn, err = tls.Dial("tcp", Addr, &config)
	if err != nil {
		return
	}
	return
}

func (b *BlockServerRemote) initClient() error {
	b.connMu.Lock()
	defer b.connMu.Unlock()
	if b.shutdown {
		// Pretend everything is fine.  That will cause the goroutine
		// calling us to exit eventually.
		return nil
	}

	var err error
	if b.conn, err = TLSConnect(b.config.CACert(), b.srvAddr); err != nil {
		libkb.G.Log.Warning("NewBlockServerRemote: cannot connect to backend "+
			"err : %v", err)
		return err
	}

	b.clt = rpc2.NewClient(rpc2.NewTransport(b.conn, libkb.NewRPCLogFactory(),
		libkb.WrapError), libkb.UnwrapError)

	return nil
}

// Config returns the configuration object
func (b *BlockServerRemote) Config() Config {
	return b.config
}

// ConnectOnce tries once to connect to the remote block server.
func (b *BlockServerRemote) ConnectOnce(ctx context.Context) error {
	shutdown := func() bool {
		b.connMu.Lock()
		defer b.connMu.Unlock()
		return b.shutdown
	}()
	if shutdown {
		// Pretend everything is fine.  That will cause the goroutine
		// calling us to exit.
		return nil
	}

	var token string
	var session *libkb.Session
	var err error
	if session, err = b.config.KBPKI().GetSession(ctx); err != nil {
		libkb.G.Log.Warning("BlockServerRemote: error getting session %q", err)
		return err
	} else if session != nil {
		token = session.GetToken()
	}

	var user keybase1.UID
	user, err = b.config.KBPKI().GetLoggedInUser(ctx)
	if err != nil {
		return err
	}

	arg := keybase1.EstablishSessionArg{
		User: user,
		Sid:  token,
	}
	clt := keybase1.BlockClient{Cli: b.clt}
	if err = clt.EstablishSession(arg); err != nil {
		libkb.G.Log.Warning("BlockServerRemote: error getting session token %q", err)
		return err
	}

	b.connectedMu.Lock()
	defer b.connectedMu.Unlock()
	close(b.connectedChan)
	b.connectedChan = nil
	return nil
}

func (b *BlockServerRemote) isConnected() bool {
	return b.connectedChan == nil
}

// WaitForReconnect waits for the timeout period to reconnect to the
// server.
func (b *BlockServerRemote) WaitForReconnect(parent context.Context) error {
	c := func() chan struct{} {
		b.connectedMu.Lock()
		defer b.connectedMu.Unlock()
		return b.connectedChan
	}()

	if c == nil {
		// we're already connected
		return nil
	}

	ctx, cancel := context.WithTimeout(parent, BServerTimeout)
	defer cancel()

	// Wait either for the timeout, or for the connection to come up
	// (c will be closed).
	select {
	case <-ctx.Done():
		return ctx.Err()
	case <-c:
		// Note: if we ever want to recover from lost connectivity, we
		// should probably put this whole method in a loop so that we
		// check connectedChan again after the channel closes.
		return nil
	}
}

// Reconnect reconnects to block server.
func (b *BlockServerRemote) Reconnect(ctx context.Context) {
	for b.initClient() != nil {
		time.Sleep(1 * time.Second)
	}

	for b.ConnectOnce(ctx) != nil {
		time.Sleep(1 * time.Second)
	}
}

// Shutdown closes the connection to this remote block server.
func (b *BlockServerRemote) Shutdown() {
	b.connMu.Lock()
	defer b.connMu.Unlock()
	if b.conn != nil {
		b.conn.Close()
	}
	b.shutdown = true
}

// Get implements the BlockServer interface for BlockServerRemote.
func (b *BlockServerRemote) Get(ctx context.Context, id BlockID,
	context BlockContext) ([]byte, BlockCryptKeyServerHalf, error) {
	libkb.G.Log.Debug("BlockServerRemote.Get id=%s uid=%s\n",
		id.String(), context.GetWriter().String())
	if !b.isConnected() {
		if err := b.WaitForReconnect(ctx); err != nil {
			return nil, BlockCryptKeyServerHalf{}, err
		}
	}
	bid := keybase1.BlockIdCombo{
		BlockHash: id.String(),
		ChargedTo: context.GetWriter(),
	}

	var res keybase1.GetBlockRes
	f := func() error {
		var err error
		//XXX: if fails due to connection problem, should reconnect
		clt := keybase1.BlockClient{Cli: b.clt}
		res, err = clt.GetBlock(bid)
		return err
	}

	err := runUnlessCanceled(ctx, f)
	if err != nil {
		libkb.G.Log.Debug("BlockServerRemote.Get id=%s err=%v\n",
			id.String(), err)
		return nil, BlockCryptKeyServerHalf{}, err
	}

	bk := BlockCryptKeyServerHalf{}
	kbuf, err := hex.DecodeString(res.BlockKey)
	if err != nil {
		return nil, BlockCryptKeyServerHalf{}, err
	}

	copy(bk.ServerHalf[:], kbuf)
	return res.Buf, bk, nil
}

// Put implements the BlockServer interface for BlockServerRemote.
// TODO: store the server-half of the block key
func (b *BlockServerRemote) Put(ctx context.Context, id BlockID, tlfID TlfID,
	context BlockContext, buf []byte,
	serverHalf BlockCryptKeyServerHalf) error {
	libkb.G.Log.Debug("BlockServerRemote.Put id=%s uid=%s\n",
		id.String(), context.GetWriter().String())
	if !b.isConnected() {
		if err := b.WaitForReconnect(ctx); err != nil {
			return err
		}
	}
	arg := keybase1.PutBlockArg{
		Bid: keybase1.BlockIdCombo{
			ChargedTo: context.GetWriter(),
			BlockHash: id.String(),
		},
		BlockKey: hex.EncodeToString(serverHalf.ServerHalf[:]),
		Folder:   tlfID.String(),
		Buf:      buf,
	}

	f := func() error {
		clt := keybase1.BlockClient{Cli: b.clt}
		return clt.PutBlock(arg)
	}
	err := runUnlessCanceled(ctx, f)
	if err != nil {
		libkb.G.Log.Debug("BlockServerRemote.Put id=%s err=%v\n",
			id.String(), err)
		return err
	}

	return nil
}

// AddBlockReference implements the BlockServer interface for BlockServerRemote
func (b *BlockServerRemote) AddBlockReference(ctx context.Context, id BlockID,
	tlfID TlfID, context BlockContext) error {
	libkb.G.Log.Debug("BlockServerRemote.AddBlockReference id=%s uid=%s\n",
		id.String(), context.GetWriter().String())
	nonce := context.GetRefNonce()
	arg := keybase1.IncBlockReferenceArg{
		Bid: keybase1.BlockIdCombo{
			ChargedTo: context.GetCreator(),
			BlockHash: id.String(),
		},
		Folder:    tlfID.String(),
		ChargedTo: context.GetWriter(), //the actual writer to decrement quota from
	}
	copy(arg.Nonce[:], nonce[:])

	f := func() error {
		clt := keybase1.BlockClient{Cli: b.clt}
		return clt.IncBlockReference(arg)
	}
	err := runUnlessCanceled(ctx, f)
	if err != nil {
		// TODO: translate a particular RPC error into
		// IncrementMissingBlockError?
		libkb.G.Log.Debug("AddBlockReference to backend err : %q", err)
		return err
	}

	return nil
}

// RemoveBlockReference implements the BlockServer interface for
// BlockServerRemote
func (b *BlockServerRemote) RemoveBlockReference(ctx context.Context, id BlockID,
	tlfID TlfID, context BlockContext) error {
	libkb.G.Log.Debug("BlockServerRemote.RemoveBlockReference id=%s uid=%s\n",
		id.String(), context.GetWriter().String())
	nonce := context.GetRefNonce()
	arg := keybase1.DecBlockReferenceArg{
		Bid: keybase1.BlockIdCombo{
			ChargedTo: context.GetCreator(),
			BlockHash: id.String(),
		},
		Folder:    tlfID.String(),
		ChargedTo: context.GetWriter(), //the actual writer to decrement quota from
	}
	copy(arg.Nonce[:], nonce[:])

	f := func() error {
		clt := keybase1.BlockClient{Cli: b.clt}
		return clt.DecBlockReference(arg)
	}
	err := runUnlessCanceled(ctx, f)
	if err != nil {
		libkb.G.Log.Debug("RemoveBlockReference to backend err : %q", err)
		return err
	}

	return nil
}
<|endoftext|>