<commit_before>// Copyright 2018 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Package api describes the external interface to the gossip hub.
package api

import (
	"crypto/sha256"
	"fmt"

	"github.com/google/certificate-transparency-go/tls"
)

// PathPrefix is the common prefix for all HTTP entrypoints.
const PathPrefix = "/gossip/v0/"

// Add Signed Blob to Hub
// POST https://<hub server>/gossip/v0/add-signed-blob
// Inputs:
//    source_id: A unique identifier for the public key used to sign the blob.
//    blob_data: The binary blob
//    source_signature: The source's signature over blob_data.
// Outputs:
//    timestamped_entry: TLS-encoded TimestampedEntry.
//    hub_signature: hub's signature gossip_timestamped_entry
// Note that the returned timestamped_entry may have a different source_signature than
// was submitted (if the signature scheme is non-deterministic and an earlier submission
// of the same blob had a different signature).

// AddSignedBlobPath is the final path component for this entrypoint.
const AddSignedBlobPath = "add-signed-blob"

// AddSignedBlobRequest represents the JSON request body sent to the add-signed-blob POST method.
type AddSignedBlobRequest struct {
	SourceID        string `json:"source_id"`
	BlobData        []byte `json:"blob_data"`
	SourceSignature []byte `json:"src_signature"`
}

// AddSignedBlobResponse represents the JSON response body for the add-signed-blob POST method.
type AddSignedBlobResponse struct {
	// TimestampedEntryData holds the TLS-encoding of a TimestampedEntry.
	TimestampedEntryData []byte `json:"timestamped_entry"`
	// HubSignature is a signature over GossipTimestamp.
	HubSignature []byte `json:"hub_signature"`
}

// TimestampedEntry holds a timestamped entry in the hub, in a form suitable for TLS-encoding.
// RFC 5246 notation:
//   struct {
//      opaque source_id<1,65535>;
//      opaque head_data<1,65535>;
//      opaque source_signature<0,65535>;
//      uint64 hub_timestamp;
//   } TimestampedEntry;
type TimestampedEntry struct {
	SourceID        []byte `tls:"minlen:1,maxlen:65535"`
	BlobData        []byte `tls:"minlen:1,maxlen:65535"`
	SourceSignature []byte `tls:"minlen:0,maxlen:65535"`
	HubTimestamp    uint64 // in nanoseconds since UNIX epoch
}

// SignedGossipTimestamp holds a timestamped entry in the gossip hub together with a
// signature from the hub.  RFC 5246 notation:
//   struct {
//      TimestampedEntry timestamped_entry;
//      opaque hub_signature<1,65535>;
//   } SignedGossipTimestamp;
type SignedGossipTimestamp struct {
	TimestampedEntry TimestampedEntry
	// HubSignature is over the TLS-encoding of GossipTimestamp.
	HubSignature []byte `tls:"minlen:1,maxlen:65535"`
}

// Retrieve Latest Signed Tree Head for Hub
// GET https://<hub server>/gossip/v0/get-sth
// Inputs: None
// Outputs:
//    head_data: TLS-encoded HubTreeHead.
//    hub_signature: signature over head_data.

// GetSTHPath is the final path component for this entrypoint.
const GetSTHPath = "get-sth"

// GetSTHResponse represents the JSON response to the get-sth GET method.
type GetSTHResponse struct {
	// HeadData holds the TLS-encoding of a HubTreeHead.
	TreeHeadData []byte `json:"head_data"`
	// HubSignature is a signature over TreeHeadData from the hub.
	HubSignature []byte `json:"hub_signature"`
}

// HubTreeHead describes a head of the Hub's Merkle tree. RFC 5246 notation:
//   struct {
//     uint64 tree_size;
//     uint64 timestamp;
//     opaque root_hash<1,65535>;
//   } HubTreeHead;
// TODO(drysdale): shift this to being a versioned structure to allow for
// future expansion (cf. github.com/google/trillian/types.LogRoot).
type HubTreeHead struct {
	TreeSize  uint64
	Timestamp uint64 // in nanoseconds since UNIX epoch
	RootHash  []byte `tls:"minlen:1,maxlen:65535"`
}

// SignedHubTreeHead describes a head of the Hub's Merkle tree, together with
// a signature from the hub.
type SignedHubTreeHead struct {
	TreeHead     HubTreeHead
	HubSignature []byte // over tls.Marshal(TreeHead)
}

// Retrieve Consistency Proof between Tree Heads
// GET https://<hub server>/gossip/v0/get-sth-consistency
// Inputs:
//    first:  The tree_size of the first tree, in decimal.
//    second:  The tree_size of the second tree, in decimal.
// Both tree sizes must be from existing v1 STHs (Signed Tree Heads).
// Outputs:
//    consistency:  An array of Merkle Tree nodes, base64 encoded.

// GetSTHConsistencyPath is the final path component for this entrypoint.
const GetSTHConsistencyPath = "get-sth-consistency"
const (
	// GetSTHConsistencyFirst is the first parameter name.
	GetSTHConsistencyFirst = "first"
	// GetSTHConsistencySecond is the second parameter name.
	GetSTHConsistencySecond = "second"
)

// GetSTHConsistencyResponse represents the JSON response to the get-sth-consistency GET method.
type GetSTHConsistencyResponse struct {
	Consistency [][]byte `json:"consistency"`
}

// Retrieve Inclusion Proof from Hub by entry hash
// GET https://<hub server>/gossip/v0/get-proof-by-hash
// Inputs:
//    hash:  A base64-encoded leaf hash, which is the leaf hash of a TLS-encoded
//           TimestampedEntry.
//    tree_size:  The tree_size of the tree on which to base the proof in deciaml.
// Outputs:
//    leaf_index:  The 0-based index of the entry corresponding to the hash parameter.
//    audit_path:  An array of Merkle Tree node, base64 encoded.

// GetProofByHashPath is the final path component for this entrypoint.
const GetProofByHashPath = "get-proof-by-hash"
const (
	// GetProofByHashArg is the first parameter name.
	GetProofByHashArg = "hash"
	// GetProofByHashSize is the second parameter name.
	GetProofByHashSize = "tree_size"
)

// GetProofByHashResponse represents the JSON response to the get-proof-by-hash GET method.
type GetProofByHashResponse struct {
	LeafIndex int64    `json:"leaf_index"`
	AuditPath [][]byte `json:"audit_path"`
}

// Retrieve Entries from Hub
// GET https://<hub server>/gossip/v0/get-entries
// Inputs:
//    start:  0-based index of first entry to retrieve, in decimal.
//    end:  0-based index of last entry to retrieve, in decimal.
// Outputs:
//    entries:  An array of objects, each of which is a TLS-encoded TimestampedEntry structure.
// TODO(drysdale): make this paginated

// GetEntriesPath is the final path component for this entrypoint.
const GetEntriesPath = "get-entries"

const (
	// GetEntriesStart is the first parameter name.
	GetEntriesStart = "start"
	// GetEntriesEnd is the second parameter name.
	GetEntriesEnd = "end"
)

// GetEntriesResponse respresents the JSON response to the get-entries GET method.
type GetEntriesResponse struct {
	// Entries holds TLS-encoded TimestampedEntry structures
	Entries [][]byte `json:"entries"` // the list of returned entries
}

// Retrieve Accepted Source Public Keys
// GET https://<hub server>/gossip/v0/get-source-keys
// Inputs: None
// Outputs:
//   entries: An array of objects, each consisting of:
//      id: The identifier for the source.
//      pub_key:  The base64-encoded public key for the source.
//      kind: The type of the source (e.g. an RFC 6962 CT Log generating
//            signed tree heads).

// GetSourceKeysPath is the final path component for this entrypoint.
const GetSourceKeysPath = "get-source-keys"

// GetSourceKeysResponse represents the JSON response to the get-source-keys GET method.
type GetSourceKeysResponse struct {
	Entries []*SourceKey `json:"entries"`
}

// Possible values for SourceKey.Kind
const (
	// UnknownKind indicates that no information is known about the source of
	// the signed blobs (and so only signature verification is possible). Any
	// unrecognized kind values will be treated as UnknownKind.
	UnknownKind = "UNKNOWN"
	// RFC6962STHKind indicates that the source is expected to only generate
	// submissions that are Signed Tree Heads as described by RFC 6962.
	// The BlobData for a submission is expected to be the result of
	// tls.Marshal(ct.TreeHeadSignature), and may be rejected if this is not
	// the case.
	RFC6962STHKind = "RFC6962STH"
	// TrillianSLRKind indicates that the source is expected to generate
	// submissions that are Signed Log Roots from the Trillian Log API.
	// The BlobData for a submission is expected to be the result of
	// tls.Marshal(types.LogRoot), and may be rejected if this is not the
	// case.
	TrillianSLRKind = "TRILLIANSLR"
	// TrillianSMRKind indicates that the source is expected to generate
	// submissions that are Signed Map Roots from the Trillian Map API.
	// The BlobData for a submission is expected to be the result of
	// tls.Marshal(types.MapRoot), and may be rejected if this is not the
	// case.
	TrillianSMRKind = "TRILLIANSMR"
	// GossipHubKind indicates that the source is expected to generate
	// submission that are Hub Tree Heads from this API.
	// The BlobData for a submission is expected to be the result of
	// tls.Marshal(HubTreeHead), and may be rejected if this is not the
	// case.
	GossipHubKind = "GOSSIPHUB"
)

// SourceKey holds key information about a source that is tracked by this hub.
type SourceKey struct {
	ID     string `json:"id"`
	PubKey []byte `json:"pub_key"` // DER encoded
	Kind   string `json:"kind"`
}

// Retrieve latest entry for a source.
// The definition of 'latest' depends on what kind the source is; in general, it is the
// most recently submitted entry, but for a source of CT STHs, it is the entry with the
// latest timestamp inside the parsed STH data.
// GET https://<hub server>/gossip/v0/get-latest-for-src
// Inputs:
//    source_id: The source to retrieve latest data for.
// Outputs:
//    entry:  A TLS-encoded TimestampedEntry structure, holding the 'latest' known entry
//            for the source.  This is best-effort, and may not be available (in which
//            case a 204 No Content status is returned).

// GetLatestForSourcePath is the final path component for this entrypoint.
const GetLatestForSourcePath = "get-latest-for-src"

const (
	// GetLatestForSourceID is the parameter name.
	GetLatestForSourceID = "src_id"
)

// GetLatestForSourceResponse represents the JSON response to the get-proof-by-hash GET method.
type GetLatestForSourceResponse struct {
	Entry []byte `json:"entry"` // a TLS-encoded TimestampedEntry
}

// TimestampedEntryHash calculates the leaf hash value for a timestamped entry in the hub:
//   SHA256(0x00 || tls-encode(entry))
func TimestampedEntryHash(entry *TimestampedEntry) ([]byte, error) {
	data, err := tls.Marshal(*entry)
	if err != nil {
		return nil, fmt.Errorf("failed to tls.Marshal: %v", err)
	}
	h := sha256.New()
	h.Write([]byte{0x00})
	h.Write(data)
	return h.Sum(nil), nil
}
<commit_msg>gossip: drop TODOs that aren't going to happen (#59)<commit_after>// Copyright 2018 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Package api describes the external interface to the gossip hub.
package api

import (
	"crypto/sha256"
	"fmt"

	"github.com/google/certificate-transparency-go/tls"
)

// PathPrefix is the common prefix for all HTTP entrypoints.
const PathPrefix = "/gossip/v0/"

// Add Signed Blob to Hub
// POST https://<hub server>/gossip/v0/add-signed-blob
// Inputs:
//    source_id: A unique identifier for the public key used to sign the blob.
//    blob_data: The binary blob
//    source_signature: The source's signature over blob_data.
// Outputs:
//    timestamped_entry: TLS-encoded TimestampedEntry.
//    hub_signature: hub's signature gossip_timestamped_entry
// Note that the returned timestamped_entry may have a different source_signature than
// was submitted (if the signature scheme is non-deterministic and an earlier submission
// of the same blob had a different signature).

// AddSignedBlobPath is the final path component for this entrypoint.
const AddSignedBlobPath = "add-signed-blob"

// AddSignedBlobRequest represents the JSON request body sent to the add-signed-blob POST method.
type AddSignedBlobRequest struct {
	SourceID        string `json:"source_id"`
	BlobData        []byte `json:"blob_data"`
	SourceSignature []byte `json:"src_signature"`
}

// AddSignedBlobResponse represents the JSON response body for the add-signed-blob POST method.
type AddSignedBlobResponse struct {
	// TimestampedEntryData holds the TLS-encoding of a TimestampedEntry.
	TimestampedEntryData []byte `json:"timestamped_entry"`
	// HubSignature is a signature over GossipTimestamp.
	HubSignature []byte `json:"hub_signature"`
}

