blob: 0d59a6e3a8563ea8a1de6472572e0b06c792c2cc [file] [log] [blame]
//
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package org.apache.cloudstack.storage;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import javax.annotation.PreDestroy;
import com.cloud.storage.StorageLayer;
import com.cloud.utils.exception.CloudRuntimeException;
import com.cloud.utils.script.OutputInterpreter;
import com.cloud.utils.script.Script;
import org.apache.cloudstack.framework.config.ConfigKey;
import org.apache.cloudstack.utils.identity.ManagementServerNode;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager;
import org.springframework.stereotype.Component;
@Component
public class NfsMountManagerImpl implements NfsMountManager {
protected Logger logger = LogManager.getLogger(getClass());
private StorageLayer storage;
private int timeout;
private final Random rand = new Random(System.currentTimeMillis());
private final ConcurrentMap<String, String> storageMounts = new ConcurrentHashMap<>();
public static final ConfigKey<String> MOUNT_PARENT = new ConfigKey<>("Advanced", String.class,
"mount.parent", "/var/cloudstack/mnt",
"The mount point on the Management Server for Secondary Storage.",
true, ConfigKey.Scope.Global);
public NfsMountManagerImpl(StorageLayer storage, int timeout) {
this.storage = storage;
this.timeout = timeout;
}
public String getMountPoint(String storageUrl, String nfsVersion) {
String mountPoint = storageMounts.get(storageUrl);
if (mountPoint != null) {
return mountPoint;
}
URI uri;
try {
uri = new URI(storageUrl);
} catch (URISyntaxException e) {
logger.error("Invalid storage URL format ", e);
throw new CloudRuntimeException("Unable to create mount point due to invalid storage URL format " + storageUrl);
}
mountPoint = mount(uri.getHost() + ":" + uri.getPath(), MOUNT_PARENT.value(), nfsVersion);
if (mountPoint == null) {
logger.error("Unable to create mount point for " + storageUrl);
throw new CloudRuntimeException("Unable to create mount point for " + storageUrl);
}
storageMounts.putIfAbsent(storageUrl, mountPoint);
return mountPoint;
}
private String mount(String path, String parent, String nfsVersion) {
String mountPoint = setupMountPoint(parent);
if (mountPoint == null) {
logger.warn("Unable to create a mount point");
return null;
}
Script command = new Script(true, "mount", timeout, logger);
command.add("-t", "nfs");
if (nfsVersion != null){
command.add("-o", "vers=" + nfsVersion);
}
// command.add("-o", "soft,timeo=133,retrans=2147483647,tcp,acdirmax=0,acdirmin=0");
if ("Mac OS X".equalsIgnoreCase(System.getProperty("os.name"))) {
command.add("-o", "resvport");
}
command.add(path);
command.add(mountPoint);
String result = command.execute();
if (result != null) {
logger.warn("Unable to mount " + path + " due to " + result);
deleteMountPath(mountPoint);
return null;
}
// Change permissions for the mountpoint
Script script = new Script(true, "chmod", timeout, logger);
script.add("1777", mountPoint);
result = script.execute();
if (result != null) {
logger.warn("Unable to set permissions for " + mountPoint + " due to " + result);
}
return mountPoint;
}
private String setupMountPoint(String parent) {
String mountPoint = null;
for (int i = 0; i < 10; i++) {
String mntPt = parent + File.separator + String.valueOf(ManagementServerNode.getManagementServerId()) + "." + Integer.toHexString(rand.nextInt(Integer.MAX_VALUE));
File file = new File(mntPt);
if (!file.exists()) {
if (storage.mkdir(mntPt)) {
mountPoint = mntPt;
break;
}
}
logger.error("Unable to create mount: " + mntPt);
}
return mountPoint;
}
private void umount(String localRootPath) {
if (!mountExists(localRootPath)) {
return;
}
Script command = new Script(true, "umount", timeout, logger);
command.add(localRootPath);
String result = command.execute();
if (result != null) {
// Fedora Core 12 errors out with any -o option executed from java
String errMsg = "Unable to umount " + localRootPath + " due to " + result;
logger.error(errMsg);
throw new CloudRuntimeException(errMsg);
}
deleteMountPath(localRootPath);
logger.debug("Successfully umounted " + localRootPath);
}
private void deleteMountPath(String localRootPath) {
try {
Files.deleteIfExists(Paths.get(localRootPath));
} catch (IOException e) {
logger.warn(String.format("unable to delete mount directory %s:%s.%n", localRootPath, e.getMessage()));
}
}
private boolean mountExists(String localRootPath) {
Script script = new Script(true, "mount", timeout, logger);
PathParser parser = new PathParser(localRootPath);
script.execute(parser);
return parser.getPaths().stream().filter(s -> s.contains(localRootPath)).findAny().map(s -> true).orElse(false);
}
public static class PathParser extends OutputInterpreter {
String _parent;
List<String> paths = new ArrayList<>();
public PathParser(String parent) {
_parent = parent;
}
@Override
public String interpret(BufferedReader reader) throws IOException {
String line;
while ((line = reader.readLine()) != null) {
paths.add(line);
}
return null;
}
public List<String> getPaths() {
return paths;
}
@Override
public boolean drain() {
return true;
}
}
@PreDestroy
public void destroy() {
logger.info("Clean up mounted NFS mount points used in current session.");
storageMounts.values().stream().forEach(this::umount);
}
}