<commit_before>/*
Copyright 2016 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 e2e_node

import (
	"flag"
	"fmt"
	"io/ioutil"
	"math/rand"
	"net/http"
	"os"
	"os/exec"
	"path"
	"path/filepath"
	"reflect"
	"strconv"
	"strings"
	"syscall"
	"time"

	"github.com/golang/glog"
)

var serverStartTimeout = flag.Duration("server-start-timeout", time.Second*120, "Time to wait for each server to become healthy.")
var reportDir = flag.String("report-dir", "", "Path to the directory where the JUnit XML reports should be saved. Default is empty, which doesn't generate these reports.")

type e2eService struct {
	killCmds []*killCmd
	rmDirs   []string

	etcdDataDir         string
	kubeletStaticPodDir string
	nodeName            string
	logFiles            map[string]logFileData
	cgroupsPerQOS       bool
}

type logFileData struct {
	files             []string
	journalctlCommand []string
}

const (
	// This is consistent with the level used in a cluster e2e test.
	LOG_VERBOSITY_LEVEL = "4"
)

func newE2eService(nodeName string, cgroupsPerQOS bool) *e2eService {
	// Special log files that need to be collected for additional debugging.
	var logFiles = map[string]logFileData{
		"kern.log":   {[]string{"/var/log/kern.log"}, []string{"-k"}},
		"docker.log": {[]string{"/var/log/docker.log", "/var/log/upstart/docker.log"}, []string{"-u", "docker"}},
	}

	return &e2eService{
		nodeName:      nodeName,
		logFiles:      logFiles,
		cgroupsPerQOS: cgroupsPerQOS,
	}
}

func (es *e2eService) start() error {
	if _, err := getK8sBin("kubelet"); err != nil {
		return err
	}
	if _, err := getK8sBin("kube-apiserver"); err != nil {
		return err
	}

	cmd, err := es.startEtcd()
	if err != nil {
		return err
	}
	es.killCmds = append(es.killCmds, cmd)
	es.rmDirs = append(es.rmDirs, es.etcdDataDir)

	cmd, err = es.startApiServer()
	if err != nil {
		return err
	}
	es.killCmds = append(es.killCmds, cmd)

	cmd, err = es.startKubeletServer()
	if err != nil {
		return err
	}
	es.killCmds = append(es.killCmds, cmd)
	es.rmDirs = append(es.rmDirs, es.kubeletStaticPodDir)

	return nil
}

// Get logs of interest either via journalctl or by creating sym links.
// Since we scp files from the remote directory, symlinks will be treated as normal files and file contents will be copied over.
func (es *e2eService) getLogFiles() {
	// Nothing to do if report dir is not specified.
	if *reportDir == "" {
		return
	}
	journaldFound := isJournaldAvailable()
	for targetFileName, logFileData := range es.logFiles {
		targetLink := path.Join(*reportDir, targetFileName)
		if journaldFound {
			// Skip log files that do not have an equivalent in journald based machines.
			if len(logFileData.journalctlCommand) == 0 {
				continue
			}
			out, err := exec.Command("sudo", append([]string{"journalctl"}, logFileData.journalctlCommand...)...).CombinedOutput()
			if err != nil {
				glog.Errorf("failed to get %q from journald: %v, %v", targetFileName, string(out), err)
			} else {
				if err = ioutil.WriteFile(targetLink, out, 0755); err != nil {
					glog.Errorf("failed to write logs to %q: %v", targetLink, err)
				}
			}
			continue
		}
		for _, file := range logFileData.files {
			if _, err := os.Stat(file); err != nil {
				// Expected file not found on this distro.
				continue
			}
			if err := copyLogFile(file, targetLink); err != nil {
				glog.Error(err)
			} else {
				break
			}
		}
	}
}

func copyLogFile(src, target string) error {
	// If not a journald based distro, then just symlink files.
	if out, err := exec.Command("sudo", "cp", src, target).CombinedOutput(); err != nil {
		return fmt.Errorf("failed to copy %q to %q: %v, %v", src, target, out, err)
	}
	if out, err := exec.Command("sudo", "chmod", "a+r", target).CombinedOutput(); err != nil {
		return fmt.Errorf("failed to make log file %q world readable: %v, %v", target, out, err)
	}
	return nil
}

func isJournaldAvailable() bool {
	_, err := exec.LookPath("journalctl")
	return err == nil
}

func (es *e2eService) stop() {
	for _, k := range es.killCmds {
		if err := k.Kill(); err != nil {
			glog.Errorf("Failed to stop %v: %v", k.name, err)
		}
	}
	for _, d := range es.rmDirs {
		err := os.RemoveAll(d)
		if err != nil {
			glog.Errorf("Failed to delete directory %s.\n%v", d, err)
		}
	}
}

func (es *e2eService) startEtcd() (*killCmd, error) {
	dataDir, err := ioutil.TempDir("", "node-e2e")
	if err != nil {
		return nil, err
	}
	es.etcdDataDir = dataDir
	cmd := exec.Command("etcd")
	// Execute etcd in the data directory instead of using --data-dir because the flag sometimes requires additional
	// configuration (e.g. --name in version 0.4.9)
	cmd.Dir = es.etcdDataDir
	hcc := newHealthCheckCommand(
		"http://127.0.0.1:4001/v2/keys/", // Trailing slash is required,
		cmd,
		"etcd.log")
	return &killCmd{name: "etcd", cmd: cmd}, es.startServer(hcc)
}

func (es *e2eService) startApiServer() (*killCmd, error) {
	cmd := exec.Command("sudo", getApiServerBin(),
		"--etcd-servers", "http://127.0.0.1:4001",
		"--insecure-bind-address", "0.0.0.0",
		"--service-cluster-ip-range", "10.0.0.1/24",
		"--kubelet-port", "10250",
		"--allow-privileged", "true",
		"--v", LOG_VERBOSITY_LEVEL, "--logtostderr",
	)
	hcc := newHealthCheckCommand(
		"http://127.0.0.1:8080/healthz",
		cmd,
		"kube-apiserver.log")
	return &killCmd{name: "kube-apiserver", cmd: cmd}, es.startServer(hcc)
}

func (es *e2eService) startKubeletServer() (*killCmd, error) {
	dataDir, err := ioutil.TempDir("", "node-e2e-pod")
	if err != nil {
		return nil, err
	}
	es.kubeletStaticPodDir = dataDir
	var killOverride *exec.Cmd
	cmdArgs := []string{}
	if systemdRun, err := exec.LookPath("systemd-run"); err == nil {
		// On systemd services, detection of a service / unit works reliably while
		// detection of a process started from an ssh session does not work.
		// Since kubelet will typically be run as a service it also makes more
		// sense to test it that way
		unitName := fmt.Sprintf("kubelet-%d.service", rand.Int31())
		cmdArgs = append(cmdArgs, systemdRun, "--unit="+unitName, getKubeletServerBin())
		killOverride = exec.Command("sudo", "systemctl", "kill", unitName)
		es.logFiles["kubelet.log"] = logFileData{
			journalctlCommand: []string{"-u", unitName},
		}
	} else {
		cmdArgs = append(cmdArgs, getKubeletServerBin())
	}
	cmdArgs = append(cmdArgs,
		"--api-servers", "http://127.0.0.1:8080",
		"--address", "0.0.0.0",
		"--port", "10250",
		"--hostname-override", es.nodeName, // Required because hostname is inconsistent across hosts
		"--volume-stats-agg-period", "10s", // Aggregate volumes frequently so tests don't need to wait as long
		"--allow-privileged", "true",
		"--serialize-image-pulls", "false",
		"--config", es.kubeletStaticPodDir,
		"--file-check-frequency", "10s", // Check file frequently so tests won't wait too long
		"--v", LOG_VERBOSITY_LEVEL, "--logtostderr",
		"--pod-cidr=10.180.0.0/24", // Assign a fixed CIDR to the node because there is no node controller.
	)
	if es.cgroupsPerQOS {
		cmdArgs = append(cmdArgs,
			"--cgroups-per-qos", "true",
			"--cgroup-root", "/",
		)
	}
	if !*disableKubenet {
		cwd, err := os.Getwd()
		if err != nil {
			return nil, err
		}
		cmdArgs = append(cmdArgs,
			"--network-plugin=kubenet",
			"--network-plugin-dir", filepath.Join(cwd, CNIDirectory, "bin")) // Enable kubenet
	}

	cmd := exec.Command("sudo", cmdArgs...)
	hcc := newHealthCheckCommand(
		"http://127.0.0.1:10255/healthz",
		cmd,
		"kubelet.log")
	return &killCmd{name: "kubelet", cmd: cmd, override: killOverride}, es.startServer(hcc)
}

func (es *e2eService) startServer(cmd *healthCheckCommand) error {
	cmdErrorChan := make(chan error)
	go func() {
		defer close(cmdErrorChan)

		// Create the output filename
		outPath := path.Join(*reportDir, cmd.outputFilename)
		outfile, err := os.Create(outPath)
		if err != nil {
			cmdErrorChan <- fmt.Errorf("Failed to create file %s for `%s` %v.", outPath, cmd, err)
			return
		}
		defer outfile.Close()
		defer outfile.Sync()

		// Set the command to write the output file
		cmd.Cmd.Stdout = outfile
		cmd.Cmd.Stderr = outfile

		// Killing the sudo command should kill the server as well.
		attrs := &syscall.SysProcAttr{}
		// Hack to set linux-only field without build tags.
		deathSigField := reflect.ValueOf(attrs).Elem().FieldByName("Pdeathsig")
		if deathSigField.IsValid() {
			deathSigField.Set(reflect.ValueOf(syscall.SIGKILL))
		} else {
			cmdErrorChan <- fmt.Errorf("Failed to set Pdeathsig field (non-linux build)")
			return
		}
		cmd.Cmd.SysProcAttr = attrs

		// Run the command
		err = cmd.Run()
		if err != nil {
			cmdErrorChan <- fmt.Errorf("%s Failed with error \"%v\".  Output written to: %s", cmd, err, outPath)
			return
		}
	}()

	endTime := time.Now().Add(*serverStartTimeout)
	for endTime.After(time.Now()) {
		select {
		case err := <-cmdErrorChan:
			return err
		case <-time.After(time.Second):
			resp, err := http.Get(cmd.HealthCheckUrl)
			if err == nil && resp.StatusCode == http.StatusOK {
				return nil
			}
		}
	}
	return fmt.Errorf("Timeout waiting for service %s", cmd)
}

// killCmd is a struct to kill a given cmd. The cmd member specifies a command
// to find the pid of and attempt to kill.
// If the override field is set, that will be used instead to kill the command.
// name is only used for logging
type killCmd struct {
	name     string
	cmd      *exec.Cmd
	override *exec.Cmd
}

func (k *killCmd) Kill() error {
	name := k.name
	cmd := k.cmd

	if k.override != nil {
		return k.override.Run()
	}

	if cmd == nil {
		return fmt.Errorf("Could not kill %s because both `override` and `cmd` are nil", name)
	}

	if cmd.Process == nil {
		glog.V(2).Infof("%s not running", name)
		return nil
	}
	pid := cmd.Process.Pid
	if pid <= 1 {
		return fmt.Errorf("invalid PID %d for %s", pid, name)
	}

	// Attempt to shut down the process in a friendly manner before forcing it.
	waitChan := make(chan error)
	go func() {
		_, err := cmd.Process.Wait()
		waitChan <- err
		close(waitChan)
	}()

	const timeout = 10 * time.Second
	for _, signal := range []string{"-TERM", "-KILL"} {
		glog.V(2).Infof("Killing process %d (%s) with %s", pid, name, signal)
		cmd := exec.Command("sudo", "kill", signal, strconv.Itoa(pid))
		// Run the 'kill' command in a separate process group so sudo doesn't ignore it
		cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
		_, err := cmd.Output()
		if err != nil {
			glog.Errorf("Error signaling process %d (%s) with %s: %v", pid, name, signal, err)
			continue
		}

		select {
		case err := <-waitChan:
			if err != nil {
				return fmt.Errorf("error stopping %s: %v", name, err)
			}
			// Success!
			return nil
		case <-time.After(timeout):
			// Continue.
		}
	}

	return fmt.Errorf("unable to stop %s", name)
}

type healthCheckCommand struct {
	*exec.Cmd
	HealthCheckUrl string
	outputFilename string
}

func newHealthCheckCommand(healthCheckUrl string, cmd *exec.Cmd, filename string) *healthCheckCommand {
	return &healthCheckCommand{
		HealthCheckUrl: healthCheckUrl,
		Cmd:            cmd,
		outputFilename: filename,
	}
}

func (hcc *healthCheckCommand) String() string {
	return fmt.Sprintf("`%s` health-check: %s", strings.Join(append([]string{hcc.Path}, hcc.Args[1:]...), " "), hcc.HealthCheckUrl)
}
<commit_msg>Remove Pdeathsig code because it doesn't do what was intended<commit_after>/*
Copyright 2016 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 e2e_node

import (
	"flag"
	"fmt"
	"io/ioutil"
	"math/rand"
	"net/http"
	"os"
	"os/exec"
	"path"
	"path/filepath"
	"strconv"
	"strings"
	"syscall"
	"time"

	"github.com/golang/glog"
)

var serverStartTimeout = flag.Duration("server-start-timeout", time.Second*120, "Time to wait for each server to become healthy.")
var reportDir = flag.String("report-dir", "", "Path to the directory where the JUnit XML reports should be saved. Default is empty, which doesn't generate these reports.")

type e2eService struct {
	killCmds []*killCmd
	rmDirs   []string

	etcdDataDir         string
	kubeletStaticPodDir string
	nodeName            string
	logFiles            map[string]logFileData
	cgroupsPerQOS       bool
}

type logFileData struct {
	files             []string
	journalctlCommand []string
}

const (
	// This is consistent with the level used in a cluster e2e test.
	LOG_VERBOSITY_LEVEL = "4"
)

func newE2eService(nodeName string, cgroupsPerQOS bool) *e2eService {
	// Special log files that need to be collected for additional debugging.
	var logFiles = map[string]logFileData{
		"kern.log":   {[]string{"/var/log/kern.log"}, []string{"-k"}},
		"docker.log": {[]string{"/var/log/docker.log", "/var/log/upstart/docker.log"}, []string{"-u", "docker"}},
	}

	return &e2eService{
		nodeName:      nodeName,
		logFiles:      logFiles,
		cgroupsPerQOS: cgroupsPerQOS,
	}
}

func (es *e2eService) start() error {
	if _, err := getK8sBin("kubelet"); err != nil {
		return err
	}
	if _, err := getK8sBin("kube-apiserver"); err != nil {
		return err
	}

	cmd, err := es.startEtcd()
	if err != nil {
		return err
	}
	es.killCmds = append(es.killCmds, cmd)
	es.rmDirs = append(es.rmDirs, es.etcdDataDir)

	cmd, err = es.startApiServer()
	if err != nil {
		return err
	}
	es.killCmds = append(es.killCmds, cmd)

	cmd, err = es.startKubeletServer()
	if err != nil {
		return err
	}
	es.killCmds = append(es.killCmds, cmd)
	es.rmDirs = append(es.rmDirs, es.kubeletStaticPodDir)

	return nil
}

// Get logs of interest either via journalctl or by creating sym links.
// Since we scp files from the remote directory, symlinks will be treated as normal files and file contents will be copied over.
func (es *e2eService) getLogFiles() {
	// Nothing to do if report dir is not specified.
	if *reportDir == "" {
		return
	}
	journaldFound := isJournaldAvailable()
	for targetFileName, logFileData := range es.logFiles {
		targetLink := path.Join(*reportDir, targetFileName)
		if journaldFound {
			// Skip log files that do not have an equivalent in journald based machines.
			if len(logFileData.journalctlCommand) == 0 {
				continue
			}
			out, err := exec.Command("sudo", append([]string{"journalctl"}, logFileData.journalctlCommand...)...).CombinedOutput()
			if err != nil {
				glog.Errorf("failed to get %q from journald: %v, %v", targetFileName, string(out), err)
			} else {
				if err = ioutil.WriteFile(targetLink, out, 0755); err != nil {
					glog.Errorf("failed to write logs to %q: %v", targetLink, err)
				}
			}
			continue
		}
		for _, file := range logFileData.files {
			if _, err := os.Stat(file); err != nil {
				// Expected file not found on this distro.
				continue
			}
			if err := copyLogFile(file, targetLink); err != nil {
				glog.Error(err)
			} else {
				break
			}
		}
	}
}

func copyLogFile(src, target string) error {
	// If not a journald based distro, then just symlink files.
	if out, err := exec.Command("sudo", "cp", src, target).CombinedOutput(); err != nil {
		return fmt.Errorf("failed to copy %q to %q: %v, %v", src, target, out, err)
	}
	if out, err := exec.Command("sudo", "chmod", "a+r", target).CombinedOutput(); err != nil {
		return fmt.Errorf("failed to make log file %q world readable: %v, %v", target, out, err)
	}
	return nil
}

func isJournaldAvailable() bool {
	_, err := exec.LookPath("journalctl")
	return err == nil
}

func (es *e2eService) stop() {
	for _, k := range es.killCmds {
		if err := k.Kill(); err != nil {
			glog.Errorf("Failed to stop %v: %v", k.name, err)
		}
	}
	for _, d := range es.rmDirs {
		err := os.RemoveAll(d)
		if err != nil {
			glog.Errorf("Failed to delete directory %s.\n%v", d, err)
		}
	}
}

func (es *e2eService) startEtcd() (*killCmd, error) {
	dataDir, err := ioutil.TempDir("", "node-e2e")
	if err != nil {
		return nil, err
	}
	es.etcdDataDir = dataDir
	cmd := exec.Command("etcd")
	// Execute etcd in the data directory instead of using --data-dir because the flag sometimes requires additional
	// configuration (e.g. --name in version 0.4.9)
	cmd.Dir = es.etcdDataDir
	hcc := newHealthCheckCommand(
		"http://127.0.0.1:4001/v2/keys/", // Trailing slash is required,
		cmd,
		"etcd.log")
	return &killCmd{name: "etcd", cmd: cmd}, es.startServer(hcc)
}

func (es *e2eService) startApiServer() (*killCmd, error) {
	cmd := exec.Command("sudo", getApiServerBin(),
		"--etcd-servers", "http://127.0.0.1:4001",
		"--insecure-bind-address", "0.0.0.0",
		"--service-cluster-ip-range", "10.0.0.1/24",
		"--kubelet-port", "10250",
		"--allow-privileged", "true",
		"--v", LOG_VERBOSITY_LEVEL, "--logtostderr",
	)
	hcc := newHealthCheckCommand(
		"http://127.0.0.1:8080/healthz",
		cmd,
		"kube-apiserver.log")
	return &killCmd{name: "kube-apiserver", cmd: cmd}, es.startServer(hcc)
}

func (es *e2eService) startKubeletServer() (*killCmd, error) {
	dataDir, err := ioutil.TempDir("", "node-e2e-pod")
	if err != nil {
		return nil, err
	}
	es.kubeletStaticPodDir = dataDir
	var killOverride *exec.Cmd
	cmdArgs := []string{}
	if systemdRun, err := exec.LookPath("systemd-run"); err == nil {
		// On systemd services, detection of a service / unit works reliably while
		// detection of a process started from an ssh session does not work.
		// Since kubelet will typically be run as a service it also makes more
		// sense to test it that way
		unitName := fmt.Sprintf("kubelet-%d.service", rand.Int31())
		cmdArgs = append(cmdArgs, systemdRun, "--unit="+unitName, getKubeletServerBin())
		killOverride = exec.Command("sudo", "systemctl", "kill", unitName)
		es.logFiles["kubelet.log"] = logFileData{
			journalctlCommand: []string{"-u", unitName},
		}
	} else {
		cmdArgs = append(cmdArgs, getKubeletServerBin())
	}
	cmdArgs = append(cmdArgs,
		"--api-servers", "http://127.0.0.1:8080",
		"--address", "0.0.0.0",
		"--port", "10250",
		"--hostname-override", es.nodeName, // Required because hostname is inconsistent across hosts
		"--volume-stats-agg-period", "10s", // Aggregate volumes frequently so tests don't need to wait as long
		"--allow-privileged", "true",
		"--serialize-image-pulls", "false",
		"--config", es.kubeletStaticPodDir,
		"--file-check-frequency", "10s", // Check file frequently so tests won't wait too long
		"--v", LOG_VERBOSITY_LEVEL, "--logtostderr",
		"--pod-cidr=10.180.0.0/24", // Assign a fixed CIDR to the node because there is no node controller.
	)
	if es.cgroupsPerQOS {
		cmdArgs = append(cmdArgs,
			"--cgroups-per-qos", "true",
			"--cgroup-root", "/",
		)
	}
	if !*disableKubenet {
		cwd, err := os.Getwd()
		if err != nil {
			return nil, err
		}
		cmdArgs = append(cmdArgs,
			"--network-plugin=kubenet",
			"--network-plugin-dir", filepath.Join(cwd, CNIDirectory, "bin")) // Enable kubenet
	}

	cmd := exec.Command("sudo", cmdArgs...)
	hcc := newHealthCheckCommand(
		"http://127.0.0.1:10255/healthz",
		cmd,
		"kubelet.log")
	return &killCmd{name: "kubelet", cmd: cmd, override: killOverride}, es.startServer(hcc)
}

func (es *e2eService) startServer(cmd *healthCheckCommand) error {
	cmdErrorChan := make(chan error)
	go func() {
		defer close(cmdErrorChan)

		// Create the output filename
		outPath := path.Join(*reportDir, cmd.outputFilename)
		outfile, err := os.Create(outPath)
		if err != nil {
			cmdErrorChan <- fmt.Errorf("Failed to create file %s for `%s` %v.", outPath, cmd, err)
			return
		}
		defer outfile.Close()
		defer outfile.Sync()

		// Set the command to write the output file
		cmd.Cmd.Stdout = outfile
		cmd.Cmd.Stderr = outfile

		// Run the command
		err = cmd.Run()
		if err != nil {
			cmdErrorChan <- fmt.Errorf("%s Failed with error \"%v\".  Output written to: %s", cmd, err, outPath)
			return
		}
	}()

	endTime := time.Now().Add(*serverStartTimeout)
	for endTime.After(time.Now()) {
		select {
		case err := <-cmdErrorChan:
			return err
		case <-time.After(time.Second):
			resp, err := http.Get(cmd.HealthCheckUrl)
			if err == nil && resp.StatusCode == http.StatusOK {
				return nil
			}
		}
	}
	return fmt.Errorf("Timeout waiting for service %s", cmd)
}

// killCmd is a struct to kill a given cmd. The cmd member specifies a command
// to find the pid of and attempt to kill.
// If the override field is set, that will be used instead to kill the command.
// name is only used for logging
type killCmd struct {
	name     string
	cmd      *exec.Cmd
	override *exec.Cmd
}

func (k *killCmd) Kill() error {
	name := k.name
	cmd := k.cmd

	if k.override != nil {
		return k.override.Run()
	}

	if cmd == nil {
		return fmt.Errorf("Could not kill %s because both `override` and `cmd` are nil", name)
	}

	if cmd.Process == nil {
		glog.V(2).Infof("%s not running", name)
		return nil
	}
	pid := cmd.Process.Pid
	if pid <= 1 {
		return fmt.Errorf("invalid PID %d for %s", pid, name)
	}

	// Attempt to shut down the process in a friendly manner before forcing it.
	waitChan := make(chan error)
	go func() {
		_, err := cmd.Process.Wait()
		waitChan <- err
		close(waitChan)
	}()

	const timeout = 10 * time.Second
	for _, signal := range []string{"-TERM", "-KILL"} {
		glog.V(2).Infof("Killing process %d (%s) with %s", pid, name, signal)
		cmd := exec.Command("sudo", "kill", signal, strconv.Itoa(pid))
		// Run the 'kill' command in a separate process group so sudo doesn't ignore it
		cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
		_, err := cmd.Output()
		if err != nil {
			glog.Errorf("Error signaling process %d (%s) with %s: %v", pid, name, signal, err)
			continue
		}

		select {
		case err := <-waitChan:
			if err != nil {
				return fmt.Errorf("error stopping %s: %v", name, err)
			}
			// Success!
			return nil
		case <-time.After(timeout):
			// Continue.
		}
	}

	return fmt.Errorf("unable to stop %s", name)
}

type healthCheckCommand struct {
	*exec.Cmd
	HealthCheckUrl string
	outputFilename string
}

func newHealthCheckCommand(healthCheckUrl string, cmd *exec.Cmd, filename string) *healthCheckCommand {
	return &healthCheckCommand{
		HealthCheckUrl: healthCheckUrl,
		Cmd:            cmd,
		outputFilename: filename,
	}
}

func (hcc *healthCheckCommand) String() string {
	return fmt.Sprintf("`%s` health-check: %s", strings.Join(append([]string{hcc.Path}, hcc.Args[1:]...), " "), hcc.HealthCheckUrl)
}
<|endoftext|>


<commit_before>// Copyright (c) 2017 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

package codegen_test

import (
	"os"
	"path/filepath"
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/uber/zanzibar/codegen"
)

var fooThrift = filepath.Join(
	os.Getenv("GOPATH"),
	"/src/github.com/uber/zanzibar/",
	"examples/example-gateway/idl/",
	"github.com/uber/zanzibar/clients/foo/foo.thrift")

func newPackageHelper(t *testing.T) *codegen.PackageHelper {
	relativeGatewayPath := "../../examples/example-gateway"
	absGatewayPath, err := filepath.Abs(relativeGatewayPath)
	if !assert.NoError(t, err, "failed to get abs path %s", err) {
		return nil
	}

	h, err := codegen.NewPackageHelper(
		"examples/example-gateway/idl",
		"examples/example-gateway/gen-code",
		tmpDir,
		"examples/example-gateway/idl/github.com/uber/zanzibar",
		"examples/example-gateway/test-configs",
	)
	if !assert.NoError(t, err, "failed to create package helper") {
		return nil
	}
	return h
}

func TestImportPath(t *testing.T) {
	h := newPackageHelper(t)
	p, err := h.TypeImportPath(fooThrift)
	assert.Nil(t, err, "should not return error")
	assert.Equal(t, "github.com/uber/zanzibar/examples/example-gateway/build/gen-code/github.com/uber/zanzibar/clients/foo/foo", p, "wrong type import path")
	_, err = h.TypeImportPath("/Users/xxx/go/src/github.com/uber/zanzibar/examples/example-gateway/build/idl/github.com/uber/zanzibar/clients/foo/foo.go")
	assert.Error(t, err, "should return error for not a thrift file")
	_, err = h.TypeImportPath("/Users/xxx/go/src/github.com/uber/zanzibar/examples/example-gateway/build/zanzibar/clients/foo/foo.thrift")
	assert.Error(t, err, "should return error for not in IDL dir")
}

func TestTypePackageName(t *testing.T) {
	h := newPackageHelper(t)
	packageName, err := h.TypePackageName(fooThrift)
	assert.Nil(t, err, "should not return error")
	assert.Equal(t, "foo", packageName, "wrong package name")
	_, err = h.TypeImportPath("/Users/xxx/go/src/github.com/uber/zanzibar/examples/example-gateway/build/idl/github.com/uber/zanzibar/clients/foo/foo.txt")
	assert.Error(t, err, "should return error for not a thrift file")
}

func TestPackageGenPath(t *testing.T) {
	h := newPackageHelper(t)
	p, err := h.PackageGenPath(fooThrift)
	assert.Nil(t, err, "should not return error")
	exp := "github.com/uber/zanzibar/.tmp_gen/clients/foo"
	assert.Equal(t, exp, p, "wrong generated Go package path")
	_, err = h.PackageGenPath("/Users/xxx/go/src/github.com/uber/zanzibar/examples/example-gateway/build/idl/github.com/uber/zanzibar/clients/foo/foo.go")
	assert.Error(t, err, "should return error for not a thrift file")
	_, err = h.PackageGenPath("/Users/xxx/go/src/github.com/uber/zanzibar/examples/example-gateway/build/zanzibar/clients/foo/foo.thrift")
	assert.Error(t, err, "should return error for not in IDL dir")
}

func TestEndpointTestConfigPath(t *testing.T) {
	h := newPackageHelper(t)
	p := h.EndpointTestConfigPath("foo", "bar")
	exp := "examples/example-gateway/test-configs/foo/bar_test.json"
	assert.Equal(t, exp, p, "wrong generated endpoint test config path")
}
<commit_msg>Update tets with relative path<commit_after>// Copyright (c) 2017 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

package codegen_test

import (
	"os"
	"path/filepath"
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/uber/zanzibar/codegen"
)

var fooThrift = filepath.Join(
	os.Getenv("GOPATH"),
	"/src/github.com/uber/zanzibar/",
	"examples/example-gateway/idl/",
	"github.com/uber/zanzibar/clients/foo/foo.thrift")

func newPackageHelper(t *testing.T) *codegen.PackageHelper {
	relativeGatewayPath := "../../examples/example-gateway"
	absGatewayPath, err := filepath.Abs(relativeGatewayPath)
	if !assert.NoError(t, err, "failed to get abs path %s", err) {
		return nil
	}

	h, err := codegen.NewPackageHelper(
		"examples/example-gateway/idl",
		"examples/example-gateway/gen-code",
		tmpDir,
		"examples/example-gateway/idl/github.com/uber/zanzibar",
		"../../examples/example-gateway/test-configs",
	)
	if !assert.NoError(t, err, "failed to create package helper") {
		return nil
	}
	return h
}

func TestImportPath(t *testing.T) {
	h := newPackageHelper(t)
	p, err := h.TypeImportPath(fooThrift)
	assert.Nil(t, err, "should not return error")
	assert.Equal(t, "github.com/uber/zanzibar/examples/example-gateway/build/gen-code/github.com/uber/zanzibar/clients/foo/foo", p, "wrong type import path")
	_, err = h.TypeImportPath("/Users/xxx/go/src/github.com/uber/zanzibar/examples/example-gateway/build/idl/github.com/uber/zanzibar/clients/foo/foo.go")
	assert.Error(t, err, "should return error for not a thrift file")
	_, err = h.TypeImportPath("/Users/xxx/go/src/github.com/uber/zanzibar/examples/example-gateway/build/zanzibar/clients/foo/foo.thrift")
	assert.Error(t, err, "should return error for not in IDL dir")
}

func TestTypePackageName(t *testing.T) {
	h := newPackageHelper(t)
	packageName, err := h.TypePackageName(fooThrift)
	assert.Nil(t, err, "should not return error")
	assert.Equal(t, "foo", packageName, "wrong package name")
	_, err = h.TypeImportPath("/Users/xxx/go/src/github.com/uber/zanzibar/examples/example-gateway/build/idl/github.com/uber/zanzibar/clients/foo/foo.txt")
	assert.Error(t, err, "should return error for not a thrift file")
}

func TestPackageGenPath(t *testing.T) {
	h := newPackageHelper(t)
	p, err := h.PackageGenPath(fooThrift)
	assert.Nil(t, err, "should not return error")
	exp := "github.com/uber/zanzibar/.tmp_gen/clients/foo"
	assert.Equal(t, exp, p, "wrong generated Go package path")
	_, err = h.PackageGenPath("/Users/xxx/go/src/github.com/uber/zanzibar/examples/example-gateway/build/idl/github.com/uber/zanzibar/clients/foo/foo.go")
	assert.Error(t, err, "should return error for not a thrift file")
	_, err = h.PackageGenPath("/Users/xxx/go/src/github.com/uber/zanzibar/examples/example-gateway/build/zanzibar/clients/foo/foo.thrift")
	assert.Error(t, err, "should return error for not in IDL dir")
}

func TestEndpointTestConfigPath(t *testing.T) {
	h := newPackageHelper(t)
	p := h.EndpointTestConfigPath("foo", "bar")
	exp := "../../examples/example-gateway/test-configs/foo/bar_test.json"
	assert.Equal(t, exp, p, "wrong generated endpoint test config path")
}
<|endoftext|>


<commit_before>package rest

import (
	"fmt"
	"github.com/viant/endly/model/msg"
	"github.com/viant/toolbox"
)

//Messages returns messages
func (r *Request) Messages() []*msg.Message {
	var response = make([]*msg.Message, 0)
	response = append(response, msg.NewMessage(msg.NewStyled(fmt.Sprintf("%v %v", r.Method, r.URL), msg.MessageStyleGeneric), msg.NewStyled("rest.Request", msg.MessageStyleGeneric)))
	requestJSON, _ := toolbox.AsJSONText(r.Request)
	response = append(response, msg.NewMessage(msg.NewStyled("Request", msg.MessageStyleGeneric), msg.NewStyled("rest.Request", msg.MessageStyleGeneric),
		msg.NewStyled(requestJSON, msg.MessageStyleInput),
	))
	return response
}

//Messages returns messages
func (r *Response) Messages() []*msg.Message {
	var response = make([]*msg.Message, 0)
	responseJSON, _ := toolbox.AsJSONText(r)
	response = append(response, msg.NewMessage(msg.NewStyled("Response", msg.MessageStyleGeneric), msg.NewStyled("rest.Response", msg.MessageStyleGeneric),
		msg.NewStyled(responseJSON, msg.MessageStyleOutput),
	))
	return response
}

//IsInput returns this request (CLI reporter interface)
func (r *Request) IsInput() bool {
	return true
}

//IsOutput returns this response (CLI reporter interface)
func (r *Response) IsOutput() bool {
	return true
}
<commit_msg>patched nil pointer<commit_after>package rest

import (
	"fmt"
	"github.com/viant/endly/model/msg"
	"github.com/viant/toolbox"
)

//Messages returns messages
func (r *Request) Messages() []*msg.Message {
	var response = make([]*msg.Message, 0)
	response = append(response, msg.NewMessage(msg.NewStyled(fmt.Sprintf("%v %v", r.Method, r.URL), msg.MessageStyleGeneric), msg.NewStyled("rest.Request", msg.MessageStyleGeneric)))
	if r.Request != nil {
		requestJSON, _ := toolbox.AsJSONText(r.Request)
		response = append(response, msg.NewMessage(msg.NewStyled("Request", msg.MessageStyleGeneric), msg.NewStyled("rest.Request", msg.MessageStyleGeneric),
			msg.NewStyled(requestJSON, msg.MessageStyleInput),
		))
	}
	return response
}

//Messages returns messages
func (r *Response) Messages() []*msg.Message {
	var response = make([]*msg.Message, 0)
	responseJSON, _ := toolbox.AsJSONText(r)
	response = append(response, msg.NewMessage(msg.NewStyled("Response", msg.MessageStyleGeneric), msg.NewStyled("rest.Response", msg.MessageStyleGeneric),
		msg.NewStyled(responseJSON, msg.MessageStyleOutput),
	))
	return response
}

//IsInput returns this request (CLI reporter interface)
func (r *Request) IsInput() bool {
	return true
}

//IsOutput returns this response (CLI reporter interface)
func (r *Response) IsOutput() bool {
	return true
}
<|endoftext|>


<commit_before>package timeutil

import (
	"fmt"
	"time"
)

// SliceTimeSeries builds a time series based on supplied interval.
func SliceTimeSeries(times []time.Time, interval Interval) []time.Time {
	if len(times) == 0 {
		return times
	}
	min, max := SliceMinMax(times)
	return MinMaxTimeSeries(min, max, interval)
}

// MinMaxTimeSeries builds a time series based on supplied interval.
func MinMaxTimeSeries(min, max time.Time, interval Interval) []time.Time {
	min, max = MinMax(min, max)
	series := []time.Time{}
	switch interval {
	case Month:
		min = MonthStart(min)
		max = MonthStart(max)
		series = append(series, min)
		cur := min
		for {
			cur = cur.AddDate(0, 1, 0)
			if cur.After(max) {
				break
			}
			series = append(series, cur)
		}
	default:
		panic(fmt.Sprintf("E_INTERVAL_NOT_SUPPORTED [%v]", interval))
	}
	return series
}
<commit_msg>update time series interfaces<commit_after>package timeutil

import (
	"fmt"
	"time"
)

// TimeSeriesSlice builds a time series based on supplied interval.
func TimeSeriesSlice(interval Interval, times []time.Time) []time.Time {
	if len(times) == 0 {
		return times
	}
	min, max := SliceMinMax(times)
	return TimeSeriesMinMax(interval, min, max)
}

// TimeSeriesMinMax builds a time series based on supplied interval.
func TimeSeriesMinMax(interval Interval, min, max time.Time) []time.Time {
	min, max = MinMax(min, max)
	series := []time.Time{}
	switch interval {
	case Month:
		min = MonthStart(min)
		max = MonthStart(max)
		series = append(series, min)
		cur := min
		for {
			cur = cur.AddDate(0, 1, 0)
			if cur.After(max) {
				break
			}
			series = append(series, cur)
		}
	default:
		panic(fmt.Sprintf("E_INTERVAL_NOT_SUPPORTED [%v]", interval))
	}
	return series
}
<|endoftext|>


<commit_before>package triki

import (
	"errors"
	"math"
)

type Board struct {
	Tboard    [9]string
	Available int
}

func NewBoard() *Board {
	b := &Board{Tboard: [9]string{},
		Available: 9}
	for x := 0; x < 9; x++ {
		b.Tboard[x] = E
	}
	return b
}

func (b *Board) CheckWin(p *Player) bool {
	return (((b.Tboard[0] == p.Symbol) && (b.Tboard[1] == p.Symbol) && (b.Tboard[2] == p.Symbol)) ||
		((b.Tboard[0] == p.Symbol) && (b.Tboard[4] == p.Symbol) && (b.Tboard[8] == p.Symbol)) ||
		((b.Tboard[0] == p.Symbol) && (b.Tboard[3] == p.Symbol) && (b.Tboard[6] == p.Symbol)) ||
		((b.Tboard[1] == p.Symbol) && (b.Tboard[4] == p.Symbol) && (b.Tboard[7] == p.Symbol)) ||
		((b.Tboard[2] == p.Symbol) && (b.Tboard[4] == p.Symbol) && (b.Tboard[6] == p.Symbol)) ||
		((b.Tboard[2] == p.Symbol) && (b.Tboard[5] == p.Symbol) && (b.Tboard[8] == p.Symbol)) ||
		((b.Tboard[3] == p.Symbol) && (b.Tboard[4] == p.Symbol) && (b.Tboard[5] == p.Symbol)))
}

func (b *Board) Play(player *Player, posx, posy float64) error {
	pos := int(math.Abs(posx - posy))
	if b.Tboard[pos] == E {
		b.Tboard[pos] = player.Symbol
		b.Available--
		return nil
	} else {
		return errors.New("Invalid Position")
	}
}

func (b *Board) Print() {
	for i := 0; i < len(b.Tboard); i++ {
		if i%3 == 0 {
			println()
		}
		print(b.Tboard[i])
	}
	println()
}
<commit_msg>board has now a String Matrix instead of String array<commit_after>package triki

import (
	"errors"
)

type Board struct {
	Tboard    [3][3]string
	Available int
}

func NewBoard() *Board {
	b := &Board{Tboard: [3][3]string{},
		Available: 9}
	for x := 0; x < 3; x++ {
		for y := 0; y < 3; y++ {
			b.Tboard[x][y] = E
		}
	}
	return b
}

func (b *Board) CheckWin(p *Player) bool {
	return (((b.Tboard[0][0] == p.Symbol) && (b.Tboard[0][1] == p.Symbol) && (b.Tboard[0][2] == p.Symbol)) ||
		((b.Tboard[0][0] == p.Symbol) && (b.Tboard[1][1] == p.Symbol) && (b.Tboard[2][2] == p.Symbol)) ||
		((b.Tboard[0][0] == p.Symbol) && (b.Tboard[1][0] == p.Symbol) && (b.Tboard[2][0] == p.Symbol)) ||
		((b.Tboard[0][1] == p.Symbol) && (b.Tboard[1][1] == p.Symbol) && (b.Tboard[2][1] == p.Symbol)) ||
		((b.Tboard[0][2] == p.Symbol) && (b.Tboard[1][1] == p.Symbol) && (b.Tboard[2][0] == p.Symbol)) ||
		((b.Tboard[0][2] == p.Symbol) && (b.Tboard[1][2] == p.Symbol) && (b.Tboard[2][2] == p.Symbol)) ||
		((b.Tboard[1][0] == p.Symbol) && (b.Tboard[1][1] == p.Symbol) && (b.Tboard[1][2] == p.Symbol)) ||
		((b.Tboard[2][0] == p.Symbol) && (b.Tboard[2][1] == p.Symbol) && (b.Tboard[2][2] == p.Symbol)))
}

func (b *Board) Play(player *Player, posx, posy int) error {
	if b.Tboard[posx][posy] == E {
		b.Tboard[posx][posy] = player.Symbol
		b.Available--
		return nil
	} else {
		return errors.New("Invalid Position")
	}
}

func (b *Board) Print() {
	for x := 0; x < 3; x++ {
		for y := 0; y < 3; y++ {
			print(b.Tboard[x][y])
		}
		println()
	}
}
<|endoftext|>


<commit_before>// +build ignore

package main

import (
	"crypto/sha256"
	redigo "github.com/garyburd/redigo/redis"
	"github.com/pierrre/imageserver"
	imageserver_cache_list "github.com/pierrre/imageserver/cache/list"
	imageserver_cache_memory "github.com/pierrre/imageserver/cache/memory"
	imageserver_cache_redis "github.com/pierrre/imageserver/cache/redis"
	imageserver_http "github.com/pierrre/imageserver/http"
	imageserver_http_parser_graphicsmagick "github.com/pierrre/imageserver/http/parser/graphicsmagick"
	imageserver_http_parser_list "github.com/pierrre/imageserver/http/parser/list"
	imageserver_http_parser_source "github.com/pierrre/imageserver/http/parser/source"
	imageserver_processor_graphicsmagick "github.com/pierrre/imageserver/processor/graphicsmagick"
	imageserver_processor_limit "github.com/pierrre/imageserver/processor/limit"
	imageserver_provider_cache "github.com/pierrre/imageserver/provider/cache"
	imageserver_provider_http "github.com/pierrre/imageserver/provider/http"
	"log"
	"net/http"
	"os"
	"strconv"
	"time"
)

func main() {
	hostname, err := os.Hostname()
	if err != nil {
		panic(err)
	}

	cache := imageserver_cache_list.ListCache{
		imageserver_cache_memory.New(10 * 1024 * 1024),
		&imageserver_cache_redis.RedisCache{
			Pool: &redigo.Pool{
				Dial: func() (redigo.Conn, error) {
					return redigo.Dial("tcp", "localhost:6379")
				},
				MaxIdle: 50,
			},
			Expire: time.Duration(7 * 24 * time.Hour),
		},
	}

	imageServer := &imageserver.Server{
		Cache:        cache,
		CacheKeyFunc: imageserver.NewParametersHashCacheKeyFunc(sha256.New),
		Provider: &imageserver_provider_cache.CacheProvider{
			Cache:        cache,
			CacheKeyFunc: imageserver_provider_cache.NewSourceHashCacheKeyFunc(sha256.New),
			Provider:     &imageserver_provider_http.HTTPProvider{},
		},
		Processor: imageserver_processor_limit.New(16, &imageserver_processor_graphicsmagick.GraphicsMagickProcessor{
			Executable: "gm",
			Timeout:    time.Duration(10 * time.Second),
			AllowedFormats: []string{
				"jpeg",
				"png",
				"bmp",
				"gif",
			},
			DefaultQualities: map[string]string{
				"jpeg": "85",
			},
		}),
	}

	httpImageServer := &imageserver_http.Server{
		Parser: &imageserver_http_parser_list.ListParser{
			&imageserver_http_parser_source.SourceParser{},
			&imageserver_http_parser_graphicsmagick.GraphicsMagickParser{},
		},
		ImageServer: imageServer,
		ETagFunc:    imageserver_http.NewParametersHashETagFunc(sha256.New),
		Expire:      time.Duration(7 * 24 * time.Hour),
		RequestFunc: func(request *http.Request) error {
			url := request.URL
			query := url.Query()
			errorCodeString := query.Get("error")
			if len(errorCodeString) == 0 {
				return nil
			}
			errorCode, err := strconv.Atoi(errorCodeString)
			if err != nil {
				return imageserver.NewError(err.Error())
			}
			return imageserver_http.NewError(errorCode)
		},
		HeaderFunc: func(header http.Header, request *http.Request, err error) {
			header.Set("X-Hostname", hostname)
		},
		ErrorFunc: func(err error, request *http.Request) {
			log.Println(err)
		},
		ResponseFunc: func(request *http.Request, statusCode int, contentSize int64, err error) {
			var errString string
			if err != nil {
				errString = err.Error()
			}
			log.Println(request.RemoteAddr, request.Method, strconv.Quote(request.URL.String()), statusCode, contentSize, strconv.Quote(errString))
		},
	}

	http.Handle("/", httpImageServer)
	err = http.ListenAndServe(":8080", nil)
	if err != nil {
		panic(err)
	}
}
<commit_msg>add "verbose" flag in advanced example<commit_after>// +build ignore

package main

import (
	"crypto/sha256"
	//_ "expvar"
	"flag"
	redigo "github.com/garyburd/redigo/redis"
	"github.com/pierrre/imageserver"
	imageserver_cache_list "github.com/pierrre/imageserver/cache/list"
	imageserver_cache_memory "github.com/pierrre/imageserver/cache/memory"
	imageserver_cache_redis "github.com/pierrre/imageserver/cache/redis"
	imageserver_http "github.com/pierrre/imageserver/http"
	imageserver_http_parser_graphicsmagick "github.com/pierrre/imageserver/http/parser/graphicsmagick"
	imageserver_http_parser_list "github.com/pierrre/imageserver/http/parser/list"
	imageserver_http_parser_source "github.com/pierrre/imageserver/http/parser/source"
	imageserver_processor_graphicsmagick "github.com/pierrre/imageserver/processor/graphicsmagick"
	imageserver_processor_limit "github.com/pierrre/imageserver/processor/limit"
	imageserver_provider_cache "github.com/pierrre/imageserver/provider/cache"
	imageserver_provider_http "github.com/pierrre/imageserver/provider/http"
	"log"
	"net/http"
	//_ "net/http/pprof"
	"os"
	"strconv"
	"time"
)

func main() {
	var verbose bool
	flag.BoolVar(&verbose, "verbose", false, "Verbose")
	flag.Parse()

	hostname, err := os.Hostname()
	if err != nil {
		panic(err)
	}

	cache := imageserver_cache_list.ListCache{
		imageserver_cache_memory.New(10 * 1024 * 1024),
		&imageserver_cache_redis.RedisCache{
			Pool: &redigo.Pool{
				Dial: func() (redigo.Conn, error) {
					return redigo.Dial("tcp", "localhost:6379")
				},
				MaxIdle: 50,
			},
			Expire: time.Duration(7 * 24 * time.Hour),
		},
	}

	imageServer := &imageserver.Server{
		Cache:        cache,
		CacheKeyFunc: imageserver.NewParametersHashCacheKeyFunc(sha256.New),
		Provider: &imageserver_provider_cache.CacheProvider{
			Cache:        cache,
			CacheKeyFunc: imageserver_provider_cache.NewSourceHashCacheKeyFunc(sha256.New),
			Provider:     &imageserver_provider_http.HTTPProvider{},
		},
		Processor: imageserver_processor_limit.New(16, &imageserver_processor_graphicsmagick.GraphicsMagickProcessor{
			Executable: "gm",
			Timeout:    time.Duration(10 * time.Second),
			AllowedFormats: []string{
				"jpeg",
				"png",
				"bmp",
				"gif",
			},
			DefaultQualities: map[string]string{
				"jpeg": "85",
			},
		}),
	}

	httpImageServer := &imageserver_http.Server{
		Parser: &imageserver_http_parser_list.ListParser{
			&imageserver_http_parser_source.SourceParser{},
			&imageserver_http_parser_graphicsmagick.GraphicsMagickParser{},
		},
		ImageServer: imageServer,
		ETagFunc:    imageserver_http.NewParametersHashETagFunc(sha256.New),
		Expire:      time.Duration(7 * 24 * time.Hour),
		RequestFunc: func(request *http.Request) error {
			url := request.URL
			query := url.Query()
			errorCodeString := query.Get("error")
			if len(errorCodeString) == 0 {
				return nil
			}
			errorCode, err := strconv.Atoi(errorCodeString)
			if err != nil {
				return imageserver.NewError(err.Error())
			}
			return imageserver_http.NewError(errorCode)
		},
		HeaderFunc: func(header http.Header, request *http.Request, err error) {
			header.Set("X-Hostname", hostname)
		},
		ErrorFunc: func(err error, request *http.Request) {
			if verbose {
				log.Println(err)
			}
		},
		ResponseFunc: func(request *http.Request, statusCode int, contentSize int64, err error) {
			if verbose {
				var errString string
				if err != nil {
					errString = err.Error()
				}
				log.Println(request.RemoteAddr, request.Method, strconv.Quote(request.URL.String()), statusCode, contentSize, strconv.Quote(errString))
			}
		},
	}

	http.Handle("/", httpImageServer)
	err = http.ListenAndServe(":8080", nil)
	if err != nil {
		panic(err)
	}
}
<|endoftext|>


<commit_before>/*
Copyright 2019 The Tekton 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 v1alpha2_test

import (
	"fmt"
	"testing"
	"time"

	"github.com/google/go-cmp/cmp"
	"github.com/tektoncd/pipeline/pkg/apis/pipeline"
	"github.com/tektoncd/pipeline/pkg/apis/pipeline/v1alpha2"
	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"knative.dev/pkg/apis"
	duckv1beta1 "knative.dev/pkg/apis/duck/v1beta1"
)

func TestTaskRun_GetBuildPodRef(t *testing.T) {
	tr := &v1alpha2.TaskRun{
		ObjectMeta: metav1.ObjectMeta{
			Namespace: "testns",
			Name:      "taskrunname",
		},
	}
	if d := cmp.Diff(tr.GetBuildPodRef(), corev1.ObjectReference{
		APIVersion: "v1",
		Kind:       "Pod",
		Namespace:  "testns",
		Name:       "taskrunname",
	}); d != "" {
		t.Fatalf("taskrun build pod ref mismatch: %s", d)
	}
}

func TestTaskRun_GetPipelineRunPVCName(t *testing.T) {
	tests := []struct {
		name            string
		tr              *v1alpha2.TaskRun
		expectedPVCName string
	}{{
		name: "invalid owner reference",
		// tr: tb.TaskRun(tb.WithOwnerReference(tb.OwnerKind("SomeOtherOwner")))
		tr: &v1alpha2.TaskRun{
			ObjectMeta: metav1.ObjectMeta{
				OwnerReferences: []metav1.OwnerReference{{
					Kind: "SomeOtherOwner",
					Name: "testpr",
				}},
			},
		},
		expectedPVCName: "",
	}, {
		name: "valid pipelinerun owner",
		tr: &v1alpha2.TaskRun{
			ObjectMeta: metav1.ObjectMeta{
				OwnerReferences: []metav1.OwnerReference{{
					Kind: "PipelineRun",
					Name: "testpr",
				}},
			},
		},
		expectedPVCName: "testpr-pvc",
	}, {
		name:            "nil taskrun",
		expectedPVCName: "",
	}}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if tt.tr.GetPipelineRunPVCName() != tt.expectedPVCName {
				t.Fatalf("taskrun pipeline run pvc name mismatch: got %s ; expected %s", tt.tr.GetPipelineRunPVCName(), tt.expectedPVCName)
			}
		})
	}
}

func TestTaskRun_HasPipelineRun(t *testing.T) {
	tests := []struct {
		name string
		tr   *v1alpha2.TaskRun
		want bool
	}{{
		name: "invalid owner reference",
		tr: &v1alpha2.TaskRun{
			ObjectMeta: metav1.ObjectMeta{
				OwnerReferences: []metav1.OwnerReference{{
					Kind: "SomeOtherOwner",
					Name: "testpr",
				}},
			},
		},
		want: false,
	}, {
		name: "valid pipelinerun owner",
		tr: &v1alpha2.TaskRun{
			ObjectMeta: metav1.ObjectMeta{
				OwnerReferences: []metav1.OwnerReference{{
					Kind: "PipelineRun",
					Name: "testpr",
				}},
			},
		},
		want: true,
	}}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if tt.tr.HasPipelineRunOwnerReference() != tt.want {
				t.Fatalf("taskrun pipeline run pvc name mismatch: got %s ; expected %t", tt.tr.GetPipelineRunPVCName(), tt.want)
			}
		})
	}
}

func TestTaskRunIsDone(t *testing.T) {
	tr := &v1alpha2.TaskRun{
		Status: v1alpha2.TaskRunStatus{
			Status: duckv1beta1.Status{
				Conditions: []apis.Condition{{
					Type:   apis.ConditionSucceeded,
					Status: corev1.ConditionFalse,
				}},
			},
		},
	}
	if !tr.IsDone() {
		t.Fatal("Expected pipelinerun status to be done")
	}
}

func TestTaskRunIsCancelled(t *testing.T) {
	tr := &v1alpha2.TaskRun{
		Spec: v1alpha2.TaskRunSpec{
			Status: v1alpha2.TaskRunSpecStatusCancelled,
		},
	}
	if !tr.IsCancelled() {
		t.Fatal("Expected pipelinerun status to be cancelled")
	}
}

func TestTaskRunKey(t *testing.T) {
	tr := &v1alpha2.TaskRun{
		ObjectMeta: metav1.ObjectMeta{
			Name: "taskrunname",
		},
	}
	expectedKey := fmt.Sprintf("TaskRun/%p", tr)
	if tr.GetRunKey() != expectedKey {
		t.Fatalf("Expected taskrun key to be %s but got %s", expectedKey, tr.GetRunKey())
	}
}

func TestTaskRunHasStarted(t *testing.T) {
	params := []struct {
		name          string
		trStatus      v1alpha2.TaskRunStatus
		expectedValue bool
	}{{
		name:          "trWithNoStartTime",
		trStatus:      v1alpha2.TaskRunStatus{},
		expectedValue: false,
	}, {
		name: "trWithStartTime",
		trStatus: v1alpha2.TaskRunStatus{
			TaskRunStatusFields: v1alpha2.TaskRunStatusFields{
				StartTime: &metav1.Time{Time: time.Now()},
			},
		},
		expectedValue: true,
	}, {
		name: "trWithZeroStartTime",
		trStatus: v1alpha2.TaskRunStatus{
			TaskRunStatusFields: v1alpha2.TaskRunStatusFields{
				StartTime: &metav1.Time{},
			},
		},
		expectedValue: false,
	}}
	for _, tc := range params {
		t.Run(tc.name, func(t *testing.T) {
			tr := &v1alpha2.TaskRun{}
			tr.Status = tc.trStatus
			if tr.HasStarted() != tc.expectedValue {
				t.Fatalf("Expected taskrun HasStarted() to return %t but got %t", tc.expectedValue, tr.HasStarted())
			}
		})
	}
}

func TestTaskRunIsOfPipelinerun(t *testing.T) {
	tests := []struct {
		name                  string
		tr                    *v1alpha2.TaskRun
		expectedValue         bool
		expetectedPipeline    string
		expetectedPipelineRun string
	}{{
		name: "yes",
		tr: &v1alpha2.TaskRun{
			ObjectMeta: metav1.ObjectMeta{
				Labels: map[string]string{
					pipeline.GroupName + pipeline.PipelineLabelKey:    "pipeline",
					pipeline.GroupName + pipeline.PipelineRunLabelKey: "pipelinerun",
				},
			},
		},
		expectedValue:         true,
		expetectedPipeline:    "pipeline",
		expetectedPipelineRun: "pipelinerun",
	}, {
		name:          "no",
		tr:            &v1alpha2.TaskRun{},
		expectedValue: false,
	}}

	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			value, pipeline, pipelineRun := test.tr.IsPartOfPipeline()
			if value != test.expectedValue {
				t.Fatalf("Expecting %v got %v", test.expectedValue, value)
			}

			if pipeline != test.expetectedPipeline {
				t.Fatalf("Mismatch in pipeline: got %s expected %s", pipeline, test.expetectedPipeline)
			}

			if pipelineRun != test.expetectedPipelineRun {
				t.Fatalf("Mismatch in pipelinerun: got %s expected %s", pipelineRun, test.expetectedPipelineRun)
			}
		})
	}
}
<commit_msg>remove code comment from v1alpha2 taskrun_types_test.go<commit_after>/*
Copyright 2019 The Tekton 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 v1alpha2_test

import (
	"fmt"
	"testing"
	"time"

	"github.com/google/go-cmp/cmp"
	"github.com/tektoncd/pipeline/pkg/apis/pipeline"
	"github.com/tektoncd/pipeline/pkg/apis/pipeline/v1alpha2"
	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"knative.dev/pkg/apis"
	duckv1beta1 "knative.dev/pkg/apis/duck/v1beta1"
)

func TestTaskRun_GetBuildPodRef(t *testing.T) {
	tr := &v1alpha2.TaskRun{
		ObjectMeta: metav1.ObjectMeta{
			Namespace: "testns",
			Name:      "taskrunname",
		},
	}
	if d := cmp.Diff(tr.GetBuildPodRef(), corev1.ObjectReference{
		APIVersion: "v1",
		Kind:       "Pod",
		Namespace:  "testns",
		Name:       "taskrunname",
	}); d != "" {
		t.Fatalf("taskrun build pod ref mismatch: %s", d)
	}
}

func TestTaskRun_GetPipelineRunPVCName(t *testing.T) {
	tests := []struct {
		name            string
		tr              *v1alpha2.TaskRun
		expectedPVCName string
	}{{
		name: "invalid owner reference",
		tr: &v1alpha2.TaskRun{
			ObjectMeta: metav1.ObjectMeta{
				OwnerReferences: []metav1.OwnerReference{{
					Kind: "SomeOtherOwner",
					Name: "testpr",
				}},
			},
		},
		expectedPVCName: "",
	}, {
		name: "valid pipelinerun owner",
		tr: &v1alpha2.TaskRun{
			ObjectMeta: metav1.ObjectMeta{
				OwnerReferences: []metav1.OwnerReference{{
					Kind: "PipelineRun",
					Name: "testpr",
				}},
			},
		},
		expectedPVCName: "testpr-pvc",
	}, {
		name:            "nil taskrun",
		expectedPVCName: "",
	}}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if tt.tr.GetPipelineRunPVCName() != tt.expectedPVCName {
				t.Fatalf("taskrun pipeline run pvc name mismatch: got %s ; expected %s", tt.tr.GetPipelineRunPVCName(), tt.expectedPVCName)
			}
		})
	}
}

func TestTaskRun_HasPipelineRun(t *testing.T) {
	tests := []struct {
		name string
		tr   *v1alpha2.TaskRun
		want bool
	}{{
		name: "invalid owner reference",
		tr: &v1alpha2.TaskRun{
			ObjectMeta: metav1.ObjectMeta{
				OwnerReferences: []metav1.OwnerReference{{
					Kind: "SomeOtherOwner",
					Name: "testpr",
				}},
			},
		},
		want: false,
	}, {
		name: "valid pipelinerun owner",
		tr: &v1alpha2.TaskRun{
			ObjectMeta: metav1.ObjectMeta{
				OwnerReferences: []metav1.OwnerReference{{
					Kind: "PipelineRun",
					Name: "testpr",
				}},
			},
		},
		want: true,
	}}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if tt.tr.HasPipelineRunOwnerReference() != tt.want {
				t.Fatalf("taskrun pipeline run pvc name mismatch: got %s ; expected %t", tt.tr.GetPipelineRunPVCName(), tt.want)
			}
		})
	}
}

func TestTaskRunIsDone(t *testing.T) {
	tr := &v1alpha2.TaskRun{
		Status: v1alpha2.TaskRunStatus{
			Status: duckv1beta1.Status{
				Conditions: []apis.Condition{{
					Type:   apis.ConditionSucceeded,
					Status: corev1.ConditionFalse,
				}},
			},
		},
	}
	if !tr.IsDone() {
		t.Fatal("Expected pipelinerun status to be done")
	}
}

func TestTaskRunIsCancelled(t *testing.T) {
	tr := &v1alpha2.TaskRun{
		Spec: v1alpha2.TaskRunSpec{
			Status: v1alpha2.TaskRunSpecStatusCancelled,
		},
	}
	if !tr.IsCancelled() {
		t.Fatal("Expected pipelinerun status to be cancelled")
	}
}

func TestTaskRunKey(t *testing.T) {
	tr := &v1alpha2.TaskRun{
		ObjectMeta: metav1.ObjectMeta{
			Name: "taskrunname",
		},
	}
	expectedKey := fmt.Sprintf("TaskRun/%p", tr)
	if tr.GetRunKey() != expectedKey {
		t.Fatalf("Expected taskrun key to be %s but got %s", expectedKey, tr.GetRunKey())
	}
}

func TestTaskRunHasStarted(t *testing.T) {
	params := []struct {
		name          string
		trStatus      v1alpha2.TaskRunStatus
		expectedValue bool
	}{{
		name:          "trWithNoStartTime",
		trStatus:      v1alpha2.TaskRunStatus{},
		expectedValue: false,
	}, {
		name: "trWithStartTime",
		trStatus: v1alpha2.TaskRunStatus{
			TaskRunStatusFields: v1alpha2.TaskRunStatusFields{
				StartTime: &metav1.Time{Time: time.Now()},
			},
		},
		expectedValue: true,
	}, {
		name: "trWithZeroStartTime",
		trStatus: v1alpha2.TaskRunStatus{
			TaskRunStatusFields: v1alpha2.TaskRunStatusFields{
				StartTime: &metav1.Time{},
			},
		},
		expectedValue: false,
	}}
	for _, tc := range params {
		t.Run(tc.name, func(t *testing.T) {
			tr := &v1alpha2.TaskRun{}
			tr.Status = tc.trStatus
			if tr.HasStarted() != tc.expectedValue {
				t.Fatalf("Expected taskrun HasStarted() to return %t but got %t", tc.expectedValue, tr.HasStarted())
			}
		})
	}
}

func TestTaskRunIsOfPipelinerun(t *testing.T) {
	tests := []struct {
		name                  string
		tr                    *v1alpha2.TaskRun
		expectedValue         bool
		expetectedPipeline    string
		expetectedPipelineRun string
	}{{
		name: "yes",
		tr: &v1alpha2.TaskRun{
			ObjectMeta: metav1.ObjectMeta{
				Labels: map[string]string{
					pipeline.GroupName + pipeline.PipelineLabelKey:    "pipeline",
					pipeline.GroupName + pipeline.PipelineRunLabelKey: "pipelinerun",
				},
			},
		},
		expectedValue:         true,
		expetectedPipeline:    "pipeline",
		expetectedPipelineRun: "pipelinerun",
	}, {
		name:          "no",
		tr:            &v1alpha2.TaskRun{},
		expectedValue: false,
	}}

	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			value, pipeline, pipelineRun := test.tr.IsPartOfPipeline()
			if value != test.expectedValue {
				t.Fatalf("Expecting %v got %v", test.expectedValue, value)
			}

			if pipeline != test.expetectedPipeline {
				t.Fatalf("Mismatch in pipeline: got %s expected %s", pipeline, test.expetectedPipeline)
			}

			if pipelineRun != test.expetectedPipelineRun {
				t.Fatalf("Mismatch in pipelinerun: got %s expected %s", pipelineRun, test.expetectedPipelineRun)
			}
		})
	}
}
<|endoftext|>


<commit_before>/*
Copyright 2015 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 aws

import (
	"math"
	"sync"
	"time"

	"github.com/aws/aws-sdk-go/aws/awserr"
	"github.com/aws/aws-sdk-go/aws/request"
	"github.com/golang/glog"
)

const (
	decayIntervalSeconds = 20
	decayFraction        = 0.8
	maxDelay             = 60 * time.Second
)

// CrossRequestRetryDelay inserts delays before AWS calls, when we are observing RequestLimitExceeded errors
// Note that we share a CrossRequestRetryDelay across multiple AWS requests; this is a process-wide back-off,
// whereas the aws-sdk-go implements a per-request exponential backoff/retry
type CrossRequestRetryDelay struct {
	backoff Backoff
}

// Create a new CrossRequestRetryDelay
func NewCrossRequestRetryDelay() *CrossRequestRetryDelay {
	c := &CrossRequestRetryDelay{}
	c.backoff.init(decayIntervalSeconds, decayFraction, maxDelay)
	return c
}

// Added to the Sign chain; called before each request
func (c *CrossRequestRetryDelay) BeforeSign(r *request.Request) {
	now := time.Now()
	delay := c.backoff.ComputeDelayForRequest(now)
	if delay > 0 {
		glog.Warningf("Inserting delay before AWS request (%s) to avoid RequestLimitExceeded: %s",
			describeRequest(r), delay.String())
		r.Config.SleepDelay(delay)

		// Avoid clock skew problems
		r.Time = now
	}
}

// Return a user-friendly string describing the request, for use in log messages
func describeRequest(r *request.Request) string {
	service := r.ClientInfo.ServiceName

	name := "?"
	if r.Operation != nil {
		name = r.Operation.Name
	}

	return service + "::" + name
}

// Added to the AfterRetry chain; called after any error
func (c *CrossRequestRetryDelay) AfterRetry(r *request.Request) {
	if r.Error == nil {
		return
	}
	awsError, ok := r.Error.(awserr.Error)
	if !ok {
		return
	}
	if awsError.Code() == "RequestLimitExceeded" {
		c.backoff.ReportError()
		glog.Warningf("Got RequestLimitExceeded error on AWS request (%s)",
			describeRequest(r))
	}
}

// Backoff manages a backoff that varies based on the recently observed failures
type Backoff struct {
	decayIntervalSeconds int64
	decayFraction        float64
	maxDelay             time.Duration

	mutex sync.Mutex

	// We count all requests & the number of requests which hit a
	// RequestLimit.  We only really care about 'recent' requests, so we
	// decay the counts exponentially to bias towards recent values.
	countErrorsRequestLimit float32
	countRequests           float32
	lastDecay               int64
}

func (b *Backoff) init(decayIntervalSeconds int, decayFraction float64, maxDelay time.Duration) {
	b.lastDecay = time.Now().Unix()
	// Bias so that if the first request hits the limit we don't immediately apply the full delay
	b.countRequests = 4
	b.decayIntervalSeconds = int64(decayIntervalSeconds)
	b.decayFraction = decayFraction
	b.maxDelay = maxDelay
}

// Computes the delay required for a request, also updating internal state to count this request
func (b *Backoff) ComputeDelayForRequest(now time.Time) time.Duration {
	b.mutex.Lock()
	defer b.mutex.Unlock()

	// Apply exponential decay to the counters
	timeDeltaSeconds := now.Unix() - b.lastDecay
	if timeDeltaSeconds > b.decayIntervalSeconds {
		intervals := float64(timeDeltaSeconds) / float64(b.decayIntervalSeconds)
		decay := float32(math.Pow(b.decayFraction, intervals))
		b.countErrorsRequestLimit *= decay
		b.countRequests *= decay
		b.lastDecay = now.Unix()
	}

	// Count this request
	b.countRequests += 1.0

	// Compute the failure rate
	errorFraction := float32(0.0)
	if b.countRequests > 0.5 {
		// Avoid tiny residuals & rounding errors
		errorFraction = b.countErrorsRequestLimit / b.countRequests
	}

	// Ignore a low fraction of errors
	// This also allows them to time-out
	if errorFraction < 0.1 {
		return time.Duration(0)
	}

	// Delay by the max delay multiplied by the recent error rate
	// (i.e. we apply a linear delay function)
	// TODO: This is pretty arbitrary
	delay := time.Nanosecond * time.Duration(float32(b.maxDelay.Nanoseconds())*errorFraction)
	// Round down to the nearest second for sanity
	return time.Second * time.Duration(int(delay.Seconds()))
}

// Called when we observe a throttling error
func (b *Backoff) ReportError() {
	b.mutex.Lock()
	defer b.mutex.Unlock()

	b.countErrorsRequestLimit += 1.0
}
<commit_msg>Check if SleepDelay of AWS request is nil before sign.<commit_after>/*
Copyright 2015 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 aws

import (
	"math"
	"sync"
	"time"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/awserr"
	"github.com/aws/aws-sdk-go/aws/request"
	"github.com/golang/glog"
)

const (
	decayIntervalSeconds = 20
	decayFraction        = 0.8
	maxDelay             = 60 * time.Second
)

// CrossRequestRetryDelay inserts delays before AWS calls, when we are observing RequestLimitExceeded errors
// Note that we share a CrossRequestRetryDelay across multiple AWS requests; this is a process-wide back-off,
// whereas the aws-sdk-go implements a per-request exponential backoff/retry
type CrossRequestRetryDelay struct {
	backoff Backoff
}

// Create a new CrossRequestRetryDelay
func NewCrossRequestRetryDelay() *CrossRequestRetryDelay {
	c := &CrossRequestRetryDelay{}
	c.backoff.init(decayIntervalSeconds, decayFraction, maxDelay)
	return c
}

// Added to the Sign chain; called before each request
func (c *CrossRequestRetryDelay) BeforeSign(r *request.Request) {
	now := time.Now()
	delay := c.backoff.ComputeDelayForRequest(now)
	if delay > 0 {
		glog.Warningf("Inserting delay before AWS request (%s) to avoid RequestLimitExceeded: %s",
			describeRequest(r), delay.String())

		if sleepFn := r.Config.SleepDelay; sleepFn != nil {
			// Support SleepDelay for backwards compatibility
			sleepFn(delay)
		} else if err := aws.SleepWithContext(r.Context(), delay); err != nil {
			r.Error = awserr.New(request.CanceledErrorCode, "request context canceled", err)
			r.Retryable = aws.Bool(false)
			return
		}

		// Avoid clock skew problems
		r.Time = now
	}
}

// Return a user-friendly string describing the request, for use in log messages
func describeRequest(r *request.Request) string {
	service := r.ClientInfo.ServiceName

	name := "?"
	if r.Operation != nil {
		name = r.Operation.Name
	}

	return service + "::" + name
}

// Added to the AfterRetry chain; called after any error
func (c *CrossRequestRetryDelay) AfterRetry(r *request.Request) {
	if r.Error == nil {
		return
	}
	awsError, ok := r.Error.(awserr.Error)
	if !ok {
		return
	}
	if awsError.Code() == "RequestLimitExceeded" {
		c.backoff.ReportError()
		glog.Warningf("Got RequestLimitExceeded error on AWS request (%s)",
			describeRequest(r))
	}
}

// Backoff manages a backoff that varies based on the recently observed failures
type Backoff struct {
	decayIntervalSeconds int64
	decayFraction        float64
	maxDelay             time.Duration

	mutex sync.Mutex

	// We count all requests & the number of requests which hit a
	// RequestLimit.  We only really care about 'recent' requests, so we
	// decay the counts exponentially to bias towards recent values.
	countErrorsRequestLimit float32
	countRequests           float32
	lastDecay               int64
}

func (b *Backoff) init(decayIntervalSeconds int, decayFraction float64, maxDelay time.Duration) {
	b.lastDecay = time.Now().Unix()
	// Bias so that if the first request hits the limit we don't immediately apply the full delay
	b.countRequests = 4
	b.decayIntervalSeconds = int64(decayIntervalSeconds)
	b.decayFraction = decayFraction
	b.maxDelay = maxDelay
}

// Computes the delay required for a request, also updating internal state to count this request
func (b *Backoff) ComputeDelayForRequest(now time.Time) time.Duration {
	b.mutex.Lock()
	defer b.mutex.Unlock()

	// Apply exponential decay to the counters
	timeDeltaSeconds := now.Unix() - b.lastDecay
	if timeDeltaSeconds > b.decayIntervalSeconds {
		intervals := float64(timeDeltaSeconds) / float64(b.decayIntervalSeconds)
		decay := float32(math.Pow(b.decayFraction, intervals))
		b.countErrorsRequestLimit *= decay
		b.countRequests *= decay
		b.lastDecay = now.Unix()
	}

	// Count this request
	b.countRequests += 1.0

	// Compute the failure rate
	errorFraction := float32(0.0)
	if b.countRequests > 0.5 {
		// Avoid tiny residuals & rounding errors
		errorFraction = b.countErrorsRequestLimit / b.countRequests
	}

	// Ignore a low fraction of errors
	// This also allows them to time-out
	if errorFraction < 0.1 {
		return time.Duration(0)
	}

	// Delay by the max delay multiplied by the recent error rate
	// (i.e. we apply a linear delay function)
	// TODO: This is pretty arbitrary
	delay := time.Nanosecond * time.Duration(float32(b.maxDelay.Nanoseconds())*errorFraction)
	// Round down to the nearest second for sanity
	return time.Second * time.Duration(int(delay.Seconds()))
}

// Called when we observe a throttling error
func (b *Backoff) ReportError() {
	b.mutex.Lock()
	defer b.mutex.Unlock()

	b.countErrorsRequestLimit += 1.0
}
<|endoftext|>


<commit_before>/*
 * Copyright 2019, EnMasse authors.
 * License: Apache License 2.0 (see the file LICENSE or http://apache.org/licenses/LICENSE-2.0.html).
 */
