<commit_before>/*
Copyright 2021 The Knative 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 broker

import (
	"context"
	"errors"

	"github.com/google/uuid"

	. "github.com/cloudevents/sdk-go/v2/test"
	"knative.dev/eventing/test/rekt/resources/broker"
	"knative.dev/reconciler-test/pkg/eventshub"
	"knative.dev/reconciler-test/pkg/feature"
	"knative.dev/reconciler-test/pkg/state"
	eventshubmain "knative.dev/reconciler-test/pkg/test_images/eventshub"
)

func DataPlaneConformance(brokerName string) *feature.FeatureSet {
	return &feature.FeatureSet{
		Name: "Knative Broker Specification - Data Plane",
		Features: []feature.Feature{
			*DataPlaneIngress(brokerName),
			*DataPlaneDelivery(brokerName),
			*DataPlaneObservability(brokerName),
		},
	}
}

type EventInfoCombined struct {
	sent     eventshubmain.EventInfo
	response eventshubmain.EventInfo
}

func DataPlaneIngress(brokerName string) *feature.Feature {
	f := feature.NewFeatureNamed("Ingress")

	f.Setup("Set Broker Name", func(ctx context.Context, t feature.T) {
		state.SetOrFail(ctx, t, "brokerName", brokerName)
	})

	f.Stable("Conformance").
		Should("A Broker SHOULD expose either an HTTP or HTTPS endpoint as ingress. It MAY expose both.",
			todo).
		Must("The ingress endpoint(s) MUST conform to at least one of the following versions of the specification: 0.3, 1.0",
			brokerAcceptsCEVersions).
		May("Other versions MAY be rejected.",
			todo).
		ShouldNot("The Broker SHOULD NOT perform an upgrade of the produced event's CloudEvents version.",
			todo).
		Should("It SHOULD support both Binary Content Mode and Structured Content Mode of the HTTP Protocol Binding for CloudEvents.",
			todo).
		May("The HTTP(S) endpoint MAY be on any port, not just the standard 80 and 443.",
			todo).
		May("Channels MAY expose other, non-HTTP endpoints in addition to HTTP at their discretion.",
			todo).
		Must("Brokers MUST reject all HTTP produce requests with a method other than POST responding with HTTP status code `405 Method Not Supported`.",
			todo).
		Must("The Broker MUST respond with a 200-level HTTP status code if a produce request is accepted.",
			todo).
		Must("If a Broker receives a produce request and is unable to parse a valid CloudEvent, then it MUST reject the request with HTTP status code `400 Bad Request`.",
			todo)

	return f
}

func DataPlaneDelivery(brokerName string) *feature.Feature {
	f := feature.NewFeatureNamed("Delivery")

	f.Setup("Set Broker Name", func(ctx context.Context, t feature.T) {
		state.SetOrFail(ctx, t, "brokerName", brokerName)
	})

	f.Stable("Conformance").
		Must("Delivered events MUST conform to the CloudEvents specification.",
			todo).
		Should("All CloudEvent attributes set by the producer, including the data and specversion attributes, SHOULD be received at the subscriber identical to how they were received by the Broker.",
			todo).
		Should("The Broker SHOULD support delivering events via Binary Content Mode or Structured Content Mode of the HTTP Protocol Binding for CloudEvents.",
			todo).
		Should("Events accepted by the Broker SHOULD be delivered at least once to all subscribers of all Triggers*.",
			todo).
		//*
		//1. are Ready when the produce request was received,
		//1. specify filters that match the event, and
		//1. exist when the event is able to be delivered.
		May("Events MAY additionally be delivered to Triggers that become Ready after the event was accepted.",
			todo).
		May("Events MAY be enqueued or delayed between acceptance from a producer and delivery to a subscriber.",
			todo).
		May("The Broker MAY choose not to deliver an event due to persistent unavailability of a subscriber or limitations such as storage capacity.",
			todo).
		Should("The Broker SHOULD attempt to notify the operator in this case.",
			todo).
		May("The Broker MAY forward these events to an alternate endpoint or storage mechanism such as a dead letter queue.",
			todo).
		May("If no ready Trigger would match an accepted event, the Broker MAY drop that event without notifying the producer.",
			todo).
		May("If multiple Triggers reference the same subscriber, the subscriber MAY be expected to acknowledge successful delivery of an event multiple times.",
			todo).
		Should("Events contained in delivery responses SHOULD be published to the Broker ingress and processed as if the event had been produced to the Broker's addressable endpoint.",
			todo).
		Should("Events contained in delivery responses that are malformed SHOULD be treated as if the event delivery had failed.",
			todo).
		May("The subscriber MAY receive a confirmation that a reply event was accepted by the Broker.",
			todo).
		Should("If the reply event was not accepted, the initial event SHOULD be redelivered to the subscriber.",
			todo)

	return f
}

func DataPlaneObservability(brokerName string) *feature.Feature {
	f := feature.NewFeatureNamed("Observability")

	f.Setup("Set Broker Name", func(ctx context.Context, t feature.T) {
		state.SetOrFail(ctx, t, "brokerName", brokerName)
	})

	f.Stable("Conformance").
		Should("The Broker SHOULD expose a variety of metrics*.",
			todo).
		// * including, but not limited to:
		//- Number of malformed produce requests (400-level responses)
		//- Number of accepted produce requests (200-level responses)
		//- Number of events delivered

		Should("Metrics SHOULD be enabled by default, with a configuration parameter included to disable them if desired.",
			todo).
		Should("Upon receiving an event with context attributes defined in the CloudEvents Distributed Tracing extension the Broker SHOULD preserve that trace header on delivery to subscribers and on reply events, unless the reply is sent with a different set of tracing attributes.",
			todo).
		Should("Forwarded trace headers SHOULD be updated with any intermediate spans emitted by the broker.",
			todo).
		Should("Spans emitted by the Broker SHOULD follow the OpenTelemetry Semantic Conventions for Messaging System*.",
			todo)
	// *
	//- messaging.system: "knative"
	//- messaging.destination: broker:name.namespace or trigger:name.namespace with
	//the Broker or Trigger to which the event is being routed
	//- messaging.protocol: the name of the underlying transport protocol
	//- messaging.message_id: the event ID

	return f
}

func todo(ctx context.Context, t feature.T) {
	t.Log("TODO, Implement this.")
}

func brokerAcceptsCEVersions(ctx context.Context, t feature.T) {
	brokerName := state.GetStringOrFail(ctx, t, "brokerName")

	u, err := broker.Address(ctx, brokerName)
	if err != nil || u == nil {
		t.Error("failed to get the address of the broker", brokerName, err)
	}

	opts := []eventshub.EventsHubOption{eventshub.StartSenderURL(u.String())}
	uuids := map[string]string{
		uuid.New().String(): "1.0",
		uuid.New().String(): "0.3",
	}
	for uuid, version := range uuids {
		// We need to use a different source name, otherwise, it will try to update
		// the pod, which is immutable.
		source := feature.MakeRandomK8sName("source")
		event := FullEvent()
		event.SetID(uuid)
		event.SetSpecVersion(version)
		opts = append(opts, eventshub.InputEvent(event))

		eventshub.Install(source, opts...)(ctx, t)
		store := eventshub.StoreFromContext(ctx, source)
		// We are looking for two events, one of them is the sent event and the other
		// is Response, so correlate them first. We want to make sure the event was sent and that the
		// response was what was expected.
		// TODO: Figure out how to send events via eventshub that are invalid (say, wrong version)
		events := correlate(store.AssertAtLeast(2, sentEventMatcher(uuid)))
		for _, e := range events {
			// Make sure HTTP response code is 2XX
			if e.response.StatusCode < 200 || e.response.StatusCode > 299 {
				t.Errorf("Expected statuscode 2XX for sequence %d got %d", e.response.Sequence, e.response.StatusCode)
			}
		}
	}
}

func sentEventMatcher(uuid string) func(eventshubmain.EventInfo) error {
	return func(ei eventshubmain.EventInfo) error {
		if (ei.Kind == eventshubmain.EventSent || ei.Kind == eventshubmain.EventResponse) && ei.SentId == uuid {
			return nil
		}
		return errors.New("no match")
	}
}

// correlate takes in an array of mixed Sent / Response events (matched with sentEventMatcher for example)
// and correlates them based on the sequence into a pair.
func correlate(in []eventshubmain.EventInfo) []EventInfoCombined {
	var out []EventInfoCombined
	// not too many events, this will suffice...
	for i, e := range in {
		if e.Kind == eventshubmain.EventSent {
			looking := e.Sequence
			for j := i + 1; j <= len(in)-1; j++ {
				if in[j].Kind == eventshubmain.EventResponse && in[j].Sequence == looking {
					out = append(out, EventInfoCombined{sent: e, response: in[j]})
				}
			}
		}
	}
	return out
}
<commit_msg>add test for wrong version of CE (#5124)<commit_after>/*
Copyright 2021 The Knative 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 broker

import (
	"context"
	"errors"

	"github.com/google/uuid"

	. "github.com/cloudevents/sdk-go/v2/test"
	"knative.dev/eventing/test/rekt/resources/broker"
	"knative.dev/reconciler-test/pkg/eventshub"
	"knative.dev/reconciler-test/pkg/feature"
	"knative.dev/reconciler-test/pkg/state"
	eventshubmain "knative.dev/reconciler-test/pkg/test_images/eventshub"
)

func DataPlaneConformance(brokerName string) *feature.FeatureSet {
	return &feature.FeatureSet{
		Name: "Knative Broker Specification - Data Plane",
		Features: []feature.Feature{
			*DataPlaneIngress(brokerName),
			*DataPlaneDelivery(brokerName),
			*DataPlaneObservability(brokerName),
		},
	}
}

type EventInfoCombined struct {
	sent     eventshubmain.EventInfo
	response eventshubmain.EventInfo
}

func DataPlaneIngress(brokerName string) *feature.Feature {
	f := feature.NewFeatureNamed("Ingress")

	f.Setup("Set Broker Name", func(ctx context.Context, t feature.T) {
		state.SetOrFail(ctx, t, "brokerName", brokerName)
	})

	f.Stable("Conformance").
		Should("A Broker SHOULD expose either an HTTP or HTTPS endpoint as ingress. It MAY expose both.",
			todo).
		Must("The ingress endpoint(s) MUST conform to at least one of the following versions of the specification: 0.3, 1.0",
			brokerAcceptsCEVersions).
		May("Other versions MAY be rejected.",
			brokerRejectsUnknownCEVersion).
		ShouldNot("The Broker SHOULD NOT perform an upgrade of the produced event's CloudEvents version.",
			todo).
		Should("It SHOULD support both Binary Content Mode and Structured Content Mode of the HTTP Protocol Binding for CloudEvents.",
			todo).
		May("The HTTP(S) endpoint MAY be on any port, not just the standard 80 and 443.",
			todo).
		May("Channels MAY expose other, non-HTTP endpoints in addition to HTTP at their discretion.",
			todo).
		Must("Brokers MUST reject all HTTP produce requests with a method other than POST responding with HTTP status code `405 Method Not Supported`.",
			todo).
		Must("The Broker MUST respond with a 200-level HTTP status code if a produce request is accepted.",
			todo).
		Must("If a Broker receives a produce request and is unable to parse a valid CloudEvent, then it MUST reject the request with HTTP status code `400 Bad Request`.",
			todo)

	return f
}

func DataPlaneDelivery(brokerName string) *feature.Feature {
	f := feature.NewFeatureNamed("Delivery")

	f.Setup("Set Broker Name", func(ctx context.Context, t feature.T) {
		state.SetOrFail(ctx, t, "brokerName", brokerName)
	})

	f.Stable("Conformance").
		Must("Delivered events MUST conform to the CloudEvents specification.",
			todo).
		Should("All CloudEvent attributes set by the producer, including the data and specversion attributes, SHOULD be received at the subscriber identical to how they were received by the Broker.",
			todo).
		Should("The Broker SHOULD support delivering events via Binary Content Mode or Structured Content Mode of the HTTP Protocol Binding for CloudEvents.",
			todo).
		Should("Events accepted by the Broker SHOULD be delivered at least once to all subscribers of all Triggers*.",
			todo).
		//*
		//1. are Ready when the produce request was received,
		//1. specify filters that match the event, and
		//1. exist when the event is able to be delivered.
		May("Events MAY additionally be delivered to Triggers that become Ready after the event was accepted.",
			todo).
		May("Events MAY be enqueued or delayed between acceptance from a producer and delivery to a subscriber.",
			todo).
		May("The Broker MAY choose not to deliver an event due to persistent unavailability of a subscriber or limitations such as storage capacity.",
			todo).
		Should("The Broker SHOULD attempt to notify the operator in this case.",
			todo).
		May("The Broker MAY forward these events to an alternate endpoint or storage mechanism such as a dead letter queue.",
			todo).
		May("If no ready Trigger would match an accepted event, the Broker MAY drop that event without notifying the producer.",
			todo).
		May("If multiple Triggers reference the same subscriber, the subscriber MAY be expected to acknowledge successful delivery of an event multiple times.",
			todo).
		Should("Events contained in delivery responses SHOULD be published to the Broker ingress and processed as if the event had been produced to the Broker's addressable endpoint.",
			todo).
		Should("Events contained in delivery responses that are malformed SHOULD be treated as if the event delivery had failed.",
			todo).
		May("The subscriber MAY receive a confirmation that a reply event was accepted by the Broker.",
			todo).
		Should("If the reply event was not accepted, the initial event SHOULD be redelivered to the subscriber.",
			todo)

	return f
}

func DataPlaneObservability(brokerName string) *feature.Feature {
	f := feature.NewFeatureNamed("Observability")

	f.Setup("Set Broker Name", func(ctx context.Context, t feature.T) {
		state.SetOrFail(ctx, t, "brokerName", brokerName)
	})

	f.Stable("Conformance").
		Should("The Broker SHOULD expose a variety of metrics*.",
			todo).
		// * including, but not limited to:
		//- Number of malformed produce requests (400-level responses)
		//- Number of accepted produce requests (200-level responses)
		//- Number of events delivered

		Should("Metrics SHOULD be enabled by default, with a configuration parameter included to disable them if desired.",
			todo).
		Should("Upon receiving an event with context attributes defined in the CloudEvents Distributed Tracing extension the Broker SHOULD preserve that trace header on delivery to subscribers and on reply events, unless the reply is sent with a different set of tracing attributes.",
			todo).
		Should("Forwarded trace headers SHOULD be updated with any intermediate spans emitted by the broker.",
			todo).
		Should("Spans emitted by the Broker SHOULD follow the OpenTelemetry Semantic Conventions for Messaging System*.",
			todo)
	// *
	//- messaging.system: "knative"
	//- messaging.destination: broker:name.namespace or trigger:name.namespace with
	//the Broker or Trigger to which the event is being routed
	//- messaging.protocol: the name of the underlying transport protocol
	//- messaging.message_id: the event ID

	return f
}

func todo(ctx context.Context, t feature.T) {
	t.Log("TODO, Implement this.")
}

func brokerAcceptsCEVersions(ctx context.Context, t feature.T) {
	brokerName := state.GetStringOrFail(ctx, t, "brokerName")

	u, err := broker.Address(ctx, brokerName)
	if err != nil || u == nil {
		t.Error("failed to get the address of the broker", brokerName, err)
	}

	opts := []eventshub.EventsHubOption{eventshub.StartSenderURL(u.String())}
	uuids := map[string]string{
		uuid.New().String(): "1.0",
		uuid.New().String(): "0.3",
	}
	for uuid, version := range uuids {
		// We need to use a different source name, otherwise, it will try to update
		// the pod, which is immutable.
		source := feature.MakeRandomK8sName("source")
		event := FullEvent()
		event.SetID(uuid)
		event.SetSpecVersion(version)
		opts = append(opts, eventshub.InputEvent(event))

		eventshub.Install(source, opts...)(ctx, t)
		store := eventshub.StoreFromContext(ctx, source)
		// We are looking for two events, one of them is the sent event and the other
		// is Response, so correlate them first. We want to make sure the event was sent and that the
		// response was what was expected.
		events := correlate(store.AssertAtLeast(2, sentEventMatcher(uuid)))
		for _, e := range events {
			// Make sure HTTP response code is 2XX
			if e.response.StatusCode < 200 || e.response.StatusCode > 299 {
				t.Errorf("Expected statuscode 2XX for sequence %d got %d", e.response.Sequence, e.response.StatusCode)
			}
		}
	}
}

func brokerRejectsUnknownCEVersion(ctx context.Context, t feature.T) {
	brokerName := state.GetStringOrFail(ctx, t, "brokerName")

	u, err := broker.Address(ctx, brokerName)
	if err != nil || u == nil {
		t.Error("failed to get the address of the broker", brokerName, err)
	}

	uuids := map[string]string{
		uuid.New().String(): "19.0",
	}
	for uuid, version := range uuids {
		// We need to use a different source name, otherwise, it will try to update
		// the pod, which is immutable.
		source := feature.MakeRandomK8sName("source")
		eventshub.Install(source,
			eventshub.StartSenderToResource(broker.Gvr(), brokerName),
			eventshub.InputHeader("ce-specversion", version),
			eventshub.InputHeader("ce-type", "sometype"),
			eventshub.InputHeader("ce-source", "400.request.sender.test.knative.dev"),
			eventshub.InputHeader("ce-id", uuid),
			eventshub.InputBody("{}}"),
		)(ctx, t)

		store := eventshub.StoreFromContext(ctx, source)
		// We are looking for two events, one of them is the sent event and the other
		// is Response, so correlate them first. We want to make sure the event was sent and that the
		// response was what was expected.
		// Note: We pass in "" for the match ID because when we construct the headers manually
		// above, they do not get stuff into the sent/response SentId fields.
		events := correlate(store.AssertAtLeast(2, sentEventMatcher("")))
		for _, e := range events {
			// Make sure HTTP response code is 4XX
			if e.response.StatusCode < 400 || e.response.StatusCode > 499 {
				t.Errorf("Expected statuscode 4XX for sequence %d got %d", e.response.Sequence, e.response.StatusCode)
			}
		}
	}
}

func sentEventMatcher(uuid string) func(eventshubmain.EventInfo) error {
	return func(ei eventshubmain.EventInfo) error {
		if (ei.Kind == eventshubmain.EventSent || ei.Kind == eventshubmain.EventResponse) && ei.SentId == uuid {
			return nil
		}
		return errors.New("no match")
	}
}

// correlate takes in an array of mixed Sent / Response events (matched with sentEventMatcher for example)
// and correlates them based on the sequence into a pair.
func correlate(in []eventshubmain.EventInfo) []EventInfoCombined {
	var out []EventInfoCombined
	// not too many events, this will suffice...
	for i, e := range in {
		if e.Kind == eventshubmain.EventSent {
			looking := e.Sequence
			for j := i + 1; j <= len(in)-1; j++ {
				if in[j].Kind == eventshubmain.EventResponse && in[j].Sequence == looking {
					out = append(out, EventInfoCombined{sent: e, response: in[j]})
				}
			}
		}
	}
	return out
}
<|endoftext|>


<commit_before>package main

import (
  "encoding/json"
  "fmt"
  "net/http"
  "strconv"
  "github.com/rcrowley/go-tigertonic"
  "github.com/rkbodenner/parallel_universe/collection"
  "github.com/rkbodenner/parallel_universe/game"
  "github.com/rkbodenner/parallel_universe/session"
)

var players = []*game.Player{
  &game.Player{1, "Player One"},
  &game.Player{2, "Player Two"},
}

var gameCollection = collection.NewCollection()
var gameIndex = make(map[uint64]*game.Game)

func initGameData() {
  for i,game := range gameCollection.Games {
    game.Id = (uint)(i+1)
    gameIndex[(uint64)(i+1)] = game
  }
}

var sessions []*session.Session
var sessionIndex = make(map[uint64]*session.Session)

func initSessionData() {
  sessions = make([]*session.Session, 2)
  sessions[0] = session.NewSession(gameCollection.Games[0], players)
  sessions[0].Step(players[0])
  sessions[0].Step(players[1])

  sessions[1] = session.NewSession(gameCollection.Games[1], players)
  sessions[1].Step(players[0])
  sessions[1].Step(players[1])

  for i,session := range sessions {
    session.Id = (uint)(i+1)
    sessionIndex[(uint64)(i+1)] = session
  }
}

func corsHandler(handler func(http.ResponseWriter, *http.Request)) func(http.ResponseWriter, *http.Request) {
  return func(w http.ResponseWriter, r *http.Request) {
    header := w.Header()
    header.Add("Access-Control-Allow-Origin", "http://localhost:8000")
    handler(w, r)
  }
}

func collectionHandler(w http.ResponseWriter, r *http.Request) {
  err := json.NewEncoder(w).Encode(gameCollection)
  if ( nil != err ) {
    fmt.Fprintln(w, err)
  }
}

func gameHandler(w http.ResponseWriter, r *http.Request) {
  id_str := r.URL.Query().Get("id")
  id, err := strconv.ParseUint(id_str, 10, 64)
  if nil != err {
    http.Error(w, "Not found", http.StatusNotFound)
    return
  }

  game, ok := gameIndex[id]
  if ok {
    err := json.NewEncoder(w).Encode(game)
    if ( nil != err ) {
      http.Error(w, "Error", http.StatusInternalServerError)
    }
  } else {
    http.Error(w, "Not found", http.StatusNotFound)
  }
}

func playersHandler(w http.ResponseWriter, r *http.Request) {
  err := json.NewEncoder(w).Encode(players)
  if ( nil != err ) {
    fmt.Fprintln(w, err)
  }
}

func sessionsHandler(w http.ResponseWriter, r *http.Request) {
  err := json.NewEncoder(w).Encode(sessions)
  if ( nil != err ) {
    http.Error(w, "Error", http.StatusInternalServerError)
  }
}

func sessionHandler(w http.ResponseWriter, r *http.Request) {
  id_str := r.URL.Query().Get("id")
  id, err := strconv.ParseUint(id_str, 10, 64)
  if nil != err {
    http.Error(w, "Not found", http.StatusNotFound)
    return
  }

  session, ok := sessionIndex[id]
  if ok {
    err := json.NewEncoder(w).Encode(session)
    if ( nil != err ) {
      http.Error(w, "Error", http.StatusInternalServerError)
    }
  } else {
    http.Error(w, "Not found", http.StatusNotFound)
  }
}

func main() {
  initGameData()
  initSessionData()

  mux := tigertonic.NewTrieServeMux()
  mux.HandleFunc("GET", "/games", corsHandler(collectionHandler))
  mux.HandleFunc("GET", "/games/{id}", corsHandler(gameHandler))
  mux.HandleFunc("GET", "/players", corsHandler(playersHandler))
  mux.HandleFunc("GET", "/sessions", corsHandler(sessionsHandler))
  mux.HandleFunc("GET", "/sessions/{id}", corsHandler(sessionHandler))
  http.ListenAndServe(":8080", mux)
}
<commit_msg>Dubious endpoint to update (i.e., finish) a step on a session<commit_after>package main

import (
  "encoding/json"
  "fmt"
  "net/http"
  "net/url"
  "strconv"
  "github.com/rcrowley/go-tigertonic"
  "github.com/rkbodenner/parallel_universe/collection"
  "github.com/rkbodenner/parallel_universe/game"
  "github.com/rkbodenner/parallel_universe/session"
)

var players = []*game.Player{
  &game.Player{1, "Player One"},
  &game.Player{2, "Player Two"},
}

var gameCollection = collection.NewCollection()
var gameIndex = make(map[uint64]*game.Game)

func initGameData() {
  for i,game := range gameCollection.Games {
    game.Id = (uint)(i+1)
    gameIndex[(uint64)(i+1)] = game
  }
}

var sessions []*session.Session
var sessionIndex = make(map[uint64]*session.Session)

func initSessionData() {
  sessions = make([]*session.Session, 2)
  sessions[0] = session.NewSession(gameCollection.Games[0], players)
  sessions[0].Step(players[0])
  sessions[0].Step(players[1])

  sessions[1] = session.NewSession(gameCollection.Games[1], players)
  sessions[1].Step(players[0])
  sessions[1].Step(players[1])

  for i,session := range sessions {
    session.Id = (uint)(i+1)
    sessionIndex[(uint64)(i+1)] = session
  }
}

func corsHandler(handler func(http.ResponseWriter, *http.Request)) func(http.ResponseWriter, *http.Request) {
  return func(w http.ResponseWriter, r *http.Request) {
    header := w.Header()
    header.Add("Access-Control-Allow-Origin", "http://localhost:8000")
    handler(w, r)
  }
}

type CollectionHandler struct{}
func (h CollectionHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  err := json.NewEncoder(w).Encode(gameCollection)
  if ( nil != err ) {
    fmt.Fprintln(w, err)
  }
}

type GameHandler struct{}
func (h GameHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  id_str := r.URL.Query().Get("id")
  id, err := strconv.ParseUint(id_str, 10, 64)
  if nil != err {
    http.Error(w, "Not found", http.StatusNotFound)
    return
  }

  game, ok := gameIndex[id]
  if ok {
    err := json.NewEncoder(w).Encode(game)
    if ( nil != err ) {
      http.Error(w, "Error", http.StatusInternalServerError)
    }
  } else {
    http.Error(w, "Not found", http.StatusNotFound)
  }
}

type PlayersHandler struct{}
func (h PlayersHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  err := json.NewEncoder(w).Encode(players)
  if ( nil != err ) {
    fmt.Fprintln(w, err)
  }
}

type SessionsHandler struct{}
func (h SessionsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  err := json.NewEncoder(w).Encode(sessions)
  if ( nil != err ) {
    http.Error(w, "Error", http.StatusInternalServerError)
  }
}

type SessionHandler struct{}
func (h SessionHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  id_str := r.URL.Query().Get("id")
  id, err := strconv.ParseUint(id_str, 10, 64)
  if nil != err {
    http.Error(w, "Not found", http.StatusNotFound)
    return
  }

  session, ok := sessionIndex[id]
  if ok {
    err := json.NewEncoder(w).Encode(session)
    if ( nil != err ) {
      http.Error(w, "Error", http.StatusInternalServerError)
    }
  } else {
    http.Error(w, "Not found", http.StatusNotFound)
  }
}

type StepHandler struct{}
func (h StepHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  session_id_str := r.URL.Query().Get("session_id")
  session_id, err := strconv.ParseUint(session_id_str, 10, 64)
  if nil != err {
    http.Error(w, "Not found", http.StatusNotFound)
    return
  }

  step_desc,err := url.QueryUnescape(r.URL.Query().Get("step_desc"))
  for _,step := range sessionIndex[session_id].SetupSteps {
    if ( step.GetRule().Description == step_desc ) {
      step.Finish()  // FIXME. Should look in request data to see what to change.
      return
    }
  }
  http.Error(w, "Not found", http.StatusNotFound)
}

func main() {
  initGameData()
  initSessionData()

  mux := tigertonic.NewTrieServeMux()
  cors := tigertonic.NewCORSBuilder().AddAllowedOrigins("http://localhost:8000")
  mux.Handle("GET", "/games", cors.Build(CollectionHandler{}))
  mux.Handle("GET", "/games/{id}", cors.Build(GameHandler{}))
  mux.Handle("GET", "/players", cors.Build(PlayersHandler{}))
  mux.Handle("GET", "/sessions", cors.Build(SessionsHandler{}))
  mux.Handle("GET", "/sessions/{id}", cors.Build(SessionHandler{}))
  mux.Handle("PUT", "/sessions/{session_id}/steps/{step_desc}", cors.Build(StepHandler{}))
  http.ListenAndServe(":8080", mux)
}
<|endoftext|>


<commit_before>package meta

import (
	"fmt"
	"sort"
	"strconv"
	"strings"
	"time"
)

const (
	stationCode = iota
	stationNetwork
	stationName
	stationLatitude
	stationLongitude
	stationElevation
	stationDepth
	stationDatum
	stationStart
	stationEnd
	stationLast
)

type Station struct {
	Reference
	Point
	Span
}

type StationList []Station

func (s StationList) Len() int           { return len(s) }
func (s StationList) Swap(i, j int)      { s[i], s[j] = s[j], s[i] }
func (s StationList) Less(i, j int) bool { return s[i].Code < s[j].Code }

func (s StationList) encode() [][]string {
	data := [][]string{{
		"Station",
		"Network",
		"Name",
		"Latitude",
		"Longitude",
		"Elevation",
		"Depth",
		"Datum",
		"Start Date",
		"End Date",
	}}
	for _, v := range s {
		data = append(data, []string{
			strings.TrimSpace(v.Code),
			strings.TrimSpace(v.Network),
			strings.TrimSpace(v.Name),
			strings.TrimSpace(v.latitude),
			strings.TrimSpace(v.longitude),
			strings.TrimSpace(v.elevation),
			strings.TrimSpace(v.depth),
			strings.TrimSpace(v.Datum),
			v.Start.Format(DateTimeFormat),
			v.End.Format(DateTimeFormat),
		})
	}
	return data
}

func (s *StationList) decode(data [][]string) error {
	var stations []Station
	if len(data) > 1 {
		for _, d := range data[1:] {
			if len(d) != stationLast {
				return fmt.Errorf("incorrect number of installed station fields")
			}
			var err error

			var lat, lon, elev, depth float64
			if lat, err = strconv.ParseFloat(d[stationLatitude], 64); err != nil {
				return err
			}
			if lon, err = strconv.ParseFloat(d[stationLongitude], 64); err != nil {
				return err
			}
			if d[stationElevation] != "" {
				if elev, err = strconv.ParseFloat(d[stationElevation], 64); err != nil {
					return err
				}
			}
			if d[stationDepth] != "" {
				if depth, err = strconv.ParseFloat(d[stationDepth], 64); err != nil {
					return err
				}
			}

			var start, end time.Time
			if start, err = time.Parse(DateTimeFormat, d[stationStart]); err != nil {
				return err
			}
			if end, err = time.Parse(DateTimeFormat, d[stationEnd]); err != nil {
				return err
			}

			stations = append(stations, Station{
				Reference: Reference{
					Code:    strings.TrimSpace(d[stationCode]),
					Network: strings.TrimSpace(d[stationNetwork]),
					Name:    strings.TrimSpace(d[stationName]),
				},
				Span: Span{
					Start: start,
					End:   end,
				},
				Point: Point{
					Latitude:  lat,
					Longitude: lon,
					Elevation: elev,
					Datum:     strings.TrimSpace(d[stationDatum]),
					Depth:     depth,

					latitude:  strings.TrimSpace(d[stationLatitude]),
					longitude: strings.TrimSpace(d[stationLongitude]),
					elevation: strings.TrimSpace(d[stationElevation]),
					depth:     strings.TrimSpace(d[stationDepth]),
				},
			})
		}

		*s = StationList(stations)
	}
	return nil
}

func LoadStations(path string) ([]Station, error) {
	var s []Station

	if err := LoadList(path, (*StationList)(&s)); err != nil {
		return nil, err
	}

	sort.Sort(StationList(s))

	return s, nil
}
<commit_msg>update station sort order<commit_after>package meta

import (
	"fmt"
	"sort"
	"strconv"
	"strings"
	"time"
)

const (
	stationCode = iota
	stationNetwork
	stationName
	stationLatitude
	stationLongitude
	stationElevation
	stationDepth
	stationDatum
	stationStart
	stationEnd
	stationLast
)

type Station struct {
	Reference
	Point
	Span
}

type StationList []Station

func (s StationList) Len() int      { return len(s) }
func (s StationList) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
func (s StationList) Less(i, j int) bool {
	switch {
	case s[i].Code < s[j].Code:
		return true
	case s[i].Code > s[j].Code:
		return false
	case s[i].Network < s[j].Network:
		return true
	default:
		return false
	}
}

func (s StationList) encode() [][]string {
	data := [][]string{{
		"Station",
		"Network",
		"Name",
		"Latitude",
		"Longitude",
		"Elevation",
		"Depth",
		"Datum",
		"Start Date",
		"End Date",
	}}
	for _, v := range s {
		data = append(data, []string{
			strings.TrimSpace(v.Code),
			strings.TrimSpace(v.Network),
			strings.TrimSpace(v.Name),
			strings.TrimSpace(v.latitude),
			strings.TrimSpace(v.longitude),
			strings.TrimSpace(v.elevation),
			strings.TrimSpace(v.depth),
			strings.TrimSpace(v.Datum),
			v.Start.Format(DateTimeFormat),
			v.End.Format(DateTimeFormat),
		})
	}
	return data
}

func (s *StationList) decode(data [][]string) error {
	var stations []Station
	if len(data) > 1 {
		for _, d := range data[1:] {
			if len(d) != stationLast {
				return fmt.Errorf("incorrect number of installed station fields")
			}
			var err error

			var lat, lon, elev, depth float64
			if lat, err = strconv.ParseFloat(d[stationLatitude], 64); err != nil {
				return err
			}
			if lon, err = strconv.ParseFloat(d[stationLongitude], 64); err != nil {
				return err
			}
			if d[stationElevation] != "" {
				if elev, err = strconv.ParseFloat(d[stationElevation], 64); err != nil {
					return err
				}
			}
			if d[stationDepth] != "" {
				if depth, err = strconv.ParseFloat(d[stationDepth], 64); err != nil {
					return err
				}
			}

			var start, end time.Time
			if start, err = time.Parse(DateTimeFormat, d[stationStart]); err != nil {
				return err
			}
			if end, err = time.Parse(DateTimeFormat, d[stationEnd]); err != nil {
				return err
			}

			stations = append(stations, Station{
				Reference: Reference{
					Code:    strings.TrimSpace(d[stationCode]),
					Network: strings.TrimSpace(d[stationNetwork]),
					Name:    strings.TrimSpace(d[stationName]),
				},
				Span: Span{
					Start: start,
					End:   end,
				},
				Point: Point{
					Latitude:  lat,
					Longitude: lon,
					Elevation: elev,
					Datum:     strings.TrimSpace(d[stationDatum]),
					Depth:     depth,

					latitude:  strings.TrimSpace(d[stationLatitude]),
					longitude: strings.TrimSpace(d[stationLongitude]),
					elevation: strings.TrimSpace(d[stationElevation]),
					depth:     strings.TrimSpace(d[stationDepth]),
				},
			})
		}

		*s = StationList(stations)
	}
	return nil
}

func LoadStations(path string) ([]Station, error) {
	var s []Station

	if err := LoadList(path, (*StationList)(&s)); err != nil {
		return nil, err
	}

	sort.Sort(StationList(s))

	return s, nil
}
<|endoftext|>


<commit_before>package db

import (
	"database/sql"
	"fmt"
	"strings"
	"time"

	"github.com/shieldproject/shield/timespec"
)

type Job struct {
	TenantUUID string `json:"-" mbus:"tenant_uuid"`
	TargetUUID string `json:"-" mbus:"target_uuid"`
	StoreUUID  string `json:"-" mbus:"store_uuid"`

	UUID     string `json:"uuid"      mbus:"uuid"`
	Name     string `json:"name"      mbus:"name"`
	Summary  string `json:"summary"   mbus:"summary"`
	KeepN    int    `json:"keep_n"    mbus:"keep_n"`
	KeepDays int    `json:"keep_days" mbus:"keep_days"`
	Schedule string `json:"schedule"  mbus:"schedule"`
	Paused   bool   `json:"paused"    mbus:"paused"`
	FixedKey bool   `json:"fixed_key" mbus:"fixed_key"`

	Target struct {
		UUID        string `json:"uuid"`
		Name        string `json:"name"`
		Agent       string `json:"agent"`
		Plugin      string `json:"plugin"`
		Compression string `json:"compression"`

		Endpoint string                 `json:"endpoint,omitempty"`
		Config   map[string]interface{} `json:"config,omitempty"`
	} `json:"target"`

	Store struct {
		UUID    string `json:"uuid"`
		Name    string `json:"name"`
		Agent   string `json:"agent"`
		Plugin  string `json:"plugin"`
		Summary string `json:"summary"`
		Healthy bool   `json:"healthy"`

		Endpoint string                 `json:"endpoint,omitempty"`
		Config   map[string]interface{} `json:"config,omitempty"`
	} `json:"store"`

	Agent string `json:"agent"`

	Healthy        bool   `json:"healthy" mbus:"healthy"`
	LastRun        int64  `json:"last_run"`
	LastTaskStatus string `json:"last_task_status"`

	Spec    *timespec.Spec `json:"-"`
	NextRun int64          `json:"-"`
}

type JobFilter struct {
	UUID         string
	SkipPaused   bool
	SkipUnpaused bool

	Overdue bool

	SearchName string

	ForTenant  string
	ForTarget  string
	ForStore   string
	ExactMatch bool
}

func (f *JobFilter) Query() (string, []interface{}) {
	wheres := []string{}
	args := []interface{}{}

	if f.UUID != "" {
		if f.ExactMatch {
			wheres = []string{"j.uuid = ?"}
			args = append(args, f.UUID)
		} else {
			wheres = []string{"j.uuid LIKE ? ESCAPE '/'"}
			args = append(args, PatternPrefix(f.UUID))
		}
	}

	if f.SearchName != "" {
		comparator := "LIKE"
		toAdd := Pattern(f.SearchName)
		if f.ExactMatch {
			comparator = "="
			toAdd = f.SearchName
		}
		wheres = append(wheres, fmt.Sprintf("j.name %s ?", comparator))
		args = append(args, toAdd)
	}

	if len(wheres) == 0 {
		wheres = []string{"1"}
	} else if len(wheres) > 1 {
		wheres = []string{strings.Join(wheres, " OR ")}
	}

	if f.ForTenant != "" {
		wheres = append(wheres, "j.tenant_uuid = ?")
		args = append(args, f.ForTenant)
	}
	if f.ForTarget != "" {
		wheres = append(wheres, "j.target_uuid = ?")
		args = append(args, f.ForTarget)
	}
	if f.ForStore != "" {
		wheres = append(wheres, "j.store_uuid = ?")
		args = append(args, f.ForStore)
	}
	if f.SkipPaused || f.SkipUnpaused {
		wheres = append(wheres, "j.paused = ?")
		if f.SkipPaused {
			args = append(args, 0)
		} else {
			args = append(args, 1)
		}
	}
	if f.Overdue {
		wheres = append(wheres, "j.next_run <= ?")
		args = append(args, time.Now().Unix())
	}

	return `
	   WITH recent_tasks AS (
	           SELECT uuid AS task_uuid, job_uuid, started_at, status
	             FROM tasks
	            WHERE stopped_at IS NOT NULL
	         GROUP BY job_uuid
	        )

	   SELECT j.uuid, j.name, j.summary, j.paused, j.schedule,
	          j.tenant_uuid, j.fixed_key, j.keep_n, j.keep_days,
	          s.uuid, s.name, s.plugin, s.endpoint, s.summary, s.healthy,
	          t.uuid, t.name, t.plugin, t.endpoint, t.agent, t.compression,
	          k.started_at, k.status

	     FROM jobs j
	          INNER JOIN stores       s  ON  s.uuid = j.store_uuid
	          INNER JOIN targets      t  ON  t.uuid = j.target_uuid
	          LEFT  JOIN recent_tasks k  ON  j.uuid = k.job_uuid

	    WHERE ` + strings.Join(wheres, " AND ") + `
	 ORDER BY j.name, j.uuid ASC`, args
}

func (db *DB) GetAllJobs(filter *JobFilter) ([]*Job, error) {
	db.exclusive.Lock()
	defer db.exclusive.Unlock()

	if filter == nil {
		filter = &JobFilter{}
	}

	l := []*Job{}
	query, args := filter.Query()
	r, err := db.query(query, args...)
	if err != nil {
		return l, err
	}
	defer r.Close()

	for r.Next() {
		j := &Job{}

		var (
			last   *int64
			status sql.NullString
		)
		if err = r.Scan(
			&j.UUID, &j.Name, &j.Summary, &j.Paused, &j.Schedule,
			&j.TenantUUID, &j.FixedKey, &j.KeepN, &j.KeepDays,
			&j.Store.UUID, &j.Store.Name, &j.Store.Plugin, &j.Store.Endpoint, &j.Store.Summary, &j.Store.Healthy,
			&j.Target.UUID, &j.Target.Name, &j.Target.Plugin, &j.Target.Endpoint,
			&j.Agent, &j.Target.Compression, &last, &status); err != nil {
			return l, err
		}
		if last != nil {
			j.LastRun = *last
		}
		j.Healthy = true
		if status.Valid {
			j.LastTaskStatus = status.String
			j.Healthy = j.LastTaskStatus == "done"
		}

		j.StoreUUID = j.Store.UUID
		j.TargetUUID = j.Target.UUID
		l = append(l, j)
	}

	return l, nil
}

func (db *DB) GetJob(id string) (*Job, error) {
	all, err := db.GetAllJobs(&JobFilter{UUID: id})
	if err != nil {
		return nil, err
	}
	if len(all) > 0 {
		return all[0], nil
	}
	return nil, nil
}

func (db *DB) PauseOrUnpauseJob(id string, pause bool) (bool, error) {
	n, err := db.Count(`SELECT uuid FROM jobs WHERE uuid = ? AND paused = ?`, id, !pause)
	if n == 0 || err != nil {
		return false, err
	}
	err = db.Exec(`UPDATE jobs SET paused = ? WHERE uuid = ? AND paused = ?`, pause, id, !pause)
	if err != nil {
		return true, err
	}
	job, err := db.GetJob(id)
	if err != nil {
		return true, err
	}
	db.sendUpdateObjectEvent(job, "tenant:"+job.TenantUUID)
	return true, nil
}

func (db *DB) PauseJob(id string) (bool, error) {
	return db.PauseOrUnpauseJob(id, true)
}

func (db *DB) UnpauseJob(id string) (bool, error) {
	return db.PauseOrUnpauseJob(id, false)
}

func (db *DB) CreateJob(job *Job) (*Job, error) {
	job.UUID = RandomID()

	err := db.exclusively(func() error {
		/* validate the tenant */
		if err := db.tenantShouldExist(job.TenantUUID); err != nil {
			return fmt.Errorf("unable to create job: %s", err)
		}

		/* validate the store */
		if err := db.storeShouldExist(job.StoreUUID); err != nil {
			return fmt.Errorf("unable to create job: %s", err)
		}

		/* validate the target */
		if err := db.targetShouldExist(job.TargetUUID); err != nil {
			return fmt.Errorf("unable to create job: %s", err)
		}

		return db.exec(`
		   INSERT INTO jobs (uuid, tenant_uuid,
		                     name, summary, schedule, keep_n, keep_days, paused,
		                     target_uuid, store_uuid, fixed_key)
		             VALUES (?, ?,
		                     ?, ?, ?, ?, ?, ?,
		                     ?, ?, ?)`,
			job.UUID, job.TenantUUID,
			job.Name, job.Summary, job.Schedule, job.KeepN, job.KeepDays, job.Paused,
			job.TargetUUID, job.StoreUUID, job.FixedKey)
	})
	if err != nil {
		return nil, err
	}

	job, err = db.GetJob(job.UUID)
	if err != nil {
		return nil, err
	}

	db.sendCreateObjectEvent(job, "tenant:"+job.TenantUUID)
	return job, nil
}

