mirror of
https://github.com/ceph/ceph-csi.git
synced 2025-06-13 10:33:35 +00:00
Addressed using k8s client APIs to fetch secrets
Based on the review comments addressed the following, - Moved away from having to update the pod with volumes when a new Ceph cluster is added for provisioning via the CSI driver - The above now used k8s APIs to fetch secrets - TBD: Need to add a watch mechanisim such that these secrets can be cached and updated when changed - Folded the Cephc configuration and ID/key config map and secrets into a single secret - Provided the ability to read the same config via mapped or created files within the pod Tests: - Ran PV creation/deletion/attach/use using new scheme StorageClass - Ran PV creation/deletion/attach/use using older scheme to ensure nothing is broken - Did not execute snapshot related tests Signed-off-by: ShyamsundarR <srangana@redhat.com>
This commit is contained in:
committed by
mergify[bot]
parent
97f8c4b677
commit
2064e674a4
138
pkg/util/configstore.go
Normal file
138
pkg/util/configstore.go
Normal file
@ -0,0 +1,138 @@
|
||||
/*
|
||||
Copyright 2018 The Ceph-CSI Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package util
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"k8s.io/klog"
|
||||
"path"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// StoreReader interface enables plugging different stores, that contain the
|
||||
// keys and data. (e.g k8s secrets or local files)
|
||||
type StoreReader interface {
|
||||
DataForKey(fsid string, key string) (string, error)
|
||||
}
|
||||
|
||||
/* ConfigKeys contents and format,
|
||||
- csMonitors: MON list, comma separated
|
||||
- csAdminID: adminID, used for provisioning
|
||||
- csUserID: userID, used for publishing
|
||||
- csAdminKey: key, for userID in csProvisionerUser
|
||||
- csUserKey: key, for userID in csPublisherUser
|
||||
- csPools: Pool list, comma separated
|
||||
*/
|
||||
|
||||
// Constants for various ConfigKeys
|
||||
const (
|
||||
csMonitors = "monitors"
|
||||
csAdminID = "adminid"
|
||||
csUserID = "userid"
|
||||
csAdminKey = "adminkey"
|
||||
csUserKey = "userkey"
|
||||
csPools = "pools"
|
||||
)
|
||||
|
||||
// ConfigStore provides various gettors for ConfigKeys
|
||||
type ConfigStore struct {
|
||||
StoreReader
|
||||
}
|
||||
|
||||
// dataForKey returns data from the config store for the provided key
|
||||
func (dc *ConfigStore) dataForKey(fsid string, key string) (string, error) {
|
||||
if dc.StoreReader != nil {
|
||||
return dc.StoreReader.DataForKey(fsid, key)
|
||||
}
|
||||
|
||||
err := errors.New("config store location uninitialized")
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Mons returns a comma separated MON list from the cluster config represented by fsid
|
||||
func (dc *ConfigStore) Mons(fsid string) (string, error) {
|
||||
return dc.dataForKey(fsid, csMonitors)
|
||||
}
|
||||
|
||||
// Pools returns a list of pool names from the cluster config represented by fsid
|
||||
func (dc *ConfigStore) Pools(fsid string) ([]string, error) {
|
||||
content, err := dc.dataForKey(fsid, csPools)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return strings.Split(content, ","), nil
|
||||
}
|
||||
|
||||
// AdminID returns the admin ID from the cluster config represented by fsid
|
||||
func (dc *ConfigStore) AdminID(fsid string) (string, error) {
|
||||
return dc.dataForKey(fsid, csAdminID)
|
||||
}
|
||||
|
||||
// UserID returns the user ID from the cluster config represented by fsid
|
||||
func (dc *ConfigStore) UserID(fsid string) (string, error) {
|
||||
return dc.dataForKey(fsid, csUserID)
|
||||
}
|
||||
|
||||
// CredentialForUser returns the credentials for the requested user ID
|
||||
// from the cluster config represented by fsid
|
||||
func (dc *ConfigStore) CredentialForUser(fsid, userID string) (data string, err error) {
|
||||
var credkey string
|
||||
user, err := dc.AdminID(fsid)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if user == userID {
|
||||
credkey = csAdminKey
|
||||
} else {
|
||||
user, err = dc.UserID(fsid)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if user != userID {
|
||||
err = fmt.Errorf("requested user (%s) not found in cluster configuration of (%s)", userID, fsid)
|
||||
return
|
||||
}
|
||||
|
||||
credkey = csUserKey
|
||||
}
|
||||
|
||||
return dc.dataForKey(fsid, credkey)
|
||||
}
|
||||
|
||||
// NewConfigStore returns a config store based on value of configRoot. If
|
||||
// configRoot is not "k8s_objects" then it is assumed to be a path to a
|
||||
// directory, under which the configuration files can be found
|
||||
func NewConfigStore(configRoot string) (*ConfigStore, error) {
|
||||
if configRoot != "k8s_objects" {
|
||||
klog.Infof("cache-store: using files in path (%s) as config store", configRoot)
|
||||
fc := &FileConfig{}
|
||||
fc.BasePath = path.Clean(configRoot)
|
||||
dc := &ConfigStore{fc}
|
||||
return dc, nil
|
||||
}
|
||||
|
||||
klog.Infof("cache-store: using k8s objects as config store")
|
||||
kc := &K8sConfig{}
|
||||
kc.Client = NewK8sClient()
|
||||
kc.Namespace = GetK8sNamespace()
|
||||
dc := &ConfigStore{kc}
|
||||
return dc, nil
|
||||
}
|
161
pkg/util/configstore_test.go
Normal file
161
pkg/util/configstore_test.go
Normal file
@ -0,0 +1,161 @@
|
||||
/*
|
||||
Copyright 2019 ceph-csi authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// nolint: gocyclo
|
||||
|
||||
package util
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var basePath = "./test_artifacts"
|
||||
var cs *ConfigStore
|
||||
|
||||
func cleanupTestData() {
|
||||
os.RemoveAll(basePath)
|
||||
}
|
||||
|
||||
// nolint: gocyclo
|
||||
func TestConfigStore(t *testing.T) {
|
||||
var err error
|
||||
var data string
|
||||
var content string
|
||||
var testDir string
|
||||
|
||||
defer cleanupTestData()
|
||||
|
||||
cs, err = NewConfigStore(basePath)
|
||||
if err != nil {
|
||||
t.Errorf("Fatal, failed to get a new config store")
|
||||
}
|
||||
|
||||
err = os.MkdirAll(basePath, 0700)
|
||||
if err != nil {
|
||||
t.Errorf("Test setup error %s", err)
|
||||
}
|
||||
|
||||
// TEST: Should fail as fsid directory is missing
|
||||
_, err = cs.Mons("testfsid")
|
||||
if err == nil {
|
||||
t.Errorf("Failed: expected error due to missing parent directory")
|
||||
}
|
||||
|
||||
testDir = basePath + "/" + "ceph-cluster-testfsid"
|
||||
err = os.MkdirAll(testDir, 0700)
|
||||
if err != nil {
|
||||
t.Errorf("Test setup error %s", err)
|
||||
}
|
||||
|
||||
// TEST: Should fail as mons file is missing
|
||||
_, err = cs.Mons("testfsid")
|
||||
if err == nil {
|
||||
t.Errorf("Failed: expected error due to missing mons file")
|
||||
}
|
||||
|
||||
data = ""
|
||||
err = ioutil.WriteFile(testDir+"/"+csMonitors, []byte(data), 0644)
|
||||
if err != nil {
|
||||
t.Errorf("Test setup error %s", err)
|
||||
}
|
||||
|
||||
// TEST: Should fail as MONs is an empty string
|
||||
content, err = cs.Mons("testfsid")
|
||||
if err == nil {
|
||||
t.Errorf("Failed: want (%s), got (%s)", data, content)
|
||||
}
|
||||
|
||||
data = "mon1,mon2,mon3"
|
||||
err = ioutil.WriteFile(testDir+"/"+csMonitors, []byte(data), 0644)
|
||||
if err != nil {
|
||||
t.Errorf("Test setup error %s", err)
|
||||
}
|
||||
|
||||
// TEST: Fetching MONs should succeed
|
||||
content, err = cs.Mons("testfsid")
|
||||
if err != nil || content != data {
|
||||
t.Errorf("Failed: want (%s), got (%s), err (%s)", data, content, err)
|
||||
}
|
||||
|
||||
data = "pool1,pool2"
|
||||
err = ioutil.WriteFile(testDir+"/"+csPools, []byte(data), 0644)
|
||||
if err != nil {
|
||||
t.Errorf("Test setup error %s", err)
|
||||
}
|
||||
|
||||
// TEST: Fetching MONs should succeed
|
||||
listContent, err := cs.Pools("testfsid")
|
||||
if err != nil || strings.Join(listContent, ",") != data {
|
||||
t.Errorf("Failed: want (%s), got (%s), err (%s)", data, content, err)
|
||||
}
|
||||
|
||||
data = "provuser"
|
||||
err = ioutil.WriteFile(testDir+"/"+csAdminID, []byte(data), 0644)
|
||||
if err != nil {
|
||||
t.Errorf("Test setup error %s", err)
|
||||
}
|
||||
|
||||
// TEST: Fetching provuser should succeed
|
||||
content, err = cs.AdminID("testfsid")
|
||||
if err != nil || content != data {
|
||||
t.Errorf("Failed: want (%s), got (%s), err (%s)", data, content, err)
|
||||
}
|
||||
|
||||
data = "pubuser"
|
||||
err = ioutil.WriteFile(testDir+"/"+csUserID, []byte(data), 0644)
|
||||
if err != nil {
|
||||
t.Errorf("Test setup error %s", err)
|
||||
}
|
||||
|
||||
// TEST: Fetching pubuser should succeed
|
||||
content, err = cs.UserID("testfsid")
|
||||
if err != nil || content != data {
|
||||
t.Errorf("Failed: want (%s), got (%s), err (%s)", data, content, err)
|
||||
}
|
||||
|
||||
data = "provkey"
|
||||
err = ioutil.WriteFile(testDir+"/"+csAdminKey, []byte(data), 0644)
|
||||
if err != nil {
|
||||
t.Errorf("Test setup error %s", err)
|
||||
}
|
||||
|
||||
// TEST: Fetching provkey should succeed
|
||||
content, err = cs.CredentialForUser("testfsid", "provuser")
|
||||
if err != nil || content != data {
|
||||
t.Errorf("Failed: want (%s), got (%s), err (%s)", data, content, err)
|
||||
}
|
||||
|
||||
data = "pubkey"
|
||||
err = ioutil.WriteFile(testDir+"/"+csUserKey, []byte(data), 0644)
|
||||
if err != nil {
|
||||
t.Errorf("Test setup error %s", err)
|
||||
}
|
||||
|
||||
// TEST: Fetching pubkey should succeed
|
||||
content, err = cs.CredentialForUser("testfsid", "pubuser")
|
||||
if err != nil || content != data {
|
||||
t.Errorf("Failed: want (%s), got (%s), err (%s)", data, content, err)
|
||||
}
|
||||
|
||||
// TEST: Fetching random user key should fail
|
||||
_, err = cs.CredentialForUser("testfsid", "random")
|
||||
if err == nil {
|
||||
t.Errorf("Failed: Expected to fail fetching random user key")
|
||||
}
|
||||
}
|
@ -17,241 +17,42 @@ limitations under the License.
|
||||
package util
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"k8s.io/klog"
|
||||
"path"
|
||||
)
|
||||
|
||||
/* FileConfig processes config information stored in files, mostly mapped into
|
||||
the runtime container.
|
||||
/*
|
||||
FileConfig is a ConfigStore interface implementation that reads configuration
|
||||
information from files.
|
||||
|
||||
The calls explicitly do not cache any information, to ensure that updated
|
||||
configuration is always read from the files (for example when these are
|
||||
mapped in as k8s config maps or secrets).
|
||||
BasePath defines the directory under which FileConfig will attempt to open and
|
||||
read contents of various Ceph cluster configurations.
|
||||
|
||||
The BasePath is the path where config files are found, and config files are
|
||||
expected to be named in the following manner,
|
||||
- BasePath/ceph-cluster-<cluster-fsid>/cluster-config
|
||||
- BasePath/ceph-cluster-<cluster-fsid>-provisioner-secret/credentials
|
||||
- BasePath/ceph-cluster-<cluster-fsid>-provisioner-secret/subjectid
|
||||
- BasePath/ceph-cluster-<cluster-fsid>-publish-secret/credentials
|
||||
- BasePath/ceph-cluster-<cluster-fsid>-publish-secret/subjectid
|
||||
Where,
|
||||
- cluster-fsid is the Ceph cluster fsid in UUID ascii notation
|
||||
- The cluster-fsid corresponds to the cluster for which the
|
||||
configuration information is present in the mentioned files
|
||||
- cluster-config is expected to be a JSON blob with the following
|
||||
structure,
|
||||
{
|
||||
"version": 1,
|
||||
"cluster-config": {
|
||||
"cluster-fsid": "<ceph-fsid>",
|
||||
"monitors": [
|
||||
"IP/DNS:port",
|
||||
"IP/DNS:port"
|
||||
],
|
||||
"pools": [
|
||||
"<pool-name>",
|
||||
"<pool-name>"
|
||||
]
|
||||
}
|
||||
}
|
||||
- credentials is expected to contain Base64 encoded credentials for the
|
||||
user encoded in subjectid
|
||||
- subjectid is the username/subject to use with calls to Ceph, and is
|
||||
also Base64 encoded
|
||||
- Provisioner secret contains secrets to use by the provisioning system
|
||||
- Publish secret contains secrets to use by the publishing/staging
|
||||
system
|
||||
Each Ceph cluster configuration is stored under a directory named,
|
||||
BasePath/ceph-cluster-<fsid>, where <fsid> is the Ceph cluster fsid.
|
||||
|
||||
Under each Ceph cluster configuration directory, individual files named as per
|
||||
the ConfigKeys constants in the ConfigStore interface, store the required
|
||||
configuration information.
|
||||
*/
|
||||
|
||||
// FileConfig type with basepath that points to source of all config files
|
||||
type FileConfig struct {
|
||||
BasePath string
|
||||
BasePath string
|
||||
}
|
||||
|
||||
// ClusterConfigv1 strongly typed JSON spec for cluster-config above
|
||||
type ClusterConfigv1 struct {
|
||||
ClusterFsID string `json:"cluster-fsid"`
|
||||
Monitors []string `json:"monitors"`
|
||||
Pools []string `json:"pools"`
|
||||
}
|
||||
|
||||
// ClusterConfigJSONv1 strongly typed JSON spec for cluster-config above
|
||||
type ClusterConfigJSONv1 struct {
|
||||
Version int `json:"version"`
|
||||
ClusterConf *ClusterConfigv1 `json:"cluster-config"`
|
||||
}
|
||||
|
||||
// Constants and enum for constructPath operation
|
||||
type pathType int
|
||||
|
||||
const (
|
||||
clusterConfig pathType = 0
|
||||
pubSubject pathType = 1
|
||||
pubCreds pathType = 2
|
||||
provSubject pathType = 3
|
||||
provCreds pathType = 4
|
||||
)
|
||||
|
||||
const (
|
||||
fNamePrefix = "ceph-cluster"
|
||||
fNameSep = "-"
|
||||
fNamePubPrefix = "publish-secret"
|
||||
fNameProvPrefix = "provisioner-secret"
|
||||
fNameCephConfig = "cluster-config"
|
||||
fNamePubSubject = "subjectid"
|
||||
fNameProvSubject = "subjectid"
|
||||
fNamePubCred = "credentials"
|
||||
fNameProvCred = "credentials"
|
||||
)
|
||||
|
||||
// constructPath constructs well defined paths based on the type of config
|
||||
// file that needs to be accessed.
|
||||
func (pType pathType) constructPath(basepath string, fsid string) (filePath string, noerr error) {
|
||||
if fsid == "" || basepath == "" {
|
||||
return "", fmt.Errorf("missing/empty fsid (%s) or basepath (%s) for config files", fsid, basepath)
|
||||
}
|
||||
|
||||
switch pType {
|
||||
case clusterConfig:
|
||||
filePath = basepath + "/" + fNamePrefix + fNameSep + fsid +
|
||||
"/" + fNameCephConfig
|
||||
case pubSubject:
|
||||
filePath = basepath + "/" + fNamePrefix + fNameSep + fsid +
|
||||
fNameSep + fNamePubPrefix + "/" + fNamePubSubject
|
||||
case pubCreds:
|
||||
filePath = basepath + "/" + fNamePrefix + fNameSep + fsid +
|
||||
fNameSep + fNamePubPrefix + "/" + fNamePubCred
|
||||
case provSubject:
|
||||
filePath = basepath + "/" + fNamePrefix + fNameSep + fsid +
|
||||
fNameSep + fNameProvPrefix + "/" + fNameProvSubject
|
||||
case provCreds:
|
||||
filePath = basepath + "/" + fNamePrefix + fNameSep + fsid +
|
||||
fNameSep + fNameProvPrefix + "/" + fNameProvCred
|
||||
default:
|
||||
return "", fmt.Errorf("invalid path type (%d) specified", pType)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// GetMons returns a comma separated MON list, that is read in from the config
|
||||
// files, based on the passed in fsid
|
||||
func (fc *FileConfig) GetMons(fsid string) (string, error) {
|
||||
fPath, err := clusterConfig.constructPath(fc.BasePath, fsid)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// #nosec
|
||||
contentRaw, err := ioutil.ReadFile(fPath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
var cephConfig ClusterConfigJSONv1
|
||||
|
||||
err = json.Unmarshal(contentRaw, &cephConfig)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if cephConfig.ClusterConf.ClusterFsID != fsid {
|
||||
return "", fmt.Errorf("mismatching Ceph cluster fsid (%s) in file, passed in (%s)", cephConfig.ClusterConf.ClusterFsID, fsid)
|
||||
}
|
||||
|
||||
if len(cephConfig.ClusterConf.Monitors) == 0 {
|
||||
return "", fmt.Errorf("monitor list empty in configuration file")
|
||||
}
|
||||
|
||||
return strings.Join(cephConfig.ClusterConf.Monitors, ","), nil
|
||||
}
|
||||
|
||||
// GetProvisionerSubjectID returns the provisioner subject ID from the on-disk
|
||||
// configuration file, based on the passed in fsid
|
||||
func (fc *FileConfig) GetProvisionerSubjectID(fsid string) (string, error) {
|
||||
fPath, err := provSubject.constructPath(fc.BasePath, fsid)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// #nosec
|
||||
contentRaw, err := ioutil.ReadFile(fPath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if string(contentRaw) == "" {
|
||||
return "", fmt.Errorf("missing/empty provisioner subject ID from file (%s)", fPath)
|
||||
}
|
||||
|
||||
return string(contentRaw), nil
|
||||
}
|
||||
|
||||
// GetPublishSubjectID returns the publish subject ID from the on-disk
|
||||
// configuration file, based on the passed in fsid
|
||||
func (fc *FileConfig) GetPublishSubjectID(fsid string) (string, error) {
|
||||
fPath, err := pubSubject.constructPath(fc.BasePath, fsid)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// #nosec
|
||||
contentRaw, err := ioutil.ReadFile(fPath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if string(contentRaw) == "" {
|
||||
return "", fmt.Errorf("missing/empty publish subject ID from file (%s)", fPath)
|
||||
}
|
||||
|
||||
return string(contentRaw), nil
|
||||
}
|
||||
|
||||
// GetCredentialForSubject returns the credentials for the requested subject
|
||||
// from the cluster config for the passed in fsid
|
||||
func (fc *FileConfig) GetCredentialForSubject(fsid, subject string) (string, error) {
|
||||
var fPath string
|
||||
var err error
|
||||
|
||||
tmpSubject, err := fc.GetPublishSubjectID(fsid)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if tmpSubject != subject {
|
||||
tmpSubject, err = fc.GetProvisionerSubjectID(fsid)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if tmpSubject != subject {
|
||||
return "", fmt.Errorf("requested subject did not match stored publish/provisioner subjectID")
|
||||
}
|
||||
|
||||
fPath, err = provCreds.constructPath(fc.BasePath, fsid)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
} else {
|
||||
fPath, err = pubCreds.constructPath(fc.BasePath, fsid)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
// #nosec
|
||||
contentRaw, err := ioutil.ReadFile(fPath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if string(contentRaw) == "" {
|
||||
return "", fmt.Errorf("missing/empty credentials in file (%s)", fPath)
|
||||
}
|
||||
|
||||
return string(contentRaw), nil
|
||||
// DataForKey reads the appropriate config file, named using key, and returns
|
||||
// the contents of the file to the caller
|
||||
func (fc *FileConfig) DataForKey(fsid string, key string) (data string, err error) {
|
||||
pathToKey := path.Join(fc.BasePath, "ceph-cluster-"+fsid, key)
|
||||
// #nosec
|
||||
content, err := ioutil.ReadFile(pathToKey)
|
||||
if err != nil || string(content) == "" {
|
||||
err = fmt.Errorf("error fetching configuration for cluster ID (%s). (%s)", fsid, err)
|
||||
return
|
||||
}
|
||||
|
||||
data = string(content)
|
||||
klog.V(3).Infof("returning data (%s) for key (%s) against cluster (%s)", data, key, fsid)
|
||||
return
|
||||
}
|
||||
|
@ -1,338 +0,0 @@
|
||||
/*
|
||||
Copyright 2019 ceph-csi authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// nolint: gocyclo
|
||||
|
||||
package util
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var testFsid = "dummy-fs-id"
|
||||
var basePath = "./test_artifacts"
|
||||
|
||||
// nolint: gocyclo
|
||||
func TestGetMons(t *testing.T) {
|
||||
var fc FileConfig
|
||||
var err error
|
||||
|
||||
configFileDir := basePath + "/" + fNamePrefix + fNameSep + testFsid
|
||||
defer os.RemoveAll(basePath)
|
||||
|
||||
fc.BasePath = basePath
|
||||
|
||||
// TEST: Empty fsid should error out
|
||||
_, err = fc.GetMons("")
|
||||
if err == nil {
|
||||
t.Errorf("Call passed, expected to fail due to fsid missing!")
|
||||
}
|
||||
|
||||
// TEST: Missing file should error out
|
||||
_, err = fc.GetMons(testFsid)
|
||||
if err == nil {
|
||||
t.Errorf("Call passed, expected to fail due to missing config file!")
|
||||
}
|
||||
|
||||
// TEST: Empty file should error out
|
||||
err = os.MkdirAll(configFileDir, 0700)
|
||||
if err != nil {
|
||||
t.Errorf("Test utility error %s", err)
|
||||
}
|
||||
|
||||
data := []byte{}
|
||||
err = ioutil.WriteFile(configFileDir+"/"+fNameCephConfig, data, 0644)
|
||||
if err != nil {
|
||||
t.Errorf("Test utility error %s", err)
|
||||
}
|
||||
|
||||
_, err = fc.GetMons(testFsid)
|
||||
if err == nil {
|
||||
t.Errorf("Call passed, expected to fail due to missing config file!")
|
||||
}
|
||||
|
||||
/* Tests with bad JSON content should get caught due to strongly typed JSON
|
||||
struct in implementation and are not tested here */
|
||||
|
||||
// TEST: Send JSON with incorrect fsid
|
||||
data = []byte(`
|
||||
{
|
||||
"version": 1,
|
||||
"cluster-config": {
|
||||
"cluster-fsid": "bad_fsid",
|
||||
"monitors": ["IP1:port1","IP2:port2"],
|
||||
"pools": ["pool1","pool2"]
|
||||
}
|
||||
}`)
|
||||
err = ioutil.WriteFile(configFileDir+"/"+fNameCephConfig, data, 0644)
|
||||
if err != nil {
|
||||
t.Errorf("Test utility error %s", err)
|
||||
}
|
||||
|
||||
_, err = fc.GetMons(testFsid)
|
||||
if err == nil {
|
||||
t.Errorf("Expected to fail on bad fsid in JSON")
|
||||
}
|
||||
|
||||
// TEST: Send JSON with empty mon list
|
||||
data = []byte(`
|
||||
{
|
||||
"version": 1,
|
||||
"cluster-config": {
|
||||
"cluster-fsid": "` + testFsid + `",
|
||||
"monitors": [],
|
||||
"pools": ["pool1","pool2"]
|
||||
}
|
||||
}`)
|
||||
err = ioutil.WriteFile(configFileDir+"/"+fNameCephConfig, data, 0644)
|
||||
if err != nil {
|
||||
t.Errorf("Test utility error %s", err)
|
||||
}
|
||||
|
||||
_, err = fc.GetMons(testFsid)
|
||||
if err == nil {
|
||||
t.Errorf("Expected to fail in empty MON list in JSON")
|
||||
}
|
||||
|
||||
// TEST: Check valid return from successful call
|
||||
data = []byte(`
|
||||
{
|
||||
"version": 1,
|
||||
"cluster-config": {
|
||||
"cluster-fsid": "` + testFsid + `",
|
||||
"monitors": ["IP1:port1","IP2:port2"],
|
||||
"pools": ["pool1","pool2"]
|
||||
}
|
||||
}`)
|
||||
err = ioutil.WriteFile(configFileDir+"/"+fNameCephConfig, data, 0644)
|
||||
if err != nil {
|
||||
t.Errorf("Test utility error %s", err)
|
||||
}
|
||||
|
||||
output, err := fc.GetMons(testFsid)
|
||||
if err != nil {
|
||||
t.Errorf("Call failed %s", err)
|
||||
}
|
||||
if output != "IP1:port1,IP2:port2" {
|
||||
t.Errorf("Failed to generate correct output: expected %s, got %s",
|
||||
"IP1:port1,IP2:port2", output)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetProvisionerSubjectID(t *testing.T) {
|
||||
var fc FileConfig
|
||||
var err error
|
||||
|
||||
configFileDir := basePath + "/" + fNamePrefix + fNameSep + testFsid + fNameSep + fNameProvPrefix
|
||||
defer os.RemoveAll(basePath)
|
||||
|
||||
fc.BasePath = basePath
|
||||
|
||||
// TEST: Empty fsid should error out
|
||||
_, err = fc.GetProvisionerSubjectID("")
|
||||
if err == nil {
|
||||
t.Errorf("Call passed, expected to fail due to fsid missing!")
|
||||
}
|
||||
|
||||
// TEST: Missing file should error out
|
||||
_, err = fc.GetProvisionerSubjectID(testFsid)
|
||||
if err == nil {
|
||||
t.Errorf("Call passed, expected to fail due to missing config file!")
|
||||
}
|
||||
|
||||
// TEST: Empty file should error out
|
||||
err = os.MkdirAll(configFileDir, 0700)
|
||||
if err != nil {
|
||||
t.Errorf("Test utility error %s", err)
|
||||
}
|
||||
|
||||
data := []byte{}
|
||||
err = ioutil.WriteFile(configFileDir+"/"+fNameProvSubject, data, 0644)
|
||||
if err != nil {
|
||||
t.Errorf("Test utility error %s", err)
|
||||
}
|
||||
|
||||
_, err = fc.GetProvisionerSubjectID(testFsid)
|
||||
if err == nil {
|
||||
t.Errorf("Call passed, expected to fail due to missing config file!")
|
||||
}
|
||||
|
||||
// TEST: Check valid return from successful call
|
||||
data = []byte("admin")
|
||||
err = ioutil.WriteFile(configFileDir+"/"+fNameProvSubject, data, 0644)
|
||||
if err != nil {
|
||||
t.Errorf("Test utility error %s", err)
|
||||
}
|
||||
|
||||
output, err := fc.GetProvisionerSubjectID(testFsid)
|
||||
if err != nil || output != "admin" {
|
||||
t.Errorf("Failed to get valid subject ID: expected %s, got %s, err %s", "admin", output, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetPublishSubjectID(t *testing.T) {
|
||||
var fc FileConfig
|
||||
var err error
|
||||
|
||||
configFileDir := basePath + "/" + fNamePrefix + fNameSep + testFsid + fNameSep + fNamePubPrefix
|
||||
defer os.RemoveAll(basePath)
|
||||
|
||||
fc.BasePath = basePath
|
||||
|
||||
// TEST: Empty fsid should error out
|
||||
_, err = fc.GetPublishSubjectID("")
|
||||
if err == nil {
|
||||
t.Errorf("Call passed, expected to fail due to fsid missing!")
|
||||
}
|
||||
|
||||
// TEST: Missing file should error out
|
||||
_, err = fc.GetPublishSubjectID(testFsid)
|
||||
if err == nil {
|
||||
t.Errorf("Call passed, expected to fail due to missing config file!")
|
||||
}
|
||||
|
||||
// TEST: Empty file should error out
|
||||
err = os.MkdirAll(configFileDir, 0700)
|
||||
if err != nil {
|
||||
t.Errorf("Test utility error %s", err)
|
||||
}
|
||||
|
||||
data := []byte{}
|
||||
err = ioutil.WriteFile(configFileDir+"/"+fNamePubSubject, data, 0644)
|
||||
if err != nil {
|
||||
t.Errorf("Test utility error %s", err)
|
||||
}
|
||||
|
||||
_, err = fc.GetPublishSubjectID(testFsid)
|
||||
if err == nil {
|
||||
t.Errorf("Call passed, expected to fail due to missing config file!")
|
||||
}
|
||||
|
||||
// TEST: Check valid return from successful call
|
||||
data = []byte("admin")
|
||||
err = ioutil.WriteFile(configFileDir+"/"+fNamePubSubject, data, 0644)
|
||||
if err != nil {
|
||||
t.Errorf("Test utility error %s", err)
|
||||
}
|
||||
|
||||
output, err := fc.GetPublishSubjectID(testFsid)
|
||||
if err != nil || output != "admin" {
|
||||
t.Errorf("Failed to get valid subject ID: expected %s, got %s, err %s", "admin", output, err)
|
||||
}
|
||||
}
|
||||
|
||||
// nolint: gocyclo
|
||||
func TestGetCredentialForSubject(t *testing.T) {
|
||||
var fc FileConfig
|
||||
var err error
|
||||
|
||||
configFileDir := basePath + "/" + fNamePrefix + fNameSep + testFsid + fNameSep + fNamePubPrefix
|
||||
defer os.RemoveAll(basePath)
|
||||
|
||||
fc.BasePath = basePath
|
||||
|
||||
// TEST: Empty fsid should error out
|
||||
_, err = fc.GetCredentialForSubject("", "subject")
|
||||
if err == nil {
|
||||
t.Errorf("Call passed, expected to fail due to fsid missing!")
|
||||
}
|
||||
|
||||
// TEST: Missing file should error out
|
||||
_, err = fc.GetCredentialForSubject(testFsid, "")
|
||||
if err == nil {
|
||||
t.Errorf("Call passed, expected to fail due to missing config file!")
|
||||
}
|
||||
|
||||
// TEST: Empty subject file should error out
|
||||
err = os.MkdirAll(configFileDir, 0700)
|
||||
if err != nil {
|
||||
t.Errorf("Test utility error %s", err)
|
||||
}
|
||||
|
||||
data := []byte{}
|
||||
err = ioutil.WriteFile(configFileDir+"/"+fNamePubSubject, data, 0644)
|
||||
if err != nil {
|
||||
t.Errorf("Test utility error %s", err)
|
||||
}
|
||||
|
||||
_, err = fc.GetCredentialForSubject(testFsid, "adminpub")
|
||||
if err == nil {
|
||||
t.Errorf("Call passed, expected to fail due to empty subject file!")
|
||||
}
|
||||
|
||||
// TEST: Empty subject cred file should error out
|
||||
data = []byte("adminpub")
|
||||
err = ioutil.WriteFile(configFileDir+"/"+fNamePubSubject, data, 0644)
|
||||
if err != nil {
|
||||
t.Errorf("Test utility error %s", err)
|
||||
}
|
||||
data = []byte{}
|
||||
err = ioutil.WriteFile(configFileDir+"/"+fNamePubCred, data, 0644)
|
||||
if err != nil {
|
||||
t.Errorf("Test utility error %s", err)
|
||||
}
|
||||
|
||||
_, err = fc.GetCredentialForSubject(testFsid, "adminpub")
|
||||
if err == nil {
|
||||
t.Errorf("Call passed, expected to fail due to missing cred content!")
|
||||
}
|
||||
|
||||
// TEST: Success fetching pub creds
|
||||
data = []byte("testpwd")
|
||||
err = ioutil.WriteFile(configFileDir+"/"+fNamePubCred, data, 0644)
|
||||
if err != nil {
|
||||
t.Errorf("Test utility error %s", err)
|
||||
}
|
||||
|
||||
output, err := fc.GetCredentialForSubject(testFsid, "adminpub")
|
||||
if err != nil || output != "testpwd" {
|
||||
t.Errorf("Failed to get valid Publish credentials: expected %s, got %s, err %s", "testpwd", output, err)
|
||||
}
|
||||
|
||||
// TEST: Fetch missing prov creds
|
||||
configFileDir = basePath + "/" + fNamePrefix + fNameSep + testFsid + fNameSep + fNameProvPrefix
|
||||
err = os.MkdirAll(configFileDir, 0700)
|
||||
if err != nil {
|
||||
t.Errorf("Test utility error %s", err)
|
||||
}
|
||||
|
||||
data = []byte("adminprov")
|
||||
err = ioutil.WriteFile(configFileDir+"/"+fNameProvSubject, data, 0644)
|
||||
if err != nil {
|
||||
t.Errorf("Test utility error %s", err)
|
||||
}
|
||||
|
||||
fmt.Printf("Starting test")
|
||||
_, err = fc.GetCredentialForSubject(testFsid, "adminprov")
|
||||
if err == nil {
|
||||
t.Errorf("Call passed, expected to fail due to missing cred content!")
|
||||
}
|
||||
|
||||
// TEST: Fetch prov creds successfully
|
||||
data = []byte("testpwd")
|
||||
err = ioutil.WriteFile(configFileDir+"/"+fNameProvCred, data, 0644)
|
||||
if err != nil {
|
||||
t.Errorf("Test utility error %s", err)
|
||||
}
|
||||
|
||||
output, err = fc.GetCredentialForSubject(testFsid, "adminprov")
|
||||
if err != nil || output != "testpwd" {
|
||||
t.Errorf("Call passed, expected to fail due to missing cred content!")
|
||||
}
|
||||
}
|
59
pkg/util/k8sconfig.go
Normal file
59
pkg/util/k8sconfig.go
Normal file
@ -0,0 +1,59 @@
|
||||
/*
|
||||
Copyright 2019 ceph-csi authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package util
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
k8s "k8s.io/client-go/kubernetes"
|
||||
"k8s.io/klog"
|
||||
)
|
||||
|
||||
/*
|
||||
K8sConfig is a ConfigStore interface implementation that reads configuration
|
||||
information from k8s secrets.
|
||||
|
||||
Each Ceph cluster configuration secret is expected to be named,
|
||||
ceph-cluster-<fsid>, where <fsid> is the Ceph cluster fsid.
|
||||
|
||||
The secret is expected to contain keys, as defined by the ConfigKeys constants
|
||||
in the ConfigStore interface.
|
||||
*/
|
||||
type K8sConfig struct {
|
||||
Client *k8s.Clientset
|
||||
Namespace string
|
||||
}
|
||||
|
||||
// DataForKey reads the appropriate k8s secret, named using fsid, and returns
|
||||
// the contents of key within the secret
|
||||
func (kc *K8sConfig) DataForKey(fsid string, key string) (data string, err error) {
|
||||
secret, err := kc.Client.CoreV1().Secrets(kc.Namespace).Get("ceph-cluster-"+fsid, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
err = fmt.Errorf("error fetching configuration for cluster ID (%s). (%s)", fsid, err)
|
||||
return
|
||||
}
|
||||
|
||||
content, ok := secret.Data[key]
|
||||
if !ok {
|
||||
err = fmt.Errorf("missing data for key (%s) in cluster configuration of (%s)", key, fsid)
|
||||
return
|
||||
}
|
||||
|
||||
data = string(content)
|
||||
klog.V(3).Infof("returning data (%s) for key (%s) against cluster (%s)", data, key, fsid)
|
||||
return
|
||||
}
|
Reference in New Issue
Block a user