Skip to content

Commit a4c4711

Browse files
author
Dingane Hlaluku
committed
Complete VMware implementation
1 parent 8d60a4f commit a4c4711

File tree

4 files changed

+289
-3
lines changed

4 files changed

+289
-3
lines changed

server/src/main/java/org/apache/cloudstack/diagnostics/DiagnosticsServiceImpl.java

Lines changed: 55 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
import com.cloud.utils.component.PluggableService;
4242
import com.cloud.utils.exception.CloudRuntimeException;
4343
import com.cloud.utils.exception.ExecutionException;
44+
import com.cloud.utils.script.Script2;
4445
import com.cloud.vm.VMInstanceVO;
4546
import com.cloud.vm.VirtualMachine;
4647
import com.cloud.vm.VirtualMachineManager;
@@ -58,8 +59,10 @@
5859
import org.apache.cloudstack.managed.context.ManagedContextRunnable;
5960
import org.apache.cloudstack.poll.BackgroundPollManager;
6061
import org.apache.cloudstack.poll.BackgroundPollTask;
62+
import org.apache.cloudstack.storage.NfsMountManager;
6163
import org.apache.cloudstack.storage.image.datastore.ImageStoreEntity;
6264
import org.apache.commons.collections.CollectionUtils;
65+
import org.apache.commons.lang3.StringUtils;
6366
import org.apache.log4j.Logger;
6467

