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