<commit_before>package main

import (
	"bufio"
	"encoding/json"
	"flag"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"os/signal"
	"path"
	"strings"
	"syscall"
	"time"

	"github.com/QubitProducts/bamboo/Godeps/_workspace/src/github.com/go-martini/martini"
	"github.com/QubitProducts/bamboo/Godeps/_workspace/src/github.com/kardianos/osext"
	"github.com/QubitProducts/bamboo/Godeps/_workspace/src/github.com/natefinch/lumberjack"
	"github.com/QubitProducts/bamboo/Godeps/_workspace/src/github.com/samuel/go-zookeeper/zk"
	"github.com/QubitProducts/bamboo/api"
	"github.com/QubitProducts/bamboo/configuration"
	"github.com/QubitProducts/bamboo/qzk"
	"github.com/QubitProducts/bamboo/services/event_bus"
)

/*
	Commandline arguments
*/
var configFilePath string
var logPath string
var serverBindPort string

func init() {
	flag.StringVar(&configFilePath, "config", "config/development.json", "Full path of the configuration JSON file")
	flag.StringVar(&logPath, "log", "", "Log path to a file. Default logs to stdout")
	flag.StringVar(&serverBindPort, "bind", ":8000", "Bind HTTP server to a specific port")
}

func main() {
	flag.Parse()
	configureLog()

	// Load configuration
	conf, err := configuration.FromFile(configFilePath)
	if err != nil {
		log.Fatal(err)
	}

	eventBus := event_bus.New()

	// Wait for died children to avoid zombies
	signalChannel := make(chan os.Signal, 2)
	signal.Notify(signalChannel, os.Interrupt, syscall.SIGCHLD)
	go func() {
		for {
			sig := <-signalChannel
			if sig == syscall.SIGCHLD {
				r := syscall.Rusage{}
				syscall.Wait4(-1, nil, 0, &r)
			}
		}
	}()

	// Create StatsD client
	conf.StatsD.CreateClient()

	// Create Zookeeper connection
	zkConn := listenToZookeeper(conf, eventBus)

	// Register handlers
	handlers := event_bus.Handlers{Conf: &conf, Zookeeper: zkConn}
	eventBus.Register(handlers.MarathonEventHandler)
	eventBus.Register(handlers.ServiceEventHandler)
	eventBus.Publish(event_bus.MarathonEvent{EventType: "bamboo_startup", Timestamp: time.Now().Format(time.RFC3339)})

	// Handle gracefully exit
	registerOSSignals()

	if conf.Marathon.UseEventStream {
		// Listen events stream from Marathon
		listenToEventStream(conf, eventBus)
	}

	// Start server
	initServer(&conf, zkConn, eventBus)
}

func initServer(conf *configuration.Configuration, conn *zk.Conn, eventBus *event_bus.EventBus) {
	stateAPI := api.StateAPI{Config: conf, Zookeeper: conn}
	serviceAPI := api.ServiceAPI{Config: conf, Zookeeper: conn}
	eventSubAPI := api.EventSubscriptionAPI{Conf: conf, EventBus: eventBus}

	conf.StatsD.Increment(1.0, "restart", 1)
	// Status live information
	router := martini.Classic()
	router.Get("/status", api.HandleStatus)

	// API
	router.Group("/api", func(api martini.Router) {
		// State API
		api.Get("/state", stateAPI.Get)
		// Service API
		api.Get("/services", serviceAPI.All)
		api.Post("/services", serviceAPI.Create)
		api.Put("/services/**", serviceAPI.Put)
		api.Delete("/services/**", serviceAPI.Delete)
		api.Post("/marathon/event_callback", eventSubAPI.Callback)
	})

	// Static pages
	router.Use(martini.Static(path.Join(executableFolder(), "webapp")))

	if !conf.Marathon.UseEventStream {
		registerMarathonEvent(conf)
	}
	router.RunOnAddr(serverBindPort)
}

// Get current executable folder path
func executableFolder() string {
	folderPath, err := osext.ExecutableFolder()
	if err != nil {
		log.Fatal(err)
	}
	return folderPath
}

func registerMarathonEvent(conf *configuration.Configuration) {

	client := &http.Client{}
	// it's safe to register with multiple marathon nodes
	for _, marathon := range conf.Marathon.Endpoints() {
		url := marathon + "/v2/eventSubscriptions?callbackUrl=" + conf.Bamboo.Endpoint + "/api/marathon/event_callback"
		req, _ := http.NewRequest("POST", url, nil)
		req.Header.Add("Content-Type", "application/json")
		if len(conf.Marathon.User) > 0 && len(conf.Marathon.Password) > 0 {
			req.SetBasicAuth(conf.Marathon.User, conf.Marathon.Password)
		}
		resp, err := client.Do(req)
		if err != nil {
			errorMsg := "An error occurred while accessing Marathon callback system: %s\n"
			log.Printf(errorMsg, err)
			return
		}
		bodyBytes, err := ioutil.ReadAll(resp.Body)
		resp.Body.Close()
		if err != nil {
			log.Fatal(err)
			return
		}
		body := string(bodyBytes)
		if strings.HasPrefix(body, "{\"message") {
			warningMsg := "Access to the callback system of Marathon seems to be failed, response: %s\n"
			log.Printf(warningMsg, body)
		}
	}
}

func createAndListen(conf configuration.Zookeeper) (chan zk.Event, *zk.Conn) {
	conn, _, err := zk.Connect(conf.ConnectionString(), time.Second*10)

	if err != nil {
		log.Panic(err)
	}

	ch, _ := qzk.ListenToConn(conn, conf.Path, true, conf.Delay())
	return ch, conn
}

func listenToZookeeper(conf configuration.Configuration, eventBus *event_bus.EventBus) *zk.Conn {
	serviceCh, serviceConn := createAndListen(conf.Bamboo.Zookeeper)

	go func() {
		for {
			select {
			case _ = <-serviceCh:
				eventBus.Publish(event_bus.ServiceEvent{EventType: "change"})
			}
		}
	}()
	return serviceConn
}

func listenToEventStream(conf configuration.Configuration, eventBus *event_bus.EventBus) {
	client := &http.Client{}
	client.Timeout = 0 * time.Second

	for _, marathon := range conf.Marathon.Endpoints() {
		go func() {
			for {
				req, err := http.NewRequest("GET", marathon+"/v2/events", nil)
				req.Header.Set("Accept", "text/event-stream")
				if err != nil {
					errorMsg := "An error occurred while creating request to Marathon events system: %s\n"
					log.Printf(errorMsg, err)
					continue
				}

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

				reader := bufio.NewReader(resp.Body)
				for {
					line, err := reader.ReadString('\n')
					if err != nil {
						if err != io.EOF {
							errorMsg := "An error occurred while reading Marathon event: %s\n"
							log.Printf(errorMsg, err)
						}
						break
					}

					if len(strings.TrimSpace(line)) == 0 {
						continue
					}

					if !strings.HasPrefix(line, "data: ") {
						errorMsg := "Wrong event format: %s\n"
						log.Printf(errorMsg, line)
						continue
					}

					line = line[6:]

					var event event_bus.MarathonEvent
					err = json.Unmarshal([]byte(line), &event)

					if err != nil {
						errorMsg := "Unable to decode JSON Marathon Event request: %s\n"
						log.Printf(errorMsg, line)
						continue
					}

					eventBus.Publish(event)
				}

				_ = resp.Body.Close()
			}
		}()
	}
}

func configureLog() {
	if len(logPath) > 0 {
		log.SetOutput(io.MultiWriter(&lumberjack.Logger{
			Filename: logPath,
			// megabytes
			MaxSize:    100,
			MaxBackups: 3,
			//days
			MaxAge: 28,
		}, os.Stdout))
	}
}

func registerOSSignals() {
	c := make(chan os.Signal, 1)
	signal.Notify(c, os.Interrupt)
	go func() {
		for _ = range c {
			log.Println("Server Stopped")
			os.Exit(0)
		}
	}()
}
<commit_msg>re #149 defer response closing and log close event<commit_after>package main

import (
	"bufio"
	"encoding/json"
	"flag"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"os/signal"
	"path"
	"strings"
	"syscall"
	"time"

	"github.com/QubitProducts/bamboo/Godeps/_workspace/src/github.com/go-martini/martini"
	"github.com/QubitProducts/bamboo/Godeps/_workspace/src/github.com/kardianos/osext"
	"github.com/QubitProducts/bamboo/Godeps/_workspace/src/github.com/natefinch/lumberjack"
	"github.com/QubitProducts/bamboo/Godeps/_workspace/src/github.com/samuel/go-zookeeper/zk"
	"github.com/QubitProducts/bamboo/api"
	"github.com/QubitProducts/bamboo/configuration"
	"github.com/QubitProducts/bamboo/qzk"
	"github.com/QubitProducts/bamboo/services/event_bus"
)

/*
	Commandline arguments
*/
var configFilePath string
var logPath string
var serverBindPort string

func init() {
	flag.StringVar(&configFilePath, "config", "config/development.json", "Full path of the configuration JSON file")
	flag.StringVar(&logPath, "log", "", "Log path to a file. Default logs to stdout")
	flag.StringVar(&serverBindPort, "bind", ":8000", "Bind HTTP server to a specific port")
}

func main() {
	flag.Parse()
	configureLog()

	// Load configuration
	conf, err := configuration.FromFile(configFilePath)
	if err != nil {
		log.Fatal(err)
	}

	eventBus := event_bus.New()

	// Wait for died children to avoid zombies
	signalChannel := make(chan os.Signal, 2)
	signal.Notify(signalChannel, os.Interrupt, syscall.SIGCHLD)
	go func() {
		for {
			sig := <-signalChannel
			if sig == syscall.SIGCHLD {
				r := syscall.Rusage{}
				syscall.Wait4(-1, nil, 0, &r)
			}
		}
	}()

	// Create StatsD client
	conf.StatsD.CreateClient()

	// Create Zookeeper connection
	zkConn := listenToZookeeper(conf, eventBus)

	// Register handlers
	handlers := event_bus.Handlers{Conf: &conf, Zookeeper: zkConn}
	eventBus.Register(handlers.MarathonEventHandler)
	eventBus.Register(handlers.ServiceEventHandler)
	eventBus.Publish(event_bus.MarathonEvent{EventType: "bamboo_startup", Timestamp: time.Now().Format(time.RFC3339)})

	// Handle gracefully exit
	registerOSSignals()

	if conf.Marathon.UseEventStream {
		// Listen events stream from Marathon
		listenToEventStream(conf, eventBus)
	}

	// Start server
	initServer(&conf, zkConn, eventBus)
}

func initServer(conf *configuration.Configuration, conn *zk.Conn, eventBus *event_bus.EventBus) {
	stateAPI := api.StateAPI{Config: conf, Zookeeper: conn}
	serviceAPI := api.ServiceAPI{Config: conf, Zookeeper: conn}
	eventSubAPI := api.EventSubscriptionAPI{Conf: conf, EventBus: eventBus}

	conf.StatsD.Increment(1.0, "restart", 1)
	// Status live information
	router := martini.Classic()
	router.Get("/status", api.HandleStatus)

	// API
	router.Group("/api", func(api martini.Router) {
		// State API
		api.Get("/state", stateAPI.Get)
		// Service API
		api.Get("/services", serviceAPI.All)
		api.Post("/services", serviceAPI.Create)
		api.Put("/services/**", serviceAPI.Put)
		api.Delete("/services/**", serviceAPI.Delete)
		api.Post("/marathon/event_callback", eventSubAPI.Callback)
	})

	// Static pages
	router.Use(martini.Static(path.Join(executableFolder(), "webapp")))

	if !conf.Marathon.UseEventStream {
		registerMarathonEvent(conf)
	}
	router.RunOnAddr(serverBindPort)
}

// Get current executable folder path
func executableFolder() string {
	folderPath, err := osext.ExecutableFolder()
	if err != nil {
		log.Fatal(err)
	}
	return folderPath
}

func registerMarathonEvent(conf *configuration.Configuration) {

	client := &http.Client{}
	// it's safe to register with multiple marathon nodes
	for _, marathon := range conf.Marathon.Endpoints() {
		url := marathon + "/v2/eventSubscriptions?callbackUrl=" + conf.Bamboo.Endpoint + "/api/marathon/event_callback"
		req, _ := http.NewRequest("POST", url, nil)
		req.Header.Add("Content-Type", "application/json")
		if len(conf.Marathon.User) > 0 && len(conf.Marathon.Password) > 0 {
			req.SetBasicAuth(conf.Marathon.User, conf.Marathon.Password)
		}
		resp, err := client.Do(req)
		if err != nil {
			errorMsg := "An error occurred while accessing Marathon callback system: %s\n"
			log.Printf(errorMsg, err)
			return
		}
		bodyBytes, err := ioutil.ReadAll(resp.Body)
		resp.Body.Close()
		if err != nil {
			log.Fatal(err)
			return
		}
		body := string(bodyBytes)
		if strings.HasPrefix(body, "{\"message") {
			warningMsg := "Access to the callback system of Marathon seems to be failed, response: %s\n"
			log.Printf(warningMsg, body)
		}
	}
}

func createAndListen(conf configuration.Zookeeper) (chan zk.Event, *zk.Conn) {
	conn, _, err := zk.Connect(conf.ConnectionString(), time.Second*10)

	if err != nil {
		log.Panic(err)
	}

	ch, _ := qzk.ListenToConn(conn, conf.Path, true, conf.Delay())
	return ch, conn
}