func (db *DB) UpdateJob(job *Job) error {
	err := db.exclusively(func() error {
		/* validate the store */
		if ok, err := db.exists(`SELECT uuid FROM stores WHERE uuid = ?`, job.StoreUUID); err != nil {
			return fmt.Errorf("unable to validate existence of store with UUID [%s]: %s", job.StoreUUID, err)
		} else if !ok {
			return fmt.Errorf("unable to set job store to [%s]: no such store in database", job.StoreUUID)
		}

		/* validate the target */
		if ok, err := db.exists(`SELECT uuid FROM targets WHERE uuid = ?`, job.TargetUUID); err != nil {
			return fmt.Errorf("unable to validate existence of target with UUID [%s]: %s", job.TargetUUID, err)
		} else if !ok {
			return fmt.Errorf("unable to set job target to [%s]: no such target in database", job.TargetUUID)
		}

		return db.exec(`
		   UPDATE jobs
		      SET name           = ?,
		          summary        = ?,
		          schedule       = ?,
		          keep_n         = ?,
		          keep_days      = ?,
		          target_uuid    = ?,
		          store_uuid     = ?,
		          fixed_key      = ?
		    WHERE uuid = ?`,
			job.Name, job.Summary, job.Schedule, job.KeepN, job.KeepDays,
			job.TargetUUID, job.StoreUUID, job.FixedKey,
			job.UUID)
	})
	if err != nil {
		return err
	}

	update, err := db.GetJob(job.UUID)
	if err != nil {
		return err
	}
	if update == nil {
		return fmt.Errorf("unable to retrieve job %s after update", job.UUID)
	}

	db.sendUpdateObjectEvent(update, "tenant:"+update.TenantUUID)
	return nil
}

