<commit_before>package gitstats

import (
	"fmt"
	"time"

	"github.com/google/go-github/github"
	. "github.com/intelsdi-x/snap-plugin-utilities/logger"
	"github.com/intelsdi-x/snap/control/plugin"
	"github.com/intelsdi-x/snap/control/plugin/cpolicy"
	"github.com/intelsdi-x/snap/core"
	"github.com/intelsdi-x/snap/core/ctypes"
	"golang.org/x/oauth2"
)

const (
	// Name of plugin
	Name = "rt-gitstats"
	// Version of plugin
	Version = 1
	// Type of plugin
	Type = plugin.CollectorPluginType
)

// make sure that we actually satisify requierd interface
var _ plugin.CollectorPlugin = (*Gitstats)(nil)

var (
	repoMetricNames = []string{
		"forks",
		"issues",
		"network",
		"stars",
		"subscribers",
		"watches",
		"size",
	}
	userMetricNames = []string{
		"public_repos",
		"public_gists",
		"followers",
		"following",
		"private_repos",
		"private_gists",
		"plan_private_repos",
		"plan_seats",
		"plan_filled_seats",
	}
)

type Gitstats struct {
}

// CollectMetrics collects metrics for testing
func (f *Gitstats) CollectMetrics(mts []plugin.MetricType) ([]plugin.MetricType, error) {
	var err error

	conf := mts[0].Config().Table()
	accessToken, ok := conf["access_token"]
	if !ok || accessToken.(ctypes.ConfigValueStr).Value == "" {
		return nil, fmt.Errorf("access token missing from config, %v", conf)
	}

	metrics, err := gitStats(accessToken.(ctypes.ConfigValueStr).Value, mts)
	if err != nil {
		return nil, err
	}

	return metrics, nil
}

type repoName struct {
	Repo  string
	Owner string
}

func gitStats(accessToken string, mts []plugin.MetricType) ([]plugin.MetricType, error) {
	ts := oauth2.StaticTokenSource(
		&oauth2.Token{AccessToken: accessToken},
	)
	tc := oauth2.NewClient(oauth2.NoContext, ts)
	client := github.NewClient(tc)
	collectionTime := time.Now()
	repos := make(map[string]map[string]map[string]int)
	users := make(map[string]map[string]int)

	userRepos := make(map[string]struct{})

	authUser := ""
	metrics := make([]plugin.MetricType, 0)

	for _, m := range mts {
		ns := m.Namespace().Strings()
		switch ns[3] {
		case "repo":
			user := ns[4]
			repo := ns[5]
			stat := ns[6]

			if user == "*" {
				//need to get user
				if authUser == "" {
					gitUser, _, err := client.Users.Get("")
					if err != nil {
						LogError("failed to get authenticated user.", err)
						return nil, err
					}
					stats, err := userStats(gitUser, client)
					if err != nil {
						LogError("failed to get stats from user object.", err)
						return nil, err
					}
					users[*gitUser.Login] = stats
					authUser = *gitUser.Login
				}
				user = authUser
			}
			if repo == "*" {
				// we only need to list a users repos once.
				if _, ok := userRepos[user]; !ok {
					repoList, _, err := client.Repositories.List(user, nil)
					if err != nil {
						LogError("failed to get repos owned by user.", err)
						return nil, err
					}
					userRepos[user] = struct{}{}
					if _, ok := repos[user]; !ok {
						repos[user] = make(map[string]map[string]int)
					}
					for _, r := range repoList {
						stats, err := repoStats(&r)
						if err != nil {
							LogError("failed to get stats from repo object.", err)
							return nil, err
						}
						repos[user][*r.Name] = stats
					}
				}
				for repo, stats := range repos[user] {
					mt := plugin.MetricType{
						Data_:      stats[stat],
						Namespace_: core.NewNamespace("raintank", "apps", "gitstats", "repo", user, repo, stat),
						Timestamp_: collectionTime,
						Version_:   m.Version(),
					}
					metrics = append(metrics, mt)
				}

			} else {
				if _, ok := repos[user]; !ok {
					repos[user] = make(map[string]map[string]int)
				}
				if _, ok := repos[user][repo]; !ok {
					r, _, err := client.Repositories.Get(user, repo)
					if err != nil {
						LogError("failed to user repos.", err)
						return nil, err
					}
					stats, err := repoStats(r)
					if err != nil {
						LogError("failed to get stats from repo object.", err)
						return nil, err
					}
					repos[user][repo] = stats
				}
				mt := plugin.MetricType{
					Data_:      repos[user][repo][stat],
					Namespace_: core.NewNamespace("raintank", "apps", "gitstats", "repo", user, repo, stat),
					Timestamp_: collectionTime,
					Version_:   m.Version(),
				}
				metrics = append(metrics, mt)
			}

		case "user":
			user := ns[4]
			stat := ns[5]
			if user == "*" {
				//need to get user
				if authUser == "" {
					gitUser, _, err := client.Users.Get(user)
					if err != nil {
						LogError("failed to get authenticated user.", err)
						return nil, err
					}
					authUser = *gitUser.Login
					stats, err := userStats(gitUser, client)
					if err != nil {
						LogError("failed to get stats from user object", err)
						return nil, err
					}
					users[*gitUser.Login] = stats
				}
			} else {
				if _, ok := users[user]; !ok {
					u, _, err := client.Users.Get(user)
					if err != nil {
						LogError("failed to lookup user.", err)
						return nil, err
					}
					stats, err := userStats(u, client)
					if err != nil {
						LogError("failed to get stats from user object.", err)
						return nil, err
					}
					users[user] = stats
				}
			}
			mt := plugin.MetricType{
				Data_:      users[user][stat],
				Namespace_: core.NewNamespace("raintank", "apps", "gitstats", "user", user, stat),
				Timestamp_: collectionTime,
				Version_:   m.Version(),
			}
			metrics = append(metrics, mt)
		}
	}

	return metrics, nil
}

func userStats(user *github.User, client *github.Client) (map[string]int, error) {
	stats := make(map[string]int)
	if user.PublicRepos != nil {
		stats["public_repos"] = *user.PublicRepos
	}
	if user.PublicGists != nil {
		stats["public_gists"] = *user.PublicGists
	}
	if user.Followers != nil {
		stats["followers"] = *user.Followers
	}
	if user.Following != nil {
		stats["following"] = *user.Following
	}

	if *user.Type == "Organization" {
		org, _, err := client.Organizations.Get(*user.Login)
		if err != nil {
			LogError("failed to lookup org data.", err)
			return nil, err
		}
		if org.PrivateGists != nil {
			stats["private_gists"] = *org.PrivateGists
		}
		if org.TotalPrivateRepos != nil {
			stats["private_repos"] = *org.TotalPrivateRepos
		}
		if org.DiskUsage != nil {
			stats["disk_usage"] = *org.DiskUsage
		}
	}

	return stats, nil
}

func repoStats(resp *github.Repository) (map[string]int, error) {
	stats := make(map[string]int)

	if resp.ForksCount != nil {
		stats["forks"] = *resp.ForksCount
	}
	if resp.OpenIssuesCount != nil {
		stats["issues"] = *resp.OpenIssuesCount
	}
	if resp.NetworkCount != nil {
		stats["network"] = *resp.NetworkCount
	}
	if resp.StargazersCount != nil {
		stats["stars"] = *resp.StargazersCount
	}
	if resp.SubscribersCount != nil {
		stats["subcribers"] = *resp.SubscribersCount
	}
	if resp.WatchersCount != nil {
		stats["watchers"] = *resp.WatchersCount
	}
	if resp.Size != nil {
		stats["size"] = *resp.Size
	}
	return stats, nil
}

//GetMetricTypes returns metric types for testing
func (f *Gitstats) GetMetricTypes(cfg plugin.ConfigType) ([]plugin.MetricType, error) {
	mts := make([]plugin.MetricType, 0)
	for _, metricName := range repoMetricNames {
		mts = append(mts, plugin.MetricType{
			Namespace_: core.NewNamespace("raintank", "apps", "gitstats", "repo").
				AddDynamicElement("owner", "repository owner").
				AddDynamicElement("repo", "repository name").
				AddStaticElement(metricName),
			Config_: cfg.ConfigDataNode,
		})
	}
	for _, metricName := range userMetricNames {
		mts = append(mts, plugin.MetricType{
			Namespace_: core.NewNamespace("raintank", "apps", "gitstats", "user").
				AddDynamicElement("user", "user or orginisation name").
				AddStaticElement(metricName),
			Config_: cfg.ConfigDataNode,
		})
	}
	return mts, nil
}

//GetConfigPolicy returns a ConfigPolicyTree for testing
func (f *Gitstats) GetConfigPolicy() (*cpolicy.ConfigPolicy, error) {
	c := cpolicy.New()
	rule, _ := cpolicy.NewStringRule("access_token", true)
	p := cpolicy.NewPolicyNode()
	p.Add(rule)
	c.Add([]string{"raintank", "apps", "gitstats"}, p)
	return c, nil
}

//Meta returns meta data for testing
func Meta() *plugin.PluginMeta {
	return plugin.NewPluginMeta(
		Name,
		Version,
		Type,
		[]string{plugin.SnapGOBContentType},
		[]string{plugin.SnapGOBContentType},
		plugin.Unsecure(true),
		plugin.ConcurrencyCount(1000),
	)
}
<commit_msg>update to work with latest go-github lib<commit_after>package gitstats

import (
	"fmt"
	"time"

	"github.com/google/go-github/github"
	. "github.com/intelsdi-x/snap-plugin-utilities/logger"
	"github.com/intelsdi-x/snap/control/plugin"
	"github.com/intelsdi-x/snap/control/plugin/cpolicy"
	"github.com/intelsdi-x/snap/core"
	"github.com/intelsdi-x/snap/core/ctypes"
	"golang.org/x/oauth2"
)

const (
	// Name of plugin
	Name = "rt-gitstats"
	// Version of plugin
	Version = 1
	// Type of plugin
	Type = plugin.CollectorPluginType
)

// make sure that we actually satisify requierd interface
var _ plugin.CollectorPlugin = (*Gitstats)(nil)

var (
	repoMetricNames = []string{
		"forks",
		"issues",
		"network",
		"stars",
		"subscribers",
		"watches",
		"size",
	}
	userMetricNames = []string{
		"public_repos",
		"public_gists",
		"followers",
		"following",
		"private_repos",
		"private_gists",
		"plan_private_repos",
		"plan_seats",
		"plan_filled_seats",
	}
)

type Gitstats struct {
}

// CollectMetrics collects metrics for testing
func (f *Gitstats) CollectMetrics(mts []plugin.MetricType) ([]plugin.MetricType, error) {
	var err error

	conf := mts[0].Config().Table()
	accessToken, ok := conf["access_token"]
	if !ok || accessToken.(ctypes.ConfigValueStr).Value == "" {
		return nil, fmt.Errorf("access token missing from config, %v", conf)
	}

	metrics, err := gitStats(accessToken.(ctypes.ConfigValueStr).Value, mts)
	if err != nil {
		return nil, err
	}

	return metrics, nil
}

type repoName struct {
	Repo  string
	Owner string
}

func gitStats(accessToken string, mts []plugin.MetricType) ([]plugin.MetricType, error) {
	ts := oauth2.StaticTokenSource(
		&oauth2.Token{AccessToken: accessToken},
	)
	tc := oauth2.NewClient(oauth2.NoContext, ts)
	client := github.NewClient(tc)
	collectionTime := time.Now()
	repos := make(map[string]map[string]map[string]int)
	users := make(map[string]map[string]int)

	userRepos := make(map[string]struct{})

	authUser := ""
	metrics := make([]plugin.MetricType, 0)

	for _, m := range mts {
		ns := m.Namespace().Strings()
		switch ns[3] {
		case "repo":
			user := ns[4]
			repo := ns[5]
			stat := ns[6]

			if user == "*" {
				//need to get user
				if authUser == "" {
					gitUser, _, err := client.Users.Get("")
					if err != nil {
						LogError("failed to get authenticated user.", err)
						return nil, err
					}
					stats, err := userStats(gitUser, client)
					if err != nil {
						LogError("failed to get stats from user object.", err)
						return nil, err
					}
					users[*gitUser.Login] = stats
					authUser = *gitUser.Login
				}
				user = authUser
			}
			if repo == "*" {
				// we only need to list a users repos once.
				if _, ok := userRepos[user]; !ok {
					repoList, _, err := client.Repositories.List(user, nil)
					if err != nil {
						LogError("failed to get repos owned by user.", err)
						return nil, err
					}
					userRepos[user] = struct{}{}
					if _, ok := repos[user]; !ok {
						repos[user] = make(map[string]map[string]int)
					}
					for _, r := range repoList {
						stats, err := repoStats(r)
						if err != nil {
							LogError("failed to get stats from repo object.", err)
							return nil, err
						}
						repos[user][*r.Name] = stats
					}
				}
				for repo, stats := range repos[user] {
					mt := plugin.MetricType{
						Data_:      stats[stat],
						Namespace_: core.NewNamespace("raintank", "apps", "gitstats", "repo", user, repo, stat),
						Timestamp_: collectionTime,
						Version_:   m.Version(),
					}
					metrics = append(metrics, mt)
				}

			} else {
				if _, ok := repos[user]; !ok {
					repos[user] = make(map[string]map[string]int)
				}
				if _, ok := repos[user][repo]; !ok {
					r, _, err := client.Repositories.Get(user, repo)
					if err != nil {
						LogError("failed to user repos.", err)
						return nil, err
					}
					stats, err := repoStats(r)
					if err != nil {
						LogError("failed to get stats from repo object.", err)
						return nil, err
					}
					repos[user][repo] = stats
				}
				mt := plugin.MetricType{
					Data_:      repos[user][repo][stat],
					Namespace_: core.NewNamespace("raintank", "apps", "gitstats", "repo", user, repo, stat),
					Timestamp_: collectionTime,
					Version_:   m.Version(),
				}
				metrics = append(metrics, mt)
			}

		case "user":
			user := ns[4]
			stat := ns[5]
			if user == "*" {
				//need to get user
				if authUser == "" {
					gitUser, _, err := client.Users.Get(user)
					if err != nil {
						LogError("failed to get authenticated user.", err)
						return nil, err
					}
					authUser = *gitUser.Login
					stats, err := userStats(gitUser, client)
					if err != nil {
						LogError("failed to get stats from user object", err)
						return nil, err
					}
					users[*gitUser.Login] = stats
				}
			} else {
				if _, ok := users[user]; !ok {
					u, _, err := client.Users.Get(user)
					if err != nil {
						LogError("failed to lookup user.", err)
						return nil, err
					}
					stats, err := userStats(u, client)
					if err != nil {
						LogError("failed to get stats from user object.", err)
						return nil, err
					}
					users[user] = stats
				}
			}
			mt := plugin.MetricType{
				Data_:      users[user][stat],
				Namespace_: core.NewNamespace("raintank", "apps", "gitstats", "user", user, stat),
				Timestamp_: collectionTime,
				Version_:   m.Version(),
			}
			metrics = append(metrics, mt)
		}
	}

	return metrics, nil
}

