-
Notifications
You must be signed in to change notification settings - Fork 82
/
helper.go
171 lines (148 loc) · 5.49 KB
/
helper.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
// Copyright (c) 2016, 2018, 2022, Oracle and/or its affiliates. All rights reserved.
// This software is dual-licensed to you under the Universal Permissive License (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl or Apache License 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose either license.
// Helper methods for Oracle Cloud Infrastructure Go SDK Samples
package helpers
import (
"crypto/sha256"
"fmt"
"io"
"io/ioutil"
"log"
"math/rand"
"reflect"
"strings"
"time"
"github.com/oracle/oci-go-sdk/v65/common"
)
// FatalIfError is equivalent to Println() followed by a call to os.Exit(1) if error is not nil
func FatalIfError(err error) {
if err != nil {
log.Fatalln(err.Error())
}
}
// RetryUntilTrueOrError retries a function until the predicate is true or it reaches a timeout.
// The operation is retried at the give frequency
func RetryUntilTrueOrError(operation func() (interface{}, error), predicate func(interface{}) (bool, error), frequency, timeout <-chan time.Time) error {
for {
select {
case <-timeout:
return fmt.Errorf("timeout reached")
case <-frequency:
result, err := operation()
if err != nil {
return err
}
isTrue, err := predicate(result)
if err != nil {
return err
}
if isTrue {
return nil
}
}
}
}
// FindLifecycleFieldValue finds lifecycle value inside the struct based on reflection
func FindLifecycleFieldValue(request interface{}) (string, error) {
val := reflect.ValueOf(request)
if val.Kind() == reflect.Ptr {
if val.IsNil() {
return "", fmt.Errorf("can not unmarshal to response a pointer to nil structure")
}
val = val.Elem()
}
var err error
typ := val.Type()
for i := 0; i < typ.NumField(); i++ {
if err != nil {
return "", err
}
sf := typ.Field(i)
//unexported
if sf.PkgPath != "" {
continue
}
sv := val.Field(i)
if sv.Kind() == reflect.Struct {
lif, err := FindLifecycleFieldValue(sv.Interface())
if err == nil {
return lif, nil
}
}
if !strings.Contains(strings.ToLower(sf.Name), "lifecyclestate") {
continue
}
return sv.String(), nil
}
return "", fmt.Errorf("request does not have a lifecycle field")
}
// CheckLifecycleState returns a function that checks for that a struct has the given lifecycle
func CheckLifecycleState(lifecycleState string) func(interface{}) (bool, error) {
return func(request interface{}) (bool, error) {
fieldLifecycle, err := FindLifecycleFieldValue(request)
if err != nil {
return false, err
}
isEqual := fieldLifecycle == lifecycleState
log.Printf("Current lifecycle state is: %s, waiting for it becomes to: %s", fieldLifecycle, lifecycleState)
return isEqual, nil
}
}
// GetRequestMetadataWithDefaultRetryPolicy returns a requestMetadata with default retry policy
// which will do retry for non-200 status code return back from service
// Notes: not all non-200 status code should do retry, this should be based on specific operation
// such as delete operation followed with get operation will retrun 404 if resource already been
// deleted
func GetRequestMetadataWithDefaultRetryPolicy() common.RequestMetadata {
return common.RequestMetadata{
RetryPolicy: getDefaultRetryPolicy(),
}
}
// GetRequestMetadataWithCustomizedRetryPolicy returns a requestMetadata which will do the retry based on
// input function (retry until the function return false)
func GetRequestMetadataWithCustomizedRetryPolicy(fn func(r common.OCIOperationResponse) bool) common.RequestMetadata {
return common.RequestMetadata{
// since the goal here is to return until the function returns false, we should not be handling
// eventual consistency in a special way, therefore setting handleEventualConsistency to false
RetryPolicy: getExponentialBackoffRetryPolicy(uint(20), fn, false),
}
}
func getDefaultRetryPolicy() *common.RetryPolicy {
// how many times to do the retry
attempts := uint(10)
// retry for all non-200 status code
retryOnAllNon200ResponseCodes := func(r common.OCIOperationResponse) bool {
return !(r.Error == nil && 199 < r.Response.HTTPResponse().StatusCode && r.Response.HTTPResponse().StatusCode < 300)
}
// since we are handling ALL non-2xx error codes, we can set handleEventualConsistency to false
return getExponentialBackoffRetryPolicy(attempts, retryOnAllNon200ResponseCodes, false)
}
func getExponentialBackoffRetryPolicy(n uint, fn func(r common.OCIOperationResponse) bool, handleEventualConsistency bool) *common.RetryPolicy {
policy := common.NewRetryPolicyWithOptions(
// only base off DefaultRetryPolicyWithoutEventualConsistency() if we're not handling eventual consistency
common.WithConditionalOption(!handleEventualConsistency, common.ReplaceWithValuesFromRetryPolicy(common.DefaultRetryPolicyWithoutEventualConsistency())),
common.WithMaximumNumberAttempts(n),
common.WithShouldRetryOperation(fn))
return &policy
}
// GetRandomString returns a random string with length equals to n
func GetRandomString(n int) string {
letters := []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
b := make([]rune, n)
for i := range b {
b[i] = letters[rand.Intn(len(letters))]
}
return string(b)
}
// WriteTempFileOfSize output random content to a file
func WriteTempFileOfSize(filesize int64) (fileName string, fileSize int64) {
hash := sha256.New()
f, _ := ioutil.TempFile("", "OCIGOSDKSampleFile")
ra := rand.New(rand.NewSource(time.Now().UnixNano()))
defer f.Close()
writer := io.MultiWriter(f, hash)
written, _ := io.CopyN(writer, ra, filesize)
fileName = f.Name()
fileSize = written
return
}