Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds ruxitagentproc.conf update mechanism for CSI driver #366

Merged
merged 15 commits into from
Nov 22, 2021
2 changes: 1 addition & 1 deletion controllers/csi/metadata/fakes.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,8 @@ func (f *FakeFailDB) InsertDynakube(tenant *Dynakube) error { retur
func (f *FakeFailDB) UpdateDynakube(tenant *Dynakube) error { return sql.ErrTxDone }
func (f *FakeFailDB) DeleteDynakube(dynakubeName string) error { return sql.ErrTxDone }
func (f *FakeFailDB) GetDynakube(dynakubeName string) (*Dynakube, error) { return nil, sql.ErrTxDone }
func (f *FakeFailDB) GetDynakubes() (map[string]string, error) { return nil, sql.ErrTxDone }

func (f *FakeFailDB) GetDynakubes() (map[string]string, error) { return nil, sql.ErrTxDone }
func (f *FakeFailDB) InsertVolume(volume *Volume) error { return sql.ErrTxDone }
func (f *FakeFailDB) DeleteVolume(volumeID string) error { return sql.ErrTxDone }
func (f *FakeFailDB) GetVolume(volumeID string) (*Volume, error) { return nil, sql.ErrTxDone }
Expand Down
12 changes: 12 additions & 0 deletions controllers/csi/metadata/path_resolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,18 @@ func (pr PathResolver) AgentBinaryDir(tenantUUID string) string {
return filepath.Join(pr.EnvDir(tenantUUID), dtcsi.AgentBinaryDir)
}

func (pr PathResolver) AgentProcessModuleConfigForVersion(tenantUUID string, version string) string {
return filepath.Join(pr.AgentBinaryDirForVersion(tenantUUID, version), "agent", "conf", "ruxitagentproc.conf")
}

func (pr PathResolver) SourceAgentProcessModuleConfigForVersion(tenantUUID string, version string) string {
return filepath.Join(pr.AgentBinaryDirForVersion(tenantUUID, version), "agent", "conf", "_ruxitagentproc.conf")
}

func (pr PathResolver) AgentRuxitProcResponseCache(tenantUUID string) string {
return filepath.Join(pr.EnvDir(tenantUUID), "revision.json")
}

func (pr PathResolver) AgentBinaryDirForVersion(tenantUUID string, version string) string {
return filepath.Join(pr.AgentBinaryDir(tenantUUID), version)
}
Expand Down
2 changes: 0 additions & 2 deletions controllers/csi/metadata/sqlite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,13 +61,11 @@ func TestCreateTables(t *testing.T) {
row = db.conn.QueryRow("SELECT name FROM sqlite_master WHERE type='table' AND name=?;", dynakubesTableName)
row.Scan(&dkTable)
assert.Equal(t, dkTable, dynakubesTableName)

}

func TestInsertDynakube(t *testing.T) {
db := FakeMemoryDB()

// Insert
err := db.InsertDynakube(&testDynakube1)
assert.Nil(t, err)
row := db.conn.QueryRow(fmt.Sprintf("SELECT * FROM %s WHERE TenantUUID = ?;", dynakubesTableName), testDynakube1.TenantUUID)
Expand Down
13 changes: 12 additions & 1 deletion controllers/csi/provisioner/agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ func newInstallAgentConfig(
}
}

func (installAgentCfg *installAgentConfig) updateAgent(version, tenantUUID string) (string, error) {
func (installAgentCfg *installAgentConfig) updateAgent(version, tenantUUID string, previousRevision uint, latestProcessModuleConfig *dtclient.ProcessModuleConfig) (string, error) {
dk := installAgentCfg.dk
logger := installAgentCfg.logger
currentVersion := installAgentCfg.getOneAgentVersionFromInstance()
Expand All @@ -63,12 +63,23 @@ func (installAgentCfg *installAgentConfig) updateAgent(version, tenantUUID strin
"Failed to install agent version: %s to tenant: %s, err: %s", currentVersion, tenantUUID, err)
return "", err
}
installAgentCfg.logger.Info("updating ruxitagentproc.conf on new version")
if err := installAgentCfg.updateProcessModuleConfig(currentVersion, tenantUUID, latestProcessModuleConfig); err != nil {
return "", err
}
installAgentCfg.recorder.Eventf(dk,
corev1.EventTypeNormal,
installAgentVersionEvent,
"Installed agent version: %s to tenant: %s", currentVersion, tenantUUID)
return currentVersion, nil
}
if previousRevision != latestProcessModuleConfig.Revision {
installAgentCfg.logger.Info("updating ruxitagentproc.conf on installed version")
if err := installAgentCfg.updateProcessModuleConfig(currentVersion, tenantUUID, latestProcessModuleConfig); err != nil {
return "", err
}
}

return "", nil
}

