SEBA-796 report connection error messages

Change-Id: I66b3339c8f9b25147fc6b67756f1afc16e332506
diff --git a/cmd/cordctl/cordctl.go b/cmd/cordctl/cordctl.go
index 8106a36..5116af0 100644
--- a/cmd/cordctl/cordctl.go
+++ b/cmd/cordctl/cordctl.go
@@ -20,6 +20,7 @@
 	"fmt"
 	flags "github.com/jessevdk/go-flags"
 	"github.com/opencord/cordctl/internal/pkg/commands"
+	"github.com/opencord/cordctl/internal/pkg/config"
 	corderrors "github.com/opencord/cordctl/internal/pkg/error"
 	"os"
 	"path"
@@ -29,7 +30,7 @@
 
 	parser := flags.NewNamedParser(path.Base(os.Args[0]),
 		flags.HelpFlag|flags.PassDoubleDash|flags.PassAfterNonOption)
-	_, err := parser.AddGroup("Global Options", "", &commands.GlobalOptions)
+	_, err := parser.AddGroup("Global Options", "", &config.GlobalOptions)
 	if err != nil {
 		panic(err)
 	}
@@ -56,7 +57,7 @@
 
 		corderror, ok := err.(corderrors.CordCtlError)
 		if ok {
-			if corderror.ShouldDumpStack() || commands.GlobalOptions.Debug {
+			if corderror.ShouldDumpStack() || config.GlobalOptions.Debug {
 				os.Stderr.WriteString("\n" + corderror.Stack())
 			}
 		}
diff --git a/internal/pkg/commands/command.go b/internal/pkg/commands/command.go
index 45ccd5d..51f8d01 100644
--- a/internal/pkg/commands/command.go
+++ b/internal/pkg/commands/command.go
@@ -19,17 +19,15 @@
 import (
 	"encoding/json"
 	"fmt"
+	"github.com/opencord/cordctl/internal/pkg/config"
+	corderrors "github.com/opencord/cordctl/internal/pkg/error"
 	"github.com/opencord/cordctl/pkg/format"
 	"github.com/opencord/cordctl/pkg/order"
 	"google.golang.org/grpc"
 	"gopkg.in/yaml.v2"
 	"io"
-	"io/ioutil"
-	"log"
 	"os"
 	"strings"
-	"time"
-	"net"
 )
 
 type OutputType uint8
@@ -47,52 +45,6 @@
 
 var CharReplacer = strings.NewReplacer("\\t", "\t", "\\n", "\n")
 
-type GrpcConfigSpec 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 string `yaml:"verify"`
-}
-
-type GlobalConfigSpec struct {
-	Server   string        `yaml:"server"`
-	Username string        `yaml:"username"`
-	Password string        `yaml:"password"`
-	Protoset string        `yaml:"protoset"`
-	Tls      TlsConfigSpec `yaml:"tls"`
-	Grpc     GrpcConfigSpec
-}
-
-var GlobalConfig = GlobalConfigSpec{
-	Server: "localhost",
-	Tls: TlsConfigSpec{
-		UseTls: false,
-	},
-	Grpc: GrpcConfigSpec{
-		Timeout: time.Second * 10,
-	},
-}
-
-var GlobalOptions struct {
-	Config   string `short:"c" long:"config" env:"CORDCONFIG" value-name:"FILE" default:"" description:"Location of client config file"`
-	Server   string `short:"s" long:"server" default:"" value-name:"SERVER:PORT" description:"IP/Host and port of XOS"`
-	Username string `short:"u" long:"username" value-name:"USERNAME" default:"" description:"Username to authenticate with XOS"`
-	Password string `short:"p" long:"password" value-name:"PASSWORD" default:"" description:"Password to authenticate with XOS"`
-	Protoset string `long:"protoset" value-name:"FILENAME" description:"Load protobuf definitions from protoset instead of reflection api"`
-	Debug    bool   `short:"d" long:"debug" description:"Enable debug mode"`
-	UseTLS   bool   `long:"tls" description:"Use TLS"`
-	CACert   string `long:"tlscacert" value-name:"CA_CERT_FILE" description:"Trust certs signed only by this CA"`
-	Cert     string `long:"tlscert" value-name:"CERT_FILE" description:"Path to TLS vertificate file"`
-	Key      string `long:"tlskey" value-name:"KEY_FILE" description:"Path to TLS key file"`
-	Verify   bool   `long:"tlsverify" description:"Use TLS and verify the remote"`
-	Yes      bool   `short:"y" long:"yes" description:"answer yes to any confirmation prompts"`
-}
-
 type OutputOptions struct {
 	Format   string `long:"format" value-name:"FORMAT" default:"" description:"Format to use to output structured data"`
 	Quiet    bool   `short:"q" long:"quiet" description:"Output only the IDs of the objects"`
@@ -124,85 +76,10 @@
 	Data     interface{}
 }
 
