mirror of
https://github.com/edgelesssys/constellation.git
synced 2025-09-20 13:04:36 -04:00
openstack: implement api client and metadata list
Signed-off-by: Paul Meyer <49727155+katexochen@users.noreply.github.com>
This commit is contained in:
parent
418f08bf40
commit
acbd70c741
9 changed files with 833 additions and 1 deletions
|
@ -10,19 +10,77 @@ import (
|
|||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"strings"
|
||||
|
||||
"github.com/edgelesssys/constellation/v2/internal/cloud/metadata"
|
||||
"github.com/edgelesssys/constellation/v2/internal/role"
|
||||
"github.com/gophercloud/gophercloud/openstack/compute/v2/servers"
|
||||
"github.com/gophercloud/gophercloud/openstack/networking/v2/subnets"
|
||||
"github.com/gophercloud/utils/openstack/clientconfig"
|
||||
)
|
||||
|
||||
const (
|
||||
roleTagFormat = "constellation-role-%s"
|
||||
microversion = "2.42"
|
||||
)
|
||||
|
||||
// Cloud is the metadata client for OpenStack.
|
||||
type Cloud struct {
|
||||
api serversAPI
|
||||
imds imdsAPI
|
||||
}
|
||||
|
||||
// New creates a new OpenStack metadata client.
|
||||
func New(ctx context.Context) (*Cloud, error) {
|
||||
imds := &imdsClient{client: &http.Client{}}
|
||||
return &Cloud{imds: imds}, nil
|
||||
|
||||
authURL, err := imds.authURL(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("getting auth URL: %w", err)
|
||||
}
|
||||
username, err := imds.username(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("getting token name: %w", err)
|
||||
}
|
||||
password, err := imds.password(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("getting token password: %w", err)
|
||||
}
|
||||
userDomainName, err := imds.userDomainName(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("getting user domain name: %w", err)
|
||||
}
|
||||
|
||||
clientOpts := &clientconfig.ClientOpts{
|
||||
AuthType: clientconfig.AuthV3Password,
|
||||
AuthInfo: &clientconfig.AuthInfo{
|
||||
AuthURL: authURL,
|
||||
UserDomainName: userDomainName,
|
||||
Username: username,
|
||||
Password: password,
|
||||
},
|
||||
}
|
||||
|
||||
serversClient, err := clientconfig.NewServiceClient("compute", clientOpts)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("creating compute client: %w", err)
|
||||
}
|
||||
serversClient.Microversion = microversion
|
||||
|
||||
subnetsClient, err := clientconfig.NewServiceClient("network", clientOpts)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("creating network client: %w", err)
|
||||
}
|
||||
subnetsClient.Microversion = microversion
|
||||
|
||||
return &Cloud{
|
||||
imds: imds,
|
||||
api: &apiClient{
|
||||
servers: serversClient,
|
||||
subnets: subnetsClient,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Self returns the metadata of the current instance.
|
||||
|
@ -51,3 +109,138 @@ func (c *Cloud) Self(ctx context.Context) (metadata.InstanceMetadata, error) {
|
|||
VPCIP: vpcIP,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// List returns the metadata of all instances belonging to the same Constellation cluster.
|
||||
func (c *Cloud) List(ctx context.Context) ([]metadata.InstanceMetadata, error) {
|
||||
uid, err := c.imds.uid(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("getting uid: %w", err)
|
||||
}
|
||||
|
||||
uidTag := fmt.Sprintf("constellation-uid-%s", uid)
|
||||
|
||||
subnet, err := c.getSubnetCIDR(uidTag)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
srvs, err := c.getServers(uidTag)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var result []metadata.InstanceMetadata
|
||||
for _, s := range srvs {
|
||||
if s.Name == "" {
|
||||
continue
|
||||
}
|
||||
if s.ID == "" {
|
||||
continue
|
||||
}
|
||||
if s.Tags == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
var serverRole role.Role
|
||||
for _, t := range *s.Tags {
|
||||
if strings.HasPrefix(t, "constellation-role-") {
|
||||
serverRole = role.FromString(strings.TrimPrefix(t, "constellation-role-"))
|
||||
break
|
||||
}
|
||||
}
|
||||
if serverRole == role.Unknown {
|
||||
continue
|
||||
}
|
||||
|
||||
subnetAddrs, err := parseSeverAddresses(s.Addresses)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parsing server %q addresses: %w", s.Name, err)
|
||||
}
|
||||
|
||||
var vpcIP string
|
||||
// In a best effort approach, we take the first fixed IPv4 address that is in the subnet
|
||||
// belonging to our cluster.
|
||||
for _, serverSubnet := range subnetAddrs {
|
||||
for _, addr := range serverSubnet.Addresses {
|
||||
if addr.Type != fixedIP {
|
||||
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
|
||||
}
|
||||
|
||||
vpcIP = addr.IP
|
||||
break
|
||||
}
|
||||
}
|
||||
if vpcIP == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
im := metadata.InstanceMetadata{
|
||||
Name: s.Name,
|
||||
ProviderID: s.ID,
|
||||
Role: serverRole,
|
||||
VPCIP: vpcIP,
|
||||
}
|
||||
result = append(result, im)
|
||||
}
|
||||
|
||||
if len(result) == 0 {
|
||||
return nil, fmt.Errorf("no instances belonging to this cluster found")
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (c *Cloud) getSubnetCIDR(uidTag string) (netip.Prefix, error) {
|
||||
listSubnetsOpts := subnets.ListOpts{Tags: uidTag}
|
||||
subnetsPage, err := c.api.ListSubnets(listSubnetsOpts).AllPages()
|
||||
if err != nil {
|
||||
return netip.Prefix{}, fmt.Errorf("listing subnets: %w", err)
|
||||
}
|
||||
|
||||
nets, err := subnets.ExtractSubnets(subnetsPage)
|
||||
if err != nil {
|
||||
return netip.Prefix{}, fmt.Errorf("extracting subnets: %w", err)
|
||||
}
|
||||
|
||||
if len(nets) != 1 {
|
||||
return netip.Prefix{}, fmt.Errorf("expected exactly one subnet, got %d", len(nets))
|
||||
}
|
||||
|
||||
cidr, err := netip.ParsePrefix(nets[0].CIDR)
|
||||
if err != nil {
|
||||
return netip.Prefix{}, fmt.Errorf("parsing subnet CIDR: %w", err)
|
||||
}
|
||||
|
||||
return cidr, nil
|
||||
}
|
||||
|
||||
func (c *Cloud) getServers(uidTag string) ([]servers.Server, error) {
|
||||
listServersOpts := servers.ListOpts{Tags: uidTag}
|
||||
serversPage, err := c.api.ListServers(listServersOpts).AllPages()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("listing servers: %w", err)
|
||||
}
|
||||
servers, err := servers.ExtractServers(serversPage)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("extracting servers: %w", err)
|
||||
}
|
||||
|
||||
return servers, nil
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue