mirror of
https://github.com/edgelesssys/constellation.git
synced 2025-09-29 20:28:47 -04:00
cli: deploy yawol as OpenStack loadbalancer
This commit is contained in:
parent
0ebe6e669d
commit
56635c3993
20 changed files with 398 additions and 41 deletions
|
@ -98,7 +98,7 @@ func (k AccountKey) CloudINI() CloudINI {
|
|||
AuthURL: k.AuthURL,
|
||||
Username: k.Username,
|
||||
Password: k.Password,
|
||||
TenantID: k.ProjectID,
|
||||
ProjectID: k.ProjectID,
|
||||
TenantName: k.ProjectName,
|
||||
UserDomainName: k.UserDomainName,
|
||||
TenantDomainName: k.ProjectDomainName,
|
||||
|
@ -111,20 +111,20 @@ type CloudINI struct {
|
|||
AuthURL string `gcfg:"auth-url" mapstructure:"auth-url" name:"os-authURL" dependsOn:"os-password|os-trustID|os-applicationCredentialSecret|os-clientCertPath"`
|
||||
Username string `name:"os-userName" value:"optional" dependsOn:"os-password"`
|
||||
Password string `name:"os-password" value:"optional" dependsOn:"os-domainID|os-domainName,os-projectID|os-projectName,os-userID|os-userName"`
|
||||
TenantID string `gcfg:"tenant-id" mapstructure:"project-id" name:"os-projectID" value:"optional" dependsOn:"os-password|os-clientCertPath"`
|
||||
ProjectID string `gcfg:"project-id" mapstructure:"project-id" name:"os-projectID" value:"optional" dependsOn:"os-password|os-clientCertPath"`
|
||||
TenantName string `gcfg:"tenant-name" mapstructure:"project-name" name:"os-projectName" value:"optional" dependsOn:"os-password|os-clientCertPath"`
|
||||
UserDomainName string `gcfg:"user-domain-name" mapstructure:"user-domain-name" name:"os-userDomainName" value:"optional"`
|
||||
TenantDomainName string `gcfg:"tenant-domain-name" mapstructure:"project-domain-name" name:"os-projectDomainName" value:"optional"`
|
||||
Region string `name:"os-region"`
|
||||
}
|
||||
|
||||
// String returns the string representation of the CloudINI.
|
||||
func (i CloudINI) String() string {
|
||||
// FullConfiguration returns the string representation of the full CloudINI.
|
||||
func (i CloudINI) FullConfiguration() string {
|
||||
// sanitize parameters to not include newlines
|
||||
authURL := newlineRegexp.ReplaceAllString(i.AuthURL, "")
|
||||
username := newlineRegexp.ReplaceAllString(i.Username, "")
|
||||
password := newlineRegexp.ReplaceAllString(i.Password, "")
|
||||
tenantID := newlineRegexp.ReplaceAllString(i.TenantID, "")
|
||||
tenantID := newlineRegexp.ReplaceAllString(i.ProjectID, "")
|
||||
tenantName := newlineRegexp.ReplaceAllString(i.TenantName, "")
|
||||
userDomainName := newlineRegexp.ReplaceAllString(i.UserDomainName, "")
|
||||
tenantDomainName := newlineRegexp.ReplaceAllString(i.TenantDomainName, "")
|
||||
|
@ -142,4 +142,24 @@ region = %s
|
|||
`, authURL, username, password, tenantID, tenantName, userDomainName, tenantDomainName, region)
|
||||
}
|
||||
|
||||
// YawolConfiguration returns the string representation of the CloudINI subset yawol expects.
|
||||
func (i CloudINI) YawolConfiguration() string {
|
||||
// sanitize parameters to not include newlines
|
||||
authURL := newlineRegexp.ReplaceAllString(i.AuthURL, "")
|
||||
username := newlineRegexp.ReplaceAllString(i.Username, "")
|
||||
password := newlineRegexp.ReplaceAllString(i.Password, "")
|
||||
projectID := newlineRegexp.ReplaceAllString(i.ProjectID, "")
|
||||
userDomainName := newlineRegexp.ReplaceAllString(i.UserDomainName, "")
|
||||
region := newlineRegexp.ReplaceAllString(i.Region, "")
|
||||
|
||||
return fmt.Sprintf(`[Global]
|
||||
auth-url = %s
|
||||
username = %s
|
||||
password = %s
|
||||
project-id = %s
|
||||
domain-name = %s
|
||||
region = %s
|
||||
`, authURL, username, password, projectID, userDomainName, region)
|
||||
}
|
||||
|
||||
var newlineRegexp = regexp.MustCompile(`[\r\n]+`)
|
||||
|
|
|
@ -143,7 +143,7 @@ func TestAccountKeyToCloudINI(t *testing.T) {
|
|||
AuthURL: "auth-url",
|
||||
Username: "username",
|
||||
Password: "password",
|
||||
TenantID: "project-id",
|
||||
ProjectID: "project-id",
|
||||
TenantName: "project-name",
|
||||
UserDomainName: "user-domain-name",
|
||||
TenantDomainName: "project-domain-name",
|
||||
|
@ -151,12 +151,12 @@ func TestAccountKeyToCloudINI(t *testing.T) {
|
|||
}, ini)
|
||||
}
|
||||
|
||||
func TestCloudINIToString(t *testing.T) {
|
||||
func TestFullConfiguration(t *testing.T) {
|
||||
ini := CloudINI{
|
||||
AuthURL: "auth-url",
|
||||
Username: "username",
|
||||
Password: "password",
|
||||
TenantID: "project-id",
|
||||
ProjectID: "project-id",
|
||||
TenantName: "project-name",
|
||||
UserDomainName: "user-domain-name",
|
||||
TenantDomainName: "project-domain-name",
|
||||
|
@ -171,5 +171,26 @@ tenant-name = project-name
|
|||
user-domain-name = user-domain-name
|
||||
tenant-domain-name = project-domain-name
|
||||
region = region-name
|
||||
`, ini.String())
|
||||
`, ini.FullConfiguration())
|
||||
}
|
||||
|
||||
func TestYawolConfiguration(t *testing.T) {
|
||||
ini := CloudINI{
|
||||
AuthURL: "auth-url",
|
||||
Username: "username",
|
||||
Password: "password",
|
||||
ProjectID: "project-id",
|
||||
TenantName: "project-name",
|
||||
UserDomainName: "user-domain-name",
|
||||
TenantDomainName: "project-domain-name",
|
||||
Region: "region-name",
|
||||
}
|
||||
assert.Equal(t, `[Global]
|
||||
auth-url = auth-url
|
||||
username = username
|
||||
password = password
|
||||
project-id = project-id
|
||||
domain-name = user-domain-name
|
||||
region = region-name
|
||||
`, ini.YawolConfiguration())
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@ type imdsAPI interface {
|
|||
initSecretHash(ctx context.Context) (string, error)
|
||||
role(ctx context.Context) (role.Role, error)
|
||||
vpcIP(ctx context.Context) (string, error)
|
||||
networkIDs(ctx context.Context) ([]string, error)
|
||||
}
|
||||
|
||||
type serversAPI interface {
|
||||
|
|
|
@ -30,6 +30,8 @@ type stubIMDSClient struct {
|
|||
roleErr error
|
||||
vpcIPResult string
|
||||
vpcIPErr error
|
||||
networkIDsResult []string
|
||||
networkIDsErr error
|
||||
}
|
||||
|
||||
func (c *stubIMDSClient) providerID(_ context.Context) (string, error) {
|
||||
|
@ -60,6 +62,10 @@ func (c *stubIMDSClient) vpcIP(_ context.Context) (string, error) {
|
|||
return c.vpcIPResult, c.vpcIPErr
|
||||
}
|
||||
|
||||
func (c *stubIMDSClient) networkIDs(_ context.Context) ([]string, error) {
|
||||
return c.networkIDsResult, c.networkIDsErr
|
||||
}
|
||||
|
||||
type stubServersClient struct {
|
||||
serversPager stubPager
|
||||
subnetsPager stubPager
|
||||
|
|
|
@ -22,18 +22,21 @@ import (
|
|||
// documentation of OpenStack Metadata Service: https://docs.openstack.org/nova/rocky/user/metadata-service.html
|
||||
|
||||
const (
|
||||
imdsMetaDataURL = "http://169.254.169.254/openstack/2018-08-27/meta_data.json"
|
||||
ec2ImdsBaseURL = "http://169.254.169.254/1.0/meta-data"
|
||||
maxCacheAge = 12 * time.Hour
|
||||
imdsMetaDataURL = "http://169.254.169.254/openstack/2018-08-27/meta_data.json"
|
||||
imdsNetworkDataURL = "http://169.254.169.254/openstack/2018-08-27/network_data.json"
|
||||
ec2ImdsBaseURL = "http://169.254.169.254/1.0/meta-data"
|
||||
maxCacheAge = 12 * time.Hour
|
||||
)
|
||||
|
||||
type imdsClient struct {
|
||||
client httpClient
|
||||
|
||||
vpcIPCache string
|
||||
vpcIPCacheTime time.Time
|
||||
cache metadataResponse
|
||||
cacheTime time.Time
|
||||
vpcIPCache string
|
||||
vpcIPCacheTime time.Time
|
||||
networkCache networkResponse
|
||||
networkCacheTime time.Time
|
||||
cache metadataResponse
|
||||
cacheTime time.Time
|
||||
}
|
||||
|
||||
// providerID returns the provider ID of the instance the function is called from.
|
||||
|
@ -223,6 +226,30 @@ func (c *imdsClient) vpcIP(ctx context.Context) (string, error) {
|
|||
return c.vpcIPCache, nil
|
||||
}
|
||||
|
||||
func (c *imdsClient) networkIDs(ctx context.Context) ([]string, error) {
|
||||
if c.timeForUpdate(c.networkCacheTime) || len(c.networkCache.Networks) == 0 {
|
||||
resp, err := httpGet(ctx, c.client, imdsNetworkDataURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var networkResp networkResponse
|
||||
if err := json.Unmarshal(resp, &networkResp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.networkCache = networkResp
|
||||
c.networkCacheTime = time.Now()
|
||||
}
|
||||
|
||||
var networkIDs []string
|
||||
for _, network := range c.networkCache.Networks {
|
||||
if network.NetworkID == "" {
|
||||
continue
|
||||
}
|
||||
networkIDs = append(networkIDs, network.NetworkID)
|
||||
}
|
||||
return networkIDs, nil
|
||||
}
|
||||
|
||||
func httpGet(ctx context.Context, c httpClient, url string) ([]byte, error) {
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, http.NoBody)
|
||||
if err != nil {
|
||||
|
@ -254,6 +281,16 @@ type metadataTags struct {
|
|||
Password string `json:"openstack-password,omitempty"`
|
||||
}
|
||||
|
||||
// networkResponse contains networkResponse with only the required values.
|
||||
type networkResponse struct {
|
||||
Networks []metadataNetwork `json:"networks,omitempty"`
|
||||
}
|
||||
|
||||
type metadataNetwork struct {
|
||||
ID string `json:"id,omitempty"`
|
||||
NetworkID string `json:"network_id,omitempty"`
|
||||
}
|
||||
|
||||
type httpClient interface {
|
||||
Do(req *http.Request) (*http.Response, error)
|
||||
}
|
||||
|
|
|
@ -338,6 +338,124 @@ func TestVPCIP(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestNetworkIDs(t *testing.T) {
|
||||
someErr := errors.New("failed")
|
||||
|
||||
testCases := map[string]struct {
|
||||
cache networkResponse
|
||||
cacheTime time.Time
|
||||
client *stubHTTPClient
|
||||
wantResult []string
|
||||
wantCall bool
|
||||
wantErr bool
|
||||
}{
|
||||
"cached": {
|
||||
cache: networkResponse{Networks: []metadataNetwork{
|
||||
{ID: "net0", NetworkID: "0000000-00000-0000-0000-000000000000"},
|
||||
{ID: "net1", NetworkID: "1111111-11111-1111-1111-111111111111"},
|
||||
{ID: "invalid"},
|
||||
}},
|
||||
cacheTime: time.Now(),
|
||||
wantResult: []string{
|
||||
"0000000-00000-0000-0000-000000000000",
|
||||
"1111111-11111-1111-1111-111111111111",
|
||||
},
|
||||
wantCall: false,
|
||||
},
|
||||
"from http": {
|
||||
client: &stubHTTPClient{
|
||||
response: `
|
||||
{
|
||||
"networks": [
|
||||
{
|
||||
"id": "net0",
|
||||
"network_id": "0000000-00000-0000-0000-000000000000"
|
||||
},
|
||||
{
|
||||
"id": "net1",
|
||||
"network_id": "1111111-11111-1111-1111-111111111111"
|
||||
}
|
||||
]
|
||||
}`,
|
||||
},
|
||||
wantResult: []string{
|
||||
"0000000-00000-0000-0000-000000000000",
|
||||
"1111111-11111-1111-1111-111111111111",
|
||||
},
|
||||
wantCall: true,
|
||||
},
|
||||
"cache outdated": {
|
||||
cache: networkResponse{Networks: []metadataNetwork{
|
||||
{ID: "net0", NetworkID: "0000000-00000-0000-0000-000000000000"},
|
||||
}},
|
||||
cacheTime: time.Now().AddDate(0, 0, -1),
|
||||
client: &stubHTTPClient{
|
||||
response: `
|
||||
{
|
||||
"networks": [
|
||||
{
|
||||
"id": "net1",
|
||||
"network_id": "1111111-11111-1111-1111-111111111111"
|
||||
}
|
||||
]
|
||||
}`,
|
||||
},
|
||||
wantResult: []string{"1111111-11111-1111-1111-111111111111"},
|
||||
wantCall: true,
|
||||
},
|
||||
"cache empty": {
|
||||
cacheTime: time.Now(),
|
||||
client: &stubHTTPClient{
|
||||
response: `
|
||||
{
|
||||
"networks": [
|
||||
{
|
||||
"id": "net0",
|
||||
"network_id": "0000000-00000-0000-0000-000000000000"
|
||||
}
|
||||
]
|
||||
}`,
|
||||
},
|
||||
wantResult: []string{"0000000-00000-0000-0000-000000000000"},
|
||||
wantCall: true,
|
||||
},
|
||||
"http error": {
|
||||
client: &stubHTTPClient{err: someErr},
|
||||
wantCall: true,
|
||||
wantErr: true,
|
||||
},
|
||||
"http empty response": {
|
||||
client: &stubHTTPClient{},
|
||||
wantCall: true,
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
imds := &imdsClient{
|
||||
client: tc.client,
|
||||
networkCache: tc.cache,
|
||||
networkCacheTime: tc.cacheTime,
|
||||
}
|
||||
|
||||
result, err := imds.networkIDs(context.Background())
|
||||
|
||||
if tc.wantErr {
|
||||
assert.Error(err)
|
||||
} else {
|
||||
assert.NoError(err)
|
||||
assert.Equal(tc.wantResult, result)
|
||||
if tc.client != nil {
|
||||
assert.Equal(tc.wantCall, tc.client.called)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTimeForUpdate(t *testing.T) {
|
||||
testCases := map[string]struct {
|
||||
cacheTime time.Time
|
||||
|
|
|
@ -297,6 +297,16 @@ func (c *Cloud) GetLoadBalancerEndpoint(ctx context.Context) (string, error) {
|
|||
return "", errors.New("no load balancer endpoint found")
|
||||
}
|
||||
|
||||
// GetNetworkIDs returns the IDs of the networks the current instance is part of.
|
||||
// This method is OpenStack specific.
|
||||
func (c *Cloud) GetNetworkIDs(ctx context.Context) ([]string, error) {
|
||||
networkIDs, err := c.imds.networkIDs(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("retrieving network IDs: %w", err)
|
||||
}
|
||||
return networkIDs, nil
|
||||
}
|
||||
|
||||
func (c *Cloud) getSubnetCIDR(uidTag string) (netip.Prefix, error) {
|
||||
listSubnetsOpts := subnets.ListOpts{Tags: uidTag}
|
||||
subnetsPage, err := c.api.ListSubnets(listSubnetsOpts).AllPages()
|
||||
|
|
|
@ -653,6 +653,46 @@ func TestGetLoadBalancerEndpoint(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
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{
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue