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

import (
	"bytes"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"math"
	"math/rand"
	"net"
	"net/url"
	"time"

	"golang.org/x/net/context"
	"google.golang.org/api/googleapi"
)

// A bucket that wraps another, calling its methods in a retry loop with
// randomized exponential backoff.
type retryBucket struct {
	maxSleep time.Duration
	wrapped  Bucket
}

func newRetryBucket(
	maxSleep time.Duration,
	wrapped Bucket) (b Bucket) {
	b = &retryBucket{
		maxSleep: maxSleep,
		wrapped:  wrapped,
	}

	return
}

////////////////////////////////////////////////////////////////////////
// Helpers
////////////////////////////////////////////////////////////////////////

func shouldRetry(err error) (b bool) {
	// HTTP 50x errors.
	if typed, ok := err.(*googleapi.Error); ok {
		if typed.Code >= 500 && typed.Code < 600 {
			b = true
			return
		}
	}

	// HTTP 429 errors (GCS uses these for rate limiting).
	if typed, ok := err.(*googleapi.Error); ok {
		if typed.Code == 429 {
			b = true
			return
		}
	}

	// Network errors, which tend to show up transiently when doing lots of
	// operations in parallel. For example:
	//
	//     dial tcp 74.125.203.95:443: too many open files
	//
	if _, ok := err.(*net.OpError); ok {
		b = true
		return
	}

	// The HTTP package returns ErrUnexpectedEOF in several places. This seems to
	// come up when the server terminates the connection in the middle of an
	// object read.
	if err == io.ErrUnexpectedEOF {
		b = true
		return
	}

	// The HTTP library also appears to leak EOF errors from... somewhere in its
	// guts as URL errors sometimes.
	if urlErr, ok := err.(*url.Error); ok {
		if urlErr.Err == io.EOF {
			b = true
			return
		}
	}

	// Sometimes the HTTP package helpfully encapsulates the real error in a URL
	// error.
	if urlErr, ok := err.(*url.Error); ok {
		b = shouldRetry(urlErr.Err)
		return
	}

	return
}

// Choose an appropriate delay for exponential backoff, given that we have
// already slept the given number of times for this logical request.
func chooseDelay(prevSleepCount uint) (d time.Duration) {
	const baseDelay = time.Millisecond

	// Choose a a delay in [0, 2^prevSleepCount * baseDelay).
	d = (1 << prevSleepCount) * baseDelay
	d = time.Duration(float64(d) * rand.Float64())

	return
}

// Exponential backoff for a function that might fail.
//
// This is essentially what is described in the "Best practices" section of the
// "Upload Objects" docs:
//
//     https://cloud.google.com/storage/docs/json_api/v1/how-tos/upload
//
// with the following exceptions:
//
//  *  We perform backoff for all operations.
//
//  *  The random component scales with the delay, so that the first sleep
//     cannot be as long as one second. The algorithm used matches the
//     description at http://en.wikipedia.org/wiki/Exponential_backoff.
//
//  *  We retry more types of errors; see shouldRetry above.
//
// State for total sleep time and number of previous sleeps is housed outside
// of this function to allow it to be "resumed" by multiple invocations of
// retryObjectReader.Read.
func expBackoff(
	ctx context.Context,
	desc string,
	maxSleep time.Duration,
	f func() error,
	prevSleepCount *uint,
	prevSleepDuration *time.Duration) (err error) {
	for {
		// Make an attempt. Stop if successful.
		err = f()
		if err == nil {
			return
		}

		// Do we want to retry?
		if !shouldRetry(err) {
			// Special case: don't spam up the logs for EOF, which io.Reader returns
			// in the normal course of things.
			if err != io.EOF {
				log.Printf(
					"Not retrying %s after error of type %T (%q): %#v",
					desc,
					err,
					err.Error(),
					err)
			}

			return
		}

		// Choose a a delay.
		d := chooseDelay(*prevSleepCount)
		*prevSleepCount++

		// Are we out of credit?
		if *prevSleepDuration+d > maxSleep {
			// Return the most recent error.
			return
		}

		// Sleep, returning early if cancelled.
		log.Printf(
			"Retrying %s after error of type %T (%q) in %v",
			desc,
			err,
			err,
			d)

		select {
		case <-ctx.Done():
			// On cancellation, return the last error we saw.
			return

		case <-time.After(d):
			*prevSleepDuration += d
			continue
		}
	}
}

// Like expBackoff, but assumes that we've never slept before (and won't need
// to sleep again).
func oneShotExpBackoff(
	ctx context.Context,
	desc string,
	maxSleep time.Duration,
	f func() error) (err error) {
	var prevSleepCount uint
	var prevSleepDuration time.Duration

	err = expBackoff(
		ctx,
		desc,
		maxSleep,
		f,
		&prevSleepCount,
		&prevSleepDuration)

	return
}

////////////////////////////////////////////////////////////////////////
// Read support
////////////////////////////////////////////////////////////////////////

type retryObjectReader struct {
	bucket *retryBucket

	// The context we should watch when sleeping for retries.
	ctx context.Context

	// What we are trying to read.
	name       string
	generation int64
	byteRange  ByteRange

	// nil when we start or have seen a permanent error.
	wrapped io.ReadCloser

	// If we've seen an error that we shouldn't retry for, this will be non-nil
	// and should be returned permanently.
	permanentErr error

	// The number of times we've slept so far, and the total amount of time we've
	// spent sleeping.
	sleepCount    uint
	sleepDuration time.Duration
}

// Set up the wrapped reader.
func (rc *retryObjectReader) setUpWrapped() (err error) {
	// Call through to create the reader.
	req := &ReadObjectRequest{
		Name:       rc.name,
		Generation: rc.generation,
		Range:      &rc.byteRange,
	}

	wrapped, err := rc.bucket.wrapped.NewReader(rc.ctx, req)
	if err != nil {
		return
	}

	rc.wrapped = wrapped
	return
}

// Set up the wrapped reader if necessary, and make one attempt to read through
// it.
//
// Clears the wrapped reader on error.
func (rc *retryObjectReader) readOnce(p []byte) (n int, err error) {
	// Set up the wrapped reader if it's not already around.
	if rc.wrapped == nil {
		err = rc.setUpWrapped()
		if err != nil {
			return
		}
	}

	// Attempt to read from it.
	n, err = rc.wrapped.Read(p)
	if err != nil {
		rc.wrapped.Close()
		rc.wrapped = nil
		return
	}

	return
}

// Invariant: we never return an error from this function unless we've given up
// on retrying. In particular, we won't return a short read because the wrapped
// reader returned a short read and an error.
func (rc *retryObjectReader) Read(p []byte) (n int, err error) {
	// Whatever we do, accumulate the bytes that we're returning to the user.
	defer func() {
		if n < 0 {
			panic(fmt.Sprintf("Negative byte count: %d", n))
		}

		rc.byteRange.Start += uint64(n)
	}()

	// If we've already decided on a permanent error, return that.
	if rc.permanentErr != nil {
		err = rc.permanentErr
		return
	}

	// If we let an error escape below, it must be a permanent one.
	defer func() {
		if err != nil {
			rc.permanentErr = err
		}
	}()

	// We will repeatedly make single attempts until we get a successful request.
	// Don't forget to accumulate the result each time.
	tryOnce := func() (err error) {
		var bytesRead int
		bytesRead, err = rc.readOnce(p)
		n += bytesRead
		p = p[bytesRead:]

		return
	}

	err = expBackoff(
		rc.ctx,
		fmt.Sprintf("Read(%q, %d)", rc.name, rc.generation),
		rc.bucket.maxSleep,
		tryOnce,
		&rc.sleepCount,
		&rc.sleepDuration)

	return
}

func (rc *retryObjectReader) Close() (err error) {
	// If we don't have a wrapped reader, there is nothing useful that we can or
	// need to do here.
	if rc.wrapped == nil {
		return
	}

	// Call through.
	err = rc.wrapped.Close()

	return
}

////////////////////////////////////////////////////////////////////////
// Public interface
////////////////////////////////////////////////////////////////////////

func (rb *retryBucket) Name() (name string) {
	name = rb.wrapped.Name()
	return
}

func (rb *retryBucket) NewReader(
	ctx context.Context,
	req *ReadObjectRequest) (rc io.ReadCloser, err error) {
	// If the user specified the latest generation, we need to figure out what
	// that is so that we can create a reader that knows how to keep a stable
	// generation despite retrying repeatedly.
	var generation int64 = req.Generation
	var sleepCount uint
	var sleepDuration time.Duration

	if generation == 0 {
		findGeneration := func() (err error) {
			o, err := rb.wrapped.StatObject(
				ctx,
				&StatObjectRequest{
					Name: req.Name,
				})

			if err != nil {
				return
			}

			generation = o.Generation
			return
		}

		err = expBackoff(
			ctx,
			fmt.Sprintf("FindLatestGeneration(%q)", req.Name),
			rb.maxSleep,
			findGeneration,
			&sleepCount,
			&sleepDuration)

		if err != nil {
			return
		}
	}

	// Choose an appropriate byte range.
	byteRange := ByteRange{0, math.MaxUint64}
	if req.Range != nil {
		byteRange = *req.Range
	}

	// Now that we know what generation we're looking for, return an appropriate
	// reader that knows how to retry when the connection fails. Make sure to
	// inherit the time spent sleeping above.
	rc = &retryObjectReader{
		bucket: rb,
		ctx:    ctx,

		name:       req.Name,
		generation: generation,
		byteRange:  byteRange,

		sleepCount:    sleepCount,
		sleepDuration: sleepDuration,
	}

	return
}

func (rb *retryBucket) CreateObject(
	ctx context.Context,
	req *CreateObjectRequest) (o *Object, err error) {
	// We can't simply replay the request multiple times, because the first
	// attempt might exhaust some of the req.Contents reader, leaving missing
	// contents for the second attempt.
	//
	// So, copy out all contents and create a modified request that serves from
	// memory.
	contents, err := ioutil.ReadAll(req.Contents)
	if err != nil {
		err = fmt.Errorf("ioutil.ReadAll: %v", err)
		return
	}

	reqCopy := *req
	reqCopy.Contents = bytes.NewReader(contents)

	// Call through with that request.
	err = oneShotExpBackoff(
		ctx,
		fmt.Sprintf("CreateObject(%q)", req.Name),
		rb.maxSleep,
		func() (err error) {
			o, err = rb.wrapped.CreateObject(ctx, &reqCopy)
			return
		})

	return
}

func (rb *retryBucket) CopyObject(
	ctx context.Context,
	req *CopyObjectRequest) (o *Object, err error) {
	err = oneShotExpBackoff(
		ctx,
		fmt.Sprintf("CopyObject(%q, %q)", req.SrcName, req.DstName),
		rb.maxSleep,
		func() (err error) {
			o, err = rb.wrapped.CopyObject(ctx, req)
			return
		})

	return
}

func (rb *retryBucket) ComposeObjects(
	ctx context.Context,
	req *ComposeObjectsRequest) (o *Object, err error) {
	err = oneShotExpBackoff(
		ctx,
		fmt.Sprintf("ComposeObjects(%q)", req.DstName),
		rb.maxSleep,
		func() (err error) {
			o, err = rb.wrapped.ComposeObjects(ctx, req)
			return
		})

	return
}

func (rb *retryBucket) StatObject(
	ctx context.Context,
	req *StatObjectRequest) (o *Object, err error) {
	err = oneShotExpBackoff(
		ctx,
		fmt.Sprintf("StatObject(%q)", req.Name),
		rb.maxSleep,
		func() (err error) {
			o, err = rb.wrapped.StatObject(ctx, req)
			return
		})

	return
}

func (rb *retryBucket) ListObjects(
	ctx context.Context,
	req *ListObjectsRequest) (listing *Listing, err error) {
	err = oneShotExpBackoff(
		ctx,
		fmt.Sprintf("ListObjects(%q)", req.Prefix),
		rb.maxSleep,
		func() (err error) {
			listing, err = rb.wrapped.ListObjects(ctx, req)
			return
		})
	return
}

func (rb *retryBucket) UpdateObject(
	ctx context.Context,
	req *UpdateObjectRequest) (o *Object, err error) {
	err = oneShotExpBackoff(
		ctx,
		fmt.Sprintf("UpdateObject(%q)", req.Name),
		rb.maxSleep,
		func() (err error) {
			o, err = rb.wrapped.UpdateObject(ctx, req)
			return
		})

	return
}

func (rb *retryBucket) DeleteObject(
	ctx context.Context,
	req *DeleteObjectRequest) (err error) {
	err = oneShotExpBackoff(
		ctx,
		fmt.Sprintf("DeleteObject(%q)", req.Name),
		rb.maxSleep,
		func() (err error) {
			err = rb.wrapped.DeleteObject(ctx, req)
			return
		})

	return
}
<commit_msg>Fixed the broken behavior.<commit_after>// Copyright 2015 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 gcs

import (
	"bytes"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"math"
	"math/rand"
	"net"
	"net/url"
	"time"

	"golang.org/x/net/context"
	"google.golang.org/api/googleapi"
)

// A bucket that wraps another, calling its methods in a retry loop with
// randomized exponential backoff.
type retryBucket struct {
	maxSleep time.Duration
	wrapped  Bucket
}