func listenToZookeeper(conf configuration.Configuration, eventBus *event_bus.EventBus) *zk.Conn {
	serviceCh, serviceConn := createAndListen(conf.Bamboo.Zookeeper)

	go func() {
		for {
			select {
			case _ = <-serviceCh:
				eventBus.Publish(event_bus.ServiceEvent{EventType: "change"})
			}
		}
	}()
	return serviceConn
}

func listenToEventStream(conf configuration.Configuration, eventBus *event_bus.EventBus) {
	client := &http.Client{}
	client.Timeout = 0 * time.Second

	for _, marathon := range conf.Marathon.Endpoints() {
		go func() {
			for {
				req, err := http.NewRequest("GET", marathon+"/v2/events", nil)
				req.Header.Set("Accept", "text/event-stream")
				if err != nil {
					errorMsg := "An error occurred while creating request to Marathon events system: %s\n"
					log.Printf(errorMsg, err)
					continue
				}

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

				defer resp.Body.Close()

				reader := bufio.NewReader(resp.Body)
				for {
					line, err := reader.ReadString('\n')
					if err != nil {
						if err != io.EOF {
							errorMsg := "An error occurred while reading Marathon event: %s\n"
							log.Printf(errorMsg, err)
						}
						break
					}

					if len(strings.TrimSpace(line)) == 0 {
						continue
					}

					if !strings.HasPrefix(line, "data: ") {
						errorMsg := "Wrong event format: %s\n"
						log.Printf(errorMsg, line)
						continue
					}

					line = line[6:]

					var event event_bus.MarathonEvent
					err = json.Unmarshal([]byte(line), &event)

					if err != nil {
						errorMsg := "Unable to decode JSON Marathon Event request: %s\n"
						log.Printf(errorMsg, line)
						continue
					}

					eventBus.Publish(event)
				}

				log.Println("Event stream connection was closed. Re-opening...")
			}
		}()
	}
}

func configureLog() {
	if len(logPath) > 0 {
		log.SetOutput(io.MultiWriter(&lumberjack.Logger{
			Filename: logPath,
			// megabytes
			MaxSize:    100,
			MaxBackups: 3,
			//days
			MaxAge: 28,
		}, os.Stdout))
	}
}

func registerOSSignals() {
	c := make(chan os.Signal, 1)
	signal.Notify(c, os.Interrupt)
	go func() {
		for _ = range c {
			log.Println("Server Stopped")
			os.Exit(0)
		}
	}()
}
<|endoftext|>


