<commit_before>/*
Copyright IBM Corp. 2016 All Rights Reserved.

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

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

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

package platforms

import (
	"archive/tar"
	"compress/gzip"
	"fmt"
	"io"
	"os"

	"strings"

	"io/ioutil"

	"github.com/hyperledger/fabric/core/chaincode/platforms/car"
	"github.com/hyperledger/fabric/core/chaincode/platforms/golang"
	"github.com/hyperledger/fabric/core/chaincode/platforms/java"
	cutil "github.com/hyperledger/fabric/core/container/util"
	pb "github.com/hyperledger/fabric/protos/peer"
	"github.com/op/go-logging"
	"github.com/spf13/viper"
)

// Interface for validating the specification and and writing the package for
// the given platform
type Platform interface {
	ValidateSpec(spec *pb.ChaincodeSpec) error
	GetDeploymentPayload(spec *pb.ChaincodeSpec) ([]byte, error)
	GenerateDockerfile(spec *pb.ChaincodeDeploymentSpec) (string, error)
	GenerateDockerBuild(spec *pb.ChaincodeDeploymentSpec, tw *tar.Writer) error
}

var logger = logging.MustGetLogger("chaincode-platform")

// Find returns the platform interface for the given platform type
func Find(chaincodeType pb.ChaincodeSpec_Type) (Platform, error) {

	switch chaincodeType {
	case pb.ChaincodeSpec_GOLANG:
		return &golang.Platform{}, nil
	case pb.ChaincodeSpec_CAR:
		return &car.Platform{}, nil
	case pb.ChaincodeSpec_JAVA:
		return &java.Platform{}, nil
	default:
		return nil, fmt.Errorf("Unknown chaincodeType: %s", chaincodeType)
	}

}

func GetDeploymentPayload(spec *pb.ChaincodeSpec) ([]byte, error) {
	platform, err := Find(spec.Type)
	if err != nil {
		return nil, err
	}

	return platform.GetDeploymentPayload(spec)
}

func getPeerTLSCert() ([]byte, error) {
	path := viper.GetString("peer.tls.cert.file")
	if _, err := os.Stat(path); os.IsNotExist(err) {
		// It's not an error if the file doesn't exist, we simply do not have a cert
		return nil, nil
	}

	// FIXME: FAB-2037 - ensure we sanely resolve relative paths specified in the yaml
	return ioutil.ReadFile(path)
}

func generateDockerfile(platform Platform, cds *pb.ChaincodeDeploymentSpec, tls bool) ([]byte, error) {

	var buf []string

	// ----------------------------------------------------------------------------------------------------
	// Let the platform define the base Dockerfile
	// ----------------------------------------------------------------------------------------------------
	base, err := platform.GenerateDockerfile(cds)
	if err != nil {
		return nil, fmt.Errorf("Failed to generate platform-specific Dockerfile: %s", err)
	}

	buf = append(buf, base)

	// ----------------------------------------------------------------------------------------------------
	// Then augment it with any general options
	// ----------------------------------------------------------------------------------------------------
	if tls {
		const guestTLSPath = "/etc/hyperledger/fabric/peer.crt"

		buf = append(buf, "ENV CORE_PEER_TLS_CERT_FILE="+guestTLSPath)
		buf = append(buf, "COPY peer.crt "+guestTLSPath)
	}

	// ----------------------------------------------------------------------------------------------------
	// Finalize it
	// ----------------------------------------------------------------------------------------------------
	contents := strings.Join(buf, "\n")

	return []byte(contents), nil
}

type InputFiles map[string][]byte

func generateDockerBuild(platform Platform, cds *pb.ChaincodeDeploymentSpec, inputFiles InputFiles, tw *tar.Writer) error {

	var err error

	// ----------------------------------------------------------------------------------------------------
	// First stream out our static inputFiles
	// ----------------------------------------------------------------------------------------------------
	for name, data := range inputFiles {
		err = cutil.WriteBytesToPackage(name, data, tw)
		if err != nil {
			return fmt.Errorf("Failed to inject Dockerfile: %s", err)
		}
	}

	// ----------------------------------------------------------------------------------------------------
	// Now give the platform an opportunity to contribute its own context to the build
	// ----------------------------------------------------------------------------------------------------
	err = platform.GenerateDockerBuild(cds, tw)
	if err != nil {
		return fmt.Errorf("Failed to generate platform-specific docker build: %s", err)
	}

	return nil
}

func GenerateDockerBuild(cds *pb.ChaincodeDeploymentSpec) (io.Reader, error) {

	inputFiles := make(InputFiles)

	// ----------------------------------------------------------------------------------------------------
	// Determine our platform driver from the spec
	// ----------------------------------------------------------------------------------------------------
	platform, err := Find(cds.ChaincodeSpec.Type)
	if err != nil {
		return nil, fmt.Errorf("Failed to determine platform type: %s", err)
	}

	// ----------------------------------------------------------------------------------------------------
	// Transfer the peer's TLS certificate to our list of input files, if applicable
	// ----------------------------------------------------------------------------------------------------
	// NOTE: We bake the peer TLS certificate in at the time we build the chaincode container if a cert is
	// found, regardless of whether TLS is enabled or not.  The main implication is that if the administrator
	// updates the peer cert, the chaincode containers will need to be invalidated and rebuilt.
	// We will manage enabling or disabling TLS at container run time via CORE_PEER_TLS_ENABLED
	cert, err := getPeerTLSCert()
	if err != nil {
		return nil, fmt.Errorf("Failed to read the TLS certificate: %s", err)
	}

	inputFiles["peer.crt"] = cert

	// ----------------------------------------------------------------------------------------------------
	// Generate the Dockerfile specific to our context
	// ----------------------------------------------------------------------------------------------------
	dockerFile, err := generateDockerfile(platform, cds, cert != nil)
	if err != nil {
		return nil, fmt.Errorf("Failed to generate a Dockerfile: %s", err)
	}

	inputFiles["Dockerfile"] = dockerFile

	// ----------------------------------------------------------------------------------------------------
	// Finally, launch an asynchronous process to stream all of the above into a docker build context
	// ----------------------------------------------------------------------------------------------------
	input, output := io.Pipe()

	go func() {
		gw := gzip.NewWriter(output)
		tw := tar.NewWriter(gw)

		err := generateDockerBuild(platform, cds, inputFiles, tw)
		if err != nil {
			logger.Error(err)
		}

		tw.Close()
		gw.Close()
		output.CloseWithError(err)
	}()

	return input, nil
}
<commit_msg>[FAB-678] Further refinements to dockerfile creation<commit_after>/*
Copyright IBM Corp. 2016 All Rights Reserved.

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

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

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

package platforms

import (
	"archive/tar"
	"compress/gzip"
	"fmt"
	"io"
	"os"

	"strings"

	"io/ioutil"

	"github.com/hyperledger/fabric/core/chaincode/platforms/car"
	"github.com/hyperledger/fabric/core/chaincode/platforms/golang"
	"github.com/hyperledger/fabric/core/chaincode/platforms/java"
	cutil "github.com/hyperledger/fabric/core/container/util"
	pb "github.com/hyperledger/fabric/protos/peer"
	"github.com/op/go-logging"
	"github.com/spf13/viper"
)

// Interface for validating the specification and and writing the package for
// the given platform
type Platform interface {
	ValidateSpec(spec *pb.ChaincodeSpec) error
	GetDeploymentPayload(spec *pb.ChaincodeSpec) ([]byte, error)
	GenerateDockerfile(spec *pb.ChaincodeDeploymentSpec) (string, error)
	GenerateDockerBuild(spec *pb.ChaincodeDeploymentSpec, tw *tar.Writer) error
}

var logger = logging.MustGetLogger("chaincode-platform")

// Find returns the platform interface for the given platform type
func Find(chaincodeType pb.ChaincodeSpec_Type) (Platform, error) {

	switch chaincodeType {
	case pb.ChaincodeSpec_GOLANG:
		return &golang.Platform{}, nil
	case pb.ChaincodeSpec_CAR:
		return &car.Platform{}, nil
	case pb.ChaincodeSpec_JAVA:
		return &java.Platform{}, nil
	default:
		return nil, fmt.Errorf("Unknown chaincodeType: %s", chaincodeType)
	}

}

func GetDeploymentPayload(spec *pb.ChaincodeSpec) ([]byte, error) {
	platform, err := Find(spec.Type)
	if err != nil {
		return nil, err
	}

	return platform.GetDeploymentPayload(spec)
}

func getPeerTLSCert() ([]byte, error) {
	path := viper.GetString("peer.tls.cert.file")
	if _, err := os.Stat(path); err != nil {

		if os.IsNotExist(err) && viper.GetBool("peer.tls.enabled") == false {
			// It's not an error if the file doesn't exist but TLS is disabled anyway
			return nil, nil
		}

		return nil, err
	}

	// FIXME: FAB-2037 - ensure we sanely resolve relative paths specified in the yaml
	return ioutil.ReadFile(path)
}

func generateDockerfile(platform Platform, cds *pb.ChaincodeDeploymentSpec, tls bool) ([]byte, error) {

	var buf []string

	// ----------------------------------------------------------------------------------------------------
	// Let the platform define the base Dockerfile
	// ----------------------------------------------------------------------------------------------------
	base, err := platform.GenerateDockerfile(cds)
	if err != nil {
		return nil, fmt.Errorf("Failed to generate platform-specific Dockerfile: %s", err)
	}

	buf = append(buf, base)

	// ----------------------------------------------------------------------------------------------------
	// Then augment it with any general options
	// ----------------------------------------------------------------------------------------------------
	if tls {
		const guestTLSPath = "/etc/hyperledger/fabric/peer.crt"

		buf = append(buf, "ENV CORE_PEER_TLS_CERT_FILE="+guestTLSPath)
		buf = append(buf, "COPY peer.crt "+guestTLSPath)
	}

	// ----------------------------------------------------------------------------------------------------
	// Finalize it
	// ----------------------------------------------------------------------------------------------------
	contents := strings.Join(buf, "\n")

	return []byte(contents), nil
}

type InputFiles map[string][]byte

func generateDockerBuild(platform Platform, cds *pb.ChaincodeDeploymentSpec, inputFiles InputFiles, tw *tar.Writer) error {

	var err error

	// ----------------------------------------------------------------------------------------------------
	// First stream out our static inputFiles
	// ----------------------------------------------------------------------------------------------------
	for name, data := range inputFiles {
		err = cutil.WriteBytesToPackage(name, data, tw)
		if err != nil {
			return fmt.Errorf("Failed to inject \"%s\": %s", name, err)
		}
	}

	// ----------------------------------------------------------------------------------------------------
	// Now give the platform an opportunity to contribute its own context to the build
	// ----------------------------------------------------------------------------------------------------
	err = platform.GenerateDockerBuild(cds, tw)
	if err != nil {
		return fmt.Errorf("Failed to generate platform-specific docker build: %s", err)
	}

	return nil
}

func GenerateDockerBuild(cds *pb.ChaincodeDeploymentSpec) (io.Reader, error) {

	inputFiles := make(InputFiles)

	// ----------------------------------------------------------------------------------------------------
	// Determine our platform driver from the spec
	// ----------------------------------------------------------------------------------------------------
	platform, err := Find(cds.ChaincodeSpec.Type)
	if err != nil {
		return nil, fmt.Errorf("Failed to determine platform type: %s", err)
	}

	// ----------------------------------------------------------------------------------------------------
	// Transfer the peer's TLS certificate to our list of input files, if applicable
	// ----------------------------------------------------------------------------------------------------
	// NOTE: We bake the peer TLS certificate in at the time we build the chaincode container if a cert is
	// found, regardless of whether TLS is enabled or not.  The main implication is that if the administrator
	// updates the peer cert, the chaincode containers will need to be invalidated and rebuilt.
	// We will manage enabling or disabling TLS at container run time via CORE_PEER_TLS_ENABLED
	cert, err := getPeerTLSCert()
	if err != nil {
		return nil, fmt.Errorf("Failed to read the TLS certificate: %s", err)
	}

	inputFiles["peer.crt"] = cert

	// ----------------------------------------------------------------------------------------------------
	// Generate the Dockerfile specific to our context
	// ----------------------------------------------------------------------------------------------------
	dockerFile, err := generateDockerfile(platform, cds, cert != nil)
	if err != nil {
		return nil, fmt.Errorf("Failed to generate a Dockerfile: %s", err)
	}

	inputFiles["Dockerfile"] = dockerFile

	// ----------------------------------------------------------------------------------------------------
	// Finally, launch an asynchronous process to stream all of the above into a docker build context
	// ----------------------------------------------------------------------------------------------------
	input, output := io.Pipe()

	go func() {
		gw := gzip.NewWriter(output)
		tw := tar.NewWriter(gw)

		err := generateDockerBuild(platform, cds, inputFiles, tw)
		if err != nil {
			logger.Error(err)
		}

		tw.Close()
		gw.Close()
		output.CloseWithError(err)
	}()

	return input, nil
}
<|endoftext|>


<commit_before>package rancher

import (
	"fmt"

	"github.com/Sirupsen/logrus"
	"github.com/docker/libcompose/utils"
	rancherClient "github.com/rancher/go-rancher/client"
	"github.com/rancher/rancher-compose/digest"
)

type NormalFactory struct {
}

func (f *NormalFactory) Hash(service *RancherService) (digest.ServiceHash, error) {
	hash, _, err := f.configAndHash(service)
	return hash, err
}

func (f *NormalFactory) configAndHash(r *RancherService) (digest.ServiceHash, *CompositeService, error) {
	rancherService, launchConfig, secondaryLaunchConfigs, err := f.config(r)
	if err != nil {
		return digest.ServiceHash{}, nil, err
	}

	hash, err := digest.CreateServiceHash(rancherService, launchConfig, secondaryLaunchConfigs)
	if err != nil {
		return digest.ServiceHash{}, nil, err
	}

	rancherService.LaunchConfig = launchConfig
	rancherService.LaunchConfig.Labels[digest.ServiceHashKey] = hash.LaunchConfig
	rancherService.SecondaryLaunchConfigs = []interface{}{}
	rancherService.Metadata[digest.ServiceHashKey] = hash.Service

	for _, secondaryLaunchConfig := range secondaryLaunchConfigs {
		secondaryLaunchConfig.Labels[digest.ServiceHashKey] = hash.SecondaryLaunchConfigs[secondaryLaunchConfig.Name]
		rancherService.SecondaryLaunchConfigs = append(rancherService.SecondaryLaunchConfigs, secondaryLaunchConfig)
	}

	return hash, rancherService, nil
}

func (f *NormalFactory) config(r *RancherService) (*CompositeService, *rancherClient.LaunchConfig, []rancherClient.SecondaryLaunchConfig, error) {
	launchConfig, secondaryLaunchConfigs, err := createLaunchConfigs(r)
	if err != nil {
		return nil, nil, nil, err
	}

	rancherConfig, _ := r.context.RancherConfig[r.name]

	service := &CompositeService{
		Service: rancherClient.Service{
			Name:              r.name,
			Metadata:          r.Metadata(),
			Scale:             int64(r.getConfiguredScale()),
			ScalePolicy:       rancherConfig.ScalePolicy,
			RetainIp:          rancherConfig.RetainIp,
			EnvironmentId:     r.Context().Environment.Id,
			SelectorContainer: r.SelectorContainer(),
			SelectorLink:      r.SelectorLink(),
		},
		ExternalIpAddresses: rancherConfig.ExternalIps,
		Hostname:            rancherConfig.Hostname,
		HealthCheck:         r.HealthCheck(""),
	}

	if err := populateLbFields(r, &launchConfig, service); err != nil {
		return nil, nil, nil, err
	}

	return service, &launchConfig, secondaryLaunchConfigs, nil
}

func (f *NormalFactory) Create(r *RancherService) error {
	hash, service, err := f.configAndHash(r)
	if err != nil {
		return err
	}

	logrus.Debugf("Creating service %s with hash: %#v", r.name, hash)
	switch FindServiceType(r) {
	case ExternalServiceType:
		return r.context.Client.Create(rancherClient.EXTERNAL_SERVICE_TYPE, &service, nil)
	case DnsServiceType:
		return r.context.Client.Create(rancherClient.DNS_SERVICE_TYPE, &service, nil)
	case LbServiceType:
		return r.context.Client.Create(rancherClient.LOAD_BALANCER_SERVICE_TYPE, &service, nil)
	default:
		_, err = r.context.Client.Service.Create(&service.Service)
	}

	return err
}

func (f *NormalFactory) Rollback(r *RancherService) error {
	existingService, err := r.FindExisting(r.Name())
	if err != nil || existingService == nil {
		return err
	}

	existingService, err = r.Client().Service.ActionRollback(existingService)
	if err != nil {
		return err
	}

	return r.Wait(existingService)
}

func isForce(name string, force bool, selected []string) bool {
	if !force {
		return false
	}
	if len(selected) == 0 {
		return true
	}

	return utils.Contains(selected, name)
}

func (f *NormalFactory) Upgrade(r *RancherService, force bool, selected []string) error {
	existingService, err := r.FindExisting(r.Name())
	if err != nil || existingService == nil {
		return err
	}

	if existingService.State != "active" && existingService.State != "inactive" {
		return fmt.Errorf("Service %s must be state=active or inactive to upgrade, currently: state=%s", r.Name(), existingService.State)
	}

	existingHash, _ := digest.LookupHash(existingService)
	secondaryNames := []string{}

	hash, err := f.Hash(r)
	if err != nil {
		return err
	}

	service := hash.Service != existingHash.Service || isForce(r.Name(), force, selected)
	launchConfig := hash.LaunchConfig != existingHash.LaunchConfig || isForce(r.Name(), force, selected)
	for newSecondary, newHash := range hash.SecondaryLaunchConfigs {
		if oldHash, ok := existingHash.SecondaryLaunchConfigs[newSecondary]; ok {
			if oldHash != newHash || isForce(newSecondary, force, selected) {
				secondaryNames = append(secondaryNames, newSecondary)
			}
		} else {
			return fmt.Errorf("Existing service %s does not have a sidekick named %s", r.Name(), newSecondary)
		}
	}

	return f.upgrade(r, existingService, service, launchConfig, secondaryNames)
}

func (f *NormalFactory) upgrade(r *RancherService, existingService *rancherClient.Service, service, launchConfig bool, secondaryNames []string) error {
	_, config, err := f.configAndHash(r)
	if err != nil {
		return err
	}

	serviceUpgrade := &rancherClient.ServiceUpgrade{
		InServiceStrategy: &rancherClient.InServiceUpgradeStrategy{
			BatchSize:      r.context.BatchSize,
			IntervalMillis: r.context.Interval,
			StartFirst:     r.RancherConfig().UpgradeStrategy.StartFirst,
		},
	}

	serviceUpgrade.InServiceStrategy.SecondaryLaunchConfigs = []interface{}{}

	if launchConfig {
		serviceUpgrade.InServiceStrategy.LaunchConfig = config.LaunchConfig
	}

	for _, name := range secondaryNames {
		for _, v := range config.SecondaryLaunchConfigs {
			if secondaryLaunchConfig, ok := v.(rancherClient.SecondaryLaunchConfig); ok {
				if secondaryLaunchConfig.Name == name {
					serviceUpgrade.InServiceStrategy.SecondaryLaunchConfigs = append(serviceUpgrade.InServiceStrategy.SecondaryLaunchConfigs, secondaryLaunchConfig)
				}
			}
		}
	}

	if service {
		// Scale must be changed through "scale" not "up", so always copy scale existing scale
		config.Scale = existingService.Scale

		logrus.Infof("Updating %s", r.Name())
		schemaType := rancherClient.SERVICE_TYPE
		switch FindServiceType(r) {
		case ExternalServiceType:
			schemaType = rancherClient.EXTERNAL_SERVICE_TYPE
		case DnsServiceType:
			schemaType = rancherClient.DNS_SERVICE_TYPE
		case LbServiceType:
			schemaType = rancherClient.LOAD_BALANCER_SERVICE_TYPE
		}

		if err := r.context.Client.Update(schemaType, &existingService.Resource, config, existingService); err != nil {
			return err
		}

		if err := r.Wait(existingService); err != nil {
			return err
		}
	}

	if launchConfig || len(secondaryNames) > 0 {
		logrus.Infof("Upgrading %s", r.Name())
		existingService, err = r.Client().Service.ActionUpgrade(existingService, serviceUpgrade)
		if err != nil {
			return err
		}
	}

	return r.Wait(existingService)
}
<commit_msg>allow adding sidekicks to the service<commit_after>package rancher

import (
	"fmt"

	"github.com/Sirupsen/logrus"
	"github.com/docker/libcompose/utils"
	rancherClient "github.com/rancher/go-rancher/client"
	"github.com/rancher/rancher-compose/digest"
)

type NormalFactory struct {
}

func (f *NormalFactory) Hash(service *RancherService) (digest.ServiceHash, error) {
	hash, _, err := f.configAndHash(service)
	return hash, err
}

func (f *NormalFactory) configAndHash(r *RancherService) (digest.ServiceHash, *CompositeService, error) {
	rancherService, launchConfig, secondaryLaunchConfigs, err := f.config(r)
	if err != nil {
		return digest.ServiceHash{}, nil, err
	}

	hash, err := digest.CreateServiceHash(rancherService, launchConfig, secondaryLaunchConfigs)
	if err != nil {
		return digest.ServiceHash{}, nil, err
	}

	rancherService.LaunchConfig = launchConfig
	rancherService.LaunchConfig.Labels[digest.ServiceHashKey] = hash.LaunchConfig
	rancherService.SecondaryLaunchConfigs = []interface{}{}
	rancherService.Metadata[digest.ServiceHashKey] = hash.Service

	for _, secondaryLaunchConfig := range secondaryLaunchConfigs {
		secondaryLaunchConfig.Labels[digest.ServiceHashKey] = hash.SecondaryLaunchConfigs[secondaryLaunchConfig.Name]
		rancherService.SecondaryLaunchConfigs = append(rancherService.SecondaryLaunchConfigs, secondaryLaunchConfig)
	}

	return hash, rancherService, nil
}

func (f *NormalFactory) config(r *RancherService) (*CompositeService, *rancherClient.LaunchConfig, []rancherClient.SecondaryLaunchConfig, error) {
	launchConfig, secondaryLaunchConfigs, err := createLaunchConfigs(r)
	if err != nil {
		return nil, nil, nil, err
	}

	rancherConfig, _ := r.context.RancherConfig[r.name]

	service := &CompositeService{
		Service: rancherClient.Service{
			Name:              r.name,
			Metadata:          r.Metadata(),
			Scale:             int64(r.getConfiguredScale()),
			ScalePolicy:       rancherConfig.ScalePolicy,
			RetainIp:          rancherConfig.RetainIp,
			EnvironmentId:     r.Context().Environment.Id,
			SelectorContainer: r.SelectorContainer(),
			SelectorLink:      r.SelectorLink(),
		},
		ExternalIpAddresses: rancherConfig.ExternalIps,
		Hostname:            rancherConfig.Hostname,
		HealthCheck:         r.HealthCheck(""),
	}

	if err := populateLbFields(r, &launchConfig, service); err != nil {
		return nil, nil, nil, err
	}

	return service, &launchConfig, secondaryLaunchConfigs, nil
}

func (f *NormalFactory) Create(r *RancherService) error {
	hash, service, err := f.configAndHash(r)
	if err != nil {
		return err
	}

	logrus.Debugf("Creating service %s with hash: %#v", r.name, hash)
	switch FindServiceType(r) {
	case ExternalServiceType:
		return r.context.Client.Create(rancherClient.EXTERNAL_SERVICE_TYPE, &service, nil)
	case DnsServiceType:
		return r.context.Client.Create(rancherClient.DNS_SERVICE_TYPE, &service, nil)
	case LbServiceType:
		return r.context.Client.Create(rancherClient.LOAD_BALANCER_SERVICE_TYPE, &service, nil)
	default:
		_, err = r.context.Client.Service.Create(&service.Service)
	}

	return err
}

func (f *NormalFactory) Rollback(r *RancherService) error {
	existingService, err := r.FindExisting(r.Name())
	if err != nil || existingService == nil {
		return err
	}

	existingService, err = r.Client().Service.ActionRollback(existingService)
	if err != nil {
		return err
	}

	return r.Wait(existingService)
}

func isForce(name string, force bool, selected []string) bool {
	if !force {
		return false
	}
	if len(selected) == 0 {
		return true
	}

	return utils.Contains(selected, name)
}

func (f *NormalFactory) Upgrade(r *RancherService, force bool, selected []string) error {
	existingService, err := r.FindExisting(r.Name())
	if err != nil || existingService == nil {
		return err
	}

	if existingService.State != "active" && existingService.State != "inactive" {
		return fmt.Errorf("Service %s must be state=active or inactive to upgrade, currently: state=%s", r.Name(), existingService.State)
	}

	existingHash, _ := digest.LookupHash(existingService)
	secondaryNames := []string{}

	hash, err := f.Hash(r)
	if err != nil {
		return err
	}

	service := hash.Service != existingHash.Service || isForce(r.Name(), force, selected)
	launchConfig := hash.LaunchConfig != existingHash.LaunchConfig || isForce(r.Name(), force, selected)
	for newSecondary, newHash := range hash.SecondaryLaunchConfigs {
		if oldHash, ok := existingHash.SecondaryLaunchConfigs[newSecondary]; ok {
			if oldHash != newHash || isForce(newSecondary, force, selected) {
				secondaryNames = append(secondaryNames, newSecondary)
			}
		} else {
			secondaryNames = append(secondaryNames, newSecondary)
		}
	}

	return f.upgrade(r, existingService, service, launchConfig, secondaryNames)
}

func (f *NormalFactory) upgrade(r *RancherService, existingService *rancherClient.Service, service, launchConfig bool, secondaryNames []string) error {
	_, config, err := f.configAndHash(r)
	if err != nil {
		return err
	}

	serviceUpgrade := &rancherClient.ServiceUpgrade{
		InServiceStrategy: &rancherClient.InServiceUpgradeStrategy{
			BatchSize:      r.context.BatchSize,
			IntervalMillis: r.context.Interval,
			StartFirst:     r.RancherConfig().UpgradeStrategy.StartFirst,
		},
	}

	serviceUpgrade.InServiceStrategy.SecondaryLaunchConfigs = []interface{}{}

	if launchConfig {
		serviceUpgrade.InServiceStrategy.LaunchConfig = config.LaunchConfig
	}

	for _, name := range secondaryNames {
		for _, v := range config.SecondaryLaunchConfigs {
			if secondaryLaunchConfig, ok := v.(rancherClient.SecondaryLaunchConfig); ok {
				if secondaryLaunchConfig.Name == name {
					serviceUpgrade.InServiceStrategy.SecondaryLaunchConfigs = append(serviceUpgrade.InServiceStrategy.SecondaryLaunchConfigs, secondaryLaunchConfig)
				}
			}
		}
	}

	if service {
		// Scale must be changed through "scale" not "up", so always copy scale existing scale
		config.Scale = existingService.Scale

		logrus.Infof("Updating %s", r.Name())
		schemaType := rancherClient.SERVICE_TYPE
		switch FindServiceType(r) {
		case ExternalServiceType:
			schemaType = rancherClient.EXTERNAL_SERVICE_TYPE
		case DnsServiceType:
			schemaType = rancherClient.DNS_SERVICE_TYPE
		case LbServiceType:
			schemaType = rancherClient.LOAD_BALANCER_SERVICE_TYPE
		}

		if err := r.context.Client.Update(schemaType, &existingService.Resource, config, existingService); err != nil {
			return err
		}

		if err := r.Wait(existingService); err != nil {
			return err
		}
	}

	if launchConfig || len(secondaryNames) > 0 {
		logrus.Infof("Upgrading %s", r.Name())
		existingService, err = r.Client().Service.ActionUpgrade(existingService, serviceUpgrade)
		if err != nil {
			return err
		}
	}

	return r.Wait(existingService)
}
<|endoftext|>


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

package prism

import (
	"fmt"
	"hash/fnv"
	"sync"
	"time"

	"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/common/model"
	"github.com/prometheus/prometheus/storage/metric"
	"github.com/weaveworks/scope/common/instrument"
	"golang.org/x/net/context"

	"github.com/weaveworks/prism/ring"
	"github.com/weaveworks/prism/user"
)

var (
	numClientsDesc = prometheus.NewDesc(
		"prism_distributor_ingester_clients",
		"The current number of ingester clients.",
		nil, nil,
	)
)

// Distributor is a storage.SampleAppender and a prism.Querier which
// forwards appends and queries to individual ingesters.
type Distributor struct {
	cfg        DistributorConfig
	clientsMtx sync.RWMutex
	clients    map[string]*IngesterClient

	queryDuration          *prometheus.HistogramVec
	receivedSamples        prometheus.Counter
	sendDuration           *prometheus.HistogramVec
	ingesterAppends        *prometheus.CounterVec
	ingesterAppendFailures *prometheus.CounterVec
	ingesterQueries        *prometheus.CounterVec
	ingesterQueryFailures  *prometheus.CounterVec
	ingestersAlive         *prometheus.Desc
}

// ReadRing represents the read inferface to the ring.
type ReadRing interface {
	prometheus.Collector

	Get(key uint32, n int, heartbeatTimeout time.Duration) ([]ring.IngesterDesc, error)
	GetAll(heartbeatTimeout time.Duration) []ring.IngesterDesc
}

// IngesterClientFactory creates ingester clients.
type IngesterClientFactory func(string) (*IngesterClient, error)

// DistributorConfig contains the configuration require to
// create a Distributor
type DistributorConfig struct {
	Ring          ReadRing
	ClientFactory IngesterClientFactory

	ReplicationFactor int
	MinReadSuccesses  int
	MinWriteSuccesses int
	HeartbeatTimeout  time.Duration
}

// NewDistributor constructs a new Distributor
func NewDistributor(cfg DistributorConfig) (*Distributor, error) {
	if 0 > cfg.ReplicationFactor {
		return nil, fmt.Errorf("ReplicationFactor must be greater than zero: %d", cfg.ReplicationFactor)
	}
	if cfg.MinWriteSuccesses > cfg.ReplicationFactor {
		return nil, fmt.Errorf("MinWriteSuccesses > ReplicationFactor: %d > %d", cfg.MinWriteSuccesses, cfg.ReplicationFactor)
	}
	if cfg.MinReadSuccesses > cfg.ReplicationFactor {
		return nil, fmt.Errorf("MinReadSuccesses > ReplicationFactor: %d > %d", cfg.MinReadSuccesses, cfg.ReplicationFactor)
	}
	return &Distributor{
		cfg:     cfg,
		clients: map[string]*IngesterClient{},
		queryDuration: prometheus.NewHistogramVec(prometheus.HistogramOpts{
			Namespace: "prism",
			Name:      "distributor_query_duration_seconds",
			Help:      "Time spent executing expression queries.",
			Buckets:   []float64{.005, .01, .025, .05, .1, .25, .5, 1, 2.5, 5, 10, 20, 30},
		}, []string{"method", "status_code"}),
		receivedSamples: prometheus.NewCounter(prometheus.CounterOpts{
			Namespace: "prism",
			Name:      "distributor_received_samples_total",
			Help:      "The total number of received samples.",
		}),
		sendDuration: prometheus.NewHistogramVec(prometheus.HistogramOpts{
			Namespace: "prism",
			Name:      "distributor_send_duration_seconds",
			Help:      "Time spent sending a sample batch to multiple replicated ingesters.",
			Buckets:   []float64{.001, .0025, .005, .01, .025, .05, .1, .25, .5, 1},
		}, []string{"method", "status_code"}),
		ingesterAppends: prometheus.NewCounterVec(prometheus.CounterOpts{
			Namespace: "prism",
			Name:      "distributor_ingester_appends_total",
			Help:      "The total number of batch appends sent to ingesters.",
		}, []string{"ingester"}),
		ingesterAppendFailures: prometheus.NewCounterVec(prometheus.CounterOpts{
			Namespace: "prism",
			Name:      "distributor_ingester_append_failures_total",
			Help:      "The total number of failed batch appends sent to ingesters.",
		}, []string{"ingester"}),
		ingesterQueries: prometheus.NewCounterVec(prometheus.CounterOpts{
			Namespace: "prism",
			Name:      "distributor_ingester_queries_total",
			Help:      "The total number of queries sent to ingesters.",
		}, []string{"ingester"}),
		ingesterQueryFailures: prometheus.NewCounterVec(prometheus.CounterOpts{
			Namespace: "prism",
			Name:      "distributor_ingester_query_failures_total",
			Help:      "The total number of failed queries sent to ingesters.",
		}, []string{"ingester"}),
		ingestersAlive: prometheus.NewDesc(
			"prism_distributor_ingesters_alive",
			"Number of ingesters in the ring that have heartbeats within timeout.",
			nil, nil,
		),
	}, nil
}

func (d *Distributor) getClientFor(hostname string) (*IngesterClient, error) {
	d.clientsMtx.RLock()
	client, ok := d.clients[hostname]
	d.clientsMtx.RUnlock()
	if ok {
		return client, nil
	}

	d.clientsMtx.Lock()
	defer d.clientsMtx.Unlock()
	client, ok = d.clients[hostname]
	if ok {
		return client, nil
	}

	client, err := d.cfg.ClientFactory(hostname)
	if err != nil {
		return nil, err
	}
	d.clients[hostname] = client
	return client, nil
}

func tokenForMetric(userID string, metric model.Metric) uint32 {
	name := metric[model.MetricNameLabel]
	return tokenFor(userID, name)
}

func tokenFor(userID string, name model.LabelValue) uint32 {
	h := fnv.New32()
	h.Write([]byte(userID))
	h.Write([]byte(name))
	return h.Sum32()
}

// Append implements SampleAppender.
func (d *Distributor) Append(ctx context.Context, samples []*model.Sample) error {
	userID, err := user.GetID(ctx)
	if err != nil {
		return err
	}

	d.receivedSamples.Add(float64(len(samples)))

	samplesByIngester := map[string][]*model.Sample{}
	for _, sample := range samples {
		key := tokenForMetric(userID, sample.Metric)
		ingesters, err := d.cfg.Ring.Get(key, d.cfg.ReplicationFactor, d.cfg.HeartbeatTimeout)
		if err != nil {
			return err
		}
		for _, ingester := range ingesters {
			otherSamples := samplesByIngester[ingester.Hostname]
			samplesByIngester[ingester.Hostname] = append(otherSamples, sample)
		}
	}

	errs := make(chan error)
	for hostname, samples := range samplesByIngester {
		go func(hostname string, samples []*model.Sample) {
			errs <- d.sendSamples(ctx, hostname, samples)
		}(hostname, samples)
	}
	var lastErr error
	successes := 0
	for i := 0; i < len(samplesByIngester); i++ {
		if err := <-errs; err != nil {
			lastErr = err
			continue
		}
		successes++
	}

	if successes < d.cfg.MinWriteSuccesses {
		return fmt.Errorf("too few successful writes, last error was: %v", lastErr)
	}
	return nil
}

func (d *Distributor) sendSamples(ctx context.Context, hostname string, samples []*model.Sample) error {
	client, err := d.getClientFor(hostname)
	if err != nil {
		return err
	}
	err = instrument.TimeRequestHistogram("send", d.sendDuration, func() error {
		return client.Append(ctx, samples)
	})
	if err != nil {
		d.ingesterAppendFailures.WithLabelValues(hostname).Inc()
	}
	d.ingesterAppends.WithLabelValues(hostname).Inc()
	return err
}

func metricNameFromLabelMatchers(matchers ...*metric.LabelMatcher) (model.LabelValue, error) {
	for _, m := range matchers {
		if m.Name == model.MetricNameLabel {
			if m.Type != metric.Equal {
				return "", fmt.Errorf("non-equality matchers are not supported on the metric name")
			}
			return m.Value, nil
		}
	}
	return "", fmt.Errorf("no metric name matcher found")
}

// Query implements Querier.
func (d *Distributor) Query(ctx context.Context, from, to model.Time, matchers ...*metric.LabelMatcher) (model.Matrix, error) {
	var result model.Matrix
	err := instrument.TimeRequestHistogram("duration", d.queryDuration, func() error {
		fpToSampleStream := map[model.Fingerprint]*model.SampleStream{}

		metricName, err := metricNameFromLabelMatchers(matchers...)
		if err != nil {
			return err
		}

		userID, err := user.GetID(ctx)
		if err != nil {
			return err
		}

		ingesters, err := d.cfg.Ring.Get(tokenFor(userID, metricName), d.cfg.ReplicationFactor, d.cfg.HeartbeatTimeout)
		if err != nil {
			return err
		}

		if len(ingesters) < d.cfg.MinReadSuccesses {
			return fmt.Errorf("Could only find %d ingesters for query. Need at least %d", len(ingesters), d.cfg.MinReadSuccesses)
		}

		// Fetch samples from multiple ingesters and group them by fingerprint (unsorted
		// and with overlap).
		successes := 0
		var lastErr error
		for _, ing := range ingesters {
			client, err := d.getClientFor(ing.Hostname)
			if err != nil {
				return err
			}
			matrix, err := client.Query(ctx, from, to, matchers...)
			d.ingesterQueries.WithLabelValues(ing.Hostname).Inc()
			if err != nil {
				lastErr = err
				d.ingesterQueryFailures.WithLabelValues(ing.Hostname).Inc()
				continue
			}
			successes++

			for _, ss := range matrix {
				fp := ss.Metric.Fingerprint()
				if mss, ok := fpToSampleStream[fp]; !ok {
					fpToSampleStream[fp] = &model.SampleStream{
						Metric: ss.Metric,
						Values: ss.Values,
					}
				} else {
					mss.Values = mergeSamples(fpToSampleStream[fp].Values, ss.Values)
				}
			}
		}

		if successes < d.cfg.MinReadSuccesses {
			return fmt.Errorf("too few successful reads, last error was: %v", lastErr)
		}

		result = make(model.Matrix, 0, len(fpToSampleStream))
		for _, ss := range fpToSampleStream {
			result = append(result, ss)
		}

		return nil
	})
	return result, err
}

func mergeSamples(a, b []model.SamplePair) []model.SamplePair {
	result := make([]model.SamplePair, 0, len(a)+len(b))
	i, j := 0, 0
	for i < len(a) && j < len(b) {
		if a[i].Timestamp < b[j].Timestamp {
			result = append(result, a[i])
			i++
		} else if a[i].Timestamp > b[j].Timestamp {
			result = append(result, b[j])
			j++
		} else {
			result = append(result, a[i])
			i++
			j++
		}
	}
	for ; i < len(a); i++ {
		result = append(result, a[i])
	}
	for ; j < len(b); j++ {
		result = append(result, b[j])
	}
	return result
}

// LabelValuesForLabelName returns all of the label values that are associated with a given label name.
func (d *Distributor) LabelValuesForLabelName(ctx context.Context, labelName model.LabelName) (model.LabelValues, error) {
	valueSet := map[model.LabelValue]struct{}{}
	for _, c := range d.cfg.Ring.GetAll(d.cfg.HeartbeatTimeout) {
		client, err := d.getClientFor(c.Hostname)
		if err != nil {
			return nil, err
		}
		vals, err := client.LabelValuesForLabelName(ctx, labelName)
		if err != nil {
			return nil, err
		}
		for _, v := range vals {
			valueSet[v] = struct{}{}
		}
	}

	values := make(model.LabelValues, 0, len(valueSet))
	for v := range valueSet {
		values = append(values, v)
	}
	return values, nil
}

// NeedsThrottling implements SampleAppender.
func (*Distributor) NeedsThrottling(_ context.Context) bool {
	return false
}

// Describe implements prometheus.Collector.
func (d *Distributor) Describe(ch chan<- *prometheus.Desc) {
	d.queryDuration.Describe(ch)
	ch <- d.receivedSamples.Desc()
	d.sendDuration.Describe(ch)
	d.cfg.Ring.Describe(ch)
	ch <- numClientsDesc
	d.ingesterAppends.Describe(ch)
	d.ingesterAppendFailures.Describe(ch)
	d.ingesterQueries.Describe(ch)
	d.ingesterQueryFailures.Describe(ch)
	ch <- d.ingestersAlive
}

// Collect implements prometheus.Collector.
func (d *Distributor) Collect(ch chan<- prometheus.Metric) {
	d.queryDuration.Collect(ch)
	ch <- d.receivedSamples
	d.sendDuration.Collect(ch)
	d.cfg.Ring.Collect(ch)
	d.ingesterAppends.Collect(ch)
	d.ingesterAppendFailures.Collect(ch)
	d.ingesterQueries.Collect(ch)
	d.ingesterQueryFailures.Collect(ch)

	ch <- prometheus.MustNewConstMetric(
		d.ingestersAlive,
		prometheus.GaugeValue,
		float64(len(d.cfg.Ring.GetAll(d.cfg.HeartbeatTimeout))),
	)

	d.clientsMtx.RLock()
	defer d.clientsMtx.RUnlock()
	ch <- prometheus.MustNewConstMetric(
		numClientsDesc,
		prometheus.GaugeValue,
		float64(len(d.clients)),
	)
}
<commit_msg>Fix nit<commit_after>// Copyright 2016 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package prism

import (
	"fmt"
	"hash/fnv"
	"sync"
	"time"

	"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/common/model"
	"github.com/prometheus/prometheus/storage/metric"
	"github.com/weaveworks/scope/common/instrument"
	"golang.org/x/net/context"

	"github.com/weaveworks/prism/ring"
	"github.com/weaveworks/prism/user"
)

var (
	numClientsDesc = prometheus.NewDesc(
		"prism_distributor_ingester_clients",
		"The current number of ingester clients.",
		nil, nil,
	)
)

// Distributor is a storage.SampleAppender and a prism.Querier which
// forwards appends and queries to individual ingesters.
type Distributor struct {
	cfg        DistributorConfig
	clientsMtx sync.RWMutex
	clients    map[string]*IngesterClient

	queryDuration          *prometheus.HistogramVec
	receivedSamples        prometheus.Counter
	sendDuration           *prometheus.HistogramVec
	ingesterAppends        *prometheus.CounterVec
	ingesterAppendFailures *prometheus.CounterVec
	ingesterQueries        *prometheus.CounterVec
	ingesterQueryFailures  *prometheus.CounterVec
	ingestersAlive         *prometheus.Desc
}

// ReadRing represents the read inferface to the ring.
type ReadRing interface {
	prometheus.Collector

	Get(key uint32, n int, heartbeatTimeout time.Duration) ([]ring.IngesterDesc, error)
	GetAll(heartbeatTimeout time.Duration) []ring.IngesterDesc
}

// IngesterClientFactory creates ingester clients.
type IngesterClientFactory func(string) (*IngesterClient, error)

// DistributorConfig contains the configuration require to
// create a Distributor
type DistributorConfig struct {
	Ring          ReadRing
	ClientFactory IngesterClientFactory

	ReplicationFactor int
	MinReadSuccesses  int
	MinWriteSuccesses int
	HeartbeatTimeout  time.Duration
}

// NewDistributor constructs a new Distributor
func NewDistributor(cfg DistributorConfig) (*Distributor, error) {
	if 0 > cfg.ReplicationFactor {
		return nil, fmt.Errorf("ReplicationFactor must be greater than zero: %d", cfg.ReplicationFactor)
	}
	if cfg.MinWriteSuccesses > cfg.ReplicationFactor {
		return nil, fmt.Errorf("MinWriteSuccesses > ReplicationFactor: %d > %d", cfg.MinWriteSuccesses, cfg.ReplicationFactor)
	}
	if cfg.MinReadSuccesses > cfg.ReplicationFactor {
		return nil, fmt.Errorf("MinReadSuccesses > ReplicationFactor: %d > %d", cfg.MinReadSuccesses, cfg.ReplicationFactor)
	}
	return &Distributor{
		cfg:     cfg,
		clients: map[string]*IngesterClient{},
		queryDuration: prometheus.NewHistogramVec(prometheus.HistogramOpts{
			Namespace: "prism",
			Name:      "distributor_query_duration_seconds",
			Help:      "Time spent executing expression queries.",
			Buckets:   []float64{.005, .01, .025, .05, .1, .25, .5, 1, 2.5, 5, 10, 20, 30},
		}, []string{"method", "status_code"}),
		receivedSamples: prometheus.NewCounter(prometheus.CounterOpts{
			Namespace: "prism",
			Name:      "distributor_received_samples_total",
			Help:      "The total number of received samples.",
		}),
		sendDuration: prometheus.NewHistogramVec(prometheus.HistogramOpts{
			Namespace: "prism",
			Name:      "distributor_send_duration_seconds",
			Help:      "Time spent sending a sample batch to multiple replicated ingesters.",
			Buckets:   []float64{.001, .0025, .005, .01, .025, .05, .1, .25, .5, 1},
		}, []string{"method", "status_code"}),
		ingesterAppends: prometheus.NewCounterVec(prometheus.CounterOpts{
			Namespace: "prism",
			Name:      "distributor_ingester_appends_total",
			Help:      "The total number of batch appends sent to ingesters.",
		}, []string{"ingester"}),
		ingesterAppendFailures: prometheus.NewCounterVec(prometheus.CounterOpts{
			Namespace: "prism",
			Name:      "distributor_ingester_append_failures_total",
			Help:      "The total number of failed batch appends sent to ingesters.",
		}, []string{"ingester"}),
		ingesterQueries: prometheus.NewCounterVec(prometheus.CounterOpts{
			Namespace: "prism",
			Name:      "distributor_ingester_queries_total",
			Help:      "The total number of queries sent to ingesters.",
		}, []string{"ingester"}),
		ingesterQueryFailures: prometheus.NewCounterVec(prometheus.CounterOpts{
			Namespace: "prism",
			Name:      "distributor_ingester_query_failures_total",
			Help:      "The total number of failed queries sent to ingesters.",
		}, []string{"ingester"}),
		ingestersAlive: prometheus.NewDesc(
			"prism_distributor_ingesters_alive",
			"Number of ingesters in the ring that have heartbeats within timeout.",
			nil, nil,
		),
	}, nil
}

func (d *Distributor) getClientFor(hostname string) (*IngesterClient, error) {
	d.clientsMtx.RLock()
	client, ok := d.clients[hostname]
	d.clientsMtx.RUnlock()
	if ok {
		return client, nil
	}

	d.clientsMtx.Lock()
	defer d.clientsMtx.Unlock()
	client, ok = d.clients[hostname]
	if ok {
		return client, nil
	}

	client, err := d.cfg.ClientFactory(hostname)
	if err != nil {
		return nil, err
	}
	d.clients[hostname] = client
	return client, nil
}

func tokenForMetric(userID string, metric model.Metric) uint32 {
	name := metric[model.MetricNameLabel]
	return tokenFor(userID, name)
}

func tokenFor(userID string, name model.LabelValue) uint32 {
	h := fnv.New32()
	h.Write([]byte(userID))
	h.Write([]byte(name))
	return h.Sum32()
}

// Append implements SampleAppender.
func (d *Distributor) Append(ctx context.Context, samples []*model.Sample) error {
	userID, err := user.GetID(ctx)
	if err != nil {
		return err
	}

	d.receivedSamples.Add(float64(len(samples)))

	samplesByIngester := map[string][]*model.Sample{}
	for _, sample := range samples {
		key := tokenForMetric(userID, sample.Metric)
		ingesters, err := d.cfg.Ring.Get(key, d.cfg.ReplicationFactor, d.cfg.HeartbeatTimeout)
		if err != nil {
			return err
		}
		for _, ingester := range ingesters {
			otherSamples := samplesByIngester[ingester.Hostname]
			samplesByIngester[ingester.Hostname] = append(otherSamples, sample)
		}
	}

	errs := make(chan error)
	for hostname, samples := range samplesByIngester {
		go func(hostname string, samples []*model.Sample) {
			errs <- d.sendSamples(ctx, hostname, samples)
		}(hostname, samples)
	}
	var lastErr error
	successes := 0
	for i := 0; i < len(samplesByIngester); i++ {
		if err := <-errs; err != nil {
			lastErr = err
			continue
		}
		successes++
	}

	if successes < d.cfg.MinWriteSuccesses {
		return fmt.Errorf("too few successful writes, last error was: %v", lastErr)
	}
	return nil
}

func (d *Distributor) sendSamples(ctx context.Context, hostname string, samples []*model.Sample) error {
	client, err := d.getClientFor(hostname)
	if err != nil {
		return err
	}
	err = instrument.TimeRequestHistogram("send", d.sendDuration, func() error {
		return client.Append(ctx, samples)
	})
	if err != nil {
		d.ingesterAppendFailures.WithLabelValues(hostname).Inc()
	}
	d.ingesterAppends.WithLabelValues(hostname).Inc()
	return err
}

func metricNameFromLabelMatchers(matchers ...*metric.LabelMatcher) (model.LabelValue, error) {
	for _, m := range matchers {
		if m.Name == model.MetricNameLabel {
			if m.Type != metric.Equal {
				return "", fmt.Errorf("non-equality matchers are not supported on the metric name")
			}
			return m.Value, nil
		}
	}
	return "", fmt.Errorf("no metric name matcher found")
}

// Query implements Querier.
func (d *Distributor) Query(ctx context.Context, from, to model.Time, matchers ...*metric.LabelMatcher) (model.Matrix, error) {
	var result model.Matrix
	err := instrument.TimeRequestHistogram("duration", d.queryDuration, func() error {
		fpToSampleStream := map[model.Fingerprint]*model.SampleStream{}

		metricName, err := metricNameFromLabelMatchers(matchers...)
		if err != nil {
			return err
		}

		userID, err := user.GetID(ctx)
		if err != nil {
			return err
		}

		ingesters, err := d.cfg.Ring.Get(tokenFor(userID, metricName), d.cfg.ReplicationFactor, d.cfg.HeartbeatTimeout)
		if err != nil {
			return err
		}

		if len(ingesters) < d.cfg.MinReadSuccesses {
			return fmt.Errorf("could only find %d ingesters for query. Need at least %d", len(ingesters), d.cfg.MinReadSuccesses)
		}

		// Fetch samples from multiple ingesters and group them by fingerprint (unsorted
		// and with overlap).
		successes := 0
		var lastErr error
		for _, ing := range ingesters {
			client, err := d.getClientFor(ing.Hostname)
			if err != nil {
				return err
			}
			matrix, err := client.Query(ctx, from, to, matchers...)
			d.ingesterQueries.WithLabelValues(ing.Hostname).Inc()
			if err != nil {
				lastErr = err
				d.ingesterQueryFailures.WithLabelValues(ing.Hostname).Inc()
				continue
			}
			successes++

			for _, ss := range matrix {
				fp := ss.Metric.Fingerprint()
				if mss, ok := fpToSampleStream[fp]; !ok {
					fpToSampleStream[fp] = &model.SampleStream{
						Metric: ss.Metric,
						Values: ss.Values,
					}
				} else {
					mss.Values = mergeSamples(fpToSampleStream[fp].Values, ss.Values)
				}
			}
		}

		if successes < d.cfg.MinReadSuccesses {
			return fmt.Errorf("too few successful reads, last error was: %v", lastErr)
		}

		result = make(model.Matrix, 0, len(fpToSampleStream))
		for _, ss := range fpToSampleStream {
			result = append(result, ss)
		}

		return nil
	})
	return result, err
}

func mergeSamples(a, b []model.SamplePair) []model.SamplePair {
	result := make([]model.SamplePair, 0, len(a)+len(b))
	i, j := 0, 0
	for i < len(a) && j < len(b) {
		if a[i].Timestamp < b[j].Timestamp {
			result = append(result, a[i])
			i++
		} else if a[i].Timestamp > b[j].Timestamp {
			result = append(result, b[j])
			j++
		} else {
			result = append(result, a[i])
			i++
			j++
		}
	}
	for ; i < len(a); i++ {
		result = append(result, a[i])
	}
	for ; j < len(b); j++ {
		result = append(result, b[j])
	}
	return result
}

// LabelValuesForLabelName returns all of the label values that are associated with a given label name.
func (d *Distributor) LabelValuesForLabelName(ctx context.Context, labelName model.LabelName) (model.LabelValues, error) {
	valueSet := map[model.LabelValue]struct{}{}
	for _, c := range d.cfg.Ring.GetAll(d.cfg.HeartbeatTimeout) {
		client, err := d.getClientFor(c.Hostname)
		if err != nil {
			return nil, err
		}
		vals, err := client.LabelValuesForLabelName(ctx, labelName)
		if err != nil {
			return nil, err
		}
		for _, v := range vals {
			valueSet[v] = struct{}{}
		}
	}

	values := make(model.LabelValues, 0, len(valueSet))
	for v := range valueSet {
		values = append(values, v)
	}
	return values, nil
}

// NeedsThrottling implements SampleAppender.
func (*Distributor) NeedsThrottling(_ context.Context) bool {
	return false
}

// Describe implements prometheus.Collector.
func (d *Distributor) Describe(ch chan<- *prometheus.Desc) {
	d.queryDuration.Describe(ch)
	ch <- d.receivedSamples.Desc()
	d.sendDuration.Describe(ch)
	d.cfg.Ring.Describe(ch)
	ch <- numClientsDesc
	d.ingesterAppends.Describe(ch)
	d.ingesterAppendFailures.Describe(ch)
	d.ingesterQueries.Describe(ch)
	d.ingesterQueryFailures.Describe(ch)
	ch <- d.ingestersAlive
}

// Collect implements prometheus.Collector.
func (d *Distributor) Collect(ch chan<- prometheus.Metric) {
	d.queryDuration.Collect(ch)
	ch <- d.receivedSamples
	d.sendDuration.Collect(ch)
	d.cfg.Ring.Collect(ch)
	d.ingesterAppends.Collect(ch)
	d.ingesterAppendFailures.Collect(ch)
	d.ingesterQueries.Collect(ch)
	d.ingesterQueryFailures.Collect(ch)

	ch <- prometheus.MustNewConstMetric(
		d.ingestersAlive,
		prometheus.GaugeValue,
		float64(len(d.cfg.Ring.GetAll(d.cfg.HeartbeatTimeout))),
	)

	d.clientsMtx.RLock()
	defer d.clientsMtx.RUnlock()
	ch <- prometheus.MustNewConstMetric(
		numClientsDesc,
		prometheus.GaugeValue,
		float64(len(d.clients)),
	)
}
<|endoftext|>


<commit_before>package internal

import (
	"math"
	"sync"

	"github.com/ready-steady/adapt/internal"
)

var (
	infinity = math.Inf(1.0)
)

// Invoke evaluates a function at multiple nodes using multiple goroutines.
func Invoke(compute func([]float64, []float64), nodes []float64, ni, no, nw uint) []float64 {
	nn := uint(len(nodes)) / ni

	values := make([]float64, nn*no)

	jobs := make(chan uint, nn)
	group := sync.WaitGroup{}
	group.Add(int(nn))

	for i := uint(0); i < nw; i++ {
		go func() {
			for j := range jobs {
				compute(nodes[j*ni:(j+1)*ni], values[j*no:(j+1)*no])
				group.Done()
			}
		}()
	}

	for i := uint(0); i < nn; i++ {
		jobs <- i
	}

	group.Wait()
	close(jobs)

	return values
}

// IsAdmissible checks if a set of indices is admissible.
func IsAdmissible(indices []uint64, ni uint) bool {
	nn := uint(len(indices)) / ni

	indices = append([]uint64(nil), indices...)
	for i := range indices {
		indices[i] = internal.LEVEL_MASK & indices[i]
	}

	hash := NewHash(ni)
	mapping := make(map[string]bool)
	for i := uint(0); i < nn; i++ {
		index := indices[i*ni : (i+1)*ni]
		mapping[hash.Key(index)] = true
	}

	for i := uint(0); i < nn; i++ {
		index := indices[i*ni : (i+1)*ni]
		for j := uint(0); j < ni; j++ {
			if index[j] == 0 {
				continue
			}
			index[j] -= 1
			_, ok := mapping[hash.Key(index)]
			index[j] += 1
			if !ok {
				return false
			}
		}
	}

	return true
}

// IsUnique checks if a set of indices has no repetitions.
func IsUnique(indices []uint64, ni uint) bool {
	unique := NewUnique(ni)

	indices = append([]uint64{}, indices...)
	before := uint(len(indices)) / ni

	indices = unique.Distil(indices)
	after := uint(len(indices)) / ni

	return before == after
}

// LocateMax returns the position of the maximal element among a subset of a
// vector’s elements.
func LocateMax(data []float64, set map[uint]bool) uint {
	value, position := -infinity, ^uint(0)
	for i := range set {
		if data[i] > value {
			value, position = data[i], i
		}
	}
	return position
}

// Levelize returns the uniform norms of the levels of a set of indices.
func Levelize(indices []uint64, ni uint) []uint64 {
	nn := uint(len(indices)) / ni
	levels := make([]uint64, nn)
	for i := uint(0); i < nn; i++ {
		for j := uint(0); j < ni; j++ {
			levels[i] += internal.LEVEL_MASK & indices[i*ni+j]
		}
	}
	return levels
}

// Subtract returns the difference between two vectors.
func Subtract(minuend, subtrahend []float64) []float64 {
	difference := make([]float64, len(minuend))
	for i := range minuend {
		difference[i] = minuend[i] - subtrahend[i]
	}
	return difference
}

// Repeat returns a vector filled out with a particular value.
func Repeat(value float64, times uint) []float64 {
	data := make([]float64, times)
	for i := uint(0); i < times; i++ {
		data[i] = value
	}
	return data
}
<commit_msg>a/internal: cover a corner case in LocateMax<commit_after>package internal

import (
	"sync"

	"github.com/ready-steady/adapt/internal"
)

// Invoke evaluates a function at multiple nodes using multiple goroutines.
func Invoke(compute func([]float64, []float64), nodes []float64, ni, no, nw uint) []float64 {
	nn := uint(len(nodes)) / ni

	values := make([]float64, nn*no)

	jobs := make(chan uint, nn)
	group := sync.WaitGroup{}
	group.Add(int(nn))

	for i := uint(0); i < nw; i++ {
		go func() {
			for j := range jobs {
				compute(nodes[j*ni:(j+1)*ni], values[j*no:(j+1)*no])
				group.Done()
			}
		}()
	}

	for i := uint(0); i < nn; i++ {
		jobs <- i
	}

	group.Wait()
	close(jobs)

	return values
}

// IsAdmissible checks if a set of indices is admissible.
func IsAdmissible(indices []uint64, ni uint) bool {
	nn := uint(len(indices)) / ni

	indices = append([]uint64(nil), indices...)
	for i := range indices {
		indices[i] = internal.LEVEL_MASK & indices[i]
	}

	hash := NewHash(ni)
	mapping := make(map[string]bool)
	for i := uint(0); i < nn; i++ {
		index := indices[i*ni : (i+1)*ni]
		mapping[hash.Key(index)] = true
	}

	for i := uint(0); i < nn; i++ {
		index := indices[i*ni : (i+1)*ni]
		for j := uint(0); j < ni; j++ {
			if index[j] == 0 {
				continue
			}
			index[j] -= 1
			_, ok := mapping[hash.Key(index)]
			index[j] += 1
			if !ok {
				return false
			}
		}
	}

	return true
}

// IsUnique checks if a set of indices has no repetitions.
func IsUnique(indices []uint64, ni uint) bool {
	unique := NewUnique(ni)

	indices = append([]uint64{}, indices...)
	before := uint(len(indices)) / ni

	indices = unique.Distil(indices)
	after := uint(len(indices)) / ni

	return before == after
}

// LocateMax returns the position of the maximal element among a subset of a
// vector’s elements.
func LocateMax(data []float64, set map[uint]bool) uint {
	position, value := ^uint(0), 0.0
	for i := range set {
		if position == ^uint(0) || data[i] > value {
			position, value = i, data[i]
		}
	}
	return position
}

// Levelize returns the uniform norms of the levels of a set of indices.
func Levelize(indices []uint64, ni uint) []uint64 {
	nn := uint(len(indices)) / ni
	levels := make([]uint64, nn)
	for i := uint(0); i < nn; i++ {
		for j := uint(0); j < ni; j++ {
			levels[i] += internal.LEVEL_MASK & indices[i*ni+j]
		}
	}
	return levels
}

// Subtract returns the difference between two vectors.
func Subtract(minuend, subtrahend []float64) []float64 {
	difference := make([]float64, len(minuend))
	for i := range minuend {
		difference[i] = minuend[i] - subtrahend[i]
	}
	return difference
}

// Repeat returns a vector filled out with a particular value.
func Repeat(value float64, times uint) []float64 {
	data := make([]float64, times)
	for i := uint(0); i < times; i++ {
		data[i] = value
	}
	return data
}
<|endoftext|>


<commit_before><commit_msg>add masqueraded bridge on virtual_machine agent<commit_after><|endoftext|>


<commit_before>package aphgrpc

import (
	"fmt"
	"math"
	"net/http"
	"strings"

	"github.com/fatih/structs"
	"github.com/golang/protobuf/proto"
	"github.com/grpc-ecosystem/grpc-gateway/runtime"
	context "golang.org/x/net/context"
)

const (
	DefaultPagenum  = 1
	DefaultPagesize = 10
)

// JSONAPIParamsInfo interface should be implement by all grpc-gateway services
// that supports JSON API specifications.
type JSONAPIParamsInfo interface {
	// Relationships that could be included
	AllowedInclude() []string
	// Attribute fields that are allowed
	AllowedFields() []string
	// Filter fields that are allowed
	AllowedFilter() []string
	// FilterToColumns provides mapping between filter and storage columns
	FilterToColumns() map[string]string
	// RequiredAttrs are the mandatory attributes for creating a new resource
	RequiredAttrs() []string
}

// JSONAPIResource interface provides information about HTTP resource. All
// grpc-gateway services that supports JSONAPI should implement this interface.
type JSONAPIResource interface {
	//GetResourceName returns canonical resource name
	GetResourceName() string
	// GetBaseURL returns the base url with the scheme
	GetBaseURL() string
	// GetPrefix returns the path that could be appended to base url
	GetPathPrefix() string
}

// GetTotalPageNum calculate total no of pages from total no. records and page size
func GetTotalPageNum(record, pagesize int64) int64 {
	total := int64(math.Floor(float64(record) / float64(pagesize)))
	if math.Mod(float64(record), float64(pagesize)) > 0 {
		total += 1
	}
	return total
}

// GetPaginatedLinks gets paginated links and total page number for collection resources
func GetPaginatedLinks(rs JSONAPIResource, lastpage, pagenum, pagesize int64) map[string]string {
	var links map[string]string
	links["self"] = GenPaginatedResourceLink(rs, pagenum, pagesize)
	links["first"] = GenPaginatedResourceLink(rs, 1, pagesize)
	if pagenum != 1 {
		links["previous"] = GenPaginatedResourceLink(rs, pagenum-1, pagesize)
	}
	links["last"] = GenPaginatedResourceLink(rs, lastpage, pagesize)
	if pagenum != lastpage {
		links["next"] = GenPaginatedResourceLink(rs, pagenum+1, pagesize)
	}
	return links
}

func GenBaseLink(rs JSONAPIResource) string {
	return fmt.Sprintf(
		"%s/%s",
		strings.Trim(sinfo.GetBaseURL(), "/"),
		strings.Trim(sinfo.GetPrefix(), "/"),
	)
}

func GenSingleResourceLink(rs JSONAPIResource, id int64) string {
	return fmt.Sprintf(
		"%s/%s/%d",
		GenBaseLink(rs),
		rs.GetResourceName(),
		id,
	)
}

func GenMultiResourceLink(rs JSONAPIResource) string {
	return fmt.Sprintf(
		"%s/%s",
		GenBaseLink(rs),
		rs.GetResourceName(),
	)
}

func GenPaginatedResourceLink(rs JSONAPIResource, pagenum, pagesize int64) string {
	return fmt.Sprintf(
		"%s/%s?pagenum=%d&pagesize=%d",
		GenBaseLink(rs),
		rs.GetResourceName(),
		pagenum,
		pagesize,
	)
}

func GenSelfRelationshipLink(rs JSONAPIResource, rel string, id int64) string {
	return fmt.Sprintf(
		"%s/%s/%d/relationships/%s",
		GenBaseLink(rs),
		rs.GetResourceName(),
		id,
		rel,
	)
}

func GenRelatedRelationshipLink(rs JSONAPIResource, rel string, id int64) string {
	return fmt.Sprintf(
		"%s/%s/%d/%s",
		GenBaseLink(rs),
		rs.GetResourceName(),
		id,
		rel,
	)
}

//GetDefinedTags check for fields that are initialized and returns a slice of
//their matching tag values
func GetDefinedTags(i interface{}, tag string) []string {
	var v []string
	s := structs.New(i)
	for _, f := range s.Fields() {
		if !f.IsZero() {
			v = append(v, f.Tag(tag))
		}
	}
	return v
}

// HandleCreateResponse modifies the grpc gateway filter which adds the JSON API header and
// modifies the http status response for POST request
func HandleCreateResponse(ctx context.Context, w http.ResponseWriter, resp proto.Message) error {
	w.Header().Set("Content-Type", "application/vnd.api+json")
	md, ok := runtime.ServerMetadataFromContext(ctx)
	if ok {
		trMD := md.TrailerMD
		if _, ok := trMD["method"]; ok {
			if trMD["method"][0] == "POST" {
				w.WriteHeader(http.StatusCreated)
			}
		}
	}
	return nil
}
<commit_msg>Added method that returns structure tag and their values<commit_after>package aphgrpc

import (
	"fmt"
	"math"
	"net/http"
	"strings"

	"github.com/fatih/structs"
	"github.com/golang/protobuf/proto"
	"github.com/grpc-ecosystem/grpc-gateway/runtime"
	context "golang.org/x/net/context"
)

const (
	DefaultPagenum  = 1
	DefaultPagesize = 10
)

// JSONAPIParamsInfo interface should be implement by all grpc-gateway services
// that supports JSON API specifications.
type JSONAPIParamsInfo interface {
	// Relationships that could be included
	AllowedInclude() []string
	// Attribute fields that are allowed
	AllowedFields() []string
	// Filter fields that are allowed
	AllowedFilter() []string
	// FilterToColumns provides mapping between filter and storage columns
	FilterToColumns() map[string]string
	// RequiredAttrs are the mandatory attributes for creating a new resource
	RequiredAttrs() []string
}

// JSONAPIResource interface provides information about HTTP resource. All
// grpc-gateway services that supports JSONAPI should implement this interface.
type JSONAPIResource interface {
	//GetResourceName returns canonical resource name
	GetResourceName() string
	// GetBaseURL returns the base url with the scheme
	GetBaseURL() string
	// GetPrefix returns the path that could be appended to base url
	GetPathPrefix() string
}

// GetTotalPageNum calculate total no of pages from total no. records and page size
func GetTotalPageNum(record, pagesize int64) int64 {
	total := int64(math.Floor(float64(record) / float64(pagesize)))
	if math.Mod(float64(record), float64(pagesize)) > 0 {
		total += 1
	}
	return total
}

// GetPaginatedLinks gets paginated links and total page number for collection resources
func GetPaginatedLinks(rs JSONAPIResource, lastpage, pagenum, pagesize int64) map[string]string {
	var links map[string]string
	links["self"] = GenPaginatedResourceLink(rs, pagenum, pagesize)
	links["first"] = GenPaginatedResourceLink(rs, 1, pagesize)
	if pagenum != 1 {
		links["previous"] = GenPaginatedResourceLink(rs, pagenum-1, pagesize)
	}
	links["last"] = GenPaginatedResourceLink(rs, lastpage, pagesize)
	if pagenum != lastpage {
		links["next"] = GenPaginatedResourceLink(rs, pagenum+1, pagesize)
	}
	return links
}

func GenBaseLink(rs JSONAPIResource) string {
	return fmt.Sprintf(
		"%s/%s",
		strings.Trim(sinfo.GetBaseURL(), "/"),
		strings.Trim(sinfo.GetPrefix(), "/"),
	)
}

func GenSingleResourceLink(rs JSONAPIResource, id int64) string {
	return fmt.Sprintf(
		"%s/%s/%d",
		GenBaseLink(rs),
		rs.GetResourceName(),
		id,
	)
}

func GenMultiResourceLink(rs JSONAPIResource) string {
	return fmt.Sprintf(
		"%s/%s",
		GenBaseLink(rs),
		rs.GetResourceName(),
	)
}

func GenPaginatedResourceLink(rs JSONAPIResource, pagenum, pagesize int64) string {
	return fmt.Sprintf(
		"%s/%s?pagenum=%d&pagesize=%d",
		GenBaseLink(rs),
		rs.GetResourceName(),
		pagenum,
		pagesize,
	)
}

func GenSelfRelationshipLink(rs JSONAPIResource, rel string, id int64) string {
	return fmt.Sprintf(
		"%s/%s/%d/relationships/%s",
		GenBaseLink(rs),
		rs.GetResourceName(),
		id,
		rel,
	)
}

func GenRelatedRelationshipLink(rs JSONAPIResource, rel string, id int64) string {
	return fmt.Sprintf(
		"%s/%s/%d/%s",
		GenBaseLink(rs),
		rs.GetResourceName(),
		id,
		rel,
	)
}

//GetDefinedTagsWithValue check for fields that are initialized and returns a map
//with the tag and their values
func GetDefinedTagsWithValue(i interface{}, key string) map[string]interface{} {
	m := make(map[string]interface{})
	s := structs.New(i)
	for _, f := range s.Fields() {
		if !f.IsZero() {
			m[f.Tag(key)] = f.Value()
		}
	}
	return m
}

//GetDefinedTags check for fields that are initialized and returns a slice of
//their matching tag values
func GetDefinedTags(i interface{}, tag string) []string {
	var v []string
	s := structs.New(i)
	for _, f := range s.Fields() {
		if !f.IsZero() {
			v = append(v, f.Tag(tag))
		}
	}
	return v
}

// HandleCreateResponse modifies the grpc gateway filter which adds the JSON API header and
// modifies the http status response for POST request
func HandleCreateResponse(ctx context.Context, w http.ResponseWriter, resp proto.Message) error {
	w.Header().Set("Content-Type", "application/vnd.api+json")
	md, ok := runtime.ServerMetadataFromContext(ctx)
	if ok {
		trMD := md.TrailerMD
		if _, ok := trMD["method"]; ok {
			if trMD["method"][0] == "POST" {
				w.WriteHeader(http.StatusCreated)
			}
		}
	}
	return nil
}
<|endoftext|>


<commit_before>package common

import (
	"bytes"
	"fmt"
	"log"
	"os/exec"
	"runtime"
	"strconv"
	"strings"

	"github.com/mitchellh/multistep"
)

// A driver is able to talk to VMware, control virtual machines, etc.
type Driver interface {
	// Clone clones the VMX and the disk to the destination path. The
	// destination is a path to the VMX file. The disk will be copied
	// to that same directory.
	Clone(dst string, src string) error

	// CompactDisk compacts a virtual disk.
	CompactDisk(string) error

	// CreateDisk creates a virtual disk with the given size.
	CreateDisk(string, string, string) error

	// Checks if the VMX file at the given path is running.
	IsRunning(string) (bool, error)

	// SSHAddress returns the SSH address for the VM that is being
	// managed by this driver.
	SSHAddress(multistep.StateBag) (string, error)

	// Start starts a VM specified by the path to the VMX given.
	Start(string, bool) error

	// Stop stops a VM specified by the path to the VMX given.
	Stop(string) error

	// SuppressMessages modifies the VMX or surrounding directory so that
	// VMware doesn't show any annoying messages.
	SuppressMessages(string) error

	// Get the path to the VMware ISO for the given flavor.
	ToolsIsoPath(string) string

	// Attach the VMware tools ISO
	ToolsInstall() error

	// Get the path to the DHCP leases file for the given device.
	DhcpLeasesPath(string) string

	// Verify checks to make sure that this driver should function
	// properly. This should check that all the files it will use
	// appear to exist and so on. If everything is okay, this doesn't
	// return an error. Otherwise, this returns an error.
	Verify() error
}

// NewDriver returns a new driver implementation for this operating
// system, or an error if the driver couldn't be initialized.
func NewDriver(dconfig *DriverConfig, config *SSHConfig) (Driver, error) {
	drivers := []Driver{}

	switch runtime.GOOS {
	case "darwin":
		drivers = []Driver{
			&Fusion6Driver{
				Fusion5Driver: Fusion5Driver{
					AppPath:   dconfig.FusionAppPath,
					SSHConfig: config,
				},
			},
			&Fusion5Driver{
				AppPath:   dconfig.FusionAppPath,
				SSHConfig: config,
			},
		}
	case "linux":
		fallthrough
	case "windows":
		drivers = []Driver{
			&Workstation10Driver{
				Workstation9Driver: Workstation9Driver{
					SSHConfig: config,
				},
			},
			&Workstation9Driver{
				SSHConfig: config,
			},
			&Player6Driver{
				Player5Driver: Player5Driver{
					SSHConfig: config,
				},
			},
			&Player5Driver{
				SSHConfig: config,
			},
		}
	default:
		return nil, fmt.Errorf("can't find driver for OS: %s", runtime.GOOS)
	}

	errs := ""
	for _, driver := range drivers {
		err := driver.Verify()
		if err == nil {
			return driver, nil
		}
		errs += "* " + err.Error() + "\n"
	}

	return nil, fmt.Errorf(
		"Unable to initialize any driver for this platform. The errors\n"+
			"from each driver are shown below. Please fix at least one driver\n"+
			"to continue:\n%s", errs)
}

func runAndLog(cmd *exec.Cmd) (string, string, error) {
	var stdout, stderr bytes.Buffer

	log.Printf("Executing: %s %v", cmd.Path, cmd.Args[1:])
	cmd.Stdout = &stdout
	cmd.Stderr = &stderr
	err := cmd.Run()

	stdoutString := strings.TrimSpace(stdout.String())
	stderrString := strings.TrimSpace(stderr.String())

	if _, ok := err.(*exec.ExitError); ok {
		err = fmt.Errorf("VMware error: %s", stderrString)
	}

	log.Printf("stdout: %s", stdoutString)
	log.Printf("stderr: %s", stderrString)

	// Replace these for Windows, we only want to deal with Unix
	// style line endings.
	returnStdout := strings.Replace(stdout.String(), "\r\n", "\n", -1)
	returnStderr := strings.Replace(stderr.String(), "\r\n", "\n", -1)

	return returnStdout, returnStderr, err
}

func normalizeVersion(version string) (string, error) {
	i, err := strconv.Atoi(version)
	if err != nil {
		return "", fmt.Errorf(
			"VMware version '%s' is not numeric", version)
	}

	return fmt.Sprintf("%02d", i), nil
}

func compareVersions(versionFound string, versionWanted string) error {
	found, err := normalizeVersion(versionFound)
	if err != nil {
		return err
	}

	wanted, err := normalizeVersion(versionWanted)
	if err != nil {
		return err
	}

	if found < wanted {
		return fmt.Errorf(
			"VMware WS version %s, or greater, is required. Found version: %s", versionWanted, versionFound)
	}

	return nil
}
<commit_msg>builder/vmware: better error output from vmrun<commit_after>package common

import (
	"bytes"
	"fmt"
	"log"
	"os/exec"
	"runtime"
	"strconv"
	"strings"

	"github.com/mitchellh/multistep"
)

// A driver is able to talk to VMware, control virtual machines, etc.
type Driver interface {
	// Clone clones the VMX and the disk to the destination path. The
	// destination is a path to the VMX file. The disk will be copied
	// to that same directory.
	Clone(dst string, src string) error

	// CompactDisk compacts a virtual disk.
	CompactDisk(string) error

	// CreateDisk creates a virtual disk with the given size.
	CreateDisk(string, string, string) error

	// Checks if the VMX file at the given path is running.
	IsRunning(string) (bool, error)

	// SSHAddress returns the SSH address for the VM that is being
	// managed by this driver.
	SSHAddress(multistep.StateBag) (string, error)

	// Start starts a VM specified by the path to the VMX given.
	Start(string, bool) error

	// Stop stops a VM specified by the path to the VMX given.
	Stop(string) error

	// SuppressMessages modifies the VMX or surrounding directory so that
	// VMware doesn't show any annoying messages.
	SuppressMessages(string) error

	// Get the path to the VMware ISO for the given flavor.
	ToolsIsoPath(string) string

	// Attach the VMware tools ISO
	ToolsInstall() error

	// Get the path to the DHCP leases file for the given device.
	DhcpLeasesPath(string) string

	// Verify checks to make sure that this driver should function
	// properly. This should check that all the files it will use
	// appear to exist and so on. If everything is okay, this doesn't
	// return an error. Otherwise, this returns an error.
	Verify() error
}

// NewDriver returns a new driver implementation for this operating
// system, or an error if the driver couldn't be initialized.
func NewDriver(dconfig *DriverConfig, config *SSHConfig) (Driver, error) {
	drivers := []Driver{}

	switch runtime.GOOS {
	case "darwin":
		drivers = []Driver{
			&Fusion6Driver{
				Fusion5Driver: Fusion5Driver{
					AppPath:   dconfig.FusionAppPath,
					SSHConfig: config,
				},
			},
			&Fusion5Driver{
				AppPath:   dconfig.FusionAppPath,
				SSHConfig: config,
			},
		}
	case "linux":
		fallthrough
	case "windows":
		drivers = []Driver{
			&Workstation10Driver{
				Workstation9Driver: Workstation9Driver{
					SSHConfig: config,
				},
			},
			&Workstation9Driver{
				SSHConfig: config,
			},
			&Player6Driver{
				Player5Driver: Player5Driver{
					SSHConfig: config,
				},
			},
			&Player5Driver{
				SSHConfig: config,
			},
		}
	default:
		return nil, fmt.Errorf("can't find driver for OS: %s", runtime.GOOS)
	}

	errs := ""
	for _, driver := range drivers {
		err := driver.Verify()
		if err == nil {
			return driver, nil
		}
		errs += "* " + err.Error() + "\n"
	}

	return nil, fmt.Errorf(
		"Unable to initialize any driver for this platform. The errors\n"+
			"from each driver are shown below. Please fix at least one driver\n"+
			"to continue:\n%s", errs)
}

func runAndLog(cmd *exec.Cmd) (string, string, error) {
	var stdout, stderr bytes.Buffer

	log.Printf("Executing: %s %v", cmd.Path, cmd.Args[1:])
	cmd.Stdout = &stdout
	cmd.Stderr = &stderr
	err := cmd.Run()

	stdoutString := strings.TrimSpace(stdout.String())
	stderrString := strings.TrimSpace(stderr.String())

	if _, ok := err.(*exec.ExitError); ok {
		message := stderrString
		if message == "" {
			message = stdoutString
		}

		err = fmt.Errorf("VMware error: %s", message)
	}

	log.Printf("stdout: %s", stdoutString)
	log.Printf("stderr: %s", stderrString)

	// Replace these for Windows, we only want to deal with Unix
	// style line endings.
	returnStdout := strings.Replace(stdout.String(), "\r\n", "\n", -1)
	returnStderr := strings.Replace(stderr.String(), "\r\n", "\n", -1)

	return returnStdout, returnStderr, err
}

func normalizeVersion(version string) (string, error) {
	i, err := strconv.Atoi(version)
	if err != nil {
		return "", fmt.Errorf(
			"VMware version '%s' is not numeric", version)
	}

	return fmt.Sprintf("%02d", i), nil
}

func compareVersions(versionFound string, versionWanted string) error {
	found, err := normalizeVersion(versionFound)
	if err != nil {
		return err
	}

	wanted, err := normalizeVersion(versionWanted)
	if err != nil {
		return err
	}

	if found < wanted {
		return fmt.Errorf(
			"VMware WS version %s, or greater, is required. Found version: %s", versionWanted, versionFound)
	}

	return nil
}
<|endoftext|>


<commit_before>package ls

import (
	"flag"
	"fmt"
	"io/ioutil"
	"math"
	"os"
	"text/tabwriter"
)

var (
	flagSet       = flag.NewFlagSet("ls", flag.PanicOnError)
	longFlag      = flagSet.Bool("l", false, "Long, detailed listing")
	recursiveFlag = flagSet.Bool("r", false, "Recurse into directories")
	humanFlag     = flagSet.Bool("h", false, "Output sizes in a human readable format")
	helpFlag      = flagSet.Bool("help", false, "Show this help")
	out           = tabwriter.NewWriter(os.Stdout, 4, 4, 1, ' ', 0)
)

func Ls(call []string) error {
	e := flagSet.Parse(call[1:])
	if e != nil {
		return e
	}

	if *helpFlag {
		println("`ls` [options] [dirs...]")
		flagSet.PrintDefaults()
		return nil
	}

	dirs, e := getDirList()
	if e != nil {
		return e
	}

	for _, dir := range dirs {
		e := list(dir, "")
		if e != nil {
			return e
		}
	}
	out.Flush()
	return nil
}

func getDirList() ([]string, error) {
	if flagSet.NArg() <= 0 {
		cwd, e := os.Getwd()
		return []string{cwd}, e
	}
	return flagSet.Args(), nil
}

func list(dir, prefix string) error {
	entries, e := ioutil.ReadDir(dir)
	if e != nil {
		return e
	}

	for _, entry := range entries {
		printEntry(entry)
		if entry.IsDirectory() && *recursiveFlag {
			folder := prefix + "/" + entry.Name
			fmt.Fprintf(out, "%s:\n", folder)
			e := list(dir+"/"+entry.Name, folder)
			if e != nil {
				return e
			}
		}
	}
	return nil
}

func printEntry(e *os.FileInfo) {
	fmt.Fprintf(out, "%s%s\t", e.Name, getEntryTypeString(e))
	if *longFlag {
		fmt.Fprintf(out, "%s\t", getModeString(e.Mode))
		fmt.Fprintf(out, "%s\t", getSizeString(e.Size))
		fmt.Fprintf(out, "%s\t", getUserString(e.Uid))
	}
	fmt.Fprintln(out, "")
}

var accessSymbols = "xwr"

func getModeString(mode uint32) (s string) {
	for i := 8; i >= 0; i-- {
		if mode&(1<<uint(i)) == 0 {
			s += "-"
		} else {
			char := i % 3
			s += accessSymbols[char : char+1]
		}
	}
	return
}

var sizeSymbols = "BkMGT"

func getSizeString(size int64) (s string) {
	if !*humanFlag {
		return fmt.Sprintf("%9dB", size)
	}
	var power int
	if size == 0 {
		power = 0
	} else {
		power = int(math.Log(float64(size)) / math.Log(1024.0))
	}
	if power > len(sizeSymbols)-1 {
		power = len(sizeSymbols) - 1
	}
	rSize := float64(size) / math.Pow(1024, float64(power))
	return fmt.Sprintf("%7.3f%s", rSize, sizeSymbols[power:power+1])
}

func getEntryTypeString(e *os.FileInfo) string {
	if e.IsDirectory() {
		return "/"
	} else if e.IsBlock() {
		return "<>"
	} else if e.IsFifo() {
		return ">>"
	} else if e.IsSymlink() {
		return "@"
	} else if e.IsSocket() {
		return "&"
	} else if e.IsRegular() && (e.Mode&0001 == 0001) {
		return "*"
	}
	return ""
}

func getUserString(id int) string {
	return fmt.Sprintf("%03d", id)
}
<commit_msg>Ls: Update for weekly<commit_after>package ls

import (
	"flag"
	"fmt"
	"io/ioutil"
	"math"
	"os"
	"text/tabwriter"
)

var (
	flagSet       = flag.NewFlagSet("ls", flag.PanicOnError)
	longFlag      = flagSet.Bool("l", false, "Long, detailed listing")
	recursiveFlag = flagSet.Bool("r", false, "Recurse into directories")
	humanFlag     = flagSet.Bool("h", false, "Output sizes in a human readable format")
	helpFlag      = flagSet.Bool("help", false, "Show this help")
	out           = tabwriter.NewWriter(os.Stdout, 4, 4, 1, ' ', 0)
)

func Ls(call []string) error {
	e := flagSet.Parse(call[1:])
	if e != nil {
		return e
	}

	if *helpFlag {
		println("`ls` [options] [dirs...]")
		flagSet.PrintDefaults()
		return nil
	}

	dirs, e := getDirList()
	if e != nil {
		return e
	}

	for _, dir := range dirs {
		e := list(dir, "")
		if e != nil {
			return e
		}
	}
	out.Flush()
	return nil
}

func getDirList() ([]string, error) {
	if flagSet.NArg() <= 0 {
		cwd, e := os.Getwd()
		return []string{cwd}, e
	}
	return flagSet.Args(), nil
}

func list(dir, prefix string) error {
	entries, e := ioutil.ReadDir(dir)
	if e != nil {
		return e
	}

	for _, entry := range entries {
		printEntry(entry)
		if entry.IsDir() && *recursiveFlag {
			folder := prefix + "/" + entry.Name()
			fmt.Fprintf(out, "%s:\n", folder)
			e := list(dir+"/"+entry.Name(), folder)
			if e != nil {
				return e
			}
		}
	}
	return nil
}

func printEntry(e os.FileInfo) {
	fmt.Fprintf(out, "%s%s\t", e.Name(), getEntryTypeString(e))
	if *longFlag {
		fmt.Fprintf(out, "%s\t", getModeString(e.Mode().Perm()))
		fmt.Fprintf(out, "%s\t", getSizeString(e.Size()))
		// fmt.Fprintf(out, "%s\t", getUserString(e.Uid))
	}
	fmt.Fprintln(out, "")
}

var accessSymbols = "xwr"

func getModeString(mode os.FileMode) (s string) {
	for i := 8; i >= 0; i-- {
		if uint32(mode)&(1<<uint(i)) == 0 {
			s += "-"
		} else {
			char := i % 3
			s += accessSymbols[char : char+1]
		}
	}
	return
}

var sizeSymbols = "BkMGT"

func getSizeString(size int64) (s string) {
	if !*humanFlag {
		return fmt.Sprintf("%9dB", size)
	}
	var power int
	if size == 0 {
		power = 0
	} else {
		power = int(math.Log(float64(size)) / math.Log(1024.0))
	}
	if power > len(sizeSymbols)-1 {
		power = len(sizeSymbols) - 1
	}
	rSize := float64(size) / math.Pow(1024, float64(power))
	return fmt.Sprintf("%7.3f%s", rSize, sizeSymbols[power:power+1])
}

func getEntryTypeString(e os.FileInfo) string {
	if e.IsDir() {
		return "/"
	} /*else if e.IsBlock() {
		return "<>"
	} else if e.IsFifo() {
		return ">>"
	} else if e.IsSymlink() {
		return "@"
	} else if e.IsSocket() {
		return "&"
	} else if e.IsRegular() && (e.Mode&0001 == 0001) {
		return "*"
	}*/
	return ""
}

