github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/provider/oracle/instance.go (about) 1 // Copyright 2017 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package oracle 5 6 import ( 7 "strings" 8 "sync" 9 "time" 10 11 "github.com/juju/errors" 12 oci "github.com/juju/go-oracle-cloud/api" 13 ociCommon "github.com/juju/go-oracle-cloud/common" 14 "github.com/juju/go-oracle-cloud/response" 15 16 "github.com/juju/juju/core/instance" 17 "github.com/juju/juju/core/status" 18 "github.com/juju/juju/environs/config" 19 "github.com/juju/juju/environs/context" 20 "github.com/juju/juju/environs/instances" 21 "github.com/juju/juju/network" 22 oraclenetwork "github.com/juju/juju/provider/oracle/network" 23 ) 24 25 // oracleInstance implements the instances.Instance interface 26 type oracleInstance struct { 27 // name of the instance, generated after the vm creation 28 name string 29 // status holds the status of the instance 30 status instance.Status 31 // machine will hold the raw instance details obtained from 32 // the provider 33 machine response.Instance 34 // arch will hold the architecture information of the instance 35 arch *string 36 instType *instances.InstanceType 37 mutex *sync.Mutex 38 env *OracleEnviron 39 } 40 41 // hardwareCharacteristics returns the hardware characteristics of the current 42 // instance 43 func (o *oracleInstance) hardwareCharacteristics() *instance.HardwareCharacteristics { 44 if o.arch == nil { 45 return nil 46 } 47 48 hc := &instance.HardwareCharacteristics{Arch: o.arch} 49 if o.instType != nil { 50 hc.Mem = &o.instType.Mem 51 hc.RootDisk = &o.instType.RootDisk 52 hc.CpuCores = &o.instType.CpuCores 53 } 54 55 return hc 56 } 57 58 // extractInstanceIDFromMachineName will return the hostname of the machine 59 // identified by the provider ID. In the Oracle compute cloud the provider 60 // IDs of the instances has the following format: 61 // /Compute-tenant_domain/tenant_username/instance_hostname/instance_UUID 62 func extractInstanceIDFromMachineName(id string) (instance.Id, error) { 63 var instId instance.Id 64 name := strings.Split(id, "/") 65 if len(name) < 4 { 66 return instId, errors.Errorf("invalid instance name: %s", id) 67 } 68 instId = instance.Id(name[3]) 69 return instId, nil 70 } 71 72 // newInstance returns a new oracleInstance 73 func newInstance(params response.Instance, env *OracleEnviron) (*oracleInstance, error) { 74 if params.Name == "" { 75 return nil, errors.New( 76 "Instance response does not contain a name", 77 ) 78 } 79 name, err := extractInstanceIDFromMachineName(params.Name) 80 if err != nil { 81 return nil, err 82 } 83 mutex := &sync.Mutex{} 84 instance := &oracleInstance{ 85 name: string(name), 86 status: instance.Status{ 87 Status: status.Status(params.State), 88 Message: "", 89 }, 90 machine: params, 91 mutex: mutex, 92 env: env, 93 } 94 95 return instance, nil 96 } 97 98 // Id is defined on the instances.Instance interface. 99 func (o *oracleInstance) Id() instance.Id { 100 if o.machine.Name != "" { 101 name, err := extractInstanceIDFromMachineName(o.machine.Name) 102 if err != nil { 103 return instance.Id(o.machine.Name) 104 } 105 return name 106 } 107 108 return instance.Id(o.name) 109 } 110 111 // Status is defined on the instances.Instance interface. 112 func (o *oracleInstance) Status(ctx context.ProviderCallContext) instance.Status { 113 return o.status 114 } 115 116 // StorageAttachments returns the storage that was attached in the moment 117 // of instance creation. This storage cannot be detached dynamically. 118 // this is also needed if you wish to determine the free disk index 119 // you can use when attaching a new disk 120 func (o *oracleInstance) StorageAttachments() []response.Storage { 121 o.mutex.Lock() 122 defer o.mutex.Unlock() 123 return o.machine.Storage_attachments 124 } 125 126 // refresh refreshes the instance raw details from the oracle api 127 // this method is mutex protected 128 func (o *oracleInstance) refresh() error { 129 o.mutex.Lock() 130 defer o.mutex.Unlock() 131 132 machine, err := o.env.client.InstanceDetails(o.machine.Name) 133 // if the request failed for any reason 134 // we should not update the information and 135 // let the old one persist 136 if err != nil { 137 return err 138 } 139 140 o.machine = machine 141 return nil 142 } 143 144 // waitForMachineStatus will ping the machine status until the timeout 145 // duration is reached or an error appeared 146 func (o *oracleInstance) waitForMachineStatus(state ociCommon.InstanceState, timeout time.Duration) error { 147 timer := o.env.clock.NewTimer(timeout) 148 defer timer.Stop() 149 150 for { 151 select { 152 case <-timer.Chan(): 153 return errors.Errorf( 154 "Timed out waiting for instance to transition from %v to %v", 155 o.machine.State, state, 156 ) 157 case <-o.env.clock.After(10 * time.Second): 158 err := o.refresh() 159 if err != nil { 160 return err 161 } 162 if o.machine.State == state { 163 return nil 164 } 165 } 166 } 167 } 168 169 // delete will delete the instance and attempt to cleanup any instance related 170 // resources 171 func (o *oracleInstance) deleteInstanceAndResources(cleanup bool) error { 172 if cleanup { 173 err := o.disassociatePublicIps(true) 174 if err != nil { 175 return err 176 } 177 } 178 179 if err := o.env.client.DeleteInstance(o.machine.Name); err != nil { 180 return errors.Trace(err) 181 } 182 183 if cleanup { 184 // Wait for instance to be deleted. The oracle API does not allow us to 185 // delete a security list if there is still a VM associated with it. 186 iteration := 0 187 for { 188 if instance, err := o.env.client.InstanceDetails(o.machine.Name); !oci.IsNotFound(err) { 189 if instance.State == ociCommon.StateError { 190 logger.Warningf("Instance %s entered error state", o.machine.Name) 191 break 192 } 193 if iteration >= 30 && instance.State == ociCommon.StateRunning { 194 logger.Warningf("Instance still in running state after %v checks. breaking loop", iteration) 195 break 196 } 197 if oci.IsInternalApi(err) { 198 logger.Errorf("got internal server error from API: %q", err) 199 } 200 <-o.env.clock.After(1 * time.Second) 201 iteration++ 202 continue 203 } 204 logger.Debugf("Machine %v successfully deleted", o.machine.Name) 205 break 206 } 207 208 // 209 // seclist, vnicset, secrules, and acl created with 210 // StartInstanceParams.InstanceConfig.MachineId, 211 // convert o.Id() to machineId for deletion. 212 // o.Id() returns a string in hostname form. 213 tag, err := o.env.namespace.MachineTag(string(o.Id())) 214 if err != nil { 215 return errors.Annotatef(err, "failed to get a machine tag to complete cleanup of instance") 216 } 217 machineId := tag.Id() 218 219 // the VM association is now gone, now we can delete the 220 // machine sec list 221 logger.Debugf("deleting seclist for instance: %s", machineId) 222 if err := o.env.DeleteMachineSecList(machineId); err != nil { 223 logger.Errorf("failed to delete seclist: %s", err) 224 if !oci.IsMethodNotAllowed(err) { 225 return errors.Trace(err) 226 } 227 } 228 logger.Debugf("deleting vnic set for instance: %s", machineId) 229 if err := o.env.DeleteMachineVnicSet(machineId); err != nil { 230 logger.Errorf("failed to delete vnic set: %s", err) 231 if !oci.IsMethodNotAllowed(err) { 232 return errors.Trace(err) 233 } 234 } 235 } 236 return nil 237 } 238 239 // unusedPublicIps returns a slice of IpReservation that are currently not used 240 func (o *oracleInstance) unusedPublicIps() ([]response.IpReservation, error) { 241 filter := []oci.Filter{ 242 { 243 Arg: "permanent", 244 Value: "true", 245 }, 246 { 247 Arg: "used", 248 Value: "false", 249 }, 250 } 251 252 res, err := o.env.client.AllIpReservations(filter) 253 if err != nil { 254 return nil, err 255 } 256 257 return res.Result, nil 258 } 259 260 // associatePublicIP associates a public IP with the current instance 261 func (o *oracleInstance) associatePublicIP() error { 262 // return all unused public IPs 263 unusedIps, err := o.unusedPublicIps() 264 if err != nil { 265 return err 266 } 267 268 for _, val := range unusedIps { 269 assocPoolName := ociCommon.NewIPPool( 270 ociCommon.IPPool(val.Name), 271 ociCommon.IPReservationType, 272 ) 273 // create the association for it 274 if _, err := o.env.client.CreateIpAssociation( 275 assocPoolName, 276 o.machine.Vcable_id, 277 ); err != nil { 278 if oci.IsBadRequest(err) { 279 // the IP probably got allocated after we fetched it 280 // from the API. Move on to the next one. 281 continue 282 } 283 284 return err 285 } else { 286 if _, err = o.env.client.UpdateIpReservation(val.Name, "", val.Parentpool, val.Permanent, o.machine.Tags); err != nil { 287 // we don't really want to terminate execution if we fail to update 288 // tags 289 logger.Errorf("failed to update IP reservation tags: %q", err) 290 } 291 return nil 292 } 293 } 294 295 // no unused IP reservations found. Allocate a new one. 296 reservation, err := o.env.client.CreateIpReservation( 297 o.machine.Name, ociCommon.PublicIPPool, true, o.machine.Tags) 298 if err != nil { 299 return err 300 } 301 302 // compose IP pool name 303 assocPoolName := ociCommon.NewIPPool( 304 ociCommon.IPPool(reservation.Name), 305 ociCommon.IPReservationType, 306 ) 307 if _, err := o.env.client.CreateIpAssociation( 308 assocPoolName, 309 o.machine.Vcable_id, 310 ); err != nil { 311 return err 312 } 313 314 return nil 315 } 316 317 // dissasociatePublicIps disassociates the public IP address from the current instance. 318 // Optionally, the remove flag will also remove the IP reservation after the IP was disassociated 319 func (o *oracleInstance) disassociatePublicIps(remove bool) error { 320 associations, err := o.publicAddressesAssociations() 321 if err != nil { 322 return err 323 } 324 325 for _, ipAssoc := range associations { 326 reservation := ipAssoc.Reservation 327 name := ipAssoc.Name 328 if err := o.env.client.DeleteIpAssociation(name); err != nil { 329 if oci.IsNotFound(err) { 330 continue 331 } 332 333 return err 334 } 335 336 if remove { 337 if err := o.env.client.DeleteIpReservation(reservation); err != nil { 338 if oci.IsNotFound(err) { 339 return nil 340 } 341 return err 342 } 343 } 344 } 345 346 return nil 347 } 348 349 // publicAddressesAssociations returns a slice of all IP associations for the current instance 350 func (o *oracleInstance) publicAddressesAssociations() ([]response.IpAssociation, error) { 351 o.mutex.Lock() 352 defer o.mutex.Unlock() 353 354 filter := []oci.Filter{ 355 { 356 Arg: "vcable", 357 Value: string(o.machine.Vcable_id), 358 }, 359 } 360 361 assoc, err := o.env.client.AllIpAssociations(filter) 362 if err != nil { 363 return nil, errors.Trace(err) 364 } 365 366 return assoc.Result, nil 367 } 368 369 // Addresses is defined on the instances.Instance interface. 370 func (o *oracleInstance) Addresses(ctx context.ProviderCallContext) ([]network.Address, error) { 371 addresses := []network.Address{} 372 373 ips, err := o.publicAddressesAssociations() 374 if err != nil { 375 return nil, errors.Trace(err) 376 } 377 378 if len(o.machine.Attributes.Network) > 0 { 379 for name, val := range o.machine.Attributes.Network { 380 if _, ip, err := oraclenetwork.GetMacAndIP(val.Address); err == nil { 381 address := network.NewScopedAddress(ip, network.ScopeCloudLocal) 382 addresses = append(addresses, address) 383 } else { 384 logger.Errorf("failed to get IP address for NIC %q: %q", name, err) 385 } 386 } 387 } 388 389 for _, val := range ips { 390 address := network.NewScopedAddress(val.Ip, network.ScopePublic) 391 addresses = append(addresses, address) 392 } 393 394 return addresses, nil 395 } 396 397 // OpenPorts is defined on the instances.Instance interface. 398 func (o *oracleInstance) OpenPorts(ctx context.ProviderCallContext, machineId string, rules []network.IngressRule) error { 399 if o.env.Config().FirewallMode() != config.FwInstance { 400 return errors.Errorf( 401 "invalid firewall mode %q for opening ports on instance", 402 o.env.Config().FirewallMode(), 403 ) 404 } 405 406 return o.env.OpenPortsOnInstance(ctx, machineId, rules) 407 } 408 409 // ClosePorts is defined on the instances.Instance interface. 410 func (o *oracleInstance) ClosePorts(ctx context.ProviderCallContext, machineId string, rules []network.IngressRule) error { 411 if o.env.Config().FirewallMode() != config.FwInstance { 412 return errors.Errorf( 413 "invalid firewall mode %q for closing ports on instance", 414 o.env.Config().FirewallMode(), 415 ) 416 } 417 418 return o.env.ClosePortsOnInstance(ctx, machineId, rules) 419 } 420 421 // IngressRules is defined on the instances.Instance interface. 422 func (o *oracleInstance) IngressRules(ctx context.ProviderCallContext, machineId string) ([]network.IngressRule, error) { 423 return o.env.MachineIngressRules(ctx, machineId) 424 }