func newRetryBucket(
	maxSleep time.Duration,
	wrapped Bucket) (b Bucket) {
	b = &retryBucket{
		maxSleep: maxSleep,
		wrapped:  wrapped,
	}

	return
}

////////////////////////////////////////////////////////////////////////
// Helpers
////////////////////////////////////////////////////////////////////////

func shouldRetry(err error) (b bool) {
	// HTTP 50x errors.
	if typed, ok := err.(*googleapi.Error); ok {
		if typed.Code >= 500 && typed.Code < 600 {
			b = true
			return
		}
	}

	// HTTP 429 errors (GCS uses these for rate limiting).
	if typed, ok := err.(*googleapi.Error); ok {
		if typed.Code == 429 {
			b = true
			return
		}
	}

	// Network errors, which tend to show up transiently when doing lots of
	// operations in parallel. For example:
	//
	//     dial tcp 74.125.203.95:443: too many open files
	//
	if _, ok := err.(*net.OpError); ok {
		b = true
		return
	}

	// The HTTP package returns ErrUnexpectedEOF in several places. This seems to
	// come up when the server terminates the connection in the middle of an
	// object read.
	if err == io.ErrUnexpectedEOF {
		b = true
		return
	}

	// The HTTP library also appears to leak EOF errors from... somewhere in its
	// guts as URL errors sometimes.
	if urlErr, ok := err.(*url.Error); ok {
		if urlErr.Err == io.EOF {
			b = true
			return
		}
	}

	// Sometimes the HTTP package helpfully encapsulates the real error in a URL
	// error.
	if urlErr, ok := err.(*url.Error); ok {
		b = shouldRetry(urlErr.Err)
		return
	}

	return
}

// Choose an appropriate delay for exponential backoff, given that we have
// already slept the given number of times for this logical request.
func chooseDelay(prevSleepCount uint) (d time.Duration) {
	const baseDelay = time.Millisecond

	// Choose a a delay in [0, 2^prevSleepCount * baseDelay).
	d = (1 << prevSleepCount) * baseDelay
	d = time.Duration(float64(d) * rand.Float64())

	return
}

// Exponential backoff for a function that might fail.
//
// This is essentially what is described in the "Best practices" section of the
// "Upload Objects" docs:
//
//     https://cloud.google.com/storage/docs/json_api/v1/how-tos/upload
//
// with the following exceptions:
//
//  *  We perform backoff for all operations.
//
//  *  The random component scales with the delay, so that the first sleep
//     cannot be as long as one second. The algorithm used matches the
//     description at http://en.wikipedia.org/wiki/Exponential_backoff.
//
//  *  We retry more types of errors; see shouldRetry above.
//
// State for total sleep time and number of previous sleeps is housed outside
// of this function to allow it to be "resumed" by multiple invocations of
// retryObjectReader.Read.
func expBackoff(
	ctx context.Context,
	desc string,
	maxSleep time.Duration,
	f func() error,
	prevSleepCount *uint,
	prevSleepDuration *time.Duration) (err error) {
	for {
		// Make an attempt. Stop if successful.
		err = f()
		if err == nil {
			return
		}

		// Do we want to retry?
		if !shouldRetry(err) {
			// Special case: don't spam up the logs for EOF, which io.Reader returns
			// in the normal course of things.
			if err != io.EOF {
				log.Printf(
					"Not retrying %s after error of type %T (%q): %#v",
					desc,
					err,
					err.Error(),
					err)
			}

			return
		}

		// Choose a a delay.
		d := chooseDelay(*prevSleepCount)
		*prevSleepCount++

		// Are we out of credit?
		if *prevSleepDuration+d > maxSleep {
			// Return the most recent error.
			return
		}

		// Sleep, returning early if cancelled.
		log.Printf(
			"Retrying %s after error of type %T (%q) in %v",
			desc,
			err,
			err,
			d)

		select {
		case <-ctx.Done():
			// On cancellation, return the last error we saw.
			return

		case <-time.After(d):
			*prevSleepDuration += d
			continue
		}
	}
}

// Like expBackoff, but assumes that we've never slept before (and won't need
// to sleep again).
func oneShotExpBackoff(
	ctx context.Context,
	desc string,
	maxSleep time.Duration,
	f func() error) (err error) {
	var prevSleepCount uint
	var prevSleepDuration time.Duration

	err = expBackoff(
		ctx,
		desc,
		maxSleep,
		f,
		&prevSleepCount,
		&prevSleepDuration)

	return
}

////////////////////////////////////////////////////////////////////////
// Read support
////////////////////////////////////////////////////////////////////////

type retryObjectReader struct {
	bucket *retryBucket

	// The context we should watch when sleeping for retries.
	ctx context.Context

	// What we are trying to read.
	name       string
	generation int64
	byteRange  ByteRange

	// nil when we start or have seen a permanent error.
	wrapped io.ReadCloser

	// If we've seen an error that we shouldn't retry for, this will be non-nil
	// and should be returned permanently.
	permanentErr error

	// The number of times we've slept so far, and the total amount of time we've
	// spent sleeping.
	sleepCount    uint
	sleepDuration time.Duration
}

// Set up the wrapped reader.
func (rc *retryObjectReader) setUpWrapped() (err error) {
	// Call through to create the reader.
	req := &ReadObjectRequest{
		Name:       rc.name,
		Generation: rc.generation,
		Range:      &rc.byteRange,
	}

	wrapped, err := rc.bucket.wrapped.NewReader(rc.ctx, req)
	if err != nil {
		return
	}

	rc.wrapped = wrapped
	return
}

// Set up the wrapped reader if necessary, and make one attempt to read through
// it.
//
// Clears the wrapped reader on error.
func (rc *retryObjectReader) readOnce(p []byte) (n int, err error) {
	// Set up the wrapped reader if it's not already around.
	if rc.wrapped == nil {
		err = rc.setUpWrapped()
		if err != nil {
			return
		}
	}

	// Attempt to read from it.
	n, err = rc.wrapped.Read(p)
	if err != nil {
		rc.wrapped.Close()
		rc.wrapped = nil
		return
	}

	return
}

// Invariant: we never return an error from this function unless we've given up
// on retrying. In particular, we won't return a short read because the wrapped
// reader returned a short read and an error.
func (rc *retryObjectReader) Read(p []byte) (n int, err error) {
	// Whatever we do, accumulate the bytes that we're returning to the user.
	defer func() {
		if n < 0 {
			panic(fmt.Sprintf("Negative byte count: %d", n))
		}

		rc.byteRange.Start += uint64(n)
	}()

	// If we've already decided on a permanent error, return that.
	if rc.permanentErr != nil {
		err = rc.permanentErr
		return
	}

	// If we let an error escape below, it must be a permanent one.
	defer func() {
		if err != nil {
			rc.permanentErr = err
		}
	}()

	// We will repeatedly make single attempts until we get a successful request.
	// Don't forget to accumulate the result each time.
	tryOnce := func() (err error) {
		var bytesRead int
		bytesRead, err = rc.readOnce(p)
		n += bytesRead
		p = p[bytesRead:]

		return
	}

	err = expBackoff(
		rc.ctx,
		fmt.Sprintf("Read(%q, %d)", rc.name, rc.generation),
		rc.bucket.maxSleep,
		tryOnce,
		&rc.sleepCount,
		&rc.sleepDuration)

	return
}

func (rc *retryObjectReader) Close() (err error) {
	// If we don't have a wrapped reader, there is nothing useful that we can or
	// need to do here.
	if rc.wrapped == nil {
		return
	}

	// Call through.
	err = rc.wrapped.Close()

	return
}

////////////////////////////////////////////////////////////////////////
// Public interface
////////////////////////////////////////////////////////////////////////

func (rb *retryBucket) Name() (name string) {
	name = rb.wrapped.Name()
	return
}

func (rb *retryBucket) NewReader(
	ctx context.Context,
	req *ReadObjectRequest) (rc io.ReadCloser, err error) {
	// If the user specified the latest generation, we need to figure out what
	// that is so that we can create a reader that knows how to keep a stable
	// generation despite retrying repeatedly.
	var generation int64 = req.Generation
	var sleepCount uint
	var sleepDuration time.Duration

	if generation == 0 {
		findGeneration := func() (err error) {
			o, err := rb.wrapped.StatObject(
				ctx,
				&StatObjectRequest{
					Name: req.Name,
				})

			if err != nil {
				return
			}

			generation = o.Generation
			return
		}

		err = expBackoff(
			ctx,
			fmt.Sprintf("FindLatestGeneration(%q)", req.Name),
			rb.maxSleep,
			findGeneration,
			&sleepCount,
			&sleepDuration)

		if err != nil {
			return
		}
	}

	// Choose an appropriate byte range.
	byteRange := ByteRange{0, math.MaxUint64}
	if req.Range != nil {
		byteRange = *req.Range
	}

	// Now that we know what generation we're looking for, return an appropriate
	// reader that knows how to retry when the connection fails. Make sure to
	// inherit the time spent sleeping above.
	rc = &retryObjectReader{
		bucket: rb,
		ctx:    ctx,

		name:       req.Name,
		generation: generation,
		byteRange:  byteRange,

		sleepCount:    sleepCount,
		sleepDuration: sleepDuration,
	}

	return
}

func (rb *retryBucket) CreateObject(
	ctx context.Context,
	req *CreateObjectRequest) (o *Object, err error) {
	// We can't simply replay the request multiple times, because the first
	// attempt might exhaust some of the req.Contents reader, leaving missing
	// contents for the second attempt.
	//
	// So, copy out all contents and create a copy of the request that we will
	// modify to serve from memory for each call.
	contents, err := ioutil.ReadAll(req.Contents)
	if err != nil {
		err = fmt.Errorf("ioutil.ReadAll: %v", err)
		return
	}

	reqCopy := *req

	// Call through with that request.
	err = oneShotExpBackoff(
		ctx,
		fmt.Sprintf("CreateObject(%q)", req.Name),
		rb.maxSleep,
		func() (err error) {
			reqCopy.Contents = bytes.NewReader(contents)
			o, err = rb.wrapped.CreateObject(ctx, &reqCopy)
			return
		})

	return
}

func (rb *retryBucket) CopyObject(
	ctx context.Context,
	req *CopyObjectRequest) (o *Object, err error) {
	err = oneShotExpBackoff(
		ctx,
		fmt.Sprintf("CopyObject(%q, %q)", req.SrcName, req.DstName),
		rb.maxSleep,
		func() (err error) {
			o, err = rb.wrapped.CopyObject(ctx, req)
			return
		})

	return
}

func (rb *retryBucket) ComposeObjects(
	ctx context.Context,
	req *ComposeObjectsRequest) (o *Object, err error) {
	err = oneShotExpBackoff(
		ctx,
		fmt.Sprintf("ComposeObjects(%q)", req.DstName),
		rb.maxSleep,
		func() (err error) {
			o, err = rb.wrapped.ComposeObjects(ctx, req)
			return
		})

	return
}

func (rb *retryBucket) StatObject(
	ctx context.Context,
	req *StatObjectRequest) (o *Object, err error) {
	err = oneShotExpBackoff(
		ctx,
		fmt.Sprintf("StatObject(%q)", req.Name),
		rb.maxSleep,
		func() (err error) {
			o, err = rb.wrapped.StatObject(ctx, req)
			return
		})

	return
}

func (rb *retryBucket) ListObjects(
	ctx context.Context,
	req *ListObjectsRequest) (listing *Listing, err error) {
	err = oneShotExpBackoff(
		ctx,
		fmt.Sprintf("ListObjects(%q)", req.Prefix),
		rb.maxSleep,
		func() (err error) {
			listing, err = rb.wrapped.ListObjects(ctx, req)
			return
		})
	return
}

func (rb *retryBucket) UpdateObject(
	ctx context.Context,
	req *UpdateObjectRequest) (o *Object, err error) {
	err = oneShotExpBackoff(
		ctx,
		fmt.Sprintf("UpdateObject(%q)", req.Name),
		rb.maxSleep,
		func() (err error) {
			o, err = rb.wrapped.UpdateObject(ctx, req)
			return
		})

	return
}

func (rb *retryBucket) DeleteObject(
	ctx context.Context,
	req *DeleteObjectRequest) (err error) {
	err = oneShotExpBackoff(
		ctx,
		fmt.Sprintf("DeleteObject(%q)", req.Name),
		rb.maxSleep,
		func() (err error) {
			err = rb.wrapped.DeleteObject(ctx, req)
			return
		})

	return
}
<|endoftext|>


<commit_before>package node

import (
	"bytes"
	"encoding/json"
	"errors"
	"net/http"
	"net/http/httptest"
	"os"
	"testing"

	"github.com/gin-gonic/gin"

	"github.com/gansoi/gansoi/boltdb"
	"github.com/gansoi/gansoi/database"
)

var (
	db *boltdb.BoltStore
)

const (
	testDBPath = "/dev/shm/gansoi-test.db"
)

type (
	TestDb struct {
		*boltdb.BoltStore
	}

	data struct {
		database.Object `storm:"inline"`
		A               string
	}

	failDB struct {
		err         error
		SaveError   error
		OneError    error
		AllError    error
		DeleteError error
	}
)

func (d *data) Validate(db database.Database) error {
	if d.A == "" {
		return errors.New("A cannot be empty")
	}

	return nil
}

