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

package gaeconfig

import (
	"errors"
	"fmt"
	"net/url"
	"strconv"
	"strings"
	"time"

	"golang.org/x/net/context"

	"go.chromium.org/luci/server/settings"
)

// DefaultExpire is a reasonable default expiration value.
const DefaultExpire = 10 * time.Minute

// DSCacheMode is the datastore cache mode.
type DSCacheMode string

const (
	// DSCacheDisabled means that the datastore cache is disabled.
	DSCacheDisabled DSCacheMode = ""
	// DSCacheEnabled means that the datastore cache is enabled.
	//
	// When enabled, all requests that have cached entries will hit the cache,
	// regardless of whether the cache is stale or not. If the cache is stale,
	// a warning will be printed during fetch.
	DSCacheEnabled DSCacheMode = "Enabled"
)

// dsCacheDisabledSetting is the user-visible value for the "disabled" cache
// mode.
const dsCacheDisabledSetting = "Disabled"

// Settings are stored in the datastore via appengine/gaesettings package.
type Settings struct {
	// ConfigServiceHost is host name (and port) of the luci-config service to
	// fetch configs from.
	//
	// For legacy reasons, the JSON value is "config_service_url".
	ConfigServiceHost string `json:"config_service_url"`

	// CacheExpirationSec is how long to hold configs in local cache.
	CacheExpirationSec int `json:"cache_expiration_sec"`

	// DatastoreCacheMode, is the datastore caching mode.
	DatastoreCacheMode DSCacheMode `json:"datastore_enabled"`
}

// SetIfChanged sets "s" to be the new Settings if it differs from the current
// settings value.
func (s *Settings) SetIfChanged(c context.Context, who, why string) error {
	return settings.SetIfChanged(c, settingsKey, s, who, why)
}

// CacheExpiration returns a Duration representing the configured cache
// expiration. If the cache expiration is not configured, CacheExpiration
// will return 0.
func (s *Settings) CacheExpiration() time.Duration {
	if s.CacheExpirationSec > 0 {
		return time.Second * time.Duration(s.CacheExpirationSec)
	}
	return 0
}

// FetchCachedSettings fetches Settings from the settings store.
//
// Uses in-process global cache to avoid hitting datastore often. The cache
// expiration time is 1 min (see gaesettings.expirationTime), meaning
// the instance will refetch settings once a minute (blocking only one unlucky
// request to do so).
//
// Returns errors only if there's no cached value (i.e. it is the first call
// to this function in this process ever) and datastore operation fails.
func FetchCachedSettings(c context.Context) (Settings, error) {
	s := Settings{}
	switch err := settings.Get(c, settingsKey, &s); err {
	case nil:
		// Backwards-compatibility with full URL: translate to host.
		s.ConfigServiceHost = translateConfigURLToHost(s.ConfigServiceHost)
		return s, nil
	case settings.ErrNoSettings:
		return DefaultSettings(), nil
	default:
		return Settings{}, err
	}
}

func mustFetchCachedSettings(c context.Context) *Settings {
	settings, err := FetchCachedSettings(c)
	if err != nil {
		panic(err)
	}
	return &settings
}

// DefaultSettings returns Settings to use if setting store is empty.
func DefaultSettings() Settings {
	return Settings{
		CacheExpirationSec: int(DefaultExpire.Seconds()),
		DatastoreCacheMode: DSCacheDisabled,
	}
}

////////////////////////////////////////////////////////////////////////////////
// UI for settings.

// settingsKey is used internally to identify gaeconfig settings in settings
// store.
const settingsKey = "gaeconfig"

type settingsUIPage struct {
	settings.BaseUIPage
}

func (settingsUIPage) Title(c context.Context) (string, error) {
	return "Configuration service settings", nil
}

func (settingsUIPage) Fields(c context.Context) ([]settings.UIField, error) {
	return []settings.UIField{
		{
			ID:    "ConfigServiceHost",
			Title: `Config service host`,
			Type:  settings.UIFieldText,
			Validator: func(v string) error {
				// Validate that the host has a length, and has no forward slashes
				// in it.
				switch {
				case len(v) == 0:
					return fmt.Errorf("host cannot be empty")
				case strings.IndexRune(v, '/') >= 0:
					return fmt.Errorf("host must be a host name, not a URL")
				default:
					return nil
				}
			},
			Help: `<p>The application may fetch configuration files stored centrally ` +
				`in an instance of <a href="https://github.com/luci/luci-py/tree/master/appengine/config_service">luci-config</a> ` +
				`service. This is the host name (e.g., "example.com") of such service. For legacy purposes, this may be an ` +
				`URL, in which case the host component will be used. If you don't know what this is, you probably don't ` +
				`use it and can keep this setting blank.</p>`,
		},
		{
			ID:    "CacheExpirationSec",
			Title: "Cache expiration, sec",
			Type:  settings.UIFieldText,
			Validator: func(v string) error {
				if i, err := strconv.Atoi(v); err != nil || i < 0 {
					return errors.New("expecting a non-negative integer")
				}
				return nil
			},
			Help: `
For better performance configuration files fetched from remote
service are cached in memcache for specified amount of time. Set it to 0 to
disable local cache.
`,
		},
		{
			ID:    "DatastoreCacheMode",
			Title: "Enable datastore-backed config caching",
			Type:  settings.UIFieldChoice,
			ChoiceVariants: []string{
				dsCacheDisabledSetting,
				string(DSCacheEnabled),
			},
			Help: `
For better performance and resilience against configuration
service outages, the local datastore can be used as a backing cache. When
enabled, all configuration requests will be made against a cached configuration
in the datastore. This configuration will be updated periodically by an
independent cron job out of band with any user requests. See
gaemiddleware
package doc for instructions how to setup this cron job.
`,
		},
	}, nil
}

func (settingsUIPage) ReadSettings(c context.Context) (map[string]string, error) {
	s := DefaultSettings()
	err := settings.GetUncached(c, settingsKey, &s)
	if err != nil && err != settings.ErrNoSettings {
		return nil, err
	}

	// Translate the DSCacheMode into a user-readable string.
	var cacheModeString string
	switch s.DatastoreCacheMode {
	// Recognized modes.
	case DSCacheEnabled:
		cacheModeString = string(s.DatastoreCacheMode)

		// Any unrecognized mode translates to "disabled".
	case DSCacheDisabled:
		fallthrough
	default:
		cacheModeString = dsCacheDisabledSetting
	}

	return map[string]string{
		"ConfigServiceHost":  s.ConfigServiceHost,
		"CacheExpirationSec": strconv.Itoa(s.CacheExpirationSec),
		"DatastoreCacheMode": cacheModeString,
	}, nil
}

func (settingsUIPage) WriteSettings(c context.Context, values map[string]string, who, why string) error {
	dsMode := DSCacheMode(values["DatastoreCacheMode"])
	switch dsMode {
	case DSCacheEnabled: // Valid.

	// Any unrecognized mode translates to disabled.
	case dsCacheDisabledSetting:
		fallthrough
	default:
		dsMode = DSCacheDisabled
	}

	modified := Settings{
		ConfigServiceHost:  values["ConfigServiceHost"],
		DatastoreCacheMode: dsMode,
	}

	var err error
	modified.CacheExpirationSec, err = strconv.Atoi(values["CacheExpirationSec"])
	if err != nil {
		return err
	}

	// Backwards-compatibility with full URL: translate to host.
	modified.ConfigServiceHost = translateConfigURLToHost(modified.ConfigServiceHost)

	return modified.SetIfChanged(c, who, why)
}

func translateConfigURLToHost(v string) string {
	// If the host is a full URL, extract just the host component.
	switch u, err := url.Parse(v); {
	case err != nil:
		return v
	case u.Host != "":
		// If we have a host (e.g., "example.com"), this will parse into the "Path"
		// field with an empty host value. Therefore, if we have a "Host" value,
		// we will use it directly (e.g., "http://example.com")
		return u.Host
	case u.Path != "":
		// If this was just an empty (correct) host, it will have parsed into the
		// Path field with an empty Host value.
		return u.Path
	default:
		return v
	}
}

func init() {
	settings.RegisterUIPage(settingsKey, settingsUIPage{})
}
<commit_msg>config: Simplify using GAE luci config client on dev server a bit.<commit_after>// Copyright 2016 The LUCI Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package gaeconfig

import (
	"errors"
	"fmt"
	"net/url"
	"strconv"
	"strings"
	"time"

	"golang.org/x/net/context"

	"go.chromium.org/gae/service/info"
	"go.chromium.org/luci/server/settings"
)

// DefaultExpire is a reasonable default expiration value to use on prod GAE.
const DefaultExpire = 10 * time.Minute

// DSCacheMode is the datastore cache mode.
type DSCacheMode string

const (
	// DSCacheDisabled means that the datastore cache is disabled.
	DSCacheDisabled DSCacheMode = ""
	// DSCacheEnabled means that the datastore cache is enabled.
	//
	// When enabled, all requests that have cached entries will hit the cache,
	// regardless of whether the cache is stale or not. If the cache is stale,
	// a warning will be printed during fetch.
	DSCacheEnabled DSCacheMode = "Enabled"
)

// dsCacheDisabledSetting is the user-visible value for the "disabled" cache
// mode.
const dsCacheDisabledSetting = "Disabled"

// Settings are stored in the datastore via appengine/gaesettings package.
type Settings struct {
	// ConfigServiceHost is host name (and port) of the luci-config service to
	// fetch configs from.
	//
	// For legacy reasons, the JSON value is "config_service_url".
	ConfigServiceHost string `json:"config_service_url"`

	// CacheExpirationSec is how long to hold configs in local cache.
	CacheExpirationSec int `json:"cache_expiration_sec"`

	// DatastoreCacheMode, is the datastore caching mode.
	DatastoreCacheMode DSCacheMode `json:"datastore_enabled"`
}

// SetIfChanged sets "s" to be the new Settings if it differs from the current
// settings value.
func (s *Settings) SetIfChanged(c context.Context, who, why string) error {
	return settings.SetIfChanged(c, settingsKey, s, who, why)
}

// CacheExpiration returns a Duration representing the configured cache
// expiration. If the cache expiration is not configured, CacheExpiration
// will return 0.
func (s *Settings) CacheExpiration() time.Duration {
	if s.CacheExpirationSec > 0 {
		return time.Second * time.Duration(s.CacheExpirationSec)
	}
	return 0
}

// FetchCachedSettings fetches Settings from the settings store.
//
// Uses in-process global cache to avoid hitting datastore often. The cache
// expiration time is 1 min (see gaesettings.expirationTime), meaning
// the instance will refetch settings once a minute (blocking only one unlucky
// request to do so).
//
// Returns errors only if there's no cached value (i.e. it is the first call
// to this function in this process ever) and datastore operation fails.
func FetchCachedSettings(c context.Context) (Settings, error) {
	s := Settings{}
	switch err := settings.Get(c, settingsKey, &s); err {
	case nil:
		// Backwards-compatibility with full URL: translate to host.
		s.ConfigServiceHost = translateConfigURLToHost(s.ConfigServiceHost)
		return s, nil
	case settings.ErrNoSettings:
		return DefaultSettings(c), nil
	default:
		return Settings{}, err
	}
}

func mustFetchCachedSettings(c context.Context) *Settings {
	settings, err := FetchCachedSettings(c)
	if err != nil {
		panic(err)
	}
	return &settings
}

// DefaultSettings returns Settings to use if setting store is empty.
func DefaultSettings(c context.Context) Settings {
	// Disable local cache on devserver by default to allows changes to local
	// configs to propagate instantly. This is usually preferred when developing
	// locally.
	exp := 0
	if !info.IsDevAppServer(c) {
		exp = int(DefaultExpire.Seconds())
	}
	return Settings{
		CacheExpirationSec: exp,
		DatastoreCacheMode: DSCacheDisabled,
	}
}

////////////////////////////////////////////////////////////////////////////////
// UI for settings.

// settingsKey is used internally to identify gaeconfig settings in settings
// store.
const settingsKey = "gaeconfig"

type settingsUIPage struct {
	settings.BaseUIPage
}

func (settingsUIPage) Title(c context.Context) (string, error) {
	return "Configuration service settings", nil
}