// TimestampedEntry holds a timestamped entry in the hub, in a form suitable for TLS-encoding.
// RFC 5246 notation:
//   struct {
//      opaque source_id<1,65535>;
//      opaque head_data<1,65535>;
//      opaque source_signature<0,65535>;
//      uint64 hub_timestamp;
//   } TimestampedEntry;
type TimestampedEntry struct {
	SourceID        []byte `tls:"minlen:1,maxlen:65535"`
	BlobData        []byte `tls:"minlen:1,maxlen:65535"`
	SourceSignature []byte `tls:"minlen:0,maxlen:65535"`
	HubTimestamp    uint64 // in nanoseconds since UNIX epoch
}

// SignedGossipTimestamp holds a timestamped entry in the gossip hub together with a
// signature from the hub.  RFC 5246 notation:
//   struct {
//      TimestampedEntry timestamped_entry;
//      opaque hub_signature<1,65535>;
//   } SignedGossipTimestamp;
type SignedGossipTimestamp struct {
	TimestampedEntry TimestampedEntry
	// HubSignature is over the TLS-encoding of GossipTimestamp.
	HubSignature []byte `tls:"minlen:1,maxlen:65535"`
}

// Retrieve Latest Signed Tree Head for Hub
// GET https://<hub server>/gossip/v0/get-sth
// Inputs: None
// Outputs:
//    head_data: TLS-encoded HubTreeHead.
//    hub_signature: signature over head_data.

// GetSTHPath is the final path component for this entrypoint.
const GetSTHPath = "get-sth"

// GetSTHResponse represents the JSON response to the get-sth GET method.
type GetSTHResponse struct {
	// HeadData holds the TLS-encoding of a HubTreeHead.
	TreeHeadData []byte `json:"head_data"`
	// HubSignature is a signature over TreeHeadData from the hub.
	HubSignature []byte `json:"hub_signature"`
}

// HubTreeHead describes a head of the Hub's Merkle tree. RFC 5246 notation:
//   struct {
//     uint64 tree_size;
//     uint64 timestamp;
//     opaque root_hash<1,65535>;
//   } HubTreeHead;
type HubTreeHead struct {
	TreeSize  uint64
	Timestamp uint64 // in nanoseconds since UNIX epoch
	RootHash  []byte `tls:"minlen:1,maxlen:65535"`
}

// SignedHubTreeHead describes a head of the Hub's Merkle tree, together with
// a signature from the hub.
type SignedHubTreeHead struct {
	TreeHead     HubTreeHead
	HubSignature []byte // over tls.Marshal(TreeHead)
}

// Retrieve Consistency Proof between Tree Heads
// GET https://<hub server>/gossip/v0/get-sth-consistency
// Inputs:
//    first:  The tree_size of the first tree, in decimal.
//    second:  The tree_size of the second tree, in decimal.
// Both tree sizes must be from existing v1 STHs (Signed Tree Heads).
// Outputs:
//    consistency:  An array of Merkle Tree nodes, base64 encoded.

// GetSTHConsistencyPath is the final path component for this entrypoint.
const GetSTHConsistencyPath = "get-sth-consistency"
const (
	// GetSTHConsistencyFirst is the first parameter name.
	GetSTHConsistencyFirst = "first"
	// GetSTHConsistencySecond is the second parameter name.
	GetSTHConsistencySecond = "second"
)

// GetSTHConsistencyResponse represents the JSON response to the get-sth-consistency GET method.
type GetSTHConsistencyResponse struct {
	Consistency [][]byte `json:"consistency"`
}

// Retrieve Inclusion Proof from Hub by entry hash
// GET https://<hub server>/gossip/v0/get-proof-by-hash
// Inputs:
//    hash:  A base64-encoded leaf hash, which is the leaf hash of a TLS-encoded
//           TimestampedEntry.
//    tree_size:  The tree_size of the tree on which to base the proof in deciaml.
// Outputs:
//    leaf_index:  The 0-based index of the entry corresponding to the hash parameter.
//    audit_path:  An array of Merkle Tree node, base64 encoded.

// GetProofByHashPath is the final path component for this entrypoint.
const GetProofByHashPath = "get-proof-by-hash"
const (
	// GetProofByHashArg is the first parameter name.
	GetProofByHashArg = "hash"
	// GetProofByHashSize is the second parameter name.
	GetProofByHashSize = "tree_size"
)

// GetProofByHashResponse represents the JSON response to the get-proof-by-hash GET method.
type GetProofByHashResponse struct {
	LeafIndex int64    `json:"leaf_index"`
	AuditPath [][]byte `json:"audit_path"`
}

// Retrieve Entries from Hub
// GET https://<hub server>/gossip/v0/get-entries
// Inputs:
//    start:  0-based index of first entry to retrieve, in decimal.
//    end:  0-based index of last entry to retrieve, in decimal.
// Outputs:
//    entries:  An array of objects, each of which is a TLS-encoded TimestampedEntry structure.

// GetEntriesPath is the final path component for this entrypoint.
const GetEntriesPath = "get-entries"

const (
	// GetEntriesStart is the first parameter name.
	GetEntriesStart = "start"
	// GetEntriesEnd is the second parameter name.
	GetEntriesEnd = "end"
)

// GetEntriesResponse respresents the JSON response to the get-entries GET method.
type GetEntriesResponse struct {
	// Entries holds TLS-encoded TimestampedEntry structures
	Entries [][]byte `json:"entries"` // the list of returned entries
}

// Retrieve Accepted Source Public Keys
// GET https://<hub server>/gossip/v0/get-source-keys
// Inputs: None
// Outputs:
//   entries: An array of objects, each consisting of:
//      id: The identifier for the source.
//      pub_key:  The base64-encoded public key for the source.
//      kind: The type of the source (e.g. an RFC 6962 CT Log generating
//            signed tree heads).

// GetSourceKeysPath is the final path component for this entrypoint.
const GetSourceKeysPath = "get-source-keys"

// GetSourceKeysResponse represents the JSON response to the get-source-keys GET method.
type GetSourceKeysResponse struct {
	Entries []*SourceKey `json:"entries"`
}

// Possible values for SourceKey.Kind
const (
	// UnknownKind indicates that no information is known about the source of
	// the signed blobs (and so only signature verification is possible). Any
	// unrecognized kind values will be treated as UnknownKind.
	UnknownKind = "UNKNOWN"
	// RFC6962STHKind indicates that the source is expected to only generate
	// submissions that are Signed Tree Heads as described by RFC 6962.
	// The BlobData for a submission is expected to be the result of
	// tls.Marshal(ct.TreeHeadSignature), and may be rejected if this is not
	// the case.
	RFC6962STHKind = "RFC6962STH"
	// TrillianSLRKind indicates that the source is expected to generate
	// submissions that are Signed Log Roots from the Trillian Log API.
	// The BlobData for a submission is expected to be the result of
	// tls.Marshal(types.LogRoot), and may be rejected if this is not the
	// case.
	TrillianSLRKind = "TRILLIANSLR"
	// TrillianSMRKind indicates that the source is expected to generate
	// submissions that are Signed Map Roots from the Trillian Map API.
	// The BlobData for a submission is expected to be the result of
	// tls.Marshal(types.MapRoot), and may be rejected if this is not the
	// case.
	TrillianSMRKind = "TRILLIANSMR"
	// GossipHubKind indicates that the source is expected to generate
	// submission that are Hub Tree Heads from this API.
	// The BlobData for a submission is expected to be the result of
	// tls.Marshal(HubTreeHead), and may be rejected if this is not the
	// case.
	GossipHubKind = "GOSSIPHUB"
)

// SourceKey holds key information about a source that is tracked by this hub.
type SourceKey struct {
	ID     string `json:"id"`
	PubKey []byte `json:"pub_key"` // DER encoded
	Kind   string `json:"kind"`
}

// Retrieve latest entry for a source.
// The definition of 'latest' depends on what kind the source is; in general, it is the
// most recently submitted entry, but for a source of CT STHs, it is the entry with the
// latest timestamp inside the parsed STH data.
// GET https://<hub server>/gossip/v0/get-latest-for-src
// Inputs:
//    source_id: The source to retrieve latest data for.
// Outputs:
//    entry:  A TLS-encoded TimestampedEntry structure, holding the 'latest' known entry
//            for the source.  This is best-effort, and may not be available (in which
//            case a 204 No Content status is returned).

// GetLatestForSourcePath is the final path component for this entrypoint.
const GetLatestForSourcePath = "get-latest-for-src"

const (
	// GetLatestForSourceID is the parameter name.
	GetLatestForSourceID = "src_id"
)

// GetLatestForSourceResponse represents the JSON response to the get-proof-by-hash GET method.
type GetLatestForSourceResponse struct {
	Entry []byte `json:"entry"` // a TLS-encoded TimestampedEntry
}

// TimestampedEntryHash calculates the leaf hash value for a timestamped entry in the hub:
//   SHA256(0x00 || tls-encode(entry))
func TimestampedEntryHash(entry *TimestampedEntry) ([]byte, error) {
	data, err := tls.Marshal(*entry)
	if err != nil {
		return nil, fmt.Errorf("failed to tls.Marshal: %v", err)
	}
	h := sha256.New()
	h.Write([]byte{0x00})
	h.Write(data)
	return h.Sum(nil), nil
}
<|endoftext|>