6568
public class DiagnosticsServiceImpl extends ManagerBase implements PluggableService, DiagnosticsService, Configurable {
@@ -81,6 +84,8 @@ public class DiagnosticsServiceImpl extends ManagerBase implements PluggableServ
8184
private BackgroundPollManager backgroundPollManager;
8285
@Inject
8386
private ImageStoreDetailsUtil imageStoreDetailsUtil;
87+
@Inject
88+
private NfsMountManager mountManager;
8489

8590
private static final ConfigKey<Boolean> EnableGarbageCollector = new ConfigKey<>("Advanced", Boolean.class,
8691
"diagnostics.data.gc.enable", "true", "enable the diagnostics data files garbage collector", true);
@@ -93,6 +98,8 @@ public class DiagnosticsServiceImpl extends ManagerBase implements PluggableServ
9398
private static final ConfigKey<Double> DiskQuotaPercentageThreshold = new ConfigKey<>("Advanced", Double.class,
9499
"diagnostics.data.disable.threshold", "0.95", "Minimum disk space percentage to initiate diagnostics file retrieval", false);
95100

101+
final static String COMMAND = "/bin/bash";
102+
final static String PERM_KEY = "/var/cloudstack/management/.ssh/id_rsa";
96103

97104
@Override
98105
@ActionEvent(eventType = EventTypes.EVENT_SYSTEM_VM_DIAGNOSTICS, eventDescription = "running diagnostics on system vm", async = true)
@@ -177,9 +184,23 @@ public String getDiagnosticsDataCommand(GetDiagnosticsDataCmd cmd) throws Execut
177184
// Copy zip file from system vm to secondary storage
178185
String fileToCopy = zipFilesAnswer.getDetails().replace("\n", "");
179186
if (vmInstance.getHypervisorType() == Hypervisor.HypervisorType.VMware){
180-
//TODO Mount Mgmt server and scp files from system VM
181-
182-
entityUrl = "";
187+
String mountPoint = null;
188+
try {
189+
Integer nfsVersion = imageStoreDetailsUtil.getNfsVersion(store.getId());
190+
mountPoint = mountManager.getMountPoint(store.getUri(), nfsVersion);
191+
192+
if (StringUtils.isNotBlank(mountPoint)) {
193+
String result = copyToSecondaryStorage(mountPoint, vmControlIp, fileToCopy);
194+
entityUrl = createFileDownloadUrl(store,ssvm.getHypervisorType(), result);
195+
}
196+
} finally {
197+
// umount secondary storage
198+
Script2 umountCmd = new Script2(COMMAND, LOGGER);
199+
umountCmd.add("-c");
200+
String cmdLine = String.format("unmount %s", mountPoint);
201+
umountCmd.add(cmdLine);
202+
umountCmd.execute();
203+
}
183204

184205
} else {
185206
// Send copy to secondary storage command to hypervisor host for non VMware hypervisor
@@ -198,6 +219,37 @@ public String getDiagnosticsDataCommand(GetDiagnosticsDataCmd cmd) throws Execut
198219
return entityUrl;
199220
}
200221

222+
private String copyToSecondaryStorage(String mountDir, String vmIp, String fileName) {
223+
String details = "Copying zip files: "+ fileName + "to:" + vmIp;
224+
LOGGER.info(details);
225+
String diagnosticsDir = "diagnostics_data";
226+
String cmdLine = String.format("mkdir -p %s/%s && /usr/bin/scp -P 3922 -o StrictHostKeyChecking=no -i %s root@%s:%s %s/%s/.",
227+
mountDir, diagnosticsDir , PERM_KEY, vmIp, fileName, mountDir, diagnosticsDir);
228+
Script2 cmd = new Script2(COMMAND, LOGGER);
229+
cmd.add("-c");
230+
cmd.add(cmdLine);
231+
String result = cmd.execute();
232+
if (result != null){
233+
String msg = String.format("Failed to copy %s from %s to secondary storage", fileName, vmIp);
234+
return msg;
235+
}
236+
cleanUpFileInVm(vmIp, fileName);
237+
String fileNameInSecondary = diagnosticsDir + "/" + fileName.replace("/root/", "");
238+
return fileNameInSecondary;
239+
}
240+
241+
private void cleanUpFileInVm(String vmIP, String filenName){
242+
String cleanupCmd = String.format("usr/bin/ssh -p 3922 -o StrictHostKeyChecking=no -i %s root@%s 'rm -f %s && echo $?'", PERM_KEY, vmIP, filenName);
243+
Script2 cmd = new Script2(COMMAND, LOGGER);
244+
cmd.add("-c");
245+
cmd.add(cleanupCmd);
246+
String result = cmd.execute();
247+
if (result != null){
248+
String msg = String.format("Failed to cleanup diagnostics zip file %s from system VM %s ", filenName, vmIP);
249+
LOGGER.warn(msg);
250+
}
251+
}
252+
201253
protected boolean hasValidChars(String optionalArgs) {
202254
if (Strings.isNullOrEmpty(optionalArgs)) {
203255
return true;
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
//
2+
// Licensed to the Apache Software Foundation (ASF) under one
3+
// or more contributor license agreements. See the NOTICE file
4+
// distributed with this work for additional information
5+
// regarding copyright ownership. The ASF licenses this file
6+
// to you under the Apache License, Version 2.0 (the
7+
// "License"); you may not use this file except in compliance
8+
// with the License. You may obtain a copy of the License at
9+
//
10+
// http://www.apache.org/licenses/LICENSE-2.0
11+
//
12+
// Unless required by applicable law or agreed to in writing,
13+
// software distributed under the License is distributed on an
14+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
// KIND, either express or implied. See the License for the
16+
// specific language governing permissions and limitations
17+
// under the License.
18+
package org.apache.cloudstack.storage;
19+
20+
public interface NfsMountManager {
21+
22+
String getMountPoint(String storageUrl, Integer nfsVersion);
23+
}
Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
//
2+
// Licensed to the Apache Software Foundation (ASF) under one
3+
// or more contributor license agreements. See the NOTICE file
4+
// distributed with this work for additional information
5+
// regarding copyright ownership. The ASF licenses this file
6+
// to you under the Apache License, Version 2.0 (the
7+
// "License"); you may not use this file except in compliance
8+
// with the License. You may obtain a copy of the License at
9+
//
10+
// http://www.apache.org/licenses/LICENSE-2.0
11+
//
12+
// Unless required by applicable law or agreed to in writing,
13+
// software distributed under the License is distributed on an
14+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
// KIND, either express or implied. See the License for the
16+
// specific language governing permissions and limitations
17+
// under the License.
18+
package org.apache.cloudstack.storage;
19+
20+
import java.io.BufferedReader;
21+
import java.io.File;
22+
import java.io.IOException;
23+
import java.net.URI;
24+
import java.net.URISyntaxException;
25+
import java.nio.file.Files;
26+
import java.nio.file.Paths;
27+
import java.util.ArrayList;
28+
import java.util.List;
29+
import java.util.Random;
30+
import java.util.concurrent.ConcurrentHashMap;
31+
import java.util.concurrent.ConcurrentMap;
32+
33+
import javax.annotation.PreDestroy;
34+
35+
import com.cloud.storage.StorageLayer;
36+
import com.cloud.utils.exception.CloudRuntimeException;
37+
import com.cloud.utils.script.OutputInterpreter;
38+
import com.cloud.utils.script.Script;
39+
import org.apache.cloudstack.framework.config.ConfigKey;
40+
import org.apache.cloudstack.utils.identity.ManagementServerNode;
41+
import org.apache.log4j.Logger;
42+
import org.springframework.stereotype.Component;
43+
44+
@Component
45+
public class NfsMountManagerImpl implements NfsMountManager {
46+
private static final Logger s_logger = Logger.getLogger(NfsMountManager.class);
47+
48+
private StorageLayer storage;
49+
private int timeout;
50+
private final Random rand = new Random(System.currentTimeMillis());
51+
private final ConcurrentMap<String, String> storageMounts = new ConcurrentHashMap<>();
52+
53+
public static final ConfigKey<String> MOUNT_PARENT = new ConfigKey<>("Advanced", String.class,
54+
"mount.parent", "/var/cloudstack/mnt",
55+
"The mount point on the Management Server for Secondary Storage.",
56+
true, ConfigKey.Scope.Global);
57+
58+
public NfsMountManagerImpl(StorageLayer storage, int timeout) {
59+
this.storage = storage;
60+
this.timeout = timeout;
61+
}
62+
63+
public String getMountPoint(String storageUrl, Integer nfsVersion) {
64+
String mountPoint = storageMounts.get(storageUrl);
65+
if (mountPoint != null) {
66+
return mountPoint;
67+
}
68+
69+
URI uri;
70+
try {
71+
uri = new URI(storageUrl);
72+
} catch (URISyntaxException e) {
73+
s_logger.error("Invalid storage URL format ", e);
74+
throw new CloudRuntimeException("Unable to create mount point due to invalid storage URL format " + storageUrl);
75+
}
76+
77+
mountPoint = mount(uri.getHost() + ":" + uri.getPath(), MOUNT_PARENT.value(), nfsVersion);
78+
if (mountPoint == null) {
79+
s_logger.error("Unable to create mount point for " + storageUrl);
80+
throw new CloudRuntimeException("Unable to create mount point for " + storageUrl);
81+
}
82+
83+
storageMounts.putIfAbsent(storageUrl, mountPoint);
84+
return mountPoint;
85+
}
86+
87+
private String mount(String path, String parent, Integer nfsVersion) {
88+
String mountPoint = setupMountPoint(parent);
89+
if (mountPoint == null) {
90+
s_logger.warn("Unable to create a mount point");
91+
return null;
92+
}
93+
94+
Script command = new Script(true, "mount", timeout, s_logger);
95+
command.add("-t", "nfs");
96+
if (nfsVersion != null){
97+
command.add("-o", "vers=" + nfsVersion);
98+
}
99+
// command.add("-o", "soft,timeo=133,retrans=2147483647,tcp,acdirmax=0,acdirmin=0");
100+
if ("Mac OS X".equalsIgnoreCase(System.getProperty("os.name"))) {
101+
command.add("-o", "resvport");
102+
}
103+
command.add(path);
104+
command.add(mountPoint);
105+
String result = command.execute();
106+
if (result != null) {
107+
s_logger.warn("Unable to mount " + path + " due to " + result);
108+
deleteMountPath(mountPoint);
109+
return null;
110+
}
111+
112+
// Change permissions for the mountpoint
113+
Script script = new Script(true, "chmod", timeout, s_logger);
114+
script.add("1777", mountPoint);
115+
result = script.execute();
116+
if (result != null) {
117+
s_logger.warn("Unable to set permissions for " + mountPoint + " due to " + result);
118+
}
119+
return mountPoint;
120+
}
121+
122+
private String setupMountPoint(String parent) {
123+
String mountPoint = null;
124+
for (int i = 0; i < 10; i++) {
125+
String mntPt = parent + File.separator + String.valueOf(ManagementServerNode.getManagementServerId()) + "." + Integer.toHexString(rand.nextInt(Integer.MAX_VALUE));
126+
File file = new File(mntPt);
127+
if (!file.exists()) {
128+
if (storage.mkdir(mntPt)) {
129+
mountPoint = mntPt;
130+
break;
131+
}
132+
}
133+
s_logger.error("Unable to create mount: " + mntPt);
134+
}
135+
136+
return mountPoint;
137+
}
138+
139+
private void umount(String localRootPath) {
140+
if (!mountExists(localRootPath)) {
141+
return;
142+
}
143+
Script command = new Script(true, "umount", timeout, s_logger);
144+
command.add(localRootPath);
145+
String result = command.execute();
146+
if (result != null) {
147+
// Fedora Core 12 errors out with any -o option executed from java
148+
String errMsg = "Unable to umount " + localRootPath + " due to " + result;
149+
s_logger.error(errMsg);
150+
throw new CloudRuntimeException(errMsg);
151+
}
152+
deleteMountPath(localRootPath);
153+
s_logger.debug("Successfully umounted " + localRootPath);
154+
}
155+
156+
private void deleteMountPath(String localRootPath) {
157+
try {
158+
Files.deleteIfExists(Paths.get(localRootPath));
159+
} catch (IOException e) {
160+
s_logger.warn(String.format("unable to delete mount directory %s:%s.%n", localRootPath, e.getMessage()));
161+
}
162+
}
163+
164+
private boolean mountExists(String localRootPath) {
165+
Script script = new Script(true, "mount", timeout, s_logger);
166+
ZfsPathParser parser = new ZfsPathParser(localRootPath);
167+
script.execute(parser);
168+
return parser.getPaths().stream().filter(s -> s.contains(localRootPath)).findAny().map(s -> true).orElse(false);
169+
}
170+
171+
public static class ZfsPathParser extends OutputInterpreter {
172+
String _parent;
173+
List<String> paths = new ArrayList<>();
174+
175+
public ZfsPathParser(String parent) {
176+
_parent = parent;
177+
}
178+
179+
@Override
180+
public String interpret(BufferedReader reader) throws IOException {
181+
String line;
182+
while ((line = reader.readLine()) != null) {
183+
paths.add(line);
184+
}
185+
return null;
186+
}
187+
188+
public List<String> getPaths() {
189+
return paths;
190+
}
191+
192+
@Override
193+
public boolean drain() {
194+
return true;
195+
}
196+
}
197+
198+
@PreDestroy
199+
public void destroy() {
200+
s_logger.info("Clean up mounted NFS mount points used in current session.");
201+
storageMounts.values().stream().forEach(this::umount);
202+
}
203+
}

server/src/main/resources/META-INF/cloudstack/core/spring-server-core-managers-context.xml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,4 +300,12 @@
300300
<bean id="directDownloadManager" class="org.apache.cloudstack.direct.download.DirectDownloadManagerImpl" />
301301

302302
<bean id="DiagnosticsService" class="org.apache.cloudstack.diagnostics.DiagnosticsServiceImpl" />
303+
304+
<bean id="storageLayer" class="com.cloud.storage.JavaStorageLayer" />
305+
306+
<bean id="nfsMountManager" class="org.apache.cloudstack.storage.NfsMountManagerImpl" >
307+
<constructor-arg name="storage" ref="storageLayer" />
308+
<constructor-arg name="timeout" value="10000" />
309+
</bean>
310+
303311
</beans>

0 commit comments

Comments
 (0)