<commit_before>package library

import (
	"fmt"
	"os"
	"path"
	"syscall"
	"time"

	"github.com/facette/facette/pkg/logger"
	"github.com/facette/facette/pkg/utils"
	"github.com/fatih/set"
	uuid "github.com/nu7hatch/gouuid"
)

// Item represents the base structure of a library item.
type Item struct {
	path        string
	ID          string    `json:"id"`
	Name        string    `json:"name"`
	Description string    `json:"description"`
	Modified    time.Time `json:"-"`
}

func (item *Item) String() string {
	return fmt.Sprintf(
		"Item{path:%q ID:%q Name:%q Description:%q Modified:%s}",
		item.path,
		item.ID,
		item.Name,
		item.Description,
		item.Modified,
	)
}

// GetItem returns the base structure of a library item.
func (item *Item) GetItem() *Item {
	return item
}

// DeleteItem removes an existing item from the library.
func (library *Library) DeleteItem(id string, itemType int) error {
	if !library.ItemExists(id, itemType) {
		return os.ErrNotExist
	}

	if itemType == LibraryItemCollection {
		// Remove reference from parent
		item, _ := library.GetItem(id, itemType)
		parent := item.(*Collection).Parent

		i := parent.IndexOfChild(id)
		parent.Children = append(parent.Children[:i], parent.Children[i+1:]...)

		// Delete sub-collections
		for _, child := range library.Collections[id].Children {
			library.DeleteItem(child.ID, LibraryItemCollection)
		}
	}

	// Remove stored JSON
	if err := syscall.Unlink(library.getFilePath(id, itemType)); err != nil {
		return err
	}

	// Delete item from library
	switch itemType {
	case LibraryItemSourceGroup, LibraryItemMetricGroup:
		delete(library.Groups, id)

	case LibraryItemScale:
		delete(library.Scales, id)

	case LibraryItemUnit:
		delete(library.Units, id)

	case LibraryItemGraph:
		delete(library.Graphs, id)

	case LibraryItemCollection:
		delete(library.Collections, id)
	}

	return nil
}

// GetItem gets an item from the library by its identifier.
func (library *Library) GetItem(id string, itemType int) (interface{}, error) {
	if !library.ItemExists(id, itemType) {
		return nil, os.ErrNotExist
	}

	switch itemType {
	case LibraryItemSourceGroup, LibraryItemMetricGroup:
		return library.Groups[id], nil

	case LibraryItemScale:
		return library.Scales[id], nil

	case LibraryItemUnit:
		return library.Units[id], nil

	case LibraryItemGraph:
		return library.Graphs[id], nil

	case LibraryItemCollection:
		return library.Collections[id], nil
	}

	return nil, fmt.Errorf("no item found")
}

// GetItemByName gets an item from the library by its name.
func (library *Library) GetItemByName(name string, itemType int) (interface{}, error) {
	switch itemType {
	case LibraryItemSourceGroup, LibraryItemMetricGroup:
		for _, item := range library.Groups {
			if item.Type != itemType || item.Name != name {
				continue
			}

			return item, nil
		}

	case LibraryItemScale:
		for _, item := range library.Scales {
			if item.Name != name {
				continue
			}

			return item, nil
		}

	case LibraryItemUnit:
		for _, item := range library.Units {
			if item.Name != name {
				continue
			}

			return item, nil
		}

	case LibraryItemGraph:
		for _, item := range library.Graphs {
			if item.Name != name {
				continue
			}

			return item, nil
		}

	case LibraryItemCollection:
		for _, item := range library.Collections {
			if item.Name != name {
				continue
			}

			return item, nil
		}
	}

	return nil, os.ErrNotExist
}

// ItemExists returns whether an item exists the library or not.
func (library *Library) ItemExists(id string, itemType int) bool {
	exists := false

	switch itemType {
	case LibraryItemSourceGroup, LibraryItemMetricGroup:
		if _, ok := library.Groups[id]; ok && library.Groups[id].Type == itemType {
			exists = true
		}

	case LibraryItemScale:
		_, exists = library.Scales[id]

	case LibraryItemUnit:
		_, exists = library.Units[id]

	case LibraryItemGraph:
		_, exists = library.Graphs[id]

	case LibraryItemCollection:
		_, exists = library.Collections[id]
	}

	return exists
}

// LoadItem loads an item by its identifier.
func (library *Library) LoadItem(id string, itemType int) error {
	// Load item from file
	switch itemType {
	case LibraryItemSourceGroup, LibraryItemMetricGroup:
		tmpGroup := &Group{}

		filePath := library.getFilePath(id, itemType)

		fileInfo, err := utils.JSONLoad(filePath, &tmpGroup)
		if err != nil {
			return fmt.Errorf("in %s, %s", filePath, err)
		}

		library.Groups[id] = tmpGroup
		library.Groups[id].Type = itemType
		library.Groups[id].Modified = fileInfo.ModTime()

	case LibraryItemScale:
		tmpScale := &Scale{}

		filePath := library.getFilePath(id, itemType)

		fileInfo, err := utils.JSONLoad(filePath, &tmpScale)
		if err != nil {
			return fmt.Errorf("in %s, %s", filePath, err)
		}

		library.Scales[id] = tmpScale
		library.Scales[id].Modified = fileInfo.ModTime()

	case LibraryItemUnit:
		tmpUnit := &Unit{}

		filePath := library.getFilePath(id, itemType)

		fileInfo, err := utils.JSONLoad(filePath, &tmpUnit)
		if err != nil {
			return fmt.Errorf("in %s, %s", filePath, err)
		}

		library.Units[id] = tmpUnit
		library.Units[id].Modified = fileInfo.ModTime()

	case LibraryItemGraph:
		tmpGraph := &Graph{}

		filePath := library.getFilePath(id, itemType)

		fileInfo, err := utils.JSONLoad(filePath, &tmpGraph)
		if err != nil {
			return fmt.Errorf("in %s, %s", filePath, err)
		}

		library.Graphs[id] = tmpGraph
		library.Graphs[id].Modified = fileInfo.ModTime()

	case LibraryItemCollection:
		var tmpCollection *struct {
			*Collection
			Parent string `json:"parent"`
		}

		filePath := library.getFilePath(id, LibraryItemCollection)

		fileInfo, err := utils.JSONLoad(filePath, &tmpCollection)
		if err != nil {
			return fmt.Errorf("in %s, %s", filePath, err)
		}

		if !library.ItemExists(id, LibraryItemCollection) {
			library.Collections[id] = &Collection{}
		}

		*library.Collections[id] = *tmpCollection.Collection

		if tmpCollection.Parent != "" {
			library.Collections[id].ParentID = tmpCollection.Parent
		}

		library.Collections[id].Modified = fileInfo.ModTime()
	}

	return nil
}

// StoreItem stores an item into the library.
func (library *Library) StoreItem(item interface{}, itemType int) error {
	var itemStruct *Item

	switch itemType {
	case LibraryItemSourceGroup, LibraryItemMetricGroup:
		itemStruct = item.(*Group).GetItem()

	case LibraryItemScale:
		itemStruct = item.(*Scale).GetItem()

	case LibraryItemUnit:
		itemStruct = item.(*Unit).GetItem()

	case LibraryItemGraph:
		itemStruct = item.(*Graph).GetItem()

	case LibraryItemCollection:
		itemStruct = item.(*Collection).GetItem()

	default:
		return os.ErrInvalid
	}

	// If item has no ID specified, generate one
	if itemStruct.ID == "" {
		uuidTemp, err := uuid.NewV4()
		if err != nil {
			return err
		}

		itemStruct.ID = uuidTemp.String()
	} else if !library.ItemExists(itemStruct.ID, itemType) {
		return os.ErrNotExist
	}

	// Check for name field presence/duplicates
	if itemStruct.Name == "" {
		logger.Log(logger.LevelError, "library", "item missing `name' field")
		return os.ErrInvalid
	}

	itemTemp, err := library.GetItemByName(itemStruct.Name, itemType)

	// Item exists, check for duplicates
	if err == nil {
		switch itemType {
		case LibraryItemSourceGroup, LibraryItemMetricGroup:
			if itemTemp.(*Group).ID != itemStruct.ID {
				logger.Log(logger.LevelError, "library", "duplicate group identifier `%s'", itemStruct.ID)
				return os.ErrExist
			}

		case LibraryItemScale:
			if itemTemp.(*Scale).ID != itemStruct.ID {
				logger.Log(logger.LevelError, "library", "duplicate scale identifier `%s'", itemStruct.ID)
				return os.ErrExist
			}

		case LibraryItemUnit:
			if itemTemp.(*Unit).ID != itemStruct.ID {
				logger.Log(logger.LevelError, "library", "duplicate unit identifier `%s'", itemStruct.ID)
				return os.ErrExist
			}

		case LibraryItemGraph:
			if itemTemp.(*Graph).ID != itemStruct.ID {
				logger.Log(logger.LevelError, "library", "duplicate graph identifier `%s'", itemStruct.ID)
				return os.ErrExist
			}

		case LibraryItemCollection:
			if itemTemp.(*Collection).ID != itemStruct.ID {
				logger.Log(logger.LevelError, "library", "duplicate collection identifier `%s'", itemStruct.ID)
				return os.ErrExist
			}
		}
	}

	// Item does not exist, store it into library
	switch itemType {
	case LibraryItemSourceGroup, LibraryItemMetricGroup:
		library.Groups[itemStruct.ID] = item.(*Group)
		library.Groups[itemStruct.ID].ID = itemStruct.ID

	case LibraryItemScale:
		library.Scales[itemStruct.ID] = item.(*Scale)
		library.Scales[itemStruct.ID].ID = itemStruct.ID

	case LibraryItemUnit:
		library.Units[itemStruct.ID] = item.(*Unit)
		library.Units[itemStruct.ID].ID = itemStruct.ID

	case LibraryItemGraph:
		// Check for definition names duplicates
		groupSet := set.New(set.ThreadSafe)
		seriesSet := set.New(set.ThreadSafe)

		for _, group := range item.(*Graph).Groups {
			if group == nil {
				logger.Log(logger.LevelError, "library", "found null group")
				return os.ErrInvalid
			} else if groupSet.Has(group.Name) {
				logger.Log(logger.LevelError, "library", "duplicate group name `%s'", group.Name)
				return os.ErrExist
			}

			groupSet.Add(group.Name)

			for _, series := range group.Series {
				if series == nil {
					logger.Log(logger.LevelError, "library", "found null series in group `%s'", group.Name)
					return os.ErrInvalid
				} else if seriesSet.Has(series.Name) {
					logger.Log(logger.LevelError, "library", "duplicate series name `%s'", series.Name)
					return os.ErrExist
				}

				seriesSet.Add(series.Name)
			}
		}

		library.Graphs[itemStruct.ID] = item.(*Graph)
		library.Graphs[itemStruct.ID].ID = itemStruct.ID

	case LibraryItemCollection:
		library.Collections[itemStruct.ID] = item.(*Collection)
		library.Collections[itemStruct.ID].ID = itemStruct.ID
	}

	itemStruct.Modified = time.Now()

	// Store JSON data
	if err := utils.JSONDump(library.getFilePath(itemStruct.ID, itemType), item, itemStruct.Modified); err != nil {
		return err
	}

	return nil
}

func (library *Library) getDirPath(itemType int) string {
	var dirName string

	switch itemType {
	case LibraryItemSourceGroup:
		dirName = "sourcegroups"

	case LibraryItemMetricGroup:
		dirName = "metricgroups"

	case LibraryItemScale:
		dirName = "scales"

	case LibraryItemUnit:
		dirName = "units"

	case LibraryItemGraph:
		dirName = "graphs"

	case LibraryItemCollection:
		dirName = "collections"
	}

	return path.Join(library.Config.DataDir, dirName)
}

func (library *Library) getFilePath(id string, itemType int) string {
	return path.Join(library.getDirPath(itemType), id[0:2], id[2:4], id+".json")
}
<commit_msg>Add check for nil parents in collection deletion<commit_after>package library

import (
	"fmt"
	"os"
	"path"
	"syscall"
	"time"

	"github.com/facette/facette/pkg/logger"
	"github.com/facette/facette/pkg/utils"
	"github.com/fatih/set"
	uuid "github.com/nu7hatch/gouuid"
)

// Item represents the base structure of a library item.
type Item struct {
	path        string
	ID          string    `json:"id"`
	Name        string    `json:"name"`
	Description string    `json:"description"`
	Modified    time.Time `json:"-"`
}

func (item *Item) String() string {
	return fmt.Sprintf(
		"Item{path:%q ID:%q Name:%q Description:%q Modified:%s}",
		item.path,
		item.ID,
		item.Name,
		item.Description,
		item.Modified,
	)
}

// GetItem returns the base structure of a library item.
func (item *Item) GetItem() *Item {
	return item
}

