<commit_before>// Copyright 2022 The gVisor 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 remote

import (
	"bytes"
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"testing"
	"time"

	"github.com/cenkalti/backoff"
	"google.golang.org/protobuf/proto"
	"google.golang.org/protobuf/types/known/anypb"
	"gvisor.dev/gvisor/pkg/fd"
	"gvisor.dev/gvisor/pkg/sentry/seccheck"
	"gvisor.dev/gvisor/pkg/sentry/seccheck/checkers/remote/test"
	pb "gvisor.dev/gvisor/pkg/sentry/seccheck/points/points_go_proto"
	"gvisor.dev/gvisor/pkg/test/testutil"
)

func waitForFile(path string) error {
	return testutil.Poll(func() error {
		if _, err := os.Stat(path); err != nil {
			if os.IsNotExist(err) {
				return err
			}
			return &backoff.PermanentError{Err: err}
		}
		return nil
	}, 5*time.Second)
}

type exampleServer struct {
	path string
	cmd  *exec.Cmd
	out  bytes.Buffer
}

func newExampleServer(quiet bool) (*exampleServer, error) {
	exe, err := testutil.FindFile("examples/seccheck/server_cc")
	if err != nil {
		return nil, fmt.Errorf("error finding server_cc: %v", err)
	}

	dir, err := os.MkdirTemp(os.TempDir(), "remote")
	if err != nil {
		return nil, fmt.Errorf("Setup(%q): %v", dir, err)
	}

	server := &exampleServer{path: filepath.Join(dir, "remote.sock")}
	server.cmd = exec.Command(exe, server.path)
	if quiet {
		server.cmd.Args = append(server.cmd.Args, "-q")
	}
	server.cmd.Stdout = &server.out
	server.cmd.Stderr = &server.out
	if err := server.cmd.Start(); err != nil {
		os.RemoveAll(dir)
		return nil, fmt.Errorf("error running %q: %v", exe, err)
	}

	if err := waitForFile(server.path); err != nil {
		server.stop()
		return nil, fmt.Errorf("error waiting for server file %q: %w", server.path, err)
	}
	return server, nil
}

func (s *exampleServer) stop() {
	_ = s.cmd.Process.Kill()
	_ = s.cmd.Wait()
	_ = os.Remove(s.path)
}

func TestBasic(t *testing.T) {
	server, err := test.NewServer()
	if err != nil {
		t.Fatalf("newServer(): %v", err)
	}
	defer server.Close()

	endpoint, err := setup(server.Path)
	if err != nil {
		t.Fatalf("setup(): %v", err)
	}
	endpointFD, err := fd.NewFromFile(endpoint)
	if err != nil {
		_ = endpoint.Close()
		t.Fatalf("NewFromFile(): %v", err)
	}
	_ = endpoint.Close()

	r, err := New(nil, endpointFD)
	if err != nil {
		t.Fatalf("New(): %v", err)
	}

	info := &pb.ExitNotifyParentInfo{ExitStatus: 123}
	if err := r.ExitNotifyParent(nil, seccheck.FieldSet{}, info); err != nil {
		t.Fatalf("ExitNotifyParent: %v", err)
	}

	server.WaitForCount(1)
	pt := server.GetPoints()[0]
	if want := pb.MessageType_MESSAGE_SENTRY_EXIT_NOTIFY_PARENT; pt.MsgType != want {
		t.Errorf("wrong message type, want: %v, got: %v", want, pt.MsgType)
	}
	got := &pb.ExitNotifyParentInfo{}
	if err := proto.Unmarshal(pt.Msg, got); err != nil {
		t.Errorf("proto.Unmarshal(ExitNotifyParentInfo): %v", err)
	}
	if !proto.Equal(info, got) {
		t.Errorf("Received point is different, want: %+v, got: %+v", info, got)
	}
	// Check that no more points were received.
	if want, got := 1, server.Count(); want != got {
		t.Errorf("wrong number of points, want: %d, got: %d", want, got)
	}
}

// Test that the example C++ server works. It's easier to test from here and
// also changes that can break it will likely originate here.
func TestExample(t *testing.T) {
	server, err := newExampleServer(false)
	if err != nil {
		t.Fatalf("newExampleServer(): %v", err)
	}
	defer server.stop()

	endpoint, err := setup(server.path)
	if err != nil {
		t.Fatalf("setup(): %v", err)
	}
	endpointFD, err := fd.NewFromFile(endpoint)
	if err != nil {
		_ = endpoint.Close()
		t.Fatalf("NewFromFile(): %v", err)
	}
	_ = endpoint.Close()

	r, err := New(nil, endpointFD)
	if err != nil {
		t.Fatalf("New(): %v", err)
	}

	info := pb.ExitNotifyParentInfo{ExitStatus: 123}
	if err := r.ExitNotifyParent(nil, seccheck.FieldSet{}, &info); err != nil {
		t.Fatalf("ExitNotifyParent: %v", err)
	}
	check := func() error {
		gotRaw := server.out.String()
		// Collapse whitespace.
		got := strings.Join(strings.Fields(gotRaw), " ")
		if !strings.Contains(got, "ExitNotifyParentInfo => exit_status: 123") {
			return fmt.Errorf("ExitNotifyParentInfo point didn't get to the server, out: %q, raw: %q", got, gotRaw)
		}
		return nil
	}
	if err := testutil.Poll(check, time.Second); err != nil {
		t.Errorf(err.Error())
	}
}

func BenchmarkSmall(t *testing.B) {
	// Run server in a separate process just to isolate it as much as possible.
	server, err := newExampleServer(false)
	if err != nil {
		t.Fatalf("newExampleServer(): %v", err)
	}
	defer server.stop()

	endpoint, err := setup(server.path)
	if err != nil {
		t.Fatalf("setup(): %v", err)
	}
	endpointFD, err := fd.NewFromFile(endpoint)
	if err != nil {
		_ = endpoint.Close()
		t.Fatalf("NewFromFile(): %v", err)
	}
	_ = endpoint.Close()

	r, err := New(nil, endpointFD)
	if err != nil {
		t.Fatalf("New(): %v", err)
	}

	t.ResetTimer()
	t.RunParallel(func(sub *testing.PB) {
		for sub.Next() {
			info := pb.ExitNotifyParentInfo{ExitStatus: 123}
			if err := r.ExitNotifyParent(nil, seccheck.FieldSet{}, &info); err != nil {
				t.Fatalf("ExitNotifyParent: %v", err)
			}
		}
	})
}

func BenchmarkProtoAny(t *testing.B) {
	info := &pb.ExitNotifyParentInfo{ExitStatus: 123}

	t.ResetTimer()
	t.RunParallel(func(sub *testing.PB) {
		for sub.Next() {
			any, err := anypb.New(info)
			if err != nil {
				t.Fatal(err)
			}
			if _, err := proto.Marshal(any); err != nil {
				t.Fatal(err)
			}
		}
	})
}

func BenchmarkProtoEnum(t *testing.B) {
	info := &pb.ExitNotifyParentInfo{ExitStatus: 123}

	t.ResetTimer()
	t.RunParallel(func(sub *testing.PB) {
		for sub.Next() {
			if _, err := proto.Marshal(info); err != nil {
				t.Fatal(err)
			}
		}
	})
}
<commit_msg>Fix race in remote_test.go<commit_after>// Copyright 2022 The gVisor 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 remote

import (
	"bytes"
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"sync"
	"testing"
	"time"

	"github.com/cenkalti/backoff"
	"google.golang.org/protobuf/proto"
	"google.golang.org/protobuf/types/known/anypb"
	"gvisor.dev/gvisor/pkg/fd"
	"gvisor.dev/gvisor/pkg/sentry/seccheck"
	"gvisor.dev/gvisor/pkg/sentry/seccheck/checkers/remote/test"
	pb "gvisor.dev/gvisor/pkg/sentry/seccheck/points/points_go_proto"
	"gvisor.dev/gvisor/pkg/test/testutil"
)

func waitForFile(path string) error {
	return testutil.Poll(func() error {
		if _, err := os.Stat(path); err != nil {
			if os.IsNotExist(err) {
				return err
			}
			return &backoff.PermanentError{Err: err}
		}
		return nil
	}, 5*time.Second)
}

type syncBuffer struct {
	mu sync.Mutex
	// +checklocks:mu
	buf bytes.Buffer
}

func (s *syncBuffer) Write(p []byte) (n int, err error) {
	s.mu.Lock()
	defer s.mu.Unlock()
	return s.buf.Write(p)
}

func (s *syncBuffer) String() string {
	s.mu.Lock()
	defer s.mu.Unlock()
	return s.buf.String()
}

type exampleServer struct {
	path string
	cmd  *exec.Cmd
	out  syncBuffer
}

func newExampleServer(quiet bool) (*exampleServer, error) {
	exe, err := testutil.FindFile("examples/seccheck/server_cc")
	if err != nil {
		return nil, fmt.Errorf("error finding server_cc: %v", err)
	}

	dir, err := os.MkdirTemp(os.TempDir(), "remote")
	if err != nil {
		return nil, fmt.Errorf("Setup(%q): %v", dir, err)
	}

	server := &exampleServer{path: filepath.Join(dir, "remote.sock")}
	server.cmd = exec.Command(exe, server.path)
	if quiet {
		server.cmd.Args = append(server.cmd.Args, "-q")
	}
	server.cmd.Stdout = &server.out
	server.cmd.Stderr = &server.out
	if err := server.cmd.Start(); err != nil {
		os.RemoveAll(dir)
		return nil, fmt.Errorf("error running %q: %v", exe, err)
	}

	if err := waitForFile(server.path); err != nil {
		server.stop()
		return nil, fmt.Errorf("error waiting for server file %q: %w", server.path, err)
	}
	return server, nil
}

func (s *exampleServer) stop() {
	_ = s.cmd.Process.Kill()
	_ = s.cmd.Wait()
	_ = os.Remove(s.path)
}

func TestBasic(t *testing.T) {
	server, err := test.NewServer()
	if err != nil {
		t.Fatalf("newServer(): %v", err)
	}
	defer server.Close()

	endpoint, err := setup(server.Path)
	if err != nil {
		t.Fatalf("setup(): %v", err)
	}
	endpointFD, err := fd.NewFromFile(endpoint)
	if err != nil {
		_ = endpoint.Close()
		t.Fatalf("NewFromFile(): %v", err)
	}
	_ = endpoint.Close()

	r, err := New(nil, endpointFD)
	if err != nil {
		t.Fatalf("New(): %v", err)
	}

	info := &pb.ExitNotifyParentInfo{ExitStatus: 123}
	if err := r.ExitNotifyParent(nil, seccheck.FieldSet{}, info); err != nil {
		t.Fatalf("ExitNotifyParent: %v", err)
	}

	server.WaitForCount(1)
	pt := server.GetPoints()[0]
	if want := pb.MessageType_MESSAGE_SENTRY_EXIT_NOTIFY_PARENT; pt.MsgType != want {
		t.Errorf("wrong message type, want: %v, got: %v", want, pt.MsgType)
	}
	got := &pb.ExitNotifyParentInfo{}
	if err := proto.Unmarshal(pt.Msg, got); err != nil {
		t.Errorf("proto.Unmarshal(ExitNotifyParentInfo): %v", err)
	}
	if !proto.Equal(info, got) {
		t.Errorf("Received point is different, want: %+v, got: %+v", info, got)
	}
	// Check that no more points were received.
	if want, got := 1, server.Count(); want != got {
		t.Errorf("wrong number of points, want: %d, got: %d", want, got)
	}
}

// Test that the example C++ server works. It's easier to test from here and
// also changes that can break it will likely originate here.
func TestExample(t *testing.T) {
	server, err := newExampleServer(false)
	if err != nil {
		t.Fatalf("newExampleServer(): %v", err)
	}
	defer server.stop()

	endpoint, err := setup(server.path)
	if err != nil {
		t.Fatalf("setup(): %v", err)
	}
	endpointFD, err := fd.NewFromFile(endpoint)
	if err != nil {
		_ = endpoint.Close()
		t.Fatalf("NewFromFile(): %v", err)
	}
	_ = endpoint.Close()

	r, err := New(nil, endpointFD)
	if err != nil {
		t.Fatalf("New(): %v", err)
	}

	info := pb.ExitNotifyParentInfo{ExitStatus: 123}
	if err := r.ExitNotifyParent(nil, seccheck.FieldSet{}, &info); err != nil {
		t.Fatalf("ExitNotifyParent: %v", err)
	}
	check := func() error {
		gotRaw := server.out.String()
		// Collapse whitespace.
		got := strings.Join(strings.Fields(gotRaw), " ")
		if !strings.Contains(got, "ExitNotifyParentInfo => exit_status: 123") {
			return fmt.Errorf("ExitNotifyParentInfo point didn't get to the server, out: %q, raw: %q", got, gotRaw)
		}
		return nil
	}
	if err := testutil.Poll(check, time.Second); err != nil {
		t.Errorf(err.Error())
	}
}

