1// Copyright 2016 VMware, Inc. All Rights Reserved. 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15package management 16 17import ( 18 "bytes" 19 "fmt" 20 "path" 21 "sort" 22 "strings" 23 24 "github.com/vmware/govmomi/object" 25 "github.com/vmware/govmomi/vim25/types" 26 "github.com/vmware/vic/cmd/vic-machine/common" 27 "github.com/vmware/vic/lib/config" 28 "github.com/vmware/vic/lib/constants" 29 "github.com/vmware/vic/pkg/errors" 30 "github.com/vmware/vic/pkg/trace" 31 "github.com/vmware/vic/pkg/vsphere/datastore" 32 "github.com/vmware/vic/pkg/vsphere/vm" 33) 34 35const ( 36 volumeRoot = "volumes" 37 dsScheme = "ds" 38) 39 40func (d *Dispatcher) deleteImages(conf *config.VirtualContainerHostConfigSpec, vch *vm.VirtualMachine) error { 41 defer trace.End(trace.Begin("", d.op)) 42 var errs []string 43 44 uuid, err := vch.UUID(d.op) 45 if err != nil { 46 return err 47 } 48 d.op.Info("Removing image stores") 49 50 for _, imageDir := range conf.ImageStores { 51 imageDSes, err := d.session.Finder.DatastoreList(d.op, imageDir.Host) 52 if err != nil { 53 errs = append(errs, err.Error()) 54 continue 55 } 56 57 if len(imageDSes) != 1 { 58 errs = append(errs, fmt.Sprintf("Found %d datastores with provided datastore path %s. Provided datastore path must identify exactly one datastore.", 59 len(imageDSes), 60 imageDir.String())) 61 62 continue 63 } 64 65 // delete images subfolder 66 imagePath := path.Join(imageDir.Path, constants.StorageParentDir, uuid) 67 if _, err = d.deleteDatastoreFiles(imageDSes[0], imagePath, true); err != nil { 68 errs = append(errs, err.Error()) 69 } 70 71 // delete kvStores subfolder 72 kvPath := path.Join(imageDir.Path, constants.KVStoreFolder) 73 if _, err = d.deleteDatastoreFiles(imageDSes[0], kvPath, true); err != nil { 74 errs = append(errs, err.Error()) 75 } 76 77 dsPath, err := datastore.URLtoDatastore(&imageDir) 78 if err != nil { 79 errs = append(errs, err.Error()) 80 continue 81 } 82 83 children, err := d.getChildren(imageDSes[0], dsPath) 84 if err != nil { 85 if !types.IsFileNotFound(err) { 86 errs = append(errs, err.Error()) 87 } 88 continue 89 } 90 91 if len(children) == 1 { // when image store path is given, we need to delete VIC dir too when it is empty. 92 d.op.Debugf("Removing empty image store parent directory [%s] %s", imageDir.Host, imageDir.Path) 93 if _, err = d.deleteDatastoreFiles(imageDSes[0], imageDir.Path, true); err != nil { 94 errs = append(errs, err.Error()) 95 } 96 } else { 97 d.op.Debug("Image store parent directory not empty, leaving in place. Still contains the following entries: %q", strings.Join(children, ", ")) 98 } 99 } 100 101 if len(errs) > 0 { 102 return errors.New(strings.Join(errs, "\n")) 103 } 104 105 return nil 106} 107 108func (d *Dispatcher) deleteParent(ds *object.Datastore, root string) (bool, error) { 109 defer trace.End(trace.Begin("", d.op)) 110 111 // always forcing delete images 112 return d.deleteDatastoreFiles(ds, root, true) 113} 114 115func (d *Dispatcher) deleteDatastoreFiles(ds *object.Datastore, path string, force bool) (bool, error) { 116 defer trace.End(trace.Begin(fmt.Sprintf("path %q, force %t", path, force), d.op)) 117 118 if ds == nil { 119 err := errors.Errorf("No datastore") 120 return false, err 121 } 122 123 // refuse to delete everything on the datstore, ignore force 124 if path == "" { 125 // #nosec: Errors unhandled. 126 dsn, _ := ds.ObjectName(d.op) 127 msg := fmt.Sprintf("refusing to remove datastore files for path \"\" on datastore %q", dsn) 128 return false, errors.New(msg) 129 } 130 131 var empty bool 132 dsPath := ds.Path(path) 133 134 res, err := d.lsFolder(ds, dsPath) 135 if err != nil { 136 if !types.IsFileNotFound(err) { 137 err = errors.Errorf("Failed to browse folder %q: %s", dsPath, err) 138 return empty, err 139 } 140 d.op.Debugf("Folder %q is not found", dsPath) 141 empty = true 142 return empty, nil 143 } 144 if len(res.File) > 0 && !force { 145 d.op.Debugf("Folder %q is not empty, leave it there", dsPath) 146 return empty, nil 147 } 148 149 m := ds.NewFileManager(d.session.Datacenter, true) 150 if err = d.deleteFilesIteratively(m, ds, dsPath); err != nil { 151 return empty, err 152 } 153 return true, nil 154} 155 156func (d *Dispatcher) isVSAN(ds *object.Datastore) bool { 157 // #nosec: Errors unhandled. 158 dsType, _ := ds.Type(d.op) 159 160 return dsType == types.HostFileSystemVolumeFileSystemTypeVsan 161} 162 163func (d *Dispatcher) deleteFilesIteratively(m *object.DatastoreFileManager, ds *object.Datastore, dsPath string) error { 164 defer trace.End(trace.Begin(dsPath, d.op)) 165 166 // If deleting top level folder fails, remove the child files to empty the folder first 167 err := d.deleteVMFSFiles(m, ds, dsPath) 168 if err != nil { 169 d.op.Debugf("Attempt to delete top level folder %s failed. Remove the children files instead.", dsPath) 170 res, err := d.getSortedChildren(ds, dsPath) 171 if err != nil { 172 if !types.IsFileNotFound(err) { 173 err = errors.Errorf("Failed to browse sub folders %q: %s", dsPath, err) 174 return err 175 } 176 d.op.Debugf("Folder %q is not found", dsPath) 177 return nil 178 } 179 180 for _, path := range res { 181 if err = d.deleteVMFSFiles(m, ds, path); err != nil && !types.IsFileNotFound(err) { 182 return err 183 } 184 } 185 186 return d.deleteVMFSFiles(m, ds, dsPath) 187 } 188 189 return nil 190} 191 192func (d *Dispatcher) deleteVMFSFiles(m *object.DatastoreFileManager, ds *object.Datastore, dsPath string) error { 193 defer trace.End(trace.Begin(dsPath, d.op)) 194 195 for _, ext := range []string{"-delta.vmdk", "-flat.vmdk", "-sesparse.vmdk"} { 196 if strings.HasSuffix(dsPath, ext) { 197 // Skip backing files, as Delete() call below will remove all related vmdk files via DeleteVirtualDisk 198 return nil 199 } 200 } 201 202 if err := m.Delete(d.op, dsPath); err != nil { 203 d.op.Debugf("Failed to delete %q: %s", dsPath, err) 204 return err 205 } 206 207 return nil 208} 209 210// getChildren returns all children under datastore path in unsorted order. (see also getSortedChildren) 211func (d *Dispatcher) getChildren(ds *object.Datastore, dsPath string) ([]string, error) { 212 res, err := d.lsSubFolder(ds, dsPath) 213 if err != nil { 214 return nil, err 215 } 216 var result []string 217 for _, dir := range res.HostDatastoreBrowserSearchResults { 218 for _, f := range dir.File { 219 dsf, ok := f.(*types.FileInfo) 220 if !ok { 221 continue 222 } 223 result = append(result, path.Join(dir.FolderPath, dsf.Path)) 224 } 225 } 226 return result, nil 227} 228 229// getSortedChildren returns all children under datastore path in reversed order. 230func (d *Dispatcher) getSortedChildren(ds *object.Datastore, dsPath string) ([]string, error) { 231 result, err := d.getChildren(ds, dsPath) 232 if err != nil { 233 return nil, err 234 } 235 sort.Sort(sort.Reverse(sort.StringSlice(result))) 236 return result, nil 237} 238 239func (d *Dispatcher) lsSubFolder(ds *object.Datastore, dsPath string) (*types.ArrayOfHostDatastoreBrowserSearchResults, error) { 240 defer trace.End(trace.Begin(dsPath, d.op)) 241 242 spec := types.HostDatastoreBrowserSearchSpec{ 243 MatchPattern: []string{"*"}, 244 } 245 246 b, err := ds.Browser(d.op) 247 if err != nil { 248 return nil, err 249 } 250 251 task, err := b.SearchDatastoreSubFolders(d.op, dsPath, &spec) 252 if err != nil { 253 return nil, err 254 } 255 256 info, err := task.WaitForResult(d.op, nil) 257 if err != nil { 258 return nil, err 259 } 260 261 res := info.Result.(types.ArrayOfHostDatastoreBrowserSearchResults) 262 return &res, nil 263} 264 265func (d *Dispatcher) lsFolder(ds *object.Datastore, dsPath string) (*types.HostDatastoreBrowserSearchResults, error) { 266 defer trace.End(trace.Begin(dsPath, d.op)) 267 268 spec := types.HostDatastoreBrowserSearchSpec{ 269 MatchPattern: []string{"*"}, 270 } 271 272 b, err := ds.Browser(d.op) 273 if err != nil { 274 return nil, err 275 } 276 277 task, err := b.SearchDatastore(d.op, dsPath, &spec) 278 if err != nil { 279 return nil, err 280 } 281 282 info, err := task.WaitForResult(d.op, nil) 283 if err != nil { 284 return nil, err 285 } 286 287 res := info.Result.(types.HostDatastoreBrowserSearchResults) 288 return &res, nil 289} 290 291func (d *Dispatcher) createVolumeStores(conf *config.VirtualContainerHostConfigSpec) error { 292 defer trace.End(trace.Begin("", d.op)) 293 for _, url := range conf.VolumeLocations { 294 295 // NFS volumestores need only make it into the config of the vch 296 if url.Scheme != dsScheme { 297 d.op.Debugf("Skipping nfs volume store for vic-machine creation operation : (%s)", url.String()) 298 continue 299 } 300 301 ds, err := d.session.Finder.Datastore(d.op, url.Host) 302 if err != nil { 303 return errors.Errorf("Could not retrieve datastore with host %q due to error %s", url.Host, err) 304 } 305 306 if url.Path == "/" || url.Path == "" { 307 url.Path = constants.StorageParentDir 308 } 309 310 nds, err := datastore.NewHelper(d.op, d.session, ds, url.Path) 311 if err != nil { 312 return errors.Errorf("Could not create volume store due to error: %s", err) 313 } 314 // FIXME: (GitHub Issue #1301) this is not valid URL syntax and should be translated appropriately when time allows 315 url.Path = nds.RootURL.String() 316 } 317 return nil 318} 319 320// returns # of removed stores 321func (d *Dispatcher) deleteVolumeStoreIfForced(conf *config.VirtualContainerHostConfigSpec, volumeStores *DeleteVolumeStores) (removed int) { 322 defer trace.End(trace.Begin("", d.op)) 323 removed = 0 324 325 deleteVolumeStores := d.force || (volumeStores != nil && *volumeStores == AllVolumeStores) 326 327 if !deleteVolumeStores { 328 if len(conf.VolumeLocations) == 0 { 329 return 0 330 } 331 332 dsVolumeStores := new(bytes.Buffer) 333 nfsVolumeStores := new(bytes.Buffer) 334 for label, url := range conf.VolumeLocations { 335 switch url.Scheme { 336 case common.DsScheme: 337 dsVolumeStores.WriteString(fmt.Sprintf("\t%s: %s\n", label, url.Path)) 338 case common.NfsScheme: 339 nfsVolumeStores.WriteString(fmt.Sprintf("\t%s: %s\n", label, url.Path)) 340 } 341 } 342 d.op.Warnf("Since --force was not specified, the following volume stores will not be removed. Use the vSphere UI or supplied nfs targets to delete content you do not wish to keep.\n vsphere volumestores:\n%s\n NFS volumestores:\n%s\n", dsVolumeStores.String(), nfsVolumeStores.String()) 343 return 0 344 } 345 346 d.op.Info("Removing volume stores") 347 for label, url := range conf.VolumeLocations { 348 349 // NOTE: We cannot remove nfs VolumeStores at vic-machine delete time. We are not guaranteed to be on the correct network for any of the nfs stores. 350 if url.Scheme != dsScheme { 351 d.op.Warnf("Cannot delete VolumeStore (%s). It may not be reachable by vic-machine and has been skipped by the delete process.", url.String()) 352 continue 353 } 354 355 // FIXME: url is being encoded by the portlayer incorrectly, so we have to convert url.Path to the right url.URL object 356 dsURL, err := datastore.ToURL(url.Path) 357 if err != nil { 358 d.op.Warnf("Didn't receive an expected volume store path format: %q", url.Path) 359 continue 360 } 361 362 if dsURL.Path == constants.StorageParentDir { 363 dsURL.Path = path.Join(dsURL.Path, constants.VolumesDir) 364 } 365 366 d.op.Debugf("Provided datastore URL: %q", url.Path) 367 d.op.Debugf("Parsed volume store path: %q", dsURL.Path) 368 d.op.Infof("Deleting volume store %q on Datastore %q at path %q", 369 label, dsURL.Host, dsURL.Path) 370 371 datastores, err := d.session.Finder.DatastoreList(d.op, dsURL.Host) 372 373 if err != nil { 374 d.op.Errorf("Error finding datastore %q: %s", dsURL.Host, err) 375 continue 376 } 377 if len(datastores) > 1 { 378 foundDatastores := new(bytes.Buffer) 379 for _, d := range datastores { 380 foundDatastores.WriteString(fmt.Sprintf("\n%s\n", d.InventoryPath)) 381 } 382 d.op.Errorf("Ambiguous datastore name (%q) provided. Results were: %q", dsURL.Host, foundDatastores) 383 continue 384 } 385 386 datastore := datastores[0] 387 if _, err := d.deleteDatastoreFiles(datastore, dsURL.Path, deleteVolumeStores); err != nil { 388 d.op.Errorf("Failed to delete volume store %q on Datastore %q at path %q", label, dsURL.Host, dsURL.Path) 389 } else { 390 removed++ 391 } 392 } 393 return removed 394 395} 396