<commit_before>package agent

import (
	"fmt"
	"net/http"
	"net/http/httptest"
	"strings"
	"testing"
	"time"

	"github.com/hashicorp/consul/acl"
	"github.com/hashicorp/consul/agent/structs"
	"github.com/hashicorp/consul/testrpc"
	"github.com/hashicorp/serf/coordinate"
)

func TestCoordinate_Disabled_Response(t *testing.T) {
	t.Parallel()
	a := NewTestAgent(t, t.Name(), `
		disable_coordinates = true
`)
	defer a.Shutdown()
	testrpc.WaitForTestAgent(t, a.RPC, "dc1")

	tests := []func(resp http.ResponseWriter, req *http.Request) (interface{}, error){
		a.srv.CoordinateDatacenters,
		a.srv.CoordinateNodes,
		a.srv.CoordinateNode,
		a.srv.CoordinateUpdate,
	}
	for i, tt := range tests {
		t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
			req, _ := http.NewRequest("PUT", "/should/not/care", nil)
			resp := httptest.NewRecorder()
			obj, err := tt(resp, req)
			if err != nil {
				t.Fatalf("err: %v", err)
			}
			if obj != nil {
				t.Fatalf("bad: %#v", obj)
			}
			if got, want := resp.Code, http.StatusUnauthorized; got != want {
				t.Fatalf("got %d want %d", got, want)
			}
			if !strings.Contains(resp.Body.String(), "Coordinate support disabled") {
				t.Fatalf("bad: %#v", resp)
			}
		})
	}
}

func TestCoordinate_Datacenters(t *testing.T) {
	t.Parallel()
	a := NewTestAgent(t, t.Name(), "")
	defer a.Shutdown()
	testrpc.WaitForTestAgent(t, a.RPC, "dc1")

	req, _ := http.NewRequest("GET", "/v1/coordinate/datacenters", nil)
	resp := httptest.NewRecorder()
	obj, err := a.srv.CoordinateDatacenters(resp, req)
	if err != nil {
		t.Fatalf("err: %v", err)
	}

	maps := obj.([]structs.DatacenterMap)
	if len(maps) != 1 ||
		maps[0].Datacenter != "dc1" ||
		len(maps[0].Coordinates) != 1 ||
		maps[0].Coordinates[0].Node != a.Config.NodeName {
		t.Fatalf("bad: %v", maps)
	}
}

func TestCoordinate_Nodes(t *testing.T) {
	t.Parallel()
	a := NewTestAgent(t, t.Name(), "")
	defer a.Shutdown()
	testrpc.WaitForTestAgent(t, a.RPC, "dc1")

	// Make sure an empty list is non-nil.
	req, _ := http.NewRequest("GET", "/v1/coordinate/nodes?dc=dc1", nil)
	resp := httptest.NewRecorder()
	obj, err := a.srv.CoordinateNodes(resp, req)
	if err != nil {
		t.Fatalf("err: %v", err)
	}

	// Check that coordinates are empty before registering a node
	coordinates, ok := obj.(structs.Coordinates)
	if !ok {
		t.Fatalf("expected: structs.Coordinates, received: %v", coordinates)
	}

	if len(coordinates) != 0 {
		t.Fatalf("coordinates should be empty, received: %v", coordinates)
	}

	// Register the nodes.
	nodes := []string{"foo", "bar"}
	for _, node := range nodes {
		req := structs.RegisterRequest{
			Datacenter: "dc1",
			Node:       node,
			Address:    "127.0.0.1",
		}
		var reply struct{}
		if err := a.RPC("Catalog.Register", &req, &reply); err != nil {
			t.Fatalf("err: %s", err)
		}
	}

	// Send some coordinates for a few nodes, waiting a little while for the
	// batch update to run.
	arg1 := structs.CoordinateUpdateRequest{
		Datacenter: "dc1",
		Node:       "foo",
		Segment:    "alpha",
		Coord:      coordinate.NewCoordinate(coordinate.DefaultConfig()),
	}
	var out struct{}
	if err := a.RPC("Coordinate.Update", &arg1, &out); err != nil {
		t.Fatalf("err: %v", err)
	}

	arg2 := structs.CoordinateUpdateRequest{
		Datacenter: "dc1",
		Node:       "bar",
		Coord:      coordinate.NewCoordinate(coordinate.DefaultConfig()),
	}
	if err := a.RPC("Coordinate.Update", &arg2, &out); err != nil {
		t.Fatalf("err: %v", err)
	}
	time.Sleep(300 * time.Millisecond)

	// Query back and check the nodes are present and sorted correctly.
	req, _ = http.NewRequest("GET", "/v1/coordinate/nodes?dc=dc1", nil)
	resp = httptest.NewRecorder()
	obj, err = a.srv.CoordinateNodes(resp, req)
	if err != nil {
		t.Fatalf("err: %v", err)
	}

	coordinates = obj.(structs.Coordinates)
	if len(coordinates) != 2 ||
		coordinates[0].Node != "bar" ||
		coordinates[1].Node != "foo" {
		t.Fatalf("bad: %v", coordinates)
	}

	// Filter on a nonexistent node segment
	req, _ = http.NewRequest("GET", "/v1/coordinate/nodes?segment=nope", nil)
	resp = httptest.NewRecorder()
	obj, err = a.srv.CoordinateNodes(resp, req)
	if err != nil {
		t.Fatalf("err: %v", err)
	}

	coordinates = obj.(structs.Coordinates)
	if len(coordinates) != 0 {
		t.Fatalf("bad: %v", coordinates)
	}

	// Filter on a real node segment
	req, _ = http.NewRequest("GET", "/v1/coordinate/nodes?segment=alpha", nil)
	resp = httptest.NewRecorder()
	obj, err = a.srv.CoordinateNodes(resp, req)
	if err != nil {
		t.Fatalf("err: %v", err)
	}

	coordinates = obj.(structs.Coordinates)
	if len(coordinates) != 1 || coordinates[0].Node != "foo" {
		t.Fatalf("bad: %v", coordinates)
	}

	// Make sure the empty filter works
	req, _ = http.NewRequest("GET", "/v1/coordinate/nodes?segment=", nil)
	resp = httptest.NewRecorder()
	obj, err = a.srv.CoordinateNodes(resp, req)
	if err != nil {
		t.Fatalf("err: %v", err)
	}

	coordinates = obj.(structs.Coordinates)
	if len(coordinates) != 1 || coordinates[0].Node != "bar" {
		t.Fatalf("bad: %v", coordinates)
	}
}

func TestCoordinate_Node(t *testing.T) {
	t.Parallel()
	a := NewTestAgent(t, t.Name(), "")
	defer a.Shutdown()
	testrpc.WaitForTestAgent(t, a.RPC, "dc1")

	// Make sure we get a 404 with no coordinates.
	req, _ := http.NewRequest("GET", "/v1/coordinate/node/foo?dc=dc1", nil)
	resp := httptest.NewRecorder()
	obj, err := a.srv.CoordinateNode(resp, req)
	if err != nil {
		t.Fatalf("err: %v", err)
	}
	if resp.Code != http.StatusNotFound {
		t.Fatalf("bad: %v", resp.Code)
	}

	// Register the nodes.
	nodes := []string{"foo", "bar"}
	for _, node := range nodes {
		req := structs.RegisterRequest{
			Datacenter: "dc1",
			Node:       node,
			Address:    "127.0.0.1",
		}
		var reply struct{}
		if err := a.RPC("Catalog.Register", &req, &reply); err != nil {
			t.Fatalf("err: %s", err)
		}
	}

	// Send some coordinates for a few nodes, waiting a little while for the
	// batch update to run.
	arg1 := structs.CoordinateUpdateRequest{
		Datacenter: "dc1",
		Node:       "foo",
		Segment:    "alpha",
		Coord:      coordinate.NewCoordinate(coordinate.DefaultConfig()),
	}
	var out struct{}
	if err := a.RPC("Coordinate.Update", &arg1, &out); err != nil {
		t.Fatalf("err: %v", err)
	}

	arg2 := structs.CoordinateUpdateRequest{
		Datacenter: "dc1",
		Node:       "bar",
		Coord:      coordinate.NewCoordinate(coordinate.DefaultConfig()),
	}
	if err := a.RPC("Coordinate.Update", &arg2, &out); err != nil {
		t.Fatalf("err: %v", err)
	}
	time.Sleep(300 * time.Millisecond)

	// Query back and check the nodes are present.
	req, _ = http.NewRequest("GET", "/v1/coordinate/node/foo?dc=dc1", nil)
	resp = httptest.NewRecorder()
	obj, err = a.srv.CoordinateNode(resp, req)
	if err != nil {
		t.Fatalf("err: %v", err)
	}

	coordinates := obj.(structs.Coordinates)
	if len(coordinates) != 1 ||
		coordinates[0].Node != "foo" {
		t.Fatalf("bad: %v", coordinates)
	}

	// Filter on a nonexistent node segment
	req, _ = http.NewRequest("GET", "/v1/coordinate/node/foo?segment=nope", nil)
	resp = httptest.NewRecorder()
	obj, err = a.srv.CoordinateNode(resp, req)
	if err != nil {
		t.Fatalf("err: %v", err)
	}
	if resp.Code != http.StatusNotFound {
		t.Fatalf("bad: %v", resp.Code)
	}

	// Filter on a real node segment
	req, _ = http.NewRequest("GET", "/v1/coordinate/node/foo?segment=alpha", nil)
	resp = httptest.NewRecorder()
	obj, err = a.srv.CoordinateNode(resp, req)
	if err != nil {
		t.Fatalf("err: %v", err)
	}

	coordinates = obj.(structs.Coordinates)
	if len(coordinates) != 1 || coordinates[0].Node != "foo" {
		t.Fatalf("bad: %v", coordinates)
	}

	// Make sure the empty filter works
	req, _ = http.NewRequest("GET", "/v1/coordinate/node/foo?segment=", nil)
	resp = httptest.NewRecorder()
	obj, err = a.srv.CoordinateNode(resp, req)
	if err != nil {
		t.Fatalf("err: %v", err)
	}
	if resp.Code != http.StatusNotFound {
		t.Fatalf("bad: %v", resp.Code)
	}
}

func TestCoordinate_Update(t *testing.T) {
	t.Parallel()
	a := NewTestAgent(t, t.Name(), "")
	defer a.Shutdown()
	testrpc.WaitForTestAgent(t, a.RPC, "dc1")

	// Register the node.
	reg := structs.RegisterRequest{
		Datacenter: "dc1",
		Node:       "foo",
		Address:    "127.0.0.1",
	}
	var reply struct{}
	if err := a.RPC("Catalog.Register", &reg, &reply); err != nil {
		t.Fatalf("err: %s", err)
	}

	// Update the coordinates and wait for it to complete.
	coord := coordinate.NewCoordinate(coordinate.DefaultConfig())
	coord.Height = -5.0
	body := structs.CoordinateUpdateRequest{
		Datacenter: "dc1",
		Node:       "foo",
		Coord:      coord,
	}
	req, _ := http.NewRequest("PUT", "/v1/coordinate/update", jsonReader(body))
	resp := httptest.NewRecorder()
	_, err := a.srv.CoordinateUpdate(resp, req)
	if err != nil {
		t.Fatalf("err: %v", err)
	}
	time.Sleep(300 * time.Millisecond)

	// Query back and check the coordinates are present.
	args := structs.NodeSpecificRequest{Node: "foo", Datacenter: "dc1"}
	var coords structs.IndexedCoordinates
	if err := a.RPC("Coordinate.Node", &args, &coords); err != nil {
		t.Fatalf("err: %s", err)
	}

	coordinates := coords.Coordinates
	if len(coordinates) != 1 ||
		coordinates[0].Node != "foo" {
		t.Fatalf("bad: %v", coordinates)
	}
}