func BenchmarkSmall(t *testing.B) {
	// Run server in a separate process just to isolate it as much as possible.
	server, err := newExampleServer(false)
	if err != nil {
		t.Fatalf("newExampleServer(): %v", err)
	}
	defer server.stop()

	endpoint, err := setup(server.path)
	if err != nil {
		t.Fatalf("setup(): %v", err)
	}
	endpointFD, err := fd.NewFromFile(endpoint)
	if err != nil {
		_ = endpoint.Close()
		t.Fatalf("NewFromFile(): %v", err)
	}
	_ = endpoint.Close()

	r, err := New(nil, endpointFD)
	if err != nil {
		t.Fatalf("New(): %v", err)
	}

	t.ResetTimer()
	t.RunParallel(func(sub *testing.PB) {
		for sub.Next() {
			info := pb.ExitNotifyParentInfo{ExitStatus: 123}
			if err := r.ExitNotifyParent(nil, seccheck.FieldSet{}, &info); err != nil {
				t.Fatalf("ExitNotifyParent: %v", err)
			}
		}
	})
}

func BenchmarkProtoAny(t *testing.B) {
	info := &pb.ExitNotifyParentInfo{ExitStatus: 123}

	t.ResetTimer()
	t.RunParallel(func(sub *testing.PB) {
		for sub.Next() {
			any, err := anypb.New(info)
			if err != nil {
				t.Fatal(err)
			}
			if _, err := proto.Marshal(any); err != nil {
				t.Fatal(err)
			}
		}
	})
}

func BenchmarkProtoEnum(t *testing.B) {
	info := &pb.ExitNotifyParentInfo{ExitStatus: 123}

	t.ResetTimer()
	t.RunParallel(func(sub *testing.PB) {
		for sub.Next() {
			if _, err := proto.Marshal(info); err != nil {
				t.Fatal(err)
			}
		}
	})
}
<|endoftext|>


<commit_before>package metric

import (
	"strconv"
	"time"

	"github.com/bigdatadev/goryman"
	"github.com/concourse/atc/db"
	"github.com/pivotal-golang/lager"
)

type Event interface {
	Emit(lager.Logger)
}

var TrackedContainers = &Gauge{}

type SchedulingFullDuration struct {
	PipelineName string
	Duration     time.Duration
}

func (event SchedulingFullDuration) Emit(logger lager.Logger) {
	state := "ok"

	if event.Duration > time.Second {
		state = "warning"
	}

	if event.Duration > 5*time.Second {
		state = "critical"
	}

	emit(
		logger.Session("full-scheduling-duration", lager.Data{
			"pipeline": event.PipelineName,
			"duration": event.Duration.String(),
		}),

		goryman.Event{
			Service: "scheduling: full duration (ms)",
			Metric:  ms(event.Duration),
			State:   state,
			Attributes: map[string]string{
				"pipeline": event.PipelineName,
			},
		},
	)
}

type SchedulingLoadVersionsDuration struct {
	PipelineName string
	Duration     time.Duration
}

func (event SchedulingLoadVersionsDuration) Emit(logger lager.Logger) {
	state := "ok"

	if event.Duration > time.Second {
		state = "warning"
	}

	if event.Duration > 5*time.Second {
		state = "critical"
	}

	emit(
		logger.Session("loading-versions-duration", lager.Data{
			"pipeline": event.PipelineName,
			"duration": event.Duration.String(),
		}),
		goryman.Event{
			Service: "scheduling: loading versions duration (ms)",
			Metric:  ms(event.Duration),
			State:   state,
			Attributes: map[string]string{
				"pipeline": event.PipelineName,
			},
		},
	)
}

type SchedulingJobDuration struct {
	PipelineName string
	JobName      string
	Duration     time.Duration
}

func (event SchedulingJobDuration) Emit(logger lager.Logger) {
	state := "ok"

	if event.Duration > time.Second {
		state = "warning"
	}

	if event.Duration > 5*time.Second {
		state = "critical"
	}

	emit(
		logger.Session("job-scheduling-duration", lager.Data{
			"pipeline": event.PipelineName,
			"job":      event.JobName,
			"duration": event.Duration.String(),
		}),
		goryman.Event{
			Service: "scheduling: job duration (ms)",
			Metric:  ms(event.Duration),
			State:   state,
			Attributes: map[string]string{
				"pipeline": event.PipelineName,
				"job":      event.JobName,
			},
		},
	)
}

type WorkerContainers struct {
	WorkerAddr string
	Containers int
}

func (event WorkerContainers) Emit(logger lager.Logger) {
	emit(
		logger.Session("worker-containers", lager.Data{
			"worker":     event.WorkerAddr,
			"containers": event.Containers,
		}),
		goryman.Event{
			Service: "worker containers",
			Metric:  event.Containers,
			State:   "ok",
			Attributes: map[string]string{
				"worker": event.WorkerAddr,
			},
		},
	)
}

type BuildStarted struct {
	PipelineName string
	JobName      string
	BuildName    string
	BuildID      int
}

func (event BuildStarted) Emit(logger lager.Logger) {
	emit(
		logger.Session("build-started", lager.Data{
			"pipeline":   event.PipelineName,
			"job":        event.JobName,
			"build-name": event.BuildName,
			"build-id":   event.BuildID,
		}),
		goryman.Event{
			Service: "build started",
			Metric:  event.BuildID,
			State:   "ok",
			Attributes: map[string]string{
				"pipeline":   event.PipelineName,
				"job":        event.JobName,
				"build_name": event.BuildName,
				"build_id":   strconv.Itoa(event.BuildID),
			},
		},
	)
}

type BuildFinished struct {
	PipelineName  string
	JobName       string
	BuildName     string
	BuildID       int
	BuildStatus   db.Status
	BuildDuration time.Duration
}

func (event BuildFinished) Emit(logger lager.Logger) {
	emit(
		logger.Session("build-finished", lager.Data{
			"pipeline":     event.PipelineName,
			"job":          event.JobName,
			"build-name":   event.BuildName,
			"build-id":     event.BuildID,
			"build-status": event.BuildStatus,
		}),
		goryman.Event{
			Service: "build finished",
			Metric:  ms(event.BuildDuration),
			State:   "ok",
			Attributes: map[string]string{
				"pipeline":     event.PipelineName,
				"job":          event.JobName,
				"build_name":   event.BuildName,
				"build_id":     strconv.Itoa(event.BuildID),
				"build_status": string(event.BuildStatus),
			},
		},
	)
}

func ms(duration time.Duration) float64 {
	return float64(duration) / 1000000
}

type HTTPReponseTime struct {
	Route    string
	Path     string
	Duration time.Duration
}

func (event HTTPReponseTime) Emit(logger lager.Logger) {
	state := "ok"

	if event.Duration > 100*time.Millisecond {
		state = "warning"
	}

	if event.Duration > 1*time.Second {
		state = "critical"
	}

	emit(
		logger.Session("http-response-time", lager.Data{
			"route": event.Route,
			"path":  event.Path,
		}),
		goryman.Event{
			Service: "http response time",
			Metric:  ms(event.Duration),
			State:   state,
			Attributes: map[string]string{
				"route": event.Route,
				"path":  event.Path,
			},
		},
	)
}
<commit_msg>Log duration of HTTP requests<commit_after>package metric

import (
	"strconv"
	"time"

	"github.com/bigdatadev/goryman"
	"github.com/concourse/atc/db"
	"github.com/pivotal-golang/lager"
)

type Event interface {
	Emit(lager.Logger)
}

var TrackedContainers = &Gauge{}

type SchedulingFullDuration struct {
	PipelineName string
	Duration     time.Duration
}

func (event SchedulingFullDuration) Emit(logger lager.Logger) {
	state := "ok"

	if event.Duration > time.Second {
		state = "warning"
	}

	if event.Duration > 5*time.Second {
		state = "critical"
	}

	emit(
		logger.Session("full-scheduling-duration", lager.Data{
			"pipeline": event.PipelineName,
			"duration": event.Duration.String(),
		}),

		goryman.Event{
			Service: "scheduling: full duration (ms)",
			Metric:  ms(event.Duration),
			State:   state,
			Attributes: map[string]string{
				"pipeline": event.PipelineName,
			},
		},
	)
}

type SchedulingLoadVersionsDuration struct {
	PipelineName string
	Duration     time.Duration
}

func (event SchedulingLoadVersionsDuration) Emit(logger lager.Logger) {
	state := "ok"

	if event.Duration > time.Second {
		state = "warning"
	}

	if event.Duration > 5*time.Second {
		state = "critical"
	}

	emit(
		logger.Session("loading-versions-duration", lager.Data{
			"pipeline": event.PipelineName,
			"duration": event.Duration.String(),
		}),
		goryman.Event{
			Service: "scheduling: loading versions duration (ms)",
			Metric:  ms(event.Duration),
			State:   state,
			Attributes: map[string]string{
				"pipeline": event.PipelineName,
			},
		},
	)
}

type SchedulingJobDuration struct {
	PipelineName string
	JobName      string
	Duration     time.Duration
}

func (event SchedulingJobDuration) Emit(logger lager.Logger) {
	state := "ok"

	if event.Duration > time.Second {
		state = "warning"
	}

	if event.Duration > 5*time.Second {
		state = "critical"
	}

	emit(
		logger.Session("job-scheduling-duration", lager.Data{
			"pipeline": event.PipelineName,
			"job":      event.JobName,
			"duration": event.Duration.String(),
		}),
		goryman.Event{
			Service: "scheduling: job duration (ms)",
			Metric:  ms(event.Duration),
			State:   state,
			Attributes: map[string]string{
				"pipeline": event.PipelineName,
				"job":      event.JobName,
			},
		},
	)
}

type WorkerContainers struct {
	WorkerAddr string
	Containers int
}

func (event WorkerContainers) Emit(logger lager.Logger) {
	emit(
		logger.Session("worker-containers", lager.Data{
			"worker":     event.WorkerAddr,
			"containers": event.Containers,
		}),
		goryman.Event{
			Service: "worker containers",
			Metric:  event.Containers,
			State:   "ok",
			Attributes: map[string]string{
				"worker": event.WorkerAddr,
			},
		},
	)
}

type BuildStarted struct {
	PipelineName string
	JobName      string
	BuildName    string
	BuildID      int
}

func (event BuildStarted) Emit(logger lager.Logger) {
	emit(
		logger.Session("build-started", lager.Data{
			"pipeline":   event.PipelineName,
			"job":        event.JobName,
			"build-name": event.BuildName,
			"build-id":   event.BuildID,
		}),
		goryman.Event{
			Service: "build started",
			Metric:  event.BuildID,
			State:   "ok",
			Attributes: map[string]string{
				"pipeline":   event.PipelineName,
				"job":        event.JobName,
				"build_name": event.BuildName,
				"build_id":   strconv.Itoa(event.BuildID),
			},
		},
	)
}

type BuildFinished struct {
	PipelineName  string
	JobName       string
	BuildName     string
	BuildID       int
	BuildStatus   db.Status
	BuildDuration time.Duration
}

func (event BuildFinished) Emit(logger lager.Logger) {
	emit(
		logger.Session("build-finished", lager.Data{
			"pipeline":     event.PipelineName,
			"job":          event.JobName,
			"build-name":   event.BuildName,
			"build-id":     event.BuildID,
			"build-status": event.BuildStatus,
		}),
		goryman.Event{
			Service: "build finished",
			Metric:  ms(event.BuildDuration),
			State:   "ok",
			Attributes: map[string]string{
				"pipeline":     event.PipelineName,
				"job":          event.JobName,
				"build_name":   event.BuildName,
				"build_id":     strconv.Itoa(event.BuildID),
				"build_status": string(event.BuildStatus),
			},
		},
	)
}

func ms(duration time.Duration) float64 {
	return float64(duration) / 1000000
}

type HTTPReponseTime struct {
	Route    string
	Path     string
	Duration time.Duration
}

func (event HTTPReponseTime) Emit(logger lager.Logger) {
	state := "ok"

	if event.Duration > 100*time.Millisecond {
		state = "warning"
	}

	if event.Duration > 1*time.Second {
		state = "critical"
	}

	emit(
		logger.Session("http-response-time", lager.Data{
			"route":    event.Route,
			"path":     event.Path,
			"duration": event.Duration.String(),
		}),
		goryman.Event{
			Service: "http response time",
			Metric:  ms(event.Duration),
			State:   state,
			Attributes: map[string]string{
				"route": event.Route,
				"path":  event.Path,
			},
		},
	)
}
<|endoftext|>


<commit_before>package main

import "testing"

type list struct {
	root listElem
}

type listElem struct {
	next *listElem
	prev *listElem
}

type entity struct {
	listElem
	x int
}

func BenchmarkListOfEntity(b *testing.B) {
	l := &list{}
	l.root.next = &l.root
	l.root.prev = &l.root

	b.ResetTimer()

	for i := 0; i < b.N; i++ {
		initList(l)
	}
}

func initList(l *list) {
	x, y, z := &entity{x: 1}, &entity{x: 2}, &entity{x: 3}

	l.root.next = &x.listElem
	x.next = &y.listElem
	y.next = &z.listElem
}
<commit_msg>Add results to bench_embed_list_test.go<commit_after>package main

/*
Results
--
$ go test -v -bench BenchmarkListOfEntity -benchmem
testing: warning: no tests to run
BenchmarkListOfEntity-4         10000000               114 ns/op              96 B/op          3 allocs/op
PASS
*/

