<commit_before>package nodist

import (
  "errors"
  "os"
  "io/ioutil"
  "strings"
  "sort"
  "encoding/json"
  "github.com/marcelklehr/semver"
)

import . "github.com/tj/go-debug"

var debug = Debug("nodist:shim")
const pathSep = string(os.PathSeparator)

func GetCurrentNodeVersionSpec(currentDir string) (spec string) {
  // Determine version spec
  var v string
  if v = os.Getenv("NODE_VERSION"); v != "" {
    spec = v
    debug("NODE_VERSION found:'%s'", spec)
  } else
  if v = os.Getenv("NODIST_NODE_VERSION"); v != "" {
    spec = v
    debug("NODIST_NODE_VERSION found:'%s'", spec)
  } else
  if v, err := getLocalEngineNode(currentDir); err == nil && strings.Trim(string(v), " \r\n") != "" {
    spec = v
    debug("Target engine found:'%s'", spec)
  } else
  if v, localFile, err := getLocalNodeVersion(currentDir); err == nil && strings.Trim(string(v), " \r\n") != "" {
    spec = string(v)
    debug("Local file found:'%s' @ %s", spec, localFile)
  } else
  if v, err := ioutil.ReadFile(os.Getenv("NODIST_PREFIX")+"\\.node-version-global"); err == nil {
    spec = string(v)
    debug("Global file found: '%s'", spec)
  }

  spec = strings.Trim(spec, "v \r\n")
  return
}

func GetCurrentNpmVersionSpec(currentDir string) (spec string) {
  // Determine version spec
  var v string
  if v = os.Getenv("NODIST_NPM_VERSION"); v != "" {
    spec = v
    debug("NODIST_NPM_VERSION found:'%s'", spec)
  } else
  if v, err := getLocalEngineNpm(currentDir); err == nil && strings.Trim(string(v), " \r\n") != "" {
    spec = v
    debug("Target engine npm spec found:'%s'", spec)
  } else
  if v, localFile, err := getLocalNpmVersion(currentDir); err == nil && strings.Trim(string(v), " \r\n") != "" {
    spec = string(v)
    debug("Local file with npm spec found:'%s' @ %s", spec, localFile)
  } else
  if v, err := ioutil.ReadFile(os.Getenv("NODIST_PREFIX")+"\\.npm-version-global"); err == nil {
    spec = string(v)
    debug("Global file found: '%s'", spec)
  }

  spec = strings.Trim(spec, "v \r\n")
  return
}

func ResolveNodeVersion(spec string) (version string, err error){
  // Find an installed version matching the spec...

  installed, err := GetInstalledNodeVersions()

  if err != nil {
    return
  }

  version, err = resolveVersion(spec, installed)
  return
}

func GetInstalledNodeVersions() (versions []*semver.Version, err error) {
  // Determine architecture
  x64 := false
  if wantX64 := os.Getenv("NODIST_X64"); wantX64 != "" {
    x64 = (wantX64 == "1")
  }
  // construct path to version dir
  path := os.Getenv("NODIST_PREFIX")+"/v"
  if x64 {
    path += "-x64"
  }
  versions, err = getInstalledVersions(path)
  return
}

func ResolveNpmVersion(spec string, nodeVersion string) (version string, err error){
  // Find an installed version matching the spec...

  installed, err := GetInstalledNpmVersions()

  if err != nil {
    return
  }

  if spec == "match" {
    spec, err = getMatchingNpmVersion(nodeVersion)
    if err != nil {
      return
    }
    // we feed this result to resolveVersion, too, because we need
    // to see if it is actually installed
  }

  version, err = resolveVersion(spec, installed)
  return
}

func resolveVersion(spec string, installed []*semver.Version) (version string, err error) {
  var constraint *semver.Constraints

  if spec != "latest" {
    constraint, err = semver.NewConstraint(spec)

    if err != nil {
      return
    }
  }

  if spec == "latest" {
    version = installed[0].String()
  }else{
    for _, v := range installed {
      debug("checking %s against %s", v.String(), spec)
      if constraint.Check(v) {
	version = v.String()
	break
      }
    }
  }

  if version == "" {
    err = errors.New("Couldn't find any matching version")
  }
  return
}

type Version struct {
  Version string
  Npm string
}

func getMatchingNpmVersion(nodeVersion string) (version string, err error) {
  file := os.Getenv("NODIST_PREFIX")+pathSep+"versions.json"
  rawJSON, err := ioutil.ReadFile(file)
  if err != nil {
    return
  }
  var versions []Version
  err = json.Unmarshal(rawJSON, &versions)
  if err != nil {
    return
  }
  for i:=0; i < len(versions); i++ {
    if versions[i].Version[1:] != nodeVersion {
      continue
    }
    version = versions[i].Npm
    return
  }
  err = errors.New("No npm version found that matches node version "+nodeVersion)
  return
}

func GetInstalledNpmVersions() (versions []*semver.Version, err error) {
  // construct path to version dir
  path := os.Getenv("NODIST_PREFIX")+"/npmv"
  versions, err = getInstalledVersions(path)
  return
}

func getInstalledVersions(path string) (versions []*semver.Version, err error) {
  entries, err := ioutil.ReadDir(path)
  if err != nil {
    return
  }

  versions = make([]*semver.Version, 0)
  for _, entry := range entries {
    if !entry.IsDir() {
      continue
    }
    v, err := semver.NewVersion(entry.Name())
    if err == nil {
      versions = append(versions, v)
    }
  }

  sort.Sort(sort.Reverse(semver.Collection(versions)))

  return
}

func getLocalNodeVersion(dir string) (version string, file string, err error) {
  version, file, err = getLocalVersion(dir, ".node-version")
  return
}

func getLocalNpmVersion(dir string) (version string, file string, err error) {
  version, file, err = getLocalVersion(dir, ".npm-version")
  return
}

func getLocalVersion(dir string, filename string) (version string, file string, returnedError error) {
  dirSlice := strings.Split(dir, pathSep) // D:\Programme\nodist => [D:, Programme, nodist]

  for len(dirSlice) != 1 {
    dir = strings.Join(dirSlice, pathSep)
    file = dir+pathSep+filename
    v, err := ioutil.ReadFile(file);

    if err == nil {
      version = string(v)
      return
    }

    if !os.IsNotExist(err) {
      returnedError = err // some other error.. bad luck.
      return
    }

    // `$ cd ..`
    dirSlice = dirSlice[:len(dirSlice)-1] // pop the last dir
  }

  version = ""
  return
}

func getLocalEngineNode(dir string) (spec string, err error) {
  packageJSON, err := getLocalPackageJSON(dir)
  if err != nil {
    return
  }
  spec = packageJSON.Engines.Node
  return
}

func getLocalEngineNpm(dir string) (spec string, err error) {
  packageJSON, err := getLocalPackageJSON(dir)
  if err != nil {
    return
  }
  spec = packageJSON.Engines.Npm
  return
}

func getLocalPackageJSON(dir string) (packageJSON PackageJSON, returnedError error) {
  debug("getTargetEngine: targetDir: %s", dir)

  dirSlice := strings.Split(dir, pathSep) // D:\Programme\nodist => [D:, Programme, nodist]

  for len(dirSlice) != 1 {
    dir = strings.Join(dirSlice, pathSep)
    file := dir+"\\package.json"
    rawPackageJSON, err := ioutil.ReadFile(file);
    debug("getTargetEngine: ReadFile %s", file)
    if err == nil {
      // no error handling for parsing, cause we don't want to use a different package.json if we've already found one
      packageJSON, returnedError = parsePackageJSON(rawPackageJSON)
      return
    }

    if !os.IsNotExist(err) {
      returnedError = err // some other error.. bad luck.
      return
    }

    // `$ cd ..`
    dirSlice = dirSlice[:len(dirSlice)-1] // pop the last dir
  }

  return
}

type PackageJSON struct {
  Engines struct {
    Npm string
    Node string
  }
}

func parsePackageJSON(rawPackageJSON []byte) (packageJSON PackageJSON, err error) {
  err = json.Unmarshal(rawPackageJSON, &packageJSON)

  if err == nil {
    debug("parsePackageJSON: %+v", packageJSON)
    return
  }

  debug("parsePackageJSON: error: %s", err.Error())

  // incorrect JSON -- bad luck
  return
}
<commit_msg>Fix #163: Add option for package.json inspection<commit_after>package nodist

import (
  "errors"
  "os"
  "io/ioutil"
  "strings"
  "sort"
  "encoding/json"
  "github.com/marcelklehr/semver"
)

import . "github.com/tj/go-debug"

var debug = Debug("nodist:shim")
const pathSep = string(os.PathSeparator)

func GetCurrentNodeVersionSpec(currentDir string) (spec string) {
  // Determine version spec
  var v string
  clever := os.Getenv("NODIST_INSPECT_PACKAGEJSON");
  if v = os.Getenv("NODE_VERSION"); v != "" {
    spec = v
    debug("NODE_VERSION found:'%s'", spec)
  } else
  if v = os.Getenv("NODIST_NODE_VERSION"); v != "" {
    spec = v
    debug("NODIST_NODE_VERSION found:'%s'", spec)
  } else
  if v, err := getLocalEngineNode(currentDir); clever != "" && err == nil && strings.Trim(string(v), " \r\n") != "" {
    spec = v
    debug("Target engine found:'%s'", spec)
  } else
  if v, localFile, err := getLocalNodeVersion(currentDir); err == nil && strings.Trim(string(v), " \r\n") != "" {
    spec = string(v)
    debug("Local file found:'%s' @ %s", spec, localFile)
  } else
  if v, err := ioutil.ReadFile(os.Getenv("NODIST_PREFIX")+"\\.node-version-global"); err == nil {
    spec = string(v)
    debug("Global file found: '%s'", spec)
  }

  spec = strings.Trim(spec, "v \r\n")
  return
}

func GetCurrentNpmVersionSpec(currentDir string) (spec string) {
  // Determine version spec
  var v string
  clever := os.Getenv("NODIST_INSPECT_PACKAGEJSON");
  if v = os.Getenv("NODIST_NPM_VERSION"); v != "" {
    spec = v
    debug("NODIST_NPM_VERSION found:'%s'", spec)
  } else
  if v, err := getLocalEngineNpm(currentDir); clever != "" && err == nil && strings.Trim(string(v), " \r\n") != "" {
    spec = v
    debug("Target engine npm spec found:'%s'", spec)
  } else
  if v, localFile, err := getLocalNpmVersion(currentDir); err == nil && strings.Trim(string(v), " \r\n") != "" {
    spec = string(v)
    debug("Local file with npm spec found:'%s' @ %s", spec, localFile)
  } else
  if v, err := ioutil.ReadFile(os.Getenv("NODIST_PREFIX")+"\\.npm-version-global"); err == nil {
    spec = string(v)
    debug("Global file found: '%s'", spec)
  }

  spec = strings.Trim(spec, "v \r\n")
  return
}

func ResolveNodeVersion(spec string) (version string, err error){
  // Find an installed version matching the spec...

  installed, err := GetInstalledNodeVersions()

  if err != nil {
    return
  }

  version, err = resolveVersion(spec, installed)
  return
}

func GetInstalledNodeVersions() (versions []*semver.Version, err error) {
  // Determine architecture
  x64 := false
  if wantX64 := os.Getenv("NODIST_X64"); wantX64 != "" {
    x64 = (wantX64 == "1")
  }
  // construct path to version dir
  path := os.Getenv("NODIST_PREFIX")+"/v"
  if x64 {
    path += "-x64"
  }
  versions, err = getInstalledVersions(path)
  return
}

func ResolveNpmVersion(spec string, nodeVersion string) (version string, err error){
  // Find an installed version matching the spec...

  installed, err := GetInstalledNpmVersions()

  if err != nil {
    return
  }

  if spec == "match" {
    spec, err = getMatchingNpmVersion(nodeVersion)
    if err != nil {
      return
    }
    // we feed this result to resolveVersion, too, because we need
    // to see if it is actually installed
  }

  version, err = resolveVersion(spec, installed)
  return
}

func resolveVersion(spec string, installed []*semver.Version) (version string, err error) {
  var constraint *semver.Constraints

  if spec != "latest" {
    constraint, err = semver.NewConstraint(spec)

    if err != nil {
      return
    }
  }

  if spec == "latest" {
    version = installed[0].String()
  }else{
    for _, v := range installed {
      debug("checking %s against %s", v.String(), spec)
      if constraint.Check(v) {
	version = v.String()
	break
      }
    }
  }

  if version == "" {
    err = errors.New("Couldn't find any matching version")
  }
  return
}