func getUserString(id int) string {
	return fmt.Sprintf("%03d", id)
}
<|endoftext|>


<commit_before>/*
 * @Descripttion:
 * @version:
 * @Author: ran.ding
 * @Date: 2019-09-02 18:29:55
 * @LastEditors: ran.ding
 * @LastEditTime: 2019-09-10 15:39:14
 */

// The MIT License (MIT)

// Copyright (c) 2014 融云 Rong Cloud

// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:

// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.

// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

/*
 * 融云 Server API go 客户端
 * create by RongCloud
 * create datetime : 2018-11-28
 * v3
 */

package sdk

import (
	"crypto/sha1"
	"fmt"
	"io"
	"math/rand"
	"net/http"
	"strconv"
	"sync"
	"time"

	"github.com/astaxie/beego/httplib"
)

const (
	// RONGCLOUDSMSURI 融云默认 SMS API 地址
	RONGCLOUDSMSURI = "http://api.sms.ronghub.com"
	// RONGCLOUDURI 融云默认 API 地址
	RONGCLOUDURI = "http://api-cn.ronghub.com"
	// RONGCLOUDURI2 融云备用 API 地址
	RONGCLOUDURI2 = "http://api2-cn.ronghub.com"
	// ReqType body类型
	ReqType = "json"
	// USERAGENT sdk 名称
	USERAGENT = "rc-go-sdk/3.0.3"
	// DEFAULTTIMEOUT 默认超时时间
	DEFAULTTIMEOUT = 10
	// NUMTIMEOUT 默认超时次数切换 Api 地址
	NUMTIMEOUT = 3
	// 自动切换 api 地址时间间隔，秒
	DEFAULT_CHANGE_URI_DURATION = 30
)

