-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathserver.ts
273 lines (222 loc) · 7.91 KB
/
server.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
import express from "express";
import bodyParser from "body-parser";
import process from "process";
import Rbd from "./rbd";
import MountPointEntry from "./mountPointEntry";
const socketAddress = "/run/docker/plugins/rbd.sock";
const pool = process.env.RBD_CONF_POOL || "rbd";
const cluster = process.env.RBD_CONF_CLUSTER || "ceph"; // ToDo: Not utilised currently
const user = process.env.RBD_CONF_KEYRING_USER || "admin"; // ToDo: Not utilised currently
const map_options = process.env.RBD_CONF_MAP_OPTIONS ? process.env.RBD_CONF_MAP_OPTIONS.split(';') : ["--exclusive"]; // default to an exclusive lock when mapping to prevent multiple containers attempting to mount the block device
const rbd = new Rbd({ pool: pool, cluster: cluster, user: user, map_options: map_options });
const app = express();
app.use(bodyParser.json({ strict: false, type: req => true }));
// Documentation about docker volume plugins can be found here: https://docs.docker.com/engine/extend/plugins_volume/
app.post("/Plugin.Activate", (request, response) => {
console.log("Activating rbd volume driver");
response.json({
"Implements": ["VolumeDriver"]
});
});
let mountPointTable = new Map<string, MountPointEntry>();
function getMountPoint(name: string): string {
return `/mnt/volumes/${pool}/${name}`;
}
/*
Instruct the plugin that the user wants to create a volume, given a user specified volume name.
The plugin does not need to actually manifest the volume on the filesystem yet (until Mount is
called). Opts is a map of driver specific options passed through from the user request.
*/
app.post("/VolumeDriver.Create", async (request, response) => {
const req = request.body as { Name: string, Opts: { size: string, fstype: string } };
const fstype = req.Opts?.fstype || "xfs";
const size = req.Opts?.size || "200M";
console.log(`Creating rbd volume ${req.Name}`);
try {
await rbd.create(req.Name, size);
let device = await rbd.map(req.Name);
await rbd.makeFilesystem(fstype, device);
await rbd.unMap(req.Name);
}
catch (error) {
return response.json({ Err: error.message });
}
response.json({
Err: ""
});
});
/*
Delete the specified volume from disk. This request is issued when a user invokes
docker rm -v to remove volumes associated with a container.
*/
app.post("/VolumeDriver.Remove", async (request, response) => {
const req = request.body as { Name: string };
console.log(`Removing rbd volume ${req.Name}`);
try {
await rbd.unMap(req.Name);
await rbd.remove(req.Name);
}
catch (error) {
return response.json({ Err: error.message });
}
response.json({
Err: ""
});
});
/*
Docker requires the plugin to provide a volume, given a user specified volume name.
Mount is called once per container start. If the same volume_name is requested more
than once, the plugin may need to keep track of each new mount request and provision
at the first mount request and deprovision at the last corresponding unmount request.
*/
app.post("/VolumeDriver.Mount", async (request, response) => {
const req = request.body as { Name: string, ID: string };
const mountPoint = getMountPoint(req.Name);
console.log(`Mounting rbd volume ${req.Name}`);
if (mountPointTable.has(mountPoint)) {
console.log(`${mountPoint} already mounted, nothing to do`);
mountPointTable.get(mountPoint).references.push(req.ID);
return response.json({
MountPoint: mountPoint,
Err: ""
});
}
try {
let device = await rbd.isMapped(req.Name);
if (!device) {
device = await rbd.map(req.Name);
}
await rbd.mount(device, mountPoint);
}
catch (error) {
return response.json({ Err: error.message });
}
mountPointTable.set(mountPoint,
new MountPointEntry(
req.Name,
mountPoint,
req.ID));
response.json({
MountPoint: mountPoint,
Err: ""
});
});
/*
Request the path to the volume with the given volume_name.
*/
app.post("/VolumeDriver.Path", (request, response) => {
const req = request.body as { Name: string };
const mountPoint = getMountPoint(req.Name);
console.log(`Request path of rbd mount ${req.Name}`);
if (mountPointTable.has(mountPoint)) {
response.json({
MountPoint: mountPoint,
Err: ""
});
} else {
response.json({ Err: "" });
}
});
/*
Docker is no longer using the named volume. Unmount is called once per container stop.
Plugin may deduce that it is safe to deprovision the volume at this point.
ID is a unique ID for the caller that is requesting the mount.
*/
app.post("/VolumeDriver.Unmount", async (request, response) => {
const req = request.body as { Name: string, ID: string };
const mountPoint = getMountPoint(req.Name);
console.log(`Unmounting rbd volume ${req.Name}`);
if (!mountPointTable.has(mountPoint)) {
const error = `Unknown volume ${req.Name}`;
console.error(error);
return response.json({ Err: error });
}
let mountPointEntry = mountPointTable.get(mountPoint);
if (!mountPointEntry.hasReference(req.ID)) {
const error = `Unknown caller id ${req.ID} for volume ${req.Name}`;
console.error(error);
return response.json({ Err: error });
}
const remainingIds = mountPointEntry.references.filter(id => id !== req.ID);
if (remainingIds.length > 0) {
console.log(`${remainingIds.length} references to volume ${req.Name} remaining, not unmounting..`);
mountPointEntry.references = remainingIds;
return response.json({ Err: "" });
}
try {
await rbd.unmount(mountPoint);
mountPointTable.delete(mountPoint);
await rbd.unMap(req.Name);
}
catch (error) {
return response.json({ Err: error.message });
}
response.json({
Err: ""
});
});
/*
Get info about volume_name.
*/
app.post("/VolumeDriver.Get", async (request, response) => {
const req = request.body as { Name: string };
const mountPoint = getMountPoint(req.Name);
const entry = mountPointTable.has(mountPoint)
? mountPointTable.get(mountPoint)
: null;
console.log(`Getting info about rbd volume ${req.Name}`);
try {
const info = await rbd.getInfo(req.Name);
if (!info) {
return response.json({ Err: "" });
}
response.json({
Volume: {
Name: req.Name,
Mountpoint: entry?.mountPoint || "",
Status: {
size: info.size
}
},
Err: ""
});
} catch (error) {
return response.json({ Err: error.message });
}
});
/*
Get the list of volumes registered with the plugin.
*/
app.post("/VolumeDriver.List", async (request, response) => {
console.log("Getting list of registered rbd volumes");
try {
const rbdList = await rbd.list();
response.json({
Volumes: rbdList.map(info => {
const mountPoint = getMountPoint(info.image);
const entry = mountPointTable.has(mountPoint)
? mountPointTable.get(mountPoint)
: null;
return {
Name: name,
Mountpoint: entry?.mountPoint || ""
};
}),
Err: ""
});
}
catch (error) {
return response.json({ Err: error.message });
}
});
app.post("/VolumeDriver.Capabilities", (request, response) => {
console.log("Getting the list of capabilities");
response.json({
Capabilities: {
Scope: "global"
}
});
});
app.listen(socketAddress, () => {
console.log(`Plugin rbd listening on socket ${socketAddress}`);
});