func userStats(user *github.User, client *github.Client) (map[string]int, error) {
	stats := make(map[string]int)
	if user.PublicRepos != nil {
		stats["public_repos"] = *user.PublicRepos
	}
	if user.PublicGists != nil {
		stats["public_gists"] = *user.PublicGists
	}
	if user.Followers != nil {
		stats["followers"] = *user.Followers
	}
	if user.Following != nil {
		stats["following"] = *user.Following
	}

	if *user.Type == "Organization" {
		org, _, err := client.Organizations.Get(*user.Login)
		if err != nil {
			LogError("failed to lookup org data.", err)
			return nil, err
		}
		if org.PrivateGists != nil {
			stats["private_gists"] = *org.PrivateGists
		}
		if org.TotalPrivateRepos != nil {
			stats["private_repos"] = *org.TotalPrivateRepos
		}
		if org.DiskUsage != nil {
			stats["disk_usage"] = *org.DiskUsage
		}
	}

	return stats, nil
}

func repoStats(resp *github.Repository) (map[string]int, error) {
	stats := make(map[string]int)

	if resp.ForksCount != nil {
		stats["forks"] = *resp.ForksCount
	}
	if resp.OpenIssuesCount != nil {
		stats["issues"] = *resp.OpenIssuesCount
	}
	if resp.NetworkCount != nil {
		stats["network"] = *resp.NetworkCount
	}
	if resp.StargazersCount != nil {
		stats["stars"] = *resp.StargazersCount
	}
	if resp.SubscribersCount != nil {
		stats["subcribers"] = *resp.SubscribersCount
	}
	if resp.WatchersCount != nil {
		stats["watchers"] = *resp.WatchersCount
	}
	if resp.Size != nil {
		stats["size"] = *resp.Size
	}
	return stats, nil
}

//GetMetricTypes returns metric types for testing
func (f *Gitstats) GetMetricTypes(cfg plugin.ConfigType) ([]plugin.MetricType, error) {
	mts := make([]plugin.MetricType, 0)
	for _, metricName := range repoMetricNames {
		mts = append(mts, plugin.MetricType{
			Namespace_: core.NewNamespace("raintank", "apps", "gitstats", "repo").
				AddDynamicElement("owner", "repository owner").
				AddDynamicElement("repo", "repository name").
				AddStaticElement(metricName),
			Config_: cfg.ConfigDataNode,
		})
	}
	for _, metricName := range userMetricNames {
		mts = append(mts, plugin.MetricType{
			Namespace_: core.NewNamespace("raintank", "apps", "gitstats", "user").
				AddDynamicElement("user", "user or orginisation name").
				AddStaticElement(metricName),
			Config_: cfg.ConfigDataNode,
		})
	}
	return mts, nil
}

//GetConfigPolicy returns a ConfigPolicyTree for testing
func (f *Gitstats) GetConfigPolicy() (*cpolicy.ConfigPolicy, error) {
	c := cpolicy.New()
	rule, _ := cpolicy.NewStringRule("access_token", true)
	p := cpolicy.NewPolicyNode()
	p.Add(rule)
	c.Add([]string{"raintank", "apps", "gitstats"}, p)
	return c, nil
}

//Meta returns meta data for testing
func Meta() *plugin.PluginMeta {
	return plugin.NewPluginMeta(
		Name,
		Version,
		Type,
		[]string{plugin.SnapGOBContentType},
		[]string{plugin.SnapGOBContentType},
		plugin.Unsecure(true),
		plugin.ConcurrencyCount(1000),
	)
}
<|endoftext|>


<commit_before>package main

import (
  "bufio"
  "io"
  "fmt"
  "log"
  "os"
  "path"
)

func main() {
  if len(os.Args) != 3 {
    log.Fatalf("Usage: %s input-file output-file", path.Base(os.Args[0]))
  }

  i, err := os.Open(os.Args[1])
  if err != nil {
    log.Fatalf("Cannot open %q for reading: %v", os.Args[1], err)
  }
  defer i.Close()
  r := bufio.NewReader(i)

  o, err := os.Create(os.Args[2])
  if err != nil {
    log.Fatalf("Cannot create new file %q: %v", os.Args[2], err)
  }
  defer o.Close()
  w := bufio.NewWriter(o)
  defer w.Flush()

  for {
    err = processBlock(r, w)
    if err == io.EOF {
      log.Println("Successfully reached EOF")
      break
    }
    if err != nil {
      log.Fatalf("Error whilst processing file: %v", err)
    }
  }
}

func processBlock(r *bufio.Reader, w *bufio.Writer) error {
  var err error
  start := true
  for {
    buf, err := r.Peek(1)
    if err != nil {
      return err
    }
    if start && (buf[0] != '0') {
      return fmt.Errorf("Expecting '0', got %q", buf[0])
    }
    if (!start) && (buf[0] == '0') {
      break
    }

    line, err := r.ReadString('\n')
    if len(line) > 0 {
      if _, werr := w.WriteString(line); err != nil {
        return werr
      }
    }

    start = false

    if err == io.EOF {
      break
    }
    if err != nil {
      return err
    }
  }
  return err
}
<commit_msg>Tidy up error detection.<commit_after>package main

import (
  "bufio"
  "io"
  "fmt"
  "log"
  "os"
  "path"
)

func main() {
  if len(os.Args) != 3 {
    log.Fatalf("Usage: %s input-file output-file", path.Base(os.Args[0]))
  }

  i, err := os.Open(os.Args[1])
  if err != nil {
    log.Fatalf("Cannot open %q for reading: %v", os.Args[1], err)
  }
  defer i.Close()
  r := bufio.NewReader(i)

  o, err := os.Create(os.Args[2])
  if err != nil {
    log.Fatalf("Cannot create new file %q: %v", os.Args[2], err)
  }
  defer o.Close()
  w := bufio.NewWriter(o)
  defer w.Flush()

  for {
    err = processBlock(r, w)
    if err == io.EOF {
      log.Println("Successfully reached EOF")
      break
    }
    if err != nil {
      log.Fatalf("Error whilst processing file: %v", err)
    }
  }
}

func processBlock(r *bufio.Reader, w *bufio.Writer) error {
  var err error
  start := true
  for {
    buf, err := r.Peek(1)
    if err != nil {
      return err
    }
    if start && (buf[0] != '0') {
      return fmt.Errorf("expecting %q, got %q", '0', buf[0])
    }
    if (!start) && (buf[0] == '0') {
      break
    }

    line, err := r.ReadString('\n')
    if len(line) > 0 {
      if _, werr := w.WriteString(line); err != nil {
        return werr
      }
    }

    start = false

    if err == io.EOF {
      break
    }
    if err != nil {
      return err
    }
  }
  return err
}
<|endoftext|>


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

package etcdtopo

import (
	"encoding/json"
	"fmt"
	"html/template"
	"net/http"
	"path"
	"strings"

	"github.com/coreos/go-etcd/etcd"
	ctlproto "github.com/youtube/vitess/go/cmd/vtctld/proto"
	"github.com/youtube/vitess/go/vt/topo"
)

const (
	explorerRoot = "/etcd"
	globalCell   = "global"
)

// Explorer is an implementation of vtctld's Explorer interface for etcd.
type Explorer struct {
	ts *Server
}

// NewExplorer implements vtctld Explorer.
func NewExplorer(ts *Server) *Explorer {
	return &Explorer{ts: ts}
}

// GetKeyspacePath implements vtctld Explorer.
func (ex Explorer) GetKeyspacePath(keyspace string) string {
	return path.Join(explorerRoot, globalCell, keyspaceDirPath(keyspace))
}

// GetShardPath implements vtctld Explorer.
func (ex Explorer) GetShardPath(keyspace, shard string) string {
	return path.Join(explorerRoot, globalCell, shardDirPath(keyspace, shard))
}

// GetSrvKeyspacePath implements vtctld Explorer.
func (ex Explorer) GetSrvKeyspacePath(cell, keyspace string) string {
	return path.Join(explorerRoot, cell, srvKeyspaceDirPath(keyspace))
}

// GetSrvShardPath implements vtctld Explorer.
func (ex Explorer) GetSrvShardPath(cell, keyspace, shard string) string {
	return path.Join(explorerRoot, cell, srvShardDirPath(keyspace, shard))
}

// GetSrvTypePath implements vtctld Explorer.
func (ex Explorer) GetSrvTypePath(cell, keyspace, shard string, tabletType topo.TabletType) string {
	return path.Join(explorerRoot, cell, endPointsDirPath(keyspace, shard, string(tabletType)))
}

// GetTabletPath implements vtctld Explorer.
func (ex Explorer) GetTabletPath(alias topo.TabletAlias) string {
	return path.Join(explorerRoot, alias.Cell, tabletDirPath(alias.TabletUidStr()))
}

// GetReplicationSlaves implements vtctld Explorer.
func (ex Explorer) GetReplicationSlaves(cell, keyspace, shard string) string {
	return path.Join(explorerRoot, cell, shardReplicationDirPath(keyspace, shard))
}

// HandlePath implements vtctld Explorer.
func (ex Explorer) HandlePath(actionRepo ctlproto.ActionRepository, rPath string, r *http.Request) interface{} {
	result := newExplorerResult(rPath)

	// Cut off explorerRoot prefix.
	if !strings.HasPrefix(rPath, explorerRoot) {
		result.Error = "invalid etcd explorer path: " + rPath
		return result
	}
	rPath = rPath[len(explorerRoot):]

	// Root is a list of cells.
	if rPath == "" {
		cells, err := ex.ts.getCellList()
		if err != nil {
			result.Error = err.Error()
			return result
		}
		result.Children = append([]string{globalCell}, cells...)
		return result
	}

	// Get a client for the requested cell.
	var client Client
	cell, rPath, err := splitCellPath(rPath)
	if err != nil {
		result.Error = err.Error()
		return result
	}
	if cell == globalCell {
		client = ex.ts.getGlobal()
	} else {
		client, err = ex.ts.getCell(cell)
		if err != nil {
			result.Error = "Can't get cell: " + err.Error()
			return result
		}
	}

	// Get the requested node data.
	resp, err := client.Get(rPath, true /* sort */, false /* recursive */)
	if err != nil {
		result.Error = err.Error()
		return result
	}
	if resp.Node == nil {
		result.Error = ErrBadResponse.Error()
		return result
	}
	result.Data = getNodeData(client, resp.Node)

	// Populate children.
	for _, node := range resp.Node.Nodes {
		result.Children = append(result.Children, path.Base(node.Key))
	}

	// Populate actions.
	if m, _ := path.Match(keyspaceDirPath("*"), rPath); m {
		actionRepo.PopulateKeyspaceActions(result.Actions, path.Base(rPath))
	} else if m, _ := path.Match(shardDirPath("*", "*"), rPath); m {
		if keyspace, shard, err := splitShardDirPath(rPath); err == nil {
			actionRepo.PopulateShardActions(result.Actions, keyspace, shard)
		}
	} else if m, _ := path.Match(tabletDirPath("*"), rPath); m {
		actionRepo.PopulateTabletActions(result.Actions, path.Base(rPath), r)
		addTabletLinks(result, result.Data)
	}
	return result
}

type explorerResult struct {
	Path     string
	Data     string
	Links    map[string]template.URL
	Children []string
	Actions  map[string]template.URL
	Error    string
}

func newExplorerResult(p string) *explorerResult {
	return &explorerResult{
		Links:   make(map[string]template.URL),
		Actions: make(map[string]template.URL),
		Path:    p,
	}
}

