diff --git a/api/src/com/cloud/agent/api/CheckS2SVpnConnectionsAnswer.java b/api/src/com/cloud/agent/api/CheckS2SVpnConnectionsAnswer.java new file mode 100644 index 00000000000..b9e9a6638ab --- /dev/null +++ b/api/src/com/cloud/agent/api/CheckS2SVpnConnectionsAnswer.java @@ -0,0 +1,55 @@ +package com.cloud.agent.api; + +import java.util.HashMap; +import java.util.Map; + +public class CheckS2SVpnConnectionsAnswer extends Answer { + Map ipToConnected; + Map ipToDetail; + String details; + + protected CheckS2SVpnConnectionsAnswer() { + ipToConnected = new HashMap(); + ipToDetail = new HashMap(); + } + + public CheckS2SVpnConnectionsAnswer(CheckS2SVpnConnectionsCommand cmd, boolean result, String details) { + super(cmd, result, details); + ipToConnected = new HashMap(); + ipToDetail = new HashMap(); + this.details = details; + if (result) { + parseDetails(details); + } + } + + protected void parseDetails(String details) { + String[] lines = details.split("&"); + for (String line : lines) { + String[] words = line.split(":"); + if (words.length != 3) { + //Not something we can parse + return; + } + String ip = words[0]; + boolean connected = words[1].equals("0"); + String detail = words[2]; + ipToConnected.put(ip, connected); + ipToDetail.put(ip, detail); + } + } + + public boolean isConnected(String ip) { + if (this.getResult()) { + return ipToConnected.get(ip); + } + return false; + } + + public String getDetail(String ip) { + if (this.getResult()) { + return ipToDetail.get(ip); + } + return null; + } +} diff --git a/api/src/com/cloud/agent/api/CheckS2SVpnConnectionsCommand.java b/api/src/com/cloud/agent/api/CheckS2SVpnConnectionsCommand.java new file mode 100644 index 00000000000..9401b47be65 --- /dev/null +++ b/api/src/com/cloud/agent/api/CheckS2SVpnConnectionsCommand.java @@ -0,0 +1,23 @@ +package com.cloud.agent.api; + +import java.util.List; + +import com.cloud.agent.api.routing.NetworkElementCommand; + +public class CheckS2SVpnConnectionsCommand extends NetworkElementCommand { + List vpnIps; + + @Override + public boolean executeInSequence() { + return true; + } + + public CheckS2SVpnConnectionsCommand(List vpnIps) { + super(); + this.vpnIps = vpnIps; + } + + public List getVpnIps() { + return vpnIps; + } +} diff --git a/core/src/com/cloud/agent/resource/virtualnetwork/VirtualRoutingResource.java b/core/src/com/cloud/agent/resource/virtualnetwork/VirtualRoutingResource.java index 370477ff4c2..f09f23b65e7 100755 --- a/core/src/com/cloud/agent/resource/virtualnetwork/VirtualRoutingResource.java +++ b/core/src/com/cloud/agent/resource/virtualnetwork/VirtualRoutingResource.java @@ -38,6 +38,8 @@ import com.cloud.agent.api.Answer; import com.cloud.agent.api.BumpUpPriorityCommand; 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.Command; import com.cloud.agent.api.GetDomRVersionAnswer; import com.cloud.agent.api.GetDomRVersionCmd; @@ -135,6 +137,8 @@ public class VirtualRoutingResource implements Manager { return execute((GetDomRVersionCmd)cmd); } else if (cmd instanceof Site2SiteVpnCfgCommand) { return execute((Site2SiteVpnCfgCommand)cmd); + } else if (cmd instanceof CheckS2SVpnConnectionsCommand) { + return execute((CheckS2SVpnConnectionsCommand)cmd); } else { return Answer.createUnsupportedCommandAnswer(cmd); @@ -506,6 +510,21 @@ public class VirtualRoutingResource implements Manager { return command.execute(); } + private CheckS2SVpnConnectionsAnswer execute(CheckS2SVpnConnectionsCommand cmd) { + final String routerIP = cmd.getAccessDetail(NetworkElementCommand.ROUTER_IP); + + String args = ""; + for (String ip : cmd.getVpnIps()) { + args += " " + ip; + } + + final String result = routerProxyWithParser("checkbatchs2svpn.sh", routerIP, args); + if (result == null || result.isEmpty()) { + return new CheckS2SVpnConnectionsAnswer(cmd, false, "CheckS2SVpnConneciontsCommand failed"); + } + return new CheckS2SVpnConnectionsAnswer(cmd, true, result); + } + protected Answer execute(CheckRouterCommand cmd) { final String routerPrivateIPAddress = cmd.getAccessDetail(NetworkElementCommand.ROUTER_IP); diff --git a/core/src/com/cloud/hypervisor/vmware/resource/VmwareResource.java b/core/src/com/cloud/hypervisor/vmware/resource/VmwareResource.java index 31cbf10c428..cf088c186c7 100755 --- a/core/src/com/cloud/hypervisor/vmware/resource/VmwareResource.java +++ b/core/src/com/cloud/hypervisor/vmware/resource/VmwareResource.java @@ -54,6 +54,8 @@ import com.cloud.agent.api.CheckOnHostAnswer; import com.cloud.agent.api.CheckOnHostCommand; 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.CheckVirtualMachineAnswer; import com.cloud.agent.api.CheckVirtualMachineCommand; import com.cloud.agent.api.Command; @@ -443,6 +445,8 @@ public class VmwareResource implements StoragePoolResource, ServerResource, Vmwa answer = execute((SetPortForwardingRulesVpcCommand) cmd); } else if (clz == Site2SiteVpnCfgCommand.class) { answer = execute((Site2SiteVpnCfgCommand) cmd); + } else if (clz == CheckS2SVpnConnectionsCommand.class) { + answer = execute((CheckS2SVpnConnectionsCommand) cmd); } else { answer = Answer.createUnsupportedCommandAnswer(cmd); } @@ -1567,6 +1571,35 @@ public class VmwareResource implements StoragePoolResource, ServerResource, Vmwa return new Answer(cmd); } + protected CheckS2SVpnConnectionsAnswer execute(CheckS2SVpnConnectionsCommand cmd) { + if (s_logger.isDebugEnabled()) { + s_logger.debug("Executing resource CheckS2SVpnConnectionsCommand: " + _gson.toJson(cmd)); + s_logger.debug("Run command on domR " + cmd.getAccessDetail(NetworkElementCommand.ROUTER_IP) + ", /opt/cloud/bin/checkbatchs2svpn.sh "); + } + + Pair result; + try { + VmwareManager mgr = getServiceContext().getStockObject(VmwareManager.CONTEXT_STOCK_NAME); + String controlIp = getRouterSshControlIp(cmd); + result = SshHelper.sshExecute(controlIp, DEFAULT_DOMR_SSHPORT, "root", mgr.getSystemVMKeyFile(), null, + "/opt/cloud/bin/checkbatchs2svpn.sh "); + + if (!result.first()) { + s_logger.error("check site-to-site vpn connections command on domR " + cmd.getAccessDetail(NetworkElementCommand.ROUTER_IP) + " failed, message: " + result.second()); + + return new CheckS2SVpnConnectionsAnswer(cmd, false, result.second()); + } + + if (s_logger.isDebugEnabled()) { + s_logger.debug("check site-to-site vpn connections command on domain router " + cmd.getAccessDetail(NetworkElementCommand.ROUTER_IP) + " completed"); + } + } catch (Throwable e) { + String msg = "CheckS2SVpnConnectionsCommand failed due to " + VmwareHelper.getExceptionMessage(e); + s_logger.error(msg, e); + return new CheckS2SVpnConnectionsAnswer(cmd, false, "CheckS2SVpnConneciontsCommand failed"); + } + return new CheckS2SVpnConnectionsAnswer(cmd, true, result.second()); + } protected Answer execute(CheckRouterCommand cmd) { if (s_logger.isDebugEnabled()) { s_logger.debug("Executing resource CheckRouterCommand: " + _gson.toJson(cmd)); diff --git a/core/src/com/cloud/hypervisor/xen/resource/CitrixResourceBase.java b/core/src/com/cloud/hypervisor/xen/resource/CitrixResourceBase.java index 25a6ea148b5..9d0d94f1abb 100644 --- a/core/src/com/cloud/hypervisor/xen/resource/CitrixResourceBase.java +++ b/core/src/com/cloud/hypervisor/xen/resource/CitrixResourceBase.java @@ -66,6 +66,8 @@ import com.cloud.agent.api.CheckOnHostAnswer; import com.cloud.agent.api.CheckOnHostCommand; 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.CheckVirtualMachineAnswer; import com.cloud.agent.api.CheckVirtualMachineCommand; import com.cloud.agent.api.CleanupNetworkRulesCmd; @@ -545,6 +547,8 @@ public abstract class CitrixResourceBase implements ServerResource, HypervisorRe return execute((SetStaticRouteCommand) cmd); } else if (clazz == Site2SiteVpnCfgCommand.class) { return execute((Site2SiteVpnCfgCommand) cmd); + } else if (clazz == CheckS2SVpnConnectionsCommand.class) { + return execute((CheckS2SVpnConnectionsCommand) cmd); } else { return Answer.createUnsupportedCommandAnswer(cmd); } @@ -1386,6 +1390,19 @@ public abstract class CitrixResourceBase implements ServerResource, HypervisorRe return new Answer(cmd); } + private CheckS2SVpnConnectionsAnswer execute(CheckS2SVpnConnectionsCommand cmd) { + Connection conn = getConnection(); + String args = "checkbatchs2svpn.sh " + cmd.getAccessDetail(NetworkElementCommand.ROUTER_IP); + for (String ip : cmd.getVpnIps()) { + args += " " + ip; + } + String result = callHostPlugin(conn, "vmops", "routerProxy", "args", args); + if (result == null || result.isEmpty()) { + return new CheckS2SVpnConnectionsAnswer(cmd, false, "CheckS2SVpnConneciontsCommand failed"); + } + return new CheckS2SVpnConnectionsAnswer(cmd, true, result); + } + private CheckRouterAnswer execute(CheckRouterCommand cmd) { Connection conn = getConnection(); String args = "checkrouter.sh " + cmd.getAccessDetail(NetworkElementCommand.ROUTER_IP); diff --git a/patches/systemvm/debian/config/opt/cloud/bin/checkbatchs2svpn.sh b/patches/systemvm/debian/config/opt/cloud/bin/checkbatchs2svpn.sh new file mode 100755 index 00000000000..bde9627ab59 --- /dev/null +++ b/patches/systemvm/debian/config/opt/cloud/bin/checkbatchs2svpn.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +for i in $* +do + info=`/opt/cloud/bin/checks2svpn.sh $i` + ret=$? + echo -n "$i:$ret:$info&" +done + diff --git a/patches/systemvm/debian/config/opt/cloud/bin/checks2svpn.sh b/patches/systemvm/debian/config/opt/cloud/bin/checks2svpn.sh index 3198824bc1b..e6bf9e52d31 100755 --- a/patches/systemvm/debian/config/opt/cloud/bin/checks2svpn.sh +++ b/patches/systemvm/debian/config/opt/cloud/bin/checks2svpn.sh @@ -12,20 +12,20 @@ cat /tmp/vpn-$1.status | grep "ISAKMP SA established" > /dev/null isakmpok=$? if [ $isakmpok -ne 0 ] then - echo "ISAKMP SA not found" + echo -n "ISAKMP SA not found" echo "Site-to-site VPN have not connected" exit 12 fi -echo "ISAKMP SA found" +echo -n "ISAKMP SA found;" cat /tmp/vpn-$1.status | grep "IPsec SA established" > /dev/null ipsecok=$? if [ $ipsecok -ne 0 ] then - echo "IPsec SA not found" + echo -n "IPsec SA not found;" echo "Site-to-site VPN have not connected" exit 11 fi -echo "IPsec SA found" +echo -n "IPsec SA found;" echo "Site-to-site VPN have connected" exit 0 diff --git a/server/src/com/cloud/network/router/VirtualNetworkApplianceManagerImpl.java b/server/src/com/cloud/network/router/VirtualNetworkApplianceManagerImpl.java index 93baf7cae66..2a240ab5d92 100755 --- a/server/src/com/cloud/network/router/VirtualNetworkApplianceManagerImpl.java +++ b/server/src/com/cloud/network/router/VirtualNetworkApplianceManagerImpl.java @@ -42,6 +42,8 @@ import com.cloud.agent.api.Answer; import com.cloud.agent.api.BumpUpPriorityCommand; 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.Command; import com.cloud.agent.api.GetDomRVersionAnswer; import com.cloud.agent.api.GetDomRVersionCmd; @@ -127,6 +129,11 @@ import com.cloud.network.Networks.TrafficType; import com.cloud.network.PhysicalNetworkServiceProvider; import com.cloud.network.PublicIpAddress; import com.cloud.network.RemoteAccessVpn; +import com.cloud.network.Site2SiteCustomerGateway; +import com.cloud.network.Site2SiteCustomerGatewayVO; +import com.cloud.network.Site2SiteVpnConnection; +import com.cloud.network.Site2SiteVpnConnectionVO; +import com.cloud.network.Site2SiteVpnGatewayVO; import com.cloud.network.SshKeysDistriMonitor; import com.cloud.network.VirtualNetworkApplianceService; import com.cloud.network.VirtualRouterProvider; @@ -160,6 +167,7 @@ import com.cloud.network.rules.StaticNat; import com.cloud.network.rules.StaticNatImpl; import com.cloud.network.rules.StaticNatRule; import com.cloud.network.rules.dao.PortForwardingRulesDao; +import com.cloud.network.vpn.Site2SiteVpnManager; import com.cloud.offering.ServiceOffering; import com.cloud.offerings.NetworkOfferingVO; import com.cloud.offerings.dao.NetworkOfferingDao; @@ -313,6 +321,8 @@ public class VirtualNetworkApplianceManagerImpl implements VirtualNetworkApplian Site2SiteVpnGatewayDao _s2sVpnGatewayDao; @Inject Site2SiteVpnConnectionDao _s2sVpnConnectionDao; + @Inject + Site2SiteVpnManager _s2sVpnMgr; int _routerRamSize; int _routerCpuMHz; @@ -896,7 +906,79 @@ public class VirtualNetworkApplianceManagerImpl implements VirtualNetworkApplian } } - + protected void updateSite2SiteVpnConnectionState(List routers) { + for (DomainRouterVO router : routers) { + List conns = _s2sVpnMgr.getConnectionsForRouter(router); + if (conns == null || conns.isEmpty()) { + continue; + } + if (router.getState() != State.Running) { + for (Site2SiteVpnConnectionVO conn : conns) { + conn.setState(Site2SiteVpnConnection.State.Disconnected); + _s2sVpnConnectionDao.persist(conn); + } + continue; + } + List ipList = new ArrayList(); + for (Site2SiteVpnConnectionVO conn : conns) { + if (conn.getState() != Site2SiteVpnConnection.State.Connected && + conn.getState() != Site2SiteVpnConnection.State.Disconnected) { + continue; + } + Site2SiteCustomerGateway gw = _s2sCustomerGatewayDao.findById(conn.getCustomerGatewayId()); + ipList.add(gw.getGatewayIp()); + } + String privateIP = router.getPrivateIpAddress(); + HostVO host = _hostDao.findById(router.getHostId()); + if (host == null || host.getStatus() != Status.Up) { + continue; + } else if (host.getManagementServerId() != ManagementServerNode.getManagementServerId()) { + /* Only cover hosts managed by this management server */ + continue; + } else if (privateIP != null) { + final CheckS2SVpnConnectionsCommand command = new CheckS2SVpnConnectionsCommand(ipList); + command.setAccessDetail(NetworkElementCommand.ROUTER_IP, getRouterControlIp(router.getId())); + command.setAccessDetail(NetworkElementCommand.ROUTER_NAME, router.getInstanceName()); + command.setWait(30); + final Answer origAnswer = _agentMgr.easySend(router.getHostId(), command); + CheckS2SVpnConnectionsAnswer answer = null; + if (origAnswer instanceof CheckS2SVpnConnectionsAnswer) { + answer = (CheckS2SVpnConnectionsAnswer)origAnswer; + } else { + s_logger.warn("Unable to update router " + router.getHostName() + "'s VPN connection status"); + continue; + } + if (!answer.getResult()) { + s_logger.warn("Unable to update router " + router.getHostName() + "'s VPN connection status"); + continue; + } + for (Site2SiteVpnConnectionVO conn : conns) { + if (conn.getState() != Site2SiteVpnConnection.State.Connected && + conn.getState() != Site2SiteVpnConnection.State.Disconnected) { + continue; + } + Site2SiteVpnConnection.State oldState = conn.getState(); + Site2SiteCustomerGateway gw = _s2sCustomerGatewayDao.findById(conn.getCustomerGatewayId()); + if (answer.isConnected(gw.getGatewayIp())) { + conn.setState(Site2SiteVpnConnection.State.Connected); + } else { + conn.setState(Site2SiteVpnConnection.State.Disconnected); + } + _s2sVpnConnectionDao.persist(conn); + if (oldState != conn.getState()) { + String title = "Site-to-site Vpn Connection to " + gw.getName() + + " just switch from " + oldState + " to " + conn.getState(); + String context = "Site-to-site Vpn Connection to " + gw.getName() + " on router " + router.getHostName() + + "(id: " + router.getId() + ") " + " just switch from " + oldState + " to " + conn.getState(); + s_logger.info(context); + _alertMgr.sendAlert(AlertManager.ALERT_TYPE_DOMAIN_ROUTER, + router.getDataCenterIdToDeployIn(), router.getPodIdToDeployIn(), title, context); + } + } + } + } + } + protected void updateRoutersRedundantState(List routers) { boolean updated = false; for (DomainRouterVO router : routers) { @@ -1089,6 +1171,7 @@ public class VirtualNetworkApplianceManagerImpl implements VirtualNetworkApplian s_logger.debug("Found " + routers.size() + " routers. "); updateRoutersRedundantState(routers); + updateSite2SiteVpnConnectionState(routers); /* FIXME assumed the a pair of redundant routers managed by same mgmt server, * then the update above can get the latest status */ diff --git a/server/src/com/cloud/network/vpn/Site2SiteVpnManager.java b/server/src/com/cloud/network/vpn/Site2SiteVpnManager.java index a44c3615ad4..567b76549df 100644 --- a/server/src/com/cloud/network/vpn/Site2SiteVpnManager.java +++ b/server/src/com/cloud/network/vpn/Site2SiteVpnManager.java @@ -1,7 +1,13 @@ package com.cloud.network.vpn; +import java.util.List; + +import com.cloud.network.Site2SiteVpnConnectionVO; +import com.cloud.vm.DomainRouterVO; + public interface Site2SiteVpnManager extends Site2SiteVpnService { boolean cleanupVpnConnectionByVpc(long vpcId); boolean cleanupVpnGatewayByVpc(long vpcId); void markDisconnectVpnConnByVpc(long vpcId); + List getConnectionsForRouter(DomainRouterVO router); } diff --git a/server/src/com/cloud/network/vpn/Site2SiteVpnManagerImpl.java b/server/src/com/cloud/network/vpn/Site2SiteVpnManagerImpl.java index 6fbe8f2a341..9121f14d1f3 100644 --- a/server/src/com/cloud/network/vpn/Site2SiteVpnManagerImpl.java +++ b/server/src/com/cloud/network/vpn/Site2SiteVpnManagerImpl.java @@ -61,6 +61,7 @@ import com.cloud.utils.db.SearchBuilder; import com.cloud.utils.db.SearchCriteria; import com.cloud.utils.exception.CloudRuntimeException; import com.cloud.utils.net.NetUtils; +import com.cloud.vm.DomainRouterVO; @Local(value = { Site2SiteVpnManager.class, Site2SiteVpnService.class } ) public class Site2SiteVpnManagerImpl implements Site2SiteVpnManager, Manager { @@ -609,4 +610,16 @@ public class Site2SiteVpnManagerImpl implements Site2SiteVpnManager, Manager { } } } + + @Override + public List getConnectionsForRouter(DomainRouterVO router) { + List conns = new ArrayList(); + // One router for one VPC + Long vpcId = router.getVpcId(); + if (router.getVpcId() == null) { + return conns; + } + conns.addAll(_vpnConnectionDao.listByVpcId(vpcId)); + return conns; + } }