func (db *DB) DeleteJob(id string) (bool, error) {
	job, err := db.GetJob(id)
	if err != nil {
		return false, err
	}

	if job == nil {
		/* already deleted */
		return true, nil
	}

	err = db.Exec(`DELETE FROM jobs WHERE uuid = ?`, job.UUID)
	if err != nil {
		return false, err
	}

	db.sendDeleteObjectEvent(job, "tenant:"+job.TenantUUID)
	return true, nil
}

func (db *DB) RescheduleJob(j *Job, t time.Time) error {
	/* note: this update does not require a message bus notification */
	return db.Exec(`UPDATE jobs SET next_run = ? WHERE uuid = ?`, t.Unix(), j.UUID)
}

func (j *Job) Reschedule() error {
	var err error
	if j.Spec == nil {
		j.Spec, err = timespec.Parse(j.Schedule)
		if err != nil {
			return err
		}
	}
	next, err := j.Spec.Next(time.Now())
	if err != nil {
		return err
	}
	j.NextRun = next.Unix()
	return nil
}

func (j *Job) Runnable() bool {
	return !j.Paused && j.NextRun <= time.Now().Unix()
}
<commit_msg>Switch to true/false for paused/unpaused<commit_after>package db

import (
	"database/sql"
	"fmt"
	"strings"
	"time"

	"github.com/shieldproject/shield/timespec"
)