// DeleteItem removes an existing item from the library.
func (library *Library) DeleteItem(id string, itemType int) error {
	if !library.ItemExists(id, itemType) {
		return os.ErrNotExist
	}

	if itemType == LibraryItemCollection {
		// Remove reference from parent
		item, _ := library.GetItem(id, itemType)
		parent := item.(*Collection).Parent

		if parent != nil {
			i := parent.IndexOfChild(id)
			parent.Children = append(parent.Children[:i], parent.Children[i+1:]...)
		}

		// Delete sub-collections
		for _, child := range library.Collections[id].Children {
			library.DeleteItem(child.ID, LibraryItemCollection)
		}
	}

	// Remove stored JSON
	if err := syscall.Unlink(library.getFilePath(id, itemType)); err != nil {
		return err
	}

	// Delete item from library
	switch itemType {
	case LibraryItemSourceGroup, LibraryItemMetricGroup:
		delete(library.Groups, id)

	case LibraryItemScale:
		delete(library.Scales, id)

	case LibraryItemUnit:
		delete(library.Units, id)

	case LibraryItemGraph:
		delete(library.Graphs, id)

	case LibraryItemCollection:
		delete(library.Collections, id)
	}

	return nil
}

// GetItem gets an item from the library by its identifier.
func (library *Library) GetItem(id string, itemType int) (interface{}, error) {
	if !library.ItemExists(id, itemType) {
		return nil, os.ErrNotExist
	}

	switch itemType {
	case LibraryItemSourceGroup, LibraryItemMetricGroup:
		return library.Groups[id], nil

	case LibraryItemScale:
		return library.Scales[id], nil

	case LibraryItemUnit:
		return library.Units[id], nil

	case LibraryItemGraph:
		return library.Graphs[id], nil

	case LibraryItemCollection:
		return library.Collections[id], nil
	}

	return nil, fmt.Errorf("no item found")
}

// GetItemByName gets an item from the library by its name.
func (library *Library) GetItemByName(name string, itemType int) (interface{}, error) {
	switch itemType {
	case LibraryItemSourceGroup, LibraryItemMetricGroup:
		for _, item := range library.Groups {
			if item.Type != itemType || item.Name != name {
				continue
			}

			return item, nil
		}

	case LibraryItemScale:
		for _, item := range library.Scales {
			if item.Name != name {
				continue
			}

			return item, nil
		}

	case LibraryItemUnit:
		for _, item := range library.Units {
			if item.Name != name {
				continue
			}

			return item, nil
		}

	case LibraryItemGraph:
		for _, item := range library.Graphs {
			if item.Name != name {
				continue
			}

			return item, nil
		}

	case LibraryItemCollection:
		for _, item := range library.Collections {
			if item.Name != name {
				continue
			}

			return item, nil
		}
	}

	return nil, os.ErrNotExist
}

// ItemExists returns whether an item exists the library or not.
func (library *Library) ItemExists(id string, itemType int) bool {
	exists := false

	switch itemType {
	case LibraryItemSourceGroup, LibraryItemMetricGroup:
		if _, ok := library.Groups[id]; ok && library.Groups[id].Type == itemType {
			exists = true
		}

	case LibraryItemScale:
		_, exists = library.Scales[id]

	case LibraryItemUnit:
		_, exists = library.Units[id]

	case LibraryItemGraph:
		_, exists = library.Graphs[id]

	case LibraryItemCollection:
		_, exists = library.Collections[id]
	}

	return exists
}

// LoadItem loads an item by its identifier.
func (library *Library) LoadItem(id string, itemType int) error {
	// Load item from file
	switch itemType {
	case LibraryItemSourceGroup, LibraryItemMetricGroup:
		tmpGroup := &Group{}

		filePath := library.getFilePath(id, itemType)

		fileInfo, err := utils.JSONLoad(filePath, &tmpGroup)
		if err != nil {
			return fmt.Errorf("in %s, %s", filePath, err)
		}

		library.Groups[id] = tmpGroup
		library.Groups[id].Type = itemType
		library.Groups[id].Modified = fileInfo.ModTime()

	case LibraryItemScale:
		tmpScale := &Scale{}

		filePath := library.getFilePath(id, itemType)

		fileInfo, err := utils.JSONLoad(filePath, &tmpScale)
		if err != nil {
			return fmt.Errorf("in %s, %s", filePath, err)
		}

		library.Scales[id] = tmpScale
		library.Scales[id].Modified = fileInfo.ModTime()

	case LibraryItemUnit:
		tmpUnit := &Unit{}

		filePath := library.getFilePath(id, itemType)

		fileInfo, err := utils.JSONLoad(filePath, &tmpUnit)
		if err != nil {
			return fmt.Errorf("in %s, %s", filePath, err)
		}

		library.Units[id] = tmpUnit
		library.Units[id].Modified = fileInfo.ModTime()

	case LibraryItemGraph:
		tmpGraph := &Graph{}

		filePath := library.getFilePath(id, itemType)

		fileInfo, err := utils.JSONLoad(filePath, &tmpGraph)
		if err != nil {
			return fmt.Errorf("in %s, %s", filePath, err)
		}

		library.Graphs[id] = tmpGraph
		library.Graphs[id].Modified = fileInfo.ModTime()

	case LibraryItemCollection:
		var tmpCollection *struct {
			*Collection
			Parent string `json:"parent"`
		}

		filePath := library.getFilePath(id, LibraryItemCollection)

		fileInfo, err := utils.JSONLoad(filePath, &tmpCollection)
		if err != nil {
			return fmt.Errorf("in %s, %s", filePath, err)
		}

		if !library.ItemExists(id, LibraryItemCollection) {
			library.Collections[id] = &Collection{}
		}

		*library.Collections[id] = *tmpCollection.Collection

		if tmpCollection.Parent != "" {
			library.Collections[id].ParentID = tmpCollection.Parent
		}

		library.Collections[id].Modified = fileInfo.ModTime()
	}

	return nil
}

// StoreItem stores an item into the library.
func (library *Library) StoreItem(item interface{}, itemType int) error {
	var itemStruct *Item

	switch itemType {
	case LibraryItemSourceGroup, LibraryItemMetricGroup:
		itemStruct = item.(*Group).GetItem()

	case LibraryItemScale:
		itemStruct = item.(*Scale).GetItem()

	case LibraryItemUnit:
		itemStruct = item.(*Unit).GetItem()

	case LibraryItemGraph:
		itemStruct = item.(*Graph).GetItem()

	case LibraryItemCollection:
		itemStruct = item.(*Collection).GetItem()

	default:
		return os.ErrInvalid
	}

	// If item has no ID specified, generate one
	if itemStruct.ID == "" {
		uuidTemp, err := uuid.NewV4()
		if err != nil {
			return err
		}

		itemStruct.ID = uuidTemp.String()
	} else if !library.ItemExists(itemStruct.ID, itemType) {
		return os.ErrNotExist
	}

	// Check for name field presence/duplicates
	if itemStruct.Name == "" {
		logger.Log(logger.LevelError, "library", "item missing `name' field")
		return os.ErrInvalid
	}

	itemTemp, err := library.GetItemByName(itemStruct.Name, itemType)

	// Item exists, check for duplicates
	if err == nil {
		switch itemType {
		case LibraryItemSourceGroup, LibraryItemMetricGroup:
			if itemTemp.(*Group).ID != itemStruct.ID {
				logger.Log(logger.LevelError, "library", "duplicate group identifier `%s'", itemStruct.ID)
				return os.ErrExist
			}

		case LibraryItemScale:
			if itemTemp.(*Scale).ID != itemStruct.ID {
				logger.Log(logger.LevelError, "library", "duplicate scale identifier `%s'", itemStruct.ID)
				return os.ErrExist
			}

		case LibraryItemUnit:
			if itemTemp.(*Unit).ID != itemStruct.ID {
				logger.Log(logger.LevelError, "library", "duplicate unit identifier `%s'", itemStruct.ID)
				return os.ErrExist
			}

		case LibraryItemGraph:
			if itemTemp.(*Graph).ID != itemStruct.ID {
				logger.Log(logger.LevelError, "library", "duplicate graph identifier `%s'", itemStruct.ID)
				return os.ErrExist
			}

		case LibraryItemCollection:
			if itemTemp.(*Collection).ID != itemStruct.ID {
				logger.Log(logger.LevelError, "library", "duplicate collection identifier `%s'", itemStruct.ID)
				return os.ErrExist
			}
		}
	}

	// Item does not exist, store it into library
	switch itemType {
	case LibraryItemSourceGroup, LibraryItemMetricGroup:
		library.Groups[itemStruct.ID] = item.(*Group)
		library.Groups[itemStruct.ID].ID = itemStruct.ID

	case LibraryItemScale:
		library.Scales[itemStruct.ID] = item.(*Scale)
		library.Scales[itemStruct.ID].ID = itemStruct.ID

	case LibraryItemUnit:
		library.Units[itemStruct.ID] = item.(*Unit)
		library.Units[itemStruct.ID].ID = itemStruct.ID

	case LibraryItemGraph:
		// Check for definition names duplicates
		groupSet := set.New(set.ThreadSafe)
		seriesSet := set.New(set.ThreadSafe)

		for _, group := range item.(*Graph).Groups {
			if group == nil {
				logger.Log(logger.LevelError, "library", "found null group")
				return os.ErrInvalid
			} else if groupSet.Has(group.Name) {
				logger.Log(logger.LevelError, "library", "duplicate group name `%s'", group.Name)
				return os.ErrExist
			}

			groupSet.Add(group.Name)

			for _, series := range group.Series {
				if series == nil {
					logger.Log(logger.LevelError, "library", "found null series in group `%s'", group.Name)
					return os.ErrInvalid
				} else if seriesSet.Has(series.Name) {
					logger.Log(logger.LevelError, "library", "duplicate series name `%s'", series.Name)
					return os.ErrExist
				}

				seriesSet.Add(series.Name)
			}
		}

		library.Graphs[itemStruct.ID] = item.(*Graph)
		library.Graphs[itemStruct.ID].ID = itemStruct.ID

	case LibraryItemCollection:
		library.Collections[itemStruct.ID] = item.(*Collection)
		library.Collections[itemStruct.ID].ID = itemStruct.ID
	}

	itemStruct.Modified = time.Now()

	// Store JSON data
	if err := utils.JSONDump(library.getFilePath(itemStruct.ID, itemType), item, itemStruct.Modified); err != nil {
		return err
	}

	return nil
}

func (library *Library) getDirPath(itemType int) string {
	var dirName string

	switch itemType {
	case LibraryItemSourceGroup:
		dirName = "sourcegroups"

	case LibraryItemMetricGroup:
		dirName = "metricgroups"

	case LibraryItemScale:
		dirName = "scales"

	case LibraryItemUnit:
		dirName = "units"

	case LibraryItemGraph:
		dirName = "graphs"

	case LibraryItemCollection:
		dirName = "collections"
	}

	return path.Join(library.Config.DataDir, dirName)
}

func (library *Library) getFilePath(id string, itemType int) string {
	return path.Join(library.getDirPath(itemType), id[0:2], id[2:4], id+".json")
}
<|endoftext|>


<commit_before>package snap

import (
	"github.com/intelsdi-x/snap/mgmt/rest/client"
	"github.com/intelsdi-x/snap/scheduler/wmap"
	"strings"
  "time"
  "fmt"
)

var (
	pClient *client.Client
)

// Session provides construct for tagging metrics for a specified time span:
// defined by Start() and Stop().
type Session struct {
	// Name is the prefix of the session name
	Name string

	// ID is a unique identifier for the session. Regenerated when Start() is called.
	ID string

	// Interval defines the sample interval for the listed metrics
	Interval time.Duration

	// Metrics to tag in session
	Metrics []string

	// Tasks
	// Processors
	// Publishers

	// TODO(niklas): Store task id for Stop()
}

// Lazy connection to snap
func ensureConnected() error {
	if pClient == nil {
		// TODO(niklas): Make 'secure' connection default
		client, err := client.New("http://localhost:8181", "v1", true)

		if err != nil {
			return err
		}

		pClient = client
	}

	return nil
}

// NewSession generates a session with a name and a list of metrics to tag.
// The interval cannot be less than second granularity.
func NewSession(name string, metrics []string, interval time.Duration) (*Session, error) {
	err := ensureConnected()
	if err != nil {
		return nil, err
	}

	return &Session{
		Name:    name,
		Metrics: metrics,
    Interval: interval,
	}, nil
}

// ListSessions lists current available sessions based on task listing from snap.
func ListSessions() ([]string, error) {
	err := ensureConnected()
	if err != nil {
		return nil, err
	}

	out := []string{}

	tasks := pClient.GetTasks()
	if tasks.Err != nil {
		return out, tasks.Err
	}

	for _, task := range tasks.ScheduledTasks {
		out = append(out, strings.Join([]string{task.ID, task.Name}, "-"))
	}

	return out, nil
}

type task struct {
	Version  int
	Schedule *client.Schedule
	Workflow *wmap.WorkflowMap
	Name     string
	Deadline string
}

// Start an experiment session.
// TODO Return session id
func (s *Session) Start() error {
	err := ensureConnected()
	if err != nil {
		return err
	}

	// Check if plugins are loaded

  // Convert from duration to "Xs" string.
  secondString := fmt.Sprintf("%ds", int(s.Interval.Seconds()))

	t := task{
		Version: 1,
		Schedule: &client.Schedule{
			Type:     "simple",
			Interval: secondString,
		},
	}
	t.Name = s.Name

	wf := wmap.NewWorkflowMap()

	// TBD: Populate workflow

	t.Workflow = wf

	r := pClient.CreateTask(t.Schedule, t.Workflow, t.Name, t.Deadline, true)
	if r.Err != nil {
		return r.Err
	}

	return nil
}

