VOL-3943 - multiple stack configuration support

Change-Id: I28cb26b6cd6fbc5b3f0e8d406a68bf5c964a57fd
diff --git a/MULTIPLE_STACKS.md b/MULTIPLE_STACKS.md
new file mode 100644
index 0000000..412f4cf
--- /dev/null
+++ b/MULTIPLE_STACKS.md
@@ -0,0 +1,27 @@
+# Multiple Stack Support
+
+`voltctl` supports multiple stack configurations in a single configuration
+file. The format of the **v3** configuration file that supports multiple
+stacks is of the format
+
+```yaml
+apiVersion: v3
+currentStack: <stack-name-reference>
+stacks:
+  - name: <stack-name-reference>
+    <values>
+```
+
+## `--stack` command line option
+
+As part of the support for multiple stacks a new command line option,
+`--stack` was added. This option can be used to specify which stack
+configuration should be used for each command invocation.
+
+## Configuration File Manipulations
+
+A new `voltctl` subcommand, `stack` was added to support the listing,
+deletion, and addition of stacks to the configuration file. In addtion
+the `stack use` command was added to persistently set the current
+stack name back to the configuration. _(see `voltctl stack --help`
+for more information)_
diff --git a/VERSION b/VERSION
index bc80560..26ca594 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-1.5.0
+1.5.1
diff --git a/cmd/voltctl/voltctl.go b/cmd/voltctl/voltctl.go
index e3da44f..074297a 100644
--- a/cmd/voltctl/voltctl.go
+++ b/cmd/voltctl/voltctl.go
@@ -69,6 +69,7 @@
 	commands.RegisterLogCommands(parser)
 	commands.RegisterEventCommands(parser)
 	commands.RegisterMessageCommands(parser)
+	commands.RegisterStackCommands(parser)
 
 	_, err = parser.Parse()
 	if err != nil {
diff --git a/internal/pkg/apis/config/v3/convert.go b/internal/pkg/apis/config/v3/convert.go
new file mode 100644
index 0000000..edb6595
--- /dev/null
+++ b/internal/pkg/apis/config/v3/convert.go
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2021-present Ciena Corporation
+ *
+ * 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 config
+
+import (
+	"strconv"
+
+	configv1 "github.com/opencord/voltctl/internal/pkg/apis/config/v1"
+	configv2 "github.com/opencord/voltctl/internal/pkg/apis/config/v2"
+)
+
+func FromConfigV1(v1 *configv1.GlobalConfigSpec) *GlobalConfigSpec {
+	v3 := NewDefaultConfig()
+	s3 := v3.Current()
+
+	s3.Server = v1.Server
+	s3.Kafka = v1.Kafka
+	s3.KvStore = v1.KvStore
+	s3.Tls.UseTls = v1.Tls.UseTls
+	s3.Tls.CACert = v1.Tls.CACert
+	s3.Tls.Cert = v1.Tls.Cert
+	s3.Tls.Key = v1.Tls.Key
+	if v1.Tls.Verify != "" {
+		if b, err := strconv.ParseBool(v1.Tls.Verify); err == nil {
+			s3.Tls.Verify = b
+		}
+	}
+	s3.Grpc.Timeout = v1.Grpc.Timeout
+	s3.Grpc.MaxCallRecvMsgSize = v1.Grpc.MaxCallRecvMsgSize
+	s3.KvStoreConfig.Timeout = v1.KvStoreConfig.Timeout
+	s3.K8sConfig = v1.K8sConfig
+	return v3
+}
+
+func FromConfigV2(v2 *configv2.GlobalConfigSpec) *GlobalConfigSpec {
+	v3 := NewDefaultConfig()
+	s3 := v3.Current()
+
+	s3.Server = v2.Server
+	s3.Kafka = v2.Kafka
+	s3.KvStore = v2.KvStore
+	s3.Tls.UseTls = v2.Tls.UseTls
+	s3.Tls.CACert = v2.Tls.CACert
+	s3.Tls.Cert = v2.Tls.Cert
+	s3.Tls.Key = v2.Tls.Key
+	s3.Tls.Verify = v2.Tls.Verify
+	s3.Grpc.ConnectTimeout = v2.Grpc.ConnectTimeout
+	s3.Grpc.Timeout = v2.Grpc.Timeout
+	s3.Grpc.MaxCallRecvMsgSize = v2.Grpc.MaxCallRecvMsgSize
+	s3.KvStoreConfig.Timeout = v2.KvStoreConfig.Timeout
+	s3.K8sConfig = v2.K8sConfig
+	return v3
+}
diff --git a/internal/pkg/apis/config/v3/defaults.go b/internal/pkg/apis/config/v3/defaults.go
new file mode 100644
index 0000000..2fcdff2
--- /dev/null
+++ b/internal/pkg/apis/config/v3/defaults.go
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2021-present Ciena Corporation
+ *
+ * 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 config
+
+import (
+	"log"
+	"os"
+	"time"
+)
+
+func NewDefaultConfig() *GlobalConfigSpec {
+	return &GlobalConfigSpec{
+		ApiVersion:   "v3",
+		CurrentStack: "",
+		Stacks:       []*StackConfigSpec{NewDefaultStack("default")},
+	}
+}
+
+func NewDefaultStack(name string) *StackConfigSpec {
+	return &StackConfigSpec{
+		Name:    name,
+		Server:  "localhost:55555",
+		Kafka:   "localhost:9093",
+		KvStore: "localhost:2379",
+		Tls: TlsConfigSpec{
+			UseTls: false,
+			Verify: false,
+		},
+		Grpc: GrpcConfigSpec{
+			ConnectTimeout:     5 * time.Second,
+			Timeout:            5 * time.Minute,
+			MaxCallRecvMsgSize: "4MB",
+		},
+		KvStoreConfig: KvStoreConfigSpec{
+			Timeout: 5 * time.Second,
+		},
+	}
+}
+
+func (g *GlobalConfigSpec) StackByName(name string) *StackConfigSpec {
+	for _, stack := range g.Stacks {
+		if stack.Name == name {
+			return stack
+		}
+	}
+	return nil
+}
+
+func (g *GlobalConfigSpec) CurrentAsStack() *StackConfigSpec {
+	if g.CurrentStack == "" {
+		if len(g.Stacks) == 0 {
+			return nil
+		}
+		return g.Stacks[0]
+	}
+	return g.StackByName(g.CurrentStack)
+}
+
+func (g GlobalConfigSpec) Current() *StackConfigSpec {
+	stack := g.CurrentAsStack()
+	if stack == nil {
+		if len(g.Stacks) > 1 {
+			log.New(os.Stderr, "ERROR: ", 0).
+				Fatal("multiple stacks configured without current specified")
+		}
+		log.New(os.Stderr, "ERROR: ", 0).
+			Fatalf("current stack specified, '%s', does not exist as a configured stack",
+				g.CurrentStack)
+	}
+	return stack
+}
diff --git a/internal/pkg/apis/config/v3/types.go b/internal/pkg/apis/config/v3/types.go
new file mode 100644
index 0000000..9639baa
--- /dev/null
+++ b/internal/pkg/apis/config/v3/types.go
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2021-present Ciena Corporation
+ *
+ * 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 config
+
+import (
+	"time"
+)
+
+type GrpcConfigSpec struct {
+	ConnectTimeout     time.Duration `yaml:"connectTimeout"`
+	Timeout            time.Duration `yaml:"timeout"`
+	MaxCallRecvMsgSize string        `yaml:"maxCallRecvMsgSize"`
+}
+
+type KvStoreConfigSpec struct {
+	Timeout time.Duration `yaml:"timeout"`
+}
+
+type TlsConfigSpec struct {
+	UseTls bool   `yaml:"useTLS"`
+	CACert string `yaml:"caCert"`
+	Cert   string `yaml:"cert"`
+	Key    string `yaml:"key"`
+	Verify bool   `yaml:"verify"`
+}
+
+type StackConfigSpec struct {
+	Name          string            `yaml:"name"`
+	Server        string            `yaml:"server"`
+	Kafka         string            `yaml:"kafka"`
+	KvStore       string            `yaml:"kvstore"`
+	Tls           TlsConfigSpec     `yaml:"tls"`
+	Grpc          GrpcConfigSpec    `yaml:"grpc"`
+	KvStoreConfig KvStoreConfigSpec `yaml:"kvstoreconfig"`
+	K8sConfig     string            `yaml:"-"`
+}
+
+type GlobalConfigSpec struct {
+	ApiVersion   string             `yaml:"apiVersion"`
+	Stacks       []*StackConfigSpec `yaml:"stacks"`
+	CurrentStack string             `yaml:"currentStack"`
+}
diff --git a/internal/pkg/commands/adapter.go b/internal/pkg/commands/adapter.go
index b4183c0..73b91e1 100644
--- a/internal/pkg/commands/adapter.go
+++ b/internal/pkg/commands/adapter.go
@@ -17,8 +17,9 @@
 
 import (
 	"context"
+
 	"github.com/golang/protobuf/ptypes/empty"
-	"github.com/jessevdk/go-flags"
+	flags "github.com/jessevdk/go-flags"
 	"github.com/opencord/voltctl/pkg/format"
 	"github.com/opencord/voltha-protos/v4/go/voltha"
 )
@@ -52,7 +53,7 @@
 
 	client := voltha.NewVolthaServiceClient(conn)
 
-	ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Grpc.Timeout)
+	ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Current().Grpc.Timeout)
 	defer cancel()
 
 	adapters, err := client.ListAdapters(ctx, &empty.Empty{})