func TestCoordinate_Update_ACLDeny(t *testing.T) {
	t.Parallel()
	a := NewTestAgent(t, t.Name(), TestACLConfig())
	defer a.Shutdown()
	testrpc.WaitForLeader(t, a.RPC, "dc1")

	coord := coordinate.NewCoordinate(coordinate.DefaultConfig())
	coord.Height = -5.0
	body := structs.CoordinateUpdateRequest{
		Datacenter: "dc1",
		Node:       "foo",
		Coord:      coord,
	}

	t.Run("no token", func(t *testing.T) {
		req, _ := http.NewRequest("PUT", "/v1/coordinate/update", jsonReader(body))
		if _, err := a.srv.CoordinateUpdate(nil, req); !acl.IsErrPermissionDenied(err) {
			t.Fatalf("err: %v", err)
		}
	})

	t.Run("valid token", func(t *testing.T) {
		req, _ := http.NewRequest("PUT", "/v1/coordinate/update?token=root", jsonReader(body))
		if _, err := a.srv.CoordinateUpdate(nil, req); err != nil {
			t.Fatalf("err: %v", err)
		}
	})
}
<commit_msg>Add retries to all `obj`<commit_after>package agent

import (
	"fmt"
	"net/http"
	"net/http/httptest"
	"strings"
	"testing"
	"time"

	"github.com/hashicorp/consul/acl"
	"github.com/hashicorp/consul/agent/structs"
	"github.com/hashicorp/consul/sdk/testutil/retry"
	"github.com/hashicorp/consul/testrpc"
	"github.com/hashicorp/serf/coordinate"
)

func TestCoordinate_Disabled_Response(t *testing.T) {
	t.Parallel()
	a := NewTestAgent(t, t.Name(), `
		disable_coordinates = true
`)
	defer a.Shutdown()
	testrpc.WaitForTestAgent(t, a.RPC, "dc1")

	tests := []func(resp http.ResponseWriter, req *http.Request) (interface{}, error){
		a.srv.CoordinateDatacenters,
		a.srv.CoordinateNodes,
		a.srv.CoordinateNode,
		a.srv.CoordinateUpdate,
	}
	for i, tt := range tests {
		t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
			req, _ := http.NewRequest("PUT", "/should/not/care", nil)
			resp := httptest.NewRecorder()
			obj, err := tt(resp, req)
			if err != nil {
				t.Fatalf("err: %v", err)
			}
			if obj != nil {
				t.Fatalf("bad: %#v", obj)
			}
			if got, want := resp.Code, http.StatusUnauthorized; got != want {
				t.Fatalf("got %d want %d", got, want)
			}
			if !strings.Contains(resp.Body.String(), "Coordinate support disabled") {
				t.Fatalf("bad: %#v", resp)
			}
		})
	}
}

func TestCoordinate_Datacenters(t *testing.T) {
	t.Parallel()
	a := NewTestAgent(t, t.Name(), "")
	defer a.Shutdown()
	testrpc.WaitForTestAgent(t, a.RPC, "dc1")

	req, _ := http.NewRequest("GET", "/v1/coordinate/datacenters", nil)
	resp := httptest.NewRecorder()
	obj, err := a.srv.CoordinateDatacenters(resp, req)
	if err != nil {
		t.Fatalf("err: %v", err)
	}

	maps := obj.([]structs.DatacenterMap)
	if len(maps) != 1 ||
		maps[0].Datacenter != "dc1" ||
		len(maps[0].Coordinates) != 1 ||
		maps[0].Coordinates[0].Node != a.Config.NodeName {
		t.Fatalf("bad: %v", maps)
	}
}

func TestCoordinate_Nodes(t *testing.T) {
	t.Parallel()
	a := NewTestAgent(t, t.Name(), "")
	defer a.Shutdown()
	testrpc.WaitForTestAgent(t, a.RPC, "dc1")

	// Make sure an empty list is non-nil.
	req, _ := http.NewRequest("GET", "/v1/coordinate/nodes?dc=dc1", nil)
	resp := httptest.NewRecorder()
	retry.Run(t, func(r *retry.R) {
		obj, err := a.srv.CoordinateNodes(resp, req)
		if err != nil {
			t.Fatalf("err: %v", err)
		}

		// Check that coordinates are empty before registering a node
		coordinates, ok := obj.(structs.Coordinates)
		if !ok {
			t.Fatalf("expected: structs.Coordinates, received: %v", coordinates)
		}

		if len(coordinates) != 0 {
			t.Fatalf("coordinates should be empty, received: %v", coordinates)
		}
	})

	// Register the nodes.
	nodes := []string{"foo", "bar"}
	for _, node := range nodes {
		req := structs.RegisterRequest{
			Datacenter: "dc1",
			Node:       node,
			Address:    "127.0.0.1",
		}
		var reply struct{}
		if err := a.RPC("Catalog.Register", &req, &reply); err != nil {
			t.Fatalf("err: %s", err)
		}
	}

	// Send some coordinates for a few nodes, waiting a little while for the
	// batch update to run.
	arg1 := structs.CoordinateUpdateRequest{
		Datacenter: "dc1",
		Node:       "foo",
		Segment:    "alpha",
		Coord:      coordinate.NewCoordinate(coordinate.DefaultConfig()),
	}
	var out struct{}
	if err := a.RPC("Coordinate.Update", &arg1, &out); err != nil {
		t.Fatalf("err: %v", err)
	}

	arg2 := structs.CoordinateUpdateRequest{
		Datacenter: "dc1",
		Node:       "bar",
		Coord:      coordinate.NewCoordinate(coordinate.DefaultConfig()),
	}
	if err := a.RPC("Coordinate.Update", &arg2, &out); err != nil {
		t.Fatalf("err: %v", err)
	}
	time.Sleep(300 * time.Millisecond)

	// Query back and check the nodes are present and sorted correctly.
	req, _ = http.NewRequest("GET", "/v1/coordinate/nodes?dc=dc1", nil)
	resp = httptest.NewRecorder()
	retry.Run(t, func(r *retry.R) {
		obj, err := a.srv.CoordinateNodes(resp, req)
		if err != nil {
			t.Fatalf("err: %v", err)
		}

		coordinates, ok := obj.(structs.Coordinates)
		if !ok {
			t.Fatalf("expected: structs.Coordinates, received: %v", coordinates)
		}
		if len(coordinates) != 2 ||
			coordinates[0].Node != "bar" ||
			coordinates[1].Node != "foo" {
			t.Fatalf("expected: bar, foo recieved: %v", coordinates)
		}
	})
	// Filter on a nonexistent node segment
	req, _ = http.NewRequest("GET", "/v1/coordinate/nodes?segment=nope", nil)
	resp = httptest.NewRecorder()
	retry.Run(t, func(r *retry.R) {
		obj, err := a.srv.CoordinateNodes(resp, req)
		if err != nil {
			t.Fatalf("err: %v", err)
		}

		coordinates, ok := obj.(structs.Coordinates)
		if !ok {
			t.Fatalf("expected: structs.Coordinates, received: %v", coordinates)
		}
		if len(coordinates) != 0 {
			t.Fatalf("coordinates should be empty, received: %v", coordinates)
		}
	})
	// Filter on a real node segment
	req, _ = http.NewRequest("GET", "/v1/coordinate/nodes?segment=alpha", nil)
	resp = httptest.NewRecorder()
	retry.Run(t, func(r *retry.R) {
		obj, err := a.srv.CoordinateNodes(resp, req)
		if err != nil {
			t.Fatalf("err: %v", err)
		}

		coordinates, ok := obj.(structs.Coordinates)
		if !ok {
			t.Fatalf("expected: structs.Coordinates, received: %v", coordinates)
		}
		if len(coordinates) != 1 || coordinates[0].Node != "foo" {
			t.Fatalf("expected: foo received: %v", coordinates)
		}
	})
	// Make sure the empty filter works
	req, _ = http.NewRequest("GET", "/v1/coordinate/nodes?segment=", nil)
	resp = httptest.NewRecorder()
	retry.Run(t, func(r *retry.R) {
		obj, err := a.srv.CoordinateNodes(resp, req)
		if err != nil {
			t.Fatalf("err: %v", err)
		}

		coordinates, ok := obj.(structs.Coordinates)
		if !ok {
			t.Fatalf("expected: structs.Coordinates, received: %v", coordinates)
		}
		if len(coordinates) != 1 || coordinates[0].Node != "bar" {
			t.Fatalf("expected: bar received: %v", coordinates)
		}
	})
}

func TestCoordinate_Node(t *testing.T) {
	t.Parallel()
	a := NewTestAgent(t, t.Name(), "")
	defer a.Shutdown()
	testrpc.WaitForTestAgent(t, a.RPC, "dc1")

	// Make sure we get a 404 with no coordinates.
	req, _ := http.NewRequest("GET", "/v1/coordinate/node/foo?dc=dc1", nil)
	resp := httptest.NewRecorder()
	obj, err := a.srv.CoordinateNode(resp, req)
	if err != nil {
		t.Fatalf("err: %v", err)
	}
	if resp.Code != http.StatusNotFound {
		t.Fatalf("bad: %v", resp.Code)
	}

	// Register the nodes.
	nodes := []string{"foo", "bar"}
	for _, node := range nodes {
		req := structs.RegisterRequest{
			Datacenter: "dc1",
			Node:       node,
			Address:    "127.0.0.1",
		}
		var reply struct{}
		if err := a.RPC("Catalog.Register", &req, &reply); err != nil {
			t.Fatalf("err: %s", err)
		}
	}

	// Send some coordinates for a few nodes, waiting a little while for the
	// batch update to run.
	arg1 := structs.CoordinateUpdateRequest{
		Datacenter: "dc1",
		Node:       "foo",
		Segment:    "alpha",
		Coord:      coordinate.NewCoordinate(coordinate.DefaultConfig()),
	}
	var out struct{}
	if err := a.RPC("Coordinate.Update", &arg1, &out); err != nil {
		t.Fatalf("err: %v", err)
	}

	arg2 := structs.CoordinateUpdateRequest{
		Datacenter: "dc1",
		Node:       "bar",
		Coord:      coordinate.NewCoordinate(coordinate.DefaultConfig()),
	}
	if err := a.RPC("Coordinate.Update", &arg2, &out); err != nil {
		t.Fatalf("err: %v", err)
	}
	time.Sleep(300 * time.Millisecond)

	// Query back and check the nodes are present.
	req, _ = http.NewRequest("GET", "/v1/coordinate/node/foo?dc=dc1", nil)
	resp = httptest.NewRecorder()
	obj, err = a.srv.CoordinateNode(resp, req)
	if err != nil {
		t.Fatalf("err: %v", err)
	}

	coordinates := obj.(structs.Coordinates)
	if len(coordinates) != 1 ||
		coordinates[0].Node != "foo" {
		t.Fatalf("bad: %v", coordinates)
	}

	// Filter on a nonexistent node segment
	req, _ = http.NewRequest("GET", "/v1/coordinate/node/foo?segment=nope", nil)
	resp = httptest.NewRecorder()
	obj, err = a.srv.CoordinateNode(resp, req)
	if err != nil {
		t.Fatalf("err: %v", err)
	}
	if resp.Code != http.StatusNotFound {
		t.Fatalf("bad: %v", resp.Code)
	}

	// Filter on a real node segment
	req, _ = http.NewRequest("GET", "/v1/coordinate/node/foo?segment=alpha", nil)
	resp = httptest.NewRecorder()
	obj, err = a.srv.CoordinateNode(resp, req)
	if err != nil {
		t.Fatalf("err: %v", err)
	}

	coordinates = obj.(structs.Coordinates)
	if len(coordinates) != 1 || coordinates[0].Node != "foo" {
		t.Fatalf("bad: %v", coordinates)
	}

	// Make sure the empty filter works
	req, _ = http.NewRequest("GET", "/v1/coordinate/node/foo?segment=", nil)
	resp = httptest.NewRecorder()
	obj, err = a.srv.CoordinateNode(resp, req)
	if err != nil {
		t.Fatalf("err: %v", err)
	}
	if resp.Code != http.StatusNotFound {
		t.Fatalf("bad: %v", resp.Code)
	}
}