-type config struct {
-	Server string `yaml:"server"`
-}
-
-func ProcessGlobalOptions() {
-	if len(GlobalOptions.Config) == 0 {
-		home, err := os.UserHomeDir()
-		if err != nil {
-			log.Printf("Unable to discover the users home directory: %s\n", err)
-		}
-		GlobalOptions.Config = fmt.Sprintf("%s/.cord/config", home)
-	}
-
-	info, err := os.Stat(GlobalOptions.Config)
-	if err == nil && !info.IsDir() {
-		configFile, err := ioutil.ReadFile(GlobalOptions.Config)
-		if err != nil {
-			log.Printf("configFile.Get err   #%v ", err)
-		}
-		err = yaml.Unmarshal(configFile, &GlobalConfig)
-		if err != nil {
-			log.Fatalf("Unmarshal: %v", err)
-		}
-	}
-
-	// Override from environment
-	//    in particualr, for passing env vars via `go test`
-	env_server, present := os.LookupEnv("CORDCTL_SERVER")
-	if present {
-		GlobalConfig.Server = env_server
-	}
-	env_username, present := os.LookupEnv("CORDCTL_USERNAME")
-	if present {
-		GlobalConfig.Username = env_username
-	}
-	env_password, present := os.LookupEnv("CORDCTL_PASSWORD")
-	if present {
-		GlobalConfig.Password = env_password
-	}
-	env_protoset, present := os.LookupEnv("CORDCTL_PROTOSET")
-	if present {
-		GlobalConfig.Protoset = env_protoset
-	}
-
-	// Override from command line
-	if GlobalOptions.Server != "" {
-		GlobalConfig.Server = GlobalOptions.Server
-	}
-	if GlobalOptions.Username != "" {
-		GlobalConfig.Username = GlobalOptions.Username
-	}
-	if GlobalOptions.Password != "" {
-		GlobalConfig.Password = GlobalOptions.Password
-	}
-	if GlobalOptions.Protoset != "" {
-		GlobalConfig.Protoset = GlobalOptions.Protoset
-	}
-
-	// Generate error messages for required settings
-	if GlobalConfig.Server == "" {
-		log.Fatal("Server is not set. Please update config file or use the -s option")
-	}
-	if GlobalConfig.Username == "" {
-		log.Fatal("Username is not set. Please update config file or use the -u option")
-	}
-	if GlobalConfig.Password == "" {
-		log.Fatal("Password is not set. Please update config file or use the -p option")
-	}
-	//Try to resolve hostname if provided for the server
-	if host, port, err := net.SplitHostPort(GlobalConfig.Server); err == nil {
-		if addrs, err := net.LookupHost(host); err == nil {
-			GlobalConfig.Server = net.JoinHostPort(addrs[0], port)
-		}
-	}
-}
-
 func NewConnection() (*grpc.ClientConn, error) {
-	ProcessGlobalOptions()
-	return grpc.Dial(GlobalConfig.Server, grpc.WithInsecure())
+	config.ProcessGlobalOptions()
+	clientConn, err := grpc.Dial(config.GlobalConfig.Server, grpc.WithInsecure())
+	return clientConn, corderrors.RpcErrorToCordError(err)
 }
 
 func GenerateOutput(result *CommandResult) {
diff --git a/internal/pkg/commands/common.go b/internal/pkg/commands/common.go
index 6f795e4..4629534 100644
--- a/internal/pkg/commands/common.go
+++ b/internal/pkg/commands/common.go
@@ -23,6 +23,7 @@
 	versionUtils "github.com/hashicorp/go-version"
 	"github.com/jhump/protoreflect/dynamic"
 	"github.com/jhump/protoreflect/grpcreflect"
+	"github.com/opencord/cordctl/internal/pkg/config"
 	corderrors "github.com/opencord/cordctl/internal/pkg/error"
 	"golang.org/x/net/context"
 	"google.golang.org/grpc"
@@ -39,8 +40,8 @@
 )
 
 func GenerateHeaders() []string {
-	username := GlobalConfig.Username
-	password := GlobalConfig.Password
+	username := config.GlobalConfig.Username
+	password := config.GlobalConfig.Password
 	sEnc := b64.StdEncoding.EncodeToString([]byte(username + ":" + password))
 	headers := []string{"authorization: basic " + sEnc}
 	return headers
@@ -48,7 +49,7 @@
 
 // Perform the GetVersion API call on the core to get the version
 func GetVersion(conn *grpc.ClientConn, descriptor grpcurl.DescriptorSource) (*dynamic.Message, error) {
-	ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Grpc.Timeout)
+	ctx, cancel := GrpcTimeoutContext(context.Background())
 	defer cancel()
 
 	headers := GenerateHeaders()
@@ -87,8 +88,8 @@
 	// support the reflection API.
 
 	var descriptor grpcurl.DescriptorSource
-	if GlobalConfig.Protoset != "" {
-		descriptor, err = grpcurl.DescriptorSourceFromProtoSets(GlobalConfig.Protoset)
+	if config.GlobalConfig.Protoset != "" {
+		descriptor, err = grpcurl.DescriptorSourceFromProtoSets(config.GlobalConfig.Protoset)
 		if err != nil {
 			return nil, nil, err
 		}
@@ -142,7 +143,7 @@
 
 // Print a confirmation prompt and get a response from the user
 func Confirmf(format string, args ...interface{}) bool {
-	if GlobalOptions.Yes {
+	if config.GlobalOptions.Yes {
 		return true
 	}
 
@@ -166,3 +167,8 @@
 		}
 	}
 }
+
+// Returns a context used for gRPC timeouts
+func GrpcTimeoutContext(ctx context.Context) (context.Context, context.CancelFunc) {
+	return context.WithTimeout(ctx, config.GlobalConfig.Grpc.Timeout)
+}
diff --git a/internal/pkg/commands/config.go b/internal/pkg/commands/config.go
index 81875a5..f5ede01 100644
--- a/internal/pkg/commands/config.go
+++ b/internal/pkg/commands/config.go
@@ -19,6 +19,7 @@
 import (
 	"fmt"
 	flags "github.com/jessevdk/go-flags"
+	"github.com/opencord/cordctl/internal/pkg/config"
 	"gopkg.in/yaml.v2"
 )
 
@@ -49,8 +50,8 @@
 
 func (options *ConfigOptions) Execute(args []string) error {
 	//GlobalConfig
-	ProcessGlobalOptions()
-	b, err := yaml.Marshal(GlobalConfig)
+	config.ProcessGlobalOptions()
+	b, err := yaml.Marshal(config.GlobalConfig)
 	if err != nil {
 		return err
 	}
diff --git a/internal/pkg/commands/orm.go b/internal/pkg/commands/orm.go
index 0ac846a..d09f15d 100644
--- a/internal/pkg/commands/orm.go
+++ b/internal/pkg/commands/orm.go
@@ -261,7 +261,7 @@
 
 // Create a model in XOS given a map of fields
 func CreateModel(conn *grpc.ClientConn, descriptor grpcurl.DescriptorSource, modelName string, fields map[string]interface{}) error {
-	ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Grpc.Timeout)
+	ctx, cancel := GrpcTimeoutContext(context.Background())
 	defer cancel()
 
 	headers := GenerateHeaders()
@@ -292,7 +292,7 @@
 
 // Update a model in XOS given a map of fields
 func UpdateModel(conn *grpc.ClientConn, descriptor grpcurl.DescriptorSource, modelName string, fields map[string]interface{}) error {
-	ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Grpc.Timeout)
+	ctx, cancel := GrpcTimeoutContext(context.Background())
 	defer cancel()
 
 	headers := GenerateHeaders()
@@ -320,7 +320,7 @@
 
 // Get a model from XOS given its ID
 func GetModel(ctx context.Context, conn *grpc.ClientConn, descriptor grpcurl.DescriptorSource, modelName string, id int32) (*dynamic.Message, error) {
-	ctx, cancel := context.WithTimeout(ctx, GlobalConfig.Grpc.Timeout)
+	ctx, cancel := GrpcTimeoutContext(context.Background())
 	defer cancel()
 
 	headers := GenerateHeaders()
@@ -432,7 +432,7 @@
 
 // List all objects of a given model
 func ListModels(ctx context.Context, conn *grpc.ClientConn, descriptor grpcurl.DescriptorSource, modelName string) ([]*dynamic.Message, error) {
-	ctx, cancel := context.WithTimeout(ctx, GlobalConfig.Grpc.Timeout)
+	ctx, cancel := GrpcTimeoutContext(context.Background())
 	defer cancel()
 
 	headers := GenerateHeaders()
@@ -465,7 +465,7 @@
 //   For example,
 //     map[string]string{"name": "==mysite"}
 func FilterModels(ctx context.Context, conn *grpc.ClientConn, descriptor grpcurl.DescriptorSource, modelName string, kind string, queries map[string]string) ([]*dynamic.Message, error) {
-	ctx, cancel := context.WithTimeout(ctx, GlobalConfig.Grpc.Timeout)
+	ctx, cancel := GrpcTimeoutContext(context.Background())
 	defer cancel()
 
 	headers := GenerateHeaders()
@@ -613,7 +613,7 @@
 
 // Get a model from XOS given its ID
 func DeleteModel(conn *grpc.ClientConn, descriptor grpcurl.DescriptorSource, modelName string, id int32) error {
-	ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Grpc.Timeout)
+	ctx, cancel := GrpcTimeoutContext(context.Background())
 	defer cancel()
 
 	headers := GenerateHeaders()
diff --git a/internal/pkg/commands/services.go b/internal/pkg/commands/services.go
index d199ee9..f3372b9 100644
--- a/internal/pkg/commands/services.go
+++ b/internal/pkg/commands/services.go
@@ -55,7 +55,7 @@
 	}
 	defer conn.Close()
 
-	ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Grpc.Timeout)
+	ctx, cancel := GrpcTimeoutContext(context.Background())
 	defer cancel()
 
 	headers := GenerateHeaders()
diff --git a/internal/pkg/commands/status.go b/internal/pkg/commands/status.go
index 802720a..2d62fe3 100644
--- a/internal/pkg/commands/status.go
+++ b/internal/pkg/commands/status.go
@@ -49,7 +49,7 @@
 	}
 	defer conn.Close()
 
-	ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Grpc.Timeout)
+	ctx, cancel := GrpcTimeoutContext(context.Background())
 	defer cancel()
 
 	headers := GenerateHeaders()
diff --git a/internal/pkg/commands/transfer_handler.go b/internal/pkg/commands/transfer_handler.go
index cc18f1b..32b63fb 100644
--- a/internal/pkg/commands/transfer_handler.go
+++ b/internal/pkg/commands/transfer_handler.go
@@ -102,7 +102,7 @@
 }
 
 func UploadFile(conn *grpc.ClientConn, descriptor grpcurl.DescriptorSource, local_name string, uri string, chunkSize int) (*UploadHandler, *dynamic.Message, error) {
-	ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Grpc.Timeout)
+	ctx, cancel := GrpcTimeoutContext(context.Background())
 	defer cancel()
 
 	headers := GenerateHeaders()
@@ -133,7 +133,7 @@
 }
 
 func DownloadFile(conn *grpc.ClientConn, descriptor grpcurl.DescriptorSource, uri string, local_name string) (*DownloadHandler, error) {
-	ctx, cancel := context.WithTimeout(context.Background(), GlobalConfig.Grpc.Timeout)
+	ctx, cancel := GrpcTimeoutContext(context.Background())
 	defer cancel()
 
 	headers := GenerateHeaders()
diff --git a/internal/pkg/config/config.go b/internal/pkg/config/config.go
new file mode 100644
index 0000000..382e0c2
--- /dev/null
+++ b/internal/pkg/config/config.go
@@ -0,0 +1,158 @@
+/*
+ * Portions copyright 2019-present Open Networking Foundation
+ * Original copyright 2019-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 (
+	"fmt"
+	"gopkg.in/yaml.v2"
+	"io/ioutil"
+	"log"
+	"net"
+	"os"
+	"strings"
+	"time"
+)
+
+type OutputType uint8
+
+const (
+	OUTPUT_TABLE OutputType = iota
+	OUTPUT_JSON
+	OUTPUT_YAML
+
+	CORE_VERSION_CONSTRAINT = ">= 3, < 4" // Support XOS major version 3
+)
+
+var CharReplacer = strings.NewReplacer("\\t", "\t", "\\n", "\n")
+
+type GrpcConfigSpec 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 string `yaml:"verify"`
+}
+
+type GlobalConfigSpec struct {
+	Server   string        `yaml:"server"`
+	Username string        `yaml:"username"`
+	Password string        `yaml:"password"`
+	Protoset string        `yaml:"protoset"`
+	Tls      TlsConfigSpec `yaml:"tls"`
+	Grpc     GrpcConfigSpec
+}
+
+var GlobalConfig = GlobalConfigSpec{
+	Server: "localhost",
+	Tls: TlsConfigSpec{
+		UseTls: false,
+	},
+	Grpc: GrpcConfigSpec{
+		Timeout: time.Second * 10,
+	},
+}
+
+var GlobalOptions struct {
+	Config   string `short:"c" long:"config" env:"CORDCONFIG" value-name:"FILE" default:"" description:"Location of client config file"`
+	Server   string `short:"s" long:"server" default:"" value-name:"SERVER:PORT" description:"IP/Host and port of XOS"`
+	Username string `short:"u" long:"username" value-name:"USERNAME" default:"" description:"Username to authenticate with XOS"`
+	Password string `short:"p" long:"password" value-name:"PASSWORD" default:"" description:"Password to authenticate with XOS"`
+	Protoset string `long:"protoset" value-name:"FILENAME" description:"Load protobuf definitions from protoset instead of reflection api"`
+	Debug    bool   `short:"d" long:"debug" description:"Enable debug mode"`
+	UseTLS   bool   `long:"tls" description:"Use TLS"`
+	CACert   string `long:"tlscacert" value-name:"CA_CERT_FILE" description:"Trust certs signed only by this CA"`
+	Cert     string `long:"tlscert" value-name:"CERT_FILE" description:"Path to TLS vertificate file"`
+	Key      string `long:"tlskey" value-name:"KEY_FILE" description:"Path to TLS key file"`
+	Verify   bool   `long:"tlsverify" description:"Use TLS and verify the remote"`
+	Yes      bool   `short:"y" long:"yes" description:"answer yes to any confirmation prompts"`
+}
+
+func ProcessGlobalOptions() {
+	if len(GlobalOptions.Config) == 0 {
+		home, err := os.UserHomeDir()
+		if err != nil {
+			log.Printf("Unable to discover the users home directory: %s\n", err)
+		}
+		GlobalOptions.Config = fmt.Sprintf("%s/.cord/config", home)
+	}
+
+	info, err := os.Stat(GlobalOptions.Config)
+	if err == nil && !info.IsDir() {
+		configFile, err := ioutil.ReadFile(GlobalOptions.Config)
+		if err != nil {
+			log.Printf("configFile.Get err   #%v ", err)
+		}
+		err = yaml.Unmarshal(configFile, &GlobalConfig)
+		if err != nil {
+			log.Fatalf("Unmarshal: %v", err)
+		}
+	}
+
+	// Override from environment
+	//    in particualr, for passing env vars via `go test`
+	env_server, present := os.LookupEnv("CORDCTL_SERVER")
+	if present {
+		GlobalConfig.Server = env_server
+	}
+	env_username, present := os.LookupEnv("CORDCTL_USERNAME")
+	if present {
+		GlobalConfig.Username = env_username
+	}
+	env_password, present := os.LookupEnv("CORDCTL_PASSWORD")
+	if present {
+		GlobalConfig.Password = env_password
+	}
+	env_protoset, present := os.LookupEnv("CORDCTL_PROTOSET")
+	if present {
+		GlobalConfig.Protoset = env_protoset
+	}
+
+	// Override from command line
+	if GlobalOptions.Server != "" {
+		GlobalConfig.Server = GlobalOptions.Server
+	}
+	if GlobalOptions.Username != "" {
+		GlobalConfig.Username = GlobalOptions.Username
+	}
+	if GlobalOptions.Password != "" {
+		GlobalConfig.Password = GlobalOptions.Password
+	}
+	if GlobalOptions.Protoset != "" {
+		GlobalConfig.Protoset = GlobalOptions.Protoset
+	}
+
+	// Generate error messages for required settings
+	if GlobalConfig.Server == "" {
+		log.Fatal("Server is not set. Please update config file or use the -s option")
+	}
+	if GlobalConfig.Username == "" {
+		log.Fatal("Username is not set. Please update config file or use the -u option")
+	}
+	if GlobalConfig.Password == "" {
+		log.Fatal("Password is not set. Please update config file or use the -p option")
+	}
+	//Try to resolve hostname if provided for the server
+	if host, port, err := net.SplitHostPort(GlobalConfig.Server); err == nil {
+		if addrs, err := net.LookupHost(host); err == nil {
+			GlobalConfig.Server = net.JoinHostPort(addrs[0], port)
+		}
+	}
+}
diff --git a/internal/pkg/error/error.go b/internal/pkg/error/error.go
index e35a14a..9377dee 100644
--- a/internal/pkg/error/error.go
+++ b/internal/pkg/error/error.go
@@ -49,6 +49,7 @@
 	"bytes"
 	"fmt"
 	go_errors "github.com/go-errors/errors"
+	"github.com/opencord/cordctl/internal/pkg/config"
 	"google.golang.org/grpc/status"
 	"runtime"
 	"strings"
@@ -329,6 +330,15 @@
 	return fmt.Sprintf("Permission Denied%s. Please verify username and password are correct", f.Obj.Clause())
 }
 
+// Unavailable
+type UnavailableError struct {
+	UserError
+}
+
+func (f UnavailableError) Error() string {
+	return fmt.Sprintf("Server Unavailable%s. Please verify server settings (%s). If correct, this may be a transient failure -- please try again", f.Obj.Clause(), config.GlobalConfig.Server)
+}
+
 // InvalidInputError is a catch-all for user mistakes that aren't covered elsewhere
 type InvalidInputError struct {
 	UserError
@@ -402,6 +412,12 @@
 			cordErr.Encapsulated = err
 			cordErr.AddStackTrace(2)
 			return cordErr
+		case "Unavailable":
+			cordErr := &UnavailableError{}
+			cordErr.Obj = obj
+			cordErr.Encapsulated = err
+			cordErr.AddStackTrace(2)
+			return cordErr
 		case "Unknown":
 			msg := st.Message()
 			if strings.HasPrefix(msg, "Exception calling application: ") {
@@ -413,6 +429,14 @@
 			cordErr.AddStackTrace(2)
 			return cordErr
 		}
+		// Errors encapsulated by grpCurl
+	} else if strings.Contains(err.Error(), "failed to query for service descriptor") &&
+		strings.Contains(err.Error(), "Unavailable") {
+		cordErr := &UnavailableError{}
+		cordErr.Obj = obj
+		cordErr.Encapsulated = err
+		cordErr.AddStackTrace(2)
+		return cordErr
 	}
 
 	return err