<commit_before>/*
	The mongo package is a very simple wrapper around the labix.org/v2/mgo
	package. It's purpose is to allow you to do CRUD operations with very
	little code. It's not exhaustive and not meant to do everything for you.
*/
package mongo

import (
	"labix.org/v2/mgo"
	"labix.org/v2/mgo/bson"

	"errors"
	"fmt"
	"reflect"
	"time"
)

var (
	mgoSession *mgo.Session
	servers    string
	database   string
	NoPtr      = errors.New("You must pass in a pointer")
)

// Set the mongo servers and the database
func SetServers(servers, db string) error {
	var err error

	database = db

	mgoSession, err = mgo.Dial(servers)
	return err
}

// Insert a single record. Must pass in a pointer to a struct. The struct must
// contain an Id field of type bson.ObjectId.
func Insert(records ...interface{}) error {
	for _, rec := range records {
		if !isPtr(rec) {
			return NoPtr
		}

		if err := addNewFields(rec); err != nil {
			return err
		}

		s, err := getMongoSession()
		if err != nil {
			return err
		}
		defer s.Close()

		coll := getColl(s, typeName(rec))
		err = coll.Insert(rec)
		if err != nil {
			return err
		}
	}

	return nil
}

// Find one or more records. If a single struct is passed in we'll return one record.
// If a slice is passed in all records will be returned. Must pass in a pointer to a
// struct or slice of structs.
func Find(i interface{}, q bson.M) error {
	if !isPtr(i) {
		return NoPtr
	}

	s, err := getMongoSession()
	if err != nil {
		return err
	}
	defer s.Close()

	coll := getColl(s, typeName(i))

	query := coll.Find(q)

	if isSlice(reflect.TypeOf(i)) {
		err = query.All(i)
	} else {
		err = query.One(i)
	}
	return err
}

// Find a single record by id. Must pass a pointer to a struct.
func FindById(i interface{}, id string) error {
	return Find(i, bson.M{"_id": id})
}

// Updates a record. Uses the Id to identify the record to update. Must pass in a pointer
// to a struct.
func Update(i interface{}) error {
	if !isPtr(i) {
		return NoPtr
	}

	err := addCurrentDateTime(i, "UpdatedAt")
	if err != nil {
		return err
	}

	s, err := getMongoSession()
	if err != nil {
		return err
	}
	defer s.Close()

	id, err := getObjIdFromStruct(i)
	if err != nil {
		return err
	}

	return getColl(s, typeName(i)).Update(bson.M{"_id": id}, i)
}

// Deletes a record. Uses the Id to identify the record to delete. Must pass in a pointer
// to a struct.
func Delete(i interface{}) error {
	if !isPtr(i) {
		return NoPtr
	}

	s, err := getMongoSession()
	if err != nil {
		return err
	}
	defer s.Close()

	id, err := getObjIdFromStruct(i)
	if err != nil {
		return err
	}

	return getColl(s, typeName(i)).RemoveId(id)
}

func getMongoSession() (*mgo.Session, error) {
	var err error

	if mgoSession == nil {
		mgoSession, err = mgo.Dial(servers)
		if err != nil {
			return nil, err
		}
	}

	return mgoSession.Clone(), nil
}

// We pass in the session because that is a clone of the original and the
// caller will need to close it when finished.
func getColl(session *mgo.Session, coll string) *mgo.Collection {
	return session.DB(database).C(coll)
}

func getObjIdFromStruct(i interface{}) (bson.ObjectId, error) {
	v := reflect.ValueOf(i)

	if v.Kind() == reflect.Ptr {
		v = v.Elem()
	}

	if v.Kind() != reflect.Struct {
		return bson.ObjectId(""), errors.New("Can't delete record. Type must be a struct.")
	}

	f := v.FieldByName("Id")
	if f.Kind() == reflect.Ptr {
		f = f.Elem()
	}

	return f.Interface().(bson.ObjectId), nil
}

func isPtr(i interface{}) bool {
	return reflect.ValueOf(i).Kind() == reflect.Ptr
}

func typeName(i interface{}) string {
	t := reflect.TypeOf(i)

	if t.Kind() == reflect.Ptr {
		t = t.Elem()
	}

	if isSlice(t) {
		t = t.Elem()

		if t.Kind() == reflect.Ptr {
			t = t.Elem()
		}
	}

	return t.Name()
}

// returns true if the interface is a slice
func isSlice(t reflect.Type) bool {
	if t.Kind() == reflect.Ptr {
		t = t.Elem()
	}
	return t.Kind() == reflect.Slice
}

func addNewFields(i interface{}) error {
	err := addId(i)
	if err != nil {
		return err
	}

	if err := addCurrentDateTime(i, "CreatedAt"); err != nil {
		return err
	}

	return addCurrentDateTime(i, "UpdatedAt")
}

func addCurrentDateTime(i interface{}, name string) error {
	if !hasStructField(i, name) {
		return nil
	}

	now := time.Now()

	v := reflect.ValueOf(i)
	if v.Kind() == reflect.Ptr {
		v = v.Elem()
	}

	f := v.FieldByName(name)
	if f.Kind() == reflect.Ptr {
		f = f.Elem()
	}

	if reflect.TypeOf(now) != f.Type() {
		return fmt.Errorf("%v must be time.Time type.", name)
	}

	if !f.CanSet() {
		return fmt.Errorf("Couldn't set time for field: %v", name)
	}

	f.Set(reflect.ValueOf(now))

	return nil
}

func hasStructField(i interface{}, field string) bool {
	t := reflect.TypeOf(i)
	if t.Kind() == reflect.Ptr {
		t = t.Elem()
	}

	if t.Kind() != reflect.Struct {
		return false
	}

	_, found := t.FieldByName(field)
	return found
}

func addId(i interface{}) error {
	v := reflect.ValueOf(i)

	if v.Kind() == reflect.Ptr {
		v = v.Elem()
	}

	if v.Kind() != reflect.Struct {
		return errors.New("Record must be a struct")
	}

	f := v.FieldByName("Id")
	if f.Kind() == reflect.Ptr {
		f = f.Elem()
	}

	if f.Kind() == reflect.String {
		if !f.Interface().(bson.ObjectId).Valid() {
			id := reflect.ValueOf(bson.NewObjectId())
			f.Set(id)
		}
	}

	return nil
}
<commit_msg>Expose GetMongoSession to allow more complicated queries.<commit_after>/*
	The mongo package is a very simple wrapper around the labix.org/v2/mgo
	package. It's purpose is to allow you to do CRUD operations with very
	little code. It's not exhaustive and not meant to do everything for you.
*/
package mongo

import (
	"labix.org/v2/mgo"
	"labix.org/v2/mgo/bson"

	"errors"
	"fmt"
	"reflect"
	"time"
)

var (
	mgoSession *mgo.Session
	servers    string
	database   string
	NoPtr      = errors.New("You must pass in a pointer")
)

// Set the mongo servers and the database
func SetServers(servers, db string) error {
	var err error

	database = db

	mgoSession, err = mgo.Dial(servers)
	return err
}

// Insert a single record. Must pass in a pointer to a struct. The struct must
// contain an Id field of type bson.ObjectId.
func Insert(records ...interface{}) error {
	for _, rec := range records {
		if !isPtr(rec) {
			return NoPtr
		}

		if err := addNewFields(rec); err != nil {
			return err
		}

		s, err := GetMongoSession()
		if err != nil {
			return err
		}
		defer s.Close()

		coll := getColl(s, typeName(rec))
		err = coll.Insert(rec)
		if err != nil {
			return err
		}
	}

	return nil
}

// Find one or more records. If a single struct is passed in we'll return one record.
// If a slice is passed in all records will be returned. Must pass in a pointer to a
// struct or slice of structs.
func Find(i interface{}, q bson.M) error {
	if !isPtr(i) {
		return NoPtr
	}

	s, err := GetMongoSession()
	if err != nil {
		return err
	}
	defer s.Close()

	coll := getColl(s, typeName(i))

	query := coll.Find(q)

	if isSlice(reflect.TypeOf(i)) {
		err = query.All(i)
	} else {
		err = query.One(i)
	}
	return err
}

// Find a single record by id. Must pass a pointer to a struct.
func FindById(i interface{}, id string) error {
	return Find(i, bson.M{"_id": id})
}

// Updates a record. Uses the Id to identify the record to update. Must pass in a pointer
// to a struct.
func Update(i interface{}) error {
	if !isPtr(i) {
		return NoPtr
	}

	err := addCurrentDateTime(i, "UpdatedAt")
	if err != nil {
		return err
	}

	s, err := GetMongoSession()
	if err != nil {
		return err
	}
	defer s.Close()

	id, err := getObjIdFromStruct(i)
	if err != nil {
		return err
	}

	return getColl(s, typeName(i)).Update(bson.M{"_id": id}, i)
}

// Deletes a record. Uses the Id to identify the record to delete. Must pass in a pointer
// to a struct.
func Delete(i interface{}) error {
	if !isPtr(i) {
		return NoPtr
	}

	s, err := GetMongoSession()
	if err != nil {
		return err
	}
	defer s.Close()

	id, err := getObjIdFromStruct(i)
	if err != nil {
		return err
	}

	return getColl(s, typeName(i)).RemoveId(id)
}

// Returns a Mongo session. You must call Session.Close() when you're done.
func GetMongoSession() (*mgo.Session, error) {
	var err error

	if mgoSession == nil {
		mgoSession, err = mgo.Dial(servers)
		if err != nil {
			return nil, err
		}
	}

	return mgoSession.Clone(), nil
}

// We pass in the session because that is a clone of the original and the
// caller will need to close it when finished.
func getColl(session *mgo.Session, coll string) *mgo.Collection {
	return session.DB(database).C(coll)
}

func getObjIdFromStruct(i interface{}) (bson.ObjectId, error) {
	v := reflect.ValueOf(i)

	if v.Kind() == reflect.Ptr {
		v = v.Elem()
	}

	if v.Kind() != reflect.Struct {
		return bson.ObjectId(""), errors.New("Can't delete record. Type must be a struct.")
	}

	f := v.FieldByName("Id")
	if f.Kind() == reflect.Ptr {
		f = f.Elem()
	}

	return f.Interface().(bson.ObjectId), nil
}

func isPtr(i interface{}) bool {
	return reflect.ValueOf(i).Kind() == reflect.Ptr
}

func typeName(i interface{}) string {
	t := reflect.TypeOf(i)

	if t.Kind() == reflect.Ptr {
		t = t.Elem()
	}

	if isSlice(t) {
		t = t.Elem()

		if t.Kind() == reflect.Ptr {
			t = t.Elem()
		}
	}

	return t.Name()
}

// returns true if the interface is a slice
func isSlice(t reflect.Type) bool {
	if t.Kind() == reflect.Ptr {
		t = t.Elem()
	}
	return t.Kind() == reflect.Slice
}

func addNewFields(i interface{}) error {
	err := addId(i)
	if err != nil {
		return err
	}

	if err := addCurrentDateTime(i, "CreatedAt"); err != nil {
		return err
	}

	return addCurrentDateTime(i, "UpdatedAt")
}

func addCurrentDateTime(i interface{}, name string) error {
	if !hasStructField(i, name) {
		return nil
	}

	now := time.Now()

	v := reflect.ValueOf(i)
	if v.Kind() == reflect.Ptr {
		v = v.Elem()
	}

	f := v.FieldByName(name)
	if f.Kind() == reflect.Ptr {
		f = f.Elem()
	}

	if reflect.TypeOf(now) != f.Type() {
		return fmt.Errorf("%v must be time.Time type.", name)
	}

	if !f.CanSet() {
		return fmt.Errorf("Couldn't set time for field: %v", name)
	}

	f.Set(reflect.ValueOf(now))

	return nil
}

func hasStructField(i interface{}, field string) bool {
	t := reflect.TypeOf(i)
	if t.Kind() == reflect.Ptr {
		t = t.Elem()
	}

	if t.Kind() != reflect.Struct {
		return false
	}

	_, found := t.FieldByName(field)
	return found
}