func TestCoordinate_Update(t *testing.T) {
	t.Parallel()
	a := NewTestAgent(t, t.Name(), "")
	defer a.Shutdown()
	testrpc.WaitForTestAgent(t, a.RPC, "dc1")

	// Register the node.
	reg := structs.RegisterRequest{
		Datacenter: "dc1",
		Node:       "foo",
		Address:    "127.0.0.1",
	}
	var reply struct{}
	if err := a.RPC("Catalog.Register", &reg, &reply); err != nil {
		t.Fatalf("err: %s", err)
	}

	// Update the coordinates and wait for it to complete.
	coord := coordinate.NewCoordinate(coordinate.DefaultConfig())
	coord.Height = -5.0
	body := structs.CoordinateUpdateRequest{
		Datacenter: "dc1",
		Node:       "foo",
		Coord:      coord,
	}
	req, _ := http.NewRequest("PUT", "/v1/coordinate/update", jsonReader(body))
	resp := httptest.NewRecorder()
	_, err := a.srv.CoordinateUpdate(resp, req)
	if err != nil {
		t.Fatalf("err: %v", err)
	}
	time.Sleep(300 * time.Millisecond)

	// Query back and check the coordinates are present.
	args := structs.NodeSpecificRequest{Node: "foo", Datacenter: "dc1"}
	var coords structs.IndexedCoordinates
	if err := a.RPC("Coordinate.Node", &args, &coords); err != nil {
		t.Fatalf("err: %s", err)
	}

	coordinates := coords.Coordinates
	if len(coordinates) != 1 ||
		coordinates[0].Node != "foo" {
		t.Fatalf("bad: %v", coordinates)
	}
}

func TestCoordinate_Update_ACLDeny(t *testing.T) {
	t.Parallel()
	a := NewTestAgent(t, t.Name(), TestACLConfig())
	defer a.Shutdown()
	testrpc.WaitForLeader(t, a.RPC, "dc1")

	coord := coordinate.NewCoordinate(coordinate.DefaultConfig())
	coord.Height = -5.0
	body := structs.CoordinateUpdateRequest{
		Datacenter: "dc1",
		Node:       "foo",
		Coord:      coord,
	}

	t.Run("no token", func(t *testing.T) {
		req, _ := http.NewRequest("PUT", "/v1/coordinate/update", jsonReader(body))
		if _, err := a.srv.CoordinateUpdate(nil, req); !acl.IsErrPermissionDenied(err) {
			t.Fatalf("err: %v", err)
		}
	})

	t.Run("valid token", func(t *testing.T) {
		req, _ := http.NewRequest("PUT", "/v1/coordinate/update?token=root", jsonReader(body))
		if _, err := a.srv.CoordinateUpdate(nil, req); err != nil {
			t.Fatalf("err: %v", err)
		}
	})
}
<|endoftext|>


<commit_before>package smoke_tests

import (
	"fmt"
	"sync"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	"github.com/pborman/uuid"
	"github.com/pivotal-cf/cf-rabbitmq-smoke-tests/tests/helper"
)

var _ = Describe("Smoke tests", func() {
	var (
		apps        map[string]string
		serviceName string
	)

	BeforeEach(func() {
		apps = map[string]string{
			"rmq-smoke-tests-ruby": "../assets/rabbit-example-app",
		}

		if testConfig.ServiceOffering == "p.rabbitmq" {
			apps["rmq-smoke-tests-spring"] = "../assets/spring-example-app"
		}

		serviceName = fmt.Sprintf("rmq-smoke-test-instance-%s", uuid.New()[:18])
	})

	AfterEach(func() {
		for appName, _ := range apps {
			helper.DeleteApp(appName)
		}
	})

	for _, plan := range testConfig.TestPlans {
		It(fmt.Sprintf("pushes an app, sends, and reads a message from RabbitMQ: plan '%s'", plan.Name), func() {
			helper.CreateService(testConfig.ServiceOffering, plan.Name, serviceName)

			var wg sync.WaitGroup
			wg.Add(len(apps))

			for appName, appPath := range apps {
				go func(appName, appPath string) {
					appURL := helper.PushAndBindApp(appName, serviceName, appPath)

					queue := fmt.Sprintf("%s-queue", appName)
					helper.SendMessage(appURL, queue, "foo")
					helper.SendMessage(appURL, queue, "bar")
					Expect(helper.ReceiveMessage(appURL, queue)).To(Equal("foo"))
					Expect(helper.ReceiveMessage(appURL, queue)).To(Equal("bar"))

					helper.UnbindService(appName, serviceName)
					wg.Done()
				}(appName, appPath)
			}

			wg.Wait()

			helper.DeleteService(serviceName)
		}, 300.0) // seconds
	}
})
<commit_msg>Allow Ginkgo to catch failures in go routines<commit_after>package smoke_tests

import (
	"fmt"
	"sync"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	"github.com/pborman/uuid"
	"github.com/pivotal-cf/cf-rabbitmq-smoke-tests/tests/helper"
)

var _ = Describe("Smoke tests", func() {
	var (
		apps        map[string]string
		serviceName string
	)

	BeforeEach(func() {
		apps = map[string]string{
			"rmq-smoke-tests-ruby": "../assets/rabbit-example-app",
		}

		if testConfig.ServiceOffering == "p.rabbitmq" {
			apps["rmq-smoke-tests-spring"] = "../assets/spring-example-app"
		}

		serviceName = fmt.Sprintf("rmq-smoke-test-instance-%s", uuid.New()[:18])
	})

	AfterEach(func() {
		for appName, _ := range apps {
			helper.DeleteApp(appName)
		}
	})

	for _, plan := range testConfig.TestPlans {
		It(fmt.Sprintf("pushes an app, sends, and reads a message from RabbitMQ: plan '%s'", plan.Name), func() {
			helper.CreateService(testConfig.ServiceOffering, plan.Name, serviceName)

			var wg sync.WaitGroup
			wg.Add(len(apps))

			for appName, appPath := range apps {
				go func(appName, appPath string) {
					defer GinkgoRecover()

					appURL := helper.PushAndBindApp(appName, serviceName, appPath)

					queue := fmt.Sprintf("%s-queue", appName)
					helper.SendMessage(appURL, queue, "foo")
					helper.SendMessage(appURL, queue, "bar")
					Expect(helper.ReceiveMessage(appURL, queue)).To(Equal("foo"))
					Expect(helper.ReceiveMessage(appURL, queue)).To(Equal("bar"))

					helper.UnbindService(appName, serviceName)
					wg.Done()
				}(appName, appPath)
			}

			wg.Wait()

			helper.DeleteService(serviceName)
		}, 300.0) // seconds
	}
})
<|endoftext|>


<commit_before>package weed_server

import (
	"bytes"
	"io"
	"mime"
	"mime/multipart"
	"net/http"
	"path"
	"strconv"
	"strings"
	"time"

	"net/url"

	"github.com/chrislusf/seaweedfs/weed/glog"
	"github.com/chrislusf/seaweedfs/weed/images"
	"github.com/chrislusf/seaweedfs/weed/operation"
	"github.com/chrislusf/seaweedfs/weed/storage"
	"github.com/chrislusf/seaweedfs/weed/util"
)

var fileNameEscaper = strings.NewReplacer("\\", "\\\\", "\"", "\\\"")

func (vs *VolumeServer) GetOrHeadHandler(w http.ResponseWriter, r *http.Request) {
	n := new(storage.Needle)
	vid, fid, filename, ext, _ := parseURLPath(r.URL.Path)
	volumeId, err := storage.NewVolumeId(vid)
	if err != nil {
		glog.V(2).Infoln("parsing error:", err, r.URL.Path)
		w.WriteHeader(http.StatusBadRequest)
		return
	}
	err = n.ParsePath(fid)
	if err != nil {
		glog.V(2).Infoln("parsing fid error:", err, r.URL.Path)
		w.WriteHeader(http.StatusBadRequest)
		return
	}

	glog.V(4).Infoln("volume", volumeId, "reading", n)
	if !vs.store.HasVolume(volumeId) {
		if !vs.ReadRedirect {
			glog.V(2).Infoln("volume is not local:", err, r.URL.Path)
			w.WriteHeader(http.StatusNotFound)
			return
		}
		lookupResult, err := operation.Lookup(vs.GetMasterNode(), volumeId.String())
		glog.V(2).Infoln("volume", volumeId, "found on", lookupResult, "error", err)
		if err == nil && len(lookupResult.Locations) > 0 {
			u, _ := url.Parse(util.NormalizeUrl(lookupResult.Locations[0].PublicUrl))
			u.Path = r.URL.Path
			arg := url.Values{}
			if c := r.FormValue("collection"); c != "" {
				arg.Set("collection", c)
			}
			u.RawQuery = arg.Encode()
			http.Redirect(w, r, u.String(), http.StatusMovedPermanently)

		} else {
			glog.V(2).Infoln("lookup error:", err, r.URL.Path)
			w.WriteHeader(http.StatusNotFound)
		}
		return
	}
	cookie := n.Cookie
	count, e := vs.store.ReadVolumeNeedle(volumeId, n)
	glog.V(4).Infoln("read bytes", count, "error", e)
	if e != nil || count <= 0 {
		glog.V(0).Infoln("read error:", e, r.URL.Path)
		w.WriteHeader(http.StatusNotFound)
		return
	}
	defer n.ReleaseMemory()
	if n.Cookie != cookie {
		glog.V(0).Infoln("request", r.URL.Path, "with unmaching cookie seen:", cookie, "expected:", n.Cookie, "from", r.RemoteAddr, "agent", r.UserAgent())
		w.WriteHeader(http.StatusNotFound)
		return
	}
	if n.LastModified != 0 {
		w.Header().Set("Last-Modified", time.Unix(int64(n.LastModified), 0).UTC().Format(http.TimeFormat))
		if r.Header.Get("If-Modified-Since") != "" {
			if t, parseError := time.Parse(http.TimeFormat, r.Header.Get("If-Modified-Since")); parseError == nil {
				if t.Unix() >= int64(n.LastModified) {
					w.WriteHeader(http.StatusNotModified)
					return
				}
			}
		}
	}
	etag := n.Etag()
	if inm := r.Header.Get("If-None-Match"); inm == etag {
		w.WriteHeader(http.StatusNotModified)
		return
	}
	w.Header().Set("Etag", etag)

	if vs.tryHandleChunkedFile(n, filename, w, r) {
		return
	}

	if n.NameSize > 0 && filename == "" {
		filename = string(n.Name)
		if ext == "" {
			ext = path.Ext(filename)
		}
	}
	mtype := ""
	if n.MimeSize > 0 {
		mt := string(n.Mime)
		if !strings.HasPrefix(mt, "application/octet-stream") {
			mtype = mt
		}
	}

	if ext != ".gz" {
		if n.IsGzipped() {
			if strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") {
				w.Header().Set("Content-Encoding", "gzip")
			} else {
				if n.Data, err = operation.UnGzipData(n.Data); err != nil {
					glog.V(0).Infoln("ungzip error:", err, r.URL.Path)
				}
			}
		}
	}
	if ext == ".png" || ext == ".jpg" || ext == ".gif" {
		width, height := 0, 0
		if r.FormValue("width") != "" {
			width, _ = strconv.Atoi(r.FormValue("width"))
		}
		if r.FormValue("height") != "" {
			height, _ = strconv.Atoi(r.FormValue("height"))
		}
		n.Data, _, _ = images.Resized(ext, n.Data, width, height)
	}

	if e := writeResponseContent(filename, mtype, bytes.NewReader(n.Data), w, r); e != nil {
		glog.V(2).Infoln("response write error:", e)
	}
}

func (vs *VolumeServer) FaviconHandler(w http.ResponseWriter, r *http.Request) {
	data, err := images.Asset("favicon/favicon.ico")
	if err != nil {
		glog.V(2).Infoln("favicon read error:", err)
		return
	}

	if e := writeResponseContent("favicon.ico", "image/x-icon", bytes.NewReader(data), w, r); e != nil {
		glog.V(2).Infoln("response write error:", e)
	}
}

func (vs *VolumeServer) tryHandleChunkedFile(n *storage.Needle, fileName string, w http.ResponseWriter, r *http.Request) (processed bool) {
	if !n.IsChunkedManifest() {
		return false
	}

	chunkManifest, e := operation.LoadChunkManifest(n.Data, n.IsGzipped())
	if e != nil {
		glog.V(0).Infof("load chunked manifest (%s) error: %v", r.URL.Path, e)
		return false
	}
	if fileName == "" && chunkManifest.Name != "" {
		fileName = chunkManifest.Name
	}
	mType := ""
	if chunkManifest.Mime != "" {
		mt := chunkManifest.Mime
		if !strings.HasPrefix(mt, "application/octet-stream") {
			mType = mt
		}
	}

	w.Header().Set("X-File-Store", "chunked")

	chunkedFileReader := &operation.ChunkedFileReader{
		Manifest: chunkManifest,
		Master:   vs.GetMasterNode(),
	}
	defer chunkedFileReader.Close()
	if e := writeResponseContent(fileName, mType, chunkedFileReader, w, r); e != nil {
		glog.V(2).Infoln("response write error:", e)
	}
	return true
}