// Stop an experiment session.
func (s *Session) Stop(UUID string) error {
	err := ensureConnected()
	if err != nil {
		return err
	}

	return nil
}
<commit_msg>gofmt of session.go<commit_after>package snap

import (
	"fmt"
	"github.com/intelsdi-x/snap/mgmt/rest/client"
	"github.com/intelsdi-x/snap/scheduler/wmap"
	"strings"
	"time"
)

var (
	pClient *client.Client
)

// Session provides construct for tagging metrics for a specified time span:
// defined by Start() and Stop().
type Session struct {
	// Name is the prefix of the session name
	Name string

	// ID is a unique identifier for the session. Regenerated when Start() is called.
	ID string

	// Interval defines the sample interval for the listed metrics
	Interval time.Duration

	// Metrics to tag in session
	Metrics []string

	// Tasks
	// Processors
	// Publishers

	// TODO(niklas): Store task id for Stop()
}

// Lazy connection to snap
func ensureConnected() error {
	if pClient == nil {
		// TODO(niklas): Make 'secure' connection default
		client, err := client.New("http://localhost:8181", "v1", true)

		if err != nil {
			return err
		}

		pClient = client
	}

	return nil
}

// NewSession generates a session with a name and a list of metrics to tag.
// The interval cannot be less than second granularity.
func NewSession(name string, metrics []string, interval time.Duration) (*Session, error) {
	err := ensureConnected()
	if err != nil {
		return nil, err
	}

	return &Session{
		Name:     name,
		Metrics:  metrics,
		Interval: interval,
	}, nil
}

// ListSessions lists current available sessions based on task listing from snap.
func ListSessions() ([]string, error) {
	err := ensureConnected()
	if err != nil {
		return nil, err
	}

	out := []string{}

	tasks := pClient.GetTasks()
	if tasks.Err != nil {
		return out, tasks.Err
	}

	for _, task := range tasks.ScheduledTasks {
		out = append(out, strings.Join([]string{task.ID, task.Name}, "-"))
	}

	return out, nil
}

type task struct {
	Version  int
	Schedule *client.Schedule
	Workflow *wmap.WorkflowMap
	Name     string
	Deadline string
}

// Start an experiment session.
// TODO Return session id
func (s *Session) Start() error {
	err := ensureConnected()
	if err != nil {
		return err
	}

	// Check if plugins are loaded

	// Convert from duration to "Xs" string.
	secondString := fmt.Sprintf("%ds", int(s.Interval.Seconds()))

	t := task{
		Version: 1,
		Schedule: &client.Schedule{
			Type:     "simple",
			Interval: secondString,
		},
	}
	t.Name = s.Name

	wf := wmap.NewWorkflowMap()

	// TBD: Populate workflow

	t.Workflow = wf

	r := pClient.CreateTask(t.Schedule, t.Workflow, t.Name, t.Deadline, true)
	if r.Err != nil {
		return r.Err
	}

	return nil
}

// Stop an experiment session.
func (s *Session) Stop(UUID string) error {
	err := ensureConnected()
	if err != nil {
		return err
	}

	return nil
}
<|endoftext|>


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

package step

import (
	"sqlflow.org/sqlflow/pkg/database"
	"sqlflow.org/sqlflow/pkg/pipe"
	pb "sqlflow.org/sqlflow/pkg/proto"
	"sqlflow.org/sqlflow/pkg/sql"
)

// Context records all resources that each "step" in the workflow may require
type Context struct {
	SQLProgram string
	Session    *pb.Session
	// Directory for save/load model, empty if we are going to save model to db
	ModelSaveDir string
	// Pipe for write back results when processing current request
	Wr *pipe.Writer
	Rd *pipe.Reader
	// Connection to the database for each request, will be closed when request is closed.
	Conn *database.DB
	// current working directory, will generate python code, save model, load model to this path
	// Cwd string
	// Will set to true if model/checkpoint is loaded into Cwd
	// IsModelLoaded bool
	// a proper submitter for current request, e.g. Tensorflow/PAI/Elasticdl
	Submitter sql.Submitter
}

// NewRequestContext construct a new RequestContext object.
func NewRequestContext(sqlProgram string, session *pb.Session, modelSaveDir string) (*Context, error) {
	submitter := sql.GetSubmitter(session.Submitter)
	conn, err := database.OpenAndConnectDB(session.GetDbConnStr())
	if err != nil {
		return nil, err
	}
	rd, wr := pipe.Pipe()
	req := &Context{
		SQLProgram:   sqlProgram,
		Session:      session,
		ModelSaveDir: modelSaveDir,
		Wr:           wr,
		Rd:           rd,
		Conn:         conn,
		Submitter:    submitter,
	}
	return req, nil
}

