<commit_before>// Copyright 2012 The Plotinum Authors. All rights reserved.
// Use of this source code is governed by an MIT-style license
// that can be found in the LICENSE file.

// vgimg implements the vg.Canvas interface using
// draw2d (code.google.com/p/draw2d/draw2d)
// as a backend to output raster images.
package vgimg

import (
	"bufio"
	"code.google.com/p/draw2d/draw2d"
	"code.google.com/p/go.image/tiff"
	"fmt"
	"github.com/go-hep/hplot/plotinum/vg"
	"image"
	"image/color"
	"image/draw"
	"image/jpeg"
	"image/png"
	"io"
)

// dpi is the number of dots per inch.
const dpi = 96

// Canvas implements the vg.Canvas interface,
// drawing to an image.Image using draw2d.
type Canvas struct {
	gc    draw2d.GraphicContext
	img   image.Image
	w, h  vg.Length
	color []color.Color

	// width is the current line width.
	width vg.Length
}

// New returns a new image canvas with
// the size specified  rounded up to the
// nearest pixel.
func New(width, height vg.Length) *Canvas {
	w := width.Inches() * dpi
	h := height.Inches() * dpi
	img := image.NewRGBA(image.Rect(0, 0, int(w+0.5), int(h+0.5)))

	return NewImage(img)
}

// NewImage returns a new image canvas
// that draws to the given image.  The
// minimum point of the given image
// should probably be 0,0.
func NewImage(img draw.Image) *Canvas {
	w := float64(img.Bounds().Max.X - img.Bounds().Min.X)
	h := float64(img.Bounds().Max.Y - img.Bounds().Min.Y)
	draw.Draw(img, img.Bounds(), image.White, image.ZP, draw.Src)
	gc := draw2d.NewGraphicContext(img)
	gc.SetDPI(dpi)
	gc.Scale(1, -1)
	gc.Translate(0, -h)
	c := &Canvas{
		gc:    gc,
		img:   img,
		w:     vg.Inches(w / dpi),
		h:     vg.Inches(h / dpi),
		color: []color.Color{color.Black},
	}
	vg.Initialize(c)
	return c
}

func (c *Canvas) Size() (w, h vg.Length) {
	return c.w, c.h
}

func (c *Canvas) SetLineWidth(w vg.Length) {
	c.width = w
	c.gc.SetLineWidth(w.Dots(c))
}

func (c *Canvas) SetLineDash(ds []vg.Length, offs vg.Length) {
	dashes := make([]float64, len(ds))
	for i, d := range ds {
		dashes[i] = d.Dots(c)
	}
	c.gc.SetLineDash(dashes, offs.Dots(c))
}

func (c *Canvas) SetColor(clr color.Color) {
	if clr == nil {
		clr = color.Black
	}
	c.gc.SetFillColor(clr)
	c.gc.SetStrokeColor(clr)
	c.color[len(c.color)-1] = clr
}

func (c *Canvas) Rotate(t float64) {
	c.gc.Rotate(t)
}

func (c *Canvas) Translate(x, y vg.Length) {
	c.gc.Translate(x.Dots(c), y.Dots(c))
}

func (c *Canvas) Scale(x, y float64) {
	c.gc.Scale(x, y)
}

func (c *Canvas) Push() {
	c.color = append(c.color, c.color[len(c.color)-1])
	c.gc.Save()
}

func (c *Canvas) Pop() {
	c.color = c.color[:len(c.color)-1]
	c.gc.Restore()
}

func (c *Canvas) Stroke(p vg.Path) {
	if c.width == 0 {
		return
	}
	c.outline(p)
	c.gc.Stroke()
}

func (c *Canvas) Fill(p vg.Path) {
	c.outline(p)
	c.gc.Fill()
}

func (c *Canvas) outline(p vg.Path) {
	c.gc.BeginPath()
	for _, comp := range p {
		switch comp.Type {
		case vg.MoveComp:
			c.gc.MoveTo(comp.X.Dots(c), comp.Y.Dots(c))

		case vg.LineComp:
			c.gc.LineTo(comp.X.Dots(c), comp.Y.Dots(c))

		case vg.ArcComp:
			c.gc.ArcTo(comp.X.Dots(c), comp.Y.Dots(c),
				comp.Radius.Dots(c), comp.Radius.Dots(c),
				comp.Start, comp.Angle)

		case vg.CloseComp:
			c.gc.Close()

		default:
			panic(fmt.Sprintf("Unknown path component: %d", comp.Type))
		}
	}
}

func (c *Canvas) DPI() float64 {
	return float64(c.gc.GetDPI())
}

func (c *Canvas) FillString(font vg.Font, x, y vg.Length, str string) {
	c.gc.Save()
	defer c.gc.Restore()

	data, ok := fontMap[font.Name()]
	if !ok {
		panic(fmt.Sprintf("Font name %s is unknown", font.Name()))
	}
	if !registeredFont[font.Name()] {
		draw2d.RegisterFont(data, font.Font())
		registeredFont[font.Name()] = true
	}
	c.gc.SetFontData(data)
	c.gc.Translate(x.Dots(c), y.Dots(c))
	c.gc.Scale(1, -1)
	c.gc.FillString(str)
}

var (
	// RegisteredFont contains the set of font names
	// that have already been registered with draw2d.
	registeredFont = map[string]bool{}

	// FontMap contains a mapping from vg's font
	// names to draw2d.FontData for the corresponding
	// font.  This is needed to register the  fonts with
	// draw2d.
	fontMap = map[string]draw2d.FontData{
		"Courier": draw2d.FontData{
			Name:   "Nimbus",
			Family: draw2d.FontFamilyMono,
			Style:  draw2d.FontStyleNormal,
		},
		"Courier-Bold": draw2d.FontData{
			Name:   "Nimbus",
			Family: draw2d.FontFamilyMono,
			Style:  draw2d.FontStyleBold,
		},
		"Courier-Oblique": draw2d.FontData{
			Name:   "Nimbus",
			Family: draw2d.FontFamilyMono,
			Style:  draw2d.FontStyleItalic,
		},
		"Courier-BoldOblique": draw2d.FontData{
			Name:   "Nimbus",
			Family: draw2d.FontFamilyMono,
			Style:  draw2d.FontStyleItalic | draw2d.FontStyleBold,
		},
		"Helvetica": draw2d.FontData{
			Name:   "Nimbus",
			Family: draw2d.FontFamilySans,
			Style:  draw2d.FontStyleNormal,
		},
		"Helvetica-Bold": draw2d.FontData{
			Name:   "Nimbus",
			Family: draw2d.FontFamilySans,
			Style:  draw2d.FontStyleBold,
		},
		"Helvetica-Oblique": draw2d.FontData{
			Name:   "Nimbus",
			Family: draw2d.FontFamilySans,
			Style:  draw2d.FontStyleItalic,
		},
		"Helvetica-BoldOblique": draw2d.FontData{
			Name:   "Nimbus",
			Family: draw2d.FontFamilySans,
			Style:  draw2d.FontStyleItalic | draw2d.FontStyleBold,
		},
		"Times-Roman": draw2d.FontData{
			Name:   "Nimbus",
			Family: draw2d.FontFamilySerif,
			Style:  draw2d.FontStyleNormal,
		},
		"Times-Bold": draw2d.FontData{
			Name:   "Nimbus",
			Family: draw2d.FontFamilySerif,
			Style:  draw2d.FontStyleBold,
		},
		"Times-Italic": draw2d.FontData{
			Name:   "Nimbus",
			Family: draw2d.FontFamilySerif,
			Style:  draw2d.FontStyleItalic,
		},
		"Times-BoldItalic": draw2d.FontData{
			Name:   "Nimbus",
			Family: draw2d.FontFamilySerif,
			Style:  draw2d.FontStyleItalic | draw2d.FontStyleBold,
		},
	}
)

// WriterCounter implements the io.Writer interface, and counts
// the total number of bytes written.
type writerCounter struct {
	io.Writer
	n int64
}

func (w *writerCounter) Write(p []byte) (int, error) {
	n, err := w.Writer.Write(p)
	w.n += int64(n)
	return n, err
}

// A JpegCanvas is an image canvas with a WriteTo method
// that writes a jpeg image.
type JpegCanvas struct {
	*Canvas
}

// WriteTo implements the io.WriterTo interface, writing a jpeg image.
func (c JpegCanvas) WriteTo(w io.Writer) (int64, error) {
	wc := writerCounter{Writer: w}
	b := bufio.NewWriter(&wc)
	if err := jpeg.Encode(b, c.img, nil); err != nil {
		return wc.n, err
	}
	err := b.Flush()
	return wc.n, err
}

// A PngCanvas is an image canvas with a WriteTo method that
// writes a png image.
type PngCanvas struct {
	*Canvas
}

// WriteTo implements the io.WriterTo interface, writing a png image.
func (c PngCanvas) WriteTo(w io.Writer) (int64, error) {
	wc := writerCounter{Writer: w}
	b := bufio.NewWriter(&wc)
	if err := png.Encode(b, c.img); err != nil {
		return wc.n, err
	}
	err := b.Flush()
	return wc.n, err
}

// A TiffCanvas is an image canvas with a WriteTo method that
// writes a tiff image.
type TiffCanvas struct {
	*Canvas
}

// WriteTo implements the io.WriterTo interface, writing a tiff image.
func (c TiffCanvas) WriteTo(w io.Writer) (int64, error) {
	wc := writerCounter{Writer: w}
	b := bufio.NewWriter(&wc)
	if err := tiff.Encode(b, c.img, nil); err != nil {
		return wc.n, err
	}
	err := b.Flush()
	return wc.n, err
}
<commit_msg>plotinum: use golang.org/x/<commit_after>// Copyright 2012 The Plotinum Authors. All rights reserved.
// Use of this source code is governed by an MIT-style license
// that can be found in the LICENSE file.

// vgimg implements the vg.Canvas interface using
// draw2d (code.google.com/p/draw2d/draw2d)
// as a backend to output raster images.
package vgimg

import (
	"bufio"
	"code.google.com/p/draw2d/draw2d"
	"golang.org/x/image/tiff"
	"fmt"
	"github.com/go-hep/hplot/plotinum/vg"
	"image"
	"image/color"
	"image/draw"
	"image/jpeg"
	"image/png"
	"io"
)

// dpi is the number of dots per inch.
const dpi = 96

// Canvas implements the vg.Canvas interface,
// drawing to an image.Image using draw2d.
type Canvas struct {
	gc    draw2d.GraphicContext
	img   image.Image
	w, h  vg.Length
	color []color.Color

	// width is the current line width.
	width vg.Length
}

// New returns a new image canvas with
// the size specified  rounded up to the
// nearest pixel.
func New(width, height vg.Length) *Canvas {
	w := width.Inches() * dpi
	h := height.Inches() * dpi
	img := image.NewRGBA(image.Rect(0, 0, int(w+0.5), int(h+0.5)))

	return NewImage(img)
}

// NewImage returns a new image canvas
// that draws to the given image.  The
// minimum point of the given image
// should probably be 0,0.
func NewImage(img draw.Image) *Canvas {
	w := float64(img.Bounds().Max.X - img.Bounds().Min.X)
	h := float64(img.Bounds().Max.Y - img.Bounds().Min.Y)
	draw.Draw(img, img.Bounds(), image.White, image.ZP, draw.Src)
	gc := draw2d.NewGraphicContext(img)
	gc.SetDPI(dpi)
	gc.Scale(1, -1)
	gc.Translate(0, -h)
	c := &Canvas{
		gc:    gc,
		img:   img,
		w:     vg.Inches(w / dpi),
		h:     vg.Inches(h / dpi),
		color: []color.Color{color.Black},
	}
	vg.Initialize(c)
	return c
}

func (c *Canvas) Size() (w, h vg.Length) {
	return c.w, c.h
}

func (c *Canvas) SetLineWidth(w vg.Length) {
	c.width = w
	c.gc.SetLineWidth(w.Dots(c))
}

func (c *Canvas) SetLineDash(ds []vg.Length, offs vg.Length) {
	dashes := make([]float64, len(ds))
	for i, d := range ds {
		dashes[i] = d.Dots(c)
	}
	c.gc.SetLineDash(dashes, offs.Dots(c))
}

func (c *Canvas) SetColor(clr color.Color) {
	if clr == nil {
		clr = color.Black
	}
	c.gc.SetFillColor(clr)
	c.gc.SetStrokeColor(clr)
	c.color[len(c.color)-1] = clr
}

func (c *Canvas) Rotate(t float64) {
	c.gc.Rotate(t)
}

func (c *Canvas) Translate(x, y vg.Length) {
	c.gc.Translate(x.Dots(c), y.Dots(c))
}

func (c *Canvas) Scale(x, y float64) {
	c.gc.Scale(x, y)
}