func writeResponseContent(filename, mimeType string, rs io.ReadSeeker, w http.ResponseWriter, r *http.Request) error {
	totalSize, e := rs.Seek(0, 2)
	if mimeType == "" {
		if ext := path.Ext(filename); ext != "" {
			mimeType = mime.TypeByExtension(ext)
		}
	}
	if mimeType != "" {
		w.Header().Set("Content-Type", mimeType)
	}
	if filename != "" {
		contentDisposition := "inline"
		if r.FormValue("dl") != "" {
			if dl, _ := strconv.ParseBool(r.FormValue("dl")); dl {
				contentDisposition = "attachment"
			}
		}
		w.Header().Set("Content-Disposition", contentDisposition+`; filename="`+fileNameEscaper.Replace(filename)+`"`)
	}
	w.Header().Set("Accept-Ranges", "bytes")
	if r.Method == "HEAD" {
		w.Header().Set("Content-Length", strconv.FormatInt(totalSize, 10))
		return nil
	}
	rangeReq := r.Header.Get("Range")
	if rangeReq == "" {
		w.Header().Set("Content-Length", strconv.FormatInt(totalSize, 10))
		if _, e = rs.Seek(0, 0); e != nil {
			return e
		}
		_, e = io.Copy(w, rs)
		return e
	}

	//the rest is dealing with partial content request
	//mostly copy from src/pkg/net/http/fs.go
	ranges, err := parseRange(rangeReq, totalSize)
	if err != nil {
		http.Error(w, err.Error(), http.StatusRequestedRangeNotSatisfiable)
		return nil
	}
	if sumRangesSize(ranges) > totalSize {
		// The total number of bytes in all the ranges
		// is larger than the size of the file by
		// itself, so this is probably an attack, or a
		// dumb client.  Ignore the range request.
		return nil
	}
	if len(ranges) == 0 {
		return nil
	}
	if len(ranges) == 1 {
		// RFC 2616, Section 14.16:
		// "When an HTTP message includes the content of a single
		// range (for example, a response to a request for a
		// single range, or to a request for a set of ranges
		// that overlap without any holes), this content is
		// transmitted with a Content-Range header, and a
		// Content-Length header showing the number of bytes
		// actually transferred.
		// ...
		// A response to a request for a single range MUST NOT
		// be sent using the multipart/byteranges media type."
		ra := ranges[0]
		w.Header().Set("Content-Length", strconv.FormatInt(ra.length, 10))
		w.Header().Set("Content-Range", ra.contentRange(totalSize))
		w.WriteHeader(http.StatusPartialContent)
		if _, e = rs.Seek(ra.start, 0); e != nil {
			return e
		}

		_, e = io.CopyN(w, rs, ra.length)
		return e
	}
	// process multiple ranges
	for _, ra := range ranges {
		if ra.start > totalSize {
			http.Error(w, "Out of Range", http.StatusRequestedRangeNotSatisfiable)
			return nil
		}
	}
	sendSize := rangesMIMESize(ranges, mimeType, totalSize)
	pr, pw := io.Pipe()
	mw := multipart.NewWriter(pw)
	w.Header().Set("Content-Type", "multipart/byteranges; boundary="+mw.Boundary())
	sendContent := pr
	defer pr.Close() // cause writing goroutine to fail and exit if CopyN doesn't finish.
	go func() {
		for _, ra := range ranges {
			part, e := mw.CreatePart(ra.mimeHeader(mimeType, totalSize))
			if e != nil {
				pw.CloseWithError(e)
				return
			}
			if _, e = rs.Seek(ra.start, 0); e != nil {
				pw.CloseWithError(e)
				return
			}
			if _, e = io.CopyN(part, rs, ra.length); e != nil {
				pw.CloseWithError(e)
				return
			}
		}
		mw.Close()
		pw.Close()
	}()
	if w.Header().Get("Content-Encoding") == "" {
		w.Header().Set("Content-Length", strconv.FormatInt(sendSize, 10))
	}
	w.WriteHeader(http.StatusPartialContent)
	_, e = io.CopyN(w, sendContent, sendSize)
	return e
}
<commit_msg>refactoring<commit_after>package weed_server

import (
	"bytes"
	"io"
	"mime"
	"mime/multipart"
	"net/http"
	"net/url"
	"path"
	"strconv"
	"strings"
	"time"

	"github.com/chrislusf/seaweedfs/weed/glog"
	"github.com/chrislusf/seaweedfs/weed/images"
	"github.com/chrislusf/seaweedfs/weed/operation"
	"github.com/chrislusf/seaweedfs/weed/storage"
	"github.com/chrislusf/seaweedfs/weed/util"
)

var fileNameEscaper = strings.NewReplacer("\\", "\\\\", "\"", "\\\"")

func (vs *VolumeServer) GetOrHeadHandler(w http.ResponseWriter, r *http.Request) {
	n := new(storage.Needle)
	vid, fid, filename, ext, _ := parseURLPath(r.URL.Path)
	volumeId, err := storage.NewVolumeId(vid)
	if err != nil {
		glog.V(2).Infoln("parsing error:", err, r.URL.Path)
		w.WriteHeader(http.StatusBadRequest)
		return
	}
	err = n.ParsePath(fid)
	if err != nil {
		glog.V(2).Infoln("parsing fid error:", err, r.URL.Path)
		w.WriteHeader(http.StatusBadRequest)
		return
	}

	glog.V(4).Infoln("volume", volumeId, "reading", n)
	if !vs.store.HasVolume(volumeId) {
		if !vs.ReadRedirect {
			glog.V(2).Infoln("volume is not local:", err, r.URL.Path)
			w.WriteHeader(http.StatusNotFound)
			return
		}
		lookupResult, err := operation.Lookup(vs.GetMasterNode(), volumeId.String())
		glog.V(2).Infoln("volume", volumeId, "found on", lookupResult, "error", err)
		if err == nil && len(lookupResult.Locations) > 0 {
			u, _ := url.Parse(util.NormalizeUrl(lookupResult.Locations[0].PublicUrl))
			u.Path = r.URL.Path
			arg := url.Values{}
			if c := r.FormValue("collection"); c != "" {
				arg.Set("collection", c)
			}
			u.RawQuery = arg.Encode()
			http.Redirect(w, r, u.String(), http.StatusMovedPermanently)

		} else {
			glog.V(2).Infoln("lookup error:", err, r.URL.Path)
			w.WriteHeader(http.StatusNotFound)
		}
		return
	}
	cookie := n.Cookie
	count, e := vs.store.ReadVolumeNeedle(volumeId, n)
	glog.V(4).Infoln("read bytes", count, "error", e)
	if e != nil || count <= 0 {
		glog.V(0).Infoln("read error:", e, r.URL.Path)
		w.WriteHeader(http.StatusNotFound)
		return
	}
	defer n.ReleaseMemory()
	if n.Cookie != cookie {
		glog.V(0).Infoln("request", r.URL.Path, "with unmaching cookie seen:", cookie, "expected:", n.Cookie, "from", r.RemoteAddr, "agent", r.UserAgent())
		w.WriteHeader(http.StatusNotFound)
		return
	}
	if n.LastModified != 0 {
		w.Header().Set("Last-Modified", time.Unix(int64(n.LastModified), 0).UTC().Format(http.TimeFormat))
		if r.Header.Get("If-Modified-Since") != "" {
			if t, parseError := time.Parse(http.TimeFormat, r.Header.Get("If-Modified-Since")); parseError == nil {
				if t.Unix() >= int64(n.LastModified) {
					w.WriteHeader(http.StatusNotModified)
					return
				}
			}
		}
	}
	etag := n.Etag()
	if inm := r.Header.Get("If-None-Match"); inm == etag {
		w.WriteHeader(http.StatusNotModified)
		return
	}
	w.Header().Set("Etag", etag)

	if vs.tryHandleChunkedFile(n, filename, w, r) {
		return
	}

	if n.NameSize > 0 && filename == "" {
		filename = string(n.Name)
		if ext == "" {
			ext = path.Ext(filename)
		}
	}
	mtype := ""
	if n.MimeSize > 0 {
		mt := string(n.Mime)
		if !strings.HasPrefix(mt, "application/octet-stream") {
			mtype = mt
		}
	}

	if ext != ".gz" {
		if n.IsGzipped() {
			if strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") {
				w.Header().Set("Content-Encoding", "gzip")
			} else {
				if n.Data, err = operation.UnGzipData(n.Data); err != nil {
					glog.V(0).Infoln("ungzip error:", err, r.URL.Path)
				}
			}
		}
	}
	if ext == ".png" || ext == ".jpg" || ext == ".gif" {
		width, height := 0, 0
		if r.FormValue("width") != "" {
			width, _ = strconv.Atoi(r.FormValue("width"))
		}
		if r.FormValue("height") != "" {
			height, _ = strconv.Atoi(r.FormValue("height"))
		}
		n.Data, _, _ = images.Resized(ext, n.Data, width, height)
	}

	if e := writeResponseContent(filename, mtype, bytes.NewReader(n.Data), w, r); e != nil {
		glog.V(2).Infoln("response write error:", e)
	}
}

func (vs *VolumeServer) FaviconHandler(w http.ResponseWriter, r *http.Request) {
	data, err := images.Asset("favicon/favicon.ico")
	if err != nil {
		glog.V(2).Infoln("favicon read error:", err)
		return
	}

	if e := writeResponseContent("favicon.ico", "image/x-icon", bytes.NewReader(data), w, r); e != nil {
		glog.V(2).Infoln("response write error:", e)
	}
}

func (vs *VolumeServer) tryHandleChunkedFile(n *storage.Needle, fileName string, w http.ResponseWriter, r *http.Request) (processed bool) {
	if !n.IsChunkedManifest() {
		return false
	}

	chunkManifest, e := operation.LoadChunkManifest(n.Data, n.IsGzipped())
	if e != nil {
		glog.V(0).Infof("load chunked manifest (%s) error: %v", r.URL.Path, e)
		return false
	}
	if fileName == "" && chunkManifest.Name != "" {
		fileName = chunkManifest.Name
	}
	mType := ""
	if chunkManifest.Mime != "" {
		mt := chunkManifest.Mime
		if !strings.HasPrefix(mt, "application/octet-stream") {
			mType = mt
		}
	}

	w.Header().Set("X-File-Store", "chunked")

	chunkedFileReader := &operation.ChunkedFileReader{
		Manifest: chunkManifest,
		Master:   vs.GetMasterNode(),
	}
	defer chunkedFileReader.Close()
	if e := writeResponseContent(fileName, mType, chunkedFileReader, w, r); e != nil {
		glog.V(2).Infoln("response write error:", e)
	}
	return true
}