<commit_before>/*
 *
 * k6 - a next-generation load testing tool
 * Copyright (C) 2019 Load Impact
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

package executor

import (
	"context"
	"sync"
	"sync/atomic"
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"gopkg.in/guregu/null.v3"

	"github.com/loadimpact/k6/lib"
	"github.com/loadimpact/k6/lib/types"
)

func getTestExternallyControlledConfig() ExternallyControlledConfig {
	return ExternallyControlledConfig{
		ExternallyControlledConfigParams: ExternallyControlledConfigParams{
			VUs:      null.IntFrom(2),
			MaxVUs:   null.IntFrom(10),
			Duration: types.NullDurationFrom(2 * time.Second),
		},
	}
}

func TestExternallyControlledRun(t *testing.T) {
	t.Parallel()

	et, err := lib.NewExecutionTuple(nil, nil)
	require.NoError(t, err)
	es := lib.NewExecutionState(lib.Options{}, et, 10, 50)

	doneIters := new(uint64)
	var ctx, cancel, executor, _ = setupExecutor(
		t, getTestExternallyControlledConfig(), es,
		simpleRunner(func(ctx context.Context) error {
			time.Sleep(200 * time.Millisecond)
			atomic.AddUint64(doneIters, 1)
			return nil
		}),
	)
	defer cancel()

	var (
		wg     sync.WaitGroup
		errCh  = make(chan error, 1)
		doneCh = make(chan struct{})
	)
	wg.Add(1)
	go func() {
		defer wg.Done()
		es.MarkStarted()
		errCh <- executor.Run(ctx, nil)
		es.MarkEnded()
		close(doneCh)
	}()

	updateConfig := func(vus, maxVUs int, errMsg string) {
		newConfig := ExternallyControlledConfigParams{
			VUs:      null.IntFrom(int64(vus)),
			MaxVUs:   null.IntFrom(int64(maxVUs)),
			Duration: types.NullDurationFrom(2 * time.Second),
		}
		err := executor.(*ExternallyControlled).UpdateConfig(ctx, newConfig)
		if errMsg != "" {
			assert.EqualError(t, err, errMsg)
		} else {
			assert.NoError(t, err)
		}
	}

	var resultVUCount [][]int64
	snapshot := func() {
		resultVUCount = append(resultVUCount,
			[]int64{es.GetCurrentlyActiveVUsCount(), es.GetInitializedVUsCount()})
	}

	wg.Add(1)
	go func() {
		defer wg.Done()
		snapshotTicker := time.NewTicker(500 * time.Millisecond)
		ticks := 0
		for {
			select {
			case <-snapshotTicker.C:
				snapshot()
				switch ticks {
				case 0, 2:
					updateConfig(4, 10, "")
				case 1:
					updateConfig(8, 20, "")
				case 3:
					updateConfig(15, 10,
						"invalid configuration supplied: the number of active VUs (15)"+
							" must be less than or equal to the number of maxVUs (10)")
					updateConfig(-1, 10,
						"invalid configuration supplied: the number of VUs shouldn't be negative")
				}
				ticks++
			case <-doneCh:
				snapshotTicker.Stop()
				snapshot()
				return
			}
		}
	}()

	wg.Wait()
	require.NoError(t, <-errCh)
	assert.Equal(t, uint64(48), atomic.LoadUint64(doneIters))
	assert.Equal(t, [][]int64{{2, 10}, {4, 10}, {8, 20}, {4, 10}, {0, 10}}, resultVUCount)
}
<commit_msg>Minor type cleanup in TestExternallyControlledRun<commit_after>/*
 *
 * k6 - a next-generation load testing tool
 * Copyright (C) 2019 Load Impact
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

package executor

import (
	"context"
	"sync"
	"sync/atomic"
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"gopkg.in/guregu/null.v3"

	"github.com/loadimpact/k6/lib"
	"github.com/loadimpact/k6/lib/types"
)

func getTestExternallyControlledConfig() ExternallyControlledConfig {
	return ExternallyControlledConfig{
		ExternallyControlledConfigParams: ExternallyControlledConfigParams{
			VUs:      null.IntFrom(2),
			MaxVUs:   null.IntFrom(10),
			Duration: types.NullDurationFrom(2 * time.Second),
		},
	}
}

func TestExternallyControlledRun(t *testing.T) {
	t.Parallel()

	et, err := lib.NewExecutionTuple(nil, nil)
	require.NoError(t, err)
	es := lib.NewExecutionState(lib.Options{}, et, 10, 50)

	doneIters := new(uint64)
	var ctx, cancel, executor, _ = setupExecutor(
		t, getTestExternallyControlledConfig(), es,
		simpleRunner(func(ctx context.Context) error {
			time.Sleep(200 * time.Millisecond)
			atomic.AddUint64(doneIters, 1)
			return nil
		}),
	)
	defer cancel()

	var (
		wg     sync.WaitGroup
		errCh  = make(chan error, 1)
		doneCh = make(chan struct{})
	)
	wg.Add(1)
	go func() {
		defer wg.Done()
		es.MarkStarted()
		errCh <- executor.Run(ctx, nil)
		es.MarkEnded()
		close(doneCh)
	}()

	updateConfig := func(vus, maxVUs int64, errMsg string) {
		newConfig := ExternallyControlledConfigParams{
			VUs:      null.IntFrom(vus),
			MaxVUs:   null.IntFrom(maxVUs),
			Duration: types.NullDurationFrom(2 * time.Second),
		}
		err := executor.(*ExternallyControlled).UpdateConfig(ctx, newConfig)
		if errMsg != "" {
			assert.EqualError(t, err, errMsg)
		} else {
			assert.NoError(t, err)
		}
	}

	var resultVUCount [][]int64
	snapshot := func() {
		resultVUCount = append(resultVUCount,
			[]int64{es.GetCurrentlyActiveVUsCount(), es.GetInitializedVUsCount()})
	}

	wg.Add(1)
	go func() {
		defer wg.Done()
		snapshotTicker := time.NewTicker(500 * time.Millisecond)
		ticks := 0
		for {
			select {
			case <-snapshotTicker.C:
				snapshot()
				switch ticks {
				case 0, 2:
					updateConfig(4, 10, "")
				case 1:
					updateConfig(8, 20, "")
				case 3:
					updateConfig(15, 10,
						"invalid configuration supplied: the number of active VUs (15)"+
							" must be less than or equal to the number of maxVUs (10)")
					updateConfig(-1, 10,
						"invalid configuration supplied: the number of VUs shouldn't be negative")
				}
				ticks++
			case <-doneCh:
				snapshotTicker.Stop()
				snapshot()
				return
			}
		}
	}()

	wg.Wait()
	require.NoError(t, <-errCh)
	assert.Equal(t, uint64(48), atomic.LoadUint64(doneIters))
	assert.Equal(t, [][]int64{{2, 10}, {4, 10}, {8, 20}, {4, 10}, {0, 10}}, resultVUCount)
}
<|endoftext|>


<commit_before>package server

import (
	"encoding/json"
	"fmt"
	"reflect"

	log "github.com/cihub/seelog"
	"github.com/golang/protobuf/proto"
	"github.com/obeattie/typhon/auth"
	"github.com/obeattie/typhon/errors"
)

type Endpoint struct {
	Name       string
	Handler    func(Request) (proto.Message, error)
	Request    interface{}
	Response   interface{}
	Authorizer auth.Authorizer

	// server is a reference to the parent server this endpoint is registered with
	Server Server
}

func (e *Endpoint) HandleRequest(req Request) (proto.Message, error) {
	var err error

	// @todo check that `Request` and `Response` are set in RegisterEndpoint

	if e.Request != nil {
		body := cloneTypedPtr(e.Request).(proto.Message)
		if err := unmarshalRequest(req, body); err != nil {
			return nil, err
		}
		req.SetBody(body)
	}

	log.Debugf("[Server] %s.%s handler received request: %+v", req.Service(), e.Name, req.Body())

	// Authenticate access to this endpoint if we're set up to do this
	if e.Authorizer != nil {
		session, err := req.Session()
		if err != nil {
			return nil, enrichError(err, req, e)
		}
		if session == nil {
			return nil, errors.Unauthorized("This endpoint requires authentication. Please provide an access token.")
		}
		if err := e.Authorizer(req, session); err != nil {
			err = enrichError(err, req, e)
			log.Warnf("Failed to authenticate access to %s endpoint", e.Name)
			return nil, err
		}
	}

	// Execute the handler
	resp, err := e.Handler(req)
	if err != nil {
		err = enrichError(err, req, e)
		log.Errorf("[Server] %s.%s handler error: %s", req.Service(), e.Name, err.Error())
	} else {
		log.Debugf("[Server] %s.%s handler response: %+v", req.Service(), e.Name, resp)
	}

	return resp, err
	// @todo return error if e.Response is set and doesn't match
}

// cloneTypedPtr takes a pointer of any type and returns a pointer to
// to a newly allocated instance of that same type.
// This allows us to write generic unmarshalling code that is independent
// of a endpoint's expected message type. This way we can handle
// unmarshalling errors outside of the actual endpoint handler methods.
func cloneTypedPtr(reqType interface{}) interface{} {
	// http://play.golang.org/p/MJOc3g7t23
	// `reflect.New` gives us a `reflect.Value`, using type of `reflect.TypeIf(e.RequestType()).Elem()` (a struct type)
	// and that struct's zero value for a value.
	// `reflectValue.Interface()` puts the type and value back together into an interface type
	reflectValue := reflect.New(reflect.TypeOf(reqType).Elem())
	return reflectValue.Interface()
}

// enrichError converts an error interface into *errors.Error and attaches
// lots of information to it.
// NOTE: if the error came from somewhere down the stack, it isn't modified
// @todo once the server context gives us a parent request and trace id, we can store even more information in the error!
func enrichError(err error, ctx Request, endpoint *Endpoint) *errors.Error {
	wrappedErr := errors.Wrap(err)

	// @todo perhaps make methods PrivateContext() and PublicContext() that
	// to deal with nil contexts
	if wrappedErr.PrivateContext == nil {
		wrappedErr.PrivateContext = map[string]string{}
	}

	// @todo an error will probably have a source_request_id or something that we can use to
	// more reliably make sure this information is only attached once, as the error travels up the service stack
	if wrappedErr.PrivateContext["service"] == "" {
		wrappedErr.PrivateContext["service"] = ctx.Service()
	}
	if wrappedErr.PrivateContext["endpoint"] == "" {
		wrappedErr.PrivateContext["endpoint"] = endpoint.Name
	}
	return wrappedErr
}

// unmarshalRequest payload into the body based on content-type
func unmarshalRequest(req Request, body proto.Message) (err error) {
	if len(req.Payload()) == 0 {
		return nil
	}

	if req.ContentType() == "application/x-protobuf" {
		return proto.Unmarshal(req.Payload(), body)
	} else {
		return json.Unmarshal(req.Payload(), body)
	}
}
<commit_msg>Unused import<commit_after>package server

import (
	"encoding/json"
	"reflect"

	log "github.com/cihub/seelog"
	"github.com/golang/protobuf/proto"
	"github.com/obeattie/typhon/auth"
	"github.com/obeattie/typhon/errors"
)

type Endpoint struct {
	Name       string
	Handler    func(Request) (proto.Message, error)
	Request    interface{}
	Response   interface{}
	Authorizer auth.Authorizer

	// server is a reference to the parent server this endpoint is registered with
	Server Server
}

func (e *Endpoint) HandleRequest(req Request) (proto.Message, error) {
	var err error

	// @todo check that `Request` and `Response` are set in RegisterEndpoint

	if e.Request != nil {
		body := cloneTypedPtr(e.Request).(proto.Message)
		if err := unmarshalRequest(req, body); err != nil {
			return nil, err
		}
		req.SetBody(body)
	}

	log.Debugf("[Server] %s.%s handler received request: %+v", req.Service(), e.Name, req.Body())

	// Authenticate access to this endpoint if we're set up to do this
	if e.Authorizer != nil {
		session, err := req.Session()
		if err != nil {
			return nil, enrichError(err, req, e)
		}
		if session == nil {
			return nil, errors.Unauthorized("This endpoint requires authentication. Please provide an access token.")
		}
		if err := e.Authorizer(req, session); err != nil {
			err = enrichError(err, req, e)
			log.Warnf("Failed to authenticate access to %s endpoint", e.Name)
			return nil, err
		}
	}

	// Execute the handler
	resp, err := e.Handler(req)
	if err != nil {
		err = enrichError(err, req, e)
		log.Errorf("[Server] %s.%s handler error: %s", req.Service(), e.Name, err.Error())
	} else {
		log.Debugf("[Server] %s.%s handler response: %+v", req.Service(), e.Name, resp)
	}

	return resp, err
	// @todo return error if e.Response is set and doesn't match
}

// cloneTypedPtr takes a pointer of any type and returns a pointer to
// to a newly allocated instance of that same type.
// This allows us to write generic unmarshalling code that is independent
// of a endpoint's expected message type. This way we can handle
// unmarshalling errors outside of the actual endpoint handler methods.
func cloneTypedPtr(reqType interface{}) interface{} {
	// http://play.golang.org/p/MJOc3g7t23
	// `reflect.New` gives us a `reflect.Value`, using type of `reflect.TypeIf(e.RequestType()).Elem()` (a struct type)
	// and that struct's zero value for a value.
	// `reflectValue.Interface()` puts the type and value back together into an interface type
	reflectValue := reflect.New(reflect.TypeOf(reqType).Elem())
	return reflectValue.Interface()
}

// enrichError converts an error interface into *errors.Error and attaches
// lots of information to it.
// NOTE: if the error came from somewhere down the stack, it isn't modified
// @todo once the server context gives us a parent request and trace id, we can store even more information in the error!
func enrichError(err error, ctx Request, endpoint *Endpoint) *errors.Error {
	wrappedErr := errors.Wrap(err)

	// @todo perhaps make methods PrivateContext() and PublicContext() that
	// to deal with nil contexts
	if wrappedErr.PrivateContext == nil {
		wrappedErr.PrivateContext = map[string]string{}
	}

	// @todo an error will probably have a source_request_id or something that we can use to
	// more reliably make sure this information is only attached once, as the error travels up the service stack
	if wrappedErr.PrivateContext["service"] == "" {
		wrappedErr.PrivateContext["service"] = ctx.Service()
	}
	if wrappedErr.PrivateContext["endpoint"] == "" {
		wrappedErr.PrivateContext["endpoint"] = endpoint.Name
	}
	return wrappedErr
}

// unmarshalRequest payload into the body based on content-type
func unmarshalRequest(req Request, body proto.Message) (err error) {
	if len(req.Payload()) == 0 {
		return nil
	}

	if req.ContentType() == "application/x-protobuf" {
		return proto.Unmarshal(req.Payload(), body)
	} else {
		return json.Unmarshal(req.Payload(), body)
	}
}
<|endoftext|>


<commit_before>package byconfigman

import (
	"github.com/cafebazaar/configman"
	"github.com/cafebazaar/hafezieh"
)

// NewCache selects a cache engine nased on the name value in config, and
// contructs it using the config values, and returns the prepared
// hafezieh.Cache instance
func NewCache(config configman.ConfigManager) hafezieh.Cache {
	// TODO
	return nil
}
<commit_msg>byconfigman isn't much practical<commit_after><|endoftext|>


<commit_before>package proxy_test

import (
	"bytes"
	"encoding/json"
	"fmt"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	. "github.com/thegreatape/betamax/proxy"
	"io"
	"io/ioutil"
	"net"
	"net/http"
	"net/url"
)

var _ = Describe("Proxy", func() {
	var proxy http.Handler
	var targetListener net.Listener
	var targetPort string
	var proxyListener net.Listener
	var proxyPort string

	BeforeEach(func() {
		targetListener, _ = net.Listen("tcp", "0.0.0.0:0")
		_, targetPort, _ = net.SplitHostPort(targetListener.Addr().String())
		proxyListener, _ = net.Listen("tcp", "0.0.0.0:0")
		_, proxyPort, _ = net.SplitHostPort(proxyListener.Addr().String())

		go http.Serve(targetListener, http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
			io.WriteString(writer, "hello, world")
		}))

		targetUrl, _ := url.Parse(fmt.Sprintf("http://127.0.0.1:%s/", targetPort))
		proxy = Proxy(targetUrl)
		go http.Serve(proxyListener, proxy)
	})

	AfterEach(func() {
		targetListener.Close()
		proxyListener.Close()
	})

	It("proxies without any configuration", func() {
		resp, _ := http.Get(fmt.Sprintf("http://127.0.0.1:%s", proxyPort))
		defer resp.Body.Close()

		body, _ := ioutil.ReadAll(resp.Body)
		Expect(string(body)).To(Equal("hello, world"))
	})

	It("returns a 500 if the target server is down", func() {
		targetListener.Close()
		resp, err := http.Get(fmt.Sprintf("http://127.0.0.1:%s", proxyPort))
		Expect(err).To(BeNil())
		Expect(resp.StatusCode).To(Equal(500))
	})

	Context("allows configuration over http", func() {
		It("returns the current configuration with a GET to /__betamax__/config", func() {
			resp, err := http.Get(fmt.Sprintf("http://127.0.0.1:%s/__betamax__/config", proxyPort))
			Expect(err).To(BeNil())

			var jsonResponse map[string]interface{}
			body, _ := ioutil.ReadAll(resp.Body)
			err = json.Unmarshal(body, &jsonResponse)
			Expect(err).To(BeNil())

			Expect(jsonResponse["cassette"]).To(Equal(""))
		})

		It("allows setting the current cassette via POST", func() {
			resp, err := http.Post(fmt.Sprintf("http://127.0.0.1:%s/__betamax__/config", proxyPort), "text/json", bytes.NewBufferString("{\"cassette\": \"test-cassette\"}"))
			Expect(err).To(BeNil())

			resp, err = http.Get(fmt.Sprintf("http://127.0.0.1:%s/__betamax__/config", proxyPort))
			Expect(err).To(BeNil())

			var jsonResponse map[string]interface{}
			body, _ := ioutil.ReadAll(resp.Body)
			err = json.Unmarshal(body, &jsonResponse)
			Expect(err).To(BeNil())

			Expect(jsonResponse["cassette"]).To(Equal("test-cassette"))
		})

	})
})
<commit_msg>Change test to use a httptest.Server so we can properly close the connection. Also WIP pending tests.<commit_after>package proxy_test

import (
	"bytes"
	"encoding/json"
	"fmt"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	. "github.com/thegreatape/betamax/proxy"
	"io"
	"io/ioutil"
	"net"
	"net/http"
	"net/http/httptest"
	"net/url"
)

var _ = Describe("Proxy", func() {
	var proxy http.Handler
	var targetServer *httptest.Server

	var proxyListener net.Listener
	var proxyPort string

	BeforeEach(func() {
		proxyListener, _ = net.Listen("tcp", "0.0.0.0:0")
		_, proxyPort, _ = net.SplitHostPort(proxyListener.Addr().String())

		targetServer = httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
			io.WriteString(writer, "hello, world")
		}))

		targetUrl, _ := url.Parse(targetServer.URL)
		proxy = Proxy(targetUrl)
		go http.Serve(proxyListener, proxy)
	})

	AfterEach(func() {
		targetServer.Close()
		proxyListener.Close()
	})

	It("proxies without any configuration", func() {
		resp, _ := http.Get(fmt.Sprintf("http://127.0.0.1:%s", proxyPort))
		defer resp.Body.Close()

		body, _ := ioutil.ReadAll(resp.Body)
		Expect(string(body)).To(Equal("hello, world"))
	})

	It("returns a 500 if the target server is down", func() {
		targetServer.Close()
		resp, err := http.Get(fmt.Sprintf("http://127.0.0.1:%s", proxyPort))
		Expect(err).To(BeNil())
		Expect(resp.StatusCode).To(Equal(500))
	})

	Context("allows configuration over http", func() {
		It("returns the current configuration with a GET to /__betamax__/config", func() {
			resp, err := http.Get(fmt.Sprintf("http://127.0.0.1:%s/__betamax__/config", proxyPort))
			Expect(err).To(BeNil())

			var jsonResponse map[string]interface{}
			body, _ := ioutil.ReadAll(resp.Body)
			err = json.Unmarshal(body, &jsonResponse)
			Expect(err).To(BeNil())

			Expect(jsonResponse["cassette"]).To(Equal(""))
		})

		It("allows setting the current cassette via POST", func() {
			resp, err := http.Post(fmt.Sprintf("http://127.0.0.1:%s/__betamax__/config", proxyPort), "text/json", bytes.NewBufferString("{\"cassette\": \"test-cassette\"}"))
			Expect(err).To(BeNil())

			resp, err = http.Get(fmt.Sprintf("http://127.0.0.1:%s/__betamax__/config", proxyPort))
			Expect(err).To(BeNil())

			var jsonResponse map[string]interface{}
			body, _ := ioutil.ReadAll(resp.Body)
			err = json.Unmarshal(body, &jsonResponse)
			Expect(err).To(BeNil())

			Expect(jsonResponse["cassette"]).To(Equal("test-cassette"))
		})
	})

	Context("records and plays back proxied responses", func() {
		It("replays responses when a cassette is set", func() {
			resp, err := http.Post(fmt.Sprintf("http://127.0.0.1:%s/__betamax__/config", proxyPort), "text/json", bytes.NewBufferString("{\"cassette\": \"test-cassette\"}"))
			Expect(err).To(BeNil())

			resp, err = http.Get(fmt.Sprintf("http://127.0.0.1:%s", proxyPort))
			body, _ := ioutil.ReadAll(resp.Body)
			Expect(err).To(BeNil())
			Expect(resp.StatusCode).To(Equal(200))
			Expect(string(body)).To(Equal("hello, world"))

			targetServer.Close()

			resp, err = http.Get(fmt.Sprintf("http://127.0.0.1:%s", proxyPort))
			body, _ = ioutil.ReadAll(resp.Body)
			Expect(err).To(BeNil())
			Expect(resp.StatusCode).To(Equal(200))
			Expect(string(body)).To(Equal("hello, world"))

		})
		PIt("records nothing without a current cassette", func() {})
		PIt("denies unrecorded responses when the option is set", func() {})
	})

})
<|endoftext|>


<commit_before>package main

import (
	gocontext "context"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"

	"github.com/Microsoft/go-winio"
	"github.com/Microsoft/hcsshim/internal/oci"
	"github.com/containerd/containerd/runtime/v2/shim"
	"github.com/containerd/containerd/runtime/v2/task"
	"github.com/containerd/ttrpc"
	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
	"github.com/urfave/cli"
)

var startCommand = cli.Command{
	Name: "start",
	Usage: `
This command will launch new shims.

The start command, as well as all binary calls to the shim, has the bundle for the container set as the cwd.

The start command MUST return an address to a shim for containerd to issue API requests for container operations.

The start command can either start a new shim or return an address to an existing shim based on the shim's logic.
`,
	SkipArgReorder: true,
	Action: func(context *cli.Context) (err error) {
		// We cant write anything to stdout/stderr for this cmd.
		logrus.SetOutput(ioutil.Discard)

		// On Windows there are two scenarios that will launch a shim.
		//
		// 1. The config.json in the bundle path contains the kubernetes
		// annotation `io.kubernetes.cri.container-type = sandbox`. This shim
		// will be served for the POD itself and all
		// `io.kubernetes.cri.container-type = container` with a matching
		// `io.kubernetes.cri.sandbox-id`. For any calls to start where the
		// config.json contains the `io.kubernetes.cri.container-type =
		// container` annotation a shim path to the
		// `io.kubernetes.cri.sandbox-id` will be returned.
		//
		// 2. The container does not have any kubernetes annotations and
		// therefore is a process isolated Windows Container, a hypervisor
		// isolated Windows Container, or a hypervisor isolated Linux Container
		// on Windows.

		const addrFmt = "\\\\.\\pipe\\ProtectedPrefix\\Administrators\\containerd-shim-%s-%s-pipe"

		var (
			address string
			pid     int
		)

		cwd, err := os.Getwd()
		if err != nil {
			return err
		}

		a, err := getSpecAnnotations(cwd)
		if err != nil {
			return err
		}

		ct, sbid, err := oci.GetSandboxTypeAndID(a)
		if err != nil {
			return err
		}

		if ct == oci.KubernetesContainerTypeContainer {
			address = fmt.Sprintf(addrFmt, namespaceFlag, sbid)

			// Connect to the hosting shim and get the pid
			c, err := winio.DialPipe(address, nil)
			if err != nil {
				return errors.Wrap(err, "failed to connect to hosting shim")
			}
			cl := ttrpc.NewClient(c)
			cl.OnClose(func() { c.Close() })
			t := task.NewTaskClient(cl)
			ctx := gocontext.Background()
			req := &task.ConnectRequest{ID: sbid}
			cr, err := t.Connect(ctx, req)

			cl.Close()
			c.Close()
			if err != nil {
				return errors.Wrap(err, "failed to get shim pid from hosting shim")
			}
			pid = int(cr.ShimPid)
		}

		// We need to serve a new one.
		if address == "" {
			isSandbox := ct == oci.KubernetesContainerTypeSandbox
			if isSandbox && idFlag != sbid {
				return errors.Errorf(
					"'id' and '%s' must match for '%s=%s'",
					oci.KubernetesSandboxIDAnnotation,
					oci.KubernetesContainerTypeAnnotation,
					oci.KubernetesContainerTypeSandbox)
			}

			self, err := os.Executable()
			if err != nil {
				return err
			}

			r, w, err := os.Pipe()
			if err != nil {
				return err
			}
			defer r.Close()
			defer w.Close()

			f, err := os.Create(filepath.Join(filepath.Dir(self), "panic.log"))
			if err != nil {
				return err
			}
			defer f.Close()

			address = fmt.Sprintf(addrFmt, namespaceFlag, idFlag)
			args := []string{
				self,
				"--namespace", namespaceFlag,
				"--address", addressFlag,
				"--publish-binary", containerdBinaryFlag,
				"--id", idFlag,
				"serve",
				"--socket", address,
			}
			if isSandbox {
				args = append(args, "--is-sandbox")
			}
			cmd := &exec.Cmd{
				Path:   self,
				Args:   args,
				Env:    os.Environ(),
				Dir:    cwd,
				Stdout: w,
				Stderr: f,
			}

			if err := cmd.Start(); err != nil {
				return err
			}
			w.Close()
			defer func() {
				if err != nil {
					cmd.Process.Kill()
				}
			}()

			// Forward the invocation stderr until the serve command closes it.
			_, err = io.Copy(os.Stderr, r)
			if err != nil {
				return err
			}
			pid = cmd.Process.Pid
		}

		if err := shim.WritePidFile(filepath.Join(cwd, "shim.pid"), pid); err != nil {
			return err
		}
		if err := shim.WriteAddress(filepath.Join(cwd, "address"), address); err != nil {
			return err
		}

		// Write the address new or existing to stdout
		if _, err := fmt.Fprint(os.Stdout, address); err != nil {
			return err
		}
		return nil
	},
}

func getSpecAnnotations(bundlePath string) (map[string]string, error) {
	// specAnnotations is a minimal representation for oci.Spec that we need
	// to serve a shim.
	type specAnnotations struct {
		// Annotations contains arbitrary metadata for the container.
		Annotations map[string]string `json:"annotations,omitempty"`
	}
	f, err := os.OpenFile(filepath.Join(bundlePath, "config.json"), os.O_RDONLY, 0)
	if err != nil {
		return nil, err
	}
	defer f.Close()
	var spec specAnnotations
	if err := json.NewDecoder(f).Decode(&spec); err != nil {
		return nil, errors.Wrap(err, "failed to deserialize valid OCI spec")
	}
	return spec.Annotations, nil
}
<commit_msg>Move panic log to bundle dir<commit_after>package main

import (
	gocontext "context"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"

	"github.com/Microsoft/go-winio"
	"github.com/Microsoft/hcsshim/internal/oci"
	"github.com/containerd/containerd/runtime/v2/shim"
	"github.com/containerd/containerd/runtime/v2/task"
	"github.com/containerd/ttrpc"
	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
	"github.com/urfave/cli"
)

var startCommand = cli.Command{
	Name: "start",
	Usage: `
This command will launch new shims.

The start command, as well as all binary calls to the shim, has the bundle for the container set as the cwd.

The start command MUST return an address to a shim for containerd to issue API requests for container operations.

The start command can either start a new shim or return an address to an existing shim based on the shim's logic.
`,
	SkipArgReorder: true,
	Action: func(context *cli.Context) (err error) {
		// We cant write anything to stdout/stderr for this cmd.
		logrus.SetOutput(ioutil.Discard)

		// On Windows there are two scenarios that will launch a shim.
		//
		// 1. The config.json in the bundle path contains the kubernetes
		// annotation `io.kubernetes.cri.container-type = sandbox`. This shim
		// will be served for the POD itself and all
		// `io.kubernetes.cri.container-type = container` with a matching
		// `io.kubernetes.cri.sandbox-id`. For any calls to start where the
		// config.json contains the `io.kubernetes.cri.container-type =
		// container` annotation a shim path to the
		// `io.kubernetes.cri.sandbox-id` will be returned.
		//
		// 2. The container does not have any kubernetes annotations and
		// therefore is a process isolated Windows Container, a hypervisor
		// isolated Windows Container, or a hypervisor isolated Linux Container
		// on Windows.

		const addrFmt = "\\\\.\\pipe\\ProtectedPrefix\\Administrators\\containerd-shim-%s-%s-pipe"

		var (
			address string
			pid     int
		)

		cwd, err := os.Getwd()
		if err != nil {
			return err
		}

		a, err := getSpecAnnotations(cwd)
		if err != nil {
			return err
		}

		ct, sbid, err := oci.GetSandboxTypeAndID(a)
		if err != nil {
			return err
		}

		if ct == oci.KubernetesContainerTypeContainer {
			address = fmt.Sprintf(addrFmt, namespaceFlag, sbid)

			// Connect to the hosting shim and get the pid
			c, err := winio.DialPipe(address, nil)
			if err != nil {
				return errors.Wrap(err, "failed to connect to hosting shim")
			}
			cl := ttrpc.NewClient(c)
			cl.OnClose(func() { c.Close() })
			t := task.NewTaskClient(cl)
			ctx := gocontext.Background()
			req := &task.ConnectRequest{ID: sbid}
			cr, err := t.Connect(ctx, req)

			cl.Close()
			c.Close()
			if err != nil {
				return errors.Wrap(err, "failed to get shim pid from hosting shim")
			}
			pid = int(cr.ShimPid)
		}

		// We need to serve a new one.
		if address == "" {
			isSandbox := ct == oci.KubernetesContainerTypeSandbox
			if isSandbox && idFlag != sbid {
				return errors.Errorf(
					"'id' and '%s' must match for '%s=%s'",
					oci.KubernetesSandboxIDAnnotation,
					oci.KubernetesContainerTypeAnnotation,
					oci.KubernetesContainerTypeSandbox)
			}

			self, err := os.Executable()
			if err != nil {
				return err
			}

			r, w, err := os.Pipe()
			if err != nil {
				return err
			}
			defer r.Close()
			defer w.Close()

			f, err := os.Create(filepath.Join(cwd, "panic.log"))
			if err != nil {
				return err
			}
			defer f.Close()

			address = fmt.Sprintf(addrFmt, namespaceFlag, idFlag)
			args := []string{
				self,
				"--namespace", namespaceFlag,
				"--address", addressFlag,
				"--publish-binary", containerdBinaryFlag,
				"--id", idFlag,
				"serve",
				"--socket", address,
			}
			if isSandbox {
				args = append(args, "--is-sandbox")
			}
			cmd := &exec.Cmd{
				Path:   self,
				Args:   args,
				Env:    os.Environ(),
				Dir:    cwd,
				Stdout: w,
				Stderr: f,
			}

			if err := cmd.Start(); err != nil {
				return err
			}
			w.Close()
			defer func() {
				if err != nil {
					cmd.Process.Kill()
				}
			}()

			// Forward the invocation stderr until the serve command closes it.
			_, err = io.Copy(os.Stderr, r)
			if err != nil {
				return err
			}
			pid = cmd.Process.Pid
		}

		if err := shim.WritePidFile(filepath.Join(cwd, "shim.pid"), pid); err != nil {
			return err
		}
		if err := shim.WriteAddress(filepath.Join(cwd, "address"), address); err != nil {
			return err
		}

		// Write the address new or existing to stdout
		if _, err := fmt.Fprint(os.Stdout, address); err != nil {
			return err
		}
		return nil
	},
}

func getSpecAnnotations(bundlePath string) (map[string]string, error) {
	// specAnnotations is a minimal representation for oci.Spec that we need
	// to serve a shim.
	type specAnnotations struct {
		// Annotations contains arbitrary metadata for the container.
		Annotations map[string]string `json:"annotations,omitempty"`
	}
	f, err := os.OpenFile(filepath.Join(bundlePath, "config.json"), os.O_RDONLY, 0)
	if err != nil {
		return nil, err
	}
	defer f.Close()
	var spec specAnnotations
	if err := json.NewDecoder(f).Decode(&spec); err != nil {
		return nil, errors.Wrap(err, "failed to deserialize valid OCI spec")
	}
	return spec.Annotations, nil
}
<|endoftext|>


<commit_before>// The HTTP server processing route and feature requests

package main

import (
	"alg"
	"encoding/json"
	"errors"
	"flag"
	"geo"
	"graph"
	"html/template"
	"io"
	"kdtree"
	"log"
	"net/http"
	"os"
	"runtime"
	"runtime/pprof"
	"strconv"
	"strings"
	"time"

	//"fmt"
)

const (
	ParameterWaypoints  = "waypoints"
	ParameterTravelmode = "travelmode"
	ParameterMetric     = "metric"
	ParameterAvoid      = "avoid"

	SeparatorWaypoints = "|"
	SeparatorLatLng    = ","

	DefaultPort = 23401 // the default port number

	TravelmodeCar = "driving"
	TravelmodeFoot = "walking"
	TravelmodeBike = "bicycling"
)

type RoutingData struct {
	graph  graph.Graph
	kdtree *kdtree.KdTree
}

var (
	featureResponse []byte

	// command line flags
	FlagPort    	int
	FlagLogging 	bool
	FlagCpuProfile 	string
	FlagCaching		bool

	startupTime time.Time

	osmData map[string] RoutingData
)

func init() {
	flag.IntVar(&FlagPort, "port", DefaultPort, "the port where the server is running")
	flag.BoolVar(&FlagLogging, "logging", false, "enables logging of requests")
	flag.StringVar(&FlagCpuProfile, "cpuprofile", "", "enables CPU profiling")
	flag.BoolVar(&FlagCaching, "caching", false, "enables caching of route requests")
}

func main() {
	runtime.GOMAXPROCS(8)
	log.Println("Starting...")

	// call the command line parser
	flag.Parse()

	if err := setup(); err != nil {
		log.Fatal("Setup failed:", err)
		return
	}

	// map URLs to functions
	http.HandleFunc("/", root)
	http.HandleFunc("/routes", routes)
	http.HandleFunc("/features", features)
	http.HandleFunc("/awesome", test)
	http.HandleFunc("/status", status)
	http.HandleFunc("/forward", forward)
	http.HandleFunc("/stop6bbw753i08wn1ca", stop)

	// start the HTTP server
	log.Println("Serving...")
	startupTime = time.Now()
	err := http.ListenAndServe(":" + strconv.Itoa(FlagPort), nil)
	if err != nil {
		log.Fatal("ListenAndServe: ", err)
	}
}

func loadFiles(base string) (*RoutingData, error) {
	g, err := graph.Open(base)
	if err != nil {
		log.Fatal("Loading graph: ", err)
		return nil, err
	}
	t, err := alg.LoadKdTree(base, g.Positions());
	if  err != nil {
		log.Fatal("Loading k-d tree: ", err)
		return nil, err
	}
	return &RoutingData{g, t}, nil
}

// setup does some initialization before the HTTP server starts.
func setup() error {
	osmData = map[string] RoutingData {}

	dat, err := loadFiles("car")
	if err != nil {
		return err
	}
	osmData["driving"] = *dat

	dat, err = loadFiles("bike")
	if err != nil {
		return err
	}
	osmData["bicycling"] = *dat

	dat, err = loadFiles("foot")
	if err != nil {
		return err
	}
	osmData["walking"] = *dat

	if FlagLogging {
		InitLogger()
	}
	if FlagCaching {
		InitCache()
	}

	// create the feature response only once (no change at runtime)
	supportedTravelmodes := TravelMode{Driving: true, Walking: true, Bicycling: true}
	supportedFeatures := &Features{TravelMode: supportedTravelmodes}
	if fp, err := json.Marshal(supportedFeatures); err != nil {
		log.Fatal("Creating feature response: ", err)
		return err
	} else {
		// only assign if the creation was successful
		featureResponse = fp
	}
	return nil
}

// root just tells that the server is alive.
func root(w http.ResponseWriter, r *http.Request) {
	io.WriteString(w, "Server is up and running")
}

// routes returns routes according to the given parameters. (at the moment only one route is returned statically)
func routes(w http.ResponseWriter, r *http.Request) {
	startTime := time.Now()
	defer LogRequest(r, startTime)

	// profiling if enabled
	if FlagCpuProfile != "" {
        f, err := os.Create(FlagCpuProfile)
        if err != nil {
            log.Fatal("Creating profile: ", err)
        }
        pprof.StartCPUProfile(f)
        defer pprof.StopCPUProfile()
    }

	// parse URL and extract parameters
	urlParameter := r.URL.Query()

	// handle waypoints parameter
	if urlParameter[ParameterWaypoints] == nil || len(urlParameter[ParameterWaypoints]) < 1 {
		http.Error(w, "no waypoints", http.StatusBadRequest)
		return
	}
	waypoints, err := getWaypoints(urlParameter["waypoints"][0])
	if err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}

	// travel mode, using strings as constant
	travelmode := TravelmodeCar // Per default driving
	if urlParameter[ParameterTravelmode] != nil {
		if urlParameter[ParameterTravelmode][0] == TravelmodeCar ||
			urlParameter[ParameterTravelmode][0] == TravelmodeFoot ||
			urlParameter[ParameterTravelmode][0] == TravelmodeBike {
			travelmode = urlParameter[ParameterTravelmode][0]
		} else {
			http.Error(w, "wrong travelmode", http.StatusBadRequest)
			return
		}
	}

	cachingKey := urlParameter["waypoints"][0] + travelmode
	if FlagCaching {
		if resp, ok := CacheGet(cachingKey); ok {
			w.Write(resp)
			return
		}
	}

	// there is no need to handle the other parameters at the moment as
	// the implementation should not fail for unknown parameters/values
	data := osmData[travelmode]
	legs := make([]Leg, len(waypoints) - 1)
	distance := 0.0
	duration := 0.0
	for i := 0; i < len(waypoints) - 1; i++ {
		_, startWays := alg.NearestNeighbor(data.kdtree, waypoints[i][0],   waypoints[i][1],   true  /* forward */)
		_, endWays   := alg.NearestNeighbor(data.kdtree, waypoints[i+1][0], waypoints[i+1][1], false /* forward */)

		// Use the Dijkatrs version using a large slice only for long roues where the map of the
		// other version can get quite large
		if getDistance(data.graph, startWays[0].Node, endWays[0].Node) > 100.0 * 1000.0 { // > 100km
			dist, vertices, edges, start, end := alg.DijkstraSlice(data.graph, startWays, endWays)
			legs[i] = PathToLeg(data.graph, dist, vertices, edges, start, end)
		} else {
			dist, vertices, edges, start, end := alg.Dijkstra(data.graph, startWays, endWays)
			legs[i] = PathToLeg(data.graph, dist, vertices, edges, start, end)
		}
		distance += float64(legs[i].Distance.Value)
		duration += float64(legs[i].Duration.Value)
	}

	route := Route{
		Distance: FormatDistance(distance),
		Duration: FormatDuration(duration),
		StartLocation: legs[0].StartLocation,
		EndLocation: legs[len(legs)-1].EndLocation,
		Legs: legs,
	}

	result := &Result{
		BoundingBox: ComputeBounds(route),
		Routes:      []Route{route},
	}

	jsonResult, err := json.Marshal(result)
	if err != nil {
		http.Error(w, "unable to create a proper JSON object", http.StatusInternalServerError)
		return
	}
	if FlagCaching {
		CachePut(cachingKey, jsonResult)
	}
	w.Write(jsonResult)
}

