package org.zstack.network.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.zstack.core.cloudbus.CloudBus;
import org.zstack.core.cloudbus.MessageSafe;
import org.zstack.core.componentloader.PluginRegistry;
import org.zstack.core.db.DatabaseFacade;
import org.zstack.core.db.DbEntityLister;
import org.zstack.core.db.SimpleQuery;
import org.zstack.core.db.SimpleQuery.Op;
import org.zstack.core.errorcode.ErrorFacade;
import org.zstack.core.notification.N;
import org.zstack.core.workflow.FlowChainBuilder;
import org.zstack.header.AbstractService;
import org.zstack.header.core.Completion;
import org.zstack.header.core.NoErrorCompletion;
import org.zstack.header.core.workflow.*;
import org.zstack.header.errorcode.ErrorCode;
import org.zstack.header.errorcode.OperationFailureException;
import org.zstack.header.exception.CloudRuntimeException;
import org.zstack.header.message.APIMessage;
import org.zstack.header.message.Message;
import org.zstack.header.network.NetworkException;
import org.zstack.header.network.l2.L2NetworkInventory;
import org.zstack.header.network.l2.L2NetworkVO;
import org.zstack.header.network.l3.L3NetworkInventory;
import org.zstack.header.network.l3.L3NetworkVO;
import org.zstack.header.network.service.*;
import org.zstack.header.network.service.NetworkServiceExtensionPoint.NetworkServiceExtensionPosition;
import org.zstack.header.vm.*;
import org.zstack.query.QueryFacade;
import org.zstack.search.GetQuery;
import org.zstack.search.SearchQuery;
import org.zstack.utils.Utils;
import org.zstack.utils.logging.CLogger;
import static org.zstack.core.Platform.operr;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class NetworkServiceManagerImpl extends AbstractService implements NetworkServiceManager, PreVmInstantiateResourceExtensionPoint,
VmReleaseResourceExtensionPoint, PostVmInstantiateResourceExtensionPoint, ReleaseNetworkServiceOnDetachingNicExtensionPoint,
InstantiateResourceOnAttachingNicExtensionPoint {
private static final CLogger logger = Utils.getLogger(NetworkServiceManagerImpl.class);
@Autowired
private CloudBus bus;
@Autowired
private PluginRegistry pluginRgty;
@Autowired
private DatabaseFacade dbf;
@Autowired
private DbEntityLister dl;
@Autowired
private QueryFacade qf;
@Autowired
private ErrorFacade errf;
private Map<String, NetworkServiceProviderFactory> providerFactories = new HashMap<String, NetworkServiceProviderFactory>();
private Map<String, ApplyNetworkServiceExtensionPoint> providerExts = new HashMap<String, ApplyNetworkServiceExtensionPoint>();
private List<NetworkServiceExtensionPoint> nsExts = new ArrayList<NetworkServiceExtensionPoint>();
private void populateExtensions() {
for (NetworkServiceProviderFactory extp : pluginRgty.getExtensionList(NetworkServiceProviderFactory.class)) {
NetworkServiceProviderFactory old = providerFactories.get(extp.getType().toString());
if (old != null) {
throw new CloudRuntimeException(String.format("duplicate NetworkServiceProviderFactory[%s, %s] for type[%s]",
extp.getClass().getName(), old.getClass().getName(), extp.getType()));
}
providerFactories.put(extp.getType().toString(), extp);
}
for (ApplyNetworkServiceExtensionPoint extp : pluginRgty.getExtensionList(ApplyNetworkServiceExtensionPoint.class)) {
ApplyNetworkServiceExtensionPoint old = providerExts.get(extp.getProviderType().toString());
if (old != null) {
throw new CloudRuntimeException(String.format("duplicate ApplyNetworkServiceExtensionPoint[%s, %s] for type[%s]",
extp.getClass().getName(), old.getClass().getName(), extp.getProviderType()));
}
providerExts.put(extp.getProviderType().toString(), extp);
}
nsExts = pluginRgty.getExtensionList(NetworkServiceExtensionPoint.class);
}
private NetworkServiceProviderFactory getProviderFactory(String type) {
NetworkServiceProviderFactory factory = providerFactories.get(type);
if (factory == null) {
throw new IllegalArgumentException(String.format("unable to find NetworkServiceProviderFactory for type[%s]", type));
}
return factory;
}
@Override
@MessageSafe
public void handleMessage(Message msg) {
if (msg instanceof APIMessage) {
handleApiMessage((APIMessage) msg);
} else {
handleLocalMessage(msg);
}
}
private void handleLocalMessage(Message msg) {
bus.dealWithUnknownMessage(msg);
}
private void handleApiMessage(APIMessage msg) {
if (msg instanceof APIAttachNetworkServiceProviderToL2NetworkMsg) {
handle((APIAttachNetworkServiceProviderToL2NetworkMsg) msg);
} else if (msg instanceof APIDetachNetworkServiceProviderFromL2NetworkMsg) {
handle((APIDetachNetworkServiceProviderFromL2NetworkMsg)msg);
} else if (msg instanceof APIListNetworkServiceProviderMsg) {
handle((APIListNetworkServiceProviderMsg)msg);
} else if (msg instanceof APISearchNetworkServiceProviderMsg) {
handle((APISearchNetworkServiceProviderMsg)msg);
} else if (msg instanceof APIGetNetworkServiceProviderMsg) {
handle((APIGetNetworkServiceProviderMsg) msg);
} else if (msg instanceof APIQueryNetworkServiceProviderMsg) {
handle((APIQueryNetworkServiceProviderMsg) msg);
} else if (msg instanceof APIGetNetworkServiceTypesMsg) {
handle((APIGetNetworkServiceTypesMsg) msg);
} else {
bus.dealWithUnknownMessage(msg);
}
}
private void handle(APIGetNetworkServiceTypesMsg msg) {
Map<String, List<String>> ret = new HashMap<String, List<String>>();
List<NetworkServiceTypeVO> types = dbf.listAll(NetworkServiceTypeVO.class);
for (NetworkServiceTypeVO vo : types) {
List<String> providers = ret.get(vo.getType());
if (providers == null) {
providers = new ArrayList<String>();
ret.put(vo.getType(), providers);
}
providers.add(vo.getNetworkServiceProviderUuid());
}
APIGetNetworkServiceTypesReply reply = new APIGetNetworkServiceTypesReply();
reply.setServiceAndProviderTypes(ret);
bus.reply(msg, reply);
}
private void handle(APIQueryNetworkServiceProviderMsg msg) {
List<NetworkServiceProviderInventory> invs = qf.query(msg, NetworkServiceProviderInventory.class);
APIQueryNetworkServiceProviderReply reply = new APIQueryNetworkServiceProviderReply();
reply.setInventories(invs);
bus.reply(msg, reply);
}
private void handle(APIGetNetworkServiceProviderMsg msg) {
GetQuery q = new GetQuery();
String res = q.getAsString(msg, NetworkServiceProviderInventory.class);
APIGetNetworkServiceProviderReply reply = new APIGetNetworkServiceProviderReply();
reply.setInventory(res);
bus.reply(msg, reply);
}
private void handle(APISearchNetworkServiceProviderMsg msg) {
SearchQuery<NetworkServiceProviderInventory> q = SearchQuery.create(msg, NetworkServiceProviderInventory.class);
APISearchNetworkServiceProviderReply reply = new APISearchNetworkServiceProviderReply();
String res = q.listAsString();
reply.setContent(res);
bus.reply(msg, reply);
}
private void handle(APIListNetworkServiceProviderMsg msg) {
List<NetworkServiceProviderVO> vos = dl.listByApiMessage(msg, NetworkServiceProviderVO.class);
List<NetworkServiceProviderInventory> invs = NetworkServiceProviderInventory.valueOf(vos);
APIListNetworkServiceProviderReply reply = new APIListNetworkServiceProviderReply();
reply.setInventories(invs);
bus.reply(msg, reply);
}
private void handle(APIDetachNetworkServiceProviderFromL2NetworkMsg msg) {
NetworkServiceProviderVO vo = dbf.findByUuid(msg.getNetworkServiceProviderUuid(), NetworkServiceProviderVO.class);
NetworkServiceProviderFactory factory = getProviderFactory(vo.getType());
NetworkServiceProvider provider = factory.getNetworkServiceProvider(vo);
L2NetworkVO l2vo = dbf.findByUuid(msg.getL2NetworkUuid(), L2NetworkVO.class);
APIDetachNetworkServiceProviderFromL2NetworkEvent evt = new APIDetachNetworkServiceProviderFromL2NetworkEvent(msg.getId());
try {
provider.detachFromL2Network(L2NetworkInventory.valueOf(l2vo), msg);
} catch (NetworkException e) {
String err = String.format("unable to detach network service provider[uuid:%s, name:%s, type:%s] to l2network[uuid:%s, name:%s, type:%s], %s",
vo.getUuid(), vo.getName(), vo.getType(), l2vo.getUuid(), l2vo.getName(), l2vo.getType(), e.getMessage());
logger.warn(err, e);
evt.setError(errf.instantiateErrorCode(NetworkServiceErrors.DETACH_NETWORK_SERVICE_PROVIDER_ERROR, err));
bus.publish(evt);
return;
}
SimpleQuery<NetworkServiceProviderL2NetworkRefVO> query = dbf.createQuery(NetworkServiceProviderL2NetworkRefVO.class);
query.select(NetworkServiceProviderL2NetworkRefVO_.id);
query.add(NetworkServiceProviderL2NetworkRefVO_.l2NetworkUuid, Op.EQ, l2vo.getUuid());
query.add(NetworkServiceProviderL2NetworkRefVO_.networkServiceProviderUuid, Op.EQ, vo.getUuid());
Long id = query.findValue();
if (id != null) {
dbf.removeByPrimaryKey(id, NetworkServiceProviderL2NetworkRefVO.class);
}
vo = dbf.findByUuid(vo.getUuid(), NetworkServiceProviderVO.class);
evt.setInventory(NetworkServiceProviderInventory.valueOf(vo));
String info = String.format("successfully detach network service provider[uuid:%s, name:%s, type:%s] to l2network[uuid:%s, name:%s, type:%s]",
vo.getUuid(), vo.getName(), vo.getType(), l2vo.getUuid(), l2vo.getName(), l2vo.getType());
logger.debug(info);
bus.publish(evt);
}
private void handle(APIAttachNetworkServiceProviderToL2NetworkMsg msg) {
NetworkServiceProviderVO vo = dbf.findByUuid(msg.getNetworkServiceProviderUuid(), NetworkServiceProviderVO.class);
NetworkServiceProviderFactory factory = getProviderFactory(vo.getType());
NetworkServiceProvider provider = factory.getNetworkServiceProvider(vo);
L2NetworkVO l2vo = dbf.findByUuid(msg.getL2NetworkUuid(), L2NetworkVO.class);
APIAttachNetworkServiceProviderToL2NetworkEvent evt = new APIAttachNetworkServiceProviderToL2NetworkEvent(msg.getId());
try {
provider.attachToL2Network(L2NetworkInventory.valueOf(l2vo), msg);
} catch (NetworkException e) {
String err = String.format("unable to attach network service provider[uuid:%s, name:%s, type:%s] to l2network[uuid:%s, name:%s, type:%s], %s",
vo.getUuid(), vo.getName(), vo.getType(), l2vo.getUuid(), l2vo.getName(), l2vo.getType(), e.getMessage());
logger.warn(err, e);
evt.setError(errf.instantiateErrorCode(NetworkServiceErrors.ATTACH_NETWORK_SERVICE_PROVIDER_ERROR, err));
bus.publish(evt);
return;
}
NetworkServiceProviderL2NetworkRefVO ref = new NetworkServiceProviderL2NetworkRefVO();
ref.setL2NetworkUuid(l2vo.getUuid());
ref.setNetworkServiceProviderUuid(vo.getUuid());
dbf.persist(ref);
vo = dbf.findByUuid(vo.getUuid(), NetworkServiceProviderVO.class);
evt.setInventory(NetworkServiceProviderInventory.valueOf(vo));
String info = String.format("successfully attach network service provider[uuid:%s, name:%s, type:%s] to l2network[uuid:%s, name:%s, type:%s]",
vo.getUuid(), vo.getName(), vo.getType(), l2vo.getUuid(), l2vo.getName(), l2vo.getType());
logger.debug(info);
bus.publish(evt);
}
@Override
public String getId() {
return bus.makeLocalServiceId(NetworkServiceConstants.SERVICE_ID);
}
@Override
public boolean start() {
populateExtensions();
return true;
}
@Override
public boolean stop() {
return true;
}
@Override
public void preBeforeInstantiateVmResource(VmInstanceSpec spec) throws VmInstantiateResourceException {
}
private void applyNetworkServices(final VmInstanceSpec spec, NetworkServiceExtensionPosition position, final Completion completion) {
if (!spec.getVmInventory().getType().equals(VmInstanceConstant.USER_VM_TYPE)) {
completion.success();
return;
}
if (nsExts.isEmpty()) {
completion.success();
return;
}
if (spec.getL3Networks() == null || spec.getL3Networks().isEmpty()) {
completion.success();
return;
}
// we run into this situation when VM nics are all detached and the
// VM is being rebooted
if (spec.getDestNics().isEmpty()) {
completion.success();
return;
}
List<String> nsTypes = spec.getRequiredNetworkServiceTypes();
FlowChain schain = FlowChainBuilder.newSimpleFlowChain().setName(String.format("apply-network-service-to-vm-%s", spec.getVmInventory().getUuid()));
schain.allowEmptyFlow();
for (final NetworkServiceExtensionPoint ns : nsExts) {
if (ns.getNetworkServiceExtensionPosition() != position) {
continue;
}
if (!nsTypes.contains(ns.getNetworkServiceType().toString())) {
continue;
}
Flow flow = new Flow() {
String __name__ = String.format("apply-network-service-%s", ns.getNetworkServiceType());
@Override
public void run(final FlowTrigger chain, Map data) {
logger.debug(String.format("NetworkServiceExtensionPoint[%s] is asking back ends to apply network service[%s] if needed", ns.getClass().getName(), ns.getNetworkServiceType()));
ns.applyNetworkService(spec, data, new Completion(chain) {
@Override
public void success() {
chain.next();
}
@Override
public void fail(ErrorCode errorCode) {
chain.fail(errorCode);
}
});
}
@Override
public void rollback(final FlowRollback chain, Map data) {
logger.debug(String.format("NetworkServiceExtensionPoint[%s] is asking back ends to release network service[%s] if needed", ns.getClass().getName(), ns.getNetworkServiceType()));
ns.releaseNetworkService(spec, data, new NoErrorCompletion() {
@Override
public void done() {
chain.rollback();
}
});
}
};
schain.then(flow);
}
schain.error(new FlowErrorHandler(completion) {
@Override
public void handle(ErrorCode err, Map data) {
completion.fail(err);
}
}).done(new FlowDoneHandler(completion) {
@Override
public void handle(Map data) {
completion.success();
}
}).start();
}
@Override
public void preInstantiateVmResource(final VmInstanceSpec spec, final Completion completion) {
applyNetworkServices(spec, NetworkServiceExtensionPosition.BEFORE_VM_CREATED, completion);
}
@Override
public NetworkServiceProviderType getTypeOfNetworkServiceProviderForService(String l3NetworkUuid, NetworkServiceType serviceType) {
L3NetworkVO l3vo = dbf.findByUuid(l3NetworkUuid, L3NetworkVO.class);
L3NetworkInventory l3inv = L3NetworkInventory.valueOf(l3vo);
NetworkServiceL3NetworkRefInventory targetRef = null;
for (NetworkServiceL3NetworkRefInventory ref : l3inv.getNetworkServices()) {
if (ref.getNetworkServiceType().equals(serviceType.toString())) {
targetRef = ref;
break;
}
}
if (targetRef == null) {
throw new OperationFailureException(operr("L3Network[uuid:%s] doesn't have network service[type:%s] enabled or no provider provides this network service",
l3NetworkUuid, serviceType));
}
SimpleQuery<NetworkServiceProviderVO> q = dbf.createQuery(NetworkServiceProviderVO.class);
q.select(NetworkServiceProviderVO_.type);
q.add(NetworkServiceProviderVO_.uuid, Op.EQ, targetRef.getNetworkServiceProviderUuid());
String providerType = q.findValue();
return NetworkServiceProviderType.valueOf(providerType);
}
@Override
public void preReleaseVmResource(final VmInstanceSpec spec, final Completion completion) {
releaseNetworkServices(spec, NetworkServiceExtensionPosition.BEFORE_VM_CREATED, new NoErrorCompletion(completion) {
@Override
public void done() {
completion.success();
}
});
}
@Override
public void postBeforeInstantiateVmResource(VmInstanceSpec spec) {
}
@Override
public void postInstantiateVmResource(final VmInstanceSpec spec, final Completion completion) {
applyNetworkServices(spec, NetworkServiceExtensionPosition.AFTER_VM_CREATED, completion);
}
private void releaseNetworkServices(final VmInstanceSpec spec, NetworkServiceExtensionPosition position, final NoErrorCompletion completion) {
if (!spec.getVmInventory().getType().equals(VmInstanceConstant.USER_VM_TYPE)) {
completion.done();
return;
}
if (nsExts.isEmpty()) {
completion.done();
return;
}
// we run into this situation when VM nics are all detached and the
// VM is being rebooted
if (spec.getDestNics().isEmpty()) {
completion.done();
return;
}
List<String> nsTypes = spec.getRequiredNetworkServiceTypes();
FlowChain schain = FlowChainBuilder.newSimpleFlowChain().setName(String.format("release-network-services-from-vm-%s", spec.getVmInventory().getUuid()));
schain.allowEmptyFlow();
for (final NetworkServiceExtensionPoint ns : nsExts) {
if (position != null && ns.getNetworkServiceExtensionPosition() != position) {
continue;
}
if (!nsTypes.contains(ns.getNetworkServiceType().toString())) {
continue;
}
NoRollbackFlow flow = new NoRollbackFlow() {
String __name__ = String.format("release-network-service-%s", ns.getNetworkServiceType());
@Override
public void run(final FlowTrigger chain, Map data) {
logger.debug(String.format("NetworkServiceExtensionPoint[%s] is asking back ends to release network service[%s] if needed", ns.getClass().getName(), ns.getNetworkServiceType()));
ns.releaseNetworkService(spec, data, new NoErrorCompletion() {
@Override
public void done() {
chain.next();
}
});
}
};
schain.then(flow);
}
schain.done(new FlowDoneHandler(completion) {
@Override
public void handle(Map data) {
logger.debug(String.format("successfully released network services for vm[uuid:%s, name:%s]", spec.getVmInventory().getUuid(), spec.getVmInventory().getName()));
completion.done();
}
}).start();
}
@Override
public void postReleaseVmResource(final VmInstanceSpec spec, final Completion completion) {
releaseNetworkServices(spec, NetworkServiceExtensionPosition.AFTER_VM_CREATED, new NoErrorCompletion(completion) {
@Override
public void done() {
completion.success();
}
});
}
@Override
public void releaseVmResource(VmInstanceSpec spec, final Completion completion) {
releaseNetworkServices(spec, null, new NoErrorCompletion(completion) {
@Override
public void done() {
completion.success();
}
});
}
@Override
public void releaseResourceOnDetachingNic(VmInstanceSpec spec, VmNicInventory nic, NoErrorCompletion completion) {
releaseNetworkServices(spec, null, completion);
}
@Override
public void instantiateResourceOnAttachingNic(VmInstanceSpec spec, L3NetworkInventory l3, Completion completion) {
preInstantiateVmResource(spec, completion);
}
@Override
public void releaseResourceOnAttachingNic(final VmInstanceSpec spec, final L3NetworkInventory l3, final NoErrorCompletion completion) {
releaseVmResource(spec, new Completion(completion) {
@Override
public void success() {
completion.done();
}
@Override
public void fail(ErrorCode errorCode) {
N.New(VmInstanceVO.class, spec.getVmInventory().getUuid()).warn_("unable to release a network service of the VM[uuid:%s] when rolling back an attached" +
" L3 network[uuid: %s], %s. You may need to reboot the VM to fix the issue", spec.getVmInventory().getUuid(), l3.getUuid(), errorCode);
completion.done();
}
});
}
}