func addId(i interface{}) error {
	v := reflect.ValueOf(i)

	if v.Kind() == reflect.Ptr {
		v = v.Elem()
	}

	if v.Kind() != reflect.Struct {
		return errors.New("Record must be a struct")
	}

	f := v.FieldByName("Id")
	if f.Kind() == reflect.Ptr {
		f = f.Elem()
	}

	if f.Kind() == reflect.String {
		if !f.Interface().(bson.ObjectId).Valid() {
			id := reflect.ValueOf(bson.NewObjectId())
			f.Set(id)
		}
	}

	return nil
}
<|endoftext|>


<commit_before>// IronCache (cloud k/v store) client library
package cache

import (
	"bytes"
	"encoding/gob"
	"encoding/json"
	"fmt"
	"time"

	"github.com/iron-io/iron_go/api"
	"github.com/iron-io/iron_go/config"
)

var (
	JSON = Codec{Marshal: json.Marshal, Unmarshal: json.Unmarshal}
	Gob  = Codec{Marshal: gobMarshal, Unmarshal: gobUnmarshal}
)

type Cache struct {
	Settings config.Settings
	Name     string
}

type Item struct {
	// Value is the Item's value
	Value interface{}
	// Object is the Item's value for use with a Codec.
	Object interface{}
	// Number of seconds until expiration. The zero value defaults to 7 days,
	// maximum is 30 days.
	Expiration time.Duration
	// Caches item only if the key is currently cached.
	Replace bool
	// Caches item only if the key isn't currently cached.
	Add bool
}

// New returns a struct ready to make requests with.
// The cacheName argument is used as namespace.
func New(cacheName string) *Cache {
	return &Cache{Settings: config.Config("iron_cache"), Name: cacheName}
}

func (c *Cache) caches(suffix ...string) *api.URL {
	return api.Action(c.Settings, "caches", suffix...)
}

func (c *Cache) ListCaches(page, perPage int) (caches []*Cache, err error) {
	out := []struct {
		Project_id string
		Name       string
	}{}

	err = c.caches().
		QueryAdd("page", "%d", page).
		QueryAdd("per_page", "%d", perPage).
		Req("GET", nil, &out)
	if err != nil {
		return
	}

	caches = make([]*Cache, 0, len(out))
	for _, item := range out {
		caches = append(caches, &Cache{
			Settings: c.Settings,
			Name:     item.Name,
		})
	}

	return
}

func (c *Cache) ServerVersion() (version string, err error) {
	out := map[string]string{}
	err = api.VersionAction(c.Settings).Req("GET", nil, &out)
	if err != nil {
		return
	}
	return out["version"], nil
}

func (c *Cache) Clear() (err error) {
	return c.caches(c.Name, "clear").Req("POST", nil, nil)
}

// Put adds an Item to the cache, overwriting any existing key of the same name.
func (c *Cache) Put(key string, item *Item) (err error) {
	in := struct {
		Body      interface{} `json:"body"`
		ExpiresIn int         `json:"expires_in,omitempty"`
		Replace   bool        `json:"replace,omitempty"`
		Add       bool        `json:"add,omitempty"`
	}{
		Body:      item.Value,
		ExpiresIn: int(item.Expiration.Seconds()),
		Replace:   item.Replace,
		Add:       item.Add,
	}

	return c.caches(c.Name, "items", key).Req("PUT", &in, nil)
}

func anyToString(value interface{}) (str interface{}, err error) {
	switch v := value.(type) {
	case string:
		str = v
	case uint, uint8, uint16, uint32, uint64, int, int8, int16, int32, int64:
		str = v
	case float32, float64:
		str = v
	case bool:
		str = v
	case fmt.Stringer:
		str = v.String()
	default:
		var bytes []byte
		if bytes, err = json.Marshal(value); err == nil {
			str = string(bytes)
		}
	}

	return
}

func (c *Cache) Set(key string, value interface{}, ttl ...int) (err error) {
	str, err := anyToString(value)
	if err == nil {
		if len(ttl) > 0 {
			err = c.Put(key, &Item{Value: str, Expiration: time.Duration(ttl[0]) * time.Second})
		} else {
			err = c.Put(key, &Item{Value: str})
		}
	}
	return
}
func (c *Cache) Add(key string, value ...interface{}) (err error) {
	str, err := anyToString(value)
	if err == nil {
		err = c.Put(key, &Item{
			Value: str, Expiration: time.Duration(123) * time.Second, Add: true,
		})
	}
	return
}
func (c *Cache) Replace(key string, value ...interface{}) (err error) {
	str, err := anyToString(value)
	if err == nil {
		err = c.Put(key, &Item{
			Value: str, Expiration: time.Duration(123) * time.Second, Replace: true,
		})
	}
	return
}

// Increment increments the corresponding item's value.
func (c *Cache) Increment(key string, amount int64) (err error) {
	in := map[string]int64{"amount": amount}
	return c.caches(c.Name, "items", key, "increment").Req("POST", &in, nil)
}

// Get gets an item from the cache.
func (c *Cache) Get(key string) (value interface{}, err error) {
	out := struct {
		Cache string      `json:"cache"`
		Key   string      `json:"key"`
		Value interface{} `json:"value"`
	}{}
	if err = c.caches(c.Name, "items", key).Req("GET", nil, &out); err == nil {
		value = out.Value
	}
	return
}

func (c *Cache) GetMeta(key string) (value map[string]interface{}, err error) {
	value = map[string]interface{}{}
	err = c.caches(c.Name, "items", key).Req("GET", nil, &value)
	return
}

// Delete removes an item from the cache.
func (c *Cache) Delete(key string) (err error) {
	return c.caches(c.Name, "items", key).Req("DELETE", nil, nil)
}

type Codec struct {
	Marshal   func(interface{}) ([]byte, error)
	Unmarshal func([]byte, interface{}) error
}

func (cd Codec) Put(c *Cache, key string, item *Item) (err error) {
	bytes, err := cd.Marshal(item.Object)
	if err != nil {
		return
	}

	item.Value = string(bytes)

	return c.Put(key, item)
}

func (cd Codec) Get(c *Cache, key string, object interface{}) (err error) {
	value, err := c.Get(key)
	if err != nil {
		return
	}

	err = cd.Unmarshal([]byte(value.(string)), object)
	if err != nil {
		return
	}

	return
}

func gobMarshal(v interface{}) ([]byte, error) {
	writer := bytes.Buffer{}
	enc := gob.NewEncoder(&writer)
	err := enc.Encode(v)
	return writer.Bytes(), err
}

func gobUnmarshal(marshalled []byte, v interface{}) error {
	reader := bytes.NewBuffer(marshalled)
	dec := gob.NewDecoder(reader)
	return dec.Decode(v)
}
<commit_msg>Added response value to Increment call<commit_after>// IronCache (cloud k/v store) client library
package cache

import (
	"bytes"
	"encoding/gob"
	"encoding/json"
	"fmt"
	"time"

	"github.com/iron-io/iron_go/api"
	"github.com/iron-io/iron_go/config"
)

var (
	JSON = Codec{Marshal: json.Marshal, Unmarshal: json.Unmarshal}
	Gob  = Codec{Marshal: gobMarshal, Unmarshal: gobUnmarshal}
)

type Cache struct {
	Settings config.Settings
	Name     string
}

type Item struct {
	// Value is the Item's value
	Value interface{}
	// Object is the Item's value for use with a Codec.
	Object interface{}
	// Number of seconds until expiration. The zero value defaults to 7 days,
	// maximum is 30 days.
	Expiration time.Duration
	// Caches item only if the key is currently cached.
	Replace bool
	// Caches item only if the key isn't currently cached.
	Add bool
}

// New returns a struct ready to make requests with.
// The cacheName argument is used as namespace.
func New(cacheName string) *Cache {
	return &Cache{Settings: config.Config("iron_cache"), Name: cacheName}
}

func (c *Cache) caches(suffix ...string) *api.URL {
	return api.Action(c.Settings, "caches", suffix...)
}

func (c *Cache) ListCaches(page, perPage int) (caches []*Cache, err error) {
	out := []struct {
		Project_id string
		Name       string
	}{}

	err = c.caches().
		QueryAdd("page", "%d", page).
		QueryAdd("per_page", "%d", perPage).
		Req("GET", nil, &out)
	if err != nil {
		return
	}

	caches = make([]*Cache, 0, len(out))
	for _, item := range out {
		caches = append(caches, &Cache{
			Settings: c.Settings,
			Name:     item.Name,
		})
	}

	return
}

func (c *Cache) ServerVersion() (version string, err error) {
	out := map[string]string{}
	err = api.VersionAction(c.Settings).Req("GET", nil, &out)
	if err != nil {
		return
	}
	return out["version"], nil
}

func (c *Cache) Clear() (err error) {
	return c.caches(c.Name, "clear").Req("POST", nil, nil)
}

// Put adds an Item to the cache, overwriting any existing key of the same name.
func (c *Cache) Put(key string, item *Item) (err error) {
	in := struct {
		Body      interface{} `json:"body"`
		ExpiresIn int         `json:"expires_in,omitempty"`
		Replace   bool        `json:"replace,omitempty"`
		Add       bool        `json:"add,omitempty"`
	}{
		Body:      item.Value,
		ExpiresIn: int(item.Expiration.Seconds()),
		Replace:   item.Replace,
		Add:       item.Add,
	}

	return c.caches(c.Name, "items", key).Req("PUT", &in, nil)
}

func anyToString(value interface{}) (str interface{}, err error) {
	switch v := value.(type) {
	case string:
		str = v
	case uint, uint8, uint16, uint32, uint64, int, int8, int16, int32, int64:
		str = v
	case float32, float64:
		str = v
	case bool:
		str = v
	case fmt.Stringer:
		str = v.String()
	default:
		var bytes []byte
		if bytes, err = json.Marshal(value); err == nil {
			str = string(bytes)
		}
	}

	return
}

func (c *Cache) Set(key string, value interface{}, ttl ...int) (err error) {
	str, err := anyToString(value)
	if err == nil {
		if len(ttl) > 0 {
			err = c.Put(key, &Item{Value: str, Expiration: time.Duration(ttl[0]) * time.Second})
		} else {
			err = c.Put(key, &Item{Value: str})
		}
	}
	return
}
func (c *Cache) Add(key string, value ...interface{}) (err error) {
	str, err := anyToString(value)
	if err == nil {
		err = c.Put(key, &Item{
			Value: str, Expiration: time.Duration(123) * time.Second, Add: true,
		})
	}
	return
}
func (c *Cache) Replace(key string, value ...interface{}) (err error) {
	str, err := anyToString(value)
	if err == nil {
		err = c.Put(key, &Item{
			Value: str, Expiration: time.Duration(123) * time.Second, Replace: true,
		})
	}
	return
}

// Increment increments the corresponding item's value.
func (c *Cache) Increment(key string, amount int64) (value interface{}, err error) {
	in := map[string]int64{"amount": amount}

	out := struct {
		Message string      `json:"msg"`
		Value   interface{} `json:"value"`
	}{}
	if err = c.caches(c.Name, "items", key, "increment").Req("POST", &in, &out); err == nil {
		value = out.Value
	}
	return
}

// Get gets an item from the cache.
func (c *Cache) Get(key string) (value interface{}, err error) {
	out := struct {
		Cache string      `json:"cache"`
		Key   string      `json:"key"`
		Value interface{} `json:"value"`
	}{}
	if err = c.caches(c.Name, "items", key).Req("GET", nil, &out); err == nil {
		value = out.Value
	}
	return
}

func (c *Cache) GetMeta(key string) (value map[string]interface{}, err error) {
	value = map[string]interface{}{}
	err = c.caches(c.Name, "items", key).Req("GET", nil, &value)
	return
}

// Delete removes an item from the cache.
func (c *Cache) Delete(key string) (err error) {
	return c.caches(c.Name, "items", key).Req("DELETE", nil, nil)
}

type Codec struct {
	Marshal   func(interface{}) ([]byte, error)
	Unmarshal func([]byte, interface{}) error
}

func (cd Codec) Put(c *Cache, key string, item *Item) (err error) {
	bytes, err := cd.Marshal(item.Object)
	if err != nil {
		return
	}

	item.Value = string(bytes)

	return c.Put(key, item)
}

func (cd Codec) Get(c *Cache, key string, object interface{}) (err error) {
	value, err := c.Get(key)
	if err != nil {
		return
	}

	err = cd.Unmarshal([]byte(value.(string)), object)
	if err != nil {
		return
	}

	return
}

