mirror of https://github.com/apache/cloudstack.git
464 lines
20 KiB
Java
464 lines
20 KiB
Java
// 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.network.element;
|
|
|
|
import java.util.HashMap;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.Set;
|
|
|
|
import javax.inject.Inject;
|
|
|
|
import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
|
|
import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager;
|
|
import org.apache.cloudstack.engine.subsystem.api.storage.EndPoint;
|
|
import org.apache.cloudstack.engine.subsystem.api.storage.EndPointSelector;
|
|
import org.apache.cloudstack.storage.configdrive.ConfigDrive;
|
|
import org.apache.cloudstack.storage.configdrive.ConfigDriveBuilder;
|
|
import org.apache.cloudstack.storage.to.TemplateObjectTO;
|
|
import org.apache.log4j.Logger;
|
|
|
|
import com.cloud.agent.AgentManager;
|
|
import com.cloud.agent.api.Answer;
|
|
import com.cloud.agent.api.HandleConfigDriveIsoCommand;
|
|
import com.cloud.agent.api.to.DiskTO;
|
|
import com.cloud.configuration.ConfigurationManager;
|
|
import com.cloud.dc.dao.DataCenterDao;
|
|
import com.cloud.deploy.DeployDestination;
|
|
import com.cloud.exception.ConcurrentOperationException;
|
|
import com.cloud.exception.InsufficientCapacityException;
|
|
import com.cloud.exception.ResourceUnavailableException;
|
|
import com.cloud.exception.UnsupportedServiceException;
|
|
import com.cloud.host.dao.HostDao;
|
|
import com.cloud.network.Network;
|
|
import com.cloud.network.Network.Capability;
|
|
import com.cloud.network.Network.Provider;
|
|
import com.cloud.network.Network.Service;
|
|
import com.cloud.network.NetworkMigrationResponder;
|
|
import com.cloud.network.NetworkModel;
|
|
import com.cloud.network.Networks.TrafficType;
|
|
import com.cloud.network.PhysicalNetworkServiceProvider;
|
|
import com.cloud.offering.NetworkOffering;
|
|
import com.cloud.service.dao.ServiceOfferingDao;
|
|
import com.cloud.storage.DataStoreRole;
|
|
import com.cloud.storage.Storage;
|
|
import com.cloud.storage.StoragePool;
|
|
import com.cloud.storage.Volume;
|
|
import com.cloud.storage.VolumeVO;
|
|
import com.cloud.storage.dao.GuestOSCategoryDao;
|
|
import com.cloud.storage.dao.GuestOSDao;
|
|
import com.cloud.storage.dao.VolumeDao;
|
|
import com.cloud.utils.component.AdapterBase;
|
|
import com.cloud.utils.crypt.DBEncryptionUtil;
|
|
import com.cloud.utils.exception.CloudRuntimeException;
|
|
import com.cloud.utils.fsm.StateListener;
|
|
import com.cloud.utils.fsm.StateMachine2;
|
|
import com.cloud.vm.Nic;
|
|
import com.cloud.vm.NicProfile;
|
|
import com.cloud.vm.ReservationContext;
|
|
import com.cloud.vm.UserVmDetailVO;
|
|
import com.cloud.vm.UserVmVO;
|
|
import com.cloud.vm.VirtualMachine;
|
|
import com.cloud.vm.VirtualMachineManager;
|
|
import com.cloud.vm.VirtualMachineProfile;
|
|
import com.cloud.vm.dao.UserVmDao;
|
|
import com.cloud.vm.dao.UserVmDetailsDao;
|
|
|
|
public class ConfigDriveNetworkElement extends AdapterBase implements NetworkElement, UserDataServiceProvider,
|
|
StateListener<VirtualMachine.State, VirtualMachine.Event, VirtualMachine>, NetworkMigrationResponder {
|
|
private static final Logger LOG = Logger.getLogger(ConfigDriveNetworkElement.class);
|
|
|
|
private static final Map<Service, Map<Capability, String>> capabilities = setCapabilities();
|
|
|
|
@Inject
|
|
NetworkModel _networkMgr;
|
|
@Inject
|
|
UserVmDao _userVmDao;
|
|
@Inject
|
|
UserVmDetailsDao _userVmDetailsDao;
|
|
@Inject
|
|
ConfigurationManager _configMgr;
|
|
@Inject
|
|
DataCenterDao _dcDao;
|
|
@Inject
|
|
ServiceOfferingDao _serviceOfferingDao;
|
|
@Inject
|
|
NetworkModel _networkModel;
|
|
@Inject
|
|
GuestOSCategoryDao _guestOSCategoryDao;
|
|
@Inject
|
|
GuestOSDao _guestOSDao;
|
|
@Inject
|
|
VolumeDao _volumeDao;
|
|
@Inject
|
|
HostDao _hostDao;
|
|
@Inject
|
|
AgentManager agentManager;
|
|
@Inject
|
|
DataStoreManager _dataStoreMgr;
|
|
@Inject
|
|
EndPointSelector _ep;
|
|
|
|
private final static Integer CONFIGDRIVEDISKSEQ = 4;
|
|
|
|
private boolean canHandle(TrafficType trafficType) {
|
|
return trafficType.equals(TrafficType.Guest);
|
|
}
|
|
|
|
@Override
|
|
public boolean start() {
|
|
VirtualMachine.State.getStateMachine().registerListener(this);
|
|
return super.start();
|
|
}
|
|
|
|
@Override
|
|
public boolean implement(Network network, NetworkOffering offering, DeployDestination dest, ReservationContext context) throws ResourceUnavailableException, ConcurrentOperationException,
|
|
InsufficientCapacityException {
|
|
return canHandle(offering.getTrafficType());
|
|
}
|
|
|
|
@Override
|
|
public boolean prepare(Network network, NicProfile nic, VirtualMachineProfile vmProfile, DeployDestination dest, ReservationContext context) throws ConcurrentOperationException,
|
|
InsufficientCapacityException, ResourceUnavailableException {
|
|
return true;
|
|
}
|
|
|
|
@Override
|
|
public boolean release(Network network, NicProfile nic, VirtualMachineProfile vm, ReservationContext context) {
|
|
if (!nic.isDefaultNic()) {
|
|
return true;
|
|
}
|
|
|
|
try {
|
|
return deleteConfigDriveIso(vm.getVirtualMachine());
|
|
} catch (ResourceUnavailableException e) {
|
|
LOG.error("Failed to delete config drive due to: ", e);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public boolean shutdown(Network network, ReservationContext context, boolean cleanup) throws ConcurrentOperationException, ResourceUnavailableException {
|
|
return true; // assume that the agent will remove userdata etc
|
|
}
|
|
|
|
@Override
|
|
public boolean destroy(Network config, ReservationContext context) throws ConcurrentOperationException, ResourceUnavailableException {
|
|
return true; // assume that the agent will remove userdata etc
|
|
}
|
|
|
|
@Override
|
|
public Provider getProvider() {
|
|
return Provider.ConfigDrive;
|
|
}
|
|
|
|
@Override
|
|
public Map<Service, Map<Capability, String>> getCapabilities() {
|
|
return capabilities;
|
|
}
|
|
|
|
private static Map<Service, Map<Capability, String>> setCapabilities() {
|
|
Map<Service, Map<Capability, String>> capabilities = new HashMap<>();
|
|
capabilities.put(Service.UserData, null);
|
|
return capabilities;
|
|
}
|
|
|
|
@Override
|
|
public boolean isReady(PhysicalNetworkServiceProvider provider) {
|
|
return true;
|
|
}
|
|
|
|
@Override
|
|
public boolean shutdownProviderInstances(PhysicalNetworkServiceProvider provider, ReservationContext context) throws ConcurrentOperationException, ResourceUnavailableException {
|
|
return true;
|
|
}
|
|
|
|
@Override
|
|
public boolean canEnableIndividualServices() {
|
|
return false;
|
|
}
|
|
|
|
private String getSshKey(VirtualMachineProfile profile) {
|
|
final UserVmDetailVO vmDetailSshKey = _userVmDetailsDao.findDetail(profile.getId(), "SSH.PublicKey");
|
|
return (vmDetailSshKey!=null ? vmDetailSshKey.getValue() : null);
|
|
}
|
|
|
|
@Override
|
|
public boolean addPasswordAndUserdata(Network network, NicProfile nic, VirtualMachineProfile profile, DeployDestination dest, ReservationContext context)
|
|
throws ConcurrentOperationException, InsufficientCapacityException, ResourceUnavailableException {
|
|
return (canHandle(network.getTrafficType())
|
|
&& configureConfigDriveData(profile, nic))
|
|
&& createConfigDriveIso(profile, dest);
|
|
}
|
|
|
|
@Override
|
|
public boolean savePassword(final Network network, final NicProfile nic, final VirtualMachineProfile vm) throws ResourceUnavailableException {
|
|
// savePassword is called by resetPasswordForVirtualMachine API which requires VM to be shutdown
|
|
// Upper layers should save password in db, we do not need to update/create config drive iso at this point
|
|
// Config drive will be created with updated password when VM starts in future
|
|
if (vm != null && vm.getVirtualMachine().getState().equals(VirtualMachine.State.Running)) {
|
|
throw new CloudRuntimeException("VM should to stopped to reset password");
|
|
}
|
|
|
|
final boolean canHandle = canHandle(network.getTrafficType());
|
|
|
|
if (canHandle) {
|
|
storePasswordInVmDetails(vm);
|
|
}
|
|
|
|
return canHandle;
|
|
}
|
|
|
|
@Override
|
|
public boolean saveSSHKey(final Network network, final NicProfile nic, final VirtualMachineProfile vm, final String sshPublicKey) throws ResourceUnavailableException {
|
|
// saveSSHKey is called by resetSSHKeyForVirtualMachine API which requires VM to be shutdown
|
|
// Upper layers should save ssh public key in db, we do not need to update/create config drive iso at this point
|
|
// Config drive will be created with updated password when VM starts in future
|
|
if (vm != null && vm.getVirtualMachine().getState().equals(VirtualMachine.State.Running)) {
|
|
throw new CloudRuntimeException("VM should to stopped to reset password");
|
|
}
|
|
|
|
final boolean canHandle = canHandle(network.getTrafficType());
|
|
|
|
if (canHandle) {
|
|
storePasswordInVmDetails(vm);
|
|
}
|
|
|
|
return canHandle;
|
|
}
|
|
|
|
@Override
|
|
public boolean saveUserData(final Network network, final NicProfile nic, final VirtualMachineProfile vm) throws ResourceUnavailableException {
|
|
// saveUserData is called by updateVirtualMachine API which requires VM to be shutdown
|
|
// Upper layers should save userdata in db, we do not need to update/create config drive iso at this point
|
|
// Config drive will be created with updated password when VM starts in future
|
|
if (vm != null && vm.getVirtualMachine().getState().equals(VirtualMachine.State.Running)) {
|
|
throw new CloudRuntimeException("VM should to stopped to reset password");
|
|
}
|
|
return canHandle(network.getTrafficType());
|
|
}
|
|
|
|
/**
|
|
* Store password in vm details so it can be picked up during VM start.
|
|
*/
|
|
private void storePasswordInVmDetails(VirtualMachineProfile vm) {
|
|
final String password = (String) vm.getParameter(VirtualMachineProfile.Param.VmPassword);
|
|
final String password_encrypted = DBEncryptionUtil.encrypt(password);
|
|
final UserVmVO userVmVO = _userVmDao.findById(vm.getId());
|
|
|
|
_userVmDetailsDao.addDetail(vm.getId(), "password", password_encrypted, false);
|
|
|
|
userVmVO.setUpdateParameters(true);
|
|
_userVmDao.update(userVmVO.getId(), userVmVO);
|
|
}
|
|
|
|
@Override
|
|
public boolean verifyServicesCombination(Set<Service> services) {
|
|
return true;
|
|
}
|
|
|
|
@Override
|
|
public boolean preStateTransitionEvent(VirtualMachine.State oldState, VirtualMachine.Event event, VirtualMachine.State newState, VirtualMachine vo, boolean status, Object opaque) {
|
|
return true;
|
|
}
|
|
|
|
@Override
|
|
public boolean postStateTransitionEvent(StateMachine2.Transition<VirtualMachine.State, VirtualMachine.Event> transition, VirtualMachine vm, boolean status, Object opaque) {
|
|
if (transition.getToState().equals(VirtualMachine.State.Expunging) && transition.getEvent().equals(VirtualMachine.Event.ExpungeOperation)) {
|
|
Nic nic = _networkModel.getDefaultNic(vm.getId());
|
|
if (nic == null) {
|
|
return true;
|
|
}
|
|
try {
|
|
final Network network = _networkMgr.getNetwork(nic.getNetworkId());
|
|
final UserDataServiceProvider userDataUpdateProvider = _networkModel.getUserDataUpdateProvider(network);
|
|
final Provider provider = userDataUpdateProvider.getProvider();
|
|
if (provider.equals(Provider.ConfigDrive)) {
|
|
try {
|
|
return deleteConfigDriveIso(vm);
|
|
} catch (ResourceUnavailableException e) {
|
|
LOG.error("Failed to delete config drive due to: ", e);
|
|
return false;
|
|
}
|
|
}
|
|
} catch (UnsupportedServiceException usse) {}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
@Override
|
|
public boolean prepareMigration(NicProfile nic, Network network, VirtualMachineProfile vm, DeployDestination dest, ReservationContext context) {
|
|
if (nic.isDefaultNic() && _networkModel.getUserDataUpdateProvider(network).getProvider().equals(Provider.ConfigDrive)) {
|
|
LOG.trace(String.format("[prepareMigration] for vm: %s", vm.getInstanceName()));
|
|
final DataStore dataStore = findDataStore(vm, dest);
|
|
addConfigDriveDisk(vm, dataStore);
|
|
return false;
|
|
}
|
|
else return true;
|
|
}
|
|
|
|
@Override
|
|
public void rollbackMigration(NicProfile nic, Network network, VirtualMachineProfile vm, ReservationContext src, ReservationContext dst) {
|
|
}
|
|
|
|
@Override
|
|
public void commitMigration(NicProfile nic, Network network, VirtualMachineProfile vm, ReservationContext src, ReservationContext dst) {
|
|
}
|
|
|
|
private DataStore findDataStore(VirtualMachineProfile profile, DeployDestination dest) {
|
|
DataStore dataStore = null;
|
|
if (VirtualMachineManager.VmConfigDriveOnPrimaryPool.value()) {
|
|
if (dest.getStorageForDisks() != null) {
|
|
for (final Volume volume : dest.getStorageForDisks().keySet()) {
|
|
if (volume.getVolumeType() == Volume.Type.ROOT) {
|
|
final StoragePool primaryPool = dest.getStorageForDisks().get(volume);
|
|
dataStore = _dataStoreMgr.getDataStore(primaryPool.getId(), DataStoreRole.Primary);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (dataStore == null) {
|
|
final List<VolumeVO> volumes = _volumeDao.findByInstanceAndType(profile.getVirtualMachine().getId(), Volume.Type.ROOT);
|
|
if (volumes != null && volumes.size() > 0) {
|
|
dataStore = _dataStoreMgr.getDataStore(volumes.get(0).getPoolId(), DataStoreRole.Primary);
|
|
}
|
|
}
|
|
} else {
|
|
dataStore = _dataStoreMgr.getImageStore(dest.getDataCenter().getId());
|
|
}
|
|
return dataStore;
|
|
}
|
|
|
|
private Long findAgentIdForImageStore(final DataStore dataStore) throws ResourceUnavailableException {
|
|
EndPoint endpoint = _ep.select(dataStore);
|
|
if (endpoint == null) {
|
|
throw new ResourceUnavailableException("Config drive creation failed, secondary store not available",
|
|
dataStore.getClass(), dataStore.getId());
|
|
}
|
|
return endpoint.getId();
|
|
}
|
|
|
|
private Long findAgentId(VirtualMachineProfile profile, DeployDestination dest, DataStore dataStore) throws ResourceUnavailableException {
|
|
Long agentId;
|
|
if (dest.getHost() == null) {
|
|
agentId = (profile.getVirtualMachine().getHostId() == null ? profile.getVirtualMachine().getLastHostId() : profile.getVirtualMachine().getHostId());
|
|
} else {
|
|
agentId = dest.getHost().getId();
|
|
}
|
|
if (!VirtualMachineManager.VmConfigDriveOnPrimaryPool.value()) {
|
|
agentId = findAgentIdForImageStore(dataStore);
|
|
}
|
|
return agentId;
|
|
}
|
|
|
|
private boolean createConfigDriveIso(VirtualMachineProfile profile, DeployDestination dest) throws ResourceUnavailableException {
|
|
final DataStore dataStore = findDataStore(profile, dest);
|
|
final Long agentId = findAgentId(profile, dest, dataStore);
|
|
if (agentId == null || dataStore == null) {
|
|
throw new ResourceUnavailableException("Config drive iso creation failed, agent or datastore not available",
|
|
ConfigDriveNetworkElement.class, 0L);
|
|
}
|
|
|
|
LOG.debug("Creating config drive ISO for vm: " + profile.getInstanceName());
|
|
|
|
final String isoFileName = ConfigDrive.configIsoFileName(profile.getInstanceName());
|
|
final String isoPath = ConfigDrive.createConfigDrivePath(profile.getInstanceName());
|
|
final String isoData = ConfigDriveBuilder.buildConfigDrive(profile.getVmData(), isoFileName, profile.getConfigDriveLabel());
|
|
final HandleConfigDriveIsoCommand configDriveIsoCommand = new HandleConfigDriveIsoCommand(isoPath, isoData, dataStore.getTO(), true);
|
|
|
|
final Answer answer = agentManager.easySend(agentId, configDriveIsoCommand);
|
|
if (!answer.getResult()) {
|
|
throw new ResourceUnavailableException(String.format("Config drive iso creation failed, details: %s",
|
|
answer.getDetails()), ConfigDriveNetworkElement.class, 0L);
|
|
}
|
|
addConfigDriveDisk(profile, dataStore);
|
|
return true;
|
|
}
|
|
|
|
private boolean deleteConfigDriveIso(final VirtualMachine vm) throws ResourceUnavailableException {
|
|
DataStore dataStore = _dataStoreMgr.getImageStore(vm.getDataCenterId());
|
|
Long agentId = findAgentIdForImageStore(dataStore);
|
|
|
|
if (VirtualMachineManager.VmConfigDriveOnPrimaryPool.value()) {
|
|
List<VolumeVO> volumes = _volumeDao.findByInstanceAndType(vm.getId(), Volume.Type.ROOT);
|
|
if (volumes != null && volumes.size() > 0) {
|
|
dataStore = _dataStoreMgr.getDataStore(volumes.get(0).getPoolId(), DataStoreRole.Primary);
|
|
}
|
|
agentId = (vm.getHostId() != null) ? vm.getHostId() : vm.getLastHostId();
|
|
}
|
|
|
|
if (agentId == null || dataStore == null) {
|
|
throw new ResourceUnavailableException("Config drive iso creation failed, agent or datastore not available",
|
|
ConfigDriveNetworkElement.class, 0L);
|
|
}
|
|
|
|
LOG.debug("Deleting config drive ISO for vm: " + vm.getInstanceName());
|
|
|
|
final String isoPath = ConfigDrive.createConfigDrivePath(vm.getInstanceName());
|
|
final HandleConfigDriveIsoCommand configDriveIsoCommand = new HandleConfigDriveIsoCommand(isoPath, null, dataStore.getTO(), false);
|
|
|
|
final Answer answer = agentManager.easySend(agentId, configDriveIsoCommand);
|
|
if (!answer.getResult()) {
|
|
LOG.error("Failed to remove config drive for instance: " + vm.getInstanceName());
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
private void addConfigDriveDisk(final VirtualMachineProfile profile, final DataStore dataStore) {
|
|
boolean isoAvailable = false;
|
|
final String isoPath = ConfigDrive.createConfigDrivePath(profile.getInstanceName());
|
|
for (DiskTO dataTo : profile.getDisks()) {
|
|
if (dataTo.getPath().equals(isoPath)) {
|
|
isoAvailable = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!isoAvailable) {
|
|
TemplateObjectTO dataTO = new TemplateObjectTO();
|
|
dataTO.setDataStore(dataStore.getTO());
|
|
dataTO.setUuid(profile.getUuid());
|
|
dataTO.setPath(isoPath);
|
|
dataTO.setFormat(Storage.ImageFormat.ISO);
|
|
|
|
profile.addDisk(new DiskTO(dataTO, CONFIGDRIVEDISKSEQ.longValue(), isoPath, Volume.Type.ISO));
|
|
} else {
|
|
LOG.warn("Config drive iso already is in VM profile.");
|
|
}
|
|
}
|
|
|
|
private boolean configureConfigDriveData(final VirtualMachineProfile profile, final NicProfile nic) {
|
|
final UserVmVO vm = _userVmDao.findById(profile.getId());
|
|
if (vm.getType() != VirtualMachine.Type.User) {
|
|
return false;
|
|
}
|
|
final Nic defaultNic = _networkModel.getDefaultNic(vm.getId());
|
|
if (defaultNic != null) {
|
|
final String sshPublicKey = getSshKey(profile);
|
|
final String serviceOffering = _serviceOfferingDao.findByIdIncludingRemoved(vm.getId(), vm.getServiceOfferingId()).getDisplayText();
|
|
boolean isWindows = _guestOSCategoryDao.findById(_guestOSDao.findById(vm.getGuestOSId()).getCategoryId()).getName().equalsIgnoreCase("Windows");
|
|
|
|
final List<String[]> vmData = _networkModel.generateVmData(vm.getUserData(), serviceOffering, vm.getDataCenterId(), vm.getInstanceName(), vm.getHostName(), vm.getId(),
|
|
vm.getUuid(), nic.getIPv4Address(), sshPublicKey, (String) profile.getParameter(VirtualMachineProfile.Param.VmPassword), isWindows);
|
|
profile.setVmData(vmData);
|
|
profile.setConfigDriveLabel(VirtualMachineManager.VmConfigDriveLabel.value());
|
|
}
|
|
return true;
|
|
}
|
|
|
|
}
|