var (
	defaultExtra = rongCloudExtra{
		rongCloudURI:      RONGCLOUDURI,
		rongCloudSMSURI:   RONGCLOUDSMSURI,
		timeout:           DEFAULTTIMEOUT,
		numTimeout:        NUMTIMEOUT,
		count:             0,
		changeUriDuration: DEFAULT_CHANGE_URI_DURATION,
		lastChageUriTime:  0,
	}
	rc   *RongCloud
	once sync.Once
)

// RongCloud appKey appSecret extra
type RongCloud struct {
	appKey    string
	appSecret string
	*rongCloudExtra
	uriLock sync.Mutex
}

// rongCloudExtra rongCloud扩展增加自定义融云服务器地址,请求超时时间
type rongCloudExtra struct {
	rongCloudURI      string
	rongCloudSMSURI   string
	timeout           time.Duration
	numTimeout        uint
	count             uint
	changeUriDuration int64
	lastChageUriTime  int64
}

// getSignature 本地生成签名
// Signature (数据签名)计算方法：将系统分配的 App Secret、Nonce (随机数)、
// Timestamp (时间戳)三个字符串按先后顺序拼接成一个字符串并进行 SHA1 哈希计算。如果调用的数据签名验证失败，接口调用会返回 HTTP 状态码 401。
func (rc RongCloud) getSignature() (nonce, timestamp, signature string) {
	nonceInt := rand.Int()
	nonce = strconv.Itoa(nonceInt)
	timeInt64 := time.Now().Unix()
	timestamp = strconv.FormatInt(timeInt64, 10)
	h := sha1.New()
	_, _ = io.WriteString(h, rc.appSecret+nonce+timestamp)
	signature = fmt.Sprintf("%x", h.Sum(nil))
	return
}