type Version struct {
  Version string
  Npm string
}

func getMatchingNpmVersion(nodeVersion string) (version string, err error) {
  file := os.Getenv("NODIST_PREFIX")+pathSep+"versions.json"
  rawJSON, err := ioutil.ReadFile(file)
  if err != nil {
    return
  }
  var versions []Version
  err = json.Unmarshal(rawJSON, &versions)
  if err != nil {
    return
  }
  for i:=0; i < len(versions); i++ {
    if versions[i].Version[1:] != nodeVersion {
      continue
    }
    version = versions[i].Npm
    return
  }
  err = errors.New("No npm version found that matches node version "+nodeVersion)
  return
}

func GetInstalledNpmVersions() (versions []*semver.Version, err error) {
  // construct path to version dir
  path := os.Getenv("NODIST_PREFIX")+"/npmv"
  versions, err = getInstalledVersions(path)
  return
}

func getInstalledVersions(path string) (versions []*semver.Version, err error) {
  entries, err := ioutil.ReadDir(path)
  if err != nil {
    return
  }

  versions = make([]*semver.Version, 0)
  for _, entry := range entries {
    if !entry.IsDir() {
      continue
    }
    v, err := semver.NewVersion(entry.Name())
    if err == nil {
      versions = append(versions, v)
    }
  }

  sort.Sort(sort.Reverse(semver.Collection(versions)))

  return
}

func getLocalNodeVersion(dir string) (version string, file string, err error) {
  version, file, err = getLocalVersion(dir, ".node-version")
  return
}

func getLocalNpmVersion(dir string) (version string, file string, err error) {
  version, file, err = getLocalVersion(dir, ".npm-version")
  return
}

func getLocalVersion(dir string, filename string) (version string, file string, returnedError error) {
  dirSlice := strings.Split(dir, pathSep) // D:\Programme\nodist => [D:, Programme, nodist]

  for len(dirSlice) != 1 {
    dir = strings.Join(dirSlice, pathSep)
    file = dir+pathSep+filename
    v, err := ioutil.ReadFile(file);

    if err == nil {
      version = string(v)
      return
    }

    if !os.IsNotExist(err) {
      returnedError = err // some other error.. bad luck.
      return
    }

    // `$ cd ..`
    dirSlice = dirSlice[:len(dirSlice)-1] // pop the last dir
  }

  version = ""
  return
}

func getLocalEngineNode(dir string) (spec string, err error) {
  packageJSON, err := getLocalPackageJSON(dir)
  if err != nil {
    return
  }
  spec = packageJSON.Engines.Node
  return
}

func getLocalEngineNpm(dir string) (spec string, err error) {
  packageJSON, err := getLocalPackageJSON(dir)
  if err != nil {
    return
  }
  spec = packageJSON.Engines.Npm
  return
}

func getLocalPackageJSON(dir string) (packageJSON PackageJSON, returnedError error) {
  debug("getTargetEngine: targetDir: %s", dir)

  dirSlice := strings.Split(dir, pathSep) // D:\Programme\nodist => [D:, Programme, nodist]

  for len(dirSlice) != 1 {
    dir = strings.Join(dirSlice, pathSep)
    file := dir+"\\package.json"
    rawPackageJSON, err := ioutil.ReadFile(file);
    debug("getTargetEngine: ReadFile %s", file)
    if err == nil {
      // no error handling for parsing, cause we don't want to use a different package.json if we've already found one
      packageJSON, returnedError = parsePackageJSON(rawPackageJSON)
      return
    }

    if !os.IsNotExist(err) {
      returnedError = err // some other error.. bad luck.
      return
    }

    // `$ cd ..`
    dirSlice = dirSlice[:len(dirSlice)-1] // pop the last dir
  }

  return
}

type PackageJSON struct {
  Engines struct {
    Npm string
    Node string
  }
}

func parsePackageJSON(rawPackageJSON []byte) (packageJSON PackageJSON, err error) {
  err = json.Unmarshal(rawPackageJSON, &packageJSON)

  if err == nil {
    debug("parsePackageJSON: %+v", packageJSON)
    return
  }

  debug("parsePackageJSON: error: %s", err.Error())

  // incorrect JSON -- bad luck
  return
}
<|endoftext|>


<commit_before>// Copyright (c) 2017 Cisco and/or its affiliates.
//
// 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 sql

import (
	"io"
)

// Broker execute SQL statements in the data store.
// It marshals/un-marshals go structures.
type Broker interface {
	// Put puts single value (inBinding) into the data store
	// Example usage:
	//
	//    err = db.Put("ID='James Bond'", &User{"James Bond", "James", "Bond"})
	//
	Put(where Expression, inBinding interface{} /* TODO opts ...PutOption*/) error

	// NewTxn creates a transaction / batch
	NewTxn() Txn

	// GetValue retrieves one item based on the query. If the item exists it is un-marshaled into the outBinding.
	//
	// Example usage 1:
	//
	//    query := sql.FROM(UserTable, sql.WHERE(sql.Field(&UserTable.ID, sql.EQ("Bond")))
	//    user := &User{}
	//    found, err := db.GetValue(query, user)
	//
	// Example usage 2:
	//
	//    query := sql.FROM(JamesBond, sql.WHERE(sql.PK(&JamesBond.ID))
	//    user := &User{}
	//    found, err := db.GetValue(query, user)
	//
	GetValue(query string, outBinding interface{}) (found bool, err error)

	// ListValues returns an iterator that enables to traverse all items returned by the query
	// Use utilities to:
	// - generate query string
	// - fill slice by values from iterator (SliceIt).
	//
	// Example usage 1 (fill slice by values from iterator):
	//
	//    query := sql.FROM(UserTable, sql.WHERE(sql.Field(&UserTable.LastName, sql.EQ("Bond")))
	//    iterator := db.ListValues(query)
	//    users := &[]User{}
	//    err := sql.SliceIt(users, iterator)
	//
	// Example usage 2:
	//
	//    query := sql.FROM(UserTable, sql.WHERE(sql.Exec("last_name='Bond'")))
	//    iterator := db.ListValues(query)
	//    users := &[]User{}
	//    err := sql.SliceIt(users, iterator)
	//
	// Example usage 3:
	//
	//    iterator := db.ListValues("select ID, first_name, last_name from User where last_name='Bond'")
	//    user := map[string]interface{}
	//    stop := iterator.GetNext(user)
	//
	ListValues(query Expression) ValIterator

	// Delete removes data that from the data store
	// Example usage 1:
	//
	//    query := sql.FROM(JamesBond, sql.WHERE(sql.PK(&JamesBond.ID))
	//    err := db.Delete(query)
	//
	// Example usage 2:
	//
	//    err := db.Delete("from User where ID='James Bond'")
	//
	// Example usage 3:
	//
	//    query := sql.FROM(UserTable, sql.WHERE(sql.Field(&UserTable.LastName, sql.EQ("Bond")))
	//    err := db.Delete(query)
	//
	Delete(fromWhere Expression) error

	// Executes the SQL statement (can be used for example for create "table/type" if not exits...)
	// Example usage:
	//
	//  	 err := db.Exec("CREATE INDEX IF NOT EXISTS...")
	Exec(statement string) error
}

// ValIterator is an iterator returned by ListValues call.
type ValIterator interface {
	// GetNext retrieves the current "row" from query result. GetValue is un-marshaled into the provided argument.
	// The stop=true will be returned if there is no more record or if error occurred (to get the error call Close())
	// Whe the stop=true is returned the outBinding was not updated.
	GetNext(outBinding interface{}) (stop bool)

	// Closer is used to retrieve error (if occurred) & releases the cursor
	io.Closer
}

// Txn allows to group operations into the transaction or batch (depending on a particular data store).
// Transaction executes usually multiple operations in a more efficient way in contrast to executing them one by one.
type Txn interface {
	// Put adds put operation into the transaction
	Put(where Expression, data interface{}) Txn
	// Delete adds delete operation, which removes value identified by the key, into the transaction
	Delete(fromWhere Expression) Txn
	// Commit tries to commit the transaction.
	Commit() error
}
<commit_msg>ODPM-419 fix small typos in sql_brokr_api.go comments<commit_after>// Copyright (c) 2017 Cisco and/or its affiliates.
//
// 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 sql

import (
	"io"
)

// Broker execute SQL statements in the data store.
// It marshals/un-marshals go structures.
type Broker interface {
	// Put puts single value (inBinding) into the data store
	// Example usage:
	//
	//    err = db.Put("ID='James Bond'", &User{"James Bond", "James", "Bond"})
	//
	Put(where Expression, inBinding interface{} /* TODO opts ...PutOption*/) error

	// NewTxn creates a transaction / batch
	NewTxn() Txn

	// GetValue retrieves one item based on the query. If the item exists it is un-marshaled into the outBinding.
	//
	// Example usage 1:
	//
	//    query := sql.FROM(UserTable, sql.WHERE(sql.Field(&UserTable.ID, sql.EQ("Bond")))
	//    user := &User{}
	//    found, err := db.GetValue(query, user)
	//
	// Example usage 2:
	//
	//    query := sql.FROM(JamesBond, sql.WHERE(sql.PK(&JamesBond.ID))
	//    user := &User{}
	//    found, err := db.GetValue(query, user)
	//
	GetValue(query string, outBinding interface{}) (found bool, err error)

	// ListValues returns an iterator that enables to traverse all items returned by the query
	// Use utilities to:
	// - generate query string
	// - fill slice by values from iterator (SliceIt).
	//
	// Example usage 1 (fill slice by values from iterator):
	//
	//    query := sql.FROM(UserTable, sql.WHERE(sql.Field(&UserTable.LastName, sql.EQ("Bond")))
	//    iterator := db.ListValues(query)
	//    users := &[]User{}
	//    err := sql.SliceIt(users, iterator)
	//
	// Example usage 2:
	//
	//    query := sql.FROM(UserTable, sql.WHERE(sql.Exec("last_name='Bond'")))
	//    iterator := db.ListValues(query)
	//    users := &[]User{}
	//    err := sql.SliceIt(users, iterator)
	//
	// Example usage 3:
	//
	//    iterator := db.ListValues("select ID, first_name, last_name from User where last_name='Bond'")
	//    user := map[string]interface{}
	//    stop := iterator.GetNext(user)
	//
	ListValues(query Expression) ValIterator

	// Delete removes data from the data store
	// Example usage 1:
	//
	//    query := sql.FROM(JamesBond, sql.WHERE(sql.PK(&JamesBond.ID))
	//    err := db.Delete(query)
	//
	// Example usage 2:
	//
	//    err := db.Delete("from User where ID='James Bond'")
	//
	// Example usage 3:
	//
	//    query := sql.FROM(UserTable, sql.WHERE(sql.Field(&UserTable.LastName, sql.EQ("Bond")))
	//    err := db.Delete(query)
	//
	Delete(fromWhere Expression) error

	// Executes the SQL statement (can be used for example for create "table/type" if not exits...)
	// Example usage:
	//
	//  	 err := db.Exec("CREATE INDEX IF NOT EXISTS...")
	Exec(statement string) error
}

// ValIterator is an iterator returned by ListValues call.
type ValIterator interface {
	// GetNext retrieves the current "row" from query result. GetValue is un-marshaled into the provided argument.
	// The stop=true will be returned if there is no more record or if error occurred (to get the error call Close())
	// When the stop=true is returned the outBinding was not updated.
	GetNext(outBinding interface{}) (stop bool)

	// Closer is used to retrieve error (if occurred) & releases the cursor
	io.Closer
}

// Txn allows to group operations into the transaction or batch (depending on a particular data store).
// Transaction executes usually multiple operations in a more efficient way in contrast to executing them one by one.
type Txn interface {
	// Put adds put operation into the transaction
	Put(where Expression, data interface{}) Txn
	// Delete adds delete operation, which removes value identified by the key, into the transaction
	Delete(fromWhere Expression) Txn
	// Commit tries to commit the transaction.
	Commit() error
}
<|endoftext|>


<commit_before>// 26 january 2015
package iface

// TODO settle the overloading of definitions of 'message'