import "testing"

type list struct {
	root listElem
}

type listElem struct {
	next *listElem
	prev *listElem
}

type entity struct {
	listElem
	x int
}

func BenchmarkListOfEntity(b *testing.B) {
	l := &list{}
	l.root.next = &l.root
	l.root.prev = &l.root

	b.ResetTimer()

	for i := 0; i < b.N; i++ {
		initList(l)
	}
}

func initList(l *list) {
	x, y, z := &entity{x: 1}, &entity{x: 2}, &entity{x: 3}

	l.root.next = &x.listElem
	x.next = &y.listElem
	y.next = &z.listElem
}
<|endoftext|>


<commit_before>package middleware

import (
	"fmt"
	jwt "github.com/dgrijalva/jwt-go"
	"github.com/gin-gonic/gin"
	"net/http"

	"github.com/techjanitor/pram-get/config"
	e "github.com/techjanitor/pram-get/errors"
	u "github.com/techjanitor/pram-get/utils"
)

type Permission uint

const (
	All Permission = 1 << iota
	Registered
	Moderators
	Admins
)

// checks for session cookie and handles permissions
func Auth(perm Permission) gin.HandlerFunc {
	return func(c *gin.Context) {

		// set default anonymous user
		user := u.User{
			Id:              1,
			Group:           1,
			IsAuthenticated: false,
		}

		// parse jwt token if its there
		token, err := jwt.ParseFromRequest(c.Request, func(token *jwt.Token) (interface{}, error) {

			// check alg to make sure its hmac
			_, ok := token.Method.(*jwt.SigningMethodHMAC)
			if !ok {
				return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"])
			}

			// compare with secret from settings
			return []byte(config.Settings.Session.Secret), nil
		})
		if err != nil && err != jwt.ErrNoTokenInRequest {
			// if theres some jwt error then return unauth
			c.JSON(e.ErrorMessage(e.ErrUnauthorized))
			c.Error(err)
			c.Abort()
			return
		}

		// process token
		if token != nil {
			// if the token is valid set the data
			if err == nil && token.Valid {

				// get uid from jwt, cast to float
				uid, ok := token.Claims["user_id"].(float64)
				if !ok {
					c.JSON(e.ErrorMessage(e.ErrInternalError))
					c.Error(err)
					c.Abort()
					return
				}

				// set user id in user struct
				user.Id = uint(uid)

				// get the rest of the user info
				err = user.Info()
				if err != nil {
					c.JSON(http.StatusBadRequest, gin.H{"error_message": err.Error()})
					c.Error(err)
					c.Abort()
					return
				}

			} else {
				c.JSON(e.ErrorMessage(e.ErrInternalError))
				c.Error(err)
				c.Abort()
				return
			}

		}

		// check if user meets set permissions
		if user.Group < perm {
			c.JSON(e.ErrorMessage(e.ErrUnauthorized))
			c.Error(e.ErrUnauthorized)
			c.Abort()
			return
		}

		// set user data
		c.Set("userdata", user)

		c.Next()

	}

}
<commit_msg>simplify perm masks<commit_after>package middleware

import (
	"fmt"
	jwt "github.com/dgrijalva/jwt-go"
	"github.com/gin-gonic/gin"
	"net/http"

	"github.com/techjanitor/pram-get/config"
	e "github.com/techjanitor/pram-get/errors"
	u "github.com/techjanitor/pram-get/utils"
)

type Permission uint

const (
	All Permission = 1 << iota
	Registered
	Moderators
	Admins
)

// checks for session cookie and handles permissions
func Auth(perm Permission) gin.HandlerFunc {
	return func(c *gin.Context) {

		// set default anonymous user
		user := u.User{
			Id:              1,
			Group:           uint(All),
			IsAuthenticated: false,
		}

		// parse jwt token if its there
		token, err := jwt.ParseFromRequest(c.Request, func(token *jwt.Token) (interface{}, error) {

			// check alg to make sure its hmac
			_, ok := token.Method.(*jwt.SigningMethodHMAC)
			if !ok {
				return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"])
			}

			// compare with secret from settings
			return []byte(config.Settings.Session.Secret), nil
		})
		if err != nil && err != jwt.ErrNoTokenInRequest {
			// if theres some jwt error then return unauth
			c.JSON(e.ErrorMessage(e.ErrUnauthorized))
			c.Error(err)
			c.Abort()
			return
		}

		// process token
		if token != nil {
			// if the token is valid set the data
			if err == nil && token.Valid {

				// get uid from jwt, cast to float
				uid, ok := token.Claims["user_id"].(float64)
				if !ok {
					c.JSON(e.ErrorMessage(e.ErrInternalError))
					c.Error(err)
					c.Abort()
					return
				}

				// set user id in user struct
				user.Id = uint(uid)

				// get the rest of the user info
				err = user.Info()
				if err != nil {
					c.JSON(http.StatusBadRequest, gin.H{"error_message": err.Error()})
					c.Error(err)
					c.Abort()
					return
				}

			} else {
				c.JSON(e.ErrorMessage(e.ErrInternalError))
				c.Error(err)
				c.Abort()
				return
			}

		}

		// check if user meets set permissions
		if user.Group < uint(perm) {
			c.JSON(e.ErrorMessage(e.ErrUnauthorized))
			c.Error(e.ErrUnauthorized)
			c.Abort()
			return
		}

		// set user data
		c.Set("userdata", user)

		c.Next()

	}

}
<|endoftext|>


<commit_before>// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Contributor: Aaron Meihm ameihm@mozilla.com [:alm]
package main

import (
	"flag"
	"fmt"
	"io/ioutil"
	"mig.ninja/mig"
	"os"
	"os/signal"
	"path"
	"sync"
	"time"
)

var ctx Context
var wg sync.WaitGroup

func doExit(rc int) {
	close(ctx.Channels.Log)
	wg.Wait()
	os.Exit(rc)
}

// Process a directory and generate a new scheduling entity if needed
func procDir(dirpath string) (err error) {
	defer func() {
		if e := recover(); e != nil {
			err = fmt.Errorf("procDir() -> %v", e)
		}
	}()

	ename := path.Base(dirpath)
	confpath := path.Join(dirpath, "entity.cfg")
	finfo, err := os.Stat(confpath)
	if err != nil {
		// Don't treat this as fatal; we will just try to load it again
		// next time.
		mlog("%v: %v", ename, err)
		return nil
	}

	var ent *entity
	// See if we already have an entity by this name, and if the
	// modification time on the configuration file is the same. If so
	// we just return.
	ent, ok := ctx.Entities[ename]
	if ok && (ent.modTime == finfo.ModTime()) {
		return
	}
	if ent != nil {
		delete(ctx.Entities, ename)
		ent.stop()
	}
	ent = &entity{}
	// Add the entity and start it.
	ent.name = ename
	ent.modTime = finfo.ModTime()
	ent.baseDir = dirpath
	ent.confPath = confpath
	err = ent.load()
	if err != nil {
		// Don't treat this as fatal; we will just try to load it again
		// next time.
		mlog("%v: %v", ename, err)
		return nil
	}
	ent.deadChan = make(chan bool, 1)
	ctx.Entities[ename] = ent
	mlog("added entity %v", ename)
	go ent.start()

	return nil
}

// Remove entities that are no longer present in the runner directory, or are
// stale
func procReap(ents []string) error {
	for k := range ctx.Entities {
		found := false
		for _, x := range ents {
			if x == k {
				found = true
			}
		}
		if !found {
			ctx.Entities[k].stop()
			delete(ctx.Entities, k)
			mlog("removed entity %v", k)
		}
	}
	// Also remove stale entities that are still present in the runner
	// directory but have encountered an error.
	for k := range ctx.Entities {
		dead := false
		select {
		case <-ctx.Entities[k].deadChan:
			dead = true
		default:
		}
		if dead {
			delete(ctx.Entities, k)
			mlog("removed stale entity %v", k)
		}
	}
	return nil
}

// Scan the runner directory periodically, adding and removing jobs as the
// configuration changes in the runner spool.
func runnerScan() (err error) {
	defer func() {
		if e := recover(); e != nil {
			err = fmt.Errorf("runnerScan() -> %v", e)
		}
	}()

	// Begin scanning the runner directory
	for {
		mlog("scanning %v", ctx.Runner.RunDirectory)
		ents, err := ioutil.ReadDir(ctx.Runner.RunDirectory)
		if err != nil {
			panic(err)
		}
		haveents := make([]string, 0)
		for _, x := range ents {
			if !x.IsDir() {
				continue
			}
			haveents = append(haveents, x.Name())
			dirpath := path.Join(ctx.Runner.RunDirectory, x.Name())
			err = procDir(dirpath)
			if err != nil {
				panic(err)
			}
		}
		err = procReap(haveents)
		if err != nil {
			panic(err)
		}

		doexit := false
		select {
		case <-time.After(time.Duration(ctx.Runner.CheckDirectory) * time.Second):
		case <-ctx.Channels.ExitNotify:
			doexit = true
		}
		if doexit {
			break
		}
	}
	mlog("runner exiting due to notification")

	return nil
}

func mlog(s string, args ...interface{}) {
	ctx.Channels.Log <- mig.Log{Desc: fmt.Sprintf(s, args...)}
}

func main() {
	var err error

	var config = flag.String("c", "/etc/mig/runner.cfg", "Load configuration from file")
	flag.Parse()

	ctx, err = initContext(*config)
	if err != nil {
		fmt.Fprintf(os.Stderr, "error: %v\n", err)
		os.Exit(9)
	}

	wg.Add(1)
	go func() {
		var stop bool
		for event := range ctx.Channels.Log {
			stop, err = mig.ProcessLog(ctx.Logging, event)
			if err != nil {
				panic("unable to process log")
			}
			if stop {
				break
			}
		}
		wg.Done()
	}()
	mlog("logging routine started")

	sigch := make(chan os.Signal, 1)
	signal.Notify(sigch, os.Interrupt, os.Kill)
	go func() {
		<-sigch
		mlog("signal, exiting")
		ctx.Channels.ExitNotify <- true
	}()

	err = loadPlugins()
	if err != nil {
		fmt.Fprintf(os.Stderr, "error: %v\n", err)
		doExit(9)
	}
	// Start up the results processor
	go processResults()

	err = runnerScan()
	if err != nil {
		mlog("runner error: %v", err)
		doExit(9)
	}

	doExit(0)
}
<commit_msg>[minor] reorganize runner main.go to be more consistent with other src<commit_after>// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Contributor: Aaron Meihm ameihm@mozilla.com [:alm]
package main

import (
	"flag"
	"fmt"
	"io/ioutil"
	"mig.ninja/mig"
	"os"
	"os/signal"
	"path"
	"sync"
	"time"
)

var ctx Context
var wg sync.WaitGroup

func main() {
	var err error

	var config = flag.String("c", "/etc/mig/runner.cfg", "Load configuration from file")
	flag.Parse()

	ctx, err = initContext(*config)
	if err != nil {
		fmt.Fprintf(os.Stderr, "error: %v\n", err)
		os.Exit(9)
	}

	wg.Add(1)
	go func() {
		var stop bool
		for event := range ctx.Channels.Log {
			stop, err = mig.ProcessLog(ctx.Logging, event)
			if err != nil {
				panic("unable to process log")
			}
			if stop {
				break
			}
		}
		wg.Done()
	}()
	mlog("logging routine started")

	sigch := make(chan os.Signal, 1)
	signal.Notify(sigch, os.Interrupt, os.Kill)
	go func() {
		<-sigch
		mlog("signal, exiting")
		ctx.Channels.ExitNotify <- true
	}()

	err = loadPlugins()
	if err != nil {
		fmt.Fprintf(os.Stderr, "error: %v\n", err)
		doExit(9)
	}
	// Start up the results processor
	go processResults()

	err = runnerScan()
	if err != nil {
		mlog("runner error: %v", err)
		doExit(9)
	}

	doExit(0)
}

// Scan the runner directory periodically, adding and removing jobs as the
// configuration changes in the runner spool.
func runnerScan() (err error) {
	defer func() {
		if e := recover(); e != nil {
			err = fmt.Errorf("runnerScan() -> %v", e)
		}
	}()

	// Begin scanning the runner directory
	for {
		mlog("scanning %v", ctx.Runner.RunDirectory)
		ents, err := ioutil.ReadDir(ctx.Runner.RunDirectory)
		if err != nil {
			panic(err)
		}
		haveents := make([]string, 0)
		for _, x := range ents {
			if !x.IsDir() {
				continue
			}
			haveents = append(haveents, x.Name())
			dirpath := path.Join(ctx.Runner.RunDirectory, x.Name())
			err = procDir(dirpath)
			if err != nil {
				panic(err)
			}
		}
		err = procReap(haveents)
		if err != nil {
			panic(err)
		}

		doexit := false
		select {
		case <-time.After(time.Duration(ctx.Runner.CheckDirectory) * time.Second):
		case <-ctx.Channels.ExitNotify:
			doexit = true
		}
		if doexit {
			break
		}
	}
	mlog("runner exiting due to notification")

	return nil
}