func (f *failDB) Save(data interface{}) error {
	if f.SaveError != nil {
		return f.SaveError
	}

	return f.err
}

func (f *failDB) One(fieldName string, value interface{}, to interface{}) error {
	if f.OneError != nil {
		return f.OneError
	}

	return f.err
}

func (f *failDB) All(to interface{}, limit int, skip int, reverse bool) error {
	if f.AllError != nil {
		return f.AllError
	}

	return f.err
}

func (f *failDB) Delete(data interface{}) error {
	if f.DeleteError != nil {
		return f.DeleteError
	}

	return f.err
}

func (f *failDB) RegisterListener(listener database.Listener) {
}

var _ database.Validator = (*data)(nil)

func init() {
	database.RegisterType(data{})

	gin.SetMode(gin.ReleaseMode)
}

func newTestDb() *TestDb {
	var err error
	db, err = boltdb.NewBoltStore(testDBPath)
	if err != nil {
		panic(err.Error())
	}

	return &TestDb{
		BoltStore: db,
	}
}

func (d *TestDb) clean() {
	err := d.Close()
	if err != nil {
		panic(err.Error())
	}
	err = os.Remove("/dev/shm/gansoi-test.db")
	if err != nil {
		panic(err.Error())
	}
}

func TestNewRestAPI(t *testing.T) {
	db := newTestDb()
	defer db.clean()

	r := NewRestAPI(data{}, db)

	if r == nil {
		t.Fatalf("NewRestAPI() returned nil")
	}
}

func request(db database.Database, method string, URI string, body []byte) *httptest.ResponseRecorder {
	r := NewRestAPI(data{}, db)
	router := gin.New()
	router.Use(gin.ErrorLogger())
	r.Router(router.Group("/"))

	reader := bytes.NewReader(body)
	req, _ := http.NewRequest(method, URI, reader)
	resp := httptest.NewRecorder()
	router.ServeHTTP(resp, req)

	return resp
}

func TestRestApiDBFail(t *testing.T) {
	db := &failDB{err: errors.New("Fail!")}
	d := &data{A: "hejsa"}

	body, _ := json.Marshal(d)
	resp := request(db, "POST", "/", body)

	if resp.Code != 500 {
		t.Fatalf("POST / returned unexpected status code: %d", resp.Code)
	}

	resp = request(db, "GET", "/", nil)
	if resp.Code != 500 {
		t.Fatalf("GET / returned unexpected status code: %d", resp.Code)
	}

	resp = request(db, "PUT", "/id1", body)
	if resp.Code != 500 {
		t.Fatalf("PUT /id1 returned unexpected status code: %d", resp.Code)
	}

	resp = request(db, "DELETE", "/id1", nil)
	if resp.Code != 404 {
		t.Fatalf("DELETE /id1 returned unexpected status code: %d", resp.Code)
	}

	db.err = nil
	db.DeleteError = errors.New("Fail!")
	resp = request(db, "DELETE", "/id1", nil)
	if resp.Code != 500 {
		t.Fatalf("DELETE /id1 returned unexpected status code: %d", resp.Code)
	}
}

func TestRestApiCreateFailedValidation(t *testing.T) {
	db := newTestDb()
	defer db.clean()

	d := &data{}

	body, _ := json.Marshal(d)
	resp := request(db, "POST", "/", body)

	if resp.Code != 400 {
		t.Fatalf("POST / returned unexpected status code: %d", resp.Code)
	}

	if resp.Body.String() != `{"error":"A cannot be empty"}` {
		t.Fatalf("create returned unexpected body. Got '%s'", resp.Body.String())
	}
}

func TestRestApiCreateFailedJSON(t *testing.T) {
	db := newTestDb()
	defer db.clean()

	body := []byte("this is not JSON")
	resp := request(db, "POST", "/", body)

	if resp.Code != 400 {
		t.Fatalf("POST / returned unexpected status code: %d", resp.Code)
	}

	if resp.Body.String() == "" {
		t.Fatalf("create returned empty body")
	}
}

func TestRestApiCreate(t *testing.T) {
	db := newTestDb()
	defer db.clean()

	d := &data{A: "hejsa"}

	body, _ := json.Marshal(d)
	resp := request(db, "POST", "/", body)

	if resp.Code != 202 {
		t.Fatalf("POST / returned unexpected status code: %d", resp.Code)
	}

	if resp.Body.String() == "" {
		t.Fatalf("create returned empty body")
	}
}

func TestRestAPIList0(t *testing.T) {
	db := newTestDb()
	defer db.clean()

	resp := request(db, "GET", "/", nil)

	if resp.Code != 200 {
		t.Fatalf("GET / returned unexpected status code: %d (Body: %s)", resp.Code, resp.Body.String())
	}

	var list []data

	err := json.Unmarshal(resp.Body.Bytes(), &list)
	if err != nil {
		t.Fatal("Get / returned invalid JSON")
	}

	if len(list) != 0 {
		t.Fatalf("GET / returned more than zero elements")
	}
}

func TestRestAPIDeleteFail(t *testing.T) {
	db := newTestDb()
	defer db.clean()

	resp := request(db, "DELETE", "/id-that-doesnt-exist", nil)

	if resp.Code != 404 {
		t.Fatalf("GET / returned unexpected status code: %d (Body: %s)", resp.Code, resp.Body.String())
	}
}

func TestRestAPIDelete(t *testing.T) {
	db := newTestDb()
	defer db.clean()

	d := &data{A: "hejsa"}
	db.Save(d)

	resp := request(db, "DELETE", "/"+d.ID, nil)
	if resp.Code != 202 {
		t.Fatalf("GET / returned unexpected status code: %d (Body: %s)", resp.Code, resp.Body.String())
	}
}
<commit_msg>Fixed TestRestApiCreateFailedValidation() to support different Go json encoders.<commit_after>package node

import (
	"bytes"
	"encoding/json"
	"errors"
	"net/http"
	"net/http/httptest"
	"os"
	"strings"
	"testing"

	"github.com/gin-gonic/gin"

	"github.com/gansoi/gansoi/boltdb"
	"github.com/gansoi/gansoi/database"
)

var (
	db *boltdb.BoltStore
)

const (
	testDBPath = "/dev/shm/gansoi-test.db"
)

type (
	TestDb struct {
		*boltdb.BoltStore
	}

	data struct {
		database.Object `storm:"inline"`
		A               string
	}

	failDB struct {
		err         error
		SaveError   error
		OneError    error
		AllError    error
		DeleteError error
	}
)

func (d *data) Validate(db database.Database) error {
	if d.A == "" {
		return errors.New("A cannot be empty")
	}

	return nil
}

func (f *failDB) Save(data interface{}) error {
	if f.SaveError != nil {
		return f.SaveError
	}

	return f.err
}

func (f *failDB) One(fieldName string, value interface{}, to interface{}) error {
	if f.OneError != nil {
		return f.OneError
	}

	return f.err
}

func (f *failDB) All(to interface{}, limit int, skip int, reverse bool) error {
	if f.AllError != nil {
		return f.AllError
	}

	return f.err
}

func (f *failDB) Delete(data interface{}) error {
	if f.DeleteError != nil {
		return f.DeleteError
	}

	return f.err
}

func (f *failDB) RegisterListener(listener database.Listener) {
}

var _ database.Validator = (*data)(nil)

func init() {
	database.RegisterType(data{})

	gin.SetMode(gin.ReleaseMode)
}

func newTestDb() *TestDb {
	var err error
	db, err = boltdb.NewBoltStore(testDBPath)
	if err != nil {
		panic(err.Error())
	}

	return &TestDb{
		BoltStore: db,
	}
}

func (d *TestDb) clean() {
	err := d.Close()
	if err != nil {
		panic(err.Error())
	}
	err = os.Remove("/dev/shm/gansoi-test.db")
	if err != nil {
		panic(err.Error())
	}
}

func TestNewRestAPI(t *testing.T) {
	db := newTestDb()
	defer db.clean()

	r := NewRestAPI(data{}, db)

	if r == nil {
		t.Fatalf("NewRestAPI() returned nil")
	}
}

func request(db database.Database, method string, URI string, body []byte) *httptest.ResponseRecorder {
	r := NewRestAPI(data{}, db)
	router := gin.New()
	router.Use(gin.ErrorLogger())
	r.Router(router.Group("/"))

	reader := bytes.NewReader(body)
	req, _ := http.NewRequest(method, URI, reader)
	resp := httptest.NewRecorder()
	router.ServeHTTP(resp, req)

	return resp
}

func TestRestApiDBFail(t *testing.T) {
	db := &failDB{err: errors.New("Fail!")}
	d := &data{A: "hejsa"}

	body, _ := json.Marshal(d)
	resp := request(db, "POST", "/", body)

	if resp.Code != 500 {
		t.Fatalf("POST / returned unexpected status code: %d", resp.Code)
	}

	resp = request(db, "GET", "/", nil)
	if resp.Code != 500 {
		t.Fatalf("GET / returned unexpected status code: %d", resp.Code)
	}

	resp = request(db, "PUT", "/id1", body)
	if resp.Code != 500 {
		t.Fatalf("PUT /id1 returned unexpected status code: %d", resp.Code)
	}

	resp = request(db, "DELETE", "/id1", nil)
	if resp.Code != 404 {
		t.Fatalf("DELETE /id1 returned unexpected status code: %d", resp.Code)
	}

	db.err = nil
	db.DeleteError = errors.New("Fail!")
	resp = request(db, "DELETE", "/id1", nil)
	if resp.Code != 500 {
		t.Fatalf("DELETE /id1 returned unexpected status code: %d", resp.Code)
	}
}

func TestRestApiCreateFailedValidation(t *testing.T) {
	db := newTestDb()
	defer db.clean()

	d := &data{}

	body, _ := json.Marshal(d)
	resp := request(db, "POST", "/", body)

	if resp.Code != 400 {
		t.Fatalf("POST / returned unexpected status code: %d", resp.Code)
	}

	b := strings.TrimSpace(resp.Body.String())

	if b != `{"error":"A cannot be empty"}` {
		t.Fatalf("create returned unexpected body. Got '%s'", resp.Body.String())
	}
}

func TestRestApiCreateFailedJSON(t *testing.T) {
	db := newTestDb()
	defer db.clean()

	body := []byte("this is not JSON")
	resp := request(db, "POST", "/", body)

	if resp.Code != 400 {
		t.Fatalf("POST / returned unexpected status code: %d", resp.Code)
	}

	if resp.Body.String() == "" {
		t.Fatalf("create returned empty body")
	}
}

func TestRestApiCreate(t *testing.T) {
	db := newTestDb()
	defer db.clean()

	d := &data{A: "hejsa"}

	body, _ := json.Marshal(d)
	resp := request(db, "POST", "/", body)

	if resp.Code != 202 {
		t.Fatalf("POST / returned unexpected status code: %d", resp.Code)
	}

	if resp.Body.String() == "" {
		t.Fatalf("create returned empty body")
	}
}

func TestRestAPIList0(t *testing.T) {
	db := newTestDb()
	defer db.clean()

	resp := request(db, "GET", "/", nil)

	if resp.Code != 200 {
		t.Fatalf("GET / returned unexpected status code: %d (Body: %s)", resp.Code, resp.Body.String())
	}

	var list []data

	err := json.Unmarshal(resp.Body.Bytes(), &list)
	if err != nil {
		t.Fatal("Get / returned invalid JSON")
	}

	if len(list) != 0 {
		t.Fatalf("GET / returned more than zero elements")
	}
}

func TestRestAPIDeleteFail(t *testing.T) {
	db := newTestDb()
	defer db.clean()

	resp := request(db, "DELETE", "/id-that-doesnt-exist", nil)

	if resp.Code != 404 {
		t.Fatalf("GET / returned unexpected status code: %d (Body: %s)", resp.Code, resp.Body.String())
	}
}

func TestRestAPIDelete(t *testing.T) {
	db := newTestDb()
	defer db.clean()

	d := &data{A: "hejsa"}
	db.Save(d)

	resp := request(db, "DELETE", "/"+d.ID, nil)
	if resp.Code != 202 {
		t.Fatalf("GET / returned unexpected status code: %d (Body: %s)", resp.Code, resp.Body.String())
	}
}
<|endoftext|>


<commit_before>package acceptance_test

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net"
	"net/http"
	"os"
	"os/exec"
	"time"

	"github.com/appc/cni/pkg/types"
	"github.com/cloudfoundry-incubator/ducati-daemon/lib/namespace"
	"github.com/cloudfoundry-incubator/ducati-daemon/models"
	"github.com/nu7hatch/gouuid"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	"github.com/onsi/gomega/gexec"
	"github.com/vishvananda/netlink"    //only linux
	"github.com/vishvananda/netlink/nl" //only linux
)