// Server represents a server.
type Server interface {
	// Send and Recv return channels that send and receive, respectively, Messages to/from the server.
	// The values of these channels are guaranteed not to change during the lifetime of the Server object, and they shall not be closed during said lifetime.
	Send() chan<- Message
	Recv() <-chan Message

	// Connect establishes the connection to the Server.
	// After Connect, Send and Recv will begin processing messages.
	// A Message of type Connected will be received once the connection has been established.
	// A Message of type Error will be received if the connection fails.
	// Connect may send protocol-specific handshake messages as necessary; the Server object's actual type should define an interface for this (for instance, for establishing the initial username and identifying information about a user).
	// (TODO what happens if already connected)
	Connect()

	// Raw returns a Message that, when sent, issues the given byte sequence directly to the server.
	// TODO hostmasks
	Raw(sequence []byte) Message

	// Join returns a Message that, when sent, ask the server to join a given channel.
	// If the Join succeeds, a Message of type Joined will be received; the value of Channel() on that Message should be used for all future accesses to that channel (until the channel is left or the server is disconnected).
	Join(channel string, password string) Message

	// Query is similar to Join, except that instead of joining a channel, it initiates a private conversation ("query") with a given user.
	// Because some protocols (IRC, for instance) don't have a special state for "querying someone else", Query() requires a line to send to the user at the same time.
	// On success, the received Message type will be QueryStarted instead of Joined, but the semantics are the same.
	Query(nick string, message []byte) Message

	// QueryDo is similar to Query, except the message is sent as an action; see Channel.Do().
	QueryDo(nick string, message []byte) Message

	// Quit returns a Message that, when sent, will lead to being disconnected from the server.
	// You may specify an optional reason.
	Quit(reason []byte) Message

	// TODO Nick()?

	// SetNick returns a Message that, when sent, requests that the server change your nickname to the given nick.
	// If successful, a Message of type YourNickChanged will be received.
	SetNick(newnick string) Message

	// Format takes the given Formatted and returns the byte sequence that it is equivalent to in the given protocol.
	// Formatting styles that are not supported are merely ignored.
	Format(f Formatted) []byte

	// Away returns a Message that, when sent, marks yourself as away.
	Away(reason string) Message

	// Back returns a Message that, when sent, marks yourself as no longer away.
	Back() Message

	// TODO user mode translation
}
<commit_msg>More TODOs.<commit_after>// 26 january 2015
package iface

// TODO settle the overloading of definitions of 'message'
// TODO really return Messages and not send them immediately?

// Server represents a server.
type Server interface {
	// Send and Recv return channels that send and receive, respectively, Messages to/from the server.
	// The values of these channels are guaranteed not to change during the lifetime of the Server object, and they shall not be closed during said lifetime.
	Send() chan<- Message
	Recv() <-chan Message

	// Connect establishes the connection to the Server.
	// After Connect, Send and Recv will begin processing messages.
	// A Message of type Connected will be received once the connection has been established.
	// A Message of type Error will be received if the connection fails.
	// Connect may send protocol-specific handshake messages as necessary; the Server object's actual type should define an interface for this (for instance, for establishing the initial username and identifying information about a user).
	// (TODO what happens if already connected)
	Connect()

	// Raw returns a Message that, when sent, issues the given byte sequence directly to the server.
	// TODO hostmasks
	Raw(sequence []byte) Message

	// Join returns a Message that, when sent, ask the server to join a given channel.
	// If the Join succeeds, a Message of type Joined will be received; the value of Channel() on that Message should be used for all future accesses to that channel (until the channel is left or the server is disconnected).
	Join(channel string, password string) Message

	// Query is similar to Join, except that instead of joining a channel, it initiates a private conversation ("query") with a given user.
	// Because some protocols (IRC, for instance) don't have a special state for "querying someone else", Query() requires a line to send to the user at the same time.
	// On success, the received Message type will be QueryStarted instead of Joined, but the semantics are the same.
	Query(nick string, message []byte) Message

	// QueryDo is similar to Query, except the message is sent as an action; see Channel.Do().
	QueryDo(nick string, message []byte) Message

	// Quit returns a Message that, when sent, will lead to being disconnected from the server.
	// You may specify an optional reason.
	Quit(reason []byte) Message

	// TODO Nick()?

	// SetNick returns a Message that, when sent, requests that the server change your nickname to the given nick.
	// If successful, a Message of type YourNickChanged will be received.
	SetNick(newnick string) Message

	// Format takes the given Formatted and returns the byte sequence that it is equivalent to in the given protocol.
	// Formatting styles that are not supported are merely ignored.
	Format(f Formatted) []byte

	// Away returns a Message that, when sent, marks yourself as away.
	Away(reason string) Message

	// Back returns a Message that, when sent, marks yourself as no longer away.
	Back() Message

	// TODO user mode translation
}
<|endoftext|>


<commit_before>// roshi-server provides a REST-y HTTP service to interact with a farm.
package main

import (
	"encoding/json"
	_ "expvar"
	"flag"
	"fmt"
	logpkg "log"
	"math"
	"net/http"
	_ "net/http/pprof"
	"net/url"
	"os"
	"runtime"
	"sort"
	"strconv"
	"strings"
	"time"

	"github.com/gorilla/pat"
	"github.com/peterbourgon/g2s"
	"github.com/soundcloud/roshi/cluster"
	"github.com/soundcloud/roshi/common"
	"github.com/soundcloud/roshi/farm"
	"github.com/soundcloud/roshi/instrumentation/statsd"
	"github.com/soundcloud/roshi/shard"
	"github.com/streadway/handy/breaker"
)

var (
	stats = g2s.Noop()
	log   = logpkg.New(os.Stdout, "", logpkg.Lmicroseconds)
)

func main() {
	var (
		redisInstances             = flag.String("redis.instances", "", "Semicolon-separated list of comma-separated lists of Redis instances")
		redisConnectTimeout        = flag.Duration("redis.connect.timeout", 3*time.Second, "Redis connect timeout")
		redisReadTimeout           = flag.Duration("redis.read.timeout", 3*time.Second, "Redis read timeout")
		redisWriteTimeout          = flag.Duration("redis.write.timeout", 3*time.Second, "Redis write timeout")
		redisMCPI                  = flag.Int("redis.mcpi", 10, "Max connections per Redis instance")
		redisHash                  = flag.String("redis.hash", "murmur3", "Redis hash function: murmur3, fnv, fnva")
		farmWriteQuorum            = flag.String("farm.write.quorum", "51%", "Write quorum, either number of clusters (2) or percentage of clusters (51%)")
		farmReadStrategy           = flag.String("farm.read.strategy", "SendAllReadAll", "Farm read strategy: SendAllReadAll, SendOneReadOne, SendAllReadFirstLinger, SendVarReadFirstLinger")
		farmReadThresholdRate      = flag.Int("farm.read.threshold.rate", 2000, "Baseline SendAll keys read per sec, additional keys are SendOne (SendVarReadFirstLinger strategy only)")
		farmReadThresholdLatency   = flag.Duration("farm.read.threshold.latency", 50*time.Millisecond, "If a SendOne read has not returned anything after this latency, it's promoted to SendAll (SendVarReadFirstLinger strategy only)")
		farmRepairStrategy         = flag.String("farm.repair.strategy", "RateLimitedRepairs", "Farm repair strategy: AllRepairs, NoRepairs, RateLimitedRepairs")
		farmRepairMaxKeysPerSecond = flag.Int("farm.repair.max.keys.per.second", 1000, "Max repaired keys per second (RateLimited repairer only)")
		maxSize                    = flag.Int("max.size", 10000, "Maximum number of events per key")
		statsdAddress              = flag.String("statsd.address", "", "Statsd address (blank to disable)")
		statsdSampleRate           = flag.Float64("statsd.sample.rate", 0.1, "Statsd sample rate for normal metrics")
		statsdBucketPrefix         = flag.String("statsd.bucket.prefix", "myservice.", "Statsd bucket key prefix, including trailing period")
		httpCircuitBreaker         = flag.Bool("http.circuit.breaker", true, "Enable HTTP server circuit breaker")
		httpAddress                = flag.String("http.address", ":6302", "HTTP listen address")
	)
	flag.Parse()
	log.Printf("GOMAXPROCS %d", runtime.GOMAXPROCS(-1))

	// Set up statsd instrumentation, if it's specified.
	if *statsdAddress != "" {
		var err error
		stats, err = g2s.Dial("udp", *statsdAddress)
		if err != nil {
			log.Fatal(err)
		}
	}

	// Parse read strategy.
	var readStrategy farm.ReadStrategy
	switch strings.ToLower(*farmReadStrategy) {
	case "sendallreadall":
		readStrategy = farm.SendAllReadAll
	case "sendonereadone":
		readStrategy = farm.SendOneReadOne
	case "sendallreadfirstlinger":
		readStrategy = farm.SendAllReadFirstLinger
	case "sendvarreadfirstlinger":
		readStrategy = farm.SendVarReadFirstLinger(*farmReadThresholdRate, *farmReadThresholdLatency)
	default:
		log.Fatalf("unknown read strategy '%s'", *farmReadStrategy)
	}
	log.Printf("using %s read strategy", *farmReadStrategy)

	// Parse repair strategy.
	var repairStrategy farm.RepairStrategy
	switch strings.ToLower(*farmRepairStrategy) {
	case "allrepairs":
		repairStrategy = farm.AllRepairs
	case "norepairs":
		repairStrategy = farm.NoRepairs
	case "ratelimitedrepairs":
		repairStrategy = farm.RateLimitedRepairs(*farmRepairMaxKeysPerSecond)
	default:
		log.Fatalf("unknown repair strategy '%s'", *farmRepairStrategy)
	}
	log.Printf("using %s repair strategy", *farmRepairStrategy)

	// Parse hash function.
	var hashFunc func(string) uint32
	switch strings.ToLower(*redisHash) {
	case "murmur3":
		hashFunc = shard.Murmur3
	case "fnv":
		hashFunc = shard.FNV
	case "fnva":
		hashFunc = shard.FNVa
	default:
		log.Fatalf("unknown hash '%s'", *redisHash)
	}

	// Build the farm.
	farm, err := newFarm(
		*redisInstances,
		*farmWriteQuorum,
		*redisConnectTimeout, *redisReadTimeout, *redisWriteTimeout,
		*redisMCPI,
		hashFunc,
		readStrategy,
		repairStrategy,
		*maxSize,
		*statsdSampleRate,
		*statsdBucketPrefix,
	)
	if err != nil {
		log.Fatal(err)
	}

	// Build the HTTP server.
	r := pat.New()
	r.Add("GET", "/debug", http.DefaultServeMux)
	r.Get("/", handleSelect(farm))
	r.Post("/", handleInsert(farm))
	r.Delete("/", handleDelete(farm))
	h := http.Handler(r)
	if *httpCircuitBreaker {
		log.Printf("using HTTP circuit breaker")
		h = breaker.DefaultBreaker(h)
	}

	// Go for it.
	log.Printf("listening on %s", *httpAddress)
	log.Fatal(http.ListenAndServe(*httpAddress, h))
}

func newFarm(
	redisInstances string,
	writeQuorumStr string,
	connectTimeout, readTimeout, writeTimeout time.Duration,
	redisMCPI int,
	hash func(string) uint32,
	readStrategy farm.ReadStrategy,
	repairStrategy farm.RepairStrategy,
	maxSize int,
	statsdSampleRate float64,
	bucketPrefix string,
) (*farm.Farm, error) {
	// Build instrumentation.
	instr := statsd.New(stats, float32(statsdSampleRate), bucketPrefix)

	// Parse out and build clusters.
	clusters := []cluster.Cluster{}
	for i, clusterInstances := range strings.Split(redisInstances, ";") {
		addresses := stripBlank(strings.Split(clusterInstances, ","))
		if len(addresses) <= 0 {
			continue
		}
		clusters = append(clusters, cluster.New(
			shard.New(
				addresses,
				connectTimeout, readTimeout, writeTimeout,
				redisMCPI,
				hash,
			),
			maxSize,
			instr,
		))
		log.Printf("Redis cluster %d: %d instance(s)", i+1, len(addresses))
	}
	if len(clusters) <= 0 {
		return nil, fmt.Errorf("no cluster(s)")
	}

	// Evaluate writeQuorum.
	writeQuorum, err := evaluateScalarPercentage(writeQuorumStr, len(clusters))
	if err != nil {
		return nil, err
	}

	// Build and return Farm.
	return farm.New(
		clusters,
		writeQuorum,
		readStrategy,
		repairStrategy,
		instr,
	), nil
}