type Job struct {
	TenantUUID string `json:"-" mbus:"tenant_uuid"`
	TargetUUID string `json:"-" mbus:"target_uuid"`
	StoreUUID  string `json:"-" mbus:"store_uuid"`

	UUID     string `json:"uuid"      mbus:"uuid"`
	Name     string `json:"name"      mbus:"name"`
	Summary  string `json:"summary"   mbus:"summary"`
	KeepN    int    `json:"keep_n"    mbus:"keep_n"`
	KeepDays int    `json:"keep_days" mbus:"keep_days"`
	Schedule string `json:"schedule"  mbus:"schedule"`
	Paused   bool   `json:"paused"    mbus:"paused"`
	FixedKey bool   `json:"fixed_key" mbus:"fixed_key"`

	Target struct {
		UUID        string `json:"uuid"`
		Name        string `json:"name"`
		Agent       string `json:"agent"`
		Plugin      string `json:"plugin"`
		Compression string `json:"compression"`

		Endpoint string                 `json:"endpoint,omitempty"`
		Config   map[string]interface{} `json:"config,omitempty"`
	} `json:"target"`

	Store struct {
		UUID    string `json:"uuid"`
		Name    string `json:"name"`
		Agent   string `json:"agent"`
		Plugin  string `json:"plugin"`
		Summary string `json:"summary"`
		Healthy bool   `json:"healthy"`

		Endpoint string                 `json:"endpoint,omitempty"`
		Config   map[string]interface{} `json:"config,omitempty"`
	} `json:"store"`

	Agent string `json:"agent"`

	Healthy        bool   `json:"healthy" mbus:"healthy"`
	LastRun        int64  `json:"last_run"`
	LastTaskStatus string `json:"last_task_status"`

	Spec    *timespec.Spec `json:"-"`
	NextRun int64          `json:"-"`
}