var _ = Describe("Networks", func() {
	var (
		session     *gexec.Session
		address     string
		networkID   string
		containerID string

		sandboxRepo        namespace.Repository
		containerNamespace namespace.Namespace
	)

	BeforeEach(func() {
		address = fmt.Sprintf("127.0.0.1:%d", 4001+GinkgoParallelNode())
		sandboxRepoDir, err := ioutil.TempDir("", "sandbox")
		Expect(err).NotTo(HaveOccurred())

		sandboxRepo, err = namespace.NewRepository(sandboxRepoDir)
		Expect(err).NotTo(HaveOccurred())

		containerRepoDir, err := ioutil.TempDir("", "containers")
		Expect(err).NotTo(HaveOccurred())

		containerRepo, err := namespace.NewRepository(containerRepoDir)
		Expect(err).NotTo(HaveOccurred())

		guid, err := uuid.NewV4()
		Expect(err).NotTo(HaveOccurred())

		containerNamespace, err = containerRepo.Create(guid.String())
		Expect(err).NotTo(HaveOccurred())

		Expect(err).NotTo(HaveOccurred())

		ducatiCmd := exec.Command(
			ducatidPath,
			"-listenAddr", address,
			"-overlayNetwork", "192.168.0.0/16",
			"-localSubnet", "192.168.99.0/24",
			"-databaseURL", testDatabase.URL(),
			"-sandboxRepoDir", sandboxRepoDir,
		)
		session, err = gexec.Start(ducatiCmd, GinkgoWriter, GinkgoWriter)
		Expect(err).NotTo(HaveOccurred())

		networkID = "some-network-id"
		containerID = "some-container-id"
	})

	AfterEach(func() {
		session.Kill()
		Eventually(session).Should(gexec.Exit())
		containerNamespace.Destroy()
	})

	It("should boot and gracefully terminate", func() {
		Consistently(session).ShouldNot(gexec.Exit())

		session.Interrupt()
		Eventually(session, 3*time.Second).Should(gexec.Exit(0))
	})

	var serverIsAvailable = func() error {
		_, err := net.Dial("tcp", address)
		return err
	}

	Describe("POST /networks/:network_id/:container_id", func() {
		var (
			createURL  string
			payload    []byte
			ipamResult types.Result
		)

		BeforeEach(func() {
			Eventually(serverIsAvailable).Should(Succeed())

			By("generating config and creating the request")
			ipamResult = types.Result{
				IP4: &types.IPConfig{
					IP: net.IPNet{
						IP:   net.ParseIP("192.168.100.2"),
						Mask: net.CIDRMask(24, 32),
					},
					Gateway: net.ParseIP("192.168.100.1"),
				},
			}

			var err error
			payload, err = json.Marshal(models.NetworksSetupContainerPayload{
				Args:               "FOO=BAR;ABC=123",
				ContainerNamespace: containerNamespace.Path(),
				InterfaceName:      "vx-eth0",
				VNI:                99,
				IPAM:               ipamResult,
			})
			Expect(err).NotTo(HaveOccurred())

			createURL = fmt.Sprintf("http://%s/networks/%s/%s", address, networkID, containerID)

		})

		It("should respond to POST /networks/:network_id/:container_id", func() {
			req, err := http.NewRequest("POST", createURL, bytes.NewReader(payload))
			Expect(err).NotTo(HaveOccurred())

			By("creating the container")
			resp, err := http.DefaultClient.Do(req)
			Expect(err).NotTo(HaveOccurred())
			defer resp.Body.Close()

			Expect(resp.StatusCode).To(Equal(http.StatusCreated))

			sandboxNS, err := sandboxRepo.Get("vni-99")
			Expect(err).NotTo(HaveOccurred())
			defer sandboxNS.Destroy()

			By("getting the newly created container")
			listURL := fmt.Sprintf("http://%s/networks/%s", address, networkID)
			resp, err = http.Get(listURL)
			Expect(err).NotTo(HaveOccurred())
			defer resp.Body.Close()

			Expect(resp.StatusCode).To(Equal(http.StatusOK))

			jsonBytes, err := ioutil.ReadAll(resp.Body)
			Expect(err).NotTo(HaveOccurred())

			var containers []models.Container
			err = json.Unmarshal(jsonBytes, &containers)
			Expect(err).NotTo(HaveOccurred())
			Expect(containers).To(HaveLen(1))
		})

		It("moves a vxlan adapter into the sandbox", func() {
			req, err := http.NewRequest("POST", createURL, bytes.NewReader(payload))
			Expect(err).NotTo(HaveOccurred())

			resp, err := http.DefaultClient.Do(req)
			Expect(err).NotTo(HaveOccurred())
			defer resp.Body.Close()

			Expect(resp.StatusCode).To(Equal(http.StatusCreated))

			sandboxNS, err := sandboxRepo.Get("vni-99")
			Expect(err).NotTo(HaveOccurred())
			defer sandboxNS.Destroy()

			sandboxNS.Execute(func(_ *os.File) error {
				link, err := netlink.LinkByName("vxlan99")
				Expect(err).NotTo(HaveOccurred())
				vxlan, ok := link.(*netlink.Vxlan)
				Expect(ok).To(BeTrue())

				Expect(vxlan.VxlanId).To(Equal(99))
				Expect(vxlan.Learning).To(BeTrue())
				Expect(vxlan.Port).To(BeEquivalentTo(nl.Swap16(4789)))
				Expect(vxlan.Proxy).To(BeTrue())
				Expect(vxlan.L2miss).To(BeTrue())
				Expect(vxlan.L3miss).To(BeTrue())
				Expect(vxlan.LinkAttrs.Flags & net.FlagUp).To(Equal(net.FlagUp))

				return nil
			})
		})

		It("creates a vxlan bridge in the sandbox", func() {
			var bridge *netlink.Bridge
			var addrs []netlink.Addr

			req, err := http.NewRequest("POST", createURL, bytes.NewReader(payload))
			Expect(err).NotTo(HaveOccurred())

			resp, err := http.DefaultClient.Do(req)
			Expect(err).NotTo(HaveOccurred())
			defer resp.Body.Close()

			Expect(resp.StatusCode).To(Equal(http.StatusCreated))

			sandboxNS, err := sandboxRepo.Get("vni-99")
			Expect(err).NotTo(HaveOccurred())
			defer sandboxNS.Destroy()

			err = sandboxNS.Execute(func(_ *os.File) error {
				link, err := netlink.LinkByName("vxlanbr99")
				if err != nil {
					return fmt.Errorf("finding link by name: %s", err)
				}

				var ok bool
				bridge, ok = link.(*netlink.Bridge)
				if !ok {
					return fmt.Errorf("unable to cast link to bridge")
				}

				addrs, err = netlink.AddrList(link, netlink.FAMILY_V4)
				if err != nil {
					return fmt.Errorf("unable to list addrs: %s", err)
				}

				return nil
			})
			Expect(err).NotTo(HaveOccurred())

			Expect(bridge.LinkAttrs.MTU).To(Equal(1450))
			Expect(bridge.LinkAttrs.Flags & net.FlagUp).To(Equal(net.FlagUp))

			Expect(addrs).To(HaveLen(1))
			Expect(addrs[0].IPNet.IP.String()).To(Equal(ipamResult.IP4.Gateway.String()))
		})

		It("creates a veth pair in the container and sandbox namespaces", func() {
			req, err := http.NewRequest("POST", createURL, bytes.NewReader(payload))
			Expect(err).NotTo(HaveOccurred())

			resp, err := http.DefaultClient.Do(req)
			Expect(err).NotTo(HaveOccurred())
			defer resp.Body.Close()

			Expect(resp.StatusCode).To(Equal(http.StatusCreated))

			sandboxNS, err := sandboxRepo.Get("vni-99")
			Expect(err).NotTo(HaveOccurred())
			defer sandboxNS.Destroy()

			err = containerNamespace.Execute(func(_ *os.File) error {
				link, err := netlink.LinkByName("vx-eth0")
				Expect(err).NotTo(HaveOccurred())

				bridge, ok := link.(*netlink.Veth)
				Expect(ok).To(BeTrue())
				Expect(bridge.LinkAttrs.MTU).To(Equal(1450))
				Expect(bridge.LinkAttrs.Flags & net.FlagUp).To(Equal(net.FlagUp))

				return nil
			})
			Expect(err).NotTo(HaveOccurred())

			err = sandboxNS.Execute(func(_ *os.File) error {
				link, err := netlink.LinkByName("some-container-")
				Expect(err).NotTo(HaveOccurred())

				bridge, ok := link.(*netlink.Veth)
				Expect(ok).To(BeTrue())
				Expect(bridge.LinkAttrs.MTU).To(Equal(1450))
				Expect(bridge.LinkAttrs.Flags & net.FlagUp).To(Equal(net.FlagUp))

				return nil
			})
			Expect(err).NotTo(HaveOccurred())
		})
	})
})
<commit_msg>Moves route tests from vxlan to daemon acceptance<commit_after>package acceptance_test

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net"
	"net/http"
	"os"
	"os/exec"
	"time"

	"github.com/appc/cni/pkg/types"
	"github.com/cloudfoundry-incubator/ducati-daemon/lib/namespace"
	"github.com/cloudfoundry-incubator/ducati-daemon/models"
	"github.com/nu7hatch/gouuid"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	"github.com/onsi/gomega/gexec"
	"github.com/vishvananda/netlink"    //only linux
	"github.com/vishvananda/netlink/nl" //only linux
)