// Close release all resources of the current request.
func (req *Context) Close() error {
	// Wr should be closed when the writing is finished
	req.Rd.Close()
	if err := req.Conn.Close(); err != nil {
		return err
	}
	return nil
}
<commit_msg>remove not used context (#1948)<commit_after><|endoftext|>


<commit_before>package store

import (
	"crypto/tls"
	"net/http"
	"strings"
	"sync"
	"time"

	log "github.com/Sirupsen/logrus"
	api "github.com/hashicorp/consul/api"
)

const (
	// DefaultWatchWaitTime is how long we block for at a time to check if the
	// watched key has changed.  This affects the minimum time it takes to
	// cancel a watch.
	DefaultWatchWaitTime = 15 * time.Second
)

// Consul embeds the client and watches
type Consul struct {
	sync.Mutex
	config           *api.Config
	client           *api.Client
	ephemeralTTL     time.Duration
	ephemeralSession string
}

type consulLock struct {
	lock *api.Lock
}

// InitializeConsul creates a new Consul client given
// a list of endpoints and optional tls config
func InitializeConsul(endpoints []string, options *Config) (Store, error) {
	s := &Consul{}

	// Create Consul client
	config := api.DefaultConfig()
	s.config = config
	config.HttpClient = http.DefaultClient
	config.Address = endpoints[0]
	config.Scheme = "http"

	// Set options
	if options != nil {
		if options.TLS != nil {
			s.setTLS(options.TLS)
		}
		if options.ConnectionTimeout != 0 {
			s.setTimeout(options.ConnectionTimeout)
		}
		if options.EphemeralTTL != 0 {
			s.setEphemeralTTL(options.EphemeralTTL)
		}
	}

	// Creates a new client
	client, err := api.NewClient(config)
	if err != nil {
		log.Errorf("Couldn't initialize consul client..")
		return nil, err
	}
	s.client = client

	return s, nil
}

// SetTLS sets Consul TLS options
func (s *Consul) setTLS(tls *tls.Config) {
	s.config.HttpClient.Transport = &http.Transport{
		TLSClientConfig: tls,
	}
	s.config.Scheme = "https"
}

// SetTimeout sets the timout for connecting to Consul
func (s *Consul) setTimeout(time time.Duration) {
	s.config.WaitTime = time
}

// SetEphemeralTTL sets the ttl for ephemeral nodes
func (s *Consul) setEphemeralTTL(time time.Duration) {
	s.ephemeralTTL = time
}

// CreateEphemeralSession creates the a global session
// once that is used to delete keys at node failure
func (s *Consul) createEphemeralSession() error {
	s.Lock()
	defer s.Unlock()
	if s.ephemeralSession == "" {
		entry := &api.SessionEntry{
			Behavior: api.SessionBehaviorDelete,
			TTL:      s.ephemeralTTL.String(),
		}
		// Create global ephemeral keys session
		session, _, err := s.client.Session().Create(entry, nil)
		if err != nil {
			return err
		}
		s.ephemeralSession = session
	}
	return nil
}

// Normalize the key for usage in Consul
func (s *Consul) normalize(key string) string {
	key = normalize(key)
	return strings.TrimPrefix(key, "/")
}

// Get the value at "key", returns the last modified index
// to use in conjunction to CAS calls
func (s *Consul) Get(key string) (*KVPair, error) {
	pair, meta, err := s.client.KV().Get(s.normalize(key), nil)
	if err != nil {
		return nil, err
	}
	if pair == nil {
		return nil, ErrKeyNotFound
	}
	return &KVPair{pair.Key, pair.Value, meta.LastIndex}, nil
}

// Put a value at "key"
func (s *Consul) Put(key string, value []byte, opts *WriteOptions) error {

	key = s.normalize(key)

	p := &api.KVPair{
		Key:   key,
		Value: value,
	}

	if opts != nil && opts.Ephemeral {
		// Creates the global ephemeral session
		// if it does not exist
		if s.ephemeralSession == "" {
			s.createEphemeralSession()
		}

		// Create lock option with the
		// EphemeralSession
		lockOpts := &api.LockOptions{
			Key:     key,
			Session: s.ephemeralSession,
		}

		// Lock and ignore if lock is held
		// It's just a placeholder for the
		// ephemeral behavior
		lock, _ := s.client.LockOpts(lockOpts)
		if lock != nil {
			lock.Lock(nil)
		}

		// Place the session on key
		p.Session = s.ephemeralSession

		// Renew the session
		_, _, err := s.client.Session().Renew(p.Session, nil)
		if err != nil {
			s.ephemeralSession = ""
			return err
		}
	}

	_, err := s.client.KV().Put(p, nil)
	return err
}

// Delete a value at "key"
func (s *Consul) Delete(key string) error {
	_, err := s.client.KV().Delete(s.normalize(key), nil)
	return err
}

// Exists checks that the key exists inside the store
func (s *Consul) Exists(key string) (bool, error) {
	_, err := s.Get(key)
	if err != nil && err == ErrKeyNotFound {
		return false, err
	}
	return true, nil
}

// List the content of a given prefix
func (s *Consul) List(prefix string) ([]*KVPair, error) {
	pairs, _, err := s.client.KV().List(s.normalize(prefix), nil)
	if err != nil {
		return nil, err
	}
	if len(pairs) == 0 {
		return nil, ErrKeyNotFound
	}
	kv := []*KVPair{}
	for _, pair := range pairs {
		if pair.Key == prefix {
			continue
		}
		kv = append(kv, &KVPair{pair.Key, pair.Value, pair.ModifyIndex})
	}
	return kv, nil
}

// DeleteTree deletes a range of keys based on prefix
func (s *Consul) DeleteTree(prefix string) error {
	_, err := s.client.KV().DeleteTree(s.normalize(prefix), nil)
	return err
}

// Watch changes on a key.
// Returns a channel that will receive changes or an error.
// Upon creating a watch, the current value will be sent to the channel.
// Providing a non-nil stopCh can be used to stop watching.
func (s *Consul) Watch(key string, stopCh <-chan struct{}) (<-chan *KVPair, error) {
	key = s.normalize(key)
	kv := s.client.KV()
	watchCh := make(chan *KVPair)

	go func() {
		defer close(watchCh)

		// Use a wait time in order to check if we should quit from time to
		// time.
		opts := &api.QueryOptions{WaitTime: DefaultWatchWaitTime}
		for {
			// Check if we should quit
			select {
			case <-stopCh:
				return
			default:
			}
			pair, meta, err := kv.Get(key, opts)
			if err != nil {
				log.Errorf("consul: %v", err)
				return
			}
			// If LastIndex didn't change then it means `Get` returned because
			// of the WaitTime and the key didn't change.
			if opts.WaitIndex == meta.LastIndex {
				continue
			}
			opts.WaitIndex = meta.LastIndex
			// FIXME: What happens when a key is deleted?
			if pair != nil {
				watchCh <- &KVPair{pair.Key, pair.Value, pair.ModifyIndex}
			}
		}
	}()

	return watchCh, nil
}

// WatchTree watches changes on a "directory"
// Returns a channel that will receive changes or an error.
// Upon creating a watch, the current value will be sent to the channel.
// Providing a non-nil stopCh can be used to stop watching.
func (s *Consul) WatchTree(prefix string, stopCh <-chan struct{}) (<-chan []*KVPair, error) {
	prefix = s.normalize(prefix)
	kv := s.client.KV()
	watchCh := make(chan []*KVPair)

	go func() {
		defer close(watchCh)

		// Use a wait time in order to check if we should quit from time to
		// time.
		opts := &api.QueryOptions{WaitTime: DefaultWatchWaitTime}
		for {
			// Check if we should quit
			select {
			case <-stopCh:
				return
			default:
			}

			pairs, meta, err := kv.List(prefix, opts)
			if err != nil {
				log.Errorf("consul: %v", err)
				return
			}
			// If LastIndex didn't change then it means `Get` returned because
			// of the WaitTime and the key didn't change.
			if opts.WaitIndex == meta.LastIndex {
				continue
			}
			opts.WaitIndex = meta.LastIndex
			kv := []*KVPair{}
			for _, pair := range pairs {
				if pair.Key == prefix {
					continue
				}
				kv = append(kv, &KVPair{pair.Key, pair.Value, pair.ModifyIndex})
			}
			watchCh <- kv
		}
	}()

	return watchCh, nil
}

// NewLock returns a handle to a lock struct which can be used to acquire and
// release the mutex.
func (s *Consul) NewLock(key string, options *LockOptions) (Locker, error) {
	consulOpts := &api.LockOptions{
		Key: s.normalize(key),
	}
	if options != nil {
		consulOpts.Value = options.Value
	}
	l, err := s.client.LockOpts(consulOpts)
	if err != nil {
		return nil, err
	}
	return &consulLock{lock: l}, nil
}

// Lock attempts to acquire the lock and blocks while doing so.
// Returns a channel that is closed if our lock is lost or an error.
func (l *consulLock) Lock() (<-chan struct{}, error) {
	return l.lock.Lock(nil)
}

// Unlock released the lock. It is an error to call this
// if the lock is not currently held.
func (l *consulLock) Unlock() error {
	return l.lock.Unlock()
}

// AtomicPut put a value at "key" if the key has not been
// modified in the meantime, throws an error if this is the case
func (s *Consul) AtomicPut(key string, value []byte, previous *KVPair, options *WriteOptions) (bool, error) {
	p := &api.KVPair{Key: s.normalize(key), Value: value, ModifyIndex: previous.LastIndex}
	if work, _, err := s.client.KV().CAS(p, nil); err != nil {
		return false, err
	} else if !work {
		return false, ErrKeyModified
	}
	return true, nil
}

// AtomicDelete deletes a value at "key" if the key has not
// been modified in the meantime, throws an error if this is the case
func (s *Consul) AtomicDelete(key string, previous *KVPair) (bool, error) {
	p := &api.KVPair{Key: s.normalize(key), ModifyIndex: previous.LastIndex}
	if work, _, err := s.client.KV().DeleteCAS(p, nil); err != nil {
		return false, err
	} else if !work {
		return false, ErrKeyModified
	}
	return true, nil
}
<commit_msg>Additional check enforcing ttl >= 10s for Consul and ephemeral entries<commit_after>package store

import (
	"crypto/tls"
	"net/http"
	"strings"
	"sync"
	"time"

	log "github.com/Sirupsen/logrus"
	api "github.com/hashicorp/consul/api"
)

const (
	// DefaultWatchWaitTime is how long we block for at a time to check if the
	// watched key has changed.  This affects the minimum time it takes to
	// cancel a watch.
	DefaultWatchWaitTime = 15 * time.Second

	// MinimumTimeToLive is the minimum TTL value allowed by Consul for
	// Ephemeral entries
	MinimumTimeToLive = 10 * time.Second
)

// Consul embeds the client and watches
type Consul struct {
	sync.Mutex
	config           *api.Config
	client           *api.Client
	ephemeralTTL     time.Duration
	ephemeralSession string
}

type consulLock struct {
	lock *api.Lock
}

// InitializeConsul creates a new Consul client given
// a list of endpoints and optional tls config
func InitializeConsul(endpoints []string, options *Config) (Store, error) {
	s := &Consul{}

	// Create Consul client
	config := api.DefaultConfig()
	s.config = config
	config.HttpClient = http.DefaultClient
	config.Address = endpoints[0]
	config.Scheme = "http"

	// Set options
	if options != nil {
		if options.TLS != nil {
			s.setTLS(options.TLS)
		}
		if options.ConnectionTimeout != 0 {
			s.setTimeout(options.ConnectionTimeout)
		}
		if options.EphemeralTTL != 0 {
			s.setEphemeralTTL(options.EphemeralTTL)
		}
	}

	// Creates a new client
	client, err := api.NewClient(config)
	if err != nil {
		log.Errorf("Couldn't initialize consul client..")
		return nil, err
	}
	s.client = client

	return s, nil
}

// SetTLS sets Consul TLS options
func (s *Consul) setTLS(tls *tls.Config) {
	s.config.HttpClient.Transport = &http.Transport{
		TLSClientConfig: tls,
	}
	s.config.Scheme = "https"
}

// SetTimeout sets the timout for connecting to Consul
func (s *Consul) setTimeout(time time.Duration) {
	s.config.WaitTime = time
}

// SetEphemeralTTL sets the ttl for ephemeral nodes
func (s *Consul) setEphemeralTTL(ttl time.Duration) {
	if ttl < MinimumTimeToLive {
		log.Fatal("Consul does not allow a ttl < 10s, please specify a ttl >= 10s")
	}
	s.ephemeralTTL = ttl
}

// CreateEphemeralSession creates the a global session
// once that is used to delete keys at node failure
func (s *Consul) createEphemeralSession() error {
	s.Lock()
	defer s.Unlock()
	if s.ephemeralSession == "" {
		entry := &api.SessionEntry{
			Behavior: api.SessionBehaviorDelete,
			TTL:      s.ephemeralTTL.String(),
		}
		// Create global ephemeral keys session
		session, _, err := s.client.Session().Create(entry, nil)
		if err != nil {
			return err
		}
		s.ephemeralSession = session
	}
	return nil
}

// Normalize the key for usage in Consul
func (s *Consul) normalize(key string) string {
	key = normalize(key)
	return strings.TrimPrefix(key, "/")
}

// Get the value at "key", returns the last modified index
// to use in conjunction to CAS calls
func (s *Consul) Get(key string) (*KVPair, error) {
	pair, meta, err := s.client.KV().Get(s.normalize(key), nil)
	if err != nil {
		return nil, err
	}
	if pair == nil {
		return nil, ErrKeyNotFound
	}
	return &KVPair{pair.Key, pair.Value, meta.LastIndex}, nil
}

// Put a value at "key"
func (s *Consul) Put(key string, value []byte, opts *WriteOptions) error {

	key = s.normalize(key)

	p := &api.KVPair{
		Key:   key,
		Value: value,
	}

	if opts != nil && opts.Ephemeral {
		// Creates the global ephemeral session
		// if it does not exist
		if s.ephemeralSession == "" {
			s.createEphemeralSession()
		}

		// Create lock option with the
		// EphemeralSession
		lockOpts := &api.LockOptions{
			Key:     key,
			Session: s.ephemeralSession,
		}

		// Lock and ignore if lock is held
		// It's just a placeholder for the
		// ephemeral behavior
		lock, _ := s.client.LockOpts(lockOpts)
		if lock != nil {
			lock.Lock(nil)
		}

		// Place the session on key
		p.Session = s.ephemeralSession

		// Renew the session
		_, _, err := s.client.Session().Renew(p.Session, nil)
		if err != nil {
			s.ephemeralSession = ""
			return err
		}
	}

	_, err := s.client.KV().Put(p, nil)
	return err
}

// Delete a value at "key"
func (s *Consul) Delete(key string) error {
	_, err := s.client.KV().Delete(s.normalize(key), nil)
	return err
}

// Exists checks that the key exists inside the store
func (s *Consul) Exists(key string) (bool, error) {
	_, err := s.Get(key)
	if err != nil && err == ErrKeyNotFound {
		return false, err
	}
	return true, nil
}

// List the content of a given prefix
func (s *Consul) List(prefix string) ([]*KVPair, error) {
	pairs, _, err := s.client.KV().List(s.normalize(prefix), nil)
	if err != nil {
		return nil, err
	}
	if len(pairs) == 0 {
		return nil, ErrKeyNotFound
	}
	kv := []*KVPair{}
	for _, pair := range pairs {
		if pair.Key == prefix {
			continue
		}
		kv = append(kv, &KVPair{pair.Key, pair.Value, pair.ModifyIndex})
	}
	return kv, nil
}

// DeleteTree deletes a range of keys based on prefix
func (s *Consul) DeleteTree(prefix string) error {
	_, err := s.client.KV().DeleteTree(s.normalize(prefix), nil)
	return err
}

// Watch changes on a key.
// Returns a channel that will receive changes or an error.
// Upon creating a watch, the current value will be sent to the channel.
// Providing a non-nil stopCh can be used to stop watching.
func (s *Consul) Watch(key string, stopCh <-chan struct{}) (<-chan *KVPair, error) {
	key = s.normalize(key)
	kv := s.client.KV()
	watchCh := make(chan *KVPair)

	go func() {
		defer close(watchCh)

		// Use a wait time in order to check if we should quit from time to
		// time.
		opts := &api.QueryOptions{WaitTime: DefaultWatchWaitTime}
		for {
			// Check if we should quit
			select {
			case <-stopCh:
				return
			default:
			}
			pair, meta, err := kv.Get(key, opts)
			if err != nil {
				log.Errorf("consul: %v", err)
				return
			}
			// If LastIndex didn't change then it means `Get` returned because
			// of the WaitTime and the key didn't change.
			if opts.WaitIndex == meta.LastIndex {
				continue
			}
			opts.WaitIndex = meta.LastIndex
			// FIXME: What happens when a key is deleted?
			if pair != nil {
				watchCh <- &KVPair{pair.Key, pair.Value, pair.ModifyIndex}
			}
		}
	}()

	return watchCh, nil
}

// WatchTree watches changes on a "directory"
// Returns a channel that will receive changes or an error.
// Upon creating a watch, the current value will be sent to the channel.
// Providing a non-nil stopCh can be used to stop watching.
func (s *Consul) WatchTree(prefix string, stopCh <-chan struct{}) (<-chan []*KVPair, error) {
	prefix = s.normalize(prefix)
	kv := s.client.KV()
	watchCh := make(chan []*KVPair)

	go func() {
		defer close(watchCh)

		// Use a wait time in order to check if we should quit from time to
		// time.
		opts := &api.QueryOptions{WaitTime: DefaultWatchWaitTime}
		for {
			// Check if we should quit
			select {
			case <-stopCh:
				return
			default:
			}

			pairs, meta, err := kv.List(prefix, opts)
			if err != nil {
				log.Errorf("consul: %v", err)
				return
			}
			// If LastIndex didn't change then it means `Get` returned because
			// of the WaitTime and the key didn't change.
			if opts.WaitIndex == meta.LastIndex {
				continue
			}
			opts.WaitIndex = meta.LastIndex
			kv := []*KVPair{}
			for _, pair := range pairs {
				if pair.Key == prefix {
					continue
				}
				kv = append(kv, &KVPair{pair.Key, pair.Value, pair.ModifyIndex})
			}
			watchCh <- kv
		}
	}()

	return watchCh, nil
}

// NewLock returns a handle to a lock struct which can be used to acquire and
// release the mutex.
func (s *Consul) NewLock(key string, options *LockOptions) (Locker, error) {
	consulOpts := &api.LockOptions{
		Key: s.normalize(key),
	}
	if options != nil {
		consulOpts.Value = options.Value
	}
	l, err := s.client.LockOpts(consulOpts)
	if err != nil {
		return nil, err
	}
	return &consulLock{lock: l}, nil
}

// Lock attempts to acquire the lock and blocks while doing so.
// Returns a channel that is closed if our lock is lost or an error.
func (l *consulLock) Lock() (<-chan struct{}, error) {
	return l.lock.Lock(nil)
}

// Unlock released the lock. It is an error to call this
// if the lock is not currently held.
func (l *consulLock) Unlock() error {
	return l.lock.Unlock()
}

// AtomicPut put a value at "key" if the key has not been
// modified in the meantime, throws an error if this is the case
func (s *Consul) AtomicPut(key string, value []byte, previous *KVPair, options *WriteOptions) (bool, error) {
	p := &api.KVPair{Key: s.normalize(key), Value: value, ModifyIndex: previous.LastIndex}
	if work, _, err := s.client.KV().CAS(p, nil); err != nil {
		return false, err
	} else if !work {
		return false, ErrKeyModified
	}
	return true, nil
}

// AtomicDelete deletes a value at "key" if the key has not
// been modified in the meantime, throws an error if this is the case
func (s *Consul) AtomicDelete(key string, previous *KVPair) (bool, error) {
	p := &api.KVPair{Key: s.normalize(key), ModifyIndex: previous.LastIndex}
	if work, _, err := s.client.KV().DeleteCAS(p, nil); err != nil {
		return false, err
	} else if !work {
		return false, ErrKeyModified
	}
	return true, nil
}
<|endoftext|>


<commit_before>/*
Copyright 2014 The Kubernetes Authors All rights reserved.

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

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

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

package version

// Base version information.
//
// This is the fallback data used when version information from git is not
// provided via go ldflags. It provides an approximation of the Kubernetes
// version for ad-hoc builds (e.g. `go build`) that cannot get the version
// information from git.
//
// If you are looking at these fields in the git tree, they look
// strange. They are modified on the fly by the build process. The
// in-tree values are dummy values used for "git archive", which also
// works for GitHub tar downloads.
//
// When releasing a new Kubernetes version, this file is updated by
// build/mark_new_version.sh to reflect the new version, and then a
// git annotated tag (using format vX.Y where X == Major version and Y
// == Minor version) is created to point to the commit that updates
// pkg/version/base.go
var (
	// TODO: Deprecate gitMajor and gitMinor, use only gitVersion
	// instead. First step in deprecation, keep the fields but make
	// them irrelevant. (Next we'll take it out, which may muck with
	// scripts consuming the kubectl version output - but most of
	// these should be looking at gitVersion already anyways.)
	gitMajor string = "1" // major version, always numeric
	gitMinor string = "2" // minor version, numeric possibly followed by "+"

	// semantic version, dervied by build scripts (see
	// https://github.com/kubernetes/kubernetes/blob/master/docs/design/versioning.md
	// for a detailed discussion of this field)
	//
	// TODO: This field is still called "gitVersion" for legacy
	// reasons. For prerelease versions, the build metadata on the
	// semantic version is a git hash, but the version itself is no
	// longer the direct output of "git describe", but a slight
	// translation to be semver compliant.
	gitVersion   string = "v1.2.0+$Format:%h$"
	gitCommit    string = "$Format:%H$"    // sha1 from git, output of $(git rev-parse HEAD)
	gitTreeState string = "not a git tree" // state of git tree, either "clean" or "dirty"
)
<commit_msg>Kubernetes version v1.2.1-beta.0<commit_after>/*
Copyright 2014 The Kubernetes Authors All rights reserved.

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

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

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

package version

// Base version information.
//
// This is the fallback data used when version information from git is not
// provided via go ldflags. It provides an approximation of the Kubernetes
// version for ad-hoc builds (e.g. `go build`) that cannot get the version
// information from git.
//
// If you are looking at these fields in the git tree, they look
// strange. They are modified on the fly by the build process. The
// in-tree values are dummy values used for "git archive", which also
// works for GitHub tar downloads.
//
// When releasing a new Kubernetes version, this file is updated by
// build/mark_new_version.sh to reflect the new version, and then a
// git annotated tag (using format vX.Y where X == Major version and Y
// == Minor version) is created to point to the commit that updates
// pkg/version/base.go
var (
	// TODO: Deprecate gitMajor and gitMinor, use only gitVersion
	// instead. First step in deprecation, keep the fields but make
	// them irrelevant. (Next we'll take it out, which may muck with
	// scripts consuming the kubectl version output - but most of
	// these should be looking at gitVersion already anyways.)
	gitMajor string = "1"  // major version, always numeric
	gitMinor string = "2+" // minor version, numeric possibly followed by "+"

	// semantic version, dervied by build scripts (see
	// https://github.com/kubernetes/kubernetes/blob/master/docs/design/versioning.md
	// for a detailed discussion of this field)
	//
	// TODO: This field is still called "gitVersion" for legacy
	// reasons. For prerelease versions, the build metadata on the
	// semantic version is a git hash, but the version itself is no
	// longer the direct output of "git describe", but a slight
	// translation to be semver compliant.
	gitVersion   string = "v1.2.1-beta.0+$Format:%h$"
	gitCommit    string = "$Format:%H$"    // sha1 from git, output of $(git rev-parse HEAD)
	gitTreeState string = "not a git tree" // state of git tree, either "clean" or "dirty"
)
<|endoftext|>


<commit_before>/*
Copyright 2014 The Kubernetes Authors.

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

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

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

package version

// Base version information.
//
// This is the fallback data used when version information from git is not
// provided via go ldflags. It provides an approximation of the Kubernetes
// version for ad-hoc builds (e.g. `go build`) that cannot get the version
// information from git.
//
// If you are looking at these fields in the git tree, they look
// strange. They are modified on the fly by the build process. The
// in-tree values are dummy values used for "git archive", which also
// works for GitHub tar downloads.
//
// When releasing a new Kubernetes version, this file is updated by
// build/mark_new_version.sh to reflect the new version, and then a
// git annotated tag (using format vX.Y where X == Major version and Y
// == Minor version) is created to point to the commit that updates
// pkg/version/base.go
var (
	// TODO: Deprecate gitMajor and gitMinor, use only gitVersion
	// instead. First step in deprecation, keep the fields but make
	// them irrelevant. (Next we'll take it out, which may muck with
	// scripts consuming the kubectl version output - but most of
	// these should be looking at gitVersion already anyways.)
	gitMajor string = "1"  // major version, always numeric
	gitMinor string = "4+" // minor version, numeric possibly followed by "+"

	// semantic version, derived by build scripts (see
	// https://github.com/kubernetes/kubernetes/blob/master/docs/design/versioning.md
	// for a detailed discussion of this field)
	//
	// TODO: This field is still called "gitVersion" for legacy
	// reasons. For prerelease versions, the build metadata on the
	// semantic version is a git hash, but the version itself is no
	// longer the direct output of "git describe", but a slight
	// translation to be semver compliant.
	gitVersion   string = "v1.4.7-beta.0+$Format:%h$"
	gitCommit    string = "$Format:%H$"    // sha1 from git, output of $(git rev-parse HEAD)
	gitTreeState string = "not a git tree" // state of git tree, either "clean" or "dirty"

	buildDate string = "1970-01-01T00:00:00Z" // build date in ISO8601 format, output of $(date -u +'%Y-%m-%dT%H:%M:%SZ')
)
<commit_msg>Kubernetes version v1.4.7<commit_after>/*
Copyright 2014 The Kubernetes Authors.

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

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

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

package version

// Base version information.
//
// This is the fallback data used when version information from git is not
// provided via go ldflags. It provides an approximation of the Kubernetes
// version for ad-hoc builds (e.g. `go build`) that cannot get the version
// information from git.
//
// If you are looking at these fields in the git tree, they look
// strange. They are modified on the fly by the build process. The
// in-tree values are dummy values used for "git archive", which also
// works for GitHub tar downloads.
//
// When releasing a new Kubernetes version, this file is updated by
// build/mark_new_version.sh to reflect the new version, and then a
// git annotated tag (using format vX.Y where X == Major version and Y
// == Minor version) is created to point to the commit that updates
// pkg/version/base.go
var (
	// TODO: Deprecate gitMajor and gitMinor, use only gitVersion
	// instead. First step in deprecation, keep the fields but make
	// them irrelevant. (Next we'll take it out, which may muck with
	// scripts consuming the kubectl version output - but most of
	// these should be looking at gitVersion already anyways.)
	gitMajor string = "1" // major version, always numeric
	gitMinor string = "4" // minor version, numeric possibly followed by "+"

	// semantic version, derived by build scripts (see
	// https://github.com/kubernetes/kubernetes/blob/master/docs/design/versioning.md
	// for a detailed discussion of this field)
	//
	// TODO: This field is still called "gitVersion" for legacy
	// reasons. For prerelease versions, the build metadata on the
	// semantic version is a git hash, but the version itself is no
	// longer the direct output of "git describe", but a slight
	// translation to be semver compliant.
	gitVersion   string = "v1.4.7+$Format:%h$"
	gitCommit    string = "$Format:%H$"    // sha1 from git, output of $(git rev-parse HEAD)
	gitTreeState string = "not a git tree" // state of git tree, either "clean" or "dirty"

	buildDate string = "1970-01-01T00:00:00Z" // build date in ISO8601 format, output of $(date -u +'%Y-%m-%dT%H:%M:%SZ')
)
<|endoftext|>


<commit_before>// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

package runtime

import (
	"github.com/ta2gch/iris/runtime/env"
	"github.com/ta2gch/iris/runtime/ilos"
	"github.com/ta2gch/iris/runtime/ilos/class"
	"github.com/ta2gch/iris/runtime/ilos/instance"
)

// While the test-form returns a true value. Specifically:
//
// 1. test-form is evaluated, producing a value Vt.
//
// 2. If Vt is nil, then the while form immediately returns nil.
//
// 3. Otherwise, if Vt is non-nil, the forms body-form* are evaluated sequentially (from left to right).
//
// 4. Upon successful completion of the body-forms*, the while form begins again with step 1.
func While(e env.Environment, testForm ilos.Instance, bodyForm ...ilos.Instance) (ilos.Instance, ilos.Instance) {
	test, err := Eval(e, testForm)
	if err != nil {
		return nil, err
	}
	for test == T {
		_, err := Progn(e, bodyForm...)
		if err != nil {
			return nil, err
		}
		test, err = Eval(e, testForm)
		if err != nil {
			return nil, err
		}
	}
	return Nil, nil
}

// For repeatedly executes a sequence of forms form*, called its body. It specifies a set of identifiers naming
// variables that will be e to the for form, their initialization, and their update for each iteration.
// When a termination condition is met, the iteration exits with a specified result value.
//
// The scope of an identifier var is the body, the steps, the end-test , and the result *. A step might be omitted,
// in which case the effect is the same as if (var init var) had been written instead of (var init).
// It is a violation if more than one iteration-spec names the same var in the same for form.
//
// The for special form is executed as follows: The init forms are evaluated sequentially from left to right.
// Then each value is used as the initial value of the variable denoted by the corresponding identifier var ,
// and the iteration phase begins.
//
// Each iteration begins by evaluating end-test . If the result is nil, the forms in the body are
// evaluated sequentially (for side-effects). Afterwards, the step-forms are evaluated sequentially
// order from left to right. Then their values are assigned to the corresponding variables and the next iteration begins.
// If end-test returns a non-nil value, then the result * are evaluated sequentially and the value of the
// last one is returned as value of the whole for macro. If no result is present, then the value of the for macro is nil.
func For(e env.Environment, iterationSpecs, endTestAndResults ilos.Instance, forms ...ilos.Instance) (ilos.Instance, ilos.Instance) {
	if err := ensure(class.List, iterationSpecs); err != nil {
		return nil, err
	}
	for _, is := range iterationSpecs.(instance.List).Slice() {
		if err := ensure(class.List, is); err != nil {
			return nil, err
		}
		i := is.(instance.List).Slice()
		switch len(i) {
		case 2, 3:
			var1 := i[0]
			init := i[1]
			if !e.Variable.Define(var1, init) {
				return nil, instance.NewImmutableBinding()
			}
		default:
			return nil, instance.NewArityError()
		}
	}
	if err := ensure(class.List, endTestAndResults); err != nil {
		return nil, err
	}
	ends := endTestAndResults.(instance.List).Slice()
	if len(ends) == 0 {
		return nil, instance.NewArityError()
	}
	endTest := ends[0]
	results := ends[1:]
	test, err := Eval(e, endTest)
	if err != nil {
		return nil, err
	}
	for test == Nil {
		_, err := Progn(e, forms...)
		if err != nil {
			return nil, err
		}
		for _, is := range iterationSpecs.(instance.List).Slice() {
			if err := ensure(class.List, is); err != nil {
				return nil, err
			}
			switch is.(instance.List).Length() {
			case 2:
			case 3:
				var1 := is.(instance.List).Nth(0)
				step := is.(instance.List).Nth(2)
				if e.Variable.Set(var1, step) {
					return nil, instance.NewImmutableBinding()
				}
			default:
				return nil, instance.NewArityError()
			}
		}
		test, err = Eval(e, endTest)
		if err != nil {
			return nil, err
		}
	}
	return Progn(e, results...)
}
<commit_msg>fixed for For<commit_after>// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

package runtime

import (
	"github.com/ta2gch/iris/runtime/env"
	"github.com/ta2gch/iris/runtime/ilos"
	"github.com/ta2gch/iris/runtime/ilos/class"
	"github.com/ta2gch/iris/runtime/ilos/instance"
)

// While the test-form returns a true value. Specifically:
//
// 1. test-form is evaluated, producing a value Vt.
//
// 2. If Vt is nil, then the while form immediately returns nil.
//
// 3. Otherwise, if Vt is non-nil, the forms body-form* are evaluated sequentially (from left to right).
//
// 4. Upon successful completion of the body-forms*, the while form begins again with step 1.
func While(e env.Environment, testForm ilos.Instance, bodyForm ...ilos.Instance) (ilos.Instance, ilos.Instance) {
	test, err := Eval(e, testForm)
	if err != nil {
		return nil, err
	}
	for test == T {
		_, err := Progn(e, bodyForm...)
		if err != nil {
			return nil, err
		}
		test, err = Eval(e, testForm)
		if err != nil {
			return nil, err
		}
	}
	return Nil, nil
}

// For repeatedly executes a sequence of forms form*, called its body. It specifies a set of identifiers naming
// variables that will be e to the for form, their initialization, and their update for each iteration.
// When a termination condition is met, the iteration exits with a specified result value.
//
// The scope of an identifier var is the body, the steps, the end-test , and the result *. A step might be omitted,
// in which case the effect is the same as if (var init var) had been written instead of (var init).
// It is a violation if more than one iteration-spec names the same var in the same for form.
//
// The for special form is executed as follows: The init forms are evaluated sequentially from left to right.
// Then each value is used as the initial value of the variable denoted by the corresponding identifier var ,
// and the iteration phase begins.
//
// Each iteration begins by evaluating end-test . If the result is nil, the forms in the body are
// evaluated sequentially (for side-effects). Afterwards, the step-forms are evaluated sequentially
// order from left to right. Then their values are assigned to the corresponding variables and the next iteration begins.
// If end-test returns a non-nil value, then the result * are evaluated sequentially and the value of the
// last one is returned as value of the whole for macro. If no result is present, then the value of the for macro is nil.
func For(e env.Environment, iterationSpecs, endTestAndResults ilos.Instance, forms ...ilos.Instance) (ilos.Instance, ilos.Instance) {
	if err := ensure(class.List, iterationSpecs); err != nil {
		return nil, err
	}
	for _, is := range iterationSpecs.(instance.List).Slice() {
		if err := ensure(class.List, is); err != nil {
			return nil, err
		}
		i := is.(instance.List).Slice()
		switch len(i) {
		case 2, 3:
			var1 := i[0]
			init := i[1]
			if !e.Variable.Define(var1, init) {
				return nil, instance.NewImmutableBinding()
			}
		default:
			return nil, instance.NewArityError()
		}
	}
	if err := ensure(class.List, endTestAndResults); err != nil {
		return nil, err
	}
	ends := endTestAndResults.(instance.List).Slice()
	if len(ends) == 0 {
		return nil, instance.NewArityError()
	}
	endTest := ends[0]
	results := ends[1:]
	test, err := Eval(e, endTest)
	if err != nil {
		return nil, err
	}
	for test == Nil {
		_, err := Progn(e, forms...)
		if err != nil {
			return nil, err
		}
		for _, is := range iterationSpecs.(instance.List).Slice() {
			if err := ensure(class.List, is); err != nil {
				return nil, err
			}
			switch is.(instance.List).Length() {
			case 2:
			case 3:
				var1 := is.(instance.List).Nth(0)
				step, err := Eval(e, is.(instance.List).Nth(2))
				if err != nil {
					return nil, err
				}
				if !e.Variable.Set(var1, step) {
					return nil, instance.NewImmutableBinding()
				}
			default:
				return nil, instance.NewArityError()
			}
		}
		test, err = Eval(e, endTest)
		if err != nil {
			return nil, err
		}
	}
	return Progn(e, results...)
}
<|endoftext|>


<commit_before>package vfs_util

import (
	"errors"
	"os"
	"path"
	"strings"

	"sourcegraph.com/sourcegraph/go-vcs/vcs/util"

	"golang.org/x/tools/godoc/vfs"
)

type prefixFileSystem struct {
	real   vfs.FileSystem
	prefix string
}

func NewPrefixFS(real vfs.FileSystem, prefix string) *prefixFileSystem {
	return &prefixFileSystem{real: real, prefix: path.Clean(prefix)}
}

func (p *prefixFileSystem) Open(name string) (vfs.ReadSeekCloser, error) {
	if strings.HasPrefix(name, p.prefix) {
		return p.real.Open(name[len(p.prefix):])
	}
	return nil, errors.New(name + " doesn't exist")
}

func (p *prefixFileSystem) Lstat(name string) (os.FileInfo, error) {
	return p.Stat(name)
}

func (p *prefixFileSystem) Stat(name string) (os.FileInfo, error) {
	if strings.HasPrefix(name, p.prefix) {
		return p.real.Stat(name[len(p.prefix):])
	}

	if !strings.HasPrefix(p.prefix, name) {
		return nil, errors.New(name + " doesn't exist")
	}

	// TODO.
	return &util.FileInfo{
		Name_: path.Base(name),
		Mode_: os.ModeDir,
		/*Size_: 0,
		ModTime_ : time.Now().UTC(),
		Sys_: nil,*/
	}, nil
}