type JobFilter struct {
	UUID         string
	SkipPaused   bool
	SkipUnpaused bool

	Overdue bool

	SearchName string

	ForTenant  string
	ForTarget  string
	ForStore   string
	ExactMatch bool
}

func (f *JobFilter) Query() (string, []interface{}) {
	wheres := []string{}
	args := []interface{}{}

	if f.UUID != "" {
		if f.ExactMatch {
			wheres = []string{"j.uuid = ?"}
			args = append(args, f.UUID)
		} else {
			wheres = []string{"j.uuid LIKE ? ESCAPE '/'"}
			args = append(args, PatternPrefix(f.UUID))
		}
	}

	if f.SearchName != "" {
		comparator := "LIKE"
		toAdd := Pattern(f.SearchName)
		if f.ExactMatch {
			comparator = "="
			toAdd = f.SearchName
		}
		wheres = append(wheres, fmt.Sprintf("j.name %s ?", comparator))
		args = append(args, toAdd)
	}

	if len(wheres) == 0 {
		wheres = []string{"1"}
	} else if len(wheres) > 1 {
		wheres = []string{strings.Join(wheres, " OR ")}
	}

	if f.ForTenant != "" {
		wheres = append(wheres, "j.tenant_uuid = ?")
		args = append(args, f.ForTenant)
	}
	if f.ForTarget != "" {
		wheres = append(wheres, "j.target_uuid = ?")
		args = append(args, f.ForTarget)
	}
	if f.ForStore != "" {
		wheres = append(wheres, "j.store_uuid = ?")
		args = append(args, f.ForStore)
	}
	if f.SkipPaused || f.SkipUnpaused {
		wheres = append(wheres, "j.paused = ?")
		if f.SkipPaused {
			args = append(args, false)
		} else {
			args = append(args, true)
		}
	}
	if f.Overdue {
		wheres = append(wheres, "j.next_run <= ?")
		args = append(args, time.Now().Unix())
	}

	return `
	   WITH recent_tasks AS (
	           SELECT uuid AS task_uuid, job_uuid, started_at, status
	             FROM tasks
	            WHERE stopped_at IS NOT NULL
	         GROUP BY job_uuid
	        )

	   SELECT j.uuid, j.name, j.summary, j.paused, j.schedule,
	          j.tenant_uuid, j.fixed_key, j.keep_n, j.keep_days,
	          s.uuid, s.name, s.plugin, s.endpoint, s.summary, s.healthy,
	          t.uuid, t.name, t.plugin, t.endpoint, t.agent, t.compression,
	          k.started_at, k.status

	     FROM jobs j
	          INNER JOIN stores       s  ON  s.uuid = j.store_uuid
	          INNER JOIN targets      t  ON  t.uuid = j.target_uuid
	          LEFT  JOIN recent_tasks k  ON  j.uuid = k.job_uuid

	    WHERE ` + strings.Join(wheres, " AND ") + `
	 ORDER BY j.name, j.uuid ASC`, args
}

func (db *DB) GetAllJobs(filter *JobFilter) ([]*Job, error) {
	db.exclusive.Lock()
	defer db.exclusive.Unlock()

	if filter == nil {
		filter = &JobFilter{}
	}

	l := []*Job{}
	query, args := filter.Query()
	r, err := db.query(query, args...)
	if err != nil {
		return l, err
	}
	defer r.Close()

	for r.Next() {
		j := &Job{}

		var (
			last   *int64
			status sql.NullString
		)
		if err = r.Scan(
			&j.UUID, &j.Name, &j.Summary, &j.Paused, &j.Schedule,
			&j.TenantUUID, &j.FixedKey, &j.KeepN, &j.KeepDays,
			&j.Store.UUID, &j.Store.Name, &j.Store.Plugin, &j.Store.Endpoint, &j.Store.Summary, &j.Store.Healthy,
			&j.Target.UUID, &j.Target.Name, &j.Target.Plugin, &j.Target.Endpoint,
			&j.Agent, &j.Target.Compression, &last, &status); err != nil {
			return l, err
		}
		if last != nil {
			j.LastRun = *last
		}
		j.Healthy = true
		if status.Valid {
			j.LastTaskStatus = status.String
			j.Healthy = j.LastTaskStatus == "done"
		}

		j.StoreUUID = j.Store.UUID
		j.TargetUUID = j.Target.UUID
		l = append(l, j)
	}

	return l, nil
}

func (db *DB) GetJob(id string) (*Job, error) {
	all, err := db.GetAllJobs(&JobFilter{UUID: id})
	if err != nil {
		return nil, err
	}
	if len(all) > 0 {
		return all[0], nil
	}
	return nil, nil
}

func (db *DB) PauseOrUnpauseJob(id string, pause bool) (bool, error) {
	n, err := db.Count(`SELECT uuid FROM jobs WHERE uuid = ? AND paused = ?`, id, !pause)
	if n == 0 || err != nil {
		return false, err
	}
	err = db.Exec(`UPDATE jobs SET paused = ? WHERE uuid = ? AND paused = ?`, pause, id, !pause)
	if err != nil {
		return true, err
	}
	job, err := db.GetJob(id)
	if err != nil {
		return true, err
	}
	db.sendUpdateObjectEvent(job, "tenant:"+job.TenantUUID)
	return true, nil
}

func (db *DB) PauseJob(id string) (bool, error) {
	return db.PauseOrUnpauseJob(id, true)
}

func (db *DB) UnpauseJob(id string) (bool, error) {
	return db.PauseOrUnpauseJob(id, false)
}

func (db *DB) CreateJob(job *Job) (*Job, error) {
	job.UUID = RandomID()

	err := db.exclusively(func() error {
		/* validate the tenant */
		if err := db.tenantShouldExist(job.TenantUUID); err != nil {
			return fmt.Errorf("unable to create job: %s", err)
		}

		/* validate the store */
		if err := db.storeShouldExist(job.StoreUUID); err != nil {
			return fmt.Errorf("unable to create job: %s", err)
		}

		/* validate the target */
		if err := db.targetShouldExist(job.TargetUUID); err != nil {
			return fmt.Errorf("unable to create job: %s", err)
		}

		return db.exec(`
		   INSERT INTO jobs (uuid, tenant_uuid,
		                     name, summary, schedule, keep_n, keep_days, paused,
		                     target_uuid, store_uuid, fixed_key)
		             VALUES (?, ?,
		                     ?, ?, ?, ?, ?, ?,
		                     ?, ?, ?)`,
			job.UUID, job.TenantUUID,
			job.Name, job.Summary, job.Schedule, job.KeepN, job.KeepDays, job.Paused,
			job.TargetUUID, job.StoreUUID, job.FixedKey)
	})
	if err != nil {
		return nil, err
	}

	job, err = db.GetJob(job.UUID)
	if err != nil {
		return nil, err
	}

	db.sendCreateObjectEvent(job, "tenant:"+job.TenantUUID)
	return job, nil
}

func (db *DB) UpdateJob(job *Job) error {
	err := db.exclusively(func() error {
		/* validate the store */
		if ok, err := db.exists(`SELECT uuid FROM stores WHERE uuid = ?`, job.StoreUUID); err != nil {
			return fmt.Errorf("unable to validate existence of store with UUID [%s]: %s", job.StoreUUID, err)
		} else if !ok {
			return fmt.Errorf("unable to set job store to [%s]: no such store in database", job.StoreUUID)
		}

		/* validate the target */
		if ok, err := db.exists(`SELECT uuid FROM targets WHERE uuid = ?`, job.TargetUUID); err != nil {
			return fmt.Errorf("unable to validate existence of target with UUID [%s]: %s", job.TargetUUID, err)
		} else if !ok {
			return fmt.Errorf("unable to set job target to [%s]: no such target in database", job.TargetUUID)
		}

		return db.exec(`
		   UPDATE jobs
		      SET name           = ?,
		          summary        = ?,
		          schedule       = ?,
		          keep_n         = ?,
		          keep_days      = ?,
		          target_uuid    = ?,
		          store_uuid     = ?,
		          fixed_key      = ?
		    WHERE uuid = ?`,
			job.Name, job.Summary, job.Schedule, job.KeepN, job.KeepDays,
			job.TargetUUID, job.StoreUUID, job.FixedKey,
			job.UUID)
	})
	if err != nil {
		return err
	}

	update, err := db.GetJob(job.UUID)
	if err != nil {
		return err
	}
	if update == nil {
		return fmt.Errorf("unable to retrieve job %s after update", job.UUID)
	}

	db.sendUpdateObjectEvent(update, "tenant:"+update.TenantUUID)
	return nil
}

func (db *DB) DeleteJob(id string) (bool, error) {
	job, err := db.GetJob(id)
	if err != nil {
		return false, err
	}

	if job == nil {
		/* already deleted */
		return true, nil
	}

	err = db.Exec(`DELETE FROM jobs WHERE uuid = ?`, job.UUID)
	if err != nil {
		return false, err
	}

	db.sendDeleteObjectEvent(job, "tenant:"+job.TenantUUID)
	return true, nil
}

func (db *DB) RescheduleJob(j *Job, t time.Time) error {
	/* note: this update does not require a message bus notification */
	return db.Exec(`UPDATE jobs SET next_run = ? WHERE uuid = ?`, t.Unix(), j.UUID)
}

func (j *Job) Reschedule() error {
	var err error
	if j.Spec == nil {
		j.Spec, err = timespec.Parse(j.Schedule)
		if err != nil {
			return err
		}
	}
	next, err := j.Spec.Next(time.Now())
	if err != nil {
		return err
	}
	j.NextRun = next.Unix()
	return nil
}

func (j *Job) Runnable() bool {
	return !j.Paused && j.NextRun <= time.Now().Unix()
}
<|endoftext|>


<commit_before>package gofpdf

import (
	"fmt"
	"math"
)

// Routines in this file are translated from the work of Moritz Wagner and
// Andreas Würmser.

// The matrix used for generalized transformations of text, drawings and images.
type TransformMatrix struct {
	A, B, C, D, E, F float64
}

// Set up a transformation context for subsequent text, drawings and images.
// The typical usage is to immediately follow a call to this method with a call
// to one or more of the transformation methods such as TransformScale(),
// TransformSkew(), etc. This is followed by text, drawing or image output and
// finally a call to TransformEnd(). All transformation contexts must be
// properly ended prior to outputting the document.
//
// See tutorial 17 for a transformation examples.
func (f *Fpdf) TransformBegin() {
	f.transformNest++
	f.out("q")
}

// Scale the width of the following text, drawings and images. scaleWd is the
// percentage scaling factor. (x, y) is center of scaling.
func (f *Fpdf) TransformScaleX(scaleWd, x, y float64) {
	f.TransformScale(scaleWd, 100, x, y)
}

// Scale the height of the following text, drawings and images. scaleHt is the
// percentage scaling factor. (x, y) is center of scaling.
func (f *Fpdf) TransformScaleY(scaleHt, x, y float64) {
	f.TransformScale(100, scaleHt, x, y)
}

// Uniformly scale the width and height of the following text, drawings and
// images. s is the percentage scaling factor for both width and height. (x, y)
// is center of scaling.
func (f *Fpdf) TransformScaleXY(s, x, y float64) {
	f.TransformScale(s, s, x, y)
}