func doExit(rc int) {
	close(ctx.Channels.Log)
	wg.Wait()
	os.Exit(rc)
}

// Process a directory and generate a new scheduling entity if needed
func procDir(dirpath string) (err error) {
	defer func() {
		if e := recover(); e != nil {
			err = fmt.Errorf("procDir() -> %v", e)
		}
	}()

	ename := path.Base(dirpath)
	confpath := path.Join(dirpath, "entity.cfg")
	finfo, err := os.Stat(confpath)
	if err != nil {
		// Don't treat this as fatal; we will just try to load it again
		// next time.
		mlog("%v: %v", ename, err)
		return nil
	}

	var ent *entity
	// See if we already have an entity by this name, and if the
	// modification time on the configuration file is the same. If so
	// we just return.
	ent, ok := ctx.Entities[ename]
	if ok && (ent.modTime == finfo.ModTime()) {
		return
	}
	if ent != nil {
		delete(ctx.Entities, ename)
		ent.stop()
	}
	ent = &entity{}
	// Add the entity and start it.
	ent.name = ename
	ent.modTime = finfo.ModTime()
	ent.baseDir = dirpath
	ent.confPath = confpath
	err = ent.load()
	if err != nil {
		// Don't treat this as fatal; we will just try to load it again
		// next time.
		mlog("%v: %v", ename, err)
		return nil
	}
	ent.deadChan = make(chan bool, 1)
	ctx.Entities[ename] = ent
	mlog("added entity %v", ename)
	go ent.start()

	return nil
}

// Remove entities that are no longer present in the runner directory, or are
// stale
func procReap(ents []string) error {
	for k := range ctx.Entities {
		found := false
		for _, x := range ents {
			if x == k {
				found = true
			}
		}
		if !found {
			ctx.Entities[k].stop()
			delete(ctx.Entities, k)
			mlog("removed entity %v", k)
		}
	}
	// Also remove stale entities that are still present in the runner
	// directory but have encountered an error.
	for k := range ctx.Entities {
		dead := false
		select {
		case <-ctx.Entities[k].deadChan:
			dead = true
		default:
		}
		if dead {
			delete(ctx.Entities, k)
			mlog("removed stale entity %v", k)
		}
	}
	return nil
}

func mlog(s string, args ...interface{}) {
	ctx.Channels.Log <- mig.Log{Desc: fmt.Sprintf(s, args...)}
}
<|endoftext|>


<commit_before>package migrate

import "github.com/flynn/go-sql"

type Migration struct {
	ID    int
	Stmts []string
}

func NewMigrations() *Migrations {
	l := make(Migrations, 0)
	return &l
}

type Migrations []Migration

func (m *Migrations) Add(id int, stmts ...string) {
	*m = append(*m, Migration{ID: id, Stmts: stmts})
}

func (m Migrations) Migrate(db *sql.DB) error {
	var initialized bool
	for _, migration := range m {
		if !initialized {
			if _, err := db.Exec("CREATE TABLE IF NOT EXISTS schema_migrations (id bigint PRIMARY KEY)"); err != nil {
				return err
			}
			initialized = true
		}

		tx, err := db.Begin()
		if err != nil {
			return err
		}

		if _, err := tx.Exec("LOCK TABLE schema_migrations IN ACCESS EXCLUSIVE MODE"); err != nil {
			tx.Rollback()
			return err
		}
		var tmp bool
		if err := tx.QueryRow("SELECT true FROM schema_migrations WHERE id = $1", migration.ID).Scan(&tmp); err != sql.ErrNoRows {
			tx.Rollback()
			if err == nil {
				continue
			}
			return err
		}

		for _, s := range migration.Stmts {
			_, err = tx.Exec(s)
			if err != nil {
				tx.Rollback()
				return err
			}
		}

		if _, err := tx.Exec("INSERT INTO schema_migrations (id) VALUES ($1)", migration.ID); err != nil {
			tx.Rollback()
			return err
		}

		if err := tx.Commit(); err != nil {
			return err
		}
	}
	return nil
}
<commit_msg>pkg/migrate: Ignore harmless errors when racing for table creation<commit_after>package migrate

import "github.com/flynn/go-sql"

type Migration struct {
	ID    int
	Stmts []string
}

func NewMigrations() *Migrations {
	l := make(Migrations, 0)
	return &l
}

type Migrations []Migration

func (m *Migrations) Add(id int, stmts ...string) {
	*m = append(*m, Migration{ID: id, Stmts: stmts})
}

func (m Migrations) Migrate(db *sql.DB) error {
	var initialized bool
	for _, migration := range m {
		if !initialized {
			db.Exec("CREATE TABLE IF NOT EXISTS schema_migrations (id bigint PRIMARY KEY)")
			initialized = true
		}

		tx, err := db.Begin()
		if err != nil {
			return err
		}

		if _, err := tx.Exec("LOCK TABLE schema_migrations IN ACCESS EXCLUSIVE MODE"); err != nil {
			tx.Rollback()
			return err
		}
		var tmp bool
		if err := tx.QueryRow("SELECT true FROM schema_migrations WHERE id = $1", migration.ID).Scan(&tmp); err != sql.ErrNoRows {
			tx.Rollback()
			if err == nil {
				continue
			}
			return err
		}

		for _, s := range migration.Stmts {
			_, err = tx.Exec(s)
			if err != nil {
				tx.Rollback()
				return err
			}
		}

		if _, err := tx.Exec("INSERT INTO schema_migrations (id) VALUES ($1)", migration.ID); err != nil {
			tx.Rollback()
			return err
		}

		if err := tx.Commit(); err != nil {
			return err
		}
	}
	return nil
}
<|endoftext|>


<commit_before>package serviceassets

import (
	"github.com/racker/perigee"
	"github.com/rackspace/gophercloud"
)

// DeleteOptsBuilder allows extensions to add additional parameters to the Delete
// request.
type DeleteOptsBuilder interface {
	ToCDNAssetDeleteParams() (string, error)
}

// DeleteOpts is a structure that holds options for deleting CDN service assets.
type DeleteOpts struct {
	// If all is set to true, specifies that the delete occurs against all of the
	// assets for the service.
	All bool `q:"all"`
	// Specifies the relative URL of the asset to be deleted.
	URL string `q:"url"`
}

// ToCDNAssetDeleteParams formats a DeleteOpts into a query string.
func (opts DeleteOpts) ToCDNAssetDeleteParams() (string, error) {
	q, err := gophercloud.BuildQueryString(opts)
	if err != nil {
		return "", err
	}
	return q.String(), nil
}

// Delete accepts a unique ID and deletes the CDN service asset associated with
// it.
func Delete(c *gophercloud.ServiceClient, id string, opts DeleteOptsBuilder) DeleteResult {
	var res DeleteResult
	_, res.Err = perigee.Request("DELETE", deleteURL(c, id), perigee.Options{
		MoreHeaders: c.AuthenticatedHeaders(),
		OkCodes:     []int{202},
	})
	return res
}
<commit_msg>allow users to pass ID or URL to purge service assets<commit_after>package serviceassets

import (
	"strings"

	"github.com/racker/perigee"
	"github.com/rackspace/gophercloud"
)

// DeleteOptsBuilder allows extensions to add additional parameters to the Delete
// request.
type DeleteOptsBuilder interface {
	ToCDNAssetDeleteParams() (string, error)
}

// DeleteOpts is a structure that holds options for deleting CDN service assets.
type DeleteOpts struct {
	// If all is set to true, specifies that the delete occurs against all of the
	// assets for the service.
	All bool `q:"all"`
	// Specifies the relative URL of the asset to be deleted.
	URL string `q:"url"`
}

// ToCDNAssetDeleteParams formats a DeleteOpts into a query string.
func (opts DeleteOpts) ToCDNAssetDeleteParams() (string, error) {
	q, err := gophercloud.BuildQueryString(opts)
	if err != nil {
		return "", err
	}
	return q.String(), nil
}

// Delete accepts a unique service ID or URL and deletes the CDN service asset associated with
// it. For example, both "96737ae3-cfc1-4c72-be88-5d0e7cc9a3f0" and
// "https://global.cdn.api.rackspacecloud.com/v1.0/services/96737ae3-cfc1-4c72-be88-5d0e7cc9a3f0"
// are valid options for idOrURL.
func Delete(c *gophercloud.ServiceClient, idOrURL string, opts DeleteOptsBuilder) DeleteResult {
	var url string
	if strings.Contains(idOrURL, "/") {
		url = idOrURL
	} else {
		url = deleteURL(c, idOrURL)
	}

	var res DeleteResult
	_, res.Err = perigee.Request("DELETE", url, perigee.Options{
		MoreHeaders: c.AuthenticatedHeaders(),
		OkCodes:     []int{202},
	})
	return res
}
<|endoftext|>


<commit_before>package cluster

import (
	"context"
	"fmt"
	"math/rand"
	"net/http"
	"os"
	"time"

	lxd "github.com/lxc/lxd/client"
	"github.com/lxc/lxd/lxd/db"
	"github.com/lxc/lxd/lxd/state"
	"github.com/lxc/lxd/shared"
	"github.com/lxc/lxd/shared/log15"
	"github.com/lxc/lxd/shared/logger"
	"github.com/pkg/errors"
)

// NotifyUpgradeCompleted sends a notification to all other nodes in the
// cluster that any possible pending database update has been applied, and any
// nodes which was waiting for this node to be upgraded should re-check if it's
// okay to move forward.
func NotifyUpgradeCompleted(state *state.State, cert *shared.CertInfo) error {
	notifier, err := NewNotifier(state, cert, NotifyTryAll)
	if err != nil {
		return err
	}

	return notifier(func(client lxd.InstanceServer) error {
		info, err := client.GetConnectionInfo()
		if err != nil {
			return errors.Wrap(err, "failed to get connection info")
		}

		url := fmt.Sprintf("%s%s", info.Addresses[0], databaseEndpoint)
		request, err := http.NewRequest("PATCH", url, nil)
		if err != nil {
			return errors.Wrap(err, "failed to create database notify upgrade request")
		}
		setDqliteVersionHeader(request)

		httpClient, err := client.GetHTTPClient()
		if err != nil {
			return errors.Wrap(err, "failed to get HTTP client")
		}

		httpClient.Timeout = 5 * time.Second
		response, err := httpClient.Do(request)
		if err != nil {
			return errors.Wrap(err, "failed to notify node about completed upgrade")
		}

		if response.StatusCode != http.StatusOK {
			return fmt.Errorf("database upgrade notification failed: %s", response.Status)
		}

		return nil
	})
}

// MaybeUpdate Check this node's version and possibly run LXD_CLUSTER_UPDATE.
func MaybeUpdate(state *state.State) error {
	shouldUpdate := false

	enabled, err := Enabled(state.Node)
	if err != nil {
		return errors.Wrap(err, "Failed to check clustering is enabled")
	}
	if !enabled {
		return nil
	}

	if state.Cluster == nil {
		return fmt.Errorf("Failed checking cluster update, state not initialised yet")
	}

	err = state.Cluster.Transaction(func(tx *db.ClusterTx) error {
		outdated, err := tx.NodeIsOutdated()
		if err != nil {
			return err
		}
		shouldUpdate = outdated
		return nil
	})

	if err != nil {
		// Just log the error and return.
		return errors.Wrap(err, "Failed to check if this node is out-of-date")
	}

	if !shouldUpdate {
		logger.Debugf("Cluster node is up-to-date")
		return nil
	}

	return triggerUpdate()
}

func triggerUpdate() error {
	logger.Infof("Node is out-of-date with respect to other cluster nodes")

	updateExecutable := os.Getenv("LXD_CLUSTER_UPDATE")
	if updateExecutable == "" {
		logger.Debug("No LXD_CLUSTER_UPDATE variable set, skipping auto-update")
		return nil
	}

	// Wait a random amout of seconds (up to 30) in order to avoid
	// restarting all cluster members at the same time, and make the
	// upgrade more graceful.
	wait := time.Duration(rand.Intn(30)) * time.Second
	logger.Infof("Triggering cluster update in %s using: %s", wait, updateExecutable)
	time.Sleep(wait)

	_, err := shared.RunCommand(updateExecutable)
	if err != nil {
		logger.Errorf("Cluster upgrade failed: '%v'", err.Error())
		return err
	}
	return nil
}

