Skip to content

Commit

Permalink
add network.[Must]GetDataNetworkIP; add popular param types. (#12)
Browse files Browse the repository at this point in the history
  • Loading branch information
raulk committed May 3, 2020
1 parent ae105b0 commit 4bb2113
Show file tree
Hide file tree
Showing 6 changed files with 212 additions and 0 deletions.
59 changes: 59 additions & 0 deletions network/address.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package network

import (
"fmt"
"net"
)

// GetDataNetworkIP examines the local network interfaces, and tries to find our
// assigned IP within the data network.
//
// This function returns the IP and a nil error if found. If running in
// a sidecar-less environment, the error ErrNoTrafficShaping is returned.
func (c *Client) GetDataNetworkIP() (net.IP, error) {
re := c.runenv
if !re.TestSidecar {
return nil, ErrNoTrafficShaping
}

ifaces, err := net.Interfaces()
if err != nil {
return nil, fmt.Errorf("unable to get local network interfaces: %s", err)
}

for _, i := range ifaces {
addrs, err := i.Addrs()
if err != nil {
re.RecordMessage("error getting addrs for interface: %s", err)
continue
}
for _, a := range addrs {
switch v := a.(type) {
case *net.IPNet:
ip := v.IP.To4()
if ip == nil {
re.RecordMessage("ignoring non ip4 addr %s", v)
continue
}
if re.TestSubnet.Contains(ip) {
re.RecordMessage("detected data network IP: %s", v)
return v.IP, nil
} else {
re.RecordMessage("%s not in data subnet %s, ignoring", ip, re.TestSubnet.String())
}
}
}
}
return nil, fmt.Errorf("unable to determine data network IP. no interface found with IP in %s", re.TestSubnet.String())
}

// MustGetDataNetworkIP calls GetDataNetworkIP, and panics if it
// errors. It is suitable to use with runner.Invoke/InvokeMap, as long as
// this method is called from the main goroutine of the test plan.
func (c *Client) MustGetDataNetworkIP() net.IP {
ip, err := c.GetDataNetworkIP()
if err != nil {
panic(err)
}
return ip
}
5 changes: 5 additions & 0 deletions network/types.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
package network

import (
"fmt"
"net"
"time"

"github.com/testground/sdk-go/sync"
)

// ErrNoTrafficShaping is returned from functions in this package when traffic
// shaping is not available, such as when using the local:exec runner.
var ErrNoTrafficShaping = fmt.Errorf("no traffic shaping available with this runner")

type FilterAction int

const (
Expand Down
2 changes: 2 additions & 0 deletions ptypes/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// Package ptypes contains types that are commonplace in test plan parameters.
package ptypes
42 changes: 42 additions & 0 deletions ptypes/duration.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package ptypes

import (
"encoding/json"
"errors"
"time"
)

// Duration wraps a time.Duration and provides JSON marshal logic.
type Duration struct {
time.Duration
}

var (
_ json.Marshaler = Duration{}
_ json.Unmarshaler = &Duration{}
)

func (d Duration) MarshalJSON() ([]byte, error) {
return json.Marshal(d.String())
}

func (d *Duration) UnmarshalJSON(b []byte) error {
var v interface{}
if err := json.Unmarshal(b, &v); err != nil {
return err
}
switch value := v.(type) {
case float64:
d.Duration = time.Duration(value)
return nil
case string:
var err error
d.Duration, err = time.ParseDuration(value)
if err != nil {
return err
}
return nil
default:
return errors.New("invalid duration")
}
}
66 changes: 66 additions & 0 deletions ptypes/rate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package ptypes

import (
"encoding/json"
"errors"
"fmt"
"strconv"
"strings"
"time"
"unicode"
)

// Rate is a param that's parsed as "quantity/interval", where `quantity` is a
// float and `interval` is a string parsable by time.ParseDuration, e.g. "1s".
//
// You can omit the numeric component of the interval to default to 1, e.g.
// "100/s" is the same as "100/1s".
//
// Examples of valid Rate strings include: "100/s", "0.5/m", "500/5m".
type Rate struct {
Quantity float64
Interval time.Duration
}

var (
_ json.Marshaler = Duration{}
_ json.Unmarshaler = &Duration{}
)

func (r Rate) MarshalJSON() ([]byte, error) {
return nil, nil
}
func (r *Rate) UnmarshalJSON(b []byte) error {
var v interface{}
if err := json.Unmarshal(b, &v); err != nil {
return err
}

str, ok := v.(string)
if !ok {
return errors.New("invalid rate param, must be string")
}

strs := strings.Split(str, "/")
if len(strs) != 2 {
return errors.New("invalid rate param. Must be in format 'quantity / interval'")
}

q, err := strconv.ParseFloat(strs[0], 64)
if err != nil {
return fmt.Errorf("error parsing quantity portion of rate: %s", err)
}
intervalStr := strings.TrimSpace(strs[1])
if !unicode.IsDigit(rune(intervalStr[0])) {
intervalStr = "1" + intervalStr
}

i, err := time.ParseDuration(intervalStr)
if err != nil {
return fmt.Errorf("error parsing interval portion of rate: %s", err)
}

r.Quantity = q
r.Interval = i
return nil
}
38 changes: 38 additions & 0 deletions ptypes/size.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package ptypes

import (
"encoding/json"
"errors"

"github.com/dustin/go-humanize"
)

// Size is a type that unmarshals human-readable binary sizes like "100 KB"
// into an uint64, where the unit is bytes.
type Size uint64

var (
_ json.Marshaler = Duration{}
_ json.Unmarshaler = &Duration{}
)

func (s Size) MarshalJSON() ([]byte, error) {
return nil, nil
}

func (s *Size) UnmarshalJSON(b []byte) error {
var v interface{}
if err := json.Unmarshal(b, &v); err != nil {
return err
}
str, ok := v.(string)
if !ok {
return errors.New("invalid size param, must be string")
}
n, err := humanize.ParseBytes(str)
if err != nil {
return err
}
*s = Size(n)
return nil
}

0 comments on commit 4bb2113

Please sign in to comment.