// getDistance returns the distance between the two given nodes
func getDistance(g graph.Graph, n1 graph.Node, n2 graph.Node) float64 {
	lat1, lng1 := g.NodeLatLng(n1)
	lat2, lng2 := g.NodeLatLng(n2)
	return geo.Distance(geo.Coordinate{Lat: lat1, Lng: lng1}, geo.Coordinate{Lat: lat2, Lng: lng2})
}

// getWaypoints parses the given waypoints.
func getWaypoints(waypointString string) ([]Point, error) {
	waypointStrings := strings.Split(waypointString, SeparatorWaypoints)
	if len(waypointStrings) < 2 {
		return nil, errors.New("too few waypoints. at least 2 waypoints are needed")
	}

	points := make([]Point, len(waypointStrings))
	for i, v := range waypointStrings {
		coordinateStrings := strings.Split(v, SeparatorLatLng)
		if len(coordinateStrings) != 2 {
			return nil, errors.New("wrong formatted coordinate in waypoint list: " + v)
		}
		lat, err := strconv.ParseFloat(coordinateStrings[0], 64 /* bitSize */)
		if err != nil {
			return nil, errors.New("wrong formatted number in waypoint list: " + coordinateStrings[0])
		}
		lng, err := strconv.ParseFloat(coordinateStrings[1], 64 /* bitSize */)
		if err != nil {
			return nil, errors.New("wrong formatted number in waypoint list: " + coordinateStrings[1])
		}
		points[i] = Point{lat, lng}
	}
	return points, nil
}