func gobMarshal(v interface{}) ([]byte, error) {
	writer := bytes.Buffer{}
	enc := gob.NewEncoder(&writer)
	err := enc.Encode(v)
	return writer.Bytes(), err
}

func gobUnmarshal(marshalled []byte, v interface{}) error {
	reader := bytes.NewBuffer(marshalled)
	dec := gob.NewDecoder(reader)
	return dec.Decode(v)
}
<|endoftext|>


<commit_before>// Copyright 2017 Corey Scott http://www.sage42.org/
//
// 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 cache

import (
	"context"
	"encoding"
	"sync/atomic"
	"time"
)

// Client defines a cache instance.
//
// This can represent the cache for the entire system or for a particular use-case/type.
//
// If a cache is used for multiple purposes, then care must be taken to ensure uniqueness of cache keys.
//
// It is not recommended to change this struct's member data after creation as a data race will likely ensue.
type Client struct {
	// Storage is the cache storage scheme. (Required)
	Storage Storage

	// Logger defines a logger to used for errors during async cache writes (optional)
	Logger Logger

	// Metrics allow for tracking cache events (hit/miss/etc) (optional)
	Metrics Metrics

	// WriteTimeout is the max time spent waiting for cache writes to complete (optional - default 3 seconds)
	WriteTimeout time.Duration

	// track pending cache writes
	pendingWrites int64
}

// Get attempts to retrieve the value from cache and when it misses will run the builder func to create the value.
//
// It will asynchronously update/save the value in the cache on after a successful builder run
func (c *Client) Get(ctx context.Context, key string, dest BinaryEncoder, builder Builder) error {
	bytes, err := c.Storage.Get(ctx, key)
	if err != nil {
		if err == ErrCacheMiss {
			c.getMetrics().Track(CacheMiss)
		} else {
			c.getLogger().Log("cache error: %s", err)
			c.getMetrics().Track(CacheGetError)
		}

		// attempt to fulfill the request on miss and error by calling the builder
		//
		// NOTE: this means that if the cache is misconfigured/down the "build" will still happen (as if it was cache miss)
		return c.onCacheMiss(ctx, key, dest, builder)
	}

	return c.onCacheHit(dest, bytes)
}

func (c *Client) onCacheMiss(ctx context.Context, key string, dest BinaryEncoder, builder Builder) error {
	err := builder.Build(ctx, key, dest)
	if err != nil {
		c.getMetrics().Track(CacheLambdaError)
		return err
	}

	atomic.AddInt64(&c.pendingWrites, 1)
	go c.updateCache(key, dest)

	return err
}

func (c *Client) onCacheHit(dest encoding.BinaryUnmarshaler, bytes []byte) error {
	err := dest.UnmarshalBinary(bytes)
	if err != nil {
		c.getMetrics().Track(CacheUnmarshalError)
		return err
	}

	c.getMetrics().Track(CacheHit)
	return nil
}

// update the cache with the supplied key/value pair
func (c *Client) updateCache(key string, val encoding.BinaryMarshaler) {
	defer func() {
		// update tracking
		atomic.AddInt64(&c.pendingWrites, -1)
	}()

	// use independent context so we don't miss cache updated
	ctx, cancelFn := context.WithTimeout(context.Background(), c.getWriteTimeout())
	defer cancelFn()

	bytes, err := val.MarshalBinary()
	if err != nil {
		c.getMetrics().Track(CacheMarshalError)
		c.getLogger().Log("failed marshal '%s' from cache with err: %s", key, err)
		return
	}

	err = c.Storage.Set(ctx, key, bytes)
	if err != nil {
		c.getMetrics().Track(CacheSetError)
		c.getLogger().Log("failed to update item '%s' in cache with err: %s", key, err)
	}
}

// Invalidate will force invalidate any matching key in the cache
func (c *Client) Invalidate(ctx context.Context, key string) error {
	err := c.Storage.Invalidate(ctx, key)
	if err != nil {
		c.getMetrics().Track(CacheInvalidateError)
		return err
	}

	return nil
}

// return the supplied logger or a no-op implementation
func (c *Client) getLogger() Logger {
	if c.Logger != nil {
		return c.Logger
	}

	return noopLogger
}

// return the supplied metric tracker or a no-op implementation
func (c *Client) getMetrics() Metrics {
	if c.Metrics != nil {
		return c.Metrics
	}

	return noopMetrics
}

// return the timeout on cache writes
func (c *Client) getWriteTimeout() time.Duration {
	if int64(c.WriteTimeout) > 0 {
		return c.WriteTimeout
	}

	return 3 * time.Second
}

// Builder builds the data for a key
type Builder interface {
	// Build returns the data for the supplied key by populating dest
	Build(ctx context.Context, key string, dest BinaryEncoder) error
}

// BuilderFunc implements Builder as a function
type BuilderFunc func(ctx context.Context, key string, dest BinaryEncoder) error

// Build implements Builder
func (b BuilderFunc) Build(ctx context.Context, key string, dest BinaryEncoder) error {
	return b(ctx, key, dest)
}

// BinaryEncoder encodes/decodes the receiver to and from binary form
type BinaryEncoder interface {
	encoding.BinaryMarshaler
	encoding.BinaryUnmarshaler
}
<commit_msg>add more logging<commit_after>// Copyright 2017 Corey Scott http://www.sage42.org/
//
// 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 cache

import (
	"context"
	"encoding"
	"sync/atomic"
	"time"
)

// Client defines a cache instance.
//
// This can represent the cache for the entire system or for a particular use-case/type.
//
// If a cache is used for multiple purposes, then care must be taken to ensure uniqueness of cache keys.
//
// It is not recommended to change this struct's member data after creation as a data race will likely ensue.
type Client struct {
	// Storage is the cache storage scheme. (Required)
	Storage Storage

	// Logger defines a logger to used for errors during async cache writes (optional)
	Logger Logger

	// Metrics allow for tracking cache events (hit/miss/etc) (optional)
	Metrics Metrics

	// WriteTimeout is the max time spent waiting for cache writes to complete (optional - default 3 seconds)
	WriteTimeout time.Duration

	// track pending cache writes
	pendingWrites int64
}

// Get attempts to retrieve the value from cache and when it misses will run the builder func to create the value.
//
// It will asynchronously update/save the value in the cache on after a successful builder run
func (c *Client) Get(ctx context.Context, key string, dest BinaryEncoder, builder Builder) error {
	bytes, err := c.Storage.Get(ctx, key)
	if err != nil {
		if err == ErrCacheMiss {
			c.getMetrics().Track(CacheMiss)
		} else {
			c.getLogger().Log("cache get error. key: '%s' error: %s", key, err)
			c.getMetrics().Track(CacheGetError)
		}

		// attempt to fulfill the request on miss and error by calling the builder
		//
		// NOTE: this means that if the cache is misconfigured/down the "build" will still happen (as if it was cache miss)
		return c.onCacheMiss(ctx, key, dest, builder)
	}

	return c.onCacheHit(dest, bytes)
}

func (c *Client) onCacheMiss(ctx context.Context, key string, dest BinaryEncoder, builder Builder) error {
	err := builder.Build(ctx, key, dest)
	if err != nil {
		c.getLogger().Log("cache miss build error. key: '%s' error: %s", key, err)
		c.getMetrics().Track(CacheLambdaError)
		return err
	}

	atomic.AddInt64(&c.pendingWrites, 1)
	go c.updateCache(key, dest)

	return err
}

func (c *Client) onCacheHit(dest encoding.BinaryUnmarshaler, bytes []byte) error {
	err := dest.UnmarshalBinary(bytes)
	if err != nil {
		c.getLogger().Log("cache hit unmarshal error. error: %s", err)
		c.getMetrics().Track(CacheUnmarshalError)
		return err
	}

	c.getMetrics().Track(CacheHit)
	return nil
}

// update the cache with the supplied key/value pair
func (c *Client) updateCache(key string, val encoding.BinaryMarshaler) {
	defer func() {
		// update tracking
		atomic.AddInt64(&c.pendingWrites, -1)
	}()

	// use independent context so we don't miss cache updated
	ctx, cancelFn := context.WithTimeout(context.Background(), c.getWriteTimeout())
	defer cancelFn()

	bytes, err := val.MarshalBinary()
	if err != nil {
		c.getLogger().Log("cache update marshal error. key: '%s' error: %s", key, err)
		c.getMetrics().Track(CacheMarshalError)
		return
	}

	err = c.Storage.Set(ctx, key, bytes)
	if err != nil {
		c.getLogger().Log("cache update set error. key: '%s' error: %s", key, err)
		c.getMetrics().Track(CacheSetError)
	}
}

// Invalidate will force invalidate any matching key in the cache
func (c *Client) Invalidate(ctx context.Context, key string) error {
	err := c.Storage.Invalidate(ctx, key)
	if err != nil {
		c.getLogger().Log("cache invalidate error. key: '%s' error: %s", key, err)
		c.getMetrics().Track(CacheInvalidateError)
		return err
	}

	return nil
}

// return the supplied logger or a no-op implementation
func (c *Client) getLogger() Logger {
	if c.Logger != nil {
		return c.Logger
	}

	return noopLogger
}

// return the supplied metric tracker or a no-op implementation
func (c *Client) getMetrics() Metrics {
	if c.Metrics != nil {
		return c.Metrics
	}

	return noopMetrics
}

// return the timeout on cache writes
func (c *Client) getWriteTimeout() time.Duration {
	if int64(c.WriteTimeout) > 0 {
		return c.WriteTimeout
	}

	return 3 * time.Second
}

// Builder builds the data for a key
type Builder interface {
	// Build returns the data for the supplied key by populating dest
	Build(ctx context.Context, key string, dest BinaryEncoder) error
}

// BuilderFunc implements Builder as a function
type BuilderFunc func(ctx context.Context, key string, dest BinaryEncoder) error

// Build implements Builder
func (b BuilderFunc) Build(ctx context.Context, key string, dest BinaryEncoder) error {
	return b(ctx, key, dest)
}

// BinaryEncoder encodes/decodes the receiver to and from binary form
type BinaryEncoder interface {
	encoding.BinaryMarshaler
	encoding.BinaryUnmarshaler
}
<|endoftext|>


<commit_before>package caddy

import (
	"net/http"

	"github.com/mholt/caddy"
	"github.com/mholt/caddy/caddyhttp/httpserver"

	"github.com/txtdirect/txtdirect"
)

func init() {
	caddy.RegisterPlugin("txtdirect", caddy.Plugin{
		ServerType: "http",
		Action:     setup,
	})
}

var allOptions = []string{"host", "gometa"}

func setup(c *caddy.Controller) error {
	var enable []string
	c.Next() // skip directive name
	for c.NextBlock() {
		option := c.Val()
		switch option {
		case "enable":
			if enable != nil {
				return c.ArgErr()
			}
			enable = c.RemainingArgs()
		case "disable":
			if enable != nil {
				return c.ArgErr()
			}
			enable = removeArrayFromArray(allOptions, c.RemainingArgs())
		default:
			return c.ArgErr() // unhandled option
		}
	}

	// If nothing is specified, enable everything
	if enable == nil {
		enable = allOptions
	}

	// Add handler to Caddy
	cfg := httpserver.GetConfig(c)
	mid := func(next httpserver.Handler) httpserver.Handler {
		return Redirect{
			Next:   next,
			Enable: enable,
		}
	}
	cfg.AddMiddleware(mid)
	return nil
}

func removeArrayFromArray(array, toBeRemoved []string) []string {
	for _, toRemove := range toBeRemoved {
		for i, option := range array {
			if option == toRemove {
				array[i] = array[len(array)-1]
				array = array[:len(array)-1]
				break
			}
		}
	}
	return array
}

// Redirect is middleware to redirect requests based on TXT records
type Redirect struct {
	Next   httpserver.Handler
	Enable []string
}

func (rd Redirect) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
	if err := txtdirect.Redirect(w, r, rd.Enable); err != nil {
		return http.StatusInternalServerError, err
	}
	return 0, nil
}
<commit_msg>Throw error is arg list is empty<commit_after>package caddy

