package org.zstack.compute.vm;
import com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException;
import org.springframework.beans.factory.annotation.Autowire;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.orm.jpa.JpaSystemException;
import org.zstack.core.Platform;
import org.zstack.core.cloudbus.CloudBus;
import org.zstack.core.cloudbus.CloudBusListCallBack;
import org.zstack.core.db.DatabaseFacade;
import org.zstack.core.errorcode.ErrorFacade;
import org.zstack.header.core.workflow.Flow;
import org.zstack.header.core.workflow.FlowException;
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.*;
import org.zstack.header.vm.*;
import org.zstack.identity.AccountManager;
import org.zstack.utils.Utils;
import org.zstack.utils.logging.CLogger;
import org.zstack.utils.network.NetworkUtils;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.List;
import java.util.Map;
import static org.zstack.core.progress.ProgressReportService.taskProgress;
@Configurable(preConstruction = true, autowire = Autowire.BY_TYPE)
public class VmAllocateNicFlow implements Flow {
private static final CLogger logger = Utils.getLogger(VmAllocateNicFlow.class);
@Autowired
protected DatabaseFacade dbf;
@Autowired
protected CloudBus bus;
@Autowired
protected ErrorFacade errf;
@Autowired
protected AccountManager acntMgr;
private VmNicVO persistAndRetryIfMacCollision(VmNicVO vo) {
int tries = 5;
while (tries-- > 0) {
try {
vo = dbf.persistAndRefresh(vo);
return vo;
} catch (JpaSystemException e) {
if (e.getRootCause() instanceof MySQLIntegrityConstraintViolationException &&
e.getRootCause().getMessage().contains("Duplicate entry")) {
logger.debug(String.format("Concurrent mac allocation. Mac[%s] has been allocated, try allocating another one. " +
"The error[Duplicate entry] printed by jdbc.spi.SqlExceptionHelper is no harm, " +
"we will try finding another mac", vo.getMac()));
logger.trace("", e);
vo.setMac(NetworkUtils.generateMacWithDeviceId((short) vo.getDeviceId()));
} else {
throw e;
}
}
}
return null;
}
private void persistNicToDb(List<VmNicInventory> nics) {
for (VmNicInventory nic : nics) {
VmNicVO vo = new VmNicVO();
vo.setUuid(nic.getUuid());
vo.setIp(nic.getIp());
vo.setL3NetworkUuid(nic.getL3NetworkUuid());
vo.setUsedIpUuid(nic.getUsedIpUuid());
vo.setVmInstanceUuid(nic.getVmInstanceUuid());
vo.setDeviceId(nic.getDeviceId());
vo.setMac(nic.getMac());
vo.setNetmask(nic.getNetmask());
vo.setGateway(nic.getGateway());
vo.setInternalName(nic.getInternalName());
vo = persistAndRetryIfMacCollision(vo);
if (vo == null) {
throw new FlowException(errf.instantiateErrorCode(VmErrors.ALLOCATE_MAC_ERROR, "unable to find an available mac address after re-try 5 times, too many collisions"));
}
}
}
@Override
public void run(final FlowTrigger trigger, final Map data) {
taskProgress("create nics");
final VmInstanceSpec spec = (VmInstanceSpec) data.get(VmInstanceConstant.Params.VmInstanceSpec.toString());
List<AllocateIpMsg> msgs = new ArrayList<AllocateIpMsg>();
Map<String, String> vmStaticIps = new StaticIpOperator().getStaticIpbyVmUuid(spec.getVmInventory().getUuid());
for (final L3NetworkInventory nw : spec.getL3Networks()) {
AllocateIpMsg msg = new AllocateIpMsg();
String staticIp = vmStaticIps.get(nw.getUuid());
if (staticIp != null) {
msg.setRequiredIp(staticIp);
}
msg.setL3NetworkUuid(nw.getUuid());
msg.setAllocateStrategy(spec.getIpAllocatorStrategy());
bus.makeTargetServiceIdByResourceUuid(msg, L3NetworkConstant.SERVICE_ID, nw.getUuid());
msgs.add(msg);
}
// it's unlikely a vm having more than 512 nics
final BitSet deviceIdBitmap = new BitSet(512);
for (VmNicInventory nic : spec.getVmInventory().getVmNics()) {
deviceIdBitmap.set(nic.getDeviceId());
}
bus.send(msgs, new CloudBusListCallBack(trigger) {
@Override
public void run(List<MessageReply> replies) {
ErrorCode err = null;
for (MessageReply r : replies) {
if (r.isSuccess()) {
int deviceId = deviceIdBitmap.nextClearBit(0);
deviceIdBitmap.set(deviceId);
AllocateIpReply areply = r.castReply();
VmNicInventory nic = new VmNicInventory();
nic.setUuid(Platform.getUuid());
nic.setIp(areply.getIpInventory().getIp());
nic.setUsedIpUuid(areply.getIpInventory().getUuid());
nic.setVmInstanceUuid(spec.getVmInventory().getUuid());
nic.setL3NetworkUuid(areply.getIpInventory().getL3NetworkUuid());
assert nic.getL3NetworkUuid() != null;
nic.setMac(NetworkUtils.generateMacWithDeviceId((short) deviceId));
nic.setDeviceId(deviceId);
nic.setNetmask(areply.getIpInventory().getNetmask());
nic.setGateway(areply.getIpInventory().getGateway());
nic.setInternalName(VmNicVO.generateNicInternalName(spec.getVmInventory().getInternalId(), nic.getDeviceId()));
spec.getDestNics().add(nic);
} else {
err = r.getError();
}
}
if (err != null) {
trigger.fail(err);
} else {
persistNicToDb(spec.getDestNics());
String acntUuid = acntMgr.getOwnerAccountUuidOfResource(spec.getVmInventory().getUuid());
for (VmNicInventory nic : spec.getDestNics()) {
acntMgr.createAccountResourceRef(acntUuid, nic.getUuid(), VmNicVO.class);
}
trigger.next();
}
}
});
}
@Override
public void rollback(final FlowRollback chain, Map data) {
VmInstanceSpec spec = (VmInstanceSpec) data.get(VmInstanceConstant.Params.VmInstanceSpec.toString());
final List<VmNicInventory> destNics = spec.getDestNics();
if (destNics.isEmpty()) {
chain.rollback();
return;
}
List<ReturnIpMsg> msgs = new ArrayList<ReturnIpMsg>();
final List<String> nicUuids = new ArrayList<String>();
for (VmNicInventory nic : destNics) {
ReturnIpMsg msg = new ReturnIpMsg();
msg.setL3NetworkUuid(nic.getL3NetworkUuid());
msg.setUsedIpUuid(nic.getUsedIpUuid());
bus.makeTargetServiceIdByResourceUuid(msg, L3NetworkConstant.SERVICE_ID, nic.getL3NetworkUuid());
msgs.add(msg);
nicUuids.add(nic.getUuid());
}
bus.send(msgs, 1, new CloudBusListCallBack(chain) {
@Override
public void run(List<MessageReply> replies) {
dbf.removeByPrimaryKeys(nicUuids, VmNicVO.class);
chain.rollback();
}
});
}
}