// features handles feature requests.
func features(w http.ResponseWriter, r *http.Request) {
	startTime := time.Now()
	defer LogRequest(r, startTime)

	w.Write(featureResponse)
}

// stop The server can be terminated with a request.
func stop(w http.ResponseWriter, r *http.Request) {
	startTime := time.Now()
	LogRequest(r, startTime)
	// wait 5 seconds so that the logger has time to write the request to file
	time.Sleep(5 * time.Second)

	os.Exit(1)
}

// forward redirects the routing request to another port
func forward(w http.ResponseWriter, r *http.Request) {
	// only extract the "port" parameter
	urlParameter := r.URL.Query()
	if urlParameter["port"] == nil || len(urlParameter["port"]) < 1 {
		http.Error(w, "no port", http.StatusBadRequest)
		return
	}
	port := urlParameter["port"][0]

	forwardParameter := ""
	for k, v := range urlParameter {
		if k != "port" {
			forwardParameter += "&" + k + "=" + v[0]
		}
	}
	// remove first &
	forwardParameter = forwardParameter[1:]
	resp, err := http.Get("http://urania.mpi-inf.mpg.de:" + port + "/routes?" + forwardParameter)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	body := make([]byte, 1024)
	length := 1
	var readErr error
	for length > 0 {
		length, readErr = resp.Body.Read(body)
		if length != 0 && readErr != nil {
			http.Error(w, "error while reading response from remote server: " + readErr.Error(), http.StatusInternalServerError)
			return
		}
		w.Write(body[:length])
	}
}