// Generally scale the following text, drawings and images. scaleWd and scaleHt
// are the percentage scaling factors for width and height. (x, y) is center of
// scaling.
func (f *Fpdf) TransformScale(scaleWd, scaleHt, x, y float64) {
	if scaleWd == 0 || scaleHt == 0 {
		f.err = fmt.Errorf("Scale factor cannot be zero")
		return
	}
	y = (f.h - y) * f.k
	x *= f.k
	scaleWd /= 100
	scaleHt /= 100
	f.Transform(TransformMatrix{scaleWd, 0, 0,
		scaleHt, x * (1 - scaleWd), y * (1 - scaleHt)})
}

// Horizontally mirror the following text, drawings and images. x is the axis
// of reflection.
func (f *Fpdf) TransformMirrorHorizontal(x float64) {
	f.TransformScale(-100, 100, x, f.y)
}

// Vertically mirror the following text, drawings and images. y is the axis
// of reflection.
func (f *Fpdf) TransformMirrorVertical(y float64) {
	f.TransformScale(100, -100, f.x, y)
}

// Symmetrically mirror the following text, drawings and images on the point
// specified by (x, y).
func (f *Fpdf) TransformMirrorPoint(x, y float64) {
	f.TransformScale(-100, -100, x, y)
}

// Symmetrically mirror the following text, drawings and images on the line
// defined by angle and the point (x, y). angles is specified in degrees and
// measured counter-clockwise from the 3 o'clock position.
func (f *Fpdf) TransformMirrorLine(angle, x, y float64) {
	f.TransformScale(-100, 100, x, y)
	f.TransformRotate(-2*(angle-90), x, y)
}

// Move the following text, drawings and images horizontally by the amount
// specified by tx.
func (f *Fpdf) TransformTranslateX(tx float64) {
	f.TransformTranslate(tx, 0)
}

// Move the following text, drawings and images vertically by the amount
// specified by ty.
func (f *Fpdf) TransformTranslateY(ty float64) {
	f.TransformTranslate(0, ty)
}

// Move the following text, drawings and images horizontally and vertically by
// the amounts specified by tx and ty.
func (f *Fpdf) TransformTranslate(tx, ty float64) {
	f.Transform(TransformMatrix{1, 0, 0, 1, tx - f.k, -ty * f.k})
}

// Rotate the following text, drawings and images around the center point (x,
// y). angle is specified in degrees and measured counter-clockwise from the 3
// o'clock position.
func (f *Fpdf) TransformRotate(angle, x, y float64) {
	y = (f.h - y) * f.k
	x *= f.k
	angle = angle * math.Pi / 180
	var tm TransformMatrix
	tm.A = math.Cos(angle)
	tm.B = math.Sin(angle)
	tm.C = -tm.B
	tm.D = tm.A
	tm.E = x + tm.B*y - tm.A*x
	tm.F = y - tm.A*y - tm.B*x
	f.Transform(tm)
}

// Horizontally skew the following text, drawings and images keeping the point
// (x, y) stationary. angleX ranges from -90 degrees (skew to the left) to 90
// degrees (skew to the right).
func (f *Fpdf) TransformSkewX(angleX, x, y float64) {
	f.TransformSkew(angleX, 0, x, y)
}

// Vertically skew the following text, drawings and images keeping the point
// (x, y) stationary. angleY ranges from -90 degrees (skew to the bottom) to 90
// degrees (skew to the top).
func (f *Fpdf) TransformSkewY(angleY, x, y float64) {
	f.TransformSkew(0, angleY, x, y)
}

// Generally skew the following text, drawings and images keeping the point (x,
// y) stationary. angleX ranges from -90 degrees (skew to the left) to 90
// degrees (skew to the right). angleY ranges from -90 degrees (skew to the
// bottom) to 90 degrees (skew to the top).
func (f *Fpdf) TransformSkew(angleX, angleY, x, y float64) {
	if angleX <= -90 || angleX >= 90 || angleY <= -90 || angleY >= 90 {
		f.err = fmt.Errorf("Skew values must be between -90° and 90°")
		return
	}
	x *= f.k
	y = (f.h - y) * f.k
	var tm TransformMatrix
	tm.A = 1
	tm.B = math.Tan(angleY * math.Pi / 180)
	tm.C = math.Tan(angleX * math.Pi / 180)
	tm.D = 1
	tm.E = -tm.C * y
	tm.F = -tm.B * x
	f.Transform(tm)
}

// Generally transform the following text, drawings and images according to the
// specified matrix. It is typically easier to use the various methods such as
// TransformRotate() and TransformMirrorVertical() instead.
func (f *Fpdf) Transform(tm TransformMatrix) {
	if f.transformNest > 0 {
		f.outf("%.3f %.3f %.3f %.3f %.3f %.3f cm",
			tm.A, tm.B, tm.C, tm.D, tm.E, tm.F)
	} else if f.err == nil {
		f.err = fmt.Errorf("Transformation context is not active")
	}
}

// Apply a transformation that was begun with a call to TransformBegin().
func (f *Fpdf) TransformEnd() {
	if f.transformNest > 0 {
		f.transformNest--
		f.out("Q")
	} else {
		f.err = fmt.Errorf("Error attempting to end transformation operation out of sequence")
	}
}
<commit_msg>Another catch by Bruno: scaling typo in TransformTranslate.<commit_after>package gofpdf

import (
	"fmt"
	"math"
)

// Routines in this file are translated from the work of Moritz Wagner and
// Andreas Würmser.

// The matrix used for generalized transformations of text, drawings and images.
type TransformMatrix struct {
	A, B, C, D, E, F float64
}

// Set up a transformation context for subsequent text, drawings and images.
// The typical usage is to immediately follow a call to this method with a call
// to one or more of the transformation methods such as TransformScale(),
// TransformSkew(), etc. This is followed by text, drawing or image output and
// finally a call to TransformEnd(). All transformation contexts must be
// properly ended prior to outputting the document.
//
// See tutorial 17 for a transformation examples.
func (f *Fpdf) TransformBegin() {
	f.transformNest++
	f.out("q")
}

// Scale the width of the following text, drawings and images. scaleWd is the
// percentage scaling factor. (x, y) is center of scaling.
func (f *Fpdf) TransformScaleX(scaleWd, x, y float64) {
	f.TransformScale(scaleWd, 100, x, y)
}

// Scale the height of the following text, drawings and images. scaleHt is the
// percentage scaling factor. (x, y) is center of scaling.
func (f *Fpdf) TransformScaleY(scaleHt, x, y float64) {
	f.TransformScale(100, scaleHt, x, y)
}

// Uniformly scale the width and height of the following text, drawings and
// images. s is the percentage scaling factor for both width and height. (x, y)
// is center of scaling.
func (f *Fpdf) TransformScaleXY(s, x, y float64) {
	f.TransformScale(s, s, x, y)
}

// Generally scale the following text, drawings and images. scaleWd and scaleHt
// are the percentage scaling factors for width and height. (x, y) is center of
// scaling.
func (f *Fpdf) TransformScale(scaleWd, scaleHt, x, y float64) {
	if scaleWd == 0 || scaleHt == 0 {
		f.err = fmt.Errorf("Scale factor cannot be zero")
		return
	}
	y = (f.h - y) * f.k
	x *= f.k
	scaleWd /= 100
	scaleHt /= 100
	f.Transform(TransformMatrix{scaleWd, 0, 0,
		scaleHt, x * (1 - scaleWd), y * (1 - scaleHt)})
}

// Horizontally mirror the following text, drawings and images. x is the axis
// of reflection.
func (f *Fpdf) TransformMirrorHorizontal(x float64) {
	f.TransformScale(-100, 100, x, f.y)
}

// Vertically mirror the following text, drawings and images. y is the axis
// of reflection.
func (f *Fpdf) TransformMirrorVertical(y float64) {
	f.TransformScale(100, -100, f.x, y)
}

// Symmetrically mirror the following text, drawings and images on the point
// specified by (x, y).
func (f *Fpdf) TransformMirrorPoint(x, y float64) {
	f.TransformScale(-100, -100, x, y)
}

// Symmetrically mirror the following text, drawings and images on the line
// defined by angle and the point (x, y). angles is specified in degrees and
// measured counter-clockwise from the 3 o'clock position.
func (f *Fpdf) TransformMirrorLine(angle, x, y float64) {
	f.TransformScale(-100, 100, x, y)
	f.TransformRotate(-2*(angle-90), x, y)
}

// Move the following text, drawings and images horizontally by the amount
// specified by tx.
func (f *Fpdf) TransformTranslateX(tx float64) {
	f.TransformTranslate(tx, 0)
}

// Move the following text, drawings and images vertically by the amount
// specified by ty.
func (f *Fpdf) TransformTranslateY(ty float64) {
	f.TransformTranslate(0, ty)
}

// Move the following text, drawings and images horizontally and vertically by
// the amounts specified by tx and ty.
func (f *Fpdf) TransformTranslate(tx, ty float64) {
	f.Transform(TransformMatrix{1, 0, 0, 1, tx * f.k, -ty * f.k})
}

// Rotate the following text, drawings and images around the center point (x,
// y). angle is specified in degrees and measured counter-clockwise from the 3
// o'clock position.
func (f *Fpdf) TransformRotate(angle, x, y float64) {
	y = (f.h - y) * f.k
	x *= f.k
	angle = angle * math.Pi / 180
	var tm TransformMatrix
	tm.A = math.Cos(angle)
	tm.B = math.Sin(angle)
	tm.C = -tm.B
	tm.D = tm.A
	tm.E = x + tm.B*y - tm.A*x
	tm.F = y - tm.A*y - tm.B*x
	f.Transform(tm)
}

// Horizontally skew the following text, drawings and images keeping the point
// (x, y) stationary. angleX ranges from -90 degrees (skew to the left) to 90
// degrees (skew to the right).
func (f *Fpdf) TransformSkewX(angleX, x, y float64) {
	f.TransformSkew(angleX, 0, x, y)
}

// Vertically skew the following text, drawings and images keeping the point
// (x, y) stationary. angleY ranges from -90 degrees (skew to the bottom) to 90
// degrees (skew to the top).
func (f *Fpdf) TransformSkewY(angleY, x, y float64) {
	f.TransformSkew(0, angleY, x, y)
}

// Generally skew the following text, drawings and images keeping the point (x,
// y) stationary. angleX ranges from -90 degrees (skew to the left) to 90
// degrees (skew to the right). angleY ranges from -90 degrees (skew to the
// bottom) to 90 degrees (skew to the top).
func (f *Fpdf) TransformSkew(angleX, angleY, x, y float64) {
	if angleX <= -90 || angleX >= 90 || angleY <= -90 || angleY >= 90 {
		f.err = fmt.Errorf("Skew values must be between -90° and 90°")
		return
	}
	x *= f.k
	y = (f.h - y) * f.k
	var tm TransformMatrix
	tm.A = 1
	tm.B = math.Tan(angleY * math.Pi / 180)
	tm.C = math.Tan(angleX * math.Pi / 180)
	tm.D = 1
	tm.E = -tm.C * y
	tm.F = -tm.B * x
	f.Transform(tm)
}

// Generally transform the following text, drawings and images according to the
// specified matrix. It is typically easier to use the various methods such as
// TransformRotate() and TransformMirrorVertical() instead.
func (f *Fpdf) Transform(tm TransformMatrix) {
	if f.transformNest > 0 {
		f.outf("%.3f %.3f %.3f %.3f %.3f %.3f cm",
			tm.A, tm.B, tm.C, tm.D, tm.E, tm.F)
	} else if f.err == nil {
		f.err = fmt.Errorf("Transformation context is not active")
	}
}

// Apply a transformation that was begun with a call to TransformBegin().
func (f *Fpdf) TransformEnd() {
	if f.transformNest > 0 {
		f.transformNest--
		f.out("Q")
	} else {
		f.err = fmt.Errorf("Error attempting to end transformation operation out of sequence")
	}
}
<|endoftext|>


<commit_before>package testutil

import (
	"github.com/BytemarkHosting/bytemark-client/lib"
)

// DefVM is a default virtual machine for use in testing
var DefVM = lib.VirtualMachineName{Group: "default", Account: "default-account"}