func handleSelect(selecter farm.Selecter) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		began := time.Now()

		if err := r.ParseForm(); err != nil {
			respondError(w, r.Method, r.URL.String(), http.StatusInternalServerError, err)
			return
		}
		offset := parseInt(r.Form, "offset", 0)
		limit := parseInt(r.Form, "limit", 10)
		coalesce := parseBool(r.Form, "coalesce", false)

		var keys [][]byte
		defer r.Body.Close()
		if err := json.NewDecoder(r.Body).Decode(&keys); err != nil {
			respondError(w, r.Method, r.URL.String(), http.StatusBadRequest, err)
			return
		}

		keyStrings := make([]string, len(keys))
		for i := range keys {
			keyStrings[i] = string(keys[i])
		}

		var records interface{}
		if coalesce {
			// We need to Select from 0 to offset+limit, flatten the map to a
			// single ordered slice, and then cut off the last limit elements.
			m, err := selecter.Select(keyStrings, 0, offset+limit)
			if err != nil {
				respondError(w, r.Method, r.URL.String(), http.StatusInternalServerError, err)
				return
			}
			records = flatten(m, offset, limit)
		} else {
			// We can directly Select using the given offset and limit.
			m, err := selecter.Select(keyStrings, offset, limit)
			if err != nil {
				respondError(w, r.Method, r.URL.String(), http.StatusInternalServerError, err)
				return
			}
			records = m
		}

		respondSelected(w, keys, offset, limit, records, time.Since(began))
	}
}

func handleInsert(inserter cluster.Inserter) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		began := time.Now()

		var tuples []common.KeyScoreMember
		if err := json.NewDecoder(r.Body).Decode(&tuples); err != nil {
			respondError(w, r.Method, r.URL.String(), http.StatusBadRequest, err)
			return
		}

		if err := inserter.Insert(tuples); err != nil {
			respondError(w, r.Method, r.URL.String(), http.StatusInternalServerError, err)
			return
		}

		respondInserted(w, len(tuples), time.Since(began))
	}
}

func handleDelete(deleter cluster.Deleter) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		began := time.Now()

		var tuples []common.KeyScoreMember
		if err := json.NewDecoder(r.Body).Decode(&tuples); err != nil {
			respondError(w, r.Method, r.URL.String(), http.StatusBadRequest, err)
			return
		}

		if err := deleter.Delete(tuples); err != nil {
			respondError(w, r.Method, r.URL.String(), http.StatusInternalServerError, err)
			return
		}

		respondDeleted(w, len(tuples), time.Since(began))
	}
}

func flatten(m map[string][]common.KeyScoreMember, offset, limit int) []common.KeyScoreMember {
	a := common.KeyScoreMembers{}
	for _, tuples := range m {
		a = append(a, tuples...)
	}

	sort.Sort(a)

	if len(a) < offset {
		return []common.KeyScoreMember{}
	}
	a = a[offset:]

	if len(a) > limit {
		a = a[:limit]
	}

	return a
}

func parseInt(values url.Values, key string, defaultValue int) int {
	value, err := strconv.ParseInt(values.Get(key), 10, 64)
	if err != nil {
		return defaultValue
	}
	return int(value)
}

func parseBool(values url.Values, key string, defaultValue bool) bool {
	value, err := strconv.ParseBool(values.Get(key))
	if err != nil {
		return defaultValue
	}
	return value
}

func respondInserted(w http.ResponseWriter, n int, duration time.Duration) {
	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(map[string]interface{}{
		"inserted": n,
		"duration": duration.String(),
	})
}

func respondSelected(w http.ResponseWriter, keys [][]byte, offset, limit int, records interface{}, duration time.Duration) {
	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(map[string]interface{}{
		"keys":     keys,
		"offset":   offset,
		"limit":    limit,
		"records":  records,
		"duration": duration.String(),
	})
}

func respondDeleted(w http.ResponseWriter, n int, duration time.Duration) {
	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(map[string]interface{}{
		"deleted":  n,
		"duration": duration.String(),
	})
}

func respondError(w http.ResponseWriter, method, url string, code int, err error) {
	log.Printf("%s %s: HTTP %d: %s", method, url, code, err)
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(code)
	json.NewEncoder(w).Encode(map[string]interface{}{
		"error":       err.Error(),
		"code":        code,
		"description": http.StatusText(code),
	})
}

func stripBlank(src []string) []string {
	dst := []string{}
	for _, s := range src {
		if s == "" {
			continue
		}
		dst = append(dst, s)
	}
	return dst
}

// evaluateScalarPercentage takes a string of the form "P%" (percent) or "S"
// (straight scalar value), and evaluates that against the passed total n.
// Percentages mean at least that percent; for example, "50%" of 3 evaluates
// to 2. It is an error if the passed string evaluates to less than 1 or more
// than n.
func evaluateScalarPercentage(s string, n int) (int, error) {
	if n <= 0 {
		return -1, fmt.Errorf("n must be at least 1")
	}

	s = strings.TrimSpace(s)
	var value int
	if strings.HasSuffix(s, "%") {
		percentInt, err := strconv.ParseInt(s[:len(s)-1], 10, 64)
		if err != nil || percentInt <= 0 || percentInt > 100 {
			return -1, fmt.Errorf("bad percentage input '%s'", s)
		}
		value = int(math.Ceil((float64(percentInt) / 100.0) * float64(n)))
	} else {
		value64, err := strconv.ParseInt(s, 10, 64)
		if err != nil {
			return -1, fmt.Errorf("bad scalar input '%s'", s)
		}
		value = int(value64)
	}
	if value <= 0 || value > n {
		return -1, fmt.Errorf("with n=%d, value=%d (from '%s') is invalid", n, value, s)
	}
	return value, nil
}
<commit_msg>roshi-server: s/shard/pool<commit_after>// roshi-server provides a REST-y HTTP service to interact with a farm.
package main

import (
	"encoding/json"
	_ "expvar"
	"flag"
	"fmt"
	logpkg "log"
	"math"
	"net/http"
	_ "net/http/pprof"
	"net/url"
	"os"
	"runtime"
	"sort"
	"strconv"
	"strings"
	"time"

	"github.com/gorilla/pat"
	"github.com/peterbourgon/g2s"
	"github.com/soundcloud/roshi/cluster"
	"github.com/soundcloud/roshi/common"
	"github.com/soundcloud/roshi/farm"
	"github.com/soundcloud/roshi/instrumentation/statsd"
	"github.com/soundcloud/roshi/pool"
	"github.com/streadway/handy/breaker"
)

var (
	stats = g2s.Noop()
	log   = logpkg.New(os.Stdout, "", logpkg.Lmicroseconds)
)

func main() {
	var (
		redisInstances             = flag.String("redis.instances", "", "Semicolon-separated list of comma-separated lists of Redis instances")
		redisConnectTimeout        = flag.Duration("redis.connect.timeout", 3*time.Second, "Redis connect timeout")
		redisReadTimeout           = flag.Duration("redis.read.timeout", 3*time.Second, "Redis read timeout")
		redisWriteTimeout          = flag.Duration("redis.write.timeout", 3*time.Second, "Redis write timeout")
		redisMCPI                  = flag.Int("redis.mcpi", 10, "Max connections per Redis instance")
		redisHash                  = flag.String("redis.hash", "murmur3", "Redis hash function: murmur3, fnv, fnva")
		farmWriteQuorum            = flag.String("farm.write.quorum", "51%", "Write quorum, either number of clusters (2) or percentage of clusters (51%)")
		farmReadStrategy           = flag.String("farm.read.strategy", "SendAllReadAll", "Farm read strategy: SendAllReadAll, SendOneReadOne, SendAllReadFirstLinger, SendVarReadFirstLinger")
		farmReadThresholdRate      = flag.Int("farm.read.threshold.rate", 2000, "Baseline SendAll keys read per sec, additional keys are SendOne (SendVarReadFirstLinger strategy only)")
		farmReadThresholdLatency   = flag.Duration("farm.read.threshold.latency", 50*time.Millisecond, "If a SendOne read has not returned anything after this latency, it's promoted to SendAll (SendVarReadFirstLinger strategy only)")
		farmRepairStrategy         = flag.String("farm.repair.strategy", "RateLimitedRepairs", "Farm repair strategy: AllRepairs, NoRepairs, RateLimitedRepairs")
		farmRepairMaxKeysPerSecond = flag.Int("farm.repair.max.keys.per.second", 1000, "Max repaired keys per second (RateLimited repairer only)")
		maxSize                    = flag.Int("max.size", 10000, "Maximum number of events per key")
		statsdAddress              = flag.String("statsd.address", "", "Statsd address (blank to disable)")
		statsdSampleRate           = flag.Float64("statsd.sample.rate", 0.1, "Statsd sample rate for normal metrics")
		statsdBucketPrefix         = flag.String("statsd.bucket.prefix", "myservice.", "Statsd bucket key prefix, including trailing period")
		httpCircuitBreaker         = flag.Bool("http.circuit.breaker", true, "Enable HTTP server circuit breaker")
		httpAddress                = flag.String("http.address", ":6302", "HTTP listen address")
	)
	flag.Parse()
	log.Printf("GOMAXPROCS %d", runtime.GOMAXPROCS(-1))

	// Set up statsd instrumentation, if it's specified.
	if *statsdAddress != "" {
		var err error
		stats, err = g2s.Dial("udp", *statsdAddress)
		if err != nil {
			log.Fatal(err)
		}
	}

	// Parse read strategy.
	var readStrategy farm.ReadStrategy
	switch strings.ToLower(*farmReadStrategy) {
	case "sendallreadall":
		readStrategy = farm.SendAllReadAll
	case "sendonereadone":
		readStrategy = farm.SendOneReadOne
	case "sendallreadfirstlinger":
		readStrategy = farm.SendAllReadFirstLinger
	case "sendvarreadfirstlinger":
		readStrategy = farm.SendVarReadFirstLinger(*farmReadThresholdRate, *farmReadThresholdLatency)
	default:
		log.Fatalf("unknown read strategy '%s'", *farmReadStrategy)
	}
	log.Printf("using %s read strategy", *farmReadStrategy)

	// Parse repair strategy.
	var repairStrategy farm.RepairStrategy
	switch strings.ToLower(*farmRepairStrategy) {
	case "allrepairs":
		repairStrategy = farm.AllRepairs
	case "norepairs":
		repairStrategy = farm.NoRepairs
	case "ratelimitedrepairs":
		repairStrategy = farm.RateLimitedRepairs(*farmRepairMaxKeysPerSecond)
	default:
		log.Fatalf("unknown repair strategy '%s'", *farmRepairStrategy)
	}
	log.Printf("using %s repair strategy", *farmRepairStrategy)

	// Parse hash function.
	var hashFunc func(string) uint32
	switch strings.ToLower(*redisHash) {
	case "murmur3":
		hashFunc = pool.Murmur3
	case "fnv":
		hashFunc = pool.FNV
	case "fnva":
		hashFunc = pool.FNVa
	default:
		log.Fatalf("unknown hash '%s'", *redisHash)
	}

	// Build the farm.
	farm, err := newFarm(
		*redisInstances,
		*farmWriteQuorum,
		*redisConnectTimeout, *redisReadTimeout, *redisWriteTimeout,
		*redisMCPI,
		hashFunc,
		readStrategy,
		repairStrategy,
		*maxSize,
		*statsdSampleRate,
		*statsdBucketPrefix,
	)
	if err != nil {
		log.Fatal(err)
	}

	// Build the HTTP server.
	r := pat.New()
	r.Add("GET", "/debug", http.DefaultServeMux)
	r.Get("/", handleSelect(farm))
	r.Post("/", handleInsert(farm))
	r.Delete("/", handleDelete(farm))
	h := http.Handler(r)
	if *httpCircuitBreaker {
		log.Printf("using HTTP circuit breaker")
		h = breaker.DefaultBreaker(h)
	}

	// Go for it.
	log.Printf("listening on %s", *httpAddress)
	log.Fatal(http.ListenAndServe(*httpAddress, h))
}