// status returns an HTML page with some status information about the server
func status(w http.ResponseWriter, r *http.Request) {
	statusInfo := make(map[string]string)
	statusInfo["startupTime"] = startupTime.Format(time.RFC3339 /* "2006-01-02T15:04:05Z07:00" */)
	uptime := time.Now().Sub(startupTime)
	hours := int64(uptime.Hours())
	minutes := int64(uptime.Minutes()) % 60
	statusInfo["uptimeHours"] = strconv.FormatInt(hours, 10 /* base */)
	statusInfo["uptimeMinutes"] = strconv.FormatInt(minutes, 10 /* base */)
	statusInfo["cacheCurrent"] = strconv.FormatInt(int64(cache.Size / 1024), 10 /* base */)
	statusInfo["cacheMax"] = strconv.FormatInt(int64(MaxCacheSize / 1024), 10 /* base */)

	if err := statusTemplate.Execute(w, statusInfo); err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
	}
}

var statusTemplate = template.Must(template.New("status").Parse(statusTemplateHTML))

const statusTemplateHTML = `
<!DOCTYPE html>

  <title>Team FortyTwo Server Status</title>

Team FortyTwo Server Status

Started: {{ .startupTime }}

Uptime: {{ .uptimeHours }} h {{ .uptimeMinutes }} min

Cache: {{ .cacheCurrent }} of {{ .cacheMax }} kB

`
<commit_msg>consequent usage of constants<commit_after>// The HTTP server processing route and feature requests

package main

import (
	"alg"
	"encoding/json"
	"errors"
	"flag"
	"geo"
	"graph"
	"html/template"
	"io"
	"kdtree"
	"log"
	"net/http"
	"os"
	"runtime"
	"runtime/pprof"
	"strconv"
	"strings"
	"time"

	//"fmt"
)

const (
	ParameterWaypoints  = "waypoints"
	ParameterTravelmode = "travelmode"
	ParameterMetric     = "metric"
	ParameterAvoid      = "avoid"

	SeparatorWaypoints = "|"
	SeparatorLatLng    = ","

	DefaultPort = 23401 // the default port number

	TravelmodeCar = "driving"
	TravelmodeFoot = "walking"
	TravelmodeBike = "bicycling"
)

type RoutingData struct {
	graph  graph.Graph
	kdtree *kdtree.KdTree
}

var (
	featureResponse []byte

	// command line flags
	FlagPort    	int
	FlagLogging 	bool
	FlagCpuProfile 	string
	FlagCaching		bool

	startupTime time.Time

	osmData map[string] RoutingData
)

func init() {
	flag.IntVar(&FlagPort, "port", DefaultPort, "the port where the server is running")
	flag.BoolVar(&FlagLogging, "logging", false, "enables logging of requests")
	flag.StringVar(&FlagCpuProfile, "cpuprofile", "", "enables CPU profiling")
	flag.BoolVar(&FlagCaching, "caching", false, "enables caching of route requests")
}

func main() {
	runtime.GOMAXPROCS(8)
	log.Println("Starting...")

	// call the command line parser
	flag.Parse()

	if err := setup(); err != nil {
		log.Fatal("Setup failed:", err)
		return
	}

	// map URLs to functions
	http.HandleFunc("/", root)
	http.HandleFunc("/routes", routes)
	http.HandleFunc("/features", features)
	http.HandleFunc("/awesome", test)
	http.HandleFunc("/status", status)
	http.HandleFunc("/forward", forward)
	http.HandleFunc("/stop6bbw753i08wn1ca", stop)

	// start the HTTP server
	log.Println("Serving...")
	startupTime = time.Now()
	err := http.ListenAndServe(":" + strconv.Itoa(FlagPort), nil)
	if err != nil {
		log.Fatal("ListenAndServe: ", err)
	}
}

func loadFiles(base string) (*RoutingData, error) {
	g, err := graph.Open(base)
	if err != nil {
		log.Fatal("Loading graph: ", err)
		return nil, err
	}
	t, err := alg.LoadKdTree(base, g.Positions());
	if  err != nil {
		log.Fatal("Loading k-d tree: ", err)
		return nil, err
	}
	return &RoutingData{g, t}, nil
}

// setup does some initialization before the HTTP server starts.
func setup() error {
	osmData = map[string] RoutingData {}

	dat, err := loadFiles("car")
	if err != nil {
		return err
	}
	osmData[TravelmodeCar] = *dat

	dat, err = loadFiles("bike")
	if err != nil {
		return err
	}
	osmData[TravelmodeBike] = *dat

	dat, err = loadFiles("foot")
	if err != nil {
		return err
	}
	osmData[TravelmodeFoot] = *dat

	if FlagLogging {
		InitLogger()
	}
	if FlagCaching {
		InitCache()
	}

	// create the feature response only once (no change at runtime)
	supportedTravelmodes := TravelMode{Driving: true, Walking: true, Bicycling: true}
	supportedFeatures := &Features{TravelMode: supportedTravelmodes}
	if fp, err := json.Marshal(supportedFeatures); err != nil {
		log.Fatal("Creating feature response: ", err)
		return err
	} else {
		// only assign if the creation was successful
		featureResponse = fp
	}
	return nil
}

// root just tells that the server is alive.
func root(w http.ResponseWriter, r *http.Request) {
	io.WriteString(w, "Server is up and running")
}

// routes returns routes according to the given parameters. (at the moment only one route is returned statically)
func routes(w http.ResponseWriter, r *http.Request) {
	startTime := time.Now()
	defer LogRequest(r, startTime)

	// profiling if enabled
	if FlagCpuProfile != "" {
        f, err := os.Create(FlagCpuProfile)
        if err != nil {
            log.Fatal("Creating profile: ", err)
        }
        pprof.StartCPUProfile(f)
        defer pprof.StopCPUProfile()
    }

	// parse URL and extract parameters
	urlParameter := r.URL.Query()

	// handle waypoints parameter
	if urlParameter[ParameterWaypoints] == nil || len(urlParameter[ParameterWaypoints]) < 1 {
		http.Error(w, "no waypoints", http.StatusBadRequest)
		return
	}
	waypoints, err := getWaypoints(urlParameter[ParameterWaypoints][0])
	if err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}

	// travel mode, using strings as constant
	travelmode := TravelmodeCar // Per default driving
	if urlParameter[ParameterTravelmode] != nil {
		if urlParameter[ParameterTravelmode][0] == TravelmodeCar ||
			urlParameter[ParameterTravelmode][0] == TravelmodeFoot ||
			urlParameter[ParameterTravelmode][0] == TravelmodeBike {
			travelmode = urlParameter[ParameterTravelmode][0]
		} else {
			http.Error(w, "wrong travelmode", http.StatusBadRequest)
			return
		}
	}

	cachingKey := urlParameter[ParameterWaypoints][0] + travelmode
	if FlagCaching {
		if resp, ok := CacheGet(cachingKey); ok {
			w.Write(resp)
			return
		}
	}

	// there is no need to handle the other parameters at the moment as
	// the implementation should not fail for unknown parameters/values
	data := osmData[travelmode]
	legs := make([]Leg, len(waypoints) - 1)
	distance := 0.0
	duration := 0.0
	for i := 0; i < len(waypoints) - 1; i++ {
		_, startWays := alg.NearestNeighbor(data.kdtree, waypoints[i][0],   waypoints[i][1],   true  /* forward */)
		_, endWays   := alg.NearestNeighbor(data.kdtree, waypoints[i+1][0], waypoints[i+1][1], false /* forward */)

		// Use the Dijkatrs version using a large slice only for long roues where the map of the
		// other version can get quite large
		if getDistance(data.graph, startWays[0].Node, endWays[0].Node) > 100.0 * 1000.0 { // > 100km
			dist, vertices, edges, start, end := alg.DijkstraSlice(data.graph, startWays, endWays)
			legs[i] = PathToLeg(data.graph, dist, vertices, edges, start, end)
		} else {
			dist, vertices, edges, start, end := alg.Dijkstra(data.graph, startWays, endWays)
			legs[i] = PathToLeg(data.graph, dist, vertices, edges, start, end)
		}
		distance += float64(legs[i].Distance.Value)
		duration += float64(legs[i].Duration.Value)
	}

	route := Route{
		Distance: FormatDistance(distance),
		Duration: FormatDuration(duration),
		StartLocation: legs[0].StartLocation,
		EndLocation: legs[len(legs)-1].EndLocation,
		Legs: legs,
	}

	result := &Result{
		BoundingBox: ComputeBounds(route),
		Routes:      []Route{route},
	}

	jsonResult, err := json.Marshal(result)
	if err != nil {
		http.Error(w, "unable to create a proper JSON object", http.StatusInternalServerError)
		return
	}
	if FlagCaching {
		CachePut(cachingKey, jsonResult)
	}
	w.Write(jsonResult)
}

// getDistance returns the distance between the two given nodes
func getDistance(g graph.Graph, n1 graph.Node, n2 graph.Node) float64 {
	lat1, lng1 := g.NodeLatLng(n1)
	lat2, lng2 := g.NodeLatLng(n2)
	return geo.Distance(geo.Coordinate{Lat: lat1, Lng: lng1}, geo.Coordinate{Lat: lat2, Lng: lng2})
}

// getWaypoints parses the given waypoints.
func getWaypoints(waypointString string) ([]Point, error) {
	waypointStrings := strings.Split(waypointString, SeparatorWaypoints)
	if len(waypointStrings) < 2 {
		return nil, errors.New("too few waypoints. at least 2 waypoints are needed")
	}

	points := make([]Point, len(waypointStrings))
	for i, v := range waypointStrings {
		coordinateStrings := strings.Split(v, SeparatorLatLng)
		if len(coordinateStrings) != 2 {
			return nil, errors.New("wrong formatted coordinate in waypoint list: " + v)
		}
		lat, err := strconv.ParseFloat(coordinateStrings[0], 64 /* bitSize */)
		if err != nil {
			return nil, errors.New("wrong formatted number in waypoint list: " + coordinateStrings[0])
		}
		lng, err := strconv.ParseFloat(coordinateStrings[1], 64 /* bitSize */)
		if err != nil {
			return nil, errors.New("wrong formatted number in waypoint list: " + coordinateStrings[1])
		}
		points[i] = Point{lat, lng}
	}
	return points, nil
}

// features handles feature requests.
func features(w http.ResponseWriter, r *http.Request) {
	startTime := time.Now()
	defer LogRequest(r, startTime)

	w.Write(featureResponse)
}

// stop The server can be terminated with a request.
func stop(w http.ResponseWriter, r *http.Request) {
	startTime := time.Now()
	LogRequest(r, startTime)
	// wait 5 seconds so that the logger has time to write the request to file
	time.Sleep(5 * time.Second)

	os.Exit(1)
}

// forward redirects the routing request to another port
func forward(w http.ResponseWriter, r *http.Request) {
	// only extract the "port" parameter
	urlParameter := r.URL.Query()
	if urlParameter["port"] == nil || len(urlParameter["port"]) < 1 {
		http.Error(w, "no port", http.StatusBadRequest)
		return
	}
	port := urlParameter["port"][0]

	forwardParameter := ""
	for k, v := range urlParameter {
		if k != "port" {
			forwardParameter += "&" + k + "=" + v[0]
		}
	}
	// remove first &
	forwardParameter = forwardParameter[1:]
	resp, err := http.Get("http://urania.mpi-inf.mpg.de:" + port + "/routes?" + forwardParameter)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	body := make([]byte, 1024)
	length := 1
	var readErr error
	for length > 0 {
		length, readErr = resp.Body.Read(body)
		if length != 0 && readErr != nil {
			http.Error(w, "error while reading response from remote server: " + readErr.Error(), http.StatusInternalServerError)
			return
		}
		w.Write(body[:length])
	}
}

