blob: 21372a17f8d4f6f5388cd59ecf02f7035c3bd26b [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 com.cloud.agent.resource.virtualnetwork;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.SocketChannel;
import org.apache.cloudstack.diagnostics.DiagnosticsAnswer;
import org.apache.cloudstack.diagnostics.DiagnosticsCommand;
import org.joda.time.Duration;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.UUID;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import javax.naming.ConfigurationException;
import org.apache.cloudstack.ca.SetupCertificateAnswer;
import org.apache.cloudstack.ca.SetupCertificateCommand;
import org.apache.cloudstack.ca.SetupKeyStoreCommand;
import org.apache.cloudstack.ca.SetupKeystoreAnswer;
import org.apache.cloudstack.utils.security.KeyStoreUtils;
import org.apache.log4j.Logger;
import com.cloud.agent.api.Answer;
import com.cloud.agent.api.CheckRouterAnswer;
import com.cloud.agent.api.CheckRouterCommand;
import com.cloud.agent.api.CheckS2SVpnConnectionsAnswer;
import com.cloud.agent.api.CheckS2SVpnConnectionsCommand;
import com.cloud.agent.api.GetDomRVersionAnswer;
import com.cloud.agent.api.GetDomRVersionCmd;
import com.cloud.agent.api.GetRouterAlertsAnswer;
import com.cloud.agent.api.routing.AggregationControlCommand;
import com.cloud.agent.api.routing.AggregationControlCommand.Action;
import com.cloud.agent.api.routing.GetRouterAlertsCommand;
import com.cloud.agent.api.routing.GroupAnswer;
import com.cloud.agent.api.routing.NetworkElementCommand;
import com.cloud.agent.resource.virtualnetwork.facade.AbstractConfigItemFacade;
import com.cloud.utils.ExecutionResult;
import com.cloud.utils.NumbersUtil;
import com.cloud.utils.exception.CloudRuntimeException;
/**
* VirtualNetworkResource controls and configures virtual networking
*
* @config
* {@table
* || Param Name | Description | Values | Default ||
* }
**/
public class VirtualRoutingResource {
private static final Logger s_logger = Logger.getLogger(VirtualRoutingResource.class);
private VirtualRouterDeployer _vrDeployer;
private Map<String, Queue<NetworkElementCommand>> _vrAggregateCommandsSet;
protected Map<String, Lock> _vrLockMap = new HashMap<String, Lock>();
private String _name;
private int _sleep;
private int _retry;
private int _port;
private Duration _eachTimeout;
private Map<String, Object> _params;
private String _cfgVersion = "1.0";
public VirtualRoutingResource(VirtualRouterDeployer deployer) {
_vrDeployer = deployer;
}
public Answer executeRequest(final NetworkElementCommand cmd) {
boolean aggregated = false;
String routerName = cmd.getAccessDetail(NetworkElementCommand.ROUTER_NAME);
Lock lock;
if (_vrLockMap.containsKey(routerName)) {
lock = _vrLockMap.get(routerName);
} else {
lock = new ReentrantLock();
_vrLockMap.put(routerName, lock);
}
lock.lock();
try {
ExecutionResult rc = _vrDeployer.prepareCommand(cmd);
if (!rc.isSuccess()) {
s_logger.error("Failed to prepare VR command due to " + rc.getDetails());
return new Answer(cmd, false, rc.getDetails());
}
assert cmd.getRouterAccessIp() != null : "Why there is no access IP for VR?";
if (cmd.isQuery()) {
return executeQueryCommand(cmd);
}
if (cmd instanceof SetupKeyStoreCommand) {
return execute((SetupKeyStoreCommand) cmd);
}
if (cmd instanceof SetupCertificateCommand) {
return execute((SetupCertificateCommand) cmd);
}
if (cmd instanceof AggregationControlCommand) {
return execute((AggregationControlCommand)cmd);
}
if (_vrAggregateCommandsSet.containsKey(routerName)) {
_vrAggregateCommandsSet.get(routerName).add(cmd);
aggregated = true;
// Clean up would be done after command has been executed
//TODO: Deal with group answer as well
return new Answer(cmd);
}
List<ConfigItem> cfg = generateCommandCfg(cmd);
if (cfg == null) {
return Answer.createUnsupportedCommandAnswer(cmd);
}
return applyConfig(cmd, cfg);
} catch (final IllegalArgumentException e) {
return new Answer(cmd, false, e.getMessage());
} finally {
lock.unlock();
if (!aggregated) {
ExecutionResult rc = _vrDeployer.cleanupCommand(cmd);
if (!rc.isSuccess()) {
s_logger.error("Failed to cleanup VR command due to " + rc.getDetails());
}
}
}
}
private Answer execute(final SetupKeyStoreCommand cmd) {
final String args = String.format("/usr/local/cloud/systemvm/conf/agent.properties " +
"/usr/local/cloud/systemvm/conf/%s " +
"%s %d " +
"/usr/local/cloud/systemvm/conf/%s",
KeyStoreUtils.KS_FILENAME,
cmd.getKeystorePassword(),
cmd.getValidityDays(),
KeyStoreUtils.CSR_FILENAME);
ExecutionResult result = _vrDeployer.executeInVR(cmd.getRouterAccessIp(), KeyStoreUtils.KS_SETUP_SCRIPT, args, Duration.standardMinutes(15));
return new SetupKeystoreAnswer(result.getDetails());
}
private Answer execute(final SetupCertificateCommand cmd) {
final String args = String.format("/usr/local/cloud/systemvm/conf/agent.properties " +
"/usr/local/cloud/systemvm/conf/%s %s " +
"/usr/local/cloud/systemvm/conf/%s \"%s\" " +
"/usr/local/cloud/systemvm/conf/%s \"%s\" " +
"/usr/local/cloud/systemvm/conf/%s \"%s\"",
KeyStoreUtils.KS_FILENAME,
KeyStoreUtils.SSH_MODE,
KeyStoreUtils.CERT_FILENAME,
cmd.getEncodedCertificate(),
KeyStoreUtils.CACERT_FILENAME,
cmd.getEncodedCaCertificates(),
KeyStoreUtils.PKEY_FILENAME,
cmd.getEncodedPrivateKey());
ExecutionResult result = _vrDeployer.executeInVR(cmd.getRouterAccessIp(), KeyStoreUtils.KS_IMPORT_SCRIPT, args, Duration.standardMinutes(15));
return new SetupCertificateAnswer(result.isSuccess());
}
private Answer executeQueryCommand(NetworkElementCommand cmd) {
if (cmd instanceof CheckRouterCommand) {
return execute((CheckRouterCommand)cmd);
} else if (cmd instanceof GetDomRVersionCmd) {
return execute((GetDomRVersionCmd)cmd);
} else if (cmd instanceof CheckS2SVpnConnectionsCommand) {
return execute((CheckS2SVpnConnectionsCommand)cmd);
} else if (cmd instanceof GetRouterAlertsCommand) {
return execute((GetRouterAlertsCommand)cmd);
} else if (cmd instanceof DiagnosticsCommand) {
return execute((DiagnosticsCommand)cmd);
} else {
s_logger.error("Unknown query command in VirtualRoutingResource!");
return Answer.createUnsupportedCommandAnswer(cmd);
}
}
private ExecutionResult applyConfigToVR(String routerAccessIp, ConfigItem c) {
return applyConfigToVR(routerAccessIp, c, VRScripts.VR_SCRIPT_EXEC_TIMEOUT);
}
private ExecutionResult applyConfigToVR(String routerAccessIp, ConfigItem c, Duration timeout) {
if (c instanceof FileConfigItem) {
FileConfigItem configItem = (FileConfigItem)c;
return _vrDeployer.createFileInVR(routerAccessIp, configItem.getFilePath(), configItem.getFileName(), configItem.getFileContents());
} else if (c instanceof ScriptConfigItem) {
ScriptConfigItem configItem = (ScriptConfigItem)c;
return _vrDeployer.executeInVR(routerAccessIp, configItem.getScript(), configItem.getArgs(), timeout);
}
throw new CloudRuntimeException("Unable to apply unknown configitem of type " + c.getClass().getSimpleName());
}
private Answer applyConfig(NetworkElementCommand cmd, List<ConfigItem> cfg) {
if (cfg.isEmpty()) {
return new Answer(cmd, true, "Nothing to do");
}
List<ExecutionResult> results = new ArrayList<ExecutionResult>();
List<String> details = new ArrayList<String>();
boolean finalResult = false;
for (ConfigItem configItem : cfg) {
long startTimestamp = System.currentTimeMillis();
ExecutionResult result = applyConfigToVR(cmd.getRouterAccessIp(), configItem, VRScripts.VR_SCRIPT_EXEC_TIMEOUT);
if (s_logger.isDebugEnabled()) {
long elapsed = System.currentTimeMillis() - startTimestamp;
s_logger.debug("Processing " + configItem + " took " + elapsed + "ms");
}
if (result == null) {
result = new ExecutionResult(false, "null execution result");
}
results.add(result);
details.add(configItem.getInfo() + (result.isSuccess() ? " - success: " : " - failed: ") + result.getDetails());
finalResult = result.isSuccess();
}
// Not sure why this matters, but log it anyway
if (cmd.getAnswersCount() != results.size()) {
s_logger.warn("Expected " + cmd.getAnswersCount() + " answers while executing " + cmd.getClass().getSimpleName() + " but received " + results.size());
}
if (results.size() == 1) {
return new Answer(cmd, finalResult, results.get(0).getDetails());
} else {
return new GroupAnswer(cmd, finalResult, results.size(), details.toArray(new String[details.size()]));
}
}
private CheckS2SVpnConnectionsAnswer execute(CheckS2SVpnConnectionsCommand cmd) {
StringBuffer buff = new StringBuffer();
for (String ip : cmd.getVpnIps()) {
buff.append(ip);
buff.append(" ");
}
ExecutionResult result = _vrDeployer.executeInVR(cmd.getRouterAccessIp(), VRScripts.S2SVPN_CHECK, buff.toString());
return new CheckS2SVpnConnectionsAnswer(cmd, result.isSuccess(), result.getDetails());
}
private GetRouterAlertsAnswer execute(GetRouterAlertsCommand cmd) {
String routerIp = cmd.getAccessDetail(NetworkElementCommand.ROUTER_IP);
String args = cmd.getPreviousAlertTimeStamp();
ExecutionResult result = _vrDeployer.executeInVR(routerIp, VRScripts.ROUTER_ALERTS, args);
String alerts[] = null;
String lastAlertTimestamp = null;
if (result.isSuccess()) {
if (!result.getDetails().isEmpty() && !result.getDetails().trim().equals("No Alerts")) {
alerts = result.getDetails().trim().split("\\\\n");
String[] lastAlert = alerts[alerts.length - 1].split(",");
lastAlertTimestamp = lastAlert[0];
}
return new GetRouterAlertsAnswer(cmd, alerts, lastAlertTimestamp);
} else {
return new GetRouterAlertsAnswer(cmd, result.getDetails());
}
}
private Answer execute(CheckRouterCommand cmd) {
final ExecutionResult result = _vrDeployer.executeInVR(cmd.getRouterAccessIp(), VRScripts.RVR_CHECK, null);
if (!result.isSuccess()) {
return new CheckRouterAnswer(cmd, result.getDetails());
}
return new CheckRouterAnswer(cmd, result.getDetails(), true);
}
private Answer execute(DiagnosticsCommand cmd) {
_eachTimeout = Duration.standardSeconds(NumbersUtil.parseInt("60", 60));
final ExecutionResult result = _vrDeployer.executeInVR(cmd.getRouterAccessIp(), VRScripts.DIAGNOSTICS, cmd.getSrciptArguments(), _eachTimeout);
if (!result.isSuccess()) {
return new DiagnosticsAnswer(cmd, false, result.getDetails());
}
return new DiagnosticsAnswer(cmd, result.isSuccess(), result.getDetails());
}
private Answer execute(GetDomRVersionCmd cmd) {
final ExecutionResult result = _vrDeployer.executeInVR(cmd.getRouterAccessIp(), VRScripts.VERSION, null);
if (!result.isSuccess()) {
return new GetDomRVersionAnswer(cmd, "GetDomRVersionCmd failed");
}
String[] lines = result.getDetails().split("&");
if (lines.length != 2) {
return new GetDomRVersionAnswer(cmd, result.getDetails());
}
return new GetDomRVersionAnswer(cmd, result.getDetails(), lines[0], lines[1]);
}
public boolean configureHostParams(final Map<String, String> params) {
if (_params.get("router.aggregation.command.each.timeout") == null) {
String value = (String)params.get("router.aggregation.command.each.timeout");
_eachTimeout = Duration.standardSeconds(NumbersUtil.parseInt(value, 10));
}
return true;
}
public boolean configure(final String name, final Map<String, Object> params) throws ConfigurationException {
_name = name;
_params = params;
String value = (String)params.get("ssh.sleep");
_sleep = NumbersUtil.parseInt(value, 10) * 1000;
value = (String)params.get("ssh.retry");
_retry = NumbersUtil.parseInt(value, 36);
value = (String)params.get("ssh.port");
_port = NumbersUtil.parseInt(value, 3922);
value = (String)params.get("router.aggregation.command.each.timeout");
_eachTimeout = Duration.standardSeconds(NumbersUtil.parseInt(value, (int)VRScripts.VR_SCRIPT_EXEC_TIMEOUT.getStandardSeconds()));
if (s_logger.isDebugEnabled()){
s_logger.debug("The router.aggregation.command.each.timeout in seconds is set to " + _eachTimeout.getStandardSeconds());
}
if (_vrDeployer == null) {
throw new ConfigurationException("Unable to find the resource for VirtualRouterDeployer!");
}
_vrAggregateCommandsSet = new HashMap<>();
return true;
}
public boolean connect(final String ipAddress) {
return connect(ipAddress, _port);
}
public boolean connect(final String ipAddress, final int port) {
return connect(ipAddress, port, _sleep);
}
public boolean connect(final String ipAddress, int retry, int sleep) {
for (int i = 0; i <= retry; i++) {
SocketChannel sch = null;
try {
if (s_logger.isDebugEnabled()) {
s_logger.debug("Trying to connect to " + ipAddress);
}
sch = SocketChannel.open();
sch.configureBlocking(true);
final InetSocketAddress addr = new InetSocketAddress(ipAddress, _port);
sch.connect(addr);
return true;
} catch (final IOException e) {
if (s_logger.isDebugEnabled()) {
s_logger.debug("Could not connect to " + ipAddress);
}
} finally {
if (sch != null) {
try {
sch.close();
} catch (final IOException e) {
}
}
}
try {
Thread.sleep(sleep);
} catch (final InterruptedException e) {
}
}
s_logger.debug("Unable to logon to " + ipAddress);
return false;
}
private List<ConfigItem> generateCommandCfg(NetworkElementCommand cmd) {
/*
* [TODO] Still have to migrate LoadBalancerConfigCommand and BumpUpPriorityCommand
* [FIXME] Have a look at SetSourceNatConfigItem
*/
s_logger.debug("Transforming " + cmd.getClass().getCanonicalName() + " to ConfigItems");
final AbstractConfigItemFacade configItemFacade = AbstractConfigItemFacade.getInstance(cmd.getClass());
return configItemFacade.generateConfig(cmd);
}
private Answer execute(AggregationControlCommand cmd) {
Action action = cmd.getAction();
String routerName = cmd.getAccessDetail(NetworkElementCommand.ROUTER_NAME);
assert routerName != null;
assert cmd.getRouterAccessIp() != null;
if (action == Action.Start) {
assert (!_vrAggregateCommandsSet.containsKey(routerName));
Queue<NetworkElementCommand> queue = new LinkedBlockingQueue<>();
_vrAggregateCommandsSet.put(routerName, queue);
return new Answer(cmd, true, "Command aggregation started");
} else if (action == Action.Finish) {
Queue<NetworkElementCommand> queue = _vrAggregateCommandsSet.get(routerName);
int answerCounts = 0;
try {
StringBuilder sb = new StringBuilder();
sb.append("#Apache CloudStack Virtual Router Config File\n");
sb.append("<version>\n" + _cfgVersion + "\n</version>\n");
for (NetworkElementCommand command : queue) {
answerCounts += command.getAnswersCount();
List<ConfigItem> cfg = generateCommandCfg(command);
if (cfg == null) {
s_logger.warn("Unknown commands for VirtualRoutingResource, but continue: " + cmd.toString());
continue;
}
for (ConfigItem c : cfg) {
sb.append(c.getAggregateCommand());
}
}
// TODO replace with applyConfig with a stop on fail
String cfgFileName = "VR-"+ UUID.randomUUID().toString() + ".cfg";
FileConfigItem fileConfigItem = new FileConfigItem(VRScripts.CONFIG_CACHE_LOCATION, cfgFileName, sb.toString());
ScriptConfigItem scriptConfigItem = new ScriptConfigItem(VRScripts.VR_CFG, "-c " + VRScripts.CONFIG_CACHE_LOCATION + cfgFileName);
// 120s is the minimal timeout
Duration timeout = _eachTimeout.withDurationAdded(_eachTimeout.getStandardSeconds(), answerCounts);
if (s_logger.isDebugEnabled()){
s_logger.debug("Aggregate action timeout in seconds is " + timeout.getStandardSeconds());
}
ExecutionResult result = applyConfigToVR(cmd.getRouterAccessIp(), fileConfigItem, timeout);
if (!result.isSuccess()) {
return new Answer(cmd, false, result.getDetails());
}
result = applyConfigToVR(cmd.getRouterAccessIp(), scriptConfigItem, timeout);
if (!result.isSuccess()) {
return new Answer(cmd, false, result.getDetails());
}
return new Answer(cmd, true, "Command aggregation finished");
} finally {
queue.clear();
_vrAggregateCommandsSet.remove(routerName);
}
}
return new Answer(cmd, false, "Fail to recognize aggregation action " + action.toString());
}
}