func getNodeData(client Client, node *etcd.Node) string {
	if !node.Dir {
		return node.Value
	}
	// Directories don't have data, but some directories have a special data file.
	resp, err := client.Get(path.Join(node.Key, dataFilename), false /* sort */, false /* recursive */)
	if err != nil || resp.Node == nil {
		return ""
	}
	return resp.Node.Value
}

// splitCellPath returns the cell name, and the rest of the path.
// For example: "/cell/rest/of/path" -> "cell", "/rest/of/path"
func splitCellPath(p string) (cell, rest string, err error) {
	parts := strings.SplitN(p, "/", 3)
	if len(parts) < 2 || parts[0] != "" {
		return "", "", fmt.Errorf("invalid etcd explorer path: %v", p)
	}
	if len(parts) < 3 {
		return parts[1], "/", nil
	}
	return parts[1], "/" + parts[2], nil
}

// splitShardDirPath takes a path that matches the path.Match() pattern
// shardDirPath("*", "*") and returns the keyspace and shard.
//
// We assume the path is of the form "/vt/keyspaces/*/*".
// If that ever changes, the unit test for this function will detect it.
func splitShardDirPath(p string) (keyspace, shard string, err error) {
	parts := strings.Split(p, "/")
	if len(parts) != 5 {
		return "", "", fmt.Errorf("invalid shard dir path: %v", p)
	}
	return parts[3], parts[4], nil
}

func addTabletLinks(result *explorerResult, data string) {
	t := &topo.Tablet{}
	err := json.Unmarshal([]byte(data), t)
	if err != nil {
		return
	}

	if port, ok := t.Portmap["vt"]; ok {
		result.Links["status"] = template.URL(fmt.Sprintf("http://%v:%v/debug/status", t.Hostname, port))
	}

	if !t.Parent.IsZero() {
		result.Links["parent"] = template.URL(
			path.Join(explorerRoot, t.Parent.Cell, tabletDirPath(t.Parent.String())))
	}
}
<commit_msg>Use full tablet alias for etcd explorer, not just uid.<commit_after>// Copyright 2014, Google Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package etcdtopo

import (
	"encoding/json"
	"fmt"
	"html/template"
	"net/http"
	"path"
	"strings"

	"github.com/coreos/go-etcd/etcd"
	ctlproto "github.com/youtube/vitess/go/cmd/vtctld/proto"
	"github.com/youtube/vitess/go/vt/topo"
)

const (
	explorerRoot = "/etcd"
	globalCell   = "global"
)

// Explorer is an implementation of vtctld's Explorer interface for etcd.
type Explorer struct {
	ts *Server
}

// NewExplorer implements vtctld Explorer.
func NewExplorer(ts *Server) *Explorer {
	return &Explorer{ts: ts}
}

// GetKeyspacePath implements vtctld Explorer.
func (ex Explorer) GetKeyspacePath(keyspace string) string {
	return path.Join(explorerRoot, globalCell, keyspaceDirPath(keyspace))
}

// GetShardPath implements vtctld Explorer.
func (ex Explorer) GetShardPath(keyspace, shard string) string {
	return path.Join(explorerRoot, globalCell, shardDirPath(keyspace, shard))
}

// GetSrvKeyspacePath implements vtctld Explorer.
func (ex Explorer) GetSrvKeyspacePath(cell, keyspace string) string {
	return path.Join(explorerRoot, cell, srvKeyspaceDirPath(keyspace))
}

// GetSrvShardPath implements vtctld Explorer.
func (ex Explorer) GetSrvShardPath(cell, keyspace, shard string) string {
	return path.Join(explorerRoot, cell, srvShardDirPath(keyspace, shard))
}

// GetSrvTypePath implements vtctld Explorer.
func (ex Explorer) GetSrvTypePath(cell, keyspace, shard string, tabletType topo.TabletType) string {
	return path.Join(explorerRoot, cell, endPointsDirPath(keyspace, shard, string(tabletType)))
}

// GetTabletPath implements vtctld Explorer.
func (ex Explorer) GetTabletPath(alias topo.TabletAlias) string {
	return path.Join(explorerRoot, alias.Cell, tabletDirPath(alias.String()))
}

// GetReplicationSlaves implements vtctld Explorer.
func (ex Explorer) GetReplicationSlaves(cell, keyspace, shard string) string {
	return path.Join(explorerRoot, cell, shardReplicationDirPath(keyspace, shard))
}

// HandlePath implements vtctld Explorer.
func (ex Explorer) HandlePath(actionRepo ctlproto.ActionRepository, rPath string, r *http.Request) interface{} {
	result := newExplorerResult(rPath)

	// Cut off explorerRoot prefix.
	if !strings.HasPrefix(rPath, explorerRoot) {
		result.Error = "invalid etcd explorer path: " + rPath
		return result
	}
	rPath = rPath[len(explorerRoot):]

	// Root is a list of cells.
	if rPath == "" {
		cells, err := ex.ts.getCellList()
		if err != nil {
			result.Error = err.Error()
			return result
		}
		result.Children = append([]string{globalCell}, cells...)
		return result
	}

	// Get a client for the requested cell.
	var client Client
	cell, rPath, err := splitCellPath(rPath)
	if err != nil {
		result.Error = err.Error()
		return result
	}
	if cell == globalCell {
		client = ex.ts.getGlobal()
	} else {
		client, err = ex.ts.getCell(cell)
		if err != nil {
			result.Error = "Can't get cell: " + err.Error()
			return result
		}
	}

	// Get the requested node data.
	resp, err := client.Get(rPath, true /* sort */, false /* recursive */)
	if err != nil {
		result.Error = err.Error()
		return result
	}
	if resp.Node == nil {
		result.Error = ErrBadResponse.Error()
		return result
	}
	result.Data = getNodeData(client, resp.Node)

	// Populate children.
	for _, node := range resp.Node.Nodes {
		result.Children = append(result.Children, path.Base(node.Key))
	}

	// Populate actions.
	if m, _ := path.Match(keyspaceDirPath("*"), rPath); m {
		actionRepo.PopulateKeyspaceActions(result.Actions, path.Base(rPath))
	} else if m, _ := path.Match(shardDirPath("*", "*"), rPath); m {
		if keyspace, shard, err := splitShardDirPath(rPath); err == nil {
			actionRepo.PopulateShardActions(result.Actions, keyspace, shard)
		}
	} else if m, _ := path.Match(tabletDirPath("*"), rPath); m {
		actionRepo.PopulateTabletActions(result.Actions, path.Base(rPath), r)
		addTabletLinks(result, result.Data)
	}
	return result
}

type explorerResult struct {
	Path     string
	Data     string
	Links    map[string]template.URL
	Children []string
	Actions  map[string]template.URL
	Error    string
}

func newExplorerResult(p string) *explorerResult {
	return &explorerResult{
		Links:   make(map[string]template.URL),
		Actions: make(map[string]template.URL),
		Path:    p,
	}
}

func getNodeData(client Client, node *etcd.Node) string {
	if !node.Dir {
		return node.Value
	}
	// Directories don't have data, but some directories have a special data file.
	resp, err := client.Get(path.Join(node.Key, dataFilename), false /* sort */, false /* recursive */)
	if err != nil || resp.Node == nil {
		return ""
	}
	return resp.Node.Value
}

// splitCellPath returns the cell name, and the rest of the path.
// For example: "/cell/rest/of/path" -> "cell", "/rest/of/path"
func splitCellPath(p string) (cell, rest string, err error) {
	parts := strings.SplitN(p, "/", 3)
	if len(parts) < 2 || parts[0] != "" {
		return "", "", fmt.Errorf("invalid etcd explorer path: %v", p)
	}
	if len(parts) < 3 {
		return parts[1], "/", nil
	}
	return parts[1], "/" + parts[2], nil
}

// splitShardDirPath takes a path that matches the path.Match() pattern
// shardDirPath("*", "*") and returns the keyspace and shard.
//
// We assume the path is of the form "/vt/keyspaces/*/*".
// If that ever changes, the unit test for this function will detect it.
func splitShardDirPath(p string) (keyspace, shard string, err error) {
	parts := strings.Split(p, "/")
	if len(parts) != 5 {
		return "", "", fmt.Errorf("invalid shard dir path: %v", p)
	}
	return parts[3], parts[4], nil
}

func addTabletLinks(result *explorerResult, data string) {
	t := &topo.Tablet{}
	err := json.Unmarshal([]byte(data), t)
	if err != nil {
		return
	}

	if port, ok := t.Portmap["vt"]; ok {
		result.Links["status"] = template.URL(fmt.Sprintf("http://%v:%v/debug/status", t.Hostname, port))
	}

	if !t.Parent.IsZero() {
		result.Links["parent"] = template.URL(
			path.Join(explorerRoot, t.Parent.Cell, tabletDirPath(t.Parent.String())))
	}
}
<|endoftext|>


<commit_before>// Package test contains utilities to test topo.Server
// implementations. If you are testing your implementation, you will
// want to call CheckAll in your test method. For an example, look at
// the tests in github.com/youtube/vitess/go/vt/zktopo.
package test

import (
	"reflect"
	"testing"

	"github.com/youtube/vitess/go/vt/key"
	"github.com/youtube/vitess/go/vt/topo"
	"golang.org/x/net/context"
)