func (p *prefixFileSystem) ReadDir(name string) ([]os.FileInfo, error) {
	if strings.HasPrefix(name, p.prefix) {
		return p.real.ReadDir(name[len(p.prefix):])
		/*fis, err := p.real.ReadDir(name[len(p.prefix):])
		goon.DumpExpr(len(fis))
		goon.DumpExpr(fis[0].Name())
		goon.DumpExpr(fis[0].Size())
		goon.DumpExpr(fis[0].Mode())
		goon.DumpExpr(fis[0].ModTime())
		goon.DumpExpr(fis[0].IsDir())
		goon.DumpExpr(fis[0].Sys())
		return fis, err*/
	}

	if !strings.HasPrefix(p.prefix, name) {
		return nil, errors.New(name + " doesn't exist")
	}

	// TODO.
	return []os.FileInfo{&util.FileInfo{
		Name_: antibase(strings.TrimPrefix(p.prefix, name)),
		Mode_: os.ModeDir,
		/*Size_: 0,
		ModTime_ : time.Now().UTC(),
		Sys_: nil,*/
	}}, nil
}

func (p *prefixFileSystem) String() string {
	return "prefixFileSystem{" + p.real.String() + "}"
}

func antibase(name string) string {
	if strings.HasPrefix(name, "/") {
		name = name[1:]
	}
	if i := strings.Index(name, "/"); i != -1 {
		name = name[:i]
	}
	return name
}
<commit_msg>Refactor to improve readability.<commit_after>package vfs_util