package authenticationservice

import (
	"context"
	"fmt"
	adminv1beta1 "github.com/enmasseproject/enmasse/pkg/apis/admin/v1beta1"
	"github.com/enmasseproject/enmasse/pkg/util"
	"github.com/enmasseproject/enmasse/pkg/util/install"
	oauthv1 "github.com/openshift/api/oauth/v1"
	routev1 "github.com/openshift/api/route/v1"
	appsv1 "k8s.io/api/apps/v1"
	corev1 "k8s.io/api/core/v1"
	resource "k8s.io/apimachinery/pkg/api/resource"
	"k8s.io/apimachinery/pkg/runtime"
	intstr "k8s.io/apimachinery/pkg/util/intstr"
	"sigs.k8s.io/controller-runtime/pkg/client"
)

func applyStandardAuthServiceDefaults(ctx context.Context, client client.Client, scheme *runtime.Scheme, authservice *adminv1beta1.AuthenticationService) error {
	if authservice.Spec.Standard == nil {
		authservice.Spec.Standard = &adminv1beta1.AuthenticationServiceSpecStandard{}
	}
	if authservice.Spec.Standard.Storage == nil {
		authservice.Spec.Standard.Storage = &adminv1beta1.AuthenticationServiceSpecStandardStorage{
			Type: adminv1beta1.Ephemeral,
		}
	}
	if authservice.Spec.Standard.DeploymentName == nil {
		authservice.Spec.Standard.DeploymentName = &authservice.Name
	}
	if authservice.Spec.Standard.ServiceName == nil {
		authservice.Spec.Standard.ServiceName = &authservice.Name
	}
	if authservice.Spec.Standard.RouteName == nil {
		authservice.Spec.Standard.RouteName = &authservice.Name
	}
	if authservice.Spec.Standard.Storage.ClaimName == nil {
		authservice.Spec.Standard.Storage.ClaimName = &authservice.Name
	}
	if authservice.Spec.Standard.Storage.DeleteClaim == nil {
		authservice.Spec.Standard.Storage.DeleteClaim = new(bool)
		*authservice.Spec.Standard.Storage.DeleteClaim = true
	}
	if authservice.Spec.Standard.Resources == nil {
		authservice.Spec.Standard.Resources = &corev1.ResourceRequirements{
			Requests: map[corev1.ResourceName]resource.Quantity{"memory": *resource.NewScaledQuantity(2, resource.Giga)},
			Limits:   map[corev1.ResourceName]resource.Quantity{"memory": *resource.NewScaledQuantity(2, resource.Giga)},
		}
	}
	if authservice.Spec.Standard.Datasource == nil {
		authservice.Spec.Standard.Datasource = &adminv1beta1.AuthenticationServiceSpecStandardDatasource{
			Type: adminv1beta1.H2Datasource,
		}
	}
	if authservice.Spec.Standard.CredentialsSecret == nil {
		secretName := "keycloak-credentials"
		authservice.Spec.Standard.CredentialsSecret = &corev1.SecretReference{
			Name: secretName,
		}
		err := CreateAuthserviceSecret(ctx, client, scheme, secretName, authservice, func(secret *corev1.Secret) error {
			secret.StringData = make(map[string]string)
			secret.StringData["admin.username"] = "admin"
			adminPassword, err := util.GeneratePassword(32)
			if err != nil {
				return err
			}
			secret.StringData["admin.password"] = adminPassword
			return nil
		})
		if err != nil {
			return err
		}
	}
	if authservice.Spec.Standard.CertificateSecret == nil {
		secretName := "standard-authservice-cert"
		authservice.Spec.Standard.CertificateSecret = &corev1.SecretReference{
			Name: secretName,
		}

		if !util.IsOpenshift() {
			err := CreateAuthserviceSecret(ctx, client, scheme, secretName, authservice, func(secret *corev1.Secret) error {
				cn := util.ServiceToCommonName(authservice.Namespace, *authservice.Spec.Standard.ServiceName)
				return util.GenerateSelfSignedCertSecret(cn, nil, nil, secret)
			})
			if err != nil {
				return err
			}
		}
	}
	return nil
}