func (c *Canvas) Push() {
	c.color = append(c.color, c.color[len(c.color)-1])
	c.gc.Save()
}

func (c *Canvas) Pop() {
	c.color = c.color[:len(c.color)-1]
	c.gc.Restore()
}

func (c *Canvas) Stroke(p vg.Path) {
	if c.width == 0 {
		return
	}
	c.outline(p)
	c.gc.Stroke()
}

func (c *Canvas) Fill(p vg.Path) {
	c.outline(p)
	c.gc.Fill()
}

func (c *Canvas) outline(p vg.Path) {
	c.gc.BeginPath()
	for _, comp := range p {
		switch comp.Type {
		case vg.MoveComp:
			c.gc.MoveTo(comp.X.Dots(c), comp.Y.Dots(c))

		case vg.LineComp:
			c.gc.LineTo(comp.X.Dots(c), comp.Y.Dots(c))

		case vg.ArcComp:
			c.gc.ArcTo(comp.X.Dots(c), comp.Y.Dots(c),
				comp.Radius.Dots(c), comp.Radius.Dots(c),
				comp.Start, comp.Angle)

		case vg.CloseComp:
			c.gc.Close()

		default:
			panic(fmt.Sprintf("Unknown path component: %d", comp.Type))
		}
	}
}

func (c *Canvas) DPI() float64 {
	return float64(c.gc.GetDPI())
}

func (c *Canvas) FillString(font vg.Font, x, y vg.Length, str string) {
	c.gc.Save()
	defer c.gc.Restore()

	data, ok := fontMap[font.Name()]
	if !ok {
		panic(fmt.Sprintf("Font name %s is unknown", font.Name()))
	}
	if !registeredFont[font.Name()] {
		draw2d.RegisterFont(data, font.Font())
		registeredFont[font.Name()] = true
	}
	c.gc.SetFontData(data)
	c.gc.Translate(x.Dots(c), y.Dots(c))
	c.gc.Scale(1, -1)
	c.gc.FillString(str)
}

var (
	// RegisteredFont contains the set of font names
	// that have already been registered with draw2d.
	registeredFont = map[string]bool{}

	// FontMap contains a mapping from vg's font
	// names to draw2d.FontData for the corresponding
	// font.  This is needed to register the  fonts with
	// draw2d.
	fontMap = map[string]draw2d.FontData{
		"Courier": draw2d.FontData{
			Name:   "Nimbus",
			Family: draw2d.FontFamilyMono,
			Style:  draw2d.FontStyleNormal,
		},
		"Courier-Bold": draw2d.FontData{
			Name:   "Nimbus",
			Family: draw2d.FontFamilyMono,
			Style:  draw2d.FontStyleBold,
		},
		"Courier-Oblique": draw2d.FontData{
			Name:   "Nimbus",
			Family: draw2d.FontFamilyMono,
			Style:  draw2d.FontStyleItalic,
		},
		"Courier-BoldOblique": draw2d.FontData{
			Name:   "Nimbus",
			Family: draw2d.FontFamilyMono,
			Style:  draw2d.FontStyleItalic | draw2d.FontStyleBold,
		},
		"Helvetica": draw2d.FontData{
			Name:   "Nimbus",
			Family: draw2d.FontFamilySans,
			Style:  draw2d.FontStyleNormal,
		},
		"Helvetica-Bold": draw2d.FontData{
			Name:   "Nimbus",
			Family: draw2d.FontFamilySans,
			Style:  draw2d.FontStyleBold,
		},
		"Helvetica-Oblique": draw2d.FontData{
			Name:   "Nimbus",
			Family: draw2d.FontFamilySans,
			Style:  draw2d.FontStyleItalic,
		},
		"Helvetica-BoldOblique": draw2d.FontData{
			Name:   "Nimbus",
			Family: draw2d.FontFamilySans,
			Style:  draw2d.FontStyleItalic | draw2d.FontStyleBold,
		},
		"Times-Roman": draw2d.FontData{
			Name:   "Nimbus",
			Family: draw2d.FontFamilySerif,
			Style:  draw2d.FontStyleNormal,
		},
		"Times-Bold": draw2d.FontData{
			Name:   "Nimbus",
			Family: draw2d.FontFamilySerif,
			Style:  draw2d.FontStyleBold,
		},
		"Times-Italic": draw2d.FontData{
			Name:   "Nimbus",
			Family: draw2d.FontFamilySerif,
			Style:  draw2d.FontStyleItalic,
		},
		"Times-BoldItalic": draw2d.FontData{
			Name:   "Nimbus",
			Family: draw2d.FontFamilySerif,
			Style:  draw2d.FontStyleItalic | draw2d.FontStyleBold,
		},
	}
)

// WriterCounter implements the io.Writer interface, and counts
// the total number of bytes written.
type writerCounter struct {
	io.Writer
	n int64
}

func (w *writerCounter) Write(p []byte) (int, error) {
	n, err := w.Writer.Write(p)
	w.n += int64(n)
	return n, err
}

// A JpegCanvas is an image canvas with a WriteTo method
// that writes a jpeg image.
type JpegCanvas struct {
	*Canvas
}

// WriteTo implements the io.WriterTo interface, writing a jpeg image.
func (c JpegCanvas) WriteTo(w io.Writer) (int64, error) {
	wc := writerCounter{Writer: w}
	b := bufio.NewWriter(&wc)
	if err := jpeg.Encode(b, c.img, nil); err != nil {
		return wc.n, err
	}
	err := b.Flush()
	return wc.n, err
}

// A PngCanvas is an image canvas with a WriteTo method that
// writes a png image.
type PngCanvas struct {
	*Canvas
}

// WriteTo implements the io.WriterTo interface, writing a png image.
func (c PngCanvas) WriteTo(w io.Writer) (int64, error) {
	wc := writerCounter{Writer: w}
	b := bufio.NewWriter(&wc)
	if err := png.Encode(b, c.img); err != nil {
		return wc.n, err
	}
	err := b.Flush()
	return wc.n, err
}

// A TiffCanvas is an image canvas with a WriteTo method that
// writes a tiff image.
type TiffCanvas struct {
	*Canvas
}

// WriteTo implements the io.WriterTo interface, writing a tiff image.
func (c TiffCanvas) WriteTo(w io.Writer) (int64, error) {
	wc := writerCounter{Writer: w}
	b := bufio.NewWriter(&wc)
	if err := tiff.Encode(b, c.img, nil); err != nil {
		return wc.n, err
	}
	err := b.Flush()
	return wc.n, err
}
<|endoftext|>


<commit_before>package main

import (
	"errors"
	"fmt"
	"log"
	"strings"
	"sync"
	"time"

	"github.com/coreos/go-etcd/etcd"
	"github.com/fsouza/go-dockerclient"
)

var (
	ErrNoSuchContainer = errors.New("no such container")
	ErrNoPublicport    = errors.New("no public port")
)

type DockerWrapper struct {
	*docker.Client
	Auth docker.AuthConfiguration
}

func (d *DockerWrapper) Pull(image, tag, repository string) error {
	return d.PullImage(
		docker.PullImageOptions{
			Repository: image,
			Tag:        tag,
			Registry:   repository,
		},
		d.Auth,
	)
}

func (d *DockerWrapper) ContainerByName(name string) (*docker.APIContainers, error) {
	name = "/" + name
	containers, err := d.ListContainers(docker.ListContainersOptions{All: true})

	if err != nil {
		return nil, err
	}

	for _, container := range containers {
		for _, cname := range container.Names {
			if cname == name {
				return &container, nil
			}
		}
	}

	return nil, ErrNoSuchContainer
}

func (d *DockerWrapper) CompletelyKill(id string) error {
	err := d.KillContainer(docker.KillContainerOptions{ID: id})
	if err != nil {
		return err
	}

	err = d.RemoveContainer(docker.RemoveContainerOptions{ID: id})
	if err != nil {
		return err
	}

	return nil
}

func (d *DockerWrapper) CreateAndStart(name string, config *docker.Config, host *docker.HostConfig) (*docker.Container, error) {
	container, err := d.CreateContainer(docker.CreateContainerOptions{
		Name:   name,
		Config: config,
	})
	if err != nil {
		return nil, err
	}

	err = d.StartContainer(container.ID, host)

	return container, err
}

func (d *DockerWrapper) PublicPort(name string, private int64) (int64, error) {
	var port int64 = 0

	container, err := d.ContainerByName(name)
	if err != nil {
		return port, err
	}

	for _, maybe := range container.Ports {
		if maybe.Type != "tcp" {
			continue
		}

		if maybe.PrivatePort == private {
			port = maybe.PublicPort
		}
	}

	if port == 0 {
		return port, ErrNoPublicport
	} else {
		return port, nil
	}
}

func DockerListener(in, out chan Message) {
	send := Messenger(TopicDocker, out)
	name := config.Group + "-" + config.ID

	var timer *time.Timer
	timeout := 2 * time.Second
	lock := sync.Mutex{}

	etcdClient := etcd.NewClient(strings.Split(config.EtcdHosts, ","))

	// listen for messages
	for message := range in {
		switch message.Topic {
		case TopicInit:
			// pull image
			if config.Image == "" {
				log.Fatal("Image is required for Docker")
			}
			send(LevelInfo, StatusGood, fmt.Sprintf("docker ready (%s)", name))

		case TopicEnvironment:
			// reset here because environment changes can come together pretty
			// quickly, and we only want to restart once per group of changes.
			if message.Level < LevelChange {
				continue
			}

			reset := func() {
				lock.Lock()
				defer lock.Unlock()
				defer func() {
					timer = nil
				}()

				//// PULL IMAGE ////
				img := config.Image + ":" + EtcdTag
				send(LevelInfo, StatusNeutral, "pulling "+img)

				err := dockerClient.Pull(config.Image, EtcdTag, config.Registry)
				if err != nil {
					send(
						LevelError, StatusBad,
						fmt.Sprintf("error pulling %s: %s", img, err),
					)
					return
				}

				send(LevelDebug, StatusNeutral, "pulled "+img)

				// acquire lock
				lockPath := "/forerunner/locks/" + config.Group
				send(LevelDebug, StatusNeutral, fmt.Sprintf("trying to acquire lock at %s", lockPath))

				// we're going to give it 100 tries (60 seconds) to acquire a
				// lock for restarting, but if we can't get it after that long
				// we'll assume a deadlock and just proceed without one
				for i := 0; i < 100; i++ {
					_, err := etcdClient.Create(lockPath, "", 10)

					if err != nil {
						if strings.Contains(err.Error(), "Key already exists") {
							time.Sleep(600 * time.Millisecond)
							continue
						} else {
							send(LevelFatal, StatusBad, err.Error())
							return
						}

					} else {
						send(LevelDebug, StatusNeutral, fmt.Sprintf("got lock after %d tries", i))

						// defer releasing that lock after we're done
						defer etcdClient.Delete(lockPath, false)

						break
					}
				}

				//// START ////
				container, err := dockerClient.ContainerByName(name)

				// first, clean up old containers. We don't know what
				// configuration they're running so we're just going to restart
				// the container with the new configuration.
				if err != nil && err != ErrNoSuchContainer {
					send(LevelFatal, StatusBad, fmt.Sprintf("error getting containers: %s", err))
					return

				} else if err == ErrNoSuchContainer {
					send(LevelDebug, StatusNeutral, "no container running")

				} else {
					send(LevelInfo, StatusNeutral, "container running, cleaning before restart")

					err = dockerClient.CompletelyKill(container.ID)
					if err != nil {
						if strings.HasPrefix(err.Error(), "No such container") {
							send(LevelWarning, StatusBad, fmt.Sprintf("%s", err))
						} else {
							send(LevelFatal, StatusBad, fmt.Sprintf("%s", err))
							return
						}
					}
				}

				// now we start the container with the current configuration
				send(LevelDebug, StatusNeutral, "starting new container")

				conf := docker.Config{
					Image: img,
					Env:   EtcdEnv,
					Cmd:   config.Command,
				}

				host := docker.HostConfig{
					PublishAllPorts: true,
				}
				if config.DNS != "" {
					host.Dns = strings.Split(config.DNS, ",")
				}

				fmt.Printf("%+v\n", host)

				_, err = dockerClient.CreateAndStart(name, &conf, &host)

				if err != nil {
					send(LevelFatal, StatusBad, fmt.Sprintf("could not start container: %s", err))
				} else {
					send(LevelChange, StatusGood, "container running")
				}
			}

			if timer == nil {
				send(LevelInfo, StatusNeutral, fmt.Sprintf("detected configuration change, waiting for %s to reset", timeout))
				timer = time.AfterFunc(timeout, reset)
			} else if timer.Reset(timeout) {
				send(LevelDebug, StatusNeutral, fmt.Sprintf("additional configuration changes, resetting timer to %s", timeout))
			} else {
				timer = time.AfterFunc(timeout, reset)
			}

		case TopicShutdown:
			container, err := dockerClient.ContainerByName(name)

			if err != nil && err != ErrNoSuchContainer {
				send(LevelFatal, StatusBad, fmt.Sprintf("error getting containers: %s", err))

			} else if err == ErrNoSuchContainer {
				send(LevelDebug, StatusNeutral, "no container running")

			} else {
				send(LevelInfo, StatusNeutral, "shutting down container")

				err = dockerClient.CompletelyKill(container.ID)
				if err != nil {
					if strings.HasPrefix(err.Error(), "No such container") {
						send(LevelWarning, StatusNeutral, fmt.Sprintf("%s", err))
					} else {
						send(LevelFatal, StatusNeutral, fmt.Sprintf("%s", err))
					}
				}
			}
			return

		default:
			log.Fatalf("Docker can't process %s messages", message.Topic)
		}
	}
}
<commit_msg>docker: use better statuses and remove debug print<commit_after>package main