// fillHeader 在 Http Header 增加API签名
func (rc RongCloud) fillHeader(req *httplib.BeegoHTTPRequest) {
	nonce, timestamp, signature := rc.getSignature()
	req.Header("App-Key", rc.appKey)
	req.Header("Nonce", nonce)
	req.Header("Timestamp", timestamp)
	req.Header("Signature", signature)
	req.Header("Content-Type", "application/x-www-form-urlencoded")
	req.Header("User-Agent", USERAGENT)
}

// fillJSONHeader 在 Http Header Content-Type 设置为josn格式
func fillJSONHeader(req *httplib.BeegoHTTPRequest) {
	req.Header("Content-Type", "application/json")
}

// NewRongCloud 创建 RongCloud 对象
func NewRongCloud(appKey, appSecret string, options ...rongCloudOption) *RongCloud {
	once.Do(func() {
		// 默认扩展配置
		defaultRongCloud := defaultExtra
		defaultRongCloud.lastChageUriTime = 0
		rc = &RongCloud{
			appKey:         appKey,
			appSecret:      appSecret,
			rongCloudExtra: &defaultRongCloud,
		}
	},
	)
	for _, option := range options {
		option(rc)
	}
	return rc
}

// GetRongCloud 获取 RongCloud 对象
func GetRongCloud() *RongCloud {
	return rc
}