func newFarm(
	redisInstances string,
	writeQuorumStr string,
	connectTimeout, readTimeout, writeTimeout time.Duration,
	redisMCPI int,
	hash func(string) uint32,
	readStrategy farm.ReadStrategy,
	repairStrategy farm.RepairStrategy,
	maxSize int,
	statsdSampleRate float64,
	bucketPrefix string,
) (*farm.Farm, error) {
	// Build instrumentation.
	instr := statsd.New(stats, float32(statsdSampleRate), bucketPrefix)

	// Parse out and build clusters.
	clusters := []cluster.Cluster{}
	for i, clusterInstances := range strings.Split(redisInstances, ";") {
		addresses := stripBlank(strings.Split(clusterInstances, ","))
		if len(addresses) <= 0 {
			continue
		}
		clusters = append(clusters, cluster.New(
			pool.New(
				addresses,
				connectTimeout, readTimeout, writeTimeout,
				redisMCPI,
				hash,
			),
			maxSize,
			instr,
		))
		log.Printf("Redis cluster %d: %d instance(s)", i+1, len(addresses))
	}
	if len(clusters) <= 0 {
		return nil, fmt.Errorf("no cluster(s)")
	}

	// Evaluate writeQuorum.
	writeQuorum, err := evaluateScalarPercentage(writeQuorumStr, len(clusters))
	if err != nil {
		return nil, err
	}

	// Build and return Farm.
	return farm.New(
		clusters,
		writeQuorum,
		readStrategy,
		repairStrategy,
		instr,
	), nil
}

func handleSelect(selecter farm.Selecter) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		began := time.Now()

		if err := r.ParseForm(); err != nil {
			respondError(w, r.Method, r.URL.String(), http.StatusInternalServerError, err)
			return
		}
		offset := parseInt(r.Form, "offset", 0)
		limit := parseInt(r.Form, "limit", 10)
		coalesce := parseBool(r.Form, "coalesce", false)

		var keys [][]byte
		defer r.Body.Close()
		if err := json.NewDecoder(r.Body).Decode(&keys); err != nil {
			respondError(w, r.Method, r.URL.String(), http.StatusBadRequest, err)
			return
		}

		keyStrings := make([]string, len(keys))
		for i := range keys {
			keyStrings[i] = string(keys[i])
		}

		var records interface{}
		if coalesce {
			// We need to Select from 0 to offset+limit, flatten the map to a
			// single ordered slice, and then cut off the last limit elements.
			m, err := selecter.Select(keyStrings, 0, offset+limit)
			if err != nil {
				respondError(w, r.Method, r.URL.String(), http.StatusInternalServerError, err)
				return
			}
			records = flatten(m, offset, limit)
		} else {
			// We can directly Select using the given offset and limit.
			m, err := selecter.Select(keyStrings, offset, limit)
			if err != nil {
				respondError(w, r.Method, r.URL.String(), http.StatusInternalServerError, err)
				return
			}
			records = m
		}

		respondSelected(w, keys, offset, limit, records, time.Since(began))
	}
}

func handleInsert(inserter cluster.Inserter) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		began := time.Now()

		var tuples []common.KeyScoreMember
		if err := json.NewDecoder(r.Body).Decode(&tuples); err != nil {
			respondError(w, r.Method, r.URL.String(), http.StatusBadRequest, err)
			return
		}

		if err := inserter.Insert(tuples); err != nil {
			respondError(w, r.Method, r.URL.String(), http.StatusInternalServerError, err)
			return
		}

		respondInserted(w, len(tuples), time.Since(began))
	}
}

func handleDelete(deleter cluster.Deleter) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		began := time.Now()

		var tuples []common.KeyScoreMember
		if err := json.NewDecoder(r.Body).Decode(&tuples); err != nil {
			respondError(w, r.Method, r.URL.String(), http.StatusBadRequest, err)
			return
		}

		if err := deleter.Delete(tuples); err != nil {
			respondError(w, r.Method, r.URL.String(), http.StatusInternalServerError, err)
			return
		}

		respondDeleted(w, len(tuples), time.Since(began))
	}
}

func flatten(m map[string][]common.KeyScoreMember, offset, limit int) []common.KeyScoreMember {
	a := common.KeyScoreMembers{}
	for _, tuples := range m {
		a = append(a, tuples...)
	}

	sort.Sort(a)

	if len(a) < offset {
		return []common.KeyScoreMember{}
	}
	a = a[offset:]

	if len(a) > limit {
		a = a[:limit]
	}

	return a
}

func parseInt(values url.Values, key string, defaultValue int) int {
	value, err := strconv.ParseInt(values.Get(key), 10, 64)
	if err != nil {
		return defaultValue
	}
	return int(value)
}

func parseBool(values url.Values, key string, defaultValue bool) bool {
	value, err := strconv.ParseBool(values.Get(key))
	if err != nil {
		return defaultValue
	}
	return value
}

func respondInserted(w http.ResponseWriter, n int, duration time.Duration) {
	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(map[string]interface{}{
		"inserted": n,
		"duration": duration.String(),
	})
}

func respondSelected(w http.ResponseWriter, keys [][]byte, offset, limit int, records interface{}, duration time.Duration) {
	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(map[string]interface{}{
		"keys":     keys,
		"offset":   offset,
		"limit":    limit,
		"records":  records,
		"duration": duration.String(),
	})
}

func respondDeleted(w http.ResponseWriter, n int, duration time.Duration) {
	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(map[string]interface{}{
		"deleted":  n,
		"duration": duration.String(),
	})
}

func respondError(w http.ResponseWriter, method, url string, code int, err error) {
	log.Printf("%s %s: HTTP %d: %s", method, url, code, err)
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(code)
	json.NewEncoder(w).Encode(map[string]interface{}{
		"error":       err.Error(),
		"code":        code,
		"description": http.StatusText(code),
	})
}

func stripBlank(src []string) []string {
	dst := []string{}
	for _, s := range src {
		if s == "" {
			continue
		}
		dst = append(dst, s)
	}
	return dst
}

// evaluateScalarPercentage takes a string of the form "P%" (percent) or "S"
// (straight scalar value), and evaluates that against the passed total n.
// Percentages mean at least that percent; for example, "50%" of 3 evaluates
// to 2. It is an error if the passed string evaluates to less than 1 or more
// than n.
func evaluateScalarPercentage(s string, n int) (int, error) {
	if n <= 0 {
		return -1, fmt.Errorf("n must be at least 1")
	}

	s = strings.TrimSpace(s)
	var value int
	if strings.HasSuffix(s, "%") {
		percentInt, err := strconv.ParseInt(s[:len(s)-1], 10, 64)
		if err != nil || percentInt <= 0 || percentInt > 100 {
			return -1, fmt.Errorf("bad percentage input '%s'", s)
		}
		value = int(math.Ceil((float64(percentInt) / 100.0) * float64(n)))
	} else {
		value64, err := strconv.ParseInt(s, 10, 64)
		if err != nil {
			return -1, fmt.Errorf("bad scalar input '%s'", s)
		}
		value = int(value64)
	}
	if value <= 0 || value > n {
		return -1, fmt.Errorf("with n=%d, value=%d (from '%s') is invalid", n, value, s)
	}
	return value, nil
}
<|endoftext|>


<commit_before>package adeptus

var (
	regex_xp = regexp.MustCompile(`\(?\d+xp\)?`) // Match `150xp` and `(150xp)`
)

type Upgrade interface {
	Mark string
	Name string
	Cost string
}

// ParseUpgrade generate an upgrade from a raw line
func ParseUpgrade(raw string) (Upgrade, error) {
	upgrade := Upgrade{}

	// Get the fields of the line
	fields := strings.Fields(raw)

	// The minimum number of fields is 2
	if len(fields) < 2 {
		return upgrade, fmt.Errorf("not enought")
	}

	// Check that the mark is a valid one
	if !in(fields[0], []string{"*", "+", "-"}) {
		return upgrade, fmt.Errorf("%s isn't a valid mark", fields[0])
	}

	// Set the upgrade mark
	upgrade.Mark = fields[0]
	fields = fields[1:]

	// Check if a field seems to be a cost field
	for i, field := range fields {
		if !regex_xp.MatchString(field) {
			continue
		}

		upgrade.Cost = regex_xp.FindString(field)
		fields = append(fields[:i], fields[i+1:]...)
		break
	}

	// The remaining line is the name of the upgrade
	upgrade.Name = strings.Join(fields, " ")

	return upgrade, nil
}
<commit_msg>Add a Line field to the Upgrade struct<commit_after>package adeptus

var (
	regex_xp = regexp.MustCompile(`\(?\d+xp\)?`) // Match `150xp` and `(150xp)`
)

type Upgrade interface {
	Mark string
	Name string
	Cost string
	Line int
}

// ParseUpgrade generate an upgrade from a raw line
func ParseUpgrade(raw string, line int) (Upgrade, error) {
	// Initialize a new upgrade
	upgrade := Upgrade{
		Line: line,
	}

	// Get the fields of the line
	fields := strings.Fields(raw)

	// The minimum number of fields is 2
	if len(fields) < 2 {
		return Upgrade{}, fmt.Errorf("not enought")
	}

	// Check that the mark is a valid one
	if !in(fields[0], []string{"*", "+", "-"}) {
		return Upgrade{}, fmt.Errorf("%s isn't a valid mark", fields[0])
	}

	// Set the upgrade mark
	upgrade.Mark = fields[0]
	fields = fields[1:]

	// Check if a field seems to be a cost field
	for i, field := range fields {
		if !regex_xp.MatchString(field) {
			continue
		}

		upgrade.Cost = regex_xp.FindString(field)
		fields = append(fields[:i], fields[i+1:]...)
		break
	}

	// The remaining line is the name of the upgrade
	upgrade.Name = strings.Join(fields, " ")

	return upgrade, nil
}
<|endoftext|>


<commit_before>//  Copyright (c) 2016 Couchbase, Inc.
//
// 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 camelcase

import (
	"reflect"
	"testing"

	"github.com/blevesearch/bleve/analysis"
)

func TestCamelCaseFilter(t *testing.T) {

	tests := []struct {
		input  analysis.TokenStream
		output analysis.TokenStream
	}{
		{
			input: analysis.TokenStream{
				&analysis.Token{
					Term: []byte(""),
				},
			},
			output: analysis.TokenStream{
				&analysis.Token{
					Term: []byte(""),
				},
			},
		},
		{
			input: analysis.TokenStream{
				&analysis.Token{
					Term: []byte("a"),
				},
			},
			output: analysis.TokenStream{
				&analysis.Token{
					Term: []byte("a"),
				},
			},
		},

		{
			input: analysis.TokenStream{
				&analysis.Token{
					Term: []byte("...aMACMac123macILoveGolang"),
				},
			},
			output: analysis.TokenStream{
				&analysis.Token{
					Term: []byte("..."),
				},
				&analysis.Token{
					Term: []byte("a"),
				},
				&analysis.Token{
					Term: []byte("MAC"),
				},
				&analysis.Token{
					Term: []byte("Mac"),
				},
				&analysis.Token{
					Term: []byte("123"),
				},
				&analysis.Token{
					Term: []byte("mac"),
				},
				&analysis.Token{
					Term: []byte("I"),
				},
				&analysis.Token{
					Term: []byte("Love"),
				},
				&analysis.Token{
					Term: []byte("Golang"),
				},
			},
		},
		{
			input: analysis.TokenStream{
				&analysis.Token{
					Term: []byte("Lang"),
				},
			},
			output: analysis.TokenStream{
				&analysis.Token{
					Term: []byte("Lang"),
				},
			},
		},
		{
			input: analysis.TokenStream{
				&analysis.Token{
					Term: []byte("GLang"),
				},
			},
			output: analysis.TokenStream{
				&analysis.Token{
					Term: []byte("G"),
				},
				&analysis.Token{
					Term: []byte("Lang"),
				},
			},
		},
		{
			input: analysis.TokenStream{
				&analysis.Token{
					Term: []byte("GOLang"),
				},
			},
			output: analysis.TokenStream{
				&analysis.Token{
					Term: []byte("GO"),
				},
				&analysis.Token{
					Term: []byte("Lang"),
				},
			},
		},
		{
			input: analysis.TokenStream{
				&analysis.Token{
					Term: []byte("GOOLang"),
				},
			},
			output: analysis.TokenStream{
				&analysis.Token{
					Term: []byte("GOO"),
				},
				&analysis.Token{
					Term: []byte("Lang"),
				},
			},
		},
		{
			input: analysis.TokenStream{
				&analysis.Token{
					Term: []byte("1234"),
				},
			},
			output: analysis.TokenStream{
				&analysis.Token{
					Term: []byte("1234"),
				},
			},
		},
		{
			input: analysis.TokenStream{
				&analysis.Token{
					Term: []byte("starbucks"),
				},
			},
			output: analysis.TokenStream{
				&analysis.Token{
					Term: []byte("starbucks"),
				},
			},
		},
		{
			input: analysis.TokenStream{
				&analysis.Token{
					Term: []byte("Starbucks TVSamsungIsGREAT000"),
				},
			},
			output: analysis.TokenStream{
				&analysis.Token{
					Term: []byte("Starbucks"),
				},
				&analysis.Token{
					Term: []byte(" "),
				},
				&analysis.Token{
					Term: []byte("TV"),
				},
				&analysis.Token{
					Term: []byte("Samsung"),
				},
				&analysis.Token{
					Term: []byte("Is"),
				},
				&analysis.Token{
					Term: []byte("GREAT"),
				},
				&analysis.Token{
					Term: []byte("000"),
				},
			},
		},
	}

	for _, test := range tests {
		ccFilter := NewCamelCaseFilter()
		actual := ccFilter.Filter(test.input)
		if !reflect.DeepEqual(actual, test.output) {
			t.Errorf("expected %s \n\n got %s", test.output, actual)
		}
	}
}
<commit_msg>Fix test<commit_after>//  Copyright (c) 2016 Couchbase, Inc.
//
// 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 camelcase

