1// Copyright 2017 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 disk
16
17import (
18	"net/url"
19
20	"github.com/vmware/govmomi/task"
21	"github.com/vmware/govmomi/vim25/mo"
22	"github.com/vmware/govmomi/vim25/soap"
23	"github.com/vmware/govmomi/vim25/types"
24	"github.com/vmware/vic/pkg/errors"
25	"github.com/vmware/vic/pkg/trace"
26	"github.com/vmware/vic/pkg/vsphere/datastore"
27	"github.com/vmware/vic/pkg/vsphere/session"
28)
29
30// Vmdk is intended to be embedded by stores that manage VMDK-based data resources
31type Vmdk struct {
32	*Manager
33	*datastore.Helper
34	*session.Session
35}
36
37const (
38	DiskBackendKey = "msg.disk.noBackEnd"
39	LockedFileKey  = "msg.fileio.lock"
40)
41
42// Mount mounts the disk, returning the mount path and the function used to unmount/detaches
43// when no longer in use
44func (v *Vmdk) Mount(op trace.Operation, uri *url.URL, persistent bool) (string, func(), error) {
45	if uri.Scheme != "ds" {
46		return "", nil, errors.New("vmdk path must be a datastore url with \"ds\" scheme")
47	}
48
49	dsPath, err := datastore.PathFromString(uri.Path)
50	if err != nil {
51		return "", nil, err
52	}
53
54	cleanFunc := func() {
55		if err := v.UnmountAndDetach(op, dsPath, persistent); err != nil {
56			op.Errorf("Error cleaning up disk: %s", err.Error())
57		}
58	}
59
60	mountPath, err := v.AttachAndMount(op, dsPath, persistent)
61	return mountPath, cleanFunc, err
62}
63
64func LockedVMDKFilter(vm *mo.VirtualMachine) bool {
65	if vm == nil {
66		return false
67	}
68
69	return vm.Runtime.PowerState == types.VirtualMachinePowerStatePoweredOn
70}
71
72// IsLockedError will determine if the error received is:
73// a. related to a vmdk
74// b. due to the vmdk being locked
75// It will return false in absence of confirmation, meaning incomplete vim errors
76// will return false
77func IsLockedError(err error) bool {
78	disks := LockedDisks(err)
79	//if device is locked, disks will not be nil
80	return len(disks) > 0
81}
82
83// LockedDisks returns locked devices path in the error if it's device lock error
84func LockedDisks(err error) []string {
85	var faultMessage []types.LocalizableMessage
86
87	if soap.IsSoapFault(err) {
88		switch f := soap.ToSoapFault(err).VimFault().(type) {
89		case *types.GenericVmConfigFault:
90			faultMessage = f.FaultMessage
91		}
92	} else if soap.IsVimFault(err) {
93		faultMessage = soap.ToVimFault(err).GetMethodFault().FaultMessage
94	} else {
95		switch err := err.(type) {
96		case task.Error:
97			faultMessage = err.Fault().GetMethodFault().FaultMessage
98		}
99	}
100
101	if faultMessage == nil {
102		return nil
103	}
104
105	lockedFile := false
106	var devices []string
107	for _, message := range faultMessage {
108		switch message.Key {
109		case LockedFileKey:
110			lockedFile = true
111		case DiskBackendKey:
112			for _, arg := range message.Arg {
113				if device, ok := arg.Value.(string); ok {
114					devices = append(devices, device)
115					continue
116				}
117			}
118		}
119	}
120	if lockedFile {
121		// make sure locked devices are returned only when both keys appear in the error
122		return devices
123	}
124	return nil
125}
126