func writeResponseContent(filename, mimeType string, rs io.ReadSeeker, w http.ResponseWriter, r *http.Request) error {
	totalSize, e := rs.Seek(0, 2)
	if mimeType == "" {
		if ext := path.Ext(filename); ext != "" {
			mimeType = mime.TypeByExtension(ext)
		}
	}
	if mimeType != "" {
		w.Header().Set("Content-Type", mimeType)
	}
	if filename != "" {
		contentDisposition := "inline"
		if r.FormValue("dl") != "" {
			if dl, _ := strconv.ParseBool(r.FormValue("dl")); dl {
				contentDisposition = "attachment"
			}
		}
		w.Header().Set("Content-Disposition", contentDisposition+`; filename="`+fileNameEscaper.Replace(filename)+`"`)
	}
	w.Header().Set("Accept-Ranges", "bytes")
	if r.Method == "HEAD" {
		w.Header().Set("Content-Length", strconv.FormatInt(totalSize, 10))
		return nil
	}
	rangeReq := r.Header.Get("Range")
	if rangeReq == "" {
		w.Header().Set("Content-Length", strconv.FormatInt(totalSize, 10))
		if _, e = rs.Seek(0, 0); e != nil {
			return e
		}
		_, e = io.Copy(w, rs)
		return e
	}

	//the rest is dealing with partial content request
	//mostly copy from src/pkg/net/http/fs.go
	ranges, err := parseRange(rangeReq, totalSize)
	if err != nil {
		http.Error(w, err.Error(), http.StatusRequestedRangeNotSatisfiable)
		return nil
	}
	if sumRangesSize(ranges) > totalSize {
		// The total number of bytes in all the ranges
		// is larger than the size of the file by
		// itself, so this is probably an attack, or a
		// dumb client.  Ignore the range request.
		return nil
	}
	if len(ranges) == 0 {
		return nil
	}
	if len(ranges) == 1 {
		// RFC 2616, Section 14.16:
		// "When an HTTP message includes the content of a single
		// range (for example, a response to a request for a
		// single range, or to a request for a set of ranges
		// that overlap without any holes), this content is
		// transmitted with a Content-Range header, and a
		// Content-Length header showing the number of bytes
		// actually transferred.
		// ...
		// A response to a request for a single range MUST NOT
		// be sent using the multipart/byteranges media type."
		ra := ranges[0]
		w.Header().Set("Content-Length", strconv.FormatInt(ra.length, 10))
		w.Header().Set("Content-Range", ra.contentRange(totalSize))
		w.WriteHeader(http.StatusPartialContent)
		if _, e = rs.Seek(ra.start, 0); e != nil {
			return e
		}

		_, e = io.CopyN(w, rs, ra.length)
		return e
	}
	// process multiple ranges
	for _, ra := range ranges {
		if ra.start > totalSize {
			http.Error(w, "Out of Range", http.StatusRequestedRangeNotSatisfiable)
			return nil
		}
	}
	sendSize := rangesMIMESize(ranges, mimeType, totalSize)
	pr, pw := io.Pipe()
	mw := multipart.NewWriter(pw)
	w.Header().Set("Content-Type", "multipart/byteranges; boundary="+mw.Boundary())
	sendContent := pr
	defer pr.Close() // cause writing goroutine to fail and exit if CopyN doesn't finish.
	go func() {
		for _, ra := range ranges {
			part, e := mw.CreatePart(ra.mimeHeader(mimeType, totalSize))
			if e != nil {
				pw.CloseWithError(e)
				return
			}
			if _, e = rs.Seek(ra.start, 0); e != nil {
				pw.CloseWithError(e)
				return
			}
			if _, e = io.CopyN(part, rs, ra.length); e != nil {
				pw.CloseWithError(e)
				return
			}
		}
		mw.Close()
		pw.Close()
	}()
	if w.Header().Get("Content-Encoding") == "" {
		w.Header().Set("Content-Length", strconv.FormatInt(sendSize, 10))
	}
	w.WriteHeader(http.StatusPartialContent)
	_, e = io.CopyN(w, sendContent, sendSize)
	return e
}
<|endoftext|>


<commit_before>/*
 * Copyright 2015 Xuyuan Pang
 * Author: Xuyuan Pang
 *
 * 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 logo

import (
	"fmt"
	"io"
	"runtime"
	"sync"
	"time"
)

// Logo function type
type Logo func(depth int, level LogLevel, msg string)

// New create a new Logo which outputs log message into io.Writer.
func New(lowest LogLevel, w io.Writer, prefix string, flag int) Logo {
	var mu sync.Mutex
	var buf []byte

	return func(depth int, lvl LogLevel, msg string) {
		// ignore low level.
		if lvl < lowest {
			return
		}
		now := time.Now()
		var file string
		var line int
		mu.Lock()
		if flag&(Lshortfile|Llongfile) != 0 {
			// release lock wihle getting caller info - it's expensive.
			mu.Unlock()
			var ok bool
			_, file, line, ok = runtime.Caller(depth + 1)
			if !ok {
				file = "???"
				line = 0
			}
			mu.Lock()
		}
		buf = buf[:0]
		formatHeader(&buf, prefix, flag, now, file, line, lvl)
		if flag&Lcolor != 0 {
			buf = append(buf, colorMap[lvl]...)
			buf = append(buf, msg...)
			buf = append(buf, colorReset...)
		} else {
			buf = append(buf, msg...)
		}
		if len(msg) > 0 && msg[len(msg)-1] != '\n' {
			buf = append(buf, '\n')
		}
		w.Write(buf)
		mu.Unlock()
	}
}

// Group combines multiple Logo interface into a new Logo which
// outputs all log message into each Logo of provided.
func Group(lowest LogLevel, logos ...Logo) Logo {
	return func(depth int, lvl LogLevel, msg string) {
		if lvl < lowest {
			return
		}
		for _, l := range logos {
			l.Output(depth+2, lvl, msg)
		}
	}
}

// Output writes the output for a logging event.  The string s contains
// the text to print specified by the flags of the
// Logger.  A newline is appended if the last character of s is not
// already a newline.  depth is used to recover the PC and is
// provided for generality, although at the moment on all pre-defined
// paths it will be 2.
func (l Logo) Output(depth int, level LogLevel, msg string) {
	l(depth, level, msg)
}

// Log messages using this level.
func (l Logo) Log(level LogLevel, format string, args ...interface{}) {
	l.Output(2, level, fmt.Sprintf(format, args...))
}

// Debug logs Debug level message.
func (l Logo) Debug(format string, args ...interface{}) {
	l.Output(2, LevelDebug, fmt.Sprintf(format, args...))
}

// Info logs Info level message.
func (l Logo) Info(format string, args ...interface{}) {
	l.Output(2, LevelInfo, fmt.Sprintf(format, args...))
}

// Warning logs warning level message.
func (l Logo) Warning(format string, args ...interface{}) {
	l.Output(2, LevelWarning, fmt.Sprintf(format, args...))
}

// Error logs Error level message.
func (l Logo) Error(format string, args ...interface{}) {
	l.Output(2, LevelError, fmt.Sprintf(format, args...))
}

// Critical log Critical level message.
func (l Logo) Critical(format string, args ...interface{}) {
	l.Output(2, LevelCritical, fmt.Sprintf(format, args...))
}
<commit_msg>Use Logger<commit_after>/*
 * Copyright 2015 Xuyuan Pang
 * Author: Xuyuan Pang
 *
 * 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 logo

import (
	"fmt"
	"io"
	"runtime"
	"sync"
	"time"
)

// Logger function type
type Logger func(depth int, level LogLevel, msg string)

// New create a new Logger which outputs log message into io.Writer.
func New(lowest LogLevel, w io.Writer, prefix string, flag int) Logger {
	var mu sync.Mutex
	var buf []byte

	return func(depth int, lvl LogLevel, msg string) {
		// ignore low level.
		if lvl < lowest {
			return
		}
		now := time.Now()
		var file string
		var line int
		mu.Lock()
		if flag&(Lshortfile|Llongfile) != 0 {
			// release lock wihle getting caller info - it's expensive.
			mu.Unlock()
			var ok bool
			_, file, line, ok = runtime.Caller(depth + 1)
			if !ok {
				file = "???"
				line = 0
			}
			mu.Lock()
		}
		buf = buf[:0]
		formatHeader(&buf, prefix, flag, now, file, line, lvl)
		if flag&Lcolor != 0 {
			buf = append(buf, colorMap[lvl]...)
			buf = append(buf, msg...)
			buf = append(buf, colorReset...)
		} else {
			buf = append(buf, msg...)
		}
		if len(msg) > 0 && msg[len(msg)-1] != '\n' {
			buf = append(buf, '\n')
		}
		w.Write(buf)
		mu.Unlock()
	}
}

// Group combines multiple Logger interface into a new Logger which
// outputs all log message into each Logger of provided.
func Group(lowest LogLevel, Loggers ...Logger) Logger {
	return func(depth int, lvl LogLevel, msg string) {
		if lvl < lowest {
			return
		}
		for _, l := range Loggers {
			l.Output(depth+2, lvl, msg)
		}
	}
}

// Output writes the output for a logging event.  The string s contains
// the text to print specified by the flags of the
// Logger.  A newline is appended if the last character of s is not
// already a newline.  depth is used to recover the PC and is
// provided for generality, although at the moment on all pre-defined
// paths it will be 2.
func (l Logger) Output(depth int, level LogLevel, msg string) {
	l(depth, level, msg)
}

// Log messages using this level.
func (l Logger) Log(level LogLevel, format string, args ...interface{}) {
	l.Output(2, level, fmt.Sprintf(format, args...))
}

// Debug logs Debug level message.
func (l Logger) Debug(format string, args ...interface{}) {
	l.Output(2, LevelDebug, fmt.Sprintf(format, args...))
}

// Info logs Info level message.
func (l Logger) Info(format string, args ...interface{}) {
	l.Output(2, LevelInfo, fmt.Sprintf(format, args...))
}

// Warning logs warning level message.
func (l Logger) Warning(format string, args ...interface{}) {
	l.Output(2, LevelWarning, fmt.Sprintf(format, args...))
}

// Error logs Error level message.
func (l Logger) Error(format string, args ...interface{}) {
	l.Output(2, LevelError, fmt.Sprintf(format, args...))
}

// Critical log Critical level message.
func (l Logger) Critical(format string, args ...interface{}) {
	l.Output(2, LevelCritical, fmt.Sprintf(format, args...))
}
<|endoftext|>


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

//go:build unix

package main

import (
	"fmt"
	"os"
)

/*
#include <signal.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

sig_atomic_t expectCSigsegv;
int *sigfwdP;

static void sigsegv() {
	expectCSigsegv = 1;
	*sigfwdP = 1;
	fprintf(stderr, "ERROR: C SIGSEGV not thrown on caught?.\n");
	exit(2);
}

static void segvhandler(int signum) {
	if (signum == SIGSEGV) {
		if (expectCSigsegv == 0) {
			fprintf(stderr, "SIGSEGV caught in C unexpectedly\n");
			exit(1);
		}
		fprintf(stdout, "OK\n");
		exit(0);  // success
	}
}

static void __attribute__ ((constructor)) sigsetup(void) {
	if (getenv("GO_TEST_CGOSIGFWD") == NULL) {
		return;
	}

	struct sigaction act;

	memset(&act, 0, sizeof act);
	act.sa_handler = segvhandler;
	sigaction(SIGSEGV, &act, NULL);
}
*/
import "C"

func init() {
	register("CgoSigfwd", CgoSigfwd)
}

var nilPtr *byte

func f() (ret bool) {
	defer func() {
		if recover() == nil {
			fmt.Errorf("ERROR: couldn't raise SIGSEGV in Go.")
			C.exit(2)
		}
		ret = true
	}()
	*nilPtr = 1
	return false
}

func CgoSigfwd() {
	if os.Getenv("GO_TEST_CGOSIGFWD") == "" {
		fmt.Fprintf(os.Stderr, "test must be run with GO_TEST_CGOSIGFWD set\n")
		os.Exit(1)
	}

	// Test that the signal originating in Go is handled (and recovered) by Go.
	if !f() {
		fmt.Fprintf(os.Stderr, "couldn't recover from SIGSEGV in Go.\n")
		C.exit(2)
	}

	// Test that the signal originating in C is handled by C.
	C.sigsegv()
}
<commit_msg>runtime: fix missing error print in TestCgoSigfwd<commit_after>// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

//go:build unix

package main

import (
	"fmt"
	"os"
)

/*
#include <signal.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

sig_atomic_t expectCSigsegv;
int *sigfwdP;

static void sigsegv() {
	expectCSigsegv = 1;
	*sigfwdP = 1;
	fprintf(stderr, "ERROR: C SIGSEGV not thrown on caught?.\n");
	exit(2);
}

static void segvhandler(int signum) {
	if (signum == SIGSEGV) {
		if (expectCSigsegv == 0) {
			fprintf(stderr, "SIGSEGV caught in C unexpectedly\n");
			exit(1);
		}
		fprintf(stdout, "OK\n");
		exit(0);  // success
	}
}

static void __attribute__ ((constructor)) sigsetup(void) {
	if (getenv("GO_TEST_CGOSIGFWD") == NULL) {
		return;
	}

	struct sigaction act;

	memset(&act, 0, sizeof act);
	act.sa_handler = segvhandler;
	sigaction(SIGSEGV, &act, NULL);
}
*/
import "C"

func init() {
	register("CgoSigfwd", CgoSigfwd)
}

var nilPtr *byte

func f() (ret bool) {
	defer func() {
		if recover() == nil {
			fmt.Fprintf(os.Stderr, "ERROR: couldn't raise SIGSEGV in Go\n")
			C.exit(2)
		}
		ret = true
	}()
	*nilPtr = 1
	return false
}