// CheckServingGraph makes sure the serving graph functions work properly.
func CheckServingGraph(ctx context.Context, t *testing.T, ts topo.Server) {
	cell := getLocalCell(t, ts)

	// test individual cell/keyspace/shard/type entries
	if _, err := ts.GetSrvTabletTypesPerShard(cell, "test_keyspace", "-10"); err != topo.ErrNoNode {
		t.Errorf("GetSrvTabletTypesPerShard(invalid): %v", err)
	}
	if _, err := ts.GetEndPoints(cell, "test_keyspace", "-10", topo.TYPE_MASTER); err != topo.ErrNoNode {
		t.Errorf("GetEndPoints(invalid): %v", err)
	}

	endPoints := topo.EndPoints{
		Entries: []topo.EndPoint{
			topo.EndPoint{
				Uid:          1,
				Host:         "host1",
				NamedPortMap: map[string]int{"vt": 1234, "mysql": 1235, "vts": 1236},
			},
		},
	}

	if err := topo.UpdateEndPoints(ctx, ts, cell, "test_keyspace", "-10", topo.TYPE_MASTER, &endPoints); err != nil {
		t.Fatalf("UpdateEndPoints(master): %v", err)
	}
	if types, err := ts.GetSrvTabletTypesPerShard(cell, "test_keyspace", "-10"); err != nil || len(types) != 1 || types[0] != topo.TYPE_MASTER {
		t.Errorf("GetSrvTabletTypesPerShard(1): %v %v", err, types)
	}

	// Delete the SrvShard (need to delete endpoints first).
	if err := ts.DeleteEndPoints(cell, "test_keyspace", "-10", topo.TYPE_MASTER); err != nil {
		t.Errorf("DeleteEndPoints: %v", err)
	}
	if err := ts.DeleteSrvShard(cell, "test_keyspace", "-10"); err != nil {
		t.Errorf("DeleteSrvShard: %v", err)
	}
	if _, err := ts.GetSrvShard(cell, "test_keyspace", "-10"); err != topo.ErrNoNode {
		t.Errorf("GetSrvShard(deleted) got %v, want ErrNoNode", err)
	}

	// Re-add endpoints.
	if err := topo.UpdateEndPoints(ctx, ts, cell, "test_keyspace", "-10", topo.TYPE_MASTER, &endPoints); err != nil {
		t.Fatalf("UpdateEndPoints(master): %v", err)
	}

	addrs, err := ts.GetEndPoints(cell, "test_keyspace", "-10", topo.TYPE_MASTER)
	if err != nil {
		t.Errorf("GetEndPoints: %v", err)
	}
	if len(addrs.Entries) != 1 || addrs.Entries[0].Uid != 1 {
		t.Errorf("GetEndPoints(1): %v", addrs)
	}
	if pm := addrs.Entries[0].NamedPortMap; pm["vt"] != 1234 || pm["mysql"] != 1235 || pm["vts"] != 1236 {
		t.Errorf("GetSrcTabletType(1).NamedPortmap: want %v, got %v", endPoints.Entries[0].NamedPortMap, pm)
	}

	if err := ts.UpdateTabletEndpoint(cell, "test_keyspace", "-10", topo.TYPE_REPLICA, &topo.EndPoint{Uid: 2, Host: "host2"}); err != nil {
		t.Fatalf("UpdateTabletEndpoint(invalid): %v", err)
	}
	if err := ts.UpdateTabletEndpoint(cell, "test_keyspace", "-10", topo.TYPE_MASTER, &topo.EndPoint{Uid: 1, Host: "host2"}); err != nil {
		t.Fatalf("UpdateTabletEndpoint(master): %v", err)
	}
	if addrs, err := ts.GetEndPoints(cell, "test_keyspace", "-10", topo.TYPE_MASTER); err != nil || len(addrs.Entries) != 1 || addrs.Entries[0].Uid != 1 {
		t.Errorf("GetEndPoints(2): %v %v", err, addrs)
	}
	if err := ts.UpdateTabletEndpoint(cell, "test_keyspace", "-10", topo.TYPE_MASTER, &topo.EndPoint{Uid: 3, Host: "host3"}); err != nil {
		t.Fatalf("UpdateTabletEndpoint(master): %v", err)
	}
	if addrs, err := ts.GetEndPoints(cell, "test_keyspace", "-10", topo.TYPE_MASTER); err != nil || len(addrs.Entries) != 2 {
		t.Errorf("GetEndPoints(2): %v %v", err, addrs)
	}

	if err := ts.DeleteEndPoints(cell, "test_keyspace", "-10", topo.TYPE_REPLICA); err != topo.ErrNoNode {
		t.Errorf("DeleteEndPoints(unknown): %v", err)
	}
	if err := ts.DeleteEndPoints(cell, "test_keyspace", "-10", topo.TYPE_MASTER); err != nil {
		t.Errorf("DeleteEndPoints(master): %v", err)
	}

	// test cell/keyspace/shard entries (SrvShard)
	srvShard := topo.SrvShard{
		ServedTypes: []topo.TabletType{topo.TYPE_MASTER},
		TabletTypes: []topo.TabletType{topo.TYPE_REPLICA, topo.TYPE_RDONLY},
	}
	if err := ts.UpdateSrvShard(cell, "test_keyspace", "-10", &srvShard); err != nil {
		t.Fatalf("UpdateSrvShard(1): %v", err)
	}
	if _, err := ts.GetSrvShard(cell, "test_keyspace", "666"); err != topo.ErrNoNode {
		t.Errorf("GetSrvShard(invalid): %v", err)
	}
	if s, err := ts.GetSrvShard(cell, "test_keyspace", "-10"); err != nil ||
		len(s.ServedTypes) != 1 ||
		s.ServedTypes[0] != topo.TYPE_MASTER ||
		len(s.TabletTypes) != 2 ||
		s.TabletTypes[0] != topo.TYPE_REPLICA ||
		s.TabletTypes[1] != topo.TYPE_RDONLY {
		t.Errorf("GetSrvShard(valid): %v", err)
	}

	// test cell/keyspace entries (SrvKeyspace)
	srvKeyspace := topo.SrvKeyspace{
		Partitions: map[topo.TabletType]*topo.KeyspacePartition{
			topo.TYPE_MASTER: &topo.KeyspacePartition{
				Shards: []topo.SrvShard{
					topo.SrvShard{
						ServedTypes: []topo.TabletType{topo.TYPE_MASTER},
					},
				},
			},
		},
		TabletTypes:        []topo.TabletType{topo.TYPE_MASTER},
		ShardingColumnName: "video_id",
		ShardingColumnType: key.KIT_UINT64,
		ServedFrom: map[topo.TabletType]string{
			topo.TYPE_REPLICA: "other_keyspace",
		},
	}
	if err := ts.UpdateSrvKeyspace(cell, "test_keyspace", &srvKeyspace); err != nil {
		t.Errorf("UpdateSrvKeyspace(1): %v", err)
	}
	if _, err := ts.GetSrvKeyspace(cell, "test_keyspace666"); err != topo.ErrNoNode {
		t.Errorf("GetSrvKeyspace(invalid): %v", err)
	}
	if k, err := ts.GetSrvKeyspace(cell, "test_keyspace"); err != nil ||
		len(k.TabletTypes) != 1 ||
		k.TabletTypes[0] != topo.TYPE_MASTER ||
		len(k.Partitions) != 1 ||
		len(k.Partitions[topo.TYPE_MASTER].Shards) != 1 ||
		len(k.Partitions[topo.TYPE_MASTER].Shards[0].ServedTypes) != 1 ||
		k.Partitions[topo.TYPE_MASTER].Shards[0].ServedTypes[0] != topo.TYPE_MASTER ||
		k.ShardingColumnName != "video_id" ||
		k.ShardingColumnType != key.KIT_UINT64 ||
		k.ServedFrom[topo.TYPE_REPLICA] != "other_keyspace" {
		t.Errorf("GetSrvKeyspace(valid): %v %v", err, k)
	}
	if k, err := ts.GetSrvKeyspaceNames(cell); err != nil || len(k) != 1 || k[0] != "test_keyspace" {
		t.Errorf("GetSrvKeyspaceNames(): %v", err)
	}

	// check that updating a SrvKeyspace out of the blue works
	if err := ts.UpdateSrvKeyspace(cell, "unknown_keyspace_so_far", &srvKeyspace); err != nil {
		t.Fatalf("UpdateSrvKeyspace(2): %v", err)
	}
	if k, err := ts.GetSrvKeyspace(cell, "unknown_keyspace_so_far"); err != nil ||
		len(k.TabletTypes) != 1 ||
		k.TabletTypes[0] != topo.TYPE_MASTER ||
		len(k.Partitions) != 1 ||
		len(k.Partitions[topo.TYPE_MASTER].Shards) != 1 ||
		len(k.Partitions[topo.TYPE_MASTER].Shards[0].ServedTypes) != 1 ||
		k.Partitions[topo.TYPE_MASTER].Shards[0].ServedTypes[0] != topo.TYPE_MASTER ||
		k.ShardingColumnName != "video_id" ||
		k.ShardingColumnType != key.KIT_UINT64 ||
		k.ServedFrom[topo.TYPE_REPLICA] != "other_keyspace" {
		t.Errorf("GetSrvKeyspace(out of the blue): %v %v", err, *k)
	}
}

// CheckWatchEndPoints makes sure WatchEndPoints works as expected
func CheckWatchEndPoints(ctx context.Context, t *testing.T, ts topo.Server) {
	cell := getLocalCell(t, ts)
	keyspace := "test_keyspace"
	shard := "-10"
	tabletType := topo.TYPE_MASTER

	// start watching, should get nil first
	notifications, stopWatching, err := ts.WatchEndPoints(cell, keyspace, shard, tabletType)
	if err != nil {
		t.Fatalf("WatchEndPoints failed: %v", err)
	}
	ep, ok := <-notifications
	if !ok || ep != nil {
		t.Fatalf("first value is wrong: %v %v", ep, ok)
	}

	// update the endpoints, should get a notification
	endPoints := topo.EndPoints{
		Entries: []topo.EndPoint{
			topo.EndPoint{
				Uid:          1,
				Host:         "host1",
				NamedPortMap: map[string]int{"vt": 1234, "mysql": 1235, "vts": 1236},
			},
		},
	}
	if err := topo.UpdateEndPoints(ctx, ts, cell, keyspace, shard, tabletType, &endPoints); err != nil {
		t.Fatalf("UpdateEndPoints failed: %v", err)
	}
	for {
		ep, ok := <-notifications
		if !ok {
			t.Fatalf("watch channel is closed???")
		}
		if ep == nil {
			// duplicate notification of the first value, that's OK
			continue
		}
		// non-empty value, that one should be ours
		if !reflect.DeepEqual(&endPoints, ep) {
			t.Fatalf("first value is wrong: %v %v", ep, ok)
		}
		break
	}

	// delete the endpoints, should get a notification
	if err := ts.DeleteEndPoints(cell, keyspace, shard, tabletType); err != nil {
		t.Fatalf("DeleteEndPoints failed: %v", err)
	}
	for {
		ep, ok := <-notifications
		if !ok {
			t.Fatalf("watch channel is closed???")
		}
		if ep == nil {
			break
		}

		// duplicate notification of the first value, that's OK,
		// but value better be good.
		if !reflect.DeepEqual(&endPoints, ep) {
			t.Fatalf("duplicate notification value is bad: %v", ep)
		}
	}

	// re-create the value, a bit different, should get a notification
	endPoints.Entries[0].Uid = 2
	if err := topo.UpdateEndPoints(ctx, ts, cell, keyspace, shard, tabletType, &endPoints); err != nil {
		t.Fatalf("UpdateEndPoints failed: %v", err)
	}
	for {
		ep, ok := <-notifications
		if !ok {
			t.Fatalf("watch channel is closed???")
		}
		if ep == nil {
			// duplicate notification of the closed value, that's OK
			continue
		}
		// non-empty value, that one should be ours
		if !reflect.DeepEqual(&endPoints, ep) {
			t.Fatalf("value after delete / re-create is wrong: %v %v", ep, ok)
		}
		break
	}

	// close the stopWatching channel, should eventually get a closed
	// notifications channel too
	close(stopWatching)
	for {
		ep, ok := <-notifications
		if !ok {
			break
		}
		if !reflect.DeepEqual(&endPoints, ep) {
			t.Fatalf("duplicate notification value is bad: %v", ep)
		}
	}
}
<commit_msg>Adding unit tests for ShardReference packing.<commit_after>// Package test contains utilities to test topo.Server
// implementations. If you are testing your implementation, you will
// want to call CheckAll in your test method. For an example, look at
// the tests in github.com/youtube/vitess/go/vt/zktopo.
package test

import (
	"reflect"
	"testing"

	"github.com/youtube/vitess/go/vt/key"
	"github.com/youtube/vitess/go/vt/topo"
	"golang.org/x/net/context"
)

