Skip to content

Commit

Permalink
Merge pull request #177 from ipfs/162-new-configs
Browse files Browse the repository at this point in the history
Issue #162: Rework configuration format
  • Loading branch information
ZenGround0 committed Oct 20, 2017
2 parents e336e94 + fb8fdb9 commit 03b21bf
Show file tree
Hide file tree
Showing 52 changed files with 3,277 additions and 1,240 deletions.
10 changes: 4 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,13 +90,11 @@ This will install `ipfs-cluster-service` and `ipfs-cluster-ctl` in your `$GOPATH

**`ipfs-cluster-service`** runs an ipfs-cluster peer:

- Initialize with `ipfs-cluster-service init`
- You will be asked to enter a cluster secret. For more on this, see [the
**Initialization** section of the `ipfs-cluster-service`
README](ipfs-cluster-service/dist/README.md#initialization).
- Run with `ipfs-cluster-service`. Check `--help` for options
- Initialize with `ipfs-cluster-service init`
- This will randomly generate a secret which should be shared among all peers.
- Run with `ipfs-cluster-service`. Check `--help` for options

For more information see the [`ipfs-cluster-service` README](ipfs-cluster-service/dist/README.md). Also, read [A guide to running IPFS Cluster](docs/ipfs-cluster-guide.md) for full a full overview of how cluster works.
For more information about `ipfs-cluster-service` see the [`ipfs-cluster-service` README](ipfs-cluster-service/dist/README.md). Also, read [A guide to running IPFS Cluster](docs/ipfs-cluster-guide.md) for full a full overview of how cluster works.

**`ipfs-cluster-ctl`** is used to interface with the ipfs-cluster peer:

Expand Down
2 changes: 1 addition & 1 deletion allocator/ascendalloc/ascendalloc.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ var logger = logging.Logger("ascendalloc")
// AscendAllocator extends the SimpleAllocator
type AscendAllocator struct{}

// NewAscendAllocator returns an initialized AscendAllocator
// NewAllocator returns an initialized AscendAllocator
func NewAllocator() AscendAllocator {
return AscendAllocator{}
}
Expand Down
2 changes: 1 addition & 1 deletion allocator/descendalloc/descendalloc.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ var logger = logging.Logger("descendalloc")
// DescendAllocator extends the SimpleAllocator
type DescendAllocator struct{}

// NewDescendAllocator returns an initialized DescendAllocator
// NewAllocator returns an initialized DescendAllocator
func NewAllocator() DescendAllocator {
return DescendAllocator{}
}
Expand Down
2 changes: 1 addition & 1 deletion allocator/util/metricsorter.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Package allocator.util is a utility package used by the allocator
// Package util is a utility package used by the allocator
// implementations. This package provides the SortNumeric function, which may be
// used by an allocator to sort peers by their metric values (ascending or
// descending).
Expand Down
236 changes: 236 additions & 0 deletions api/rest/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,236 @@
package rest

import (
"crypto/tls"
"encoding/json"
"errors"
"fmt"
"path/filepath"
"time"

"github.com/ipfs/ipfs-cluster/config"

ma "github.com/multiformats/go-multiaddr"
)

const configKey = "restapi"

// These are the default values for Config
const (
DefaultListenAddr = "/ip4/127.0.0.1/tcp/9094"
DefaultReadTimeout = 30 * time.Second
DefaultReadHeaderTimeout = 5 * time.Second
DefaultWriteTimeout = 60 * time.Second
DefaultIdleTimeout = 120 * time.Second
)

// Config is used to intialize the API object and allows to
// customize the behaviour of it. It implements the config.ComponentConfig
// interface.
type Config struct {
config.Saver

// Listen parameters for the the Cluster HTTP API component.
ListenAddr ma.Multiaddr

// TLS configuration for the HTTP listener
TLS *tls.Config

// SSLCertFile is a path to a certificate file used to secure the HTTP
// API endpoint. Leave empty to use plain HTTP instead.
pathSSLCertFile string

// SSLKeyFile is a path to the private key corresponding to the
// SSLCertFile.
pathSSLKeyFile string

// Maximum duration before timing out reading a full request
ReadTimeout time.Duration

// Maximum duration before timing out reading the headers of a request
ReadHeaderTimeout time.Duration

// Maximum duration before timing out write of the response
WriteTimeout time.Duration

// Server-side amount of time a Keep-Alive connection will be
// kept idle before being reused
IdleTimeout time.Duration

// BasicAuthCreds is a map of username-password pairs
// which are authorized to use Basic Authentication
BasicAuthCreds map[string]string
}

type jsonConfig struct {
ListenMultiaddress string `json:"listen_multiaddress"`
SSLCertFile string `json:"ssl_cert_file,omitempty"`
SSLKeyFile string `json:"ssl_key_file,omitempty"`
ReadTimeout string `json:"read_timeout"`
ReadHeaderTimeout string `json:"read_header_timeout"`
WriteTimeout string `json:"write_timeout"`
IdleTimeout string `json:"idle_timeout"`
BasicAuthCreds map[string]string `json:"basic_auth_credentials"`
}

// ConfigKey returns a human-friendly identifier for this type of
// Config.
func (cfg *Config) ConfigKey() string {
return configKey
}

// Default initializes this Config with working values.
func (cfg *Config) Default() error {
listen, _ := ma.NewMultiaddr(DefaultListenAddr)
cfg.ListenAddr = listen
cfg.pathSSLCertFile = ""
cfg.pathSSLKeyFile = ""
cfg.ReadTimeout = DefaultReadTimeout
cfg.ReadHeaderTimeout = DefaultReadHeaderTimeout
cfg.WriteTimeout = DefaultWriteTimeout
cfg.IdleTimeout = DefaultIdleTimeout
cfg.BasicAuthCreds = nil

return nil
}

// Validate makes sure that all fields in this Config have
// working values, at least in appearance.
func (cfg *Config) Validate() error {
if cfg.ListenAddr == nil {
return errors.New("restapi.listen_multiaddress not set")
}

if cfg.ReadTimeout <= 0 {
return errors.New("restapi.read_timeout is invalid")
}

if cfg.ReadHeaderTimeout <= 0 {
return errors.New("restapi.read_header_timeout is invalid")
}

if cfg.WriteTimeout <= 0 {
return errors.New("restapi.write_timeout is invalid")
}

if cfg.IdleTimeout <= 0 {
return errors.New("restapi.idle_timeout invalid")
}

if cfg.BasicAuthCreds != nil && len(cfg.BasicAuthCreds) == 0 {
return errors.New("restapi.basic_auth_creds should be null or have at least one entry")
}

if (cfg.pathSSLCertFile != "" || cfg.pathSSLKeyFile != "") && cfg.TLS == nil {
return errors.New("error loading SSL certificate or key")
}

return nil
}

// LoadJSON parses a raw JSON byte slice created by ToJSON() and sets the
// configuration fields accordingly.
func (cfg *Config) LoadJSON(raw []byte) error {
jcfg := &jsonConfig{}
err := json.Unmarshal(raw, jcfg)
if err != nil {
logger.Error("Error unmarshaling restapi config")
return err
}

// A further improvement here below is that only non-zero fields
// are assigned. In that case, make sure you have Defaulted
// everything else.
// cfg.Default()

listen, err := ma.NewMultiaddr(jcfg.ListenMultiaddress)
if err != nil {
err = fmt.Errorf("error parsing listen_multiaddress: %s", err)
return err
}

cfg.ListenAddr = listen
cert := jcfg.SSLCertFile
key := jcfg.SSLKeyFile
cfg.pathSSLCertFile = cert
cfg.pathSSLKeyFile = key

if cert != "" || key != "" {
// if one is missing, newTLSConfig will
// error loudly
if !filepath.IsAbs(cert) {
cert = filepath.Join(cfg.BaseDir, cert)
}
if !filepath.IsAbs(key) {
key = filepath.Join(cfg.BaseDir, key)
}
logger.Debug(cfg.BaseDir)
logger.Debug(cert, key)
tlsCfg, err := newTLSConfig(cert, key)
if err != nil {
return err
}
cfg.TLS = tlsCfg
}

// errors ignored as Validate() below will catch them
t, _ := time.ParseDuration(jcfg.ReadTimeout)
cfg.ReadTimeout = t

t, _ = time.ParseDuration(jcfg.ReadHeaderTimeout)
cfg.ReadHeaderTimeout = t

t, _ = time.ParseDuration(jcfg.WriteTimeout)
cfg.WriteTimeout = t

t, _ = time.ParseDuration(jcfg.IdleTimeout)
cfg.IdleTimeout = t

cfg.BasicAuthCreds = jcfg.BasicAuthCreds

return cfg.Validate()
}

// ToJSON produce a human-friendly JSON representation of the Config
// object.
func (cfg *Config) ToJSON() (raw []byte, err error) {
// Multiaddress String() may panic
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("%s", r)
}
}()

jcfg := &jsonConfig{}
jcfg.ListenMultiaddress = cfg.ListenAddr.String()
jcfg.SSLCertFile = cfg.pathSSLCertFile
jcfg.SSLKeyFile = cfg.pathSSLKeyFile
jcfg.ReadTimeout = cfg.ReadTimeout.String()
jcfg.ReadHeaderTimeout = cfg.ReadHeaderTimeout.String()
jcfg.WriteTimeout = cfg.WriteTimeout.String()
jcfg.IdleTimeout = cfg.IdleTimeout.String()
jcfg.BasicAuthCreds = cfg.BasicAuthCreds

raw, err = config.DefaultJSONMarshal(jcfg)
return
}

func newTLSConfig(certFile, keyFile string) (*tls.Config, error) {
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
if err != nil {
return nil, errors.New("Error loading TLS certficate/key: " + err.Error())
}
// based on https://github.com/denji/golang-tls
return &tls.Config{
MinVersion: tls.VersionTLS12,
CurvePreferences: []tls.CurveID{tls.CurveP521, tls.CurveP384, tls.CurveP256},
PreferServerCipherSuites: true,
CipherSuites: []uint16{
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
tls.TLS_RSA_WITH_AES_256_GCM_SHA384,
tls.TLS_RSA_WITH_AES_256_CBC_SHA,
},
Certificates: []tls.Certificate{cert},
}, nil
}
97 changes: 97 additions & 0 deletions api/rest/config_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package rest

import (
"encoding/json"
"testing"
)

var cfgJSON = []byte(`
{
"listen_multiaddress": "/ip4/127.0.0.1/tcp/9094",
"ssl_cert_file": "test/server.crt",
"ssl_key_file": "test/server.key",
"read_timeout": "30s",
"read_header_timeout": "5s",
"write_timeout": "1m0s",
"idle_timeout": "2m0s",
"basic_auth_credentials": null
}
`)

func TestLoadJSON(t *testing.T) {
cfg := &Config{}
err := cfg.LoadJSON(cfgJSON)
if err != nil {
t.Fatal(err)
}

j := &jsonConfig{}

json.Unmarshal(cfgJSON, j)
j.ListenMultiaddress = "abc"
tst, _ := json.Marshal(j)
err = cfg.LoadJSON(tst)
if err == nil {
t.Error("expected error decoding listen multiaddress")
}

j = &jsonConfig{}
json.Unmarshal(cfgJSON, j)
j.ReadTimeout = "0"
tst, _ = json.Marshal(j)
err = cfg.LoadJSON(tst)
if err == nil {
t.Error("expected error in read_timeout")
}

j = &jsonConfig{}
json.Unmarshal(cfgJSON, j)
j.BasicAuthCreds = make(map[string]string)
tst, _ = json.Marshal(j)
err = cfg.LoadJSON(tst)
if err == nil {
t.Error("expected error with empty basic auth map")
}

j = &jsonConfig{}
json.Unmarshal(cfgJSON, j)
j.SSLCertFile = "abc"
tst, _ = json.Marshal(j)
err = cfg.LoadJSON(tst)
if err == nil {
t.Error("expected error with TLS configuration")
}
}

func TestToJSON(t *testing.T) {
cfg := &Config{}
cfg.LoadJSON(cfgJSON)
newjson, err := cfg.ToJSON()
if err != nil {
t.Fatal(err)
}
cfg = &Config{}
err = cfg.LoadJSON(newjson)
if err != nil {
t.Fatal(err)
}
}

func TestDefault(t *testing.T) {
cfg := &Config{}
cfg.Default()
if cfg.Validate() != nil {
t.Fatal("error validating")
}

cfg.ListenAddr = nil
if cfg.Validate() == nil {
t.Fatal("expected error validating")
}

cfg.Default()
cfg.IdleTimeout = 0
if cfg.Validate() == nil {
t.Fatal("expected error validating")
}
}
Loading

0 comments on commit 03b21bf

Please sign in to comment.