import (
	"errors"
	"fmt"
	"log"
	"strings"
	"sync"
	"time"

	"github.com/coreos/go-etcd/etcd"
	"github.com/fsouza/go-dockerclient"
)

var (
	ErrNoSuchContainer = errors.New("no such container")
	ErrNoPublicport    = errors.New("no public port")
)

type DockerWrapper struct {
	*docker.Client
	Auth docker.AuthConfiguration
}

func (d *DockerWrapper) Pull(image, tag, repository string) error {
	return d.PullImage(
		docker.PullImageOptions{
			Repository: image,
			Tag:        tag,
			Registry:   repository,
		},
		d.Auth,
	)
}

func (d *DockerWrapper) ContainerByName(name string) (*docker.APIContainers, error) {
	name = "/" + name
	containers, err := d.ListContainers(docker.ListContainersOptions{All: true})

	if err != nil {
		return nil, err
	}

	for _, container := range containers {
		for _, cname := range container.Names {
			if cname == name {
				return &container, nil
			}
		}
	}

	return nil, ErrNoSuchContainer
}

func (d *DockerWrapper) CompletelyKill(id string) error {
	err := d.KillContainer(docker.KillContainerOptions{ID: id})
	if err != nil {
		return err
	}

	err = d.RemoveContainer(docker.RemoveContainerOptions{ID: id})
	if err != nil {
		return err
	}

	return nil
}

func (d *DockerWrapper) CreateAndStart(name string, config *docker.Config, host *docker.HostConfig) (*docker.Container, error) {
	container, err := d.CreateContainer(docker.CreateContainerOptions{
		Name:   name,
		Config: config,
	})
	if err != nil {
		return nil, err
	}

	err = d.StartContainer(container.ID, host)

	return container, err
}

func (d *DockerWrapper) PublicPort(name string, private int64) (int64, error) {
	var port int64 = 0

	container, err := d.ContainerByName(name)
	if err != nil {
		return port, err
	}

	for _, maybe := range container.Ports {
		if maybe.Type != "tcp" {
			continue
		}

		if maybe.PrivatePort == private {
			port = maybe.PublicPort
		}
	}

	if port == 0 {
		return port, ErrNoPublicport
	} else {
		return port, nil
	}
}

func DockerListener(in, out chan Message) {
	send := Messenger(TopicDocker, out)
	name := config.Group + "-" + config.ID

	var timer *time.Timer
	timeout := 2 * time.Second
	lock := sync.Mutex{}

	etcdClient := etcd.NewClient(strings.Split(config.EtcdHosts, ","))

	// listen for messages
	for message := range in {
		switch message.Topic {
		case TopicInit:
			// pull image
			if config.Image == "" {
				log.Fatal("Image is required for Docker")
			}
			send(LevelInfo, StatusGood, fmt.Sprintf("docker ready (%s)", name))

		case TopicEnvironment:
			// reset here because environment changes can come together pretty
			// quickly, and we only want to restart once per group of changes.
			if message.Level < LevelChange {
				continue
			}

			reset := func() {
				lock.Lock()
				defer lock.Unlock()
				defer func() {
					timer = nil
				}()

				//// PULL IMAGE ////
				img := config.Image + ":" + EtcdTag
				send(LevelInfo, StatusNeutral, "pulling "+img)

				err := dockerClient.Pull(config.Image, EtcdTag, config.Registry)
				if err != nil {
					send(
						LevelError, StatusBad,
						fmt.Sprintf("error pulling %s: %s", img, err),
					)
					return
				}

				send(LevelDebug, StatusNeutral, "pulled "+img)

				// acquire lock
				lockPath := "/forerunner/locks/" + config.Group
				send(LevelDebug, StatusNeutral, fmt.Sprintf("trying to acquire lock at %s", lockPath))

				// we're going to give it 100 tries (60 seconds) to acquire a
				// lock for restarting, but if we can't get it after that long
				// we'll assume a deadlock and just proceed without one
				for i := 0; i < 100; i++ {
					_, err := etcdClient.Create(lockPath, "", 10)

					if err != nil {
						if strings.Contains(err.Error(), "Key already exists") {
							time.Sleep(600 * time.Millisecond)
							continue
						} else {
							send(LevelFatal, StatusBad, err.Error())
							return
						}

					} else {
						send(LevelDebug, StatusNeutral, fmt.Sprintf("got lock after %d tries", i))

						// defer releasing that lock after we're done
						defer etcdClient.Delete(lockPath, false)

						break
					}
				}

				//// START ////
				container, err := dockerClient.ContainerByName(name)

				// first, clean up old containers. We don't know what
				// configuration they're running so we're just going to restart
				// the container with the new configuration.
				if err != nil && err != ErrNoSuchContainer {
					send(LevelFatal, StatusBad, fmt.Sprintf("error getting containers: %s", err))
					return

				} else if err == ErrNoSuchContainer {
					send(LevelDebug, StatusNeutral, "no container running")

				} else {
					send(LevelInfo, StatusNeutral, "container running, cleaning before restart")

					err = dockerClient.CompletelyKill(container.ID)
					if err != nil {
						if strings.HasPrefix(err.Error(), "No such container") {
							send(LevelWarning, StatusBad, fmt.Sprintf("%s", err))
						} else {
							send(LevelFatal, StatusBad, fmt.Sprintf("%s", err))
							return
						}
					}
				}

				// now we start the container with the current configuration
				send(LevelDebug, StatusNeutral, "starting new container")

				conf := docker.Config{
					Image: img,
					Env:   EtcdEnv,
					Cmd:   config.Command,
				}

				host := docker.HostConfig{
					PublishAllPorts: true,
				}
				if config.DNS != "" {
					host.Dns = strings.Split(config.DNS, ",")
				}

				_, err = dockerClient.CreateAndStart(name, &conf, &host)

				if err != nil {
					send(LevelFatal, StatusBad, fmt.Sprintf("could not start container: %s", err))
				} else {
					send(LevelChange, StatusUp, "container running")
				}
			}

			if timer == nil {
				send(LevelInfo, StatusNeutral, fmt.Sprintf("detected configuration change, waiting for %s to reset", timeout))
				timer = time.AfterFunc(timeout, reset)
			} else if timer.Reset(timeout) {
				send(LevelDebug, StatusNeutral, fmt.Sprintf("additional configuration changes, resetting timer to %s", timeout))
			} else {
				timer = time.AfterFunc(timeout, reset)
			}

		case TopicShutdown:
			container, err := dockerClient.ContainerByName(name)

			if err != nil && err != ErrNoSuchContainer {
				send(LevelFatal, StatusBad, fmt.Sprintf("error getting containers: %s", err))

			} else if err == ErrNoSuchContainer {
				send(LevelDebug, StatusNeutral, "no container running")

			} else {
				send(LevelInfo, StatusDown, "shutting down container")

				err = dockerClient.CompletelyKill(container.ID)
				if err != nil {
					if strings.HasPrefix(err.Error(), "No such container") {
						send(LevelWarning, StatusNeutral, fmt.Sprintf("%s", err))
					} else {
						send(LevelFatal, StatusNeutral, fmt.Sprintf("%s", err))
					}
				}
			}
			return

		default:
			log.Fatalf("Docker can't process %s messages", message.Topic)
		}
	}
}
<|endoftext|>


<commit_before>package openstack

import (
	"fmt"
	"log"
	"time"

	"github.com/hashicorp/terraform/helper/resource"
	"github.com/hashicorp/terraform/helper/schema"

	"github.com/gophercloud/gophercloud"
	"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/pools"
)

func resourceMemberV2() *schema.Resource {
	return &schema.Resource{
		Create: resourceMemberV2Create,
		Read:   resourceMemberV2Read,
		Update: resourceMemberV2Update,
		Delete: resourceMemberV2Delete,

		Schema: map[string]*schema.Schema{
			"region": &schema.Schema{
				Type:        schema.TypeString,
				Required:    true,
				ForceNew:    true,
				DefaultFunc: schema.EnvDefaultFunc("OS_REGION_NAME", ""),
			},

			"name": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
			},

			"tenant_id": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
				Computed: true,
				ForceNew: true,
			},

			"address": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
			},

			"protocol_port": &schema.Schema{
				Type:     schema.TypeInt,
				Required: true,
				ForceNew: true,
			},

			"weight": &schema.Schema{
				Type:     schema.TypeInt,
				Optional: true,
				Computed: true,
				ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) {
					value := v.(int)
					if value < 1 {
						errors = append(errors, fmt.Errorf(
							"Only numbers greater than 0 are supported values for 'weight'"))
					}
					return
				},
			},

			"subnet_id": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
			},

			"admin_state_up": &schema.Schema{
				Type:     schema.TypeBool,
				Default:  true,
				Optional: true,
			},

			"pool_id": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
			},

			"id": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
				Computed: true,
			},
		},
	}
}

func resourceMemberV2Create(d *schema.ResourceData, meta interface{}) error {
	config := meta.(*Config)
	networkingClient, err := config.networkingV2Client(GetRegion(d))
	if err != nil {
		return fmt.Errorf("Error creating OpenStack networking client: %s", err)
	}

	adminStateUp := d.Get("admin_state_up").(bool)
	createOpts := pools.CreateMemberOpts{
		Name:         d.Get("name").(string),
		TenantID:     d.Get("tenant_id").(string),
		Address:      d.Get("address").(string),
		ProtocolPort: d.Get("protocol_port").(int),
		Weight:       d.Get("weight").(int),
		AdminStateUp: &adminStateUp,
	}

	// Must omit if not set
	if v, ok := d.GetOk("subnet_id"); ok {
		createOpts.SubnetID = v.(string)
	}

	poolID := d.Get("pool_id").(string)

	log.Printf("[DEBUG] Create Options: %#v", createOpts)
	member, err := pools.CreateMember(networkingClient, poolID, createOpts).Extract()
	if err != nil {
		return fmt.Errorf("Error creating OpenStack LBaaSV2 member: %s", err)
	}
	log.Printf("[INFO] member ID: %s", member.ID)

	log.Printf("[DEBUG] Waiting for Openstack LBaaSV2 member (%s) to become available.", member.ID)

	stateConf := &resource.StateChangeConf{
		Pending:    []string{"PENDING_CREATE"},
		Target:     []string{"ACTIVE"},
		Refresh:    waitForMemberActive(networkingClient, poolID, member.ID),
		Timeout:    2 * time.Minute,
		Delay:      5 * time.Second,
		MinTimeout: 3 * time.Second,
	}

	_, err = stateConf.WaitForState()
	if err != nil {
		return err
	}

	d.SetId(member.ID)

	return resourceMemberV2Read(d, meta)
}

func resourceMemberV2Read(d *schema.ResourceData, meta interface{}) error {
	config := meta.(*Config)
	networkingClient, err := config.networkingV2Client(GetRegion(d))
	if err != nil {
		return fmt.Errorf("Error creating OpenStack networking client: %s", err)
	}

	member, err := pools.GetMember(networkingClient, d.Get("pool_id").(string), d.Id()).Extract()
	if err != nil {
		return CheckDeleted(d, err, "LBV2 Member")
	}

	log.Printf("[DEBUG] Retrieved OpenStack LBaaSV2 Member %s: %+v", d.Id(), member)

	d.Set("name", member.Name)
	d.Set("weight", member.Weight)
	d.Set("admin_state_up", member.AdminStateUp)
	d.Set("tenant_id", member.TenantID)
	d.Set("subnet_id", member.SubnetID)
	d.Set("address", member.Address)
	d.Set("protocol_port", member.ProtocolPort)
	d.Set("id", member.ID)

	return nil
}