var _ = Describe("Networks", func() {
	var (
		session     *gexec.Session
		address     string
		networkID   string
		containerID string

		sandboxRepo        namespace.Repository
		containerNamespace namespace.Namespace
	)

	BeforeEach(func() {
		address = fmt.Sprintf("127.0.0.1:%d", 4001+GinkgoParallelNode())
		sandboxRepoDir, err := ioutil.TempDir("", "sandbox")
		Expect(err).NotTo(HaveOccurred())

		sandboxRepo, err = namespace.NewRepository(sandboxRepoDir)
		Expect(err).NotTo(HaveOccurred())

		containerRepoDir, err := ioutil.TempDir("", "containers")
		Expect(err).NotTo(HaveOccurred())

		containerRepo, err := namespace.NewRepository(containerRepoDir)
		Expect(err).NotTo(HaveOccurred())

		guid, err := uuid.NewV4()
		Expect(err).NotTo(HaveOccurred())

		containerNamespace, err = containerRepo.Create(guid.String())
		Expect(err).NotTo(HaveOccurred())

		Expect(err).NotTo(HaveOccurred())

		ducatiCmd := exec.Command(
			ducatidPath,
			"-listenAddr", address,
			"-overlayNetwork", "192.168.0.0/16",
			"-localSubnet", "192.168.99.0/24",
			"-databaseURL", testDatabase.URL(),
			"-sandboxRepoDir", sandboxRepoDir,
		)
		session, err = gexec.Start(ducatiCmd, GinkgoWriter, GinkgoWriter)
		Expect(err).NotTo(HaveOccurred())

		networkID = "some-network-id"
		containerID = "some-container-id"
	})

	AfterEach(func() {
		session.Kill()
		Eventually(session).Should(gexec.Exit())
		containerNamespace.Destroy()
	})

	It("should boot and gracefully terminate", func() {
		Consistently(session).ShouldNot(gexec.Exit())

		session.Interrupt()
		Eventually(session, 3*time.Second).Should(gexec.Exit(0))
	})

	var serverIsAvailable = func() error {
		_, err := net.Dial("tcp", address)
		return err
	}

	Describe("POST /networks/:network_id/:container_id", func() {
		var (
			createURL  string
			payload    []byte
			ipamResult types.Result
		)

		BeforeEach(func() {
			Eventually(serverIsAvailable).Should(Succeed())

			By("generating config and creating the request")
			ipamResult = types.Result{
				IP4: &types.IPConfig{
					IP: net.IPNet{
						IP:   net.ParseIP("192.168.1.2"),
						Mask: net.CIDRMask(24, 32),
					},
					Gateway: net.ParseIP("192.168.1.1"),
					Routes: []types.Route{{
						Dst: net.IPNet{
							IP:   net.ParseIP("192.168.0.0"),
							Mask: net.CIDRMask(16, 32),
						},
						GW: net.ParseIP("192.168.1.1"),
					}},
				},
			}

			_, destination, err := net.ParseCIDR("10.10.10.0/24")
			Expect(err).NotTo(HaveOccurred())

			ipamResult.IP4.Routes = append(ipamResult.IP4.Routes, types.Route{Dst: *destination})

			payload, err = json.Marshal(models.NetworksSetupContainerPayload{
				Args:               "FOO=BAR;ABC=123",
				ContainerNamespace: containerNamespace.Path(),
				InterfaceName:      "vx-eth0",
				VNI:                99,
				IPAM:               ipamResult,
			})
			Expect(err).NotTo(HaveOccurred())

			createURL = fmt.Sprintf("http://%s/networks/%s/%s", address, networkID, containerID)

		})

		It("should respond to POST /networks/:network_id/:container_id", func() {
			req, err := http.NewRequest("POST", createURL, bytes.NewReader(payload))
			Expect(err).NotTo(HaveOccurred())

			By("creating the container")
			resp, err := http.DefaultClient.Do(req)
			Expect(err).NotTo(HaveOccurred())
			defer resp.Body.Close()

			Expect(resp.StatusCode).To(Equal(http.StatusCreated))

			sandboxNS, err := sandboxRepo.Get("vni-99")
			Expect(err).NotTo(HaveOccurred())
			defer sandboxNS.Destroy()

			By("getting the newly created container")
			listURL := fmt.Sprintf("http://%s/networks/%s", address, networkID)
			resp, err = http.Get(listURL)
			Expect(err).NotTo(HaveOccurred())
			defer resp.Body.Close()

			Expect(resp.StatusCode).To(Equal(http.StatusOK))

			jsonBytes, err := ioutil.ReadAll(resp.Body)
			Expect(err).NotTo(HaveOccurred())

			var containers []models.Container
			err = json.Unmarshal(jsonBytes, &containers)
			Expect(err).NotTo(HaveOccurred())
			Expect(containers).To(HaveLen(1))
		})

		It("moves a vxlan adapter into the sandbox", func() {
			req, err := http.NewRequest("POST", createURL, bytes.NewReader(payload))
			Expect(err).NotTo(HaveOccurred())

			resp, err := http.DefaultClient.Do(req)
			Expect(err).NotTo(HaveOccurred())
			defer resp.Body.Close()

			Expect(resp.StatusCode).To(Equal(http.StatusCreated))

			sandboxNS, err := sandboxRepo.Get("vni-99")
			Expect(err).NotTo(HaveOccurred())
			defer sandboxNS.Destroy()

			sandboxNS.Execute(func(_ *os.File) error {
				link, err := netlink.LinkByName("vxlan99")
				Expect(err).NotTo(HaveOccurred())
				vxlan, ok := link.(*netlink.Vxlan)
				Expect(ok).To(BeTrue())

				Expect(vxlan.VxlanId).To(Equal(99))
				Expect(vxlan.Learning).To(BeTrue())
				Expect(vxlan.Port).To(BeEquivalentTo(nl.Swap16(4789)))
				Expect(vxlan.Proxy).To(BeTrue())
				Expect(vxlan.L2miss).To(BeTrue())
				Expect(vxlan.L3miss).To(BeTrue())
				Expect(vxlan.LinkAttrs.Flags & net.FlagUp).To(Equal(net.FlagUp))

				return nil
			})
		})

		It("creates a vxlan bridge in the sandbox", func() {
			var bridge *netlink.Bridge
			var addrs []netlink.Addr

			req, err := http.NewRequest("POST", createURL, bytes.NewReader(payload))
			Expect(err).NotTo(HaveOccurred())

			resp, err := http.DefaultClient.Do(req)
			Expect(err).NotTo(HaveOccurred())
			defer resp.Body.Close()

			Expect(resp.StatusCode).To(Equal(http.StatusCreated))

			sandboxNS, err := sandboxRepo.Get("vni-99")
			Expect(err).NotTo(HaveOccurred())
			defer sandboxNS.Destroy()

			err = sandboxNS.Execute(func(_ *os.File) error {
				link, err := netlink.LinkByName("vxlanbr99")
				if err != nil {
					return fmt.Errorf("finding link by name: %s", err)
				}

				var ok bool
				bridge, ok = link.(*netlink.Bridge)
				if !ok {
					return fmt.Errorf("unable to cast link to bridge")
				}

				addrs, err = netlink.AddrList(link, netlink.FAMILY_V4)
				if err != nil {
					return fmt.Errorf("unable to list addrs: %s", err)
				}

				return nil
			})
			Expect(err).NotTo(HaveOccurred())

			Expect(bridge.LinkAttrs.MTU).To(Equal(1450))
			Expect(bridge.LinkAttrs.Flags & net.FlagUp).To(Equal(net.FlagUp))

			Expect(addrs).To(HaveLen(1))
			Expect(addrs[0].IPNet.IP.String()).To(Equal(ipamResult.IP4.Gateway.String()))
		})

		It("creates a veth pair in the container and sandbox namespaces", func() {
			req, err := http.NewRequest("POST", createURL, bytes.NewReader(payload))
			Expect(err).NotTo(HaveOccurred())

			resp, err := http.DefaultClient.Do(req)
			Expect(err).NotTo(HaveOccurred())
			defer resp.Body.Close()

			Expect(resp.StatusCode).To(Equal(http.StatusCreated))

			sandboxNS, err := sandboxRepo.Get("vni-99")
			Expect(err).NotTo(HaveOccurred())
			defer sandboxNS.Destroy()

			err = containerNamespace.Execute(func(_ *os.File) error {
				link, err := netlink.LinkByName("vx-eth0")
				Expect(err).NotTo(HaveOccurred())

				bridge, ok := link.(*netlink.Veth)
				Expect(ok).To(BeTrue())
				Expect(bridge.LinkAttrs.MTU).To(Equal(1450))
				Expect(bridge.LinkAttrs.Flags & net.FlagUp).To(Equal(net.FlagUp))

				return nil
			})
			Expect(err).NotTo(HaveOccurred())

			err = sandboxNS.Execute(func(_ *os.File) error {
				link, err := netlink.LinkByName("some-container-")
				Expect(err).NotTo(HaveOccurred())

				bridge, ok := link.(*netlink.Veth)
				Expect(ok).To(BeTrue())
				Expect(bridge.LinkAttrs.MTU).To(Equal(1450))
				Expect(bridge.LinkAttrs.Flags & net.FlagUp).To(Equal(net.FlagUp))

				return nil
			})
			Expect(err).NotTo(HaveOccurred())
		})

		Context("when there are routes", func() {
			It("should contain the routes", func() {
				req, err := http.NewRequest("POST", createURL, bytes.NewReader(payload))
				Expect(err).NotTo(HaveOccurred())

				resp, err := http.DefaultClient.Do(req)
				Expect(err).NotTo(HaveOccurred())
				defer resp.Body.Close()

				Expect(resp.StatusCode).To(Equal(http.StatusCreated))

				sandboxNS, err := sandboxRepo.Get("vni-99")
				Expect(err).NotTo(HaveOccurred())
				defer sandboxNS.Destroy()

				err = containerNamespace.Execute(func(_ *os.File) error {
					l, err := netlink.LinkByName("vx-eth0")
					Expect(err).NotTo(HaveOccurred())

					routes, err := netlink.RouteList(l, netlink.FAMILY_V4)
					Expect(err).NotTo(HaveOccurred())
					Expect(routes).To(HaveLen(3))

					var sanitizedRoutes []netlink.Route
					for _, route := range routes {
						sanitizedRoutes = append(sanitizedRoutes, netlink.Route{
							Gw:  route.Gw,
							Dst: route.Dst,
							Src: route.Src,
						})
					}

					_, vxlanNet, err := net.ParseCIDR("192.168.0.0/16")
					Expect(sanitizedRoutes).To(ContainElement(netlink.Route{
						Dst: vxlanNet,
						Gw:  ipamResult.IP4.Gateway.To4(),
					}))

					_, linkLocal, err := net.ParseCIDR("192.168.1.0/24")
					Expect(err).NotTo(HaveOccurred())
					Expect(sanitizedRoutes).To(ContainElement(netlink.Route{
						Dst: linkLocal,
						Src: ipamResult.IP4.IP.IP.To4(),
					}))

					_, dest, err := net.ParseCIDR("10.10.10.0/24")
					Expect(sanitizedRoutes).To(ContainElement(netlink.Route{
						Dst: dest,
						Gw:  ipamResult.IP4.Gateway.To4(),
					}))

					return nil
				})
			})
		})
	})
})
<|endoftext|>


<commit_before>// +build linux

package linux

import (
	"context"
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"

	"google.golang.org/grpc"

	"github.com/boltdb/bolt"
	eventsapi "github.com/containerd/containerd/api/services/events/v1"
	"github.com/containerd/containerd/api/types"
	"github.com/containerd/containerd/containers"
	"github.com/containerd/containerd/events"
	client "github.com/containerd/containerd/linux/shim"
	shim "github.com/containerd/containerd/linux/shim/v1"
	"github.com/containerd/containerd/log"
	"github.com/containerd/containerd/metadata"
	"github.com/containerd/containerd/namespaces"
	"github.com/containerd/containerd/plugin"
	"github.com/containerd/containerd/runtime"
	runc "github.com/containerd/go-runc"
	google_protobuf "github.com/golang/protobuf/ptypes/empty"
	"github.com/pkg/errors"

	"golang.org/x/sys/unix"
)

var (
	ErrTaskNotExists     = errors.New("task does not exist")
	ErrTaskAlreadyExists = errors.New("task already exists")
	pluginID             = fmt.Sprintf("%s.%s", plugin.RuntimePlugin, "linux")
	empty                = &google_protobuf.Empty{}
)

const (
	configFilename = "config.json"
	defaultRuntime = "runc"
	defaultShim    = "containerd-shim"
)

func init() {
	plugin.Register(&plugin.Registration{
		Type: plugin.RuntimePlugin,
		ID:   "linux",
		Init: New,
		Requires: []plugin.PluginType{
			plugin.TaskMonitorPlugin,
			plugin.MetadataPlugin,
		},
		Config: &Config{
			Shim:    defaultShim,
			Runtime: defaultRuntime,
		},
	})
}

var _ = (runtime.Runtime)(&Runtime{})

type Config struct {
	// Shim is a path or name of binary implementing the Shim GRPC API
	Shim string `toml:"shim,omitempty"`
	// Runtime is a path or name of an OCI runtime used by the shim
	Runtime string `toml:"runtime,omitempty"`
	// NoShim calls runc directly from within the pkg
	NoShim bool `toml:"no_shim,omitempty"`
}

func New(ic *plugin.InitContext) (interface{}, error) {
	if err := os.MkdirAll(ic.Root, 0700); err != nil {
		return nil, err
	}
	monitor, err := ic.Get(plugin.TaskMonitorPlugin)
	if err != nil {
		return nil, err
	}
	m, err := ic.Get(plugin.MetadataPlugin)
	if err != nil {
		return nil, err
	}
	cfg := ic.Config.(*Config)
	c, cancel := context.WithCancel(ic.Context)
	r := &Runtime{
		root:          ic.Root,
		remote:        !cfg.NoShim,
		shim:          cfg.Shim,
		runtime:       cfg.Runtime,
		events:        make(chan *eventsapi.RuntimeEvent, 2048),
		eventsContext: c,
		eventsCancel:  cancel,
		monitor:       monitor.(runtime.TaskMonitor),
		tasks:         newTaskList(),
		emitter:       events.GetPoster(ic.Context),
		db:            m.(*bolt.DB),
	}
	// set the events output for a monitor if it generates events
	r.monitor.Events(r.events)
	tasks, err := r.restoreTasks(ic.Context)
	if err != nil {
		return nil, err
	}
	for _, t := range tasks {
		if err := r.tasks.addWithNamespace(t.namespace, t); err != nil {
			return nil, err
		}
		if err := r.handleEvents(ic.Context, t.shim); err != nil {
			return nil, err
		}
	}
	return r, nil
}

type Runtime struct {
	root    string
	shim    string
	runtime string
	remote  bool

	events        chan *eventsapi.RuntimeEvent
	eventsContext context.Context
	eventsCancel  func()
	monitor       runtime.TaskMonitor
	tasks         *taskList
	emitter       events.Poster
	db            *bolt.DB
}

func (r *Runtime) ID() string {
	return pluginID
}

func (r *Runtime) Create(ctx context.Context, id string, opts runtime.CreateOpts) (_ runtime.Task, err error) {
	namespace, err := namespaces.NamespaceRequired(ctx)
	if err != nil {
		return nil, err
	}
	bundle, err := newBundle(filepath.Join(r.root, namespace), namespace, id, opts.Spec.Value)
	if err != nil {
		return nil, err
	}
	defer func() {
		if err != nil {
			bundle.Delete()
		}
	}()
	s, err := bundle.NewShim(ctx, r.shim, r.remote)
	if err != nil {
		return nil, err
	}
	defer func() {
		if err != nil {
			if kerr := s.KillShim(ctx); kerr != nil {
				log.G(ctx).WithError(err).Error("failed to kill shim")
			}
		}
	}()
	if err = r.handleEvents(ctx, s); err != nil {
		return nil, err
	}
	sopts := &shim.CreateTaskRequest{
		ID:         id,
		Bundle:     bundle.path,
		Runtime:    r.runtime,
		Stdin:      opts.IO.Stdin,
		Stdout:     opts.IO.Stdout,
		Stderr:     opts.IO.Stderr,
		Terminal:   opts.IO.Terminal,
		Checkpoint: opts.Checkpoint,
		Options:    opts.Options,
	}
	for _, m := range opts.Rootfs {
		sopts.Rootfs = append(sopts.Rootfs, &types.Mount{
			Type:    m.Type,
			Source:  m.Source,
			Options: m.Options,
		})
	}
	if _, err = s.Create(ctx, sopts); err != nil {
		return nil, errors.New(grpc.ErrorDesc(err))
	}
	t := newTask(id, namespace, s)
	if err := r.tasks.add(ctx, t); err != nil {
		return nil, err
	}
	// after the task is created, add it to the monitor
	if err = r.monitor.Monitor(t); err != nil {
		return nil, err
	}

	var runtimeMounts []*eventsapi.RuntimeMount
	for _, m := range opts.Rootfs {
		runtimeMounts = append(runtimeMounts, &eventsapi.RuntimeMount{
			Type:    m.Type,
			Source:  m.Source,
			Options: m.Options,
		})
	}
	if err := r.emit(ctx, "/runtime/create", &eventsapi.RuntimeCreate{
		ContainerID: id,
		Bundle:      bundle.path,
		RootFS:      runtimeMounts,
		IO: &eventsapi.RuntimeIO{
			Stdin:    opts.IO.Stdin,
			Stdout:   opts.IO.Stdout,
			Stderr:   opts.IO.Stderr,
			Terminal: opts.IO.Terminal,
		},
		Checkpoint: opts.Checkpoint,
	}); err != nil {
		return nil, err
	}
	return t, nil
}