import (
	"net/http"

	"github.com/mholt/caddy"
	"github.com/mholt/caddy/caddyhttp/httpserver"

	"github.com/txtdirect/txtdirect"
)

func init() {
	caddy.RegisterPlugin("txtdirect", caddy.Plugin{
		ServerType: "http",
		Action:     setup,
	})
}

var allOptions = []string{"host", "gometa"}

func setup(c *caddy.Controller) error {
	var enable []string
	c.Next() // skip directive name
	for c.NextBlock() {
		option := c.Val()
		switch option {
		case "enable":
			if enable != nil {
				return c.ArgErr()
			}
			enable = c.RemainingArgs()
			if len(enable) == 0 {
				return c.ArgErr()
			}

		case "disable":
			if enable != nil {
				return c.ArgErr()
			}
			toDisable := c.RemainingArgs()
			if len(toDisable) == 0 {
				return c.ArgErr()
			}
			enable = removeArrayFromArray(allOptions, toDisable)

		default:
			return c.ArgErr() // unhandled option
		}
	}

	// If nothing is specified, enable everything
	if enable == nil {
		enable = allOptions
	}

	// Add handler to Caddy
	cfg := httpserver.GetConfig(c)
	mid := func(next httpserver.Handler) httpserver.Handler {
		return Redirect{
			Next:   next,
			Enable: enable,
		}
	}
	cfg.AddMiddleware(mid)
	return nil
}

func removeArrayFromArray(array, toBeRemoved []string) []string {
	for _, toRemove := range toBeRemoved {
		for i, option := range array {
			if option == toRemove {
				array[i] = array[len(array)-1]
				array = array[:len(array)-1]
				break
			}
		}
	}
	return array
}

// Redirect is middleware to redirect requests based on TXT records
type Redirect struct {
	Next   httpserver.Handler
	Enable []string
}

func (rd Redirect) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
	if err := txtdirect.Redirect(w, r, rd.Enable); err != nil {
		return http.StatusInternalServerError, err
	}
	return 0, nil
}
<|endoftext|>


<commit_before>package ui

import (
	"fmt"
	"unicode/utf8"
)

const spaceLength = 60

func ShowMenu(version, host, port string) {
	fmt.Printf(`
 ┌%s┐
 │%s│
 │%s│
 └%s┘
  `,
		fillSpace("", "─"),
		fillSpace(fmt.Sprintf("CHAOS (%s)", version), " "),
		fillSpace(host+":"+port, " "),
		fillSpace("", "─"),
	)
}

func fillSpace(content, filler string) string {
	spaceToFillSize := spaceLength - utf8.RuneCountInString(content)
	spaceBothSide := spaceToFillSize / 2

	var finalStr string
	for i := 0; i < spaceBothSide; i++ {
		finalStr += filler
	}
	finalStr += content
	for i := 0; i < spaceBothSide; i++ {
		finalStr += filler
	}

	finalStrCount := utf8.RuneCountInString(finalStr)
	if finalStrCount < spaceLength {
		diff := spaceLength - finalStrCount
		for i := 0; i < diff; i++ {
			finalStr += filler
		}
	}
	return finalStr
}
<commit_msg>update client menu<commit_after>package ui

import (
	"fmt"
	"unicode/utf8"
)

const spaceLength = 60

func ShowMenu(version, host, port string) {
	if len(port) > 0 {
		port = fmt.Sprint(":", port)
	}

	fmt.Printf(`
 ┌%s┐
 │%s│
 │%s│
 └%s┘
  `,
		fillSpace("", "─"),
		fillSpace(fmt.Sprintf("CHAOS (%s)", version), " "),
		fillSpace(host+port, " "),
		fillSpace("", "─"),
	)
}

func fillSpace(content, filler string) string {
	spaceToFillSize := spaceLength - utf8.RuneCountInString(content)
	spaceBothSide := spaceToFillSize / 2

	var finalStr string
	for i := 0; i < spaceBothSide; i++ {
		finalStr += filler
	}
	finalStr += content
	for i := 0; i < spaceBothSide; i++ {
		finalStr += filler
	}

	finalStrCount := utf8.RuneCountInString(finalStr)
	if finalStrCount < spaceLength {
		diff := spaceLength - finalStrCount
		for i := 0; i < diff; i++ {
			finalStr += filler
		}
	}
	return finalStr
}
<|endoftext|>


<commit_before>// Copyright (c) 2017 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

package msgpack

import (
	"errors"
	"sync"

	"github.com/m3db/m3cluster/services"
	"github.com/m3db/m3metrics/protocol/msgpack"
	"github.com/m3db/m3x/log"

	"github.com/uber-go/tally"
)

var (
	errInstanceQueueClosed = errors.New("instance queue is closed")
	errWriterQueueFull     = errors.New("writer queue is full")
)

// instanceQueue processes write requests for given instance.
type instanceQueue interface {
	// Enqueue enqueues a data buffer.
	Enqueue(buf msgpack.Buffer) error

	// Close closes the queue.
	Close() error
}

type writeFn func([]byte) error

type queue struct {
	sync.RWMutex

	log      xlog.Logger
	metrics  queueMetrics
	instance services.PlacementInstance
	conn     *connection
	bufCh    chan msgpack.Buffer
	closed   bool

	writeFn writeFn
}

func newInstanceQueue(instance services.PlacementInstance, opts ServerOptions) instanceQueue {
	conn := newConnection(instance.Endpoint(), opts.ConnectionOptions())
	iOpts := opts.InstrumentOptions()
	q := &queue{
		log:      iOpts.Logger(),
		metrics:  newQueueMetrics(iOpts.MetricsScope()),
		instance: instance,
		conn:     conn,
		bufCh:    make(chan msgpack.Buffer, opts.InstanceQueueSize()),
	}
	q.writeFn = q.conn.Write

	go q.drain()
	return q
}

func (q *queue) Enqueue(buf msgpack.Buffer) error {
	q.RLock()
	if q.closed {
		q.RUnlock()
		q.metrics.queueClosedErrors.Inc(1)
		return errInstanceQueueClosed
	}
	// NB(xichen): the buffer already batches multiple metric buf points
	// to maximize per packet utilization so there should be no need to perform
	// additional batching here.
	select {
	case q.bufCh <- buf:
	default:
		q.RUnlock()

		// Close the buffer so it's resources are freed.
		buf.Close()

		q.metrics.queueFullErrors.Inc(1)
		return errWriterQueueFull
	}
	q.RUnlock()
	return nil
}

func (q *queue) Close() error {
	q.Lock()
	defer q.Unlock()

	if q.closed {
		return errInstanceQueueClosed
	}
	q.closed = true
	close(q.bufCh)
	return nil
}

func (q *queue) drain() {
	for buf := range q.bufCh {
		if err := q.writeFn(buf.Bytes()); err != nil {
			q.log.WithFields(
				xlog.NewLogField("instance", q.instance.Endpoint()),
				xlog.NewLogErrField(err),
			).Error("write data error")
			q.metrics.connWriteErrors.Inc(1)
		}
		buf.Close()
	}
	q.conn.Close()
}

type queueMetrics struct {
	queueClosedErrors tally.Counter
	queueFullErrors   tally.Counter
	connWriteErrors   tally.Counter
}

