Skip to content

Commit

Permalink
Append controller domain and get IP for AUTHURL
Browse files Browse the repository at this point in the history
After supporting the management reconfiguration in StarlingX, the
openstack keystone endpoint address may change after updating the
management network.

To avoid the DM cannot access the openstack endpoints after changing
the endpoint address during network reconfiguration, this commit
enables functions:

1. always append controller domain into the authURLs, which can be
used to get the management floating IP address after DNSMask service
enabled.
2. query IP address by domains in the authURLs.

Test plan:
1. Passed - deploy AIOSX with existing deployment configuratin.
2. Passed - update management network with new management subnet in
the AIOSX, verified the DM can keep communicate with the sysinv API
after unlock.
3. Passed - check V2 logs, verified the failure of get one IP address
of a domain will not fail the client creation, verified the wrong
format in the authURL will fail the client creation as expected,
verified the duplicated IP address will be removed in the result.

Signed-off-by: Yuxing Jiang <Yuxing.Jiang@windriver.com>
  • Loading branch information
yjian118 committed Jan 10, 2024
1 parent b35bd6e commit 2c4789e
Show file tree
Hide file tree
Showing 4 changed files with 163 additions and 9 deletions.
2 changes: 0 additions & 2 deletions DEVELOPER.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,8 +107,6 @@ consider adding your user id to the local docker group.
sudo usermod -a -G docker ${USER}
newgrp docker
```
## Install golangci
curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(go env GOPATH)/bin v1.17.1

## Install other dependencies
sudo apt-get install make
Expand Down
34 changes: 33 additions & 1 deletion common/utilities.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/* SPDX-License-Identifier: Apache-2.0 */
/* Copyright(c) 2019 Wind River Systems, Inc. */
/* Copyright(c) 2019-2024 Wind River Systems, Inc. */

package common

Expand Down Expand Up @@ -131,3 +131,35 @@ func RemoveString(slice []string, s string) (result []string) {
}
return
}

// DedupeSlice is a utility function that removes a duplicated element from
// a slice.
// TODO(yuxing): switch to generic comparable after switch to go 1.20 which
// supports comparable.
func DedupeSlice[T string | int](sliceList []T) []T {
dedupeMap := make(map[T]bool)
list := []T{}
for _, item := range sliceList {
if _, value := dedupeMap[item]; !value {
dedupeMap[item] = true
list = append(list, item)
}
}
return list
}

/*
func DedupeSlice[T comparable](sliceList []T) []T {
dedupeMap := make(map[T]bool)
list := []T{}
for _, item := range sliceList {
if _, value := dedupeMap[item]; !value {
dedupeMap[item] = true
list = append(list, item)
}
}
return list
}
*/
47 changes: 46 additions & 1 deletion common/utilities_test.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/* SPDX-License-Identifier: Apache-2.0 */
/* Copyright(c) 2019-2022 Wind River Systems, Inc. */
/* Copyright(c) 2019-2024 Wind River Systems, Inc. */

package common

Expand Down Expand Up @@ -349,4 +349,49 @@ var _ = Describe("Common utils", func() {
})
})
})

Describe("DedupeSlice utility", func() {
Context("with a slice with duplicates", func() {
It("should remove the string duplicates", func() {
stringTests := []struct {
name string
given []string
wantResult []string
}{
{name: "one string duplicate",
given: []string{"foo0", "foo1", "foo1", "foo2", "foo3"},
wantResult: []string{"foo0", "foo1", "foo2", "foo3"},
},
{name: "two string duplicates",
given: []string{"foo0", "foo1", "foo1", "foo2", "foo2"},
wantResult: []string{"foo0", "foo1", "foo2"},
},
}
for _, tt := range stringTests {
gotResult := DedupeSlice(tt.given)
Expect(reflect.DeepEqual(gotResult, tt.wantResult)).To(BeTrue())
}
})
It("should remove the int duplicates", func() {
intTests := []struct {
name string
given []int
wantResult []int
}{
{name: "one int duplicate",
given: []int{101, 202, 303, 404, 303},
wantResult: []int{101, 202, 303, 404},
},
{name: "two int duplicates",
given: []int{101, 101, 202, 303, 404, 101},
wantResult: []int{101, 202, 303, 404},
},
}
for _, tt := range intTests {
gotResult := DedupeSlice(tt.given)
Expect(reflect.DeepEqual(gotResult, tt.wantResult)).To(BeTrue())
}
})
})
})
})
89 changes: 84 additions & 5 deletions controllers/manager/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package manager

import (
"context"
"net"
"net/http"
"net/url"
"os"
Expand All @@ -17,6 +18,7 @@ import (
"github.com/gophercloud/gophercloud/starlingx/inventory/v1/system"
"github.com/gophercloud/gophercloud/starlingx/nfv/v1/systemconfigupdate"
perrors "github.com/pkg/errors"
common "github.com/wind-river/cloud-platform-deployment-manager/common"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/types"
)
Expand Down Expand Up @@ -44,10 +46,11 @@ const (

const (
// Well-known openstack API attribute values for the system API
SystemEndpointName = "sysinv"
SystemEndpointType = "platform"
VimEndpointName = "vim"
VimEndpointType = "nfv"
SystemEndpointName = "sysinv"
SystemEndpointType = "platform"
VimEndpointName = "vim"
VimEndpointType = "nfv"
KeystoneEndpointURL = "http://controller:5000/v3"
)

// Builds the client authentication options from a given secret which should
Expand Down Expand Up @@ -81,6 +84,7 @@ func GetAuthOptionsFromSecret(endpointSecret *v1.Secret) ([]gophercloud.AuthOpti
if authURL == "" {
return nil, NewClientError("OS_AUTH_URL must be provided")
}

if userID == "" && username == "" {
return nil, NewClientError("OS_USERID or OS_USERNAME must be provided")
}
Expand All @@ -93,8 +97,13 @@ func GetAuthOptionsFromSecret(endpointSecret *v1.Secret) ([]gophercloud.AuthOpti
return nil, NewClientError("OS_APPLICATION_CREDENTIAL_SECRET must be provided")
}

parsedAuthURLs, err := parseURLs(authURL)
if err != nil {
return nil, NewClientError("OS_AUTH_URL format error")
}

result := make([]gophercloud.AuthOptions, 0)
for _, entry := range strings.Split(authURL, ",") {
for _, entry := range parsedAuthURLs {
// The authURL may be specified as a comma separated list of URL therefore
// return a list of auth options to the caller.
ao := gophercloud.AuthOptions{
Expand All @@ -117,6 +126,76 @@ func GetAuthOptionsFromSecret(endpointSecret *v1.Secret) ([]gophercloud.AuthOpti
return result, nil
}

func replaceDomainWithIP(originalURL string) (string, error) {
// Parse the URL
parsedURL, err := url.Parse(originalURL)
if err != nil {
return "", err
}

// Resolve the IP address for the domain
ipAddresses, err := net.LookupHost(parsedURL.Hostname())

if err != nil {
// Consider not print errors as the DNS/DNS mask are not configured
// to get the IP address.
log.V(2).Info("unable to find IP address", "Domain", parsedURL.Hostname())
return "", nil
}

// Use the first IP address.
// TODO(yuxing): may want to handle multiple addresses differently.
ipAddress := ipAddresses[0]

// Join IP address with port to ensure wrapping IPv6 addresses with square
// brackets.
parsedHost := net.JoinHostPort(ipAddress, parsedURL.Port())

// Replace the domain with the IP address in the URL.
newURL := strings.Replace(originalURL, parsedURL.Host, parsedHost, 1)

return newURL, nil
}

func parseURLs(authURL string) ([]string, error) {
authURLs := strings.Split(authURL, ",")
if !common.ContainsString(authURLs, KeystoneEndpointURL) {
// add controller by default as it is expected to be used as the domain
// of local clients
authURLs = append(authURLs, KeystoneEndpointURL)
}

parsedURLs := make([]string, 0)
for _, entry := range authURLs {
u, err := url.ParseRequestURI(entry)
if err != nil {
// Not a valid URL
return nil, err
}

log.V(2).Info("Parsing IP of host", "host", u.Hostname())
if net.ParseIP(u.Hostname()) != nil {
// Already IP address format as host name
parsedURLs = append(parsedURLs, entry)
} else {
// Need to search and replace the hostname with IP address
newURL, err := replaceDomainWithIP(entry)
if err != nil {
return nil, err
}
if newURL != "" {
log.V(2).Info("Replaced domain with IP", "URL", entry, "new URL", newURL)
parsedURLs = append(parsedURLs, newURL)
}
}
}

// Remove duplicates
parsedURLs = common.DedupeSlice(parsedURLs)
log.V(2).Info("Parsed AUTH URLS", "URLs", strings.Join(parsedURLs, ","))
return parsedURLs, nil
}

func GetAuthOptionsFromEnv() (gophercloud.AuthOptions, error) {
password := os.Getenv(PasswordKey)
username := os.Getenv(UsernameKey)
Expand Down

0 comments on commit 2c4789e

Please sign in to comment.