func (settingsUIPage) Fields(c context.Context) ([]settings.UIField, error) {
	return []settings.UIField{
		{
			ID:    "ConfigServiceHost",
			Title: `Config service host`,
			Type:  settings.UIFieldText,
			Validator: func(v string) error {
				if strings.ContainsRune(v, '/') {
					return fmt.Errorf("host must be a host name, not a URL")
				}
				return nil
			},
			Help: `<p>The application may fetch configuration files stored centrally ` +
				`in an instance of <a href="https://chromium.googlesource.com/infra/luci/luci-py/+/master/appengine/config_service">luci-config</a> ` +
				`service. This is the host name (e.g., "example.com") of such service. For legacy purposes, this may be an ` +
				`URL, in which case the host component will be used. If you don't know what this is, you probably don't ` +
				`use it and can keep this setting blank.</p>`,
		},
		{
			ID:    "CacheExpirationSec",
			Title: "Cache expiration, sec",
			Type:  settings.UIFieldText,
			Validator: func(v string) error {
				if i, err := strconv.Atoi(v); err != nil || i < 0 {
					return errors.New("expecting a non-negative integer")
				}
				return nil
			},
			Help: `
For better performance configuration files fetched from remote
service are cached in memcache for specified amount of time. Set it to 0 to
disable local cache.
`,
		},
		{
			ID:    "DatastoreCacheMode",
			Title: "Enable datastore-backed config caching",
			Type:  settings.UIFieldChoice,
			ChoiceVariants: []string{
				dsCacheDisabledSetting,
				string(DSCacheEnabled),
			},
			Help: `
For better performance and resilience against configuration
service outages, the local datastore can be used as a backing cache. When
enabled, all configuration requests will be made against a cached configuration
in the datastore. This configuration will be updated periodically by an
independent cron job out of band with any user requests. See
gaemiddleware
package doc for instructions how to setup this cron job.
`,
		},
	}, nil
}

func (settingsUIPage) ReadSettings(c context.Context) (map[string]string, error) {
	s := DefaultSettings(c)
	err := settings.GetUncached(c, settingsKey, &s)
	if err != nil && err != settings.ErrNoSettings {
		return nil, err
	}

	// Translate the DSCacheMode into a user-readable string.
	var cacheModeString string
	switch s.DatastoreCacheMode {
	// Recognized modes.
	case DSCacheEnabled:
		cacheModeString = string(s.DatastoreCacheMode)

		// Any unrecognized mode translates to "disabled".
	case DSCacheDisabled:
		fallthrough
	default:
		cacheModeString = dsCacheDisabledSetting
	}

	return map[string]string{
		"ConfigServiceHost":  s.ConfigServiceHost,
		"CacheExpirationSec": strconv.Itoa(s.CacheExpirationSec),
		"DatastoreCacheMode": cacheModeString,
	}, nil
}

func (settingsUIPage) WriteSettings(c context.Context, values map[string]string, who, why string) error {
	dsMode := DSCacheMode(values["DatastoreCacheMode"])
	switch dsMode {
	case DSCacheEnabled: // Valid.

	// Any unrecognized mode translates to disabled.
	case dsCacheDisabledSetting:
		fallthrough
	default:
		dsMode = DSCacheDisabled
	}

	modified := Settings{
		ConfigServiceHost:  values["ConfigServiceHost"],
		DatastoreCacheMode: dsMode,
	}

	var err error
	modified.CacheExpirationSec, err = strconv.Atoi(values["CacheExpirationSec"])
	if err != nil {
		return err
	}

	// Backwards-compatibility with full URL: translate to host.
	modified.ConfigServiceHost = translateConfigURLToHost(modified.ConfigServiceHost)

	return modified.SetIfChanged(c, who, why)
}

func translateConfigURLToHost(v string) string {
	// If the host is a full URL, extract just the host component.
	switch u, err := url.Parse(v); {
	case err != nil:
		return v
	case u.Host != "":
		// If we have a host (e.g., "example.com"), this will parse into the "Path"
		// field with an empty host value. Therefore, if we have a "Host" value,
		// we will use it directly (e.g., "http://example.com")
		return u.Host
	case u.Path != "":
		// If this was just an empty (correct) host, it will have parsed into the
		// Path field with an empty Host value.
		return u.Path
	default:
		return v
	}
}

func init() {
	settings.RegisterUIPage(settingsKey, settingsUIPage{})
}
<|endoftext|>


<commit_before>// Copyright 2015 Google Inc. All Rights Reserved.
// Author: jacobsa@google.com (Aaron Jacobs)

package gcsfake

import (
	"errors"
	"io"
	"sort"
	"sync"

	"github.com/jacobsa/gcloud/gcs"
	"golang.org/x/net/context"
	"google.golang.org/cloud/storage"
)

// Create an in-memory bucket with the given name and empty contents.
func NewFakeBucket(name string) gcs.Bucket {
	return &bucket{name: name}
}

type object struct {
	// A storage.Object representing metadata for this object. Never changes.
	metadata *storage.Object

	// The contents of the object. These never change.
	contents []byte
}

// A slice of objects compared by name.
type objectSlice []object

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

// Return the smallest i such that s[i].metadata.Name >= name, or len(s) if
// there is no such i.
func (s objectSlice) lowerBound(name string) int {
	pred := func(i int) bool {
		return s[i].metadata.Name >= name
	}

	return sort.Search(len(s), pred)
}

// Return the smallest i such that s[i].metadata.Name == name, or len(s) if
// there is no such i.
func (s objectSlice) find(name string) int {
	lb := s.lowerBound(name)
	if lb < len(s) && s[lb].metadata.Name == name {
		return lb
	}

	return len(s)
}

type bucket struct {
	name string
	mu   sync.RWMutex

	// The set of extant objects.
	//
	// INVARIANT: Strictly increasing.
	objects objectSlice // GUARDED_BY(mu)
}

func (b *bucket) Name() string {
	return b.name
}

// LOCKS_EXCLUDED(b.mu)
func (b *bucket) ListObjects(
	ctx context.Context,
	query *storage.Query) (listing *storage.Objects, err error) {
	b.mu.RLock()
	defer b.mu.RUnlock()

	// Set up the result object.
	listing = new(storage.Objects)

	// Handle nil queries.
	if query == nil {
		query = &storage.Query{}
	}

	// Handle defaults.
	maxResults := query.MaxResults
	if maxResults == 0 {
		maxResults = 1000
	}

	// Find where in the space of object names to start.
	nameStart := query.Prefix
	if query.Cursor != "" && query.Cursor > nameStart {
		nameStart = query.Cursor
	}

	// Find the range of indexes within the array to scan.
	indexStart := b.objects.lowerBound(nameStart)
	indexLimit := minInt(len(b.objects), indexStart+maxResults)

	// Scan the array.
	for i := indexStart; i < indexLimit; i++ {
		var o object = b.objects[i]

		// TODO(jacobsa): Handle prefixes.
		listing.Results = append(listing.Results, o.metadata)
	}

	// Set up a cursor for where to start the next scan if we didn't exhaust the
	// results.
	if indexLimit < len(b.objects) {
		listing.Next = &storage.Query{}
		*listing.Next = *query
		listing.Next.Cursor = b.objects[indexLimit].metadata.Name
	}

	return
}

func (b *bucket) NewReader(
	ctx context.Context,
	objectName string) (io.ReadCloser, error) {
	return nil, errors.New("TODO: Implement NewReader.")
}

func (b *bucket) NewWriter(
	ctx context.Context,
	attrs *storage.ObjectAttrs) (gcs.ObjectWriter, error) {
	return newObjectWriter(b, attrs), nil
}

// LOCKS_EXCLUDED(b.mu)
func (b *bucket) DeleteObject(
	ctx context.Context,
	name string) error {
	b.mu.Lock()
	defer b.mu.Unlock()

	// Do we possess the object with the given name?
	index := b.objects.find(name)
	if index == len(b.objects) {
		return errors.New("Object not found.")
	}

	// Remove the object.
	b.objects = append(b.objects[:index], b.objects[index+1:]...)

	return nil
}

// Create an object struct for the given attributes and contents.
//
// EXCLUSIVE_LOCKS_REQUIRED(b.mu)
func (b *bucket) mintObject(
	attrs *storage.ObjectAttrs,
	contents []byte) (o object) {
	// Set up metadata.
	// TODO(jacobsa): Other fields.
	o.metadata = &storage.Object{
		Bucket: b.Name(),
		Name:   attrs.Name,
	}

	// Set up contents.
	o.contents = contents

	return
}

// Add a record for an object with the given attributes and contents, then
// return the minted metadata.
//
// LOCKS_EXCLUDED(b.mu)
func (b *bucket) addObject(
	attrs *storage.ObjectAttrs,
	contents []byte) *storage.Object {
	b.mu.Lock()
	defer b.mu.Unlock()

	// Create an object record from the given attributes.
	var o object = b.mintObject(attrs, contents)

	// Add it to our list of object.
	b.objects = append(b.objects, o)
	sort.Sort(b.objects)

	return o.metadata
}

func minInt(a, b int) int {
	if a < b {
		return a
	}

	return b
}

func maxInt(a, b int) int {
	if a > b {
		return a
	}

	return b
}
<commit_msg>Added some more metadata.<commit_after>// Copyright 2015 Google Inc. All Rights Reserved.
// Author: jacobsa@google.com (Aaron Jacobs)

package gcsfake

import (
	"errors"
	"io"
	"sort"
	"sync"

	"github.com/jacobsa/gcloud/gcs"
	"golang.org/x/net/context"
	"google.golang.org/cloud/storage"
)

// Create an in-memory bucket with the given name and empty contents.
func NewFakeBucket(name string) gcs.Bucket {
	return &bucket{name: name}
}

type object struct {
	// A storage.Object representing metadata for this object. Never changes.
	metadata *storage.Object

	// The contents of the object. These never change.
	contents []byte
}

// A slice of objects compared by name.
type objectSlice []object

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

// Return the smallest i such that s[i].metadata.Name >= name, or len(s) if
// there is no such i.
func (s objectSlice) lowerBound(name string) int {
	pred := func(i int) bool {
		return s[i].metadata.Name >= name
	}

	return sort.Search(len(s), pred)
}

// Return the smallest i such that s[i].metadata.Name == name, or len(s) if
// there is no such i.
func (s objectSlice) find(name string) int {
	lb := s.lowerBound(name)
	if lb < len(s) && s[lb].metadata.Name == name {
		return lb
	}

	return len(s)
}

type bucket struct {
	name string
	mu   sync.RWMutex

	// The set of extant objects.
	//
	// INVARIANT: Strictly increasing.
	objects objectSlice // GUARDED_BY(mu)
}

func (b *bucket) Name() string {
	return b.name
}

// LOCKS_EXCLUDED(b.mu)
func (b *bucket) ListObjects(
	ctx context.Context,
	query *storage.Query) (listing *storage.Objects, err error) {
	b.mu.RLock()
	defer b.mu.RUnlock()

	// Set up the result object.
	listing = new(storage.Objects)

	// Handle nil queries.
	if query == nil {
		query = &storage.Query{}
	}

	// Handle defaults.
	maxResults := query.MaxResults
	if maxResults == 0 {
		maxResults = 1000
	}

	// Find where in the space of object names to start.
	nameStart := query.Prefix
	if query.Cursor != "" && query.Cursor > nameStart {
		nameStart = query.Cursor
	}

	// Find the range of indexes within the array to scan.
	indexStart := b.objects.lowerBound(nameStart)
	indexLimit := minInt(len(b.objects), indexStart+maxResults)

	// Scan the array.
	for i := indexStart; i < indexLimit; i++ {
		var o object = b.objects[i]

		// TODO(jacobsa): Handle prefixes.
		listing.Results = append(listing.Results, o.metadata)
	}

	// Set up a cursor for where to start the next scan if we didn't exhaust the
	// results.
	if indexLimit < len(b.objects) {
		listing.Next = &storage.Query{}
		*listing.Next = *query
		listing.Next.Cursor = b.objects[indexLimit].metadata.Name
	}

	return
}

func (b *bucket) NewReader(
	ctx context.Context,
	objectName string) (io.ReadCloser, error) {
	return nil, errors.New("TODO: Implement NewReader.")
}

func (b *bucket) NewWriter(
	ctx context.Context,
	attrs *storage.ObjectAttrs) (gcs.ObjectWriter, error) {
	return newObjectWriter(b, attrs), nil
}

// LOCKS_EXCLUDED(b.mu)
func (b *bucket) DeleteObject(
	ctx context.Context,
	name string) error {
	b.mu.Lock()
	defer b.mu.Unlock()

	// Do we possess the object with the given name?
	index := b.objects.find(name)
	if index == len(b.objects) {
		return errors.New("Object not found.")
	}

	// Remove the object.
	b.objects = append(b.objects[:index], b.objects[index+1:]...)

	return nil
}

