package org.zstack.network.service.virtualrouter.dhcp;
import org.springframework.beans.factory.annotation.Autowire;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.transaction.annotation.Transactional;
import org.zstack.compute.vm.VmSystemTags;
import org.zstack.core.cloudbus.CloudBus;
import org.zstack.core.cloudbus.CloudBusCallBack;
import org.zstack.core.db.DatabaseFacade;
import org.zstack.core.db.SimpleQuery;
import org.zstack.core.db.SimpleQuery.Op;
import org.zstack.core.errorcode.ErrorFacade;
import org.zstack.core.timeout.ApiTimeoutManager;
import org.zstack.header.core.workflow.Flow;
import org.zstack.header.core.workflow.FlowRollback;
import org.zstack.header.core.workflow.FlowTrigger;
import org.zstack.header.errorcode.ErrorCode;
import org.zstack.header.message.MessageReply;
import org.zstack.header.network.l3.L3NetworkDnsVO;
import org.zstack.header.network.l3.L3NetworkDnsVO_;
import org.zstack.header.network.l3.L3NetworkInventory;
import org.zstack.header.network.l3.L3NetworkVO;
import org.zstack.header.network.service.NetworkServiceL3NetworkRefInventory;
import org.zstack.header.network.service.NetworkServiceType;
import org.zstack.header.vm.VmInstanceConstant;
import org.zstack.header.vm.VmInstanceState;
import org.zstack.header.vm.VmNicVO;
import org.zstack.network.service.virtualrouter.*;
import org.zstack.network.service.virtualrouter.VirtualRouterCommands.AddDhcpEntryCmd;
import org.zstack.network.service.virtualrouter.VirtualRouterCommands.AddDhcpEntryRsp;
import org.zstack.network.service.virtualrouter.VirtualRouterCommands.DhcpInfo;
import org.zstack.utils.Utils;
import org.zstack.utils.logging.CLogger;
import static org.zstack.core.Platform.operr;
import javax.persistence.Tuple;
import javax.persistence.TypedQuery;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
@Configurable(preConstruction = true, autowire = Autowire.BY_TYPE)
public class VirtualRouterSyncDHCPOnStartFlow implements Flow {
private static final CLogger logger = Utils.getLogger(VirtualRouterSyncDHCPOnStartFlow.class);
@Autowired
private DatabaseFacade dbf;
@Autowired
private VirtualRouterManager vrMgr;
@Autowired
private ErrorFacade errf;
@Autowired
private CloudBus bus;
@Autowired
private ApiTimeoutManager apiTimeoutManager;
private List<String> getDns(String l3NetworkUuid) {
SimpleQuery<L3NetworkDnsVO> q = dbf.createQuery(L3NetworkDnsVO.class);
q.select(L3NetworkDnsVO_.dns);
q.add(L3NetworkDnsVO_.l3NetworkUuid, Op.EQ, l3NetworkUuid);
return q.listValue();
}
private boolean hasSnatService(L3NetworkInventory l3nw) {
for (NetworkServiceL3NetworkRefInventory ref : l3nw.getNetworkServices()) {
if (ref.getNetworkServiceType().equals(NetworkServiceType.SNAT.toString())) {
return true;
}
}
return false;
}
@Transactional(readOnly = true)
private List<DhcpInfo> getUserVmNicsOnNetwork(VirtualRouterVmInventory vr, String l3NetworkUuid) {
String sql = "select vm.uuid, vm.defaultL3NetworkUuid, nic.uuid, l3.dnsDomain from VmNicVO nic, VmInstanceVO vm, L3NetworkVO l3 where l3.uuid = vm.defaultL3NetworkUuid and vm.state = (:vmState) and nic.vmInstanceUuid = vm.uuid and vm.type = :vmType and nic.l3NetworkUuid = :l3uuid";
TypedQuery<Tuple> q = dbf.getEntityManager().createQuery(sql, Tuple.class);
q.setParameter("l3uuid", l3NetworkUuid);
q.setParameter("vmType", VmInstanceConstant.USER_VM_TYPE);
q.setParameter("vmState", VmInstanceState.Running);
List<Tuple> ts = q.getResultList();
L3NetworkVO l3vo = dbf.getEntityManager().find(L3NetworkVO.class, l3NetworkUuid);
L3NetworkInventory l3inv = L3NetworkInventory.valueOf(l3vo);
List<DhcpInfo> infos = new ArrayList<DhcpInfo>(ts.size());
for (Tuple t : ts) {
String vmUuid = t.get(0, String.class);
String defaultL3Uuid = t.get(1, String.class);
String nicUuid = t.get(2, String.class);
String defaultL3DnsDomain = t.get(3, String.class);
VmNicVO nic = dbf.getEntityManager().find(VmNicVO.class, nicUuid);
DhcpInfo info = new DhcpInfo();
info.setGateway(nic.getGateway());
info.setIp(nic.getIp());
info.setMac(nic.getMac());
info.setVrNicMac(vr.getGuestNic().getMac());
info.setNetmask(nic.getNetmask());
if (l3NetworkUuid.equals(defaultL3Uuid)) {
info.setDefaultL3Network(true);
info.setDnsDomain(defaultL3DnsDomain);
String hostname = VmSystemTags.HOSTNAME.getTokenByResourceUuid(vmUuid, VmSystemTags.HOSTNAME_TOKEN);
if (hostname != null) {
if (info.getDnsDomain() != null) {
hostname = String.format("%s.%s", hostname, info.getDnsDomain());
}
info.setHostname(hostname);
}
info.setDns(Arrays.asList(vr.getGuestNicByL3NetworkUuid(l3NetworkUuid).getIp()));
}
if (hasSnatService(l3inv)) {
info.setDns(Arrays.asList(vr.getGuestNic().getIp()));
} else {
info.setDns(getDns(l3NetworkUuid));
}
infos.add(info);
}
return infos;
}
@Override
public void run(final FlowTrigger chain, Map data) {
final VirtualRouterVmInventory vr = (VirtualRouterVmInventory) data.get(VirtualRouterConstant.Param.VR.toString());
List<String> nwServed = vr.getGuestL3Networks();
List<String> l3Uuids = vrMgr.selectL3NetworksNeedingSpecificNetworkService(nwServed, NetworkServiceType.DHCP);
if (l3Uuids.isEmpty()) {
chain.next();
return;
}
if (VirtualRouterSystemTags.DEDICATED_ROLE_VR.hasTag(vr.getUuid()) && !VirtualRouterSystemTags.VR_DHCP_ROLE.hasTag(vr.getUuid())) {
chain.next();
return;
}
new VirtualRouterRoleManager().makeDhcpRole(vr.getUuid());
AddDhcpEntryCmd cmd = new AddDhcpEntryCmd();
cmd.setRebuild(true);
for (String l3uuid : l3Uuids) {
List<DhcpInfo> infos = getUserVmNicsOnNetwork(vr, l3uuid);
cmd.getDhcpEntries().addAll(infos);
}
if (cmd.getDhcpEntries().isEmpty()) {
chain.next();
return;
}
VirtualRouterAsyncHttpCallMsg msg = new VirtualRouterAsyncHttpCallMsg();
msg.setCommand(cmd);
msg.setCommandTimeout(apiTimeoutManager.getTimeout(cmd.getClass(), "30m"));
msg.setPath(VirtualRouterConstant.VR_ADD_DHCP_PATH);
msg.setVmInstanceUuid(vr.getUuid());
bus.makeTargetServiceIdByResourceUuid(msg, VmInstanceConstant.SERVICE_ID, vr.getUuid());
bus.send(msg, new CloudBusCallBack(chain) {
@Override
public void run(MessageReply reply) {
if (!reply.isSuccess()) {
logger.warn(String.format("unable to program dhcp entries served by virtual router[uuid:%s, ip:%s], %s", vr.getUuid(), vr.getManagementNic().getIp(), reply.getError()));
chain.fail(reply.getError());
return;
}
VirtualRouterAsyncHttpCallReply re = reply.castReply();
AddDhcpEntryRsp ret = re.toResponse(AddDhcpEntryRsp.class);
if (!ret.isSuccess()) {
ErrorCode err = operr("unable to program dhcp entries served by virtual router[uuid:%s, ip:%s], %s", vr.getUuid(), vr.getManagementNic().getIp(), ret.getError());
chain.fail(err);
} else {
logger.debug(String.format("successfully programmed dhcp entries served by virtual router[uuid:%s, ip:%s]", vr.getUuid(), vr.getManagementNic().getIp()));
chain.next();
}
}
});
}
@Override
public void rollback(FlowRollback chain, Map data) {
chain.rollback();
}
}