import (
	"reflect"
	"testing"

	"github.com/blevesearch/bleve/analysis"
)

func TestCamelCaseFilter(t *testing.T) {

	tests := []struct {
		input  analysis.TokenStream
		output analysis.TokenStream
	}{
		{
			input:  tokenStream(""),
			output: tokenStream(""),
		},
		{
			input:  tokenStream("a"),
			output: tokenStream("a"),
		},

		{
			input:  tokenStream("...aMACMac123macILoveGolang"),
			output: tokenStream("...", "a", "MAC", "Mac", "123", "mac", "I", "Love", "Golang"),
		},
		{
			input:  tokenStream("Lang"),
			output: tokenStream("Lang"),
		},
		{
			input:  tokenStream("GLang"),
			output: tokenStream("G", "Lang"),
		},
		{
			input:  tokenStream("GOLang"),
			output: tokenStream("GO", "Lang"),
		},
		{
			input:  tokenStream("GOOLang"),
			output: tokenStream("GOO", "Lang"),
		},
		{
			input:  tokenStream("1234"),
			output: tokenStream("1234"),
		},
		{
			input:  tokenStream("starbucks"),
			output: tokenStream("starbucks"),
		},
		{
			input:  tokenStream("Starbucks TVSamsungIsGREAT000"),
			output: tokenStream("Starbucks", " ", "TV", "Samsung", "Is", "GREAT", "000"),
		},
	}

	for _, test := range tests {
		ccFilter := NewCamelCaseFilter()
		actual := ccFilter.Filter(test.input)
		if !reflect.DeepEqual(actual, test.output) {
			t.Errorf("expected %s \n\n got %s", test.output, actual)
		}
	}
}

func tokenStream(termStrs ...string) analysis.TokenStream {
	tokenStream := make([]*analysis.Token, len(termStrs))
	index := 0
	for i, termStr := range termStrs {
		tokenStream[i] = &analysis.Token{
			Term:     []byte(termStr),
			Position: i + 1,
			Start:    index,
			End:      index + len(termStr),
		}
		index += len(termStr)
	}
	return analysis.TokenStream(tokenStream)
}
<|endoftext|>


<commit_before>package recurring

import "time"

// Recurring holds a channel that delivers 'ticks' of a clock at a given time of day (UTC).
type Recurring struct {
	C      <-chan time.Time // The channel on which the ticks are delivered
	c      chan time.Time
	ticker *time.Ticker
	quit   chan interface{}
}

func deadline(start time.Time, hour, min, sec, nsec int) time.Duration {
	now := start.UTC()
	nextTick := time.Date(now.Year(), now.Month(), now.Day(), hour, min, sec, nsec, time.UTC)
	if nextTick.Before(now) || nextTick.Equal(now) {
		nextTick = nextTick.Add(24 * time.Hour)
	}
	return nextTick.Sub(now)
}

// New returns a new Recurring containing a channel that will send the time at a given time of day (UTC)
// specified by the arguments. It will skip ticks if a receiver is slow enough (ie, more than 24 hours).
// Stop the Recurring to release associated resources.
func New(hour, min, sec, nsec int) *Recurring {
	now := time.Now().UTC()
	first := deadline(now, hour, min, sec, nsec)
	r := &Recurring{
		ticker: time.NewTicker(first),
		quit:   make(chan interface{}),
		c:      make(chan time.Time),
	}
	r.C = r.c
	go func() {
		for {
			select {
			case t := <-r.ticker.C:
				r.ticker.Stop()
				r.c <- t
				now := time.Now().UTC()
				r.ticker = time.NewTicker(deadline(now, hour, min, sec, nsec))
			case <-r.quit:
				r.ticker.Stop()
				return
			}
		}
	}()
	return r
}

// Stop turns off a Recurring. After Stop, no more ticks will be sent.
// Stop does not close the channel, to prevent a read from the channel succeeding incorrectly.
func (r *Recurring) Stop() {
	close(r.quit)
}
<commit_msg>refactor to allow easier testing<commit_after>package recurring

import "time"

// Recurring holds a channel that delivers 'ticks' of a clock at a given time of day (UTC).
type Recurring struct {
	C                    <-chan time.Time // The channel on which the ticks are delivered.
	c                    chan time.Time
	ticker               *time.Ticker
	quit                 chan interface{}
	hour, min, sec, nsec int
}

func deadline(start time.Time, hour, min, sec, nsec int) time.Duration {
	now := start.UTC()
	nextTick := time.Date(now.Year(), now.Month(), now.Day(), hour, min, sec, nsec, time.UTC)
	if nextTick.Before(now) || nextTick.Equal(now) {
		nextTick = nextTick.Add(24 * time.Hour)
	}
	return nextTick.Sub(now)
}

// New returns a new Recurring containing a channel that will send the time at a given time of day (UTC)
// specified by the arguments. It will skip ticks if a receiver is slow enough (ie, more than 24 hours).
// Stop the Recurring to release associated resources.
func New(hour, min, sec, nsec int) *Recurring {
	now := time.Now().UTC()
	first := deadline(now, hour, min, sec, nsec)
	r := &Recurring{
		ticker: time.NewTicker(first),
		quit:   make(chan interface{}),
		c:      make(chan time.Time),
		hour:   hour,
		min:    min,
		sec:    sec,
		nsec:   nsec,
	}
	r.C = r.c
	go r.wait()
	return r
}

func (r *Recurring) wait() {
	for {
		select {
		case t := <-r.ticker.C:
			r.ticker.Stop()
			r.c <- t
			now := time.Now().UTC()
			r.ticker = time.NewTicker(deadline(now, r.hour, r.min, r.sec, r.nsec))
		case <-r.quit:
			r.ticker.Stop()
			return
		}
	}
}

// Stop turns off a Recurring. After Stop, no more ticks will be sent.
// Stop does not close the channel, to prevent a read from the channel succeeding incorrectly.
func (r *Recurring) Stop() {
	close(r.quit)
}
<|endoftext|>