// This function assigns the Spare raft role to all cluster members that are
// not currently part of the raft configuration. It's used for upgrading a
// cluster from a version without roles support.
func upgradeMembersWithoutRole(gateway *Gateway, members []db.NodeInfo, nodes []db.RaftNode) error {
	// Used raft IDs.
	ids := map[uint64]bool{}
	for _, node := range nodes {
		ids[node.ID] = true
	}

	client, err := gateway.getClient()
	if err != nil {
		return errors.Wrap(err, "Failed to connect to local dqlite node")
	}
	defer client.Close()

	// Check that each member is present in the raft configuration, and add
	// if not.
	for _, member := range members {
		found := false
		for _, node := range nodes {
			if member.ID == 1 || member.Address == node.Address {
				found = true
				break
			}
		}
		if found {
			continue
		}

		// Try to use the same ID as the node, but it might not be
		// possible if it's use.
		id := uint64(member.ID)
		if _, ok := ids[id]; ok {
			for _, other := range members {
				if _, ok := ids[uint64(other.ID)]; !ok {
					id = uint64(other.ID)
					break
				}
			}
			// Sanity check: can't really happen since there are
			// always at least as many members as there are nodes,
			// and all of them have different IDs.
			if id == uint64(member.ID) {
				panic("no available ID")
			}
		}
		ids[id] = true

		info := db.RaftNode{
			ID:      id,
			Address: member.Address,
			Role:    db.RaftSpare,
		}

		logger.Info("Add spare dqlite node", log15.Ctx{"id": info.ID, "address": info.Address})

		ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
		defer cancel()
		err = client.Add(ctx, info)
		if err != nil {
			return errors.Wrap(err, "Failed to add dqlite node")
		}

	}

	return nil
}
<commit_msg>lxd/cluster: If raft node 1 gets remove during recovery, add it back<commit_after>package cluster

import (
	"context"
	"fmt"
	"math/rand"
	"net/http"
	"os"
	"time"

	lxd "github.com/lxc/lxd/client"
	"github.com/lxc/lxd/lxd/db"
	"github.com/lxc/lxd/lxd/state"
	"github.com/lxc/lxd/shared"
	"github.com/lxc/lxd/shared/log15"
	"github.com/lxc/lxd/shared/logger"
	"github.com/pkg/errors"
)

// NotifyUpgradeCompleted sends a notification to all other nodes in the
// cluster that any possible pending database update has been applied, and any
// nodes which was waiting for this node to be upgraded should re-check if it's
// okay to move forward.
func NotifyUpgradeCompleted(state *state.State, cert *shared.CertInfo) error {
	notifier, err := NewNotifier(state, cert, NotifyTryAll)
	if err != nil {
		return err
	}

	return notifier(func(client lxd.InstanceServer) error {
		info, err := client.GetConnectionInfo()
		if err != nil {
			return errors.Wrap(err, "failed to get connection info")
		}

		url := fmt.Sprintf("%s%s", info.Addresses[0], databaseEndpoint)
		request, err := http.NewRequest("PATCH", url, nil)
		if err != nil {
			return errors.Wrap(err, "failed to create database notify upgrade request")
		}
		setDqliteVersionHeader(request)

		httpClient, err := client.GetHTTPClient()
		if err != nil {
			return errors.Wrap(err, "failed to get HTTP client")
		}

		httpClient.Timeout = 5 * time.Second
		response, err := httpClient.Do(request)
		if err != nil {
			return errors.Wrap(err, "failed to notify node about completed upgrade")
		}

		if response.StatusCode != http.StatusOK {
			return fmt.Errorf("database upgrade notification failed: %s", response.Status)
		}

		return nil
	})
}

// MaybeUpdate Check this node's version and possibly run LXD_CLUSTER_UPDATE.
func MaybeUpdate(state *state.State) error {
	shouldUpdate := false

	enabled, err := Enabled(state.Node)
	if err != nil {
		return errors.Wrap(err, "Failed to check clustering is enabled")
	}
	if !enabled {
		return nil
	}

	if state.Cluster == nil {
		return fmt.Errorf("Failed checking cluster update, state not initialised yet")
	}

	err = state.Cluster.Transaction(func(tx *db.ClusterTx) error {
		outdated, err := tx.NodeIsOutdated()
		if err != nil {
			return err
		}
		shouldUpdate = outdated
		return nil
	})

	if err != nil {
		// Just log the error and return.
		return errors.Wrap(err, "Failed to check if this node is out-of-date")
	}

	if !shouldUpdate {
		logger.Debugf("Cluster node is up-to-date")
		return nil
	}

	return triggerUpdate()
}

func triggerUpdate() error {
	logger.Infof("Node is out-of-date with respect to other cluster nodes")

	updateExecutable := os.Getenv("LXD_CLUSTER_UPDATE")
	if updateExecutable == "" {
		logger.Debug("No LXD_CLUSTER_UPDATE variable set, skipping auto-update")
		return nil
	}

	// Wait a random amout of seconds (up to 30) in order to avoid
	// restarting all cluster members at the same time, and make the
	// upgrade more graceful.
	wait := time.Duration(rand.Intn(30)) * time.Second
	logger.Infof("Triggering cluster update in %s using: %s", wait, updateExecutable)
	time.Sleep(wait)

	_, err := shared.RunCommand(updateExecutable)
	if err != nil {
		logger.Errorf("Cluster upgrade failed: '%v'", err.Error())
		return err
	}
	return nil
}

// This function assigns the Spare raft role to all cluster members that are
// not currently part of the raft configuration. It's used for upgrading a
// cluster from a version without roles support.
func upgradeMembersWithoutRole(gateway *Gateway, members []db.NodeInfo, nodes []db.RaftNode) error {
	// Used raft IDs.
	ids := map[uint64]bool{}
	for _, node := range nodes {
		ids[node.ID] = true
	}

	client, err := gateway.getClient()
	if err != nil {
		return errors.Wrap(err, "Failed to connect to local dqlite node")
	}
	defer client.Close()

	// Check that each member is present in the raft configuration, and add
	// if not.
	for _, member := range members {
		found := false
		for _, node := range nodes {
			if member.ID == 1 && node.ID == 1 || member.Address == node.Address {
				found = true
				break
			}
		}
		if found {
			continue
		}

		// Try to use the same ID as the node, but it might not be
		// possible if it's use.
		id := uint64(member.ID)
		if _, ok := ids[id]; ok {
			for _, other := range members {
				if _, ok := ids[uint64(other.ID)]; !ok {
					id = uint64(other.ID)
					break
				}
			}
			// Sanity check: can't really happen since there are
			// always at least as many members as there are nodes,
			// and all of them have different IDs.
			if id == uint64(member.ID) {
				panic("no available ID")
			}
		}
		ids[id] = true

		info := db.RaftNode{
			ID:      id,
			Address: member.Address,
			Role:    db.RaftSpare,
		}

		logger.Info("Add spare dqlite node", log15.Ctx{"id": info.ID, "address": info.Address})

		ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
		defer cancel()
		err = client.Add(ctx, info)
		if err != nil {
			return errors.Wrap(err, "Failed to add dqlite node")
		}

	}

	return nil
}
<|endoftext|>


<commit_before>package wats

import (
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	. "github.com/onsi/gomega/gbytes"
	. "github.com/onsi/gomega/gexec"

	"github.com/cloudfoundry-incubator/cf-test-helpers/cf"
	"github.com/cloudfoundry-incubator/cf-test-helpers/generator"
)

var _ = Describe("Changing an app's start command", func() {
	var appName string

	BeforeEach(func() {
		appName = generator.RandomName()
	})

	AfterEach(func() {
		Eventually(cf.Cf("logs", appName, "--recent")).Should(Exit())
		Eventually(cf.Cf("delete", appName, "-f")).Should(Exit(0))
	})

	Describe("staged on Diego and running on Diego", func() {
		It("exercises the app through its lifecycle", func() {
			By("pushing it", func() {
				Eventually(cf.Cf(
					"push", appName,
					"-p", "../assets/batch-script",
					"--no-start",
					"--no-route",
					"-b", "https://github.com/ryandotsmith/null-buildpack.git",
					"-s", "windows2012R2",
					"-c", "loop.bat"), CF_PUSH_TIMEOUT).Should(Exit(0))
			})

			By("staging and running it on Diego", func() {
				enableDiego(appName)
				disableSsh(appName)
				disableHealthCheck(appName)
				session := cf.Cf("start", appName)
				Eventually(session, CF_PUSH_TIMEOUT).Should(Exit(0))
			})

			By("verifying it's up", func() {
				output := func() *Buffer {
					session := cf.Cf("logs", appName, "--recent")
					Eventually(session).Should(Exit(0))
					return session.Out
				}
				Eventually(output).Should(Say("Hi there!!!"))
			})
		})
	})
})
<commit_msg>Mark new failing CATS test as pending.<commit_after>package wats

import (
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	. "github.com/onsi/gomega/gbytes"
	. "github.com/onsi/gomega/gexec"

	"github.com/cloudfoundry-incubator/cf-test-helpers/cf"
	"github.com/cloudfoundry-incubator/cf-test-helpers/generator"
)

var _ = Describe("Changing an app's start command", func() {
	var appName string

	BeforeEach(func() {
		appName = generator.RandomName()
	})

	AfterEach(func() {
		Eventually(cf.Cf("logs", appName, "--recent")).Should(Exit())
		Eventually(cf.Cf("delete", appName, "-f")).Should(Exit(0))
	})

	Describe("staged on Diego and running on Diego", func() {
		XIt("exercises the app through its lifecycle", func() {
			By("pushing it", func() {
				Eventually(cf.Cf(
					"push", appName,
					"-p", "../assets/batch-script",
					"--no-start",
					"--no-route",
					"-b", "https://github.com/ryandotsmith/null-buildpack.git",
					"-s", "windows2012R2",
					"-c", "loop.bat"), CF_PUSH_TIMEOUT).Should(Exit(0))
			})

			By("staging and running it on Diego", func() {
				enableDiego(appName)
				disableSsh(appName)
				disableHealthCheck(appName)
				session := cf.Cf("start", appName)
				Eventually(session, CF_PUSH_TIMEOUT).Should(Exit(0))
			})

			By("verifying it's up", func() {
				output := func() *Buffer {
					session := cf.Cf("logs", appName, "--recent")
					Eventually(session).Should(Exit(0))
					return session.Out
				}
				Eventually(output).Should(Say("Hi there!!!"))
			})
		})
	})
})
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"fmt"
	"io/ioutil"
	"net/http"
	_ "net/http/pprof"
	"os/exec"
	"regexp"
	"strconv"
	"strings"

	"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/log"
)

const namespace = "mirth"

var (
	up = prometheus.NewDesc(
		prometheus.BuildFQName(namespace, "", "up"),
		"Was the last Mirth query successful.",
		nil, nil,
	)
	channelsDeployed = prometheus.NewDesc(
		prometheus.BuildFQName(namespace, "", "channels_deployed"),
		"How many channels are deployed.",
		nil, nil,
	)
	channelsStarted = prometheus.NewDesc(
		prometheus.BuildFQName(namespace, "", "channels_started"),
		"How many of the deployed channels are started.",
		nil, nil,
	)
	messagesReceived = prometheus.NewDesc(
		prometheus.BuildFQName(namespace, "", "messages_received_total"),
		"How many messages have been received (per channel).",
		[]string{"channel"}, nil,
	)
	messagesFiltered = prometheus.NewDesc(
		prometheus.BuildFQName(namespace, "", "messages_filtered_total"),
		"How many messages have been filtered (per channel).",
		[]string{"channel"}, nil,
	)
	messagesQueued = prometheus.NewDesc(
		prometheus.BuildFQName(namespace, "", "messages_queued"),
		"How many messages are currently queued (per channel).",
		[]string{"channel"}, nil,
	)
	messagesSent = prometheus.NewDesc(
		prometheus.BuildFQName(namespace, "", "messages_sent_total"),
		"How many messages have been sent (per channel).",
		[]string{"channel"}, nil,
	)
	messagesErrored = prometheus.NewDesc(
		prometheus.BuildFQName(namespace, "", "messages_errored_total"),
		"How many messages have errored (per channel).",
		[]string{"channel"}, nil,
	)
)

type Exporter struct {
	jarPath, configPath string
}

func NewExporter(mccliJarPath, mccliConfigPath string) *Exporter {
	return &Exporter{
		jarPath:    mccliJarPath,
		configPath: mccliConfigPath,
	}
}

func (e *Exporter) Describe(ch chan<- *prometheus.Desc) {
	ch <- up
	ch <- channelsDeployed
	ch <- channelsStarted
	ch <- messagesReceived
	ch <- messagesFiltered
	ch <- messagesQueued
	ch <- messagesSent
	ch <- messagesErrored
}

func (e *Exporter) Collect(ch chan<- prometheus.Metric) {
	lines, err := e.fetchStatLines()
	if err != nil {
		ch <- prometheus.MustNewConstMetric(
			up, prometheus.GaugeValue, 0,
		)
		log.Error(err)
		return
	}
	ch <- prometheus.MustNewConstMetric(
		up, prometheus.GaugeValue, 1,
	)
	e.readStatus(lines, ch)
	e.readChannelStats(lines, ch)
}

func (e *Exporter) fetchStatLines() ([]string, error) {
	cmd := exec.Command("java", "-jar", e.jarPath, "-c", e.configPath)
	stdin, err := cmd.StdinPipe()
	if err != nil {
		return nil, err
	}
	stdout, err := cmd.StdoutPipe()
	if err != nil {
		return nil, err
	}
	if err := cmd.Start(); err != nil {
		return nil, err
	}
	fmt.Fprintln(stdin, "status")
	fmt.Fprintln(stdin, "channel stats")
	stdin.Close()
	bytesOut, err := ioutil.ReadAll(stdout)
	if err != nil {
		return nil, err
	}
	if err := cmd.Wait(); err != nil {
		return nil, err
	}
	lines := strings.Split(string(bytesOut), "\n")
	if len(lines) < 3 {
		return nil, fmt.Errorf("Unexpected output: %s", string(bytesOut))
	}
	log.Debug(string(bytesOut))
	return lines, nil
}