diff --git a/internal/pkg/commands/command.go b/internal/pkg/commands/command.go
index 3e1beb4..93944a8 100644
--- a/internal/pkg/commands/command.go
+++ b/internal/pkg/commands/command.go
@@ -35,6 +35,7 @@
 	"github.com/golang/protobuf/proto"
 	configv1 "github.com/opencord/voltctl/internal/pkg/apis/config/v1"
 	configv2 "github.com/opencord/voltctl/internal/pkg/apis/config/v2"
+	configv3 "github.com/opencord/voltctl/internal/pkg/apis/config/v3"
 	"github.com/opencord/voltctl/pkg/filter"
 	"github.com/opencord/voltctl/pkg/format"
 	"github.com/opencord/voltctl/pkg/order"
@@ -70,12 +71,13 @@
 
 	CharReplacer = strings.NewReplacer("\\t", "\t", "\\n", "\n")
 
-	GlobalConfig = configv2.NewDefaultConfig()
+	GlobalConfig = configv3.NewDefaultConfig()
 
 	GlobalCommandOptions = make(map[string]map[string]string)
 
 	GlobalOptions struct {
 		Config  string `short:"c" long:"config" env:"VOLTCONFIG" value-name:"FILE" default:"" description:"Location of client config file"`
+		Stack   string `short:"v" long:"stack" env:"STACK" value-name:"STACK" default:"" description:"Name of stack to use in multistack deployment"`
 		Server  string `short:"s" long:"server" default:"" value-name:"SERVER:PORT" description:"IP/Host and port of VOLTHA"`
 		Kafka   string `short:"k" long:"kafka" default:"" value-name:"SERVER:PORT" description:"IP/Host and port of Kafka"`
 		KvStore string `short:"e" long:"kvstore" env:"KVSTORE" value-name:"SERVER:PORT" description:"IP/Host and port of KV store (etcd)"`
@@ -194,75 +196,18 @@
 }
 
 func ProcessGlobalOptions() {
-	if len(GlobalOptions.Config) == 0 {
-		home, err := os.UserHomeDir()
-		if err != nil {
-			Warn.Printf("Unable to discover the user's home directory: %s", err)
-			home = "~"
+	ReadConfig()
+
+	// If a stack is selected via command line set it
+	if GlobalOptions.Stack != "" {
+		if GlobalConfig.StackByName(GlobalOptions.Stack) == nil {
+			Error.Fatalf("stack specified, '%s', not found in configuration",
+				GlobalOptions.Stack)
 		}
-		GlobalOptions.Config = filepath.Join(home, ".volt", "config")
+		GlobalConfig.CurrentStack = GlobalOptions.Stack
 	}
 
-	if info, err := os.Stat(GlobalOptions.Config); err == nil && !info.IsDir() {
-		configFile, err := ioutil.ReadFile(GlobalOptions.Config)
-		if err != nil {
-			Error.Fatalf("Unable to read the configuration file '%s': %s",
-				GlobalOptions.Config, err.Error())
-		}
-		// First try the latest version of the config api then work
-		// backwards
-		if err = yaml.Unmarshal(configFile, &GlobalConfig); err != nil {
-			GlobalConfigV1 := configv1.NewDefaultConfig()
-			if err = yaml.Unmarshal(configFile, &GlobalConfigV1); err != nil {
-				Error.Fatalf("Unable to parse the configuration file '%s': %s",
-					GlobalOptions.Config, err.Error())
-			}
-			GlobalConfig = configv2.FromConfigV1(GlobalConfigV1)
-		}
-	}
-
-	// Override from command line
-	if GlobalOptions.Server != "" {
-		GlobalConfig.Server = GlobalOptions.Server
-	}
-
-	if GlobalOptions.UseTLS {
-		GlobalConfig.Tls.UseTls = true
-	}
-
-	if GlobalOptions.Verify {
-		GlobalConfig.Tls.Verify = true
-	}
-
-	if GlobalOptions.Kafka != "" {
-		GlobalConfig.Kafka = GlobalOptions.Kafka
-	}
-
-	if GlobalOptions.KvStore != "" {
-		GlobalConfig.KvStore = GlobalOptions.KvStore
-	}
-
-	if GlobalOptions.KvStoreTimeout != "" {
-		timeout, err := time.ParseDuration(GlobalOptions.KvStoreTimeout)
-		if err != nil {
-			Error.Fatalf("Unable to parse specified KV strore timeout duration '%s': %s",
-				GlobalOptions.KvStoreTimeout, err.Error())
-		}
-		GlobalConfig.KvStoreConfig.Timeout = timeout
-	}
-
-	if GlobalOptions.Timeout != "" {
-		timeout, err := time.ParseDuration(GlobalOptions.Timeout)
-		if err != nil {
-			Error.Fatalf("Unable to parse specified timeout duration '%s': %s",
-				GlobalOptions.Timeout, err.Error())
-		}
-		GlobalConfig.Grpc.Timeout = timeout
-	}
-
-	if GlobalOptions.MaxCallRecvMsgSize != "" {
-		GlobalConfig.Grpc.MaxCallRecvMsgSize = GlobalOptions.MaxCallRecvMsgSize
-	}
+	ApplyOptionOverrides(GlobalConfig.Current())
 
 	// If a k8s cert/key were not specified, then attempt to read it from
 	// any $HOME/.kube/config if it exists
@@ -297,13 +242,98 @@
 	}
 }
 
+func ApplyOptionOverrides(stack *configv3.StackConfigSpec) {
+
+	if stack == nil {
+		// nothing to do
+		return
+	}
+	// Override from command line
+	if GlobalOptions.Server != "" {
+		stack.Server = GlobalOptions.Server
+	}
+
+	if GlobalOptions.UseTLS {
+		stack.Tls.UseTls = true
+	}
+
+	if GlobalOptions.Verify {
+		stack.Tls.Verify = true
+	}
+
+	if GlobalOptions.Kafka != "" {
+		stack.Kafka = GlobalOptions.Kafka
+	}
+
+	if GlobalOptions.KvStore != "" {
+		stack.KvStore = GlobalOptions.KvStore
+	}
+
+	if GlobalOptions.KvStoreTimeout != "" {
+		timeout, err := time.ParseDuration(GlobalOptions.KvStoreTimeout)
+		if err != nil {
+			Error.Fatalf("Unable to parse specified KV strore timeout duration '%s': %s",
+				GlobalOptions.KvStoreTimeout, err.Error())
+		}
+		stack.KvStoreConfig.Timeout = timeout
+	}
+
+	if GlobalOptions.Timeout != "" {
+		timeout, err := time.ParseDuration(GlobalOptions.Timeout)
+		if err != nil {
+			Error.Fatalf("Unable to parse specified timeout duration '%s': %s",
+				GlobalOptions.Timeout, err.Error())
+		}
+		stack.Grpc.Timeout = timeout
+	}
+
+	if GlobalOptions.MaxCallRecvMsgSize != "" {
+		stack.Grpc.MaxCallRecvMsgSize = GlobalOptions.MaxCallRecvMsgSize
+	}
+}
+
+func ReadConfig() {
+	if len(GlobalOptions.Config) == 0 {
+		home, err := os.UserHomeDir()
+		if err != nil {
+			Warn.Printf("Unable to discover the user's home directory: %s", err)
+			home = "~"
+		}
+		GlobalOptions.Config = filepath.Join(home, ".volt", "config")
+	}
+
+	if info, err := os.Stat(GlobalOptions.Config); err == nil && !info.IsDir() {
+		configFile, err := ioutil.ReadFile(GlobalOptions.Config)
+		if err != nil {
+			Error.Fatalf("Unable to read the configuration file '%s': %s",
+				GlobalOptions.Config, err.Error())
+		}
+		// First try the latest version of the config api then work
+		// backwards
+		if err = yaml.Unmarshal(configFile, &GlobalConfig); err != nil {
+			GlobalConfigV2 := configv2.NewDefaultConfig()
+			if err = yaml.Unmarshal(configFile, &GlobalConfigV2); err != nil {
+				GlobalConfigV1 := configv1.NewDefaultConfig()
+				if err = yaml.Unmarshal(configFile, &GlobalConfigV1); err != nil {
+					Error.Fatalf("Unable to parse the configuration file '%s': %s",
+						GlobalOptions.Config, err.Error())
+				}
+				GlobalConfig = configv3.FromConfigV1(GlobalConfigV1)
+			} else {
+				GlobalConfig = configv3.FromConfigV2(GlobalConfigV2)
+			}
+		}
+	}
+
+}
+
 func NewConnection() (*grpc.ClientConn, error) {
 	ProcessGlobalOptions()
 
 	// convert grpc.msgSize into bytes
-	n, err := parseSize(GlobalConfig.Grpc.MaxCallRecvMsgSize)
+	n, err := parseSize(GlobalConfig.Current().Grpc.MaxCallRecvMsgSize)
 	if err != nil {
-		Error.Fatalf("Cannot convert msgSize %s to bytes", GlobalConfig.Grpc.MaxCallRecvMsgSize)
+		Error.Fatalf("Cannot convert msgSize %s to bytes", GlobalConfig.Current().Grpc.MaxCallRecvMsgSize)
 	}
 
 	var opts []grpc.DialOption
@@ -313,17 +343,17 @@
 		grpc.WithBlock(),
 		grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(int(n))))
 