import (
	"errors"
	"os"
	"path"
	"strings"

	"sourcegraph.com/sourcegraph/go-vcs/vcs/util"

	"golang.org/x/tools/godoc/vfs"
)

type prefixFileSystem struct {
	real   vfs.FileSystem
	prefix string
}

func NewPrefixFS(real vfs.FileSystem, prefix string) *prefixFileSystem {
	return &prefixFileSystem{real: real, prefix: path.Clean(prefix)}
}

func (p *prefixFileSystem) Open(name string) (vfs.ReadSeekCloser, error) {
	if strings.HasPrefix(name, p.prefix) {
		return p.real.Open(name[len(p.prefix):])
	}
	return nil, errors.New(name + " doesn't exist")
}

func (p *prefixFileSystem) Lstat(name string) (os.FileInfo, error) {
	return p.Stat(name)
}

func (p *prefixFileSystem) Stat(name string) (os.FileInfo, error) {
	if strings.HasPrefix(name, p.prefix) {
		return p.real.Stat(name[len(p.prefix):])
	}

	if !strings.HasPrefix(p.prefix, name) {
		return nil, errors.New(name + " doesn't exist")
	}

	// TODO.
	return &util.FileInfo{
		Name_: path.Base(name),
		Mode_: os.ModeDir,
		/*Size_: 0,
		ModTime_ : time.Now().UTC(),
		Sys_: nil,*/
	}, nil
}

func (p *prefixFileSystem) ReadDir(name string) ([]os.FileInfo, error) {
	if strings.HasPrefix(name, p.prefix) {
		return p.real.ReadDir(name[len(p.prefix):])
		/*fis, err := p.real.ReadDir(name[len(p.prefix):])
		goon.DumpExpr(len(fis))
		goon.DumpExpr(fis[0].Name())
		goon.DumpExpr(fis[0].Size())
		goon.DumpExpr(fis[0].Mode())
		goon.DumpExpr(fis[0].ModTime())
		goon.DumpExpr(fis[0].IsDir())
		goon.DumpExpr(fis[0].Sys())
		return fis, err*/
	}

	if !strings.HasPrefix(p.prefix, name) {
		return nil, errors.New(name + " doesn't exist")
	}

	// TODO.
	return []os.FileInfo{&util.FileInfo{
		Name_: antibase(strings.TrimPrefix(p.prefix, name)),
		Mode_: os.ModeDir,
		/*Size_: 0,
		ModTime_ : time.Now().UTC(),
		Sys_: nil,*/
	}}, nil
}

func (p *prefixFileSystem) String() string {
	return "prefixFileSystem{" + p.real.String() + "}"
}

func antibase(name string) string {
	name = strings.TrimPrefix(name, "/")
	if i := strings.Index(name, "/"); i != -1 {
		name = name[:i]
	}
	return name
}
<|endoftext|>


<commit_before>package plugins

import (
	"fmt"
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"runtime"
	"sync"
	"syscall"
	"time"

	cgroupConfig "github.com/opencontainers/runc/libcontainer/configs"

	"github.com/hashicorp/nomad/client/allocdir"
	"github.com/hashicorp/nomad/client/driver/env"
	"github.com/hashicorp/nomad/nomad/structs"
)

type ExecutorContext struct {
	TaskEnv          *env.TaskEnvironment
	AllocDir         *allocdir.AllocDir
	TaskName         string
	TaskResources    *structs.Resources
	FSIsolation      bool
	ResourceLimits   bool
	UnprivilegedUser bool
}

type ExecCommand struct {
	Cmd  string
	Args []string
}

type ProcessState struct {
	Pid      int
	ExitCode int
	Time     time.Time
}

type Executor interface {
	LaunchCmd(command *ExecCommand, ctx *ExecutorContext) (*ProcessState, error)
	Wait() (*ProcessState, error)
	ShutDown() error
	Exit() error
}

type UniversalExecutor struct {
	cmd exec.Cmd
	ctx *ExecutorContext

	taskDir       string
	groups        *cgroupConfig.Cgroup
	exitState     *ProcessState
	processExited chan interface{}

	logger *log.Logger
	lock   sync.Mutex
}

func NewExecutor(logger *log.Logger) Executor {
	return &UniversalExecutor{logger: logger, processExited: make(chan interface{})}
}

func (e *UniversalExecutor) LaunchCmd(command *ExecCommand, ctx *ExecutorContext) (*ProcessState, error) {
	e.ctx = ctx
	e.cmd.Path = command.Cmd
	e.cmd.Args = append([]string{command.Cmd}, command.Args...)
	if filepath.Base(command.Cmd) == command.Cmd {
		if lp, err := exec.LookPath(command.Cmd); err != nil {
		} else {
			e.cmd.Path = lp
		}
	}
	if err := e.configureTaskDir(); err != nil {
		return nil, err
	}
	if err := e.configureIsolation(); err != nil {
		return nil, err
	}

	if e.ctx.UnprivilegedUser {
		if err := e.runAs("nobody"); err != nil {
			return nil, err
		}
	}

	stdoPath := filepath.Join(e.taskDir, allocdir.TaskLocal, fmt.Sprintf("%v.stdout", ctx.TaskName))
	stdo, err := os.OpenFile(stdoPath, os.O_CREATE|os.O_RDWR|os.O_APPEND, 0666)
	if err != nil {
		return nil, err
	}
	e.cmd.Stdout = stdo

	stdePath := filepath.Join(e.taskDir, allocdir.TaskLocal, fmt.Sprintf("%v.stderr", ctx.TaskName))
	stde, err := os.OpenFile(stdePath, os.O_CREATE|os.O_RDWR|os.O_APPEND, 0666)
	if err != nil {
		return nil, err
	}
	e.cmd.Stderr = stde

	e.cmd.Env = ctx.TaskEnv.EnvList()

	if err := e.cmd.Start(); err != nil {
		return nil, fmt.Errorf("error starting command: %v", err)
	}

	e.applyLimits()
	go e.wait()
	return &ProcessState{Pid: e.cmd.Process.Pid, ExitCode: -1, Time: time.Now()}, nil
}

func (e *UniversalExecutor) Wait() (*ProcessState, error) {
	<-e.processExited
	return e.exitState, nil
}

func (e *UniversalExecutor) wait() {
	err := e.cmd.Wait()
	if err == nil {
		e.exitState = &ProcessState{Pid: 0, ExitCode: 0, Time: time.Now()}
		return
	}
	exitCode := 1
	if exitErr, ok := err.(*exec.ExitError); ok {
		if status, ok := exitErr.Sys().(syscall.WaitStatus); ok {
			exitCode = status.ExitStatus()
		}
	}
	if e.ctx.FSIsolation {
		e.removeChrootMounts()
	}
	if e.ctx.ResourceLimits {
		e.destroyCgroup()
	}
	e.exitState = &ProcessState{Pid: 0, ExitCode: exitCode, Time: time.Now()}
	close(e.processExited)
}

func (e *UniversalExecutor) Exit() error {
	e.logger.Printf("[INFO] Exiting plugin for task %q", e.ctx.TaskName)
	proc, err := os.FindProcess(e.cmd.Process.Pid)
	if err != nil {
		return fmt.Errorf("failied to find user process %v: %v", e.cmd.Process.Pid, err)
	}
	if e.ctx.FSIsolation {
		e.removeChrootMounts()
	}
	if e.ctx.ResourceLimits {
		e.destroyCgroup()
	}
	return proc.Kill()
}

func (e *UniversalExecutor) ShutDown() error {
	proc, err := os.FindProcess(e.cmd.Process.Pid)
	if err != nil {
		return err
	}
	if runtime.GOOS == "windows" {
		return proc.Kill()
	}
	return proc.Signal(os.Interrupt)
}

func (e *UniversalExecutor) configureTaskDir() error {
	taskDir, ok := e.ctx.AllocDir.TaskDirs[e.ctx.TaskName]
	e.taskDir = taskDir
	if !ok {
		return fmt.Errorf("Couldn't find task directory for task %v", e.ctx.TaskName)
	}
	e.cmd.Dir = taskDir
	return nil
}
<commit_msg>Fixed a test related to wait<commit_after>package plugins

import (
	"fmt"
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"runtime"
	"sync"
	"syscall"
	"time"

	cgroupConfig "github.com/opencontainers/runc/libcontainer/configs"

	"github.com/hashicorp/nomad/client/allocdir"
	"github.com/hashicorp/nomad/client/driver/env"
	"github.com/hashicorp/nomad/nomad/structs"
)

type ExecutorContext struct {
	TaskEnv          *env.TaskEnvironment
	AllocDir         *allocdir.AllocDir
	TaskName         string
	TaskResources    *structs.Resources
	FSIsolation      bool
	ResourceLimits   bool
	UnprivilegedUser bool
}

type ExecCommand struct {
	Cmd  string
	Args []string
}

type ProcessState struct {
	Pid      int
	ExitCode int
	Time     time.Time
}

type Executor interface {
	LaunchCmd(command *ExecCommand, ctx *ExecutorContext) (*ProcessState, error)
	Wait() (*ProcessState, error)
	ShutDown() error
	Exit() error
}

type UniversalExecutor struct {
	cmd exec.Cmd
	ctx *ExecutorContext

	taskDir       string
	groups        *cgroupConfig.Cgroup
	exitState     *ProcessState
	processExited chan interface{}

	logger *log.Logger
	lock   sync.Mutex
}

func NewExecutor(logger *log.Logger) Executor {
	return &UniversalExecutor{logger: logger, processExited: make(chan interface{})}
}