<commit_before>/*
Copyright 2015 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 to keep track of API Versions that should be registered in api.Scheme.
package registered

import (
	"os"
	"strings"

	"github.com/golang/glog"
	apiutil "k8s.io/kubernetes/pkg/api/util"
)

// List of registered API versions.
// The list is in the order of most preferred to the least.
var RegisteredVersions []string

func init() {
	// TODO: caesarxuchao: rename this variable to validGroupVersions
	validAPIVersions := map[string]bool{
		"v1": true,
		"extensions/v1beta1": true,
	}

	// The default list of supported api versions, in order of most preferred to the least.
	defaultSupportedVersions := "v1,extensions/v1beta1"
	// Env var KUBE_API_VERSIONS is a comma separated list of API versions that should be registered in the scheme.
	// The versions should be in the order of most preferred to the least.
	supportedVersions := os.Getenv("KUBE_API_VERSIONS")
	if supportedVersions == "" {
		supportedVersions = defaultSupportedVersions
	}
	versions := strings.Split(supportedVersions, ",")
	for _, version := range versions {
		// Verify that the version is valid.
		valid, ok := validAPIVersions[version]
		if !ok || !valid {
			// Not a valid API version.
			glog.Fatalf("invalid api version: %s in KUBE_API_VERSIONS: %s. List of valid API versions: %v",
				version, os.Getenv("KUBE_API_VERSIONS"), validAPIVersions)
		}
		RegisteredVersions = append(RegisteredVersions, version)
	}
}

// Returns true if the given api version is one of the registered api versions.
func IsRegisteredAPIVersion(version string) bool {
	for _, apiVersion := range RegisteredVersions {
		if apiVersion == version {
			return true
		}
	}
	return false
}

// GroupVersionsForGroup returns the registered versions of a group in the form
// of "group/version".
func GroupVersionsForGroup(group string) []string {
	ret := []string{}
	for _, v := range RegisteredVersions {
		if apiutil.GetGroup(v) == group {
			ret = append(ret, v)
		}
	}
	return ret
}
<commit_msg>gofmt<commit_after>/*
Copyright 2015 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 to keep track of API Versions that should be registered in api.Scheme.
package registered

import (
	"os"
	"strings"

	"github.com/golang/glog"
	apiutil "k8s.io/kubernetes/pkg/api/util"
)

// List of registered API versions.
// The list is in the order of most preferred to the least.
var RegisteredVersions []string

func init() {
	// TODO: caesarxuchao: rename this variable to validGroupVersions
	validAPIVersions := map[string]bool{
		"v1":                 true,
		"extensions/v1beta1": true,
	}

	// The default list of supported api versions, in order of most preferred to the least.
	defaultSupportedVersions := "v1,extensions/v1beta1"
	// Env var KUBE_API_VERSIONS is a comma separated list of API versions that should be registered in the scheme.
	// The versions should be in the order of most preferred to the least.
	supportedVersions := os.Getenv("KUBE_API_VERSIONS")
	if supportedVersions == "" {
		supportedVersions = defaultSupportedVersions
	}
	versions := strings.Split(supportedVersions, ",")
	for _, version := range versions {
		// Verify that the version is valid.
		valid, ok := validAPIVersions[version]
		if !ok || !valid {
			// Not a valid API version.
			glog.Fatalf("invalid api version: %s in KUBE_API_VERSIONS: %s. List of valid API versions: %v",
				version, os.Getenv("KUBE_API_VERSIONS"), validAPIVersions)
		}
		RegisteredVersions = append(RegisteredVersions, version)
	}
}

// Returns true if the given api version is one of the registered api versions.
func IsRegisteredAPIVersion(version string) bool {
	for _, apiVersion := range RegisteredVersions {
		if apiVersion == version {
			return true
		}
	}
	return false
}

// GroupVersionsForGroup returns the registered versions of a group in the form
// of "group/version".
func GroupVersionsForGroup(group string) []string {
	ret := []string{}
	for _, v := range RegisteredVersions {
		if apiutil.GetGroup(v) == group {
			ret = append(ret, v)
		}
	}
	return ret
}
<|endoftext|>


<commit_before>package metrics

import "github.com/prometheus/client_golang/prometheus"

var LaunchOperationsLatency = prometheus.NewSummaryVec(
	prometheus.SummaryOpts{
		Namespace: "kubernikus",
		Subsystem: "launch",
		Name:      "operation_latency_microseconds",
		Help:      "Total duration of reconciliation in microseconds.",
	},
	[]string{"method"})

var LaunchOperationsTotal = prometheus.NewCounterVec(
	prometheus.CounterOpts{
		Namespace: "kubernikus",
		Subsystem: "launch",
		Name:      "operation_total",
		Help:      "Number of operations."},
	[]string{"method"})

var LaunchSuccessfulOperationsTotal = prometheus.NewCounterVec(
	prometheus.CounterOpts{
		Namespace: "kubernikus",
		Subsystem: "launch",
		Name:      "successful_operation_total",
		Help:      "Number of successful operations."},
	[]string{"method"})

var LaunchFailedOperationsTotal = prometheus.NewCounterVec(
	prometheus.CounterOpts{
		Namespace: "kubernikus",
		Subsystem: "launch",
		Name:      "failed_operation_total",
		Help:      "Number of failed operations."},
	[]string{"method"})
<commit_msg>fixes label name<commit_after>package metrics

import "github.com/prometheus/client_golang/prometheus"

var LaunchOperationsLatency = prometheus.NewSummaryVec(
	prometheus.SummaryOpts{
		Namespace: "kubernikus",
		Subsystem: "launch",
		Name:      "operation_latency_seconds",
		Help:      "Total duration of reconciliation in microseconds.",
	},
	[]string{"method"})

var LaunchOperationsTotal = prometheus.NewCounterVec(
	prometheus.CounterOpts{
		Namespace: "kubernikus",
		Subsystem: "launch",
		Name:      "operation_total",
		Help:      "Number of operations."},
	[]string{"method"})

var LaunchSuccessfulOperationsTotal = prometheus.NewCounterVec(
	prometheus.CounterOpts{
		Namespace: "kubernikus",
		Subsystem: "launch",
		Name:      "successful_operation_total",
		Help:      "Number of successful operations."},
	[]string{"method"})

var LaunchFailedOperationsTotal = prometheus.NewCounterVec(
	prometheus.CounterOpts{
		Namespace: "kubernikus",
		Subsystem: "launch",
		Name:      "failed_operation_total",
		Help:      "Number of failed operations."},
	[]string{"method"})
<|endoftext|>


<commit_before>package dockerscript

import (
	"github.com/dotcloud/docker/pkg/dockerscript/scanner"
	"fmt"
	"io"
	"strings"
)

type Command struct {
	Args []string
	Children []*Command
}

func Parse(src io.Reader) ([]*Command, error) {
	s := &scanner.Scanner{}
	s.Init(src)
	s.Whitespace = 1<<'\t' | 1<<' '
	//s.Mode = ScanIdents | ScanFloats | ScanChars | ScanStrings | ScanRawStrings | ScanComments | SkipComments
	s.Mode = scanner.ScanStrings | scanner.ScanRawStrings | scanner.ScanIdents
	expr, err := parse(s, "")
	if err != nil {
		return nil, fmt.Errorf("line %d:%d: %v\n", s.Pos().Line, s.Pos().Column, err)
	}
	return expr, nil
}

func (cmd *Command) subString(depth int) string {
	var prefix string
	for i:=0; i<depth; i++ {
		prefix += "  "
	}
	s := prefix + strings.Join(cmd.Args, ", ")
	if len(cmd.Children) > 0 {
		s += " {\n"
		for _, subcmd := range cmd.Children {
			s += subcmd.subString(depth + 1)
		}
		s += prefix + "}"
	}
	s += "\n"
	return s
}

func (cmd *Command) String() string {
	return cmd.subString(0)
}

func parseArgs(s *scanner.Scanner) ([]string, rune, error) {
	var parseError error
	// FIXME: we overwrite previously set error
	s.Error = func(s *scanner.Scanner, msg string) {
		parseError = fmt.Errorf(msg)
		// parseError = fmt.Errorf("line %d:%d: %s\n", s.Pos().Line, s.Pos().Column, msg)
	}
	var args []string
	tok := s.Scan()
	for tok != scanner.EOF {
		if parseError != nil {
			return args, tok, parseError
		}
		text := s.TokenText()
		//fmt.Printf("--> [%s]\n", text)
		if text == "{" || text == "}" || text == "\n" || text == "\r" || text == ";" {
			return args, tok, nil
		}
		args = append(args, text)
		tok = s.Scan()
	}
	return args, tok, nil
}

func parse(s *scanner.Scanner, opener string) (expr []*Command, err error) {
	defer func() {
		fmt.Printf("parse() returned %d commands:\n", len(expr))
		for _, c := range expr {
			fmt.Printf("\t----> %s\n", c)
		}
	}()
	for {
		args, tok, err := parseArgs(s)
		if err != nil {
			return nil, err
		}
		cmd := &Command{Args: args}
		fmt.Printf("---> args=%v finished by %s\n", args, s.TokenText())
		afterArgs := s.TokenText()
		if afterArgs == "{" {
			fmt.Printf("---> therefore calling parse() of sub-expression\n")
			children, err := parse(s, "{")
			fmt.Printf("---> parse() of sub-epxression returned %d commands (ended by %s) and error=%v\n", children, s.TokenText(), err)
			if err != nil {
				return nil, err
			}
			cmd.Children = children
		} else if afterArgs == "}" && opener != "{" {
			return nil, fmt.Errorf("unexpected end of block '}'")
		}
		if len(cmd.Args) > 0 || len(cmd.Children) > 0 {
			expr = append(expr, cmd)
		}
		if tok == scanner.EOF || afterArgs == "}" {
			break
		}
	}
	return expr, nil
}
<commit_msg>pkg/dockerscript: remove debug messages<commit_after>package dockerscript

import (
	"github.com/dotcloud/docker/pkg/dockerscript/scanner"
	"fmt"
	"io"
	"strings"
)

type Command struct {
	Args []string
	Children []*Command
}

func Parse(src io.Reader) ([]*Command, error) {
	s := &scanner.Scanner{}
	s.Init(src)
	s.Whitespace = 1<<'\t' | 1<<' '
	s.Mode = scanner.ScanStrings | scanner.ScanRawStrings | scanner.ScanIdents
	expr, err := parse(s, "")
	if err != nil {
		return nil, fmt.Errorf("line %d:%d: %v\n", s.Pos().Line, s.Pos().Column, err)
	}
	return expr, nil
}

func (cmd *Command) subString(depth int) string {
	var prefix string
	for i:=0; i<depth; i++ {
		prefix += "  "
	}
	s := prefix + strings.Join(cmd.Args, ", ")
	if len(cmd.Children) > 0 {
		s += " {\n"
		for _, subcmd := range cmd.Children {
			s += subcmd.subString(depth + 1)
		}
		s += prefix + "}"
	}
	s += "\n"
	return s
}

func (cmd *Command) String() string {
	return cmd.subString(0)
}

func parseArgs(s *scanner.Scanner) ([]string, rune, error) {
	var parseError error
	// FIXME: we overwrite previously set error
	s.Error = func(s *scanner.Scanner, msg string) {
		parseError = fmt.Errorf(msg)
		// parseError = fmt.Errorf("line %d:%d: %s\n", s.Pos().Line, s.Pos().Column, msg)
	}
	var args []string
	tok := s.Scan()
	for tok != scanner.EOF {
		if parseError != nil {
			return args, tok, parseError
		}
		text := s.TokenText()
		if text == "{" || text == "}" || text == "\n" || text == "\r" || text == ";" {
			return args, tok, nil
		}
		args = append(args, text)
		tok = s.Scan()
	}
	return args, tok, nil
}

func parse(s *scanner.Scanner, opener string) (expr []*Command, err error) {
	/*
	defer func() {
		fmt.Printf("parse() returned %d commands:\n", len(expr))
		for _, c := range expr {
			fmt.Printf("\t----> %s\n", c)
		}
	}()
	*/
	for {
		args, tok, err := parseArgs(s)
		if err != nil {
			return nil, err
		}
		cmd := &Command{Args: args}
		afterArgs := s.TokenText()
		if afterArgs == "{" {
			children, err := parse(s, "{")
			if err != nil {
				return nil, err
			}
			cmd.Children = children
		} else if afterArgs == "}" && opener != "{" {
			return nil, fmt.Errorf("unexpected end of block '}'")
		}
		if len(cmd.Args) > 0 || len(cmd.Children) > 0 {
			expr = append(expr, cmd)
		}
		if tok == scanner.EOF || afterArgs == "}" {
			break
		}
	}
	return expr, nil
}
<|endoftext|>