// CheckServingGraph makes sure the serving graph functions work properly.
func CheckServingGraph(ctx context.Context, t *testing.T, ts topo.Server) {
	cell := getLocalCell(t, ts)

	// test individual cell/keyspace/shard/type entries
	if _, err := ts.GetSrvTabletTypesPerShard(cell, "test_keyspace", "-10"); err != topo.ErrNoNode {
		t.Errorf("GetSrvTabletTypesPerShard(invalid): %v", err)
	}
	if _, err := ts.GetEndPoints(cell, "test_keyspace", "-10", topo.TYPE_MASTER); err != topo.ErrNoNode {
		t.Errorf("GetEndPoints(invalid): %v", err)
	}

	endPoints := topo.EndPoints{
		Entries: []topo.EndPoint{
			topo.EndPoint{
				Uid:          1,
				Host:         "host1",
				NamedPortMap: map[string]int{"vt": 1234, "mysql": 1235, "vts": 1236},
			},
		},
	}

	if err := topo.UpdateEndPoints(ctx, ts, cell, "test_keyspace", "-10", topo.TYPE_MASTER, &endPoints); err != nil {
		t.Fatalf("UpdateEndPoints(master): %v", err)
	}
	if types, err := ts.GetSrvTabletTypesPerShard(cell, "test_keyspace", "-10"); err != nil || len(types) != 1 || types[0] != topo.TYPE_MASTER {
		t.Errorf("GetSrvTabletTypesPerShard(1): %v %v", err, types)
	}

	// Delete the SrvShard (need to delete endpoints first).
	if err := ts.DeleteEndPoints(cell, "test_keyspace", "-10", topo.TYPE_MASTER); err != nil {
		t.Errorf("DeleteEndPoints: %v", err)
	}
	if err := ts.DeleteSrvShard(cell, "test_keyspace", "-10"); err != nil {
		t.Errorf("DeleteSrvShard: %v", err)
	}
	if _, err := ts.GetSrvShard(cell, "test_keyspace", "-10"); err != topo.ErrNoNode {
		t.Errorf("GetSrvShard(deleted) got %v, want ErrNoNode", err)
	}

	// Re-add endpoints.
	if err := topo.UpdateEndPoints(ctx, ts, cell, "test_keyspace", "-10", topo.TYPE_MASTER, &endPoints); err != nil {
		t.Fatalf("UpdateEndPoints(master): %v", err)
	}

	addrs, err := ts.GetEndPoints(cell, "test_keyspace", "-10", topo.TYPE_MASTER)
	if err != nil {
		t.Errorf("GetEndPoints: %v", err)
	}
	if len(addrs.Entries) != 1 || addrs.Entries[0].Uid != 1 {
		t.Errorf("GetEndPoints(1): %v", addrs)
	}
	if pm := addrs.Entries[0].NamedPortMap; pm["vt"] != 1234 || pm["mysql"] != 1235 || pm["vts"] != 1236 {
		t.Errorf("GetSrcTabletType(1).NamedPortmap: want %v, got %v", endPoints.Entries[0].NamedPortMap, pm)
	}

	if err := ts.UpdateTabletEndpoint(cell, "test_keyspace", "-10", topo.TYPE_REPLICA, &topo.EndPoint{Uid: 2, Host: "host2"}); err != nil {
		t.Fatalf("UpdateTabletEndpoint(invalid): %v", err)
	}
	if err := ts.UpdateTabletEndpoint(cell, "test_keyspace", "-10", topo.TYPE_MASTER, &topo.EndPoint{Uid: 1, Host: "host2"}); err != nil {
		t.Fatalf("UpdateTabletEndpoint(master): %v", err)
	}
	if addrs, err := ts.GetEndPoints(cell, "test_keyspace", "-10", topo.TYPE_MASTER); err != nil || len(addrs.Entries) != 1 || addrs.Entries[0].Uid != 1 {
		t.Errorf("GetEndPoints(2): %v %v", err, addrs)
	}
	if err := ts.UpdateTabletEndpoint(cell, "test_keyspace", "-10", topo.TYPE_MASTER, &topo.EndPoint{Uid: 3, Host: "host3"}); err != nil {
		t.Fatalf("UpdateTabletEndpoint(master): %v", err)
	}
	if addrs, err := ts.GetEndPoints(cell, "test_keyspace", "-10", topo.TYPE_MASTER); err != nil || len(addrs.Entries) != 2 {
		t.Errorf("GetEndPoints(2): %v %v", err, addrs)
	}

	if err := ts.DeleteEndPoints(cell, "test_keyspace", "-10", topo.TYPE_REPLICA); err != topo.ErrNoNode {
		t.Errorf("DeleteEndPoints(unknown): %v", err)
	}
	if err := ts.DeleteEndPoints(cell, "test_keyspace", "-10", topo.TYPE_MASTER); err != nil {
		t.Errorf("DeleteEndPoints(master): %v", err)
	}

	// test cell/keyspace/shard entries (SrvShard)
	srvShard := topo.SrvShard{
		ServedTypes: []topo.TabletType{topo.TYPE_MASTER},
		TabletTypes: []topo.TabletType{topo.TYPE_REPLICA, topo.TYPE_RDONLY},
	}
	if err := ts.UpdateSrvShard(cell, "test_keyspace", "-10", &srvShard); err != nil {
		t.Fatalf("UpdateSrvShard(1): %v", err)
	}
	if _, err := ts.GetSrvShard(cell, "test_keyspace", "666"); err != topo.ErrNoNode {
		t.Errorf("GetSrvShard(invalid): %v", err)
	}
	if s, err := ts.GetSrvShard(cell, "test_keyspace", "-10"); err != nil ||
		len(s.ServedTypes) != 1 ||
		s.ServedTypes[0] != topo.TYPE_MASTER ||
		len(s.TabletTypes) != 2 ||
		s.TabletTypes[0] != topo.TYPE_REPLICA ||
		s.TabletTypes[1] != topo.TYPE_RDONLY {
		t.Errorf("GetSrvShard(valid): %v", err)
	}

	// test cell/keyspace entries (SrvKeyspace)
	srvKeyspace := topo.SrvKeyspace{
		Partitions: map[topo.TabletType]*topo.KeyspacePartition{
			topo.TYPE_MASTER: &topo.KeyspacePartition{
				Shards: []topo.SrvShard{
					topo.SrvShard{
						ServedTypes: []topo.TabletType{topo.TYPE_MASTER},
					},
				},
				ShardReferences: []topo.ShardReference{
					topo.ShardReference{
						Name:     "-80",
						KeyRange: newKeyRange("-80"),
					},
				},
			},
		},
		TabletTypes:        []topo.TabletType{topo.TYPE_MASTER},
		ShardingColumnName: "video_id",
		ShardingColumnType: key.KIT_UINT64,
		ServedFrom: map[topo.TabletType]string{
			topo.TYPE_REPLICA: "other_keyspace",
		},
	}
	if err := ts.UpdateSrvKeyspace(cell, "test_keyspace", &srvKeyspace); err != nil {
		t.Errorf("UpdateSrvKeyspace(1): %v", err)
	}
	if _, err := ts.GetSrvKeyspace(cell, "test_keyspace666"); err != topo.ErrNoNode {
		t.Errorf("GetSrvKeyspace(invalid): %v", err)
	}
	if k, err := ts.GetSrvKeyspace(cell, "test_keyspace"); err != nil ||
		len(k.TabletTypes) != 1 ||
		k.TabletTypes[0] != topo.TYPE_MASTER ||
		len(k.Partitions) != 1 ||
		len(k.Partitions[topo.TYPE_MASTER].Shards) != 1 ||
		len(k.Partitions[topo.TYPE_MASTER].Shards[0].ServedTypes) != 1 ||
		k.Partitions[topo.TYPE_MASTER].Shards[0].ServedTypes[0] != topo.TYPE_MASTER ||
		len(k.Partitions[topo.TYPE_MASTER].ShardReferences) != 1 ||
		k.Partitions[topo.TYPE_MASTER].ShardReferences[0].Name != "-80" ||
		k.Partitions[topo.TYPE_MASTER].ShardReferences[0].KeyRange != newKeyRange("-80") ||
		k.ShardingColumnName != "video_id" ||
		k.ShardingColumnType != key.KIT_UINT64 ||
		k.ServedFrom[topo.TYPE_REPLICA] != "other_keyspace" {
		t.Errorf("GetSrvKeyspace(valid): %v %v", err, k)
	}
	if k, err := ts.GetSrvKeyspaceNames(cell); err != nil || len(k) != 1 || k[0] != "test_keyspace" {
		t.Errorf("GetSrvKeyspaceNames(): %v", err)
	}

	// check that updating a SrvKeyspace out of the blue works
	if err := ts.UpdateSrvKeyspace(cell, "unknown_keyspace_so_far", &srvKeyspace); err != nil {
		t.Fatalf("UpdateSrvKeyspace(2): %v", err)
	}
	if k, err := ts.GetSrvKeyspace(cell, "unknown_keyspace_so_far"); err != nil ||
		len(k.TabletTypes) != 1 ||
		k.TabletTypes[0] != topo.TYPE_MASTER ||
		len(k.Partitions) != 1 ||
		len(k.Partitions[topo.TYPE_MASTER].Shards) != 1 ||
		len(k.Partitions[topo.TYPE_MASTER].Shards[0].ServedTypes) != 1 ||
		k.Partitions[topo.TYPE_MASTER].Shards[0].ServedTypes[0] != topo.TYPE_MASTER ||
		len(k.Partitions[topo.TYPE_MASTER].ShardReferences) != 1 ||
		k.Partitions[topo.TYPE_MASTER].ShardReferences[0].Name != "-80" ||
		k.Partitions[topo.TYPE_MASTER].ShardReferences[0].KeyRange != newKeyRange("-80") ||
		k.ShardingColumnName != "video_id" ||
		k.ShardingColumnType != key.KIT_UINT64 ||
		k.ServedFrom[topo.TYPE_REPLICA] != "other_keyspace" {
		t.Errorf("GetSrvKeyspace(out of the blue): %v %v", err, *k)
	}
}

// CheckWatchEndPoints makes sure WatchEndPoints works as expected
func CheckWatchEndPoints(ctx context.Context, t *testing.T, ts topo.Server) {
	cell := getLocalCell(t, ts)
	keyspace := "test_keyspace"
	shard := "-10"
	tabletType := topo.TYPE_MASTER

	// start watching, should get nil first
	notifications, stopWatching, err := ts.WatchEndPoints(cell, keyspace, shard, tabletType)
	if err != nil {
		t.Fatalf("WatchEndPoints failed: %v", err)
	}
	ep, ok := <-notifications
	if !ok || ep != nil {
		t.Fatalf("first value is wrong: %v %v", ep, ok)
	}

	// update the endpoints, should get a notification
	endPoints := topo.EndPoints{
		Entries: []topo.EndPoint{
			topo.EndPoint{
				Uid:          1,
				Host:         "host1",
				NamedPortMap: map[string]int{"vt": 1234, "mysql": 1235, "vts": 1236},
			},
		},
	}
	if err := topo.UpdateEndPoints(ctx, ts, cell, keyspace, shard, tabletType, &endPoints); err != nil {
		t.Fatalf("UpdateEndPoints failed: %v", err)
	}
	for {
		ep, ok := <-notifications
		if !ok {
			t.Fatalf("watch channel is closed???")
		}
		if ep == nil {
			// duplicate notification of the first value, that's OK
			continue
		}
		// non-empty value, that one should be ours
		if !reflect.DeepEqual(&endPoints, ep) {
			t.Fatalf("first value is wrong: %v %v", ep, ok)
		}
		break
	}

	// delete the endpoints, should get a notification
	if err := ts.DeleteEndPoints(cell, keyspace, shard, tabletType); err != nil {
		t.Fatalf("DeleteEndPoints failed: %v", err)
	}
	for {
		ep, ok := <-notifications
		if !ok {
			t.Fatalf("watch channel is closed???")
		}
		if ep == nil {
			break
		}

		// duplicate notification of the first value, that's OK,
		// but value better be good.
		if !reflect.DeepEqual(&endPoints, ep) {
			t.Fatalf("duplicate notification value is bad: %v", ep)
		}
	}

	// re-create the value, a bit different, should get a notification
	endPoints.Entries[0].Uid = 2
	if err := topo.UpdateEndPoints(ctx, ts, cell, keyspace, shard, tabletType, &endPoints); err != nil {
		t.Fatalf("UpdateEndPoints failed: %v", err)
	}
	for {
		ep, ok := <-notifications
		if !ok {
			t.Fatalf("watch channel is closed???")
		}
		if ep == nil {
			// duplicate notification of the closed value, that's OK
			continue
		}
		// non-empty value, that one should be ours
		if !reflect.DeepEqual(&endPoints, ep) {
			t.Fatalf("value after delete / re-create is wrong: %v %v", ep, ok)
		}
		break
	}

	// close the stopWatching channel, should eventually get a closed
	// notifications channel too
	close(stopWatching)
	for {
		ep, ok := <-notifications
		if !ok {
			break
		}
		if !reflect.DeepEqual(&endPoints, ep) {
			t.Fatalf("duplicate notification value is bad: %v", ep)
		}
	}
}
<|endoftext|>


<commit_before>// ----------------------------------------------------------------------------
//
//     ***     AUTO GENERATED CODE    ***    AUTO GENERATED CODE     ***
//
// ----------------------------------------------------------------------------
//
//     This file is automatically generated by Magic Modules and manual
//     changes will be clobbered when the file is regenerated.
//
//     Please read more about how to change this file in
//     .github/CONTRIBUTING.md.
//
// ----------------------------------------------------------------------------

package google

import "reflect"

func GetCloudTasksQueueCaiObject(d TerraformResourceData, config *Config) (Asset, error) {
	name, err := assetName(d, config, "//cloudtasks.googleapis.com/projects/{{project}}/locations/{{location}}/queues/{{name}}")
	if err != nil {
		return Asset{}, err
	}
	if obj, err := GetCloudTasksQueueApiObject(d, config); err == nil {
		return Asset{
			Name: name,
			Type: "cloudtasks.googleapis.com/Queue",
			Resource: &AssetResource{
				Version:              "v2",
				DiscoveryDocumentURI: "https://www.googleapis.com/discovery/v1/apis/cloudtasks/v2/rest",
				DiscoveryName:        "Queue",
				Data:                 obj,
			},
		}, nil
	} else {
		return Asset{}, err
	}
}

func GetCloudTasksQueueApiObject(d TerraformResourceData, config *Config) (map[string]interface{}, error) {
	obj := make(map[string]interface{})
	nameProp, err := expandCloudTasksQueueName(d.Get("name"), d, config)
	if err != nil {
		return nil, err
	} else if v, ok := d.GetOkExists("name"); !isEmptyValue(reflect.ValueOf(nameProp)) && (ok || !reflect.DeepEqual(v, nameProp)) {
		obj["name"] = nameProp
	}
	appEngineRoutingOverrideProp, err := expandCloudTasksQueueAppEngineRoutingOverride(d.Get("app_engine_routing_override"), d, config)
	if err != nil {
		return nil, err
	} else if v, ok := d.GetOkExists("app_engine_routing_override"); !isEmptyValue(reflect.ValueOf(appEngineRoutingOverrideProp)) && (ok || !reflect.DeepEqual(v, appEngineRoutingOverrideProp)) {
		obj["appEngineRoutingOverride"] = appEngineRoutingOverrideProp
	}
	rateLimitsProp, err := expandCloudTasksQueueRateLimits(d.Get("rate_limits"), d, config)
	if err != nil {
		return nil, err
	} else if v, ok := d.GetOkExists("rate_limits"); !isEmptyValue(reflect.ValueOf(rateLimitsProp)) && (ok || !reflect.DeepEqual(v, rateLimitsProp)) {
		obj["rateLimits"] = rateLimitsProp
	}
	retryConfigProp, err := expandCloudTasksQueueRetryConfig(d.Get("retry_config"), d, config)
	if err != nil {
		return nil, err
	} else if v, ok := d.GetOkExists("retry_config"); !isEmptyValue(reflect.ValueOf(retryConfigProp)) && (ok || !reflect.DeepEqual(v, retryConfigProp)) {
		obj["retryConfig"] = retryConfigProp
	}

	return obj, nil
}

func expandCloudTasksQueueName(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
	return replaceVars(d, config, "projects/{{project}}/locations/{{location}}/queues/{{name}}")
}

func expandCloudTasksQueueAppEngineRoutingOverride(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
	l := v.([]interface{})
	if len(l) == 0 || l[0] == nil {
		return nil, nil
	}
	raw := l[0]
	original := raw.(map[string]interface{})
	transformed := make(map[string]interface{})

	transformedService, err := expandCloudTasksQueueAppEngineRoutingOverrideService(original["service"], d, config)
	if err != nil {
		return nil, err
	} else if val := reflect.ValueOf(transformedService); val.IsValid() && !isEmptyValue(val) {
		transformed["service"] = transformedService
	}

	transformedVersion, err := expandCloudTasksQueueAppEngineRoutingOverrideVersion(original["version"], d, config)
	if err != nil {
		return nil, err
	} else if val := reflect.ValueOf(transformedVersion); val.IsValid() && !isEmptyValue(val) {
		transformed["version"] = transformedVersion
	}

	transformedInstance, err := expandCloudTasksQueueAppEngineRoutingOverrideInstance(original["instance"], d, config)
	if err != nil {
		return nil, err
	} else if val := reflect.ValueOf(transformedInstance); val.IsValid() && !isEmptyValue(val) {
		transformed["instance"] = transformedInstance
	}

	transformedHost, err := expandCloudTasksQueueAppEngineRoutingOverrideHost(original["host"], d, config)
	if err != nil {
		return nil, err
	} else if val := reflect.ValueOf(transformedHost); val.IsValid() && !isEmptyValue(val) {
		transformed["host"] = transformedHost
	}

	return transformed, nil
}