Expand Down
116 changes: 116 additions & 0 deletions controllers/csi/provisioner/processmoduleconfig.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
package csiprovisioner

import (
"encoding/json"
"io"
"io/ioutil"
"os"

"github.com/Dynatrace/dynatrace-operator/dtclient"
"github.com/Dynatrace/dynatrace-operator/processmoduleconfig"
)

// getProcessModuleConfig gets the latest `RuxitProcResponse`, it can come from the tenant if we don't have the latest revision saved locally,
// otherwise we use the locally cached response
func (r *OneAgentProvisioner) getProcessModuleConfig(dtc dtclient.Client, tenantUUID string) (*dtclient.ProcessModuleConfig, uint, error) {
var storedRevision uint
storedProcessModuleConfig, err := r.readProcessModuleConfigCache(tenantUUID)
if os.IsNotExist(err) {
latestProcessModuleConfig, err := dtc.GetProcessModuleConfig(storedRevision)
if err != nil {
return nil, storedRevision, err
}
return latestProcessModuleConfig, storedRevision, nil
} else if err != nil {
return nil, storedRevision, err
}
storedRevision = storedProcessModuleConfig.Revision
latestProcessModuleConfig, err := dtc.GetProcessModuleConfig(storedProcessModuleConfig.Revision)
if err != nil {
return nil, storedRevision, err
}
if latestProcessModuleConfig != nil {
return latestProcessModuleConfig, storedRevision, nil
}
return storedProcessModuleConfig, storedRevision, nil
}

func (r *OneAgentProvisioner) readProcessModuleConfigCache(tenantUUID string) (*dtclient.ProcessModuleConfig, error) {
var processModuleConfig dtclient.ProcessModuleConfig
processModuleConfigCache, err := r.fs.Open(r.path.AgentRuxitProcResponseCache(tenantUUID))
if err != nil {
return nil, err
}
jsonBytes, err := ioutil.ReadAll(processModuleConfigCache)
processModuleConfigCache.Close()
if err != nil {
return nil, err
}
if err = json.Unmarshal(jsonBytes, &processModuleConfig); err != nil {
return nil, err
}

return &processModuleConfig, nil
}

