github.com/openshift/installer@v1.4.17/pkg/destroy/powervs/job.go (about) 1 package powervs 2 3 import ( 4 "context" 5 "fmt" 6 "math" 7 "strings" 8 "time" 9 10 "github.com/IBM-Cloud/power-go-client/power/models" 11 "k8s.io/apimachinery/pkg/util/wait" 12 ) 13 14 const jobTypeName = "job" 15 16 // listJobs lists jobs in the vpc. 17 func (o *ClusterUninstaller) listJobs() (cloudResources, error) { 18 var jobs *models.Jobs 19 var job *models.Job 20 var err error 21 22 o.Logger.Debugf("Listing jobs") 23 24 if o.jobClient == nil { 25 result := []cloudResource{} 26 return cloudResources{}.insert(result...), nil 27 } 28 29 ctx, cancel := o.contextWithTimeout() 30 defer cancel() 31 32 select { 33 case <-ctx.Done(): 34 o.Logger.Debugf("listJobs: case <-ctx.Done()") 35 return nil, o.Context.Err() // we're cancelled, abort 36 default: 37 } 38 39 jobs, err = o.jobClient.GetAll() 40 if err != nil { 41 return nil, fmt.Errorf("failed to list jobs: %w", err) 42 } 43 44 result := []cloudResource{} 45 for _, job = range jobs.Jobs { 46 // https://github.com/IBM-Cloud/power-go-client/blob/master/power/models/job.go 47 if strings.Contains(*job.Operation.ID, o.InfraID) { 48 if *job.Status.State == "completed" { 49 continue 50 } 51 o.Logger.Debugf("listJobs: FOUND: %s (%s) (%s)", *job.Operation.ID, *job.ID, *job.Status.State) 52 result = append(result, cloudResource{ 53 key: *job.Operation.ID, 54 name: *job.Operation.ID, 55 status: *job.Status.State, 56 typeName: jobTypeName, 57 id: *job.ID, 58 }) 59 } 60 } 61 62 return cloudResources{}.insert(result...), nil 63 } 64 65 // DeleteJobResult The different states deleting a job can take. 66 type DeleteJobResult int 67 68 const ( 69 // DeleteJobSuccess A job has finished successfully. 70 DeleteJobSuccess DeleteJobResult = iota 71 72 // DeleteJobRunning A job is currently running. 73 DeleteJobRunning 74 75 // DeleteJobError A job has resulted in an error. 76 DeleteJobError 77 ) 78 79 func (o *ClusterUninstaller) deleteJob(item cloudResource) (DeleteJobResult, error) { 80 var job *models.Job 81 var err error 82 83 ctx, cancel := o.contextWithTimeout() 84 defer cancel() 85 86 select { 87 case <-ctx.Done(): 88 o.Logger.Debugf("deleteJob: case <-ctx.Done()") 89 return DeleteJobError, o.Context.Err() // we're cancelled, abort 90 default: 91 } 92 93 job, err = o.jobClient.Get(item.id) 94 if err != nil { 95 o.Logger.Debugf("listJobs: deleteJob: job %q no longer exists", item.name) 96 o.deletePendingItems(item.typeName, []cloudResource{item}) 97 o.Logger.Infof("Deleted Job %q", item.name) 98 return DeleteJobSuccess, nil 99 } 100 101 switch *job.Status.State { 102 case "completed": 103 // err = o.jobClient.Delete(item.id) 104 // if err != nil { 105 // return DeleteJobError, fmt.Errorf("failed to delete job %s: %w", item.name, err) 106 // } 107 108 o.deletePendingItems(item.typeName, []cloudResource{item}) 109 o.Logger.Debugf("Deleting job %q", item.name) 110 111 return DeleteJobSuccess, nil 112 113 case "active": 114 o.Logger.Debugf("Waiting for job %q to delete (status is %q)", item.name, *job.Status.State) 115 return DeleteJobRunning, nil 116 117 case "failed": 118 err = fmt.Errorf("@TODO we cannot query error message inside the job") 119 return DeleteJobError, fmt.Errorf("job %v has failed: %w", item.id, err) 120 121 default: 122 o.Logger.Debugf("Default waiting for job %q to delete (status is %q)", item.name, *job.Status.State) 123 return DeleteJobRunning, nil 124 } 125 } 126 127 // destroyJobs removes all job resources that have a name prefixed 128 // with the cluster's infra ID. 129 func (o *ClusterUninstaller) destroyJobs() error { 130 firstPassList, err := o.listJobs() 131 if err != nil { 132 return err 133 } 134 135 if len(firstPassList.list()) == 0 { 136 return nil 137 } 138 139 items := o.insertPendingItems(jobTypeName, firstPassList.list()) 140 141 ctx, cancel := o.contextWithTimeout() 142 defer cancel() 143 144 for _, item := range items { 145 select { 146 case <-ctx.Done(): 147 o.Logger.Debugf("destroyJobs: case <-ctx.Done()") 148 return o.Context.Err() // we're cancelled, abort 149 default: 150 } 151 152 backoff := wait.Backoff{ 153 Duration: 15 * time.Second, 154 Factor: 1.1, 155 Cap: leftInContext(ctx), 156 Steps: math.MaxInt32} 157 err = wait.ExponentialBackoffWithContext(ctx, backoff, func(context.Context) (bool, error) { 158 result, err2 := o.deleteJob(item) 159 switch result { 160 case DeleteJobSuccess: 161 o.Logger.Debugf("destroyJobs: deleteJob returns DeleteJobSuccess") 162 return true, nil 163 case DeleteJobRunning: 164 o.Logger.Debugf("destroyJobs: deleteJob returns DeleteJobRunning") 165 return false, nil 166 case DeleteJobError: 167 o.Logger.Debugf("destroyJobs: deleteJob returns DeleteJobError: %v", err2) 168 return false, err2 169 default: 170 return false, fmt.Errorf("destroyJobs: deleteJob unknown result enum %v", result) 171 } 172 }) 173 if err != nil { 174 o.Logger.Fatal("destroyJobs: ExponentialBackoffWithContext (destroy) returns ", err) 175 } 176 } 177 178 if items = o.getPendingItems(jobTypeName); len(items) > 0 { 179 for _, item := range items { 180 o.Logger.Debugf("destroyJobs: found %s in pending items", item.name) 181 } 182 return fmt.Errorf("destroyJobs: %d undeleted items pending", len(items)) 183 } 184 185 backoff := wait.Backoff{ 186 Duration: 15 * time.Second, 187 Factor: 1.1, 188 Cap: leftInContext(ctx), 189 Steps: math.MaxInt32} 190 err = wait.ExponentialBackoffWithContext(ctx, backoff, func(context.Context) (bool, error) { 191 secondPassList, err2 := o.listJobs() 192 if err2 != nil { 193 return false, err2 194 } 195 if len(secondPassList) == 0 { 196 // We finally don't see any remaining instances! 197 return true, nil 198 } 199 for _, item := range secondPassList { 200 o.Logger.Debugf("destroyJobs: found %s in second pass", item.name) 201 } 202 return false, nil 203 }) 204 if err != nil { 205 o.Logger.Fatal("destroyJobs: ExponentialBackoffWithContext (list) returns ", err) 206 } 207 208 return nil 209 }