func (r *Runtime) Delete(ctx context.Context, c runtime.Task) (*runtime.Exit, error) {
	namespace, err := namespaces.NamespaceRequired(ctx)
	if err != nil {
		return nil, err
	}
	lc, ok := c.(*Task)
	if !ok {
		return nil, fmt.Errorf("task cannot be cast as *linux.Task")
	}
	if err := r.monitor.Stop(lc); err != nil {
		return nil, err
	}
	rsp, err := lc.shim.Delete(ctx, empty)
	if err != nil {
		return nil, errors.New(grpc.ErrorDesc(err))
	}
	if err := lc.shim.KillShim(ctx); err != nil {
		log.G(ctx).WithError(err).Error("failed to kill shim")
	}
	r.tasks.delete(ctx, lc)

	var (
		bundle = loadBundle(filepath.Join(r.root, namespace, lc.id), namespace)
		i      = c.Info()
	)
	if err := r.emit(ctx, "/runtime/delete", &eventsapi.RuntimeDelete{
		ContainerID: i.ID,
		Runtime:     i.Runtime,
		ExitStatus:  rsp.ExitStatus,
		ExitedAt:    rsp.ExitedAt,
	}); err != nil {
		return nil, err
	}
	return &runtime.Exit{
		Status:    rsp.ExitStatus,
		Timestamp: rsp.ExitedAt,
		Pid:       rsp.Pid,
	}, bundle.Delete()
}

func (r *Runtime) Tasks(ctx context.Context) ([]runtime.Task, error) {
	namespace, err := namespaces.NamespaceRequired(ctx)
	if err != nil {
		return nil, err
	}
	var o []runtime.Task
	tasks, ok := r.tasks.tasks[namespace]
	if !ok {
		return o, nil
	}
	for _, t := range tasks {
		o = append(o, t)
	}
	return o, nil
}

func (r *Runtime) restoreTasks(ctx context.Context) ([]*Task, error) {
	dir, err := ioutil.ReadDir(r.root)
	if err != nil {
		return nil, err
	}
	var o []*Task
	for _, namespace := range dir {
		if !namespace.IsDir() {
			continue
		}
		name := namespace.Name()
		log.G(ctx).WithField("namespace", name).Debug("loading tasks in namespace")
		tasks, err := r.loadTasks(ctx, name)
		if err != nil {
			return nil, err
		}
		o = append(o, tasks...)
	}
	return o, nil
}

func (r *Runtime) Get(ctx context.Context, id string) (runtime.Task, error) {
	return r.tasks.get(ctx, id)
}

func (r *Runtime) loadTasks(ctx context.Context, ns string) ([]*Task, error) {
	dir, err := ioutil.ReadDir(filepath.Join(r.root, ns))
	if err != nil {
		return nil, err
	}
	var o []*Task
	for _, path := range dir {
		if !path.IsDir() {
			continue
		}
		id := path.Name()
		bundle := loadBundle(filepath.Join(r.root, ns, id), ns)

		s, err := bundle.Connect(ctx, r.remote)
		if err != nil {
			log.G(ctx).WithError(err).Error("connecting to shim")
			if err := r.terminate(ctx, bundle, ns, id); err != nil {
				log.G(ctx).WithError(err).WithField("bundle", bundle.path).Error("failed to terminate task, leaving bundle for debugging")
				continue
			}
			if err := bundle.Delete(); err != nil {
				log.G(ctx).WithError(err).Error("delete bundle")
			}
			continue
		}
		o = append(o, &Task{
			id:        id,
			shim:      s,
			namespace: ns,
		})
	}
	return o, nil
}

func (r *Runtime) handleEvents(ctx context.Context, s *client.Client) error {
	events, err := s.Stream(r.eventsContext, &shim.StreamEventsRequest{})
	if err != nil {
		return err
	}
	go r.forward(ctx, events)
	return nil
}

// forward forwards events from a shim to the events service and monitors
func (r *Runtime) forward(ctx context.Context, events shim.Shim_StreamClient) {
	for {
		e, err := events.Recv()
		if err != nil {
			if !strings.HasSuffix(err.Error(), "transport is closing") {
				log.G(r.eventsContext).WithError(err).Error("get event from shim")
			}
			return
		}
		r.events <- e
		if err := r.emit(ctx, "/runtime/"+getTopic(e), e); err != nil {
			return
		}
	}
}

func getTopic(e *eventsapi.RuntimeEvent) string {
	switch e.Type {
	case eventsapi.RuntimeEvent_CREATE:
		return "task-create"
	case eventsapi.RuntimeEvent_START:
		return "task-start"
	case eventsapi.RuntimeEvent_EXEC_ADDED:
		return "task-execadded"
	case eventsapi.RuntimeEvent_OOM:
		return "task-oom"
	case eventsapi.RuntimeEvent_EXIT:
		return "task-exit"
	}
	return ""
}

func (r *Runtime) terminate(ctx context.Context, bundle *bundle, ns, id string) error {
	ctx = namespaces.WithNamespace(ctx, ns)
	rt, err := r.getRuntime(ctx, ns, id)
	if err != nil {
		return err
	}
	if err := rt.Delete(ctx, id, &runc.DeleteOpts{
		Force: true,
	}); err != nil {
		log.G(ctx).WithError(err).Warnf("delete runtime state %s", id)
	}
	if err := unix.Unmount(filepath.Join(bundle.path, "rootfs"), 0); err != nil {
		log.G(ctx).WithError(err).Warnf("unmount task rootfs %s", id)
	}
	return nil
}

func (r *Runtime) getRuntime(ctx context.Context, ns, id string) (*runc.Runc, error) {
	var c containers.Container
	if err := r.db.View(func(tx *bolt.Tx) error {
		store := metadata.NewContainerStore(tx)
		var err error
		c, err = store.Get(ctx, id)
		return err
	}); err != nil {
		return nil, err
	}
	return &runc.Runc{
		// TODO: until we have a way to store/retrieve the original command
		// we can only rely on runc from the default $PATH
		Command:      runc.DefaultCommand,
		LogFormat:    runc.JSON,
		PdeathSignal: unix.SIGKILL,
		Root:         filepath.Join(client.RuncRoot, ns),
	}, nil
}

func (r *Runtime) emit(ctx context.Context, topic string, evt interface{}) error {
	emitterCtx := events.WithTopic(ctx, topic)
	if err := r.emitter.Post(emitterCtx, evt); err != nil {
		return err
	}

	return nil
}
<commit_msg>linux: Drop unused variable `c` by discarding unused assignment<commit_after>// +build linux

package linux

import (
	"context"
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"

	"google.golang.org/grpc"

	"github.com/boltdb/bolt"
	eventsapi "github.com/containerd/containerd/api/services/events/v1"
	"github.com/containerd/containerd/api/types"
	"github.com/containerd/containerd/events"
	client "github.com/containerd/containerd/linux/shim"
	shim "github.com/containerd/containerd/linux/shim/v1"
	"github.com/containerd/containerd/log"
	"github.com/containerd/containerd/metadata"
	"github.com/containerd/containerd/namespaces"
	"github.com/containerd/containerd/plugin"
	"github.com/containerd/containerd/runtime"
	runc "github.com/containerd/go-runc"
	google_protobuf "github.com/golang/protobuf/ptypes/empty"
	"github.com/pkg/errors"

	"golang.org/x/sys/unix"
)

var (
	ErrTaskNotExists     = errors.New("task does not exist")
	ErrTaskAlreadyExists = errors.New("task already exists")
	pluginID             = fmt.Sprintf("%s.%s", plugin.RuntimePlugin, "linux")
	empty                = &google_protobuf.Empty{}
)

const (
	configFilename = "config.json"
	defaultRuntime = "runc"
	defaultShim    = "containerd-shim"
)

func init() {
	plugin.Register(&plugin.Registration{
		Type: plugin.RuntimePlugin,
		ID:   "linux",
		Init: New,
		Requires: []plugin.PluginType{
			plugin.TaskMonitorPlugin,
			plugin.MetadataPlugin,
		},
		Config: &Config{
			Shim:    defaultShim,
			Runtime: defaultRuntime,
		},
	})
}

var _ = (runtime.Runtime)(&Runtime{})

type Config struct {
	// Shim is a path or name of binary implementing the Shim GRPC API
	Shim string `toml:"shim,omitempty"`
	// Runtime is a path or name of an OCI runtime used by the shim
	Runtime string `toml:"runtime,omitempty"`
	// NoShim calls runc directly from within the pkg
	NoShim bool `toml:"no_shim,omitempty"`
}

func New(ic *plugin.InitContext) (interface{}, error) {
	if err := os.MkdirAll(ic.Root, 0700); err != nil {
		return nil, err
	}
	monitor, err := ic.Get(plugin.TaskMonitorPlugin)
	if err != nil {
		return nil, err
	}
	m, err := ic.Get(plugin.MetadataPlugin)
	if err != nil {
		return nil, err
	}
	cfg := ic.Config.(*Config)
	c, cancel := context.WithCancel(ic.Context)
	r := &Runtime{
		root:          ic.Root,
		remote:        !cfg.NoShim,
		shim:          cfg.Shim,
		runtime:       cfg.Runtime,
		events:        make(chan *eventsapi.RuntimeEvent, 2048),
		eventsContext: c,
		eventsCancel:  cancel,
		monitor:       monitor.(runtime.TaskMonitor),
		tasks:         newTaskList(),
		emitter:       events.GetPoster(ic.Context),
		db:            m.(*bolt.DB),
	}
	// set the events output for a monitor if it generates events
	r.monitor.Events(r.events)
	tasks, err := r.restoreTasks(ic.Context)
	if err != nil {
		return nil, err
	}
	for _, t := range tasks {
		if err := r.tasks.addWithNamespace(t.namespace, t); err != nil {
			return nil, err
		}
		if err := r.handleEvents(ic.Context, t.shim); err != nil {
			return nil, err
		}
	}
	return r, nil
}

type Runtime struct {
	root    string
	shim    string
	runtime string
	remote  bool

	events        chan *eventsapi.RuntimeEvent
	eventsContext context.Context
	eventsCancel  func()
	monitor       runtime.TaskMonitor
	tasks         *taskList
	emitter       events.Poster
	db            *bolt.DB
}

func (r *Runtime) ID() string {
	return pluginID
}

func (r *Runtime) Create(ctx context.Context, id string, opts runtime.CreateOpts) (_ runtime.Task, err error) {
	namespace, err := namespaces.NamespaceRequired(ctx)
	if err != nil {
		return nil, err
	}
	bundle, err := newBundle(filepath.Join(r.root, namespace), namespace, id, opts.Spec.Value)
	if err != nil {
		return nil, err
	}
	defer func() {
		if err != nil {
			bundle.Delete()
		}
	}()
	s, err := bundle.NewShim(ctx, r.shim, r.remote)
	if err != nil {
		return nil, err
	}
	defer func() {
		if err != nil {
			if kerr := s.KillShim(ctx); kerr != nil {
				log.G(ctx).WithError(err).Error("failed to kill shim")
			}
		}
	}()
	if err = r.handleEvents(ctx, s); err != nil {
		return nil, err
	}
	sopts := &shim.CreateTaskRequest{
		ID:         id,
		Bundle:     bundle.path,
		Runtime:    r.runtime,
		Stdin:      opts.IO.Stdin,
		Stdout:     opts.IO.Stdout,
		Stderr:     opts.IO.Stderr,
		Terminal:   opts.IO.Terminal,
		Checkpoint: opts.Checkpoint,
		Options:    opts.Options,
	}
	for _, m := range opts.Rootfs {
		sopts.Rootfs = append(sopts.Rootfs, &types.Mount{
			Type:    m.Type,
			Source:  m.Source,
			Options: m.Options,
		})
	}
	if _, err = s.Create(ctx, sopts); err != nil {
		return nil, errors.New(grpc.ErrorDesc(err))
	}
	t := newTask(id, namespace, s)
	if err := r.tasks.add(ctx, t); err != nil {
		return nil, err
	}
	// after the task is created, add it to the monitor
	if err = r.monitor.Monitor(t); err != nil {
		return nil, err
	}

	var runtimeMounts []*eventsapi.RuntimeMount
	for _, m := range opts.Rootfs {
		runtimeMounts = append(runtimeMounts, &eventsapi.RuntimeMount{
			Type:    m.Type,
			Source:  m.Source,
			Options: m.Options,
		})
	}
	if err := r.emit(ctx, "/runtime/create", &eventsapi.RuntimeCreate{
		ContainerID: id,
		Bundle:      bundle.path,
		RootFS:      runtimeMounts,
		IO: &eventsapi.RuntimeIO{
			Stdin:    opts.IO.Stdin,
			Stdout:   opts.IO.Stdout,
			Stderr:   opts.IO.Stderr,
			Terminal: opts.IO.Terminal,
		},
		Checkpoint: opts.Checkpoint,
	}); err != nil {
		return nil, err
	}
	return t, nil
}

