util/util.go (180 lines of code) (raw):
// Copyright (c) 2015 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
package util
import (
"errors"
"fmt"
"math/rand"
"net"
"regexp"
"strconv"
"time"
)
// HostportPattern is regex to match a host:port
var HostportPattern = regexp.MustCompile(`^(\d+.\d+.\d+.\d+):\d+$`)
// CaptureHost takes a host:port and returns the host.
func CaptureHost(hostport string) string {
host, _, err := net.SplitHostPort(hostport)
if err != nil {
return ""
}
return host
}
// CheckHostnameIPMismatch checks for a hostname/IP mismatch in a map of hosts to
// host:ports. If there is a mismatch, returns the mismatched hosts and an error,
// otherwise nil and nil.
func CheckHostnameIPMismatch(local string, hostsMap map[string][]string) ([]string, error) {
var message string
test := func(message string, filter func(string) bool) ([]string, error) {
var mismatched []string
for _, hostports := range hostsMap {
for _, hostport := range hostports {
if filter(hostport) {
mismatched = append(mismatched, hostport)
}
}
}
if len(mismatched) > 0 {
return mismatched, errors.New(message)
}
return nil, nil
}
if HostportPattern.MatchString(local) {
message = "Your host identifier looks like an IP address and there are bootstrap " +
"hosts that appear to be specified with hostnames. These inconsistencies may " +
"lead to subtle node communication issues."
return test(message, func(hostport string) bool {
return !HostportPattern.MatchString(hostport)
})
}
message = "Your host identifier looks like a hostname and there are bootstrap hosts " +
"that appear to be specified with IP addresses. These inconsistencies may lead " +
"to subtle node communication issues"
return test(message, func(hostport string) bool {
return HostportPattern.MatchString(hostport)
})
}
// CheckLocalMissing checks a slice of host:ports for the given local host:port, return
// an error if not found, otherwise nil.
func CheckLocalMissing(local string, hostports []string) error {
if !StringInSlice(hostports, local) {
return errors.New("local node missing from hosts")
}
return nil
}
// SingleNodeCluster determines if hostport is the only host:port contained within
// the hostMap.
func SingleNodeCluster(hostport string, hostMap map[string][]string) bool {
// If the map contains more than one host then clearly there is more than
// one address so we can't be a single node cluster.
if len(hostMap) > 1 {
return false
}
host := CaptureHost(hostport)
// If hostport is not even in the map at all then it's not possible for
// us to be a single node cluster.
_, ok := hostMap[host]
if !ok {
return false
}
// Same as above; there is most than one host
if len(hostMap[host]) > 1 {
return false
}
// There is a single hostport in the map; check that it matches
if hostMap[host][0] == hostport {
return true
}
// There is a single hostport in the map but it doesn't match the given
// hostport.
return false
}
// HostPortsByHost parses a list of host/port conbinations and creates a map
// of unique hosts each containing a slice of the hostsport instances for each
// host.
func HostPortsByHost(hostports []string) (hostMap map[string][]string) {
hostMap = make(map[string][]string)
for _, hostport := range hostports {
host := CaptureHost(hostport)
if host != "" {
hostMap[host] = append(hostMap[host], hostport)
}
}
return
}
// TimeZero returns a time such that time.IsZero() is true
func TimeZero() time.Time {
return time.Date(1, 1, 1, 0, 0, 0, 0, time.UTC)
}
// TimeNowMS returns Unix time in milliseconds for time.Now()
func TimeNowMS() int64 {
return UnixMS(time.Now())
}
// MS returns the number of milliseconds given in a duration
func MS(d time.Duration) int64 {
return d.Nanoseconds() / 1000000
}
// UnixMS returns Unix time in milliseconds for the given time
func UnixMS(t time.Time) int64 {
return t.UnixNano() / 1000000
}
// StringInSlice returns whether the string is contained within the slice.
func StringInSlice(slice []string, str string) bool {
for _, b := range slice {
if str == b {
return true
}
}
return false
}
// ShuffleStrings takes a slice of strings and returns a new slice containing
// the same strings in a random order.
func ShuffleStrings(strings []string) []string {
newStrings := make([]string, len(strings))
newIndexes := rand.Perm(len(strings))
for o, n := range newIndexes {
newStrings[n] = strings[o]
}
return newStrings
}
// ShuffleStringsInPlace uses the Fisher–Yates shuffle to randomize the strings
// in place.
func ShuffleStringsInPlace(strings []string) {
for i := range strings {
j := rand.Intn(i + 1)
strings[i], strings[j] = strings[j], strings[i]
}
}
// TakeNode takes an element from nodes at the given index, or at a random index if
// index < 0. Mutates nodes.
func TakeNode(nodes *[]string, index int) string {
if len(*nodes) == 0 {
return ""
}
var i int
if index >= 0 {
if index >= len(*nodes) {
return ""
}
i = index
} else {
i = rand.Intn(len(*nodes))
}
node := (*nodes)[i]
*nodes = append((*nodes)[:i], (*nodes)[i+1:]...)
return node
}
// SelectInt takes an option and a default value and returns the default value if
// the option is equal to zero, and the option otherwise.
func SelectInt(opt, def int) int {
if opt == 0 {
return def
}
return opt
}
// SelectFloat takes an option and a default value and returns the default value if
// the option is equal to zero, and the option otherwise.
func SelectFloat(opt, def float64) float64 {
if opt == 0 {
return def
}
return opt
}
// SelectDuration takes an option and a default value and returns the default value if
// the option is equal to zero, and the option otherwise.
func SelectDuration(opt, def time.Duration) time.Duration {
if opt == time.Duration(0) {
return def
}
return opt
}
// SelectBool takes an option and a default value and returns the default value if
// the option is equal to the zero value, and the option otherwise.
func SelectBool(opt, def bool) bool {
if opt == false {
return def
}
return opt
}
// Min returns the lowest integer and is defined because golang only has a min
// function for floats and not for ints.
func Min(first int, rest ...int) int {
m := first
for _, value := range rest {
if value < m {
m = value
}
}
return m
}
// Timestamp is a bi-directional binding of Time to interger Unix timestamp in
// JSON.
type Timestamp time.Time
// MarshalJSON returns the JSON encoding of the timestamp.
func (t *Timestamp) MarshalJSON() ([]byte, error) {
ts := time.Time(*t).Unix()
stamp := fmt.Sprint(ts)
return []byte(stamp), nil
}
// UnmarshalJSON sets the timestamp to the value in the specified JSON.
func (t *Timestamp) UnmarshalJSON(b []byte) error {
ts, err := strconv.Atoi(string(b))
if err != nil {
return err
}
*t = Timestamp(time.Unix(int64(ts), 0))
return nil
}