func (e *Exporter) readStatus(lines []string, ch chan<- prometheus.Metric) {
	deployed := regexp.MustCompile(`^[0-9a-f-]{36}\s+[a-zA-Z]+\s+`)
	started := regexp.MustCompile(`\s+Started\s+`)
	deployedCount, startedCount := 0, 0
	for _, line := range lines {
		if deployed.MatchString(line) {
			deployedCount++
			if started.MatchString(line) {
				startedCount++
			}
		}
	}
	ch <- prometheus.MustNewConstMetric(
		channelsDeployed, prometheus.GaugeValue, float64(deployedCount),
	)
	ch <- prometheus.MustNewConstMetric(
		channelsStarted, prometheus.GaugeValue, float64(startedCount),
	)
}

func (e *Exporter) readChannelStats(lines []string, ch chan<- prometheus.Metric) {
	stat := regexp.MustCompile(`^(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(.+)$`)
	for _, line := range lines {
		if stat.MatchString(line) {
			group := stat.FindStringSubmatch(line)
			channel := group[6]
			received, _ := strconv.ParseFloat(group[1], 64)
			ch <- prometheus.MustNewConstMetric(
				messagesReceived, prometheus.CounterValue, received, channel,
			)
			filtered, _ := strconv.ParseFloat(group[2], 64)
			ch <- prometheus.MustNewConstMetric(
				messagesFiltered, prometheus.CounterValue, filtered, channel,
			)
			queued, _ := strconv.ParseFloat(group[3], 64)
			ch <- prometheus.MustNewConstMetric(
				messagesQueued, prometheus.GaugeValue, queued, channel,
			)
			sent, _ := strconv.ParseFloat(group[4], 64)
			ch <- prometheus.MustNewConstMetric(
				messagesSent, prometheus.CounterValue, sent, channel,
			)
			errored, _ := strconv.ParseFloat(group[5], 64)
			ch <- prometheus.MustNewConstMetric(
				messagesErrored, prometheus.CounterValue, errored, channel,
			)
		}
	}
}

func main() {
	var (
		listenAddress = flag.String("web.listen-address", ":9140",
			"Address to listen on for telemetry")
		metricsPath = flag.String("web.telemetry-path", "/metrics",
			"Path under which to expose metrics")
		mccliConfigPath = flag.String("mccli.config-path", "./mirth-cli-config.properties",
			"Path to properties file for Mirth Connect CLI")
		mccliJarPath = flag.String("mccli.jar-path", "./mirth-cli-launcher.jar",
			"Path to jar file for Mirth Connect CLI")
	)
	flag.Parse()

	exporter := NewExporter(*mccliJarPath, *mccliConfigPath)
	prometheus.MustRegister(exporter)

	log.Infof("Starting server: %s", *listenAddress)
	http.Handle(*metricsPath, prometheus.Handler())
	log.Fatal(http.ListenAndServe(*listenAddress, nil))
}
<commit_msg>Add root handler with link to metricsPath<commit_after>package main

import (
	"flag"
	"fmt"
	"io/ioutil"
	"net/http"
	_ "net/http/pprof"
	"os/exec"
	"regexp"
	"strconv"
	"strings"

	"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/log"
)

const namespace = "mirth"

var (
	up = prometheus.NewDesc(
		prometheus.BuildFQName(namespace, "", "up"),
		"Was the last Mirth query successful.",
		nil, nil,
	)
	channelsDeployed = prometheus.NewDesc(
		prometheus.BuildFQName(namespace, "", "channels_deployed"),
		"How many channels are deployed.",
		nil, nil,
	)
	channelsStarted = prometheus.NewDesc(
		prometheus.BuildFQName(namespace, "", "channels_started"),
		"How many of the deployed channels are started.",
		nil, nil,
	)
	messagesReceived = prometheus.NewDesc(
		prometheus.BuildFQName(namespace, "", "messages_received_total"),
		"How many messages have been received (per channel).",
		[]string{"channel"}, nil,
	)
	messagesFiltered = prometheus.NewDesc(
		prometheus.BuildFQName(namespace, "", "messages_filtered_total"),
		"How many messages have been filtered (per channel).",
		[]string{"channel"}, nil,
	)
	messagesQueued = prometheus.NewDesc(
		prometheus.BuildFQName(namespace, "", "messages_queued"),
		"How many messages are currently queued (per channel).",
		[]string{"channel"}, nil,
	)
	messagesSent = prometheus.NewDesc(
		prometheus.BuildFQName(namespace, "", "messages_sent_total"),
		"How many messages have been sent (per channel).",
		[]string{"channel"}, nil,
	)
	messagesErrored = prometheus.NewDesc(
		prometheus.BuildFQName(namespace, "", "messages_errored_total"),
		"How many messages have errored (per channel).",
		[]string{"channel"}, nil,
	)
)

type Exporter struct {
	jarPath, configPath string
}

func NewExporter(mccliJarPath, mccliConfigPath string) *Exporter {
	return &Exporter{
		jarPath:    mccliJarPath,
		configPath: mccliConfigPath,
	}
}

func (e *Exporter) Describe(ch chan<- *prometheus.Desc) {
	ch <- up
	ch <- channelsDeployed
	ch <- channelsStarted
	ch <- messagesReceived
	ch <- messagesFiltered
	ch <- messagesQueued
	ch <- messagesSent
	ch <- messagesErrored
}

func (e *Exporter) Collect(ch chan<- prometheus.Metric) {
	lines, err := e.fetchStatLines()
	if err != nil {
		ch <- prometheus.MustNewConstMetric(
			up, prometheus.GaugeValue, 0,
		)
		log.Error(err)
		return
	}
	ch <- prometheus.MustNewConstMetric(
		up, prometheus.GaugeValue, 1,
	)
	e.readStatus(lines, ch)
	e.readChannelStats(lines, ch)
}

func (e *Exporter) fetchStatLines() ([]string, error) {
	cmd := exec.Command("java", "-jar", e.jarPath, "-c", e.configPath)
	stdin, err := cmd.StdinPipe()
	if err != nil {
		return nil, err
	}
	stdout, err := cmd.StdoutPipe()
	if err != nil {
		return nil, err
	}
	if err := cmd.Start(); err != nil {
		return nil, err
	}
	fmt.Fprintln(stdin, "status")
	fmt.Fprintln(stdin, "channel stats")
	stdin.Close()
	bytesOut, err := ioutil.ReadAll(stdout)
	if err != nil {
		return nil, err
	}
	if err := cmd.Wait(); err != nil {
		return nil, err
	}
	lines := strings.Split(string(bytesOut), "\n")
	if len(lines) < 3 {
		return nil, fmt.Errorf("Unexpected output: %s", string(bytesOut))
	}
	log.Debug(string(bytesOut))
	return lines, nil
}

func (e *Exporter) readStatus(lines []string, ch chan<- prometheus.Metric) {
	deployed := regexp.MustCompile(`^[0-9a-f-]{36}\s+[a-zA-Z]+\s+`)
	started := regexp.MustCompile(`\s+Started\s+`)
	deployedCount, startedCount := 0, 0
	for _, line := range lines {
		if deployed.MatchString(line) {
			deployedCount++
			if started.MatchString(line) {
				startedCount++
			}
		}
	}
	ch <- prometheus.MustNewConstMetric(
		channelsDeployed, prometheus.GaugeValue, float64(deployedCount),
	)
	ch <- prometheus.MustNewConstMetric(
		channelsStarted, prometheus.GaugeValue, float64(startedCount),
	)
}

func (e *Exporter) readChannelStats(lines []string, ch chan<- prometheus.Metric) {
	stat := regexp.MustCompile(`^(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(.+)$`)
	for _, line := range lines {
		if stat.MatchString(line) {
			group := stat.FindStringSubmatch(line)
			channel := group[6]
			received, _ := strconv.ParseFloat(group[1], 64)
			ch <- prometheus.MustNewConstMetric(
				messagesReceived, prometheus.CounterValue, received, channel,
			)
			filtered, _ := strconv.ParseFloat(group[2], 64)
			ch <- prometheus.MustNewConstMetric(
				messagesFiltered, prometheus.CounterValue, filtered, channel,
			)
			queued, _ := strconv.ParseFloat(group[3], 64)
			ch <- prometheus.MustNewConstMetric(
				messagesQueued, prometheus.GaugeValue, queued, channel,
			)
			sent, _ := strconv.ParseFloat(group[4], 64)
			ch <- prometheus.MustNewConstMetric(
				messagesSent, prometheus.CounterValue, sent, channel,
			)
			errored, _ := strconv.ParseFloat(group[5], 64)
			ch <- prometheus.MustNewConstMetric(
				messagesErrored, prometheus.CounterValue, errored, channel,
			)
		}
	}
}

func main() {
	var (
		listenAddress = flag.String("web.listen-address", ":9140",
			"Address to listen on for telemetry")
		metricsPath = flag.String("web.telemetry-path", "/metrics",
			"Path under which to expose metrics")
		mccliConfigPath = flag.String("mccli.config-path", "./mirth-cli-config.properties",
			"Path to properties file for Mirth Connect CLI")
		mccliJarPath = flag.String("mccli.jar-path", "./mirth-cli-launcher.jar",
			"Path to jar file for Mirth Connect CLI")
	)
	flag.Parse()

	exporter := NewExporter(*mccliJarPath, *mccliConfigPath)
	prometheus.MustRegister(exporter)

	log.Infof("Starting server: %s", *listenAddress)
	http.Handle(*metricsPath, prometheus.Handler())
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		w.Write([]byte(`
             <title>Mirth Exporter</title>

Mirth Exporter

<a href='` + *metricsPath + `'>Metrics

             `))
	})
	log.Fatal(http.ListenAndServe(*listenAddress, nil))
}
<|endoftext|>


<commit_before>package govaluate

import (
	"errors"
	"fmt"
)

const isoDateFormat string = "2006-01-02T15:04:05.999999999Z0700"

var DUMMY_PARAMETERS = MapParameters(map[string]interface{}{})

/*
	EvaluableExpression represents a set of ExpressionTokens which, taken together,
	are an expression that can be evaluated down into a single value.
*/
type EvaluableExpression struct {

	/*
		Represents the query format used to output dates. Typically only used when creating SQL or Mongo queries from an expression.
		Defaults to the complete ISO8601 format, including nanoseconds.
	*/
	QueryDateFormat string

	tokens           []ExpressionToken
	evaluationStages *evaluationStage
	inputExpression  string
}

/*
	Parses a new EvaluableExpression from the given [expression] string.
	Returns an error if the given expression has invalid syntax.
*/
func NewEvaluableExpression(expression string) (*EvaluableExpression, error) {

	functions := make(map[string]ExpressionFunction)
	return NewEvaluableExpressionWithFunctions(expression, functions)
}

/*
	Similar to [NewEvaluableExpression], except that instead of a string, an already-tokenized expression is given.
	This is useful in cases where you may be generating an expression automatically, or using some other parser (e.g., to parse from a query language)
*/
func NewEvaluableExpressionFromTokens(tokens []ExpressionToken) (*EvaluableExpression, error) {

	var ret *EvaluableExpression
	var err error

	ret = new(EvaluableExpression)
	ret.QueryDateFormat = isoDateFormat

	err = checkBalance(tokens)
	if err != nil {
		return nil, err
	}

	err = checkExpressionSyntax(tokens)
	if err != nil {
		return nil, err
	}

	ret.tokens, err = optimizeTokens(tokens)
	if err != nil {
		return nil, err
	}

	ret.evaluationStages, err = planStages(ret.tokens)
	if err != nil {
		return nil, err
	}

	return ret, nil
}

/*
	Similar to [NewEvaluableExpression], except enables the use of user-defined functions.
	Functions passed into this will be available to the expression.
*/
func NewEvaluableExpressionWithFunctions(expression string, functions map[string]ExpressionFunction) (*EvaluableExpression, error) {

	var ret *EvaluableExpression
	var err error

	ret = new(EvaluableExpression)
	ret.QueryDateFormat = isoDateFormat
	ret.inputExpression = expression

	ret.tokens, err = parseTokens(expression, functions)
	if err != nil {
		return nil, err
	}

	err = checkExpressionSyntax(ret.tokens)
	if err != nil {
		return nil, err
	}

	ret.evaluationStages, err = planStages(ret.tokens)
	if err != nil {
		return nil, err
	}

	return ret, nil
}

/*
	Same as `Eval`, but automatically wraps a map of parameters into a `govalute.Parameters` structure.
*/
func (this EvaluableExpression) Evaluate(parameters map[string]interface{}) (interface{}, error) {

	if parameters == nil {
		return this.Eval(nil)
	}
	return this.Eval(MapParameters(parameters))
}