func (r *Runtime) Delete(ctx context.Context, c runtime.Task) (*runtime.Exit, error) {
	namespace, err := namespaces.NamespaceRequired(ctx)
	if err != nil {
		return nil, err
	}
	lc, ok := c.(*Task)
	if !ok {
		return nil, fmt.Errorf("task cannot be cast as *linux.Task")
	}
	if err := r.monitor.Stop(lc); err != nil {
		return nil, err
	}
	rsp, err := lc.shim.Delete(ctx, empty)
	if err != nil {
		return nil, errors.New(grpc.ErrorDesc(err))
	}
	if err := lc.shim.KillShim(ctx); err != nil {
		log.G(ctx).WithError(err).Error("failed to kill shim")
	}
	r.tasks.delete(ctx, lc)

	var (
		bundle = loadBundle(filepath.Join(r.root, namespace, lc.id), namespace)
		i      = c.Info()
	)
	if err := r.emit(ctx, "/runtime/delete", &eventsapi.RuntimeDelete{
		ContainerID: i.ID,
		Runtime:     i.Runtime,
		ExitStatus:  rsp.ExitStatus,
		ExitedAt:    rsp.ExitedAt,
	}); err != nil {
		return nil, err
	}
	return &runtime.Exit{
		Status:    rsp.ExitStatus,
		Timestamp: rsp.ExitedAt,
		Pid:       rsp.Pid,
	}, bundle.Delete()
}

func (r *Runtime) Tasks(ctx context.Context) ([]runtime.Task, error) {
	namespace, err := namespaces.NamespaceRequired(ctx)
	if err != nil {
		return nil, err
	}
	var o []runtime.Task
	tasks, ok := r.tasks.tasks[namespace]
	if !ok {
		return o, nil
	}
	for _, t := range tasks {
		o = append(o, t)
	}
	return o, nil
}

func (r *Runtime) restoreTasks(ctx context.Context) ([]*Task, error) {
	dir, err := ioutil.ReadDir(r.root)
	if err != nil {
		return nil, err
	}
	var o []*Task
	for _, namespace := range dir {
		if !namespace.IsDir() {
			continue
		}
		name := namespace.Name()
		log.G(ctx).WithField("namespace", name).Debug("loading tasks in namespace")
		tasks, err := r.loadTasks(ctx, name)
		if err != nil {
			return nil, err
		}
		o = append(o, tasks...)
	}
	return o, nil
}

func (r *Runtime) Get(ctx context.Context, id string) (runtime.Task, error) {
	return r.tasks.get(ctx, id)
}

func (r *Runtime) loadTasks(ctx context.Context, ns string) ([]*Task, error) {
	dir, err := ioutil.ReadDir(filepath.Join(r.root, ns))
	if err != nil {
		return nil, err
	}
	var o []*Task
	for _, path := range dir {
		if !path.IsDir() {
			continue
		}
		id := path.Name()
		bundle := loadBundle(filepath.Join(r.root, ns, id), ns)

		s, err := bundle.Connect(ctx, r.remote)
		if err != nil {
			log.G(ctx).WithError(err).Error("connecting to shim")
			if err := r.terminate(ctx, bundle, ns, id); err != nil {
				log.G(ctx).WithError(err).WithField("bundle", bundle.path).Error("failed to terminate task, leaving bundle for debugging")
				continue
			}
			if err := bundle.Delete(); err != nil {
				log.G(ctx).WithError(err).Error("delete bundle")
			}
			continue
		}
		o = append(o, &Task{
			id:        id,
			shim:      s,
			namespace: ns,
		})
	}
	return o, nil
}

func (r *Runtime) handleEvents(ctx context.Context, s *client.Client) error {
	events, err := s.Stream(r.eventsContext, &shim.StreamEventsRequest{})
	if err != nil {
		return err
	}
	go r.forward(ctx, events)
	return nil
}

// forward forwards events from a shim to the events service and monitors
func (r *Runtime) forward(ctx context.Context, events shim.Shim_StreamClient) {
	for {
		e, err := events.Recv()
		if err != nil {
			if !strings.HasSuffix(err.Error(), "transport is closing") {
				log.G(r.eventsContext).WithError(err).Error("get event from shim")
			}
			return
		}
		r.events <- e
		if err := r.emit(ctx, "/runtime/"+getTopic(e), e); err != nil {
			return
		}
	}
}

func getTopic(e *eventsapi.RuntimeEvent) string {
	switch e.Type {
	case eventsapi.RuntimeEvent_CREATE:
		return "task-create"
	case eventsapi.RuntimeEvent_START:
		return "task-start"
	case eventsapi.RuntimeEvent_EXEC_ADDED:
		return "task-execadded"
	case eventsapi.RuntimeEvent_OOM:
		return "task-oom"
	case eventsapi.RuntimeEvent_EXIT:
		return "task-exit"
	}
	return ""
}

func (r *Runtime) terminate(ctx context.Context, bundle *bundle, ns, id string) error {
	ctx = namespaces.WithNamespace(ctx, ns)
	rt, err := r.getRuntime(ctx, ns, id)
	if err != nil {
		return err
	}
	if err := rt.Delete(ctx, id, &runc.DeleteOpts{
		Force: true,
	}); err != nil {
		log.G(ctx).WithError(err).Warnf("delete runtime state %s", id)
	}
	if err := unix.Unmount(filepath.Join(bundle.path, "rootfs"), 0); err != nil {
		log.G(ctx).WithError(err).Warnf("unmount task rootfs %s", id)
	}
	return nil
}

func (r *Runtime) getRuntime(ctx context.Context, ns, id string) (*runc.Runc, error) {
	if err := r.db.View(func(tx *bolt.Tx) error {
		store := metadata.NewContainerStore(tx)
		var err error
		_, err = store.Get(ctx, id)
		return err
	}); err != nil {
		return nil, err
	}
	return &runc.Runc{
		// TODO: until we have a way to store/retrieve the original command
		// we can only rely on runc from the default $PATH
		Command:      runc.DefaultCommand,
		LogFormat:    runc.JSON,
		PdeathSignal: unix.SIGKILL,
		Root:         filepath.Join(client.RuncRoot, ns),
	}, nil
}

func (r *Runtime) emit(ctx context.Context, topic string, evt interface{}) error {
	emitterCtx := events.WithTopic(ctx, topic)
	if err := r.emitter.Post(emitterCtx, evt); err != nil {
		return err
	}

	return nil
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"encoding/json"
	"flag"
	"fmt"
	"github.com/yvasiyarov/swagger/parser"
	"go/ast"
	"log"
	"os"
	"path"
	"strings"
)

const (
	AVAILABLE_FORMATS = "go|swagger|asciidoc|markdown"
)

var apiPackage = flag.String("apiPackage", "", "The package that implements the API controllers, relative to $GOPATH/src")
var mainApiFile = flag.String("mainApiFile", "", "The file that contains the general API annotations, relative to $GOPATH/src")
var basePath = flag.String("basePath", "http://127.0.0.1:3000", "Web service base path")
var outputFormat = flag.String("format", "go", "Output format type for the generated files: "+AVAILABLE_FORMATS)
var outputSpec = flag.String("output", "", "Output (path) for the generated file(s)")

var generatedFileTemplate = `
package main
//This file is generated automatically. Do not try to edit it manually.

var resourceListingJson = {{resourceListing}}
var apiDescriptionsJson = {{apiDescriptions}}
`

// It must return true if funcDeclaration is controller. We will try to parse only comments before controllers
func IsController(funcDeclaration *ast.FuncDecl) bool {
	if funcDeclaration.Recv != nil && len(funcDeclaration.Recv.List) > 0 {
		if starExpression, ok := funcDeclaration.Recv.List[0].Type.(*ast.StarExpr); ok {
			receiverName := fmt.Sprint(starExpression.X)
			return strings.Index(receiverName, "Context") != -1 || strings.Index(receiverName, "Controller") != -1
		}
	}
	return false
}

func generateSwaggerDocs(parser *parser.Parser) {
	fd, err := os.Create(path.Join("./", "docs.go"))
	if err != nil {
		log.Fatalf("Can not create document file: %v\n", err)
	}
	defer fd.Close()

	var apiDescriptions bytes.Buffer
	for apiKey, apiDescription := range parser.TopLevelApis {
		apiDescriptions.WriteString("\"" + apiKey + "\":")

		apiDescriptions.WriteString("`")
		json, err := json.MarshalIndent(apiDescription, "", "    ")
		if err != nil {
			log.Fatalf("Can not serialise []ApiDescription to JSON: %v\n", err)
		}
		apiDescriptions.Write(json)
		apiDescriptions.WriteString("`,")
	}

	doc := strings.Replace(generatedFileTemplate, "{{resourceListing}}", "`"+string(parser.GetResourceListingJson())+"`", -1)
	doc = strings.Replace(doc, "{{apiDescriptions}}", "map[string]string{"+apiDescriptions.String()+"}", -1)

	fd.WriteString(doc)
}

func generateMarkup(parser *parser.Parser, markup Markup, fileExtension string) {
	var filename string
	if *outputSpec == "" {
		filename = path.Join("./", "API", fileExtension)
	} else {
		filename = path.Join(*outputSpec)
	}
	fd, err := os.Create(filename)
	if err != nil {
		log.Fatalf("Can not create document file: %v\n", err)
	}
	defer fd.Close()

	var buf bytes.Buffer

	/***************************************************************
	* Overall API
	***************************************************************/
	buf.WriteString(markup.sectionHeader(1, parser.Listing.Infos.Title))
	buf.WriteString(fmt.Sprintf("%s\n\n", parser.Listing.Infos.Description))

	/***************************************************************
	* Table of Contents (List of Sub-APIs)
	***************************************************************/
	buf.WriteString("Table of Contents\n\n")
	for _, ref := range parser.Listing.Apis {
		buf.WriteString(markup.numberedItem(1, markup.link(ref.Path[1:], ref.Description)))
	}
	buf.WriteString("\n")

	for apiKey, apiDescription := range parser.TopLevelApis {
		/***************************************************************
		* Sub-API Specifications
		***************************************************************/
		buf.WriteString(markup.anchor(apiKey))
		buf.WriteString(markup.sectionHeader(2, apiKey))

		buf.WriteString(markup.tableHeader(""))
		buf.WriteString(markup.tableRow("Specification", "Value"))
		buf.WriteString(markup.tableRow("Resource Path", apiDescription.ResourcePath))
		buf.WriteString(markup.tableRow("API Version", apiDescription.ApiVersion))
		buf.WriteString(markup.tableRow("BasePath for the API", apiDescription.BasePath))
		buf.WriteString(markup.tableRow("Consumes", strings.Join(apiDescription.Consumes, ", ")))
		buf.WriteString(markup.tableRow("Produces", strings.Join(apiDescription.Produces, ", ")))
		buf.WriteString(markup.tableFooter())

		/***************************************************************
		* Sub-API Operations (Summary)
		***************************************************************/
		buf.WriteString("\n")
		buf.WriteString(markup.sectionHeader(3, "Operations"))
		buf.WriteString("\n")

		buf.WriteString(markup.tableHeader(""))
		buf.WriteString(markup.tableRow("Resource Path", "Operation", "Description"))
		for _, subapi := range apiDescription.Apis {
			for _, op := range subapi.Operations {
				pathString := strings.Replace(strings.Replace(subapi.Path, "{", "\\{", -1), "}", "\\}", -1)
				buf.WriteString(markup.tableRow(markup.link(op.Nickname, pathString), markup.link(op.Nickname, op.HttpMethod), markup.link(op.Nickname, op.Summary)))
			}
		}
		buf.WriteString(markup.tableFooter())
		buf.WriteString("\n")

		/***************************************************************
		* Sub-API Operations (Details)
		***************************************************************/
		for _, subapi := range apiDescription.Apis {
			for _, op := range subapi.Operations {
				buf.WriteString("\n")
				operationString := fmt.Sprintf("%s [%s]", strings.Replace(strings.Replace(subapi.Path, "{", "\\{", -1), "}", "\\}", -1), op.HttpMethod)
				buf.WriteString(markup.anchor(op.Nickname))
				buf.WriteString(markup.sectionHeader(4, "API: "+operationString))
				buf.WriteString("\n\n" + op.Summary + "\n\n\n")

				if len(op.Parameters) > 0 {
					buf.WriteString(markup.tableHeader(""))
					buf.WriteString(markup.tableRow("Param Name", "Param Type", "Data Type", "Description", "Required?"))
					for _, param := range op.Parameters {
						isRequired := ""
						if param.Required {
							isRequired = "Yes"
						}
						buf.WriteString(markup.tableRow(param.Name, param.ParamType, param.DataType, param.Description, isRequired))
					}
					buf.WriteString(markup.tableFooter())
				}

				if len(op.ResponseMessages) > 0 {
					buf.WriteString(markup.tableHeader(""))
					buf.WriteString(markup.tableRow("Code", "Message", "Model"))
					for _, msg := range op.ResponseMessages {
						buf.WriteString(markup.tableRow(fmt.Sprintf("%v", msg.Code), msg.Message, msg.ResponseModel))
					}
					buf.WriteString(markup.tableFooter())
				}
			}
		}
		buf.WriteString("\n")
	}

	fd.WriteString(buf.String())
}
func generateSwaggerUiFiles(parser *parser.Parser) {
	fd, err := os.Create(path.Join(*outputSpec, "index.json"))
	if err != nil {
		log.Fatalf("Can not create the master index.json file: %v\n", err)
	}
	defer fd.Close()
	fd.WriteString(string(parser.GetResourceListingJson()))

	for apiKey, apiDescription := range parser.TopLevelApis {
		err = os.MkdirAll(path.Join(*outputSpec, apiKey), 0777)
		fd, err = os.Create(path.Join(*outputSpec, apiKey, "index.json"))
		if err != nil {
			log.Fatalf("Can not create the %s/index.json file: %v\n", apiKey, err)
		}
		defer fd.Close()
		json, err := json.MarshalIndent(apiDescription, "", "    ")
		if err != nil {
			log.Fatalf("Can not serialise []ApiDescription to JSON: %v\n", err)
		}
		fd.Write(json)
		log.Printf("Wrote %v/index.json", apiKey)
	}
}