func CgoSigfwd() {
	if os.Getenv("GO_TEST_CGOSIGFWD") == "" {
		fmt.Fprintf(os.Stderr, "test must be run with GO_TEST_CGOSIGFWD set\n")
		os.Exit(1)
	}

	// Test that the signal originating in Go is handled (and recovered) by Go.
	if !f() {
		fmt.Fprintf(os.Stderr, "couldn't recover from SIGSEGV in Go.\n")
		C.exit(2)
	}

	// Test that the signal originating in C is handled by C.
	C.sigsegv()
}
<|endoftext|>


<commit_before>package agent

import (
	"bytes"
	_ "crypto/sha512" // import sha512 to make sha512 ssl certs work
	"fmt"
	"io"
	"mime/multipart"
	"net/http"
	// "net/http/httputil"
	"errors"
	"net/url"
	"os"

	"github.com/buildkite/agent/api"
	"github.com/buildkite/agent/logger"
)

type FormUploader struct {
}

func (u *FormUploader) Setup(destination string) error {
	return nil
}

// The FormUploader doens't specify a URL, as one is provided by Buildkite
// after uploading
func (u *FormUploader) URL(artifact *api.Artifact) string {
	return ""
}

func (u *FormUploader) Upload(artifact *api.Artifact) error {
	// Create a HTTP request for uploading the file
	request, err := createUploadRequest(artifact)
	if err != nil {
		return err
	}

	// Create the client
	client := &http.Client{}

	// Perform the request
	logger.Debug("%s %s", request.Method, request.URL)
	response, err := client.Do(request)

	// Check for errors
	if err != nil {
		return err
	} else {
		// Be sure to close the response body at the end of
		// this function
		defer response.Body.Close()

		if response.StatusCode/100 != 2 {
			body := &bytes.Buffer{}
			_, err := body.ReadFrom(response.Body)
			if err != nil {
				return err
			}

			// Return a custom error with the response body from the page
			message := fmt.Sprintf("%s (%d)", body, response.StatusCode)
			return errors.New(message)
		}
	}

	return nil
}

// Creates a new file upload http request with optional extra params
func createUploadRequest(artifact *api.Artifact) (*http.Request, error) {
	file, err := os.Open(artifact.AbsolutePath)
	if err != nil {
		return nil, err
	}
	defer file.Close()

	body := &bytes.Buffer{}
	writer := multipart.NewWriter(body)

	// Set the post data for the request
	for key, val := range artifact.UploadInstructions.Data {
		err = writer.WriteField(key, val)
		if err != nil {
			return nil, err
		}
	}

	// It's important that we add the form field last because when
	// uploading to an S3 form, they are really nit-picky about the field
	// order, and the file needs to be the last one other it doesn't work.
	part, err := writer.CreateFormFile(artifact.UploadInstructions.Action.FileInput, artifact.Path)
	if err != nil {
		return nil, err
	}

	_, err = io.Copy(part, file)
	if err != nil {
		return nil, err
	}

	err = writer.Close()
	if err != nil {
		return nil, err
	}

	// Create the URL that we'll send data to
	uri, err := url.Parse(artifact.UploadInstructions.Action.URL)
	if err != nil {
		return nil, err
	}

	uri.Path = artifact.UploadInstructions.Action.Path

	// Create the request
	req, err := http.NewRequest(artifact.UploadInstructions.Action.Method, uri.String(), body)
	if err != nil {
		return nil, err
	}

	// Finally add the multipart content type to the request
	req.Header.Add("Content-Type", writer.FormDataContentType())

	return req, nil
}
<commit_msg>Handle the new ${artifact:filepath} variable from the UploadInstructions<commit_after>package agent

import (
	"bytes"
	_ "crypto/sha512" // import sha512 to make sha512 ssl certs work
	"fmt"
	"io"
	"mime/multipart"
	"net/http"
	"regexp"
	// "net/http/httputil"
	"errors"
	"net/url"
	"os"

	"github.com/buildkite/agent/api"
	"github.com/buildkite/agent/logger"
)

var ArtifactFilepathVariableRegex = regexp.MustCompile("\\$\\{artifact\\:filepath\\}")

type FormUploader struct {
}

func (u *FormUploader) Setup(destination string) error {
	return nil
}

// The FormUploader doens't specify a URL, as one is provided by Buildkite
// after uploading
func (u *FormUploader) URL(artifact *api.Artifact) string {
	return ""
}

func (u *FormUploader) Upload(artifact *api.Artifact) error {
	// Create a HTTP request for uploading the file
	request, err := createUploadRequest(artifact)
	if err != nil {
		return err
	}

	// Create the client
	client := &http.Client{}

	// Perform the request
	logger.Debug("%s %s", request.Method, request.URL)
	response, err := client.Do(request)

	// Check for errors
	if err != nil {
		return err
	} else {
		// Be sure to close the response body at the end of
		// this function
		defer response.Body.Close()

		if response.StatusCode/100 != 2 {
			body := &bytes.Buffer{}
			_, err := body.ReadFrom(response.Body)
			if err != nil {
				return err
			}

			// Return a custom error with the response body from the page
			message := fmt.Sprintf("%s (%d)", body, response.StatusCode)
			return errors.New(message)
		}
	}

	return nil
}

// Creates a new file upload http request with optional extra params
func createUploadRequest(artifact *api.Artifact) (*http.Request, error) {
	file, err := os.Open(artifact.AbsolutePath)
	if err != nil {
		return nil, err
	}
	defer file.Close()

	body := &bytes.Buffer{}
	writer := multipart.NewWriter(body)

	// Set the post data for the request
	for key, val := range artifact.UploadInstructions.Data {
		// Replace the magical ${artifact:filepath} variable with the
		// artifact's filepath
		newVal := ArtifactFilepathVariableRegex.ReplaceAllLiteralString(val, artifact.Path)

		// Write the new value to the form
		err = writer.WriteField(key, newVal)
		if err != nil {
			return nil, err
		}
	}

	// It's important that we add the form field last because when
	// uploading to an S3 form, they are really nit-picky about the field
	// order, and the file needs to be the last one other it doesn't work.
	part, err := writer.CreateFormFile(artifact.UploadInstructions.Action.FileInput, artifact.Path)
	if err != nil {
		return nil, err
	}

	_, err = io.Copy(part, file)
	if err != nil {
		return nil, err
	}

	err = writer.Close()
	if err != nil {
		return nil, err
	}

	// Create the URL that we'll send data to
	uri, err := url.Parse(artifact.UploadInstructions.Action.URL)
	if err != nil {
		return nil, err
	}

	uri.Path = artifact.UploadInstructions.Action.Path

	// Create the request
	req, err := http.NewRequest(artifact.UploadInstructions.Action.Method, uri.String(), body)
	if err != nil {
		return nil, err
	}

	// Finally add the multipart content type to the request
	req.Header.Add("Content-Type", writer.FormDataContentType())

	return req, nil
}
<|endoftext|>


<commit_before>// weather-api.go
//
// This is a simple example of how to use the api package.
//
// The getWeather() function takes the NOAA station id for a given location (full list at http://w1.weather.gov/xml/current_obs/)
// and returns the current weather conditions as an xml-formatted string.
//
// Inside main(), getWeather is assigned to respond to requests where "/weather/" is found in the url from the client;
// it will send its responses back in text/xml format, using utf-8, back to the client.
//
// This example server runs on port 9001, and so any request in the form:
//
// http://[localhost/domain/ip of server]:9001/weather/[station id]
//
// will work, as long as [station id] corresponds to a valid NOAA value.

package main

import (
	"fmt"
	"io/ioutil"
	"net/http"
	"strings"
	"github.com/dpapathanasiou/api"
)

// The getWeather function accepts an http.ResponseWriter and http.Request object as input;
// the latter is used to find specific information about the client request, and how to process it.
// The http.ResponseWriter is included if it's necessary to write additional headers to the reply,
// beyond the Content-type and Content-length values provided automatically by the api package
// (in this specific example, the http.ResponseWriter is not used).
func getWeather(w http.ResponseWriter, r *http.Request) string {
	xml := "<error>Bad Request</error>" // the default response string
	parts := strings.Split(r.URL.Path, "/")

	if len(parts) < 3 {
		// there is nothing following /weather/ in the request url, so return an error message in xml
		xml = "<error>Please specify a NOAA station id</error>"
	} else {
		// Use the string immediately following /weather/ in the request url as the location;
		// the location must be a valid NOAA station id, as defined here: http://w1.weather.gov/xml/current_obs/
		location := parts[2]
		res, err := http.Get(fmt.Sprintf("http://w1.weather.gov/xml/current_obs/%s.xml", location))
		if err != nil {
			api.Srv.Logger.Print(err)
		}

		if res.StatusCode == 200 {
			// There is a NOAA station id matching the location in the request url
			// and we were able to get a valid reply from the NOAA server in xml
			b, err := ioutil.ReadAll(res.Body)
			if err != nil {
				api.Srv.Logger.Print(err)
			}
			res.Body.Close()

			// The xml returned from NOAA has an xsl definition that will confuse some clients, so remove it
			// (probably should use Go's xml parsing package, but this is good enough for a quick-and-dirty example like this
			xml = strings.Replace(string(b), "<?xml-stylesheet href=\"latest_ob.xsl\" type=\"text/xsl\"?>", "", -1)

			// also show an example of what the log output is for this request
			api.Srv.Logger.Printf("INFO: found current weather for NOAA station id %s\n", location)
		} else {
			// While the http.Get() request succeeded, something else went wrong,
			// most likely a 404 status, which means the station id is invalid
			api.Srv.Logger.Printf("WARN: problem finding weather for NOAA station id %s (NOAA server reply: %d)\n", location, res.StatusCode)
			xml = fmt.Sprintf("<error status=\"%d\">Could not get weather for NOAA station id %s</error>", res.StatusCode, location)
		}
	}
	return xml
}

// The main function shows how to use the api package to handle different request patterns.
// First, a map of type { string: func(http.ResponseWriter, *http.Request) } is created.
// Next, the map is populated with pattern strings (as they as found in the request url), mapped
// to the api.Respond function (which defines both the media type and the charset), which calls
// the function which actually processes the client request, and returns a string in the expected
// format. This example defines just one pattern and response (i.e., "/weather/" returns an xml
// reply in utf-8), but other patterns and response functions can be added to the multiplexer.
func main() {
	handlers := map[string]func(http.ResponseWriter, *http.Request){}
	handlers["/weather/"] = func(w http.ResponseWriter, r *http.Request) {
		api.Respond("text/xml", "utf-8", getWeather)(w, r)
	}

	api.NewServer(9001, api.DefaultServerReadTimeout, handlers)
}
<commit_msg>Update example/weather-api.go<commit_after>// weather-api.go
//
// This is a simple example of how to use the api package.
//
// The getWeather() function takes the NOAA station id for a given location (full list at http://w1.weather.gov/xml/current_obs/)
// and returns the current weather conditions as an xml-formatted string.
//
// Inside main(), getWeather is assigned to respond to requests where "/weather/" is found in the url from the client;
// it will send its responses back in text/xml format, using utf-8, back to the client.
//
// This example server runs on port 9001, and so any request in the form:
//
// http://[localhost/domain/ip of server]:9001/weather/[station id]
//
// will work, as long as [station id] corresponds to a valid NOAA value.

package main

import (
	"fmt"
	"io/ioutil"
	"net/http"
	"strings"
	"github.com/dpapathanasiou/go-api"
)