func applyStandardAuthServiceDeployment(authservice *adminv1beta1.AuthenticationService, deployment *appsv1.Deployment) error {

	install.ApplyDeploymentDefaults(deployment, "standard-authservice", *authservice.Spec.Standard.DeploymentName)

	install.ApplyInitContainer(deployment, "keycloak-plugin", func(container *corev1.Container) {
		install.ApplyContainerImage(container, "keycloak-plugin", authservice.Spec.Standard.InitImage)
		install.ApplyEnvSimple(container, "KEYCLOAK_DIR", "/opt/jboss/keycloak")
		install.ApplyVolumeMountSimple(container, "keycloak-providers", "/opt/jboss/keycloak/providers", false)
		install.ApplyVolumeMountSimple(container, "keycloak-configuration", "/opt/jboss/keycloak/standalone/configuration", false)
		install.ApplyVolumeMountSimple(container, "standard-authservice-cert", "/opt/enmasse/cert", false)
		// Only allow setting data source on initial creation
		if util.IsNewObject(deployment) {
			install.ApplyEnvSimple(container, "KEYCLOAK_CONFIG_FILE", "standalone-"+string(authservice.Spec.Standard.Datasource.Type)+".xml")
		}
	})
	install.ApplyContainer(deployment, "keycloak", func(container *corev1.Container) {
		install.ApplyContainerImage(container, "keycloak", authservice.Spec.Standard.Image)
		install.ApplyEnvSimple(container, "KEYCLOAK_SASL_XOAUTH_BASE_URI", authservice.Annotations["enmasse.io/oauth-url"])
		jvmOptions := "-Dvertx.cacheDirBase=/tmp -Djboss.bind.address=0.0.0.0 -Djava.net.preferIPv4Stack=true -Duser.timezone=UTC"
		if authservice.Spec.Standard.JvmOptions != nil {
			jvmOptions += " " + *authservice.Spec.Standard.JvmOptions
		} else if qty, ok := authservice.Spec.Standard.Resources.Requests["memory"]; ok {
			containerMemoryRequest := qty.ScaledValue(resource.Mega)
			jvmOptions += fmt.Sprintf(" -Xms%dm -Xmx%dm", containerMemoryRequest/2, containerMemoryRequest/2)
		}
		install.ApplyEnvSimple(container, "JAVA_OPTS", jvmOptions)
		install.ApplyEnvSecret(container, "KEYCLOAK_USER", "admin.username", authservice.Spec.Standard.CredentialsSecret.Name)
		install.ApplyEnvSecret(container, "KEYCLOAK_PASSWORD", "admin.password", authservice.Spec.Standard.CredentialsSecret.Name)

		if authservice.Spec.Standard.Datasource.Type == adminv1beta1.PostgresqlDatasource {
			install.ApplyEnvSimple(container, "DB_HOST", authservice.Spec.Standard.Datasource.Host)
			install.ApplyEnvSimple(container, "DB_PORT", fmt.Sprintf("%d", authservice.Spec.Standard.Datasource.Port))
			install.ApplyEnvSimple(container, "DB_DATABASE", authservice.Spec.Standard.Datasource.Database)
			install.ApplyEnvSecret(container, "DB_USERNAME", "database-user", authservice.Spec.Standard.Datasource.CredentialsSecret.Name)
			install.ApplyEnvSecret(container, "DB_PASSWORD", "database-password", authservice.Spec.Standard.Datasource.CredentialsSecret.Name)
		}

		container.Args = []string{"start-keycloak.sh", "-b", "0.0.0.0", "-c", "standalone-openshift.xml"}

		container.Ports = []corev1.ContainerPort{{
			ContainerPort: 5671,
			Name:          "amqps",
		}, {
			ContainerPort: 8443,
			Name:          "https",
		}}
		container.ReadinessProbe = &corev1.Probe{
			InitialDelaySeconds: 60,
			Handler: corev1.Handler{
				HTTPGet: &corev1.HTTPGetAction{
					Port:   intstr.FromString("https"),
					Path:   "/auth",
					Scheme: "HTTPS",
				},
			},
		}
		container.LivenessProbe = &corev1.Probe{
			InitialDelaySeconds: 120,
			Handler: corev1.Handler{
				HTTPGet: &corev1.HTTPGetAction{
					Port:   intstr.FromString("https"),
					Path:   "/auth",
					Scheme: "HTTPS",
				},
			},
		}
		if authservice.Spec.Standard.Resources != nil {
			container.Resources = *authservice.Spec.Standard.Resources
		}
		install.ApplyVolumeMountSimple(container, "keycloak-providers", "/opt/jboss/keycloak/providers", false)
		install.ApplyVolumeMountSimple(container, "keycloak-configuration", "/opt/jboss/keycloak/standalone/configuration", false)
		install.ApplyVolumeMountSimple(container, "keycloak-persistence", "/opt/jboss/keycloak/standalone/data", false)
		install.ApplyVolumeMountSimple(container, "standard-authservice-cert", "/opt/enmasse/cert", true)
	})

	install.ApplySecretVolume(deployment, "standard-authservice-cert", authservice.Spec.Standard.CertificateSecret.Name)
	install.ApplyEmptyDirVolume(deployment, "keycloak-providers")
	install.ApplyEmptyDirVolume(deployment, "keycloak-configuration")
	install.ApplyEmptyDirVolume(deployment, "keycloak-configuration")

	// Only allow setting volume on initial creation
	if util.IsNewObject(deployment) {
		if authservice.Spec.Standard.Storage.Type == adminv1beta1.PersistentClaim {
			install.ApplyPersistentVolume(deployment, "keycloak-persistence", *authservice.Spec.Standard.Storage.ClaimName)
		} else {
			install.ApplyEmptyDirVolume(deployment, "keycloak-persistence")
		}
	}

	if authservice.Spec.Standard.ServiceAccountName != nil {
		deployment.Spec.Template.Spec.ServiceAccountName = *authservice.Spec.Standard.ServiceAccountName
	} else {
		deployment.Spec.Template.Spec.ServiceAccountName = "standard-authservice"
	}
	deployment.Spec.Strategy = appsv1.DeploymentStrategy{
		Type: appsv1.RecreateDeploymentStrategyType,
	}
	return nil
}