func resourceMemberV2Update(d *schema.ResourceData, meta interface{}) error {
	config := meta.(*Config)
	networkingClient, err := config.networkingV2Client(GetRegion(d))
	if err != nil {
		return fmt.Errorf("Error creating OpenStack networking client: %s", err)
	}

	var updateOpts pools.UpdateMemberOpts
	if d.HasChange("name") {
		updateOpts.Name = d.Get("name").(string)
	}
	if d.HasChange("weight") {
		updateOpts.Weight = d.Get("weight").(int)
	}
	if d.HasChange("admin_state_up") {
		asu := d.Get("admin_state_up").(bool)
		updateOpts.AdminStateUp = &asu
	}

	log.Printf("[DEBUG] Updating OpenStack LBaaSV2 Member %s with options: %+v", d.Id(), updateOpts)

	_, err = pools.UpdateMember(networkingClient, d.Get("pool_id").(string), d.Id(), updateOpts).Extract()
	if err != nil {
		return fmt.Errorf("Error updating OpenStack LBaaSV2 Member: %s", err)
	}

	return resourceMemberV2Read(d, meta)
}

func resourceMemberV2Delete(d *schema.ResourceData, meta interface{}) error {
	config := meta.(*Config)
	networkingClient, err := config.networkingV2Client(GetRegion(d))
	if err != nil {
		return fmt.Errorf("Error creating OpenStack networking client: %s", err)
	}

	stateConf := &resource.StateChangeConf{
		Pending:    []string{"ACTIVE", "PENDING_DELETE"},
		Target:     []string{"DELETED"},
		Refresh:    waitForMemberDelete(networkingClient, d.Get("pool_id").(string), d.Id()),
		Timeout:    2 * time.Minute,
		Delay:      5 * time.Second,
		MinTimeout: 3 * time.Second,
	}

	_, err = stateConf.WaitForState()
	if err != nil {
		return fmt.Errorf("Error deleting OpenStack LBaaSV2 Member: %s", err)
	}

	d.SetId("")
	return nil
}

func waitForMemberActive(networkingClient *gophercloud.ServiceClient, poolID string, memberID string) resource.StateRefreshFunc {
	return func() (interface{}, string, error) {
		member, err := pools.GetMember(networkingClient, poolID, memberID).Extract()
		if err != nil {
			return nil, "", err
		}

		// The member resource has no Status attribute, so a successful Get is the best we can do
		log.Printf("[DEBUG] OpenStack LBaaSV2 Member: %+v", member)
		return member, "ACTIVE", nil
	}
}

func waitForMemberDelete(networkingClient *gophercloud.ServiceClient, poolID string, memberID string) resource.StateRefreshFunc {
	return func() (interface{}, string, error) {
		log.Printf("[DEBUG] Attempting to delete OpenStack LBaaSV2 Member %s", memberID)

		member, err := pools.GetMember(networkingClient, poolID, memberID).Extract()
		if err != nil {
			if _, ok := err.(gophercloud.ErrDefault404); ok {
				log.Printf("[DEBUG] Successfully deleted OpenStack LBaaSV2 Member %s", memberID)
				return member, "DELETED", nil
			}
			return member, "ACTIVE", err
		}

		log.Printf("[DEBUG] Openstack LBaaSV2 Member: %+v", member)
		err = pools.DeleteMember(networkingClient, poolID, memberID).ExtractErr()
		if err != nil {
			if _, ok := err.(gophercloud.ErrDefault404); ok {
				log.Printf("[DEBUG] Successfully deleted OpenStack LBaaSV2 Member %s", memberID)
				return member, "DELETED", nil
			}

			if errCode, ok := err.(gophercloud.ErrUnexpectedResponseCode); ok {
				if errCode.Actual == 409 {
					log.Printf("[DEBUG] OpenStack LBaaSV2 Member (%s) is still in use.", memberID)
					return member, "ACTIVE", nil
				}
			}

			return member, "ACTIVE", err
		}

		log.Printf("[DEBUG] OpenStack LBaaSV2 Member %s still active.", memberID)
		return member, "ACTIVE", nil
	}
}
<commit_msg>provider/openstack: 409 Response on Member Create<commit_after>package openstack

import (
	"fmt"
	"log"
	"time"

	"github.com/hashicorp/terraform/helper/resource"
	"github.com/hashicorp/terraform/helper/schema"

	"github.com/gophercloud/gophercloud"
	"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/lbaas_v2/pools"
)

func resourceMemberV2() *schema.Resource {
	return &schema.Resource{
		Create: resourceMemberV2Create,
		Read:   resourceMemberV2Read,
		Update: resourceMemberV2Update,
		Delete: resourceMemberV2Delete,

		Schema: map[string]*schema.Schema{
			"region": &schema.Schema{
				Type:        schema.TypeString,
				Required:    true,
				ForceNew:    true,
				DefaultFunc: schema.EnvDefaultFunc("OS_REGION_NAME", ""),
			},

			"name": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
			},

			"tenant_id": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
				Computed: true,
				ForceNew: true,
			},

			"address": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
			},

			"protocol_port": &schema.Schema{
				Type:     schema.TypeInt,
				Required: true,
				ForceNew: true,
			},

			"weight": &schema.Schema{
				Type:     schema.TypeInt,
				Optional: true,
				Computed: true,
				ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) {
					value := v.(int)
					if value < 1 {
						errors = append(errors, fmt.Errorf(
							"Only numbers greater than 0 are supported values for 'weight'"))
					}
					return
				},
			},

			"subnet_id": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
			},

			"admin_state_up": &schema.Schema{
				Type:     schema.TypeBool,
				Default:  true,
				Optional: true,
			},

			"pool_id": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
			},

			"id": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
				Computed: true,
			},
		},
	}
}

func resourceMemberV2Create(d *schema.ResourceData, meta interface{}) error {
	config := meta.(*Config)
	networkingClient, err := config.networkingV2Client(GetRegion(d))
	if err != nil {
		return fmt.Errorf("Error creating OpenStack networking client: %s", err)
	}

	adminStateUp := d.Get("admin_state_up").(bool)
	createOpts := pools.CreateMemberOpts{
		Name:         d.Get("name").(string),
		TenantID:     d.Get("tenant_id").(string),
		Address:      d.Get("address").(string),
		ProtocolPort: d.Get("protocol_port").(int),
		Weight:       d.Get("weight").(int),
		AdminStateUp: &adminStateUp,
	}

	// Must omit if not set
	if v, ok := d.GetOk("subnet_id"); ok {
		createOpts.SubnetID = v.(string)
	}

	poolID := d.Get("pool_id").(string)

	log.Printf("[DEBUG] Create Options: %#v", createOpts)

	var member *pools.Member
	err = resource.Retry(10*time.Minute, func() *resource.RetryError {
		var err error
		log.Printf("[DEBUG] Attempting to create LBaaSV2 member")
		member, err = pools.CreateMember(networkingClient, poolID, createOpts).Extract()
		if err != nil {
			if errCode, ok := err.(gophercloud.ErrUnexpectedResponseCode); ok {
				if errCode.Actual == 409 || errCode.Actual == 500 {
					log.Printf("[DEBUG] OpenStack LBaaSV2 member is still creating.")
					return resource.RetryableError(err)
				}
			}
			return resource.NonRetryableError(err)
		}

		return nil
	})

	if err != nil {
		return fmt.Errorf("Error creating OpenStack LBaaSV2 member: %s", err)
	}
	log.Printf("[INFO] member ID: %s", member.ID)

	log.Printf("[DEBUG] Waiting for Openstack LBaaSV2 member (%s) to become available.", member.ID)

	stateConf := &resource.StateChangeConf{
		Pending:    []string{"PENDING_CREATE"},
		Target:     []string{"ACTIVE"},
		Refresh:    waitForMemberActive(networkingClient, poolID, member.ID),
		Timeout:    2 * time.Minute,
		Delay:      5 * time.Second,
		MinTimeout: 3 * time.Second,
	}

	_, err = stateConf.WaitForState()
	if err != nil {
		return err
	}

	d.SetId(member.ID)

	return resourceMemberV2Read(d, meta)
}

func resourceMemberV2Read(d *schema.ResourceData, meta interface{}) error {
	config := meta.(*Config)
	networkingClient, err := config.networkingV2Client(GetRegion(d))
	if err != nil {
		return fmt.Errorf("Error creating OpenStack networking client: %s", err)
	}

	member, err := pools.GetMember(networkingClient, d.Get("pool_id").(string), d.Id()).Extract()
	if err != nil {
		return CheckDeleted(d, err, "LBV2 Member")
	}

	log.Printf("[DEBUG] Retrieved OpenStack LBaaSV2 Member %s: %+v", d.Id(), member)

	d.Set("name", member.Name)
	d.Set("weight", member.Weight)
	d.Set("admin_state_up", member.AdminStateUp)
	d.Set("tenant_id", member.TenantID)
	d.Set("subnet_id", member.SubnetID)
	d.Set("address", member.Address)
	d.Set("protocol_port", member.ProtocolPort)
	d.Set("id", member.ID)

	return nil
}

func resourceMemberV2Update(d *schema.ResourceData, meta interface{}) error {
	config := meta.(*Config)
	networkingClient, err := config.networkingV2Client(GetRegion(d))
	if err != nil {
		return fmt.Errorf("Error creating OpenStack networking client: %s", err)
	}

	var updateOpts pools.UpdateMemberOpts
	if d.HasChange("name") {
		updateOpts.Name = d.Get("name").(string)
	}
	if d.HasChange("weight") {
		updateOpts.Weight = d.Get("weight").(int)
	}
	if d.HasChange("admin_state_up") {
		asu := d.Get("admin_state_up").(bool)
		updateOpts.AdminStateUp = &asu
	}

	log.Printf("[DEBUG] Updating OpenStack LBaaSV2 Member %s with options: %+v", d.Id(), updateOpts)

	_, err = pools.UpdateMember(networkingClient, d.Get("pool_id").(string), d.Id(), updateOpts).Extract()
	if err != nil {
		return fmt.Errorf("Error updating OpenStack LBaaSV2 Member: %s", err)
	}

	return resourceMemberV2Read(d, meta)
}

func resourceMemberV2Delete(d *schema.ResourceData, meta interface{}) error {
	config := meta.(*Config)
	networkingClient, err := config.networkingV2Client(GetRegion(d))
	if err != nil {
		return fmt.Errorf("Error creating OpenStack networking client: %s", err)
	}

	stateConf := &resource.StateChangeConf{
		Pending:    []string{"ACTIVE", "PENDING_DELETE"},
		Target:     []string{"DELETED"},
		Refresh:    waitForMemberDelete(networkingClient, d.Get("pool_id").(string), d.Id()),
		Timeout:    2 * time.Minute,
		Delay:      5 * time.Second,
		MinTimeout: 3 * time.Second,
	}

	_, err = stateConf.WaitForState()
	if err != nil {
		return fmt.Errorf("Error deleting OpenStack LBaaSV2 Member: %s", err)
	}

	d.SetId("")
	return nil
}

func waitForMemberActive(networkingClient *gophercloud.ServiceClient, poolID string, memberID string) resource.StateRefreshFunc {
	return func() (interface{}, string, error) {
		member, err := pools.GetMember(networkingClient, poolID, memberID).Extract()
		if err != nil {
			return nil, "", err
		}

		// The member resource has no Status attribute, so a successful Get is the best we can do
		log.Printf("[DEBUG] OpenStack LBaaSV2 Member: %+v", member)
		return member, "ACTIVE", nil
	}
}

func waitForMemberDelete(networkingClient *gophercloud.ServiceClient, poolID string, memberID string) resource.StateRefreshFunc {
	return func() (interface{}, string, error) {
		log.Printf("[DEBUG] Attempting to delete OpenStack LBaaSV2 Member %s", memberID)

		member, err := pools.GetMember(networkingClient, poolID, memberID).Extract()
		if err != nil {
			if _, ok := err.(gophercloud.ErrDefault404); ok {
				log.Printf("[DEBUG] Successfully deleted OpenStack LBaaSV2 Member %s", memberID)
				return member, "DELETED", nil
			}
			return member, "ACTIVE", err
		}

		log.Printf("[DEBUG] Openstack LBaaSV2 Member: %+v", member)
		err = pools.DeleteMember(networkingClient, poolID, memberID).ExtractErr()
		if err != nil {
			if _, ok := err.(gophercloud.ErrDefault404); ok {
				log.Printf("[DEBUG] Successfully deleted OpenStack LBaaSV2 Member %s", memberID)
				return member, "DELETED", nil
			}

			if errCode, ok := err.(gophercloud.ErrUnexpectedResponseCode); ok {
				if errCode.Actual == 409 {
					log.Printf("[DEBUG] OpenStack LBaaSV2 Member (%s) is still in use.", memberID)
					return member, "PENDING_DELETE", nil
				}
			}

			return member, "ACTIVE", err
		}

		log.Printf("[DEBUG] OpenStack LBaaSV2 Member %s still active.", memberID)
		return member, "ACTIVE", nil
	}
}
<|endoftext|>