func expandCloudTasksQueueAppEngineRoutingOverrideService(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
	return v, nil
}

func expandCloudTasksQueueAppEngineRoutingOverrideVersion(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
	return v, nil
}

func expandCloudTasksQueueAppEngineRoutingOverrideInstance(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
	return v, nil
}

func expandCloudTasksQueueAppEngineRoutingOverrideHost(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
	return v, nil
}

func expandCloudTasksQueueRateLimits(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
	l := v.([]interface{})
	if len(l) == 0 || l[0] == nil {
		return nil, nil
	}
	raw := l[0]
	original := raw.(map[string]interface{})
	transformed := make(map[string]interface{})

	transformedMaxDispatchesPerSecond, err := expandCloudTasksQueueRateLimitsMaxDispatchesPerSecond(original["max_dispatches_per_second"], d, config)
	if err != nil {
		return nil, err
	} else if val := reflect.ValueOf(transformedMaxDispatchesPerSecond); val.IsValid() && !isEmptyValue(val) {
		transformed["maxDispatchesPerSecond"] = transformedMaxDispatchesPerSecond
	}

	transformedMaxConcurrentDispatches, err := expandCloudTasksQueueRateLimitsMaxConcurrentDispatches(original["max_concurrent_dispatches"], d, config)
	if err != nil {
		return nil, err
	} else if val := reflect.ValueOf(transformedMaxConcurrentDispatches); val.IsValid() && !isEmptyValue(val) {
		transformed["maxConcurrentDispatches"] = transformedMaxConcurrentDispatches
	}

	transformedMaxBurstSize, err := expandCloudTasksQueueRateLimitsMaxBurstSize(original["max_burst_size"], d, config)
	if err != nil {
		return nil, err
	} else if val := reflect.ValueOf(transformedMaxBurstSize); val.IsValid() && !isEmptyValue(val) {
		transformed["maxBurstSize"] = transformedMaxBurstSize
	}

	return transformed, nil
}

func expandCloudTasksQueueRateLimitsMaxDispatchesPerSecond(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
	return v, nil
}

func expandCloudTasksQueueRateLimitsMaxConcurrentDispatches(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
	return v, nil
}

func expandCloudTasksQueueRateLimitsMaxBurstSize(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
	return v, nil
}

func expandCloudTasksQueueRetryConfig(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
	l := v.([]interface{})
	if len(l) == 0 || l[0] == nil {
		return nil, nil
	}
	raw := l[0]
	original := raw.(map[string]interface{})
	transformed := make(map[string]interface{})

	transformedMaxAttempts, err := expandCloudTasksQueueRetryConfigMaxAttempts(original["max_attempts"], d, config)
	if err != nil {
		return nil, err
	} else if val := reflect.ValueOf(transformedMaxAttempts); val.IsValid() && !isEmptyValue(val) {
		transformed["maxAttempts"] = transformedMaxAttempts
	}

	transformedMaxRetryDuration, err := expandCloudTasksQueueRetryConfigMaxRetryDuration(original["max_retry_duration"], d, config)
	if err != nil {
		return nil, err
	} else if val := reflect.ValueOf(transformedMaxRetryDuration); val.IsValid() && !isEmptyValue(val) {
		transformed["maxRetryDuration"] = transformedMaxRetryDuration
	}

	transformedMinBackoff, err := expandCloudTasksQueueRetryConfigMinBackoff(original["min_backoff"], d, config)
	if err != nil {
		return nil, err
	} else if val := reflect.ValueOf(transformedMinBackoff); val.IsValid() && !isEmptyValue(val) {
		transformed["minBackoff"] = transformedMinBackoff
	}

	transformedMaxBackoff, err := expandCloudTasksQueueRetryConfigMaxBackoff(original["max_backoff"], d, config)
	if err != nil {
		return nil, err
	} else if val := reflect.ValueOf(transformedMaxBackoff); val.IsValid() && !isEmptyValue(val) {
		transformed["maxBackoff"] = transformedMaxBackoff
	}

	transformedMaxDoublings, err := expandCloudTasksQueueRetryConfigMaxDoublings(original["max_doublings"], d, config)
	if err != nil {
		return nil, err
	} else if val := reflect.ValueOf(transformedMaxDoublings); val.IsValid() && !isEmptyValue(val) {
		transformed["maxDoublings"] = transformedMaxDoublings
	}

	return transformed, nil
}

func expandCloudTasksQueueRetryConfigMaxAttempts(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
	return v, nil
}

func expandCloudTasksQueueRetryConfigMaxRetryDuration(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
	return v, nil
}

func expandCloudTasksQueueRetryConfigMinBackoff(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
	return v, nil
}

func expandCloudTasksQueueRetryConfigMaxBackoff(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
	return v, nil
}