func applyStandardAuthServiceService(authservice *adminv1beta1.AuthenticationService, service *corev1.Service) error {

	install.ApplyServiceDefaults(service, "standard-authservice", *authservice.Spec.Standard.ServiceName)
	service.Spec.Selector = install.CreateDefaultLabels(nil, "standard-authservice", *authservice.Spec.Standard.DeploymentName)

	if service.Annotations == nil {
		service.Annotations = make(map[string]string)
	}
	service.Annotations["service.alpha.openshift.io/serving-cert-secret-name"] = "standard-authservice-cert"
	service.Spec.Ports = []corev1.ServicePort{
		{
			Port:       8443,
			Protocol:   corev1.ProtocolTCP,
			TargetPort: intstr.FromString("https"),
			Name:       "https",
		},
		{
			Port:       5671,
			Protocol:   corev1.ProtocolTCP,
			TargetPort: intstr.FromString("amqps"),
			Name:       "amqps",
		},
	}
	return nil
}

func applyStandardAuthServiceVolume(authservice *adminv1beta1.AuthenticationService, pvc *corev1.PersistentVolumeClaim) error {

	install.ApplyDefaultLabels(&pvc.ObjectMeta, "standard-authservice", *authservice.Spec.Standard.Storage.ClaimName)

	// Only allow setting volume on initial creation
	if util.IsNewObject(pvc) {
		selector := authservice.Spec.Standard.Storage.Selector
		storageClassName := authservice.Spec.Standard.Storage.Class
		resources := corev1.ResourceRequirements{
			Requests: map[corev1.ResourceName]resource.Quantity{"storage": authservice.Spec.Standard.Storage.Size},
		}

		pvc.Spec.AccessModes = []corev1.PersistentVolumeAccessMode{
			corev1.ReadWriteOnce,
		}
		pvc.Spec.Selector = selector
		pvc.Spec.StorageClassName = storageClassName
		pvc.Spec.Resources = resources
	}
	return nil
}