func (r *OneAgentProvisioner) writeProcessModuleConfigCache(tenantUUID string, processModuleConfig *dtclient.ProcessModuleConfig) error {
processModuleConfigCache, err := r.fs.OpenFile(r.path.AgentRuxitProcResponseCache(tenantUUID), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
if err != nil {
return err
}
jsonBytes, err := json.Marshal(processModuleConfig)
if err != nil {
processModuleConfigCache.Close()
return err
}
_, err = processModuleConfigCache.Write(jsonBytes)
processModuleConfigCache.Close()
return err
}

func (installAgentCfg *installAgentConfig) updateProcessModuleConfig(version, tenantUUID string, processModuleConfig *dtclient.ProcessModuleConfig) error {
if processModuleConfig != nil {
installAgentCfg.logger.Info("updating ruxitagentproc.conf", "agentVersion", version, "tenantUUID", tenantUUID)
usedProcessModuleConfigPath := installAgentCfg.path.AgentProcessModuleConfigForVersion(tenantUUID, version)
sourceProcessModuleConfigPath := installAgentCfg.path.SourceAgentProcessModuleConfigForVersion(tenantUUID, version)
if err := installAgentCfg.checkProcessModuleConfigCopy(sourceProcessModuleConfigPath, usedProcessModuleConfigPath); err != nil {
return err
}
return processmoduleconfig.Update(installAgentCfg.fs, sourceProcessModuleConfigPath, usedProcessModuleConfigPath, processModuleConfig.ToMap())
}
installAgentCfg.logger.Info("no changes to ruxitagentproc.conf, skipping update")
return nil
}

// checkProcessModuleConfigCopy checks if we already made a copy of the original ruxitagentproc.conf file.
// After the initial install of a version we copy the ruxitagentproc.conf to _ruxitagentproc.conf and we use the _ruxitagentproc.conf + the api response to re-create the ruxitagentproc.conf
// so its easier to update
func (installAgentCfg *installAgentConfig) checkProcessModuleConfigCopy(sourcePath, destPath string) error {
if _, err := installAgentCfg.fs.Open(sourcePath); os.IsNotExist(err) {
fileInfo, err := installAgentCfg.fs.Stat(destPath)
if err != nil {
return err
}

sourceProcessModuleConfigFile, err := installAgentCfg.fs.OpenFile(sourcePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, fileInfo.Mode())
if err != nil {
return err
}

usedProcessModuleConfigFile, err := installAgentCfg.fs.Open(destPath)
if err != nil {
return err
}
_, err = io.Copy(sourceProcessModuleConfigFile, usedProcessModuleConfigFile)
if err != nil {
sourceProcessModuleConfigFile.Close()
usedProcessModuleConfigFile.Close()
return err
}
if err = sourceProcessModuleConfigFile.Close(); err != nil {
return err
}
return usedProcessModuleConfigFile.Close()
}
return nil
}
183 changes: 183 additions & 0 deletions controllers/csi/provisioner/processmoduleconfig_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
package csiprovisioner

import (
"encoding/json"
"io/ioutil"
"os"
"path/filepath"
"testing"

"github.com/Dynatrace/dynatrace-operator/controllers/csi/metadata"
"github.com/Dynatrace/dynatrace-operator/dtclient"
"github.com/Dynatrace/dynatrace-operator/logger"
"github.com/spf13/afero"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

var (
testTenantUUID = "zib123"
testVersion = "v123"
testRuxitProcResponse = dtclient.ProcessModuleConfig{
Revision: 3,
Properties: []dtclient.ProcessModuleProperty{
{
Section: "test",
Key: "test",
Value: "test3",
},
},
}
testRuxitProcResponseCache = dtclient.ProcessModuleConfig{
Revision: 1,
Properties: []dtclient.ProcessModuleProperty{
{
Section: "test",
Key: "test",
Value: "test1",
},
},
}

testRuxitConf = `
[general]
key value
`
)

func prepTestFsCache(fs afero.Fs) {
testCache, _ := json.Marshal(testRuxitProcResponseCache)
path := metadata.PathResolver{}
cache, _ := fs.OpenFile(path.AgentRuxitProcResponseCache(testTenantUUID), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
cache.Write(testCache)
}

func TestGetRuxitProcResponse(t *testing.T) {
var emptyResponse *dtclient.ProcessModuleConfig
t.Run(`no cache + no revision (dry run)`, func(t *testing.T) {
var defaultRevision uint
memFs := afero.NewMemMapFs()
mockClient := &dtclient.MockDynatraceClient{}
mockClient.On("GetProcessModuleConfig", defaultRevision).
Return(&testRuxitProcResponse, nil)
r := &OneAgentProvisioner{
fs: memFs,
}

response, storedRevision, err := r.getProcessModuleConfig(mockClient, testTenantUUID)

require.Nil(t, err)
assert.Equal(t, testRuxitProcResponse, *response)
assert.Equal(t, defaultRevision, storedRevision)
})
t.Run(`cache + latest revision (cached run)`, func(t *testing.T) {
memFs := afero.NewMemMapFs()
prepTestFsCache(memFs)
mockClient := &dtclient.MockDynatraceClient{}
mockClient.On("GetProcessModuleConfig", testRuxitProcResponseCache.Revision).
Return(emptyResponse, nil)
r := &OneAgentProvisioner{
fs: memFs,
}

response, storedRevision, err := r.getProcessModuleConfig(mockClient, testTenantUUID)

require.Nil(t, err)
assert.Equal(t, testRuxitProcResponseCache, *response)
assert.Equal(t, testRuxitProcResponseCache.Revision, storedRevision)
})
t.Run(`cache + old revision (outdated cache should be ignored)`, func(t *testing.T) {
memFs := afero.NewMemMapFs()
prepTestFsCache(memFs)
mockClient := &dtclient.MockDynatraceClient{}
mockClient.On("GetProcessModuleConfig", testRuxitProcResponseCache.Revision).
Return(&testRuxitProcResponse, nil)
r := &OneAgentProvisioner{
fs: memFs,
}

response, storedRevision, err := r.getProcessModuleConfig(mockClient, testTenantUUID)

require.Nil(t, err)
assert.Equal(t, testRuxitProcResponse, *response)
assert.Equal(t, testRuxitProcResponseCache.Revision, storedRevision)
})
}

func TestReadRuxitCache(t *testing.T) {
memFs := afero.NewMemMapFs()
prepTestFsCache(memFs)
r := &OneAgentProvisioner{
fs: memFs,
}

cache, err := r.readProcessModuleConfigCache(testTenantUUID)
require.Nil(t, err)
assert.Equal(t, testRuxitProcResponseCache, *cache)
}

func TestWriteRuxitCache(t *testing.T) {
memFs := afero.NewMemMapFs()
r := &OneAgentProvisioner{
fs: memFs,
}

err := r.writeProcessModuleConfigCache(testTenantUUID, &testRuxitProcResponseCache)

require.Nil(t, err)
cache, err := r.readProcessModuleConfigCache(testTenantUUID)
require.Nil(t, err)
assert.Equal(t, testRuxitProcResponseCache, *cache)
}

func prepTestConfFs(fs afero.Fs) {
path := metadata.PathResolver{}
fs.MkdirAll(filepath.Base(path.AgentProcessModuleConfigForVersion(testTenantUUID, testVersion)), 0755)
usedConf, _ := fs.OpenFile(path.AgentProcessModuleConfigForVersion(testTenantUUID, testVersion), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
usedConf.WriteString(testRuxitConf)
}

func assertTestConf(t *testing.T, fs afero.Fs, path, expected string) {
file, err := fs.Open(path)
require.Nil(t, err)
content, err := ioutil.ReadAll(file)
require.Nil(t, err)
assert.Equal(t, expected, string(content))
}

func TestUpdateRuxitConf(t *testing.T) {
path := metadata.PathResolver{}
memFs := afero.NewMemMapFs()
prepTestConfFs(memFs)
agentConfig := &installAgentConfig{
fs: memFs,
logger: logger.NewDTLogger(),
}
expectedUsed := `
[general]
key value

[test]
test test3
`

agentConfig.updateProcessModuleConfig(testVersion, testTenantUUID, &testRuxitProcResponse)

assertTestConf(t, memFs, path.AgentProcessModuleConfigForVersion(testTenantUUID, testVersion), expectedUsed)
assertTestConf(t, memFs, path.SourceAgentProcessModuleConfigForVersion(testTenantUUID, testVersion), testRuxitConf)
}

func TestCheckRuxitConfCopy(t *testing.T) {
memFs := afero.NewMemMapFs()
path := metadata.PathResolver{}
prepTestConfFs(memFs)
agentConfig := &installAgentConfig{
fs: memFs,
}
sourcePath := path.SourceAgentProcessModuleConfigForVersion(testTenantUUID, testVersion)
destPath := path.AgentProcessModuleConfigForVersion(testTenantUUID, testVersion)

agentConfig.checkProcessModuleConfigCopy(sourcePath, destPath)

assertTestConf(t, memFs, sourcePath, testRuxitConf)
}
Loading