func InitParser() *parser.Parser {
	parser := parser.NewParser()

	parser.BasePath = *basePath
	parser.IsController = IsController

	parser.TypesImplementingMarshalInterface["NullString"] = "string"
	parser.TypesImplementingMarshalInterface["NullInt64"] = "int"
	parser.TypesImplementingMarshalInterface["NullFloat64"] = "float"
	parser.TypesImplementingMarshalInterface["NullBool"] = "bool"

	return parser
}

func main() {
	flag.Parse()

	if *mainApiFile == "" {
		*mainApiFile = *apiPackage + "/main.go"
	}
	if *apiPackage == "" {
		flag.PrintDefaults()
		return
	}

	parser := InitParser()
	gopath := os.Getenv("GOPATH")
	if gopath == "" {
		log.Fatalf("Please, set $GOPATH environment variable\n")
	}

	log.Println("Start parsing")
	parser.ParseGeneralApiInfo(path.Join(gopath, "src", *mainApiFile))
	parser.ParseApi(*apiPackage)
	log.Println("Finish parsing")

	format := strings.ToLower(*outputFormat)
	switch format {
	case "go":
		generateSwaggerDocs(parser)
		log.Println("Doc file generated")
	case "asciidoc":
		markupAsciiDoc := new(MarkupAsciiDoc)
		generateMarkup(parser, markupAsciiDoc, ".adoc")
		log.Println("AsciiDoc file generated")
	case "markdown":
		// markupMarkdown := new(MarkupMarkdown)
		// generateMarkup(parser, markupMarkdown, ".md")
		// log.Println("Markdown file generated")
	case "swagger":
		generateSwaggerUiFiles(parser)
		log.Println("Swagger UI files generated")
	default:
		log.Fatalf("Invalid -format specified. Must be one of %v.", AVAILABLE_FORMATS)
	}

}
<commit_msg>Added tables with the model definitions to the end of each sub-Api.<commit_after>package main

import (
	"bytes"
	"encoding/json"
	"flag"
	"fmt"
	"github.com/yvasiyarov/swagger/parser"
	"go/ast"
	"log"
	"os"
	"path"
	"strings"
)

const (
	AVAILABLE_FORMATS = "go|swagger|asciidoc|markdown"
)

var apiPackage = flag.String("apiPackage", "", "The package that implements the API controllers, relative to $GOPATH/src")
var mainApiFile = flag.String("mainApiFile", "", "The file that contains the general API annotations, relative to $GOPATH/src")
var basePath = flag.String("basePath", "http://127.0.0.1:3000", "Web service base path")
var outputFormat = flag.String("format", "go", "Output format type for the generated files: "+AVAILABLE_FORMATS)
var outputSpec = flag.String("output", "", "Output (path) for the generated file(s)")

var generatedFileTemplate = `
package main
//This file is generated automatically. Do not try to edit it manually.

var resourceListingJson = {{resourceListing}}
var apiDescriptionsJson = {{apiDescriptions}}
`

// It must return true if funcDeclaration is controller. We will try to parse only comments before controllers
func IsController(funcDeclaration *ast.FuncDecl) bool {
	if funcDeclaration.Recv != nil && len(funcDeclaration.Recv.List) > 0 {
		if starExpression, ok := funcDeclaration.Recv.List[0].Type.(*ast.StarExpr); ok {
			receiverName := fmt.Sprint(starExpression.X)
			return strings.Index(receiverName, "Context") != -1 || strings.Index(receiverName, "Controller") != -1
		}
	}
	return false
}

func generateSwaggerDocs(parser *parser.Parser) {
	fd, err := os.Create(path.Join("./", "docs.go"))
	if err != nil {
		log.Fatalf("Can not create document file: %v\n", err)
	}
	defer fd.Close()

	var apiDescriptions bytes.Buffer
	for apiKey, apiDescription := range parser.TopLevelApis {
		apiDescriptions.WriteString("\"" + apiKey + "\":")

		apiDescriptions.WriteString("`")
		json, err := json.MarshalIndent(apiDescription, "", "    ")
		if err != nil {
			log.Fatalf("Can not serialise []ApiDescription to JSON: %v\n", err)
		}
		apiDescriptions.Write(json)
		apiDescriptions.WriteString("`,")
	}

	doc := strings.Replace(generatedFileTemplate, "{{resourceListing}}", "`"+string(parser.GetResourceListingJson())+"`", -1)
	doc = strings.Replace(doc, "{{apiDescriptions}}", "map[string]string{"+apiDescriptions.String()+"}", -1)

	fd.WriteString(doc)
}

func generateMarkup(parser *parser.Parser, markup Markup, fileExtension string) {
	var filename string
	if *outputSpec == "" {
		filename = path.Join("./", "API", fileExtension)
	} else {
		filename = path.Join(*outputSpec)
	}
	fd, err := os.Create(filename)
	if err != nil {
		log.Fatalf("Can not create document file: %v\n", err)
	}
	defer fd.Close()

	var buf bytes.Buffer

	/***************************************************************
	* Overall API
	***************************************************************/
	buf.WriteString(markup.sectionHeader(1, parser.Listing.Infos.Title))
	buf.WriteString(fmt.Sprintf("%s\n\n", parser.Listing.Infos.Description))

	/***************************************************************
	* Table of Contents (List of Sub-APIs)
	***************************************************************/
	buf.WriteString("Table of Contents\n\n")
	for _, ref := range parser.Listing.Apis {
		buf.WriteString(markup.numberedItem(1, markup.link(ref.Path[1:], ref.Description)))
	}
	buf.WriteString("\n")

	for apiKey, apiDescription := range parser.TopLevelApis {
		/***************************************************************
		* Sub-API Specifications
		***************************************************************/
		buf.WriteString(markup.anchor(apiKey))
		buf.WriteString(markup.sectionHeader(2, apiKey))

		buf.WriteString(markup.tableHeader(""))
		buf.WriteString(markup.tableRow("Specification", "Value"))
		buf.WriteString(markup.tableRow("Resource Path", apiDescription.ResourcePath))
		buf.WriteString(markup.tableRow("API Version", apiDescription.ApiVersion))
		buf.WriteString(markup.tableRow("BasePath for the API", apiDescription.BasePath))
		buf.WriteString(markup.tableRow("Consumes", strings.Join(apiDescription.Consumes, ", ")))
		buf.WriteString(markup.tableRow("Produces", strings.Join(apiDescription.Produces, ", ")))
		buf.WriteString(markup.tableFooter())

		/***************************************************************
		* Sub-API Operations (Summary)
		***************************************************************/
		buf.WriteString("\n")
		buf.WriteString(markup.sectionHeader(3, "Operations"))
		buf.WriteString("\n")

		buf.WriteString(markup.tableHeader(""))
		buf.WriteString(markup.tableRow("Resource Path", "Operation", "Description"))
		for _, subapi := range apiDescription.Apis {
			for _, op := range subapi.Operations {
				pathString := strings.Replace(strings.Replace(subapi.Path, "{", "\\{", -1), "}", "\\}", -1)
				buf.WriteString(markup.tableRow(pathString, markup.link(op.Nickname, op.HttpMethod), op.Summary))
			}
		}
		buf.WriteString(markup.tableFooter())
		buf.WriteString("\n")

		/***************************************************************
		* Sub-API Operations (Details)
		***************************************************************/
		for _, subapi := range apiDescription.Apis {
			for _, op := range subapi.Operations {
				buf.WriteString("\n")
				operationString := fmt.Sprintf("%s [%s]", strings.Replace(strings.Replace(subapi.Path, "{", "\\{", -1), "}", "\\}", -1), op.HttpMethod)
				buf.WriteString(markup.anchor(op.Nickname))
				buf.WriteString(markup.sectionHeader(4, "API: "+operationString))
				buf.WriteString("\n\n" + op.Summary + "\n\n\n")

				if len(op.Parameters) > 0 {
					buf.WriteString(markup.tableHeader(""))
					buf.WriteString(markup.tableRow("Param Name", "Param Type", "Data Type", "Description", "Required?"))
					for _, param := range op.Parameters {
						isRequired := ""
						if param.Required {
							isRequired = "Yes"
						}
						buf.WriteString(markup.tableRow(param.Name, param.ParamType, param.DataType, param.Description, isRequired))
					}
					buf.WriteString(markup.tableFooter())
				}

				if len(op.ResponseMessages) > 0 {
					buf.WriteString(markup.tableHeader(""))
					buf.WriteString(markup.tableRow("Code", "Message", "Model"))
					for _, msg := range op.ResponseMessages {
						shortName := shortModelName(msg.ResponseModel)
						modelText := shortName
						if msg.ResponseModel != shortName {
							modelText = markup.link(msg.ResponseModel, shortName)
						}
						buf.WriteString(markup.tableRow(fmt.Sprintf("%v", msg.Code), msg.Message, modelText))
					}
					buf.WriteString(markup.tableFooter())
				}
			}
		}
		buf.WriteString("\n")

		/***************************************************************
		* Models
		***************************************************************/
		buf.WriteString("\n")
		buf.WriteString(markup.sectionHeader(3, "Models"))
		buf.WriteString("\n")

		for modelKey, model := range apiDescription.Models {
			buf.WriteString(markup.anchor(modelKey))
			buf.WriteString(markup.sectionHeader(4, shortModelName(modelKey)))
			buf.WriteString(markup.tableHeader(""))
			buf.WriteString(markup.tableRow("Field Name", "Field Type", "Description"))
			for fieldName, fieldProps := range model.Properties {
				buf.WriteString(markup.tableRow(fieldName, fieldProps.Type, fieldProps.Description))
			}
			buf.WriteString(markup.tableFooter())
			buf.WriteString("\nNote: These fields are listed in random order (for now).\n")
		}
		buf.WriteString("\n")

	}

	fd.WriteString(buf.String())
}

func shortModelName(longModelName string) string {
	parts := strings.Split(longModelName, ".")
	return parts[len(parts)-1]
}

func generateSwaggerUiFiles(parser *parser.Parser) {
	fd, err := os.Create(path.Join(*outputSpec, "index.json"))
	if err != nil {
		log.Fatalf("Can not create the master index.json file: %v\n", err)
	}
	defer fd.Close()
	fd.WriteString(string(parser.GetResourceListingJson()))

	for apiKey, apiDescription := range parser.TopLevelApis {
		err = os.MkdirAll(path.Join(*outputSpec, apiKey), 0777)
		fd, err = os.Create(path.Join(*outputSpec, apiKey, "index.json"))
		if err != nil {
			log.Fatalf("Can not create the %s/index.json file: %v\n", apiKey, err)
		}
		defer fd.Close()
		json, err := json.MarshalIndent(apiDescription, "", "    ")
		if err != nil {
			log.Fatalf("Can not serialise []ApiDescription to JSON: %v\n", err)
		}
		fd.Write(json)
		log.Printf("Wrote %v/index.json", apiKey)
	}
}

func InitParser() *parser.Parser {
	parser := parser.NewParser()

	parser.BasePath = *basePath
	parser.IsController = IsController

	parser.TypesImplementingMarshalInterface["NullString"] = "string"
	parser.TypesImplementingMarshalInterface["NullInt64"] = "int"
	parser.TypesImplementingMarshalInterface["NullFloat64"] = "float"
	parser.TypesImplementingMarshalInterface["NullBool"] = "bool"

	return parser
}

func main() {
	flag.Parse()

	if *mainApiFile == "" {
		*mainApiFile = *apiPackage + "/main.go"
	}
	if *apiPackage == "" {
		flag.PrintDefaults()
		return
	}

	parser := InitParser()
	gopath := os.Getenv("GOPATH")
	if gopath == "" {
		log.Fatalf("Please, set $GOPATH environment variable\n")
	}

	log.Println("Start parsing")
	parser.ParseGeneralApiInfo(path.Join(gopath, "src", *mainApiFile))
	parser.ParseApi(*apiPackage)
	log.Println("Finish parsing")

	format := strings.ToLower(*outputFormat)
	switch format {
	case "go":
		generateSwaggerDocs(parser)
		log.Println("Doc file generated")
	case "asciidoc":
		markupAsciiDoc := new(MarkupAsciiDoc)
		generateMarkup(parser, markupAsciiDoc, ".adoc")
		log.Println("AsciiDoc file generated")
	case "markdown":
		// markupMarkdown := new(MarkupMarkdown)
		// generateMarkup(parser, markupMarkdown, ".md")
		// log.Println("Markdown file generated")
	case "swagger":
		generateSwaggerUiFiles(parser)
		log.Println("Swagger UI files generated")
	default:
		log.Fatalf("Invalid -format specified. Must be one of %v.", AVAILABLE_FORMATS)
	}

}
<|endoftext|>