func applyRoute(authservice *adminv1beta1.AuthenticationService, route *routev1.Route, caCertificate string) error {

	install.ApplyDefaultLabels(&route.ObjectMeta, "standard-authservice", *authservice.Spec.Standard.RouteName)

	route.Spec = routev1.RouteSpec{
		To: routev1.RouteTargetReference{
			Kind: "Service",
			Name: authservice.Name,
		},
		TLS: &routev1.TLSConfig{
			Termination:   routev1.TLSTerminationReencrypt,
			CACertificate: caCertificate,
		},
		Port: &routev1.RoutePort{
			TargetPort: intstr.FromString("https"),
		},
	}
	return nil
}

func applyOauthClient(authservice *adminv1beta1.AuthenticationService, oauth *oauthv1.OAuthClient, redirectUri string) error {
	install.ApplyDefaultLabels(&oauth.ObjectMeta, "oauthclient", oauth.Name)
	if oauth.Secret == "" {
		password, err := util.GeneratePassword(32)
		if err != nil {
			return err
		}
		oauth.Secret = password
	}
	oauth.GrantMethod = oauthv1.GrantHandlerAuto
	oauth.RedirectURIs = []string{
		redirectUri,
	}
	return nil
}
<commit_msg>Fix upgrade<commit_after>/*
 * Copyright 2019, EnMasse authors.
 * License: Apache License 2.0 (see the file LICENSE or http://apache.org/licenses/LICENSE-2.0.html).
 */