// changeURI 切换 Api 服务器地址
func (rc *RongCloud) changeURI() {
	nowUnix := time.Now().Unix()
	// 检查距离上次更换uri的时间间隔
	rc.uriLock.Lock()
	if (nowUnix - rc.lastChageUriTime) >= rc.changeUriDuration {
		switch rc.rongCloudURI {
		case RONGCLOUDURI:
			rc.rongCloudURI = RONGCLOUDURI2
		case RONGCLOUDURI2:
			rc.rongCloudURI = RONGCLOUDURI
		default:
		}
		rc.lastChageUriTime = nowUnix
	}
	rc.uriLock.Unlock()
}

// PrivateURI 私有云设置 Api 地址
func (rc *RongCloud) PrivateURI(uri, sms string) {
	rc.rongCloudURI = uri
	rc.rongCloudSMSURI = sms
}

// urlError 判断是否为 url.Error
func (rc *RongCloud) urlError(err error) {
	// if rc.numTimeout == 0 {
	//     return
	// }
	// if reflect.TypeOf(err) == reflect.TypeOf(&url.Error{}) {
	//     if err.(*url.Error).Timeout() {
	//         if rc.count >= rc.numTimeout {
	//             rc.changeURI()
	//             rc.count = 1
	//         } else {
	//             rc.count++
	//         }
	//     }
	// }
}

