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

set config command + tests #178

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 32 additions & 1 deletion cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,35 @@ Running
reads the connection and session details needed for connecting to a server from `connection-session-config.yaml` and
sends a config TX.
It creates directories in `local/config` with the respective certificates, a yaml file, named shared_cluster_config.yml, that includes the cluster configuration
and a yaml file, named version.yml, that includes the version.
and a yaml file, named version.yml, that includes the version.



#### Set Config Command
1. Run from 'orion-sdk' root folder.
2. For Set Config Run:
2.1 `bin/bcdbadmin config get [args]`.
2.2 `bin/bcdbadmin config set [args]`.

Replace `[args]` with corresponding flags. The flags for config get are detailed in the table above.

###
##### Flags
| Flags | Description |
|-----------------------------------|--------------------------------------------------------------------------|
| `-d, --db-connection-config-path` | the absolute or relative path of CLI connection configuration file |
| `-c, --cluster-config-path` | the absolute or relative path to the new cluster configuration yaml file |

Both flags are necessary flags. If any flag is missing, the cli will raise an error.

NOTE: the new cluster configuration yaml file should be named as: "new_cluster_config.yml" and should be located in the same directory as the directory given as flag via the GET command.


###
##### Example:

Running
`bin/bcdbadmin config set -d "connection-session-config.yaml" -c "local/new_cluster_config.yml"`
reads the connection and session details needed for connecting to a server from `connection-session-config.yaml` and
sends a config TX.
It reads the `local/new_cluster_config.yml` to fetch the new cluster configuration and set it.
8 changes: 4 additions & 4 deletions cli/commands/cli_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ type cliConfigParams struct {
func (c *cliConfigParams) CreateDbAndOpenSession() error {
var err error
if err = c.cliConfig.ReadAndConstructCliConnConfig(c.cliConfigPath); err != nil {
return errors.Wrapf(err, "failed to read CLI configuration file")
return errors.Wrapf(err, "failed to read CLI connection configuration file")
}

c.db, err = bcdb.Create(&c.cliConfig.ConnectionConfig)
Expand All @@ -43,18 +43,18 @@ func (c *cliConfigParams) CreateDbAndOpenSession() error {
// ReadAndConstructCliConnConfig read unmarshal the yaml config file into a cliConnectionConfig object
func (c *cliConnectionConfig) ReadAndConstructCliConnConfig(filePath string) error {
if filePath == "" {
return errors.New("path to the shared configuration file is empty")
return errors.New("path to the connection configuration file is empty")
}

v := viper.New()
v.SetConfigFile(filePath)

if err := v.ReadInConfig(); err != nil {
return errors.Wrapf(err, "error reading shared config file: %s", filePath)
return errors.Wrapf(err, "error reading connection config file: %s", filePath)
}

if err := v.UnmarshalExact(c); err != nil {
return errors.Wrapf(err, "unable to unmarshal shared config file: '%s' into struct", filePath)
return errors.Wrapf(err, "unable to unmarshal connection config file: '%s' into struct", filePath)
}

clientLogger, err := logger.New(
Expand Down
232 changes: 227 additions & 5 deletions cli/commands/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"github.com/hyperledger-labs/orion-server/pkg/types"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"gopkg.in/yaml.v2"
)

Expand Down Expand Up @@ -50,12 +51,17 @@ func getConfigCmd() *cobra.Command {

func setConfigCmd() *cobra.Command {
setConfigCmd := &cobra.Command{
Use: "set",
Short: "Set cluster configuration",
RunE: func(cmd *cobra.Command, args []string) error {
return errors.New("not implemented yet")
},
Use: "set",
Short: "Set cluster configuration",
Example: "cli config set -d <path-to-connection-and-session-config> -c <path-to-new-cluster-config>",
RunE: setConfig,
}

setConfigCmd.PersistentFlags().StringP("cluster-config-path", "c", "", "set the absolute or relative path of the new server configuration file")
if err := setConfigCmd.MarkPersistentFlagRequired("cluster-config-path"); err != nil {
panic(err.Error())
}

return setConfigCmd
}

Expand Down Expand Up @@ -106,6 +112,75 @@ func getConfig(cmd *cobra.Command, args []string) error {
return nil
}

func setConfig(cmd *cobra.Command, args []string) error {
cliConfigPath, err := cmd.Flags().GetString("db-connection-config-path")
if err != nil {
return errors.Wrapf(err, "failed to fetch the path of CLI connection configuration file")
}

newClusterConfigPath, err := cmd.Flags().GetString("cluster-config-path")
if err != nil {
return errors.Wrapf(err, "failed to fetch the new server configuration path")
}

lastVersion, err := readVersionYaml(path.Join(newClusterConfigPath, "version.yml"))
if err != nil {
return errors.Wrapf(err, "failed to read the version yaml file")
}

params := cliConfigParams{
cliConfigPath: cliConfigPath,
cliConfig: cliConnectionConfig{},
db: nil,
session: nil,
}

err = params.CreateDbAndOpenSession()
if err != nil {
return err
}

tx, err := params.session.ConfigTx()
if err != nil {
return errors.Wrapf(err, "failed to instanciate a config TX")
}
defer abort(tx)

_, version, err := tx.GetClusterConfig()
if err != nil {
return errors.Wrapf(err, "failed to fetch cluster config")
}

// Check that the version in the version.yaml file is the same version as recieved by GetClusterConfig TX
if version != lastVersion {
errors.New("Cluster configuration cannot be updated since the version is not up to date")
}

// Read the new shared configuration and build the new cluster config
newSharedConfig, err := readSharedConfigYaml(path.Join(newClusterConfigPath, "new_cluster_config.yml"))
if err != nil {
return errors.Wrapf(err, "failed to read the new cluster configuration file")
}

newConfig, err := buildClusterConfig(newSharedConfig)
if err != nil {
return errors.Wrapf(err, "failed to build the cluster configuration from the shared configuration")
}

// Set cluster configuration to the new one
err = tx.SetClusterConfig(newConfig)
if err != nil {
return errors.Wrapf(err, "failed to set the cluster config")
}

_, _, err = tx.Commit(true)
if err != nil {
return errors.Wrapf(err, "failed to commit tx")
}

return nil
}

func abort(tx bcdb.TxContext) {
_ = tx.Abort()
}
Expand All @@ -124,6 +199,10 @@ func WriteClusterConfigToYaml(clusterConfig *types.ClusterConfig, version *types
}

err = os.WriteFile(path.Join(configYamlFilePath, "shared_cluster_config.yml"), c, 0644)
if err != nil {
return err
}

err = os.WriteFile(path.Join(configYamlFilePath, "version.yml"), v, 0644)
if err != nil {
return err
Expand Down Expand Up @@ -292,3 +371,146 @@ func buildSharedClusterConfig(clusterConfig *types.ClusterConfig, configYamlFile

return sharedConfiguration
}

// readVersionYaml reads the version from the file and returns it.
func readVersionYaml(versionFile string) (*types.Version, error) {
if versionFile == "" {
return nil, errors.New("path to the shared configuration file is empty")
}

v := viper.New()
v.SetConfigFile(versionFile)

if err := v.ReadInConfig(); err != nil {
return nil, errors.Wrapf(err, "error reading version file: %s", versionFile)
}

version := &types.Version{}
if err := v.UnmarshalExact(version); err != nil {
return nil, errors.Wrapf(err, "unable to unmarshal version file: '%s' into struct", version)
}
return version, nil
}

// readSharedConfigYaml reads the shared config from the file and returns it.
func readSharedConfigYaml(sharedConfigFile string) (*SharedConfiguration, error) {
if sharedConfigFile == "" {
return nil, errors.New("path to the shared configuration file is empty")
}

v := viper.New()
v.SetConfigFile(sharedConfigFile)

if err := v.ReadInConfig(); err != nil {
return nil, errors.Wrapf(err, "error reading shared config file: %s", sharedConfigFile)
}

sharedConf := &SharedConfiguration{}
if err := v.UnmarshalExact(sharedConf); err != nil {
return nil, errors.Wrapf(err, "unable to unmarshal shared config file: '%s' into struct", sharedConfigFile)
}
return sharedConf, nil
}

// buildClusterConfig builds a cluster configuration from a shared configuration
func buildClusterConfig(sharedConfig *SharedConfiguration) (*types.ClusterConfig, error) {
var nodesConfig []*types.NodeConfig
for _, node := range sharedConfig.Nodes {
cert, err := os.ReadFile(node.CertificatePath)
pemBlock, _ := pem.Decode(cert)
cert = pemBlock.Bytes
if err != nil {
return nil, errors.Wrapf(err, "unable read the certificate of node '%s'", node.NodeID)
}
nodeConfig := &types.NodeConfig{
Id: node.NodeID,
Address: node.Host,
Port: node.Port,
Certificate: cert,
}
nodesConfig = append(nodesConfig, nodeConfig)
}

var adminsConfig []*types.Admin
for _, admin := range sharedConfig.Admin {
cert, err := os.ReadFile(admin.CertificatePath)
pemBlock, _ := pem.Decode(cert)
cert = pemBlock.Bytes
if err != nil {
return nil, errors.Wrapf(err, "unable read the certificate of admin '%s'", admin.ID)
}
adminConfig := &types.Admin{
Id: admin.ID,
Certificate: cert,
}
adminsConfig = append(adminsConfig, adminConfig)
}

var rootCACertsConfig [][]byte
for i, rootCACertPath := range sharedConfig.CAConfig.RootCACertsPath {
rootCACertConfig, err := os.ReadFile(rootCACertPath)
pemBlock, _ := pem.Decode(rootCACertConfig)
rootCACertConfig = pemBlock.Bytes
if err != nil {
return nil, errors.Wrapf(err, "unable read the certificate of rootCA '%d'", i)
}
rootCACertsConfig = append(rootCACertsConfig, rootCACertConfig)
}

var intermediateCACertsConfig [][]byte
for i, intermediateCACertPath := range sharedConfig.CAConfig.IntermediateCACertsPath {
intermediateCACertConfig, err := os.ReadFile(intermediateCACertPath)
pemBlock, _ := pem.Decode(intermediateCACertConfig)
intermediateCACertConfig = pemBlock.Bytes
if err != nil {
return nil, errors.Wrapf(err, "unable read the certificate of intermediateCA '%d'", i)
}
intermediateCACertsConfig = append(intermediateCACertsConfig, intermediateCACertConfig)
}

var membersConfig []*types.PeerConfig
for _, member := range sharedConfig.Consensus.Members {
memberConfig := &types.PeerConfig{
NodeId: member.NodeId,
RaftId: member.RaftId,
PeerHost: member.PeerHost,
PeerPort: member.PeerPort,
}
membersConfig = append(membersConfig, memberConfig)
}

var observersConfig []*types.PeerConfig
for _, observer := range sharedConfig.Consensus.Observers {
observerConfig := &types.PeerConfig{
NodeId: observer.NodeId,
RaftId: observer.RaftId,
PeerHost: observer.PeerHost,
PeerPort: observer.PeerPort,
}
observersConfig = append(observersConfig, observerConfig)
}

clusterConfig := &types.ClusterConfig{
Nodes: nodesConfig,
Admins: adminsConfig,
CertAuthConfig: &types.CAConfig{
Roots: rootCACertsConfig,
Intermediates: intermediateCACertsConfig,
},
ConsensusConfig: &types.ConsensusConfig{
Algorithm: sharedConfig.Consensus.Algorithm,
Members: membersConfig,
Observers: observersConfig,
RaftConfig: &types.RaftConfig{
TickInterval: sharedConfig.Consensus.RaftConfig.TickInterval,
ElectionTicks: sharedConfig.Consensus.RaftConfig.ElectionTicks,
HeartbeatTicks: sharedConfig.Consensus.RaftConfig.HeartbeatTicks,
MaxInflightBlocks: sharedConfig.Consensus.RaftConfig.MaxInflightBlocks,
SnapshotIntervalSize: sharedConfig.Consensus.RaftConfig.SnapshotIntervalSize,
},
},
LedgerConfig: &types.LedgerConfig{StateMerklePatriciaTrieDisabled: sharedConfig.Ledger.StateMerklePatriciaTrieDisabled},
}

return clusterConfig, nil
}
Loading