// status returns an HTML page with some status information about the server
func status(w http.ResponseWriter, r *http.Request) {
	statusInfo := make(map[string]string)
	statusInfo["startupTime"] = startupTime.Format(time.RFC3339 /* "2006-01-02T15:04:05Z07:00" */)
	uptime := time.Now().Sub(startupTime)
	hours := int64(uptime.Hours())
	minutes := int64(uptime.Minutes()) % 60
	statusInfo["uptimeHours"] = strconv.FormatInt(hours, 10 /* base */)
	statusInfo["uptimeMinutes"] = strconv.FormatInt(minutes, 10 /* base */)
	statusInfo["cacheCurrent"] = strconv.FormatInt(int64(cache.Size / 1024), 10 /* base */)
	statusInfo["cacheMax"] = strconv.FormatInt(int64(MaxCacheSize / 1024), 10 /* base */)

	if err := statusTemplate.Execute(w, statusInfo); err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
	}
}

var statusTemplate = template.Must(template.New("status").Parse(statusTemplateHTML))

const statusTemplateHTML = `
<!DOCTYPE html>

  <title>Team FortyTwo Server Status</title>

Team FortyTwo Server Status

Started: {{ .startupTime }}

Uptime: {{ .uptimeHours }} h {{ .uptimeMinutes }} min

Cache: {{ .cacheCurrent }} of {{ .cacheMax }} kB

`
<|endoftext|>


<commit_before>// Copyright 2019 Google LLC
//
// 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
//
//     https://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 main

import (
	"io/ioutil"
	"log"
	"os"

	"github.com/golang/protobuf/proto"
	plugin "github.com/golang/protobuf/protoc-gen-go/plugin"
	"github.com/googleapis/gapic-config-validator/internal/validator"
)