/**
判断 http status code, 如果大于 500 就切换一次域名
*/
func (rc *RongCloud) checkStatusCode(resp *http.Response) {
	if resp.StatusCode >= 500 && resp.StatusCode < 600 {
		rc.changeURI()
	}

	return
}
<commit_msg>更新UA，保持版本一致<commit_after>/*
 * @Descripttion:
 * @version:
 * @Author: ran.ding
 * @Date: 2019-09-02 18:29:55
 * @LastEditors: ran.ding
 * @LastEditTime: 2019-09-10 15:39:14
 */

// The MIT License (MIT)

// Copyright (c) 2014 融云 Rong Cloud

// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:

// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.

// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

/*
 * 融云 Server API go 客户端
 * create by RongCloud
 * create datetime : 2018-11-28
 * v3
 */

package sdk

import (
	"crypto/sha1"
	"fmt"
	"io"
	"math/rand"
	"net/http"
	"strconv"
	"sync"
	"time"

	"github.com/astaxie/beego/httplib"
)

const (
	// RONGCLOUDSMSURI 融云默认 SMS API 地址
	RONGCLOUDSMSURI = "http://api.sms.ronghub.com"
	// RONGCLOUDURI 融云默认 API 地址
	RONGCLOUDURI = "http://api-cn.ronghub.com"
	// RONGCLOUDURI2 融云备用 API 地址
	RONGCLOUDURI2 = "http://api2-cn.ronghub.com"
	// ReqType body类型
	ReqType = "json"
	// USERAGENT sdk 名称
	USERAGENT = "rc-go-sdk/3.1.1"
	// DEFAULTTIMEOUT 默认超时时间
	DEFAULTTIMEOUT = 10
	// NUMTIMEOUT 默认超时次数切换 Api 地址
	NUMTIMEOUT = 3
	// 自动切换 api 地址时间间隔，秒
	DEFAULT_CHANGE_URI_DURATION = 30
)