package authenticationservice

import (
	"context"
	"fmt"
	adminv1beta1 "github.com/enmasseproject/enmasse/pkg/apis/admin/v1beta1"
	"github.com/enmasseproject/enmasse/pkg/util"
	"github.com/enmasseproject/enmasse/pkg/util/install"
	oauthv1 "github.com/openshift/api/oauth/v1"
	routev1 "github.com/openshift/api/route/v1"
	appsv1 "k8s.io/api/apps/v1"
	corev1 "k8s.io/api/core/v1"
	resource "k8s.io/apimachinery/pkg/api/resource"
	"k8s.io/apimachinery/pkg/runtime"
	intstr "k8s.io/apimachinery/pkg/util/intstr"
	"sigs.k8s.io/controller-runtime/pkg/client"
)

func applyStandardAuthServiceDefaults(ctx context.Context, client client.Client, scheme *runtime.Scheme, authservice *adminv1beta1.AuthenticationService) error {
	if authservice.Spec.Standard == nil {
		authservice.Spec.Standard = &adminv1beta1.AuthenticationServiceSpecStandard{}
	}
	if authservice.Spec.Standard.Storage == nil {
		authservice.Spec.Standard.Storage = &adminv1beta1.AuthenticationServiceSpecStandardStorage{
			Type: adminv1beta1.Ephemeral,
		}
	}
	if authservice.Spec.Standard.DeploymentName == nil {
		authservice.Spec.Standard.DeploymentName = &authservice.Name
	}
	if authservice.Spec.Standard.ServiceName == nil {
		authservice.Spec.Standard.ServiceName = &authservice.Name
	}
	if authservice.Spec.Standard.RouteName == nil {
		authservice.Spec.Standard.RouteName = &authservice.Name
	}
	if authservice.Spec.Standard.Storage.ClaimName == nil {
		authservice.Spec.Standard.Storage.ClaimName = &authservice.Name
	}
	if authservice.Spec.Standard.Storage.DeleteClaim == nil {
		authservice.Spec.Standard.Storage.DeleteClaim = new(bool)
		*authservice.Spec.Standard.Storage.DeleteClaim = true
	}
	if authservice.Spec.Standard.Resources == nil {
		authservice.Spec.Standard.Resources = &corev1.ResourceRequirements{
			Requests: map[corev1.ResourceName]resource.Quantity{"memory": *resource.NewScaledQuantity(2, resource.Giga)},
			Limits:   map[corev1.ResourceName]resource.Quantity{"memory": *resource.NewScaledQuantity(2, resource.Giga)},
		}
	}
	if authservice.Spec.Standard.Datasource == nil {
		authservice.Spec.Standard.Datasource = &adminv1beta1.AuthenticationServiceSpecStandardDatasource{
			Type: adminv1beta1.H2Datasource,
		}
	}
	if authservice.Spec.Standard.CredentialsSecret == nil {
		secretName := "keycloak-credentials"
		authservice.Spec.Standard.CredentialsSecret = &corev1.SecretReference{
			Name: secretName,
		}
		err := CreateAuthserviceSecret(ctx, client, scheme, secretName, authservice, func(secret *corev1.Secret) error {
			secret.StringData = make(map[string]string)
			secret.StringData["admin.username"] = "admin"
			adminPassword, err := util.GeneratePassword(32)
			if err != nil {
				return err
			}
			secret.StringData["admin.password"] = adminPassword
			return nil
		})
		if err != nil {
			return err
		}
	}
	if authservice.Spec.Standard.CertificateSecret == nil {
		secretName := "standard-authservice-cert"
		authservice.Spec.Standard.CertificateSecret = &corev1.SecretReference{
			Name: secretName,
		}

		if !util.IsOpenshift() {
			err := CreateAuthserviceSecret(ctx, client, scheme, secretName, authservice, func(secret *corev1.Secret) error {
				cn := util.ServiceToCommonName(authservice.Namespace, *authservice.Spec.Standard.ServiceName)
				return util.GenerateSelfSignedCertSecret(cn, nil, nil, secret)
			})
			if err != nil {
				return err
			}
		}
	}
	return nil
}