<commit_before>package resourcelocator

import (
	"os"
	"bytes"
	"fmt"
	"io"
)

var embeddedFiles = make(map[string] []byte)
func SetEmbeddedFiles(newMap map[string] []byte) {
	embeddedFiles = newMap
}

func Locate(path string) []byte {
	path = path[1:]
	ret := embeddedFiles[path]
	if ret != nil {
		return ret
	}
	return locateExternal(path)
}

func locateExternal(path string) []byte {
	file, err := os.Open(path)
	if err != nil {
		panic(err)
	}
	defer file.Close()

	b := new(bytes.Buffer)
	b.ReadFrom(file)
	return b.Bytes()
}

func CreateEmbeddedLocator(fileName, packageName, resourceDir string) {
	info, err := os.Stat(resourceDir)
	if err != nil {
		panic(err)
	}
	file, err := os.Create(fileName)
	if err != nil {
		panic(err)
	}

	appendHeader(file, packageName)
	defer func(f *os.File){
		appendFooter(f)
		f.Close()
	}(file)
	processFile(file, info.Name(), info)
	fmt.Println("Done")
}

func appendHeader(writer io.Writer, packageName string) {
	fmt.Fprintf(writer, "package %s\n\n", packageName)
	fmt.Fprintf(writer, "import \"github.com/baardsen/resourcelocator\"\n\n")
	fmt.Fprintln(writer, "func init() {")
	fmt.Fprintln(writer, "\tembeddedFiles := make(map[string] []byte)")
}

func appendFooter(writer io.Writer) {
	fmt.Fprintln(writer, "\tresourcelocator.SetEmbeddedFiles(embeddedFiles)")
	fmt.Fprintln(writer, "}")
}

func processFile(writer io.Writer, path string, info os.FileInfo) {
	file, err := os.Open(path)
	if err!= nil {
		fmt.Printf("Error opening %s: %+v\n", path, err)
		return
	}
	defer file.Close()

	if info.IsDir() {
		files, _ :=file.Readdir(0)
		for _, f := range files {
			processFile(writer, path + "/" + f.Name(), f)
		}
		return
	}
	slice := make([]byte, info.Size())
	_, err = file.Read(slice)
	if err != nil {
		fmt.Printf("Error reading %s: %+v\n", path, err)
		return
	}
	fmt.Fprintf(writer, "\tembeddedFiles[\"%s\"] = []byte{", path)
	for _, b := range slice {
		fmt.Fprintf(writer, "0x%x, ", b)
	}
	fmt.Fprintln(writer, "}")
}<commit_msg>go fmt<commit_after>package resourcelocator

import (
	"bytes"
	"fmt"
	"io"
	"os"
)

var embeddedFiles = make(map[string][]byte)

func SetEmbeddedFiles(newMap map[string][]byte) {
	embeddedFiles = newMap
}

func Locate(path string) []byte {
	path = path[1:]
	ret := embeddedFiles[path]
	if ret != nil {
		return ret
	}
	return locateExternal(path)
}

func locateExternal(path string) []byte {
	file, err := os.Open(path)
	if err != nil {
		panic(err)
	}
	defer file.Close()

	b := new(bytes.Buffer)
	b.ReadFrom(file)
	return b.Bytes()
}

func CreateEmbeddedLocator(fileName, packageName, resourceDir string) {
	info, err := os.Stat(resourceDir)
	if err != nil {
		panic(err)
	}
	file, err := os.Create(fileName)
	if err != nil {
		panic(err)
	}

	appendHeader(file, packageName)
	defer func(f *os.File) {
		appendFooter(f)
		f.Close()
	}(file)
	processFile(file, info.Name(), info)
	fmt.Println("Done")
}

func appendHeader(writer io.Writer, packageName string) {
	fmt.Fprintf(writer, "package %s\n\n", packageName)
	fmt.Fprintf(writer, "import \"github.com/baardsen/resourcelocator\"\n\n")
	fmt.Fprintln(writer, "func init() {")
	fmt.Fprintln(writer, "\tembeddedFiles := make(map[string] []byte)")
}

func appendFooter(writer io.Writer) {
	fmt.Fprintln(writer, "\tresourcelocator.SetEmbeddedFiles(embeddedFiles)")
	fmt.Fprintln(writer, "}")
}

func processFile(writer io.Writer, path string, info os.FileInfo) {
	file, err := os.Open(path)
	if err != nil {
		fmt.Printf("Error opening %s: %+v\n", path, err)
		return
	}
	defer file.Close()

	if info.IsDir() {
		files, _ := file.Readdir(0)
		for _, f := range files {
			processFile(writer, path+"/"+f.Name(), f)
		}
		return
	}
	slice := make([]byte, info.Size())
	_, err = file.Read(slice)
	if err != nil {
		fmt.Printf("Error reading %s: %+v\n", path, err)
		return
	}
	fmt.Fprintf(writer, "\tembeddedFiles[\"%s\"] = []byte{", path)
	for _, b := range slice {
		fmt.Fprintf(writer, "0x%x, ", b)
	}
	fmt.Fprintln(writer, "}")
}
<|endoftext|>


<commit_before>package neterr

import (
	"io"
	"net"
	"net/url"

	"github.com/getlantern/idletiming"
)

// IsNetworkError returns true if the error's cause is: io.ErrUnexpectedEOF,
// any *net.OpError, any *url.Error, any URL that implements `Temporary()`
// (and returns true)
func IsNetworkError(err error) bool {
	if err == io.ErrUnexpectedEOF {
		return true
	}

	if causer, ok := err.(causer); ok {
		return IsNetworkError(causer.Cause())
	}

	if urlError, ok := err.(*url.Error); ok {
		return IsNetworkError(urlError.Err)
	}

	if _, ok := err.(*net.OpError); ok {
		return true
	}

	if err == idletiming.ErrIdled {
		return true
	}

	if te, ok := err.(temporary); ok {
		return te.Temporary()
	}

	return false
}

type temporary interface {
	Temporary() bool
}

type causer interface {
	Cause() error
}
<commit_msg>neterr: detect http/2 errors<commit_after>package neterr

import (
	"io"
	"net"
	"net/url"
	"strings"

	"github.com/getlantern/idletiming"
)

// IsNetworkError returns true if the error's cause is: io.ErrUnexpectedEOF,
// any *net.OpError, any *url.Error, any URL that implements `Temporary()`
// (and returns true)
func IsNetworkError(err error) bool {
	if err == io.ErrUnexpectedEOF {
		return true
	}

	if causer, ok := err.(causer); ok {
		return IsNetworkError(causer.Cause())
	}

	if urlError, ok := err.(*url.Error); ok {
		return IsNetworkError(urlError.Err)
	}

	if _, ok := err.(*net.OpError); ok {
		return true
	}

	if err == idletiming.ErrIdled {
		return true
	}

	{
		// net/http's http2 errors are unexported structs, I don't know
		// of a better way to detect this :(
		// see net/http/h2_bundle.go
		msg := err.Error()
		if strings.HasPrefix(msg, "stream error: stream ID ") {
			return true
		}
		if strings.HasPrefix(msg, "connection error: ") {
			return true
		}
	}

	if te, ok := err.(temporary); ok {
		return te.Temporary()
	}

	return false
}

type temporary interface {
	Temporary() bool
}

type causer interface {
	Cause() error
}
<|endoftext|>


<commit_before>// Package errors implements a plugin to get Linux & Windows error and status information.
package errors

import (
	"path/filepath"
	"strings"

	"github.com/StalkR/goircbot/bot"
)

func handle(e *bot.Event, table [][]string) {
	arg := strings.TrimSpace(e.Args)
	if len(arg) == 0 {
		return
	}
	e.Bot.Privmsg(e.Target, find(table, arg))
}

// Register registers the plugin with a bot.
func Register(b bot.Bot, dir string) error {
	winerrors, err := parse(filepath.Join(dir, "winerrors.txt"))
	if err != nil {
		return err
	}
	ntstatus, err := parse(filepath.Join(dir, "ntstatus.txt"))
	if err != nil {
		return err
	}
	errnos, err := parse(filepath.Join(dir, "errnos.txt"))
	if err != nil {
		return err
	}

	b.Commands().Add("error", bot.Command{
		Help:    "get Windows error code information",
		Handler: func(e *bot.Event) { handle(e, winerrors) },
		Pub:     true,
		Priv:    true,
		Hidden:  false})
	b.Commands().Add("status", bot.Command{
		Help:    "get Windows status code information",
		Handler: func(e *bot.Event) { handle(e, ntstatus) },
		Pub:     true,
		Priv:    true,
		Hidden:  false})
	b.Commands().Add("errno", bot.Command{
		Help:    "get Windows status code information",
		Handler: func(e *bot.Event) { handle(e, errnos) },
		Pub:     true,
		Priv:    true,
		Hidden:  false})
	return nil
}
<commit_msg>plugin/errors: fix errno help<commit_after>// Package errors implements a plugin to get Linux & Windows error and status information.
package errors

import (
	"path/filepath"
	"strings"

	"github.com/StalkR/goircbot/bot"
)

func handle(e *bot.Event, table [][]string) {
	arg := strings.TrimSpace(e.Args)
	if len(arg) == 0 {
		return
	}
	e.Bot.Privmsg(e.Target, find(table, arg))
}

// Register registers the plugin with a bot.
func Register(b bot.Bot, dir string) error {
	winerrors, err := parse(filepath.Join(dir, "winerrors.txt"))
	if err != nil {
		return err
	}
	ntstatus, err := parse(filepath.Join(dir, "ntstatus.txt"))
	if err != nil {
		return err
	}
	errnos, err := parse(filepath.Join(dir, "errnos.txt"))
	if err != nil {
		return err
	}

	b.Commands().Add("error", bot.Command{
		Help:    "get Windows error code information",
		Handler: func(e *bot.Event) { handle(e, winerrors) },
		Pub:     true,
		Priv:    true,
		Hidden:  false})
	b.Commands().Add("status", bot.Command{
		Help:    "get Windows status code information",
		Handler: func(e *bot.Event) { handle(e, ntstatus) },
		Pub:     true,
		Priv:    true,
		Hidden:  false})
	b.Commands().Add("errno", bot.Command{
		Help:    "get Linux error code information",
		Handler: func(e *bot.Event) { handle(e, errnos) },
		Pub:     true,
		Priv:    true,
		Hidden:  false})
	return nil
}
<|endoftext|>


<commit_before>package chronograf

import (
	"context"
	"net/http"
	"time"
)

// General errors.
const (
	ErrUpstreamTimeout     = Error("request to backend timed out")
	ErrExplorationNotFound = Error("exploration not found")
	ErrSourceNotFound      = Error("source not found")
	ErrServerNotFound      = Error("server not found")
	ErrLayoutNotFound      = Error("layout not found")
	ErrDashboardNotFound   = Error("dashboard not found")
	ErrUserNotFound        = Error("user not found")
	ErrLayoutInvalid       = Error("layout is invalid")
	ErrAlertNotFound       = Error("alert not found")
	ErrAuthentication      = Error("user not authenticated")
)

// Error is a domain error encountered while processing chronograf requests
type Error string

func (e Error) Error() string {
	return string(e)
}

// Logger represents an abstracted structured logging implementation. It
// provides methods to trigger log messages at various alert levels and a
// WithField method to set keys for a structured log message.
type Logger interface {
	Debug(...interface{})
	Info(...interface{})
	Warn(...interface{})
	Error(...interface{})
	Fatal(...interface{})
	Panic(...interface{})

	WithField(string, interface{}) Logger
}

// Assets returns a handler to serve the website.
type Assets interface {
	Handler() http.Handler
}

// TimeSeries represents a queryable time series database.
type TimeSeries interface {
	// Query retrieves time series data from the database.
	Query(context.Context, Query) (Response, error)
	// Connect will connect to the time series using the information in `Source`.
	Connect(context.Context, *Source) error
}

// Range represents an upper and lower bound for data
type Range struct {
	Upper int64 `json:"upper"` // Upper is the upper bound
	Lower int64 `json:"lower"` // Lower is the lower bound
}

// Query retrieves a Response from a TimeSeries.
type Query struct {
	Command  string   `json:"query"`              // Command is the query itself
	DB       string   `json:"db,omitempty"`       // DB is optional and if empty will not be used.
	RP       string   `json:"rp,omitempty"`       // RP is a retention policy and optional; if empty will not be used.
	Wheres   []string `json:"wheres,omitempty"`   // Wheres restricts the query to certain attributes
	GroupBys []string `json:"groupbys,omitempty"` // GroupBys collate the query by these tags
	Label    string   `json:"label,omitempty"`    // Label is the Y-Axis label for the data
	Range    *Range   `json:"range,omitempty"`    // Range is the default Y-Axis range for the data
}