func newQueueMetrics(s tally.Scope) queueMetrics {
	return queueMetrics{
		queueClosedErrors: s.Tagged(
			map[string]string{"error-type": "queue-closed", "action": "enqueue"},
		).Counter("errors"),
		queueFullErrors: s.Tagged(
			map[string]string{"error-type": "queue-full", "action": "enqueue"},
		).Counter("errors"),
		connWriteErrors: s.Tagged(
			map[string]string{"error-type": "conn-write", "action": "drain"},
		).Counter("errors"),
	}
}
<commit_msg>[queue] Add queue size metrics per instance (#23)<commit_after>// Copyright (c) 2017 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

package msgpack

import (
	"errors"
	"sync"
	"time"

	"github.com/m3db/m3cluster/services"
	"github.com/m3db/m3metrics/protocol/msgpack"
	"github.com/m3db/m3x/log"

	"github.com/uber-go/tally"
)

const (
	defaultQueueSizeNumBuckets = 10
)

var (
	errInstanceQueueClosed = errors.New("instance queue is closed")
	errWriterQueueFull     = errors.New("writer queue is full")
)

// instanceQueue processes write requests for given instance.
type instanceQueue interface {
	// Enqueue enqueues a data buffer.
	Enqueue(buf msgpack.Buffer) error

	// Close closes the queue.
	Close() error
}

type writeFn func([]byte) error

type queue struct {
	sync.RWMutex

	log      xlog.Logger
	metrics  queueMetrics
	instance services.PlacementInstance
	conn     *connection
	bufCh    chan msgpack.Buffer
	doneCh   chan struct{}
	closed   bool

	writeFn writeFn
}

func newInstanceQueue(instance services.PlacementInstance, opts ServerOptions) instanceQueue {
	var (
		conn      = newConnection(instance.Endpoint(), opts.ConnectionOptions())
		iOpts     = opts.InstrumentOptions()
		queueSize = opts.InstanceQueueSize()
	)
	q := &queue{
		log:      iOpts.Logger(),
		metrics:  newQueueMetrics(iOpts.MetricsScope(), instance.ID(), queueSize),
		instance: instance,
		conn:     conn,
		bufCh:    make(chan msgpack.Buffer, queueSize),
		doneCh:   make(chan struct{}),
	}
	q.writeFn = q.conn.Write

	go q.drain()
	go q.reportQueueSize(iOpts.ReportInterval())
	return q
}

func (q *queue) Enqueue(buf msgpack.Buffer) error {
	q.RLock()
	if q.closed {
		q.RUnlock()
		q.metrics.queueClosedErrors.Inc(1)
		return errInstanceQueueClosed
	}
	// NB(xichen): the buffer already batches multiple metric buf points
	// to maximize per packet utilization so there should be no need to perform
	// additional batching here.
	select {
	case q.bufCh <- buf:
	default:
		q.RUnlock()

		// Close the buffer so it's resources are freed.
		buf.Close()

		q.metrics.queueFullErrors.Inc(1)
		return errWriterQueueFull
	}
	q.RUnlock()
	return nil
}

func (q *queue) Close() error {
	q.Lock()
	defer q.Unlock()

	if q.closed {
		return errInstanceQueueClosed
	}
	q.closed = true
	close(q.doneCh)
	close(q.bufCh)
	return nil
}

func (q *queue) drain() {
	for buf := range q.bufCh {
		if err := q.writeFn(buf.Bytes()); err != nil {
			q.log.WithFields(
				xlog.NewLogField("instance", q.instance.Endpoint()),
				xlog.NewLogErrField(err),
			).Error("write data error")
			q.metrics.connWriteErrors.Inc(1)
		}
		buf.Close()
	}
	q.conn.Close()
}

func (q *queue) reportQueueSize(reportInterval time.Duration) {
	ticker := time.NewTicker(reportInterval)
	defer ticker.Stop()

	for {
		select {
		case <-ticker.C:
			q.metrics.queueSize.RecordValue(float64(len(q.bufCh)))
		case <-q.doneCh:
			return
		}
	}
}

type queueMetrics struct {
	queueSize         tally.Histogram
	queueClosedErrors tally.Counter
	queueFullErrors   tally.Counter
	connWriteErrors   tally.Counter
}

func newQueueMetrics(s tally.Scope, instanceID string, queueSize int) queueMetrics {
	numBuckets := defaultQueueSizeNumBuckets
	if queueSize < numBuckets {
		numBuckets = queueSize
	}
	buckets := tally.MustMakeLinearValueBuckets(0, float64(queueSize/numBuckets), numBuckets)
	return queueMetrics{
		queueSize: s.Tagged(
			map[string]string{"instance-id": instanceID},
		).Histogram("queue-size", buckets),
		queueClosedErrors: s.Tagged(
			map[string]string{"error-type": "queue-closed", "action": "enqueue"},
		).Counter("errors"),
		queueFullErrors: s.Tagged(
			map[string]string{"error-type": "queue-full", "action": "enqueue"},
		).Counter("errors"),
		connWriteErrors: s.Tagged(
			map[string]string{"error-type": "conn-write", "action": "drain"},
		).Counter("errors"),
	}
}
<|endoftext|>


<commit_before>package packager

import (
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
)

import (
	// data "butler/data"
	fileutil "butler/fileutil"
	proto "code.google.com/p/goprotobuf/proto"
	tp "tritium/proto"
	yaml "goyaml"
)

type Packager struct {
	MixerDir      string
	IncludePaths  []string
	IsTransformer bool
	Dependencies  map[string]string
	*tp.Mixer
}

const (
	PACKAGER_VERSION = 1
	LIB_DIR          = "lib"
	SCRIPTS_DIR      = "scripts"
	ENTRY_FILE       = "main.ts"
	DEPS_FILE        = "dependencies.yml"
	MIXER_DIR        = "mixers"
)

func New(relSrcDir string) *Packager {
	pkgr := new(Packager)

	wd, wdErr := os.Getwd()
	if wdErr != nil {
		panic("unable to determine current directory for mixer creation")
	}

	absSrcDir, absErr := filepath.Abs(relSrcDir)
	if absErr != nil {
		panic("unable to absolutize mixer source directory for mixer creation")
	}

	pkgr.MixerDir        = absSrcDir
	pkgr.IncludePaths    = make([]string, 1)
	pkgr.IncludePaths[0] = wd

	pkgr.Mixer           = tp.NewMixer(absSrcDir)
	pkgr.PackagerVersion = proto.Int32(1)
	pkgr.ReadDependenciesFile()

	pkgr.Mixer.Package      = new(tp.Package)
	pkgr.Mixer.Package.Name = proto.String(pkgr.Mixer.GetName())
	pkgr.Mixer.Package.Path = proto.String(pkgr.MixerDir)

	return pkgr
}

func (pkgr *Packager) ReadDependenciesFile() {
	depPath := filepath.Join(pkgr.MixerDir, DEPS_FILE)
	depPathExists, existsErr := fileutil.Exists(depPath)
	if existsErr != nil {
		panic(fmt.Sprintf("error reading dependencies file for `%s`", pkgr.Mixer.GetName()))
	}
	if !depPathExists {
		return
	}
	data, readErr := ioutil.ReadFile(filepath.Join(pkgr.MixerDir, DEPS_FILE))
	if readErr != nil {
		panic(fmt.Sprintf("error reading dependencies file for `%s`", pkgr.Mixer.GetName()))
	}

	depmap := make(map[string]string)
	yaml.Unmarshal(data, &depmap)
	pkgr.Dependencies = depmap
	// pkgr.Dependencies = make([]string, 0)

	// for name, version := range depmap {
	// 	pkgr.Dependencies = append(pkgr.Dependencies, fmt.Sprintf("%s:%s", name, version))
	// }
}

func (pkgr *Packager) BuildMixer() *tp.Mixer {

	return pkgr.Mixer
}<commit_msg>Working on dependency resolution and stuff.<commit_after>package packager

import (
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
)

import (
	// data "butler/data"
	fileutil "butler/fileutil"
	proto "code.google.com/p/goprotobuf/proto"
	tp "tritium/proto"
	yaml "goyaml"
)

type Packager struct {
	MixerDir      string
	IncludePaths  []string
	IsTransformer bool
	Dependencies  map[string]string
	*tp.Mixer
}

const (
	PACKAGER_VERSION = 1
	LIB_DIR          = "lib"
	SCRIPTS_DIR      = "scripts"
	ENTRY_FILE       = "main.ts"
	DEPS_FILE        = "dependencies.yml"
	MIXER_DIR        = "mixers"
)

func New(relSrcDir string) *Packager {
	pkgr := new(Packager)

	wd, wdErr := os.Getwd()
	if wdErr != nil {
		panic("unable to determine current directory for mixer creation")
	}

	absSrcDir, absErr := filepath.Abs(relSrcDir)
	if absErr != nil {
		panic("unable to absolutize mixer source directory for mixer creation")
	}

	pkgr.MixerDir        = absSrcDir
	pkgr.IncludePaths    = make([]string, 1)
	pkgr.IncludePaths[0] = wd

	pkgr.Mixer           = tp.NewMixer(absSrcDir)
	pkgr.PackagerVersion = proto.Int32(1)
	pkgr.readDependenciesFile()

	pkgr.Mixer.Package      = new(tp.Package)
	pkgr.Mixer.Package.Name = proto.String(pkgr.Mixer.GetName())
	pkgr.Mixer.Package.Path = proto.String(pkgr.MixerDir)

	return pkgr
}

func (pkgr *Packager) readDependenciesFile() {
	depPath := filepath.Join(pkgr.MixerDir, DEPS_FILE)
	depPathExists, existsErr := fileutil.Exists(depPath)
	if existsErr != nil {
		panic(fmt.Sprintf("error reading dependencies file for `%s`", pkgr.Mixer.GetName()))
	}
	if !depPathExists {
		return
	}
	data, readErr := ioutil.ReadFile(filepath.Join(pkgr.MixerDir, DEPS_FILE))
	if readErr != nil {
		panic(fmt.Sprintf("error reading dependencies file for `%s`", pkgr.Mixer.GetName()))
	}
	pkgr.Dependencies = make(map[string]string)
	yaml.Unmarshal(data, &pkgr.Dependencies)
}

func (pkgr *Packager) Build() {
	pkgr.resolveDependencies()
	pkgr.resolveTypes()
	pkgr.buildLib()
}

func (pkgr *Packager) resolveDependencies() {
	for name, version := range pkgr.Dependencies {
		needed := pkgr.loadDependency(name, version)
		needed.Build()
		pkgr.merge(needed)
	}
}

func (pkgr *Packager) loadDependency(name, specifiedVersion string) *Packager {
	foundMixerSrc := false
	foundCompiledMixer := false
	for _, incPath := range pkgr.IncludePaths {
		depPath := filepath.Join(incPath, name)
		there, err := fileutil.Exists(depPath)
		if !there || err != nil {
			continue
		}
		foundMixerSrc = true
		needed := New(depPath)
		if needed.GetVersion() != specifiedVersion {
			continue
		}
		needed.Build()
		return needed
	}

	// TODO: check if the dependency is among the compiled mixers

	if foundMixerSrc || foundCompiledMixer {
		panic(fmt.Sprintf("version %s needed for dependency `%s` of `%s`",
		      specifiedVersion, name, pkgr.GetName()))
	}
	panic(fmt.Sprintf("unable to find dependency `%s` of `%s`", name, pkgr.GetName()))
	return nil
}

<|endoftext|>


<commit_before>package elicit_test

import (
	"fmt"
	"io/ioutil"
	"mmatt/elicit"
	"os"
	"os/exec"
	"path/filepath"
	"regexp"
	"strings"
	"testing"
)

var tempdir string

func Test(t *testing.T) {
	elicit.New().
		WithSpecsFolder("./specs").
		WithTransforms(transforms).
		WithSteps(steps).
		BeforeScenarios(createTempDir).
		AfterScenarios(removeTempDir).
		RunTests(t)
}

var steps = elicit.Steps{}
var transforms = elicit.Transforms{}

func init() {
	steps["Create a temporary environment"] =
		func(t *testing.T) {
			createFile(t, "specs_test.go", testfile)
		}

	steps["Create an? `(.*)` file:"] =
		func(t *testing.T, filename string, text elicit.TextBlock) {
			createFile(t, filename, text.Content)
		}

	steps["Create (?:a step definition|step definitions|transform definitions):"] =
		func(t *testing.T, text elicit.TextBlock) {
			createFile(t, "steps_test.go", fmt.Sprintf(stepFileFmt, "", text.Content))
		}

	steps["Create (?:a step definition|step definitions) using (.+):"] =
		func(t *testing.T, imports []string, text elicit.TextBlock) {
			createFile(t, "steps_test.go", fmt.Sprintf(stepFileFmt, strings.Join(imports, "\n"), text.Content))
		}

	steps["Running `(go test.*)` will output:"] =
		func(t *testing.T, command string, text elicit.TextBlock) {
			output := runGoTest(t, command)

			expected, actual := quoteOutput(text.Content), quoteOutput(output)
			if !strings.Contains(actual, expected) {
				t.Errorf("\n\nExpected:\n\n%s\n\nto contain:\n\n%s\n", actual, expected)
			}
		}

	steps["Running `(go test.*)` will output the following lines:"] =
		func(t *testing.T, command string, text elicit.TextBlock) {
			output := runGoTest(t, command)

			missingLines := []string{}
			for _, line := range strings.Split(text.Content, "\n") {
				if !strings.Contains(output, line) {
					missingLines = append(missingLines, line)
				}
			}

			if len(missingLines) > 0 {
				t.Errorf("\n\nExpected:\n\n%s\n\nto contain the lines:\n\n%s\n",
					quoteOutput(output),
					quoteOutput(strings.Join(missingLines, "\n")))
			}
		}

	steps["`(.+)` will contain:"] =
		func(t *testing.T, filename string, text elicit.TextBlock) {
			path := filepath.Join(tempdir, filename)

			if _, err := os.Stat(path); os.IsNotExist(err) {
				t.Error(filename, err)
			}

			if contents, err := ioutil.ReadFile(path); err != nil {
				t.Error("reading", filename, err)
			} else {
				actual := string(contents)
				expected := strings.TrimSpace(text.Content)
				if actual != expected {
					t.Errorf("\n\nExpected:\n\n%s\n\nto equal:\n\n%s\n", quoteOutput(actual), quoteOutput(expected))
				}
			}
		}
}

func createTempDir() {
	var err error
	tempdir, err = ioutil.TempDir("", "elicit_test")

	if err != nil {
		panic(fmt.Errorf("creating tempdir: %s", err))
	}
}

func removeTempDir() {
	if err := os.RemoveAll(tempdir); err != nil {
		panic(fmt.Errorf("removing tempdir %q: %s", tempdir, err))
	}
}

func createFile(t *testing.T, filename, contents string) {
	if tempdir == "" {
		t.Fatal("creating file: tempdir not set")
	}

	outpath := filepath.Join(tempdir, filename)

	if _, err := os.Stat(outpath); os.IsNotExist(err) {
		ioutil.WriteFile(outpath, []byte(contents), 0777)
	} else {
		t.Fatal("creating file:", outpath, "already exists")
	}
}

func runGoTest(t *testing.T, command string) string {
	if err := os.Chdir(tempdir); err != nil {
		t.Fatalf("switching to tempdir %s: %s", tempdir, err)
	}

	parts := strings.Split(command, " ")
	output, _ := exec.Command(parts[0], parts[1:]...).CombinedOutput()

	return string(output)
}

func quoteOutput(s string) string {
	s = strings.TrimSpace(s)
	s = regexp.MustCompile(`\033\[\d+(;\d+)?m`).ReplaceAllString(s, "")
	s = regexp.MustCompile(` $`).ReplaceAllString(s, "·")
	s = strings.Replace(s, "\t", "  ➟ ", -1)
	s = "  | " + strings.Join(strings.Split(s, "\n"), "\n  | ")
	return s
}

const testfile = `
package elicit_test

import (
    "mmatt/elicit"
    "testing"
)

func Test(t *testing.T) {
    elicit.New().
        WithSpecsFolder(".").
        WithSteps(steps).
        WithTransforms(transforms).
        RunTests(t)
}

var steps = elicit.Steps{}
var transforms = elicit.Transforms{}
`

const stepFileFmt = `
package elicit_test

import (
	"testing"
	%s
)

func init() {
	%s
}
`
<commit_msg>Restore Working Directory After Each Run<commit_after>package elicit_test

import (
	"fmt"
	"io/ioutil"
	"mmatt/elicit"
	"os"
	"os/exec"
	"path/filepath"
	"regexp"
	"strings"
	"testing"
)

var startdir, tempdir string

func init() {
	wd, err := os.Getwd()
	if err != nil {
		panic(fmt.Errorf("os.Getwd(): %s", err))
	}
	startdir = wd
}

func Test(t *testing.T) {
	elicit.New().
		WithSpecsFolder("./specs").
		WithTransforms(transforms).
		WithSteps(steps).
		BeforeScenarios(createTempDir).
		AfterScenarios(removeTempDir).
		RunTests(t)
}

var steps = elicit.Steps{}
var transforms = elicit.Transforms{}

func init() {
	steps["Create a temporary environment"] =
		func(t *testing.T) {
			createFile(t, "specs_test.go", testfile)
		}

	steps["Create an? `(.*)` file:"] =
		func(t *testing.T, filename string, text elicit.TextBlock) {
			createFile(t, filename, text.Content)
		}

	steps["Create (?:a step definition|step definitions|transform definitions):"] =
		func(t *testing.T, text elicit.TextBlock) {
			createFile(t, "steps_test.go", fmt.Sprintf(stepFileFmt, "", text.Content))
		}

	steps["Create (?:a step definition|step definitions) using (.+):"] =
		func(t *testing.T, imports []string, text elicit.TextBlock) {
			createFile(t, "steps_test.go", fmt.Sprintf(stepFileFmt, strings.Join(imports, "\n"), text.Content))
		}

	steps["Running `(go test.*)` will output:"] =
		func(t *testing.T, command string, text elicit.TextBlock) {
			output := runGoTest(t, command)

			expected, actual := quoteOutput(text.Content), quoteOutput(output)
			if !strings.Contains(actual, expected) {
				t.Errorf("\n\nExpected:\n\n%s\n\nto contain:\n\n%s\n", actual, expected)
			}
		}

	steps["Running `(go test.*)` will output the following lines:"] =
		func(t *testing.T, command string, text elicit.TextBlock) {
			output := runGoTest(t, command)

			missingLines := []string{}
			for _, line := range strings.Split(text.Content, "\n") {
				if !strings.Contains(output, line) {
					missingLines = append(missingLines, line)
				}
			}

			if len(missingLines) > 0 {
				t.Errorf("\n\nExpected:\n\n%s\n\nto contain the lines:\n\n%s\n",
					quoteOutput(output),
					quoteOutput(strings.Join(missingLines, "\n")))
			}
		}

	steps["`(.+)` will contain:"] =
		func(t *testing.T, filename string, text elicit.TextBlock) {
			path := filepath.Join(tempdir, filename)

			if _, err := os.Stat(path); os.IsNotExist(err) {
				t.Error(filename, err)
			}

			if contents, err := ioutil.ReadFile(path); err != nil {
				t.Error("reading", filename, err)
			} else {
				actual := string(contents)
				expected := strings.TrimSpace(text.Content)
				if actual != expected {
					t.Errorf("\n\nExpected:\n\n%s\n\nto equal:\n\n%s\n", quoteOutput(actual), quoteOutput(expected))
				}
			}
		}
}

func createTempDir() {
	var err error
	tempdir, err = ioutil.TempDir("", "elicit_test")

	if err != nil {
		panic(fmt.Errorf("creating tempdir: %s", err))
	}
}

func removeTempDir() {
	if err := os.RemoveAll(tempdir); err != nil {
		panic(fmt.Errorf("removing tempdir %q: %s", tempdir, err))
	}
	if err := os.Chdir(startdir); err != nil {
		panic(fmt.Errorf("reverting wd to %q: %s", startdir, err))
	}
}

func createFile(t *testing.T, filename, contents string) {
	if tempdir == "" {
		t.Fatal("creating file: tempdir not set")
	}

	outpath := filepath.Join(tempdir, filename)

	if _, err := os.Stat(outpath); os.IsNotExist(err) {
		ioutil.WriteFile(outpath, []byte(contents), 0777)
	} else {
		t.Fatal("creating file:", outpath, "already exists")
	}
}

func runGoTest(t *testing.T, command string) string {
	if err := os.Chdir(tempdir); err != nil {
		t.Fatalf("switching to tempdir %s: %s", tempdir, err)
	}

	parts := strings.Split(command, " ")
	output, _ := exec.Command(parts[0], parts[1:]...).CombinedOutput()

	return string(output)
}

func quoteOutput(s string) string {
	s = strings.TrimSpace(s)
	s = regexp.MustCompile(`\033\[\d+(;\d+)?m`).ReplaceAllString(s, "")
	s = regexp.MustCompile(` $`).ReplaceAllString(s, "·")
	s = strings.Replace(s, "\t", "  ➟ ", -1)
	s = "  | " + strings.Join(strings.Split(s, "\n"), "\n  | ")
	return s
}

const testfile = `
package elicit_test

import (
    "mmatt/elicit"
    "testing"
)

func Test(t *testing.T) {
    elicit.New().
        WithSpecsFolder(".").
        WithSteps(steps).
        WithTransforms(transforms).
        RunTests(t)
}

var steps = elicit.Steps{}
var transforms = elicit.Transforms{}
`

const stepFileFmt = `
package elicit_test

import (
	"testing"
	%s
)

func init() {
	%s
}
`
<|endoftext|>


<commit_before>package semaphore

type Semaphore struct {
	Ready chan token
	n     int
}

type token struct{}

func New(n int) *Semaphore {
	return &Semaphore{
		Ready: make(chan token, n),
		n:     n,
	}
}

func (s *Semaphore) Start() {
	s.Signal(s.n)
}

func (s *Semaphore) Stop() {
	for {
		select {
		case <-s.Ready:
		default:
			return
		}
	}
}

func (s *Semaphore) Signal(n int) {
	for i := 0; i < n; i++ {
		select {
		case s.Ready <- token{}:
		default:
			return
		}
	}
}
<commit_msg>remove semaphore package<commit_after><|endoftext|>


<commit_before>package main

import (
	"github.com/crackcomm/nsqueue/producer"
)

var (
	nsqdAddr = "127.0.0.1:4150"
)

func main() {
	producer.Connect(nsqdAddr)

	var crawlJob struct {
		host string
	}
	crawlJob.host = "google.com"

	body, _ := producer.EncJson(crawlJob)
	for i := 0; i < 10000; i++ {
		producer.PublishAsync("crawl", body, nil)
		// producer.PublishJsonAsync("crawl", crawlJob, nil)
	}
}
<commit_msg>examples -> latency-test<commit_after>package main

import (
	"flag"
	"fmt"
	"github.com/crackcomm/nsqueue/producer"
	"time"
)

var (
	amount   = flag.Int("amount", 20, "Amount of messages to produce every 100 ms")
	nsqdAddr = flag.String("nsqd", "127.0.0.1:4150", "nsqd tcp address")
)

func main() {
	flag.Parse()
	producer.Connect(*nsqdAddr)

	for _ = range time.NewTicker(100 * time.Millisecond).C {
		fmt.Println("Ping...")
		for i := 0; i < *amount; i++ {
			body, _ := time.Now().MarshalBinary()
			producer.PublishAsync("latency-test", body, nil)
		}
	}
}
<|endoftext|>


<commit_before>package jenkinsbootstrapper

import (
	"fmt"
	"io"
	"net/http"

	"github.com/golang/glog"
	"k8s.io/kubernetes/pkg/admission"
	kapi "k8s.io/kubernetes/pkg/api"
	kapierrors "k8s.io/kubernetes/pkg/api/errors"
	"k8s.io/kubernetes/pkg/api/meta"
	"k8s.io/kubernetes/pkg/apimachinery/registered"
	clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
	coreclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion"
	"k8s.io/kubernetes/pkg/client/restclient"
	"k8s.io/kubernetes/pkg/kubectl/resource"
	"k8s.io/kubernetes/pkg/runtime"
	kutilerrors "k8s.io/kubernetes/pkg/util/errors"

	"github.com/openshift/origin/pkg/api/latest"
	authenticationclient "github.com/openshift/origin/pkg/auth/client"
	buildapi "github.com/openshift/origin/pkg/build/api"
	jenkinscontroller "github.com/openshift/origin/pkg/build/controller/jenkins"
	"github.com/openshift/origin/pkg/client"
	configapi "github.com/openshift/origin/pkg/cmd/server/api"
	"github.com/openshift/origin/pkg/config/cmd"
)

func init() {
	admission.RegisterPlugin("openshift.io/JenkinsBootstrapper", func(c clientset.Interface, config io.Reader) (admission.Interface, error) {
		return NewJenkinsBootstrapper(c.Core()), nil
	})
}

type jenkinsBootstrapper struct {
	*admission.Handler

	privilegedRESTClientConfig restclient.Config
	serviceClient              coreclient.ServicesGetter
	openshiftClient            client.Interface

	jenkinsConfig configapi.JenkinsPipelineConfig
}

// NewJenkinsBootstrapper returns an admission plugin that will create required jenkins resources as the user if they are needed.
func NewJenkinsBootstrapper(serviceClient coreclient.ServicesGetter) admission.Interface {
	return &jenkinsBootstrapper{
		Handler:       admission.NewHandler(admission.Create),
		serviceClient: serviceClient,
	}
}

func (a *jenkinsBootstrapper) Admit(attributes admission.Attributes) error {
	if a.jenkinsConfig.AutoProvisionEnabled != nil && !*a.jenkinsConfig.AutoProvisionEnabled {
		return nil
	}
	if len(attributes.GetSubresource()) != 0 {
		return nil
	}
	if attributes.GetResource().GroupResource() != buildapi.Resource("buildconfigs") && attributes.GetResource().GroupResource() != buildapi.Resource("builds") {
		return nil
	}
	if !needsJenkinsTemplate(attributes.GetObject()) {
		return nil
	}

	namespace := attributes.GetNamespace()

	svcName := a.jenkinsConfig.ServiceName
	if len(svcName) == 0 {
		return nil
	}

	// TODO pull this from a cache.
	if _, err := a.serviceClient.Services(namespace).Get(svcName); !kapierrors.IsNotFound(err) {
		// if it isn't a "not found" error, return the error.  Either its nil and there's nothing to do or something went really wrong
		return err
	}

	glog.V(3).Infof("Adding new jenkins service %q to the project %q", svcName, namespace)
	jenkinsTemplate := jenkinscontroller.NewPipelineTemplate(namespace, a.jenkinsConfig, a.openshiftClient)
	objects, errs := jenkinsTemplate.Process()
	if len(errs) > 0 {
		return kutilerrors.NewAggregate(errs)
	}
	if !jenkinsTemplate.HasJenkinsService(objects) {
		return fmt.Errorf("template %s/%s does not contain required service %q", a.jenkinsConfig.TemplateNamespace, a.jenkinsConfig.TemplateName, a.jenkinsConfig.ServiceName)
	}

	impersonatingConfig := a.privilegedRESTClientConfig
	oldWrapTransport := impersonatingConfig.WrapTransport
	impersonatingConfig.WrapTransport = func(rt http.RoundTripper) http.RoundTripper {
		return authenticationclient.NewImpersonatingRoundTripper(attributes.GetUserInfo(), oldWrapTransport(rt))
	}

	var bulkErr error

	bulk := &cmd.Bulk{
		Mapper: &resource.Mapper{
			RESTMapper:  registered.RESTMapper(),
			ObjectTyper: kapi.Scheme,
			ClientMapper: resource.ClientMapperFunc(func(mapping *meta.RESTMapping) (resource.RESTClient, error) {
				if latest.OriginKind(mapping.GroupVersionKind) {
					return client.New(&impersonatingConfig)
				}
				return restclient.RESTClientFor(&impersonatingConfig)
			}),
		},
		Op: cmd.Create,
		After: func(info *resource.Info, err error) bool {
			if kapierrors.IsAlreadyExists(err) {
				return false
			}
			if err != nil {
				bulkErr = err
				return true
			}
			return false
		},
	}
	// we're intercepting the error we care about using After
	bulk.Run(objects, namespace)
	if bulkErr != nil {
		return bulkErr
	}

	glog.V(1).Infof("Jenkins Pipeline service %q created", svcName)

	return nil

}

func needsJenkinsTemplate(obj runtime.Object) bool {
	switch t := obj.(type) {
	case *buildapi.Build:
		return t.Spec.Strategy.JenkinsPipelineStrategy != nil
	case *buildapi.BuildConfig:
		return t.Spec.Strategy.JenkinsPipelineStrategy != nil
	default:
		return false
	}
}

func (a *jenkinsBootstrapper) SetJenkinsPipelineConfig(jenkinsConfig configapi.JenkinsPipelineConfig) {
	a.jenkinsConfig = jenkinsConfig
}

func (a *jenkinsBootstrapper) SetRESTClientConfig(restClientConfig restclient.Config) {
	a.privilegedRESTClientConfig = restClientConfig
}

func (a *jenkinsBootstrapper) SetOpenshiftClient(oclient client.Interface) {
	a.openshiftClient = oclient
}
<commit_msg>Fix jenkins admission plugin<commit_after>package jenkinsbootstrapper

import (
	"fmt"
	"io"
	"net/http"

	"github.com/golang/glog"
	"k8s.io/kubernetes/pkg/admission"
	kapi "k8s.io/kubernetes/pkg/api"
	kapierrors "k8s.io/kubernetes/pkg/api/errors"
	"k8s.io/kubernetes/pkg/api/meta"
	"k8s.io/kubernetes/pkg/apimachinery/registered"
	clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
	coreclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion"
	"k8s.io/kubernetes/pkg/client/restclient"
	kclient "k8s.io/kubernetes/pkg/client/unversioned"
	"k8s.io/kubernetes/pkg/kubectl/resource"
	"k8s.io/kubernetes/pkg/runtime"
	kutilerrors "k8s.io/kubernetes/pkg/util/errors"

	"github.com/openshift/origin/pkg/api/latest"
	authenticationclient "github.com/openshift/origin/pkg/auth/client"
	buildapi "github.com/openshift/origin/pkg/build/api"
	jenkinscontroller "github.com/openshift/origin/pkg/build/controller/jenkins"
	"github.com/openshift/origin/pkg/client"
	oadmission "github.com/openshift/origin/pkg/cmd/server/admission"
	configapi "github.com/openshift/origin/pkg/cmd/server/api"
	"github.com/openshift/origin/pkg/config/cmd"
)

func init() {
	admission.RegisterPlugin("openshift.io/JenkinsBootstrapper", func(c clientset.Interface, config io.Reader) (admission.Interface, error) {
		return NewJenkinsBootstrapper(c.Core()), nil
	})
}

type jenkinsBootstrapper struct {
	*admission.Handler

	privilegedRESTClientConfig restclient.Config
	serviceClient              coreclient.ServicesGetter
	openshiftClient            client.Interface

	jenkinsConfig configapi.JenkinsPipelineConfig
}

var _ = oadmission.WantsJenkinsPipelineConfig(&jenkinsBootstrapper{})
var _ = oadmission.WantsRESTClientConfig(&jenkinsBootstrapper{})
var _ = oadmission.WantsOpenshiftClient(&jenkinsBootstrapper{})

// NewJenkinsBootstrapper returns an admission plugin that will create required jenkins resources as the user if they are needed.
func NewJenkinsBootstrapper(serviceClient coreclient.ServicesGetter) admission.Interface {
	return &jenkinsBootstrapper{
		Handler:       admission.NewHandler(admission.Create),
		serviceClient: serviceClient,
	}
}

func (a *jenkinsBootstrapper) Admit(attributes admission.Attributes) error {
	if a.jenkinsConfig.AutoProvisionEnabled != nil && !*a.jenkinsConfig.AutoProvisionEnabled {
		return nil
	}
	if len(attributes.GetSubresource()) != 0 {
		return nil
	}
	if attributes.GetResource().GroupResource() != buildapi.Resource("buildconfigs") && attributes.GetResource().GroupResource() != buildapi.Resource("builds") {
		return nil
	}
	if !needsJenkinsTemplate(attributes.GetObject()) {
		return nil
	}

	namespace := attributes.GetNamespace()

	svcName := a.jenkinsConfig.ServiceName
	if len(svcName) == 0 {
		return nil
	}

	// TODO pull this from a cache.
	if _, err := a.serviceClient.Services(namespace).Get(svcName); !kapierrors.IsNotFound(err) {
		// if it isn't a "not found" error, return the error.  Either its nil and there's nothing to do or something went really wrong
		return err
	}

	glog.V(3).Infof("Adding new jenkins service %q to the project %q", svcName, namespace)
	jenkinsTemplate := jenkinscontroller.NewPipelineTemplate(namespace, a.jenkinsConfig, a.openshiftClient)
	objects, errs := jenkinsTemplate.Process()
	if len(errs) > 0 {
		return kutilerrors.NewAggregate(errs)
	}
	if !jenkinsTemplate.HasJenkinsService(objects) {
		return fmt.Errorf("template %s/%s does not contain required service %q", a.jenkinsConfig.TemplateNamespace, a.jenkinsConfig.TemplateName, a.jenkinsConfig.ServiceName)
	}

	impersonatingConfig := a.privilegedRESTClientConfig
	oldWrapTransport := impersonatingConfig.WrapTransport
	impersonatingConfig.WrapTransport = func(rt http.RoundTripper) http.RoundTripper {
		return authenticationclient.NewImpersonatingRoundTripper(attributes.GetUserInfo(), oldWrapTransport(rt))
	}

	var bulkErr error

	bulk := &cmd.Bulk{
		Mapper: &resource.Mapper{
			RESTMapper:  registered.RESTMapper(),
			ObjectTyper: kapi.Scheme,
			ClientMapper: resource.ClientMapperFunc(func(mapping *meta.RESTMapping) (resource.RESTClient, error) {
				// TODO this is a nasty copy&paste from pkg/cmd/util/clientcmd/factory_object_mapping.go#ClientForMapping
				if latest.OriginKind(mapping.GroupVersionKind) {
					if err := client.SetOpenShiftDefaults(&impersonatingConfig); err != nil {
						return nil, err
					}
					impersonatingConfig.APIPath = "/apis"
					if mapping.GroupVersionKind.Group == kapi.GroupName {
						impersonatingConfig.APIPath = "/oapi"
					}
					gv := mapping.GroupVersionKind.GroupVersion()
					impersonatingConfig.GroupVersion = &gv
					return restclient.RESTClientFor(&impersonatingConfig)
				}
				// TODO and this from vendor/k8s.io/kubernetes/pkg/kubectl/cmd/util/factory_object_mapping.go#ClientForMapping
				if err := kclient.SetKubernetesDefaults(&impersonatingConfig); err != nil {
					return nil, err
				}
				gvk := mapping.GroupVersionKind
				switch gvk.Group {
				case kapi.GroupName:
					impersonatingConfig.APIPath = "/api"
				default:
					impersonatingConfig.APIPath = "/apis"
				}
				gv := gvk.GroupVersion()
				impersonatingConfig.GroupVersion = &gv
				return restclient.RESTClientFor(&impersonatingConfig)
			}),
		},
		Op: cmd.Create,
		After: func(info *resource.Info, err error) bool {
			if kapierrors.IsAlreadyExists(err) {
				return false
			}
			if err != nil {
				bulkErr = err
				return true
			}
			return false
		},
	}
	// we're intercepting the error we care about using After
	bulk.Run(objects, namespace)
	if bulkErr != nil {
		return bulkErr
	}

	glog.V(1).Infof("Jenkins Pipeline service %q created", svcName)

	return nil

}

func needsJenkinsTemplate(obj runtime.Object) bool {
	switch t := obj.(type) {
	case *buildapi.Build:
		return t.Spec.Strategy.JenkinsPipelineStrategy != nil
	case *buildapi.BuildConfig:
		return t.Spec.Strategy.JenkinsPipelineStrategy != nil
	default:
		return false
	}
}

func (a *jenkinsBootstrapper) SetJenkinsPipelineConfig(jenkinsConfig configapi.JenkinsPipelineConfig) {
	a.jenkinsConfig = jenkinsConfig
}

func (a *jenkinsBootstrapper) SetRESTClientConfig(restClientConfig restclient.Config) {
	a.privilegedRESTClientConfig = restClientConfig
}

func (a *jenkinsBootstrapper) SetOpenshiftClient(oclient client.Interface) {
	a.openshiftClient = oclient
}
<|endoftext|>


<commit_before>package rkeworkerupgrader

import (
	"fmt"

	nodehelper "github.com/rancher/rancher/pkg/node"
	nodeserver "github.com/rancher/rancher/pkg/rkenodeconfigserver"
	rkeservices "github.com/rancher/rke/services"
	v3 "github.com/rancher/types/apis/management.cattle.io/v3"
	"github.com/sirupsen/logrus"
)

func (uh *upgradeHandler) prepareNode(node *v3.Node, toDrain bool, nodeDrainInput *v3.NodeDrainInput) error {
	var nodeCopy *v3.Node
	if toDrain {
		if node.Spec.DesiredNodeUnschedulable == "drain" {
			return nil
		}
		nodeCopy = node.DeepCopy()
		nodeCopy.Spec.DesiredNodeUnschedulable = "drain"
		nodeCopy.Spec.NodeDrainInput = nodeDrainInput
	} else {
		if node.Spec.DesiredNodeUnschedulable == "true" || node.Spec.InternalNodeSpec.Unschedulable {
			return nil
		}
		nodeCopy = node.DeepCopy()
		nodeCopy.Spec.DesiredNodeUnschedulable = "true"
	}

	if _, err := uh.nodes.Update(nodeCopy); err != nil {
		return err
	}
	return nil
}

func (uh *upgradeHandler) processNode(node *v3.Node, cluster *v3.Cluster, msg string) error {
	nodePlan, err := uh.getNodePlan(node, cluster)
	if err != nil {
		return fmt.Errorf("setNodePlan: error getting node plan for [%s]: %v", node.Name, err)
	}

	nodeCopy := node.DeepCopy()
	nodeCopy.Status.NodePlan.Plan = nodePlan
	nodeCopy.Status.NodePlan.Version = cluster.Status.NodeVersion
	nodeCopy.Status.NodePlan.AgentCheckInterval = nodeserver.AgentCheckIntervalDuringUpgrade

	v3.NodeConditionUpgraded.Unknown(nodeCopy)
	v3.NodeConditionUpgraded.Message(nodeCopy, msg)

	if _, err := uh.nodes.Update(nodeCopy); err != nil {
		return err
	}

	return nil
}

func (uh *upgradeHandler) updateNodeActive(node *v3.Node) error {
	nodeCopy := node.DeepCopy()
	v3.NodeConditionUpgraded.True(nodeCopy)
	v3.NodeConditionUpgraded.Message(nodeCopy, "")

	// reset the node
	nodeCopy.Spec.DesiredNodeUnschedulable = "false"
	nodeCopy.Status.NodePlan.AgentCheckInterval = nodeserver.DefaultAgentCheckInterval

	if _, err := uh.nodes.Update(nodeCopy); err != nil {
		return err
	}

	return nil
}

func skipNode(node *v3.Node) bool {
	if node.DeletionTimestamp != nil {
		logrus.Debugf("node [%s] is getting deleted", node.Name)
		return true
	}

	if node.Status.NodeConfig == nil {
		logrus.Debugf("node [%s] nodeConfig is empty", node.Name)
		return true
	}

	if !workerOnly(node.Status.NodeConfig.Role) {
		logrus.Debugf("node [%s] is not a workerOnly node", node.Name)
		return true
	}

	// skip nodes marked for ignore by user
	if node.Labels != nil && node.Labels[ignoreKey] == ignoreValue {
		logrus.Debugf("node [%s] is marked with ignoreLabel %s: %v", node.Name, ignoreKey, ignoreValue)
		return true
	}

	// skip provisioning nodes
	if !v3.NodeConditionProvisioned.IsTrue(node) {
		logrus.Debugf("node [%s] is not provisioned", node.Name)
		return true
	}

	// skip registering nodes
	if !v3.NodeConditionRegistered.IsTrue(node) {
		logrus.Debugf("node [%s] is not registered", node.Name)
		return true
	}

	return false
}

func (uh *upgradeHandler) filterNodes(nodes []*v3.Node, expectedVersion int) (map[string]*v3.Node, map[string]*v3.Node, map[string]*v3.Node, map[string]*v3.Node, int, int, int) {
	done, upgrading, filtered := 0, 0, 0
	toPrepareMap, toProcessMap, upgradedMap, notReadyMap := map[string]*v3.Node{}, map[string]*v3.Node{}, map[string]*v3.Node{}, map[string]*v3.Node{}

	for _, node := range nodes {

		if skipNode(node) {
			continue
		}

		filtered++

		// check for nodeConditionReady
		if !nodehelper.IsMachineReady(node) {
			notReadyMap[node.Name] = node
			logrus.Debugf("node [%s] is not ready", node.Name)
			continue
		}

		if node.Status.AppliedNodeVersion == expectedVersion {
			if v3.NodeConditionUpgraded.IsTrue(node) && !node.Spec.InternalNodeSpec.Unschedulable {
				done++
			} else {
				// node hasn't un-cordoned, so consider it upgrading in terms of maxUnavailable count
				upgrading++
				upgradedMap[node.Name] = node
			}
			continue
		}

		if preparingNode(node) {
			// draining or cordoning
			upgrading++
			continue
		}

		if preparedNode(node) {
			// node ready to upgrade
			upgrading++
			toProcessMap[node.Name] = node
			continue
		}

		toPrepareMap[node.Name] = node
	}

	return toPrepareMap, toProcessMap, upgradedMap, notReadyMap, filtered, upgrading, done
}

func preparingNode(node *v3.Node) bool {
	return node.Spec.DesiredNodeUnschedulable == "drain" || node.Spec.DesiredNodeUnschedulable == "true"
}

func preparedNode(node *v3.Node) bool {
	return v3.NodeConditionDrained.IsTrue(node) || node.Spec.InternalNodeSpec.Unschedulable || v3.NodeConditionUpgraded.IsUnknown(node)
}

func workerOnly(roles []string) bool {
	worker := false
	for _, role := range roles {
		if role == rkeservices.ETCDRole {
			return false
		}
		if role == rkeservices.ControlRole {
			return false
		}
		if role == rkeservices.WorkerRole {
			worker = true
		}
	}
	return worker
}
<commit_msg>read labels from Node.Status.NodeLabels<commit_after>package rkeworkerupgrader

import (
	"fmt"

	nodehelper "github.com/rancher/rancher/pkg/node"
	nodeserver "github.com/rancher/rancher/pkg/rkenodeconfigserver"
	rkeservices "github.com/rancher/rke/services"
	v3 "github.com/rancher/types/apis/management.cattle.io/v3"
	"github.com/sirupsen/logrus"
)

func (uh *upgradeHandler) prepareNode(node *v3.Node, toDrain bool, nodeDrainInput *v3.NodeDrainInput) error {
	var nodeCopy *v3.Node
	if toDrain {
		if node.Spec.DesiredNodeUnschedulable == "drain" {
			return nil
		}
		nodeCopy = node.DeepCopy()
		nodeCopy.Spec.DesiredNodeUnschedulable = "drain"
		nodeCopy.Spec.NodeDrainInput = nodeDrainInput
	} else {
		if node.Spec.DesiredNodeUnschedulable == "true" || node.Spec.InternalNodeSpec.Unschedulable {
			return nil
		}
		nodeCopy = node.DeepCopy()
		nodeCopy.Spec.DesiredNodeUnschedulable = "true"
	}

	if _, err := uh.nodes.Update(nodeCopy); err != nil {
		return err
	}
	return nil
}

func (uh *upgradeHandler) processNode(node *v3.Node, cluster *v3.Cluster, msg string) error {
	nodePlan, err := uh.getNodePlan(node, cluster)
	if err != nil {
		return fmt.Errorf("setNodePlan: error getting node plan for [%s]: %v", node.Name, err)
	}

	nodeCopy := node.DeepCopy()
	nodeCopy.Status.NodePlan.Plan = nodePlan
	nodeCopy.Status.NodePlan.Version = cluster.Status.NodeVersion
	nodeCopy.Status.NodePlan.AgentCheckInterval = nodeserver.AgentCheckIntervalDuringUpgrade

	v3.NodeConditionUpgraded.Unknown(nodeCopy)
	v3.NodeConditionUpgraded.Message(nodeCopy, msg)

	if _, err := uh.nodes.Update(nodeCopy); err != nil {
		return err
	}

	return nil
}

func (uh *upgradeHandler) updateNodeActive(node *v3.Node) error {
	nodeCopy := node.DeepCopy()
	v3.NodeConditionUpgraded.True(nodeCopy)
	v3.NodeConditionUpgraded.Message(nodeCopy, "")

	// reset the node
	nodeCopy.Spec.DesiredNodeUnschedulable = "false"
	nodeCopy.Status.NodePlan.AgentCheckInterval = nodeserver.DefaultAgentCheckInterval

	if _, err := uh.nodes.Update(nodeCopy); err != nil {
		return err
	}

	return nil
}

func skipNode(node *v3.Node) bool {
	clusterName := node.Namespace
	if node.DeletionTimestamp != nil {
		logrus.Debugf("cluster [%s] worker-upgrade: node [%s] is getting deleted", clusterName, node.Name)
		return true
	}

	if node.Status.NodeConfig == nil {
		logrus.Debugf("cluster [%s] worker-upgrade: node [%s] nodeConfig is empty", clusterName, node.Name)
		return true
	}

	if !workerOnly(node.Status.NodeConfig.Role) {
		logrus.Debugf("cluster [%s] worker-upgrade: node [%s] is not a workerOnly node", clusterName, node.Name)
		return true
	}

	// skip nodes marked for ignore by user
	if node.Status.NodeLabels != nil && node.Status.NodeLabels[ignoreKey] == ignoreValue {
		logrus.Debugf("cluster [%s] worker-upgrade: node [%s] is marked with ignoreLabel %s: %v", clusterName, node.Name, ignoreKey, ignoreValue)
		return true
	}

	// skip provisioning nodes
	if !v3.NodeConditionProvisioned.IsTrue(node) {
		logrus.Debugf("cluster [%s] worker-upgrade: node [%s] is not provisioned", clusterName, node.Name)
		return true
	}

	// skip registering nodes
	if !v3.NodeConditionRegistered.IsTrue(node) {
		logrus.Debugf("cluster [%s] worker-upgrade: node [%s] is not registered", clusterName, node.Name)
		return true
	}

	return false
}

func (uh *upgradeHandler) filterNodes(nodes []*v3.Node, expectedVersion int) (map[string]*v3.Node, map[string]*v3.Node, map[string]*v3.Node, map[string]*v3.Node, int, int, int) {
	done, upgrading, filtered := 0, 0, 0
	toPrepareMap, toProcessMap, upgradedMap, notReadyMap := map[string]*v3.Node{}, map[string]*v3.Node{}, map[string]*v3.Node{}, map[string]*v3.Node{}

	for _, node := range nodes {

		if skipNode(node) {
			continue
		}

		filtered++

		// check for nodeConditionReady
		if !nodehelper.IsMachineReady(node) {
			notReadyMap[node.Name] = node
			logrus.Debugf("cluster [%s] worker-upgrade: node [%s] is not ready", node.Namespace, node.Name)
			continue
		}

		if node.Status.AppliedNodeVersion == expectedVersion {
			if v3.NodeConditionUpgraded.IsTrue(node) && !node.Spec.InternalNodeSpec.Unschedulable {
				done++
			} else {
				// node hasn't un-cordoned, so consider it upgrading in terms of maxUnavailable count
				upgrading++
				upgradedMap[node.Name] = node
			}
			continue
		}

		if preparingNode(node) {
			// draining or cordoning
			upgrading++
			continue
		}

		if preparedNode(node) {
			// node ready to upgrade
			upgrading++
			toProcessMap[node.Name] = node
			continue
		}

		toPrepareMap[node.Name] = node
	}

	return toPrepareMap, toProcessMap, upgradedMap, notReadyMap, filtered, upgrading, done
}

func preparingNode(node *v3.Node) bool {
	return node.Spec.DesiredNodeUnschedulable == "drain" || node.Spec.DesiredNodeUnschedulable == "true"
}

func preparedNode(node *v3.Node) bool {
	return v3.NodeConditionDrained.IsTrue(node) || node.Spec.InternalNodeSpec.Unschedulable || v3.NodeConditionUpgraded.IsUnknown(node)
}

func workerOnly(roles []string) bool {
	worker := false
	for _, role := range roles {
		if role == rkeservices.ETCDRole {
			return false
		}
		if role == rkeservices.ControlRole {
			return false
		}
		if role == rkeservices.WorkerRole {
			worker = true
		}
	}
	return worker
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"strconv"
	"strings"

	"github.com/goburrow/modbus"
)

/*
### register mode = standard
=> register address = (mm x 100) + ppp - 1
   where mm <= 162 && ppp <= 99

### register mode = modified
=> register address = (mm x 256) + ppp - 1
   where mm <= 63 && ppp <= 255
*/

type Parameter struct {
	Menu  int
	Index int
	Size  uint16
}

// NewParameterFromMenu creates a parameter from a menu.index string.
func NewParameterFromMenu(menu string) (Parameter, error) {
	var err error
	var p Parameter

	toks := strings.Split(menu, ".")
	m, err := strconv.Atoi(toks[0])
	if err != nil {
		return p, err
	}
	if m > 162 {
		return p, fmt.Errorf("motor: invalid menu value (%d>162) [pr=%s]", m, menu)
	}

	i, err := strconv.Atoi(toks[1])
	if err != nil {
		return p, err
	}
	if i >= 100 {
		return p, fmt.Errorf("motor: invalid index value (%d>=100) [pr=%s]", i, menu)
	}

	return Parameter{Menu: m, Index: i, Size: 1}, err
}

// NewParameter creates a parameter from its modbus address register.
func NewParameter(reg uint16) Parameter {
	return Parameter{
		Menu:  int(reg / 100),
		Index: int(reg%100) + 1,
		Size:  1,
	}
}

func (p Parameter) ToModbus() uint16 {
	return uint16(p.Menu*100 + p.Index - 1)
}

func (p Parameter) String() string {
	return fmt.Sprintf("%02d.%03d", p.Menu, p.Index)
}

type Motor struct {
	Address string
	c       modbus.Client
}

func NewMotor(addr string) Motor {
	return Motor{
		Address: addr,
		c:       modbus.TCPClient(addr),
	}
}

func (m *Motor) read(p Parameter) ([]byte, error) {
	o, err := m.c.ReadHoldingRegisters(p.ToModbus(), p.Size)
	if err != nil {
		return nil, err
	}
	return o, err
}

func (m *Motor) write(p Parameter, v []byte) ([]byte, error) {
	return m.c.WriteMultipleRegisters(p.ToModbus(), 1, v)
}
<commit_msg>motor: doc cosmetics<commit_after>package main

import (
	"fmt"
	"strconv"
	"strings"

	"github.com/goburrow/modbus"
)

/*
### register mode = standard
=> register address = (mm x 100) + ppp - 1
   where mm <= 162 && ppp <= 99

### register mode = modified
=> register address = (mm x 256) + ppp - 1
   where mm <= 63 && ppp <= 255
*/

type Parameter struct {
	Menu  int
	Index int
	Size  uint16
}

// NewParameterFromMenu creates a parameter from a menu.index string.
func NewParameterFromMenu(menu string) (Parameter, error) {
	var err error
	var p Parameter

	toks := strings.Split(menu, ".")
	m, err := strconv.Atoi(toks[0])
	if err != nil {
		return p, err
	}
	if m > 162 {
		return p, fmt.Errorf("motor: invalid menu value (%d>162) [pr=%s]", m, menu)
	}

	i, err := strconv.Atoi(toks[1])
	if err != nil {
		return p, err
	}
	if i >= 100 {
		return p, fmt.Errorf("motor: invalid index value (%d>=100) [pr=%s]", i, menu)
	}

	return Parameter{Menu: m, Index: i, Size: 1}, err
}

// NewParameter creates a parameter from its modbus register.
func NewParameter(reg uint16) Parameter {
	return Parameter{
		Menu:  int(reg / 100),
		Index: int(reg%100) + 1,
		Size:  1,
	}
}

func (p Parameter) ToModbus() uint16 {
	return uint16(p.Menu*100 + p.Index - 1)
}

func (p Parameter) String() string {
	return fmt.Sprintf("%02d.%03d", p.Menu, p.Index)
}

type Motor struct {
	Address string
	c       modbus.Client
}

func NewMotor(addr string) Motor {
	return Motor{
		Address: addr,
		c:       modbus.TCPClient(addr),
	}
}

func (m *Motor) read(p Parameter) ([]byte, error) {
	o, err := m.c.ReadHoldingRegisters(p.ToModbus(), p.Size)
	if err != nil {
		return nil, err
	}
	return o, err
}

func (m *Motor) write(p Parameter, v []byte) ([]byte, error) {
	return m.c.WriteMultipleRegisters(p.ToModbus(), 1, v)
}
<|endoftext|>