// Create an object struct for the given attributes and contents.
//
// EXCLUSIVE_LOCKS_REQUIRED(b.mu)
func (b *bucket) mintObject(
	attrs *storage.ObjectAttrs,
	contents []byte) (o object) {
	// Set up metadata.
	// TODO(jacobsa): Other fields.
	o.metadata = &storage.Object{
		Bucket:   b.Name(),
		Name:     attrs.Name,
		Owner:    "user-fake",
		Size:     int64(len(contents)),
		Metadata: attrs.Metadata,
	}

	// Set up contents.
	o.contents = contents

	return
}

// Add a record for an object with the given attributes and contents, then
// return the minted metadata.
//
// LOCKS_EXCLUDED(b.mu)
func (b *bucket) addObject(
	attrs *storage.ObjectAttrs,
	contents []byte) *storage.Object {
	b.mu.Lock()
	defer b.mu.Unlock()

	// Create an object record from the given attributes.
	var o object = b.mintObject(attrs, contents)

	// Add it to our list of object.
	b.objects = append(b.objects, o)
	sort.Sort(b.objects)

	return o.metadata
}

func minInt(a, b int) int {
	if a < b {
		return a
	}

	return b
}

func maxInt(a, b int) int {
	if a > b {
		return a
	}

	return b
}
<|endoftext|>


<commit_before>package torrent

import (
	"bytes"
	"encoding/binary"
	"fmt"
	"log"
	"math/rand"
	"net"
	"net/url"
	"strconv"
	"time"
)

// Code to talk to trackers.
// Implements BEP 12 Multitracker Metadata Extension

type ClientStatusReport struct {
	Event      string
	InfoHash   string
	PeerId     string
	Port       uint16
	Uploaded   uint64
	Downloaded uint64
	Left       uint64
}

func startTrackerClient(announce string, announceList [][]string, trackerInfoChan chan *TrackerResponse, reports chan ClientStatusReport) {
	if announce != "" && announceList == nil {
		// Convert the plain announce into an announceList to simplify logic
		announceList = [][]string{[]string{announce}}
	}

	if announceList != nil {
		announceList = shuffleAnnounceList(announceList)
	}

	go func() {
		for report := range reports {
			tr := queryTrackers(announceList, report)
			if tr != nil {
				trackerInfoChan <- tr
			}
		}
	}()
}

// Deep copy announcelist and shuffle each level.
func shuffleAnnounceList(announceList [][]string) (result [][]string) {
	result = make([][]string, len(announceList))
	for i, level := range announceList {
		result[i] = shuffleAnnounceListLevel(level)
	}
	return
}

func shuffleAnnounceListLevel(level []string) (shuffled []string) {
	items := len(level)
	shuffled = make([]string, items)
	perm := rand.Perm(items)
	for i, v := range perm {
		shuffled[v] = level[i]
	}
	return
}

func queryTrackers(announceList [][]string, report ClientStatusReport) (tr *TrackerResponse) {
	for _, level := range announceList {
		for i, tracker := range level {
			var err error
			tr, err = queryTracker(report, tracker)
			if err == nil {
				// Move successful tracker to front of slice for next announcement
				// cycle.
				copy(level[1:i+1], level[0:i])
				level[0] = tracker
				return
			}
		}
	}
	log.Println("Error: Did not successfully contact a tracker:", announceList)
	return
}

func queryTracker(report ClientStatusReport, trackerUrl string) (tr *TrackerResponse, err error) {
	u, err := url.Parse(trackerUrl)
	if err != nil {
		log.Println("Error: Invalid announce URL(", trackerUrl, "):", err)
		return
	}
	switch u.Scheme {
	case "http":
		return queryHTTPTracker(report, u)
	case "udp":
		return queryUDPTracker(report, u)
	default:
		return nil, fmt.Errorf("Unknown scheme %v in %v", u.Scheme, trackerUrl)
	}
}

func queryHTTPTracker(report ClientStatusReport, u *url.URL) (tr *TrackerResponse, err error) {
	uq := u.Query()
	uq.Add("info_hash", report.InfoHash)
	uq.Add("peer_id", report.PeerId)
	uq.Add("port", strconv.FormatUint(uint64(report.Port), 10))
	uq.Add("uploaded", strconv.FormatUint(report.Uploaded, 10))
	uq.Add("downloaded", strconv.FormatUint(report.Downloaded, 10))
	uq.Add("left", strconv.FormatUint(report.Left, 10))
	uq.Add("compact", "1")

	// Don't report IPv6 address, the user might prefer to keep
	// that information private when communicating with IPv4 hosts.
	if false {
		ipv6Address, err := findLocalIPV6AddressFor(u.Host)
		if err == nil {
			log.Println("our ipv6", ipv6Address)
			uq.Add("ipv6", ipv6Address)
		}
	}

	if report.Event != "" {
		uq.Add("event", report.Event)
	}

	// This might reorder the existing query string in the Announce url
	// This might break some broken trackers that don't parse URLs properly.

	u.RawQuery = uq.Encode()

	tr, err = getTrackerInfo(u.String())
	if tr == nil || err != nil {
		log.Println("Error: Could not fetch tracker info:", err)
	} else if tr.FailureReason != "" {
		log.Println("Error: Tracker returned failure reason:", tr.FailureReason)
		err = fmt.Errorf("tracker failure %s", tr.FailureReason)
	}
	return
}

func findLocalIPV6AddressFor(hostAddr string) (local string, err error) {
	// Figure out our IPv6 address to talk to a given host.
	host, hostPort, err := net.SplitHostPort(hostAddr)
	if err != nil {
		host = hostAddr
		hostPort = "1234"
	}
	dummyAddr := net.JoinHostPort(host, hostPort)
	log.Println("Looking for host ", dummyAddr)
	conn, err := net.Dial("udp6", dummyAddr)
	if err != nil {
		log.Println("No IPV6 for host ", host, err)
		return "", err
	}
	defer conn.Close()
	localAddr := conn.LocalAddr()
	local, _, err = net.SplitHostPort(localAddr.String())
	if err != nil {
		local = localAddr.String()
	}
	return
}

func queryUDPTracker(report ClientStatusReport, u *url.URL) (tr *TrackerResponse, err error) {
	serverAddr, err := net.ResolveUDPAddr("udp", u.Host)
	if err != nil {
		return
	}
	con, err := net.DialUDP("udp", nil, serverAddr)
	if err != nil {
		return
	}
	defer func() { con.Close() }()

	var connectionID uint64
	for retry := uint(0); retry < uint(8); retry++ {
		err = con.SetDeadline(time.Now().Add(15 * (1 << retry) * time.Second))
		if err != nil {
			return
		}

		connectionID, err = connectToUDPTracker(con)
		if err == nil {
			break
		}
		if nerr, ok := err.(net.Error); ok && nerr.Timeout() {
			continue
		}
		if err != nil {
			return
		}
	}

	return getAnnouncementFromUDPTracker(con, connectionID, report)
}

func connectToUDPTracker(con *net.UDPConn) (connectionID uint64, err error) {
	var connectionRequest_connectionID uint64 = 0x41727101980
	var action uint32 = 0
	transactionID := rand.Uint32()

	connectionRequest := new(bytes.Buffer)
	err = binary.Write(connectionRequest, binary.BigEndian, connectionRequest_connectionID)
	if err != nil {
		return
	}
	err = binary.Write(connectionRequest, binary.BigEndian, action)
	if err != nil {
		return
	}
	err = binary.Write(connectionRequest, binary.BigEndian, transactionID)
	if err != nil {
		return
	}

	_, err = con.Write(connectionRequest.Bytes())
	if err != nil {
		return
	}

	connectionResponseBytes := make([]byte, 16)

	var connectionResponseLen int
	connectionResponseLen, err = con.Read(connectionResponseBytes)
	if err != nil {
		return
	}
	if connectionResponseLen != 16 {
		err = fmt.Errorf("Unexpected response size %d", connectionResponseLen)
		return
	}
	connectionResponse := bytes.NewBuffer(connectionResponseBytes)
	var connectionResponseAction uint32
	err = binary.Read(connectionResponse, binary.BigEndian, &connectionResponseAction)
	if err != nil {
		return
	}
	if connectionResponseAction != 0 {
		err = fmt.Errorf("Unexpected response action %d", connectionResponseAction)
		return
	}
	var connectionResponseTransactionID uint32
	err = binary.Read(connectionResponse, binary.BigEndian, &connectionResponseTransactionID)
	if err != nil {
		return
	}
	if connectionResponseTransactionID != transactionID {
		err = fmt.Errorf("Unexpected response transactionID %x != %x",
			connectionResponseTransactionID, transactionID)
		return
	}

	err = binary.Read(connectionResponse, binary.BigEndian, &connectionID)
	if err != nil {
		return
	}
	return
}

func getAnnouncementFromUDPTracker(con *net.UDPConn, connectionID uint64, report ClientStatusReport) (tr *TrackerResponse, err error) {
	transactionID := rand.Uint32()

	announcementRequest := new(bytes.Buffer)
	err = binary.Write(announcementRequest, binary.BigEndian, connectionID)
	if err != nil {
		return
	}
	var action uint32 = 1
	err = binary.Write(announcementRequest, binary.BigEndian, action)
	if err != nil {
		return
	}
	err = binary.Write(announcementRequest, binary.BigEndian, transactionID)
	if err != nil {
		return
	}
	err = binary.Write(announcementRequest, binary.BigEndian, []byte(report.InfoHash))
	if err != nil {
		return
	}
	err = binary.Write(announcementRequest, binary.BigEndian, []byte(report.PeerId))
	if err != nil {
		return
	}
	err = binary.Write(announcementRequest, binary.BigEndian, report.Downloaded)
	if err != nil {
		return
	}
	err = binary.Write(announcementRequest, binary.BigEndian, report.Left)
	if err != nil {
		return
	}
	err = binary.Write(announcementRequest, binary.BigEndian, report.Uploaded)
	if err != nil {
		return
	}
	var event uint32 = 0
	switch report.Event {
	case "":
		event = 0
	case "completed":
		event = 1
	case "started":
		event = 2
	case "stopped":
		event = 3
	default:
		err = fmt.Errorf("Unknown event string %v", report.Event)
		return
	}
	err = binary.Write(announcementRequest, binary.BigEndian, event)
	if err != nil {
		return
	}
	var ipAddress uint32 = 0
	err = binary.Write(announcementRequest, binary.BigEndian, ipAddress)
	if err != nil {
		return
	}
	var key uint32 = 0
	err = binary.Write(announcementRequest, binary.BigEndian, key)
	if err != nil {
		return
	}

	const peerRequestCount = 10
	var numWant uint32 = peerRequestCount
	err = binary.Write(announcementRequest, binary.BigEndian, numWant)
	if err != nil {
		return
	}
	err = binary.Write(announcementRequest, binary.BigEndian, report.Port)
	if err != nil {
		return
	}

	_, err = con.Write(announcementRequest.Bytes())
	if err != nil {
		return
	}

	const minimumResponseLen = 20
	const peerDataSize = 6
	expectedResponseLen := minimumResponseLen + peerDataSize*peerRequestCount
	responseBytes := make([]byte, expectedResponseLen)

	var responseLen int
	responseLen, err = con.Read(responseBytes)
	if err != nil {
		return
	}
	if responseLen < minimumResponseLen {
		err = fmt.Errorf("Unexpected response size %d", responseLen)
		return
	}
	response := bytes.NewBuffer(responseBytes)
	var responseAction uint32
	err = binary.Read(response, binary.BigEndian, &responseAction)
	if err != nil {
		return
	}
	if action != 1 {
		err = fmt.Errorf("Unexpected response action %d", action)
		return
	}
	var responseTransactionID uint32
	err = binary.Read(response, binary.BigEndian, &responseTransactionID)
	if err != nil {
		return
	}
	if transactionID != responseTransactionID {
		err = fmt.Errorf("Unexpected response transactionID %x", responseTransactionID)
		return
	}
	var interval uint32
	err = binary.Read(response, binary.BigEndian, &interval)
	if err != nil {
		return
	}
	var leechers uint32
	err = binary.Read(response, binary.BigEndian, &leechers)
	if err != nil {
		return
	}
	var seeders uint32
	err = binary.Read(response, binary.BigEndian, &seeders)
	if err != nil {
		return
	}

	peerCount := (responseLen - minimumResponseLen) / peerDataSize
	peerDataBytes := make([]byte, peerDataSize*peerCount)
	err = binary.Read(response, binary.BigEndian, &peerDataBytes)
	if err != nil {
		return
	}

	tr = &TrackerResponse{
		Interval:   uint(interval),
		Complete:   uint(seeders),
		Incomplete: uint(leechers),
		Peers:      string(peerDataBytes)}
	return
}
<commit_msg>Allow https trackers.<commit_after>package torrent

