CLOUDSTACK-424: Update userdata should propagate to VR

The code that would propage userdata to router, when updateVirtualMachine api
is called, was missing. As per the docs [0], userdata should be a base64 encoded
string upto 2KB which is put on domr's html directory adn using HTTP GET the
userdata information can be obtained from the domr.

The updateVirtualMachine api [0] would accept a base64 encoded string
and decoded and put into the domr's /var/www/html/userdata/<uservm ip>/user-data
file. The operation does not require the VM to be in stopped state, though it is
advised to stop and call this api in case the user vm has a script which gets
the userdata information from domr while starting.

For example, this script can be used to fetch the data:
server_ip=$(grep dhcp-server-identifier /var/lib/dhclient-eth0.leases | tail -1 | awk '{print $NF}' | tr '\;' ' ')
wget http://${server_ip}/latest/user-data

This feature can be useful, for example to use into puppet facts [1], or to do
automation and horizontal scaling etc. based on userdata.

[0] http://incubator.apache.org/cloudstack/docs/api/apidocs-4.0.0/domain_admin/updateVirtualMachine.html
[1] http://geek.jasonhancock.com/2011/11/09/cloudstack-userdata-into-puppet-facts/

BUG-ID : CLOUDSTACK-424
Reviewed-by: Rohit Yadav <bhaisaab@apache.org>
Reported-by: Nick Wales

Signed-off-by: Rohit Yadav <bhaisaab@apache.org>
This commit is contained in:
Rohit Yadav 2012-11-15 17:53:33 +05:30
parent 7ae8c4ad7e
commit 4c86b1545d
12 changed files with 125 additions and 11 deletions

View File