<commit_before>/*
Copyright 2019 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 proxy

import (
	"fmt"
	"net/http"
	"os"
	"testing"

	"k8s.io/client-go/rest"
)

func TestIsValidEnv(t *testing.T) {
	var testCases = []struct {
		env  string
		want bool
	}{
		{"", false},
		{"HTTPS-PROXY", false},
		{"NOPROXY", false},
	}
	for _, tc := range testCases {
		t.Run(tc.env, func(t *testing.T) {
			if got := isValidEnv(tc.env); got != tc.want {
				t.Errorf("isValidEnv(\"%v\") got %v; want %v", tc.env, got, tc.want)
			}
		})
	}

}
func TestIsInBlock(t *testing.T) {

	var testCases = []struct {
		ip        string
		block     string
		want      bool
		wanntAErr bool
	}{
		{"", "192.168.0.1/32", false, true},
		{"192.168.0.1", "192.168.0.1/32", true, false},
		{"192.168.0.2", "192.168.0.1/32", false, false},
		{"192.168.0.1", "192.168.0.1/18", true, false},
		{"abcd", "192.168.0.1/18", false, true},
		{"192.168.0.1", "foo", false, true},
	}
	for _, tc := range testCases {
		t.Run(fmt.Sprintf("%s in %s Want: %t WantAErr: %t", tc.ip, tc.block, tc.want, tc.wanntAErr), func(t *testing.T) {
			got, err := isInBlock(tc.ip, tc.block)
			gotErr := false
			if err != nil {
				gotErr = true
			}
			if gotErr != tc.wanntAErr {
				t.Errorf("isInBlock(%v,%v) got error is %v ; want error is %v", tc.ip, tc.block, gotErr, tc.wanntAErr)
			}

			if got != tc.want {
				t.Errorf("isInBlock(%v,%v) got %v; want %v", tc.ip, tc.block, got, tc.want)
			}

		})
	}
}

func TestUpdateEnv(t *testing.T) {
	var testCases = []struct {
		ip      string
		env     string
		wantErr bool
	}{
		{"192.168.0.13", "NO_PROXY", false},
		{"", "NO_PROXY", true},
		{"", "", true},
		{"192.168.0.13", "", true},
		{"192.168.0.13", "NPROXY", true},
	}
	for _, tc := range testCases {
		t.Run(fmt.Sprintf("%s in %s WantAErr: %t", tc.ip, tc.env, tc.wantErr), func(t *testing.T) {
			origVal := os.Getenv(tc.env)
			gotErr := false
			err := updateEnv(tc.ip, tc.env)
			if err != nil {
				gotErr = true
			}
			if gotErr != tc.wantErr {
				t.Errorf("updateEnv(%v,%v) got error is %v ; want error is %v", tc.ip, tc.env, gotErr, tc.wantErr)
			}
			err = os.Setenv(tc.env, origVal)
			if err != nil && tc.env != "" {
				t.Errorf("Error reverting the env var (%s) to its original value (%s)", tc.env, origVal)
			}

		})
	}

}

func TestCheckEnv(t *testing.T) {
	var testCases = []struct {
		ip           string
		envName      string
		want         bool
		mockEnvValue string
	}{
		{"", "NO_PROXY", false, ""},
		{"192.168.0.13", "NO_PROXY", false, ""},
		{"192.168.0.13", "NO_PROXY", false, ","},
		{"192.168.0.13", "NO_PROXY", true, "192.168.0.13"},
		{"192.168.0.13", "NO_PROXY", true, ",192.168.0.13"},
		{"192.168.0.13", "NO_PROXY", true, "10.10.0.13,192.168.0.13"},
		{"192.168.0.13", "NO_PROXY", true, "192.168.0.13/22"},
		{"192.168.0.13", "NO_PROXY", true, "10.10.0.13,192.168.0.13"},
		{"192.168.0.13", "NO_PROXY", false, "10.10.0.13/22"},
		{"10.10.10.4", "NO_PROXY", true, "172.168.0.0/30,10.10.10.0/24"},
	}
	for _, tc := range testCases {
		t.Run(fmt.Sprintf("%s in %s", tc.ip, tc.envName), func(t *testing.T) {
			originalEnv := os.Getenv(tc.envName)
			defer func() { // revert to pre-test env var
				err := os.Setenv(tc.envName, originalEnv)
				if err != nil {
					t.Fatalf("Error reverting env (%s) to its original value (%s) var after test ", tc.envName, originalEnv)
				}
			}()

			if err := os.Setenv(tc.envName, tc.mockEnvValue); err != nil {
				t.Error("Error setting env var for taste case")
			}
			if got := checkEnv(tc.ip, tc.envName); got != tc.want {
				t.Errorf("CheckEnv(%v,%v) got  %v ; want is %v", tc.ip, tc.envName, got, tc.want)
			}
		})
	}

}

func TestIsIPExcluded(t *testing.T) {
	var testCases = []struct {
		ip, env  string
		excluded bool
	}{
		{"1.2.3.4", "7.7.7.7", false},
		{"1.2.3.4", "1.2.3.4", true},
		{"1.2.3.4", "", false},
		{"foo", "", false},
		{"foo", "bar", false},
		{"foo", "1.2.3.4", false},
	}
	for _, tc := range testCases {
		originalEnv := os.Getenv("NO_PROXY")
		defer func() { // revert to pre-test env var
			err := os.Setenv("NO_PROXY", originalEnv)
			if err != nil {
				t.Fatalf("Error reverting env NO_PROXY to its original value (%s) var after test ", originalEnv)
			}
		}()
		t.Run(fmt.Sprintf("exclude %s NO_PROXY(%v)", tc.ip, tc.env), func(t *testing.T) {
			if err := os.Setenv("NO_PROXY", tc.env); err != nil {
				t.Errorf("Error during setting env: %v", err)
			}
			if excluded := IsIPExcluded(tc.ip); excluded != tc.excluded {
				t.Fatalf("IsIPExcluded(%v) should return %v. NO_PROXY=%v", tc.ip, tc.excluded, tc.env)
			}
		})
	}
}

func TestExcludeIP(t *testing.T) {
	var testCases = []struct {
		ip, env  string
		wantAErr bool
	}{
		{"1.2.3.4", "", false},
		{"", "", true},
		{"7.7.7.7", "7.7.7.7", false},
		{"7.7.7.7", "1.2.3.4", false},
		{"foo", "", true},
		{"foo", "1.2.3.4", true},
	}
	originalEnv := os.Getenv("NO_PROXY")
	defer func() { // revert to pre-test env var
		err := os.Setenv("NO_PROXY", originalEnv)
		if err != nil {
			t.Fatalf("Error reverting env NO_PROXY to its original value (%s) var after test ", originalEnv)
		}
	}()
	for _, tc := range testCases {
		t.Run(fmt.Sprintf("exclude %s NO_PROXY(%s)", tc.ip, tc.env), func(t *testing.T) {
			if err := os.Setenv("NO_PROXY", tc.env); err != nil {
				t.Errorf("Error during setting env: %v", err)
			}
			err := ExcludeIP(tc.ip)
			if err != nil && !tc.wantAErr {
				t.Errorf("ExcludeIP(%v) returned unexpected error %v", tc.ip, err)
			}
			if err == nil && tc.wantAErr {
				t.Errorf("ExcludeIP(%v) should return error but error is %v", tc.ip, err)
			}
		})
	}
}

func TestUpdateTransport(t *testing.T) {
	t.Run("new", func(t *testing.T) {
		rc := rest.Config{}
		c := UpdateTransport(&rc)
		tr := &http.Transport{}
		tr.RegisterProtocol("file", http.NewFileTransport(http.Dir("/tmp")))
		rt := c.WrapTransport(tr)
		if _, ok := rt.(http.RoundTripper); !ok {
			t.Fatalf("Cannot cast rt(%v) to http.RoundTripper", rt)
		}
	})
	t.Run("existing", func(t *testing.T) {
		// rest config initialized with WrapTransport function
		rc := rest.Config{WrapTransport: func(http.RoundTripper) http.RoundTripper {
			rt := &http.Transport{}
			rt.RegisterProtocol("file", http.NewFileTransport(http.Dir("/tmp")))
			return rt
		}}

		transport := &http.Transport{}
		transport.RegisterProtocol("file", http.NewFileTransport(http.Dir("/")))

		c := UpdateTransport(&rc)
		rt := c.WrapTransport(nil)

		if rt == rc.WrapTransport(transport) {
			t.Fatalf("Expected to reuse existing RoundTripper(%v) but found %v", rt, transport)
		}

	})
	t.Run("nil", func(t *testing.T) {
		rc := rest.Config{}
		c := UpdateTransport(&rc)
		rt := c.WrapTransport(nil)
		if rt != nil {
			t.Fatalf("Expected RoundTripper nil for invocation WrapTransport(nil)")
		}
	})
}
<commit_msg>proxy_test: improve TestIsValidEnv unit test<commit_after>/*
Copyright 2019 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 proxy

import (
	"fmt"
	"net/http"
	"os"
	"testing"

	"k8s.io/client-go/rest"
)

func TestIsValidEnv(t *testing.T) {
	var testCases = []struct {
		env  string
		want bool
	}{
		{"", false},
		{"HTTPS-PROXY", false},
		{"NOPROXY", false},
		{"http_proxy", true},
	}
	for _, tc := range testCases {
		t.Run(tc.env, func(t *testing.T) {
			if got := isValidEnv(tc.env); got != tc.want {
				t.Errorf("isValidEnv(\"%v\") got %v; want %v", tc.env, got, tc.want)
			}
		})
	}

}
func TestIsInBlock(t *testing.T) {

	var testCases = []struct {
		ip        string
		block     string
		want      bool
		wanntAErr bool
	}{
		{"", "192.168.0.1/32", false, true},
		{"192.168.0.1", "192.168.0.1/32", true, false},
		{"192.168.0.2", "192.168.0.1/32", false, false},
		{"192.168.0.1", "192.168.0.1/18", true, false},
		{"abcd", "192.168.0.1/18", false, true},
		{"192.168.0.1", "foo", false, true},
	}
	for _, tc := range testCases {
		t.Run(fmt.Sprintf("%s in %s Want: %t WantAErr: %t", tc.ip, tc.block, tc.want, tc.wanntAErr), func(t *testing.T) {
			got, err := isInBlock(tc.ip, tc.block)
			gotErr := false
			if err != nil {
				gotErr = true
			}
			if gotErr != tc.wanntAErr {
				t.Errorf("isInBlock(%v,%v) got error is %v ; want error is %v", tc.ip, tc.block, gotErr, tc.wanntAErr)
			}

			if got != tc.want {
				t.Errorf("isInBlock(%v,%v) got %v; want %v", tc.ip, tc.block, got, tc.want)
			}

		})
	}
}

func TestUpdateEnv(t *testing.T) {
	var testCases = []struct {
		ip      string
		env     string
		wantErr bool
	}{
		{"192.168.0.13", "NO_PROXY", false},
		{"", "NO_PROXY", true},
		{"", "", true},
		{"192.168.0.13", "", true},
		{"192.168.0.13", "NPROXY", true},
	}
	for _, tc := range testCases {
		t.Run(fmt.Sprintf("%s in %s WantAErr: %t", tc.ip, tc.env, tc.wantErr), func(t *testing.T) {
			origVal := os.Getenv(tc.env)
			gotErr := false
			err := updateEnv(tc.ip, tc.env)
			if err != nil {
				gotErr = true
			}
			if gotErr != tc.wantErr {
				t.Errorf("updateEnv(%v,%v) got error is %v ; want error is %v", tc.ip, tc.env, gotErr, tc.wantErr)
			}
			err = os.Setenv(tc.env, origVal)
			if err != nil && tc.env != "" {
				t.Errorf("Error reverting the env var (%s) to its original value (%s)", tc.env, origVal)
			}

		})
	}

}

func TestCheckEnv(t *testing.T) {
	var testCases = []struct {
		ip           string
		envName      string
		want         bool
		mockEnvValue string
	}{
		{"", "NO_PROXY", false, ""},
		{"192.168.0.13", "NO_PROXY", false, ""},
		{"192.168.0.13", "NO_PROXY", false, ","},
		{"192.168.0.13", "NO_PROXY", true, "192.168.0.13"},
		{"192.168.0.13", "NO_PROXY", true, ",192.168.0.13"},
		{"192.168.0.13", "NO_PROXY", true, "10.10.0.13,192.168.0.13"},
		{"192.168.0.13", "NO_PROXY", true, "192.168.0.13/22"},
		{"192.168.0.13", "NO_PROXY", true, "10.10.0.13,192.168.0.13"},
		{"192.168.0.13", "NO_PROXY", false, "10.10.0.13/22"},
		{"10.10.10.4", "NO_PROXY", true, "172.168.0.0/30,10.10.10.0/24"},
	}
	for _, tc := range testCases {
		t.Run(fmt.Sprintf("%s in %s", tc.ip, tc.envName), func(t *testing.T) {
			originalEnv := os.Getenv(tc.envName)
			defer func() { // revert to pre-test env var
				err := os.Setenv(tc.envName, originalEnv)
				if err != nil {
					t.Fatalf("Error reverting env (%s) to its original value (%s) var after test ", tc.envName, originalEnv)
				}
			}()

			if err := os.Setenv(tc.envName, tc.mockEnvValue); err != nil {
				t.Error("Error setting env var for taste case")
			}
			if got := checkEnv(tc.ip, tc.envName); got != tc.want {
				t.Errorf("CheckEnv(%v,%v) got  %v ; want is %v", tc.ip, tc.envName, got, tc.want)
			}
		})
	}

}

func TestIsIPExcluded(t *testing.T) {
	var testCases = []struct {
		ip, env  string
		excluded bool
	}{
		{"1.2.3.4", "7.7.7.7", false},
		{"1.2.3.4", "1.2.3.4", true},
		{"1.2.3.4", "", false},
		{"foo", "", false},
		{"foo", "bar", false},
		{"foo", "1.2.3.4", false},
	}
	for _, tc := range testCases {
		originalEnv := os.Getenv("NO_PROXY")
		defer func() { // revert to pre-test env var
			err := os.Setenv("NO_PROXY", originalEnv)
			if err != nil {
				t.Fatalf("Error reverting env NO_PROXY to its original value (%s) var after test ", originalEnv)
			}
		}()
		t.Run(fmt.Sprintf("exclude %s NO_PROXY(%v)", tc.ip, tc.env), func(t *testing.T) {
			if err := os.Setenv("NO_PROXY", tc.env); err != nil {
				t.Errorf("Error during setting env: %v", err)
			}
			if excluded := IsIPExcluded(tc.ip); excluded != tc.excluded {
				t.Fatalf("IsIPExcluded(%v) should return %v. NO_PROXY=%v", tc.ip, tc.excluded, tc.env)
			}
		})
	}
}

func TestExcludeIP(t *testing.T) {
	var testCases = []struct {
		ip, env  string
		wantAErr bool
	}{
		{"1.2.3.4", "", false},
		{"", "", true},
		{"7.7.7.7", "7.7.7.7", false},
		{"7.7.7.7", "1.2.3.4", false},
		{"foo", "", true},
		{"foo", "1.2.3.4", true},
	}
	originalEnv := os.Getenv("NO_PROXY")
	defer func() { // revert to pre-test env var
		err := os.Setenv("NO_PROXY", originalEnv)
		if err != nil {
			t.Fatalf("Error reverting env NO_PROXY to its original value (%s) var after test ", originalEnv)
		}
	}()
	for _, tc := range testCases {
		t.Run(fmt.Sprintf("exclude %s NO_PROXY(%s)", tc.ip, tc.env), func(t *testing.T) {
			if err := os.Setenv("NO_PROXY", tc.env); err != nil {
				t.Errorf("Error during setting env: %v", err)
			}
			err := ExcludeIP(tc.ip)
			if err != nil && !tc.wantAErr {
				t.Errorf("ExcludeIP(%v) returned unexpected error %v", tc.ip, err)
			}
			if err == nil && tc.wantAErr {
				t.Errorf("ExcludeIP(%v) should return error but error is %v", tc.ip, err)
			}
		})
	}
}

func TestUpdateTransport(t *testing.T) {
	t.Run("new", func(t *testing.T) {
		rc := rest.Config{}
		c := UpdateTransport(&rc)
		tr := &http.Transport{}
		tr.RegisterProtocol("file", http.NewFileTransport(http.Dir("/tmp")))
		rt := c.WrapTransport(tr)
		if _, ok := rt.(http.RoundTripper); !ok {
			t.Fatalf("Cannot cast rt(%v) to http.RoundTripper", rt)
		}
	})
	t.Run("existing", func(t *testing.T) {
		// rest config initialized with WrapTransport function
		rc := rest.Config{WrapTransport: func(http.RoundTripper) http.RoundTripper {
			rt := &http.Transport{}
			rt.RegisterProtocol("file", http.NewFileTransport(http.Dir("/tmp")))
			return rt
		}}

		transport := &http.Transport{}
		transport.RegisterProtocol("file", http.NewFileTransport(http.Dir("/")))

		c := UpdateTransport(&rc)
		rt := c.WrapTransport(nil)

		if rt == rc.WrapTransport(transport) {
			t.Fatalf("Expected to reuse existing RoundTripper(%v) but found %v", rt, transport)
		}

	})
	t.Run("nil", func(t *testing.T) {
		rc := rest.Config{}
		c := UpdateTransport(&rc)
		rt := c.WrapTransport(nil)
		if rt != nil {
			t.Fatalf("Expected RoundTripper nil for invocation WrapTransport(nil)")
		}
	})
}
<|endoftext|>