import (
	"bytes"
	"encoding/binary"
	"fmt"
	"log"
	"math/rand"
	"net"
	"net/url"
	"strconv"
	"time"
)

// Code to talk to trackers.
// Implements BEP 12 Multitracker Metadata Extension

type ClientStatusReport struct {
	Event      string
	InfoHash   string
	PeerId     string
	Port       uint16
	Uploaded   uint64
	Downloaded uint64
	Left       uint64
}

func startTrackerClient(announce string, announceList [][]string, trackerInfoChan chan *TrackerResponse, reports chan ClientStatusReport) {
	if announce != "" && announceList == nil {
		// Convert the plain announce into an announceList to simplify logic
		announceList = [][]string{[]string{announce}}
	}

	if announceList != nil {
		announceList = shuffleAnnounceList(announceList)
	}

	go func() {
		for report := range reports {
			tr := queryTrackers(announceList, report)
			if tr != nil {
				trackerInfoChan <- tr
			}
		}
	}()
}

// Deep copy announcelist and shuffle each level.
func shuffleAnnounceList(announceList [][]string) (result [][]string) {
	result = make([][]string, len(announceList))
	for i, level := range announceList {
		result[i] = shuffleAnnounceListLevel(level)
	}
	return
}

func shuffleAnnounceListLevel(level []string) (shuffled []string) {
	items := len(level)
	shuffled = make([]string, items)
	perm := rand.Perm(items)
	for i, v := range perm {
		shuffled[v] = level[i]
	}
	return
}

func queryTrackers(announceList [][]string, report ClientStatusReport) (tr *TrackerResponse) {
	for _, level := range announceList {
		for i, tracker := range level {
			var err error
			tr, err = queryTracker(report, tracker)
			if err == nil {
				// Move successful tracker to front of slice for next announcement
				// cycle.
				copy(level[1:i+1], level[0:i])
				level[0] = tracker
				return
			}
		}
	}
	log.Println("Error: Did not successfully contact a tracker:", announceList)
	return
}

func queryTracker(report ClientStatusReport, trackerUrl string) (tr *TrackerResponse, err error) {
	u, err := url.Parse(trackerUrl)
	if err != nil {
		log.Println("Error: Invalid announce URL(", trackerUrl, "):", err)
		return
	}
	switch u.Scheme {
	case "http":
		fallthrough
	case "https":
		return queryHTTPTracker(report, u)
	case "udp":
		return queryUDPTracker(report, u)
	default:
		return nil, fmt.Errorf("Unknown scheme %v in %v", u.Scheme, trackerUrl)
	}
}

func queryHTTPTracker(report ClientStatusReport, u *url.URL) (tr *TrackerResponse, err error) {
	uq := u.Query()
	uq.Add("info_hash", report.InfoHash)
	uq.Add("peer_id", report.PeerId)
	uq.Add("port", strconv.FormatUint(uint64(report.Port), 10))
	uq.Add("uploaded", strconv.FormatUint(report.Uploaded, 10))
	uq.Add("downloaded", strconv.FormatUint(report.Downloaded, 10))
	uq.Add("left", strconv.FormatUint(report.Left, 10))
	uq.Add("compact", "1")

	// Don't report IPv6 address, the user might prefer to keep
	// that information private when communicating with IPv4 hosts.
	if false {
		ipv6Address, err := findLocalIPV6AddressFor(u.Host)
		if err == nil {
			log.Println("our ipv6", ipv6Address)
			uq.Add("ipv6", ipv6Address)
		}
	}

	if report.Event != "" {
		uq.Add("event", report.Event)
	}

	// This might reorder the existing query string in the Announce url
	// This might break some broken trackers that don't parse URLs properly.

	u.RawQuery = uq.Encode()

	tr, err = getTrackerInfo(u.String())
	if tr == nil || err != nil {
		log.Println("Error: Could not fetch tracker info:", err)
	} else if tr.FailureReason != "" {
		log.Println("Error: Tracker returned failure reason:", tr.FailureReason)
		err = fmt.Errorf("tracker failure %s", tr.FailureReason)
	}
	return
}

func findLocalIPV6AddressFor(hostAddr string) (local string, err error) {
	// Figure out our IPv6 address to talk to a given host.
	host, hostPort, err := net.SplitHostPort(hostAddr)
	if err != nil {
		host = hostAddr
		hostPort = "1234"
	}
	dummyAddr := net.JoinHostPort(host, hostPort)
	log.Println("Looking for host ", dummyAddr)
	conn, err := net.Dial("udp6", dummyAddr)
	if err != nil {
		log.Println("No IPV6 for host ", host, err)
		return "", err
	}
	defer conn.Close()
	localAddr := conn.LocalAddr()
	local, _, err = net.SplitHostPort(localAddr.String())
	if err != nil {
		local = localAddr.String()
	}
	return
}

func queryUDPTracker(report ClientStatusReport, u *url.URL) (tr *TrackerResponse, err error) {
	serverAddr, err := net.ResolveUDPAddr("udp", u.Host)
	if err != nil {
		return
	}
	con, err := net.DialUDP("udp", nil, serverAddr)
	if err != nil {
		return
	}
	defer func() { con.Close() }()

	var connectionID uint64
	for retry := uint(0); retry < uint(8); retry++ {
		err = con.SetDeadline(time.Now().Add(15 * (1 << retry) * time.Second))
		if err != nil {
			return
		}

		connectionID, err = connectToUDPTracker(con)
		if err == nil {
			break
		}
		if nerr, ok := err.(net.Error); ok && nerr.Timeout() {
			continue
		}
		if err != nil {
			return
		}
	}

	return getAnnouncementFromUDPTracker(con, connectionID, report)
}

func connectToUDPTracker(con *net.UDPConn) (connectionID uint64, err error) {
	var connectionRequest_connectionID uint64 = 0x41727101980
	var action uint32 = 0
	transactionID := rand.Uint32()

	connectionRequest := new(bytes.Buffer)
	err = binary.Write(connectionRequest, binary.BigEndian, connectionRequest_connectionID)
	if err != nil {
		return
	}
	err = binary.Write(connectionRequest, binary.BigEndian, action)
	if err != nil {
		return
	}
	err = binary.Write(connectionRequest, binary.BigEndian, transactionID)
	if err != nil {
		return
	}

	_, err = con.Write(connectionRequest.Bytes())
	if err != nil {
		return
	}

	connectionResponseBytes := make([]byte, 16)

	var connectionResponseLen int
	connectionResponseLen, err = con.Read(connectionResponseBytes)
	if err != nil {
		return
	}
	if connectionResponseLen != 16 {
		err = fmt.Errorf("Unexpected response size %d", connectionResponseLen)
		return
	}
	connectionResponse := bytes.NewBuffer(connectionResponseBytes)
	var connectionResponseAction uint32
	err = binary.Read(connectionResponse, binary.BigEndian, &connectionResponseAction)
	if err != nil {
		return
	}
	if connectionResponseAction != 0 {
		err = fmt.Errorf("Unexpected response action %d", connectionResponseAction)
		return
	}
	var connectionResponseTransactionID uint32
	err = binary.Read(connectionResponse, binary.BigEndian, &connectionResponseTransactionID)
	if err != nil {
		return
	}
	if connectionResponseTransactionID != transactionID {
		err = fmt.Errorf("Unexpected response transactionID %x != %x",
			connectionResponseTransactionID, transactionID)
		return
	}

	err = binary.Read(connectionResponse, binary.BigEndian, &connectionID)
	if err != nil {
		return
	}
	return
}

func getAnnouncementFromUDPTracker(con *net.UDPConn, connectionID uint64, report ClientStatusReport) (tr *TrackerResponse, err error) {
	transactionID := rand.Uint32()

	announcementRequest := new(bytes.Buffer)
	err = binary.Write(announcementRequest, binary.BigEndian, connectionID)
	if err != nil {
		return
	}
	var action uint32 = 1
	err = binary.Write(announcementRequest, binary.BigEndian, action)
	if err != nil {
		return
	}
	err = binary.Write(announcementRequest, binary.BigEndian, transactionID)
	if err != nil {
		return
	}
	err = binary.Write(announcementRequest, binary.BigEndian, []byte(report.InfoHash))
	if err != nil {
		return
	}
	err = binary.Write(announcementRequest, binary.BigEndian, []byte(report.PeerId))
	if err != nil {
		return
	}
	err = binary.Write(announcementRequest, binary.BigEndian, report.Downloaded)
	if err != nil {
		return
	}
	err = binary.Write(announcementRequest, binary.BigEndian, report.Left)
	if err != nil {
		return
	}
	err = binary.Write(announcementRequest, binary.BigEndian, report.Uploaded)
	if err != nil {
		return
	}
	var event uint32 = 0
	switch report.Event {
	case "":
		event = 0
	case "completed":
		event = 1
	case "started":
		event = 2
	case "stopped":
		event = 3
	default:
		err = fmt.Errorf("Unknown event string %v", report.Event)
		return
	}
	err = binary.Write(announcementRequest, binary.BigEndian, event)
	if err != nil {
		return
	}
	var ipAddress uint32 = 0
	err = binary.Write(announcementRequest, binary.BigEndian, ipAddress)
	if err != nil {
		return
	}
	var key uint32 = 0
	err = binary.Write(announcementRequest, binary.BigEndian, key)
	if err != nil {
		return
	}

	const peerRequestCount = 10
	var numWant uint32 = peerRequestCount
	err = binary.Write(announcementRequest, binary.BigEndian, numWant)
	if err != nil {
		return
	}
	err = binary.Write(announcementRequest, binary.BigEndian, report.Port)
	if err != nil {
		return
	}

	_, err = con.Write(announcementRequest.Bytes())
	if err != nil {
		return
	}

	const minimumResponseLen = 20
	const peerDataSize = 6
	expectedResponseLen := minimumResponseLen + peerDataSize*peerRequestCount
	responseBytes := make([]byte, expectedResponseLen)

	var responseLen int
	responseLen, err = con.Read(responseBytes)
	if err != nil {
		return
	}
	if responseLen < minimumResponseLen {
		err = fmt.Errorf("Unexpected response size %d", responseLen)
		return
	}
	response := bytes.NewBuffer(responseBytes)
	var responseAction uint32
	err = binary.Read(response, binary.BigEndian, &responseAction)
	if err != nil {
		return
	}
	if action != 1 {
		err = fmt.Errorf("Unexpected response action %d", action)
		return
	}
	var responseTransactionID uint32
	err = binary.Read(response, binary.BigEndian, &responseTransactionID)
	if err != nil {
		return
	}
	if transactionID != responseTransactionID {
		err = fmt.Errorf("Unexpected response transactionID %x", responseTransactionID)
		return
	}
	var interval uint32
	err = binary.Read(response, binary.BigEndian, &interval)
	if err != nil {
		return
	}
	var leechers uint32
	err = binary.Read(response, binary.BigEndian, &leechers)
	if err != nil {
		return
	}
	var seeders uint32
	err = binary.Read(response, binary.BigEndian, &seeders)
	if err != nil {
		return
	}

	peerCount := (responseLen - minimumResponseLen) / peerDataSize
	peerDataBytes := make([]byte, peerDataSize*peerCount)
	err = binary.Read(response, binary.BigEndian, &peerDataBytes)
	if err != nil {
		return
	}

	tr = &TrackerResponse{
		Interval:   uint(interval),
		Complete:   uint(seeders),
		Incomplete: uint(leechers),
		Peers:      string(peerDataBytes)}
	return
}
<|endoftext|>


<commit_before>// Copyright 2015 Google Inc. All Rights Reserved.
// Author: jacobsa@google.com (Aaron Jacobs)

package memfs

import (
	"github.com/jacobsa/fuse"
	"github.com/jacobsa/fuse/fuseutil"
	"github.com/jacobsa/gcloud/syncutil"
)