-	if GlobalConfig.Tls.UseTls {
+	if GlobalConfig.Current().Tls.UseTls {
 		creds := credentials.NewTLS(&tls.Config{
-			InsecureSkipVerify: !GlobalConfig.Tls.Verify})
+			InsecureSkipVerify: !GlobalConfig.Current().Tls.Verify})
 		opts = append(opts, grpc.WithTransportCredentials(creds))
 	} else {
 		opts = append(opts, grpc.WithInsecure())
 	}
 	ctx, cancel := context.WithTimeout(context.TODO(),
-		GlobalConfig.Grpc.ConnectTimeout)
+		GlobalConfig.Current().Grpc.ConnectTimeout)
 	defer cancel()
-	return grpc.DialContext(ctx, GlobalConfig.Server, opts...)
+	return grpc.DialContext(ctx, GlobalConfig.Current().Server, opts...)
 }
 
 func ConvertJsonProtobufArray(data_in interface{}) (string, error) {
diff --git a/internal/pkg/commands/command_test.go b/internal/pkg/commands/command_test.go
index e0bd4bd..f17e06d 100644
--- a/internal/pkg/commands/command_test.go
+++ b/internal/pkg/commands/command_test.go
@@ -37,7 +37,7 @@
 	assert.Nil(t, err, "unexpected error paring arguments")
 	ProcessGlobalOptions()
 
-	assert.Equal(t, "localhost:55555", GlobalConfig.Server, "wrong default hostname for server")
+	assert.Equal(t, "localhost:55555", GlobalConfig.Current().Server, "wrong default hostname for server")
 }
 
 func TestParseSize(t *testing.T) {
diff --git a/internal/pkg/commands/config.go b/internal/pkg/commands/config.go
index 59a1df1..28cb6e2 100644
--- a/internal/pkg/commands/config.go
+++ b/internal/pkg/commands/config.go
@@ -36,10 +36,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.`
 
-type CommandOptionsDump struct{}
-
 type ConfigOptions struct {
-	Commands CommandOptionsDump `command:"commands"`
 }
 
 func RegisterConfigCommands(parent *flags.Parser) {
@@ -51,8 +48,8 @@
 }
 
 func (options *ConfigOptions) Execute(args []string) error {
-	//GlobalConfig
-	ProcessGlobalOptions()
+	ReadConfig()
+	ApplyOptionOverrides(nil)
 	b, err := yaml.Marshal(GlobalConfig)
 	if err != nil {
 		return err
@@ -61,14 +58,3 @@
 	fmt.Println(string(b))
 	return nil
 }
-
-func (commands *CommandOptionsDump) Execute(args []string) error {
-	ProcessGlobalOptions()
-	b, err := yaml.Marshal(GlobalCommandOptions)
-	if err != nil {
-		return err
-	}
-	fmt.Println(copyrightNotice)
-	fmt.Println(string(b))
-	return nil
-}
diff --git a/internal/pkg/commands/devicegroups.go b/internal/pkg/commands/devicegroups.go
index 05b681b..f42547b 100644
--- a/internal/pkg/commands/devicegroups.go
+++ b/internal/pkg/commands/devicegroups.go
@@ -17,6 +17,7 @@
 
 import (
 	"context"
+
 	"github.com/golang/protobuf/ptypes/empty"
 	flags "github.com/jessevdk/go-flags"
 	"github.com/opencord/voltctl/pkg/format"
@@ -54,7 +55,7 @@
 
 	client := voltha.NewVolthaServiceClient(conn)
 
-	ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Grpc.Timeout)
+	ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Current().Grpc.Timeout)
 	defer cancel()
 
 	deviceGroups, err := client.ListDeviceGroups(ctx, &empty.Empty{})
diff --git a/internal/pkg/commands/devices.go b/internal/pkg/commands/devices.go
index 58bf6e4..d1e0340 100644
--- a/internal/pkg/commands/devices.go
+++ b/internal/pkg/commands/devices.go
@@ -366,7 +366,7 @@
 		return nil
 	}
 
-	ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Grpc.Timeout)
+	ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Current().Grpc.Timeout)
 	defer cancel()
 
 	id := voltha.ID{Id: string(deviceId)}
@@ -419,7 +419,7 @@
 		return nil
 	}
 
-	ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Grpc.Timeout)
+	ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Current().Grpc.Timeout)
 	defer cancel()
 
 	id := voltha.ID{Id: string(deviceId)}
@@ -477,7 +477,7 @@
 		return nil
 	}
 
-	ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Grpc.Timeout)
+	ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Current().Grpc.Timeout)
 	defer cancel()
 
 	id := voltha.ID{Id: string(deviceId)}
@@ -507,7 +507,7 @@
 
 	client := voltha.NewVolthaServiceClient(conn)
 
-	ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Grpc.Timeout)
+	ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Current().Grpc.Timeout)
 	defer cancel()
 
 	devices, err := client.ListDevices(ctx, &empty.Empty{})
@@ -535,7 +535,7 @@
 
 	client := voltha.NewVolthaServiceClient(conn)
 
-	ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Grpc.Timeout)
+	ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Current().Grpc.Timeout)
 	defer cancel()
 
 	devices, err := client.ListDevices(ctx, &empty.Empty{})
@@ -597,7 +597,7 @@
 
 	client := voltha.NewVolthaServiceClient(conn)
 
-	ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Grpc.Timeout)
+	ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Current().Grpc.Timeout)
 	defer cancel()
 
 	createdDevice, err := client.CreateDevice(ctx, &device)
@@ -621,7 +621,7 @@
 	client := voltha.NewVolthaServiceClient(conn)
 	var lastErr error
 	for _, i := range options.Args.Ids {
-		ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Grpc.Timeout)
+		ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Current().Grpc.Timeout)
 		defer cancel()
 
 		id := voltha.ID{Id: string(i)}
@@ -656,7 +656,7 @@
 
 	var lastErr error
 	for _, i := range options.Args.Ids {
-		ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Grpc.Timeout)
+		ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Current().Grpc.Timeout)
 		defer cancel()
 
 		id := voltha.ID{Id: string(i)}
@@ -687,7 +687,7 @@
 
 	var lastErr error
 	for _, i := range options.Args.Ids {
-		ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Grpc.Timeout)
+		ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Current().Grpc.Timeout)
 		defer cancel()
 
 		id := voltha.ID{Id: string(i)}
@@ -718,7 +718,7 @@
 
 	var lastErr error
 	for _, i := range options.Args.Ids {
-		ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Grpc.Timeout)
+		ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Current().Grpc.Timeout)
 		defer cancel()
 
 		id := voltha.ID{Id: string(i)}
@@ -748,7 +748,7 @@
 
 	client := voltha.NewVolthaServiceClient(conn)
 
-	ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Grpc.Timeout)
+	ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Current().Grpc.Timeout)
 	defer cancel()
 
 	id := voltha.ID{Id: string(options.Args.Id)}
@@ -803,7 +803,7 @@
 
 	client := voltha.NewVolthaServiceClient(conn)
 
-	ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Grpc.Timeout)
+	ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Current().Grpc.Timeout)
 	defer cancel()
 
 	id := voltha.ID{Id: string(options.Args.Id)}
@@ -841,7 +841,7 @@
 
 	client := voltha.NewVolthaServiceClient(conn)
 
-	ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Grpc.Timeout)
+	ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Current().Grpc.Timeout)
 	defer cancel()
 
 	port := voltha.Port{DeviceId: string(options.Args.Id), PortNo: uint32(options.Args.PortId)}
@@ -865,7 +865,7 @@
 
 	client := voltha.NewVolthaServiceClient(conn)
 
-	ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Grpc.Timeout)
+	ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Current().Grpc.Timeout)
 	defer cancel()
 
 	port := voltha.Port{DeviceId: string(options.Args.Id), PortNo: uint32(options.Args.PortId)}
@@ -888,7 +888,7 @@
 
 	client := voltha.NewVolthaServiceClient(conn)
 
-	ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Grpc.Timeout)
+	ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Current().Grpc.Timeout)
 	defer cancel()
 
 	id := voltha.ID{Id: string(options.Args.Id)}
@@ -918,7 +918,7 @@
 
 	client := voltha.NewVolthaServiceClient(conn)
 
-	ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Grpc.Timeout)
+	ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Current().Grpc.Timeout)
 	defer cancel()
 
 	id := voltha.ID{Id: string(options.Args.Id)}
@@ -962,7 +962,7 @@
 
 	client := voltha.NewVolthaServiceClient(conn)
 
-	ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Grpc.Timeout)
+	ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Current().Grpc.Timeout)
 	defer cancel()
 
 	id := voltha.ID{Id: string(options.Args.Id)}
@@ -1014,7 +1014,7 @@
 
 	client := voltha.NewVolthaServiceClient(conn)
 
-	ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Grpc.Timeout)
+	ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Current().Grpc.Timeout)
 	defer cancel()
 
 	id := voltha.ID{Id: string(options.Args.Id)}
@@ -1061,7 +1061,7 @@
 
 	client := voltha.NewVolthaServiceClient(conn)
 
-	ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Grpc.Timeout)
+	ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Current().Grpc.Timeout)
 	defer cancel()
 
 	id := voltha.ID{Id: string(options.Args.Id)}
@@ -1109,7 +1109,7 @@
 
 	client := voltha.NewVolthaServiceClient(conn)
 
-	ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Grpc.Timeout)
+	ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Current().Grpc.Timeout)
 	defer cancel()
 
 	id := voltha.ID{Id: string(options.Args.Id)}
@@ -1152,7 +1152,7 @@
 
 	client := voltha.NewVolthaServiceClient(conn)
 
-	ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Grpc.Timeout)
+	ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Current().Grpc.Timeout)
 	defer cancel()
 
 	id := voltha.ID{Id: string(options.Args.Id)}
@@ -1197,7 +1197,7 @@
 
 	client := voltha.NewVolthaServiceClient(conn)
 
-	ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Grpc.Timeout)
+	ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Current().Grpc.Timeout)
 	defer cancel()
 
 	id := voltha.ID{Id: string(options.Args.Id)}
@@ -1245,7 +1245,7 @@
 
 	client := voltha.NewVolthaServiceClient(conn)
 
-	ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Grpc.Timeout)
+	ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Current().Grpc.Timeout)
 	defer cancel()
 
 	id := voltha.ID{Id: string(options.Args.Id)}
@@ -1298,7 +1298,7 @@
 
 	client := voltha.NewVolthaServiceClient(conn)
 
-	ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Grpc.Timeout)
+	ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Current().Grpc.Timeout)
 	defer cancel()
 
 	id := voltha.ID{Id: string(options.Args.Id)}
@@ -1356,7 +1356,7 @@
 
 	client := voltha.NewVolthaServiceClient(conn)
 
-	ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Grpc.Timeout)
+	ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Current().Grpc.Timeout)
 	defer cancel()
 
 	id := voltha.ID{Id: string(options.Args.Id)}
@@ -1403,7 +1403,7 @@
 
 	client := voltha.NewVolthaServiceClient(conn)
 
-	ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Grpc.Timeout)
+	ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Current().Grpc.Timeout)
 	defer cancel()
 
 	id := common.ID{Id: string(options.Args.Id)}
@@ -1451,7 +1451,7 @@
 
 	client := voltha.NewVolthaServiceClient(conn)
 
-	ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Grpc.Timeout)
+	ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Current().Grpc.Timeout)
 	defer cancel()
 
 	downloadImage := voltha.ImageDownload{
@@ -1481,7 +1481,7 @@
 
 	client := voltha.NewVolthaServiceClient(conn)
 
-	ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Grpc.Timeout)
+	ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Current().Grpc.Timeout)
 	defer cancel()
 
 	downloadImage := voltha.ImageDownload{
@@ -1536,7 +1536,7 @@
 		},
 	}
 
-	ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Grpc.Timeout)
+	ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Current().Grpc.Timeout)
 	defer cancel()
 	rv, err := client.GetExtValue(ctx, &singleGetValReq)
 	if err != nil {
@@ -1581,7 +1581,7 @@
 			},
 		},
 	}
-	ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Grpc.Timeout)
+	ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Current().Grpc.Timeout)
 	defer cancel()
 	rv, err := client.GetExtValue(ctx, &singleGetValReq)
 	if err != nil {
@@ -1622,7 +1622,7 @@
 
 	val := voltha.ValueSpecifier{Id: string(options.Args.Id), Value: common.ValueType_Type(valueflag)}
 
-	ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Grpc.Timeout)
+	ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Current().Grpc.Timeout)
 	defer cancel()
 
 	rv, err := client.GetExtValue(ctx, &val)
diff --git a/internal/pkg/commands/events.go b/internal/pkg/commands/events.go
index 2aaa03a..06f6aee 100644
--- a/internal/pkg/commands/events.go
+++ b/internal/pkg/commands/events.go
@@ -19,6 +19,12 @@
 	"encoding/json"
 	"errors"
 	"fmt"
+	"log"
+	"os"
+	"os/signal"
+	"strings"
+	"time"
+
 	"github.com/Shopify/sarama"
 	"github.com/golang/protobuf/jsonpb"
 	"github.com/golang/protobuf/proto"
@@ -28,11 +34,6 @@
 	"github.com/opencord/voltctl/pkg/filter"
 	"github.com/opencord/voltctl/pkg/format"
 	"github.com/opencord/voltha-protos/v4/go/voltha"
-	"log"
-	"os"
-	"os/signal"
-	"strings"
-	"time"
 )
 
 const (
@@ -289,7 +290,7 @@
 
 func (options *EventListenOpts) Execute(args []string) error {
 	ProcessGlobalOptions()
-	if GlobalConfig.Kafka == "" {
+	if GlobalConfig.Current().Kafka == "" {
 		return errors.New("Kafka address is not specified")
 	}
 
@@ -297,7 +298,7 @@
 	config.ClientID = "go-kafka-consumer"
 	config.Consumer.Return.Errors = true
 	config.Version = sarama.V1_0_0_0
-	brokers := []string{GlobalConfig.Kafka}
+	brokers := []string{GlobalConfig.Current().Kafka}
 
 	client, err := sarama.NewClient(brokers, config)
 	if err != nil {
diff --git a/internal/pkg/commands/flows.go b/internal/pkg/commands/flows.go
index 0c94f96..4127545 100644
--- a/internal/pkg/commands/flows.go
+++ b/internal/pkg/commands/flows.go
@@ -116,7 +116,7 @@
 
 	client := voltha.NewVolthaServiceClient(conn)
 
-	ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Grpc.Timeout)
+	ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Current().Grpc.Timeout)
 	defer cancel()
 
 	id := voltha.ID{Id: string(options.Args.Id)}
diff --git a/internal/pkg/commands/log.go b/internal/pkg/commands/log.go
index a1110b4..8663b70 100644
--- a/internal/pkg/commands/log.go
+++ b/internal/pkg/commands/log.go
@@ -251,15 +251,15 @@
 
 func constructConfigManager(ctx context.Context) (*config.ConfigManager, func(), error) {
 	var tlsConfig *tls.Config
-	if GlobalConfig.Tls.UseTls {
-		tlsConfig = &tls.Config{InsecureSkipVerify: !GlobalConfig.Tls.Verify}
+	if GlobalConfig.Current().Tls.UseTls {
+		tlsConfig = &tls.Config{InsecureSkipVerify: !GlobalConfig.Current().Tls.Verify}
 	}
 	logconfig := log.ConstructZapConfig(log.JSON, log.FatalLevel, log.Fields{})
 	client, err := kvstore.NewEtcdCustomClient(
 		ctx,
 		&v3Client.Config{
-			Endpoints:   []string{GlobalConfig.KvStore},
-			DialTimeout: GlobalConfig.KvStoreConfig.Timeout,
+			Endpoints:   []string{GlobalConfig.Current().KvStore},
+			DialTimeout: GlobalConfig.Current().KvStoreConfig.Timeout,
 			LogConfig:   &logconfig,
 			TLS:         tlsConfig,
 		})
@@ -267,7 +267,7 @@
 		return nil, nil, fmt.Errorf("Unable to create kvstore client %s", err)
 	}
 
-	cm := config.NewConfigManager(ctx, client, supportedKvStoreType, GlobalConfig.KvStore, GlobalConfig.KvStoreConfig.Timeout)
+	cm := config.NewConfigManager(ctx, client, supportedKvStoreType, GlobalConfig.Current().KvStore, GlobalConfig.Current().KvStoreConfig.Timeout)
 	return cm, func() { client.Close(ctx) }, nil
 }
 
@@ -279,7 +279,7 @@
 	ProcessGlobalOptions()
 
 	log.SetAllLogLevel(log.FatalLevel)
-	ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.KvStoreConfig.Timeout)
+	ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Current().KvStoreConfig.Timeout)
 	defer cancel()
 
 	cm, cleanupFunc, err := constructConfigManager(ctx)
@@ -399,7 +399,7 @@
 	ProcessGlobalOptions()
 
 	log.SetAllLogLevel(log.FatalLevel)
-	ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.KvStoreConfig.Timeout)
+	ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Current().KvStoreConfig.Timeout)
 	defer cancel()
 
 	cm, cleanupFunc, err := constructConfigManager(ctx)
@@ -581,7 +581,7 @@
 		return fmt.Errorf(err.Error())
 	}
 
-	ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.KvStoreConfig.Timeout)
+	ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Current().KvStoreConfig.Timeout)
 	defer cancel()
 
 	cm, cleanupFunc, err := constructConfigManager(ctx)
@@ -683,7 +683,7 @@
 	ProcessGlobalOptions()
 
 	log.SetAllLogLevel(log.FatalLevel)
-	ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.KvStoreConfig.Timeout)
+	ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Current().KvStoreConfig.Timeout)
 	defer cancel()
 
 	cm, cleanupFunc, err := constructConfigManager(ctx)
@@ -695,7 +695,7 @@
 	if len(options.Args.Component) == 0 {
 		componentList, err = cm.RetrieveComponentList(ctx, config.ConfigTypeLogLevel)
 		if err != nil {
-			return fmt.Errorf("Unable to retrieve list of voltha components : %s \nIs ETCD available at %s?", err, GlobalConfig.KvStore)
+			return fmt.Errorf("Unable to retrieve list of voltha components : %s \nIs ETCD available at %s?", err, GlobalConfig.Current().KvStore)
 		}
 	} else {
 		componentList = toStringArray(options.Args.Component)
@@ -801,7 +801,7 @@
 		return fmt.Errorf("%s", err)
 	}
 
-	ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.KvStoreConfig.Timeout)
+	ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Current().KvStoreConfig.Timeout)
 	defer cancel()
 
 	cm, cleanupFunc, err := constructConfigManager(ctx)
@@ -862,7 +862,7 @@
 	ProcessGlobalOptions()
 
 	log.SetAllLogLevel(log.FatalLevel)
-	ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.KvStoreConfig.Timeout)
+	ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Current().KvStoreConfig.Timeout)
 	defer cancel()
 
 	cm, cleanupFunc, err := constructConfigManager(ctx)
@@ -939,7 +939,7 @@
 
 	log.SetAllLogLevel(log.FatalLevel)
 
-	ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.KvStoreConfig.Timeout)
+	ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Current().KvStoreConfig.Timeout)
 	defer cancel()
 
 	cm, cleanupFunc, err := constructConfigManager(ctx)
@@ -1018,7 +1018,7 @@
 
 	log.SetAllLogLevel(log.FatalLevel)
 
-	ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.KvStoreConfig.Timeout)
+	ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Current().KvStoreConfig.Timeout)
 	defer cancel()
 
 	cm, cleanupFunc, err := constructConfigManager(ctx)
@@ -1099,7 +1099,7 @@
 
 	log.SetAllLogLevel(log.FatalLevel)
 
-	ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.KvStoreConfig.Timeout)
+	ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Current().KvStoreConfig.Timeout)
 	defer cancel()
 
 	cm, cleanupFunc, err := constructConfigManager(ctx)
@@ -1183,7 +1183,7 @@
 	ProcessGlobalOptions()
 
 	log.SetAllLogLevel(log.FatalLevel)
-	ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.KvStoreConfig.Timeout)
+	ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Current().KvStoreConfig.Timeout)
 	defer cancel()
 
 	cm, cleanupFunc, err := constructConfigManager(ctx)
@@ -1258,7 +1258,7 @@
 	ProcessGlobalOptions()
 
 	log.SetAllLogLevel(log.FatalLevel)
-	ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.KvStoreConfig.Timeout)
+	ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Current().KvStoreConfig.Timeout)
 	defer cancel()
 
 	cm, cleanupFunc, err := constructConfigManager(ctx)
@@ -1332,7 +1332,7 @@
 func (options *ListLogCorrelationOpts) Execute(args []string) error {
 	ProcessGlobalOptions()
 	log.SetAllLogLevel(log.FatalLevel)
-	ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.KvStoreConfig.Timeout)
+	ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Current().KvStoreConfig.Timeout)
 	defer cancel()
 
 	cm, cleanupFunc, err := constructConfigManager(ctx)
diff --git a/internal/pkg/commands/logicaldevices.go b/internal/pkg/commands/logicaldevices.go
index c529603..9191385 100644
--- a/internal/pkg/commands/logicaldevices.go
+++ b/internal/pkg/commands/logicaldevices.go
@@ -89,7 +89,7 @@
 
 	client := voltha.NewVolthaServiceClient(conn)
 
-	ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Grpc.Timeout)
+	ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Current().Grpc.Timeout)
 	defer cancel()
 
 	logicalDevices, err := client.ListLogicalDevices(ctx, &empty.Empty{})
@@ -117,7 +117,7 @@
 
 	client := voltha.NewVolthaServiceClient(conn)
 
-	ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Grpc.Timeout)
+	ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Current().Grpc.Timeout)
 	defer cancel()
 
 	logicalDevices, err := client.ListLogicalDevices(ctx, &empty.Empty{})
@@ -165,7 +165,7 @@
 
 	client := voltha.NewVolthaServiceClient(conn)
 
-	ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Grpc.Timeout)
+	ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Current().Grpc.Timeout)
 	defer cancel()
 
 	id := voltha.ID{Id: string(options.Args.Id)}
@@ -232,7 +232,7 @@
 
 	client := voltha.NewVolthaServiceClient(conn)
 
-	ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Grpc.Timeout)
+	ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Current().Grpc.Timeout)
 	defer cancel()
 
 	id := voltha.ID{Id: string(options.Args.Id)}
diff --git a/internal/pkg/commands/message.go b/internal/pkg/commands/message.go
index bef4cac..338affd 100644
--- a/internal/pkg/commands/message.go
+++ b/internal/pkg/commands/message.go
@@ -353,7 +353,7 @@
 
 func (options *MessageListenOpts) Execute(args []string) error {
 	ProcessGlobalOptions()
-	if GlobalConfig.Kafka == "" {
+	if GlobalConfig.Current().Kafka == "" {
 		return errors.New("Kafka address is not specified")
 	}
 
@@ -361,7 +361,7 @@
 	config.ClientID = "go-kafka-consumer"
 	config.Consumer.Return.Errors = true
 	config.Version = sarama.V1_0_0_0
-	brokers := []string{GlobalConfig.Kafka}
+	brokers := []string{GlobalConfig.Current().Kafka}
 
 	client, err := sarama.NewClient(brokers, config)
 	if err != nil {
diff --git a/internal/pkg/commands/stacks.go b/internal/pkg/commands/stacks.go
new file mode 100644
index 0000000..9a894cc
--- /dev/null
+++ b/internal/pkg/commands/stacks.go
@@ -0,0 +1,194 @@
+/*
+ * Copyright 2021-present Ciena Corporation
+ *
+ * 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 commands
+
+import (
+	"fmt"
+	"os"
+
+	flags "github.com/jessevdk/go-flags"
+	configv3 "github.com/opencord/voltctl/internal/pkg/apis/config/v3"
+	"github.com/opencord/voltctl/pkg/format"
+	yaml "gopkg.in/yaml.v2"
+)
+
+const (
+	DefaultOutputFormat = "table{{.Current}}\t{{.Name}}\t{{.Server}}\t{{.KvStore}}\t{{.Kafka}}"
+)
+
+type StackUse struct {
+	Args struct {
+		Name string `positional-arg-name:"NAME" required:"yes"`
+	} `positional-args:"yes"`
+}
+
+type StackAdd struct {
+	Args struct {
+		Name string `positional-arg-name:"NAME" required:"yes"`
+	} `positional-args:"yes"`
+}
+
+type StackDelete struct {
+	Args struct {
+		Name string `positional-arg-name:"NAME" required:"yes"`
+	} `positional-args:"yes"`
+}
+
+type StackList struct {
+	ListOutputOptions
+}
+
+type StackOptions struct {
+	List   StackList   `command:"list" description:"list all configured stacks"`
+	Add    StackAdd    `command:"add" description:"add or update the named stack using command line options"`
+	Delete StackDelete `command:"delete" description:"delete the specified stack configuration"`
+	Use    StackUse    `command:"use" description:"perist the specified stack to be used by default"`
+}
+
+func RegisterStackCommands(parent *flags.Parser) {
+	if _, err := parent.AddCommand("stack", "generate voltctl configuration", "Commands to generate voltctl configuration", &StackOptions{}); err != nil {
+		Error.Fatalf("Unexpected error while attempting to register config commands : %s", err)
+	}
+}
+
+type StackInfo struct {
+	Current string `json:"current"`
+	Name    string `json:"name"`
+	Server  string `json:"server"`
+	Kafka   string `json:"kafka"`
+	KvStore string `json:"kvstore"`
+}
+
+func write() error {
+	w, err := os.OpenFile(GlobalOptions.Config, os.O_TRUNC|os.O_WRONLY|os.O_CREATE, 0644)
+	if err != nil {
+		return err
+	}
+	defer w.Close()
+	encode := yaml.NewEncoder(w)
+	if err := encode.Encode(GlobalConfig); err != nil {
+		return err
+	}
+	return nil
+}
+
+func (options *StackList) Execute(args []string) error {
+
+	ReadConfig()
+	ApplyOptionOverrides(nil)
+
+	var data []StackInfo
+	for _, stack := range GlobalConfig.Stacks {
+		s := StackInfo{
+			Name:    stack.Name,
+			Server:  stack.Server,
+			Kafka:   stack.Kafka,
+			KvStore: stack.KvStore,
+		}
+		if stack.Name == GlobalConfig.CurrentStack {
+			s.Current = "*"
+		}
+		data = append(data, s)
+	}
+
+	outputFormat := CharReplacer.Replace(options.Format)
+	if outputFormat == "" {
+		outputFormat = GetCommandOptionWithDefault("stack-list", "format",
+			DefaultOutputFormat)
+	}
+	if options.Quiet {
+		outputFormat = "{{.Name}}"
+	}
+	orderBy := options.OrderBy
+	if orderBy == "" {
+		orderBy = GetCommandOptionWithDefault("stack-list", "order", "")
+	}
+
+	result := CommandResult{
+		Format:    format.Format(outputFormat),
+		Filter:    options.Filter,
+		OrderBy:   orderBy,
+		OutputAs:  toOutputType(options.OutputAs),
+		NameLimit: options.NameLimit,
+		Data:      data,
+	}
+
+	GenerateOutput(&result)
+	return nil
+}
+
+func (options *StackUse) Execute(args []string) error {
+
+	ReadConfig()
+
+	for _, stack := range GlobalConfig.Stacks {
+		if stack.Name == options.Args.Name {
+			GlobalConfig.CurrentStack = stack.Name
+			ApplyOptionOverrides(stack)
+			if err := write(); err != nil {
+				Error.Fatal(err.Error())
+			}
+			fmt.Printf("wrote: '%s'\n", GlobalOptions.Config)
+			return nil
+		}
+	}
+
+	Error.Fatalf("unknown stack: '%s'", options.Args.Name)
+
+	return nil
+}
+
+func (options *StackDelete) Execute(args []string) error {
+
+	ReadConfig()
+	ApplyOptionOverrides(nil)
+
+	for i, stack := range GlobalConfig.Stacks {
+		if stack.Name == options.Args.Name {
+			GlobalConfig.Stacks = append(GlobalConfig.Stacks[:i], GlobalConfig.Stacks[i+1:]...)
+			if GlobalConfig.CurrentStack == stack.Name {
+				GlobalConfig.CurrentStack = ""
+			}
+			if err := write(); err != nil {
+				Error.Fatal(err.Error())
+			}
+			fmt.Printf("wrote: '%s'\n", GlobalOptions.Config)
+			return nil
+		}
+	}
+
+	Error.Fatalf("stack not found, '%s'", options.Args.Name)
+	return nil
+}
+
+func (options *StackAdd) Execute(args []string) error {
+
+	ReadConfig()
+	stack := GlobalConfig.StackByName(options.Args.Name)
+
+	if stack == nil {
+		stack = configv3.NewDefaultStack(options.Args.Name)
+		GlobalConfig.Stacks = append(GlobalConfig.Stacks, stack)
+	}
+	if GlobalConfig.CurrentStack == "" {
+		GlobalConfig.CurrentStack = options.Args.Name
+	}
+	ApplyOptionOverrides(stack)
+	if err := write(); err != nil {
+		Error.Fatal(err.Error())
+	}
+	return nil
+}
diff --git a/internal/pkg/commands/version.go b/internal/pkg/commands/version.go
index db47ed8..4e3946f 100644
--- a/internal/pkg/commands/version.go
+++ b/internal/pkg/commands/version.go
@@ -136,7 +136,7 @@
 
 	client := voltha.NewVolthaServiceClient(conn)
 
-	ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Grpc.Timeout)
+	ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Current().Grpc.Timeout)
 	defer cancel()
 
 	voltha, err := client.GetVoltha(ctx, &empty.Empty{})
diff --git a/voltctl.v3.config b/voltctl.v3.config
new file mode 100644
index 0000000..d890fb7
--- /dev/null
+++ b/voltctl.v3.config
@@ -0,0 +1,33 @@
+# Copyright 2021-present Ciena Corporation
+#
+# 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.
+apiVersion: v2
+stacks:
+- name: default
+  server: localhost:55555
+  kafka: localhost:9093
+  kvstore: localhost:2379
+  tls:
+    useTLS: false
+    caCert: ""
+    cert: ""
+    key: ""
+    verify: false
+  grpc:
+    connectTimeout: 5s
+    timeout: 5m0s
+    maxCallRecvMsgSize: 4M
+  kvstoreconfig:
+    timeout: 5s
+currentStack: ""
+