@ -25,18 +25,20 @@ import com.cloud.api.Implementation;
import com.cloud.api.Parameter;
import com.cloud.api.ServerApiException;
import com.cloud.api.response.UserVmResponse;
import com.cloud.exception.InsufficientCapacityException;
import com.cloud.exception.ResourceUnavailableException;
import com.cloud.user.Account;
import com.cloud.user.UserContext;
import com.cloud.uservm.UserVm;
@Implementation(description="Updates properties of a virtual machine. The VM has to be stopped and restarted for the " +
"new properties to take effect. UpdateVirtualMachine does not first check whether the VM is stopped. " +
"Therefore, stop the VM manually before issuing this call.", responseObject=UserVmResponse.class)
public class UpdateVMCmd extends BaseCmd{
public static final Logger s_logger = Logger.getLogger(UpdateVMCmd.class.getName());
private static final String s_name = "updatevirtualmachineresponse";
"new properties to take effect. UpdateVirtualMachine does not first check whether the VM is stopped. " +
"Therefore, stop the VM manually before issuing this call.", responseObject=UserVmResponse.class)
public class UpdateVMCmd extends BaseCmd{
public static final Logger s_logger = Logger.getLogger(UpdateVMCmd.class.getName());
private static final String s_name = "updatevirtualmachineresponse";
/////////////////////////////////////////////////////
//////////////// API parameters /////////////////////
/////////////////////////////////////////////////////
@ -114,7 +116,8 @@ public class UpdateVMCmd extends BaseCmd{
}
@Override
public void execute(){
public void execute() throws ResourceUnavailableException,
InsufficientCapacityException, ServerApiException {
UserContext.current().setEventDetails("Vm Id: "+getId());
UserVm result = _userVmService.updateVirtualMachine(this);
if (result != null){

View File

@ -29,4 +29,5 @@ import com.cloud.vm.VirtualMachineProfile;
public interface UserDataServiceProvider extends NetworkElement {
public boolean addPasswordAndUserdata(Network network, NicProfile nic, VirtualMachineProfile<? extends VirtualMachine> vm, DeployDestination dest, ReservationContext context) throws ConcurrentOperationException, InsufficientCapacityException, ResourceUnavailableException;
boolean savePassword(Network network, NicProfile nic, VirtualMachineProfile<? extends VirtualMachine> vm) throws ResourceUnavailableException;
boolean saveUserData(Network network, NicProfile nic, VirtualMachineProfile<? extends VirtualMachine> vm) throws ResourceUnavailableException;
}

View File

@ -113,7 +113,7 @@ public interface UserVmService {
UserVm rebootVirtualMachine(RebootVMCmd cmd) throws InsufficientCapacityException, ResourceUnavailableException;
UserVm updateVirtualMachine(UpdateVMCmd cmd);
UserVm updateVirtualMachine(UpdateVMCmd cmd) throws ResourceUnavailableException, InsufficientCapacityException;
UserVm recoverVirtualMachine(RecoverVMCmd cmd) throws ResourceAllocationException;

View File

@ -50,6 +50,7 @@ import com.cloud.network.rules.StaticNat;
import com.cloud.offering.NetworkOffering;
import com.cloud.offerings.NetworkOfferingVO;
import com.cloud.user.Account;
import com.cloud.uservm.UserVm;
import com.cloud.utils.Pair;
import com.cloud.vm.Nic;
import com.cloud.vm.NicProfile;
@ -189,6 +190,8 @@ public interface NetworkManager extends NetworkService {
UserDataServiceProvider getPasswordResetProvider(Network network);
UserDataServiceProvider getUserDataUpdateProvider(Network network);
boolean networkIsConfiguredForExternalNetworking(long zoneId, long networkId);
Map<Capability, String> getNetworkServiceCapabilities(long networkId, Service service);

View File

@ -184,6 +184,7 @@ import com.cloud.user.User;
import com.cloud.user.UserContext;
import com.cloud.user.dao.AccountDao;
import com.cloud.user.dao.UserStatisticsDao;
import com.cloud.uservm.UserVm;
import com.cloud.utils.AnnotationHelper;
import com.cloud.utils.NumbersUtil;
import com.cloud.utils.Pair;
@ -4341,6 +4342,18 @@ public class NetworkManagerImpl implements NetworkManager, NetworkService, Manag
return (UserDataServiceProvider)getElementImplementingProvider(passwordProvider);
}
@Override
public UserDataServiceProvider getUserDataUpdateProvider(Network network) {
String userDataProvider = _ntwkSrvcDao.getProviderForServiceInNetwork(network.getId(), Service.UserData);
if (userDataProvider == null) {
s_logger.debug("Network " + network + " doesn't support service " + Service.UserData.getName());
return null;
}
return (UserDataServiceProvider)getElementImplementingProvider(userDataProvider);
}
@Override
public boolean networkIsConfiguredForExternalNetworking(long zoneId, long networkId) {
boolean netscalerInNetwork = isProviderForNetwork(Network.Provider.Netscaler, networkId);

View File

@ -251,6 +251,12 @@ public class CloudZonesNetworkElement extends AdapterBase implements NetworkElem
return false;
}
@Override
public boolean saveUserData(Network network, NicProfile nic, VirtualMachineProfile<? extends VirtualMachine> vm) throws ResourceUnavailableException {
// TODO Auto-generated method stub
return false;
}
@Override
public boolean verifyServicesCombination(Set<Service> services) {
return true;

View File

@ -661,6 +661,24 @@ public class VirtualRouterElement extends AdapterBase implements VirtualRouterEl
return _routerMgr.savePasswordToRouter(network, nic, uservm, routers);
}
@Override
public boolean saveUserData(Network network, NicProfile nic, VirtualMachineProfile<? extends VirtualMachine> vm)
throws ResourceUnavailableException {
if (!canHandle(network, null)) {
return false;
}
List<DomainRouterVO> routers = _routerDao.listByNetworkAndRole(network.getId(), Role.VIRTUAL_ROUTER);
if (routers == null || routers.isEmpty()) {
s_logger.debug("Can't find virtual router element in network " + network.getId());
return true;
}
@SuppressWarnings("unchecked")
VirtualMachineProfile<UserVm> uservm = (VirtualMachineProfile<UserVm>) vm;
return _routerMgr.saveUserDataToRouter(network, nic, uservm, routers);
}
@Override
public String getPropertiesFile() {
return "virtualrouter_commands.properties";

View File

@ -62,7 +62,10 @@ public interface VirtualNetworkApplianceManager extends Manager, VirtualNetworkA
*/
boolean savePasswordToRouter(Network network, NicProfile nic, VirtualMachineProfile<UserVm> profile,
List<? extends VirtualRouter> routers) throws ResourceUnavailableException;
boolean saveUserDataToRouter(Network network, NicProfile nic, VirtualMachineProfile<UserVm> profile,
List<? extends VirtualRouter> routers) throws ResourceUnavailableException;
List<DomainRouterVO> deployVirtualRouterInGuestNetwork(Network guestNetwork, DeployDestination dest, Account owner,
Map<VirtualMachineProfile.Param, Object> params, boolean isRedundant) throws InsufficientCapacityException,
ResourceUnavailableException, ConcurrentOperationException;

View File

@ -464,6 +464,24 @@ public class VirtualNetworkApplianceManagerImpl implements VirtualNetworkApplian
});
}
@Override
public boolean saveUserDataToRouter(Network network, final NicProfile nic, VirtualMachineProfile<UserVm> profile, List<? extends VirtualRouter> routers) throws ResourceUnavailableException {
_userVmDao.loadDetails((UserVmVO) profile.getVirtualMachine());
final VirtualMachineProfile<UserVm> updatedProfile = profile;
return applyRules(network, routers, "save userdata entry", false, null, false, new RuleApplier() {
@Override
public boolean execute(Network network, VirtualRouter router) throws ResourceUnavailableException {
// for basic zone, send vm data/password information only to the router in the same pod
Commands cmds = new Commands(OnError.Stop);
NicVO nicVo = _nicDao.findById(nic.getId());
createVmDataCommand(router, updatedProfile.getVirtualMachine(), nicVo, null, cmds);
return sendCommandsToRouter(router, cmds);
}
});
}
@Override @ActionEvent(eventType = EventTypes.EVENT_ROUTER_STOP, eventDescription = "stopping router Vm", async = true)
public VirtualRouter stopRouter(long routerId, boolean forced) throws ResourceUnavailableException, ConcurrentOperationException {
UserContext context = UserContext.current();

View File

@ -1756,13 +1756,14 @@ public class UserVmManagerImpl implements UserVmManager, UserVmService, Manager
@Override
@ActionEvent(eventType = EventTypes.EVENT_VM_UPDATE, eventDescription = "updating Vm")
public UserVm updateVirtualMachine(UpdateVMCmd cmd) {
public UserVm updateVirtualMachine(UpdateVMCmd cmd)
throws ResourceUnavailableException, InsufficientCapacityException {
String displayName = cmd.getDisplayName();
String group = cmd.getGroup();
Boolean ha = cmd.getHaEnable();
Long id = cmd.getId();
Long osTypeId = cmd.getOsTypeId();
String userData = cmd.getUserData();
String userData = cmd.getUserData().replace("\\n", "");
// Input validation
UserVmVO vmInstance = null;
@ -1799,9 +1800,11 @@ public class UserVmManagerImpl implements UserVmManager, UserVmService, Manager
throw new InvalidParameterValueException("Vm with id " + id + " is not in the right state");
}
boolean updateUserdata = false;
if (userData != null) {
validateUserData(userData);
// update userData on domain router.
updateUserdata = true;
} else {
userData = vmInstance.getUserData();
}
@ -1833,9 +1836,43 @@ public class UserVmManagerImpl implements UserVmManager, UserVmService, Manager
_vmDao.updateVM(id, displayName, ha, osTypeId, userData);
if (updateUserdata) {
boolean result = updateUserDataInternal(_vmDao.findById(id));
if (result) {
s_logger.debug("User data successfully updated for vm id="+id);
} else {
throw new CloudRuntimeException("Failed to reset userdata for the virtual machine ");
}
}
return _vmDao.findById(id);
}
private boolean updateUserDataInternal(UserVm vm)
throws ResourceUnavailableException, InsufficientCapacityException {
VMTemplateVO template = _templateDao.findByIdIncludingRemoved(vm.getTemplateId());
Nic defaultNic = _networkMgr.getDefaultNic(vm.getId());
if (defaultNic == null) {
s_logger.error("Unable to update userdata for vm id=" + vm.getId() + " as the instance doesn't have default nic");
return false;
}
Network defaultNetwork = _networkDao.findById(defaultNic.getNetworkId());
NicProfile defaultNicProfile = new NicProfile(defaultNic, defaultNetwork, null, null, null,
_networkMgr.isSecurityGroupSupportedInNetwork(defaultNetwork),
_networkMgr.getNetworkTag(template.getHypervisorType(), defaultNetwork));
VirtualMachineProfile<VMInstanceVO> vmProfile = new VirtualMachineProfileImpl<VMInstanceVO>((VMInstanceVO)vm);
UserDataServiceProvider element = _networkMgr.getUserDataUpdateProvider(defaultNetwork);
if (element == null) {
throw new CloudRuntimeException("Can't find network element for " + Service.UserData.getName() + " provider needed for UserData update");
}
boolean result = element.saveUserData(defaultNetwork, defaultNicProfile, vmProfile);
return true;
}
@Override
@ActionEvent(eventType = EventTypes.EVENT_VM_START, eventDescription = "starting Vm", async = true)
public UserVm startVirtualMachine(StartVMCmd cmd) throws ExecutionException, ConcurrentOperationException, ResourceUnavailableException, InsufficientCapacityException {

View File

@ -615,6 +615,12 @@ public class MockNetworkManagerImpl implements NetworkManager, Manager, NetworkS
return null;
}
@Override
public UserDataServiceProvider getUserDataUpdateProvider(Network network) {
// TODO Auto-generated method stub
return null;
}
@Override
public PhysicalNetworkServiceProvider updateNetworkServiceProvider(Long id, String state, List<String> enabledServices) {
// TODO Auto-generated method stub

View File

@ -810,6 +810,12 @@ public class MockNetworkManagerImpl implements NetworkManager, Manager{
return null;
}
@Override
public UserDataServiceProvider getUserDataUpdateProvider(Network network) {
// TODO Auto-generated method stub
return null;
}
/* (non-Javadoc)
* @see com.cloud.network.NetworkManager#networkIsConfiguredForExternalNetworking(long, long)
*/