// Common attributes for files and directories.
//
// TODO(jacobsa): Add tests for interacting with a file/directory after it has
// been unlinked, including creating a new file. Make sure we don't screw up
// and reuse an inode ID while it is still in use.
type inode struct {
	/////////////////////////
	// Constant data
	/////////////////////////

	// Is this a directory? If not, it is a file.
	bool dir

	/////////////////////////
	// Mutable state
	/////////////////////////

	mu syncutil.InvariantMutex

	// The current attributes of this inode.
	//
	// INVARIANT: No non-permission mode bits are set besides os.ModeDir
	// INVARIANT: If dir, then os.ModeDir is set
	// INVARIANT: If !dir, then os.ModeDir is not set
	attributes fuse.InodeAttributes // GUARDED_BY(mu)

	// For directories, entries describing the children of the directory.
	//
	// This array can never be shortened, nor can its elements be moved, because
	// we use its indices for Dirent.Offset, which is exposed to the user who
	// might be calling readdir in a loop while concurrently modifying the
	// directory. Unused entries can, however, be reused.
	//
	// TODO(jacobsa): Add good tests exercising concurrent modifications while
	// doing readdir, seekdir, etc. calls.
	//
	// INVARIANT: If dir is false, this is nil.
	// INVARIANT: For each i, entries[i].Offset == i+1
	entries []fuseutil.Dirent // GUARDED_BY(mu)

	// For files, the current contents of the file.
	//
	// INVARIANT: If dir is true, this is nil.
	contents []byte // GUARDED_BY(mu)
}

func newInode(dir bool) (inode *inode)

func (inode *inode) checkInvariants()
<commit_msg>Fixed a build error.<commit_after>// Copyright 2015 Google Inc. All Rights Reserved.
// Author: jacobsa@google.com (Aaron Jacobs)

package memfs

import (
	"github.com/jacobsa/fuse"
	"github.com/jacobsa/fuse/fuseutil"
	"github.com/jacobsa/gcloud/syncutil"
)

// Common attributes for files and directories.
//
// TODO(jacobsa): Add tests for interacting with a file/directory after it has
// been unlinked, including creating a new file. Make sure we don't screw up
// and reuse an inode ID while it is still in use.
type inode struct {
	/////////////////////////
	// Constant data
	/////////////////////////

	// Is this a directory? If not, it is a file.
	dir bool

	/////////////////////////
	// Mutable state
	/////////////////////////

	mu syncutil.InvariantMutex

	// The current attributes of this inode.
	//
	// INVARIANT: No non-permission mode bits are set besides os.ModeDir
	// INVARIANT: If dir, then os.ModeDir is set
	// INVARIANT: If !dir, then os.ModeDir is not set
	attributes fuse.InodeAttributes // GUARDED_BY(mu)

	// For directories, entries describing the children of the directory.
	//
	// This array can never be shortened, nor can its elements be moved, because
	// we use its indices for Dirent.Offset, which is exposed to the user who
	// might be calling readdir in a loop while concurrently modifying the
	// directory. Unused entries can, however, be reused.
	//
	// TODO(jacobsa): Add good tests exercising concurrent modifications while
	// doing readdir, seekdir, etc. calls.
	//
	// INVARIANT: If dir is false, this is nil.
	// INVARIANT: For each i, entries[i].Offset == i+1
	entries []fuseutil.Dirent // GUARDED_BY(mu)

	// For files, the current contents of the file.
	//
	// INVARIANT: If dir is true, this is nil.
	contents []byte // GUARDED_BY(mu)
}

func newInode(dir bool) (inode *inode)

func (inode *inode) checkInvariants()
<|endoftext|>


<commit_before>package routers

import (
  "github.com/astaxie/beego"
  "github.com/dockboard/docker-registry/controllers"
)

func init() {
  beego.Router("/", &controllers.MainController{})

  //Ping Router
  beego.Router("/_ping", &controllers.PingController{}, "get:GetPing")
  beego.Router("/_ping/", &controllers.PingController{}, "get:GetPing")
  beego.Router("/v1/_ping", &controllers.PingController{}, "get:GetPing")
  beego.Router("/v1/_ping/", &controllers.PingController{}, "get:GetPing")

  //User Router
  beego.Router("/v1/users/", &controllers.UsersController{}, "get:GetUsers")
  beego.Router("/v1/users/", &controllers.UsersController{}, "post:PostUsers")
  beego.Router("/v1/users", &controllers.UsersController{}, "get:GetUsers")
  beego.Router("/v1/users", &controllers.UsersController{}, "post:PostUsers")

  //根据 Router 的规则把此定义放在上部。
  //Push -> 3. 根据 Repository 的所有 Tag 信息循环写入所有的 Tag
  beego.Router("/v1/repositories/:namespace/:repo_name/tags/:tag/", &controllers.RepositoryController{}, "put:PutTag")
  beego.Router("/v1/repositories/:namespace/:repo_name/tags/:tag", &controllers.RepositoryController{}, "put:PutTag")

  //Push -> 4. 最后执行，并没有上传任何有效数据
  beego.Router("/v1/repositories/:namespace/:repo_name/images/", &controllers.RepositoryController{}, "put:PutRepositoryImages")
  beego.Router("/v1/repositories/:namespace/:repo_name/images", &controllers.RepositoryController{}, "put:PutRepositoryImages")

  //Pull -> 1. 获取 repository 的 images 信息
  beego.Router("/v1/repositories/:namespace/:repo_name/images", &controllers.RepositoryController{}, "get:GetRepositoryImages")
  //Pull -> 2. 获取 repository 的 tags 信息
  beego.Router("/v1/repositories/:namespace/:repo_name/tags", &controllers.RepositoryController{}, "get:GetRepositoryTags")
  //Pull -> 3. 获取 image 的 ancestry 信息
  beego.Router("/v1/images/:image_id/ancestry", &controllers.ImageController{}, "get:GetImageAncestry")
  //Pull -> 4. 获取 image 的 json 信息
  beego.Router("/v1/images/:image_id/json/", &controllers.ImageController{}, "get:GetImageJson")
  beego.Router("/v1/images/:image_id/json", &controllers.ImageController{}, "get:GetImageJson")
  //Pull -> 5. 获取 image 的 layer 文件
  beego.Router("/v1/images/:image_id/layer", &controllers.ImageController{}, "get:GetImageLayer")

  //Push Router Begin
  //Push -> 1. 写入要上传的 Repository 的 JSON 信息，此 JSON 信息是一个包含所有 Image ID 的 JSON 字符串。
  beego.Router("/v1/repositories/:namespace/:repo_name/", &controllers.RepositoryController{}, "put:PutRepository")
  beego.Router("/v1/repositories/:namespace/:repo_name", &controllers.RepositoryController{}, "put:PutRepository")
  //Push -> 2. 根据 Repository 的所有 Image 开始循环处理
  //Push -> 2.1 根据 Image 的 ID 获取 Image 的 JSON 数据，如果返回 404 就要上传此 Image 的 JSON 数据和 Layer 文件 同 Pull -> 4
  //Push -> 2.2 上传 Image 的 JSON 文件
  beego.Router("/v1/images/:image_id/json/", &controllers.ImageController{}, "put:PutImageJson")
  beego.Router("/v1/images/:image_id/json", &controllers.ImageController{}, "put:PutImageJson")
  //Push -> 2.3 上传 Image 的 Layer 文件
  beego.Router("/v1/images/:image_id/layer/", &controllers.ImageController{}, "put:PutImageIdLayer")
  beego.Router("/v1/images/:image_id/layer", &controllers.ImageController{}, "put:PutImageIdLayer")
  //Push -> 2.4 上传 Image 的 Checksum 文件
  beego.Router("/v1/images/:image_id/checksum/", &controllers.ImageController{}, "put:PutChecksum")
  beego.Router("/v1/images/:image_id/checksum", &controllers.ImageController{}, "put:PutChecksum")
  //End Image 循环
  //而后的操作请看上部的 Push -> 3 和 Push -> 4
  //Push Router End

  beego.Router("/_status", &controllers.StatusController{})
  beego.Router("/v1/_status", &controllers.StatusController{})
}
<commit_msg>Add more routes with "/"<commit_after>package routers

import (
  "github.com/astaxie/beego"
  "github.com/dockboard/docker-registry/controllers"
)

func init() {
  beego.Router("/", &controllers.MainController{})

  //Ping Router
  beego.Router("/_ping", &controllers.PingController{}, "get:GetPing")
  beego.Router("/_ping/", &controllers.PingController{}, "get:GetPing")
  beego.Router("/v1/_ping", &controllers.PingController{}, "get:GetPing")
  beego.Router("/v1/_ping/", &controllers.PingController{}, "get:GetPing")

  //User Router
  beego.Router("/v1/users/", &controllers.UsersController{}, "get:GetUsers")
  beego.Router("/v1/users/", &controllers.UsersController{}, "post:PostUsers")
  beego.Router("/v1/users", &controllers.UsersController{}, "get:GetUsers")
  beego.Router("/v1/users", &controllers.UsersController{}, "post:PostUsers")

  //根据 Router 的规则把此定义放在上部。
  //Push -> 3. 根据 Repository 的所有 Tag 信息循环写入所有的 Tag
  beego.Router("/v1/repositories/:namespace/:repo_name/tags/:tag/", &controllers.RepositoryController{}, "put:PutTag")
  beego.Router("/v1/repositories/:namespace/:repo_name/tags/:tag", &controllers.RepositoryController{}, "put:PutTag")

  //Push -> 4. 最后执行，并没有上传任何有效数据
  beego.Router("/v1/repositories/:namespace/:repo_name/images/", &controllers.RepositoryController{}, "put:PutRepositoryImages")
  beego.Router("/v1/repositories/:namespace/:repo_name/images", &controllers.RepositoryController{}, "put:PutRepositoryImages")

  //Pull -> 1. 获取 repository 的 images 信息
  beego.Router("/v1/repositories/:namespace/:repo_name/images/", &controllers.RepositoryController{}, "get:GetRepositoryImages")
  beego.Router("/v1/repositories/:namespace/:repo_name/images", &controllers.RepositoryController{}, "get:GetRepositoryImages")
  //Pull -> 2. 获取 repository 的 tags 信息
  beego.Router("/v1/repositories/:namespace/:repo_name/tags/", &controllers.RepositoryController{}, "get:GetRepositoryTags")
  beego.Router("/v1/repositories/:namespace/:repo_name/tags", &controllers.RepositoryController{}, "get:GetRepositoryTags")
  //Pull -> 3. 获取 image 的 ancestry 信息
  beego.Router("/v1/images/:image_id/ancestry/", &controllers.ImageController{}, "get:GetImageAncestry")
  beego.Router("/v1/images/:image_id/ancestry", &controllers.ImageController{}, "get:GetImageAncestry")
  //Pull -> 4. 获取 image 的 json 信息
  beego.Router("/v1/images/:image_id/json/", &controllers.ImageController{}, "get:GetImageJson")
  beego.Router("/v1/images/:image_id/json", &controllers.ImageController{}, "get:GetImageJson")
  //Pull -> 5. 获取 image 的 layer 文件
  beego.Router("/v1/images/:image_id/layer/", &controllers.ImageController{}, "get:GetImageLayer")
  beego.Router("/v1/images/:image_id/layer", &controllers.ImageController{}, "get:GetImageLayer")

  //Push Router Begin
  //Push -> 1. 写入要上传的 Repository 的 JSON 信息，此 JSON 信息是一个包含所有 Image ID 的 JSON 字符串。
  beego.Router("/v1/repositories/:namespace/:repo_name/", &controllers.RepositoryController{}, "put:PutRepository")
  beego.Router("/v1/repositories/:namespace/:repo_name", &controllers.RepositoryController{}, "put:PutRepository")
  //Push -> 2. 根据 Repository 的所有 Image 开始循环处理
  //Push -> 2.1 根据 Image 的 ID 获取 Image 的 JSON 数据，如果返回 404 就要上传此 Image 的 JSON 数据和 Layer 文件 同 Pull -> 4
  //Push -> 2.2 上传 Image 的 JSON 文件
  beego.Router("/v1/images/:image_id/json/", &controllers.ImageController{}, "put:PutImageJson")
  beego.Router("/v1/images/:image_id/json", &controllers.ImageController{}, "put:PutImageJson")
  //Push -> 2.3 上传 Image 的 Layer 文件
  beego.Router("/v1/images/:image_id/layer/", &controllers.ImageController{}, "put:PutImageIdLayer")
  beego.Router("/v1/images/:image_id/layer", &controllers.ImageController{}, "put:PutImageIdLayer")
  //Push -> 2.4 上传 Image 的 Checksum 文件
  beego.Router("/v1/images/:image_id/checksum/", &controllers.ImageController{}, "put:PutChecksum")
  beego.Router("/v1/images/:image_id/checksum", &controllers.ImageController{}, "put:PutChecksum")
  //End Image 循环
  //而后的操作请看上部的 Push -> 3 和 Push -> 4
  //Push Router End

  beego.Router("/_status", &controllers.StatusController{})
  beego.Router("/v1/_status", &controllers.StatusController{})
}
<|endoftext|>


<commit_before>// +build linux