/*
	Runs the entire expression using the given [parameters].
	e.g., If the expression contains a reference to the variable "foo", it will be taken from `parameters.Get("foo")`.

	This function returns errors if the combination of expression and parameters cannot be run,
	such as if a variable in the expression is not present in [parameters].

	In all non-error circumstances, this returns the single value result of the expression and parameters given.
	e.g., if the expression is "1 + 1", this will return 2.0.
	e.g., if the expression is "foo + 1" and parameters contains "foo" = 2, this will return 3.0
*/
func (this EvaluableExpression) Eval(parameters Parameters) (interface{}, error) {

	if this.evaluationStages == nil {
		return nil, nil
	}

	if parameters != nil {
		parameters = &sanitizedParameters{parameters}
	}
	return evaluateStage(this.evaluationStages, parameters)
}

func evaluateStage(stage *evaluationStage, parameters Parameters) (interface{}, error) {

	var left, right interface{}
	var err error

	if stage.leftStage != nil {
		left, err = evaluateStage(stage.leftStage, parameters)
		if err != nil {
			return nil, err
		}
	}

	if stage.rightStage != nil {
		right, err = evaluateStage(stage.rightStage, parameters)
		if err != nil {
			return nil, err
		}
	}

	// type checks
	if stage.typeCheck == nil {

		err = typeCheck(stage.leftTypeCheck, left, stage.symbol, stage.typeErrorFormat)
		if err != nil {
			return nil, err
		}

		err = typeCheck(stage.rightTypeCheck, right, stage.symbol, stage.typeErrorFormat)
		if err != nil {
			return nil, err
		}
	} else {
		// special case where the type check needs to know both sides to determine if the operator can handle it
		if !stage.typeCheck(left, right) {
			errorMsg := fmt.Sprintf(stage.typeErrorFormat, left, stage.symbol.String())
			return nil, errors.New(errorMsg)
		}
	}

	return stage.operator(left, right, parameters)
}

func typeCheck(check stageTypeCheck, value interface{}, symbol OperatorSymbol, format string) error {

	if check == nil {
		return nil
	}

	if check(value) {
		return nil
	}

	errorMsg := fmt.Sprintf(format, value, symbol.String())
	return errors.New(errorMsg)
}

/*
	Returns an array representing the ExpressionTokens that make up this expression.
*/
func (this EvaluableExpression) Tokens() []ExpressionToken {

	return this.tokens
}

/*
	Returns the original expression used to create this EvaluableExpression.
*/
func (this EvaluableExpression) String() string {

	return this.inputExpression
}
<commit_msg>Added 'unchecked' mode to expressions<commit_after>package govaluate

import (
	"errors"
	"fmt"
)

const isoDateFormat string = "2006-01-02T15:04:05.999999999Z0700"

var DUMMY_PARAMETERS = MapParameters(map[string]interface{}{})

/*
	EvaluableExpression represents a set of ExpressionTokens which, taken together,
	are an expression that can be evaluated down into a single value.
*/
type EvaluableExpression struct {

	/*
		Represents the query format used to output dates. Typically only used when creating SQL or Mongo queries from an expression.
		Defaults to the complete ISO8601 format, including nanoseconds.
	*/
	QueryDateFormat string

	/*
		Whether or not to safely check types when evaluating.
		If true, this library will return error messages when invalid types are used.
		If false, the library will panic when operators encounter types they can't use.

		This is exclusively for users who need to squeeze every ounce of speed out of the library as they can,
		and you should only set this to false if you know exactly what you're doing.
	*/
	ChecksTypes bool

	tokens           []ExpressionToken
	evaluationStages *evaluationStage
	inputExpression  string
}

/*
	Parses a new EvaluableExpression from the given [expression] string.
	Returns an error if the given expression has invalid syntax.
*/
func NewEvaluableExpression(expression string) (*EvaluableExpression, error) {

	functions := make(map[string]ExpressionFunction)
	return NewEvaluableExpressionWithFunctions(expression, functions)
}

/*
	Similar to [NewEvaluableExpression], except that instead of a string, an already-tokenized expression is given.
	This is useful in cases where you may be generating an expression automatically, or using some other parser (e.g., to parse from a query language)
*/
func NewEvaluableExpressionFromTokens(tokens []ExpressionToken) (*EvaluableExpression, error) {

	var ret *EvaluableExpression
	var err error

	ret = new(EvaluableExpression)
	ret.QueryDateFormat = isoDateFormat

	err = checkBalance(tokens)
	if err != nil {
		return nil, err
	}

	err = checkExpressionSyntax(tokens)
	if err != nil {
		return nil, err
	}

	ret.tokens, err = optimizeTokens(tokens)
	if err != nil {
		return nil, err
	}

	ret.evaluationStages, err = planStages(ret.tokens)
	if err != nil {
		return nil, err
	}

	ret.ChecksTypes = true
	return ret, nil
}

/*
	Similar to [NewEvaluableExpression], except enables the use of user-defined functions.
	Functions passed into this will be available to the expression.
*/
func NewEvaluableExpressionWithFunctions(expression string, functions map[string]ExpressionFunction) (*EvaluableExpression, error) {

	var ret *EvaluableExpression
	var err error

	ret = new(EvaluableExpression)
	ret.QueryDateFormat = isoDateFormat
	ret.inputExpression = expression

	ret.tokens, err = parseTokens(expression, functions)
	if err != nil {
		return nil, err
	}

	err = checkBalance(ret.tokens)
	if err != nil {
		return nil, err
	}

	err = checkExpressionSyntax(ret.tokens)
	if err != nil {
		return nil, err
	}

	ret.tokens, err = optimizeTokens(ret.tokens)
	if err != nil {
		return nil, err
	}

	ret.evaluationStages, err = planStages(ret.tokens)
	if err != nil {
		return nil, err
	}

	ret.ChecksTypes = true
	return ret, nil
}

/*
	Same as `Eval`, but automatically wraps a map of parameters into a `govalute.Parameters` structure.
*/
func (this EvaluableExpression) Evaluate(parameters map[string]interface{}) (interface{}, error) {

	if parameters == nil {
		return this.Eval(nil)
	}
	return this.Eval(MapParameters(parameters))
}

/*
	Runs the entire expression using the given [parameters].
	e.g., If the expression contains a reference to the variable "foo", it will be taken from `parameters.Get("foo")`.

	This function returns errors if the combination of expression and parameters cannot be run,
	such as if a variable in the expression is not present in [parameters].

	In all non-error circumstances, this returns the single value result of the expression and parameters given.
	e.g., if the expression is "1 + 1", this will return 2.0.
	e.g., if the expression is "foo + 1" and parameters contains "foo" = 2, this will return 3.0
*/
func (this EvaluableExpression) Eval(parameters Parameters) (interface{}, error) {

	if this.evaluationStages == nil {
		return nil, nil
	}

	if parameters != nil {
		parameters = &sanitizedParameters{parameters}
	}
	return this.evaluateStage(this.evaluationStages, parameters)
}

func (this EvaluableExpression) evaluateStage(stage *evaluationStage, parameters Parameters) (interface{}, error) {

	var left, right interface{}
	var err error

	if stage.leftStage != nil {
		left, err = this.evaluateStage(stage.leftStage, parameters)
		if err != nil {
			return nil, err
		}
	}

	if stage.rightStage != nil {
		right, err = this.evaluateStage(stage.rightStage, parameters)
		if err != nil {
			return nil, err
		}
	}

	if this.ChecksTypes {
		if stage.typeCheck == nil {

			err = typeCheck(stage.leftTypeCheck, left, stage.symbol, stage.typeErrorFormat)
			if err != nil {
				return nil, err
			}

			err = typeCheck(stage.rightTypeCheck, right, stage.symbol, stage.typeErrorFormat)
			if err != nil {
				return nil, err
			}
		} else {
			// special case where the type check needs to know both sides to determine if the operator can handle it
			if !stage.typeCheck(left, right) {
				errorMsg := fmt.Sprintf(stage.typeErrorFormat, left, stage.symbol.String())
				return nil, errors.New(errorMsg)
			}
		}
	}

	return stage.operator(left, right, parameters)
}

func typeCheck(check stageTypeCheck, value interface{}, symbol OperatorSymbol, format string) error {

	if check == nil {
		return nil
	}

	if check(value) {
		return nil
	}

	errorMsg := fmt.Sprintf(format, value, symbol.String())
	return errors.New(errorMsg)
}

/*
	Returns an array representing the ExpressionTokens that make up this expression.
*/
func (this EvaluableExpression) Tokens() []ExpressionToken {

	return this.tokens
}

/*
	Returns the original expression used to create this EvaluableExpression.
*/
func (this EvaluableExpression) String() string {

	return this.inputExpression
}
<|endoftext|>


<commit_before>// Copyright 2014 Jarmo Puttonen <jarmo.puttonen@gmail.com>. All rights reserved.
// Use of this source code is governed by a MIT-style
// licence that can be found in the LICENCE file.

/*Package paparazzogo implements a caching proxy for
serving MJPEG-stream as JPG-images.
*/
package paparazzogo

/*
Test coverage: 86.9% of statements
*/

import (
	"bytes"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/http/httptest"
	"strconv"
	"strings"
	"testing"
	"time"
)

//Multipart body for testing.
var firstPart = "01234567890"
var boundary = "MyBoundary"
var validBody = `
Content-type: multipart/x-mixed-replace;boundary=` + boundary + `

--` + boundary + `
Content-type: text/plain

` + firstPart + `

--` + boundary + `--
   `
var invalidBody = `
Content-type: multipart/x-mixed-replace;boundary=` + boundary + `
--` + boundary + `
Content-type: text/plain
` + firstPart + `
--` + boundary + `--
   `
var malformedPart = `
Content-type: multipart/x-mixed-replace;boundary=` + boundary + `

--` + boundary + `
Content-type: text/plain

` + firstPart + `

--` + boundary + `
   `

var streamBody = `
--` + boundary + `
Content-type: text/plain

` + firstPart + `

--` + boundary + `--
   `

func Test_NewMjpegproxy(t *testing.T) {
	mp := NewMjpegproxy()
	if mp == nil {
		t.Fatal("Could not create Mjpegproxy!")
	}
}

func Test_CloseStream(t *testing.T) {
	mp := NewMjpegproxy()
	mp.CloseStream()
	if mp.running != false {
		t.Fatalf("Wrong run state: %s", mp.running)
	}
}

func Test_setRunning(t *testing.T) {
	mp := NewMjpegproxy()
	mp.setRunning(true)
	if mp.running != true {
		t.Fatalf("Wrong run state: expected true, got %s", mp.running)
	}
	mp.setRunning(false)
	if mp.running != false {
		t.Fatalf("Wrong run state: expected false, got %s", mp.running)
	}
}

func Test_GetRunning(t *testing.T) {
	mp := NewMjpegproxy()
	mp.setRunning(true)
	if mp.GetRunning() != true {
		t.Fatalf("Wrong run state: expected true, got %s", mp.GetRunning())
	}
	mp.setRunning(false)
	if mp.running != false {
		t.Fatalf("Wrong run state: expected false, got %s", mp.GetRunning())
	}
}

func Test_getresponse_valid(t *testing.T) {
	msg := "Test string"
	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintln(w, msg)
	}))
	defer ts.Close()
	mp := NewMjpegproxy()
	request, err := http.NewRequest("GET", ts.URL, nil)
	if err != nil {
		t.Fatal("Failed to create request.")
	}
	res, err := mp.getresponse(request)
	if err != nil {
		t.Fatal(err)
	}
	responsebody, err := ioutil.ReadAll(res.Body)
	res.Body.Close()
	if err != nil {
		t.Fatal(err)
	}
	if !strings.Contains(string(responsebody), msg) {
		t.Fatalf("Response body mismatch: %s vs %s", msg, string(responsebody))
	}
}

func Test_getresponse_invalid_status(t *testing.T) {
	invalidstatus := 418
	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		http.Error(w, "", invalidstatus)
	}))
	defer ts.Close()
	mp := NewMjpegproxy()
	request, err := http.NewRequest("GET", ts.URL, nil)
	if err != nil {
		t.Fatal("Failed to create request.")
	}
	_, err = mp.getresponse(request)
	if err == nil {
		t.Fatal("Unexpected nil error!")
	}
	str := strconv.Itoa(invalidstatus)
	if !strings.Contains(err.Error(), str) {
		t.Fatalf("Wrong status code: %s vs %s", err.Error(), str)
	}
}
func Test_getresponse_noconnection(t *testing.T) {
	mp := NewMjpegproxy()
	request, err := http.NewRequest("GET", "http://127.0.0.1:99999/", nil)
	if err != nil {
		t.Fatal("Failed to create request.")
	}
	_, err = mp.getresponse(request)
	if err == nil {
		t.Fatal("Unexpected nil error!")
	}
	if !strings.Contains(err.Error(), "invalid port 99999") {
		t.Fatalf("Wrong error on connection refuse: %s", err.Error())
	}
}

func Test_getboundary_valid(t *testing.T) {
	mp := NewMjpegproxy()
	response := &http.Response{
		Status:     "200 OK",
		StatusCode: 200,
		Proto:      "HTTP/1.0",
		Header: http.Header{
			"Content-Type": []string{"multipart/x-mixed-replace; boundary=" + boundary},
		},
		Body:          ioutil.NopCloser(bytes.NewBufferString(validBody)),
		ContentLength: int64(len(validBody)),
	}
	_, err := mp.getboundary(response)
	if err != nil {
		t.Fatal(err)
	}
}

