-
Notifications
You must be signed in to change notification settings - Fork 283
/
validator.go
231 lines (187 loc) Β· 6.3 KB
/
validator.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
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
package snow
import (
"context"
"fmt"
"reflect"
"strconv"
"strings"
"github.com/pkg/errors"
"github.com/aws/eks-anywhere/pkg/api/v1alpha1"
)
const (
defaultAwsSshKeyName = "eksa-default"
snowballMinSoftwareVersion = 102
minimumVCPU = 2
)
// Validator includes a client registry that maintains a snow device aws client map,
// and a local imds service that is used to fetch metadata of the host instance.
type Validator struct {
// clientRegistry maintains a device aws client mapping.
clientRegistry ClientRegistry
// imds is a local imds client built with the default aws config. This imds client can only
// interact with the local instance metata service.
imds LocalIMDSClient
}
// ValidatorOpt updates an Validator.
type ValidatorOpt func(*Validator)
// WithIMDS returns a ValidatorOpt that sets the imds client.
func WithIMDS(imds LocalIMDSClient) ValidatorOpt {
return func(c *Validator) {
c.imds = imds
}
}
// NewValidator creates a snow validator.
func NewValidator(clientRegistry ClientRegistry, opts ...ValidatorOpt) *Validator {
v := &Validator{
clientRegistry: clientRegistry,
}
for _, o := range opts {
o(v)
}
return v
}
// ValidateEC2SshKeyNameExists validates the ssh key existence in each device in the device list.
func (v *Validator) ValidateEC2SshKeyNameExists(ctx context.Context, m *v1alpha1.SnowMachineConfig) error {
if m.Spec.SshKeyName == "" {
return nil
}
clientMap, err := v.clientRegistry.Get(ctx)
if err != nil {
return err
}
for _, ip := range m.Spec.Devices {
client, ok := clientMap[ip]
if !ok {
return fmt.Errorf("credentials not found for device [%s]", ip)
}
keyExists, err := client.EC2KeyNameExists(ctx, m.Spec.SshKeyName)
if err != nil {
return fmt.Errorf("describing key pair on snow device [%s]: %v", ip, err)
}
if !keyExists {
return fmt.Errorf("aws key pair [%s] does not exist on snow device [deviceIP=%s]", m.Spec.SshKeyName, ip)
}
}
return nil
}
// ValidateEC2ImageExistsOnDevice validates the ami id (if specified) existence in each device in the device list.
func (v *Validator) ValidateEC2ImageExistsOnDevice(ctx context.Context, m *v1alpha1.SnowMachineConfig) error {
if m.Spec.AMIID == "" {
return nil
}
clientMap, err := v.clientRegistry.Get(ctx)
if err != nil {
return err
}
for _, ip := range m.Spec.Devices {
client, ok := clientMap[ip]
if !ok {
return fmt.Errorf("credentials not found for device [%s]", ip)
}
imageExists, err := client.EC2ImageExists(ctx, m.Spec.AMIID)
if err != nil {
return fmt.Errorf("describing image on snow device [%s]: %v", ip, err)
}
if !imageExists {
return fmt.Errorf("aws image [%s] does not exist", m.Spec.AMIID)
}
}
return nil
}
// ValidateDeviceIsUnlocked verifies if all snow devices in the device list are unlocked.
func (v *Validator) ValidateDeviceIsUnlocked(ctx context.Context, m *v1alpha1.SnowMachineConfig) error {
clientMap, err := v.clientRegistry.Get(ctx)
if err != nil {
return err
}
for _, ip := range m.Spec.Devices {
client, ok := clientMap[ip]
if !ok {
return fmt.Errorf("credentials not found for device [%s]", ip)
}
deviceUnlocked, err := client.IsSnowballDeviceUnlocked(ctx)
if err != nil {
return fmt.Errorf("checking unlock status for device [%s]: %v", ip, err)
}
if !deviceUnlocked {
return fmt.Errorf("device [%s] is not unlocked. Please unlock the device before you proceed", ip)
}
}
return nil
}
func validateInstanceTypeInDevice(ctx context.Context, client AwsClient, instanceType, deviceIP string) error {
instanceTypes, err := client.EC2InstanceTypes(ctx)
if err != nil {
return fmt.Errorf("fetching supported instance types for device [%s]: %v", deviceIP, err)
}
for _, it := range instanceTypes {
if instanceType != it.Name {
continue
}
if it.DefaultVCPU != nil && *it.DefaultVCPU < minimumVCPU {
return fmt.Errorf("the instance type [%s] has %d vCPU. Please choose an instance type with at least %d default vCPU", instanceType, *it.DefaultVCPU, minimumVCPU)
}
return nil
}
return fmt.Errorf("the instance type [%s] is not supported in device [%s]", instanceType, deviceIP)
}
// ValidateInstanceType validates whether the instance type is compatible to run in each device.
func (v *Validator) ValidateInstanceType(ctx context.Context, m *v1alpha1.SnowMachineConfig) error {
clientMap, err := v.clientRegistry.Get(ctx)
if err != nil {
return err
}
for _, ip := range m.Spec.Devices {
client, ok := clientMap[ip]
if !ok {
return fmt.Errorf("credentials not found for device [%s]", ip)
}
if err := validateInstanceTypeInDevice(ctx, client, m.Spec.InstanceType, ip); err != nil {
return err
}
}
return nil
}
// ValidateDeviceSoftware validates whether the snow software is compatible to run eks-a in each device.
func (v *Validator) ValidateDeviceSoftware(ctx context.Context, m *v1alpha1.SnowMachineConfig) error {
clientMap, err := v.clientRegistry.Get(ctx)
if err != nil {
return err
}
for _, ip := range m.Spec.Devices {
client, ok := clientMap[ip]
if !ok {
return fmt.Errorf("credentials not found for device [%s]", ip)
}
version, err := client.SnowballDeviceSoftwareVersion(ctx)
if err != nil {
return fmt.Errorf("checking software version for device [%s]: %v", ip, err)
}
versionInt, err := strconv.Atoi(version)
if err != nil {
return fmt.Errorf("checking software version for device [%s]: %v", ip, err)
}
if versionInt < snowballMinSoftwareVersion {
return fmt.Errorf("the software version installed [%s] on device [%s] is below the minimum supported version [%d]", version, ip, snowballMinSoftwareVersion)
}
}
return nil
}
// ValidateControlPlaneIP checks whether the control plane ip is valid for creating a snow cluster.
func (v *Validator) ValidateControlPlaneIP(ctx context.Context, controlPlaneIP string) error {
if v.imds == nil || reflect.ValueOf(v.imds).IsNil() {
return errors.New("imds client is not initialized")
}
instanceIP, err := v.imds.EC2InstanceIP(ctx)
if err != nil {
if strings.Contains(err.Error(), "404") {
// the admin instance is not running inside snow devices or doesn't have a public IP
return nil
}
return fmt.Errorf("fetching host instance ip: %v", err)
}
if controlPlaneIP == instanceIP {
return fmt.Errorf("control plane host ip cannot be same as the admin instance ip")
}
return nil
}