/*
Copyright 2021 The Kubernetes Authors.

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

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

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

package e2enode

import (
	"context"
	"fmt"
	"strconv"
	"time"

	"k8s.io/apimachinery/pkg/fields"

	"github.com/onsi/ginkgo"
	"github.com/onsi/gomega"
	"k8s.io/kubernetes/pkg/apis/scheduling"
	"k8s.io/kubernetes/pkg/features"
	"k8s.io/kubernetes/test/e2e/framework"

	v1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	kubeletconfig "k8s.io/kubernetes/pkg/kubelet/apis/config"
	kubelettypes "k8s.io/kubernetes/pkg/kubelet/types"
	testutils "k8s.io/kubernetes/test/utils"
)

var _ = SIGDescribe("GracefulNodeShutdown [Serial] [NodeAlphaFeature:GracefulNodeShutdown]", func() {
	f := framework.NewDefaultFramework("graceful-node-shutdown")
	ginkgo.Context("when gracefully shutting down", func() {

		const (
			pollInterval                        = 1 * time.Second
			podStatusUpdateTimeout              = 5 * time.Second
			nodeStatusUpdateTimeout             = 10 * time.Second
			nodeShutdownGracePeriod             = 20 * time.Second
			nodeShutdownGracePeriodCriticalPods = 10 * time.Second
		)

		tempSetCurrentKubeletConfig(f, func(initialConfig *kubeletconfig.KubeletConfiguration) {
			initialConfig.FeatureGates = map[string]bool{
				string(features.GracefulNodeShutdown): true,
			}
			initialConfig.ShutdownGracePeriod = metav1.Duration{Duration: nodeShutdownGracePeriod}
			initialConfig.ShutdownGracePeriodCriticalPods = metav1.Duration{Duration: nodeShutdownGracePeriodCriticalPods}
		})

		ginkgo.BeforeEach(func() {
			ginkgo.By("Wait for the node to be ready")
			waitForNodeReady()
		})

		ginkgo.AfterEach(func() {
			ginkgo.By("Emitting Shutdown false signal; cancelling the shutdown")
			err := emitSignalPrepareForShutdown(false)
			framework.ExpectNoError(err)
		})

		ginkgo.It("should be able to gracefully shutdown pods with various grace periods", func() {
			nodeName := getNodeName(f)
			nodeSelector := fields.Set{
				"spec.nodeName": nodeName,
			}.AsSelector().String()

			// Define test pods
			pods := []*v1.Pod{
				getGracePeriodOverrideTestPod("period-120", nodeName, 120, false),
				getGracePeriodOverrideTestPod("period-5", nodeName, 5, false),
				getGracePeriodOverrideTestPod("period-critical-120", nodeName, 120, true),
				getGracePeriodOverrideTestPod("period-critical-5", nodeName, 5, true),
			}

			ginkgo.By("Creating batch pods")
			f.PodClient().CreateBatch(pods)

			list, err := f.PodClient().List(context.TODO(), metav1.ListOptions{
				FieldSelector: nodeSelector,
			})
			framework.ExpectNoError(err)
			framework.ExpectEqual(len(list.Items), len(pods), "the number of pods is not as expected")

			ginkgo.By("Verifying batch pods are running")
			for _, pod := range list.Items {
				if podReady, err := testutils.PodRunningReady(&pod); err != nil || !podReady {
					framework.Failf("Failed to start batch pod: %v", pod.Name)
				}
			}

			ginkgo.By("Emitting shutdown signal")
			err = emitSignalPrepareForShutdown(true)
			framework.ExpectNoError(err)

			ginkgo.By("Verifying that non-critical pods are shutdown")
			// Not critical pod should be shutdown
			gomega.Eventually(func() error {
				list, err = f.PodClient().List(context.TODO(), metav1.ListOptions{
					FieldSelector: nodeSelector,
				})
				if err != nil {
					return err
				}
				framework.ExpectEqual(len(list.Items), len(pods), "the number of pods is not as expected")

				for _, pod := range list.Items {
					if kubelettypes.IsCriticalPod(&pod) {
						if pod.Status.Phase != v1.PodRunning {
							framework.Logf("Expecting critcal pod to be running, but it's not currently. Pod: %q, Pod Status Phase: %q, Pod Status Reason: %q", pod.Name, pod.Status.Phase, pod.Status.Reason)
							return fmt.Errorf("critical pod should not be shutdown, phase: %s", pod.Status.Phase)
						}
					} else {
						if pod.Status.Phase != v1.PodFailed || pod.Status.Reason != "Shutdown" {
							framework.Logf("Expecting non-critcal pod to be shutdown, but it's not currently. Pod: %q, Pod Status Phase: %q, Pod Status Reason: %q", pod.Name, pod.Status.Phase, pod.Status.Reason)
							return fmt.Errorf("pod should be shutdown, phase: %s", pod.Status.Phase)
						}
					}
				}
				return nil
			}, podStatusUpdateTimeout, pollInterval).Should(gomega.BeNil())

			ginkgo.By("Verifying that all pods are shutdown")
			// All pod should be shutdown
			gomega.Eventually(func() error {
				list, err = f.PodClient().List(context.TODO(), metav1.ListOptions{
					FieldSelector: nodeSelector,
				})
				if err != nil {
					return err
				}
				framework.ExpectEqual(len(list.Items), len(pods), "the number of pods is not as expected")

				for _, pod := range list.Items {
					if pod.Status.Phase != v1.PodFailed || pod.Status.Reason != "Shutdown" {
						framework.Logf("Expecting pod to be shutdown, but it's not currently: Pod: %q, Pod Status Phase: %q, Pod Status Reason: %q", pod.Name, pod.Status.Phase, pod.Status.Reason)
						return fmt.Errorf("pod should be shutdown, phase: %s", pod.Status.Phase)
					}
				}
				return nil
			},
				// Critical pod starts shutdown after (nodeShutdownGracePeriod-nodeShutdownGracePeriodCriticalPods)
				podStatusUpdateTimeout+(nodeShutdownGracePeriod-nodeShutdownGracePeriodCriticalPods),
				pollInterval).Should(gomega.BeNil())
		})

		ginkgo.It("should be able to handle a cancelled shutdown", func() {
			ginkgo.By("Emitting Shutdown signal")
			err := emitSignalPrepareForShutdown(true)
			framework.ExpectNoError(err)
			gomega.Eventually(func() error {
				isReady := getNodeReadyStatus(f)
				if isReady {
					return fmt.Errorf("node did not become shutdown as expected")
				}
				return nil
			}, nodeStatusUpdateTimeout, pollInterval).Should(gomega.BeNil())

			ginkgo.By("Emitting Shutdown false signal; cancelling the shutdown")
			err = emitSignalPrepareForShutdown(false)
			framework.ExpectNoError(err)
			gomega.Eventually(func() error {
				isReady := getNodeReadyStatus(f)
				if !isReady {
					return fmt.Errorf("node did not recover as expected")
				}
				return nil
			}, nodeStatusUpdateTimeout, pollInterval).Should(gomega.BeNil())
		})
	})
})

func getGracePeriodOverrideTestPod(name string, node string, gracePeriod int64, critical bool) *v1.Pod {
	pod := &v1.Pod{
		TypeMeta: metav1.TypeMeta{
			Kind:       "Pod",
			APIVersion: "v1",
		},
		ObjectMeta: metav1.ObjectMeta{
			Name: name,
		},
		Spec: v1.PodSpec{
			Containers: []v1.Container{
				{
					Name:    name,
					Image:   busyboxImage,
					Command: []string{"sh", "-c"},
					Args: []string{`
_term() {
	echo "Caught SIGTERM signal!"
	while true; do sleep 5; done
}
trap _term SIGTERM
while true; do sleep 5; done
`},
				},
			},
			TerminationGracePeriodSeconds: &gracePeriod,
			NodeName:                      node,
		},
	}
	if critical {
		pod.ObjectMeta.Annotations = map[string]string{
			kubelettypes.ConfigSourceAnnotationKey: kubelettypes.FileSource,
		}
		pod.Spec.PriorityClassName = scheduling.SystemNodeCritical

		framework.ExpectEqual(kubelettypes.IsCriticalPod(pod), true, "pod should be a critical pod")
	} else {
		framework.ExpectEqual(kubelettypes.IsCriticalPod(pod), false, "pod should not be a critical pod")
	}
	return pod
}

// Emits a fake PrepareForShutdown dbus message on system dbus. Will cause kubelet to react to an active shutdown event.
func emitSignalPrepareForShutdown(b bool) error {
	cmd := "dbus-send --system /org/freedesktop/login1 org.freedesktop.login1.Manager.PrepareForShutdown boolean:" + strconv.FormatBool(b)
	_, err := runCommand("sh", "-c", cmd)
	return err
}

func getNodeReadyStatus(f *framework.Framework) bool {
	nodeList, err := f.ClientSet.CoreV1().Nodes().List(context.TODO(), metav1.ListOptions{})
	framework.ExpectNoError(err)
	// Assuming that there is only one node, because this is a node e2e test.
	framework.ExpectEqual(len(nodeList.Items), 1)
	return isNodeReady(&nodeList.Items[0])
}
<commit_msg>Add test, after restart dbus, should be able to gracefully shutdown<commit_after>// +build linux

/*
Copyright 2021 The Kubernetes Authors.

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

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

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

package e2enode

import (
	"context"
	"fmt"
	"strconv"
	"time"

	"k8s.io/apimachinery/pkg/fields"

	"github.com/onsi/ginkgo"
	"github.com/onsi/gomega"
	"k8s.io/kubernetes/pkg/apis/scheduling"
	"k8s.io/kubernetes/pkg/features"
	"k8s.io/kubernetes/test/e2e/framework"

	v1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	kubeletconfig "k8s.io/kubernetes/pkg/kubelet/apis/config"
	kubelettypes "k8s.io/kubernetes/pkg/kubelet/types"
	testutils "k8s.io/kubernetes/test/utils"
)

var _ = SIGDescribe("GracefulNodeShutdown [Serial] [NodeAlphaFeature:GracefulNodeShutdown]", func() {
	f := framework.NewDefaultFramework("graceful-node-shutdown")
	ginkgo.Context("when gracefully shutting down", func() {

		const (
			pollInterval                        = 1 * time.Second
			podStatusUpdateTimeout              = 5 * time.Second
			nodeStatusUpdateTimeout             = 10 * time.Second
			nodeShutdownGracePeriod             = 20 * time.Second
			nodeShutdownGracePeriodCriticalPods = 10 * time.Second
		)

		tempSetCurrentKubeletConfig(f, func(initialConfig *kubeletconfig.KubeletConfiguration) {
			initialConfig.FeatureGates = map[string]bool{
				string(features.GracefulNodeShutdown): true,
			}
			initialConfig.ShutdownGracePeriod = metav1.Duration{Duration: nodeShutdownGracePeriod}
			initialConfig.ShutdownGracePeriodCriticalPods = metav1.Duration{Duration: nodeShutdownGracePeriodCriticalPods}
		})

		ginkgo.BeforeEach(func() {
			ginkgo.By("Wait for the node to be ready")
			waitForNodeReady()
		})

		ginkgo.AfterEach(func() {
			ginkgo.By("Emitting Shutdown false signal; cancelling the shutdown")
			err := emitSignalPrepareForShutdown(false)
			framework.ExpectNoError(err)
		})

		ginkgo.It("should be able to gracefully shutdown pods with various grace periods", func() {
			nodeName := getNodeName(f)
			nodeSelector := fields.Set{
				"spec.nodeName": nodeName,
			}.AsSelector().String()

			// Define test pods
			pods := []*v1.Pod{
				getGracePeriodOverrideTestPod("period-120", nodeName, 120, false),
				getGracePeriodOverrideTestPod("period-5", nodeName, 5, false),
				getGracePeriodOverrideTestPod("period-critical-120", nodeName, 120, true),
				getGracePeriodOverrideTestPod("period-critical-5", nodeName, 5, true),
			}

			ginkgo.By("Creating batch pods")
			f.PodClient().CreateBatch(pods)

			list, err := f.PodClient().List(context.TODO(), metav1.ListOptions{
				FieldSelector: nodeSelector,
			})
			framework.ExpectNoError(err)
			framework.ExpectEqual(len(list.Items), len(pods), "the number of pods is not as expected")

			ginkgo.By("Verifying batch pods are running")
			for _, pod := range list.Items {
				if podReady, err := testutils.PodRunningReady(&pod); err != nil || !podReady {
					framework.Failf("Failed to start batch pod: %v", pod.Name)
				}
			}

			ginkgo.By("Emitting shutdown signal")
			err = emitSignalPrepareForShutdown(true)
			framework.ExpectNoError(err)

			ginkgo.By("Verifying that non-critical pods are shutdown")
			// Not critical pod should be shutdown
			gomega.Eventually(func() error {
				list, err = f.PodClient().List(context.TODO(), metav1.ListOptions{
					FieldSelector: nodeSelector,
				})
				if err != nil {
					return err
				}
				framework.ExpectEqual(len(list.Items), len(pods), "the number of pods is not as expected")

				for _, pod := range list.Items {
					if kubelettypes.IsCriticalPod(&pod) {
						if pod.Status.Phase != v1.PodRunning {
							framework.Logf("Expecting critcal pod to be running, but it's not currently. Pod: %q, Pod Status Phase: %q, Pod Status Reason: %q", pod.Name, pod.Status.Phase, pod.Status.Reason)
							return fmt.Errorf("critical pod should not be shutdown, phase: %s", pod.Status.Phase)
						}
					} else {
						if pod.Status.Phase != v1.PodFailed || pod.Status.Reason != "Shutdown" {
							framework.Logf("Expecting non-critcal pod to be shutdown, but it's not currently. Pod: %q, Pod Status Phase: %q, Pod Status Reason: %q", pod.Name, pod.Status.Phase, pod.Status.Reason)
							return fmt.Errorf("pod should be shutdown, phase: %s", pod.Status.Phase)
						}
					}
				}
				return nil
			}, podStatusUpdateTimeout, pollInterval).Should(gomega.BeNil())

			ginkgo.By("Verifying that all pods are shutdown")
			// All pod should be shutdown
			gomega.Eventually(func() error {
				list, err = f.PodClient().List(context.TODO(), metav1.ListOptions{
					FieldSelector: nodeSelector,
				})
				if err != nil {
					return err
				}
				framework.ExpectEqual(len(list.Items), len(pods), "the number of pods is not as expected")

				for _, pod := range list.Items {
					if pod.Status.Phase != v1.PodFailed || pod.Status.Reason != "Shutdown" {
						framework.Logf("Expecting pod to be shutdown, but it's not currently: Pod: %q, Pod Status Phase: %q, Pod Status Reason: %q", pod.Name, pod.Status.Phase, pod.Status.Reason)
						return fmt.Errorf("pod should be shutdown, phase: %s", pod.Status.Phase)
					}
				}
				return nil
			},
				// Critical pod starts shutdown after (nodeShutdownGracePeriod-nodeShutdownGracePeriodCriticalPods)
				podStatusUpdateTimeout+(nodeShutdownGracePeriod-nodeShutdownGracePeriodCriticalPods),
				pollInterval).Should(gomega.BeNil())
		})

		ginkgo.It("should be able to handle a cancelled shutdown", func() {
			ginkgo.By("Emitting Shutdown signal")
			err := emitSignalPrepareForShutdown(true)
			framework.ExpectNoError(err)
			gomega.Eventually(func() error {
				isReady := getNodeReadyStatus(f)
				if isReady {
					return fmt.Errorf("node did not become shutdown as expected")
				}
				return nil
			}, nodeStatusUpdateTimeout, pollInterval).Should(gomega.BeNil())

			ginkgo.By("Emitting Shutdown false signal; cancelling the shutdown")
			err = emitSignalPrepareForShutdown(false)
			framework.ExpectNoError(err)
			gomega.Eventually(func() error {
				isReady := getNodeReadyStatus(f)
				if !isReady {
					return fmt.Errorf("node did not recover as expected")
				}
				return nil
			}, nodeStatusUpdateTimeout, pollInterval).Should(gomega.BeNil())
		})

		ginkgo.It("after restart dbus, should be able to gracefully shutdown", func() {
			ginkgo.By("Restart Dbus")
			err := restartDbus()
			framework.ExpectNoError(err)

			ginkgo.By("Emitting Shutdown signal")
			err = emitSignalPrepareForShutdown(true)
			framework.ExpectNoError(err)

			gomega.Eventually(func() error {
				isReady := getNodeReadyStatus(f)
				if isReady {
					return fmt.Errorf("node did not become shutdown as expected")
				}
				return nil
			}, nodeStatusUpdateTimeout, pollInterval).Should(gomega.BeNil())
		})
	})
})

func getGracePeriodOverrideTestPod(name string, node string, gracePeriod int64, critical bool) *v1.Pod {
	pod := &v1.Pod{
		TypeMeta: metav1.TypeMeta{
			Kind:       "Pod",
			APIVersion: "v1",
		},
		ObjectMeta: metav1.ObjectMeta{
			Name: name,
		},
		Spec: v1.PodSpec{
			Containers: []v1.Container{
				{
					Name:    name,
					Image:   busyboxImage,
					Command: []string{"sh", "-c"},
					Args: []string{`
_term() {
	echo "Caught SIGTERM signal!"
	while true; do sleep 5; done
}
trap _term SIGTERM
while true; do sleep 5; done
`},
				},
			},
			TerminationGracePeriodSeconds: &gracePeriod,
			NodeName:                      node,
		},
	}
	if critical {
		pod.ObjectMeta.Annotations = map[string]string{
			kubelettypes.ConfigSourceAnnotationKey: kubelettypes.FileSource,
		}
		pod.Spec.PriorityClassName = scheduling.SystemNodeCritical

		framework.ExpectEqual(kubelettypes.IsCriticalPod(pod), true, "pod should be a critical pod")
	} else {
		framework.ExpectEqual(kubelettypes.IsCriticalPod(pod), false, "pod should not be a critical pod")
	}
	return pod
}

// Emits a fake PrepareForShutdown dbus message on system dbus. Will cause kubelet to react to an active shutdown event.
func emitSignalPrepareForShutdown(b bool) error {
	cmd := "dbus-send --system /org/freedesktop/login1 org.freedesktop.login1.Manager.PrepareForShutdown boolean:" + strconv.FormatBool(b)
	_, err := runCommand("sh", "-c", cmd)
	return err
}

func getNodeReadyStatus(f *framework.Framework) bool {
	nodeList, err := f.ClientSet.CoreV1().Nodes().List(context.TODO(), metav1.ListOptions{})
	framework.ExpectNoError(err)
	// Assuming that there is only one node, because this is a node e2e test.
	framework.ExpectEqual(len(nodeList.Items), 1)
	return isNodeReady(&nodeList.Items[0])
}

func restartDbus() error {
	cmd := "systemctl restart dbus"
	_, err := runCommand("sh", "-c", cmd)
	return err
}
<|endoftext|>


<commit_before>// +build benchmark,!no-etcd,!integration

/*
Copyright 2014 The Kubernetes Authors 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 integration

import (
	"flag"
	"fmt"
	"os"
	"testing"
	"time"

	"github.com/golang/glog"
	"k8s.io/kubernetes/pkg/api"
	client "k8s.io/kubernetes/pkg/client/unversioned"
	"k8s.io/kubernetes/pkg/fields"
	"k8s.io/kubernetes/pkg/labels"
	etcdgeneric "k8s.io/kubernetes/pkg/registry/generic/etcd"
	"k8s.io/kubernetes/pkg/storage/etcd/etcdtest"
	"k8s.io/kubernetes/test/integration/framework"
)

// Command line flag globals, parsed in init and used by the benchmarks:
//	* pods && !tasks: Start -pods, scale number of parallel tasks with b.N
//  * !pods && tasks: Start -tasks, scale pods with b.N
//  * pods && tasks: Ignore b.N, benchmark behaves like a test constrained by -benchtime.
//  * !pods && !tasks: scale pods and workers with b.N.
// -workers specifies the number of workers to shard tasks across.
// Eg: go test bench . -bench-pods 3000 -bench-tasks 100 -bench-tasks 10:
// Create 100 tasks each listing 3000 pods, and run them 10 at a time.
var (
	Workers int
	Pods    int
	Tasks   int
)

const Glog_fatalf = 3

func init() {
	q := flag.Int("bench-quiet", 3, "Quietness, don't glog severities <= given level during the benchmark.")
	pods := flag.Int("bench-pods", -1, "Number of pods for the benchmark. If unspecified, uses b.N.")
	workers := flag.Int("bench-workers", -1, "Number workers for the benchmark. If unspecified, uses tasks.")
	tasks := flag.Int("bench-tasks", -1, "Number of tasks for the benchmark. These tasks are sharded across the workers. If unspecified, uses b.N.")
	flag.Parse()

	// Unfortunately this v level goes in the opposite direction as stderrthreshold.
	flag.Set("v", fmt.Sprintf("%d", *q))

	// We need quiet logs to parse benchmark results, which includes Errorf.
	flag.Set("logtostderr", "false")
	flag.Set("stderrthreshold", fmt.Sprintf("%d", Glog_fatalf-*q))
	Pods = *pods
	Workers = *workers
	Tasks = *tasks
	framework.DeleteAllEtcdKeys()
}

// getPods returns the cmd line -pods or b.N if -pods wasn't specified.
// Benchmarks can call getPods to get the number of pods they need to
// create for a given benchmark.
func getPods(bN int) int {
	if Pods < 0 {
		return bN
	}
	return Pods
}

// getTasks returns the cmd line -workers or b.N if -workers wasn't specified.
// Benchmarks would call getTasks to get the number of workers required to
// perform the benchmark in parallel.
func getTasks(bN int) int {
	if Tasks < 0 {
		return bN
	}
	return Tasks
}

// getIterations returns the number of iterations required by each benchmark for
// go to produce reliable timing results.
func getIterations(bN int) int {
	// Anything with constant pods is only linear if we iterate b.N times.
	if Pods > 0 {
		return bN
	}
	return 1
}

// startPodsOnNodes creates numPods sharded across numNodes
func startPodsOnNodes(numPods, numNodes int, restClient *client.Client) {
	podsPerNode := numPods / numNodes
	if podsPerNode < 1 {
		podsPerNode = 1
	}
	framework.RunParallel(func(id int) error {
		return framework.StartPods(podsPerNode, fmt.Sprintf("host.%d", id), restClient)
	}, numNodes, -1)
}

// Benchmark pod listing by waiting on `Tasks` listers to list `Pods` pods via `Workers`.
func BenchmarkPodList(b *testing.B) {
	b.StopTimer()
	m := framework.NewMasterComponents(&framework.Config{nil, true, false, 250.0, 500})
	defer m.Stop(true, true)

	numPods, numTasks, iter := getPods(b.N), getTasks(b.N), getIterations(b.N)
	podsPerNode := numPods / numTasks
	if podsPerNode < 1 {
		podsPerNode = 1
	}
	glog.Infof("Starting benchmark: b.N %d, pods %d, workers %d, podsPerNode %d",
		b.N, numPods, numTasks, podsPerNode)

	startPodsOnNodes(numPods, numTasks, m.RestClient)
	// Stop the rc manager so it doesn't steal resources
	m.Stop(false, true)

	b.StartTimer()
	for i := 0; i < iter; i++ {
		framework.RunParallel(func(id int) error {
			host := fmt.Sprintf("host.%d", id)
			now := time.Now()
			defer func() {
				glog.V(3).Infof("Worker %d: Node %v listing pods took %v", id, host, time.Since(now))
			}()
			if pods, err := m.RestClient.Pods(framework.TestNS).List(
				labels.Everything(),
				fields.OneTermEqualSelector(client.PodHost, host)); err != nil {
				return err
			} else if len(pods.Items) < podsPerNode {
				glog.Fatalf("List retrieved %d pods, which is less than %d", len(pods.Items), podsPerNode)
			}
			return nil
		}, numTasks, Workers)
	}
	b.StopTimer()
}

// Benchmark pod listing by waiting on `Tasks` listers to list `Pods` pods via `Workers`.
func BenchmarkPodListEtcd(b *testing.B) {
	b.StopTimer()
	m := framework.NewMasterComponents(&framework.Config{nil, true, false, 250.0, 500})
	defer m.Stop(true, true)

	numPods, numTasks, iter := getPods(b.N), getTasks(b.N), getIterations(b.N)
	podsPerNode := numPods / numTasks
	if podsPerNode < 1 {
		podsPerNode = 1
	}

	startPodsOnNodes(numPods, numTasks, m.RestClient)
	// Stop the rc manager so it doesn't steal resources
	m.Stop(false, true)

	glog.Infof("Starting benchmark: b.N %d, pods %d, workers %d, podsPerNode %d",
		b.N, numPods, numTasks, podsPerNode)

	ctx := api.WithNamespace(api.NewContext(), framework.TestNS)
	key := etcdgeneric.NamespaceKeyRootFunc(ctx, fmt.Sprintf("%s/pods", etcdtest.PathPrefix()))

	b.StartTimer()
	for i := 0; i < iter; i++ {
		framework.RunParallel(func(id int) error {
			now := time.Now()
			defer func() {
				glog.V(3).Infof("Worker %d: listing pods took %v", id, time.Since(now))
			}()
			if response, err := m.EtcdStorage.Client.Get(key, true, true); err != nil {
				return err
			} else if len(response.Node.Nodes) < podsPerNode {
				glog.Fatalf("List retrieved %d pods, which is less than %d", len(response.Node.Nodes), podsPerNode)
			}
			return nil
		}, numTasks, Workers)
	}
	b.StopTimer()
}

func TestMain(m *testing.M) {
	os.Exit(m.Run())
}
<commit_msg>Fix compile errors in benchmark<commit_after>// +build benchmark,!no-etcd,!integration

/*
Copyright 2014 The Kubernetes Authors 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 integration

import (
	"flag"
	"fmt"
	"os"
	"testing"
	"time"

	"github.com/golang/glog"
	client "k8s.io/kubernetes/pkg/client/unversioned"
	"k8s.io/kubernetes/pkg/fields"
	"k8s.io/kubernetes/pkg/labels"
	"k8s.io/kubernetes/test/integration/framework"
)

// Command line flag globals, parsed in init and used by the benchmarks:
//	* pods && !tasks: Start -pods, scale number of parallel tasks with b.N
//  * !pods && tasks: Start -tasks, scale pods with b.N
//  * pods && tasks: Ignore b.N, benchmark behaves like a test constrained by -benchtime.
//  * !pods && !tasks: scale pods and workers with b.N.
// -workers specifies the number of workers to shard tasks across.
// Eg: go test bench . -bench-pods 3000 -bench-tasks 100 -bench-tasks 10:
// Create 100 tasks each listing 3000 pods, and run them 10 at a time.
var (
	Workers int
	Pods    int
	Tasks   int
)

const Glog_fatalf = 3

func init() {
	q := flag.Int("bench-quiet", 3, "Quietness, don't glog severities <= given level during the benchmark.")
	pods := flag.Int("bench-pods", -1, "Number of pods for the benchmark. If unspecified, uses b.N.")
	workers := flag.Int("bench-workers", -1, "Number workers for the benchmark. If unspecified, uses tasks.")
	tasks := flag.Int("bench-tasks", -1, "Number of tasks for the benchmark. These tasks are sharded across the workers. If unspecified, uses b.N.")
	flag.Parse()

	// Unfortunately this v level goes in the opposite direction as stderrthreshold.
	flag.Set("v", fmt.Sprintf("%d", *q))

	// We need quiet logs to parse benchmark results, which includes Errorf.
	flag.Set("logtostderr", "false")
	flag.Set("stderrthreshold", fmt.Sprintf("%d", Glog_fatalf-*q))
	Pods = *pods
	Workers = *workers
	Tasks = *tasks
	framework.DeleteAllEtcdKeys()
}

// getPods returns the cmd line -pods or b.N if -pods wasn't specified.
// Benchmarks can call getPods to get the number of pods they need to
// create for a given benchmark.
func getPods(bN int) int {
	if Pods < 0 {
		return bN
	}
	return Pods
}

// getTasks returns the cmd line -workers or b.N if -workers wasn't specified.
// Benchmarks would call getTasks to get the number of workers required to
// perform the benchmark in parallel.
func getTasks(bN int) int {
	if Tasks < 0 {
		return bN
	}
	return Tasks
}

// getIterations returns the number of iterations required by each benchmark for
// go to produce reliable timing results.
func getIterations(bN int) int {
	// Anything with constant pods is only linear if we iterate b.N times.
	if Pods > 0 {
		return bN
	}
	return 1
}

// startPodsOnNodes creates numPods sharded across numNodes
func startPodsOnNodes(numPods, numNodes int, restClient *client.Client) {
	podsPerNode := numPods / numNodes
	if podsPerNode < 1 {
		podsPerNode = 1
	}
	framework.RunParallel(func(id int) error {
		return framework.StartPods(podsPerNode, fmt.Sprintf("host.%d", id), restClient)
	}, numNodes, -1)
}

// Benchmark pod listing by waiting on `Tasks` listers to list `Pods` pods via `Workers`.
func BenchmarkPodList(b *testing.B) {
	b.StopTimer()
	m := framework.NewMasterComponents(&framework.Config{nil, true, false, 250.0, 500})
	defer m.Stop(true, true)

	numPods, numTasks, iter := getPods(b.N), getTasks(b.N), getIterations(b.N)
	podsPerNode := numPods / numTasks
	if podsPerNode < 1 {
		podsPerNode = 1
	}
	glog.Infof("Starting benchmark: b.N %d, pods %d, workers %d, podsPerNode %d",
		b.N, numPods, numTasks, podsPerNode)

	startPodsOnNodes(numPods, numTasks, m.RestClient)
	// Stop the rc manager so it doesn't steal resources
	m.Stop(false, true)

	b.StartTimer()
	for i := 0; i < iter; i++ {
		framework.RunParallel(func(id int) error {
			host := fmt.Sprintf("host.%d", id)
			now := time.Now()
			defer func() {
				glog.V(3).Infof("Worker %d: Node %v listing pods took %v", id, host, time.Since(now))
			}()
			if pods, err := m.RestClient.Pods(framework.TestNS).List(
				labels.Everything(),
				fields.OneTermEqualSelector(client.PodHost, host)); err != nil {
				return err
			} else if len(pods.Items) < podsPerNode {
				glog.Fatalf("List retrieved %d pods, which is less than %d", len(pods.Items), podsPerNode)
			}
			return nil
		}, numTasks, Workers)
	}
	b.StopTimer()
}

// Benchmark pod listing by waiting on `Tasks` listers to list `Pods` pods via `Workers`.
func BenchmarkPodListEtcd(b *testing.B) {
	b.StopTimer()
	m := framework.NewMasterComponents(&framework.Config{nil, true, false, 250.0, 500})
	defer m.Stop(true, true)

	numPods, numTasks, iter := getPods(b.N), getTasks(b.N), getIterations(b.N)
	podsPerNode := numPods / numTasks
	if podsPerNode < 1 {
		podsPerNode = 1
	}

	startPodsOnNodes(numPods, numTasks, m.RestClient)
	// Stop the rc manager so it doesn't steal resources
	m.Stop(false, true)

	glog.Infof("Starting benchmark: b.N %d, pods %d, workers %d, podsPerNode %d",
		b.N, numPods, numTasks, podsPerNode)

	b.StartTimer()
	for i := 0; i < iter; i++ {
		framework.RunParallel(func(id int) error {
			now := time.Now()
			defer func() {
				glog.V(3).Infof("Worker %d: listing pods took %v", id, time.Since(now))
			}()
			pods, err := m.RestClient.Pods(framework.TestNS).List(labels.Everything(), fields.Everything())
			if err != nil {
				return err
			}
			if len(pods.Items) < numPods {
				glog.Fatalf("List retrieved %d pods, which is less than %d", len(pods.Items), numPods)
			}
			return nil
		}, numTasks, Workers)
	}
	b.StopTimer()
}

func TestMain(m *testing.M) {
	os.Exit(m.Run())
}
<|endoftext|>


<commit_before>package main

import (
	g "github.com/oatot/oatot/generated"
	"golang.org/x/net/context"
	"google.golang.org/grpc"
	"log"
)

// #include "../game/g_local.h"
// #include "../game/bg_public.h"
import "C"

var (
	grpcAddr = "127.0.0.1:13283"
	client   g.OatotClient
)

//export GInitializeClient
func GInitializeClient() {
	// Set up a connection to the server.
	conn, err := grpc.Dial(grpcAddr, grpc.WithInsecure())
	if err != nil {
		log.Fatalf("Did not connect: %v", err)
	}
	defer conn.Close()
	client = g.NewOatotClient(conn)
}

//export GOaChangeGameStage
func GOaChangeGameStage(newStage C.gameStage_t) {
}

//export GOaCloseBids
func GOaCloseBids(winner *C.char) {
}

//export GOaCloseBidsByIncident
func GOaCloseBidsByIncident() {
}

//export GOaIsNew
func GOaIsNew(clGuid *C.char) C.int {
	return C.int(0)
}

//export GOaRegister
func GOaRegister(clGuid *C.char) {
	clGuidStr := C.GoString(clGuid)
	_, err := client.OaRegister(
		context.Background(),
		&g.OaRegisterRequest{
			OaAuth: &g.OaAuth{ClGuid: &clGuidStr},
		},
	)
	if err != nil {
		log.Fatalf("OaRegister: %v", err)
	}
}

//export GOaTransferMoney
func GOaTransferMoney(clGuid *C.char, amount C.int) {
}

//export GOaActiveBidsSums
func GOaActiveBidsSums(horse *C.char) C.betSum_t {
	return C.betSum_t{1, 2}
}

//export GOaMyBalance
func GOaMyBalance(clGuid *C.char, currency *C.char) C.balance_t {
	return C.balance_t{1, 2}
}

//export GOaMyBid
func GOaMyBid(clGuid *C.char, bid C.bid_t) {
}

//export GOaDiscardBet
func GOaDiscardBet(clGuid *C.char, betId int) {
}

//export GOaMyActiveBids
func GOaMyActiveBids(clGuid *C.char, activeBids *C.bid_t) C.int {
	return 0
}

//export GOaMyPastBids
func GOaMyPastBids(clGuid *C.char, page *C.char, nextPage *C.char, pastBids *C.fullbid_t) C.int {
	return 0
}

//export GOaMyBidsSummary
func GOaMyBidsSummary(clGuid *C.char) C.bidsSummary_t {
	return C.bidsSummary_t{C.currencySummary_t{0, 0, 0, 0, 0}, C.currencySummary_t{0, 0, 0, 0, 0}}
}

func main() {}
<commit_msg>Do not close the connection<commit_after>package main

import (
	g "github.com/oatot/oatot/generated"
	"golang.org/x/net/context"
	"google.golang.org/grpc"
	"log"
)

// #include "../game/g_local.h"
// #include "../game/bg_public.h"
import "C"

var (
	grpcAddr = "127.0.0.1:13283"
	client   g.OatotClient
)

//export GInitializeClient
func GInitializeClient() {
	// Set up a connection to the server.
	conn, err := grpc.Dial(grpcAddr, grpc.WithInsecure())
	if err != nil {
		log.Fatalf("Did not connect: %v", err)
	}
	client = g.NewOatotClient(conn)
}

//export GOaChangeGameStage
func GOaChangeGameStage(newStage C.gameStage_t) {
}

//export GOaCloseBids
func GOaCloseBids(winner *C.char) {
}

//export GOaCloseBidsByIncident
func GOaCloseBidsByIncident() {
}

//export GOaIsNew
func GOaIsNew(clGuid *C.char) C.int {
	return C.int(0)
}

//export GOaRegister
func GOaRegister(clGuid *C.char) {
	clGuidStr := C.GoString(clGuid)
	_, err := client.OaRegister(
		context.Background(),
		&g.OaRegisterRequest{
			OaAuth: &g.OaAuth{ClGuid: &clGuidStr},
		},
	)
	if err != nil {
		log.Fatalf("OaRegister: %v", err)
	}
}

//export GOaTransferMoney
func GOaTransferMoney(clGuid *C.char, amount C.int) {
}

//export GOaActiveBidsSums
func GOaActiveBidsSums(horse *C.char) C.betSum_t {
	return C.betSum_t{1, 2}
}

//export GOaMyBalance
func GOaMyBalance(clGuid *C.char, currency *C.char) C.balance_t {
	return C.balance_t{1, 2}
}

//export GOaMyBid
func GOaMyBid(clGuid *C.char, bid C.bid_t) {
}

//export GOaDiscardBet
func GOaDiscardBet(clGuid *C.char, betId int) {
}

//export GOaMyActiveBids
func GOaMyActiveBids(clGuid *C.char, activeBids *C.bid_t) C.int {
	return 0
}

//export GOaMyPastBids
func GOaMyPastBids(clGuid *C.char, page *C.char, nextPage *C.char, pastBids *C.fullbid_t) C.int {
	return 0
}

//export GOaMyBidsSummary
func GOaMyBidsSummary(clGuid *C.char) C.bidsSummary_t {
	return C.bidsSummary_t{C.currencySummary_t{0, 0, 0, 0, 0}, C.currencySummary_t{0, 0, 0, 0, 0}}
}

func main() {}
<|endoftext|>