github.com/mirantis/virtlet@v1.5.2-0.20191204181327-1659b8a48e9b/pkg/libvirttools/gc.go (about) 1 /* 2 Copyright 2017 Mirantis 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package libvirttools 18 19 import ( 20 "fmt" 21 "os" 22 "path/filepath" 23 "strings" 24 25 "github.com/Mirantis/virtlet/pkg/blockdev" 26 "github.com/Mirantis/virtlet/pkg/metadata" 27 "github.com/Mirantis/virtlet/pkg/metadata/types" 28 ) 29 30 const ( 31 configFilenameTemplate = "config-*.iso" 32 ) 33 34 // GarbageCollect retrieves from metadata store list of container ids, 35 // passes it to all GC submodules, collecting from them list of 36 // possible errors, which is returned to outer scope 37 func (v *VirtualizationTool) GarbageCollect() (allErrors []error) { 38 ids, fatal, errors := v.retrieveListOfContainerIDs() 39 if errors != nil { 40 allErrors = append(allErrors, errors...) 41 } 42 if fatal { 43 return 44 } 45 46 allErrors = append(allErrors, v.removeOrphanDomains(ids)...) 47 allErrors = append(allErrors, v.removeOrphanRootVolumes(ids)...) 48 allErrors = append(allErrors, v.removeOrphanQcow2Volumes(ids)...) 49 allErrors = append(allErrors, v.removeOrphanConfigImages(ids, configIsoDir)...) 50 allErrors = append(allErrors, v.removeOrphanVirtualBlockDevices(ids, "", "")...) 51 52 return 53 } 54 55 func (v *VirtualizationTool) retrieveListOfContainerIDs() ([]string, bool, []error) { 56 var containerIDs []string 57 58 sandboxes, err := v.metadataStore.ListPodSandboxes(nil) 59 if err != nil { 60 return nil, true, []error{ 61 fmt.Errorf("cannot list pod sandboxes: %v", err), 62 } 63 } 64 65 var allErrors []error 66 for _, sandbox := range sandboxes { 67 if err := v.checkSandboxNetNs(sandbox); err != nil { 68 allErrors = append(allErrors, err) 69 continue 70 } 71 72 containers, err := v.metadataStore.ListPodContainers(sandbox.GetID()) 73 if err != nil { 74 allErrors = append( 75 allErrors, 76 fmt.Errorf( 77 "cannot list containers for pod %s: %v", 78 sandbox.GetID(), 79 err, 80 ), 81 ) 82 continue 83 } 84 for _, container := range containers { 85 containerIDs = append(containerIDs, container.GetID()) 86 } 87 } 88 89 return containerIDs, false, allErrors 90 } 91 92 func (v *VirtualizationTool) checkSandboxNetNs(sandbox metadata.PodSandboxMetadata) error { 93 sinfo, err := sandbox.Retrieve() 94 if err != nil { 95 return err 96 } 97 98 if !v.fsys.IsPathAnNs(sinfo.ContainerSideNetwork.NsPath) { 99 // NS didn't found, need RunSandbox again 100 if err := sandbox.Save(func(s *types.PodSandboxInfo) (*types.PodSandboxInfo, error) { 101 if s != nil { 102 s.State = types.PodSandboxState_SANDBOX_NOTREADY 103 } 104 return s, nil 105 }); err != nil { 106 return err 107 } 108 } 109 110 return nil 111 } 112 113 func inList(list []string, filter func(string) bool) bool { 114 for _, element := range list { 115 if filter(element) { 116 return true 117 } 118 } 119 return false 120 } 121 122 func (v *VirtualizationTool) removeOrphanDomains(ids []string) []error { 123 domains, err := v.domainConn.ListDomains() 124 if err != nil { 125 return []error{fmt.Errorf("cannot list domains: %v", err)} 126 } 127 128 var allErrors []error 129 for _, domain := range domains { 130 name, err := domain.Name() 131 if err != nil { 132 allErrors = append( 133 allErrors, 134 fmt.Errorf("cannot retrieve domain name: %v", err), 135 ) 136 } 137 138 filter := func(id string) bool { 139 return strings.HasPrefix(name, "virtlet-"+id[:13]) 140 } 141 142 if !inList(ids, filter) { 143 d, err := v.DomainConnection().LookupDomainByName(name) 144 if err != nil { 145 allErrors = append( 146 allErrors, 147 fmt.Errorf( 148 "cannot lookup domain '%s' by name: %v", 149 name, 150 err, 151 ), 152 ) 153 continue 154 } 155 156 // ignore errors from stopping domain - it can be (and probably is) already stopped 157 d.Destroy() 158 if err := d.Undefine(); err != nil { 159 allErrors = append( 160 allErrors, 161 fmt.Errorf( 162 "cannot undefine domain '%s': %v", 163 name, 164 err, 165 ), 166 ) 167 } 168 } 169 } 170 171 return allErrors 172 } 173 174 func (v *VirtualizationTool) removeOrphanRootVolumes(ids []string) []error { 175 volumePool, err := v.StoragePool() 176 if err != nil { 177 return []error{fmt.Errorf("cannot get the storage pool: %v", err)} 178 } 179 volumes, err := volumePool.ListVolumes() 180 if err != nil { 181 return []error{fmt.Errorf("cannot list libvirt volumes: %v", err)} 182 } 183 184 var allErrors []error 185 for _, volume := range volumes { 186 path, err := volume.Path() 187 if err != nil { 188 allErrors = append( 189 allErrors, 190 fmt.Errorf("cannot retrieve volume path: %v", err), 191 ) 192 continue 193 } 194 195 filename := filepath.Base(path) 196 filter := func(id string) bool { 197 return "virtlet_root_"+id == filename 198 } 199 200 if strings.HasPrefix(filename, "virtlet_root_") && !inList(ids, filter) { 201 if err := volume.Remove(); err != nil { 202 allErrors = append( 203 allErrors, 204 fmt.Errorf( 205 "cannot remove volume with path '%s': %v", 206 path, 207 err, 208 ), 209 ) 210 } 211 } 212 } 213 214 return allErrors 215 } 216 217 func (v *VirtualizationTool) removeOrphanQcow2Volumes(ids []string) []error { 218 volumePool, err := v.StoragePool() 219 if err != nil { 220 return []error{fmt.Errorf("cannot get the storage pool: %v", err)} 221 } 222 volumes, err := volumePool.ListVolumes() 223 if err != nil { 224 return []error{fmt.Errorf("cannot list domains: %v", err)} 225 } 226 227 var allErrors []error 228 for _, volume := range volumes { 229 path, err := volume.Path() 230 if err != nil { 231 allErrors = append( 232 allErrors, 233 fmt.Errorf("cannot retrieve volume path: %v", err), 234 ) 235 continue 236 } 237 238 filename := filepath.Base(path) 239 filter := func(id string) bool { 240 return strings.HasPrefix(filename, "virtlet-"+id) 241 } 242 243 if strings.HasPrefix(filename, "virtlet-") && !inList(ids, filter) { 244 if err := volume.Remove(); err != nil { 245 allErrors = append( 246 allErrors, 247 fmt.Errorf( 248 "cannot remove volume with path '%s': %v", 249 path, 250 err, 251 ), 252 ) 253 } 254 } 255 } 256 257 return allErrors 258 } 259 260 func (v *VirtualizationTool) removeOrphanConfigImages(ids []string, directory string) []error { 261 files, err := filepath.Glob(filepath.Join(directory, configFilenameTemplate)) 262 if err != nil { 263 return []error{ 264 fmt.Errorf( 265 "error while globbing '%s' files in '%s' directory: %v", 266 configFilenameTemplate, 267 configIsoDir, 268 err, 269 ), 270 } 271 } 272 273 var allErrors []error 274 for _, path := range files { 275 filename := filepath.Base(path) 276 277 filter := func(id string) bool { 278 return filename == "config-"+id+".iso" 279 } 280 281 if strings.HasPrefix(filename, "config-") && strings.HasSuffix(filename, ".iso") && !inList(ids, filter) { 282 if err := os.Remove(path); err != nil { 283 allErrors = append( 284 allErrors, 285 fmt.Errorf( 286 "cannot remove volume with path '%s': %v", 287 path, 288 err, 289 ), 290 ) 291 } 292 } 293 } 294 295 return allErrors 296 } 297 298 func (v *VirtualizationTool) removeOrphanVirtualBlockDevices(ids []string, devPath, sysfsPath string) []error { 299 idsInUse := make(map[string]bool) 300 for _, id := range ids { 301 idsInUse[id] = true 302 } 303 ldh := blockdev.NewLogicalDeviceHandler(v.Commander(), devPath, sysfsPath) 304 dmNames, err := ldh.ListVirtletLogicalDevices() 305 if err != nil { 306 return []error{err} 307 } 308 309 var allErrors []error 310 for _, dmName := range dmNames { 311 if !strings.HasPrefix(dmName, blockdev.VirtletLogicalDevicePrefix) { 312 panic("bad dmname " + dmName) 313 } 314 id := dmName[len(blockdev.VirtletLogicalDevicePrefix):] 315 if idsInUse[id] { 316 continue 317 } 318 if err := ldh.Unmap(dmName); err != nil { 319 allErrors = append( 320 allErrors, 321 fmt.Errorf("error unmapping %q: %v", dmName, err)) 322 } 323 } 324 325 return allErrors 326 }