func (e *UniversalExecutor) LaunchCmd(command *ExecCommand, ctx *ExecutorContext) (*ProcessState, error) {
	e.ctx = ctx
	e.cmd.Path = command.Cmd
	e.cmd.Args = append([]string{command.Cmd}, command.Args...)
	if filepath.Base(command.Cmd) == command.Cmd {
		if lp, err := exec.LookPath(command.Cmd); err != nil {
		} else {
			e.cmd.Path = lp
		}
	}
	if err := e.configureTaskDir(); err != nil {
		return nil, err
	}
	if err := e.configureIsolation(); err != nil {
		return nil, err
	}

	if e.ctx.UnprivilegedUser {
		if err := e.runAs("nobody"); err != nil {
			return nil, err
		}
	}

	stdoPath := filepath.Join(e.taskDir, allocdir.TaskLocal, fmt.Sprintf("%v.stdout", ctx.TaskName))
	stdo, err := os.OpenFile(stdoPath, os.O_CREATE|os.O_RDWR|os.O_APPEND, 0666)
	if err != nil {
		return nil, err
	}
	e.cmd.Stdout = stdo

	stdePath := filepath.Join(e.taskDir, allocdir.TaskLocal, fmt.Sprintf("%v.stderr", ctx.TaskName))
	stde, err := os.OpenFile(stdePath, os.O_CREATE|os.O_RDWR|os.O_APPEND, 0666)
	if err != nil {
		return nil, err
	}
	e.cmd.Stderr = stde

	e.cmd.Env = ctx.TaskEnv.EnvList()

	if err := e.cmd.Start(); err != nil {
		return nil, fmt.Errorf("error starting command: %v", err)
	}

	e.applyLimits()
	go e.wait()
	return &ProcessState{Pid: e.cmd.Process.Pid, ExitCode: -1, Time: time.Now()}, nil
}

func (e *UniversalExecutor) Wait() (*ProcessState, error) {
	<-e.processExited
	return e.exitState, nil
}

func (e *UniversalExecutor) wait() {
	defer close(e.processExited)
	err := e.cmd.Wait()
	if err == nil {
		e.exitState = &ProcessState{Pid: 0, ExitCode: 0, Time: time.Now()}
		return
	}
	exitCode := 1
	if exitErr, ok := err.(*exec.ExitError); ok {
		if status, ok := exitErr.Sys().(syscall.WaitStatus); ok {
			exitCode = status.ExitStatus()
		}
	}
	if e.ctx.FSIsolation {
		e.removeChrootMounts()
	}
	if e.ctx.ResourceLimits {
		e.destroyCgroup()
	}
	e.exitState = &ProcessState{Pid: 0, ExitCode: exitCode, Time: time.Now()}
}

func (e *UniversalExecutor) Exit() error {
	e.logger.Printf("[INFO] Exiting plugin for task %q", e.ctx.TaskName)
	proc, err := os.FindProcess(e.cmd.Process.Pid)
	if err != nil {
		return fmt.Errorf("failied to find user process %v: %v", e.cmd.Process.Pid, err)
	}
	if e.ctx.FSIsolation {
		e.removeChrootMounts()
	}
	if e.ctx.ResourceLimits {
		e.destroyCgroup()
	}
	return proc.Kill()
}

func (e *UniversalExecutor) ShutDown() error {
	proc, err := os.FindProcess(e.cmd.Process.Pid)
	if err != nil {
		return err
	}
	if runtime.GOOS == "windows" {
		return proc.Kill()
	}
	return proc.Signal(os.Interrupt)
}

func (e *UniversalExecutor) configureTaskDir() error {
	taskDir, ok := e.ctx.AllocDir.TaskDirs[e.ctx.TaskName]
	e.taskDir = taskDir
	if !ok {
		return fmt.Errorf("Couldn't find task directory for task %v", e.ctx.TaskName)
	}
	e.cmd.Dir = taskDir
	return nil
}
<|endoftext|>


<commit_before>package fingerprint

import (
	"fmt"
	"log"

	"github.com/hashicorp/nomad/client/config"
	"github.com/hashicorp/nomad/nomad/structs"
)

// BuiltinFingerprints contains the built in registered fingerprints
// which are available
var BuiltinFingerprints = map[string]Factory{
	"arch":    NewArchFingerprint,
	"cpu":     NewCPUFingerprint,
	"host":    NewHostFingerprint,
	"memory":  NewMemoryFingerprint,
	"storage": NewStorageFingerprint,
}

// NewFingerprint is used to instantiate and return a new fingerprint
// given the name and a logger
func NewFingerprint(name string, logger *log.Logger) (Fingerprint, error) {
	// Lookup the factory function
	factory, ok := BuiltinFingerprints[name]
	if !ok {
		return nil, fmt.Errorf("unknown fingerprint '%s'", name)
	}

	// Instantiate the fingerprint
	f := factory(logger)
	return f, nil
}

// Factory is used to instantiate a new Fingerprint
type Factory func(*log.Logger) Fingerprint

// Fingerprint is used for doing "fingerprinting" of the
// host to automatically determine attributes, resources,
// and metadata about it. Each of these is a heuristic, and
// many of them can be applied on a particular host.
type Fingerprint interface {
	// Fingerprint is used to update properties of the Node,
	// and returns if the fingerprint was applicable and a potential error.
	Fingerprint(*config.Config, *structs.Node) (bool, error)
}
<commit_msg>Rework client/fingerprint/fingerprint.go to use a slice and enforce ordering<commit_after>package fingerprint

import (
	"fmt"
	"log"

	"github.com/hashicorp/nomad/client/config"
	"github.com/hashicorp/nomad/nomad/structs"
)

// BuiltinFingerprints is a slice containing the key names of all regestered
// fingerprints available, to provided an ordered iteration
var BuiltinFingerprints = []string{
	"arch",
	"cpu",
	"host",
	"memory",
	"storage",
	"unix_network",
	"aws_network",
}

// builtinFingerprintMap contains the built in registered fingerprints
// which are available, corresponding to a key found in BuiltinFingerprints
var builtinFingerprintMap = map[string]Factory{
	"arch":         NewArchFingerprint,
	"cpu":          NewCPUFingerprint,
	"host":         NewHostFingerprint,
	"memory":       NewMemoryFingerprint,
	"storage":      NewStorageFingerprint,
	"unix_network": NewUnixNetworkFingerprinter,
	"aws_network":  NewAWSNetworkFingerprinter,
}

// NewFingerprint is used to instantiate and return a new fingerprint
// given the name and a logger
func NewFingerprint(name string, logger *log.Logger) (Fingerprint, error) {
	// Lookup the factory function
	factory, ok := builtinFingerprintMap[name]
	if !ok {
		return nil, fmt.Errorf("unknown fingerprint '%s'", name)
	}

	// Instantiate the fingerprint
	f := factory(logger)
	return f, nil
}

// Factory is used to instantiate a new Fingerprint
type Factory func(*log.Logger) Fingerprint

// Fingerprint is used for doing "fingerprinting" of the
// host to automatically determine attributes, resources,
// and metadata about it. Each of these is a heuristic, and
// many of them can be applied on a particular host.
type Fingerprint interface {
	// Fingerprint is used to update properties of the Node,
	// and returns if the fingerprint was applicable and a potential error.
	Fingerprint(*config.Config, *structs.Node) (bool, error)
}
<|endoftext|>


<commit_before>// Command surfer scrapes the signal status page of a SB6121 cable modem and
// exports values as prometheus metrics.
package main

import (
	"flag"
	"net/http"
	"strconv"
	"strings"

	"github.com/andybalholm/cascadia"
	"github.com/golang/glog"
	"github.com/golang/groupcache/singleflight"
	"github.com/prometheus/client_golang/prometheus"

	"golang.org/x/net/html"
)

const signalURL = "http://192.168.100.1/cmSignalData.htm"

var (
	port = flag.Int("port", 6666, "port to listen on when serving prometheus metrics")

	downstreamSNRMetric = prometheus.NewGaugeVec(prometheus.GaugeOpts{
		Name: "downstream_snr",
		Help: "Downstream signal-to-noise ratio in dB",
	},
		[]string{"channel", "frequency_hz", "modulation"},
	)
	downstreamPowerLevelMetric = prometheus.NewGaugeVec(prometheus.GaugeOpts{
		Name: "downstream_power_level",
		Help: "Downstream power level reading in dBmV",
	},
		[]string{"channel", "frequency_hz", "modulation"},
	)

	upstreamSymbolRateMetric = prometheus.NewGaugeVec(prometheus.GaugeOpts{
		Name: "upstream_symbol_rate",
		Help: "Upstream symbol rate in sym/sec",
	},
		[]string{"channel", "frequency_hz", "modulation", "ranging_service", "ranging_status"},
	)
	upstreamPowerLevelMetric = prometheus.NewGaugeVec(prometheus.GaugeOpts{
		Name: "upstream_power_level",
		Help: "Upstream power level reading in dBmV",
	},
		[]string{"channel", "frequency_hz", "modulation", "ranging_service", "ranging_status"},
	)
)

func init() {
	prometheus.MustRegister(downstreamSNRMetric)
	prometheus.MustRegister(downstreamPowerLevelMetric)
	prometheus.MustRegister(upstreamSymbolRateMetric)
	prometheus.MustRegister(upstreamPowerLevelMetric)
}

func getText(n *html.Node) string {
	text := []string{}
	for c := n.FirstChild; c != nil; c = c.NextSibling {
		switch c.Type {
		case html.TextNode:
			text = append(text, c.Data)
		default:
			text = append(text, getText(c))
		}
	}

	return strings.TrimSpace(strings.Join(text, ""))
}

func updateDownstream(n *html.Node) {
	glog.Infoln("Updating downstream table")
	type stat struct {
		frequency  string
		snr        float64
		modulation string
		powerLevel float64
	}
	stats := map[string]*stat{}
	var ids []string

	// Remove nested tables
	for _, t := range cascadia.MustCompile("table table").MatchAll(n) {
		t.Parent.RemoveChild(t)
	}

	for row, tr := range cascadia.MustCompile("tr").MatchAll(n)[1:] {
		switch row {
		case 0:
			// ID
			for _, td := range cascadia.MustCompile("td").MatchAll(tr)[1:] {
				id := getText(td)
				ids = append(ids, id)
				stats[id] = &stat{}
			}
		case 1:
			// Frequency
			for i, td := range cascadia.MustCompile("td").MatchAll(tr)[1:] {
				stats[ids[i]].frequency = strings.Fields(getText(td))[0]
			}
		case 2:
			// SNR
			for i, td := range cascadia.MustCompile("td").MatchAll(tr)[1:] {
				f, err := strconv.ParseFloat(strings.Fields(getText(td))[0], 64)
				if err != nil {
					continue
				}
				stats[ids[i]].snr = f
			}
		case 3:
			// Modulation
			for i, td := range cascadia.MustCompile("td").MatchAll(tr)[1:] {
				stats[ids[i]].modulation = getText(td)
			}
		case 4:
			for i, td := range cascadia.MustCompile("td").MatchAll(tr)[1:] {
				// Power level
				f, err := strconv.ParseFloat(strings.Fields(getText(td))[0], 64)
				if err != nil {
					continue
				}
				stats[ids[i]].powerLevel = f
			}
		default:
			glog.Fatalf("Unhandled %d row in downstream table", row)
		}
	}
	glog.Infof("updateDownstream data:")
	for k, v := range stats {
		glog.Infof("  %v: %v", k, v)
		downstreamSNRMetric.WithLabelValues(k, v.frequency, v.modulation).Set(v.snr)
		downstreamPowerLevelMetric.WithLabelValues(k, v.frequency, v.modulation).Set(v.powerLevel)
	}
}

func updateUpstream(n *html.Node) {
	glog.Infoln("Updating upstream table")
	type stat struct {
		frequency      string
		rangingService string
		rangingStatus  string
		symbolRate     float64
		modulation     string
		powerLevel     float64
	}
	stats := map[string]*stat{}
	var ids []string
	for row, tr := range cascadia.MustCompile("tr").MatchAll(n)[1:] {
		switch row {
		case 0:
			// ID
			for _, td := range cascadia.MustCompile("td").MatchAll(tr)[1:] {
				id := getText(td)
				ids = append(ids, id)
				stats[id] = &stat{}
			}
		case 1:
			// Frequency
			for i, td := range cascadia.MustCompile("td").MatchAll(tr)[1:] {
				stats[ids[i]].frequency = strings.Fields(getText(td))[0]
			}
		case 2:
			// Ranging Service ID
			for i, td := range cascadia.MustCompile("td").MatchAll(tr)[1:] {
				stats[ids[i]].rangingService = getText(td)
			}
		case 3:
			// Symbol Rate
			for i, td := range cascadia.MustCompile("td").MatchAll(tr)[1:] {
				f, err := strconv.ParseFloat(strings.Fields(getText(td))[0], 64)
				if err != nil {
					continue
				}
				stats[ids[i]].symbolRate = f * 1000000
			}
		case 4:
			// Power level
			for i, td := range cascadia.MustCompile("td").MatchAll(tr)[1:] {
				f, err := strconv.ParseFloat(strings.Fields(getText(td))[0], 64)
				if err != nil {
					continue
				}
				stats[ids[i]].powerLevel = f
			}
		case 5:
			// Modulation
			for i, td := range cascadia.MustCompile("td").MatchAll(tr)[1:] {
				stats[ids[i]].modulation = strings.Replace(getText(td), "\n", " ", -1)
			}
		case 6:
			// Ranging Status TODO
			for i, td := range cascadia.MustCompile("td").MatchAll(tr)[1:] {
				stats[ids[i]].rangingStatus = getText(td)
			}
		default:
			glog.Fatalf("Unhandled %d row in downstream table", row)
		}
	}
	glog.Infof("updateUpstream data:")
	for k, v := range stats {
		glog.Infof("  %v: %v", k, v)
		upstreamSymbolRateMetric.WithLabelValues(k, v.frequency, v.modulation, v.rangingService, v.rangingStatus).Set(v.symbolRate)
		upstreamPowerLevelMetric.WithLabelValues(k, v.frequency, v.modulation, v.rangingService, v.rangingStatus).Set(v.powerLevel)
	}
}