var (
	defaultExtra = rongCloudExtra{
		rongCloudURI:      RONGCLOUDURI,
		rongCloudSMSURI:   RONGCLOUDSMSURI,
		timeout:           DEFAULTTIMEOUT,
		numTimeout:        NUMTIMEOUT,
		count:             0,
		changeUriDuration: DEFAULT_CHANGE_URI_DURATION,
		lastChageUriTime:  0,
	}
	rc   *RongCloud
	once sync.Once
)

// RongCloud appKey appSecret extra
type RongCloud struct {
	appKey    string
	appSecret string
	*rongCloudExtra
	uriLock sync.Mutex
}

// rongCloudExtra rongCloud扩展增加自定义融云服务器地址,请求超时时间
type rongCloudExtra struct {
	rongCloudURI      string
	rongCloudSMSURI   string
	timeout           time.Duration
	numTimeout        uint
	count             uint
	changeUriDuration int64
	lastChageUriTime  int64
}

// getSignature 本地生成签名
// Signature (数据签名)计算方法：将系统分配的 App Secret、Nonce (随机数)、
// Timestamp (时间戳)三个字符串按先后顺序拼接成一个字符串并进行 SHA1 哈希计算。如果调用的数据签名验证失败，接口调用会返回 HTTP 状态码 401。
func (rc RongCloud) getSignature() (nonce, timestamp, signature string) {
	nonceInt := rand.Int()
	nonce = strconv.Itoa(nonceInt)
	timeInt64 := time.Now().Unix()
	timestamp = strconv.FormatInt(timeInt64, 10)
	h := sha1.New()
	_, _ = io.WriteString(h, rc.appSecret+nonce+timestamp)
	signature = fmt.Sprintf("%x", h.Sum(nil))
	return
}

