1package structs
2
3import (
4	"errors"
5	"fmt"
6	"strconv"
7	"strings"
8)
9
10const (
11	errNoLeader                   = "No cluster leader"
12	errNotReadyForConsistentReads = "Not ready to serve consistent reads"
13	errNoRegionPath               = "No path to region"
14	errTokenNotFound              = "ACL token not found"
15	errPermissionDenied           = "Permission denied"
16	errNoNodeConn                 = "No path to node"
17	errUnknownMethod              = "Unknown rpc method"
18	errUnknownNomadVersion        = "Unable to determine Nomad version"
19	errNodeLacksRpc               = "Node does not support RPC; requires 0.8 or later"
20	errMissingAllocID             = "Missing allocation ID"
21
22	// Prefix based errors that are used to check if the error is of a given
23	// type. These errors should be created with the associated constructor.
24	ErrUnknownAllocationPrefix = "Unknown allocation"
25	ErrUnknownNodePrefix       = "Unknown node"
26	ErrUnknownJobPrefix        = "Unknown job"
27	ErrUnknownEvaluationPrefix = "Unknown evaluation"
28	ErrUnknownDeploymentPrefix = "Unknown deployment"
29
30	errRPCCodedErrorPrefix = "RPC Error:: "
31
32	errDeploymentTerminalNoCancel    = "can't cancel terminal deployment"
33	errDeploymentTerminalNoFail      = "can't fail terminal deployment"
34	errDeploymentTerminalNoPause     = "can't pause terminal deployment"
35	errDeploymentTerminalNoPromote   = "can't promote terminal deployment"
36	errDeploymentTerminalNoResume    = "can't resume terminal deployment"
37	errDeploymentTerminalNoUnblock   = "can't unblock terminal deployment"
38	errDeploymentTerminalNoRun       = "can't run terminal deployment"
39	errDeploymentTerminalNoSetHealth = "can't set health of allocations for a terminal deployment"
40	errDeploymentRunningNoUnblock    = "can't unblock running deployment"
41)
42
43var (
44	ErrNoLeader                   = errors.New(errNoLeader)
45	ErrNotReadyForConsistentReads = errors.New(errNotReadyForConsistentReads)
46	ErrNoRegionPath               = errors.New(errNoRegionPath)
47	ErrTokenNotFound              = errors.New(errTokenNotFound)
48	ErrPermissionDenied           = errors.New(errPermissionDenied)
49	ErrNoNodeConn                 = errors.New(errNoNodeConn)
50	ErrUnknownMethod              = errors.New(errUnknownMethod)
51	ErrUnknownNomadVersion        = errors.New(errUnknownNomadVersion)
52	ErrNodeLacksRpc               = errors.New(errNodeLacksRpc)
53	ErrMissingAllocID             = errors.New(errMissingAllocID)
54
55	ErrUnknownNode = errors.New(ErrUnknownNodePrefix)
56
57	ErrDeploymentTerminalNoCancel    = errors.New(errDeploymentTerminalNoCancel)
58	ErrDeploymentTerminalNoFail      = errors.New(errDeploymentTerminalNoFail)
59	ErrDeploymentTerminalNoPause     = errors.New(errDeploymentTerminalNoPause)
60	ErrDeploymentTerminalNoPromote   = errors.New(errDeploymentTerminalNoPromote)
61	ErrDeploymentTerminalNoResume    = errors.New(errDeploymentTerminalNoResume)
62	ErrDeploymentTerminalNoUnblock   = errors.New(errDeploymentTerminalNoUnblock)
63	ErrDeploymentTerminalNoRun       = errors.New(errDeploymentTerminalNoRun)
64	ErrDeploymentTerminalNoSetHealth = errors.New(errDeploymentTerminalNoSetHealth)
65	ErrDeploymentRunningNoUnblock    = errors.New(errDeploymentRunningNoUnblock)
66
67	ErrCSIClientRPCIgnorable = errors.New("CSI client error (ignorable)")
68	ErrCSIClientRPCRetryable = errors.New("CSI client error (retryable)")
69)
70
71// IsErrNoLeader returns whether the error is due to there being no leader.
72func IsErrNoLeader(err error) bool {
73	return err != nil && strings.Contains(err.Error(), errNoLeader)
74}
75
76// IsErrNoRegionPath returns whether the error is due to there being no path to
77// the given region.
78func IsErrNoRegionPath(err error) bool {
79	return err != nil && strings.Contains(err.Error(), errNoRegionPath)
80}
81
82// IsErrTokenNotFound returns whether the error is due to the passed token not
83// being resolvable.
84func IsErrTokenNotFound(err error) bool {
85	return err != nil && strings.Contains(err.Error(), errTokenNotFound)
86}
87
88// IsErrPermissionDenied returns whether the error is due to the operation not
89// being allowed due to lack of permissions.
90func IsErrPermissionDenied(err error) bool {
91	return err != nil && strings.Contains(err.Error(), errPermissionDenied)
92}
93
94// IsErrNoNodeConn returns whether the error is due to there being no path to
95// the given node.
96func IsErrNoNodeConn(err error) bool {
97	return err != nil && strings.Contains(err.Error(), errNoNodeConn)
98}
99
100// IsErrUnknownMethod returns whether the error is due to the operation not
101// being allowed due to lack of permissions.
102func IsErrUnknownMethod(err error) bool {
103	return err != nil && strings.Contains(err.Error(), errUnknownMethod)
104}
105
106func IsErrRPCCoded(err error) bool {
107	return err != nil && strings.HasPrefix(err.Error(), errRPCCodedErrorPrefix)
108}
109
110// NewErrUnknownAllocation returns a new error caused by the allocation being
111// unknown.
112func NewErrUnknownAllocation(allocID string) error {
113	return fmt.Errorf("%s %q", ErrUnknownAllocationPrefix, allocID)
114}
115
116// NewErrUnknownNode returns a new error caused by the node being unknown.
117func NewErrUnknownNode(nodeID string) error {
118	return fmt.Errorf("%s %q", ErrUnknownNodePrefix, nodeID)
119}
120
121// NewErrUnknownJob returns a new error caused by the job being unknown.
122func NewErrUnknownJob(jobID string) error {
123	return fmt.Errorf("%s %q", ErrUnknownJobPrefix, jobID)
124}
125
126// NewErrUnknownEvaluation returns a new error caused by the evaluation being
127// unknown.
128func NewErrUnknownEvaluation(evaluationID string) error {
129	return fmt.Errorf("%s %q", ErrUnknownEvaluationPrefix, evaluationID)
130}
131
132// NewErrUnknownDeployment returns a new error caused by the deployment being
133// unknown.
134func NewErrUnknownDeployment(deploymentID string) error {
135	return fmt.Errorf("%s %q", ErrUnknownDeploymentPrefix, deploymentID)
136}
137
138// IsErrUnknownAllocation returns whether the error is due to an unknown
139// allocation.
140func IsErrUnknownAllocation(err error) bool {
141	return err != nil && strings.Contains(err.Error(), ErrUnknownAllocationPrefix)
142}
143
144// IsErrUnknownNode returns whether the error is due to an unknown
145// node.
146func IsErrUnknownNode(err error) bool {
147	return err != nil && strings.Contains(err.Error(), ErrUnknownNodePrefix)
148}
149
150// IsErrUnknownJob returns whether the error is due to an unknown
151// job.
152func IsErrUnknownJob(err error) bool {
153	return err != nil && strings.Contains(err.Error(), ErrUnknownJobPrefix)
154}
155
156// IsErrUnknownEvaluation returns whether the error is due to an unknown
157// evaluation.
158func IsErrUnknownEvaluation(err error) bool {
159	return err != nil && strings.Contains(err.Error(), ErrUnknownEvaluationPrefix)
160}
161
162// IsErrUnknownDeployment returns whether the error is due to an unknown
163// deployment.
164func IsErrUnknownDeployment(err error) bool {
165	return err != nil && strings.Contains(err.Error(), ErrUnknownDeploymentPrefix)
166}
167
168// IsErrUnknownNomadVersion returns whether the error is due to Nomad being
169// unable to determine the version of a node.
170func IsErrUnknownNomadVersion(err error) bool {
171	return err != nil && strings.Contains(err.Error(), errUnknownNomadVersion)
172}
173
174// IsErrNodeLacksRpc returns whether error is due to a Nomad server being
175// unable to connect to a client node because the client is too old (pre-v0.8).
176func IsErrNodeLacksRpc(err error) bool {
177	return err != nil && strings.Contains(err.Error(), errNodeLacksRpc)
178}
179
180// NewErrRPCCoded wraps an RPC error with a code to be converted to HTTP status
181// code
182func NewErrRPCCoded(code int, msg string) error {
183	return fmt.Errorf("%s%d,%s", errRPCCodedErrorPrefix, code, msg)
184}
185
186// NewErrRPCCoded wraps an RPC error with a code to be converted to HTTP status
187// code
188func NewErrRPCCodedf(code int, format string, args ...interface{}) error {
189	msg := fmt.Sprintf(format, args...)
190	return fmt.Errorf("%s%d,%s", errRPCCodedErrorPrefix, code, msg)
191}
192
193// CodeFromRPCCodedErr returns the code and message of error if it's an RPC error
194// created through NewErrRPCCoded function.  Returns `ok` false if error is not
195// an rpc error
196func CodeFromRPCCodedErr(err error) (code int, msg string, ok bool) {
197	if err == nil || !strings.HasPrefix(err.Error(), errRPCCodedErrorPrefix) {
198		return 0, "", false
199	}
200
201	headerLen := len(errRPCCodedErrorPrefix)
202	parts := strings.SplitN(err.Error()[headerLen:], ",", 2)
203	if len(parts) != 2 {
204		return 0, "", false
205	}
206
207	code, err = strconv.Atoi(parts[0])
208	if err != nil {
209		return 0, "", false
210	}
211
212	return code, parts[1], true
213}
214