github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/state/machineremovals.go (about) 1 // Copyright 2016 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package state 5 6 import ( 7 "sort" 8 "strings" 9 10 "github.com/juju/collections/set" 11 "github.com/juju/errors" 12 "github.com/juju/mgo/v3/bson" 13 "github.com/juju/mgo/v3/txn" 14 "github.com/juju/names/v5" 15 jujutxn "github.com/juju/txn/v3" 16 ) 17 18 // machineRemovalDoc indicates that this machine needs to be removed 19 // and any necessary provider-level cleanup should now be done. 20 type machineRemovalDoc struct { 21 DocID string `bson:"_id"` 22 MachineID string `bson:"machine-id"` 23 } 24 25 func (m *Machine) markForRemovalOps() ([]txn.Op, error) { 26 if m.doc.Life != Dead { 27 return nil, errors.Errorf("machine is not dead") 28 } 29 ops := []txn.Op{{ 30 C: machinesC, 31 Id: m.doc.DocID, 32 Assert: isDeadDoc, 33 }, { 34 C: machineRemovalsC, 35 Id: m.globalKey(), 36 Insert: &machineRemovalDoc{MachineID: m.Id()}, 37 }} 38 return ops, nil 39 } 40 41 // MarkForRemoval requests that this machine be removed after any 42 // needed provider-level cleanup is done. 43 func (m *Machine) MarkForRemoval() (err error) { 44 defer errors.DeferredAnnotatef(&err, "cannot remove machine %s", m.doc.Id) 45 buildTxn := func(attempt int) ([]txn.Op, error) { 46 // Check if it has already been marked first. 47 // If so, we just do nothing and return success. 48 col, close := m.st.db().GetCollection(machineRemovalsC) 49 defer close() 50 51 remCount, err := col.FindId(m.globalKey()).Count() 52 if err != nil { 53 return nil, errors.Trace(err) 54 } 55 if remCount > 0 { 56 return nil, jujutxn.ErrNoOperations 57 } 58 59 if attempt != 0 { 60 if err := m.Refresh(); err != nil { 61 return nil, errors.Trace(err) 62 } 63 } 64 ops, err := m.markForRemovalOps() 65 if err != nil { 66 return nil, errors.Trace(err) 67 } 68 return ops, nil 69 } 70 return m.st.db().Run(buildTxn) 71 } 72 73 // AllMachineRemovals returns (the ids of) all of the machines that 74 // need to be removed but need provider-level cleanup. 75 func (st *State) AllMachineRemovals() ([]string, error) { 76 removals, close := st.db().GetCollection(machineRemovalsC) 77 defer close() 78 79 var docs []machineRemovalDoc 80 err := removals.Find(nil).All(&docs) 81 if err != nil { 82 return nil, errors.Trace(err) 83 } 84 results := make([]string, len(docs)) 85 for i := range docs { 86 results[i] = docs[i].MachineID 87 } 88 return results, nil 89 } 90 91 func (st *State) allMachinesMatching(query bson.D) ([]*Machine, error) { 92 machines, close := st.db().GetCollection(machinesC) 93 defer close() 94 95 var docs []machineDoc 96 err := machines.Find(query).All(&docs) 97 if err != nil { 98 return nil, errors.Trace(err) 99 } 100 results := make([]*Machine, len(docs)) 101 for i, doc := range docs { 102 results[i] = newMachine(st, &doc) 103 } 104 return results, nil 105 } 106 107 func plural(count int) string { 108 if count == 1 { 109 return "" 110 } 111 return "s" 112 } 113 114 func collectMissingMachineIds(expectedIds []string, machines []*Machine) []string { 115 expectedSet := set.NewStrings(expectedIds...) 116 actualSet := set.NewStrings() 117 for _, machine := range machines { 118 actualSet.Add(machine.Id()) 119 } 120 return expectedSet.Difference(actualSet).SortedValues() 121 } 122 123 func checkValidMachineIds(machineIds []string) error { 124 var invalidIds []string 125 for _, id := range machineIds { 126 if !names.IsValidMachine(id) { 127 invalidIds = append(invalidIds, id) 128 } 129 } 130 if len(invalidIds) == 0 { 131 return nil 132 } 133 return errors.Errorf("Invalid machine id%s: %s", 134 plural(len(invalidIds)), 135 strings.Join(invalidIds, ", "), 136 ) 137 } 138 139 func (st *State) completeMachineRemovalsOps(ids []string) ([]txn.Op, error) { 140 removals, err := st.AllMachineRemovals() 141 if err != nil { 142 return nil, errors.Trace(err) 143 } 144 removalSet := set.NewStrings(removals...) 145 query := bson.D{{"machineid", bson.D{{"$in", ids}}}} 146 machinesToRemove, err := st.allMachinesMatching(query) 147 if err != nil { 148 return nil, errors.Trace(err) 149 } 150 151 var ops []txn.Op 152 var missingRemovals []string 153 for _, machine := range machinesToRemove { 154 if !removalSet.Contains(machine.Id()) { 155 missingRemovals = append(missingRemovals, machine.Id()) 156 continue 157 } 158 159 ops = append(ops, txn.Op{ 160 C: machineRemovalsC, 161 Id: machine.globalKey(), 162 Assert: txn.DocExists, 163 Remove: true, 164 }) 165 removeMachineOps, err := machine.removeOps() 166 if err != nil { 167 return nil, errors.Trace(err) 168 } 169 ops = append(ops, removeMachineOps...) 170 } 171 // We should complain about machines that still exist but haven't 172 // been marked for removal. 173 if len(missingRemovals) > 0 { 174 sort.Strings(missingRemovals) 175 return nil, errors.Errorf( 176 "cannot remove machine%s %s: not marked for removal", 177 plural(len(missingRemovals)), 178 strings.Join(missingRemovals, ", "), 179 ) 180 } 181 182 // Log last to reduce the likelihood of repeating the message on 183 // retries. 184 if len(machinesToRemove) < len(ids) { 185 missingMachines := collectMissingMachineIds(ids, machinesToRemove) 186 logger.Debugf("skipping nonexistent machine%s: %s", 187 plural(len(missingMachines)), 188 strings.Join(missingMachines, ", "), 189 ) 190 } 191 192 return ops, nil 193 } 194 195 // CompleteMachineRemovals finishes the removal of the specified 196 // machines. The machines must have been marked for removal 197 // previously. Valid-looking-but-unknown machine ids are ignored so 198 // that this is idempotent. 199 func (st *State) CompleteMachineRemovals(ids ...string) error { 200 if err := checkValidMachineIds(ids); err != nil { 201 return errors.Trace(err) 202 } 203 204 buildTxn := func(int) ([]txn.Op, error) { 205 // We don't need to reget state for subsequent attempts since 206 // completeMachineRemovalsOps gets the removals and the 207 // machines each time anyway. 208 ops, err := st.completeMachineRemovalsOps(ids) 209 if err != nil { 210 return nil, errors.Trace(err) 211 } 212 return ops, nil 213 } 214 return st.db().Run(buildTxn) 215 }