// fillHeader 在 Http Header 增加API签名
func (rc RongCloud) fillHeader(req *httplib.BeegoHTTPRequest) {
	nonce, timestamp, signature := rc.getSignature()
	req.Header("App-Key", rc.appKey)
	req.Header("Nonce", nonce)
	req.Header("Timestamp", timestamp)
	req.Header("Signature", signature)
	req.Header("Content-Type", "application/x-www-form-urlencoded")
	req.Header("User-Agent", USERAGENT)
}

// fillJSONHeader 在 Http Header Content-Type 设置为josn格式
func fillJSONHeader(req *httplib.BeegoHTTPRequest) {
	req.Header("Content-Type", "application/json")
}

// NewRongCloud 创建 RongCloud 对象
func NewRongCloud(appKey, appSecret string, options ...rongCloudOption) *RongCloud {
	once.Do(func() {
		// 默认扩展配置
		defaultRongCloud := defaultExtra
		defaultRongCloud.lastChageUriTime = 0
		rc = &RongCloud{
			appKey:         appKey,
			appSecret:      appSecret,
			rongCloudExtra: &defaultRongCloud,
		}
	},
	)
	for _, option := range options {
		option(rc)
	}
	return rc
}

// GetRongCloud 获取 RongCloud 对象
func GetRongCloud() *RongCloud {
	return rc
}

// changeURI 切换 Api 服务器地址
func (rc *RongCloud) changeURI() {
	nowUnix := time.Now().Unix()
	// 检查距离上次更换uri的时间间隔
	rc.uriLock.Lock()
	if (nowUnix - rc.lastChageUriTime) >= rc.changeUriDuration {
		switch rc.rongCloudURI {
		case RONGCLOUDURI:
			rc.rongCloudURI = RONGCLOUDURI2
		case RONGCLOUDURI2:
			rc.rongCloudURI = RONGCLOUDURI
		default:
		}
		rc.lastChageUriTime = nowUnix
	}
	rc.uriLock.Unlock()
}

// PrivateURI 私有云设置 Api 地址
func (rc *RongCloud) PrivateURI(uri, sms string) {
	rc.rongCloudURI = uri
	rc.rongCloudSMSURI = sms
}

// urlError 判断是否为 url.Error
func (rc *RongCloud) urlError(err error) {
	// if rc.numTimeout == 0 {
	//     return
	// }
	// if reflect.TypeOf(err) == reflect.TypeOf(&url.Error{}) {
	//     if err.(*url.Error).Timeout() {
	//         if rc.count >= rc.numTimeout {
	//             rc.changeURI()
	//             rc.count = 1
	//         } else {
	//             rc.count++
	//         }
	//     }
	// }
}

/**
判断 http status code, 如果大于 500 就切换一次域名
*/
func (rc *RongCloud) checkStatusCode(resp *http.Response) {
	if resp.StatusCode >= 500 && resp.StatusCode < 600 {
		rc.changeURI()
	}

	return
}
<|endoftext|>