// The getWeather function accepts an http.ResponseWriter and http.Request object as input;
// the latter is used to find specific information about the client request, and how to process it.
// The http.ResponseWriter is included if it's necessary to write additional headers to the reply,
// beyond the Content-type and Content-length values provided automatically by the api package
// (in this specific example, the http.ResponseWriter is not used).
func getWeather(w http.ResponseWriter, r *http.Request) string {
	xml := "<error>Bad Request</error>" // the default response string
	parts := strings.Split(r.URL.Path, "/")

	if len(parts) < 3 {
		// there is nothing following /weather/ in the request url, so return an error message in xml
		xml = "<error>Please specify a NOAA station id</error>"
	} else {
		// Use the string immediately following /weather/ in the request url as the location;
		// the location must be a valid NOAA station id, as defined here: http://w1.weather.gov/xml/current_obs/
		location := parts[2]
		res, err := http.Get(fmt.Sprintf("http://w1.weather.gov/xml/current_obs/%s.xml", location))
		if err != nil {
			api.Srv.Logger.Print(err)
		}

		if res.StatusCode == 200 {
			// There is a NOAA station id matching the location in the request url
			// and we were able to get a valid reply from the NOAA server in xml
			b, err := ioutil.ReadAll(res.Body)
			if err != nil {
				api.Srv.Logger.Print(err)
			}
			res.Body.Close()

			// The xml returned from NOAA has an xsl definition that will confuse some clients, so remove it
			// (probably should use Go's xml parsing package, but this is good enough for a quick-and-dirty example like this
			xml = strings.Replace(string(b), "<?xml-stylesheet href=\"latest_ob.xsl\" type=\"text/xsl\"?>", "", -1)

			// also show an example of what the log output is for this request
			api.Srv.Logger.Printf("INFO: found current weather for NOAA station id %s\n", location)
		} else {
			// While the http.Get() request succeeded, something else went wrong,
			// most likely a 404 status, which means the station id is invalid
			api.Srv.Logger.Printf("WARN: problem finding weather for NOAA station id %s (NOAA server reply: %d)\n", location, res.StatusCode)
			xml = fmt.Sprintf("<error status=\"%d\">Could not get weather for NOAA station id %s</error>", res.StatusCode, location)
		}
	}
	return xml
}

// The main function shows how to use the api package to handle different request patterns.
// First, a map of type { string: func(http.ResponseWriter, *http.Request) } is created.
// Next, the map is populated with pattern strings (as they as found in the request url), mapped
// to the api.Respond function (which defines both the media type and the charset), which calls
// the function which actually processes the client request, and returns a string in the expected
// format. This example defines just one pattern and response (i.e., "/weather/" returns an xml
// reply in utf-8), but other patterns and response functions can be added to the multiplexer.
func main() {
	handlers := map[string]func(http.ResponseWriter, *http.Request){}
	handlers["/weather/"] = func(w http.ResponseWriter, r *http.Request) {
		api.Respond("text/xml", "utf-8", getWeather)(w, r)
	}

	api.NewServer(9001, api.DefaultServerReadTimeout, handlers)
}
<|endoftext|>


<commit_before>package app

import (
	"fmt"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"sync"
	"time"

	"github.com/ugorji/go/codec"
	"golang.org/x/net/context"

	"github.com/weaveworks/common/mtime"
	"github.com/weaveworks/scope/report"
)

// Reporter is something that can produce reports on demand. It's a convenient
// interface for parts of the app, and several experimental components.
type Reporter interface {
	Report(context.Context) (report.Report, error)
	WaitOn(context.Context, chan struct{})
	UnWait(context.Context, chan struct{})
}

// Adder is something that can accept reports. It's a convenient interface for
// parts of the app, and several experimental components.  It takes the following
// arguments:
// - context.Context: the request context
// - report.Report: the deserialised report
// - []byte: the serialised report (as gzip'd msgpack)
type Adder interface {
	Add(context.Context, report.Report, []byte) error
}

// A Collector is a Reporter and an Adder
type Collector interface {
	Reporter
	Adder
}

// Collector receives published reports from multiple producers. It yields a
// single merged report, representing all collected reports.
type collector struct {
	mtx        sync.Mutex
	reports    []report.Report
	timestamps []time.Time
	window     time.Duration
	cached     *report.Report
	merger     Merger
	waitableCondition
}

type waitableCondition struct {
	sync.Mutex
	waiters map[chan struct{}]struct{}
}

func (wc *waitableCondition) WaitOn(_ context.Context, waiter chan struct{}) {
	wc.Lock()
	wc.waiters[waiter] = struct{}{}
	wc.Unlock()
}

func (wc *waitableCondition) UnWait(_ context.Context, waiter chan struct{}) {
	wc.Lock()
	delete(wc.waiters, waiter)
	wc.Unlock()
}

func (wc *waitableCondition) Broadcast() {
	wc.Lock()
	for waiter := range wc.waiters {
		// Non-block write to channel
		select {
		case waiter <- struct{}{}:
		default:
		}
	}
	wc.Unlock()
}

// NewCollector returns a collector ready for use.
func NewCollector(window time.Duration) Collector {
	return &collector{
		window: window,
		waitableCondition: waitableCondition{
			waiters: map[chan struct{}]struct{}{},
		},
		merger: NewSmartMerger(),
	}
}

// Add adds a report to the collector's internal state. It implements Adder.
func (c *collector) Add(_ context.Context, rpt report.Report, _ []byte) error {
	c.mtx.Lock()
	defer c.mtx.Unlock()
	c.reports = append(c.reports, rpt)
	c.timestamps = append(c.timestamps, mtime.Now())

	c.clean()
	c.cached = nil
	if rpt.Shortcut {
		c.Broadcast()
	}
	return nil
}

// Report returns a merged report over all added reports. It implements
// Reporter.
func (c *collector) Report(_ context.Context) (report.Report, error) {
	c.mtx.Lock()
	defer c.mtx.Unlock()

	// If the oldest report is still within range,
	// and there is a cached report, return that.
	if c.cached != nil && len(c.reports) > 0 {
		oldest := mtime.Now().Add(-c.window)
		if c.timestamps[0].After(oldest) {
			return *c.cached, nil
		}
	}

	c.clean()
	rpt := c.merger.Merge(c.reports).Upgrade()
	c.cached = &rpt
	return rpt, nil
}

func (c *collector) clean() {
	var (
		cleanedReports    = make([]report.Report, 0, len(c.reports))
		cleanedTimestamps = make([]time.Time, 0, len(c.timestamps))
		oldest            = mtime.Now().Add(-c.window)
	)
	for i, r := range c.reports {
		if c.timestamps[i].After(oldest) {
			cleanedReports = append(cleanedReports, r)
			cleanedTimestamps = append(cleanedTimestamps, c.timestamps[i])
		}
	}
	c.reports = cleanedReports
	c.timestamps = cleanedTimestamps
}

// StaticCollector always returns the given report.
type StaticCollector report.Report

// Report returns a merged report over all added reports. It implements
// Reporter.
func (c StaticCollector) Report(context.Context) (report.Report, error) { return report.Report(c), nil }

// Add adds a report to the collector's internal state. It implements Adder.
func (c StaticCollector) Add(context.Context, report.Report, []byte) error { return nil }

// WaitOn lets other components wait on a new report being received. It
// implements Reporter.
func (c StaticCollector) WaitOn(context.Context, chan struct{}) {}

// UnWait lets other components stop waiting on a new report being received. It
// implements Reporter.
func (c StaticCollector) UnWait(context.Context, chan struct{}) {}

// NewFileCollector reads and parses the files at path (a file or
// directory) as reports.  If there are multiple files, and they all
// have names representing "nanoseconds since epoch" timestamps,
// e.g. "1488557088545489008.msgpack.gz", then the collector will
// return merged reports resulting from replaying the file reports in
// a loop at a sequence and speed determined by the timestamps.
// Otherwise the collector always returns the merger of all reports.
func NewFileCollector(path string, window time.Duration) (Collector, error) {
	var (
		timestamps []time.Time
		reports    []report.Report
	)
	allTimestamped := true
	if err := filepath.Walk(path,
		func(p string, info os.FileInfo, err error) error {
			if err != nil {
				return err
			}
			if info.IsDir() {
				return nil
			}
			t, err := timestampFromFilepath(p)
			if err != nil {
				allTimestamped = false
			}
			timestamps = append(timestamps, t)

			rpt, err := readReport(p)
			if err != nil {
				return err
			}
			reports = append(reports, rpt)
			return nil
		}); err != nil {
		return nil, err
	}
	if len(reports) > 1 && allTimestamped {
		collector := NewCollector(window)
		go replay(collector, timestamps, reports)
		return collector, nil
	}
	return StaticCollector(NewSmartMerger().Merge(reports).Upgrade()), nil
}

func timestampFromFilepath(path string) (time.Time, error) {
	name := filepath.Base(path)
	for {
		ext := filepath.Ext(name)
		if ext == "" {
			break
		}
		name = strings.TrimSuffix(name, ext)
	}
	nanosecondsSinceEpoch, err := strconv.ParseInt(name, 10, 64)
	if err != nil {
		return time.Time{}, fmt.Errorf("filename '%s' is not a number (representing nanoseconds since epoch): %v", name, err)
	}
	return time.Unix(0, nanosecondsSinceEpoch), nil
}

func readReport(path string) (rpt report.Report, _ error) {
	f, err := os.Open(path)
	if err != nil {
		return rpt, err
	}
	defer f.Close()

	var (
		handle  codec.Handle
		gzipped bool
	)
	fileType := filepath.Ext(path)
	if fileType == ".gz" {
		gzipped = true
		fileType = filepath.Ext(strings.TrimSuffix(path, fileType))
	}
	switch fileType {
	case ".json":
		handle = &codec.JsonHandle{}
	case ".msgpack":
		handle = &codec.MsgpackHandle{}
	default:
		return rpt, fmt.Errorf("Unsupported file extension: %v", fileType)
	}

	err = rpt.ReadBinary(f, gzipped, handle)

	return rpt, err
}

func replay(a Adder, timestamps []time.Time, reports []report.Report) {
	// calculate delays between report n and n+1
	l := len(timestamps)
	delays := make([]time.Duration, l, l)
	for i, t := range timestamps[0 : l-1] {
		delays[i] = timestamps[i+1].Sub(t)
		if delays[i] < 0 {
			panic(fmt.Errorf("replay timestamps are not in order! %v", timestamps))
		}
	}
	// We don't know how long to wait before looping round, so make a
	// good guess.
	delays[l-1] = timestamps[l-1].Sub(timestamps[0]) / time.Duration(l)

	due := time.Now()
	for {
		for i, r := range reports {
			a.Add(nil, r, nil)
			due = due.Add(delays[i])
			delay := due.Sub(time.Now())
			if delay > 0 {
				time.Sleep(delay)
			}
		}
	}
}
<commit_msg>quantise reports<commit_after>package app

import (
	"fmt"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"sync"
	"time"

	"github.com/ugorji/go/codec"
	"golang.org/x/net/context"

	"github.com/weaveworks/common/mtime"
	"github.com/weaveworks/scope/report"
)

// We merge all reports received within the specified interval, and
// discard the orignals. Higher figures improve the performance of
// Report(), but at the expense of lower time resolution, since time
// is effectively advancing in quantiles.
//
// The current figure is identical to the default
// probe.publishInterval, which results in performance improvements
// as soon as there is more than one probe.
const reportQuantisationInterval = 3 * time.Second

// Reporter is something that can produce reports on demand. It's a convenient
// interface for parts of the app, and several experimental components.
type Reporter interface {
	Report(context.Context) (report.Report, error)
	WaitOn(context.Context, chan struct{})
	UnWait(context.Context, chan struct{})
}

// Adder is something that can accept reports. It's a convenient interface for
// parts of the app, and several experimental components.  It takes the following
// arguments:
// - context.Context: the request context
// - report.Report: the deserialised report
// - []byte: the serialised report (as gzip'd msgpack)
type Adder interface {
	Add(context.Context, report.Report, []byte) error
}

// A Collector is a Reporter and an Adder
type Collector interface {
	Reporter
	Adder
}

// Collector receives published reports from multiple producers. It yields a
// single merged report, representing all collected reports.
type collector struct {
	mtx        sync.Mutex
	reports    []report.Report
	timestamps []time.Time
	window     time.Duration
	cached     *report.Report
	merger     Merger
	waitableCondition
}

type waitableCondition struct {
	sync.Mutex
	waiters map[chan struct{}]struct{}
}

func (wc *waitableCondition) WaitOn(_ context.Context, waiter chan struct{}) {
	wc.Lock()
	wc.waiters[waiter] = struct{}{}
	wc.Unlock()
}

func (wc *waitableCondition) UnWait(_ context.Context, waiter chan struct{}) {
	wc.Lock()
	delete(wc.waiters, waiter)
	wc.Unlock()
}

func (wc *waitableCondition) Broadcast() {
	wc.Lock()
	for waiter := range wc.waiters {
		// Non-block write to channel
		select {
		case waiter <- struct{}{}:
		default:
		}
	}
	wc.Unlock()
}

// NewCollector returns a collector ready for use.
func NewCollector(window time.Duration) Collector {
	return &collector{
		window: window,
		waitableCondition: waitableCondition{
			waiters: map[chan struct{}]struct{}{},
		},
		merger: NewSmartMerger(),
	}
}