func applyStandardAuthServiceDeployment(authservice *adminv1beta1.AuthenticationService, deployment *appsv1.Deployment) error {

	install.ApplyDeploymentDefaults(deployment, "standard-authservice", *authservice.Spec.Standard.DeploymentName)

	install.ApplyInitContainer(deployment, "keycloak-plugin", func(container *corev1.Container) {
		install.ApplyContainerImage(container, "keycloak-plugin", authservice.Spec.Standard.InitImage)
		install.ApplyEnvSimple(container, "KEYCLOAK_DIR", "/opt/jboss/keycloak")
		install.ApplyVolumeMountSimple(container, "keycloak-providers", "/opt/jboss/keycloak/providers", false)
		install.ApplyVolumeMountSimple(container, "keycloak-configuration", "/opt/jboss/keycloak/standalone/configuration", false)
		install.ApplyVolumeMountSimple(container, "standard-authservice-cert", "/opt/enmasse/cert", false)
		install.ApplyEnvSimple(container, "KEYCLOAK_CONFIG_FILE", "standalone-"+string(authservice.Spec.Standard.Datasource.Type)+".xml")
	})
	install.ApplyContainer(deployment, "keycloak", func(container *corev1.Container) {
		install.ApplyContainerImage(container, "keycloak", authservice.Spec.Standard.Image)
		install.ApplyEnvSimple(container, "KEYCLOAK_SASL_XOAUTH_BASE_URI", authservice.Annotations["enmasse.io/oauth-url"])
		jvmOptions := "-Dvertx.cacheDirBase=/tmp -Djboss.bind.address=0.0.0.0 -Djava.net.preferIPv4Stack=true -Duser.timezone=UTC"
		if authservice.Spec.Standard.JvmOptions != nil {
			jvmOptions += " " + *authservice.Spec.Standard.JvmOptions
		} else if qty, ok := authservice.Spec.Standard.Resources.Requests["memory"]; ok {
			containerMemoryRequest := qty.ScaledValue(resource.Mega)
			jvmOptions += fmt.Sprintf(" -Xms%dm -Xmx%dm", containerMemoryRequest/2, containerMemoryRequest/2)
		}
		install.ApplyEnvSimple(container, "JAVA_OPTS", jvmOptions)
		install.ApplyEnvSecret(container, "KEYCLOAK_USER", "admin.username", authservice.Spec.Standard.CredentialsSecret.Name)
		install.ApplyEnvSecret(container, "KEYCLOAK_PASSWORD", "admin.password", authservice.Spec.Standard.CredentialsSecret.Name)

		if authservice.Spec.Standard.Datasource.Type == adminv1beta1.PostgresqlDatasource {
			install.ApplyEnvSimple(container, "DB_HOST", authservice.Spec.Standard.Datasource.Host)
			install.ApplyEnvSimple(container, "DB_PORT", fmt.Sprintf("%d", authservice.Spec.Standard.Datasource.Port))
			install.ApplyEnvSimple(container, "DB_DATABASE", authservice.Spec.Standard.Datasource.Database)
			install.ApplyEnvSecret(container, "DB_USERNAME", "database-user", authservice.Spec.Standard.Datasource.CredentialsSecret.Name)
			install.ApplyEnvSecret(container, "DB_PASSWORD", "database-password", authservice.Spec.Standard.Datasource.CredentialsSecret.Name)
		}

		container.Args = []string{"start-keycloak.sh", "-b", "0.0.0.0", "-c", "standalone-openshift.xml"}

		container.Ports = []corev1.ContainerPort{{
			ContainerPort: 5671,
			Name:          "amqps",
		}, {
			ContainerPort: 8443,
			Name:          "https",
		}}
		container.ReadinessProbe = &corev1.Probe{
			InitialDelaySeconds: 60,
			Handler: corev1.Handler{
				HTTPGet: &corev1.HTTPGetAction{
					Port:   intstr.FromString("https"),
					Path:   "/auth",
					Scheme: "HTTPS",
				},
			},
		}
		container.LivenessProbe = &corev1.Probe{
			InitialDelaySeconds: 120,
			Handler: corev1.Handler{
				HTTPGet: &corev1.HTTPGetAction{
					Port:   intstr.FromString("https"),
					Path:   "/auth",
					Scheme: "HTTPS",
				},
			},
		}
		if authservice.Spec.Standard.Resources != nil {
			container.Resources = *authservice.Spec.Standard.Resources
		}
		install.ApplyVolumeMountSimple(container, "keycloak-providers", "/opt/jboss/keycloak/providers", false)
		install.ApplyVolumeMountSimple(container, "keycloak-configuration", "/opt/jboss/keycloak/standalone/configuration", false)
		install.ApplyVolumeMountSimple(container, "keycloak-persistence", "/opt/jboss/keycloak/standalone/data", false)
		install.ApplyVolumeMountSimple(container, "standard-authservice-cert", "/opt/enmasse/cert", true)
	})

	install.ApplySecretVolume(deployment, "standard-authservice-cert", authservice.Spec.Standard.CertificateSecret.Name)
	install.ApplyEmptyDirVolume(deployment, "keycloak-providers")
	install.ApplyEmptyDirVolume(deployment, "keycloak-configuration")
	install.ApplyEmptyDirVolume(deployment, "keycloak-configuration")

	// Only allow setting volume on initial creation
	if util.IsNewObject(deployment) {
		if authservice.Spec.Standard.Storage.Type == adminv1beta1.PersistentClaim {
			install.ApplyPersistentVolume(deployment, "keycloak-persistence", *authservice.Spec.Standard.Storage.ClaimName)
		} else {
			install.ApplyEmptyDirVolume(deployment, "keycloak-persistence")
		}
	}

	if authservice.Spec.Standard.ServiceAccountName != nil {
		deployment.Spec.Template.Spec.ServiceAccountName = *authservice.Spec.Standard.ServiceAccountName
	} else {
		deployment.Spec.Template.Spec.ServiceAccountName = "standard-authservice"
	}
	deployment.Spec.Strategy = appsv1.DeploymentStrategy{
		Type: appsv1.RecreateDeploymentStrategyType,
	}
	return nil
}

