Skip to content

Commit

Permalink
Add new OpenStack Swift scaler (#1462)
Browse files Browse the repository at this point in the history
Signed-off-by: Pedro Felipe Dominguite <p.dominguite@sidi.org.br>

* Add Swift scaler unit tests

Signed-off-by: Pedro Felipe Dominguite <p.dominguite@sidi.org.br>

* Change scaler name to openstack-swift

Signed-off-by: Pedro Felipe Dominguite <p.dominguite@sidi.org.br>

* Change scaler name references to openstackSwift

* Add objectPrefix to metricName

Signed-off-by: Pedro Felipe Dominguite <p.dominguite@sidi.org.br>

* Use kedautil for handling http

Signed-off-by: Pedro Felipe Dominguite <p.dominguite@sidi.org.br>

* Add read stream error handler

Signed-off-by: Pedro Felipe Dominguite <p.dominguite@sidi.org.br>

* Update CHANGELOG.md

Signed-off-by: Pedro Felipe Dominguite <p.dominguite@sidi.org.br>
  • Loading branch information
pdominguite authored Jan 8, 2021
1 parent 6d282ae commit 3ff035a
Show file tree
Hide file tree
Showing 5 changed files with 755 additions and 1 deletion.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@
- Can use Pod Identity with Azure Event Hub scaler ([#994](https://github.com/kedacore/keda/issues/994))
- Introducing InfluxDB scaler ([#1239](https://github.com/kedacore/keda/issues/1239))
- Add Redis cluster support for Redis list and Redis streams scalers ([#1437](https://github.com/kedacore/keda/pull/1437))
- Global authentication credentials can be managed using ClusterTriggerAuthentication objects ([#1452](https://github.com/kedacore/keda/pull/1452),[#1486](https://github.com/kedacore/keda/pull/1486))
- Global authentication credentials can be managed using ClusterTriggerAuthentication objects ([#1452](https://github.com/kedacore/keda/pull/1452))
- Introducing OpenStack Swift scaler ([#1342](https://github.com/kedacore/keda/issues/1342))

### Improvements
- Support add ScaledJob's label to its job ([#1311](https://github.com/kedacore/keda/issues/1311))
Expand Down
210 changes: 210 additions & 0 deletions pkg/scalers/openstack/keystone_authentication.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
package openstack

import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"path"
"time"

kedautil "github.com/kedacore/keda/v2/pkg/util"
)

const tokensEndpoint = "/auth/tokens"

// KeystoneAuthMetadata contains all the necessary metadata for Keystone authentication
type KeystoneAuthMetadata struct {
AuthURL string `json:"-"`
AuthToken string `json:"-"`
HTTPClient *http.Client `json:"-"`
Properties *authProps `json:"auth"`
}

type authProps struct {
Identity *identityProps `json:"identity"`
Scope *scopeProps `json:"scope,omitempty"`
}

type identityProps struct {
Methods []string `json:"methods"`
Password *passwordProps `json:"password,omitempty"`
AppCredential *appCredentialProps `json:"application_credential,omitempty"`
}

type passwordProps struct {
User *userProps `json:"user"`
}

type appCredentialProps struct {
ID string `json:"id"`
Secret string `json:"secret"`
}

type scopeProps struct {
Project *projectProps `json:"project"`
}

type userProps struct {
ID string `json:"id"`
Password string `json:"password"`
}

type projectProps struct {
ID string `json:"id"`
}

// GetToken retrieves a token from Keystone
func (authProps *KeystoneAuthMetadata) GetToken() (string, error) {
jsonBody, jsonError := json.Marshal(authProps)

if jsonError != nil {
return "", jsonError
}

body := bytes.NewReader(jsonBody)

tokenURL, err := url.Parse(authProps.AuthURL)

if err != nil {
return "", fmt.Errorf("the authURL is invalid: %s", err.Error())
}

tokenURL.Path = path.Join(tokenURL.Path, tokensEndpoint)

getTokenRequest, getTokenRequestError := http.NewRequest("POST", tokenURL.String(), body)

getTokenRequest.Header.Set("Content-Type", "application/json")

if getTokenRequestError != nil {
return "", getTokenRequestError
}

resp, requestError := authProps.HTTPClient.Do(getTokenRequest)

if requestError != nil {
return "", requestError
}

defer resp.Body.Close()

if resp.StatusCode >= 200 && resp.StatusCode < 300 {
authProps.AuthToken = resp.Header["X-Subject-Token"][0]
return resp.Header["X-Subject-Token"][0], nil
}

errBody, readBodyErr := ioutil.ReadAll(resp.Body)

if readBodyErr != nil {
return "", readBodyErr
}

return "", fmt.Errorf(string(errBody))
}

// IsTokenValid checks if a authentication token is valid
func IsTokenValid(authProps KeystoneAuthMetadata) (bool, error) {
token := authProps.AuthToken

tokenURL, err := url.Parse(authProps.AuthURL)

if err != nil {
return false, fmt.Errorf("the authURL is invalid: %s", err.Error())
}

tokenURL.Path = path.Join(tokenURL.Path, tokensEndpoint)

checkTokenRequest, checkRequestError := http.NewRequest("HEAD", tokenURL.String(), nil)
checkTokenRequest.Header.Set("X-Subject-Token", token)
checkTokenRequest.Header.Set("X-Auth-Token", token)

if checkRequestError != nil {
return false, checkRequestError
}

checkResp, requestError := authProps.HTTPClient.Do(checkTokenRequest)

if requestError != nil {
return false, requestError
}

defer checkResp.Body.Close()

if checkResp.StatusCode >= 400 {
return false, nil
}

return true, nil
}

// NewPasswordAuth creates a struct containing metadata for authentication using password method
func NewPasswordAuth(authURL string, userID string, userPassword string, projectID string, httpTimeout int) (*KeystoneAuthMetadata, error) {
var tokenError error

passAuth := new(KeystoneAuthMetadata)

passAuth.Properties = new(authProps)

passAuth.Properties.Scope = new(scopeProps)
passAuth.Properties.Scope.Project = new(projectProps)

passAuth.Properties.Identity = new(identityProps)
passAuth.Properties.Identity.Password = new(passwordProps)
passAuth.Properties.Identity.Password.User = new(userProps)

url, err := url.Parse(authURL)

if err != nil {
return nil, fmt.Errorf("authURL is invalid: %s", err.Error())
}

url.Path = path.Join(url.Path, "")

passAuth.AuthURL = url.String()

passAuth.HTTPClient = kedautil.CreateHTTPClient(time.Duration(httpTimeout) * time.Second)

passAuth.Properties.Identity.Methods = []string{"password"}
passAuth.Properties.Identity.Password.User.ID = userID
passAuth.Properties.Identity.Password.User.Password = userPassword

passAuth.Properties.Scope.Project.ID = projectID

passAuth.AuthToken, tokenError = passAuth.GetToken()

return passAuth, tokenError
}

// NewAppCredentialsAuth creates a struct containing metadata for authentication using application credentials method
func NewAppCredentialsAuth(authURL string, id string, secret string, httpTimeout int) (*KeystoneAuthMetadata, error) {
var tokenError error

appAuth := new(KeystoneAuthMetadata)

appAuth.Properties = new(authProps)

appAuth.Properties.Identity = new(identityProps)

url, err := url.Parse(authURL)

if err != nil {
return nil, fmt.Errorf("authURL is invalid: %s", err.Error())
}

url.Path = path.Join(url.Path, "")

appAuth.AuthURL = url.String()

appAuth.HTTPClient = kedautil.CreateHTTPClient(time.Duration(httpTimeout) * time.Second)

appAuth.Properties.Identity.AppCredential = new(appCredentialProps)
appAuth.Properties.Identity.Methods = []string{"application_credential"}
appAuth.Properties.Identity.AppCredential.ID = id
appAuth.Properties.Identity.AppCredential.Secret = secret

appAuth.AuthToken, tokenError = appAuth.GetToken()

return appAuth, tokenError
}
Loading

0 comments on commit 3ff035a

Please sign in to comment.