func updateSignalStats(z *html.Tokenizer) {
	glog.Infoln("Updating signal stats table")
}

func get() error {
	resp, err := http.Get(signalURL)
	if err != nil {
		return err
	}
	defer resp.Body.Close()
	n, err := html.Parse(resp.Body)
	if err != nil {
		return err
	}

	// All top-level tables are immediate descendants of center.  One table has
	// a nested table in a td, which this filter excludes.
	sel := cascadia.MustCompile("center > table")
	for i, t := range sel.MatchAll(n) {
		glog.Infof("Table %d %v", i, t)
		switch i {
		case 0:
			updateDownstream(t)
		case 1:
			updateUpstream(t)
		}
	}
	return nil
}

func main() {
	flag.Parse()
	defer glog.Flush()

	g := &singleflight.Group{}
	ph := prometheus.Handler()
	// Refresh data every prometheus poll.
	http.Handle("/metrics", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		// Only make one query to the cable modem if concurrent requests come in.
		if _, err := g.Do("get", func() (interface{}, error) {
			if err := get(); err != nil {
				return nil, err
			}
			return nil, nil
		}); err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
		ph.ServeHTTP(w, r)
	}))
	http.ListenAndServe(":"+strconv.Itoa(*port), nil)
}
<commit_msg>Add connection stats metrics.<commit_after>// Command surfer scrapes the signal status page of a SB6121 cable modem and
// exports values as prometheus metrics.
package main

import (
	"flag"
	"net/http"
	"strconv"
	"strings"

	"github.com/andybalholm/cascadia"
	"github.com/golang/glog"
	"github.com/golang/groupcache/singleflight"
	"github.com/prometheus/client_golang/prometheus"

	"golang.org/x/net/html"
)

const signalURL = "http://192.168.100.1/cmSignalData.htm"

var (
	port = flag.Int("port", 6666, "port to listen on when serving prometheus metrics")

	downstreamSNRMetric = prometheus.NewGaugeVec(prometheus.GaugeOpts{
		Name: "downstream_snr",
		Help: "Downstream signal-to-noise ratio in dB",
	},
		[]string{"channel", "frequency_hz", "modulation"},
	)
	downstreamPowerLevelMetric = prometheus.NewGaugeVec(prometheus.GaugeOpts{
		Name: "downstream_power_level",
		Help: "Downstream power level reading in dBmV",
	},
		[]string{"channel", "frequency_hz", "modulation"},
	)

	codewordsUnerroredMetric = prometheus.NewCounterVec(prometheus.CounterOpts{
		Name: "codewords_unerrored",
		Help: "Unerrored codeword count",
	},
		[]string{"channel"},
	)
	codewordsCorrectableMetric = prometheus.NewCounterVec(prometheus.CounterOpts{
		Name: "codewords_correctable",
		Help: "Correctable codeword count",
	},
		[]string{"channel"},
	)
	codewordsUncorrectableMetric = prometheus.NewCounterVec(prometheus.CounterOpts{
		Name: "codewords_uncorrectable",
		Help: "Uncorrectable codeword count",
	},
		[]string{"channel"},
	)

	upstreamSymbolRateMetric = prometheus.NewGaugeVec(prometheus.GaugeOpts{
		Name: "upstream_symbol_rate",
		Help: "Upstream symbol rate in sym/sec",
	},
		[]string{"channel", "frequency_hz", "modulation", "ranging_service", "ranging_status"},
	)
	upstreamPowerLevelMetric = prometheus.NewGaugeVec(prometheus.GaugeOpts{
		Name: "upstream_power_level",
		Help: "Upstream power level reading in dBmV",
	},
		[]string{"channel", "frequency_hz", "modulation", "ranging_service", "ranging_status"},
	)
)

func init() {
	prometheus.MustRegister(downstreamSNRMetric)
	prometheus.MustRegister(downstreamPowerLevelMetric)
	prometheus.MustRegister(upstreamSymbolRateMetric)
	prometheus.MustRegister(upstreamPowerLevelMetric)
	prometheus.MustRegister(codewordsUnerroredMetric)
	prometheus.MustRegister(codewordsCorrectableMetric)
	prometheus.MustRegister(codewordsUncorrectableMetric)
}

func getText(n *html.Node) string {
	text := []string{}
	for c := n.FirstChild; c != nil; c = c.NextSibling {
		switch c.Type {
		case html.TextNode:
			text = append(text, c.Data)
		default:
			text = append(text, getText(c))
		}
	}

	return strings.TrimSpace(strings.Join(text, ""))
}

func updateDownstream(n *html.Node) {
	glog.V(2).Infoln("Updating downstream table")
	type stat struct {
		frequency  string
		snr        float64
		modulation string
		powerLevel float64
	}
	stats := map[string]*stat{}
	var ids []string

	// Remove nested tables
	for _, t := range cascadia.MustCompile("table table").MatchAll(n) {
		t.Parent.RemoveChild(t)
	}

	for row, tr := range cascadia.MustCompile("tr").MatchAll(n)[1:] {
		switch row {
		case 0:
			// ID
			for _, td := range cascadia.MustCompile("td").MatchAll(tr)[1:] {
				id := getText(td)
				ids = append(ids, id)
				stats[id] = &stat{}
			}
		case 1:
			// Frequency
			for i, td := range cascadia.MustCompile("td").MatchAll(tr)[1:] {
				stats[ids[i]].frequency = strings.Fields(getText(td))[0]
			}
		case 2:
			// SNR
			for i, td := range cascadia.MustCompile("td").MatchAll(tr)[1:] {
				f, err := strconv.ParseFloat(strings.Fields(getText(td))[0], 64)
				if err != nil {
					continue
				}
				stats[ids[i]].snr = f
			}
		case 3:
			// Modulation
			for i, td := range cascadia.MustCompile("td").MatchAll(tr)[1:] {
				stats[ids[i]].modulation = getText(td)
			}
		case 4:
			for i, td := range cascadia.MustCompile("td").MatchAll(tr)[1:] {
				// Power level
				f, err := strconv.ParseFloat(strings.Fields(getText(td))[0], 64)
				if err != nil {
					continue
				}
				stats[ids[i]].powerLevel = f
			}
		default:
			glog.Fatalf("Unhandled %d row in downstream table", row)
		}
	}
	glog.V(2).Infof("updateDownstream data:")
	for k, v := range stats {
		glog.V(2).Infof("  %v: %v", k, v)
		downstreamSNRMetric.WithLabelValues(k, v.frequency, v.modulation).Set(v.snr)
		downstreamPowerLevelMetric.WithLabelValues(k, v.frequency, v.modulation).Set(v.powerLevel)
	}
}

func updateUpstream(n *html.Node) {
	glog.V(2).Infoln("Updating upstream table")
	type stat struct {
		frequency      string
		rangingService string
		rangingStatus  string
		symbolRate     float64
		modulation     string
		powerLevel     float64
	}
	stats := map[string]*stat{}
	var ids []string
	for row, tr := range cascadia.MustCompile("tr").MatchAll(n)[1:] {
		switch row {
		case 0:
			// ID
			for _, td := range cascadia.MustCompile("td").MatchAll(tr)[1:] {
				id := getText(td)
				ids = append(ids, id)
				stats[id] = &stat{}
			}
		case 1:
			// Frequency
			for i, td := range cascadia.MustCompile("td").MatchAll(tr)[1:] {
				stats[ids[i]].frequency = strings.Fields(getText(td))[0]
			}
		case 2:
			// Ranging Service ID
			for i, td := range cascadia.MustCompile("td").MatchAll(tr)[1:] {
				stats[ids[i]].rangingService = getText(td)
			}
		case 3:
			// Symbol Rate
			for i, td := range cascadia.MustCompile("td").MatchAll(tr)[1:] {
				f, err := strconv.ParseFloat(strings.Fields(getText(td))[0], 64)
				if err != nil {
					continue
				}
				stats[ids[i]].symbolRate = f * 1000000
			}
		case 4:
			// Power level
			for i, td := range cascadia.MustCompile("td").MatchAll(tr)[1:] {
				f, err := strconv.ParseFloat(strings.Fields(getText(td))[0], 64)
				if err != nil {
					continue
				}
				stats[ids[i]].powerLevel = f
			}
		case 5:
			// Modulation
			for i, td := range cascadia.MustCompile("td").MatchAll(tr)[1:] {
				stats[ids[i]].modulation = strings.Replace(getText(td), "\n", " ", -1)
			}
		case 6:
			// Ranging Status TODO
			for i, td := range cascadia.MustCompile("td").MatchAll(tr)[1:] {
				stats[ids[i]].rangingStatus = getText(td)
			}
		default:
			glog.Fatalf("Unhandled %d row in upstream table", row)
		}
	}
	glog.V(2).Infof("updateUpstream data:")
	for k, v := range stats {
		glog.V(2).Infof("  %v: %v", k, v)
		upstreamSymbolRateMetric.WithLabelValues(k, v.frequency, v.modulation, v.rangingService, v.rangingStatus).Set(v.symbolRate)
		upstreamPowerLevelMetric.WithLabelValues(k, v.frequency, v.modulation, v.rangingService, v.rangingStatus).Set(v.powerLevel)
	}
}

func updateSignalStats(n *html.Node) {
	glog.V(2).Infoln("Updating signal stats table")
	type stat struct {
		unerrored     float64
		correctable   float64
		uncorrectable float64
	}
	stats := map[string]*stat{}
	var ids []string
	for row, tr := range cascadia.MustCompile("tr").MatchAll(n)[1:] {
		switch row {
		case 0:
			// ID
			for _, td := range cascadia.MustCompile("td").MatchAll(tr)[1:] {
				id := getText(td)
				ids = append(ids, id)
				stats[id] = &stat{}
			}
		case 1:
			// Total Unerrored Codewords
			for i, td := range cascadia.MustCompile("td").MatchAll(tr)[1:] {
				f, err := strconv.ParseFloat(strings.Fields(getText(td))[0], 64)
				if err != nil {
					continue
				}
				stats[ids[i]].unerrored = f
			}
		case 2:
			// Total Correctable Codewords
			for i, td := range cascadia.MustCompile("td").MatchAll(tr)[1:] {
				f, err := strconv.ParseFloat(strings.Fields(getText(td))[0], 64)
				if err != nil {
					continue
				}
				stats[ids[i]].correctable = f
			}
		case 3:
			// Total Uncorrectable Codewords
			for i, td := range cascadia.MustCompile("td").MatchAll(tr)[1:] {
				f, err := strconv.ParseFloat(strings.Fields(getText(td))[0], 64)
				if err != nil {
					continue
				}
				stats[ids[i]].uncorrectable = f
			}
		default:
			glog.Fatalf("Unhandled %d row in signal stats table", row)
		}
	}
	glog.V(2).Infof("updateSignalStats data:")
	for k, v := range stats {
		glog.V(2).Infof("  %v: %v", k, v)
		codewordsUnerroredMetric.WithLabelValues(k).Set(v.unerrored)
		codewordsCorrectableMetric.WithLabelValues(k).Set(v.correctable)
		codewordsUncorrectableMetric.WithLabelValues(k).Set(v.uncorrectable)
	}
}

func get() error {
	resp, err := http.Get(signalURL)
	if err != nil {
		return err
	}
	defer resp.Body.Close()
	n, err := html.Parse(resp.Body)
	if err != nil {
		return err
	}

	// All top-level tables are immediate descendants of center.  One table has
	// a nested table in a td, which this filter excludes.
	sel := cascadia.MustCompile("center > table")
	for i, t := range sel.MatchAll(n) {
		switch i {
		case 0:
			updateDownstream(t)
		case 1:
			updateUpstream(t)
		case 2:
			updateSignalStats(t)
		}
	}
	return nil
}

func main() {
	flag.Parse()
	defer glog.Flush()

	g := &singleflight.Group{}
	ph := prometheus.Handler()
	// Refresh data every prometheus poll.
	http.Handle("/metrics", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		// Only make one query to the cable modem if concurrent requests come in.
		if _, err := g.Do("get", func() (interface{}, error) {
			if err := get(); err != nil {
				return nil, err
			}
			return nil, nil
		}); err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
		ph.ServeHTTP(w, r)
	}))
	glog.Fatalf("Listener returned: %v", http.ListenAndServe(":"+strconv.Itoa(*port), nil))
}
<|endoftext|>