func applyStandardAuthServiceService(authservice *adminv1beta1.AuthenticationService, service *corev1.Service) error {

	install.ApplyServiceDefaults(service, "standard-authservice", *authservice.Spec.Standard.ServiceName)
	service.Spec.Selector = install.CreateDefaultLabels(nil, "standard-authservice", *authservice.Spec.Standard.DeploymentName)

	if service.Annotations == nil {
		service.Annotations = make(map[string]string)
	}
	service.Annotations["service.alpha.openshift.io/serving-cert-secret-name"] = "standard-authservice-cert"
	service.Spec.Ports = []corev1.ServicePort{
		{
			Port:       8443,
			Protocol:   corev1.ProtocolTCP,
			TargetPort: intstr.FromString("https"),
			Name:       "https",
		},
		{
			Port:       5671,
			Protocol:   corev1.ProtocolTCP,
			TargetPort: intstr.FromString("amqps"),
			Name:       "amqps",
		},
	}
	return nil
}

func applyStandardAuthServiceVolume(authservice *adminv1beta1.AuthenticationService, pvc *corev1.PersistentVolumeClaim) error {

	install.ApplyDefaultLabels(&pvc.ObjectMeta, "standard-authservice", *authservice.Spec.Standard.Storage.ClaimName)

	// Only allow setting volume on initial creation
	if util.IsNewObject(pvc) {
		selector := authservice.Spec.Standard.Storage.Selector
		storageClassName := authservice.Spec.Standard.Storage.Class
		resources := corev1.ResourceRequirements{
			Requests: map[corev1.ResourceName]resource.Quantity{"storage": authservice.Spec.Standard.Storage.Size},
		}

		pvc.Spec.AccessModes = []corev1.PersistentVolumeAccessMode{
			corev1.ReadWriteOnce,
		}
		pvc.Spec.Selector = selector
		pvc.Spec.StorageClassName = storageClassName
		pvc.Spec.Resources = resources
	}
	return nil
}

func applyRoute(authservice *adminv1beta1.AuthenticationService, route *routev1.Route, caCertificate string) error {

	install.ApplyDefaultLabels(&route.ObjectMeta, "standard-authservice", *authservice.Spec.Standard.RouteName)

	route.Spec = routev1.RouteSpec{
		To: routev1.RouteTargetReference{
			Kind: "Service",
			Name: authservice.Name,
		},
		TLS: &routev1.TLSConfig{
			Termination:   routev1.TLSTerminationReencrypt,
			CACertificate: caCertificate,
		},
		Port: &routev1.RoutePort{
			TargetPort: intstr.FromString("https"),
		},
	}
	return nil
}

func applyOauthClient(authservice *adminv1beta1.AuthenticationService, oauth *oauthv1.OAuthClient, redirectUri string) error {
	install.ApplyDefaultLabels(&oauth.ObjectMeta, "oauthclient", oauth.Name)
	if oauth.Secret == "" {
		password, err := util.GeneratePassword(32)
		if err != nil {
			return err
		}
		oauth.Secret = password
	}
	oauth.GrantMethod = oauthv1.GrantHandlerAuto
	oauth.RedirectURIs = []string{
		redirectUri,
	}
	return nil
}
<|endoftext|>