Allow creating a network without configuring it in daemon.yml
This commit is contained in:
parent
73af69fa04
commit
3111d2ca74
@ -30,7 +30,7 @@ The requirements for this host are:
|
|||||||
|
|
||||||
* At least 3 directories should be chosen, each of which will be committing at
|
* At least 3 directories should be chosen, each of which will be committing at
|
||||||
least 1GB. Ideally these directories should be on different physical disks,
|
least 1GB. Ideally these directories should be on different physical disks,
|
||||||
but if that's not possible it's ok. See the Next Steps section.
|
but if that's not possible it's ok.
|
||||||
|
|
||||||
* None of the resources being used for this network (the UDP port or storage
|
* None of the resources being used for this network (the UDP port or storage
|
||||||
locations) should be being used by other networks.
|
locations) should be being used by other networks.
|
||||||
|
@ -25,8 +25,12 @@ create the signature.
|
|||||||
|
|
||||||
## Releasing
|
## Releasing
|
||||||
|
|
||||||
Releases are uploaded to the repository's Releases page, and release naming
|
Release artifactes are hosted at `micropelago.net` under
|
||||||
follows the conventional semantic versioning system. Each release should be
|
`/isle/releases/<release name>`. An `index.gmi` page should be created in that
|
||||||
accompanied by a set of changes which have occurred since the last release,
|
directory which includes links to each artifact, as well as a changelog
|
||||||
described both in the `CHANGELOG.md` file and in the description on the Release
|
detailing all new features and fixes included since the previous release.
|
||||||
itself.
|
|
||||||
|
A link to the new release should be included at `/isle/releases/index.gmi`.
|
||||||
|
|
||||||
|
The release shoulld be tagged in the git repo using its release name as well,
|
||||||
|
with the tag notes linking to the `micropelago.net` page.
|
||||||
|
@ -11,6 +11,11 @@ go test ./... # Test everything
|
|||||||
|
|
||||||
## Integration Tests
|
## Integration Tests
|
||||||
|
|
||||||
|
NOTE: before running integration tests you will want to make sure you can [build
|
||||||
|
Isle][building] in the first place.
|
||||||
|
|
||||||
|
[building]: ./building.md
|
||||||
|
|
||||||
Integration tests are those which require processes or state external to the
|
Integration tests are those which require processes or state external to the
|
||||||
test itself. Integration tests are marked using the
|
test itself. Integration tests are marked using the
|
||||||
`toolkit.MarkIntegrationTest` function, which will cause them to be skipped
|
`toolkit.MarkIntegrationTest` function, which will cause them to be skipped
|
||||||
|
@ -6,7 +6,9 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"isle/nebula"
|
"isle/nebula"
|
||||||
"isle/toolkit"
|
"isle/toolkit"
|
||||||
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
"dev.mediocregopher.com/mediocre-go-lib.git/mlog"
|
"dev.mediocregopher.com/mediocre-go-lib.git/mlog"
|
||||||
)
|
)
|
||||||
@ -34,14 +36,41 @@ func (f *textUnmarshalerFlag[T, P]) String() string {
|
|||||||
|
|
||||||
func (f *textUnmarshalerFlag[T, P]) Type() string { return "string" }
|
func (f *textUnmarshalerFlag[T, P]) Type() string { return "string" }
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
type (
|
type (
|
||||||
hostNameFlag = textUnmarshalerFlag[nebula.HostName, *nebula.HostName]
|
hostNameFlag = textUnmarshalerFlag[nebula.HostName, *nebula.HostName]
|
||||||
ipNetFlag = textUnmarshalerFlag[nebula.IPNet, *nebula.IPNet]
|
ipNetFlag = textUnmarshalerFlag[nebula.IPNet, *nebula.IPNet]
|
||||||
ipFlag = textUnmarshalerFlag[netip.Addr, *netip.Addr]
|
ipFlag = textUnmarshalerFlag[netip.Addr, *netip.Addr]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
type (
|
||||||
|
addrFlagStr string
|
||||||
|
addrFlag = textUnmarshalerFlag[addrFlagStr, *addrFlagStr]
|
||||||
|
)
|
||||||
|
|
||||||
|
func (f addrFlagStr) MarshalText() ([]byte, error) {
|
||||||
|
return []byte(f), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *addrFlagStr) UnmarshalText(b []byte) error {
|
||||||
|
str := string(b)
|
||||||
|
|
||||||
|
_, portStr, err := net.SplitHostPort(str)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := strconv.ParseUint(portStr, 10, 16); err != nil {
|
||||||
|
return fmt.Errorf("invalid port %q", portStr)
|
||||||
|
}
|
||||||
|
|
||||||
|
*f = addrFlagStr(str)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
type logLevelFlag struct {
|
type logLevelFlag struct {
|
||||||
mlog.Level
|
mlog.Level
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,8 @@ package main
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"isle/bootstrap"
|
||||||
"isle/daemon"
|
"isle/daemon"
|
||||||
"isle/daemon/jsonrpc2"
|
"isle/daemon/jsonrpc2"
|
||||||
"isle/toolkit"
|
"isle/toolkit"
|
||||||
@ -15,6 +17,14 @@ import (
|
|||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func bootstrapNewCreationParams(name, domain string) bootstrap.CreationParams {
|
||||||
|
return bootstrap.CreationParams{
|
||||||
|
ID: fmt.Sprintf("%s-%s", name, domain),
|
||||||
|
Name: name,
|
||||||
|
Domain: domain,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type runHarness struct {
|
type runHarness struct {
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
logger *mlog.Logger
|
logger *mlog.Logger
|
||||||
@ -63,6 +73,7 @@ func (h *runHarness) run(t *testing.T, args ...string) error {
|
|||||||
stdout: h.stdout,
|
stdout: h.stdout,
|
||||||
changeStager: h.changeStager,
|
changeStager: h.changeStager,
|
||||||
daemonRPC: daemonRPCClient,
|
daemonRPC: daemonRPCClient,
|
||||||
|
bootstrapNewCreationParams: bootstrapNewCreationParams,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,10 +6,15 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"isle/bootstrap"
|
"isle/bootstrap"
|
||||||
"isle/daemon"
|
"isle/daemon"
|
||||||
|
"isle/daemon/daecommon"
|
||||||
"isle/daemon/network"
|
"isle/daemon/network"
|
||||||
"isle/jsonutil"
|
"isle/jsonutil"
|
||||||
"isle/nebula"
|
"isle/nebula"
|
||||||
|
"isle/toolkit"
|
||||||
|
"path/filepath"
|
||||||
"slices"
|
"slices"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
var subCmdNetworkCreate = subCmd{
|
var subCmdNetworkCreate = subCmd{
|
||||||
@ -19,6 +24,7 @@ var subCmdNetworkCreate = subCmd{
|
|||||||
var (
|
var (
|
||||||
ipNet ipNetFlag
|
ipNet ipNetFlag
|
||||||
hostName hostNameFlag
|
hostName hostNameFlag
|
||||||
|
vpnPublicAddr addrFlag
|
||||||
)
|
)
|
||||||
|
|
||||||
name := ctx.flags.StringP(
|
name := ctx.flags.StringP(
|
||||||
@ -44,6 +50,19 @@ var subCmdNetworkCreate = subCmd{
|
|||||||
"Name of this host, which will be the first host in the network",
|
"Name of this host, which will be the first host in the network",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
vpnPublicAddrF := ctx.flags.VarPF(
|
||||||
|
&vpnPublicAddr,
|
||||||
|
"vpn-public-address",
|
||||||
|
"",
|
||||||
|
"Public address (host:port) that this host is publicly available on",
|
||||||
|
)
|
||||||
|
|
||||||
|
storageAllocStrs := ctx.flags.StringArray(
|
||||||
|
"storage-allocation",
|
||||||
|
nil,
|
||||||
|
"Storage allocation on this host, in the form '<capacity-in-gb>@<path>`",
|
||||||
|
)
|
||||||
|
|
||||||
ctx, err := ctx.withParsedFlags(&withParsedFlagsOpts{
|
ctx, err := ctx.withParsedFlags(&withParsedFlagsOpts{
|
||||||
noNetwork: true,
|
noNetwork: true,
|
||||||
})
|
})
|
||||||
@ -58,14 +77,61 @@ var subCmdNetworkCreate = subCmd{
|
|||||||
return errors.New("--name, --domain, --ip-net, and --hostname are required")
|
return errors.New("--name, --domain, --ip-net, and --hostname are required")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type storageAlloc struct {
|
||||||
|
capacity uint64
|
||||||
|
path string
|
||||||
|
}
|
||||||
|
|
||||||
|
storageAllocs := make([]storageAlloc, len(*storageAllocStrs))
|
||||||
|
for i, str := range *storageAllocStrs {
|
||||||
|
capStr, path, ok := strings.Cut(str, "@")
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf(
|
||||||
|
"malformed --storage-allocation %q, no '@' found", str,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
capacity, err := strconv.ParseUint(capStr, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf(
|
||||||
|
"invalid --storage-allocation capacity %q", capStr,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
storageAllocs[i] = storageAlloc{capacity, path}
|
||||||
|
}
|
||||||
|
|
||||||
daemonRPC, err := ctx.newDaemonRPC()
|
daemonRPC, err := ctx.newDaemonRPC()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("creating daemon RPC client: %w", err)
|
return fmt.Errorf("creating daemon RPC client: %w", err)
|
||||||
}
|
}
|
||||||
defer daemonRPC.Close()
|
defer daemonRPC.Close()
|
||||||
|
|
||||||
|
var networkConfig *daecommon.NetworkConfig
|
||||||
|
if vpnPublicAddrF.Changed || len(storageAllocs) > 0 {
|
||||||
|
networkConfig = toolkit.Ptr(
|
||||||
|
daecommon.NewNetworkConfig(func(c *daecommon.NetworkConfig) {
|
||||||
|
c.VPN.PublicAddr = string(vpnPublicAddr.V)
|
||||||
|
for _, a := range storageAllocs {
|
||||||
|
c.Storage.Allocations = append(
|
||||||
|
c.Storage.Allocations,
|
||||||
|
daecommon.ConfigStorageAllocation{
|
||||||
|
DataPath: filepath.Join(a.path, "data"),
|
||||||
|
MetaPath: filepath.Join(a.path, "meta"),
|
||||||
|
Capacity: int(a.capacity),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
err = daemonRPC.CreateNetwork(
|
err = daemonRPC.CreateNetwork(
|
||||||
ctx, *name, *domain, ipNet.V, hostName.V,
|
ctx,
|
||||||
|
ctx.opts.bootstrapNewCreationParams(*name, *domain),
|
||||||
|
ipNet.V,
|
||||||
|
hostName.V,
|
||||||
|
&daemon.CreateNetworkOpts{Config: networkConfig},
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("creating network: %w", err)
|
return fmt.Errorf("creating network: %w", err)
|
||||||
|
@ -5,14 +5,133 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"isle/bootstrap"
|
"isle/bootstrap"
|
||||||
"isle/daemon"
|
"isle/daemon"
|
||||||
|
"isle/daemon/daecommon"
|
||||||
"isle/nebula"
|
"isle/nebula"
|
||||||
"isle/toolkit"
|
"isle/toolkit"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func TestNetworkCreate(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
expect func(*testing.T, *daemon.MockRPC)
|
||||||
|
flags []string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "no given config",
|
||||||
|
expect: func(t *testing.T, daemonRPC *daemon.MockRPC) {
|
||||||
|
daemonRPC.
|
||||||
|
On(
|
||||||
|
"CreateNetwork",
|
||||||
|
toolkit.MockArg[context.Context](),
|
||||||
|
bootstrapNewCreationParams("aaa", "a.com"),
|
||||||
|
nebula.MustParseIPNet(t, "172.16.1.0/24"),
|
||||||
|
nebula.HostName("foo"),
|
||||||
|
&daemon.CreateNetworkOpts{},
|
||||||
|
).
|
||||||
|
Return(nil).
|
||||||
|
Once()
|
||||||
|
},
|
||||||
|
flags: []string{
|
||||||
|
"--name=aaa",
|
||||||
|
"--domain=a.com",
|
||||||
|
"--ip-net=172.16.1.0/24",
|
||||||
|
"--hostname=foo",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "partially given config",
|
||||||
|
expect: func(t *testing.T, daemonRPC *daemon.MockRPC) {
|
||||||
|
networkConfig := daecommon.NewNetworkConfig(func(c *daecommon.NetworkConfig) {
|
||||||
|
c.VPN.PublicAddr = "1.2.3.4:5"
|
||||||
|
})
|
||||||
|
|
||||||
|
daemonRPC.
|
||||||
|
On(
|
||||||
|
"CreateNetwork",
|
||||||
|
toolkit.MockArg[context.Context](),
|
||||||
|
bootstrapNewCreationParams("aaa", "a.com"),
|
||||||
|
nebula.MustParseIPNet(t, "172.16.1.0/24"),
|
||||||
|
nebula.HostName("foo"),
|
||||||
|
&daemon.CreateNetworkOpts{
|
||||||
|
Config: &networkConfig,
|
||||||
|
},
|
||||||
|
).
|
||||||
|
Return(nil).
|
||||||
|
Once()
|
||||||
|
},
|
||||||
|
flags: []string{
|
||||||
|
"--name=aaa",
|
||||||
|
"--domain=a.com",
|
||||||
|
"--ip-net=172.16.1.0/24",
|
||||||
|
"--hostname=foo",
|
||||||
|
"--vpn-public-address=1.2.3.4:5",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "fully given config",
|
||||||
|
expect: func(t *testing.T, daemonRPC *daemon.MockRPC) {
|
||||||
|
networkConfig := daecommon.NewNetworkConfig(func(c *daecommon.NetworkConfig) {
|
||||||
|
c.VPN.PublicAddr = "1.2.3.4:5"
|
||||||
|
c.Storage.Allocations = []daecommon.ConfigStorageAllocation{
|
||||||
|
{
|
||||||
|
DataPath: "/a/data",
|
||||||
|
MetaPath: "/a/meta",
|
||||||
|
Capacity: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
DataPath: "/b/data",
|
||||||
|
MetaPath: "/b/meta",
|
||||||
|
Capacity: 200,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
daemonRPC.
|
||||||
|
On(
|
||||||
|
"CreateNetwork",
|
||||||
|
toolkit.MockArg[context.Context](),
|
||||||
|
bootstrapNewCreationParams("aaa", "a.com"),
|
||||||
|
nebula.MustParseIPNet(t, "172.16.1.0/24"),
|
||||||
|
nebula.HostName("foo"),
|
||||||
|
&daemon.CreateNetworkOpts{
|
||||||
|
Config: &networkConfig,
|
||||||
|
},
|
||||||
|
).
|
||||||
|
Return(nil).
|
||||||
|
Once()
|
||||||
|
},
|
||||||
|
flags: []string{
|
||||||
|
"--name=aaa",
|
||||||
|
"--domain=a.com",
|
||||||
|
"--ip-net=172.16.1.0/24",
|
||||||
|
"--hostname=foo",
|
||||||
|
"--vpn-public-address=1.2.3.4:5",
|
||||||
|
"--storage-allocation=100@/a",
|
||||||
|
"--storage-allocation=200@/b",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
var (
|
||||||
|
h = newRunHarness(t)
|
||||||
|
args = append([]string{"network", "create"}, test.flags...)
|
||||||
|
)
|
||||||
|
|
||||||
|
test.expect(t, h.daemonRPC)
|
||||||
|
assert.NoError(t, h.run(t, args...))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestNetworkList(t *testing.T) {
|
func TestNetworkList(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
|
@ -5,6 +5,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"isle/bootstrap"
|
||||||
"isle/daemon"
|
"isle/daemon"
|
||||||
"isle/daemon/jsonrpc2"
|
"isle/daemon/jsonrpc2"
|
||||||
"isle/jsonutil"
|
"isle/jsonutil"
|
||||||
@ -40,6 +41,9 @@ type subCmdCtxOpts struct {
|
|||||||
stdout io.Writer
|
stdout io.Writer
|
||||||
changeStager *changeStager
|
changeStager *changeStager
|
||||||
daemonRPC daemon.RPC
|
daemonRPC daemon.RPC
|
||||||
|
|
||||||
|
// defaults to bootstrap.NewCreationParams
|
||||||
|
bootstrapNewCreationParams func(name, domain string) bootstrap.CreationParams
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *subCmdCtxOpts) withDefaults() *subCmdCtxOpts {
|
func (o *subCmdCtxOpts) withDefaults() *subCmdCtxOpts {
|
||||||
@ -55,6 +59,10 @@ func (o *subCmdCtxOpts) withDefaults() *subCmdCtxOpts {
|
|||||||
o.stdout = os.Stdout
|
o.stdout = os.Stdout
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if o.bootstrapNewCreationParams == nil {
|
||||||
|
o.bootstrapNewCreationParams = bootstrap.NewCreationParams
|
||||||
|
}
|
||||||
|
|
||||||
return o
|
return o
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,7 +3,6 @@ package main
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var subCmdVPNPublicAddressGet = subCmd{
|
var subCmdVPNPublicAddressGet = subCmd{
|
||||||
@ -38,8 +37,11 @@ var subCmdVPNPublicAddressSet = subCmd{
|
|||||||
name: "set",
|
name: "set",
|
||||||
descr: "Set the public address of the host, or overwrite the already configured one",
|
descr: "Set the public address of the host, or overwrite the already configured one",
|
||||||
do: func(ctx subCmdCtx) error {
|
do: func(ctx subCmdCtx) error {
|
||||||
publicAddr := ctx.flags.String(
|
var publicAddr addrFlag
|
||||||
"public-addr",
|
|
||||||
|
publicAddrF := ctx.flags.VarPF(
|
||||||
|
&publicAddr,
|
||||||
|
"to",
|
||||||
"",
|
"",
|
||||||
"Public address (host:port) that this host is publicly available on",
|
"Public address (host:port) that this host is publicly available on",
|
||||||
)
|
)
|
||||||
@ -49,10 +51,8 @@ var subCmdVPNPublicAddressSet = subCmd{
|
|||||||
return fmt.Errorf("parsing flags: %w", err)
|
return fmt.Errorf("parsing flags: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if *publicAddr == "" {
|
if !publicAddrF.Changed {
|
||||||
return errors.New("--public-addr is required")
|
return errors.New("--public-addr is required")
|
||||||
} else if _, _, err := net.SplitHostPort(*publicAddr); err != nil {
|
|
||||||
return fmt.Errorf("invalid --public-addr: %w", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
daemonRPC, err := ctx.newDaemonRPC()
|
daemonRPC, err := ctx.newDaemonRPC()
|
||||||
@ -66,7 +66,7 @@ var subCmdVPNPublicAddressSet = subCmd{
|
|||||||
return fmt.Errorf("getting network config: %w", err)
|
return fmt.Errorf("getting network config: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
config.VPN.PublicAddr = *publicAddr
|
config.VPN.PublicAddr = string(publicAddr.V)
|
||||||
|
|
||||||
return daemonRPC.SetConfig(ctx, config)
|
return daemonRPC.SetConfig(ctx, config)
|
||||||
},
|
},
|
||||||
|
@ -47,15 +47,15 @@ func (c *rpcClient) CreateNebulaCertificate(ctx context.Context, h1 nebula.HostN
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *rpcClient) CreateNetwork(ctx context.Context, name string, domain string, ipNet nebula.IPNet, hostName nebula.HostName) (err error) {
|
func (c *rpcClient) CreateNetwork(ctx context.Context, creationParams bootstrap.CreationParams, ipNet nebula.IPNet, hostName nebula.HostName, opts *CreateNetworkOpts) (err error) {
|
||||||
err = c.client.Call(
|
err = c.client.Call(
|
||||||
ctx,
|
ctx,
|
||||||
nil,
|
nil,
|
||||||
"CreateNetwork",
|
"CreateNetwork",
|
||||||
name,
|
creationParams,
|
||||||
domain,
|
|
||||||
ipNet,
|
ipNet,
|
||||||
hostName,
|
hostName,
|
||||||
|
opts,
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -21,6 +21,7 @@ var _ RPC = (*Daemon)(nil)
|
|||||||
type joinedNetwork struct {
|
type joinedNetwork struct {
|
||||||
id string
|
id string
|
||||||
network.Network
|
network.Network
|
||||||
|
creationParams bootstrap.CreationParams
|
||||||
config *daecommon.NetworkConfig
|
config *daecommon.NetworkConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -96,7 +97,7 @@ func New(
|
|||||||
return nil, fmt.Errorf("loading network %q: %w", id, err)
|
return nil, fmt.Errorf("loading network %q: %w", id, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
d.networks[id] = joinedNetwork{id, n, networkConfig}
|
d.networks[id] = joinedNetwork{id, n, creationParams, networkConfig}
|
||||||
}
|
}
|
||||||
|
|
||||||
return d, nil
|
return d, nil
|
||||||
@ -114,26 +115,37 @@ func New(
|
|||||||
// and will have full administrative privileges.
|
// and will have full administrative privileges.
|
||||||
//
|
//
|
||||||
// Errors:
|
// Errors:
|
||||||
// - network.ErrInvalidConfig
|
// - [network.ErrInvalidConfig]
|
||||||
// - ErrAlreadyJoined
|
// - [ErrAlreadyJoined]
|
||||||
|
// - [ErrManagedNetworkConfig] - if `opts.NetworkConfig` is given, but a
|
||||||
|
// NetworkConfig is also provided in the Daemon's Config.
|
||||||
func (d *Daemon) CreateNetwork(
|
func (d *Daemon) CreateNetwork(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
name, domain string, ipNet nebula.IPNet, hostName nebula.HostName,
|
creationParams bootstrap.CreationParams,
|
||||||
|
ipNet nebula.IPNet,
|
||||||
|
hostName nebula.HostName,
|
||||||
|
opts *CreateNetworkOpts,
|
||||||
) error {
|
) error {
|
||||||
|
opts = opts.withDefaults()
|
||||||
|
|
||||||
d.l.Lock()
|
d.l.Lock()
|
||||||
defer d.l.Unlock()
|
defer d.l.Unlock()
|
||||||
|
|
||||||
var (
|
var (
|
||||||
creationParams = bootstrap.NewCreationParams(name, domain)
|
|
||||||
networkConfig = pickNetworkConfig(
|
networkConfig = pickNetworkConfig(
|
||||||
d.daemonConfig.Networks, creationParams,
|
d.daemonConfig.Networks, creationParams,
|
||||||
)
|
)
|
||||||
networkLogger = networkLogger(d.logger, creationParams)
|
networkLogger = networkLogger(d.logger, creationParams)
|
||||||
)
|
)
|
||||||
|
|
||||||
if joined, err := alreadyJoined(ctx, d.networks, creationParams); err != nil {
|
if opts.Config != nil {
|
||||||
return fmt.Errorf("checking if already joined to network: %w", err)
|
if networkConfig != nil {
|
||||||
} else if joined {
|
return ErrManagedNetworkConfig
|
||||||
|
}
|
||||||
|
networkConfig = opts.Config
|
||||||
|
}
|
||||||
|
|
||||||
|
if alreadyJoined(d.networks, creationParams) {
|
||||||
return ErrAlreadyJoined
|
return ErrAlreadyJoined
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -154,7 +166,7 @@ func (d *Daemon) CreateNetwork(
|
|||||||
|
|
||||||
networkLogger.Info(ctx, "Network created successfully")
|
networkLogger.Info(ctx, "Network created successfully")
|
||||||
d.networks[creationParams.ID] = joinedNetwork{
|
d.networks[creationParams.ID] = joinedNetwork{
|
||||||
creationParams.ID, n, networkConfig,
|
creationParams.ID, n, creationParams, networkConfig,
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -181,9 +193,7 @@ func (d *Daemon) JoinNetwork(
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
if joined, err := alreadyJoined(ctx, d.networks, creationParams); err != nil {
|
if alreadyJoined(d.networks, creationParams) {
|
||||||
return fmt.Errorf("checking if already joined to network: %w", err)
|
|
||||||
} else if joined {
|
|
||||||
return ErrAlreadyJoined
|
return ErrAlreadyJoined
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -203,7 +213,9 @@ func (d *Daemon) JoinNetwork(
|
|||||||
}
|
}
|
||||||
|
|
||||||
networkLogger.Info(ctx, "Network joined successfully")
|
networkLogger.Info(ctx, "Network joined successfully")
|
||||||
d.networks[networkID] = joinedNetwork{networkID, n, networkConfig}
|
d.networks[networkID] = joinedNetwork{
|
||||||
|
networkID, n, creationParams, networkConfig,
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,6 +5,7 @@ import (
|
|||||||
"isle/bootstrap"
|
"isle/bootstrap"
|
||||||
"isle/daemon/daecommon"
|
"isle/daemon/daecommon"
|
||||||
"isle/daemon/network"
|
"isle/daemon/network"
|
||||||
|
"isle/nebula"
|
||||||
"isle/toolkit"
|
"isle/toolkit"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
@ -234,6 +235,134 @@ func TestNew(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDaemon_CreateNetwork(t *testing.T) {
|
||||||
|
t.Run("ErrManagedNetworkConfig", func(t *testing.T) {
|
||||||
|
var (
|
||||||
|
networkConfig = daecommon.NewNetworkConfig(nil)
|
||||||
|
h = newHarness(t, &harnessOpts{
|
||||||
|
config: daecommon.Config{
|
||||||
|
Networks: map[string]daecommon.NetworkConfig{
|
||||||
|
"AAA": networkConfig,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
creationParams = bootstrap.NewCreationParams("AAA", "a.com")
|
||||||
|
ipNet = nebula.MustParseIPNet(t, "172.16.0.0/24")
|
||||||
|
)
|
||||||
|
|
||||||
|
err := h.daemon.CreateNetwork(
|
||||||
|
h.ctx,
|
||||||
|
creationParams,
|
||||||
|
ipNet,
|
||||||
|
nebula.HostName("foo"),
|
||||||
|
&CreateNetworkOpts{Config: &networkConfig},
|
||||||
|
)
|
||||||
|
assert.ErrorIs(t, err, ErrManagedNetworkConfig)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("ErrAlreadyJoined", func(t *testing.T) {
|
||||||
|
var (
|
||||||
|
creationParams = bootstrap.NewCreationParams("AAA", "a.com")
|
||||||
|
networkConfig = daecommon.NewNetworkConfig(nil)
|
||||||
|
h = newHarness(t, &harnessOpts{
|
||||||
|
expectNetworksLoaded: []expectNetworkLoad{{
|
||||||
|
creationParams, nil, network.NewMockNetwork(t),
|
||||||
|
}},
|
||||||
|
expectStoredConfigs: map[string]daecommon.NetworkConfig{
|
||||||
|
creationParams.ID: networkConfig,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
ipNet = nebula.MustParseIPNet(t, "172.16.0.0/24")
|
||||||
|
)
|
||||||
|
|
||||||
|
err := h.daemon.CreateNetwork(
|
||||||
|
h.ctx,
|
||||||
|
bootstrap.NewCreationParams("AAA", "aaa.com"),
|
||||||
|
ipNet,
|
||||||
|
nebula.HostName("foo"),
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
assert.ErrorIs(t, err, ErrAlreadyJoined)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("success/config given", func(t *testing.T) {
|
||||||
|
networkA := network.NewMockNetwork(t)
|
||||||
|
networkA.On("Shutdown").Return(nil).Once()
|
||||||
|
|
||||||
|
var (
|
||||||
|
h = newHarness(t, nil)
|
||||||
|
creationParams = bootstrap.NewCreationParams("AAA", "a.com")
|
||||||
|
networkConfig = daecommon.NewNetworkConfig(func(c *daecommon.NetworkConfig) {
|
||||||
|
c.VPN.PublicAddr = "1.2.3.4:50"
|
||||||
|
})
|
||||||
|
ipNet = nebula.MustParseIPNet(t, "172.16.0.0/24")
|
||||||
|
hostName = nebula.HostName("foo")
|
||||||
|
)
|
||||||
|
|
||||||
|
h.networkLoader.
|
||||||
|
On(
|
||||||
|
"Create",
|
||||||
|
toolkit.MockArg[context.Context](),
|
||||||
|
toolkit.MockArg[*mlog.Logger](),
|
||||||
|
creationParams,
|
||||||
|
ipNet,
|
||||||
|
hostName,
|
||||||
|
&network.Opts{
|
||||||
|
Config: &networkConfig,
|
||||||
|
},
|
||||||
|
).
|
||||||
|
Return(networkA, nil).
|
||||||
|
Once()
|
||||||
|
|
||||||
|
err := h.daemon.CreateNetwork(
|
||||||
|
h.ctx,
|
||||||
|
creationParams,
|
||||||
|
ipNet,
|
||||||
|
hostName,
|
||||||
|
&CreateNetworkOpts{
|
||||||
|
Config: &networkConfig,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Contains(t, h.daemon.networks, creationParams.ID)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("success/no config given", func(t *testing.T) {
|
||||||
|
networkA := network.NewMockNetwork(t)
|
||||||
|
networkA.On("Shutdown").Return(nil).Once()
|
||||||
|
|
||||||
|
var (
|
||||||
|
h = newHarness(t, nil)
|
||||||
|
creationParams = bootstrap.NewCreationParams("AAA", "a.com")
|
||||||
|
ipNet = nebula.MustParseIPNet(t, "172.16.0.0/24")
|
||||||
|
hostName = nebula.HostName("foo")
|
||||||
|
)
|
||||||
|
|
||||||
|
h.networkLoader.
|
||||||
|
On(
|
||||||
|
"Create",
|
||||||
|
toolkit.MockArg[context.Context](),
|
||||||
|
toolkit.MockArg[*mlog.Logger](),
|
||||||
|
creationParams,
|
||||||
|
ipNet,
|
||||||
|
hostName,
|
||||||
|
&network.Opts{},
|
||||||
|
).
|
||||||
|
Return(networkA, nil).
|
||||||
|
Once()
|
||||||
|
|
||||||
|
err := h.daemon.CreateNetwork(
|
||||||
|
h.ctx,
|
||||||
|
creationParams,
|
||||||
|
ipNet,
|
||||||
|
hostName,
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Contains(t, h.daemon.networks, creationParams.ID)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestDaemon_SetConfig(t *testing.T) {
|
func TestDaemon_SetConfig(t *testing.T) {
|
||||||
t.Run("success", func(t *testing.T) {
|
t.Run("success", func(t *testing.T) {
|
||||||
var (
|
var (
|
||||||
|
@ -58,22 +58,13 @@ func pickNetwork(
|
|||||||
}
|
}
|
||||||
|
|
||||||
func alreadyJoined(
|
func alreadyJoined(
|
||||||
ctx context.Context,
|
networks map[string]joinedNetwork, creationParams bootstrap.CreationParams,
|
||||||
networks map[string]joinedNetwork,
|
) bool {
|
||||||
creationParams bootstrap.CreationParams,
|
for _, network := range networks {
|
||||||
) (
|
if network.creationParams.Conflicts(creationParams) {
|
||||||
bool, error,
|
return true
|
||||||
) {
|
|
||||||
for networkID, network := range networks {
|
|
||||||
existingCreationParams, err := network.GetNetworkCreationParams(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return false, fmt.Errorf(
|
|
||||||
"getting creation params of network %q: %w", networkID, err,
|
|
||||||
)
|
|
||||||
} else if existingCreationParams.Conflicts(creationParams) {
|
|
||||||
return true, nil
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false, nil
|
return false
|
||||||
}
|
}
|
||||||
|
@ -14,16 +14,31 @@ import (
|
|||||||
"dev.mediocregopher.com/mediocre-go-lib.git/mlog"
|
"dev.mediocregopher.com/mediocre-go-lib.git/mlog"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// CreateNetworkOpts are optional arguments to the CreateNetwork method. A nil
|
||||||
|
// value is equivalent to a zero value.
|
||||||
|
type CreateNetworkOpts struct {
|
||||||
|
// Config will be used as the NetworkConfig for the new Network, rather than
|
||||||
|
// picking one provided in the Daemon's Config.
|
||||||
|
Config *daecommon.NetworkConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *CreateNetworkOpts) withDefaults() *CreateNetworkOpts {
|
||||||
|
if o == nil {
|
||||||
|
o = new(CreateNetworkOpts)
|
||||||
|
}
|
||||||
|
return o
|
||||||
|
}
|
||||||
|
|
||||||
// RPC defines the methods which the Daemon exposes over RPC (via the RPCHandler
|
// RPC defines the methods which the Daemon exposes over RPC (via the RPCHandler
|
||||||
// or HTTPHandler methods). Method documentation can be found on the Daemon
|
// or HTTPHandler methods). Method documentation can be found on the Daemon
|
||||||
// type.
|
// type.
|
||||||
type RPC interface {
|
type RPC interface {
|
||||||
CreateNetwork(
|
CreateNetwork(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
name string,
|
creationParams bootstrap.CreationParams,
|
||||||
domain string,
|
|
||||||
ipNet nebula.IPNet,
|
ipNet nebula.IPNet,
|
||||||
hostName nebula.HostName,
|
hostName nebula.HostName,
|
||||||
|
opts *CreateNetworkOpts,
|
||||||
) error
|
) error
|
||||||
|
|
||||||
JoinNetwork(context.Context, network.JoiningBootstrap) error
|
JoinNetwork(context.Context, network.JoiningBootstrap) error
|
||||||
|
@ -76,17 +76,17 @@ func (_m *MockRPC) CreateNebulaCertificate(_a0 context.Context, _a1 nebula.HostN
|
|||||||
return r0, r1
|
return r0, r1
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateNetwork provides a mock function with given fields: ctx, name, domain, ipNet, hostName
|
// CreateNetwork provides a mock function with given fields: ctx, creationParams, ipNet, hostName, opts
|
||||||
func (_m *MockRPC) CreateNetwork(ctx context.Context, name string, domain string, ipNet nebula.IPNet, hostName nebula.HostName) error {
|
func (_m *MockRPC) CreateNetwork(ctx context.Context, creationParams bootstrap.CreationParams, ipNet nebula.IPNet, hostName nebula.HostName, opts *CreateNetworkOpts) error {
|
||||||
ret := _m.Called(ctx, name, domain, ipNet, hostName)
|
ret := _m.Called(ctx, creationParams, ipNet, hostName, opts)
|
||||||
|
|
||||||
if len(ret) == 0 {
|
if len(ret) == 0 {
|
||||||
panic("no return value specified for CreateNetwork")
|
panic("no return value specified for CreateNetwork")
|
||||||
}
|
}
|
||||||
|
|
||||||
var r0 error
|
var r0 error
|
||||||
if rf, ok := ret.Get(0).(func(context.Context, string, string, nebula.IPNet, nebula.HostName) error); ok {
|
if rf, ok := ret.Get(0).(func(context.Context, bootstrap.CreationParams, nebula.IPNet, nebula.HostName, *CreateNetworkOpts) error); ok {
|
||||||
r0 = rf(ctx, name, domain, ipNet, hostName)
|
r0 = rf(ctx, creationParams, ipNet, hostName, opts)
|
||||||
} else {
|
} else {
|
||||||
r0 = ret.Error(0)
|
r0 = ret.Error(0)
|
||||||
}
|
}
|
||||||
|
@ -4,11 +4,21 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
// IPNet is the CIDR of a nebula network.
|
// IPNet is the CIDR of a nebula network.
|
||||||
type IPNet net.IPNet
|
type IPNet net.IPNet
|
||||||
|
|
||||||
|
// MustParseIPNet is a test helper for parsing a string into an IPNet.
|
||||||
|
func MustParseIPNet(t *testing.T, str string) IPNet {
|
||||||
|
var ipNet IPNet
|
||||||
|
if err := ipNet.UnmarshalText([]byte(str)); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
return ipNet
|
||||||
|
}
|
||||||
|
|
||||||
// UnmarshalText parses and validates an IPNet from a text string.
|
// UnmarshalText parses and validates an IPNet from a text string.
|
||||||
func (n *IPNet) UnmarshalText(b []byte) error {
|
func (n *IPNet) UnmarshalText(b []byte) error {
|
||||||
str := string(b)
|
str := string(b)
|
||||||
|
@ -10,3 +10,8 @@ func IsZero[T any](v T) bool {
|
|||||||
var zero T
|
var zero T
|
||||||
return reflect.DeepEqual(v, zero)
|
return reflect.DeepEqual(v, zero)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ptr returns a pointer to the given value.
|
||||||
|
func Ptr[T any](v T) *T {
|
||||||
|
return &v
|
||||||
|
}
|
||||||
|
12
tasks/soon/code/storage-allocation-modify.md
Normal file
12
tasks/soon/code/storage-allocation-modify.md
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
---
|
||||||
|
type: task
|
||||||
|
---
|
||||||
|
|
||||||
|
# Implement `storage allocation modify` Sub-Command
|
||||||
|
|
||||||
|
It should be possible to modify an allocation from the command-line, including
|
||||||
|
changing its capacity, location, and port numbers.
|
||||||
|
|
||||||
|
Location will be the difficult one. It will require shutting down the process,
|
||||||
|
copying the data from the old location, starting the process back up, and either
|
||||||
|
removing or renaming the old data to mark it as being a backup.
|
@ -1,7 +1,5 @@
|
|||||||
---
|
---
|
||||||
type: task
|
type: task
|
||||||
after:
|
|
||||||
- code/**
|
|
||||||
---
|
---
|
||||||
|
|
||||||
# Update Documentation
|
# Update Documentation
|
||||||
@ -11,3 +9,13 @@ updated.
|
|||||||
|
|
||||||
Check through all development documentation, especially that surrounding
|
Check through all development documentation, especially that surrounding
|
||||||
testing.
|
testing.
|
||||||
|
|
||||||
|
Doc changes to included are:
|
||||||
|
|
||||||
|
- New page on network configuration, how it can be done via the `daemon.yml`
|
||||||
|
file or via the command-line (but not both!)
|
||||||
|
|
||||||
|
- Rework "Contributing a Lighthouse" so that it doesn't directly mention nebula
|
||||||
|
or lighthouses at all.
|
||||||
|
|
||||||
|
- Remove nebula reference from `daemon.yml` comments.
|
||||||
|
Loading…
Reference in New Issue
Block a user