constellation/internal/cloud/openstack/openstack_test.go

731 lines
19 KiB
Go
Raw Normal View History

/*
Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
package openstack
import (
"context"
"errors"
"fmt"
"testing"
"github.com/edgelesssys/constellation/v2/internal/cloud/metadata"
"github.com/edgelesssys/constellation/v2/internal/role"
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/openstack/compute/v2/servers"
"github.com/gophercloud/gophercloud/openstack/networking/v2/subnets"
"github.com/gophercloud/gophercloud/pagination"
"github.com/stretchr/testify/assert"
)
func TestSelf(t *testing.T) {
someErr := fmt.Errorf("failed")
testCases := map[string]struct {
imds imdsAPI
want metadata.InstanceMetadata
wantErr bool
}{
"success": {
imds: &stubIMDSClient{
nameResult: "name",
providerIDResult: "providerID",
roleResult: role.ControlPlane,
vpcIPResult: "192.0.2.1",
},
want: metadata.InstanceMetadata{
Name: "name",
ProviderID: "providerID",
Role: role.ControlPlane,
VPCIP: "192.0.2.1",
},
},
"fail to get name": {
imds: &stubIMDSClient{
nameErr: someErr,
providerIDResult: "providerID",
roleResult: role.ControlPlane,
vpcIPResult: "192.0.2.1",
},
wantErr: true,
},
"fail to get provider ID": {
imds: &stubIMDSClient{
nameResult: "name",
providerIDErr: someErr,
roleResult: role.ControlPlane,
vpcIPResult: "192.0.2.1",
},
wantErr: true,
},
"fail to get role": {
imds: &stubIMDSClient{
nameResult: "name",
providerIDResult: "providerID",
roleErr: someErr,
vpcIPResult: "192.0.2.1",
},
wantErr: true,
},
"fail to get VPC IP": {
imds: &stubIMDSClient{
nameResult: "name",
providerIDResult: "providerID",
roleResult: role.ControlPlane,
vpcIPErr: someErr,
},
wantErr: true,
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
assert := assert.New(t)
c := &Cloud{imds: tc.imds}
got, err := c.Self(context.Background())
if tc.wantErr {
assert.Error(err)
} else {
assert.NoError(err)
assert.Equal(tc.want, got)
}
})
}
}
func TestList(t *testing.T) {
someErr := fmt.Errorf("failed")
// newTestAddrs returns a set of raw server addresses as we would get from
// a ListServers call and as expected by the parseSeverAddresses function.
// The hardcoded addresses don't match what we are looking for. A valid
// address can be injected. You can pass a second valid address to test
// that the first valid one is chosen.
newTestAddrs := func(vpcIP1, vpcIP2 string) map[string]any {
return map[string]any{
"network1": []any{
map[string]any{
"addr": "198.51.100.0",
"version": 4,
"OS-EXT-IPS:type": "fixed",
"OS-EXT-IPS-MAC:mac_addr": "fa:16:3e:0c:0c:0c",
},
},
"network2": []any{
map[string]any{
"addr": "192.0.2.1",
"version": 4,
"OS-EXT-IPS:type": "floating",
"OS-EXT-IPS-MAC:mac_addr": "fa:16:3e:0c:0c:0c",
},
map[string]any{
"addr": "2001:db8:3333:4444:5555:6666:7777:8888",
"version": 6,
"OS-EXT-IPS:type": "fixed",
"OS-EXT-IPS-MAC:mac_addr": "fa:16:3e:0c:0c:0c",
},
map[string]any{
"addr": vpcIP1,
"version": 4,
"OS-EXT-IPS:type": "fixed",
"OS-EXT-IPS-MAC:mac_addr": "fa:16:3e:0c:0c:0c",
},
map[string]any{
"addr": vpcIP2,
"version": 4,
"OS-EXT-IPS:type": "fixed",
"OS-EXT-IPS-MAC:mac_addr": "fa:16:3e:0c:0c:0c",
},
},
}
}
testCases := map[string]struct {
imds imdsAPI
api serversAPI
want []metadata.InstanceMetadata
wantErr bool
}{
"success": {
imds: &stubIMDSClient{uidResult: "uid"},
api: &stubServersClient{
serversPager: newSeverPager([]servers.Server{
{
Name: "name1",
ID: "id1",
Tags: &[]string{"constellation-role-control-plane", "constellation-uid-7777"},
Addresses: newTestAddrs("192.0.2.5", ""),
},
{
Name: "name2",
ID: "id2",
Tags: &[]string{"constellation-role-worker", "constellation-uid-7777"},
Addresses: newTestAddrs("192.0.2.6", "192.0.2.99"),
},
{
Name: "name3",
ID: "id3",
Tags: &[]string{"constellation-role-worker", "constellation-uid-8888"},
Addresses: newTestAddrs("198.51.100.1", ""),
},
}, nil),
subnetsPager: newSubnetPager([]subnets.Subnet{{CIDR: "192.0.2.0/24"}}, nil),
},
want: []metadata.InstanceMetadata{
{
Name: "name1",
ProviderID: "id1",
Role: role.ControlPlane,
VPCIP: "192.0.2.5",
},
{
Name: "name2",
ProviderID: "id2",
Role: role.Worker,
VPCIP: "192.0.2.6",
},
},
},
"no servers found": {
imds: &stubIMDSClient{uidResult: "uid"},
api: &stubServersClient{
serversPager: newSeverPager([]servers.Server{}, nil),
subnetsPager: newSubnetPager([]subnets.Subnet{{CIDR: "192.0.2.0/24"}}, nil),
},
wantErr: true,
},
"imds uid error": {
imds: &stubIMDSClient{uidErr: someErr},
wantErr: true,
},
"list subnets error": {
imds: &stubIMDSClient{uidResult: "uid"},
api: &stubServersClient{
subnetsPager: stubPager{allPagesErr: someErr},
},
wantErr: true,
},
"extract subnets error": {
imds: &stubIMDSClient{uidResult: "uid"},
api: &stubServersClient{
subnetsPager: newSubnetPager([]subnets.Subnet{}, someErr),
},
wantErr: true,
},
"multiple subnets error": {
imds: &stubIMDSClient{uidResult: "uid"},
api: &stubServersClient{
subnetsPager: newSubnetPager([]subnets.Subnet{
{CIDR: "192.0.2.0/24"},
{CIDR: "198.51.100.0/24"},
}, nil),
},
wantErr: true,
},
"parse subnet error": {
imds: &stubIMDSClient{uidResult: "uid"},
api: &stubServersClient{
subnetsPager: newSubnetPager([]subnets.Subnet{{CIDR: "notAnIP"}}, nil),
},
wantErr: true,
},
"list servers error": {
imds: &stubIMDSClient{uidResult: "uid"},
api: &stubServersClient{
serversPager: stubPager{allPagesErr: someErr},
subnetsPager: newSubnetPager([]subnets.Subnet{{CIDR: "192.0.2.0/24"}}, nil),
},
wantErr: true,
},
"extract servers error": {
imds: &stubIMDSClient{uidResult: "uid"},
api: &stubServersClient{
serversPager: newSeverPager([]servers.Server{}, someErr),
subnetsPager: newSubnetPager([]subnets.Subnet{{CIDR: "192.0.2.0/24"}}, nil),
},
wantErr: true,
},
"sever with empty name skipped": {
imds: &stubIMDSClient{uidResult: "uid"},
api: &stubServersClient{
serversPager: newSeverPager([]servers.Server{
{
ID: "id1",
Tags: &[]string{"constellation-role-control-plane", "constellation-uid-7777"},
Addresses: newTestAddrs("192.0.2.5", ""),
},
}, nil),
subnetsPager: newSubnetPager([]subnets.Subnet{{CIDR: "192.0.2.0/24"}}, nil),
},
wantErr: true,
},
"sever with nil tags skipped": {
imds: &stubIMDSClient{uidResult: "uid"},
api: &stubServersClient{
serversPager: newSeverPager([]servers.Server{
{
Name: "name1",
ID: "id1",
Addresses: newTestAddrs("192.0.2.5", ""),
},
}, nil),
subnetsPager: newSubnetPager([]subnets.Subnet{{CIDR: "192.0.2.0/24"}}, nil),
},
wantErr: true,
},
"server with empty ID skipped": {
imds: &stubIMDSClient{uidResult: "uid"},
api: &stubServersClient{
serversPager: newSeverPager([]servers.Server{
{
Name: "name1",
Tags: &[]string{"constellation-role-control-plane", "constellation-uid-7777"},
Addresses: newTestAddrs("192.0.2.5", ""),
},
}, nil),
subnetsPager: newSubnetPager([]subnets.Subnet{{CIDR: "192.0.2.0/24"}}, nil),
},
wantErr: true,
},
"server with unknown role skipped": {
imds: &stubIMDSClient{uidResult: "uid"},
api: &stubServersClient{
serversPager: newSeverPager([]servers.Server{
{
Name: "name1",
ID: "id1",
Tags: &[]string{"constellation-role-unknown", "constellation-uid-7777"},
Addresses: newTestAddrs("192.0.2.5", ""),
},
}, nil),
subnetsPager: newSubnetPager([]subnets.Subnet{{CIDR: "192.0.2.0/24"}}, nil),
},
wantErr: true,
},
"server without role skipped": {
imds: &stubIMDSClient{uidResult: "uid"},
api: &stubServersClient{
serversPager: newSeverPager([]servers.Server{
{
Name: "name1",
ID: "id1",
Tags: &[]string{"constellation-uid-7777"},
Addresses: newTestAddrs("192.0.2.5", ""),
},
}, nil),
subnetsPager: newSubnetPager([]subnets.Subnet{{CIDR: "192.0.2.0/24"}}, nil),
},
wantErr: true,
},
"server without parseable addresses skipped": {
imds: &stubIMDSClient{uidResult: "uid"},
api: &stubServersClient{
serversPager: newSeverPager([]servers.Server{
{
Name: "name1",
ID: "id1",
Tags: &[]string{"constellation-role-control-plane", "constellation-uid-7777"},
Addresses: map[string]any{"foo": "bar"},
},
}, nil),
subnetsPager: newSubnetPager([]subnets.Subnet{{CIDR: "192.0.2.0/24"}}, nil),
},
wantErr: true,
},
"server addresses contains in": {
imds: &stubIMDSClient{uidResult: "uid"},
api: &stubServersClient{
serversPager: newSeverPager([]servers.Server{
{
Name: "name1",
ID: "id1",
Tags: &[]string{"constellation-role-control-plane", "constellation-uid-7777"},
Addresses: newTestAddrs("invalidIP", ""),
},
}, nil),
subnetsPager: newSubnetPager([]subnets.Subnet{{CIDR: "192.0.2.0/24"}}, nil),
},
wantErr: true,
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
assert := assert.New(t)
c := &Cloud{imds: tc.imds, api: tc.api}
got, err := c.List(context.Background())
if tc.wantErr {
assert.Error(err)
} else {
assert.NoError(err)
assert.Equal(tc.want, got)
}
})
}
}
func TestUID(t *testing.T) {
testCases := map[string]struct {
imds *stubIMDSClient
want string
wantErr bool
}{
"error returned from IMDS client": {
imds: &stubIMDSClient{uidErr: errors.New("failed")},
wantErr: true,
},
"UID returned from IMDS client": {
imds: &stubIMDSClient{uidResult: "uid"},
want: "uid",
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
assert := assert.New(t)
c := &Cloud{imds: tc.imds}
got, err := c.UID(context.Background())
if tc.wantErr {
assert.Error(err)
} else {
assert.NoError(err)
assert.Equal(tc.want, got)
}
})
}
}
func TestInitSecretHash(t *testing.T) {
testCases := map[string]struct {
imds *stubIMDSClient
want []byte
wantErr bool
}{
"error returned from IMDS client": {
imds: &stubIMDSClient{initSecretHashErr: errors.New("failed")},
wantErr: true,
},
"initSecretHash returned from IMDS client": {
imds: &stubIMDSClient{initSecretHashResult: "initSecretHash"},
want: []byte("initSecretHash"),
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
assert := assert.New(t)
c := &Cloud{imds: tc.imds}
got, err := c.InitSecretHash(context.Background())
if tc.wantErr {
assert.Error(err)
} else {
assert.NoError(err)
assert.Equal(tc.want, got)
}
})
}
}
func TestGetLoadBalancerEndpoint(t *testing.T) {
// newTestAddrs returns a set of raw server addresses as we would get from
// a ListServers call and as expected by the parseSeverAddresses function.
// The hardcoded addresses don't match what we are looking for. A valid
// address can be injected. You can pass a second valid address to test
// that the first valid one is chosen.
newTestAddrs := func(floatingIP1, floatingIP2 string, fixedIP1 string) map[string]any {
return map[string]any{
"network1": []any{
map[string]any{
"addr": "192.0.2.2",
"version": 4,
"OS-EXT-IPS:type": "floating",
"OS-EXT-IPS-MAC:mac_addr": "fa:16:3e:0c:0c:0c",
},
},
"network2": []any{
map[string]any{
"addr": fixedIP1,
"version": 4,
"OS-EXT-IPS:type": "fixed",
"OS-EXT-IPS-MAC:mac_addr": "fa:16:3e:0c:0c:0c",
},
map[string]any{
"addr": "2001:db8:3333:4444:5555:6666:7777:8888",
"version": 6,
"OS-EXT-IPS:type": "floating",
"OS-EXT-IPS-MAC:mac_addr": "fa:16:3e:0c:0c:0c",
},
map[string]any{
"addr": floatingIP1,
"version": 4,
"OS-EXT-IPS:type": "floating",
"OS-EXT-IPS-MAC:mac_addr": "fa:16:3e:0c:0c:0c",
},
map[string]any{
"addr": floatingIP2,
"version": 4,
"OS-EXT-IPS:type": "floating",
"OS-EXT-IPS-MAC:mac_addr": "fa:16:3e:0c:0c:0c",
},
},
}
}
testCases := map[string]struct {
imds *stubIMDSClient
api *stubServersClient
want string
wantErr bool
}{
"error returned from IMDS client": {
imds: &stubIMDSClient{uidErr: errors.New("failed")},
wantErr: true,
},
"error returned from getSubnetCIDR": {
imds: &stubIMDSClient{},
api: &stubServersClient{
subnetsPager: newSubnetPager(nil, errors.New("failed")),
},
wantErr: true,
},
"error returned from getServers": {
imds: &stubIMDSClient{},
api: &stubServersClient{
subnetsPager: newSubnetPager([]subnets.Subnet{{CIDR: "192.0.2.0/24"}}, nil),
serversPager: newSeverPager(nil, errors.New("failed")),
},
wantErr: true,
},
"sever with empty name skipped": {
imds: &stubIMDSClient{},
api: &stubServersClient{
subnetsPager: newSubnetPager([]subnets.Subnet{{CIDR: "192.0.2.0/24"}}, nil),
serversPager: newSeverPager([]servers.Server{
{
ID: "id1",
Tags: &[]string{"constellation-role-control-plane", "constellation-uid-7777"},
Addresses: newTestAddrs("198.51.100.0", "", "192.0.2.1"),
},
}, nil),
},
wantErr: true,
},
"server with empty ID skipped": {
imds: &stubIMDSClient{},
api: &stubServersClient{
subnetsPager: newSubnetPager([]subnets.Subnet{{CIDR: "192.0.2.0/24"}}, nil),
serversPager: newSeverPager([]servers.Server{
{
Name: "name1",
Tags: &[]string{"constellation-role-control-plane", "constellation-uid-7777"},
Addresses: newTestAddrs("198.51.100.0", "", "192.0.2.1"),
},
}, nil),
},
wantErr: true,
},
"sever with nil tags skipped": {
imds: &stubIMDSClient{},
api: &stubServersClient{
subnetsPager: newSubnetPager([]subnets.Subnet{{CIDR: "192.0.2.0/24"}}, nil),
serversPager: newSeverPager([]servers.Server{
{
Name: "name1",
ID: "id1",
Addresses: newTestAddrs("198.51.100.0", "", "192.0.2.1"),
},
}, nil),
},
wantErr: true,
},
"server has invalid address": {
imds: &stubIMDSClient{},
api: &stubServersClient{
subnetsPager: newSubnetPager([]subnets.Subnet{{CIDR: "192.0.2.0/24"}}, nil),
serversPager: newSeverPager([]servers.Server{
{
Name: "name1",
ID: "id1",
Tags: &[]string{"constellation-role-control-plane", "constellation-uid-7777"},
Addresses: newTestAddrs("", "", "invalidIP"),
},
}, nil),
},
wantErr: true,
},
"server without parseable addresses skipped": {
imds: &stubIMDSClient{},
api: &stubServersClient{
subnetsPager: newSubnetPager([]subnets.Subnet{{CIDR: "192.0.2.0/24"}}, nil),
serversPager: newSeverPager([]servers.Server{
{
Name: "name1",
ID: "id1",
Tags: &[]string{"constellation-role-control-plane", "constellation-uid-7777"},
Addresses: map[string]any{
"somekey": "invalid",
},
},
}, nil),
},
wantErr: true,
},
"invalid endpoint returned from server addresses": {
imds: &stubIMDSClient{},
api: &stubServersClient{
subnetsPager: newSubnetPager([]subnets.Subnet{{CIDR: "192.0.2.0/24"}}, nil),
serversPager: newSeverPager([]servers.Server{
{
Name: "name1",
ID: "id1",
Tags: &[]string{"constellation-role-control-plane", "constellation-uid-7777"},
Addresses: newTestAddrs("invalidIP", "", "192.0.2.1"),
},
}, nil),
},
wantErr: true,
},
"valid endpoint returned from server addresses not in subnet CIDR": {
imds: &stubIMDSClient{},
api: &stubServersClient{
subnetsPager: newSubnetPager([]subnets.Subnet{{CIDR: "192.0.2.0/24"}}, nil),
serversPager: newSeverPager([]servers.Server{
{
Name: "name1",
ID: "id1",
Tags: &[]string{"constellation-role-control-plane", "constellation-uid-7777"},
Addresses: newTestAddrs("198.51.100.0", "", "192.0.2.1"),
},
}, nil),
},
want: "198.51.100.0",
},
"first valid endpoint returned from server addresses not in subnet CIDR": {
imds: &stubIMDSClient{},
api: &stubServersClient{
subnetsPager: newSubnetPager([]subnets.Subnet{{CIDR: "192.0.2.0/24"}}, nil),
serversPager: newSeverPager([]servers.Server{
{
Name: "name1",
ID: "id1",
Tags: &[]string{"constellation-role-control-plane", "constellation-uid-7777"},
Addresses: newTestAddrs("198.51.100.0", "198.51.100.1", "192.0.2.1"),
},
}, nil),
},
want: "198.51.100.0",
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
assert := assert.New(t)
c := &Cloud{
imds: tc.imds,
api: tc.api,
}
got, err := c.GetLoadBalancerEndpoint(context.Background())
if tc.wantErr {
assert.Error(err)
} else {
assert.NoError(err)
assert.Equal(tc.want, got)
}
})
}
}
func TestGetNetworkIDs(t *testing.T) {
someErr := fmt.Errorf("failed")
testCases := map[string]struct {
imds imdsAPI
want []string
wantErr bool
}{
"success": {
imds: &stubIMDSClient{
networkIDsResult: []string{"id1", "id2"},
},
want: []string{"id1", "id2"},
},
"fail to get network IDs": {
imds: &stubIMDSClient{
networkIDsErr: someErr,
},
wantErr: true,
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
assert := assert.New(t)
c := &Cloud{imds: tc.imds}
got, err := c.GetNetworkIDs(context.Background())
if tc.wantErr {
assert.Error(err)
} else {
assert.NoError(err)
assert.Equal(tc.want, got)
}
})
}
}
// newSubnetPager returns a subnet pager as we would get from a ListSubnets.
func newSubnetPager(nets []subnets.Subnet, err error) stubPager {
return stubPager{
page: subnets.SubnetPage{
LinkedPageBase: pagination.LinkedPageBase{
PageResult: pagination.PageResult{
Result: gophercloud.Result{
Body: struct {
Subnets []subnets.Subnet `json:"subnets"`
}{nets},
Err: err,
},
},
},
},
}
}
// newSeverPager returns a server pager as we would get from a ListServers.
func newSeverPager(srvs []servers.Server, err error) stubPager {
return stubPager{
page: servers.ServerPage{
LinkedPageBase: pagination.LinkedPageBase{
PageResult: pagination.PageResult{
Result: gophercloud.Result{
Body: struct {
Servers []servers.Server `json:"servers"`
}{srvs},
Err: err,
},
},
},
},
}
}