Skip to content

Commit dd45cac

Browse files
committed
VMware vSphere Driver
This patch introduces the new VMware vSphere VMDK Driver - "vmdk". The driver uses VMCI Sockets (https://www.vmware.com/support/developer/vmci-sdk/) to communicate directly with a vSphere host, removing the need for the sizeable VMware SDK for Go library dependency.
1 parent c676347 commit dd45cac

21 files changed

Lines changed: 2310 additions & 3 deletions

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
*.d
88
*.out
99
got
10+
gob
1011
libstorage.paw
1112
.site/
1213
site/
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
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+
15+
// Shared info (magic, err. codes, etc) on vSocket command channel
16+
17+
#ifndef _CONNECTION_TYPES_H_
18+
#define _CONNECTION_TYPES_H_
19+
20+
#define MAGIC 0xbadbeef
21+
22+
// -1 always indicates failure
23+
#define CONN_FAILURE (-1)
24+
25+
// 0 is usually success. Note: sometimes we return socket FD on success
26+
#define CONN_SUCCESS (0)
27+
28+
// First non privileged port
29+
#define START_NON_PRIVILEGED_PORT 1024
30+
31+
/*
32+
* Check and set errno helper
33+
* Useful when send/recv gets us less than we wanted, and we want to set errno
34+
* for the caller to know about the protocol Issue
35+
*/
36+
#define CHECK_ERRNO(_ret) {if (_ret >= 0 && errno == 0) { errno = EBADMSG; }}
37+
38+
/*
39+
* This function acquires and returns address family for vSockets.
40+
* On failure returns -1 an sets errno (if not set by VMCISock_GetAFValue ())
41+
*
42+
* The address family for vSockets must be acquired, it is not static.
43+
* The code opens and keeps FD to /dev/vsock to indicate to the kernel
44+
* that VMCI driver is used by this process.
45+
* Needs to be called once per process.
46+
* <af> is expected to be closed by process completion
47+
*/
48+
static inline int
49+
vsock_get_family(void)
50+
{
51+
static int af = -1;
52+
53+
errno = 0;
54+
if (af == -1) { // TODO: for multi-thread will need a lock. Issue #35
55+
af = VMCISock_GetAFValue();
56+
}
57+
if (af == -1 && errno == 0) {
58+
errno = EAFNOSUPPORT; // report "family not supported" upstairs
59+
}
60+
return af;
61+
}
62+
63+
#endif // _CONNECTION_TYPES_H_

drivers/storage/vmw/vmci/vmci.go

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
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+
15+
// The default (ESX) implementation of the VmdkCmdRunner interface.
16+
// This implementation sends synchronous commands to and receives responses
17+
// from ESX.
18+
19+
// +build linux darwin
20+
// +build !libstorage_storage_driver libstorage_storage_driver_vmdk libstorage_storage_driver_photon
21+
22+
package vmci
23+
24+
import (
25+
"encoding/json"
26+
"errors"
27+
"fmt"
28+
"os"
29+
"sync"
30+
"syscall"
31+
"time"
32+
"unsafe"
33+
34+
log "github.com/Sirupsen/logrus"
35+
)
36+
37+
/*
38+
#include "vmci_client.h"
39+
*/
40+
import "C"
41+
42+
// EsxVmdkCmd struct - empty , we use it only to implement VmdkCmdRunner
43+
// interface
44+
type EsxVmdkCmd struct {
45+
Mtx *sync.Mutex // For serialization of Run comand/response
46+
}
47+
48+
const (
49+
commBackendName string = "vsocket"
50+
maxRetryCount = 5
51+
// Server side understand protocol version. If you are changing
52+
// client/server protocol we use over VMCI, PLEASE DO NOT FORGET TO CHANGE
53+
// IT FOR SERVER in file <vmdk_ops.py> !
54+
clientProtocolVersion = "2"
55+
)
56+
57+
// A request to be passed to ESX service
58+
type requestToVmci struct {
59+
Ops string `json:"cmd"`
60+
Details VolumeInfo `json:"details"`
61+
Version string `json:"version,omitempty"`
62+
}
63+
64+
// VolumeInfo we get about the volume from upstairs
65+
type VolumeInfo struct {
66+
Name string `json:"Name"`
67+
Options map[string]string `json:"Opts,omitempty"`
68+
}
69+
70+
type vmciError struct {
71+
Error string `json:",omitempty"`
72+
}
73+
74+
// EsxPort used to connect to ESX, passed in as command line param
75+
var EsxPort = 1019
76+
77+
// Run command Guest VM requests on ESX via vmdkops_serv.py listening on
78+
// vSocket
79+
// *
80+
// * For each request:
81+
// * - Establishes a vSocket connection
82+
// * - Sends json string up to ESX
83+
// * - waits for reply and returns resulting JSON or an error
84+
func (vmdkCmd EsxVmdkCmd) Run(
85+
cmd string, name string, opts map[string]string) ([]byte, error) {
86+
87+
vmdkCmd.Mtx.Lock()
88+
defer vmdkCmd.Mtx.Unlock()
89+
protocolVersion := os.Getenv("VDVS_TEST_PROTOCOL_VERSION")
90+
log.Debugf("Run get request: version=%s", protocolVersion)
91+
if protocolVersion == "" {
92+
protocolVersion = clientProtocolVersion
93+
}
94+
jsonStr, err := json.Marshal(&requestToVmci{
95+
Ops: cmd,
96+
Details: VolumeInfo{Name: name, Options: opts},
97+
Version: protocolVersion})
98+
if err != nil {
99+
return nil, fmt.Errorf("Failed to marshal json: %v", err)
100+
}
101+
102+
cmdS := C.CString(string(jsonStr))
103+
defer C.free(unsafe.Pointer(cmdS))
104+
105+
beS := C.CString(commBackendName)
106+
defer C.free(unsafe.Pointer(beS))
107+
108+
// Get the response data in json
109+
ans := (*C.be_answer)(C.calloc(1, C.sizeof_struct_be_answer))
110+
defer C.free(unsafe.Pointer(ans))
111+
112+
var ret C.be_sock_status
113+
for i := 0; i <= maxRetryCount; i++ {
114+
ret, err = C.Vmci_GetReply(C.int(EsxPort), cmdS, beS, ans)
115+
if ret == 0 {
116+
// Received no error, exit loop.
117+
// C.Vmci_GetReply indicates success/faulure by <ret> value.
118+
// Cgo interface adds <err> based on errno. We do not explicitly
119+
// reset errno in our code. Still, we do not want a stale errno
120+
// to confuse this code into thinking there was an error even when
121+
// ret==0, so explicitly declare success on <ret> value only, and
122+
break
123+
}
124+
125+
var msg string
126+
if err != nil {
127+
var errno syscall.Errno
128+
errno = err.(syscall.Errno)
129+
msg = fmt.Sprintf("Run '%s' failed: %v (errno=%d) - %s",
130+
cmd, err, int(errno), C.GoString(&ans.errBuf[0]))
131+
if i < maxRetryCount {
132+
log.Warnf(msg + " Retrying...")
133+
time.Sleep(time.Second * 1)
134+
continue
135+
}
136+
if errno == syscall.ECONNRESET || errno == syscall.ETIMEDOUT {
137+
msg += " Cannot communicate with ESX, please refer to the " +
138+
"FAQ " +
139+
"https://github.com/vmware/docker-volume-vsphere/wiki#faq"
140+
}
141+
} else {
142+
msg = fmt.Sprintf("Internal issue: ret != 0 but errno is not "+
143+
"set. Cancelling operation - %s ", C.GoString(&ans.errBuf[0]))
144+
}
145+
146+
log.Warnf(msg)
147+
return nil, errors.New(msg)
148+
}
149+
150+
response := []byte(C.GoString(ans.buf))
151+
C.Vmci_FreeBuf(ans)
152+
153+
err = unmarshalError(response)
154+
if err != nil && len(err.Error()) != 0 {
155+
return nil, err
156+
}
157+
// There was no error, so return the slice containing the json response
158+
return response, nil
159+
}
160+
161+
func unmarshalError(str []byte) error {
162+
// Unmarshalling null always succeeds
163+
if string(str) == "null" {
164+
return nil
165+
}
166+
errStruct := vmciError{}
167+
err := json.Unmarshal(str, &errStruct)
168+
if err != nil {
169+
// We didn't unmarshal an error, so there is no error ;)
170+
return nil
171+
}
172+
// Return the unmarshaled error string as an `error`
173+
return errors.New(errStruct.Error)
174+
}

0 commit comments

Comments
 (0)