// Response is the result of a query against a TimeSeries
type Response interface {
	MarshalJSON() ([]byte, error)
}

// Source is connection information to a time-series data store.
type Source struct {
	ID                 int    `json:"id,omitempty,string"`          // ID is the unique ID of the source
	Name               string `json:"name"`                         // Name is the user-defined name for the source
	Type               string `json:"type,omitempty"`               // Type specifies which kinds of source (enterprise vs oss)
	Username           string `json:"username,omitempty"`           // Username is the username to connect to the source
	Password           string `json:"password,omitempty"`           // Password is in CLEARTEXT
	URL                string `json:"url"`                          // URL are the connections to the source
	InsecureSkipVerify bool   `json:"insecureSkipVerify,omitempty"` // InsecureSkipVerify as true means any certificate presented by the source is accepted.
	Default            bool   `json:"default"`                      // Default specifies the default source for the application
	Telegraf           string `json:"telegraf"`                     // Telegraf is the db telegraf is written to.  By default it is "telegraf"
}

// SourcesStore stores connection information for a `TimeSeries`
type SourcesStore interface {
	// All returns all sources in the store
	All(context.Context) ([]Source, error)
	// Add creates a new source in the SourcesStore and returns Source with ID
	Add(context.Context, Source) (Source, error)
	// Delete the Source from the store
	Delete(context.Context, Source) error
	// Get retrieves Source if `ID` exists
	Get(ctx context.Context, ID int) (Source, error)
	// Update the Source in the store.
	Update(context.Context, Source) error
}

// AlertRule represents rules for building a tickscript alerting task
type AlertRule struct {
	ID            string        `json:"id,omitempty"` // ID is the unique ID of the alert
	Query         QueryConfig   `json:"query"`        // Query is the filter of data for the alert.
	Every         string        `json:"every"`        // Every how often to check for the alerting criteria
	Alerts        []string      `json:"alerts"`       // AlertServices name all the services to notify (e.g. pagerduty)
	Message       string        `json:"message"`      // Message included with alert
	Trigger       string        `json:"trigger"`      // Trigger is a type that defines when to trigger the alert
	TriggerValues TriggerValues `json:"values"`       // Defines the values that cause the alert to trigger
	Name          string        `json:"name"`         // Name is the user-defined name for the alert
}

// AlertRulesStore stores rules for building tickscript alerting tasks
type AlertRulesStore interface {
	// All returns all rules in the store for the given source and kapacitor id
	All(ctx context.Context, sourceID, kapaID int) ([]AlertRule, error)
	// Add creates a new rule in the AlertRulesStore and returns AlertRule with ID for a given source and kapacitor id
	Add(ctx context.Context, sourceID, kapaID int, rule AlertRule) (AlertRule, error)
	// Delete the AlertRule from the store for a given source and kapacitor ID
	Delete(ctx context.Context, sourceID, kapaID int, rule AlertRule) error
	// Get retrieves AlertRule if `ID` exists within a given source and kapacitor id
	Get(ctx context.Context, sourceID, kapaID int, ID string) (AlertRule, error)
	// Update the AlertRule in the store within a given source and kapacitor id
	Update(ctx context.Context, sourceID, kapaID int, rule AlertRule) error
}

// TICKScript task to be used by kapacitor
type TICKScript string

// Ticker generates tickscript tasks for kapacitor
type Ticker interface {
	// Generate will create the tickscript to be used as a kapacitor task
	Generate(AlertRule) (TICKScript, error)
}

// TriggerValues specifies the alerting logic for a specific trigger type
type TriggerValues struct {
	Change        string `json:"change,omitempty"`        // Change specifies if the change is a percent or absolute
	Period        string `json:"period,omitempty"`        // Period length of time before deadman is alerted
	Shift         string `json:"shift,omitempty"`         // Shift is the amount of time to look into the past for the alert to compare to the present
	Operator      string `json:"operator,omitempty"`      // Operator for alert comparison
	RangeOperator string `json:"rangeOperator,omitempty"` // RangeOperator is an optional operator for range comparisons
	Value         string `json:"value,omitempty"`         // Value is the boundary value when alert goes critical
	RangeValue    string `json:"rangeValue,omitempty"`    // RangeValue is an optional value for range comparisons
}

// Field represent influxql fields and functions from the UI
type Field struct {
	Field string   `json:"field"`
	Funcs []string `json:"funcs"`
}

// GroupBy represents influxql group by tags from the UI
type GroupBy struct {
	Time string   `json:"time"`
	Tags []string `json:"tags"`
}

// QueryConfig represents UI query from the data explorer
type QueryConfig struct {
	ID              string              `json:"id,omitempty"`
	Database        string              `json:"database"`
	Measurement     string              `json:"measurement"`
	RetentionPolicy string              `json:"retentionPolicy"`
	Fields          []Field             `json:"fields"`
	Tags            map[string][]string `json:"tags"`
	GroupBy         GroupBy             `json:"groupBy"`
	AreTagsAccepted bool                `json:"areTagsAccepted"`
	RawText         string              `json:"rawText,omitempty"`
}

// Server represents a proxy connection to an HTTP server
type Server struct {
	ID       int    // ID is the unique ID of the server
	SrcID    int    // SrcID of the data source
	Name     string // Name is the user-defined name for the server
	Username string // Username is the username to connect to the server
	Password string // Password is in CLEARTEXT
	URL      string // URL are the connections to the server
}

// ServersStore stores connection information for a `Server`
type ServersStore interface {
	// All returns all servers in the store
	All(context.Context) ([]Server, error)
	// Add creates a new source in the ServersStore and returns Server with ID
	Add(context.Context, Server) (Server, error)
	// Delete the Server from the store
	Delete(context.Context, Server) error
	// Get retrieves Server if `ID` exists
	Get(ctx context.Context, ID int) (Server, error)
	// Update the Server in the store.
	Update(context.Context, Server) error
}

// ID creates uniq ID string
type ID interface {
	// Generate creates a unique ID string
	Generate() (string, error)
}

// UserID is a unique ID for a source user.
type UserID int

// User represents an authenticated user.
type User struct {
	ID    UserID `json:"id"`
	Email string `json:"email"`
}

// UsersStore is the Storage and retrieval of authentication information
type UsersStore interface {
	// Create a new User in the UsersStore
	Add(context.Context, *User) (*User, error)
	// Delete the User from the UsersStore
	Delete(context.Context, *User) error
	// Get retrieves a user if `ID` exists.
	Get(ctx context.Context, ID UserID) (*User, error)
	// Update the user's permissions or roles
	Update(context.Context, *User) error
	// FindByEmail will retrieve a user by email address.
	FindByEmail(ctx context.Context, Email string) (*User, error)
}

// DashboardID is the dashboard ID
type DashboardID int

// Dashboard represents all visual and query data for a dashboard
type Dashboard struct {
	ID    DashboardID     `json:"id"`
	Cells []DashboardCell `json:"cells"`
	Name  string          `json:"name"`
}

// DashboardCell holds visual and query information for a cell
type DashboardCell struct {
	X       int32    `json:"x"`
	Y       int32    `json:"y"`
	W       int32    `json:"w"`
	H       int32    `json:"h"`
	Name    string   `json:"name"`
	Queries []string `json:"queries"`
	Type    string   `json:"type"`
}

// DashboardsStore is the storage and retrieval of dashboards
type DashboardsStore interface {
	// All lists all dashboards from the DashboardStore
	All(context.Context) ([]Dashboard, error)
	// Create a new Dashboard in the DashboardStore
	Add(context.Context, Dashboard) (Dashboard, error)
	// Delete the Dashboard from the DashboardStore if `ID` exists.
	Delete(context.Context, Dashboard) error
	// Get retrieves a dashboard if `ID` exists.
	Get(ctx context.Context, id DashboardID) (Dashboard, error)
	// Update replaces the dashboard information
	Update(context.Context, Dashboard) error
}

// ExplorationID is a unique ID for an Exploration.
type ExplorationID int

// Exploration is a serialization of front-end Data Explorer.
type Exploration struct {
	ID        ExplorationID
	Name      string    // User provided name of the Exploration.
	UserID    UserID    // UserID is the owner of this Exploration.
	Data      string    // Opaque blob of JSON data.
	CreatedAt time.Time // Time the exploration was first created.
	UpdatedAt time.Time // Latest time the exploration was updated.
	Default   bool      // Flags an exploration as the default.
}

// ExplorationStore stores front-end serializations of data explorer sessions.
type ExplorationStore interface {
	// Search the ExplorationStore for each Exploration owned by `UserID`.
	Query(ctx context.Context, userID UserID) ([]*Exploration, error)
	// Create a new Exploration in the ExplorationStore.
	Add(context.Context, *Exploration) (*Exploration, error)
	// Delete the Exploration from the ExplorationStore.
	Delete(context.Context, *Exploration) error
	// Retrieve an Exploration if `ID` exists.
	Get(ctx context.Context, ID ExplorationID) (*Exploration, error)
	// Update the Exploration; will also update the `UpdatedAt` time.
	Update(context.Context, *Exploration) error
}

// Cell is a rectangle and multiple time series queries to visualize.
type Cell struct {
	X       int32   `json:"x"`
	Y       int32   `json:"y"`
	W       int32   `json:"w"`
	H       int32   `json:"h"`
	I       string  `json:"i"`
	Name    string  `json:"name"`
	Queries []Query `json:"queries"`
	Type    string  `json:"type"`
}

// Layout is a collection of Cells for visualization
type Layout struct {
	ID          string `json:"id"`
	Application string `json:"app"`
	Measurement string `json:"measurement"`
	Autoflow    bool   `json:"autoflow"`
	Cells       []Cell `json:"cells"`
}

// LayoutStore stores dashboards and associated Cells
type LayoutStore interface {
	// All returns all dashboards in the store
	All(context.Context) ([]Layout, error)
	// Add creates a new dashboard in the LayoutStore
	Add(context.Context, Layout) (Layout, error)
	// Delete the dashboard from the store
	Delete(context.Context, Layout) error
	// Get retrieves Layout if `ID` exists
	Get(ctx context.Context, ID string) (Layout, error)
	// Update the dashboard in the store.
	Update(context.Context, Layout) error
}

// Principal is any entity that can be authenticated
type Principal string

// PrincipalKey is used to pass principal
// via context.Context to request-scoped
// functions.
const PrincipalKey Principal = "principal"

// Authenticator represents a service for authenticating users.
type Authenticator interface {
	// Authenticate returns User associated with token if successful.
	Authenticate(ctx context.Context, token string) (Principal, error)
	// Token generates a valid token for Principal lasting a duration
	Token(context.Context, Principal, time.Duration) (string, error)
}

// TokenExtractor extracts tokens from http requests
type TokenExtractor interface {
	// Extract will return the token or an error.
	Extract(r *http.Request) (string, error)
}
<commit_msg>Remove RangeOperator from chronograf.go, upon @goller's instruction.<commit_after>package chronograf

import (
	"context"
	"net/http"
	"time"
)

// General errors.
const (
	ErrUpstreamTimeout     = Error("request to backend timed out")
	ErrExplorationNotFound = Error("exploration not found")
	ErrSourceNotFound      = Error("source not found")
	ErrServerNotFound      = Error("server not found")
	ErrLayoutNotFound      = Error("layout not found")
	ErrDashboardNotFound   = Error("dashboard not found")
	ErrUserNotFound        = Error("user not found")
	ErrLayoutInvalid       = Error("layout is invalid")
	ErrAlertNotFound       = Error("alert not found")
	ErrAuthentication      = Error("user not authenticated")
)

// Error is a domain error encountered while processing chronograf requests
type Error string

func (e Error) Error() string {
	return string(e)
}

// Logger represents an abstracted structured logging implementation. It
// provides methods to trigger log messages at various alert levels and a
// WithField method to set keys for a structured log message.
type Logger interface {
	Debug(...interface{})
	Info(...interface{})
	Warn(...interface{})
	Error(...interface{})
	Fatal(...interface{})
	Panic(...interface{})

	WithField(string, interface{}) Logger
}

// Assets returns a handler to serve the website.
type Assets interface {
	Handler() http.Handler
}

// TimeSeries represents a queryable time series database.
type TimeSeries interface {
	// Query retrieves time series data from the database.
	Query(context.Context, Query) (Response, error)
	// Connect will connect to the time series using the information in `Source`.
	Connect(context.Context, *Source) error
}

// Range represents an upper and lower bound for data
type Range struct {
	Upper int64 `json:"upper"` // Upper is the upper bound
	Lower int64 `json:"lower"` // Lower is the lower bound
}