<commit_before>/*
Copyright 2020 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 controlplane

import (
	"context"
	"crypto/sha256"
	"encoding/base32"
	"fmt"
	"os"
	"strings"
	"testing"
	"time"

	coordinationv1 "k8s.io/api/coordination/v1"
	corev1 "k8s.io/api/core/v1"
	apierrors "k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/util/wait"
	"k8s.io/apiserver/pkg/features"
	utilfeature "k8s.io/apiserver/pkg/util/feature"
	"k8s.io/client-go/kubernetes"
	featuregatetesting "k8s.io/component-base/featuregate/testing"
	kubeapiservertesting "k8s.io/kubernetes/cmd/kube-apiserver/app/testing"
	"k8s.io/kubernetes/pkg/controlplane"
	"k8s.io/kubernetes/test/integration/framework"
	"k8s.io/utils/pointer"
)

const (
	testLeaseName = "apiserver-lease-test"
)

func expectedAPIServerIdentity(hostname string) string {
	hash := sha256.Sum256([]byte(hostname))
	return "kube-apiserver-" + strings.ToLower(base32.StdEncoding.WithPadding(base32.NoPadding).EncodeToString(hash[:16]))
}

func TestCreateLeaseOnStart(t *testing.T) {
	defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.APIServerIdentity, true)()
	result := kubeapiservertesting.StartTestServerOrDie(t, nil, nil, framework.SharedEtcd())
	defer result.TearDownFn()

	kubeclient, err := kubernetes.NewForConfig(result.ClientConfig)
	if err != nil {
		t.Fatalf("Unexpected error: %v", err)
	}

	hostname, err := os.Hostname()
	if err != nil {
		t.Fatalf("Unexpected error getting apiserver hostname: %v", err)
	}

	t.Logf(`Waiting the kube-apiserver Lease to be created`)
	if err := wait.PollImmediate(500*time.Millisecond, 10*time.Second, func() (bool, error) {
		leases, err := kubeclient.
			CoordinationV1().
			Leases(metav1.NamespaceSystem).
			List(context.TODO(), metav1.ListOptions{LabelSelector: controlplane.KubeAPIServerIdentityLeaseLabelSelector})
		if err != nil {
			return false, err
		}

		if leases == nil {
			return false, nil
		}

		if len(leases.Items) != 1 {
			return false, nil
		}

		lease := leases.Items[0]
		if lease.Name != expectedAPIServerIdentity(hostname) {
			return false, fmt.Errorf("unexpected apiserver identity, got: %v, expected: %v", lease.Name, expectedAPIServerIdentity(hostname))
		}

		if lease.Labels[corev1.LabelHostname] != hostname {
			return false, fmt.Errorf("unexpected hostname label, got: %v, expected: %v", lease.Labels[corev1.LabelHostname], hostname)
		}

		return true, nil
	}); err != nil {
		t.Fatalf("Failed to see the kube-apiserver lease: %v", err)
	}
}

func TestLeaseGarbageCollection(t *testing.T) {
	defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.APIServerIdentity, true)()
	result := kubeapiservertesting.StartTestServerOrDie(t, nil,
		// This shorten the GC check period to make the test run faster.
		// Since we are testing GC behavior on leases we create, what happens to
		// the real apiserver lease doesn't matter.
		[]string{"--identity-lease-duration-seconds=1"},
		framework.SharedEtcd())
	defer result.TearDownFn()

	kubeclient, err := kubernetes.NewForConfig(result.ClientConfig)
	if err != nil {
		t.Fatalf("Unexpected error: %v", err)
	}
	expiredLease := newTestLease(time.Now().Add(-2*time.Hour), metav1.NamespaceSystem)
	t.Run("expired apiserver lease should be garbage collected",
		testLeaseGarbageCollected(t, kubeclient, expiredLease))

	freshLease := newTestLease(time.Now().Add(-2*time.Minute), metav1.NamespaceSystem)
	t.Run("fresh apiserver lease should not be garbage collected",
		testLeaseNotGarbageCollected(t, kubeclient, freshLease))

	expiredLease.Labels = nil
	t.Run("expired non-identity lease should not be garbage collected",
		testLeaseNotGarbageCollected(t, kubeclient, expiredLease))

	// identity leases (with k8s.io/component label) created in user namespaces should not be GC'ed
	expiredNonKubeSystemLease := newTestLease(time.Now().Add(-2*time.Hour), metav1.NamespaceDefault)
	t.Run("expired non-system identity lease should not be garbage collected",
		testLeaseNotGarbageCollected(t, kubeclient, expiredNonKubeSystemLease))
}

func testLeaseGarbageCollected(t *testing.T, client kubernetes.Interface, lease *coordinationv1.Lease) func(t *testing.T) {
	return func(t *testing.T) {
		ns := lease.Namespace
		if _, err := client.CoordinationV1().Leases(ns).Create(context.TODO(), lease, metav1.CreateOptions{}); err != nil {
			t.Fatalf("Unexpected error creating lease: %v", err)
		}
		if err := wait.PollImmediate(500*time.Millisecond, 5*time.Second, func() (bool, error) {
			_, err := client.CoordinationV1().Leases(ns).Get(context.TODO(), lease.Name, metav1.GetOptions{})
			if err == nil {
				return false, nil
			}
			if apierrors.IsNotFound(err) {
				return true, nil
			}
			return false, err
		}); err != nil {
			t.Fatalf("Failed to see the expired lease garbage collected: %v", err)
		}

	}
}

func testLeaseNotGarbageCollected(t *testing.T, client kubernetes.Interface, lease *coordinationv1.Lease) func(t *testing.T) {
	return func(t *testing.T) {
		ns := lease.Namespace
		if _, err := client.CoordinationV1().Leases(ns).Create(context.TODO(), lease, metav1.CreateOptions{}); err != nil {
			t.Fatalf("Unexpected error creating lease: %v", err)
		}
		if err := wait.PollImmediate(500*time.Millisecond, 5*time.Second, func() (bool, error) {
			_, err := client.CoordinationV1().Leases(ns).Get(context.TODO(), lease.Name, metav1.GetOptions{})
			if err != nil && apierrors.IsNotFound(err) {
				return true, nil
			}
			return false, nil
		}); err == nil {
			t.Fatalf("Unexpected valid lease getting garbage collected")
		}
		if _, err := client.CoordinationV1().Leases(ns).Get(context.TODO(), lease.Name, metav1.GetOptions{}); err != nil {
			t.Fatalf("Failed to retrieve valid lease: %v", err)
		}
		if err := client.CoordinationV1().Leases(ns).Delete(context.TODO(), lease.Name, metav1.DeleteOptions{}); err != nil {
			t.Fatalf("Failed to clean up valid lease: %v", err)
		}
	}
}

func newTestLease(acquireTime time.Time, namespace string) *coordinationv1.Lease {
	return &coordinationv1.Lease{
		ObjectMeta: metav1.ObjectMeta{
			Name:      testLeaseName,
			Namespace: namespace,
			Labels: map[string]string{
				controlplane.IdentityLeaseComponentLabelKey: controlplane.KubeAPIServer,
			},
		},
		Spec: coordinationv1.LeaseSpec{
			HolderIdentity:       pointer.StringPtr(testLeaseName),
			LeaseDurationSeconds: pointer.Int32Ptr(3600),
			AcquireTime:          &metav1.MicroTime{Time: acquireTime},
			RenewTime:            &metav1.MicroTime{Time: acquireTime},
		},
	}
}
<commit_msg>test/integration/controlplane: update APIServerIdentity tests to use public lease parameter vars<commit_after>/*
Copyright 2020 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 controlplane

import (
	"context"
	"crypto/sha256"
	"encoding/base32"
	"fmt"
	"os"
	"strings"
	"testing"
	"time"

	coordinationv1 "k8s.io/api/coordination/v1"
	corev1 "k8s.io/api/core/v1"
	apierrors "k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/util/wait"
	"k8s.io/apiserver/pkg/features"
	utilfeature "k8s.io/apiserver/pkg/util/feature"
	"k8s.io/client-go/kubernetes"
	featuregatetesting "k8s.io/component-base/featuregate/testing"
	kubeapiservertesting "k8s.io/kubernetes/cmd/kube-apiserver/app/testing"
	"k8s.io/kubernetes/pkg/controlplane"
	"k8s.io/kubernetes/test/integration/framework"
	"k8s.io/utils/pointer"
)

const (
	testLeaseName = "apiserver-lease-test"
)

func expectedAPIServerIdentity(hostname string) string {
	hash := sha256.Sum256([]byte(hostname))
	return "kube-apiserver-" + strings.ToLower(base32.StdEncoding.WithPadding(base32.NoPadding).EncodeToString(hash[:16]))
}

func TestCreateLeaseOnStart(t *testing.T) {
	defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.APIServerIdentity, true)()
	result := kubeapiservertesting.StartTestServerOrDie(t, nil, nil, framework.SharedEtcd())
	defer result.TearDownFn()

	kubeclient, err := kubernetes.NewForConfig(result.ClientConfig)
	if err != nil {
		t.Fatalf("Unexpected error: %v", err)
	}

	hostname, err := os.Hostname()
	if err != nil {
		t.Fatalf("Unexpected error getting apiserver hostname: %v", err)
	}

	t.Logf(`Waiting the kube-apiserver Lease to be created`)
	if err := wait.PollImmediate(500*time.Millisecond, 10*time.Second, func() (bool, error) {
		leases, err := kubeclient.
			CoordinationV1().
			Leases(metav1.NamespaceSystem).
			List(context.TODO(), metav1.ListOptions{LabelSelector: controlplane.KubeAPIServerIdentityLeaseLabelSelector})
		if err != nil {
			return false, err
		}

		if leases == nil {
			return false, nil
		}

		if len(leases.Items) != 1 {
			return false, nil
		}

		lease := leases.Items[0]
		if lease.Name != expectedAPIServerIdentity(hostname) {
			return false, fmt.Errorf("unexpected apiserver identity, got: %v, expected: %v", lease.Name, expectedAPIServerIdentity(hostname))
		}

		if lease.Labels[corev1.LabelHostname] != hostname {
			return false, fmt.Errorf("unexpected hostname label, got: %v, expected: %v", lease.Labels[corev1.LabelHostname], hostname)
		}

		return true, nil
	}); err != nil {
		t.Fatalf("Failed to see the kube-apiserver lease: %v", err)
	}
}

func TestLeaseGarbageCollection(t *testing.T) {
	oldIdentityLeaseDurationSeconds := controlplane.IdentityLeaseDurationSeconds
	oldIdentityLeaseGCPeriod := controlplane.IdentityLeaseGCPeriod
	oldIdentityLeaseRenewIntervalPeriod := controlplane.IdentityLeaseRenewIntervalPeriod
	defer func() {
		// reset the default values for leases after this test
		controlplane.IdentityLeaseDurationSeconds = oldIdentityLeaseDurationSeconds
		controlplane.IdentityLeaseGCPeriod = oldIdentityLeaseGCPeriod
		controlplane.IdentityLeaseRenewIntervalPeriod = oldIdentityLeaseRenewIntervalPeriod
	}()

	// Shorten lease parameters so GC behavior can be exercised in integration tests
	controlplane.IdentityLeaseDurationSeconds = 1
	controlplane.IdentityLeaseGCPeriod = time.Second
	controlplane.IdentityLeaseRenewIntervalPeriod = time.Second

	defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.APIServerIdentity, true)()
	result := kubeapiservertesting.StartTestServerOrDie(t, nil, nil, framework.SharedEtcd())
	defer result.TearDownFn()

	kubeclient, err := kubernetes.NewForConfig(result.ClientConfig)
	if err != nil {
		t.Fatalf("Unexpected error: %v", err)
	}
	expiredLease := newTestLease(time.Now().Add(-2*time.Hour), metav1.NamespaceSystem)
	t.Run("expired apiserver lease should be garbage collected",
		testLeaseGarbageCollected(t, kubeclient, expiredLease))

	freshLease := newTestLease(time.Now().Add(-2*time.Minute), metav1.NamespaceSystem)
	t.Run("fresh apiserver lease should not be garbage collected",
		testLeaseNotGarbageCollected(t, kubeclient, freshLease))

	expiredLease.Labels = nil
	t.Run("expired non-identity lease should not be garbage collected",
		testLeaseNotGarbageCollected(t, kubeclient, expiredLease))

	// identity leases (with k8s.io/component label) created in user namespaces should not be GC'ed
	expiredNonKubeSystemLease := newTestLease(time.Now().Add(-2*time.Hour), metav1.NamespaceDefault)
	t.Run("expired non-system identity lease should not be garbage collected",
		testLeaseNotGarbageCollected(t, kubeclient, expiredNonKubeSystemLease))
}

func testLeaseGarbageCollected(t *testing.T, client kubernetes.Interface, lease *coordinationv1.Lease) func(t *testing.T) {
	return func(t *testing.T) {
		ns := lease.Namespace
		if _, err := client.CoordinationV1().Leases(ns).Create(context.TODO(), lease, metav1.CreateOptions{}); err != nil {
			t.Fatalf("Unexpected error creating lease: %v", err)
		}
		if err := wait.PollImmediate(500*time.Millisecond, 5*time.Second, func() (bool, error) {
			_, err := client.CoordinationV1().Leases(ns).Get(context.TODO(), lease.Name, metav1.GetOptions{})
			if err == nil {
				return false, nil
			}
			if apierrors.IsNotFound(err) {
				return true, nil
			}
			return false, err
		}); err != nil {
			t.Fatalf("Failed to see the expired lease garbage collected: %v", err)
		}

	}
}

func testLeaseNotGarbageCollected(t *testing.T, client kubernetes.Interface, lease *coordinationv1.Lease) func(t *testing.T) {
	return func(t *testing.T) {
		ns := lease.Namespace
		if _, err := client.CoordinationV1().Leases(ns).Create(context.TODO(), lease, metav1.CreateOptions{}); err != nil {
			t.Fatalf("Unexpected error creating lease: %v", err)
		}
		if err := wait.PollImmediate(500*time.Millisecond, 5*time.Second, func() (bool, error) {
			_, err := client.CoordinationV1().Leases(ns).Get(context.TODO(), lease.Name, metav1.GetOptions{})
			if err != nil && apierrors.IsNotFound(err) {
				return true, nil
			}
			return false, nil
		}); err == nil {
			t.Fatalf("Unexpected valid lease getting garbage collected")
		}
		if _, err := client.CoordinationV1().Leases(ns).Get(context.TODO(), lease.Name, metav1.GetOptions{}); err != nil {
			t.Fatalf("Failed to retrieve valid lease: %v", err)
		}
		if err := client.CoordinationV1().Leases(ns).Delete(context.TODO(), lease.Name, metav1.DeleteOptions{}); err != nil {
			t.Fatalf("Failed to clean up valid lease: %v", err)
		}
	}
}

func newTestLease(acquireTime time.Time, namespace string) *coordinationv1.Lease {
	return &coordinationv1.Lease{
		ObjectMeta: metav1.ObjectMeta{
			Name:      testLeaseName,
			Namespace: namespace,
			Labels: map[string]string{
				controlplane.IdentityLeaseComponentLabelKey: controlplane.KubeAPIServer,
			},
		},
		Spec: coordinationv1.LeaseSpec{
			HolderIdentity:       pointer.StringPtr(testLeaseName),
			LeaseDurationSeconds: pointer.Int32Ptr(3600),
			AcquireTime:          &metav1.MicroTime{Time: acquireTime},
			RenewTime:            &metav1.MicroTime{Time: acquireTime},
		},
	}
}
<|endoftext|>


<commit_before>package yiigo

import (
	"fmt"
	"reflect"
	"strings"
	"sync"

	"gopkg.in/ini.v1"

	_ "github.com/go-sql-driver/mysql"
	"github.com/jmoiron/sqlx"
)

var (
	// DB default connection
	DB    *sqlx.DB
	dbmap sync.Map
)

// SQLExpr SQL expression
type SQLExpr struct {
	Expr string
	Args []interface{}
}

func initMySQL() error {
	sections := childSections("mysql")

	if len(sections) > 0 {
		return initMultiDB(sections)
	}

	return initSingleDB()
}

func initSingleDB() error {
	var err error

	section := env.Section("mysql")

	DB, err = dbDial(section)

	if err != nil {
		return fmt.Errorf("mysql error: %s", err.Error())
	}

	return nil
}

func initMultiDB(sections []*ini.Section) error {
	for _, v := range sections {
		db, err := dbDial(v)

		if err != nil {
			return fmt.Errorf("mysql error: %s", err.Error())
		}

		dbmap.Store(v.Name(), db)
	}

	if v, ok := dbmap.Load("mysql.default"); ok {
		DB = v.(*sqlx.DB)
	}

	return nil
}

func dbDial(section *ini.Section) (*sqlx.DB, error) {
	host := section.Key("host").MustString("localhost")
	port := section.Key("port").MustInt(3306)
	username := section.Key("username").MustString("root")
	password := section.Key("password").MustString("")
	database := section.Key("database").MustString("test")
	charset := section.Key("charset").MustString("utf8mb4")
	collection := section.Key("collection").MustString("utf8mb4_general_ci")
	maxOpenConns := section.Key("maxOpenConns").MustInt(20)
	maxIdleConns := section.Key("maxIdleConns").MustInt(10)

	dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=%s&collation=%s&parseTime=True&loc=Local", username, password, host, port, database, charset, collection)

	db, err := sqlx.Connect("mysql", dsn)

	if err != nil {
		return nil, err
	}

	db.SetMaxOpenConns(maxOpenConns)
	db.SetMaxIdleConns(maxIdleConns)

	return db, nil
}

// DBConn get db connection
func DBConn(conn ...string) (*sqlx.DB, error) {
	c := "default"

	if len(conn) > 0 {
		c = conn[0]
	}

	schema := fmt.Sprintf("mysql.%s", c)

	v, ok := dbmap.Load(schema)

	if !ok {
		return nil, fmt.Errorf("mysql %s is not connected", schema)
	}

	return v.(*sqlx.DB), nil
}

// InsertSQL returns insert sql and binds
func InsertSQL(table string, data interface{}) (string, []interface{}) {
	v := reflect.Indirect(reflect.ValueOf(data))

	sql := ""
	binds := []interface{}{}

	switch v.Kind() {
	case reflect.Struct:
		sql, binds = singleInsert(table, v)
	case reflect.Slice:
		if count := v.Len(); count > 0 {
			sql, binds = batchInsert(table, v, count)
		}
	}

	return sql, binds
}

// UpdateSQL returns update sql and binds
func UpdateSQL(sql string, data interface{}, args ...interface{}) (string, []interface{}) {
	v := reflect.Indirect(reflect.ValueOf(data))

	_sql := ""
	binds := []interface{}{}

	switch v.Kind() {
	case reflect.Map:
		if x, ok := data.(X); ok {
			_sql, binds = updateByMap(sql, x, args...)
		}
	case reflect.Struct:
		_sql, binds = updateByStruct(sql, v, args...)
	}

	return _sql, binds
}

// Expr returns expression, eg: yiigo.Expr("price * ? + ?", 2, 100)
func Expr(expr string, args ...interface{}) *SQLExpr {
	return &SQLExpr{Expr: expr, Args: args}
}

func singleInsert(table string, v reflect.Value) (string, []interface{}) {
	fieldNum := v.NumField()

	columns := make([]string, 0, fieldNum)
	placeholders := make([]string, 0, fieldNum)
	binds := make([]interface{}, 0, fieldNum)

	t := v.Type()

	for i := 0; i < fieldNum; i++ {
		column := t.Field(i).Tag.Get("db")

		if column == "" {
			column = t.Field(i).Name
		}

		columns = append(columns, fmt.Sprintf("`%s`", column))
		placeholders = append(placeholders, "?")
		binds = append(binds, v.Field(i).Interface())
	}

	sql := fmt.Sprintf("INSERT INTO `%s` (%s) VALUES (%s)", table, strings.Join(columns, ", "), strings.Join(placeholders, ", "))

	return sql, binds
}

func batchInsert(table string, v reflect.Value, count int) (string, []interface{}) {
	first := reflect.Indirect(v.Index(0))

	if first.Kind() != reflect.Struct {
		panic("the data must be a slice to struct")
	}

	fieldNum := first.NumField()

	columns := make([]string, 0, fieldNum)
	placeholders := make([]string, 0, fieldNum)
	binds := make([]interface{}, 0, fieldNum*count)

	t := first.Type()

	for i := 0; i < fieldNum; i++ {
		column := t.Field(i).Tag.Get("db")

		if column == "" {
			column = t.Field(i).Name
		}

		columns = append(columns, fmt.Sprintf("`%s`", column))
	}

	for i := 0; i < count; i++ {
		phrs := make([]string, 0, fieldNum)

		for j := 0; j < fieldNum; j++ {
			phrs = append(phrs, "?")
			binds = append(binds, reflect.Indirect(v.Index(i)).Field(j).Interface())
		}

		placeholders = append(placeholders, fmt.Sprintf("(%s)", strings.Join(phrs, ", ")))
	}

	sql := fmt.Sprintf("INSERT INTO `%s` (%s) VALUES %s", table, strings.Join(columns, ", "), strings.Join(placeholders, ","))

	return sql, binds
}

func updateByMap(sql string, data X, args ...interface{}) (string, []interface{}) {
	sets := []string{}
	binds := []interface{}{}

	for k, v := range data {
		if e, ok := v.(*SQLExpr); ok {
			sets = append(sets, fmt.Sprintf("`%s` = %s", k, e.Expr))
			binds = append(binds, e.Args...)
		} else {
			sets = append(sets, fmt.Sprintf("`%s` = ?", k))
			binds = append(binds, v)
		}
	}

	sql = strings.Replace(sql, "?", strings.Join(sets, ", "), 1)
	binds = append(binds, args...)

	return sql, binds
}

func updateByStruct(sql string, v reflect.Value, args ...interface{}) (string, []interface{}) {
	fieldNum := v.NumField()

	sets := make([]string, 0, fieldNum)
	binds := make([]interface{}, 0, fieldNum+len(args))

	t := v.Type()

	for i := 0; i < fieldNum; i++ {
		column := t.Field(i).Tag.Get("db")

		if column == "" {
			column = t.Field(i).Name
		}

		field := v.Field(i).Interface()

		if e, ok := field.(*SQLExpr); ok {
			sets = append(sets, fmt.Sprintf("`%s` = %s", column, e.Expr))
			binds = append(binds, e.Args...)
		} else {
			sets = append(sets, fmt.Sprintf("`%s` = ?", column))
			binds = append(binds, field)
		}
	}

	sql = strings.Replace(sql, "?", strings.Join(sets, ", "), 1)
	binds = append(binds, args...)

	return sql, binds
}
<commit_msg>insert with yiigo.X<commit_after>package yiigo

import (
	"fmt"
	"reflect"
	"strings"
	"sync"

	"gopkg.in/ini.v1"

	_ "github.com/go-sql-driver/mysql"
	"github.com/jmoiron/sqlx"
)

var (
	// DB default connection
	DB    *sqlx.DB
	dbmap sync.Map
)

// SQLExpr SQL expression
type SQLExpr struct {
	Expr string
	Args []interface{}
}

func initMySQL() error {
	sections := childSections("mysql")

	if len(sections) > 0 {
		return initMultiDB(sections)
	}

	return initSingleDB()
}

func initSingleDB() error {
	var err error

	section := env.Section("mysql")

	DB, err = dbDial(section)

	if err != nil {
		return fmt.Errorf("mysql error: %s", err.Error())
	}

	return nil
}

func initMultiDB(sections []*ini.Section) error {
	for _, v := range sections {
		db, err := dbDial(v)

		if err != nil {
			return fmt.Errorf("mysql error: %s", err.Error())
		}

		dbmap.Store(v.Name(), db)
	}

	if v, ok := dbmap.Load("mysql.default"); ok {
		DB = v.(*sqlx.DB)
	}

	return nil
}

func dbDial(section *ini.Section) (*sqlx.DB, error) {
	host := section.Key("host").MustString("localhost")
	port := section.Key("port").MustInt(3306)
	username := section.Key("username").MustString("root")
	password := section.Key("password").MustString("")
	database := section.Key("database").MustString("test")
	charset := section.Key("charset").MustString("utf8mb4")
	collection := section.Key("collection").MustString("utf8mb4_general_ci")
	maxOpenConns := section.Key("maxOpenConns").MustInt(20)
	maxIdleConns := section.Key("maxIdleConns").MustInt(10)

	dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=%s&collation=%s&parseTime=True&loc=Local", username, password, host, port, database, charset, collection)

	db, err := sqlx.Connect("mysql", dsn)

	if err != nil {
		return nil, err
	}

	db.SetMaxOpenConns(maxOpenConns)
	db.SetMaxIdleConns(maxIdleConns)

	return db, nil
}

// DBConn get db connection
func DBConn(conn ...string) (*sqlx.DB, error) {
	c := "default"

	if len(conn) > 0 {
		c = conn[0]
	}

	schema := fmt.Sprintf("mysql.%s", c)

	v, ok := dbmap.Load(schema)

	if !ok {
		return nil, fmt.Errorf("mysql %s is not connected", schema)
	}

	return v.(*sqlx.DB), nil
}

// InsertSQL returns insert sql and binds
// data expect struct, []struct, yiigo.X, []yiigo.X
func InsertSQL(table string, data interface{}) (string, []interface{}) {
	v := reflect.Indirect(reflect.ValueOf(data))

	sql := ""
	binds := []interface{}{}

	switch v.Kind() {
	case reflect.Map:
		if x, ok := data.(X); ok {
			sql, binds = singleInsertWithMap(sql, x)
		}
	case reflect.Struct:
		sql, binds = singleInsertWithStruct(table, v)
	case reflect.Slice:
		if count := v.Len(); count > 0 {
			elemKind := v.Type().Elem().Kind()

			if elemKind == reflect.Map {
				if x, ok := data.([]X); ok {
					sql, binds = batchInsertWithMap(table, x, count)
				}

				break
			}

			if elemKind == reflect.Struct {
				sql, binds = batchInsertWithStruct(table, v, count)

				break
			}
		}
	}

	return sql, binds
}

// UpdateSQL returns update sql and binds
// data expect struct, yiigo.X
func UpdateSQL(sql string, data interface{}, args ...interface{}) (string, []interface{}) {
	v := reflect.Indirect(reflect.ValueOf(data))

	_sql := ""
	binds := []interface{}{}

	switch v.Kind() {
	case reflect.Map:
		if x, ok := data.(X); ok {
			_sql, binds = updateWithMap(sql, x, args...)
		}
	case reflect.Struct:
		_sql, binds = updateWithStruct(sql, v, args...)
	}

	return _sql, binds
}

// Expr returns expression, eg: yiigo.Expr("price * ? + ?", 2, 100)
func Expr(expr string, args ...interface{}) *SQLExpr {
	return &SQLExpr{Expr: expr, Args: args}
}

func singleInsertWithMap(table string, data X) (string, []interface{}) {
	fieldNum := len(data)

	columns := make([]string, 0, fieldNum)
	placeholders := make([]string, 0, fieldNum)
	binds := make([]interface{}, 0, fieldNum)

	for k, v := range data {
		columns = append(columns, fmt.Sprintf("`%s`", k))
		placeholders = append(placeholders, "?")
		binds = append(binds, v)
	}

	sql := fmt.Sprintf("INSERT INTO `%s` (%s) VALUES (%s)", table, strings.Join(columns, ", "), strings.Join(placeholders, ", "))

	return sql, binds
}

func singleInsertWithStruct(table string, v reflect.Value) (string, []interface{}) {
	fieldNum := v.NumField()

	columns := make([]string, 0, fieldNum)
	placeholders := make([]string, 0, fieldNum)
	binds := make([]interface{}, 0, fieldNum)

	t := v.Type()

	for i := 0; i < fieldNum; i++ {
		column := t.Field(i).Tag.Get("db")

		if column == "" {
			column = t.Field(i).Name
		}

		columns = append(columns, fmt.Sprintf("`%s`", column))
		placeholders = append(placeholders, "?")
		binds = append(binds, v.Field(i).Interface())
	}

	sql := fmt.Sprintf("INSERT INTO `%s` (%s) VALUES (%s)", table, strings.Join(columns, ", "), strings.Join(placeholders, ", "))

	return sql, binds
}

func batchInsertWithStruct(table string, v reflect.Value, count int) (string, []interface{}) {
	first := reflect.Indirect(v.Index(0))

	if first.Kind() != reflect.Struct {
		panic("the data must be a slice to struct")
	}

	fieldNum := first.NumField()

	columns := make([]string, 0, fieldNum)
	placeholders := make([]string, 0, fieldNum)
	binds := make([]interface{}, 0, fieldNum*count)

	t := first.Type()

	for i := 0; i < fieldNum; i++ {
		column := t.Field(i).Tag.Get("db")

		if column == "" {
			column = t.Field(i).Name
		}

		columns = append(columns, fmt.Sprintf("`%s`", column))
	}

	for i := 0; i < count; i++ {
		phrs := make([]string, 0, fieldNum)

		for j := 0; j < fieldNum; j++ {
			phrs = append(phrs, "?")
			binds = append(binds, reflect.Indirect(v.Index(i)).Field(j).Interface())
		}

		placeholders = append(placeholders, fmt.Sprintf("(%s)", strings.Join(phrs, ", ")))
	}

	sql := fmt.Sprintf("INSERT INTO `%s` (%s) VALUES %s", table, strings.Join(columns, ", "), strings.Join(placeholders, ","))

	return sql, binds
}

func batchInsertWithMap(table string, data []X, count int) (string, []interface{}) {
	fieldNum := len(data[0])

	fields := make([]string, 0, fieldNum)
	columns := make([]string, 0, fieldNum)
	placeholders := make([]string, 0, fieldNum)
	binds := make([]interface{}, 0, fieldNum*count)

	for k := range data[0] {
		fields = append(fields, k)
		columns = append(columns, fmt.Sprintf("`%s`", k))
	}

	fmt.Println(columns)

	for _, x := range data {
		phrs := make([]string, 0, fieldNum)

		for _, v := range fields {
			phrs = append(phrs, "?")
			binds = append(binds, x[v])
		}

		placeholders = append(placeholders, fmt.Sprintf("(%s)", strings.Join(phrs, ", ")))
	}

	sql := fmt.Sprintf("INSERT INTO `%s` (%s) VALUES %s", table, strings.Join(columns, ", "), strings.Join(placeholders, ","))

	return sql, binds
}

func updateWithMap(sql string, data X, args ...interface{}) (string, []interface{}) {
	sets := []string{}
	binds := []interface{}{}

	for k, v := range data {
		if e, ok := v.(*SQLExpr); ok {
			sets = append(sets, fmt.Sprintf("`%s` = %s", k, e.Expr))
			binds = append(binds, e.Args...)
		} else {
			sets = append(sets, fmt.Sprintf("`%s` = ?", k))
			binds = append(binds, v)
		}
	}

	sql = strings.Replace(sql, "?", strings.Join(sets, ", "), 1)
	binds = append(binds, args...)

	return sql, binds
}

func updateWithStruct(sql string, v reflect.Value, args ...interface{}) (string, []interface{}) {
	fieldNum := v.NumField()

	sets := make([]string, 0, fieldNum)
	binds := make([]interface{}, 0, fieldNum+len(args))

	t := v.Type()

	for i := 0; i < fieldNum; i++ {
		column := t.Field(i).Tag.Get("db")

		if column == "" {
			column = t.Field(i).Name
		}

		field := v.Field(i).Interface()

		if e, ok := field.(*SQLExpr); ok {
			sets = append(sets, fmt.Sprintf("`%s` = %s", column, e.Expr))
			binds = append(binds, e.Args...)
		} else {
			sets = append(sets, fmt.Sprintf("`%s` = ?", column))
			binds = append(binds, field)
		}
	}

	sql = strings.Replace(sql, "?", strings.Join(sets, ", "), 1)
	binds = append(binds, args...)

	return sql, binds
}
<|endoftext|>


<commit_before>package equalfile

import (
	"bytes"
	"encoding/hex"
	"fmt"
	"hash"
	"io"
	"os"
)

const defaultMaxSize = 10000000000 // Only the first 10^10 bytes are compared.
const defaultBufSize = 20000

type Options struct {
	Debug         bool // enable debugging to stdout
	ForceFileRead bool // prevent shortcut at filesystem level (link, pathname, etc)
	MaxSize       int64
}

type Cmp struct {
	Opt Options

	readCount int
	readMin   int
	readMax   int
	readSum   int64

	hashType         hash.Hash
	hashMatchCompare bool
	hashTable        map[string]hashSum

	buf []byte
}

type hashSum struct {
	result []byte
	err    error
}

// New creates Cmp for multiple comparison mode.
func NewMultiple(buf []byte, options Options, h hash.Hash, compareOnMatch bool) *Cmp {
	c := &Cmp{
		Opt:              options,
		hashType:         h,
		hashMatchCompare: compareOnMatch,
		hashTable:        map[string]hashSum{},
		buf:              buf,
	}
	if c.Opt.MaxSize == 0 {
		c.Opt.MaxSize = defaultMaxSize
	}
	if c.buf == nil || len(c.buf) == 0 {
		c.buf = make([]byte, defaultBufSize)
	}
	return c
}

// New creates Cmp for single comparison mode.
func New(buf []byte, options Options) *Cmp {
	return NewMultiple(buf, options, nil, true)
}

func (c *Cmp) getHash(path string) ([]byte, error) {
	h, found := c.hashTable[path]
	if found {
		return h.result, h.err
	}

	f, openErr := os.Open(path)
	if openErr != nil {
		return nil, openErr
	}
	defer f.Close()

	sum := make([]byte, c.hashType.Size())
	c.hashType.Reset()
	n, copyErr := io.CopyN(c.hashType, f, c.Opt.MaxSize)
	copy(sum, c.hashType.Sum(nil))

	if copyErr == io.EOF && n < c.Opt.MaxSize {
		copyErr = nil
	}

	return c.newHash(path, sum, copyErr)
}

func (c *Cmp) newHash(path string, sum []byte, e error) ([]byte, error) {

	c.hashTable[path] = hashSum{sum, e}

	if c.Opt.Debug {
		fmt.Printf("newHash[%s]=%v: error=[%v]\n", path, hex.EncodeToString(sum), e)
	}

	return sum, e
}

func (c *Cmp) multipleMode() bool {
	return c.hashType != nil
}

// CompareFile verifies that files with names path1, path2 have same contents.
func (c *Cmp) CompareFile(path1, path2 string) (bool, error) {

	if c.multipleMode() {
		h1, err1 := c.getHash(path1)
		if err1 != nil {
			return false, err1
		}
		h2, err2 := c.getHash(path2)
		if err2 != nil {
			return false, err2
		}
		if !bytes.Equal(h1, h2) {
			return false, nil // hashes mismatch
		}
		// hashes match
		if !c.hashMatchCompare {
			return true, nil // accept hash match without byte-by-byte comparison
		}
		// do byte-by-byte comparison
		if c.Opt.Debug {
			fmt.Printf("CompareFile(%s,%s): hash match, will compare bytes\n", path1, path2)
		}
	}

	r1, openErr1 := os.Open(path1)
	if openErr1 != nil {
		return false, openErr1
	}
	defer r1.Close()
	info1, statErr1 := r1.Stat()
	if statErr1 != nil {
		return false, statErr1
	}

	r2, openErr2 := os.Open(path2)
	if openErr2 != nil {
		return false, openErr2
	}
	defer r2.Close()
	info2, statErr2 := r2.Stat()
	if statErr2 != nil {
		return false, statErr2
	}

	if info1.Size() != info2.Size() {
		return false, nil
	}

	if !c.Opt.ForceFileRead {
		// shortcut: ask the filesystem: are these files the same? (link, pathname, etc)
		if os.SameFile(info1, info2) {
			return true, nil
		}
	}

	return c.CompareReader(r1, r2)
}

func (c *Cmp) read(r io.Reader, buf []byte) (int, error) {
	n, err := r.Read(buf)

	if c.Opt.Debug {
		c.readCount++
		c.readSum += int64(n)
		if n < c.readMin {
			c.readMin = n
		}
		if n > c.readMax {
			c.readMax = n
		}
	}

	return n, err
}

// CompareReader verifies that two readers provide same content.
func (c *Cmp) CompareReader(r1, r2 io.Reader) (bool, error) {

	if c.Opt.Debug {
		c.readCount = 0
		c.readMin = 2000000000
		c.readMax = 0
		c.readSum = 0
	}

	equal, err := c.compareReader(r1, r2)

	if c.Opt.Debug {
		fmt.Printf("DEBUG CompareReader(%d,%d): readCount=%d readMin=%d readMax=%d readSum=%d\n",
			len(c.buf), c.Opt.MaxSize, c.readCount, c.readMin, c.readMax, c.readSum)
	}

	return equal, err
}

func readPartial(c *Cmp, r io.Reader, buf []byte, n1, n2 int) (int, error) {
	for n1 < n2 {
		n, err := c.read(r, buf[n1:n2])
		n1 += n
		if err != nil {
			return n1, err
		}
	}
	return n1, nil
}

func (c *Cmp) compareReader(r1, r2 io.Reader) (bool, error) {

	maxSize := c.Opt.MaxSize
	if maxSize < 1 {
		return false, fmt.Errorf("nonpositive max size")
	}

	buf := c.buf

	size := len(buf) / 2
	if size < 1 {
		return false, fmt.Errorf("insufficient buffer size")
	}

	buf1 := buf[:size]
	buf2 := buf[size : 2*size] // must force same size as buf1

	if len(buf1) != len(buf2) {
		return false, fmt.Errorf("buffer size mismatch buf1=%d buf2=%d", len(buf1), len(buf2))
	}

	eof1 := false
	eof2 := false
	var readSize int64

	for !eof1 && !eof2 {
		n1, err1 := c.read(r1, buf1)
		switch err1 {
		case io.EOF:
			eof1 = true
		case nil:
		default:
			return false, err1
		}

		n2, err2 := c.read(r2, buf2)
		switch err2 {
		case io.EOF:
			eof2 = true
		case nil:
		default:
			return false, err2
		}

		switch {
		case n1 < n2:
			n, errPart := readPartial(c, r1, buf1, n1, n2)
			switch errPart {
			case io.EOF:
				eof1 = true
			case nil:
			default:
				return false, errPart
			}
			n1 = n
		case n2 < n1:
			n, errPart := readPartial(c, r2, buf2, n2, n1)
			switch errPart {
			case io.EOF:
				eof2 = true
			case nil:
			default:
				return false, errPart
			}
			n2 = n
		}

		if n1 != n2 {
			return false, nil
		}

		if !bytes.Equal(buf1[:n1], buf2[:n2]) {
			return false, nil
		}

		readSize += int64(n1)
		if readSize > maxSize {
			return true, fmt.Errorf("max read size reached")
		}
	}

	if !eof1 || !eof2 {
		return false, nil
	}

	return true, nil
}
<commit_msg>Clarify MaxSize.<commit_after>package equalfile

import (
	"bytes"
	"encoding/hex"
	"fmt"
	"hash"
	"io"
	"os"
)

const defaultMaxSize = 10000000000 // Only the first 10^10 bytes are compared.
const defaultBufSize = 20000

type Options struct {
	Debug         bool  // enable debugging to stdout
	ForceFileRead bool  // prevent shortcut at filesystem level (link, pathname, etc)
	MaxSize       int64 // prevent forever reading from an infinite reader
}

type Cmp struct {
	Opt Options

	readCount int
	readMin   int
	readMax   int
	readSum   int64

	hashType         hash.Hash
	hashMatchCompare bool
	hashTable        map[string]hashSum

	buf []byte
}

type hashSum struct {
	result []byte
	err    error
}

// New creates Cmp for multiple comparison mode.
func NewMultiple(buf []byte, options Options, h hash.Hash, compareOnMatch bool) *Cmp {
	c := &Cmp{
		Opt:              options,
		hashType:         h,
		hashMatchCompare: compareOnMatch,
		hashTable:        map[string]hashSum{},
		buf:              buf,
	}
	if c.Opt.MaxSize == 0 {
		c.Opt.MaxSize = defaultMaxSize
	}
	if c.buf == nil || len(c.buf) == 0 {
		c.buf = make([]byte, defaultBufSize)
	}
	return c
}

// New creates Cmp for single comparison mode.
func New(buf []byte, options Options) *Cmp {
	return NewMultiple(buf, options, nil, true)
}

func (c *Cmp) getHash(path string) ([]byte, error) {
	h, found := c.hashTable[path]
	if found {
		return h.result, h.err
	}

	f, openErr := os.Open(path)
	if openErr != nil {
		return nil, openErr
	}
	defer f.Close()

	sum := make([]byte, c.hashType.Size())
	c.hashType.Reset()
	n, copyErr := io.CopyN(c.hashType, f, c.Opt.MaxSize)
	copy(sum, c.hashType.Sum(nil))

	if copyErr == io.EOF && n < c.Opt.MaxSize {
		copyErr = nil
	}

	return c.newHash(path, sum, copyErr)
}

func (c *Cmp) newHash(path string, sum []byte, e error) ([]byte, error) {

	c.hashTable[path] = hashSum{sum, e}

	if c.Opt.Debug {
		fmt.Printf("newHash[%s]=%v: error=[%v]\n", path, hex.EncodeToString(sum), e)
	}

	return sum, e
}

func (c *Cmp) multipleMode() bool {
	return c.hashType != nil
}

// CompareFile verifies that files with names path1, path2 have same contents.
func (c *Cmp) CompareFile(path1, path2 string) (bool, error) {

	if c.multipleMode() {
		h1, err1 := c.getHash(path1)
		if err1 != nil {
			return false, err1
		}
		h2, err2 := c.getHash(path2)
		if err2 != nil {
			return false, err2
		}
		if !bytes.Equal(h1, h2) {
			return false, nil // hashes mismatch
		}
		// hashes match
		if !c.hashMatchCompare {
			return true, nil // accept hash match without byte-by-byte comparison
		}
		// do byte-by-byte comparison
		if c.Opt.Debug {
			fmt.Printf("CompareFile(%s,%s): hash match, will compare bytes\n", path1, path2)
		}
	}

	r1, openErr1 := os.Open(path1)
	if openErr1 != nil {
		return false, openErr1
	}
	defer r1.Close()
	info1, statErr1 := r1.Stat()
	if statErr1 != nil {
		return false, statErr1
	}

	r2, openErr2 := os.Open(path2)
	if openErr2 != nil {
		return false, openErr2
	}
	defer r2.Close()
	info2, statErr2 := r2.Stat()
	if statErr2 != nil {
		return false, statErr2
	}

	if info1.Size() != info2.Size() {
		return false, nil
	}

	if !c.Opt.ForceFileRead {
		// shortcut: ask the filesystem: are these files the same? (link, pathname, etc)
		if os.SameFile(info1, info2) {
			return true, nil
		}
	}

	return c.CompareReader(r1, r2)
}

func (c *Cmp) read(r io.Reader, buf []byte) (int, error) {
	n, err := r.Read(buf)

	if c.Opt.Debug {
		c.readCount++
		c.readSum += int64(n)
		if n < c.readMin {
			c.readMin = n
		}
		if n > c.readMax {
			c.readMax = n
		}
	}

	return n, err
}

// CompareReader verifies that two readers provide same content.
func (c *Cmp) CompareReader(r1, r2 io.Reader) (bool, error) {

	if c.Opt.Debug {
		c.readCount = 0
		c.readMin = 2000000000
		c.readMax = 0
		c.readSum = 0
	}

	equal, err := c.compareReader(r1, r2)

	if c.Opt.Debug {
		fmt.Printf("DEBUG CompareReader(%d,%d): readCount=%d readMin=%d readMax=%d readSum=%d\n",
			len(c.buf), c.Opt.MaxSize, c.readCount, c.readMin, c.readMax, c.readSum)
	}

	return equal, err
}

func readPartial(c *Cmp, r io.Reader, buf []byte, n1, n2 int) (int, error) {
	for n1 < n2 {
		n, err := c.read(r, buf[n1:n2])
		n1 += n
		if err != nil {
			return n1, err
		}
	}
	return n1, nil
}

func (c *Cmp) compareReader(r1, r2 io.Reader) (bool, error) {

	maxSize := c.Opt.MaxSize
	if maxSize < 1 {
		return false, fmt.Errorf("nonpositive max size")
	}

	buf := c.buf

	size := len(buf) / 2
	if size < 1 {
		return false, fmt.Errorf("insufficient buffer size")
	}

	buf1 := buf[:size]
	buf2 := buf[size : 2*size] // must force same size as buf1

	if len(buf1) != len(buf2) {
		return false, fmt.Errorf("buffer size mismatch buf1=%d buf2=%d", len(buf1), len(buf2))
	}

	eof1 := false
	eof2 := false
	var readSize int64

	for !eof1 && !eof2 {
		n1, err1 := c.read(r1, buf1)
		switch err1 {
		case io.EOF:
			eof1 = true
		case nil:
		default:
			return false, err1
		}

		n2, err2 := c.read(r2, buf2)
		switch err2 {
		case io.EOF:
			eof2 = true
		case nil:
		default:
			return false, err2
		}

		switch {
		case n1 < n2:
			n, errPart := readPartial(c, r1, buf1, n1, n2)
			switch errPart {
			case io.EOF:
				eof1 = true
			case nil:
			default:
				return false, errPart
			}
			n1 = n
		case n2 < n1:
			n, errPart := readPartial(c, r2, buf2, n2, n1)
			switch errPart {
			case io.EOF:
				eof2 = true
			case nil:
			default:
				return false, errPart
			}
			n2 = n
		}

		if n1 != n2 {
			return false, nil
		}

		if !bytes.Equal(buf1[:n1], buf2[:n2]) {
			return false, nil
		}

		readSize += int64(n1)
		if readSize > maxSize {
			return true, fmt.Errorf("max read size reached")
		}
	}

	if !eof1 || !eof2 {
		return false, nil
	}

	return true, nil
}
<|endoftext|>


<commit_before>package api_test

import (
	"code.google.com/p/gogoprotobuf/proto"
	"fmt"
	. "github.com/cloudfoundry/cli/cf/api"
	"github.com/cloudfoundry/loggregatorlib/logmessage"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	"time"
)

var _ = Describe("Testing with ginkgo", func() {
	It("PriorityQueue", func() {
		pq := NewSortedMessageQueue(10*time.Millisecond, time.Now)

		msg3 := logMessageWithTime("message 3", 130)
		pq.PushMessage(msg3)
		msg2 := logMessageWithTime("message 2", 120)
		pq.PushMessage(msg2)
		msg4 := logMessageWithTime("message 4", 140)
		pq.PushMessage(msg4)
		msg1 := logMessageWithTime("message 1", 110)
		pq.PushMessage(msg1)

		Expect(getMsgString(pq.PopMessage())).To(Equal(getMsgString(msg1)))
		Expect(getMsgString(pq.PopMessage())).To(Equal(getMsgString(msg2)))
		Expect(getMsgString(pq.PopMessage())).To(Equal(getMsgString(msg3)))
		Expect(getMsgString(pq.PopMessage())).To(Equal(getMsgString(msg4)))
	})

	It("pops on empty queue", func() {
		pq := NewSortedMessageQueue(10*time.Millisecond, time.Now)
		Expect(pq.PopMessage()).To(BeNil())
	})

	It("NextTimeStamp returns the timestamp of the log message at the head of the queue", func() {
		currentTime := time.Unix(5, 0)
		clock := func() time.Time {
			return currentTime
		}

		pq := NewSortedMessageQueue(5*time.Second, clock)
		Expect(pq.NextTimestamp()).To(Equal(MAX_INT64))

		msg2 := logMessageWithTime("message 2", 130)
		pq.PushMessage(msg2)

		currentTime = time.Unix(6, 0)
		msg1 := logMessageWithTime("message 1", 100)
		pq.PushMessage(msg1)
		Expect(pq.NextTimestamp()).To(Equal(time.Unix(11, 0).UnixNano()))

		readMessage := pq.PopMessage()
		Expect(readMessage.GetTimestamp()).To(Equal(int64(100)))
		Expect(pq.NextTimestamp()).To(Equal(time.Unix(10, 0).UnixNano()))

		readMessage = pq.PopMessage()
		Expect(readMessage.GetTimestamp()).To(Equal(int64(130)))
		Expect(pq.NextTimestamp()).To(Equal(MAX_INT64))
	})

	It("sorts messages based on their timestamp", func() {
		pq := NewSortedMessageQueue(10*time.Millisecond, time.Now)

		msg1 := logMessageWithTime("message first", 109)
		pq.PushMessage(msg1)

		for i := 1; i < 1000; i++ {
			msg := logMessageWithTime(fmt.Sprintf("message %d", i), 110)
			pq.PushMessage(msg)
		}
		msg2 := logMessageWithTime("message last", 111)
		pq.PushMessage(msg2)

		Expect(getMsgString(pq.PopMessage())).To(Equal("message first"))

		for i := 1; i < 1000; i++ {
			Expect(getMsgString(pq.PopMessage())).To(Equal(fmt.Sprintf("message %d", i)))
		}

		Expect(getMsgString(pq.PopMessage())).To(Equal("message last"))
	})
})

func logMessageWithTime(messageString string, timestamp int) *logmessage.LogMessage {
	return generateMessage(messageString, int64(timestamp))
}

func generateMessage(messageString string, timestamp int64) *logmessage.LogMessage {
	messageType := logmessage.LogMessage_OUT
	sourceName := "DEA"
	return &logmessage.LogMessage{
		Message:     []byte(messageString),
		AppId:       proto.String("my-app-guid"),
		MessageType: &messageType,
		SourceName:  &sourceName,
		Timestamp:   proto.Int64(timestamp),
	}
}

func getMsgString(message *logmessage.LogMessage) string {
	return string(message.GetMessage())
}
<commit_msg>Remove a stray "testing with ginkgo" line<commit_after>package api_test

import (
	"fmt"
	"time"

	"code.google.com/p/gogoprotobuf/proto"
	"github.com/cloudfoundry/loggregatorlib/logmessage"

	. "github.com/cloudfoundry/cli/cf/api"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
)

var _ = Describe("is a priority queue used to sort loggregator messages", func() {
	It("PriorityQueue returns a new queue", func() {
		pq := NewSortedMessageQueue(10*time.Millisecond, time.Now)

		msg3 := logMessageWithTime("message 3", 130)
		pq.PushMessage(msg3)
		msg2 := logMessageWithTime("message 2", 120)
		pq.PushMessage(msg2)
		msg4 := logMessageWithTime("message 4", 140)
		pq.PushMessage(msg4)
		msg1 := logMessageWithTime("message 1", 110)
		pq.PushMessage(msg1)

		Expect(getMsgString(pq.PopMessage())).To(Equal(getMsgString(msg1)))
		Expect(getMsgString(pq.PopMessage())).To(Equal(getMsgString(msg2)))
		Expect(getMsgString(pq.PopMessage())).To(Equal(getMsgString(msg3)))
		Expect(getMsgString(pq.PopMessage())).To(Equal(getMsgString(msg4)))
	})

	It("pops on empty queue", func() {
		pq := NewSortedMessageQueue(10*time.Millisecond, time.Now)
		Expect(pq.PopMessage()).To(BeNil())
	})

	It("NextTimeStamp returns the timestamp of the log message at the head of the queue", func() {
		currentTime := time.Unix(5, 0)
		clock := func() time.Time {
			return currentTime
		}

		pq := NewSortedMessageQueue(5*time.Second, clock)
		Expect(pq.NextTimestamp()).To(Equal(MAX_INT64))

		msg2 := logMessageWithTime("message 2", 130)
		pq.PushMessage(msg2)

		currentTime = time.Unix(6, 0)
		msg1 := logMessageWithTime("message 1", 100)
		pq.PushMessage(msg1)
		Expect(pq.NextTimestamp()).To(Equal(time.Unix(11, 0).UnixNano()))

		readMessage := pq.PopMessage()
		Expect(readMessage.GetTimestamp()).To(Equal(int64(100)))
		Expect(pq.NextTimestamp()).To(Equal(time.Unix(10, 0).UnixNano()))

		readMessage = pq.PopMessage()
		Expect(readMessage.GetTimestamp()).To(Equal(int64(130)))
		Expect(pq.NextTimestamp()).To(Equal(MAX_INT64))
	})

	It("sorts messages based on their timestamp", func() {
		pq := NewSortedMessageQueue(10*time.Millisecond, time.Now)

		msg1 := logMessageWithTime("message first", 109)
		pq.PushMessage(msg1)

		for i := 1; i < 1000; i++ {
			msg := logMessageWithTime(fmt.Sprintf("message %d", i), 110)
			pq.PushMessage(msg)
		}
		msg2 := logMessageWithTime("message last", 111)
		pq.PushMessage(msg2)

		Expect(getMsgString(pq.PopMessage())).To(Equal("message first"))

		for i := 1; i < 1000; i++ {
			Expect(getMsgString(pq.PopMessage())).To(Equal(fmt.Sprintf("message %d", i)))
		}

		Expect(getMsgString(pq.PopMessage())).To(Equal("message last"))
	})
})

func logMessageWithTime(messageString string, timestamp int) *logmessage.LogMessage {
	return generateMessage(messageString, int64(timestamp))
}

func generateMessage(messageString string, timestamp int64) *logmessage.LogMessage {
	messageType := logmessage.LogMessage_OUT
	sourceName := "DEA"
	return &logmessage.LogMessage{
		Message:     []byte(messageString),
		AppId:       proto.String("my-app-guid"),
		MessageType: &messageType,
		SourceName:  &sourceName,
		Timestamp:   proto.Int64(timestamp),
	}
}

func getMsgString(message *logmessage.LogMessage) string {
	return string(message.GetMessage())
}
<|endoftext|>


<commit_before>/*
Copyright (c) 2015-2016 VMware, Inc. All Rights Reserved.

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

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

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

package portgroup

import (
	"context"
	"flag"
	"fmt"
	"io"
	"reflect"
	"sort"
	"strings"

	"github.com/vmware/govmomi/govc/cli"
	"github.com/vmware/govmomi/govc/flags"
	"github.com/vmware/govmomi/object"
	"github.com/vmware/govmomi/vim25/mo"
	"github.com/vmware/govmomi/vim25/types"
)

type info struct {
	*flags.DatacenterFlag

	pg         string
	active     bool
	connected  bool
	inside     bool
	uplinkPort bool
	vlanID     int
	count      int
	dvsRules   bool
}

var protocols = map[int32]string{
	1:  "icmp",
	2:  "igmp",
	6:  "tcp",
	17: "udp",
	58: "ipv6-icmp",
}

type trafficRule struct {
	Description        string
	Direction          string
	Protocol           string
	SourceAddress      string
	SourceIpPort       string
	DestinationAddress string
	DestinationIpPort  string
}

func init() {
	cli.Register("dvs.portgroup.info", &info{})
}

func (cmd *info) Register(ctx context.Context, f *flag.FlagSet) {
	cmd.DatacenterFlag, ctx = flags.NewDatacenterFlag(ctx)
	cmd.DatacenterFlag.Register(ctx, f)

	f.StringVar(&cmd.pg, "pg", "", "Distributed Virtual Portgroup")
	f.BoolVar(&cmd.active, "active", false, "Filter by port active or inactive status")
	f.BoolVar(&cmd.connected, "connected", false, "Filter by port connected or disconnected status")
	f.BoolVar(&cmd.inside, "inside", true, "Filter by port inside or outside status")
	f.BoolVar(&cmd.uplinkPort, "uplinkPort", false, "Filter for uplink ports")
	f.IntVar(&cmd.vlanID, "vlan", 0, "Filter by VLAN ID (0 = unfiltered)")
	f.IntVar(&cmd.count, "count", 0, "Number of matches to return (0 = unlimited)")
	f.BoolVar(&cmd.dvsRules, "r", false, "Show DVS rules")
}

func (cmd *info) Usage() string {
	return "DVS"
}

func (cmd *info) Description() string {
	return `Portgroup info for DVS.

Examples:
  govc dvs.portgroup.info DSwitch
  govc dvs.portgroup.info -pg InternalNetwork DSwitch
  govc find / -type DistributedVirtualSwitch | xargs -n1 govc dvs.portgroup.info`
}

type infoResult struct {
	Port []types.DistributedVirtualPort
	cmd  *info
}

func printPort(port types.BaseDvsIpPort) string {
	if port != nil {
		switch port.(type) {
		case *types.DvsSingleIpPort:
			return fmt.Sprintf("%d", port.(*types.DvsSingleIpPort).PortNumber)
		case *types.DvsIpPortRange:
			return fmt.Sprintf("%d-%d", port.(*types.DvsIpPortRange).StartPortNumber, port.(*types.DvsIpPortRange).EndPortNumber)
		}
	}
	return "Any"
}

func printAddress(address types.BaseIpAddress) string {
	if address != nil {
		switch (address).(type) {
		case *types.SingleIp:
			return fmt.Sprintf("%s", address.(*types.SingleIp).Address)
		case *types.IpRange:
			return fmt.Sprintf("%s/%d", address.(*types.IpRange).AddressPrefix, address.(*types.IpRange).PrefixLength)
		}
	}
	return "Any"
}

func printTable(trafficRuleSet map[int]map[int]trafficRule, portID int) {
	if len(trafficRuleSet[portID]) == 0 {
		return
	}

	keys := []int{}
	for k := range trafficRuleSet[portID] {
		keys = append(keys, k)
	}
	sort.Ints(keys)
	tabWidthInt := 22
	tabWidth := fmt.Sprintf("%d", tabWidthInt)
	headLen := tabWidthInt*8 + 8*2 - 1
	fmt.Printf("+" + strings.Repeat("-", headLen) + "+\n")
	format := "| %-" + tabWidth +
		"s| %-" + tabWidth +
		"s| %-" + tabWidth +
		"s| %-" + tabWidth +
		"s| %-" + tabWidth +
		"s| %-" + tabWidth +
		"s| %-" + tabWidth +
		"s| %-" + tabWidth +
		"s|\n"
	fmt.Printf(format,
		"Sequence",
		"Description",
		"Direction",
		"Protocol",
		"SourceAddress",
		"SourceIpPort",
		"DestinationAddress",
		"DestinationIpPort")
	fmt.Printf("+" + strings.Repeat("-", headLen) + "+\n")
	for _, id := range keys {
		fmt.Printf(format,
			fmt.Sprintf("%d", id),
			trafficRuleSet[portID][id].Description,
			trafficRuleSet[portID][id].Direction,
			trafficRuleSet[portID][id].Protocol,
			trafficRuleSet[portID][id].SourceAddress,
			trafficRuleSet[portID][id].SourceIpPort,
			trafficRuleSet[portID][id].DestinationAddress,
			trafficRuleSet[portID][id].DestinationIpPort)
	}
	fmt.Printf("+" + strings.Repeat("-", headLen) + "+\n")
}

func (r *infoResult) Write(w io.Writer) error {
	trafficRuleSet := make(map[int]map[int]trafficRule)
	for portID, port := range r.Port {
		var vlanID int32
		setting := port.Config.Setting.(*types.VMwareDVSPortSetting)

		switch vlan := setting.Vlan.(type) {
		case *types.VmwareDistributedVirtualSwitchVlanIdSpec:
			vlanID = vlan.VlanId
		case *types.VmwareDistributedVirtualSwitchTrunkVlanSpec:
		case *types.VmwareDistributedVirtualSwitchPvlanSpec:
			vlanID = vlan.PvlanId
		}

		// Show port info if: VLAN ID is not defined, or VLAN ID matches requested VLAN
		if r.cmd.vlanID == 0 || vlanID == int32(r.cmd.vlanID) {
			fmt.Printf("PortgroupKey: %s\n", port.PortgroupKey)
			fmt.Printf("DvsUuid:      %s\n", port.DvsUuid)
			fmt.Printf("VlanId:       %d\n", vlanID)
			fmt.Printf("PortKey:      %s\n\n", port.Key)

			trafficRuleSet[portID] = make(map[int]trafficRule)

			if r.cmd.dvsRules && setting.FilterPolicy != nil &&
				setting.FilterPolicy.FilterConfig != nil &&
				len(setting.FilterPolicy.FilterConfig) > 0 {

				rules := setting.FilterPolicy.FilterConfig[0].GetDvsTrafficFilterConfig()
				if rules != nil && rules.TrafficRuleset != nil && rules.TrafficRuleset.Rules != nil {
					for _, rule := range rules.TrafficRuleset.Rules {
						for _, q := range rule.Qualifier {
							var protocol string
							if val, ok := protocols[q.GetDvsIpNetworkRuleQualifier().Protocol.Value]; ok {
								protocol = val
							} else {
								protocol = fmt.Sprintf("%d", q.GetDvsIpNetworkRuleQualifier().Protocol.Value)
							}

							trafficRuleSet[portID][int(rule.Sequence)] = trafficRule{
								Description:        rule.Description,
								Direction:          rule.Direction,
								Protocol:           protocol,
								SourceAddress:      printAddress(q.GetDvsIpNetworkRuleQualifier().SourceAddress),
								SourceIpPort:       printPort(q.GetDvsIpNetworkRuleQualifier().SourceIpPort),
								DestinationAddress: printAddress(q.GetDvsIpNetworkRuleQualifier().DestinationAddress),
								DestinationIpPort:  printPort(q.GetDvsIpNetworkRuleQualifier().DestinationIpPort),
							}
						}
					}
				}
			}
		}
	}

	if r.cmd.dvsRules && len(r.Port) > 0 {
		eq := 0
		for i, _ := range r.Port {
			if i > 0 {
				reflect.DeepEqual(trafficRuleSet[i-1], trafficRuleSet[i])
				if reflect.DeepEqual(trafficRuleSet[i-1], trafficRuleSet[i]) {
					eq++
				} else {
					fmt.Printf("%s and %s port rules are unequal\n", r.Port[i-1].Key, r.Port[i].Key)
					break
				}
			}
		}

		if eq == len(trafficRuleSet)-1 {
			printTable(trafficRuleSet, 0)
		} else {
			for portID, port := range r.Port {
				fmt.Printf("\nPortKey:      %s\n", port.Key)
				printTable(trafficRuleSet, portID)
			}
		}
	}

	return nil
}

func (cmd *info) Run(ctx context.Context, f *flag.FlagSet) error {
	if f.NArg() != 1 {
		return flag.ErrHelp
	}

	finder, err := cmd.Finder()
	if err != nil {
		return err
	}

	// Retrieve DVS reference
	net, err := finder.Network(ctx, f.Arg(0))
	if err != nil {
		return err
	}

	// Convert to DVS object type
	dvs, ok := net.(*object.DistributedVirtualSwitch)
	if !ok {
		return fmt.Errorf("%s (%s) is not a DVS", f.Arg(0), net.Reference().Type)
	}

	// Set base search criteria
	criteria := types.DistributedVirtualSwitchPortCriteria{
		Connected:  types.NewBool(cmd.connected),
		Active:     types.NewBool(cmd.active),
		UplinkPort: types.NewBool(cmd.uplinkPort),
		Inside:     types.NewBool(cmd.inside),
	}

	// If a distributed virtual portgroup path is set, then add its portgroup key to the base criteria
	if len(cmd.pg) > 0 {
		// Retrieve distributed virtual portgroup reference
		net, err = finder.Network(ctx, cmd.pg)
		if err != nil {
			return err
		}

		// Convert distributed virtual portgroup object type
		dvpg, ok := net.(*object.DistributedVirtualPortgroup)
		if !ok {
			return fmt.Errorf("%s (%s) is not a DVPG", cmd.pg, net.Reference().Type)
		}

		// Obtain portgroup key property
		var dvp mo.DistributedVirtualPortgroup
		if err := dvpg.Properties(ctx, dvpg.Reference(), []string{"key"}, &dvp); err != nil {
			return err
		}

		// Add portgroup key to port search criteria
		criteria.PortgroupKey = []string{dvp.Key}
	}

	res, err := dvs.FetchDVPorts(ctx, &criteria)
	if err != nil {
		return err
	}

	// Truncate output to -count if specified
	if cmd.count > 0 && cmd.count < len(res) {
		res = res[:cmd.count]
	}

	info := infoResult{
		cmd:  cmd,
		Port: res,
	}

	return cmd.WriteResult(&info)
}
<commit_msg>Print action for dvs security groups<commit_after>/*
Copyright (c) 2015-2016 VMware, Inc. All Rights Reserved.

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

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

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

package portgroup

import (
	"context"
	"flag"
	"fmt"
	"io"
	"reflect"
	"sort"
	"strings"

	"github.com/vmware/govmomi/govc/cli"
	"github.com/vmware/govmomi/govc/flags"
	"github.com/vmware/govmomi/object"
	"github.com/vmware/govmomi/vim25/mo"
	"github.com/vmware/govmomi/vim25/types"
)

type info struct {
	*flags.DatacenterFlag

	pg         string
	active     bool
	connected  bool
	inside     bool
	uplinkPort bool
	vlanID     int
	count      int
	dvsRules   bool
}

var protocols = map[int32]string{
	1:  "icmp",
	2:  "igmp",
	6:  "tcp",
	17: "udp",
	58: "ipv6-icmp",
}

type trafficRule struct {
	Description        string
	Direction          string
	Action             string
	Protocol           string
	SourceAddress      string
	SourceIpPort       string
	DestinationAddress string
	DestinationIpPort  string
}

func init() {
	cli.Register("dvs.portgroup.info", &info{})
}

func (cmd *info) Register(ctx context.Context, f *flag.FlagSet) {
	cmd.DatacenterFlag, ctx = flags.NewDatacenterFlag(ctx)
	cmd.DatacenterFlag.Register(ctx, f)

	f.StringVar(&cmd.pg, "pg", "", "Distributed Virtual Portgroup")
	f.BoolVar(&cmd.active, "active", false, "Filter by port active or inactive status")
	f.BoolVar(&cmd.connected, "connected", false, "Filter by port connected or disconnected status")
	f.BoolVar(&cmd.inside, "inside", true, "Filter by port inside or outside status")
	f.BoolVar(&cmd.uplinkPort, "uplinkPort", false, "Filter for uplink ports")
	f.IntVar(&cmd.vlanID, "vlan", 0, "Filter by VLAN ID (0 = unfiltered)")
	f.IntVar(&cmd.count, "count", 0, "Number of matches to return (0 = unlimited)")
	f.BoolVar(&cmd.dvsRules, "r", false, "Show DVS rules")
}

func (cmd *info) Usage() string {
	return "DVS"
}

func (cmd *info) Description() string {
	return `Portgroup info for DVS.

Examples:
  govc dvs.portgroup.info DSwitch
  govc dvs.portgroup.info -pg InternalNetwork DSwitch
  govc find / -type DistributedVirtualSwitch | xargs -n1 govc dvs.portgroup.info`
}

type infoResult struct {
	Port []types.DistributedVirtualPort
	cmd  *info
}

func printPort(port types.BaseDvsIpPort) string {
	if port != nil {
		switch port.(type) {
		case *types.DvsSingleIpPort:
			return fmt.Sprintf("%d", port.(*types.DvsSingleIpPort).PortNumber)
		case *types.DvsIpPortRange:
			return fmt.Sprintf("%d-%d", port.(*types.DvsIpPortRange).StartPortNumber, port.(*types.DvsIpPortRange).EndPortNumber)
		}
	}
	return "Any"
}

func printAddress(address types.BaseIpAddress) string {
	if address != nil {
		switch (address).(type) {
		case *types.SingleIp:
			return fmt.Sprintf("%s", address.(*types.SingleIp).Address)
		case *types.IpRange:
			return fmt.Sprintf("%s/%d", address.(*types.IpRange).AddressPrefix, address.(*types.IpRange).PrefixLength)
		}
	}
	return "Any"
}

func printAction(action types.BaseDvsNetworkRuleAction) string {
	if action != nil {
		switch (action).(type) {
		case *types.DvsAcceptNetworkRuleAction:
			return fmt.Sprintf("Accept")
		case *types.DvsDropNetworkRuleAction:
			return fmt.Sprintf("Drop")
		}
	}
	return "n/a"
}

func printTable(trafficRuleSet map[int]map[int]trafficRule, portID int) {
	if len(trafficRuleSet[portID]) == 0 {
		return
	}

	keys := []int{}
	for k := range trafficRuleSet[portID] {
		keys = append(keys, k)
	}
	sort.Ints(keys)
	tabWidthInt := 22
	tabWidth := fmt.Sprintf("%d", tabWidthInt)
	headLen := 9*(tabWidthInt+2) - 1
	fmt.Printf("+" + strings.Repeat("-", headLen) + "+\n")
	format := "| %-" + tabWidth +
		"s| %-" + tabWidth +
		"s| %-" + tabWidth +
		"s| %-" + tabWidth +
		"s| %-" + tabWidth +
		"s| %-" + tabWidth +
		"s| %-" + tabWidth +
		"s| %-" + tabWidth +
		"s| %-" + tabWidth +
		"s|\n"
	fmt.Printf(format,
		"Sequence",
		"Description",
		"Direction",
		"Action",
		"Protocol",
		"SourceAddress",
		"SourceIpPort",
		"DestinationAddress",
		"DestinationIpPort")
	fmt.Printf("+" + strings.Repeat("-", headLen) + "+\n")
	for _, id := range keys {
		fmt.Printf(format,
			fmt.Sprintf("%d", id),
			trafficRuleSet[portID][id].Description,
			trafficRuleSet[portID][id].Direction,
			trafficRuleSet[portID][id].Action,
			trafficRuleSet[portID][id].Protocol,
			trafficRuleSet[portID][id].SourceAddress,
			trafficRuleSet[portID][id].SourceIpPort,
			trafficRuleSet[portID][id].DestinationAddress,
			trafficRuleSet[portID][id].DestinationIpPort)
	}
	fmt.Printf("+" + strings.Repeat("-", headLen) + "+\n")
}

func (r *infoResult) Write(w io.Writer) error {
	trafficRuleSet := make(map[int]map[int]trafficRule)
	for portID, port := range r.Port {
		var vlanID int32
		setting := port.Config.Setting.(*types.VMwareDVSPortSetting)

		switch vlan := setting.Vlan.(type) {
		case *types.VmwareDistributedVirtualSwitchVlanIdSpec:
			vlanID = vlan.VlanId
		case *types.VmwareDistributedVirtualSwitchTrunkVlanSpec:
		case *types.VmwareDistributedVirtualSwitchPvlanSpec:
			vlanID = vlan.PvlanId
		}

		// Show port info if: VLAN ID is not defined, or VLAN ID matches requested VLAN
		if r.cmd.vlanID == 0 || vlanID == int32(r.cmd.vlanID) {
			fmt.Printf("PortgroupKey: %s\n", port.PortgroupKey)
			fmt.Printf("DvsUuid:      %s\n", port.DvsUuid)
			fmt.Printf("VlanId:       %d\n", vlanID)
			fmt.Printf("PortKey:      %s\n\n", port.Key)

			trafficRuleSet[portID] = make(map[int]trafficRule)

			if r.cmd.dvsRules && setting.FilterPolicy != nil &&
				setting.FilterPolicy.FilterConfig != nil &&
				len(setting.FilterPolicy.FilterConfig) > 0 {

				rules := setting.FilterPolicy.FilterConfig[0].GetDvsTrafficFilterConfig()
				if rules != nil && rules.TrafficRuleset != nil && rules.TrafficRuleset.Rules != nil {
					for _, rule := range rules.TrafficRuleset.Rules {
						for _, q := range rule.Qualifier {
							var protocol string
							if val, ok := protocols[q.GetDvsIpNetworkRuleQualifier().Protocol.Value]; ok {
								protocol = val
							} else {
								protocol = fmt.Sprintf("%d", q.GetDvsIpNetworkRuleQualifier().Protocol.Value)
							}

							trafficRuleSet[portID][int(rule.Sequence)] = trafficRule{
								Description:        rule.Description,
								Direction:          rule.Direction,
								Action:             printAction(rule.Action),
								Protocol:           protocol,
								SourceAddress:      printAddress(q.GetDvsIpNetworkRuleQualifier().SourceAddress),
								SourceIpPort:       printPort(q.GetDvsIpNetworkRuleQualifier().SourceIpPort),
								DestinationAddress: printAddress(q.GetDvsIpNetworkRuleQualifier().DestinationAddress),
								DestinationIpPort:  printPort(q.GetDvsIpNetworkRuleQualifier().DestinationIpPort),
							}
						}
					}
				}
			}
		}
	}

	if r.cmd.dvsRules && len(r.Port) > 0 {
		eq := 0
		for i, _ := range r.Port {
			if i > 0 {
				reflect.DeepEqual(trafficRuleSet[i-1], trafficRuleSet[i])
				if reflect.DeepEqual(trafficRuleSet[i-1], trafficRuleSet[i]) {
					eq++
				} else {
					fmt.Printf("%s and %s port rules are unequal\n", r.Port[i-1].Key, r.Port[i].Key)
					break
				}
			}
		}

		if eq == len(trafficRuleSet)-1 {
			printTable(trafficRuleSet, 0)
		} else {
			for portID, port := range r.Port {
				fmt.Printf("\nPortKey:      %s\n", port.Key)
				printTable(trafficRuleSet, portID)
			}
		}
	}

	return nil
}

func (cmd *info) Run(ctx context.Context, f *flag.FlagSet) error {
	if f.NArg() != 1 {
		return flag.ErrHelp
	}

	finder, err := cmd.Finder()
	if err != nil {
		return err
	}

	// Retrieve DVS reference
	net, err := finder.Network(ctx, f.Arg(0))
	if err != nil {
		return err
	}

	// Convert to DVS object type
	dvs, ok := net.(*object.DistributedVirtualSwitch)
	if !ok {
		return fmt.Errorf("%s (%s) is not a DVS", f.Arg(0), net.Reference().Type)
	}

	// Set base search criteria
	criteria := types.DistributedVirtualSwitchPortCriteria{
		Connected:  types.NewBool(cmd.connected),
		Active:     types.NewBool(cmd.active),
		UplinkPort: types.NewBool(cmd.uplinkPort),
		Inside:     types.NewBool(cmd.inside),
	}

	// If a distributed virtual portgroup path is set, then add its portgroup key to the base criteria
	if len(cmd.pg) > 0 {
		// Retrieve distributed virtual portgroup reference
		net, err = finder.Network(ctx, cmd.pg)
		if err != nil {
			return err
		}

		// Convert distributed virtual portgroup object type
		dvpg, ok := net.(*object.DistributedVirtualPortgroup)
		if !ok {
			return fmt.Errorf("%s (%s) is not a DVPG", cmd.pg, net.Reference().Type)
		}

		// Obtain portgroup key property
		var dvp mo.DistributedVirtualPortgroup
		if err := dvpg.Properties(ctx, dvpg.Reference(), []string{"key"}, &dvp); err != nil {
			return err
		}

		// Add portgroup key to port search criteria
		criteria.PortgroupKey = []string{dvp.Key}
	}

	res, err := dvs.FetchDVPorts(ctx, &criteria)
	if err != nil {
		return err
	}

	// Truncate output to -count if specified
	if cmd.count > 0 && cmd.count < len(res) {
		res = res[:cmd.count]
	}

	info := infoResult{
		cmd:  cmd,
		Port: res,
	}

	return cmd.WriteResult(&info)
}
<|endoftext|>


<commit_before>package tddbc

import (
	"testing"
)

func Test正常_開区間を作成し下端点が取得できることを確認する(t *testing.T) {
	var data = []struct {
		lower    int
		upper    int
		expected int
	}{
		{3, 8, 3},
		{1, 8, 1},
		{2, 8, 2},
	}

	for _, d := range data {
		expected := d.expected
		target := NewOpenRange(d.lower, d.upper)
		if target.lower != expected {
			t.Errorf("failed get lower expected=%d, actual=%d\n", expected, target.lower)
		}
	}
}

func Test正常_開区間を作成し上端点が取得できることを確認する(t *testing.T) {
	var data = []struct {
		lower    int
		upper    int
		expected int
	}{
		{3, 8, 8},
		// {3, 9, 9},
		// {3, 7, 7},
	}

	for _, d := range data {
		expected := d.expected
		target := NewOpenRange(d.lower, d.upper)
		if target.upper != expected {
			t.Errorf("failed get lower expected=%d, actual=%d\n", expected, target.lower)
		}
	}
}
<commit_msg>開区間を作成し上端点が取得できるメソッドに複数のパラメータを指定したテスト完了<commit_after>package tddbc

import (
	"testing"
)

func Test正常_開区間を作成し下端点が取得できることを確認する(t *testing.T) {
	var data = []struct {
		lower    int
		upper    int
		expected int
	}{
		{3, 8, 3},
		{1, 8, 1},
		{2, 8, 2},
	}

	for _, d := range data {
		expected := d.expected
		target := NewOpenRange(d.lower, d.upper)
		if target.lower != expected {
			t.Errorf("failed get lower expected=%d, actual=%d\n", expected, target.lower)
		}
	}
}

func Test正常_開区間を作成し上端点が取得できることを確認する(t *testing.T) {
	var data = []struct {
		lower    int
		upper    int
		expected int
	}{
		{3, 8, 8},
		{3, 9, 9},
		{3, 7, 7},
	}

	for _, d := range data {
		expected := d.expected
		target := NewOpenRange(d.lower, d.upper)
		if target.upper != expected {
			t.Errorf("failed get lower expected=%d, actual=%d\n", expected, target.lower)
		}
	}
}
<|endoftext|>


<commit_before>package commands

import (
	"fmt"
	"strings"
	"time"

	"github.com/camd67/moebot/moebot_bot/bot/permissions"
	"github.com/camd67/moebot/moebot_bot/util/db"
)

type TimerCommand struct {
	chTimers map[string]time.Time
	Checker  permissions.PermissionChecker
}

func NewTimerCommand() *TimerCommand {
	tc := &TimerCommand{}
	tc.chTimers = map[string]time.Time{}
	return tc
}

func (tc *TimerCommand) Execute(pack *CommPackage) {
	channelID := pack.message.ChannelID
	if len(pack.params) > 0 && strings.EqualFold(pack.params[0], "start") {
		if tc.Checker.HasPermission(pack.message.Author.ID, pack.member.Roles, pack.guild, db.PermMod) {
			tc.chTimers[channelID] = time.Now()
			pack.session.ChannelMessageSend(pack.message.ChannelID, "Timer started OwO")
		} else {
			pack.session.ChannelMessageSend(pack.message.ChannelID, "You... you don't have permission to do that! ( ≧Д≦)")
		}
	} else {
		if v, ok := tc.chTimers[channelID]; ok {
			pack.session.ChannelMessageSend(pack.message.ChannelID, fmtDuration(time.Since(v)))
		} else {
			pack.session.ChannelMessageSend(pack.message.ChannelID, "No timer started for this channel -w-")
		}
	}
}

func fmtDuration(dur time.Duration) string {
	remainingDur := dur.Round(time.Second)
	hours := remainingDur / time.Hour
	remainingDur -= hours * time.Hour
	minutes := remainingDur / time.Minute
	remainingDur -= minutes * time.Minute
	seconds := remainingDur / time.Second
	return fmt.Sprintf("%02d:%02d:%02d", hours, minutes, seconds)
}

func (tc *TimerCommand) GetPermLevel() db.Permission {
	return db.PermAll
}

func (tc *TimerCommand) GetCommandKeys() []string {
	return []string{"TIMER"}
}

func (tc *TimerCommand) GetCommandHelp(commPrefix string) string {
	return fmt.Sprintf("`%[1]s timer` - Checks the timestamp. Moderators may provide the `--start` option to begin start or restart the timer.", commPrefix)
}
<commit_msg>Update the message responses<commit_after>package commands

import (
	"fmt"
	"strings"
	"time"

	"github.com/camd67/moebot/moebot_bot/bot/permissions"
	"github.com/camd67/moebot/moebot_bot/util/db"
)

type TimerCommand struct {
	chTimers map[string]time.Time
	Checker  permissions.PermissionChecker
}

func NewTimerCommand() *TimerCommand {
	tc := &TimerCommand{}
	tc.chTimers = map[string]time.Time{}
	return tc
}

func (tc *TimerCommand) Execute(pack *CommPackage) {
	channelID := pack.message.ChannelID
	if len(pack.params) > 0 && strings.EqualFold(pack.params[0], "start") {
		if tc.Checker.HasPermission(pack.message.Author.ID, pack.member.Roles, pack.guild, db.PermMod) {
			tc.chTimers[channelID] = time.Now()
			pack.session.ChannelMessageSend(pack.message.ChannelID, "Timer started! OwO")
		} else {
			pack.session.ChannelMessageSend(pack.message.ChannelID, pack.message.Author.Mention()+", you... you don't have permission to do that! ( ≧Д≦)")
		}
	} else {
		if v, ok := tc.chTimers[channelID]; ok {
			pack.session.ChannelMessageSend(pack.message.ChannelID, fmtDuration(time.Since(v)))
		} else {
			pack.session.ChannelMessageSend(pack.message.ChannelID, "No timer started for this channel... -w-")
		}
	}
}

func fmtDuration(dur time.Duration) string {
	remainingDur := dur.Round(time.Second)
	hours := remainingDur / time.Hour
	remainingDur -= hours * time.Hour
	minutes := remainingDur / time.Minute
	remainingDur -= minutes * time.Minute
	seconds := remainingDur / time.Second
	return fmt.Sprintf("%02d:%02d:%02d", hours, minutes, seconds)
}

func (tc *TimerCommand) GetPermLevel() db.Permission {
	return db.PermAll
}

func (tc *TimerCommand) GetCommandKeys() []string {
	return []string{"TIMER"}
}

func (tc *TimerCommand) GetCommandHelp(commPrefix string) string {
	return fmt.Sprintf("`%[1]s timer` - Checks the timestamp. Moderators may provide the `--start` option to begin start or restart the timer.", commPrefix)
}
<|endoftext|>


<commit_before>package textview

import (
	"reflect"
	"testing"

	"github.com/elves/elvish/cli/el"
	"github.com/elves/elvish/cli/term"
	"github.com/elves/elvish/edit/ui"
	"github.com/elves/elvish/styled"
)

var bb = ui.NewBufferBuilder

var renderTests = []el.RenderTest{
	{
		Name: "text fits entirely",
		Given: &Widget{State: State{
			Lines: []string{"line 1", "line 2", "line 3"}}},
		Width: 10, Height: 4,
		Want: bb(10).
			WritePlain("line 1").Newline().
			WritePlain("line 2").Newline().
			WritePlain("line 3").Buffer(),
	},
	{
		Name: "text cropped horizontally",
		Given: &Widget{State: State{
			Lines: []string{"a very long line"}}},
		Width: 10, Height: 4,
		Want: bb(10).
			WritePlain("a very lon").Buffer(),
	},
	{
		Name: "text cropped vertically",
		Given: &Widget{State: State{
			Lines: []string{"line 1", "line 2", "line 3"}}},
		Width: 10, Height: 2,
		Want: bb(10).
			WritePlain("line 1").Newline().
			WritePlain("line 2").Buffer(),
	},
	{
		Name: "text cropped vertically, with scrollbar",
		Given: &Widget{
			Scrollable: true,
			State: State{
				Lines: []string{"line 1", "line 2", "line 3", "line 4"}}},
		Width: 10, Height: 2,
		Want: bb(10).
			WritePlain("line 1   ").
			WriteStyled(styled.MakeText(" ", "inverse", "magenta")).Newline().
			WritePlain("line 2   ").
			WriteStyled(styled.MakeText("│", "magenta")).Buffer(),
	},
	{
		Name: "State.First adjusted to fit text",
		Given: &Widget{State: State{
			First: 2,
			Lines: []string{"line 1", "line 2", "line 3"}}},
		Width: 10, Height: 3,
		Want: bb(10).
			WritePlain("line 1").Newline().
			WritePlain("line 2").Newline().
			WritePlain("line 3").Buffer(),
	},
}

func TestRender(t *testing.T) {
	el.TestRender(t, renderTests)
}

var handleTests = []struct {
	name        string
	widget      *Widget
	event       term.Event
	wantHandled bool
	wantState   State
}{
	{
		"up doing nothing when not scrollable",
		&Widget{
			State: State{Lines: []string{"1", "2", "3", "4"}, First: 1}},
		term.K(ui.Up),
		false,
		State{Lines: []string{"1", "2", "3", "4"}, First: 1},
	},
	{
		"up moving window up when scrollable",
		&Widget{
			Scrollable: true,
			State:      State{Lines: []string{"1", "2", "3", "4"}, First: 1}},
		term.K(ui.Up),
		true,
		State{Lines: []string{"1", "2", "3", "4"}, First: 0},
	},
	{
		"up doing nothing when already at top",
		&Widget{
			Scrollable: true,
			State:      State{Lines: []string{"1", "2", "3", "4"}, First: 0}},
		term.K(ui.Up),
		true,
		State{Lines: []string{"1", "2", "3", "4"}, First: 0},
	},
	{
		"down moving window down when scrollable",
		&Widget{
			Scrollable: true,
			State:      State{Lines: []string{"1", "2", "3", "4"}, First: 1}},
		term.K(ui.Down),
		true,
		State{Lines: []string{"1", "2", "3", "4"}, First: 2},
	},
	{
		"down doing nothing when already at bottom",
		&Widget{
			Scrollable: true,
			State:      State{Lines: []string{"1", "2", "3", "4"}, First: 3}},
		term.K(ui.Down),
		true,
		State{Lines: []string{"1", "2", "3", "4"}, First: 3},
	},
	{
		"overlay",
		&Widget{
			OverlayHandler: el.MapHandler{term.K('a'): func() {}},
		},
		term.K('a'),
		true,
		State{},
	},
}

func TestHandle(t *testing.T) {
	for _, test := range handleTests {
		t.Run(test.name, func(t *testing.T) {
			w := test.widget
			handled := w.Handle(test.event)
			if handled != test.wantHandled {
				t.Errorf("got handled %v, want %v", handled, test.wantHandled)
			}
			if !reflect.DeepEqual(w.State, test.wantState) {
				t.Errorf("got state %v, want %v", w.State, test.wantState)
			}
		})
	}
}

func TestCopyState(t *testing.T) {
	state := State{Lines: []string{"a", "b", "c"}, First: 1}
	w := &Widget{State: state}
	copied := w.CopyState()
	if !reflect.DeepEqual(copied, state) {
		t.Errorf("Got copied state %v, want %v", copied, state)
	}
}
<commit_msg>cli/el/textview: Rewrite TestHandle using el.TestHandle.<commit_after>package textview

import (
	"reflect"
	"testing"

	"github.com/elves/elvish/cli/el"
	"github.com/elves/elvish/cli/term"
	"github.com/elves/elvish/edit/ui"
	"github.com/elves/elvish/styled"
)

var bb = ui.NewBufferBuilder

var renderTests = []el.RenderTest{
	{
		Name: "text fits entirely",
		Given: &Widget{State: State{
			Lines: []string{"line 1", "line 2", "line 3"}}},
		Width: 10, Height: 4,
		Want: bb(10).
			WritePlain("line 1").Newline().
			WritePlain("line 2").Newline().
			WritePlain("line 3").Buffer(),
	},
	{
		Name: "text cropped horizontally",
		Given: &Widget{State: State{
			Lines: []string{"a very long line"}}},
		Width: 10, Height: 4,
		Want: bb(10).
			WritePlain("a very lon").Buffer(),
	},
	{
		Name: "text cropped vertically",
		Given: &Widget{State: State{
			Lines: []string{"line 1", "line 2", "line 3"}}},
		Width: 10, Height: 2,
		Want: bb(10).
			WritePlain("line 1").Newline().
			WritePlain("line 2").Buffer(),
	},
	{
		Name: "text cropped vertically, with scrollbar",
		Given: &Widget{
			Scrollable: true,
			State: State{
				Lines: []string{"line 1", "line 2", "line 3", "line 4"}}},
		Width: 10, Height: 2,
		Want: bb(10).
			WritePlain("line 1   ").
			WriteStyled(styled.MakeText(" ", "inverse", "magenta")).Newline().
			WritePlain("line 2   ").
			WriteStyled(styled.MakeText("│", "magenta")).Buffer(),
	},
	{
		Name: "State.First adjusted to fit text",
		Given: &Widget{State: State{
			First: 2,
			Lines: []string{"line 1", "line 2", "line 3"}}},
		Width: 10, Height: 3,
		Want: bb(10).
			WritePlain("line 1").Newline().
			WritePlain("line 2").Newline().
			WritePlain("line 3").Buffer(),
	},
}

func TestRender(t *testing.T) {
	el.TestRender(t, renderTests)
}

var handleTests = []el.HandleTest{
	{
		Name: "up doing nothing when not scrollable",
		Given: &Widget{
			State: State{Lines: []string{"1", "2", "3", "4"}, First: 1}},
		Event: term.K(ui.Up),

		WantUnhandled: true,
	},
	{
		Name: "up moving window up when scrollable",
		Given: &Widget{
			Scrollable: true,
			State:      State{Lines: []string{"1", "2", "3", "4"}, First: 1}},
		Event: term.K(ui.Up),

		WantNewState: State{Lines: []string{"1", "2", "3", "4"}, First: 0},
	},
	{
		Name: "up doing nothing when already at top",
		Given: &Widget{
			Scrollable: true,
			State:      State{Lines: []string{"1", "2", "3", "4"}, First: 0}},
		Event: term.K(ui.Up),

		WantNewState: State{Lines: []string{"1", "2", "3", "4"}, First: 0},
	},
	{
		Name: "down moving window down when scrollable",
		Given: &Widget{
			Scrollable: true,
			State:      State{Lines: []string{"1", "2", "3", "4"}, First: 1}},
		Event: term.K(ui.Down),

		WantNewState: State{Lines: []string{"1", "2", "3", "4"}, First: 2},
	},
	{
		Name: "down doing nothing when already at bottom",
		Given: &Widget{
			Scrollable: true,
			State:      State{Lines: []string{"1", "2", "3", "4"}, First: 3}},
		Event: term.K(ui.Down),

		WantNewState: State{Lines: []string{"1", "2", "3", "4"}, First: 3},
	},
	{
		Name: "overlay",
		Given: &Widget{
			OverlayHandler: el.MapHandler{term.K('a'): func() {}},
		},
		Event: term.K('a'),

		WantNewState: State{},
	},
}

func TestHandle(t *testing.T) {
	el.TestHandle(t, handleTests)
}

func TestCopyState(t *testing.T) {
	state := State{Lines: []string{"a", "b", "c"}, First: 1}
	w := &Widget{State: state}
	copied := w.CopyState()
	if !reflect.DeepEqual(copied, state) {
		t.Errorf("Got copied state %v, want %v", copied, state)
	}
}
<|endoftext|>