// Add adds a report to the collector's internal state. It implements Adder.
func (c *collector) Add(_ context.Context, rpt report.Report, _ []byte) error {
	c.mtx.Lock()
	defer c.mtx.Unlock()
	c.reports = append(c.reports, rpt)
	c.timestamps = append(c.timestamps, mtime.Now())

	c.clean()
	c.cached = nil
	if rpt.Shortcut {
		c.Broadcast()
	}
	return nil
}

// Report returns a merged report over all added reports. It implements
// Reporter.
func (c *collector) Report(_ context.Context) (report.Report, error) {
	c.mtx.Lock()
	defer c.mtx.Unlock()

	// If the oldest report is still within range,
	// and there is a cached report, return that.
	if c.cached != nil && len(c.reports) > 0 {
		oldest := mtime.Now().Add(-c.window)
		if c.timestamps[0].After(oldest) {
			return *c.cached, nil
		}
	}

	c.clean()
	c.quantise()

	rpt := c.merger.Merge(c.reports).Upgrade()
	c.cached = &rpt
	return rpt, nil
}

// remove reports older than the app.window
func (c *collector) clean() {
	var (
		cleanedReports    = make([]report.Report, 0, len(c.reports))
		cleanedTimestamps = make([]time.Time, 0, len(c.timestamps))
		oldest            = mtime.Now().Add(-c.window)
	)
	for i, r := range c.reports {
		if c.timestamps[i].After(oldest) {
			cleanedReports = append(cleanedReports, r)
			cleanedTimestamps = append(cleanedTimestamps, c.timestamps[i])
		}
	}
	c.reports = cleanedReports
	c.timestamps = cleanedTimestamps
}

// Merge reports received within the same reportQuantisationInterval.
//
// Quantisation is relative to the time of the first report in a given
// interval, rather than absolute time. So, for example, with a
// reportQuantisationInterval of 3s and reports with timestamps [0, 1,
// 2, 5, 6, 7], the result contains merged reports with
// timestamps/content of [0:{0,1,2}, 5:{5,6,7}].
func (c *collector) quantise() {
	if len(c.reports) == 0 {
		return
	}
	var (
		quantisedReports    = make([]report.Report, 0, len(c.reports))
		quantisedTimestamps = make([]time.Time, 0, len(c.timestamps))
	)
	quantumStartIdx := 0
	quantumStartTimestamp := c.timestamps[0]
	for i, t := range c.timestamps {
		if t.Sub(quantumStartTimestamp) < reportQuantisationInterval {
			continue
		}
		quantisedReports = append(quantisedReports, c.merger.Merge(c.reports[quantumStartIdx:i]))
		quantisedTimestamps = append(quantisedTimestamps, quantumStartTimestamp)
		quantumStartIdx = i
		quantumStartTimestamp = t
	}
	c.reports = append(quantisedReports, c.merger.Merge(c.reports[quantumStartIdx:]))
	c.timestamps = append(quantisedTimestamps, c.timestamps[quantumStartIdx])
}

// StaticCollector always returns the given report.
type StaticCollector report.Report

// Report returns a merged report over all added reports. It implements
// Reporter.
func (c StaticCollector) Report(context.Context) (report.Report, error) { return report.Report(c), nil }

// Add adds a report to the collector's internal state. It implements Adder.
func (c StaticCollector) Add(context.Context, report.Report, []byte) error { return nil }

// WaitOn lets other components wait on a new report being received. It
// implements Reporter.
func (c StaticCollector) WaitOn(context.Context, chan struct{}) {}

// UnWait lets other components stop waiting on a new report being received. It
// implements Reporter.
func (c StaticCollector) UnWait(context.Context, chan struct{}) {}

// NewFileCollector reads and parses the files at path (a file or
// directory) as reports.  If there are multiple files, and they all
// have names representing "nanoseconds since epoch" timestamps,
// e.g. "1488557088545489008.msgpack.gz", then the collector will
// return merged reports resulting from replaying the file reports in
// a loop at a sequence and speed determined by the timestamps.
// Otherwise the collector always returns the merger of all reports.
func NewFileCollector(path string, window time.Duration) (Collector, error) {
	var (
		timestamps []time.Time
		reports    []report.Report
	)
	allTimestamped := true
	if err := filepath.Walk(path,
		func(p string, info os.FileInfo, err error) error {
			if err != nil {
				return err
			}
			if info.IsDir() {
				return nil
			}
			t, err := timestampFromFilepath(p)
			if err != nil {
				allTimestamped = false
			}
			timestamps = append(timestamps, t)

			rpt, err := readReport(p)
			if err != nil {
				return err
			}
			reports = append(reports, rpt)
			return nil
		}); err != nil {
		return nil, err
	}
	if len(reports) > 1 && allTimestamped {
		collector := NewCollector(window)
		go replay(collector, timestamps, reports)
		return collector, nil
	}
	return StaticCollector(NewSmartMerger().Merge(reports).Upgrade()), nil
}

func timestampFromFilepath(path string) (time.Time, error) {
	name := filepath.Base(path)
	for {
		ext := filepath.Ext(name)
		if ext == "" {
			break
		}
		name = strings.TrimSuffix(name, ext)
	}
	nanosecondsSinceEpoch, err := strconv.ParseInt(name, 10, 64)
	if err != nil {
		return time.Time{}, fmt.Errorf("filename '%s' is not a number (representing nanoseconds since epoch): %v", name, err)
	}
	return time.Unix(0, nanosecondsSinceEpoch), nil
}

func readReport(path string) (rpt report.Report, _ error) {
	f, err := os.Open(path)
	if err != nil {
		return rpt, err
	}
	defer f.Close()

	var (
		handle  codec.Handle
		gzipped bool
	)
	fileType := filepath.Ext(path)
	if fileType == ".gz" {
		gzipped = true
		fileType = filepath.Ext(strings.TrimSuffix(path, fileType))
	}
	switch fileType {
	case ".json":
		handle = &codec.JsonHandle{}
	case ".msgpack":
		handle = &codec.MsgpackHandle{}
	default:
		return rpt, fmt.Errorf("Unsupported file extension: %v", fileType)
	}

	err = rpt.ReadBinary(f, gzipped, handle)

	return rpt, err
}

func replay(a Adder, timestamps []time.Time, reports []report.Report) {
	// calculate delays between report n and n+1
	l := len(timestamps)
	delays := make([]time.Duration, l, l)
	for i, t := range timestamps[0 : l-1] {
		delays[i] = timestamps[i+1].Sub(t)
		if delays[i] < 0 {
			panic(fmt.Errorf("replay timestamps are not in order! %v", timestamps))
		}
	}
	// We don't know how long to wait before looping round, so make a
	// good guess.
	delays[l-1] = timestamps[l-1].Sub(timestamps[0]) / time.Duration(l)

	due := time.Now()
	for {
		for i, r := range reports {
			a.Add(nil, r, nil)
			due = due.Add(delays[i])
			delay := due.Sub(time.Now())
			if delay > 0 {
				time.Sleep(delay)
			}
		}
	}
}
<|endoftext|>


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

// This package implements a basic LISP interpretor for embedding in a go program for scripting.
// This file provides a REPL.

package golisp

import (
	"container/list"
	"fmt"
)

func Repl() {
	IsInteractive = true
	fmt.Printf("Welcome to GoLisp\n")
	fmt.Printf("Copyright 2014 SteelSeries\n")
	fmt.Printf("Evaluate '(quit)' to exit.\n\n")
	prompt := "> "
	LoadHistoryFromFile(".golisp_history")
	lastInput := ""
	for true {
		defer func() {
			if x := recover(); x != nil {
				fmt.Printf("Don't Panic! %v\n", x)
			}
		}()
		DebugCurrentFrame = nil
		DebugSingleStep = false
		DebugEvalInDebugRepl = false
		Global.CurrentCode = list.New()
		inputp := ReadLine(&prompt)
		fmt.Printf("inputp: %v\n", inputp)
		if inputp == nil {
			QuitImpl(nil, nil)
		} else {
			input := *inputp
			fmt.Printf("input: <%s>\n", inputp)
			if input != "" {
				if input != lastInput {
					AddHistory(input)
				}
				lastInput = input
				code, err := Parse(input)
				if err != nil {
					fmt.Printf("Error: %s\n", err)
				} else {
					d, err := Eval(code, Global)
					if err != nil {
						fmt.Printf("Error in evaluation: %s\n", err)
						if DebugOnError {
							DebugRepl(DebugErrorEnv)
						}
					} else {
						fmt.Printf("==> %s\n", String(d))
					}
				}
			}
		}
	}
}
<commit_msg>Clean up some instrumentation.<commit_after>// Copyright 2014 SteelSeries ApS.  All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// This package implements a basic LISP interpretor for embedding in a go program for scripting.
// This file provides a REPL.

package golisp

import (
	"container/list"
	"fmt"
)

func Repl() {
	IsInteractive = true
	fmt.Printf("Welcome to GoLisp\n")
	fmt.Printf("Copyright 2014 SteelSeries\n")
	fmt.Printf("Evaluate '(quit)' to exit.\n\n")
	prompt := "> "
	LoadHistoryFromFile(".golisp_history")
	lastInput := ""
	for true {
		defer func() {
			if x := recover(); x != nil {
				fmt.Printf("Don't Panic! %v\n", x)
			}
		}()
		DebugCurrentFrame = nil
		DebugSingleStep = false
		DebugEvalInDebugRepl = false
		Global.CurrentCode = list.New()
		inputp := ReadLine(&prompt)
//		fmt.Printf("inputp: %v\n", inputp)
		if inputp == nil {
			QuitImpl(nil, nil)
		} else {
			input := *inputp
//			fmt.Printf("input: <%s>\n", inputp)
			if input != "" {
				if input != lastInput {
					AddHistory(input)
				}
				lastInput = input
				code, err := Parse(input)
				if err != nil {
					fmt.Printf("Error: %s\n", err)
				} else {
					d, err := Eval(code, Global)
					if err != nil {
						fmt.Printf("Error in evaluation: %s\n", err)
						if DebugOnError {
							DebugRepl(DebugErrorEnv)
						}
					} else {
						fmt.Printf("==> %s\n", String(d))
					}
				}
			}
		}
	}
}
<|endoftext|>


<commit_before>package main

import (
	"io/ioutil"
	"log"

	"github.com/veandco/go-sdl2/sdl"
	"github.com/veandco/go-sdl2/mix"
)

func main() {
	if err := sdl.Init(sdl.INIT_AUDIO); err != nil {
		log.Println(err)
		return
	}
	defer sdl.Quit()

	if err := mix.OpenAudio(44100, mix.DEFAULT_FORMAT, 2, 4096); err != nil {
		log.Println(err)
		return
	}
	defer mix.CloseAudio()

	// Load entire WAV data from file
	data, err := ioutil.ReadFile("../../assets/test.wav")
	if err != nil {
		log.Println(err)
	}

	// Load WAV from data (memory)
	chunk, err := mix.QuickLoad_WAV(data)
	if err != nil {
		log.Println(err)
	}
	defer chunk.Free()

	// Play 4 times
	chunk.Play(1, 3)

	// Wait until it finishes playing
	for mix.Playing(-1) == 1 {
		sdl.Delay(16)
	}
}

<commit_msg>examples: audio_wav_memory: audio_wav_memory.go: Update API<commit_after>package main

import (
	"io/ioutil"
	"log"

	"github.com/veandco/go-sdl2/sdl"
	"github.com/veandco/go-sdl2/mix"
)

func main() {
	if err := sdl.Init(sdl.INIT_AUDIO); err != nil {
		log.Println(err)
		return
	}
	defer sdl.Quit()

	if err := mix.OpenAudio(44100, mix.DEFAULT_FORMAT, 2, 4096); err != nil {
		log.Println(err)
		return
	}
	defer mix.CloseAudio()

	// Load entire WAV data from file
	data, err := ioutil.ReadFile("../../assets/test.wav")
	if err != nil {
		log.Println(err)
	}

	// Load WAV from data (memory)
	chunk, err := mix.QuickLoadWAV(data)
	if err != nil {
		log.Println(err)
	}
	defer chunk.Free()

	// Play 4 times
	chunk.Play(1, 3)

	// Wait until it finishes playing
	for mix.Playing(-1) == 1 {
		sdl.Delay(16)
	}
}

<|endoftext|>