<commit_before>/*
Copyright 2017 The Kubernetes 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 app implements a server that runs the service catalog controllers.
package app

import (
	"fmt"
	"net"
	"net/http"
	"net/http/pprof"
	"os"
	goruntime "runtime"
	"strconv"
	"time"

	"k8s.io/client-go/kubernetes"
	v1core "k8s.io/client-go/kubernetes/typed/core/v1"

	"k8s.io/client-go/discovery"
	"k8s.io/client-go/pkg/api"
	"k8s.io/client-go/pkg/api/v1"
	"k8s.io/client-go/rest"
	"k8s.io/client-go/tools/clientcmd"
	"k8s.io/client-go/tools/record"

	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime/schema"
	"k8s.io/apimachinery/pkg/util/wait"
	"k8s.io/apiserver/pkg/server/healthz"
	"k8s.io/kubernetes/pkg/client/clientset_generated/clientset"
	"k8s.io/kubernetes/pkg/client/leaderelection"
	"k8s.io/kubernetes/pkg/client/leaderelection/resourcelock"
	"k8s.io/kubernetes/pkg/util/configz"

	// The API groups for our API must be installed before we can use the
	// client to work with them.  This needs to be done once per process; this
	// is the point at which we handle this for the controller-manager
	// process.  Please do not remove.
	_ "github.com/kubernetes-incubator/service-catalog/pkg/apis/servicecatalog/install"
	// The core API has to be installed in order for the client to understand
	// error messages from the API server.  Please do not remove.
	_ "k8s.io/client-go/pkg/api/install"

	"github.com/kubernetes-incubator/service-catalog/cmd/controller-manager/app/options"
	"github.com/kubernetes-incubator/service-catalog/pkg/brokerapi/openservicebroker"
	servicecataloginformers "github.com/kubernetes-incubator/service-catalog/pkg/client/informers_generated/externalversions"
	"github.com/kubernetes-incubator/service-catalog/pkg/controller"

	"github.com/golang/glog"
	"github.com/spf13/cobra"
	"github.com/spf13/pflag"
)

// NewControllerManagerCommand creates a *cobra.Command object with default
// parameters.
func NewControllerManagerCommand() *cobra.Command {
	s := options.NewControllerManagerServer()
	s.AddFlags(pflag.CommandLine)
	cmd := &cobra.Command{
		Use: "controller-manager",
		Long: `The service-catalog controller manager is a daemon that embeds
the core control loops shipped with the service catalog.`,
		Run: func(cmd *cobra.Command, args []string) {
		},
	}

	return cmd
}

const controllerManagerAgentName = "service-catalog-controller-manager"
const controllerDiscoveryAgentName = "service-catalog-controller-discovery"

// Run runs the service-catalog controller-manager; should never exit.
func Run(controllerManagerOptions *options.ControllerManagerServer) error {
	// TODO: what does this do

	// if c, err := configz.New("componentconfig"); err == nil {
	// 	c.Set(controllerManagerOptions.KubeControllerManagerConfiguration)
	// } else {
	// 	glog.Errorf("unable to register configz: %s", err)
	// }

	// Build the K8s kubeconfig / client / clientBuilder
	glog.V(4).Info("Building k8s kubeconfig")

	var err error
	var k8sKubeconfig *rest.Config
	if controllerManagerOptions.K8sAPIServerURL == "" && controllerManagerOptions.K8sKubeconfigPath == "" {
		k8sKubeconfig, err = rest.InClusterConfig()
	} else {
		k8sKubeconfig, err = clientcmd.BuildConfigFromFlags(
			controllerManagerOptions.K8sAPIServerURL,
			controllerManagerOptions.K8sKubeconfigPath)
	}
	if err != nil {
		return fmt.Errorf("failed to get Kubernetes client config: %v", err)
	}
	k8sKubeconfig.GroupVersion = &schema.GroupVersion{}

	k8sKubeconfig.ContentConfig.ContentType = controllerManagerOptions.ContentType
	// Override kubeconfig qps/burst settings from flags
	k8sKubeconfig.QPS = controllerManagerOptions.KubeAPIQPS
	k8sKubeconfig.Burst = int(controllerManagerOptions.KubeAPIBurst)
	k8sKubeClient, err := kubernetes.NewForConfig(
		rest.AddUserAgent(k8sKubeconfig, controllerManagerAgentName),
	)
	if err != nil {
		return fmt.Errorf("invalid Kubernetes API configuration: %v", err)
	}
	leaderElectionClient := clientset.NewForConfigOrDie(rest.AddUserAgent(k8sKubeconfig, "leader-election"))

	glog.V(4).Infof("Building service-catalog kubeconfig for url: %v\n", controllerManagerOptions.ServiceCatalogAPIServerURL)

	var serviceCatalogKubeconfig *rest.Config
	// Build the service-catalog kubeconfig / clientBuilder
	if controllerManagerOptions.ServiceCatalogAPIServerURL == "" && controllerManagerOptions.ServiceCatalogKubeconfigPath == "" {
		// explicitly fall back to InClusterConfig, assuming we're talking to an API server which does aggregation
		// (BuildConfigFromFlags does this, but gives a more generic warning message than we do here)
		glog.V(4).Infof("Using inClusterConfig to talk to service catalog API server -- make sure your API server is registered with the aggregator")
		serviceCatalogKubeconfig, err = rest.InClusterConfig()
	} else {
		serviceCatalogKubeconfig, err = clientcmd.BuildConfigFromFlags(
			controllerManagerOptions.ServiceCatalogAPIServerURL,
			controllerManagerOptions.ServiceCatalogKubeconfigPath)
	}
	if err != nil {
		// TODO: disambiguate API errors
		return fmt.Errorf("failed to get Service Catalog client configuration: %v", err)
	}

	// due to using both k8s.io/kubernetes and k8s.io/client-go, we need to convert this object over

	glog.V(4).Info("Starting http server and mux")
	// Start http server and handlers
	go func() {
		mux := http.NewServeMux()
		healthz.InstallHandler(mux)
		configz.InstallHandler(mux)

		if controllerManagerOptions.EnableProfiling {
			mux.HandleFunc("/debug/pprof/", pprof.Index)
			mux.HandleFunc("/debug/pprof/profile", pprof.Profile)
			mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
			mux.HandleFunc("/debug/pprof/trace", pprof.Trace)
			if controllerManagerOptions.EnableContentionProfiling {
				goruntime.SetBlockProfileRate(1)
			}
		}
		server := &http.Server{
			Addr:    net.JoinHostPort(controllerManagerOptions.Address, strconv.Itoa(int(controllerManagerOptions.Port))),
			Handler: mux,
		}
		glog.Fatal(server.ListenAndServe())
	}()

	// Create event broadcaster
	glog.V(4).Info("Creating event broadcaster")
	eventBroadcaster := record.NewBroadcaster()
	eventBroadcaster.StartLogging(glog.Infof)
	eventBroadcaster.StartRecordingToSink(&v1core.EventSinkImpl{Interface: k8sKubeClient.Core().Events("")})
	recorder := eventBroadcaster.NewRecorder(api.Scheme, v1.EventSource{Component: controllerManagerAgentName})

	// 'run' is the logic to run the controllers for the controller manager
	run := func(stop <-chan struct{}) {
		serviceCatalogClientBuilder := controller.SimpleClientBuilder{
			ClientConfig: serviceCatalogKubeconfig,
		}

		// TODO: understand service account story for this controller-manager

		// if len(s.ServiceAccountKeyFile) > 0 && controllerManagerOptions.UseServiceAccountCredentials {
		// 	k8sClientBuilder = controller.SAControllerClientBuilder{
		// 		ClientConfig: restclient.AnonymousClientConfig(k8sKubeconfig),
		// 		CoreClient:   k8sKubeClient.Core(),
		// 		Namespace:    "kube-system",
		// 	}
		// } else {
		// 	k8sClientBuilder = rootClientBuilder
		// }

		err := StartControllers(controllerManagerOptions, k8sKubeconfig, serviceCatalogClientBuilder, recorder, stop)
		glog.Fatalf("error running controllers: %v", err)
		panic("unreachable")
	}

	if !controllerManagerOptions.LeaderElection.LeaderElect {
		run(make(<-chan (struct{})))
		panic("unreachable")
	}

	// Identity used to distinguish between multiple cloud controller manager instances
	id, err := os.Hostname()
	if err != nil {
		return err
	}

	glog.V(5).Infof("Using namespace %v for leader election lock", controllerManagerOptions.LeaderElectionNamespace)

	// Lock required for leader election
	rl := resourcelock.EndpointsLock{
		EndpointsMeta: metav1.ObjectMeta{
			Namespace: controllerManagerOptions.LeaderElectionNamespace,
			Name:      "service-catalog-controller-manager",
		},
		Client: leaderElectionClient,
		LockConfig: resourcelock.ResourceLockConfig{
			Identity:      id + "-external-service-catalog-controller",
			EventRecorder: recorder,
		},
	}

	// Try and become the leader and start cloud controller manager loops
	leaderelection.RunOrDie(leaderelection.LeaderElectionConfig{
		Lock:          &rl,
		LeaseDuration: controllerManagerOptions.LeaderElection.LeaseDuration.Duration,
		RenewDeadline: controllerManagerOptions.LeaderElection.RenewDeadline.Duration,
		RetryPeriod:   controllerManagerOptions.LeaderElection.RetryPeriod.Duration,
		Callbacks: leaderelection.LeaderCallbacks{
			OnStartedLeading: run,
			OnStoppedLeading: func() {
				glog.Fatalf("leaderelection lost")
			},
		},
	})
	panic("unreachable")
}

// getAvailableResources uses the discovery client to determine which API
// groups are available in the endpoint reachable from the given client and
// returns a map of them.
func getAvailableResources(clientBuilder controller.ClientBuilder) (map[schema.GroupVersionResource]bool, error) {
	var discoveryClient discovery.DiscoveryInterface

	// If apiserver is not running we should wait for some time and fail only then. This is particularly
	// important when we start apiserver and controller manager at the same time.
	err := wait.PollImmediate(time.Second, 10*time.Second, func() (bool, error) {
		client, err := clientBuilder.Client(controllerDiscoveryAgentName)
		if err != nil {
			glog.Errorf("Failed to get api versions from server: %v", err)
			return false, nil
		}

		glog.V(4).Info("Created client for discovery")

		discoveryClient = client.Discovery()
		return true, nil
	})

	if err != nil {
		return nil, fmt.Errorf("failed to get api versions from server: %v", err)
	}

	resourceMap, err := discoveryClient.ServerResources()
	if err != nil {
		return nil, fmt.Errorf("failed to get supported resources from server: %v", err)
	}

	allResources := map[schema.GroupVersionResource]bool{}
	for _, apiResourceList := range resourceMap {
		glog.V(4).Infof("Resource: %#v", apiResourceList)
		version, err := schema.ParseGroupVersion(apiResourceList.GroupVersion)
		if err != nil {
			return nil, err
		}
		for _, apiResource := range apiResourceList.APIResources {
			allResources[version.WithResource(apiResource.Name)] = true
		}
	}

	return allResources, nil
}

// StartControllers starts all the controllers in the service-catalog
// controller manager.
func StartControllers(s *options.ControllerManagerServer,
	coreKubeconfig *rest.Config,
	serviceCatalogClientBuilder controller.ClientBuilder,
	recorder record.EventRecorder,
	stop <-chan struct{}) error {

	// Get available service-catalog resources
	glog.V(5).Info("Getting available resources")
	availableResources, err := getAvailableResources(serviceCatalogClientBuilder)
	if err != nil {
		return err
	}

	coreKubeconfig = rest.AddUserAgent(coreKubeconfig, controllerManagerAgentName)
	coreClient, err := kubernetes.NewForConfig(coreKubeconfig)
	if err != nil {
		glog.Fatal(err)
	}

	// Launch service-catalog controller
	if availableResources[schema.GroupVersionResource{Group: "servicecatalog.k8s.io", Version: "v1alpha1", Resource: "brokers"}] {
		glog.V(5).Info("Creating shared informers; resync interval: %v", s.ResyncInterval)
		// Build the informer factory for service-catalog resources
		informerFactory := servicecataloginformers.NewSharedInformerFactory(
			serviceCatalogClientBuilder.ClientOrDie("shared-informers"),
			s.ResyncInterval,
		)
		// All shared informers are v1alpha1 API level
		serviceCatalogSharedInformers := informerFactory.Servicecatalog().V1alpha1()

		glog.V(5).Info("Creating controller; broker relist interval: %v", s.BrokerRelistInterval)
		serviceCatalogController, err := controller.NewController(
			coreClient,
			serviceCatalogClientBuilder.ClientOrDie(controllerManagerAgentName).ServicecatalogV1alpha1(),
			serviceCatalogSharedInformers.Brokers(),
			serviceCatalogSharedInformers.ServiceClasses(),
			serviceCatalogSharedInformers.Instances(),
			serviceCatalogSharedInformers.Bindings(),
			openservicebroker.NewClient,
			s.BrokerRelistInterval,
			s.OSBAPIContextProfile,
			recorder,
		)
		if err != nil {
			return err
		}

		glog.V(5).Info("Running controller")
		go serviceCatalogController.Run(s.ConcurrentSyncs, stop)

		glog.V(1).Info("Starting shared informers")
		informerFactory.Start(stop)
	} else {
		return fmt.Errorf("unable to start service-catalog controller: servicecatalog/v1alpha1 is not available")
	}

	select {}
}
<commit_msg>Use correct infof calls in controller_manager (#950)<commit_after>/*
Copyright 2017 The Kubernetes 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 app implements a server that runs the service catalog controllers.
package app

import (
	"fmt"
	"net"
	"net/http"
	"net/http/pprof"
	"os"
	goruntime "runtime"
	"strconv"
	"time"

	"k8s.io/client-go/kubernetes"
	v1core "k8s.io/client-go/kubernetes/typed/core/v1"

	"k8s.io/client-go/discovery"
	"k8s.io/client-go/pkg/api"
	"k8s.io/client-go/pkg/api/v1"
	"k8s.io/client-go/rest"
	"k8s.io/client-go/tools/clientcmd"
	"k8s.io/client-go/tools/record"

	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime/schema"
	"k8s.io/apimachinery/pkg/util/wait"
	"k8s.io/apiserver/pkg/server/healthz"
	"k8s.io/kubernetes/pkg/client/clientset_generated/clientset"
	"k8s.io/kubernetes/pkg/client/leaderelection"
	"k8s.io/kubernetes/pkg/client/leaderelection/resourcelock"
	"k8s.io/kubernetes/pkg/util/configz"

	// The API groups for our API must be installed before we can use the
	// client to work with them.  This needs to be done once per process; this
	// is the point at which we handle this for the controller-manager
	// process.  Please do not remove.
	_ "github.com/kubernetes-incubator/service-catalog/pkg/apis/servicecatalog/install"
	// The core API has to be installed in order for the client to understand
	// error messages from the API server.  Please do not remove.
	_ "k8s.io/client-go/pkg/api/install"

	"github.com/kubernetes-incubator/service-catalog/cmd/controller-manager/app/options"
	"github.com/kubernetes-incubator/service-catalog/pkg/brokerapi/openservicebroker"
	servicecataloginformers "github.com/kubernetes-incubator/service-catalog/pkg/client/informers_generated/externalversions"
	"github.com/kubernetes-incubator/service-catalog/pkg/controller"

	"github.com/golang/glog"
	"github.com/spf13/cobra"
	"github.com/spf13/pflag"
)

// NewControllerManagerCommand creates a *cobra.Command object with default
// parameters.
func NewControllerManagerCommand() *cobra.Command {
	s := options.NewControllerManagerServer()
	s.AddFlags(pflag.CommandLine)
	cmd := &cobra.Command{
		Use: "controller-manager",
		Long: `The service-catalog controller manager is a daemon that embeds
the core control loops shipped with the service catalog.`,
		Run: func(cmd *cobra.Command, args []string) {
		},
	}

	return cmd
}

const controllerManagerAgentName = "service-catalog-controller-manager"
const controllerDiscoveryAgentName = "service-catalog-controller-discovery"

// Run runs the service-catalog controller-manager; should never exit.
func Run(controllerManagerOptions *options.ControllerManagerServer) error {
	// TODO: what does this do

	// if c, err := configz.New("componentconfig"); err == nil {
	// 	c.Set(controllerManagerOptions.KubeControllerManagerConfiguration)
	// } else {
	// 	glog.Errorf("unable to register configz: %s", err)
	// }

	// Build the K8s kubeconfig / client / clientBuilder
	glog.V(4).Info("Building k8s kubeconfig")

	var err error
	var k8sKubeconfig *rest.Config
	if controllerManagerOptions.K8sAPIServerURL == "" && controllerManagerOptions.K8sKubeconfigPath == "" {
		k8sKubeconfig, err = rest.InClusterConfig()
	} else {
		k8sKubeconfig, err = clientcmd.BuildConfigFromFlags(
			controllerManagerOptions.K8sAPIServerURL,
			controllerManagerOptions.K8sKubeconfigPath)
	}
	if err != nil {
		return fmt.Errorf("failed to get Kubernetes client config: %v", err)
	}
	k8sKubeconfig.GroupVersion = &schema.GroupVersion{}

	k8sKubeconfig.ContentConfig.ContentType = controllerManagerOptions.ContentType
	// Override kubeconfig qps/burst settings from flags
	k8sKubeconfig.QPS = controllerManagerOptions.KubeAPIQPS
	k8sKubeconfig.Burst = int(controllerManagerOptions.KubeAPIBurst)
	k8sKubeClient, err := kubernetes.NewForConfig(
		rest.AddUserAgent(k8sKubeconfig, controllerManagerAgentName),
	)
	if err != nil {
		return fmt.Errorf("invalid Kubernetes API configuration: %v", err)
	}
	leaderElectionClient := clientset.NewForConfigOrDie(rest.AddUserAgent(k8sKubeconfig, "leader-election"))

	glog.V(4).Infof("Building service-catalog kubeconfig for url: %v\n", controllerManagerOptions.ServiceCatalogAPIServerURL)

	var serviceCatalogKubeconfig *rest.Config
	// Build the service-catalog kubeconfig / clientBuilder
	if controllerManagerOptions.ServiceCatalogAPIServerURL == "" && controllerManagerOptions.ServiceCatalogKubeconfigPath == "" {
		// explicitly fall back to InClusterConfig, assuming we're talking to an API server which does aggregation
		// (BuildConfigFromFlags does this, but gives a more generic warning message than we do here)
		glog.V(4).Infof("Using inClusterConfig to talk to service catalog API server -- make sure your API server is registered with the aggregator")
		serviceCatalogKubeconfig, err = rest.InClusterConfig()
	} else {
		serviceCatalogKubeconfig, err = clientcmd.BuildConfigFromFlags(
			controllerManagerOptions.ServiceCatalogAPIServerURL,
			controllerManagerOptions.ServiceCatalogKubeconfigPath)
	}
	if err != nil {
		// TODO: disambiguate API errors
		return fmt.Errorf("failed to get Service Catalog client configuration: %v", err)
	}

	// due to using both k8s.io/kubernetes and k8s.io/client-go, we need to convert this object over

	glog.V(4).Info("Starting http server and mux")
	// Start http server and handlers
	go func() {
		mux := http.NewServeMux()
		healthz.InstallHandler(mux)
		configz.InstallHandler(mux)

		if controllerManagerOptions.EnableProfiling {
			mux.HandleFunc("/debug/pprof/", pprof.Index)
			mux.HandleFunc("/debug/pprof/profile", pprof.Profile)
			mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
			mux.HandleFunc("/debug/pprof/trace", pprof.Trace)
			if controllerManagerOptions.EnableContentionProfiling {
				goruntime.SetBlockProfileRate(1)
			}
		}
		server := &http.Server{
			Addr:    net.JoinHostPort(controllerManagerOptions.Address, strconv.Itoa(int(controllerManagerOptions.Port))),
			Handler: mux,
		}
		glog.Fatal(server.ListenAndServe())
	}()

	// Create event broadcaster
	glog.V(4).Info("Creating event broadcaster")
	eventBroadcaster := record.NewBroadcaster()
	eventBroadcaster.StartLogging(glog.Infof)
	eventBroadcaster.StartRecordingToSink(&v1core.EventSinkImpl{Interface: k8sKubeClient.Core().Events("")})
	recorder := eventBroadcaster.NewRecorder(api.Scheme, v1.EventSource{Component: controllerManagerAgentName})

	// 'run' is the logic to run the controllers for the controller manager
	run := func(stop <-chan struct{}) {
		serviceCatalogClientBuilder := controller.SimpleClientBuilder{
			ClientConfig: serviceCatalogKubeconfig,
		}

		// TODO: understand service account story for this controller-manager

		// if len(s.ServiceAccountKeyFile) > 0 && controllerManagerOptions.UseServiceAccountCredentials {
		// 	k8sClientBuilder = controller.SAControllerClientBuilder{
		// 		ClientConfig: restclient.AnonymousClientConfig(k8sKubeconfig),
		// 		CoreClient:   k8sKubeClient.Core(),
		// 		Namespace:    "kube-system",
		// 	}
		// } else {
		// 	k8sClientBuilder = rootClientBuilder
		// }

		err := StartControllers(controllerManagerOptions, k8sKubeconfig, serviceCatalogClientBuilder, recorder, stop)
		glog.Fatalf("error running controllers: %v", err)
		panic("unreachable")
	}

	if !controllerManagerOptions.LeaderElection.LeaderElect {
		run(make(<-chan (struct{})))
		panic("unreachable")
	}

	// Identity used to distinguish between multiple cloud controller manager instances
	id, err := os.Hostname()
	if err != nil {
		return err
	}

	glog.V(5).Infof("Using namespace %v for leader election lock", controllerManagerOptions.LeaderElectionNamespace)

	// Lock required for leader election
	rl := resourcelock.EndpointsLock{
		EndpointsMeta: metav1.ObjectMeta{
			Namespace: controllerManagerOptions.LeaderElectionNamespace,
			Name:      "service-catalog-controller-manager",
		},
		Client: leaderElectionClient,
		LockConfig: resourcelock.ResourceLockConfig{
			Identity:      id + "-external-service-catalog-controller",
			EventRecorder: recorder,
		},
	}

	// Try and become the leader and start cloud controller manager loops
	leaderelection.RunOrDie(leaderelection.LeaderElectionConfig{
		Lock:          &rl,
		LeaseDuration: controllerManagerOptions.LeaderElection.LeaseDuration.Duration,
		RenewDeadline: controllerManagerOptions.LeaderElection.RenewDeadline.Duration,
		RetryPeriod:   controllerManagerOptions.LeaderElection.RetryPeriod.Duration,
		Callbacks: leaderelection.LeaderCallbacks{
			OnStartedLeading: run,
			OnStoppedLeading: func() {
				glog.Fatalf("leaderelection lost")
			},
		},
	})
	panic("unreachable")
}

// getAvailableResources uses the discovery client to determine which API
// groups are available in the endpoint reachable from the given client and
// returns a map of them.
func getAvailableResources(clientBuilder controller.ClientBuilder) (map[schema.GroupVersionResource]bool, error) {
	var discoveryClient discovery.DiscoveryInterface

	// If apiserver is not running we should wait for some time and fail only then. This is particularly
	// important when we start apiserver and controller manager at the same time.
	err := wait.PollImmediate(time.Second, 10*time.Second, func() (bool, error) {
		client, err := clientBuilder.Client(controllerDiscoveryAgentName)
		if err != nil {
			glog.Errorf("Failed to get api versions from server: %v", err)
			return false, nil
		}

		glog.V(4).Info("Created client for discovery")

		discoveryClient = client.Discovery()
		return true, nil
	})

	if err != nil {
		return nil, fmt.Errorf("failed to get api versions from server: %v", err)
	}

	resourceMap, err := discoveryClient.ServerResources()
	if err != nil {
		return nil, fmt.Errorf("failed to get supported resources from server: %v", err)
	}

	allResources := map[schema.GroupVersionResource]bool{}
	for _, apiResourceList := range resourceMap {
		glog.V(4).Infof("Resource: %#v", apiResourceList)
		version, err := schema.ParseGroupVersion(apiResourceList.GroupVersion)
		if err != nil {
			return nil, err
		}
		for _, apiResource := range apiResourceList.APIResources {
			allResources[version.WithResource(apiResource.Name)] = true
		}
	}

	return allResources, nil
}

// StartControllers starts all the controllers in the service-catalog
// controller manager.
func StartControllers(s *options.ControllerManagerServer,
	coreKubeconfig *rest.Config,
	serviceCatalogClientBuilder controller.ClientBuilder,
	recorder record.EventRecorder,
	stop <-chan struct{}) error {

	// Get available service-catalog resources
	glog.V(5).Info("Getting available resources")
	availableResources, err := getAvailableResources(serviceCatalogClientBuilder)
	if err != nil {
		return err
	}

	coreKubeconfig = rest.AddUserAgent(coreKubeconfig, controllerManagerAgentName)
	coreClient, err := kubernetes.NewForConfig(coreKubeconfig)
	if err != nil {
		glog.Fatal(err)
	}

	// Launch service-catalog controller
	if availableResources[schema.GroupVersionResource{Group: "servicecatalog.k8s.io", Version: "v1alpha1", Resource: "brokers"}] {
		glog.V(5).Infof("Creating shared informers; resync interval: %v", s.ResyncInterval)
		// Build the informer factory for service-catalog resources
		informerFactory := servicecataloginformers.NewSharedInformerFactory(
			serviceCatalogClientBuilder.ClientOrDie("shared-informers"),
			s.ResyncInterval,
		)
		// All shared informers are v1alpha1 API level
		serviceCatalogSharedInformers := informerFactory.Servicecatalog().V1alpha1()

		glog.V(5).Infof("Creating controller; broker relist interval: %v", s.BrokerRelistInterval)
		serviceCatalogController, err := controller.NewController(
			coreClient,
			serviceCatalogClientBuilder.ClientOrDie(controllerManagerAgentName).ServicecatalogV1alpha1(),
			serviceCatalogSharedInformers.Brokers(),
			serviceCatalogSharedInformers.ServiceClasses(),
			serviceCatalogSharedInformers.Instances(),
			serviceCatalogSharedInformers.Bindings(),
			openservicebroker.NewClient,
			s.BrokerRelistInterval,
			s.OSBAPIContextProfile,
			recorder,
		)
		if err != nil {
			return err
		}

		glog.V(5).Info("Running controller")
		go serviceCatalogController.Run(s.ConcurrentSyncs, stop)

		glog.V(1).Info("Starting shared informers")
		informerFactory.Start(stop)
	} else {
		return fmt.Errorf("unable to start service-catalog controller: servicecatalog/v1alpha1 is not available")
	}

	select {}
}
<|endoftext|>


<commit_before>package kv

import (
	"testing"

	"github.com/docker/swarm/pkg/store"
	"github.com/stretchr/testify/assert"
)

func TestInitialize(t *testing.T) {
	d := &Discovery{backend: store.MOCK}
	assert.EqualError(t, d.Initialize("127.0.0.1", 0), "invalid format \"127.0.0.1\", missing <path>")

	d = &Discovery{backend: store.MOCK}
	assert.NoError(t, d.Initialize("127.0.0.1:1234/path", 0))
	s := d.store.(*store.Mock)
	assert.Len(t, s.Endpoints, 1)
	assert.Equal(t, s.Endpoints[0], "127.0.0.1:1234")
	assert.Equal(t, d.prefix, "path")

	d = &Discovery{backend: store.MOCK}
	assert.NoError(t, d.Initialize("127.0.0.1:1234,127.0.0.2:1234,127.0.0.3:1234/path", 0))
	s = d.store.(*store.Mock)
	assert.Len(t, s.Endpoints, 3)
	assert.Equal(t, s.Endpoints[0], "127.0.0.1:1234")
	assert.Equal(t, s.Endpoints[1], "127.0.0.2:1234")
	assert.Equal(t, s.Endpoints[2], "127.0.0.3:1234")
	assert.Equal(t, d.prefix, "path")
}
<commit_msg>discovery: Added kv tests using the mock Store.<commit_after>package kv

import (
	"errors"
	"testing"
	"time"

	"github.com/docker/swarm/discovery"
	"github.com/docker/swarm/pkg/store"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/mock"
)

func TestInitialize(t *testing.T) {
	d := &Discovery{backend: store.MOCK}
	assert.EqualError(t, d.Initialize("127.0.0.1", 0), "invalid format \"127.0.0.1\", missing <path>")

	d = &Discovery{backend: store.MOCK}
	assert.NoError(t, d.Initialize("127.0.0.1:1234/path", 0))
	s := d.store.(*store.Mock)
	assert.Len(t, s.Endpoints, 1)
	assert.Equal(t, s.Endpoints[0], "127.0.0.1:1234")
	assert.Equal(t, d.prefix, "path")

	d = &Discovery{backend: store.MOCK}
	assert.NoError(t, d.Initialize("127.0.0.1:1234,127.0.0.2:1234,127.0.0.3:1234/path", 0))
	s = d.store.(*store.Mock)
	assert.Len(t, s.Endpoints, 3)
	assert.Equal(t, s.Endpoints[0], "127.0.0.1:1234")
	assert.Equal(t, s.Endpoints[1], "127.0.0.2:1234")
	assert.Equal(t, s.Endpoints[2], "127.0.0.3:1234")
	assert.Equal(t, d.prefix, "path")
}

func TestWatch(t *testing.T) {
	d := &Discovery{backend: store.MOCK}
	assert.NoError(t, d.Initialize("127.0.0.1:1234/path", 0))
	s := d.store.(*store.Mock)

	mockCh := make(chan []*store.KVPair)

	// The first watch will fail.
	s.On("WatchTree", "path", mock.Anything).Return(mockCh, errors.New("test error")).Once()
	// The second one will succeed.
	s.On("WatchTree", "path", mock.Anything).Return(mockCh, nil).Once()
	expected := discovery.Entries{
		&discovery.Entry{Host: "1.1.1.1", Port: "1111"},
		&discovery.Entry{Host: "2.2.2.2", Port: "2222"},
	}
	kvs := []*store.KVPair{
		{Key: "path/1.1.1.1", Value: []byte("1.1.1.1:1111")},
		{Key: "path/1.1.1.1", Value: []byte("2.2.2.2:2222")},
	}

	stopCh := make(chan struct{})
	ch, errCh := d.Watch(stopCh)

	// It should fire an error since the first WatchRange call failed.
	assert.EqualError(t, <-errCh, "test error")
	// We have to drain the error channel otherwise Watch will get stuck.
	go func() {
		for _ = range errCh {
		}
	}()

	// Push the entries into the store channel and make sure discovery emits.
	mockCh <- kvs
	assert.Equal(t, <-ch, expected)

	// Add a new entry.
	expected = append(expected, &discovery.Entry{Host: "3.3.3.3", Port: "3333"})
	kvs = append(kvs, &store.KVPair{Key: "path/3.3.3.3", Value: []byte("3.3.3.3:3333")})
	mockCh <- kvs
	assert.Equal(t, <-ch, expected)

	// Make sure that if an error occurs it retries.
	// This third call to WatchTree will be checked later by AssertExpectations.
	s.On("WatchTree", "path", mock.Anything).Return(mockCh, nil)
	close(mockCh)
	// Give it enough time to call WatchTree.
	time.Sleep(3)

	// Stop and make sure it closes all channels.
	close(stopCh)
	assert.Nil(t, <-ch)
	assert.Nil(t, <-errCh)

	s.AssertExpectations(t)
}
<|endoftext|>


<commit_before>package cmd

import (
	"fmt"
	"reflect"
	"testing"

	"github.com/bouk/monkey"
	"github.com/evandroflores/claimr/model"
	"github.com/stretchr/testify/assert"
)

func TestTryToAddBigName(t *testing.T) {

	containerName := "lorem-ipsum-container-big-name"
	teamName := "TestTeam"
	channelName := "TestChannel"
	userName := "user"

	mockResponse, patchReply := createMockReply(t, fmt.Sprintf("try a smaller container name up to %d characters", model.MaxNameSize))
	patchGetEvent := createMockEvent(t, teamName, channelName, userName)
	mockRequest, patchParam := createMockRequest(t, map[string]string{"container-name": containerName})

	add(mockRequest, mockResponse)

	patchReply.Unpatch()
	patchGetEvent.Unpatch()
	patchParam.Unpatch()
}

func TestTryToAddExistentContainer(t *testing.T) {
	containerName := "my-container-exists"
	teamName := "TestTeam"
	channelName := "TestChannel"
	userName := "user"

	container := model.Container{TeamID: teamName, ChannelID: channelName, Name: containerName}
	err := container.Add()
	defer container.Delete()

	assert.NoError(t, err)

	mockResponse, patchReply := createMockReply(t, Messages["same-name"])
	patchGetEvent := createMockEvent(t, teamName, channelName, userName)
	mockRequest, patchParam := createMockRequest(t, map[string]string{"container-name": containerName})

	add(mockRequest, mockResponse)

	patchReply.Unpatch()
	patchGetEvent.Unpatch()
	patchParam.Unpatch()
}

func TestAddError(t *testing.T) {

	guard := monkey.PatchInstanceMethod(reflect.TypeOf(model.Container{}), "Add",
		func(container model.Container) error {
			return fmt.Errorf("simulated error")
		})
	defer guard.Unpatch()

	containerName := "my-container-ok"
	teamName := "TestTeam"
	channelName := "TestChannel"
	userName := "user"

	mockResponse, patchReply := createMockReply(t, "simulated error")
	patchGetEvent := createMockEvent(t, teamName, channelName, userName)
	mockRequest, patchParam := createMockRequest(t, map[string]string{"container-name": containerName})

	add(mockRequest, mockResponse)

	patchReply.Unpatch()
	patchGetEvent.Unpatch()
	patchParam.Unpatch()
}

func TestAddContainer(t *testing.T) {
	containerName := "my-container-ok"
	teamName := "TestTeam"
	channelName := "TestChannel"
	userName := "user"

	msg := fmt.Sprintf(Messages["added-to-channel"], containerName, channelName)
	mockResponse, patchReply := createMockReply(t, msg)
	patchGetEvent := createMockEvent(t, teamName, channelName, userName)
	mockRequest, patchParam := createMockRequest(t, map[string]string{"container-name": containerName})

	defer func() {
		container, _ := model.GetContainer(teamName, channelName, containerName)
		container.Delete()
	}()

	add(mockRequest, mockResponse)

	patchReply.Unpatch()
	patchGetEvent.Unpatch()
	patchParam.Unpatch()
}
<commit_msg>cmd.add.tests Messages<commit_after>package cmd

import (
	"fmt"
	"reflect"
	"testing"

	"github.com/bouk/monkey"
	"github.com/evandroflores/claimr/model"
	"github.com/stretchr/testify/assert"
)

func TestTryToAddBigName(t *testing.T) {

	containerName := "lorem-ipsum-container-big-name"
	teamName := "TestTeam"
	channelName := "TestChannel"
	userName := "user"

	mockResponse, patchReply := createMockReply(t, Messages["name-too-big"])
	patchGetEvent := createMockEvent(t, teamName, channelName, userName)
	mockRequest, patchParam := createMockRequest(t, map[string]string{"container-name": containerName})

	add(mockRequest, mockResponse)

	patchReply.Unpatch()
	patchGetEvent.Unpatch()
	patchParam.Unpatch()
}

func TestTryToAddExistentContainer(t *testing.T) {
	containerName := "my-container-exists"
	teamName := "TestTeam"
	channelName := "TestChannel"
	userName := "user"

	container := model.Container{TeamID: teamName, ChannelID: channelName, Name: containerName}
	err := container.Add()
	defer container.Delete()

	assert.NoError(t, err)

	mockResponse, patchReply := createMockReply(t, Messages["same-name"])
	patchGetEvent := createMockEvent(t, teamName, channelName, userName)
	mockRequest, patchParam := createMockRequest(t, map[string]string{"container-name": containerName})

	add(mockRequest, mockResponse)

	patchReply.Unpatch()
	patchGetEvent.Unpatch()
	patchParam.Unpatch()
}

func TestAddError(t *testing.T) {

	guard := monkey.PatchInstanceMethod(reflect.TypeOf(model.Container{}), "Add",
		func(container model.Container) error {
			return fmt.Errorf("simulated error")
		})
	defer guard.Unpatch()

	containerName := "my-container-ok"
	teamName := "TestTeam"
	channelName := "TestChannel"
	userName := "user"

	mockResponse, patchReply := createMockReply(t, "simulated error")
	patchGetEvent := createMockEvent(t, teamName, channelName, userName)
	mockRequest, patchParam := createMockRequest(t, map[string]string{"container-name": containerName})

	add(mockRequest, mockResponse)

	patchReply.Unpatch()
	patchGetEvent.Unpatch()
	patchParam.Unpatch()
}

func TestAddContainer(t *testing.T) {
	containerName := "my-container-ok"
	teamName := "TestTeam"
	channelName := "TestChannel"
	userName := "user"

	msg := fmt.Sprintf(Messages["added-to-channel"], containerName, channelName)
	mockResponse, patchReply := createMockReply(t, msg)
	patchGetEvent := createMockEvent(t, teamName, channelName, userName)
	mockRequest, patchParam := createMockRequest(t, map[string]string{"container-name": containerName})

	defer func() {
		container, _ := model.GetContainer(teamName, channelName, containerName)
		container.Delete()
	}()

	add(mockRequest, mockResponse)

	patchReply.Unpatch()
	patchGetEvent.Unpatch()
	patchParam.Unpatch()
}
<|endoftext|>


<commit_before>package cmd

import (
	"fmt"
	"log"
	"net"
	"net/http"
	"net/http/httputil"
	"net/url"
	"os"
	"os/exec"
	"path/filepath"
	"strings"

	"github.com/justincarter/docker-workbench/machine"
	"github.com/urfave/cli"
)

// Commands config
var Commands = []cli.Command{
	{
		Name:   "create",
		Usage:  "Create a new workbench machine in the current directory",
		Action: Create,
	},
	{
		Name:   "up",
		Usage:  "Start the workbench machine and show details",
		Action: Up,
	},
	{
		Name:   "proxy",
		Usage:  "Start a reverse proxy to the app in the current directory",
		Action: Proxy,
	},
}

// FlightCheck helper checks for prerequisite commands
func FlightCheck() error {

	toolbox := []string{"docker", "docker-machine", "docker-compose"}
	missing := []string{}
	for _, c := range toolbox {
		if _, err := exec.LookPath(c); err != nil {
			missing = append(missing, c)
		}
	}
	if len(missing) > 0 {
		return fmt.Errorf("docker-workbench: %s was not found. Make sure you have installed Docker Toolbox", strings.Join(missing, ", "))
	}
	if _, err := exec.LookPath(machine.VBoxManagePath()); err != nil {
		return fmt.Errorf("docker-workbench: VBoxManage was not found. Make sure you have installed VirtualBox")
	}

	return nil
}

// NotFound command
func NotFound(c *cli.Context, command string) {
	fmt.Printf("docker-workbench: '%s' is not a docker-workbench command. See 'docker-workbench help'.", command)
	os.Exit(1)
}

// Version command
func Version(c *cli.Context) {
	fmt.Printf("v%s", c.App.Version)
}

// Create command
func Create(c *cli.Context) error {

	// get name from the current working directory
	workdir, _ := os.Getwd()
	name := filepath.Base(workdir)

	if !machine.Exists(name) {
		machine.Create(name)
		machine.EvalEnv(name)

		fmt.Println("Configuring bootsync.sh...")
		machine.SSH(name, "sudo echo 'sudo mkdir -p /workbench && sudo mount -t vboxsf -o uid=1000,gid=50 workbench /workbench' >  /tmp/bootsync.sh")
		machine.SSH(name, "sudo cp /tmp/bootsync.sh /var/lib/boot2docker/bootsync.sh")
		machine.SSH(name, "sudo chmod +x /var/lib/boot2docker/bootsync.sh")

		fmt.Println("Installing workbench apps...")
		machine.SSH(name, "docker run -d --restart=always --name=workbench_proxy -p 80:80 -v '/var/run/docker.sock:/tmp/docker.sock:ro' daemonite/workbench-proxy")
		machine.Stop(name)

		fmt.Println("Adding /workbench shared folder...")
		machine.ShareFolder(name, workdir)
	}

	machine.Start(name)
	machine.EvalEnv(name)
	machine.EvalHint(name, false)
	printWorkbenchInfo("*", name)

	return nil
}

// Up command
func Up(c *cli.Context) error {

	app, name, err := getWorkbenchContext(false)

	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}

	machine.Start(name)
	machine.EvalHint(name, true)
	if app != "*" {
		fmt.Println("\nStart the application:")
		fmt.Println("docker-compose up")
	}
	printWorkbenchInfo(app, name)

	return nil
}

// Proxy command
func Proxy(c *cli.Context) error {
	app, name, err := getWorkbenchContext(true)
	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}
	port := getProxyPort(c)

	ip, ok := machine.IP(name)
	if ok == true {
		fmt.Printf("Starting reverse proxy on port %s...\n", port)
		ips, err := getProxyIPs()
		if err != nil {
			fmt.Println(err)
			os.Exit(1)
		}
		fmt.Printf("Listening on:\n\n")
		for _, thisip := range ips {
			fmt.Printf("http://%s.%s.nip.io:%s/\n", app, thisip, port)
		}
		fmt.Println("\nPress Ctrl-C to terminate proxy")
		startProxy(app, name, ip, port)
	} else {
		fmt.Println("\nCould not find the IP address for this workbench. Have you run docker-workbench up?")
		os.Exit(1)
	}

	return nil
}

// getWorkbenchContext finds the application name and workbench machine name  from the current directory
func getWorkbenchContext(requireApp bool) (app string, name string, err error) {
	err = nil
	app = "*"
	// get name from the current working directory
	workdir, _ := os.Getwd()
	name = filepath.Base(workdir)
	if !machine.Exists(name) {
		// get name from the parent of the current working directory
		app = name
		name = filepath.Base(filepath.Dir(workdir))

		if !machine.Exists(name) {
			err = fmt.Errorf("Workbench machine '%s' not found.", app)
		}
	}
	if requireApp == true && app == "*" {
		err = fmt.Errorf("\nCould not find the app to proxy for Workbench machine '%s'. Try running from an app directory?", name)
	}
	return
}

// printWorkbenchInfo prints the application URL using the given app name and workbench machine IP
func printWorkbenchInfo(app, name string) {
	ip, ok := machine.IP(name)
	if ok == true {
		fmt.Println("\nBrowse the workbench using:")
		fmt.Printf("http://%s.%s.nip.io/\n", app, ip)
	} else {
		fmt.Println("\nCould not find the IP address for this workbench")
		os.Exit(1)
	}
}

// getProxyIPs returns a slice of IP address strings that should be browsable when using the Proxy command
func getProxyIPs() ([]string, error) {
	var e error
	ips := []string{}

	ifaces, err := net.Interfaces()
	if err != nil {
		e = fmt.Errorf("\nCould not find local network interfaces")
	}

	for _, i := range ifaces {
		addrs, _ := i.Addrs()
		for _, addr := range addrs {
			var ip string
			switch v := addr.(type) {
			case *net.IPNet:
				ip = v.IP.String()
			case *net.IPAddr:
				ip = v.IP.String()
			}
			// output valid local IPv4 addresses, excluding loopbacks and docker machine default interface
			if machine.ValidIPv4(ip) && ip != "127.0.0.1" && ip != "192.168.99.1" && strings.Split(ip, ".")[0] != "169" {
				ips = append(ips, ip)
			}
		}
	}
	if len(ips) == 0 {
		e = fmt.Errorf("\nCould not find local network interfaces")
	}

	return ips, e
}

func getProxyPort(c *cli.Context) string {
	return "9999"
}

func startProxy(app, name, ip, port string) {
	l, err := net.Listen("tcp4", fmt.Sprintf(":%s", port))
	if err != nil {
		log.Fatal(err)
	}
	proxy := httputil.NewSingleHostReverseProxy(&url.URL{
		Scheme: "http",
		Host:   fmt.Sprintf("%s.%s.nip.io", app, ip),
	})
	log.Fatal(http.Serve(l, proxy))
}
<commit_msg>refactor proxy ip lookups and validation<commit_after>package cmd

import (
	"fmt"
	"log"
	"net"
	"net/http"
	"net/http/httputil"
	"net/url"
	"os"
	"os/exec"
	"path/filepath"
	"strings"

	"github.com/justincarter/docker-workbench/machine"
	"github.com/urfave/cli"
)

// Commands config
var Commands = []cli.Command{
	{
		Name:   "create",
		Usage:  "Create a new workbench machine in the current directory",
		Action: Create,
	},
	{
		Name:   "up",
		Usage:  "Start the workbench machine and show details",
		Action: Up,
	},
	{
		Name:   "proxy",
		Usage:  "Start a reverse proxy to the app in the current directory",
		Action: Proxy,
	},
}

// FlightCheck helper checks for prerequisite commands
func FlightCheck() error {

	toolbox := []string{"docker", "docker-machine", "docker-compose"}
	missing := []string{}
	for _, c := range toolbox {
		if _, err := exec.LookPath(c); err != nil {
			missing = append(missing, c)
		}
	}
	if len(missing) > 0 {
		return fmt.Errorf("docker-workbench: %s was not found. Make sure you have installed Docker Toolbox", strings.Join(missing, ", "))
	}
	if _, err := exec.LookPath(machine.VBoxManagePath()); err != nil {
		return fmt.Errorf("docker-workbench: VBoxManage was not found. Make sure you have installed VirtualBox")
	}

	return nil
}

// NotFound command
func NotFound(c *cli.Context, command string) {
	fmt.Printf("docker-workbench: '%s' is not a docker-workbench command. See 'docker-workbench help'.", command)
	os.Exit(1)
}

// Version command
func Version(c *cli.Context) {
	fmt.Printf("v%s", c.App.Version)
}

// Create command
func Create(c *cli.Context) error {

	// get name from the current working directory
	workdir, _ := os.Getwd()
	name := filepath.Base(workdir)

	if !machine.Exists(name) {
		machine.Create(name)
		machine.EvalEnv(name)

		fmt.Println("Configuring bootsync.sh...")
		machine.SSH(name, "sudo echo 'sudo mkdir -p /workbench && sudo mount -t vboxsf -o uid=1000,gid=50 workbench /workbench' >  /tmp/bootsync.sh")
		machine.SSH(name, "sudo cp /tmp/bootsync.sh /var/lib/boot2docker/bootsync.sh")
		machine.SSH(name, "sudo chmod +x /var/lib/boot2docker/bootsync.sh")

		fmt.Println("Installing workbench apps...")
		machine.SSH(name, "docker run -d --restart=always --name=workbench_proxy -p 80:80 -v '/var/run/docker.sock:/tmp/docker.sock:ro' daemonite/workbench-proxy")
		machine.Stop(name)

		fmt.Println("Adding /workbench shared folder...")
		machine.ShareFolder(name, workdir)
	}

	machine.Start(name)
	machine.EvalEnv(name)
	machine.EvalHint(name, false)
	printWorkbenchInfo("*", name)

	return nil
}

// Up command
func Up(c *cli.Context) error {

	app, name, err := getWorkbenchContext(false)

	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}

	machine.Start(name)
	machine.EvalHint(name, true)
	if app != "*" {
		fmt.Println("\nStart the application:")
		fmt.Println("docker-compose up")
	}
	printWorkbenchInfo(app, name)

	return nil
}

// Proxy command
func Proxy(c *cli.Context) error {
	app, name, err := getWorkbenchContext(true)
	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}
	port := getProxyPort(c)

	ip, ok := machine.IP(name)
	if ok == true {
		fmt.Printf("Starting reverse proxy on port %s...\n", port)
		ips, err := getProxyIPs()
		if err != nil {
			fmt.Println(err)
			os.Exit(1)
		}
		fmt.Printf("Listening on:\n\n")
		for _, thisip := range ips {
			fmt.Printf("http://%s.%s.nip.io:%s/\n", app, thisip, port)
		}
		fmt.Println("\nPress Ctrl-C to terminate proxy")
		startProxy(app, name, ip, port)
	} else {
		fmt.Println("\nCould not find the IP address for this workbench. Have you run docker-workbench up?")
		os.Exit(1)
	}

	return nil
}

// getWorkbenchContext finds the application name and workbench machine name  from the current directory
func getWorkbenchContext(requireApp bool) (app string, name string, err error) {
	err = nil
	app = "*"
	// get name from the current working directory
	workdir, _ := os.Getwd()
	name = filepath.Base(workdir)
	if !machine.Exists(name) {
		// get name from the parent of the current working directory
		app = name
		name = filepath.Base(filepath.Dir(workdir))

		if !machine.Exists(name) {
			err = fmt.Errorf("Workbench machine '%s' not found.", app)
		}
	}
	if requireApp == true && app == "*" {
		err = fmt.Errorf("\nCould not find the app to proxy for Workbench machine '%s'. Try running from an app directory?", name)
	}
	return
}

// printWorkbenchInfo prints the application URL using the given app name and workbench machine IP
func printWorkbenchInfo(app, name string) {
	ip, ok := machine.IP(name)
	if ok == true {
		fmt.Println("\nBrowse the workbench using:")
		fmt.Printf("http://%s.%s.nip.io/\n", app, ip)
	} else {
		fmt.Println("\nCould not find the IP address for this workbench")
		os.Exit(1)
	}
}

// getProxyIPs returns a slice of IP address strings that should be browsable when using the Proxy command
func getProxyIPs() ([]string, error) {
	var e error

	ifaces, err := net.Interfaces()
	if err != nil {
		e = fmt.Errorf("\nCould not find local network interfaces")
	}

	ips := getIPsFromIfaces(ifaces)
	if len(ips) == 0 {
		e = fmt.Errorf("\nCould not find local network interfaces")
	}

	return ips, e
}

func getIPsFromIfaces(ifaces []net.Interface) []string {
	ips := []string{}
	for _, i := range ifaces {
		addrs, _ := i.Addrs()
		for _, addr := range addrs {
			var ip string
			switch v := addr.(type) {
			case *net.IPNet:
				ip = v.IP.String()
			case *net.IPAddr:
				ip = v.IP.String()
			}
			if validProxyIP(ip) {
				ips = append(ips, ip)
			}
		}
	}
	return ips
}

func validProxyIP(ip string) bool {
	// disallow non-IPv4 addresses, loopback interfaces and docker machine default interface
	if !machine.ValidIPv4(ip) || ip == "127.0.0.1" || ip == "192.168.99.1" || strings.Split(ip, ".")[0] == "169" {
		return false
	}
	return true
}

func getProxyPort(c *cli.Context) string {
	return "9999"
}

func startProxy(app, name, ip, port string) {
	l, err := net.Listen("tcp4", fmt.Sprintf(":%s", port))
	if err != nil {
		log.Fatal(err)
	}
	proxy := httputil.NewSingleHostReverseProxy(&url.URL{
		Scheme: "http",
		Host:   fmt.Sprintf("%s.%s.nip.io", app, ip),
	})
	log.Fatal(http.Serve(l, proxy))
}
<|endoftext|>


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

package main

import (
	"encoding/json"
	"flag"
	"fmt"
	"log"
	"os"
	"path/filepath"
	"strings"

	"github.com/golang/dep"
	"github.com/pkg/errors"
	"github.com/sdboyer/gps"
)

const initShortHelp = `Initialize a new project with manifest and lock files`
const initLongHelp = `
Initialize the project at filepath root by parsing its dependencies and writing
manifest and lock files. If root isn't specified, use the current directory.

The version of each dependency will reflect the current state of the GOPATH. If
a dependency doesn't exist in the GOPATH, it won't be written to the manifest,
but it will be solved-for, and will appear in the lock.

Note: init may use the network to solve the dependency graph.

Note: init does NOT vendor dependencies at the moment. See dep ensure.
`

func (cmd *initCommand) Name() string      { return "init" }
func (cmd *initCommand) Args() string      { return "[root]" }
func (cmd *initCommand) ShortHelp() string { return initShortHelp }
func (cmd *initCommand) LongHelp() string  { return initLongHelp }
func (cmd *initCommand) Hidden() bool      { return false }

func (cmd *initCommand) Register(fs *flag.FlagSet) {}

type initCommand struct{}

func trimPathPrefix(p1, p2 string) string {
	if filepath.HasPrefix(p1, p2) {
		return p1[len(p2):]
	}
	return p1
}

func (cmd *initCommand) Run(ctx *dep.Ctx, args []string) error {
	if len(args) > 1 {
		return errors.Errorf("too many args (%d)", len(args))
	}

	var root string
	if len(args) <= 0 {
		wd, err := os.Getwd()
		if err != nil {
			return err
		}
		root = wd
	} else {
		root = args[0]
	}

	mf := filepath.Join(root, dep.ManifestName)
	lf := filepath.Join(root, dep.LockName)

	mok, err := dep.IsRegular(mf)
	if err != nil {
		return err
	}
	if mok {
		return fmt.Errorf("manifest file %q already exists", mf)
	}
	// Manifest file does not exist.

	lok, err := dep.IsRegular(lf)
	if err != nil {
		return err
	}
	if lok {
		return fmt.Errorf("invalid state: manifest %q does not exist, but lock %q does", mf, lf)
	}

	cpr, err := ctx.SplitAbsoluteProjectRoot(root)
	if err != nil {
		return errors.Wrap(err, "determineProjectRoot")
	}
	vlogf("Finding dependencies for %q...", cpr)
	pkgT, err := gps.ListPackages(root, cpr)
	if err != nil {
		return errors.Wrap(err, "gps.ListPackages")
	}
	vlogf("Found %d dependencies.", len(pkgT.Packages))
	sm, err := ctx.SourceManager()
	if err != nil {
		return errors.Wrap(err, "getSourceManager")
	}
	sm.UseDefaultSignalHandling()
	defer sm.Release()

	pd, err := getProjectData(ctx, pkgT, cpr, sm)
	if err != nil {
		return err
	}
	m := dep.Manifest{
		Dependencies: pd.constraints,
	}

	// Make an initial lock from what knowledge we've collected about the
	// versions on disk
	l := dep.Lock{
		P: make([]gps.LockedProject, 0, len(pd.ondisk)),
	}

	for pr, v := range pd.ondisk {
		// That we have to chop off these path prefixes is a symptom of
		// a problem in gps itself
		pkgs := make([]string, 0, len(pd.dependencies[pr]))
		prslash := string(pr) + "/"
		for _, pkg := range pd.dependencies[pr] {
			if pkg == string(pr) {
				pkgs = append(pkgs, ".")
			} else {
				pkgs = append(pkgs, trimPathPrefix(pkg, prslash))
			}
		}

		l.P = append(l.P, gps.NewLockedProject(
			gps.ProjectIdentifier{ProjectRoot: pr}, v, pkgs),
		)
	}

	sw := dep.SafeWriter{
		Root:          root,
		SourceManager: sm,
		Manifest:      &m,
	}

	if len(pd.notondisk) > 0 {
		vlogf("Solving...")
		params := gps.SolveParameters{
			RootDir:         root,
			RootPackageTree: pkgT,
			Manifest:        &m,
			Lock:            &l,
		}

		if *verbose {
			params.Trace = true
			params.TraceLogger = log.New(os.Stderr, "", 0)
		}
		s, err := gps.Prepare(params, sm)
		if err != nil {
			return errors.Wrap(err, "prepare solver")
		}

		soln, err := s.Solve()
		if err != nil {
			handleAllTheFailuresOfTheWorld(err)
			return err
		}
		sw.Lock = dep.LockFromInterface(soln)
	} else {
		sw.Lock = &l
	}

	vlogf("Writing manifest and lock files.")

	if err := sw.WriteAllSafe(true); err != nil {
		return errors.Wrap(err, "safe write of manifest and lock")
	}

	return nil
}

// contains checks if a array of strings contains a value
func contains(a []string, b string) bool {
	for _, v := range a {
		if b == v {
			return true
		}
	}
	return false
}

// isStdLib reports whether $GOROOT/src/path should be considered
// part of the standard distribution. For historical reasons we allow people to add
// their own code to $GOROOT instead of using $GOPATH, but we assume that
// code will start with a domain name (dot in the first element).
// This was loving taken from src/cmd/go/pkg.go in Go's code (isStandardImportPath).
func isStdLib(path string) bool {
	i := strings.Index(path, "/")
	if i < 0 {
		i = len(path)
	}
	elem := path[:i]
	return !strings.Contains(elem, ".")
}

// TODO solve failures can be really creative - we need to be similarly creative
// in handling them and informing the user appropriately
func handleAllTheFailuresOfTheWorld(err error) {
	fmt.Printf("solve error: %s", err)
}

func writeFile(path string, in json.Marshaler) error {
	f, err := os.Create(path)
	if err != nil {
		return err
	}
	defer f.Close()

	b, err := in.MarshalJSON()
	if err != nil {
		return err
	}

	_, err = f.Write(b)
	return err
}

func hasImportPathPrefix(s, prefix string) bool {
	if s == prefix {
		return true
	}
	return strings.HasPrefix(s, prefix+"/")
}

type projectData struct {
	constraints  gps.ProjectConstraints          // constraints that could be found
	dependencies map[gps.ProjectRoot][]string    // all dependencies (imports) found by project root
	notondisk    map[gps.ProjectRoot]bool        // projects that were not found on disk
	ondisk       map[gps.ProjectRoot]gps.Version // projects that were found on disk
}

func getProjectData(ctx *dep.Ctx, pkgT gps.PackageTree, cpr string, sm *gps.SourceMgr) (projectData, error) {
	vlogf("Building dependency graph...")

	constraints := make(gps.ProjectConstraints)
	dependencies := make(map[gps.ProjectRoot][]string)
	packages := make(map[string]bool)
	notondisk := make(map[gps.ProjectRoot]bool)
	ondisk := make(map[gps.ProjectRoot]gps.Version)

	syncDep := func(pr gps.ProjectRoot, sm *gps.SourceMgr) {
		message := "Cached"
		if err := sm.SyncSourceFor(gps.ProjectIdentifier{ProjectRoot: pr}); err != nil {
			message = "Unable to cache"
		}
		fmt.Fprintf(os.Stderr, "%s %s\n", message, pr)
	}

	for _, v := range pkgT.Packages {
		// TODO: Some errors maybe should not be skipped ;-)
		if v.Err != nil {
			vlogf("%v", v.Err)
			continue
		}
		vlogf("Package %q, analyzing...", v.P.ImportPath)

		for _, ip := range v.P.Imports {
			if isStdLib(ip) {
				continue
			}
			if hasImportPathPrefix(ip, cpr) {
				// Don't analyze imports from the current project.
				continue
			}
			pr, err := sm.DeduceProjectRoot(ip)
			if err != nil {
				return projectData{}, errors.Wrap(err, "sm.DeduceProjectRoot") // TODO: Skip and report ?
			}

			packages[ip] = true
			if _, ok := dependencies[pr]; ok {
				if !contains(dependencies[pr], ip) {
					dependencies[pr] = append(dependencies[pr], ip)
				}

				continue
			}
			go syncDep(pr, sm)

			vlogf("Package %q has import %q, analyzing...", v.P.ImportPath, ip)

			dependencies[pr] = []string{ip}
			v, err := ctx.VersionInWorkspace(pr)
			if err != nil {
				notondisk[pr] = true
				vlogf("Could not determine version for %q, omitting from generated manifest", pr)
				continue
			}

			ondisk[pr] = v
			pp := gps.ProjectProperties{}
			switch v.Type() {
			case gps.IsBranch, gps.IsVersion, gps.IsRevision:
				pp.Constraint = v
			case gps.IsSemver:
				c, _ := gps.NewSemverConstraint("^" + v.String())
				pp.Constraint = c
			}

			constraints[pr] = pp
		}
	}

	vlogf("Analyzing transitive imports...")
	// Explore the packages we've found for transitive deps, either
	// completing the lock or identifying (more) missing projects that we'll
	// need to ask gps to solve for us.
	colors := make(map[string]uint8)
	const (
		white uint8 = iota
		grey
		black
	)

	// cache of PackageTrees, so we don't parse projects more than once
	ptrees := make(map[gps.ProjectRoot]gps.PackageTree)

	// depth-first traverser
	var dft func(string) error
	dft = func(pkg string) error {
		switch colors[pkg] {
		case white:
			vlogf("Analyzing %q...", pkg)
			colors[pkg] = grey

			pr, err := sm.DeduceProjectRoot(pkg)
			if err != nil {
				return errors.Wrap(err, "could not deduce project root for "+pkg)
			}

			// We already visited this project root earlier via some other
			// pkg within it, and made the decision that it's not on disk.
			// Respect that decision, and pop the stack.
			if notondisk[pr] {
				colors[pkg] = black
				return nil
			}

			ptree, has := ptrees[pr]
			if !has {
				// It's fine if the root does not exist - it indicates that this
				// project is not present in the workspace, and so we need to
				// solve to deal with this dep.
				r := filepath.Join(ctx.GOPATH, "src", string(pr))
				_, err := os.Lstat(r)
				if os.IsNotExist(err) {
					colors[pkg] = black
					notondisk[pr] = true
					return nil
				}

				ptree, err = gps.ListPackages(r, string(pr))
				if err != nil {
					// Any error here other than an a nonexistent dir (which
					// can't happen because we covered that case above) is
					// probably critical, so bail out.
					return errors.Wrap(err, "gps.ListPackages")
				}
				ptrees[pr] = ptree
			}

			// Get a reachmap that includes main pkgs (even though importing
			// them is an error, what we're checking right now is simply whether
			// there's a package with go code present on disk), and does not
			// backpropagate errors (again, because our only concern right now
			// is package existence).
			rm, errmap := ptree.ToReachMap(true, false, false, nil)
			reached, ok := rm[pkg]
			if !ok {
				colors[pkg] = black
				// not on disk...
				notondisk[pr] = true
				return nil
			}
			if _, ok := errmap[pkg]; ok {
				// The package is on disk, but contains some errors.
				colors[pkg] = black
				return nil
			}

			if _, ok := dependencies[pr]; ok {
				if !contains(dependencies[pr], pkg) {
					dependencies[pr] = append(dependencies[pr], pkg)
				}
			} else {
				dependencies[pr] = []string{pkg}
				go syncDep(pr, sm)
			}

			// project must be on disk at this point; question is
			// whether we're first seeing it here, in the transitive
			// exploration, or if it arose in the direct dep parts
			if _, in := ondisk[pr]; !in {
				v, err := ctx.VersionInWorkspace(pr)
				if err != nil {
					colors[pkg] = black
					notondisk[pr] = true
					return nil
				}
				ondisk[pr] = v
			}

			// recurse
			for _, rpkg := range reached.External {
				if isStdLib(rpkg) {
					continue
				}

				err := dft(rpkg)
				if err != nil {
					return err
				}
			}

			colors[pkg] = black
		case grey:
			return fmt.Errorf("Import cycle detected on %s", pkg)
		}
		return nil
	}

	// run the depth-first traversal from the set of immediate external
	// package imports we found in the current project
	for pkg := range packages {
		err := dft(pkg)
		if err != nil {
			return projectData{}, err // already errors.Wrap()'d internally
		}
	}

	pd := projectData{
		constraints:  constraints,
		dependencies: dependencies,
		notondisk:    notondisk,
		ondisk:       ondisk,
	}
	return pd, nil
}
<commit_msg>Adding a newline to the end of solve err<commit_after>// Copyright 2016 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package main

import (
	"encoding/json"
	"flag"
	"fmt"
	"log"
	"os"
	"path/filepath"
	"strings"

	"github.com/golang/dep"
	"github.com/pkg/errors"
	"github.com/sdboyer/gps"
)

const initShortHelp = `Initialize a new project with manifest and lock files`
const initLongHelp = `
Initialize the project at filepath root by parsing its dependencies and writing
manifest and lock files. If root isn't specified, use the current directory.

The version of each dependency will reflect the current state of the GOPATH. If
a dependency doesn't exist in the GOPATH, it won't be written to the manifest,
but it will be solved-for, and will appear in the lock.

Note: init may use the network to solve the dependency graph.

Note: init does NOT vendor dependencies at the moment. See dep ensure.
`

func (cmd *initCommand) Name() string      { return "init" }
func (cmd *initCommand) Args() string      { return "[root]" }
func (cmd *initCommand) ShortHelp() string { return initShortHelp }
func (cmd *initCommand) LongHelp() string  { return initLongHelp }
func (cmd *initCommand) Hidden() bool      { return false }

func (cmd *initCommand) Register(fs *flag.FlagSet) {}

type initCommand struct{}

func trimPathPrefix(p1, p2 string) string {
	if filepath.HasPrefix(p1, p2) {
		return p1[len(p2):]
	}
	return p1
}

func (cmd *initCommand) Run(ctx *dep.Ctx, args []string) error {
	if len(args) > 1 {
		return errors.Errorf("too many args (%d)", len(args))
	}

	var root string
	if len(args) <= 0 {
		wd, err := os.Getwd()
		if err != nil {
			return err
		}
		root = wd
	} else {
		root = args[0]
	}

	mf := filepath.Join(root, dep.ManifestName)
	lf := filepath.Join(root, dep.LockName)

	mok, err := dep.IsRegular(mf)
	if err != nil {
		return err
	}
	if mok {
		return fmt.Errorf("manifest file %q already exists", mf)
	}
	// Manifest file does not exist.

	lok, err := dep.IsRegular(lf)
	if err != nil {
		return err
	}
	if lok {
		return fmt.Errorf("invalid state: manifest %q does not exist, but lock %q does", mf, lf)
	}

	cpr, err := ctx.SplitAbsoluteProjectRoot(root)
	if err != nil {
		return errors.Wrap(err, "determineProjectRoot")
	}
	vlogf("Finding dependencies for %q...", cpr)
	pkgT, err := gps.ListPackages(root, cpr)
	if err != nil {
		return errors.Wrap(err, "gps.ListPackages")
	}
	vlogf("Found %d dependencies.", len(pkgT.Packages))
	sm, err := ctx.SourceManager()
	if err != nil {
		return errors.Wrap(err, "getSourceManager")
	}
	sm.UseDefaultSignalHandling()
	defer sm.Release()

	pd, err := getProjectData(ctx, pkgT, cpr, sm)
	if err != nil {
		return err
	}
	m := dep.Manifest{
		Dependencies: pd.constraints,
	}

	// Make an initial lock from what knowledge we've collected about the
	// versions on disk
	l := dep.Lock{
		P: make([]gps.LockedProject, 0, len(pd.ondisk)),
	}

	for pr, v := range pd.ondisk {
		// That we have to chop off these path prefixes is a symptom of
		// a problem in gps itself
		pkgs := make([]string, 0, len(pd.dependencies[pr]))
		prslash := string(pr) + "/"
		for _, pkg := range pd.dependencies[pr] {
			if pkg == string(pr) {
				pkgs = append(pkgs, ".")
			} else {
				pkgs = append(pkgs, trimPathPrefix(pkg, prslash))
			}
		}

		l.P = append(l.P, gps.NewLockedProject(
			gps.ProjectIdentifier{ProjectRoot: pr}, v, pkgs),
		)
	}

	sw := dep.SafeWriter{
		Root:          root,
		SourceManager: sm,
		Manifest:      &m,
	}

	if len(pd.notondisk) > 0 {
		vlogf("Solving...")
		params := gps.SolveParameters{
			RootDir:         root,
			RootPackageTree: pkgT,
			Manifest:        &m,
			Lock:            &l,
		}

		if *verbose {
			params.Trace = true
			params.TraceLogger = log.New(os.Stderr, "", 0)
		}
		s, err := gps.Prepare(params, sm)
		if err != nil {
			return errors.Wrap(err, "prepare solver")
		}

		soln, err := s.Solve()
		if err != nil {
			handleAllTheFailuresOfTheWorld(err)
			return err
		}
		sw.Lock = dep.LockFromInterface(soln)
	} else {
		sw.Lock = &l
	}

	vlogf("Writing manifest and lock files.")

	if err := sw.WriteAllSafe(true); err != nil {
		return errors.Wrap(err, "safe write of manifest and lock")
	}

	return nil
}

// contains checks if a array of strings contains a value
func contains(a []string, b string) bool {
	for _, v := range a {
		if b == v {
			return true
		}
	}
	return false
}

// isStdLib reports whether $GOROOT/src/path should be considered
// part of the standard distribution. For historical reasons we allow people to add
// their own code to $GOROOT instead of using $GOPATH, but we assume that
// code will start with a domain name (dot in the first element).
// This was loving taken from src/cmd/go/pkg.go in Go's code (isStandardImportPath).
func isStdLib(path string) bool {
	i := strings.Index(path, "/")
	if i < 0 {
		i = len(path)
	}
	elem := path[:i]
	return !strings.Contains(elem, ".")
}

// TODO solve failures can be really creative - we need to be similarly creative
// in handling them and informing the user appropriately
func handleAllTheFailuresOfTheWorld(err error) {
	fmt.Printf("solve error: %s\n", err)
}

func writeFile(path string, in json.Marshaler) error {
	f, err := os.Create(path)
	if err != nil {
		return err
	}
	defer f.Close()

	b, err := in.MarshalJSON()
	if err != nil {
		return err
	}

	_, err = f.Write(b)
	return err
}

func hasImportPathPrefix(s, prefix string) bool {
	if s == prefix {
		return true
	}
	return strings.HasPrefix(s, prefix+"/")
}

type projectData struct {
	constraints  gps.ProjectConstraints          // constraints that could be found
	dependencies map[gps.ProjectRoot][]string    // all dependencies (imports) found by project root
	notondisk    map[gps.ProjectRoot]bool        // projects that were not found on disk
	ondisk       map[gps.ProjectRoot]gps.Version // projects that were found on disk
}

func getProjectData(ctx *dep.Ctx, pkgT gps.PackageTree, cpr string, sm *gps.SourceMgr) (projectData, error) {
	vlogf("Building dependency graph...")

	constraints := make(gps.ProjectConstraints)
	dependencies := make(map[gps.ProjectRoot][]string)
	packages := make(map[string]bool)
	notondisk := make(map[gps.ProjectRoot]bool)
	ondisk := make(map[gps.ProjectRoot]gps.Version)

	syncDep := func(pr gps.ProjectRoot, sm *gps.SourceMgr) {
		message := "Cached"
		if err := sm.SyncSourceFor(gps.ProjectIdentifier{ProjectRoot: pr}); err != nil {
			message = "Unable to cache"
		}
		fmt.Fprintf(os.Stderr, "%s %s\n", message, pr)
	}

	for _, v := range pkgT.Packages {
		// TODO: Some errors maybe should not be skipped ;-)
		if v.Err != nil {
			vlogf("%v", v.Err)
			continue
		}
		vlogf("Package %q, analyzing...", v.P.ImportPath)

		for _, ip := range v.P.Imports {
			if isStdLib(ip) {
				continue
			}
			if hasImportPathPrefix(ip, cpr) {
				// Don't analyze imports from the current project.
				continue
			}
			pr, err := sm.DeduceProjectRoot(ip)
			if err != nil {
				return projectData{}, errors.Wrap(err, "sm.DeduceProjectRoot") // TODO: Skip and report ?
			}

			packages[ip] = true
			if _, ok := dependencies[pr]; ok {
				if !contains(dependencies[pr], ip) {
					dependencies[pr] = append(dependencies[pr], ip)
				}

				continue
			}
			go syncDep(pr, sm)

			vlogf("Package %q has import %q, analyzing...", v.P.ImportPath, ip)

			dependencies[pr] = []string{ip}
			v, err := ctx.VersionInWorkspace(pr)
			if err != nil {
				notondisk[pr] = true
				vlogf("Could not determine version for %q, omitting from generated manifest", pr)
				continue
			}

			ondisk[pr] = v
			pp := gps.ProjectProperties{}
			switch v.Type() {
			case gps.IsBranch, gps.IsVersion, gps.IsRevision:
				pp.Constraint = v
			case gps.IsSemver:
				c, _ := gps.NewSemverConstraint("^" + v.String())
				pp.Constraint = c
			}

			constraints[pr] = pp
		}
	}

	vlogf("Analyzing transitive imports...")
	// Explore the packages we've found for transitive deps, either
	// completing the lock or identifying (more) missing projects that we'll
	// need to ask gps to solve for us.
	colors := make(map[string]uint8)
	const (
		white uint8 = iota
		grey
		black
	)

	// cache of PackageTrees, so we don't parse projects more than once
	ptrees := make(map[gps.ProjectRoot]gps.PackageTree)

	// depth-first traverser
	var dft func(string) error
	dft = func(pkg string) error {
		switch colors[pkg] {
		case white:
			vlogf("Analyzing %q...", pkg)
			colors[pkg] = grey

			pr, err := sm.DeduceProjectRoot(pkg)
			if err != nil {
				return errors.Wrap(err, "could not deduce project root for "+pkg)
			}

			// We already visited this project root earlier via some other
			// pkg within it, and made the decision that it's not on disk.
			// Respect that decision, and pop the stack.
			if notondisk[pr] {
				colors[pkg] = black
				return nil
			}

			ptree, has := ptrees[pr]
			if !has {
				// It's fine if the root does not exist - it indicates that this
				// project is not present in the workspace, and so we need to
				// solve to deal with this dep.
				r := filepath.Join(ctx.GOPATH, "src", string(pr))
				_, err := os.Lstat(r)
				if os.IsNotExist(err) {
					colors[pkg] = black
					notondisk[pr] = true
					return nil
				}

				ptree, err = gps.ListPackages(r, string(pr))
				if err != nil {
					// Any error here other than an a nonexistent dir (which
					// can't happen because we covered that case above) is
					// probably critical, so bail out.
					return errors.Wrap(err, "gps.ListPackages")
				}
				ptrees[pr] = ptree
			}

			// Get a reachmap that includes main pkgs (even though importing
			// them is an error, what we're checking right now is simply whether
			// there's a package with go code present on disk), and does not
			// backpropagate errors (again, because our only concern right now
			// is package existence).
			rm, errmap := ptree.ToReachMap(true, false, false, nil)
			reached, ok := rm[pkg]
			if !ok {
				colors[pkg] = black
				// not on disk...
				notondisk[pr] = true
				return nil
			}
			if _, ok := errmap[pkg]; ok {
				// The package is on disk, but contains some errors.
				colors[pkg] = black
				return nil
			}

			if _, ok := dependencies[pr]; ok {
				if !contains(dependencies[pr], pkg) {
					dependencies[pr] = append(dependencies[pr], pkg)
				}
			} else {
				dependencies[pr] = []string{pkg}
				go syncDep(pr, sm)
			}

			// project must be on disk at this point; question is
			// whether we're first seeing it here, in the transitive
			// exploration, or if it arose in the direct dep parts
			if _, in := ondisk[pr]; !in {
				v, err := ctx.VersionInWorkspace(pr)
				if err != nil {
					colors[pkg] = black
					notondisk[pr] = true
					return nil
				}
				ondisk[pr] = v
			}

			// recurse
			for _, rpkg := range reached.External {
				if isStdLib(rpkg) {
					continue
				}

				err := dft(rpkg)
				if err != nil {
					return err
				}
			}

			colors[pkg] = black
		case grey:
			return fmt.Errorf("Import cycle detected on %s", pkg)
		}
		return nil
	}

	// run the depth-first traversal from the set of immediate external
	// package imports we found in the current project
	for pkg := range packages {
		err := dft(pkg)
		if err != nil {
			return projectData{}, err // already errors.Wrap()'d internally
		}
	}

	pd := projectData{
		constraints:  constraints,
		dependencies: dependencies,
		notondisk:    notondisk,
		ondisk:       ondisk,
	}
	return pd, nil
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"net"
	"os"
	"os/exec"
	"os/signal"
	"path/filepath"
	"strconv"

	sys "golang.org/x/sys/unix"

	"github.com/derekparker/delve/service"
	"github.com/derekparker/delve/service/rpc"
	"github.com/derekparker/delve/terminal"
	"github.com/spf13/cobra"
)

const version string = "0.6.0.beta"

var (
	Log      bool
	Headless bool
	Addr     string
)

func main() {
	// Main dlv root command.
	rootCommand := &cobra.Command{
		Use:   "dlv",
		Short: "Delve is a debugger for the Go programming language.",
	}
	rootCommand.Flags().StringVarP(&Addr, "listen", "l", "localhost:0", "Debugging server listen address.")
	rootCommand.Flags().BoolVarP(&Log, "log", "", false, "Enable debugging server logging.")
	rootCommand.Flags().BoolVarP(&Headless, "headless", "", false, "Run debug server only, in headless mode.")

	// 'version' subcommand.
	versionCommand := &cobra.Command{
		Use:   "version",
		Short: "Prints version.",
		Run: func(cmd *cobra.Command, args []string) {
			fmt.Println("Delve version: " + version)
		},
	}
	rootCommand.AddCommand(versionCommand)

	// 'run' subcommand.
	runCommand := &cobra.Command{
		Use:   "run",
		Short: "Compile and begin debugging program.",
		Long: `Compiles your program with optimizations disabled,
starts and attaches to it, and enable you to immediately begin debugging your program.`,
		Run: func(cmd *cobra.Command, args []string) {
			const debugname = "debug"
			goBuild := exec.Command("go", "build", "-o", debugname, "-gcflags", "-N -l")
			goBuild.Stderr = os.Stderr
			err := goBuild.Run()
			if err != nil {
				os.Exit(1)
			}
			fp, err := filepath.Abs("./" + debugname)
			if err != nil {
				fmt.Fprintf(os.Stderr, err.Error())
				os.Exit(1)
			}

			processArgs := append([]string{"./" + debugname}, args...)
			status := execute(0, processArgs)
			os.Remove(fp)
			os.Exit(status)
		},
	}
	rootCommand.AddCommand(runCommand)

	// 'test' subcommand.
	testCommand := &cobra.Command{
		Use:   "test",
		Short: "Compile test binary and begin debugging program.",
		Long: `Compiles a test binary with optimizations disabled,
starts and attaches to it, and enable you to immediately begin debugging your program.`,
		Run: func(cmd *cobra.Command, args []string) {
			wd, err := os.Getwd()
			if err != nil {
				fmt.Fprintf(os.Stderr, err.Error())
				os.Exit(1)
			}
			base := filepath.Base(wd)
			goTest := exec.Command("go", "test", "-c", "-gcflags", "-N -l")
			goTest.Stderr = os.Stderr
			err = goTest.Run()
			if err != nil {
				os.Exit(1)
			}
			debugname := "./" + base + ".test"
			processArgs := append([]string{debugname}, args...)

			status := execute(0, processArgs)
			os.Remove(debugname)
			os.Exit(status)
		},
	}
	rootCommand.AddCommand(testCommand)

	// 'attach' subcommand.
	attachCommand := &cobra.Command{
		Use:   "attach [pid]",
		Short: "Attach to running process and begin debugging.",
		Long:  "Attach to running process and begin debugging.",
		Run: func(cmd *cobra.Command, args []string) {
			pid, err := strconv.Atoi(args[0])
			if err != nil {
				fmt.Fprintf(os.Stderr, "Invalid pid: %d", args[0])
				os.Exit(1)
			}
			os.Exit(execute(pid, nil))
		},
	}
	rootCommand.AddCommand(attachCommand)

	rootCommand.Execute()
}

func execute(attachPid int, processArgs []string) int {
	// Make a TCP listener
	listener, err := net.Listen("tcp", Addr)
	if err != nil {
		fmt.Printf("couldn't start listener: %s\n", err)
		return 1
	}
	defer listener.Close()

	// Create and start a debugger server
	var server service.Server
	server = rpc.NewServer(&service.Config{
		Listener:    listener,
		ProcessArgs: processArgs,
		AttachPid:   attachPid,
	}, Log)
	if err := server.Run(); err != nil {
		fmt.Fprintln(os.Stderr, err)
		return 1
	}

	var status int
	if !Headless {
		// Create and start a terminal
		var client service.Client
		client = rpc.NewClient(listener.Addr().String())
		term := terminal.New(client)
		err, status = term.Run()
	} else {
		ch := make(chan os.Signal)
		signal.Notify(ch, sys.SIGINT)
		<-ch
		err = server.Stop(true)
	}

	if err != nil {
		fmt.Println(err)
	}

	return status
}
<commit_msg>Fix typo<commit_after>package main

import (
	"fmt"
	"net"
	"os"
	"os/exec"
	"os/signal"
	"path/filepath"
	"strconv"

	sys "golang.org/x/sys/unix"

	"github.com/derekparker/delve/service"
	"github.com/derekparker/delve/service/rpc"
	"github.com/derekparker/delve/terminal"
	"github.com/spf13/cobra"
)

const version string = "0.6.0.beta"

var (
	Log      bool
	Headless bool
	Addr     string
)

func main() {
	// Main dlv root command.
	rootCommand := &cobra.Command{
		Use:   "dlv",
		Short: "Delve is a debugger for the Go programming language.",
	}
	rootCommand.Flags().StringVarP(&Addr, "listen", "l", "localhost:0", "Debugging server listen address.")
	rootCommand.Flags().BoolVarP(&Log, "log", "", false, "Enable debugging server logging.")
	rootCommand.Flags().BoolVarP(&Headless, "headless", "", false, "Run debug server only, in headless mode.")

	// 'version' subcommand.
	versionCommand := &cobra.Command{
		Use:   "version",
		Short: "Prints version.",
		Run: func(cmd *cobra.Command, args []string) {
			fmt.Println("Delve version: " + version)
		},
	}
	rootCommand.AddCommand(versionCommand)

	// 'run' subcommand.
	runCommand := &cobra.Command{
		Use:   "run",
		Short: "Compile and begin debugging program.",
		Long: `Compiles your program with optimizations disabled,
starts and attaches to it, and enables you to immediately begin debugging your program.`,
		Run: func(cmd *cobra.Command, args []string) {
			const debugname = "debug"
			goBuild := exec.Command("go", "build", "-o", debugname, "-gcflags", "-N -l")
			goBuild.Stderr = os.Stderr
			err := goBuild.Run()
			if err != nil {
				os.Exit(1)
			}
			fp, err := filepath.Abs("./" + debugname)
			if err != nil {
				fmt.Fprintf(os.Stderr, err.Error())
				os.Exit(1)
			}

			processArgs := append([]string{"./" + debugname}, args...)
			status := execute(0, processArgs)
			os.Remove(fp)
			os.Exit(status)
		},
	}
	rootCommand.AddCommand(runCommand)

	// 'test' subcommand.
	testCommand := &cobra.Command{
		Use:   "test",
		Short: "Compile test binary and begin debugging program.",
		Long: `Compiles a test binary with optimizations disabled,
starts and attaches to it, and enable you to immediately begin debugging your program.`,
		Run: func(cmd *cobra.Command, args []string) {
			wd, err := os.Getwd()
			if err != nil {
				fmt.Fprintf(os.Stderr, err.Error())
				os.Exit(1)
			}
			base := filepath.Base(wd)
			goTest := exec.Command("go", "test", "-c", "-gcflags", "-N -l")
			goTest.Stderr = os.Stderr
			err = goTest.Run()
			if err != nil {
				os.Exit(1)
			}
			debugname := "./" + base + ".test"
			processArgs := append([]string{debugname}, args...)

			status := execute(0, processArgs)
			os.Remove(debugname)
			os.Exit(status)
		},
	}
	rootCommand.AddCommand(testCommand)

	// 'attach' subcommand.
	attachCommand := &cobra.Command{
		Use:   "attach [pid]",
		Short: "Attach to running process and begin debugging.",
		Long:  "Attach to running process and begin debugging.",
		Run: func(cmd *cobra.Command, args []string) {
			pid, err := strconv.Atoi(args[0])
			if err != nil {
				fmt.Fprintf(os.Stderr, "Invalid pid: %d", args[0])
				os.Exit(1)
			}
			os.Exit(execute(pid, nil))
		},
	}
	rootCommand.AddCommand(attachCommand)

	rootCommand.Execute()
}

func execute(attachPid int, processArgs []string) int {
	// Make a TCP listener
	listener, err := net.Listen("tcp", Addr)
	if err != nil {
		fmt.Printf("couldn't start listener: %s\n", err)
		return 1
	}
	defer listener.Close()

	// Create and start a debugger server
	var server service.Server
	server = rpc.NewServer(&service.Config{
		Listener:    listener,
		ProcessArgs: processArgs,
		AttachPid:   attachPid,
	}, Log)
	if err := server.Run(); err != nil {
		fmt.Fprintln(os.Stderr, err)
		return 1
	}

	var status int
	if !Headless {
		// Create and start a terminal
		var client service.Client
		client = rpc.NewClient(listener.Addr().String())
		term := terminal.New(client)
		err, status = term.Run()
	} else {
		ch := make(chan os.Signal)
		signal.Notify(ch, sys.SIGINT)
		<-ch
		err = server.Stop(true)
	}

	if err != nil {
		fmt.Println(err)
	}

	return status
}
<|endoftext|>


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

package main

import (
	"bytes"
	"expvar"
	"flag"
	"fmt"
	"math/rand"
	"os"
	"os/signal"
	"path"
	"runtime"
	"runtime/pprof"
	"sort"
	"strings"
	"time"

	"github.com/couchbaselabs/blance"

	log "github.com/couchbase/clog"

	"github.com/couchbaselabs/cbgt"
	"github.com/couchbaselabs/cbgt/cmd"
	"github.com/couchbaselabs/cbgt/rebalance"
)

var VERSION = "v0.0.0"

var expvars = expvar.NewMap("stats")

func main() {
	flag.Parse()

	if flags.Help {
		flag.Usage()
		os.Exit(2)
	}

	if flags.Version {
		fmt.Printf("%s main: %s, data: %s\n",
			path.Base(os.Args[0]), VERSION, cbgt.VERSION)
		os.Exit(0)
	}

	if os.Getenv("GOMAXPROCS") == "" {
		runtime.GOMAXPROCS(runtime.NumCPU())
	}

	log.Printf("main: %s started (%s/%s)",
		os.Args[0], VERSION, cbgt.VERSION)

	rand.Seed(time.Now().UTC().UnixNano())

	go dumpOnSignalForPlatform()

	MainWelcome(flagAliases)

	nodesToRemove := []string(nil)
	if len(flags.RemoveNodes) > 0 {
		nodesToRemove = strings.Split(flags.RemoveNodes, ",")
	}

	bindHttp := "NO-BIND-HTTP"
	register := "unchanged"
	dataDir := "NO-DATA-DIR"

	// If cfg is down, we error, leaving it to some user-supplied
	// outside watchdog to backoff and restart/retry.
	cfg, err := cmd.MainCfg("mcp", flags.CfgConnect,
		bindHttp, register, dataDir)
	if err != nil {
		if err == cmd.ErrorBindHttp {
			log.Fatalf("%v", err)
			return
		}
		log.Fatalf("main: could not start cfg, cfgConnect: %s, err: %v\n"+
			"  Please check that your -cfg/-cfgConnect parameter (%q)\n"+
			"  is correct and/or that your configuration provider\n"+
			"  is available.",
			flags.CfgConnect, err, flags.CfgConnect)
		return
	}

	r, err := rebalance.StartRebalance(cbgt.VERSION, cfg, flags.Server,
		nodesToRemove,
		rebalance.RebalanceOptions{
			DryRun:  flags.DryRun,
			Verbose: flags.Verbose,
		})
	if err != nil {
		log.Fatalf("main: StartRebalance, err: %v", err)
		return
	}

	reportProgress(r)

	r.Stop()

	log.Printf("main: done")
}

// ------------------------------------------------------------

type ProgressEntry struct {
	pindex, sourcePartition, node string // Immutable.

	stateOp     rebalance.StateOp
	initUUIDSeq cbgt.UUIDSeq
	currUUIDSeq cbgt.UUIDSeq
	wantUUIDSeq cbgt.UUIDSeq

	move int
	done bool
}

func reportProgress(r *rebalance.Rebalancer) {
	var lastEmit string

	maxNodeLen := 0
	maxPIndexLen := 0

	seenNodes := map[string]bool{}
	seenNodesSorted := []string(nil)

	// Map of pindex -> (source) partition -> node -> *ProgressEntry
	progressEntries := map[string]map[string]map[string]*ProgressEntry{}

	seenPIndexes := map[string]bool{}
	seenPIndexesSorted := []string(nil)

	updateProgressEntry := func(pindex, sourcePartition, node string,
		cb func(*ProgressEntry)) {
		if !seenNodes[node] {
			seenNodes[node] = true
			seenNodesSorted = append(seenNodesSorted, node)
			sort.Strings(seenNodesSorted)

			if maxNodeLen < len(node) {
				maxNodeLen = len(node)
			}
		}

		if maxPIndexLen < len(pindex) {
			maxPIndexLen = len(pindex)
		}

		sourcePartitions, exists := progressEntries[pindex]
		if !exists || sourcePartitions == nil {
			sourcePartitions = map[string]map[string]*ProgressEntry{}
			progressEntries[pindex] = sourcePartitions
		}

		nodes, exists := sourcePartitions[sourcePartition]
		if !exists || nodes == nil {
			nodes = map[string]*ProgressEntry{}
			sourcePartitions[sourcePartition] = nodes
		}

		progressEntry, exists := nodes[node]
		if !exists || progressEntry == nil {
			progressEntry = &ProgressEntry{
				pindex:          pindex,
				sourcePartition: sourcePartition,
				node:            node,
				move:            -1,
			}
			nodes[node] = progressEntry
		}

		cb(progressEntry)

		// TODO: Check UUID matches, too.

		if !seenPIndexes[pindex] {
			seenPIndexes[pindex] = true
			seenPIndexesSorted =
				append(seenPIndexesSorted, pindex)

			sort.Strings(seenPIndexesSorted)
		}
	}

	for progress := range r.ProgressCh() {
		if progress.Error != nil {
			r.Log("main: error, progress: %+v", progress)
			continue
		}

		r.Visit(func(
			currStates rebalance.CurrStates,
			currSeqs rebalance.CurrSeqs,
			wantSeqs rebalance.WantSeqs,
			mapNextMoves map[string]*blance.NextMoves) {
			for _, pindexes := range currStates {
				for pindex, nodes := range pindexes {
					for node, stateOp := range nodes {
						updateProgressEntry(pindex, "", node,
							func(pe *ProgressEntry) {
								pe.stateOp = stateOp
							})
					}
				}
			}

			for pindex, sourcePartitions := range currSeqs {
				for sourcePartition, nodes := range sourcePartitions {
					for node, currUUIDSeq := range nodes {
						updateProgressEntry(pindex,
							sourcePartition, node,
							func(pe *ProgressEntry) {
								pe.currUUIDSeq = currUUIDSeq

								if pe.initUUIDSeq.UUID == "" {
									pe.initUUIDSeq = currUUIDSeq
								}
							})
					}
				}
			}

			for pindex, sourcePartitions := range wantSeqs {
				for sourcePartition, nodes := range sourcePartitions {
					for node, wantUUIDSeq := range nodes {
						updateProgressEntry(pindex,
							sourcePartition, node,
							func(pe *ProgressEntry) {
								pe.wantUUIDSeq = wantUUIDSeq
							})
					}
				}
			}

			for pindex, nextMoves := range mapNextMoves {
				for i, nodeStateOp := range nextMoves.Moves {
					updateProgressEntry(pindex, "", nodeStateOp.Node,
						func(pe *ProgressEntry) {
							pe.move = i
							pe.done = i < nextMoves.Next
						})
				}
			}
		})

		currEmit := progressTable(maxNodeLen, maxPIndexLen,
			seenNodes,
			seenNodesSorted,
			seenPIndexes,
			seenPIndexesSorted,
			progressEntries)
		if currEmit != lastEmit {
			r.Log("%s", currEmit)
		}

		lastEmit = currEmit
	}
}

func progressTable(maxNodeLen, maxPIndexLen int,
	seenNodes map[string]bool,
	seenNodesSorted []string,
	seenPIndexes map[string]bool,
	seenPIndexesSorted []string,
	progressEntries map[string]map[string]map[string]*ProgressEntry,
) string {
	var b bytes.Buffer

	written, _ := b.Write([]byte("%%%"))
	for i := written; i < maxPIndexLen; i++ {
		b.WriteByte(' ')
	}
	b.WriteByte(' ')

	for i, seenNode := range seenNodesSorted {
		if i > 0 {
			b.WriteByte(' ')
		}
		b.Write([]byte(seenNode))
	}
	b.WriteByte('\n')

	for _, seenPIndex := range seenPIndexesSorted {
		b.Write([]byte(" %                  "))
		b.Write([]byte(seenPIndex))

		for _, seenNode := range seenNodesSorted {
			b.WriteByte(' ')

			sourcePartitions, exists :=
				progressEntries[seenPIndex]
			if !exists || sourcePartitions == nil {
				progressCell(&b, nil, nil, maxNodeLen)
				continue
			}

			nodes, exists := sourcePartitions[""]
			if !exists || nodes == nil {
				progressCell(&b, nil, nil, maxNodeLen)
				continue
			}

			pe, exists := nodes[seenNode]
			if !exists || pe == nil {
				progressCell(&b, nil, nil, maxNodeLen)
				continue
			}

			progressCell(&b, pe, sourcePartitions, maxNodeLen)
		}

		b.WriteByte('\n')
	}

	return b.String()
}

var opMap = map[string]string{
	"":        ".",
	"add":     "+",
	"del":     "-",
	"promote": "P",
	"demote":  "D",
}

func progressCell(b *bytes.Buffer,
	pe *ProgressEntry,
	sourcePartitions map[string]map[string]*ProgressEntry,
	maxNodeLen int) {
	written := 0

	totPct := 0.0 // To compute average pct.
	numPct := 0

	if pe != nil {
		written, _ = fmt.Fprintf(b, "%d ", pe.move)

		if sourcePartitions != nil {
			n, _ := b.Write([]byte(opMap[pe.stateOp.Op]))
			written = written + n

			for sourcePartition, nodes := range sourcePartitions {
				if sourcePartition == "" {
					continue
				}

				pex := nodes[pe.node]
				if pex == nil || pex.wantUUIDSeq.UUID == "" {
					continue
				}

				if pex.wantUUIDSeq.Seq <= pex.currUUIDSeq.Seq {
					totPct = totPct + 1.0
					numPct = numPct + 1
					continue
				}

				n := pex.currUUIDSeq.Seq - pex.initUUIDSeq.Seq
				d := pex.wantUUIDSeq.Seq - pex.initUUIDSeq.Seq
				if d > 0 {
					pct := float64(n) / float64(d)
					totPct = totPct + pct
					numPct = numPct + 1
				}
			}
		}
	} else {
		b.Write([]byte("  ."))
		written = 3
	}

	if numPct > 0 {
		avgPct := totPct / float64(numPct)

		n, _ := fmt.Fprintf(b, " %.1f%%", avgPct*100.0)
		written = written + n
	}

	for i := written; i < maxNodeLen; i++ {
		b.WriteByte(' ')
	}
}

// ------------------------------------------------------------

func MainWelcome(flagAliases map[string][]string) {
	flag.VisitAll(func(f *flag.Flag) {
		if flagAliases[f.Name] != nil {
			log.Printf("  -%s=%q\n", f.Name, f.Value)
		}
	})
	log.Printf("  GOMAXPROCS=%d", runtime.GOMAXPROCS(-1))
}

func dumpOnSignal(signals ...os.Signal) {
	c := make(chan os.Signal, 1)
	signal.Notify(c, signals...)
	for _ = range c {
		log.Printf("dump: goroutine...")
		pprof.Lookup("goroutine").WriteTo(os.Stderr, 1)
		log.Printf("dump: heap...")
		pprof.Lookup("heap").WriteTo(os.Stderr, 1)
	}
}
<commit_msg>TODO on mcp emitting node ADDR:PORT<commit_after>//  Copyright (c) 2015 Couchbase, Inc.
//  Licensed under the Apache License, Version 2.0 (the "License");
//  you may not use this file except in compliance with the
//  License. You may obtain a copy of the License at
//    http://www.apache.org/licenses/LICENSE-2.0
//  Unless required by applicable law or agreed to in writing,
//  software distributed under the License is distributed on an "AS
//  IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
//  express or implied. See the License for the specific language
//  governing permissions and limitations under the License.

package main

import (
	"bytes"
	"expvar"
	"flag"
	"fmt"
	"math/rand"
	"os"
	"os/signal"
	"path"
	"runtime"
	"runtime/pprof"
	"sort"
	"strings"
	"time"

	"github.com/couchbaselabs/blance"

	log "github.com/couchbase/clog"

	"github.com/couchbaselabs/cbgt"
	"github.com/couchbaselabs/cbgt/cmd"
	"github.com/couchbaselabs/cbgt/rebalance"
)

var VERSION = "v0.0.0"

var expvars = expvar.NewMap("stats")

func main() {
	flag.Parse()

	if flags.Help {
		flag.Usage()
		os.Exit(2)
	}

	if flags.Version {
		fmt.Printf("%s main: %s, data: %s\n",
			path.Base(os.Args[0]), VERSION, cbgt.VERSION)
		os.Exit(0)
	}

	if os.Getenv("GOMAXPROCS") == "" {
		runtime.GOMAXPROCS(runtime.NumCPU())
	}

	log.Printf("main: %s started (%s/%s)",
		os.Args[0], VERSION, cbgt.VERSION)

	rand.Seed(time.Now().UTC().UnixNano())

	go dumpOnSignalForPlatform()

	MainWelcome(flagAliases)

	nodesToRemove := []string(nil)
	if len(flags.RemoveNodes) > 0 {
		nodesToRemove = strings.Split(flags.RemoveNodes, ",")
	}

	bindHttp := "NO-BIND-HTTP"
	register := "unchanged"
	dataDir := "NO-DATA-DIR"

	// If cfg is down, we error, leaving it to some user-supplied
	// outside watchdog to backoff and restart/retry.
	cfg, err := cmd.MainCfg("mcp", flags.CfgConnect,
		bindHttp, register, dataDir)
	if err != nil {
		if err == cmd.ErrorBindHttp {
			log.Fatalf("%v", err)
			return
		}
		log.Fatalf("main: could not start cfg, cfgConnect: %s, err: %v\n"+
			"  Please check that your -cfg/-cfgConnect parameter (%q)\n"+
			"  is correct and/or that your configuration provider\n"+
			"  is available.",
			flags.CfgConnect, err, flags.CfgConnect)
		return
	}

	r, err := rebalance.StartRebalance(cbgt.VERSION, cfg, flags.Server,
		nodesToRemove,
		rebalance.RebalanceOptions{
			DryRun:  flags.DryRun,
			Verbose: flags.Verbose,
		})
	if err != nil {
		log.Fatalf("main: StartRebalance, err: %v", err)
		return
	}

	reportProgress(r)

	r.Stop()

	log.Printf("main: done")
}

// ------------------------------------------------------------

type ProgressEntry struct {
	pindex, sourcePartition, node string // Immutable.

	stateOp     rebalance.StateOp
	initUUIDSeq cbgt.UUIDSeq
	currUUIDSeq cbgt.UUIDSeq
	wantUUIDSeq cbgt.UUIDSeq

	move int
	done bool
}

func reportProgress(r *rebalance.Rebalancer) {
	var lastEmit string

	maxNodeLen := 0
	maxPIndexLen := 0

	seenNodes := map[string]bool{}
	seenNodesSorted := []string(nil)

	// Map of pindex -> (source) partition -> node -> *ProgressEntry
	progressEntries := map[string]map[string]map[string]*ProgressEntry{}

	seenPIndexes := map[string]bool{}
	seenPIndexesSorted := []string(nil)

	updateProgressEntry := func(pindex, sourcePartition, node string,
		cb func(*ProgressEntry)) {
		if !seenNodes[node] {
			seenNodes[node] = true
			seenNodesSorted = append(seenNodesSorted, node)
			sort.Strings(seenNodesSorted)

			if maxNodeLen < len(node) {
				maxNodeLen = len(node)
			}
		}

		if maxPIndexLen < len(pindex) {
			maxPIndexLen = len(pindex)
		}

		sourcePartitions, exists := progressEntries[pindex]
		if !exists || sourcePartitions == nil {
			sourcePartitions = map[string]map[string]*ProgressEntry{}
			progressEntries[pindex] = sourcePartitions
		}

		nodes, exists := sourcePartitions[sourcePartition]
		if !exists || nodes == nil {
			nodes = map[string]*ProgressEntry{}
			sourcePartitions[sourcePartition] = nodes
		}

		progressEntry, exists := nodes[node]
		if !exists || progressEntry == nil {
			progressEntry = &ProgressEntry{
				pindex:          pindex,
				sourcePartition: sourcePartition,
				node:            node,
				move:            -1,
			}
			nodes[node] = progressEntry
		}

		cb(progressEntry)

		// TODO: Check UUID matches, too.

		if !seenPIndexes[pindex] {
			seenPIndexes[pindex] = true
			seenPIndexesSorted =
				append(seenPIndexesSorted, pindex)

			sort.Strings(seenPIndexesSorted)
		}
	}

	for progress := range r.ProgressCh() {
		if progress.Error != nil {
			r.Log("main: error, progress: %+v", progress)
			continue
		}

		r.Visit(func(
			currStates rebalance.CurrStates,
			currSeqs rebalance.CurrSeqs,
			wantSeqs rebalance.WantSeqs,
			mapNextMoves map[string]*blance.NextMoves) {
			for _, pindexes := range currStates {
				for pindex, nodes := range pindexes {
					for node, stateOp := range nodes {
						updateProgressEntry(pindex, "", node,
							func(pe *ProgressEntry) {
								pe.stateOp = stateOp
							})
					}
				}
			}

			for pindex, sourcePartitions := range currSeqs {
				for sourcePartition, nodes := range sourcePartitions {
					for node, currUUIDSeq := range nodes {
						updateProgressEntry(pindex,
							sourcePartition, node,
							func(pe *ProgressEntry) {
								pe.currUUIDSeq = currUUIDSeq

								if pe.initUUIDSeq.UUID == "" {
									pe.initUUIDSeq = currUUIDSeq
								}
							})
					}
				}
			}

			for pindex, sourcePartitions := range wantSeqs {
				for sourcePartition, nodes := range sourcePartitions {
					for node, wantUUIDSeq := range nodes {
						updateProgressEntry(pindex,
							sourcePartition, node,
							func(pe *ProgressEntry) {
								pe.wantUUIDSeq = wantUUIDSeq
							})
					}
				}
			}

			for pindex, nextMoves := range mapNextMoves {
				for i, nodeStateOp := range nextMoves.Moves {
					updateProgressEntry(pindex, "", nodeStateOp.Node,
						func(pe *ProgressEntry) {
							pe.move = i
							pe.done = i < nextMoves.Next
						})
				}
			}
		})

		currEmit := progressTable(maxNodeLen, maxPIndexLen,
			seenNodes,
			seenNodesSorted,
			seenPIndexes,
			seenPIndexesSorted,
			progressEntries)
		if currEmit != lastEmit {
			r.Log("%s", currEmit)
		}

		lastEmit = currEmit
	}
}

func progressTable(maxNodeLen, maxPIndexLen int,
	seenNodes map[string]bool,
	seenNodesSorted []string,
	seenPIndexes map[string]bool,
	seenPIndexesSorted []string,
	progressEntries map[string]map[string]map[string]*ProgressEntry,
) string {
	var b bytes.Buffer

	written, _ := b.Write([]byte("%%%"))
	for i := written; i < maxPIndexLen; i++ {
		b.WriteByte(' ')
	}
	b.WriteByte(' ')

	for i, seenNode := range seenNodesSorted {
		if i > 0 {
			b.WriteByte(' ')
		}

		// TODO: Emit node human readable ADDR:PORT.
		b.Write([]byte(seenNode))
	}
	b.WriteByte('\n')

	for _, seenPIndex := range seenPIndexesSorted {
		b.Write([]byte(" %                  "))
		b.Write([]byte(seenPIndex))

		for _, seenNode := range seenNodesSorted {
			b.WriteByte(' ')

			sourcePartitions, exists :=
				progressEntries[seenPIndex]
			if !exists || sourcePartitions == nil {
				progressCell(&b, nil, nil, maxNodeLen)
				continue
			}

			nodes, exists := sourcePartitions[""]
			if !exists || nodes == nil {
				progressCell(&b, nil, nil, maxNodeLen)
				continue
			}

			pe, exists := nodes[seenNode]
			if !exists || pe == nil {
				progressCell(&b, nil, nil, maxNodeLen)
				continue
			}

			progressCell(&b, pe, sourcePartitions, maxNodeLen)
		}

		b.WriteByte('\n')
	}

	return b.String()
}

var opMap = map[string]string{
	"":        ".",
	"add":     "+",
	"del":     "-",
	"promote": "P",
	"demote":  "D",
}

func progressCell(b *bytes.Buffer,
	pe *ProgressEntry,
	sourcePartitions map[string]map[string]*ProgressEntry,
	maxNodeLen int) {
	written := 0

	totPct := 0.0 // To compute average pct.
	numPct := 0

	if pe != nil {
		written, _ = fmt.Fprintf(b, "%d ", pe.move)

		if sourcePartitions != nil {
			n, _ := b.Write([]byte(opMap[pe.stateOp.Op]))
			written = written + n

			for sourcePartition, nodes := range sourcePartitions {
				if sourcePartition == "" {
					continue
				}

				pex := nodes[pe.node]
				if pex == nil || pex.wantUUIDSeq.UUID == "" {
					continue
				}

				if pex.wantUUIDSeq.Seq <= pex.currUUIDSeq.Seq {
					totPct = totPct + 1.0
					numPct = numPct + 1
					continue
				}

				n := pex.currUUIDSeq.Seq - pex.initUUIDSeq.Seq
				d := pex.wantUUIDSeq.Seq - pex.initUUIDSeq.Seq
				if d > 0 {
					pct := float64(n) / float64(d)
					totPct = totPct + pct
					numPct = numPct + 1
				}
			}
		}
	} else {
		b.Write([]byte("  ."))
		written = 3
	}

	if numPct > 0 {
		avgPct := totPct / float64(numPct)

		n, _ := fmt.Fprintf(b, " %.1f%%", avgPct*100.0)
		written = written + n
	}

	for i := written; i < maxNodeLen; i++ {
		b.WriteByte(' ')
	}
}

// ------------------------------------------------------------

func MainWelcome(flagAliases map[string][]string) {
	flag.VisitAll(func(f *flag.Flag) {
		if flagAliases[f.Name] != nil {
			log.Printf("  -%s=%q\n", f.Name, f.Value)
		}
	})
	log.Printf("  GOMAXPROCS=%d", runtime.GOMAXPROCS(-1))
}

func dumpOnSignal(signals ...os.Signal) {
	c := make(chan os.Signal, 1)
	signal.Notify(c, signals...)
	for _ = range c {
		log.Printf("dump: goroutine...")
		pprof.Lookup("goroutine").WriteTo(os.Stderr, 1)
		log.Printf("dump: heap...")
		pprof.Lookup("heap").WriteTo(os.Stderr, 1)
	}
}
<|endoftext|>