mirror of
https://github.com/edgelesssys/constellation.git
synced 2024-12-26 07:59:37 -05:00
openstack: implement api client UID, InitSecretHash and GetLoadBalancerEndpoint
This commit is contained in:
parent
3e73530b4f
commit
1b2a927b84
@ -8,6 +8,7 @@ package openstack
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
@ -207,6 +208,95 @@ func (c *Cloud) List(ctx context.Context) ([]metadata.InstanceMetadata, error) {
|
|||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UID retrieves the UID of the constellation.
|
||||||
|
func (c *Cloud) UID(ctx context.Context) (string, error) {
|
||||||
|
uid, err := c.imds.uid(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("retrieving instance UID: %w", err)
|
||||||
|
}
|
||||||
|
return uid, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// InitSecretHash retrieves the InitSecretHash of the current instance.
|
||||||
|
func (c *Cloud) InitSecretHash(ctx context.Context) ([]byte, error) {
|
||||||
|
initSecretHash, err := c.imds.initSecretHash(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("retrieving init secret hash: %w", err)
|
||||||
|
}
|
||||||
|
return []byte(initSecretHash), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetLoadBalancerEndpoint returns the endpoint of the load balancer.
|
||||||
|
// For OpenStack, the load balancer is a floating ip attached to
|
||||||
|
// a control plane node.
|
||||||
|
// TODO(malt3): Rewrite to use real load balancer once it is available.
|
||||||
|
func (c *Cloud) GetLoadBalancerEndpoint(ctx context.Context) (string, error) {
|
||||||
|
uid, err := c.imds.uid(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("getting uid: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
uidTag := fmt.Sprintf("constellation-uid-%s", uid)
|
||||||
|
|
||||||
|
subnet, err := c.getSubnetCIDR(uidTag)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
srvs, err := c.getServers(uidTag)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, s := range srvs {
|
||||||
|
if s.Name == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if s.ID == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if s.Tags == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
subnetAddrs, err := parseSeverAddresses(s.Addresses)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("parsing server %q addresses: %w", s.Name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// In a best effort approach, we take the first fixed IPv4 address that is outside the subnet
|
||||||
|
// belonging to our cluster and assume it is the "load balancer" floating ip.
|
||||||
|
for _, serverSubnet := range subnetAddrs {
|
||||||
|
for _, addr := range serverSubnet.Addresses {
|
||||||
|
if addr.Type != floatingIP {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if addr.IPVersion != ipV4 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if addr.IP == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
parsedAddr, err := netip.ParseAddr(addr.IP)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if subnet.Contains(parsedAddr) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
return addr.IP, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", errors.New("no load balancer endpoint found")
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Cloud) getSubnetCIDR(uidTag string) (netip.Prefix, error) {
|
func (c *Cloud) getSubnetCIDR(uidTag string) (netip.Prefix, error) {
|
||||||
listSubnetsOpts := subnets.ListOpts{Tags: uidTag}
|
listSubnetsOpts := subnets.ListOpts{Tags: uidTag}
|
||||||
subnetsPage, err := c.api.ListSubnets(listSubnetsOpts).AllPages()
|
subnetsPage, err := c.api.ListSubnets(listSubnetsOpts).AllPages()
|
||||||
|
@ -8,6 +8,7 @@ package openstack
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
@ -145,42 +146,6 @@ func TestList(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// newSubnetPager returns a subnet pager as we would get from a ListSubnets
|
|
||||||
newSubnetPager := func(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
|
|
||||||
newSeverPager := func(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,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
testCases := map[string]struct {
|
testCases := map[string]struct {
|
||||||
imds imdsAPI
|
imds imdsAPI
|
||||||
api serversAPI
|
api serversAPI
|
||||||
@ -407,3 +372,319 @@ func TestList(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user