func main() {
	reqBytes, err := ioutil.ReadAll(os.Stdin)
	if err != nil {
		log.Fatal(err)
	}
	var req plugin.CodeGeneratorRequest
	if err := req.Unmarshal(reqBytes); err != nil {
		log.Fatal(err)
	}

	resp, err := validator.Validate(&req)
	if err != nil {
		resp.Error = proto.String(err.Error())
	}

	outBytes, err := proto.Marshal(resp)
	if err != nil {
		log.Fatal(err)
	}
	if _, err := os.Stdout.Write(outBytes); err != nil {
		log.Fatal(err)
	}
}
<commit_msg>validator: use proto Unmarshal instead of CodeGeneratorRequest (#96)<commit_after>// Copyright 2019 Google LLC
//
// 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
//
//     https://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 main

import (
	"io/ioutil"
	"log"
	"os"

	"github.com/golang/protobuf/proto"
	plugin "github.com/golang/protobuf/protoc-gen-go/plugin"
	"github.com/googleapis/gapic-config-validator/internal/validator"
)

func main() {
	reqBytes, err := ioutil.ReadAll(os.Stdin)
	if err != nil {
		log.Fatal(err)
	}
	var req plugin.CodeGeneratorRequest
	if err := proto.Unmarshal(reqBytes, &req); err != nil {
		log.Fatal(err)
	}

	resp, err := validator.Validate(&req)
	if err != nil {
		resp.Error = proto.String(err.Error())
	}

	outBytes, err := proto.Marshal(resp)
	if err != nil {
		log.Fatal(err)
	}
	if _, err := os.Stdout.Write(outBytes); err != nil {
		log.Fatal(err)
	}
}
<|endoftext|>


<commit_before>package controller

import (
	"fmt"
	"os"
	"testing"

	"net/http"

	"github.com/almighty/almighty-core/app"
	"github.com/almighty/almighty-core/application"
	config "github.com/almighty/almighty-core/configuration"
	"github.com/almighty/almighty-core/migration"
	"github.com/almighty/almighty-core/models"
	"github.com/almighty/almighty-core/remoteworkitem"
	"github.com/almighty/almighty-core/rendering"
	"github.com/almighty/almighty-core/resource"
	"github.com/almighty/almighty-core/workitem"
	"github.com/goadesign/goa"
	"github.com/jinzhu/gorm"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"golang.org/x/net/context"
)

var DB *gorm.DB
var RwiScheduler *remoteworkitem.Scheduler
var configuration *config.ConfigurationData

func TestMain(m *testing.M) {
	var err error

	configuration, err = config.GetConfigurationData()
	if err != nil {
		panic(fmt.Errorf("Failed to setup the configuration: %s", err.Error()))
	}

	if _, c := os.LookupEnv(resource.Database); c != false {

		DB, err = gorm.Open("postgres", configuration.GetPostgresConfigString())

		if err != nil {
			panic("Failed to connect database: " + err.Error())
		}
		defer DB.Close()

		// Make sure the database is populated with the correct types (e.g. bug etc.)
		if configuration.GetPopulateCommonTypes() {
			if err := models.Transactional(DB, func(tx *gorm.DB) error {
				return migration.PopulateCommonTypes(context.Background(), tx, workitem.NewWorkItemTypeRepository(tx))
			}); err != nil {
				panic(err.Error())
			}

		}

		// RemoteWorkItemScheduler now available for all other test cases
		RwiScheduler = remoteworkitem.NewScheduler(DB)
	}
	os.Exit(func() int {
		c := m.Run()
		RwiScheduler.Stop()
		return c
	}())
}

func TestNewWorkitemController(t *testing.T) {
	t.Parallel()
	resource.Require(t, resource.UnitTest)
	assert.Panics(t, func() {
		NewWorkitemController(goa.New("Test service"), nil)
	})
}

func TestParseInts(t *testing.T) {
	t.Parallel()
	resource.Require(t, resource.UnitTest)

	integers, err := parseInts(nil)
	assert.Equal(t, nil, err)
	assert.Equal(t, []int{}, integers)

	str := "1, 2, foo"
	_, err = parseInts(&str)
	assert.NotNil(t, err)
}

func TestParseLimit(t *testing.T) {
	t.Parallel()
	resource.Require(t, resource.UnitTest)

	// Test parsing error in parseInts
	str := "1000, foo"
	integers, length, err := parseLimit(&str)
	assert.NotNil(t, err)
	assert.Equal(t, 0, length)
	assert.Nil(t, integers)

	// Test length = 1
	str = "1000"
	integers, length, err = parseLimit(&str)
	assert.Nil(t, err)
	assert.Equal(t, 1000, length)
	assert.Nil(t, integers)

	// Test empty string
	str = ""
	integers, length, err = parseLimit(&str)
	assert.Nil(t, err)
	assert.Equal(t, 100, length)
	assert.Nil(t, integers)
}

func TestSetPagingLinks(t *testing.T) {
	links := &app.PagingLinks{}
	setPagingLinks(links, "", 0, 0, 1, 0)
	assert.Equal(t, "?page[offset]=0&page[limit]=1", *links.First)
	assert.Equal(t, "?page[offset]=0&page[limit]=0", *links.Last)
	assert.Nil(t, links.Next)
	assert.Nil(t, links.Prev)

	setPagingLinks(links, "prefix", 0, 0, 1, 0)
	assert.Equal(t, "prefix?page[offset]=0&page[limit]=1", *links.First)
	assert.Equal(t, "prefix?page[offset]=0&page[limit]=0", *links.Last)
	assert.Nil(t, links.Next)
	assert.Nil(t, links.Prev)

	setPagingLinks(links, "", 0, 0, 1, 1)
	assert.Equal(t, "?page[offset]=0&page[limit]=1", *links.First)
	assert.Equal(t, "?page[offset]=0&page[limit]=1", *links.Last)
	assert.Equal(t, "?page[offset]=0&page[limit]=1", *links.Next)
	assert.Nil(t, links.Prev)

	setPagingLinks(links, "", 0, 1, 1, 0)
	assert.Equal(t, "?page[offset]=0&page[limit]=0", *links.First)
	assert.Equal(t, "?page[offset]=0&page[limit]=0", *links.Last)
	assert.Equal(t, "?page[offset]=0&page[limit]=1", *links.Next)
	assert.Nil(t, links.Prev)

	setPagingLinks(links, "", 0, 1, 1, 1)
	assert.Equal(t, "?page[offset]=0&page[limit]=0", *links.First)
	assert.Equal(t, "?page[offset]=0&page[limit]=1", *links.Last)
	assert.Equal(t, "?page[offset]=0&page[limit]=1", *links.Next)
	assert.Equal(t, "?page[offset]=0&page[limit]=1", *links.Prev)

	setPagingLinks(links, "", 0, 2, 1, 1)
	assert.Equal(t, "?page[offset]=0&page[limit]=0", *links.First)
	assert.Equal(t, "?page[offset]=0&page[limit]=1", *links.Last)
	assert.Equal(t, "?page[offset]=0&page[limit]=1", *links.Next)
	assert.Equal(t, "?page[offset]=0&page[limit]=1", *links.Prev)

	setPagingLinks(links, "", 0, 3, 4, 4)
	assert.Equal(t, "?page[offset]=0&page[limit]=3", *links.First)
	assert.Equal(t, "?page[offset]=3&page[limit]=4", *links.Last)
	assert.Equal(t, "?page[offset]=3&page[limit]=4", *links.Next)
	assert.Equal(t, "?page[offset]=0&page[limit]=3", *links.Prev)
}

func TestConvertWorkItemWithDescription(t *testing.T) {
	request := http.Request{Host: "localhost"}
	requestData := &goa.RequestData{Request: &request}
	// map[string]interface{}
	fields := map[string]interface{}{
		workitem.SystemTitle:       "title",
		workitem.SystemDescription: "description",
	}
	wi := app.WorkItem{
		Fields: fields,
	}
	wi2 := ConvertWorkItem(requestData, &wi)
	assert.Equal(t, "title", wi2.Attributes[workitem.SystemTitle])
	assert.Equal(t, "description", wi2.Attributes[workitem.SystemDescription])
}

func TestConvertWorkItemWithoutDescription(t *testing.T) {
	request := http.Request{Host: "localhost"}
	requestData := &goa.RequestData{Request: &request}
	// map[string]interface{}
	fields := map[string]interface{}{
		workitem.SystemTitle: "title",
	}
	wi := app.WorkItem{
		Fields: fields,
	}
	wi2 := ConvertWorkItem(requestData, &wi)
	assert.Equal(t, "title", wi2.Attributes[workitem.SystemTitle])
	assert.Nil(t, wi2.Attributes[workitem.SystemDescription])
}

func TestConvertJSONAPIToWorkItemWithLegacyDescription(t *testing.T) {
	appl := new(application.Application)
	attributes := map[string]interface{}{
		workitem.SystemTitle:       "title",
		workitem.SystemDescription: "description",
	}
	source := app.WorkItem2{Type: workitem.SystemBug, Attributes: attributes}
	target := &app.WorkItem{Fields: map[string]interface{}{}}
	err := ConvertJSONAPIToWorkItem(*appl, source, target)
	require.Nil(t, err)
	require.NotNil(t, target)
	require.NotNil(t, target.Fields)
	expectedDescription := rendering.NewMarkupContentFromLegacy("description")
	assert.Equal(t, expectedDescription, target.Fields[workitem.SystemDescription])
}

func TestConvertJSONAPIToWorkItemWithDescriptionContentNoMarkup(t *testing.T) {
	appl := new(application.Application)
	attributes := map[string]interface{}{
		workitem.SystemTitle:       "title",
		workitem.SystemDescription: rendering.NewMarkupContentFromLegacy("description"),
	}
	source := app.WorkItem2{Type: workitem.SystemBug, Attributes: attributes}
	target := &app.WorkItem{Fields: map[string]interface{}{}}
	err := ConvertJSONAPIToWorkItem(*appl, source, target)
	require.Nil(t, err)
	require.NotNil(t, target)
	require.NotNil(t, target.Fields)
	expectedDescription := rendering.NewMarkupContentFromLegacy("description")
	assert.Equal(t, expectedDescription, target.Fields[workitem.SystemDescription])
}

func TestConvertJSONAPIToWorkItemWithDescriptionContentAndMarkup(t *testing.T) {
	appl := new(application.Application)
	attributes := map[string]interface{}{
		workitem.SystemTitle:       "title",
		workitem.SystemDescription: rendering.NewMarkupContent("description", rendering.SystemMarkupMarkdown),
	}
	source := app.WorkItem2{Type: workitem.SystemBug, Attributes: attributes}
	target := &app.WorkItem{Fields: map[string]interface{}{}}
	err := ConvertJSONAPIToWorkItem(*appl, source, target)
	require.Nil(t, err)
	require.NotNil(t, target)
	require.NotNil(t, target.Fields)
	expectedDescription := rendering.NewMarkupContent("description", rendering.SystemMarkupMarkdown)
	assert.Equal(t, expectedDescription, target.Fields[workitem.SystemDescription])
}

func TestConvertJSONAPIToWorkItemWithTitle(t *testing.T) {
	title := "title"
	appl := new(application.Application)
	attributes := map[string]interface{}{
		workitem.SystemTitle: title,
	}
	source := app.WorkItem2{Type: workitem.SystemBug, Attributes: attributes}
	target := &app.WorkItem{Fields: map[string]interface{}{}}
	err := ConvertJSONAPIToWorkItem(*appl, source, target)
	require.Nil(t, err)
	require.NotNil(t, target)
	require.NotNil(t, target.Fields)
	assert.Equal(t, title, target.Fields[workitem.SystemTitle])
}

func TestConvertJSONAPIToWorkItemWithMissingTitle(t *testing.T) {
	// given
	appl := new(application.Application)
	attributes := map[string]interface{}{}
	source := app.WorkItem2{Type: workitem.SystemBug, Attributes: attributes}
	target := &app.WorkItem{Fields: map[string]interface{}{}}
	// when
	err := ConvertJSONAPIToWorkItem(*appl, source, target)
	// then: no error expected at this level, even though the title is missing
	require.Nil(t, err)
}

func TestConvertJSONAPIToWorkItemWithEmptyTitle(t *testing.T) {
	// given
	appl := new(application.Application)
	attributes := map[string]interface{}{
		workitem.SystemTitle: "",
	}
	source := app.WorkItem2{Type: workitem.SystemBug, Attributes: attributes}
	target := &app.WorkItem{Fields: map[string]interface{}{}}
	// when
	err := ConvertJSONAPIToWorkItem(*appl, source, target)
	// then: no error expected at this level, even though the title is missing
	require.Nil(t, err)
}
<commit_msg>fix wrong test (#888)<commit_after>package controller

import (
	"fmt"
	"os"
	"testing"

	"net/http"

	"github.com/almighty/almighty-core/app"
	"github.com/almighty/almighty-core/application"
	config "github.com/almighty/almighty-core/configuration"
	"github.com/almighty/almighty-core/migration"
	"github.com/almighty/almighty-core/models"
	"github.com/almighty/almighty-core/remoteworkitem"
	"github.com/almighty/almighty-core/rendering"
	"github.com/almighty/almighty-core/resource"
	"github.com/almighty/almighty-core/workitem"
	"github.com/goadesign/goa"
	"github.com/jinzhu/gorm"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"golang.org/x/net/context"
)

var DB *gorm.DB
var RwiScheduler *remoteworkitem.Scheduler
var configuration *config.ConfigurationData

func TestMain(m *testing.M) {
	var err error

	configuration, err = config.GetConfigurationData()
	if err != nil {
		panic(fmt.Errorf("Failed to setup the configuration: %s", err.Error()))
	}

	if _, c := os.LookupEnv(resource.Database); c != false {

		DB, err = gorm.Open("postgres", configuration.GetPostgresConfigString())

		if err != nil {
			panic("Failed to connect database: " + err.Error())
		}
		defer DB.Close()

		// Make sure the database is populated with the correct types (e.g. bug etc.)
		if configuration.GetPopulateCommonTypes() {
			if err := models.Transactional(DB, func(tx *gorm.DB) error {
				return migration.PopulateCommonTypes(context.Background(), tx, workitem.NewWorkItemTypeRepository(tx))
			}); err != nil {
				panic(err.Error())
			}

		}

		// RemoteWorkItemScheduler now available for all other test cases
		RwiScheduler = remoteworkitem.NewScheduler(DB)
	}
	os.Exit(func() int {
		c := m.Run()
		RwiScheduler.Stop()
		return c
	}())
}

func TestNewWorkitemController(t *testing.T) {
	t.Parallel()
	resource.Require(t, resource.UnitTest)
	assert.Panics(t, func() {
		NewWorkitemController(goa.New("Test service"), nil)
	})
}

func TestParseInts(t *testing.T) {
	t.Parallel()
	resource.Require(t, resource.UnitTest)

	integers, err := parseInts(nil)
	assert.Equal(t, nil, err)
	assert.Equal(t, []int{}, integers)

	str := "1, 2, foo"
	_, err = parseInts(&str)
	assert.NotNil(t, err)
}

func TestParseLimit(t *testing.T) {
	t.Parallel()
	resource.Require(t, resource.UnitTest)

	// Test parsing error in parseInts
	str := "1000, foo"
	integers, length, err := parseLimit(&str)
	assert.NotNil(t, err)
	assert.Equal(t, 0, length)
	assert.Nil(t, integers)

	// Test length = 1
	str = "1000"
	integers, length, err = parseLimit(&str)
	assert.Nil(t, err)
	assert.Equal(t, 1000, length)
	assert.Nil(t, integers)

	// Test empty string
	str = ""
	integers, length, err = parseLimit(&str)
	assert.Nil(t, err)
	assert.Equal(t, 100, length)
	assert.Nil(t, integers)
}

func TestSetPagingLinks(t *testing.T) {
	links := &app.PagingLinks{}
	setPagingLinks(links, "", 0, 0, 1, 0)
	assert.Equal(t, "?page[offset]=0&page[limit]=1", *links.First)
	assert.Equal(t, "?page[offset]=0&page[limit]=0", *links.Last)
	assert.Nil(t, links.Next)
	assert.Nil(t, links.Prev)

	setPagingLinks(links, "prefix", 0, 0, 1, 0)
	assert.Equal(t, "prefix?page[offset]=0&page[limit]=1", *links.First)
	assert.Equal(t, "prefix?page[offset]=0&page[limit]=0", *links.Last)
	assert.Nil(t, links.Next)
	assert.Nil(t, links.Prev)

	setPagingLinks(links, "", 0, 0, 1, 1)
	assert.Equal(t, "?page[offset]=0&page[limit]=1", *links.First)
	assert.Equal(t, "?page[offset]=0&page[limit]=1", *links.Last)
	assert.Equal(t, "?page[offset]=0&page[limit]=1", *links.Next)
	assert.Nil(t, links.Prev)

	setPagingLinks(links, "", 0, 1, 1, 0)
	assert.Equal(t, "?page[offset]=0&page[limit]=0", *links.First)
	assert.Equal(t, "?page[offset]=0&page[limit]=0", *links.Last)
	assert.Equal(t, "?page[offset]=0&page[limit]=1", *links.Next)
	assert.Nil(t, links.Prev)

	setPagingLinks(links, "", 0, 1, 1, 1)
	assert.Equal(t, "?page[offset]=0&page[limit]=0", *links.First)
	assert.Equal(t, "?page[offset]=0&page[limit]=1", *links.Last)
	assert.Equal(t, "?page[offset]=0&page[limit]=1", *links.Next)
	assert.Equal(t, "?page[offset]=0&page[limit]=1", *links.Prev)

	setPagingLinks(links, "", 0, 2, 1, 1)
	assert.Equal(t, "?page[offset]=0&page[limit]=0", *links.First)
	assert.Equal(t, "?page[offset]=0&page[limit]=1", *links.Last)
	assert.Equal(t, "?page[offset]=0&page[limit]=1", *links.Next)
	assert.Equal(t, "?page[offset]=0&page[limit]=1", *links.Prev)

	setPagingLinks(links, "", 0, 3, 4, 4)
	assert.Equal(t, "?page[offset]=0&page[limit]=3", *links.First)
	assert.Equal(t, "?page[offset]=3&page[limit]=4", *links.Last)
	assert.Equal(t, "?page[offset]=3&page[limit]=4", *links.Next)
	assert.Equal(t, "?page[offset]=0&page[limit]=3", *links.Prev)
}

func TestConvertWorkItemWithDescription(t *testing.T) {
	request := http.Request{Host: "localhost"}
	requestData := &goa.RequestData{Request: &request}
	// map[string]interface{}
	fields := map[string]interface{}{
		workitem.SystemTitle:       "title",
		workitem.SystemDescription: "description",
	}
	wi := app.WorkItem{
		Fields: fields,
	}
	wi2 := ConvertWorkItem(requestData, &wi)
	assert.Equal(t, "title", wi2.Attributes[workitem.SystemTitle])
	assert.Equal(t, "description", wi2.Attributes[workitem.SystemDescription])
}

func TestConvertWorkItemWithoutDescription(t *testing.T) {
	request := http.Request{Host: "localhost"}
	requestData := &goa.RequestData{Request: &request}
	// map[string]interface{}
	fields := map[string]interface{}{
		workitem.SystemTitle: "title",
	}
	wi := app.WorkItem{
		Fields: fields,
	}
	wi2 := ConvertWorkItem(requestData, &wi)
	assert.Equal(t, "title", wi2.Attributes[workitem.SystemTitle])
	assert.Nil(t, wi2.Attributes[workitem.SystemDescription])
}

func prepareWI2(attributes map[string]interface{}) app.WorkItem2 {
	return app.WorkItem2{
		Type: "workitems",
		Relationships: &app.WorkItemRelationships{
			BaseType: &app.RelationBaseType{
				Data: &app.BaseTypeData{
					Type: "workitemtypes",
					ID:   workitem.SystemBug,
				},
			},
		},
		Attributes: attributes,
	}
}

func TestConvertJSONAPIToWorkItemWithLegacyDescription(t *testing.T) {
	appl := new(application.Application)
	attributes := map[string]interface{}{
		workitem.SystemTitle:       "title",
		workitem.SystemDescription: "description",
	}
	source := prepareWI2(attributes)
	target := &app.WorkItem{Fields: map[string]interface{}{}}
	err := ConvertJSONAPIToWorkItem(*appl, source, target)
	require.Nil(t, err)
	require.NotNil(t, target)
	require.NotNil(t, target.Fields)
	expectedDescription := rendering.NewMarkupContentFromLegacy("description")
	assert.Equal(t, expectedDescription, target.Fields[workitem.SystemDescription])
}

func TestConvertJSONAPIToWorkItemWithDescriptionContentNoMarkup(t *testing.T) {
	appl := new(application.Application)
	attributes := map[string]interface{}{
		workitem.SystemTitle:       "title",
		workitem.SystemDescription: rendering.NewMarkupContentFromLegacy("description"),
	}
	source := prepareWI2(attributes)
	target := &app.WorkItem{Fields: map[string]interface{}{}}
	err := ConvertJSONAPIToWorkItem(*appl, source, target)
	require.Nil(t, err)
	require.NotNil(t, target)
	require.NotNil(t, target.Fields)
	expectedDescription := rendering.NewMarkupContentFromLegacy("description")
	assert.Equal(t, expectedDescription, target.Fields[workitem.SystemDescription])
}

func TestConvertJSONAPIToWorkItemWithDescriptionContentAndMarkup(t *testing.T) {
	appl := new(application.Application)
	attributes := map[string]interface{}{
		workitem.SystemTitle:       "title",
		workitem.SystemDescription: rendering.NewMarkupContent("description", rendering.SystemMarkupMarkdown),
	}
	source := prepareWI2(attributes)
	target := &app.WorkItem{Fields: map[string]interface{}{}}
	err := ConvertJSONAPIToWorkItem(*appl, source, target)
	require.Nil(t, err)
	require.NotNil(t, target)
	require.NotNil(t, target.Fields)
	expectedDescription := rendering.NewMarkupContent("description", rendering.SystemMarkupMarkdown)
	assert.Equal(t, expectedDescription, target.Fields[workitem.SystemDescription])
}

func TestConvertJSONAPIToWorkItemWithTitle(t *testing.T) {
	title := "title"
	appl := new(application.Application)
	attributes := map[string]interface{}{
		workitem.SystemTitle: title,
	}
	source := prepareWI2(attributes)
	target := &app.WorkItem{Fields: map[string]interface{}{}}
	err := ConvertJSONAPIToWorkItem(*appl, source, target)
	require.Nil(t, err)
	require.NotNil(t, target)
	require.NotNil(t, target.Fields)
	assert.Equal(t, title, target.Fields[workitem.SystemTitle])
}

func TestConvertJSONAPIToWorkItemWithMissingTitle(t *testing.T) {
	// given
	appl := new(application.Application)
	attributes := map[string]interface{}{}
	source := prepareWI2(attributes)
	target := &app.WorkItem{Fields: map[string]interface{}{}}
	// when
	err := ConvertJSONAPIToWorkItem(*appl, source, target)
	// then: no error expected at this level, even though the title is missing
	require.Nil(t, err)
}

func TestConvertJSONAPIToWorkItemWithEmptyTitle(t *testing.T) {
	// given
	appl := new(application.Application)
	attributes := map[string]interface{}{
		workitem.SystemTitle: "",
	}
	source := prepareWI2(attributes)
	target := &app.WorkItem{Fields: map[string]interface{}{}}
	// when
	err := ConvertJSONAPIToWorkItem(*appl, source, target)
	// then: no error expected at this level, even though the title is missing
	require.Nil(t, err)
}
<|endoftext|>