func expandCloudTasksQueueRetryConfigMaxDoublings(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
	return v, nil
}
<commit_msg> Added stackdriver_logging_config to cloud_tasks_queue resource (#4077) (#545)<commit_after>// ----------------------------------------------------------------------------
//
//     ***     AUTO GENERATED CODE    ***    AUTO GENERATED CODE     ***
//
// ----------------------------------------------------------------------------
//
//     This file is automatically generated by Magic Modules and manual
//     changes will be clobbered when the file is regenerated.
//
//     Please read more about how to change this file in
//     .github/CONTRIBUTING.md.
//
// ----------------------------------------------------------------------------

package google

import "reflect"

func GetCloudTasksQueueCaiObject(d TerraformResourceData, config *Config) (Asset, error) {
	name, err := assetName(d, config, "//cloudtasks.googleapis.com/projects/{{project}}/locations/{{location}}/queues/{{name}}")
	if err != nil {
		return Asset{}, err
	}
	if obj, err := GetCloudTasksQueueApiObject(d, config); err == nil {
		return Asset{
			Name: name,
			Type: "cloudtasks.googleapis.com/Queue",
			Resource: &AssetResource{
				Version:              "v2",
				DiscoveryDocumentURI: "https://www.googleapis.com/discovery/v1/apis/cloudtasks/v2/rest",
				DiscoveryName:        "Queue",
				Data:                 obj,
			},
		}, nil
	} else {
		return Asset{}, err
	}
}

func GetCloudTasksQueueApiObject(d TerraformResourceData, config *Config) (map[string]interface{}, error) {
	obj := make(map[string]interface{})
	nameProp, err := expandCloudTasksQueueName(d.Get("name"), d, config)
	if err != nil {
		return nil, err
	} else if v, ok := d.GetOkExists("name"); !isEmptyValue(reflect.ValueOf(nameProp)) && (ok || !reflect.DeepEqual(v, nameProp)) {
		obj["name"] = nameProp
	}
	appEngineRoutingOverrideProp, err := expandCloudTasksQueueAppEngineRoutingOverride(d.Get("app_engine_routing_override"), d, config)
	if err != nil {
		return nil, err
	} else if v, ok := d.GetOkExists("app_engine_routing_override"); !isEmptyValue(reflect.ValueOf(appEngineRoutingOverrideProp)) && (ok || !reflect.DeepEqual(v, appEngineRoutingOverrideProp)) {
		obj["appEngineRoutingOverride"] = appEngineRoutingOverrideProp
	}
	rateLimitsProp, err := expandCloudTasksQueueRateLimits(d.Get("rate_limits"), d, config)
	if err != nil {
		return nil, err
	} else if v, ok := d.GetOkExists("rate_limits"); !isEmptyValue(reflect.ValueOf(rateLimitsProp)) && (ok || !reflect.DeepEqual(v, rateLimitsProp)) {
		obj["rateLimits"] = rateLimitsProp
	}
	retryConfigProp, err := expandCloudTasksQueueRetryConfig(d.Get("retry_config"), d, config)
	if err != nil {
		return nil, err
	} else if v, ok := d.GetOkExists("retry_config"); !isEmptyValue(reflect.ValueOf(retryConfigProp)) && (ok || !reflect.DeepEqual(v, retryConfigProp)) {
		obj["retryConfig"] = retryConfigProp
	}
	stackdriverLoggingConfigProp, err := expandCloudTasksQueueStackdriverLoggingConfig(d.Get("stackdriver_logging_config"), d, config)
	if err != nil {
		return nil, err
	} else if v, ok := d.GetOkExists("stackdriver_logging_config"); !isEmptyValue(reflect.ValueOf(stackdriverLoggingConfigProp)) && (ok || !reflect.DeepEqual(v, stackdriverLoggingConfigProp)) {
		obj["stackdriverLoggingConfig"] = stackdriverLoggingConfigProp
	}

	return obj, nil
}

func expandCloudTasksQueueName(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
	return replaceVars(d, config, "projects/{{project}}/locations/{{location}}/queues/{{name}}")
}

func expandCloudTasksQueueAppEngineRoutingOverride(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
	l := v.([]interface{})
	if len(l) == 0 || l[0] == nil {
		return nil, nil
	}
	raw := l[0]
	original := raw.(map[string]interface{})
	transformed := make(map[string]interface{})

	transformedService, err := expandCloudTasksQueueAppEngineRoutingOverrideService(original["service"], d, config)
	if err != nil {
		return nil, err
	} else if val := reflect.ValueOf(transformedService); val.IsValid() && !isEmptyValue(val) {
		transformed["service"] = transformedService
	}

	transformedVersion, err := expandCloudTasksQueueAppEngineRoutingOverrideVersion(original["version"], d, config)
	if err != nil {
		return nil, err
	} else if val := reflect.ValueOf(transformedVersion); val.IsValid() && !isEmptyValue(val) {
		transformed["version"] = transformedVersion
	}

	transformedInstance, err := expandCloudTasksQueueAppEngineRoutingOverrideInstance(original["instance"], d, config)
	if err != nil {
		return nil, err
	} else if val := reflect.ValueOf(transformedInstance); val.IsValid() && !isEmptyValue(val) {
		transformed["instance"] = transformedInstance
	}

	transformedHost, err := expandCloudTasksQueueAppEngineRoutingOverrideHost(original["host"], d, config)
	if err != nil {
		return nil, err
	} else if val := reflect.ValueOf(transformedHost); val.IsValid() && !isEmptyValue(val) {
		transformed["host"] = transformedHost
	}

	return transformed, nil
}

func expandCloudTasksQueueAppEngineRoutingOverrideService(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
	return v, nil
}

func expandCloudTasksQueueAppEngineRoutingOverrideVersion(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
	return v, nil
}

func expandCloudTasksQueueAppEngineRoutingOverrideInstance(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
	return v, nil
}

func expandCloudTasksQueueAppEngineRoutingOverrideHost(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
	return v, nil
}

func expandCloudTasksQueueRateLimits(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
	l := v.([]interface{})
	if len(l) == 0 || l[0] == nil {
		return nil, nil
	}
	raw := l[0]
	original := raw.(map[string]interface{})
	transformed := make(map[string]interface{})

	transformedMaxDispatchesPerSecond, err := expandCloudTasksQueueRateLimitsMaxDispatchesPerSecond(original["max_dispatches_per_second"], d, config)
	if err != nil {
		return nil, err
	} else if val := reflect.ValueOf(transformedMaxDispatchesPerSecond); val.IsValid() && !isEmptyValue(val) {
		transformed["maxDispatchesPerSecond"] = transformedMaxDispatchesPerSecond
	}

	transformedMaxConcurrentDispatches, err := expandCloudTasksQueueRateLimitsMaxConcurrentDispatches(original["max_concurrent_dispatches"], d, config)
	if err != nil {
		return nil, err
	} else if val := reflect.ValueOf(transformedMaxConcurrentDispatches); val.IsValid() && !isEmptyValue(val) {
		transformed["maxConcurrentDispatches"] = transformedMaxConcurrentDispatches
	}

	transformedMaxBurstSize, err := expandCloudTasksQueueRateLimitsMaxBurstSize(original["max_burst_size"], d, config)
	if err != nil {
		return nil, err
	} else if val := reflect.ValueOf(transformedMaxBurstSize); val.IsValid() && !isEmptyValue(val) {
		transformed["maxBurstSize"] = transformedMaxBurstSize
	}

	return transformed, nil
}

func expandCloudTasksQueueRateLimitsMaxDispatchesPerSecond(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
	return v, nil
}

func expandCloudTasksQueueRateLimitsMaxConcurrentDispatches(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
	return v, nil
}

func expandCloudTasksQueueRateLimitsMaxBurstSize(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
	return v, nil
}

func expandCloudTasksQueueRetryConfig(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
	l := v.([]interface{})
	if len(l) == 0 || l[0] == nil {
		return nil, nil
	}
	raw := l[0]
	original := raw.(map[string]interface{})
	transformed := make(map[string]interface{})

	transformedMaxAttempts, err := expandCloudTasksQueueRetryConfigMaxAttempts(original["max_attempts"], d, config)
	if err != nil {
		return nil, err
	} else if val := reflect.ValueOf(transformedMaxAttempts); val.IsValid() && !isEmptyValue(val) {
		transformed["maxAttempts"] = transformedMaxAttempts
	}

	transformedMaxRetryDuration, err := expandCloudTasksQueueRetryConfigMaxRetryDuration(original["max_retry_duration"], d, config)
	if err != nil {
		return nil, err
	} else if val := reflect.ValueOf(transformedMaxRetryDuration); val.IsValid() && !isEmptyValue(val) {
		transformed["maxRetryDuration"] = transformedMaxRetryDuration
	}

	transformedMinBackoff, err := expandCloudTasksQueueRetryConfigMinBackoff(original["min_backoff"], d, config)
	if err != nil {
		return nil, err
	} else if val := reflect.ValueOf(transformedMinBackoff); val.IsValid() && !isEmptyValue(val) {
		transformed["minBackoff"] = transformedMinBackoff
	}

	transformedMaxBackoff, err := expandCloudTasksQueueRetryConfigMaxBackoff(original["max_backoff"], d, config)
	if err != nil {
		return nil, err
	} else if val := reflect.ValueOf(transformedMaxBackoff); val.IsValid() && !isEmptyValue(val) {
		transformed["maxBackoff"] = transformedMaxBackoff
	}

	transformedMaxDoublings, err := expandCloudTasksQueueRetryConfigMaxDoublings(original["max_doublings"], d, config)
	if err != nil {
		return nil, err
	} else if val := reflect.ValueOf(transformedMaxDoublings); val.IsValid() && !isEmptyValue(val) {
		transformed["maxDoublings"] = transformedMaxDoublings
	}

	return transformed, nil
}

func expandCloudTasksQueueRetryConfigMaxAttempts(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
	return v, nil
}

func expandCloudTasksQueueRetryConfigMaxRetryDuration(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
	return v, nil
}

func expandCloudTasksQueueRetryConfigMinBackoff(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
	return v, nil
}

func expandCloudTasksQueueRetryConfigMaxBackoff(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
	return v, nil
}

func expandCloudTasksQueueRetryConfigMaxDoublings(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
	return v, nil
}

func expandCloudTasksQueueStackdriverLoggingConfig(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
	l := v.([]interface{})
	if len(l) == 0 || l[0] == nil {
		return nil, nil
	}
	raw := l[0]
	original := raw.(map[string]interface{})
	transformed := make(map[string]interface{})

	transformedSamplingRatio, err := expandCloudTasksQueueStackdriverLoggingConfigSamplingRatio(original["sampling_ratio"], d, config)
	if err != nil {
		return nil, err
	} else if val := reflect.ValueOf(transformedSamplingRatio); val.IsValid() && !isEmptyValue(val) {
		transformed["samplingRatio"] = transformedSamplingRatio
	}

	return transformed, nil
}

func expandCloudTasksQueueStackdriverLoggingConfigSamplingRatio(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
	return v, nil
}
<|endoftext|>


<commit_before>package indicators

import (
	"container/list"
	"errors"
	"github.com/thetruetrade/gotrade"
)

// A Rate of Change Indicator (Roc), no storage, for use in other indicators
type RocWithoutStorage struct {
	*baseIndicator
	*baseFloatBounds

	// private variables
	valueAvailableAction ValueAvailableActionFloat
	periodCounter        int
	periodHistory        *list.List
	timePeriod           int
}

// NewRocWithoutStorage creates a Rate of Change Indicator (Roc) without storage
func NewRocWithoutStorage(timePeriod int, valueAvailableAction ValueAvailableActionFloat) (indicator *RocWithoutStorage, err error) {

	// an indicator without storage MUST have a value available action
	if valueAvailableAction == nil {
		return nil, ErrValueAvailableActionIsNil
	}

	// the minimum timeperiod for this indicator is 1
	if timePeriod < 1 {
		return nil, errors.New("timePeriod is less than the minimum (1)")
	}

	// check the maximum timeperiod
	if timePeriod > MaximumLookbackPeriod {
		return nil, errors.New("timePeriod is greater than the maximum (100000)")
	}

	lookback := timePeriod
	ind := RocWithoutStorage{
		baseIndicator:        newBaseIndicator(lookback),
		baseFloatBounds:      newBaseFloatBounds(),
		periodCounter:        (timePeriod * -1),
		periodHistory:        list.New(),
		valueAvailableAction: valueAvailableAction,
		timePeriod:           timePeriod,
	}

	return &ind, nil
}

// A Rate of Change Indicator (Roc)
type Roc struct {
	*RocWithoutStorage
	selectData gotrade.DataSelectionFunc

	// public variables
	Data []float64
}

// NewRoc creates a Rate of Change Indicator (Roc) for online usage
func NewRoc(timePeriod int, selectData gotrade.DataSelectionFunc) (indicator *Roc, err error) {
	ind := Roc{selectData: selectData}
	ind.RocWithoutStorage, err = NewRocWithoutStorage(timePeriod,
		func(dataItem float64, streamBarIndex int) {
			ind.Data = append(ind.Data, dataItem)
		})

	return &ind, err
}

// NewDefaultRoc creates a Rate of Change Indicator (Roc) for online usage with default parameters
//	- timePeriod: 10
func NewDefaultRoc() (indicator *Roc, err error) {
	timePeriod := 10
	return NewRoc(timePeriod, gotrade.UseClosePrice)
}

// NewRocWithSrcLen creates a Rate of Change Indicator (Roc) for offline usage
func NewRocWithSrcLen(sourceLength uint, timePeriod int, selectData gotrade.DataSelectionFunc) (indicator *Roc, err error) {
	ind, err := NewRoc(timePeriod, selectData)

	// only initialise the storage if there is enough source data to require it
	if sourceLength-uint(ind.GetLookbackPeriod()) > 1 {
		ind.Data = make([]float64, 0, sourceLength-uint(ind.GetLookbackPeriod()))
	}

	return ind, err
}

// NewDefaultRocWithSrcLen creates a Rate of Change Indicator (Roc) for offline usage with default parameters
func NewDefaultRocWithSrcLen(sourceLength uint) (indicator *Roc, err error) {
	ind, err := NewDefaultRoc()

	// only initialise the storage if there is enough source data to require it
	if sourceLength-uint(ind.GetLookbackPeriod()) > 1 {
		ind.Data = make([]float64, 0, sourceLength-uint(ind.GetLookbackPeriod()))
	}

	return ind, err
}

// NewRocForStream creates a Rate of Change Indicator (Roc) for online usage with a source data stream
func NewRocForStream(priceStream gotrade.DOHLCVStreamSubscriber, timePeriod int, selectData gotrade.DataSelectionFunc) (indicator *Roc, err error) {
	ind, err := NewRoc(timePeriod, selectData)
	priceStream.AddTickSubscription(ind)
	return ind, err
}

// NewDefaultRocForStream creates a Rate of Change Indicator (Roc) for online usage with a source data stream
func NewDefaultRocForStream(priceStream gotrade.DOHLCVStreamSubscriber) (indicator *Roc, err error) {
	ind, err := NewDefaultRoc()
	priceStream.AddTickSubscription(ind)
	return ind, err
}

// NewRocForStreamWithSrcLen creates a Rate of Change Indicator (Roc) for offline usage with a source data stream
func NewRocForStreamWithSrcLen(sourceLength uint, priceStream gotrade.DOHLCVStreamSubscriber, timePeriod int, selectData gotrade.DataSelectionFunc) (indicator *Roc, err error) {
	ind, err := NewRocWithSrcLen(sourceLength, timePeriod, selectData)
	priceStream.AddTickSubscription(ind)
	return ind, err
}

// NewDefaultRocForStreamWithSrcLen creates a Rate of Change Indicator (Roc) for offline usage with a source data stream
func NewDefaultRocForStreamWithSrcLen(sourceLength uint, priceStream gotrade.DOHLCVStreamSubscriber) (indicator *Roc, err error) {
	ind, err := NewDefaultRocWithSrcLen(sourceLength)
	priceStream.AddTickSubscription(ind)
	return ind, err
}

// ReceiveDOHLCVTick consumes a source data DOHLCV price tick
func (ind *Roc) ReceiveDOHLCVTick(tickData gotrade.DOHLCV, streamBarIndex int) {
	var selectedData = ind.selectData(tickData)
	ind.ReceiveTick(selectedData, streamBarIndex)
}

func (ind *RocWithoutStorage) ReceiveTick(tickData float64, streamBarIndex int) {
	ind.periodCounter += 1
	ind.periodHistory.PushBack(tickData)

	if ind.periodCounter > 0 {

		//    Roc = (price/previousPrice - 1) * 100
		previousPrice := ind.periodHistory.Front().Value.(float64)

		// increment the number of results this indicator can be expected to return
		ind.dataLength += 1
		if ind.validFromBar == -1 {
			// set the streamBarIndex from which this indicator returns valid results
			ind.validFromBar = streamBarIndex
		}
		var result float64
		if previousPrice != 0 {
			result = 100.0 * ((tickData / previousPrice) - 1)
		} else {
			result = 0.0
		}

		// update the maximum result value
		if result > ind.maxValue {
			ind.maxValue = result
		}

		// update the minimum result value
		if result < ind.minValue {
			ind.minValue = result
		}

		// notify of a new result value though the value available action
		ind.valueAvailableAction(result, streamBarIndex)
	}

	if ind.periodHistory.Len() > ind.timePeriod {
		first := ind.periodHistory.Front()
		ind.periodHistory.Remove(first)
	}
}
<commit_msg>#76 Remove duplication - roc<commit_after>package indicators

import (
	"container/list"
	"errors"
	"github.com/thetruetrade/gotrade"
)

// A Rate of Change Indicator (Roc), no storage, for use in other indicators
type RocWithoutStorage struct {
	*baseIndicatorWithFloatBounds

	// private variables
	periodCounter int
	periodHistory *list.List
	timePeriod    int
}

// NewRocWithoutStorage creates a Rate of Change Indicator (Roc) without storage
func NewRocWithoutStorage(timePeriod int, valueAvailableAction ValueAvailableActionFloat) (indicator *RocWithoutStorage, err error) {

	// an indicator without storage MUST have a value available action
	if valueAvailableAction == nil {
		return nil, ErrValueAvailableActionIsNil
	}

	// the minimum timeperiod for this indicator is 1
	if timePeriod < 1 {
		return nil, errors.New("timePeriod is less than the minimum (1)")
	}

	// check the maximum timeperiod
	if timePeriod > MaximumLookbackPeriod {
		return nil, errors.New("timePeriod is greater than the maximum (100000)")
	}

	lookback := timePeriod
	ind := RocWithoutStorage{
		baseIndicatorWithFloatBounds: newBaseIndicatorWithFloatBounds(lookback, valueAvailableAction),
		periodCounter:                (timePeriod * -1),
		periodHistory:                list.New(),
		timePeriod:                   timePeriod,
	}

	return &ind, nil
}

// A Rate of Change Indicator (Roc)
type Roc struct {
	*RocWithoutStorage
	selectData gotrade.DataSelectionFunc

	// public variables
	Data []float64
}

// NewRoc creates a Rate of Change Indicator (Roc) for online usage
func NewRoc(timePeriod int, selectData gotrade.DataSelectionFunc) (indicator *Roc, err error) {
	ind := Roc{selectData: selectData}
	ind.RocWithoutStorage, err = NewRocWithoutStorage(timePeriod,
		func(dataItem float64, streamBarIndex int) {
			ind.Data = append(ind.Data, dataItem)
		})

	return &ind, err
}

// NewDefaultRoc creates a Rate of Change Indicator (Roc) for online usage with default parameters
//	- timePeriod: 10
func NewDefaultRoc() (indicator *Roc, err error) {
	timePeriod := 10
	return NewRoc(timePeriod, gotrade.UseClosePrice)
}

// NewRocWithSrcLen creates a Rate of Change Indicator (Roc) for offline usage
func NewRocWithSrcLen(sourceLength uint, timePeriod int, selectData gotrade.DataSelectionFunc) (indicator *Roc, err error) {
	ind, err := NewRoc(timePeriod, selectData)

	// only initialise the storage if there is enough source data to require it
	if sourceLength-uint(ind.GetLookbackPeriod()) > 1 {
		ind.Data = make([]float64, 0, sourceLength-uint(ind.GetLookbackPeriod()))
	}

	return ind, err
}

// NewDefaultRocWithSrcLen creates a Rate of Change Indicator (Roc) for offline usage with default parameters
func NewDefaultRocWithSrcLen(sourceLength uint) (indicator *Roc, err error) {
	ind, err := NewDefaultRoc()

	// only initialise the storage if there is enough source data to require it
	if sourceLength-uint(ind.GetLookbackPeriod()) > 1 {
		ind.Data = make([]float64, 0, sourceLength-uint(ind.GetLookbackPeriod()))
	}

	return ind, err
}

// NewRocForStream creates a Rate of Change Indicator (Roc) for online usage with a source data stream
func NewRocForStream(priceStream gotrade.DOHLCVStreamSubscriber, timePeriod int, selectData gotrade.DataSelectionFunc) (indicator *Roc, err error) {
	ind, err := NewRoc(timePeriod, selectData)
	priceStream.AddTickSubscription(ind)
	return ind, err
}

// NewDefaultRocForStream creates a Rate of Change Indicator (Roc) for online usage with a source data stream
func NewDefaultRocForStream(priceStream gotrade.DOHLCVStreamSubscriber) (indicator *Roc, err error) {
	ind, err := NewDefaultRoc()
	priceStream.AddTickSubscription(ind)
	return ind, err
}

// NewRocForStreamWithSrcLen creates a Rate of Change Indicator (Roc) for offline usage with a source data stream
func NewRocForStreamWithSrcLen(sourceLength uint, priceStream gotrade.DOHLCVStreamSubscriber, timePeriod int, selectData gotrade.DataSelectionFunc) (indicator *Roc, err error) {
	ind, err := NewRocWithSrcLen(sourceLength, timePeriod, selectData)
	priceStream.AddTickSubscription(ind)
	return ind, err
}

// NewDefaultRocForStreamWithSrcLen creates a Rate of Change Indicator (Roc) for offline usage with a source data stream
func NewDefaultRocForStreamWithSrcLen(sourceLength uint, priceStream gotrade.DOHLCVStreamSubscriber) (indicator *Roc, err error) {
	ind, err := NewDefaultRocWithSrcLen(sourceLength)
	priceStream.AddTickSubscription(ind)
	return ind, err
}

// ReceiveDOHLCVTick consumes a source data DOHLCV price tick
func (ind *Roc) ReceiveDOHLCVTick(tickData gotrade.DOHLCV, streamBarIndex int) {
	var selectedData = ind.selectData(tickData)
	ind.ReceiveTick(selectedData, streamBarIndex)
}

func (ind *RocWithoutStorage) ReceiveTick(tickData float64, streamBarIndex int) {
	ind.periodCounter += 1
	ind.periodHistory.PushBack(tickData)

	if ind.periodCounter > 0 {

		//    Roc = (price/previousPrice - 1) * 100
		previousPrice := ind.periodHistory.Front().Value.(float64)

		var result float64
		if previousPrice != 0 {
			result = 100.0 * ((tickData / previousPrice) - 1)
		} else {
			result = 0.0
		}

		ind.UpdateIndicatorWithNewValue(result, streamBarIndex)
	}

	if ind.periodHistory.Len() > ind.timePeriod {
		first := ind.periodHistory.Front()
		ind.periodHistory.Remove(first)
	}
}
<|endoftext|>


<commit_before>// Copyright 2012, 2013 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package state

import (
	"errors"
	"fmt"
	"labix.org/v2/mgo/txn"
	"launchpad.net/juju-core/utils"
)

// minimumUnitsDoc allows for keeping track of relevant changes on the
// service MinimumUnits field and on the number of alive units for the service.
// A new document is created when MinimumUnits is set to a non zero value.
// A document is deleted when either the associated service is destroyed
// or MinimumUnits is restored to zero. The Revno is increased when either
// MinimumUnits for a service is increased or a unit is destroyed.
// TODO(frankban): the MinimumUnitsWatcher reacts to changes sending events,
// each one describing one or more services. A worker reacts to those events
// ensuring the number of units for the service is never less than the actual
// alive units: new units are added if required (see EnsureMinimumUnits below).
type minimumUnitsDoc struct {
	// Since the referred entity type is always the Service, it is safe here
	// to use the service name as id in place of its globalKey.
	ServiceName string `bson:"_id"`
	Revno       int
	TxnRevno    int64 `bson:"txn-revno"`
}

// SetMinimumUnits changes the amount of minimum units required by the service.
func (s *Service) SetMinimumUnits(minimumUnits int) (err error) {
	defer utils.ErrorContextf(&err, "cannot set minimum units for service %q", s)
	if minimumUnits < 0 {
		return errors.New("minimum units must be a positive number")
	}
	serviceName := s.doc.Name
	serviceOp := txn.Op{
		C:      s.st.services.Name,
		Id:     serviceName,
		Assert: isAliveDoc,
		Update: D{{"$set", D{{"minimumunits", minimumUnits}}}},
	}
	// Removing the document never fails. Racing clients trying to create the
	// document generate one failure, but the second attempt should succeed.
	// If one client tries to update the document, and a racing client removes
	// it, the former should be able to re-create the document in the second
	// attempt. If the referred-to service advanced his life cycle to a not
	// alive state, an error is returned after two failing attempts.
	for i := 0; i < 3; i++ {
		ops := []txn.Op{serviceOp}
		if count, err := s.st.minimumUnits.FindId(serviceName).Count(); err != nil {
			return err
		} else if count == 0 {
			if i == 2 {
				return errors.New("service is no longer alive")
			}
			if minimumUnits != 0 {
				ops = append(ops, minimumUnitsInsertOp(s.st, s.doc.Name))
			}
		} else {
			if minimumUnits == 0 {
				ops = append(ops, minimumUnitsRemoveOp(s.st, s.doc.Name))
			} else if minimumUnits > s.doc.MinimumUnits {
				ops = append(ops, minimumUnitsUpdateOp(s.st, s.doc.Name))
			}
		}
		if err := s.st.runTransaction(ops); err == nil {
			s.doc.MinimumUnits = minimumUnits
			return nil
		} else if err != txn.ErrAborted {
			return err
		}
	}
	return ErrExcessiveContention
}

// minimumUnitsInsertOp returns the operation required to insert a minimum
// units document for the service in MongoDB.
func minimumUnitsInsertOp(st *State, serviceName string) txn.Op {
	return txn.Op{
		C:      st.minimumUnits.Name,
		Id:     serviceName,
		Assert: txn.DocMissing,
		Insert: &minimumUnitsDoc{ServiceName: serviceName},
	}
}

// minimumUnitsIncreaseOp returns the operation required to increase the
// minimum units revno for the service in MongoDB, ignoring the case of
// document not existing. This is included in the operations performed when
// a unit is destroyed: if the document exists, then we need to update the
// Revno. If the service does not require a minimum amount of units, then
// the operation is a noop.
func minimumUnitsIncreaseOp(st *State, serviceName string) txn.Op {
	return txn.Op{
		C:      st.minimumUnits.Name,
		Id:     serviceName,
		Update: D{{"$inc", D{{"revno", 1}}}},
	}
}

// minimumUnitsUpdateOp returns the operation required to increase the
// minimum units revno for the service in MongoDB. The document must exist.
func minimumUnitsUpdateOp(st *State, serviceName string) txn.Op {
	op := minimumUnitsIncreaseOp(st, serviceName)
	op.Assert = txn.DocExists
	return op
}

// minimumUnitsRemoveOp returns the operation required to remove the minimum
// units document from MongoDB.
func minimumUnitsRemoveOp(st *State, serviceName string) txn.Op {
	return txn.Op{
		C:      st.minimumUnits.Name,
		Id:     serviceName,
		Remove: true,
	}
}

// MinimumUnits returns the minimum units count for the service.
func (s *Service) MinimumUnits() int {
	return s.doc.MinimumUnits
}

// AliveUnitsCount returns the amount of alive units of the service.
func (s *Service) AliveUnitsCount() (int, error) {
	query := D{{"service", s.doc.Name}, {"life", Alive}}
	alive, err := s.st.units.Find(query).Count()
	if err != nil {
		return 0, fmt.Errorf(
			"cannot get alive units count from service %q: %v", s, err)
	}
	return alive, nil
}

// EnsureMinimumUnits adds new units if the service MinimumUnits value is
// greater than the number of alive units.
func (s *Service) EnsureMinimumUnits() error {
	aliveUnits, err := s.AliveUnitsCount()
	if err != nil {
		return err
	}
	missing := s.MinimumUnits() - aliveUnits
	if missing <= 0 {
		return nil
	}
	for i := 0; i < missing; i++ {
		unit, err := s.AddUnit()
		if err != nil {
			return fmt.Errorf("cannot add unit %d/%d to service %q: %v",
				i+1, missing, s.Name(), err)
		}
		if err := s.st.AssignUnit(unit, AssignNew); err != nil {
			return err
		}
	}
	return nil
}
<commit_msg>Comment changes as per review.<commit_after>// Copyright 2012, 2013 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package state

import (
	"errors"
	"fmt"
	"labix.org/v2/mgo/txn"
	"launchpad.net/juju-core/utils"
)

// minimumUnitsDoc keeps track of relevant changes on the service's
// MinimumUnits field and on the number of alive units for the service.
// A new document is created when MinimumUnits is set to a non zero value.
// A document is deleted when either the associated service is destroyed
// or MinimumUnits is restored to zero. The Revno is increased when either
// MinimumUnits for a service is increased or a unit is destroyed.
// TODO(frankban): the MinimumUnitsWatcher reacts to changes sending events,
// each one describing one or more services. A worker reacts to those events
// ensuring the number of units for the service is never less than the actual
// alive units: new units are added if required (see EnsureMinimumUnits below).
type minimumUnitsDoc struct {
	// ServiceName is safe to be used here in place of its globalKey, since
	// the referred entity type is always the Service.
	ServiceName string `bson:"_id"`
	Revno       int
	TxnRevno    int64 `bson:"txn-revno"`
}

// SetMinimumUnits changes the amount of minimum units required by the service.
func (s *Service) SetMinimumUnits(minimumUnits int) (err error) {
	defer utils.ErrorContextf(&err, "cannot set minimum units for service %q", s)
	if minimumUnits < 0 {
		return errors.New("minimum units must be a positive number")
	}
	serviceName := s.doc.Name
	serviceOp := txn.Op{
		C:      s.st.services.Name,
		Id:     serviceName,
		Assert: isAliveDoc,
		Update: D{{"$set", D{{"minimumunits", minimumUnits}}}},
	}
	// Removing the document never fails. Racing clients trying to create the
	// document generate one failure, but the second attempt should succeed.
	// If one client tries to update the document, and a racing client removes
	// it, the former should be able to re-create the document in the second
	// attempt. If the referred-to service advanced his life cycle to a not
	// alive state, an error is returned after two failing attempts.
	for i := 0; i < 3; i++ {
		ops := []txn.Op{serviceOp}
		if count, err := s.st.minimumUnits.FindId(serviceName).Count(); err != nil {
			return err
		} else if count == 0 {
			if i == 2 {
				return errors.New("service is no longer alive")
			}
			if minimumUnits != 0 {
				ops = append(ops, minimumUnitsInsertOp(s.st, s.doc.Name))
			}
		} else {
			if minimumUnits == 0 {
				ops = append(ops, minimumUnitsRemoveOp(s.st, s.doc.Name))
			} else if minimumUnits > s.doc.MinimumUnits {
				ops = append(ops, minimumUnitsUpdateOp(s.st, s.doc.Name))
			}
		}
		if err := s.st.runTransaction(ops); err == nil {
			s.doc.MinimumUnits = minimumUnits
			return nil
		} else if err != txn.ErrAborted {
			return err
		}
	}
	return ErrExcessiveContention
}

// minimumUnitsInsertOp returns the operation required to insert a minimum
// units document for the service in MongoDB.
func minimumUnitsInsertOp(st *State, serviceName string) txn.Op {
	return txn.Op{
		C:      st.minimumUnits.Name,
		Id:     serviceName,
		Assert: txn.DocMissing,
		Insert: &minimumUnitsDoc{ServiceName: serviceName},
	}
}

// minimumUnitsIncreaseOp returns the operation required to increase the
// minimum units revno for the service in MongoDB, ignoring the case of
// document not existing. This is included in the operations performed when
// a unit is destroyed: if the document exists, then we need to update the
// Revno. If the service does not require a minimum amount of units, then
// the operation is a noop.
func minimumUnitsIncreaseOp(st *State, serviceName string) txn.Op {
	return txn.Op{
		C:      st.minimumUnits.Name,
		Id:     serviceName,
		Update: D{{"$inc", D{{"revno", 1}}}},
	}
}

// minimumUnitsUpdateOp returns the operation required to increase the
// minimum units revno for the service in MongoDB. The document must exist.
func minimumUnitsUpdateOp(st *State, serviceName string) txn.Op {
	op := minimumUnitsIncreaseOp(st, serviceName)
	op.Assert = txn.DocExists
	return op
}

// minimumUnitsRemoveOp returns the operation required to remove the minimum
// units document from MongoDB.
func minimumUnitsRemoveOp(st *State, serviceName string) txn.Op {
	return txn.Op{
		C:      st.minimumUnits.Name,
		Id:     serviceName,
		Remove: true,
	}
}

// MinimumUnits returns the minimum units count for the service.
func (s *Service) MinimumUnits() int {
	return s.doc.MinimumUnits
}

// AliveUnitsCount returns the amount of alive units of the service.
func (s *Service) AliveUnitsCount() (int, error) {
	query := D{{"service", s.doc.Name}, {"life", Alive}}
	alive, err := s.st.units.Find(query).Count()
	if err != nil {
		return 0, fmt.Errorf(
			"cannot get alive units count from service %q: %v", s, err)
	}
	return alive, nil
}

// EnsureMinimumUnits adds new units if the service MinimumUnits value is
// greater than the number of alive units.
func (s *Service) EnsureMinimumUnits() error {
	aliveUnits, err := s.AliveUnitsCount()
	if err != nil {
		return err
	}
	missing := s.MinimumUnits() - aliveUnits
	if missing <= 0 {
		return nil
	}
	for i := 0; i < missing; i++ {
		unit, err := s.AddUnit()
		if err != nil {
			return fmt.Errorf("cannot add unit %d/%d to service %q: %v",
				i+1, missing, s.Name(), err)
		}
		if err := s.st.AssignUnit(unit, AssignNew); err != nil {
			return err
		}
	}
	return nil
}
<|endoftext|>