github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/provider/oci/instance.go (about) 1 // Copyright 2018 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package oci 5 6 import ( 7 "context" 8 "net/http" 9 "strings" 10 "sync" 11 "time" 12 13 "github.com/juju/errors" 14 "github.com/juju/juju/core/status" 15 16 "github.com/juju/juju/core/instance" 17 envcontext "github.com/juju/juju/environs/context" 18 "github.com/juju/juju/environs/instances" 19 "github.com/juju/juju/network" 20 "github.com/juju/juju/provider/oci/common" 21 22 ociCore "github.com/oracle/oci-go-sdk/core" 23 ) 24 25 const ( 26 // MinVolumeSizeMB is the minimum size in MB for a volume or boot disk 27 MinVolumeSizeMB = 51200 28 29 // MaxVolumeSizeMB is the maximum size in MB for a volume or boot disk 30 MaxVolumeSizeMB = 16777216 31 ) 32 33 type ociInstance struct { 34 arch string 35 instType *instances.InstanceType 36 env *Environ 37 mutex sync.Mutex 38 etag *string 39 raw ociCore.Instance 40 } 41 42 type vnicWithIndex struct { 43 Vnic ociCore.Vnic 44 Idx int 45 } 46 47 var _ instances.Instance = (*ociInstance)(nil) 48 var maxPollIterations = 30 49 var pollTime time.Duration = 10 * time.Second 50 51 var statusMap map[ociCore.InstanceLifecycleStateEnum]status.Status = map[ociCore.InstanceLifecycleStateEnum]status.Status{ 52 ociCore.InstanceLifecycleStateProvisioning: status.Provisioning, 53 ociCore.InstanceLifecycleStateRunning: status.Running, 54 ociCore.InstanceLifecycleStateStarting: status.Provisioning, 55 ociCore.InstanceLifecycleStateStopping: status.Running, 56 ociCore.InstanceLifecycleStateStopped: status.Running, 57 ociCore.InstanceLifecycleStateCreatingImage: status.Provisioning, 58 ociCore.InstanceLifecycleStateTerminating: status.Running, 59 ociCore.InstanceLifecycleStateTerminated: status.Running, 60 } 61 62 // newInstance returns a new oracleInstance 63 func newInstance(raw ociCore.Instance, env *Environ) (*ociInstance, error) { 64 if raw.Id == nil { 65 return nil, errors.New( 66 "Instance response does not contain an ID", 67 ) 68 } 69 instance := &ociInstance{ 70 raw: raw, 71 env: env, 72 arch: "amd64", 73 } 74 75 return instance, nil 76 } 77 78 // SetInstance sets the raw property of ociInstance{} 79 // Testing purposes. 80 func (o *ociInstance) SetInstance(inst ociCore.Instance) { 81 o.raw = inst 82 } 83 84 func (o *ociInstance) availabilityZone() string { 85 return *o.raw.AvailabilityDomain 86 } 87 88 // Id implements instances.Instance 89 func (o *ociInstance) Id() instance.Id { 90 return instance.Id(*o.raw.Id) 91 } 92 93 // Status implements instances.Instance 94 func (o *ociInstance) Status(ctx envcontext.ProviderCallContext) instance.Status { 95 if err := o.refresh(); err != nil { 96 common.HandleCredentialError(err, ctx) 97 return instance.Status{} 98 } 99 state, ok := statusMap[o.raw.LifecycleState] 100 if !ok { 101 state = status.Unknown 102 } 103 return instance.Status{ 104 Status: state, 105 Message: strings.ToLower(string(o.raw.LifecycleState)), 106 } 107 } 108 109 func (o *ociInstance) getVnics() ([]vnicWithIndex, error) { 110 attachmentRequest := ociCore.ListVnicAttachmentsRequest{ 111 CompartmentId: o.raw.CompartmentId, 112 InstanceId: o.raw.Id, 113 } 114 attachments, err := o.env.Compute.ListVnicAttachments(context.Background(), attachmentRequest) 115 if err != nil { 116 return nil, errors.Trace(err) 117 } 118 nics := []vnicWithIndex{} 119 for _, val := range attachments.Items { 120 vnicID := val.VnicId 121 request := ociCore.GetVnicRequest{ 122 VnicId: vnicID, 123 } 124 response, err := o.env.Networking.GetVnic(context.Background(), request) 125 if err != nil { 126 return nil, errors.Trace(err) 127 } 128 nics = append(nics, vnicWithIndex{Vnic: response.Vnic, Idx: *val.NicIndex}) 129 } 130 return nics, nil 131 } 132 133 func (o *ociInstance) getAddresses() ([]network.Address, error) { 134 vnics, err := o.getVnics() 135 if err != nil { 136 return nil, errors.Trace(err) 137 } 138 addresses := []network.Address{} 139 140 for _, val := range vnics { 141 if val.Vnic.PrivateIp != nil { 142 privateAddress := network.Address{ 143 Value: *val.Vnic.PrivateIp, 144 Type: network.IPv4Address, 145 Scope: network.ScopeCloudLocal, 146 } 147 addresses = append(addresses, privateAddress) 148 } 149 if val.Vnic.PublicIp != nil { 150 publicAddress := network.Address{ 151 Value: *val.Vnic.PublicIp, 152 Type: network.IPv4Address, 153 Scope: network.ScopePublic, 154 } 155 addresses = append(addresses, publicAddress) 156 } 157 } 158 return addresses, nil 159 } 160 161 // Addresses implements instances.Instance 162 func (o *ociInstance) Addresses(ctx envcontext.ProviderCallContext) ([]network.Address, error) { 163 addresses, err := o.getAddresses() 164 common.HandleCredentialError(err, ctx) 165 return addresses, err 166 } 167 168 func (o *ociInstance) isTerminating() bool { 169 terminatedStatus := ociCore.InstanceLifecycleStateTerminated 170 terminatingStatus := ociCore.InstanceLifecycleStateTerminating 171 if o.raw.LifecycleState == terminatedStatus || o.raw.LifecycleState == terminatingStatus { 172 return true 173 } 174 return false 175 } 176 177 func (o *ociInstance) waitForPublicIP(ctx envcontext.ProviderCallContext) error { 178 iteration := 0 179 startTime := time.Now() 180 for { 181 addresses, err := o.Addresses(ctx) 182 if err != nil { 183 common.HandleCredentialError(err, ctx) 184 return errors.Trace(err) 185 } 186 if iteration >= maxPollIterations { 187 logger.Debugf("could not find a public IP after %s. breaking loop", time.Since(startTime)) 188 break 189 } 190 191 for _, val := range addresses { 192 if val.Scope == network.ScopePublic { 193 logger.Infof("Found public IP: %s", val) 194 return nil 195 } 196 } 197 <-o.env.clock.After(pollTime) 198 iteration++ 199 continue 200 } 201 return errors.NotFoundf("failed to find public IP for instance: %s", *o.raw.Id) 202 } 203 204 func (o *ociInstance) deleteInstance(ctx envcontext.ProviderCallContext) error { 205 err := o.refresh() 206 if errors.IsNotFound(err) { 207 return nil 208 } 209 210 if o.isTerminating() { 211 logger.Debugf("instance %q is alrealy in terminating state", *o.raw.Id) 212 return nil 213 } 214 request := ociCore.TerminateInstanceRequest{ 215 InstanceId: o.raw.Id, 216 IfMatch: o.etag, 217 } 218 response, err := o.env.Compute.TerminateInstance(context.Background(), request) 219 if err != nil && !o.env.isNotFound(response.RawResponse) { 220 common.HandleCredentialError(err, ctx) 221 return err 222 } 223 iteration := 0 224 for { 225 if err := o.refresh(); err != nil { 226 if errors.IsNotFound(err) { 227 break 228 } 229 common.HandleCredentialError(err, ctx) 230 return err 231 } 232 logger.Infof("Waiting for machine to transition to Terminating: %s", o.raw.LifecycleState) 233 if o.isTerminating() { 234 break 235 } 236 if iteration >= maxPollIterations && o.raw.LifecycleState == ociCore.InstanceLifecycleStateRunning { 237 return errors.Errorf("Instance still in running state after %v checks", iteration) 238 } 239 <-o.env.clock.After(pollTime) 240 iteration++ 241 continue 242 } 243 // TODO(gsamfira): cleanup firewall rules 244 // TODO(gsamfira): cleanup VNIC? 245 return nil 246 } 247 248 // hardwareCharacteristics returns the hardware characteristics of the current 249 // instance 250 func (o *ociInstance) hardwareCharacteristics() *instance.HardwareCharacteristics { 251 if o.arch == "" { 252 return nil 253 } 254 255 hc := &instance.HardwareCharacteristics{Arch: &o.arch} 256 if o.instType != nil { 257 hc.Mem = &o.instType.Mem 258 hc.RootDisk = &o.instType.RootDisk 259 hc.CpuCores = &o.instType.CpuCores 260 } 261 262 return hc 263 } 264 265 func (o *ociInstance) waitForMachineStatus(state ociCore.InstanceLifecycleStateEnum, timeout time.Duration) error { 266 timer := o.env.clock.NewTimer(timeout) 267 defer timer.Stop() 268 269 for { 270 select { 271 case <-timer.Chan(): 272 return errors.Errorf( 273 "Timed out waiting for instance to transition from %v to %v", 274 o.raw.LifecycleState, state, 275 ) 276 case <-o.env.clock.After(pollTime): 277 err := o.refresh() 278 if err != nil { 279 return err 280 } 281 if o.raw.LifecycleState == state { 282 return nil 283 } 284 } 285 } 286 } 287 288 func (o *ociInstance) refresh() error { 289 o.mutex.Lock() 290 defer o.mutex.Unlock() 291 request := ociCore.GetInstanceRequest{ 292 InstanceId: o.raw.Id, 293 } 294 response, err := o.env.Compute.GetInstance(context.Background(), request) 295 if err != nil { 296 if response.RawResponse != nil && response.RawResponse.StatusCode == http.StatusNotFound { 297 // If we care about 404 errors, this makes it easier to test using 298 // errors.IsNotFound 299 return errors.NotFoundf("instance %s was not found", *o.raw.Id) 300 } 301 return err 302 } 303 o.etag = response.Etag 304 o.raw = response.Instance 305 return nil 306 }