func Test_getboundary_noboundary(t *testing.T) {
	mp := NewMjpegproxy()
	response := &http.Response{
		Status:     "200 OK",
		StatusCode: 200,
		Proto:      "HTTP/1.0",
		Header: http.Header{
			"Content-Type": []string{"multipart/x-mixed-replace"},
		},
		Body:          ioutil.NopCloser(bytes.NewBufferString(validBody)),
		ContentLength: int64(len(validBody)),
	}
	_, err := mp.getboundary(response)
	if err == nil {
		t.Fatal("Unexpected nil error!")
	}
	if !strings.Contains(err.Error(), "No multipart boundary param in Content-Type!") {
		t.Fatalf("Wrong error: %s", err.Error())
	}
}

func Test_getboundary_wrong(t *testing.T) {
	mp := NewMjpegproxy()
	response := &http.Response{
		Status:     "200 OK",
		StatusCode: 200,
		Proto:      "HTTP/1.0",
		Header: http.Header{
			"Content-Type": []string{"multipart/form-data"},
		},
		Body:          ioutil.NopCloser(bytes.NewBufferString(validBody)),
		ContentLength: int64(len(validBody)),
	}
	_, err := mp.getboundary(response)
	if err == nil {
		t.Fatal("Unexpected nil error!")
	}
	if !strings.Contains(err.Error(), "Wrong Content-Type: expected multipart/x-mixed-replace, got multipart/form-data") {
		t.Fatalf("Wrong error: %s", err.Error())
	}

}

func Test_getboundary_invalid(t *testing.T) {
	mp := NewMjpegproxy()
	response := &http.Response{
		Status:     "200 OK",
		StatusCode: 200,
		Proto:      "HTTP/1.0",
		Header: http.Header{
			"Content-Type": []string{"multipart/"},
		},
		Body:          ioutil.NopCloser(bytes.NewBufferString(validBody)),
		ContentLength: int64(len(validBody)),
	}
	_, err := mp.getboundary(response)
	if err == nil {
		t.Fatal("Unexpected nil error!")
	}
	if !strings.Contains(err.Error(), "mime: expected token after slash") {
		t.Fatalf("Wrong error: %s", err.Error())
	}

}

func Test_getboundary_noCT(t *testing.T) {
	mp := NewMjpegproxy()
	response := &http.Response{
		Status:        "200 OK",
		StatusCode:    200,
		Proto:         "HTTP/1.0",
		Header:        http.Header{},
		Body:          ioutil.NopCloser(bytes.NewBufferString(validBody)),
		ContentLength: int64(len(validBody)),
	}
	_, err := mp.getboundary(response)
	if err == nil {
		t.Fatal("Unexpected nil error!")
	}
	if !strings.Contains(err.Error(), "Content-Type isn't specified!") {
		t.Fatalf("Wrong error: %s", err.Error())
	}

}

func Test_ServeHTTP(t *testing.T) {
	mp := NewMjpegproxy()
	req := &http.Request{}
	testString := []byte("Test String")
	mp.curImg.Write(testString)
	recorder := httptest.NewRecorder()
	mp.ServeHTTP(recorder, req)
	if time.Since(mp.lastConn) > time.Second {
		t.Fatal("Unexpected lastconn value!")
	}
	if !bytes.Equal(recorder.Body.Bytes(), testString) {
		t.Fatalf("Content mismatch: expected %s, got %s", string(testString), string(mp.curImg.Bytes()))
	}
}

func Test_OpenStream_logic(t *testing.T) {
	user := "user"
	pass := "pass"
	mp := NewMjpegproxy()
	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "multipart/x-mixed-replace;boundary="+boundary)
		fmt.Fprintln(w, streamBody)
	}))
	defer ts.Close()
	mp.setRunning(true)
	mp.OpenStream(ts.URL, user, pass, time.Second)
	defer mp.CloseStream()
	time.Sleep(time.Second)
	mp.conChan <- time.Now()
	time.Sleep(time.Millisecond)
	if !strings.Contains(mp.curImg.String(), firstPart) {
		t.Fatalf("Wrong response: expected %s, got %s", firstPart, mp.curImg.String())
	}
	mp.setRunning(false)
}
<commit_msg>Fix timing issue on test.<commit_after>// Copyright 2014 Jarmo Puttonen <jarmo.puttonen@gmail.com>. All rights reserved.
// Use of this source code is governed by a MIT-style
// licence that can be found in the LICENCE file.

/*Package paparazzogo implements a caching proxy for
serving MJPEG-stream as JPG-images.
*/
package paparazzogo

/*
Test coverage: 86.9% of statements
*/

import (
	"bytes"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/http/httptest"
	"strconv"
	"strings"
	"testing"
	"time"
)

//Multipart body for testing.
var firstPart = "01234567890"
var boundary = "MyBoundary"
var validBody = `
Content-type: multipart/x-mixed-replace;boundary=` + boundary + `

--` + boundary + `
Content-type: text/plain

` + firstPart + `

--` + boundary + `--
   `
var invalidBody = `
Content-type: multipart/x-mixed-replace;boundary=` + boundary + `
--` + boundary + `
Content-type: text/plain
` + firstPart + `
--` + boundary + `--
   `
var malformedPart = `
Content-type: multipart/x-mixed-replace;boundary=` + boundary + `

--` + boundary + `
Content-type: text/plain

` + firstPart + `

--` + boundary + `
   `

var streamBody = `
--` + boundary + `
Content-type: text/plain

` + firstPart + `

--` + boundary + `--
   `

func Test_NewMjpegproxy(t *testing.T) {
	mp := NewMjpegproxy()
	if mp == nil {
		t.Fatal("Could not create Mjpegproxy!")
	}
}

func Test_CloseStream(t *testing.T) {
	mp := NewMjpegproxy()
	mp.CloseStream()
	if mp.running != false {
		t.Fatalf("Wrong run state: %s", mp.running)
	}
}

func Test_setRunning(t *testing.T) {
	mp := NewMjpegproxy()
	mp.setRunning(true)
	if mp.running != true {
		t.Fatalf("Wrong run state: expected true, got %s", mp.running)
	}
	mp.setRunning(false)
	if mp.running != false {
		t.Fatalf("Wrong run state: expected false, got %s", mp.running)
	}
}

func Test_GetRunning(t *testing.T) {
	mp := NewMjpegproxy()
	mp.setRunning(true)
	if mp.GetRunning() != true {
		t.Fatalf("Wrong run state: expected true, got %s", mp.GetRunning())
	}
	mp.setRunning(false)
	if mp.running != false {
		t.Fatalf("Wrong run state: expected false, got %s", mp.GetRunning())
	}
}

func Test_getresponse_valid(t *testing.T) {
	msg := "Test string"
	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintln(w, msg)
	}))
	defer ts.Close()
	mp := NewMjpegproxy()
	request, err := http.NewRequest("GET", ts.URL, nil)
	if err != nil {
		t.Fatal("Failed to create request.")
	}
	res, err := mp.getresponse(request)
	if err != nil {
		t.Fatal(err)
	}
	responsebody, err := ioutil.ReadAll(res.Body)
	res.Body.Close()
	if err != nil {
		t.Fatal(err)
	}
	if !strings.Contains(string(responsebody), msg) {
		t.Fatalf("Response body mismatch: %s vs %s", msg, string(responsebody))
	}
}

func Test_getresponse_invalid_status(t *testing.T) {
	invalidstatus := 418
	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		http.Error(w, "", invalidstatus)
	}))
	defer ts.Close()
	mp := NewMjpegproxy()
	request, err := http.NewRequest("GET", ts.URL, nil)
	if err != nil {
		t.Fatal("Failed to create request.")
	}
	_, err = mp.getresponse(request)
	if err == nil {
		t.Fatal("Unexpected nil error!")
	}
	str := strconv.Itoa(invalidstatus)
	if !strings.Contains(err.Error(), str) {
		t.Fatalf("Wrong status code: %s vs %s", err.Error(), str)
	}
}
func Test_getresponse_noconnection(t *testing.T) {
	mp := NewMjpegproxy()
	request, err := http.NewRequest("GET", "http://127.0.0.1:99999/", nil)
	if err != nil {
		t.Fatal("Failed to create request.")
	}
	_, err = mp.getresponse(request)
	if err == nil {
		t.Fatal("Unexpected nil error!")
	}
	if !strings.Contains(err.Error(), "invalid port 99999") {
		t.Fatalf("Wrong error on connection refuse: %s", err.Error())
	}
}

func Test_getboundary_valid(t *testing.T) {
	mp := NewMjpegproxy()
	response := &http.Response{
		Status:     "200 OK",
		StatusCode: 200,
		Proto:      "HTTP/1.0",
		Header: http.Header{
			"Content-Type": []string{"multipart/x-mixed-replace; boundary=" + boundary},
		},
		Body:          ioutil.NopCloser(bytes.NewBufferString(validBody)),
		ContentLength: int64(len(validBody)),
	}
	_, err := mp.getboundary(response)
	if err != nil {
		t.Fatal(err)
	}
}

func Test_getboundary_noboundary(t *testing.T) {
	mp := NewMjpegproxy()
	response := &http.Response{
		Status:     "200 OK",
		StatusCode: 200,
		Proto:      "HTTP/1.0",
		Header: http.Header{
			"Content-Type": []string{"multipart/x-mixed-replace"},
		},
		Body:          ioutil.NopCloser(bytes.NewBufferString(validBody)),
		ContentLength: int64(len(validBody)),
	}
	_, err := mp.getboundary(response)
	if err == nil {
		t.Fatal("Unexpected nil error!")
	}
	if !strings.Contains(err.Error(), "No multipart boundary param in Content-Type!") {
		t.Fatalf("Wrong error: %s", err.Error())
	}
}

func Test_getboundary_wrong(t *testing.T) {
	mp := NewMjpegproxy()
	response := &http.Response{
		Status:     "200 OK",
		StatusCode: 200,
		Proto:      "HTTP/1.0",
		Header: http.Header{
			"Content-Type": []string{"multipart/form-data"},
		},
		Body:          ioutil.NopCloser(bytes.NewBufferString(validBody)),
		ContentLength: int64(len(validBody)),
	}
	_, err := mp.getboundary(response)
	if err == nil {
		t.Fatal("Unexpected nil error!")
	}
	if !strings.Contains(err.Error(), "Wrong Content-Type: expected multipart/x-mixed-replace, got multipart/form-data") {
		t.Fatalf("Wrong error: %s", err.Error())
	}

}

func Test_getboundary_invalid(t *testing.T) {
	mp := NewMjpegproxy()
	response := &http.Response{
		Status:     "200 OK",
		StatusCode: 200,
		Proto:      "HTTP/1.0",
		Header: http.Header{
			"Content-Type": []string{"multipart/"},
		},
		Body:          ioutil.NopCloser(bytes.NewBufferString(validBody)),
		ContentLength: int64(len(validBody)),
	}
	_, err := mp.getboundary(response)
	if err == nil {
		t.Fatal("Unexpected nil error!")
	}
	if !strings.Contains(err.Error(), "mime: expected token after slash") {
		t.Fatalf("Wrong error: %s", err.Error())
	}

}

func Test_getboundary_noCT(t *testing.T) {
	mp := NewMjpegproxy()
	response := &http.Response{
		Status:        "200 OK",
		StatusCode:    200,
		Proto:         "HTTP/1.0",
		Header:        http.Header{},
		Body:          ioutil.NopCloser(bytes.NewBufferString(validBody)),
		ContentLength: int64(len(validBody)),
	}
	_, err := mp.getboundary(response)
	if err == nil {
		t.Fatal("Unexpected nil error!")
	}
	if !strings.Contains(err.Error(), "Content-Type isn't specified!") {
		t.Fatalf("Wrong error: %s", err.Error())
	}

}

func Test_ServeHTTP(t *testing.T) {
	mp := NewMjpegproxy()
	req := &http.Request{}
	testString := []byte("Test String")
	mp.curImg.Write(testString)
	recorder := httptest.NewRecorder()
	mp.ServeHTTP(recorder, req)
	if time.Since(mp.lastConn) > time.Second {
		t.Fatal("Unexpected lastconn value!")
	}
	if !bytes.Equal(recorder.Body.Bytes(), testString) {
		t.Fatalf("Content mismatch: expected %s, got %s", string(testString), string(mp.curImg.Bytes()))
	}
}

func Test_OpenStream_logic(t *testing.T) {
	user := "user"
	pass := "pass"
	mp := NewMjpegproxy()
	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "multipart/x-mixed-replace;boundary="+boundary)
		fmt.Fprintln(w, streamBody)
	}))
	defer ts.Close()
	mp.setRunning(true)
	mp.OpenStream(ts.URL, user, pass, time.Second)
	defer mp.CloseStream()
	time.Sleep(time.Second)
	mp.conChan <- time.Now()
	time.Sleep(time.Millisecond*50)
	if !strings.Contains(mp.curImg.String(), firstPart) {
		t.Fatalf("Wrong response: expected %s, got %s", firstPart, mp.curImg.String())
	}
	mp.setRunning(false)
}
<|endoftext|>