// Query retrieves a Response from a TimeSeries.
type Query struct {
	Command  string   `json:"query"`              // Command is the query itself
	DB       string   `json:"db,omitempty"`       // DB is optional and if empty will not be used.
	RP       string   `json:"rp,omitempty"`       // RP is a retention policy and optional; if empty will not be used.
	Wheres   []string `json:"wheres,omitempty"`   // Wheres restricts the query to certain attributes
	GroupBys []string `json:"groupbys,omitempty"` // GroupBys collate the query by these tags
	Label    string   `json:"label,omitempty"`    // Label is the Y-Axis label for the data
	Range    *Range   `json:"range,omitempty"`    // Range is the default Y-Axis range for the data
}

// Response is the result of a query against a TimeSeries
type Response interface {
	MarshalJSON() ([]byte, error)
}

// Source is connection information to a time-series data store.
type Source struct {
	ID                 int    `json:"id,omitempty,string"`          // ID is the unique ID of the source
	Name               string `json:"name"`                         // Name is the user-defined name for the source
	Type               string `json:"type,omitempty"`               // Type specifies which kinds of source (enterprise vs oss)
	Username           string `json:"username,omitempty"`           // Username is the username to connect to the source
	Password           string `json:"password,omitempty"`           // Password is in CLEARTEXT
	URL                string `json:"url"`                          // URL are the connections to the source
	InsecureSkipVerify bool   `json:"insecureSkipVerify,omitempty"` // InsecureSkipVerify as true means any certificate presented by the source is accepted.
	Default            bool   `json:"default"`                      // Default specifies the default source for the application
	Telegraf           string `json:"telegraf"`                     // Telegraf is the db telegraf is written to.  By default it is "telegraf"
}

// SourcesStore stores connection information for a `TimeSeries`
type SourcesStore interface {
	// All returns all sources in the store
	All(context.Context) ([]Source, error)
	// Add creates a new source in the SourcesStore and returns Source with ID
	Add(context.Context, Source) (Source, error)
	// Delete the Source from the store
	Delete(context.Context, Source) error
	// Get retrieves Source if `ID` exists
	Get(ctx context.Context, ID int) (Source, error)
	// Update the Source in the store.
	Update(context.Context, Source) error
}

// AlertRule represents rules for building a tickscript alerting task
type AlertRule struct {
	ID            string        `json:"id,omitempty"` // ID is the unique ID of the alert
	Query         QueryConfig   `json:"query"`        // Query is the filter of data for the alert.
	Every         string        `json:"every"`        // Every how often to check for the alerting criteria
	Alerts        []string      `json:"alerts"`       // AlertServices name all the services to notify (e.g. pagerduty)
	Message       string        `json:"message"`      // Message included with alert
	Trigger       string        `json:"trigger"`      // Trigger is a type that defines when to trigger the alert
	TriggerValues TriggerValues `json:"values"`       // Defines the values that cause the alert to trigger
	Name          string        `json:"name"`         // Name is the user-defined name for the alert
}

// AlertRulesStore stores rules for building tickscript alerting tasks
type AlertRulesStore interface {
	// All returns all rules in the store for the given source and kapacitor id
	All(ctx context.Context, sourceID, kapaID int) ([]AlertRule, error)
	// Add creates a new rule in the AlertRulesStore and returns AlertRule with ID for a given source and kapacitor id
	Add(ctx context.Context, sourceID, kapaID int, rule AlertRule) (AlertRule, error)
	// Delete the AlertRule from the store for a given source and kapacitor ID
	Delete(ctx context.Context, sourceID, kapaID int, rule AlertRule) error
	// Get retrieves AlertRule if `ID` exists within a given source and kapacitor id
	Get(ctx context.Context, sourceID, kapaID int, ID string) (AlertRule, error)
	// Update the AlertRule in the store within a given source and kapacitor id
	Update(ctx context.Context, sourceID, kapaID int, rule AlertRule) error
}

// TICKScript task to be used by kapacitor
type TICKScript string

// Ticker generates tickscript tasks for kapacitor
type Ticker interface {
	// Generate will create the tickscript to be used as a kapacitor task
	Generate(AlertRule) (TICKScript, error)
}

// TriggerValues specifies the alerting logic for a specific trigger type
type TriggerValues struct {
	Change     string `json:"change,omitempty"`     // Change specifies if the change is a percent or absolute
	Period     string `json:"period,omitempty"`     // Period length of time before deadman is alerted
	Shift      string `json:"shift,omitempty"`      // Shift is the amount of time to look into the past for the alert to compare to the present
	Operator   string `json:"operator,omitempty"`   // Operator for alert comparison
	Value      string `json:"value,omitempty"`      // Value is the boundary value when alert goes critical
	RangeValue string `json:"rangeValue,omitempty"` // RangeValue is an optional value for range comparisons
}

// Field represent influxql fields and functions from the UI
type Field struct {
	Field string   `json:"field"`
	Funcs []string `json:"funcs"`
}

// GroupBy represents influxql group by tags from the UI
type GroupBy struct {
	Time string   `json:"time"`
	Tags []string `json:"tags"`
}

// QueryConfig represents UI query from the data explorer
type QueryConfig struct {
	ID              string              `json:"id,omitempty"`
	Database        string              `json:"database"`
	Measurement     string              `json:"measurement"`
	RetentionPolicy string              `json:"retentionPolicy"`
	Fields          []Field             `json:"fields"`
	Tags            map[string][]string `json:"tags"`
	GroupBy         GroupBy             `json:"groupBy"`
	AreTagsAccepted bool                `json:"areTagsAccepted"`
	RawText         string              `json:"rawText,omitempty"`
}

// Server represents a proxy connection to an HTTP server
type Server struct {
	ID       int    // ID is the unique ID of the server
	SrcID    int    // SrcID of the data source
	Name     string // Name is the user-defined name for the server
	Username string // Username is the username to connect to the server
	Password string // Password is in CLEARTEXT
	URL      string // URL are the connections to the server
}

// ServersStore stores connection information for a `Server`
type ServersStore interface {
	// All returns all servers in the store
	All(context.Context) ([]Server, error)
	// Add creates a new source in the ServersStore and returns Server with ID
	Add(context.Context, Server) (Server, error)
	// Delete the Server from the store
	Delete(context.Context, Server) error
	// Get retrieves Server if `ID` exists
	Get(ctx context.Context, ID int) (Server, error)
	// Update the Server in the store.
	Update(context.Context, Server) error
}

// ID creates uniq ID string
type ID interface {
	// Generate creates a unique ID string
	Generate() (string, error)
}

// UserID is a unique ID for a source user.
type UserID int

// User represents an authenticated user.
type User struct {
	ID    UserID `json:"id"`
	Email string `json:"email"`
}

// UsersStore is the Storage and retrieval of authentication information
type UsersStore interface {
	// Create a new User in the UsersStore
	Add(context.Context, *User) (*User, error)
	// Delete the User from the UsersStore
	Delete(context.Context, *User) error
	// Get retrieves a user if `ID` exists.
	Get(ctx context.Context, ID UserID) (*User, error)
	// Update the user's permissions or roles
	Update(context.Context, *User) error
	// FindByEmail will retrieve a user by email address.
	FindByEmail(ctx context.Context, Email string) (*User, error)
}

// DashboardID is the dashboard ID
type DashboardID int

// Dashboard represents all visual and query data for a dashboard
type Dashboard struct {
	ID    DashboardID     `json:"id"`
	Cells []DashboardCell `json:"cells"`
	Name  string          `json:"name"`
}

// DashboardCell holds visual and query information for a cell
type DashboardCell struct {
	X       int32    `json:"x"`
	Y       int32    `json:"y"`
	W       int32    `json:"w"`
	H       int32    `json:"h"`
	Name    string   `json:"name"`
	Queries []string `json:"queries"`
	Type    string   `json:"type"`
}

// DashboardsStore is the storage and retrieval of dashboards
type DashboardsStore interface {
	// All lists all dashboards from the DashboardStore
	All(context.Context) ([]Dashboard, error)
	// Create a new Dashboard in the DashboardStore
	Add(context.Context, Dashboard) (Dashboard, error)
	// Delete the Dashboard from the DashboardStore if `ID` exists.
	Delete(context.Context, Dashboard) error
	// Get retrieves a dashboard if `ID` exists.
	Get(ctx context.Context, id DashboardID) (Dashboard, error)
	// Update replaces the dashboard information
	Update(context.Context, Dashboard) error
}

// ExplorationID is a unique ID for an Exploration.
type ExplorationID int

// Exploration is a serialization of front-end Data Explorer.
type Exploration struct {
	ID        ExplorationID
	Name      string    // User provided name of the Exploration.
	UserID    UserID    // UserID is the owner of this Exploration.
	Data      string    // Opaque blob of JSON data.
	CreatedAt time.Time // Time the exploration was first created.
	UpdatedAt time.Time // Latest time the exploration was updated.
	Default   bool      // Flags an exploration as the default.
}

// ExplorationStore stores front-end serializations of data explorer sessions.
type ExplorationStore interface {
	// Search the ExplorationStore for each Exploration owned by `UserID`.
	Query(ctx context.Context, userID UserID) ([]*Exploration, error)
	// Create a new Exploration in the ExplorationStore.
	Add(context.Context, *Exploration) (*Exploration, error)
	// Delete the Exploration from the ExplorationStore.
	Delete(context.Context, *Exploration) error
	// Retrieve an Exploration if `ID` exists.
	Get(ctx context.Context, ID ExplorationID) (*Exploration, error)
	// Update the Exploration; will also update the `UpdatedAt` time.
	Update(context.Context, *Exploration) error
}

// Cell is a rectangle and multiple time series queries to visualize.
type Cell struct {
	X       int32   `json:"x"`
	Y       int32   `json:"y"`
	W       int32   `json:"w"`
	H       int32   `json:"h"`
	I       string  `json:"i"`
	Name    string  `json:"name"`
	Queries []Query `json:"queries"`
	Type    string  `json:"type"`
}

// Layout is a collection of Cells for visualization
type Layout struct {
	ID          string `json:"id"`
	Application string `json:"app"`
	Measurement string `json:"measurement"`
	Autoflow    bool   `json:"autoflow"`
	Cells       []Cell `json:"cells"`
}

// LayoutStore stores dashboards and associated Cells
type LayoutStore interface {
	// All returns all dashboards in the store
	All(context.Context) ([]Layout, error)
	// Add creates a new dashboard in the LayoutStore
	Add(context.Context, Layout) (Layout, error)
	// Delete the dashboard from the store
	Delete(context.Context, Layout) error
	// Get retrieves Layout if `ID` exists
	Get(ctx context.Context, ID string) (Layout, error)
	// Update the dashboard in the store.
	Update(context.Context, Layout) error
}

// Principal is any entity that can be authenticated
type Principal string

// PrincipalKey is used to pass principal
// via context.Context to request-scoped
// functions.
const PrincipalKey Principal = "principal"

// Authenticator represents a service for authenticating users.
type Authenticator interface {
	// Authenticate returns User associated with token if successful.
	Authenticate(ctx context.Context, token string) (Principal, error)
	// Token generates a valid token for Principal lasting a duration
	Token(context.Context, Principal, time.Duration) (string, error)
}

// TokenExtractor extracts tokens from http requests
type TokenExtractor interface {
	// Extract will return the token or an error.
	Extract(r *http.Request) (string, error)
}
<|endoftext|>


<commit_before>package chronograf

import (
	"context"
	"net/http"
	"time"
)

// General errors.
const (
	ErrUpstreamTimeout     = Error("request to backend timed out")
	ErrExplorationNotFound = Error("exploration not found")
	ErrSourceNotFound      = Error("source not found")
	ErrServerNotFound      = Error("server not found")
	ErrLayoutNotFound      = Error("layout not found")
	ErrAuthentication      = Error("user not authenticated")
)

// Error is a domain error encountered while processing chronograf requests
type Error string

func (e Error) Error() string {
	return string(e)
}

// Logger represents an abstracted structured logging implementation. It
// provides methods to trigger log messages at various alert levels and a
// WithField method to set keys for a structured log message.
type Logger interface {
	Info(...interface{})
	Warn(...interface{})
	Debug(...interface{})
	Panic(...interface{})
	Error(...interface{})

	WithField(string, interface{}) Logger
}

// Assets returns a handler to serve the website.
type Assets interface {
	Handler() http.Handler
}

// TimeSeries represents a queryable time series database.
type TimeSeries interface {
	// Query retrieves time series data from the database.
	Query(context.Context, Query) (Response, error)
	// Connect will connect to the time series using the information in `Source`.
	Connect(context.Context, *Source) error
}

// Query retrieves a Response from a TimeSeries.
type Query struct {
	Command string `json:"query"` // Command is the query itself
	DB      string `json:"db"`    // DB is optional and if empty will not be used.
	RP      string `json:"rp"`    // RP is a retention policy and optional; if empty will not be used.
}