// DefVM is a default group for use in testing
var DefGroup = lib.GroupName{Group: "default", Account: "default-account"}
<commit_msg>update comments on default exported variables<commit_after>package testutil

import (
	"github.com/BytemarkHosting/bytemark-client/lib"
)

// DefVM is used to return a default virtual machine for use in testing
var DefVM = lib.VirtualMachineName{Group: "default", Account: "default-account"}

// DefGroup is used to return a default group for use in testing
var DefGroup = lib.GroupName{Group: "default", Account: "default-account"}
<|endoftext|>


<commit_before>/*
Copyright 2019 The Skaffold Authors

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

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

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

package app

import (
	"bytes"
	"os"
	"testing"

	"github.com/GoogleContainerTools/skaffold/testutil"
)

func TestMain(t *testing.T) {
	var (
		output    bytes.Buffer
		errOutput bytes.Buffer
	)

	defer func(args []string) { os.Args = args }(os.Args)
	os.Args = []string{"skaffold", "help"}

	err := Run(&output, &errOutput)

	testutil.CheckError(t, false, err)
	testutil.CheckContains(t, "Available Commands", output.String())
	testutil.CheckDeepEqual(t, "", errOutput.String())
}
<commit_msg>Add test for unknown command<commit_after>/*
Copyright 2019 The Skaffold Authors

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

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

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

package app

import (
	"bytes"
	"io/ioutil"
	"os"
	"testing"

	"github.com/GoogleContainerTools/skaffold/testutil"
)

func TestMainHelp(t *testing.T) {
	var (
		output    bytes.Buffer
		errOutput bytes.Buffer
	)

	defer func(args []string) { os.Args = args }(os.Args)
	os.Args = []string{"skaffold", "help"}

	err := Run(&output, &errOutput)

	testutil.CheckError(t, false, err)
	testutil.CheckContains(t, "Available Commands", output.String())
	testutil.CheckDeepEqual(t, "", errOutput.String())
}

func TestMainUnknownCommand(t *testing.T) {
	defer func(args []string) { os.Args = args }(os.Args)
	os.Args = []string{"skaffold", "unknown"}

	err := Run(ioutil.Discard, ioutil.Discard)

	testutil.CheckError(t, true, err)
}
<|endoftext|>


<commit_before>// Copyright 2013-2014 Alexandre Fiori
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// Web server of freegeoip.net

package main

import (
	"database/sql"
	"encoding/binary"
	"encoding/json"
	"encoding/xml"
	"expvar"
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"net"
	"net/http"
	"os"
	"os/signal"
	"runtime"
	"runtime/pprof"
	"strings"
	"sync"
	"time"

	_ "code.google.com/p/gosqlite/sqlite3"
	"github.com/fiorix/go-web/httpxtra"
)

var (
	conf        *ConfigFile
	protoCount  = expvar.NewMap("Protocol") // HTTP or HTTPS
	outputCount = expvar.NewMap("Output")   // json, xml, csv or other
	statusCount = expvar.NewMap("Status")   // 200, 403, 404, etc
)

func main() {
	cf := flag.String("config", "freegeoip.conf", "set config file")
	prof := flag.Bool("profile", false, "run cpu and mem profiling")
	flag.Parse()

	if buf, err := ioutil.ReadFile(*cf); err != nil {
		log.Fatal(err)
	} else {
		conf = &ConfigFile{}
		if err := xml.Unmarshal(buf, conf); err != nil {
			log.Fatal(err)
		}
	}

	if *prof {
		profile()
	}

	runtime.GOMAXPROCS(runtime.NumCPU())
	log.Printf("FreeGeoIP server starting. debug=%t", conf.Debug)

	if conf.Debug && len(conf.DebugSrv) > 0 {
		go func() {
			// server for expvar's /debug/vars only
			log.Printf("Starting DEBUG HTTP server on %s", conf.DebugSrv)
			log.Fatal(http.ListenAndServe(conf.DebugSrv, nil))
		}()
	}

	mux := http.NewServeMux()
	mux.Handle("/", http.FileServer(http.Dir(conf.DocumentRoot)))

	h := LookupHandler()
	mux.HandleFunc("/csv/", h)
	mux.HandleFunc("/xml/", h)
	mux.HandleFunc("/json/", h)

	wg := new(sync.WaitGroup)
	for _, l := range conf.Listen {
		if l.Addr == "" {
			continue
		}
		wg.Add(1)
		h := httpxtra.Handler{Handler: mux, XHeaders: l.XHeaders}
		if l.Log {
			h.Logger = logger
		}

		s := http.Server{
			Addr:         l.Addr,
			Handler:      h,
			ReadTimeout:  15 * time.Second,
			WriteTimeout: 15 * time.Second,
		}

		if l.KeyFile == "" && l.CertFile == "" {
			log.Printf("Starting HTTP server on %s "+
				"log=%t xheaders=%t",
				l.Addr, l.Log, l.XHeaders)
			go func() {
				log.Fatal(httpxtra.ListenAndServe(s))
			}()
		} else {
			log.Printf("Starting HTTPS server on %s "+
				"log=%t xheaders=%t cert=%s key=%s",
				l.Addr, l.Log, l.XHeaders,
				l.CertFile, l.KeyFile)
			go func() {
				log.Fatal(s.ListenAndServeTLS(
					l.CertFile,
					l.KeyFile,
				))
			}()
		}
	}

	wg.Wait()
}

// LookupHandler handles GET on /csv, /xml and /json.
func LookupHandler() http.HandlerFunc {
	db, err := sql.Open("sqlite3", conf.IPDB.File)
	if err != nil {
		log.Fatal(err)
	}

	_, err = db.Exec("PRAGMA cache_size=" + conf.IPDB.CacheSize)
	if err != nil {
		log.Fatal(err)
	}

	stmt, err := db.Prepare(ipdb_query)
	if err != nil {
		log.Fatal(err)
	}

	log.Println("Caching database, please wait...")
	cache := NewCache(db)

	//defer stmt.Close()

	var quota Quota
	if len(conf.Redis) == 0 {
		quota = new(MapQuota)
		quota.Setup()
		log.Printf("Using internal map to manage quota.")
	} else {
		quota = new(RedisQuota)
		quota.Setup(conf.Redis...)
		log.Printf("Using redis to manage quota: %s", conf.Redis)
	}

	return func(w http.ResponseWriter, r *http.Request) {
		switch r.Method {
		case "GET":
			w.Header().Set("Access-Control-Allow-Origin", "*")
		case "OPTIONS":
			w.Header().Set("Access-Control-Allow-Origin", "*")
			w.Header().Set("Content-Type", "text/plain")
			w.Header().Set("Access-Control-Allow-Methods", "GET")
			w.Header().Set("Access-Control-Allow-Headers", "X-Requested-With")
			w.WriteHeader(200)
			return
		default:
			w.Header().Set("Allow", "GET, OPTIONS")
			http.Error(w, http.StatusText(405), 405)
			return
		}

		// GET continues...
		var srcIP net.IP

		// If xheaders is enabled, RemoteAddr might be a copy of
		// the X-Real-IP or X-Forwarded-For HTTP headers, which
		// can be a comma separated list of IPs. In this case,
		// only the first IP in the list is used.
		r.RemoteAddr = strings.SplitN(r.RemoteAddr, ",", 2)[0]

		if ip, _, err := net.SplitHostPort(r.RemoteAddr); err != nil {
			srcIP = net.ParseIP(r.RemoteAddr) // Use X-Real-IP
		} else {
			srcIP = net.ParseIP(ip)
		}

		if srcIP == nil {
			http.Error(w, http.StatusText(400), 400)
			return
		}

		nsrcIP, err := ip2int(srcIP)
		if err != nil {
			if conf.Debug {
				log.Println(err)
			}
			http.Error(w, http.StatusText(400), 400)
			return
		}

		// Check quota.
		if conf.Limit.MaxRequests > 0 {
			var ok bool
			if ok, err = quota.Ok(nsrcIP); err != nil {
				if conf.Debug {
					log.Println(err) // redis error
				}
				http.Error(w, http.StatusText(503), 503)
				return
			} else if !ok {
				// Over quota, soz :(
				http.Error(w, http.StatusText(403), 403)
				return
			}
		}

		var (
			queryIP  net.IP
			nqueryIP uint32
		)

		// Parse URL (e.g. /csv/ip, /xml/)
		a := strings.SplitN(r.URL.Path, "/", 3)
		if len(a) == 3 && a[2] != "" {
			addrs, err := net.LookupHost(a[2])
			if err != nil {
				// DNS lookup failed, assume host not found.
				http.Error(w, http.StatusText(404), 404)
				return
			}

			if queryIP = net.ParseIP(addrs[0]); queryIP == nil {
				http.Error(w, http.StatusText(400), 400)
				return
			}

			nqueryIP, err = ip2int(net.ParseIP(addrs[0]))
			if err != nil {
				if conf.Debug {
					log.Println(err)
				}
				http.Error(w, http.StatusText(400), 400)
				return
			}

		} else {
			queryIP = srcIP
			nqueryIP = nsrcIP
		}

		// Query the db.
		geoip, err := ipdb_lookup(stmt, cache, queryIP, nqueryIP)
		if err != nil {
			http.NotFound(w, r)
			return
		}

		w.WriteHeader(200)

		switch a[1][0] {
		case 'j': // json
			enc := json.NewEncoder(w)
			if cb := r.FormValue("callback"); cb != "" {
				w.Header().Set("Content-Type", "text/javascript")
				fmt.Fprintf(w, "%s(", cb)
				enc.Encode(geoip)
				fmt.Fprintf(w, ");")
			} else {
				w.Header().Set("Content-Type", "application/json")
				enc.Encode(geoip)
			}
		case 'x': // xml
			w.Header().Set("Content-Type", "application/xml")
			fmt.Fprintf(w, xml.Header)
			enc := xml.NewEncoder(w)
			enc.Indent("", " ")
			enc.Encode(geoip)
			fmt.Fprintf(w, "\n")
		case 'c': // csv
			w.Header().Set("Content-Type", "application/csv")
			fmt.Fprintf(w, `"%s","%s","%s","%s","%s","%s",`+
				`"%s","%0.4f","%0.4f","%s","%s"`+"\r\n",
				geoip.Ip,
				geoip.CountryCode, geoip.CountryName,
				geoip.RegionCode, geoip.RegionName,
				geoip.CityName, geoip.ZipCode,
				geoip.Latitude, geoip.Longitude,
				geoip.MetroCode, geoip.AreaCode)
		}
	}
}

func ip2int(ip net.IP) (uint32, error) {
	ipv4 := ip.To4()
	if ipv4 == nil {
		return 0, fmt.Errorf("IP %s is not IPv4", ip.String())
	}

	return binary.BigEndian.Uint32(ipv4), nil
}

func profile() {
	f, err := os.Create("freegeoip.cpu.prof")
	if err != nil {
		log.Fatal(err)
	}

	pprof.StartCPUProfile(f)
	sig := make(chan os.Signal, 1)
	signal.Notify(sig, os.Interrupt, os.Kill)

	go func() {
		<-sig
		pprof.StopCPUProfile()
		f.Close()
		f, err = os.Create("freegeoip.mem.prof")
		if err != nil {
			log.Fatal(err)
		}
		pprof.WriteHeapProfile(f)
		os.Exit(0)
	}()
}

func logger(r *http.Request, created time.Time, status, bytes int) {
	//fmt.Println(httpxtra.ApacheCommonLog(r, created, status, bytes))
	var (
		s, ip string
		err   error
	)
	if r.TLS == nil {
		s = "HTTP"
	} else {
		s = "HTTPS"
	}
	if ip, _, err = net.SplitHostPort(r.RemoteAddr); err != nil {
		ip = r.RemoteAddr
	}
	log.Printf("%s %d %s %q (%s) :: %s",
		s,
		status,
		r.Method,
		r.URL.Path,
		ip,
		time.Since(created),
	)
	if conf.Debug {
		protoCount.Add(s, 1)
		statusCount.Add(fmt.Sprintf("%d", status), 1)
		switch strings.SplitN(r.URL.Path, "/", 2)[1] {
		case "json/":
			outputCount.Add("json", 1)
		case "xml/":
			outputCount.Add("xml", 1)
		case "csv/":
			outputCount.Add("csv", 1)
		default:
			outputCount.Add("other", 1)
		}
	}
}

type ConfigFile struct {
	XMLName      xml.Name `xml:"Server"`
	Debug        bool     `xml:"debug,attr"`
	DebugSrv     string   `xml:"debugsrv,attr"`
	DocumentRoot string

	Listen []*struct {
		Log      bool   `xml:"log,attr"`
		XHeaders bool   `xml:"xheaders,attr"`
		Addr     string `xml:"addr,attr"`
		CertFile string
		KeyFile  string
	}

	IPDB struct {
		File      string `xml:",attr"`
		CacheSize string `xml:",attr"`
	}

	Limit struct {
		MaxRequests int `xml:",attr"`
		Expire      int `xml:",attr"`
	}

	Redis []string `xml:"Redis>Addr"`
}
<commit_msg>Back to mattn's driver<commit_after>// Copyright 2013-2014 Alexandre Fiori
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// Web server of freegeoip.net

package main

import (
	"database/sql"
	"encoding/binary"
	"encoding/json"
	"encoding/xml"
	"expvar"
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"net"
	"net/http"
	"os"
	"os/signal"
	"runtime"
	"runtime/pprof"
	"strings"
	"sync"
	"time"

	"github.com/fiorix/go-web/httpxtra"

	_ "github.com/mattn/go-sqlite3"
	//_ "code.google.com/p/gosqlite/sqlite3"
)

var (
	conf        *ConfigFile
	protoCount  = expvar.NewMap("Protocol") // HTTP or HTTPS
	outputCount = expvar.NewMap("Output")   // json, xml, csv or other
	statusCount = expvar.NewMap("Status")   // 200, 403, 404, etc
)

func main() {
	cf := flag.String("config", "freegeoip.conf", "set config file")
	prof := flag.Bool("profile", false, "run cpu and mem profiling")
	flag.Parse()

	if buf, err := ioutil.ReadFile(*cf); err != nil {
		log.Fatal(err)
	} else {
		conf = &ConfigFile{}
		if err := xml.Unmarshal(buf, conf); err != nil {
			log.Fatal(err)
		}
	}

	if *prof {
		profile()
	}

	runtime.GOMAXPROCS(runtime.NumCPU())
	log.Printf("FreeGeoIP server starting. debug=%t", conf.Debug)

	if conf.Debug && len(conf.DebugSrv) > 0 {
		go func() {
			// server for expvar's /debug/vars only
			log.Printf("Starting DEBUG HTTP server on %s", conf.DebugSrv)
			log.Fatal(http.ListenAndServe(conf.DebugSrv, nil))
		}()
	}

	mux := http.NewServeMux()
	mux.Handle("/", http.FileServer(http.Dir(conf.DocumentRoot)))

	h := LookupHandler()
	mux.HandleFunc("/csv/", h)
	mux.HandleFunc("/xml/", h)
	mux.HandleFunc("/json/", h)

	wg := new(sync.WaitGroup)
	for _, l := range conf.Listen {
		if l.Addr == "" {
			continue
		}
		wg.Add(1)
		h := httpxtra.Handler{Handler: mux, XHeaders: l.XHeaders}
		if l.Log {
			h.Logger = logger
		}

		s := http.Server{
			Addr:         l.Addr,
			Handler:      h,
			ReadTimeout:  15 * time.Second,
			WriteTimeout: 15 * time.Second,
		}

		if l.KeyFile == "" && l.CertFile == "" {
			log.Printf("Starting HTTP server on %s "+
				"log=%t xheaders=%t",
				l.Addr, l.Log, l.XHeaders)
			go func() {
				log.Fatal(httpxtra.ListenAndServe(s))
			}()
		} else {
			log.Printf("Starting HTTPS server on %s "+
				"log=%t xheaders=%t cert=%s key=%s",
				l.Addr, l.Log, l.XHeaders,
				l.CertFile, l.KeyFile)
			go func() {
				log.Fatal(s.ListenAndServeTLS(
					l.CertFile,
					l.KeyFile,
				))
			}()
		}
	}

	wg.Wait()
}

// LookupHandler handles GET on /csv, /xml and /json.
func LookupHandler() http.HandlerFunc {
	db, err := sql.Open("sqlite3", conf.IPDB.File)
	if err != nil {
		log.Fatal(err)
	}

	_, err = db.Exec("PRAGMA cache_size=" + conf.IPDB.CacheSize)
	if err != nil {
		log.Fatal(err)
	}

	stmt, err := db.Prepare(ipdb_query)
	if err != nil {
		log.Fatal(err)
	}

	log.Println("Caching database, please wait...")
	cache := NewCache(db)

	//defer stmt.Close()

	var quota Quota
	if len(conf.Redis) == 0 {
		quota = new(MapQuota)
		quota.Setup()
		log.Printf("Using internal map to manage quota.")
	} else {
		quota = new(RedisQuota)
		quota.Setup(conf.Redis...)
		log.Printf("Using redis to manage quota: %s", conf.Redis)
	}

	return func(w http.ResponseWriter, r *http.Request) {
		switch r.Method {
		case "GET":
			w.Header().Set("Access-Control-Allow-Origin", "*")
		case "OPTIONS":
			w.Header().Set("Access-Control-Allow-Origin", "*")
			w.Header().Set("Content-Type", "text/plain")
			w.Header().Set("Access-Control-Allow-Methods", "GET")
			w.Header().Set("Access-Control-Allow-Headers", "X-Requested-With")
			w.WriteHeader(200)
			return
		default:
			w.Header().Set("Allow", "GET, OPTIONS")
			http.Error(w, http.StatusText(405), 405)
			return
		}

		// GET continues...
		var srcIP net.IP

		// If xheaders is enabled, RemoteAddr might be a copy of
		// the X-Real-IP or X-Forwarded-For HTTP headers, which
		// can be a comma separated list of IPs. In this case,
		// only the first IP in the list is used.
		r.RemoteAddr = strings.SplitN(r.RemoteAddr, ",", 2)[0]

		if ip, _, err := net.SplitHostPort(r.RemoteAddr); err != nil {
			srcIP = net.ParseIP(r.RemoteAddr) // Use X-Real-IP
		} else {
			srcIP = net.ParseIP(ip)
		}

		if srcIP == nil {
			http.Error(w, http.StatusText(400), 400)
			return
		}

		nsrcIP, err := ip2int(srcIP)
		if err != nil {
			if conf.Debug {
				log.Println(err)
			}
			http.Error(w, http.StatusText(400), 400)
			return
		}

		// Check quota.
		if conf.Limit.MaxRequests > 0 {
			var ok bool
			if ok, err = quota.Ok(nsrcIP); err != nil {
				if conf.Debug {
					log.Println(err) // redis error
				}
				http.Error(w, http.StatusText(503), 503)
				return
			} else if !ok {
				// Over quota, soz :(
				http.Error(w, http.StatusText(403), 403)
				return
			}
		}

		var (
			queryIP  net.IP
			nqueryIP uint32
		)

		// Parse URL (e.g. /csv/ip, /xml/)
		a := strings.SplitN(r.URL.Path, "/", 3)
		if len(a) == 3 && a[2] != "" {
			addrs, err := net.LookupHost(a[2])
			if err != nil {
				// DNS lookup failed, assume host not found.
				http.Error(w, http.StatusText(404), 404)
				return
			}

			if queryIP = net.ParseIP(addrs[0]); queryIP == nil {
				http.Error(w, http.StatusText(400), 400)
				return
			}

			nqueryIP, err = ip2int(net.ParseIP(addrs[0]))
			if err != nil {
				if conf.Debug {
					log.Println(err)
				}
				http.Error(w, http.StatusText(400), 400)
				return
			}

		} else {
			queryIP = srcIP
			nqueryIP = nsrcIP
		}

		// Query the db.
		geoip, err := ipdb_lookup(stmt, cache, queryIP, nqueryIP)
		if err != nil {
			http.NotFound(w, r)
			return
		}

		w.WriteHeader(200)

		switch a[1][0] {
		case 'j': // json
			enc := json.NewEncoder(w)
			if cb := r.FormValue("callback"); cb != "" {
				w.Header().Set("Content-Type", "text/javascript")
				fmt.Fprintf(w, "%s(", cb)
				enc.Encode(geoip)
				fmt.Fprintf(w, ");")
			} else {
				w.Header().Set("Content-Type", "application/json")
				enc.Encode(geoip)
			}
		case 'x': // xml
			w.Header().Set("Content-Type", "application/xml")
			fmt.Fprintf(w, xml.Header)
			enc := xml.NewEncoder(w)
			enc.Indent("", " ")
			enc.Encode(geoip)
			fmt.Fprintf(w, "\n")
		case 'c': // csv
			w.Header().Set("Content-Type", "application/csv")
			fmt.Fprintf(w, `"%s","%s","%s","%s","%s","%s",`+
				`"%s","%0.4f","%0.4f","%s","%s"`+"\r\n",
				geoip.Ip,
				geoip.CountryCode, geoip.CountryName,
				geoip.RegionCode, geoip.RegionName,
				geoip.CityName, geoip.ZipCode,
				geoip.Latitude, geoip.Longitude,
				geoip.MetroCode, geoip.AreaCode)
		}
	}
}

func ip2int(ip net.IP) (uint32, error) {
	ipv4 := ip.To4()
	if ipv4 == nil {
		return 0, fmt.Errorf("IP %s is not IPv4", ip.String())
	}

	return binary.BigEndian.Uint32(ipv4), nil
}

func profile() {
	f, err := os.Create("freegeoip.cpu.prof")
	if err != nil {
		log.Fatal(err)
	}

	pprof.StartCPUProfile(f)
	sig := make(chan os.Signal, 1)
	signal.Notify(sig, os.Interrupt, os.Kill)

	go func() {
		<-sig
		pprof.StopCPUProfile()
		f.Close()
		f, err = os.Create("freegeoip.mem.prof")
		if err != nil {
			log.Fatal(err)
		}
		pprof.WriteHeapProfile(f)
		os.Exit(0)
	}()
}

func logger(r *http.Request, created time.Time, status, bytes int) {
	//fmt.Println(httpxtra.ApacheCommonLog(r, created, status, bytes))
	var (
		s, ip string
		err   error
	)
	if r.TLS == nil {
		s = "HTTP"
	} else {
		s = "HTTPS"
	}
	if ip, _, err = net.SplitHostPort(r.RemoteAddr); err != nil {
		ip = r.RemoteAddr
	}
	log.Printf("%s %d %s %q (%s) :: %s",
		s,
		status,
		r.Method,
		r.URL.Path,
		ip,
		time.Since(created),
	)
	if conf.Debug {
		protoCount.Add(s, 1)
		statusCount.Add(fmt.Sprintf("%d", status), 1)
		switch strings.SplitN(r.URL.Path, "/", 2)[1] {
		case "json/":
			outputCount.Add("json", 1)
		case "xml/":
			outputCount.Add("xml", 1)
		case "csv/":
			outputCount.Add("csv", 1)
		default:
			outputCount.Add("other", 1)
		}
	}
}

type ConfigFile struct {
	XMLName      xml.Name `xml:"Server"`
	Debug        bool     `xml:"debug,attr"`
	DebugSrv     string   `xml:"debugsrv,attr"`
	DocumentRoot string

	Listen []*struct {
		Log      bool   `xml:"log,attr"`
		XHeaders bool   `xml:"xheaders,attr"`
		Addr     string `xml:"addr,attr"`
		CertFile string
		KeyFile  string
	}

	IPDB struct {
		File      string `xml:",attr"`
		CacheSize string `xml:",attr"`
	}

	Limit struct {
		MaxRequests int `xml:",attr"`
		Expire      int `xml:",attr"`
	}

	Redis []string `xml:"Redis>Addr"`
}
<|endoftext|>