// Response is the result of a query against a TimeSeries
type Response interface {
	MarshalJSON() ([]byte, error)
}

// Source is connection information to a time-series data store.
type Source struct {
	ID       int    `json:"id,omitempty,string"` // ID is the unique ID of the source
	Name     string `json:"name"`                // Name is the user-defined name for the source
	Type     string `json:"type,omitempty"`      // Type specifies which kinds of source (enterprise vs oss)
	Username string `json:"username,omitempty"`  // Username is the username to connect to the source
	Password string `json:"password,omitempty"`  // Password is in CLEARTEXT FIXME
	URL      string `json:"url"`                 // URL are the connections to the source
	Default  bool   `json:"default"`             // Default specifies the default source for the application
}

// SourcesStore stores connection information for a `TimeSeries`
type SourcesStore interface {
	// All returns all sources in the store
	All(context.Context) ([]Source, error)
	// Add creates a new source in the SourcesStore and returns Source with ID
	Add(context.Context, Source) (Source, error)
	// Delete the Source from the store
	Delete(context.Context, Source) error
	// Get retrieves Source if `ID` exists
	Get(ctx context.Context, ID int) (Source, error)
	// Update the Source in the store.
	Update(context.Context, Source) error
}

// Server represents a proxy connection to an HTTP server
type Server struct {
	ID       int    // ID is the unique ID of the server
	SrcID    int    // SrcID of the data source
	Name     string // Name is the user-defined name for the server
	Username string // Username is the username to connect to the server
	Password string // Password is in CLEARTEXT FIXME
	URL      string // URL are the connections to the server
}

// ServersStore stores connection information for a `Server`
type ServersStore interface {
	// All returns all servers in the store
	All(context.Context) ([]Server, error)
	// Add creates a new source in the ServersStore and returns Server with ID
	Add(context.Context, Server) (Server, error)
	// Delete the Server from the store
	Delete(context.Context, Server) error
	// Get retrieves Server if `ID` exists
	Get(ctx context.Context, ID int) (Server, error)
	// Update the Server in the store.
	Update(context.Context, Server) error
}

// ID creates uniq ID string
type ID interface {
	// Generate creates a unique ID string
	Generate() (string, error)
}

// UserID is a unique ID for a source user.
type UserID int

// User represents an authenticated user.
type User struct {
	ID   UserID
	Name string
}

// AuthStore is the Storage and retrieval of authentication information
type AuthStore struct {
	// User management for the AuthStore
	Users interface {
		// Create a new User in the AuthStore
		Add(context.Context, User) error
		// Delete the User from the AuthStore
		Delete(context.Context, User) error
		// Retrieve a user if `ID` exists.
		Get(ctx context.Context, ID int) error
		// Update the user's permissions or roles
		Update(context.Context, User) error
	}
}

// ExplorationID is a unique ID for an Exploration.
type ExplorationID int

// Exploration is a serialization of front-end Data Explorer.
type Exploration struct {
	ID        ExplorationID
	Name      string    // User provided name of the Exploration.
	UserID    UserID    // UserID is the owner of this Exploration.
	Data      string    // Opaque blob of JSON data.
	CreatedAt time.Time // Time the exploration was first created.
	UpdatedAt time.Time // Latest time the exploration was updated.
	Default   bool      // Flags an exploration as the default.
}

// ExplorationStore stores front-end serializations of data explorer sessions.
type ExplorationStore interface {
	// Search the ExplorationStore for each Exploration owned by `UserID`.
	Query(ctx context.Context, userID UserID) ([]*Exploration, error)
	// Create a new Exploration in the ExplorationStore.
	Add(context.Context, *Exploration) (*Exploration, error)
	// Delete the Exploration from the ExplorationStore.
	Delete(context.Context, *Exploration) error
	// Retrieve an Exploration if `ID` exists.
	Get(ctx context.Context, ID ExplorationID) (*Exploration, error)
	// Update the Exploration; will also update the `UpdatedAt` time.
	Update(context.Context, *Exploration) error
}

// Cell is a rectangle and multiple time series queries to visualize.
type Cell struct {
	X       int32   `json:"x"`
	Y       int32   `json:"y"`
	W       int32   `json:"w"`
	H       int32   `json:"h"`
	I       string  `json:"i"`
	Name    string  `json:"name"`
	Queries []Query `json:"queries"`
}

// Layout is a collection of Cells for visualization
type Layout struct {
	ID          string `json:"id,string"`
	Application string `json:"app"`
	Measurement string `json:"measurement"`
	Cells       []Cell `json:"cells"`
}

// LayoutStore stores dashboards and associated Cells
type LayoutStore interface {
	// All returns all dashboards in the store
	All(context.Context) ([]Layout, error)
	// Add creates a new dashboard in the LayoutStore
	Add(context.Context, Layout) (Layout, error)
	// Delete the dashboard from the store
	Delete(context.Context, Layout) error
	// Get retrieves Layout if `ID` exists
	Get(ctx context.Context, ID string) (Layout, error)
	// Update the dashboard in the store.
	Update(context.Context, Layout) error
}

// Principal is any entity that can be authenticated
type Principal string

// PrincipalKey is used to pass principal
// via context.Context to request-scoped
// functions.
const PrincipalKey Principal = "principal"

// Authenticator represents a service for authenticating users.
type Authenticator interface {
	// Authenticate returns User associated with token if successful.
	Authenticate(ctx context.Context, token string) (Principal, error)
	// Token generates a valid token for Principal lasting a duration
	Token(context.Context, Principal, time.Duration) (string, error)
}

// TokenExtractor extracts tokens from http requests
type TokenExtractor interface {
	// Extract will return the token or an error.
	Extract(r *http.Request) (string, error)
}
<commit_msg>Remove erroneous ,string struct tag<commit_after>package chronograf

import (
	"context"
	"net/http"
	"time"
)

// General errors.
const (
	ErrUpstreamTimeout     = Error("request to backend timed out")
	ErrExplorationNotFound = Error("exploration not found")
	ErrSourceNotFound      = Error("source not found")
	ErrServerNotFound      = Error("server not found")
	ErrLayoutNotFound      = Error("layout not found")
	ErrAuthentication      = Error("user not authenticated")
)

// Error is a domain error encountered while processing chronograf requests
type Error string

func (e Error) Error() string {
	return string(e)
}

// Logger represents an abstracted structured logging implementation. It
// provides methods to trigger log messages at various alert levels and a
// WithField method to set keys for a structured log message.
type Logger interface {
	Info(...interface{})
	Warn(...interface{})
	Debug(...interface{})
	Panic(...interface{})
	Error(...interface{})

	WithField(string, interface{}) Logger
}

// Assets returns a handler to serve the website.
type Assets interface {
	Handler() http.Handler
}

// TimeSeries represents a queryable time series database.
type TimeSeries interface {
	// Query retrieves time series data from the database.
	Query(context.Context, Query) (Response, error)
	// Connect will connect to the time series using the information in `Source`.
	Connect(context.Context, *Source) error
}

// Query retrieves a Response from a TimeSeries.
type Query struct {
	Command string `json:"query"` // Command is the query itself
	DB      string `json:"db"`    // DB is optional and if empty will not be used.
	RP      string `json:"rp"`    // RP is a retention policy and optional; if empty will not be used.
}

// Response is the result of a query against a TimeSeries
type Response interface {
	MarshalJSON() ([]byte, error)
}

// Source is connection information to a time-series data store.
type Source struct {
	ID       int    `json:"id,omitempty,string"` // ID is the unique ID of the source
	Name     string `json:"name"`                // Name is the user-defined name for the source
	Type     string `json:"type,omitempty"`      // Type specifies which kinds of source (enterprise vs oss)
	Username string `json:"username,omitempty"`  // Username is the username to connect to the source
	Password string `json:"password,omitempty"`  // Password is in CLEARTEXT FIXME
	URL      string `json:"url"`                 // URL are the connections to the source
	Default  bool   `json:"default"`             // Default specifies the default source for the application
}

// SourcesStore stores connection information for a `TimeSeries`
type SourcesStore interface {
	// All returns all sources in the store
	All(context.Context) ([]Source, error)
	// Add creates a new source in the SourcesStore and returns Source with ID
	Add(context.Context, Source) (Source, error)
	// Delete the Source from the store
	Delete(context.Context, Source) error
	// Get retrieves Source if `ID` exists
	Get(ctx context.Context, ID int) (Source, error)
	// Update the Source in the store.
	Update(context.Context, Source) error
}

// Server represents a proxy connection to an HTTP server
type Server struct {
	ID       int    // ID is the unique ID of the server
	SrcID    int    // SrcID of the data source
	Name     string // Name is the user-defined name for the server
	Username string // Username is the username to connect to the server
	Password string // Password is in CLEARTEXT FIXME
	URL      string // URL are the connections to the server
}

// ServersStore stores connection information for a `Server`
type ServersStore interface {
	// All returns all servers in the store
	All(context.Context) ([]Server, error)
	// Add creates a new source in the ServersStore and returns Server with ID
	Add(context.Context, Server) (Server, error)
	// Delete the Server from the store
	Delete(context.Context, Server) error
	// Get retrieves Server if `ID` exists
	Get(ctx context.Context, ID int) (Server, error)
	// Update the Server in the store.
	Update(context.Context, Server) error
}

// ID creates uniq ID string
type ID interface {
	// Generate creates a unique ID string
	Generate() (string, error)
}

// UserID is a unique ID for a source user.
type UserID int

// User represents an authenticated user.
type User struct {
	ID   UserID
	Name string
}

// AuthStore is the Storage and retrieval of authentication information
type AuthStore struct {
	// User management for the AuthStore
	Users interface {
		// Create a new User in the AuthStore
		Add(context.Context, User) error
		// Delete the User from the AuthStore
		Delete(context.Context, User) error
		// Retrieve a user if `ID` exists.
		Get(ctx context.Context, ID int) error
		// Update the user's permissions or roles
		Update(context.Context, User) error
	}
}

// ExplorationID is a unique ID for an Exploration.
type ExplorationID int

// Exploration is a serialization of front-end Data Explorer.
type Exploration struct {
	ID        ExplorationID
	Name      string    // User provided name of the Exploration.
	UserID    UserID    // UserID is the owner of this Exploration.
	Data      string    // Opaque blob of JSON data.
	CreatedAt time.Time // Time the exploration was first created.
	UpdatedAt time.Time // Latest time the exploration was updated.
	Default   bool      // Flags an exploration as the default.
}

// ExplorationStore stores front-end serializations of data explorer sessions.
type ExplorationStore interface {
	// Search the ExplorationStore for each Exploration owned by `UserID`.
	Query(ctx context.Context, userID UserID) ([]*Exploration, error)
	// Create a new Exploration in the ExplorationStore.
	Add(context.Context, *Exploration) (*Exploration, error)
	// Delete the Exploration from the ExplorationStore.
	Delete(context.Context, *Exploration) error
	// Retrieve an Exploration if `ID` exists.
	Get(ctx context.Context, ID ExplorationID) (*Exploration, error)
	// Update the Exploration; will also update the `UpdatedAt` time.
	Update(context.Context, *Exploration) error
}

// Cell is a rectangle and multiple time series queries to visualize.
type Cell struct {
	X       int32   `json:"x"`
	Y       int32   `json:"y"`
	W       int32   `json:"w"`
	H       int32   `json:"h"`
	I       string  `json:"i"`
	Name    string  `json:"name"`
	Queries []Query `json:"queries"`
}

// Layout is a collection of Cells for visualization
type Layout struct {
	ID          string `json:"id"`
	Application string `json:"app"`
	Measurement string `json:"measurement"`
	Cells       []Cell `json:"cells"`
}

// LayoutStore stores dashboards and associated Cells
type LayoutStore interface {
	// All returns all dashboards in the store
	All(context.Context) ([]Layout, error)
	// Add creates a new dashboard in the LayoutStore
	Add(context.Context, Layout) (Layout, error)
	// Delete the dashboard from the store
	Delete(context.Context, Layout) error
	// Get retrieves Layout if `ID` exists
	Get(ctx context.Context, ID string) (Layout, error)
	// Update the dashboard in the store.
	Update(context.Context, Layout) error
}

// Principal is any entity that can be authenticated
type Principal string

// PrincipalKey is used to pass principal
// via context.Context to request-scoped
// functions.
const PrincipalKey Principal = "principal"

// Authenticator represents a service for authenticating users.
type Authenticator interface {
	// Authenticate returns User associated with token if successful.
	Authenticate(ctx context.Context, token string) (Principal, error)
	// Token generates a valid token for Principal lasting a duration
	Token(context.Context, Principal, time.Duration) (string, error)
}

// TokenExtractor extracts tokens from http requests
type TokenExtractor interface {
	// Extract will return the token or an error.
	Extract(r *http.Request) (string, error)
}
<|endoftext|>