From ee4f6cf81d92af068d1e7b56fae08beeb47b4fd0 Mon Sep 17 00:00:00 2001 From: Rohit Yadav Date: Wed, 16 May 2018 13:48:05 +0530 Subject: [PATCH 01/10] cfgdrive: introduce new engine storage component and add rpm deps Signed-off-by: Rohit Yadav --- client/pom.xml | 5 ++++ engine/pom.xml | 1 + .../storage/configdrive/ConfigDrive.java | 24 +++++++++++++++++++ packaging/centos63/cloud.spec | 1 + packaging/centos7/cloud.spec | 1 + plugins/hypervisors/kvm/pom.xml | 5 ++++ server/pom.xml | 5 ++++ .../element/ConfigDriveNetworkElement.java | 15 ++++++------ 8 files changed, 49 insertions(+), 8 deletions(-) create mode 100644 engine/storage/configdrive/src/org/apache/cloudstack/storage/configdrive/ConfigDrive.java diff --git a/client/pom.xml b/client/pom.xml index 9907d8cc2ac5..f6ebe4166ade 100644 --- a/client/pom.xml +++ b/client/pom.xml @@ -378,6 +378,11 @@ cloud-engine-storage-cache ${project.version} + + org.apache.cloudstack + cloud-engine-storage-configdrive + ${project.version} + org.apache.cloudstack cloud-controller-secondary-storage diff --git a/engine/pom.xml b/engine/pom.xml index be86f1c9dc19..4fc66d12a604 100644 --- a/engine/pom.xml +++ b/engine/pom.xml @@ -53,6 +53,7 @@ storage/datamotion storage/cache storage/snapshot + storage/configdrive components-api network service diff --git a/engine/storage/configdrive/src/org/apache/cloudstack/storage/configdrive/ConfigDrive.java b/engine/storage/configdrive/src/org/apache/cloudstack/storage/configdrive/ConfigDrive.java new file mode 100644 index 000000000000..78af05ba6313 --- /dev/null +++ b/engine/storage/configdrive/src/org/apache/cloudstack/storage/configdrive/ConfigDrive.java @@ -0,0 +1,24 @@ +// 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 org.apache.cloudstack.storage.configdrive; + +public class ConfigDrive { + + public final static String CONFIGDRIVEFILENAME = "configdrive.iso"; + public final static String CONFIGDRIVEDIR = "configdrive"; +} diff --git a/packaging/centos63/cloud.spec b/packaging/centos63/cloud.spec index 87d2c1ec5780..9655ba72d72c 100644 --- a/packaging/centos63/cloud.spec +++ b/packaging/centos63/cloud.spec @@ -130,6 +130,7 @@ Requires: perl Requires: libvirt-python Requires: qemu-img Requires: qemu-kvm +Requires: genisoimage Provides: cloud-agent Obsoletes: cloud-agent < 4.1.0 Obsoletes: cloud-agent-libs < 4.1.0 diff --git a/packaging/centos7/cloud.spec b/packaging/centos7/cloud.spec index 525421c0108d..0a8292e1012b 100644 --- a/packaging/centos7/cloud.spec +++ b/packaging/centos7/cloud.spec @@ -111,6 +111,7 @@ Requires: perl Requires: libvirt-python Requires: qemu-img Requires: qemu-kvm +Requires: genisoimage Provides: cloud-agent Group: System Environment/Libraries %description agent diff --git a/plugins/hypervisors/kvm/pom.xml b/plugins/hypervisors/kvm/pom.xml index d90e79adfc9b..1d0dd66cf878 100644 --- a/plugins/hypervisors/kvm/pom.xml +++ b/plugins/hypervisors/kvm/pom.xml @@ -39,6 +39,11 @@ cloud-plugin-network-ovs ${project.version} + + org.apache.cloudstack + cloud-engine-storage-configdrive + ${project.version} + com.ceph rados diff --git a/server/pom.xml b/server/pom.xml index 534b1a34c70d..55d0a6e247fc 100644 --- a/server/pom.xml +++ b/server/pom.xml @@ -141,6 +141,11 @@ cloud-framework-agent-lb ${project.version} + + org.apache.cloudstack + cloud-engine-storage-configdrive + ${project.version} + org.opensaml opensaml diff --git a/server/src/com/cloud/network/element/ConfigDriveNetworkElement.java b/server/src/com/cloud/network/element/ConfigDriveNetworkElement.java index 585b443557b0..734365a43df5 100644 --- a/server/src/com/cloud/network/element/ConfigDriveNetworkElement.java +++ b/server/src/com/cloud/network/element/ConfigDriveNetworkElement.java @@ -22,6 +22,7 @@ import java.util.Set; import javax.inject.Inject; +import org.apache.cloudstack.storage.configdrive.ConfigDrive; import org.apache.log4j.Logger; import org.apache.cloudstack.engine.orchestration.service.VolumeOrchestrationService; @@ -118,8 +119,6 @@ public class ConfigDriveNetworkElement extends AdapterBase implements NetworkEle @Inject VolumeOrchestrationService _volumeMgr; - private final static String CONFIGDRIVEFILENAME = "configdrive.iso"; - private final static String CONFIGDRIVEDIR = "ConfigDrive"; private final static Integer CONFIGDRIVEDISKSEQ = 4; private boolean canHandle(TrafficType trafficType) { @@ -152,7 +151,7 @@ public boolean release(Network network, NicProfile nic, VirtualMachineProfile vm // Remove form secondary storage DataStore secondaryStore = _dataStoreMgr.getImageStore(network.getDataCenterId()); - String isoFile = "/" + CONFIGDRIVEDIR + "/" + vm.getInstanceName()+ "/" + CONFIGDRIVEFILENAME; + String isoFile = "/" + ConfigDrive.CONFIGDRIVEDIR + "/" + vm.getInstanceName()+ "/" + ConfigDrive.CONFIGDRIVEFILENAME; HandleConfigDriveIsoCommand deleteCommand = new HandleConfigDriveIsoCommand(vm.getVmData(), vm.getConfigDriveLabel(), secondaryStore.getTO(), isoFile, false, false); // Delete the ISO on the secondary store @@ -262,7 +261,7 @@ public boolean postStateTransitionEvent(StateMachine2.Transition Date: Wed, 16 May 2018 15:27:49 +0530 Subject: [PATCH 02/10] cfg: introduce cfg storage component deps to ssvm Signed-off-by: Rohit Yadav --- .../api/HandleConfigDriveIsoCommand.java | 1 - engine/storage/configdrive/pom.xml | 43 +++++++++++++++++++ .../storage/configdrive/ConfigDrive.java | 3 ++ services/secondary-storage/server/pom.xml | 5 +++ .../resource/NfsSecondaryStorageResource.java | 11 +++-- 5 files changed, 56 insertions(+), 7 deletions(-) create mode 100644 engine/storage/configdrive/pom.xml diff --git a/core/src/com/cloud/agent/api/HandleConfigDriveIsoCommand.java b/core/src/com/cloud/agent/api/HandleConfigDriveIsoCommand.java index d6d87d48c054..e026b7ad28b7 100644 --- a/core/src/com/cloud/agent/api/HandleConfigDriveIsoCommand.java +++ b/core/src/com/cloud/agent/api/HandleConfigDriveIsoCommand.java @@ -39,7 +39,6 @@ public HandleConfigDriveIsoCommand(List vmData, String label, DataStor this.update = update; this.destStore = destStore; - this.isoFile = isoFile; } diff --git a/engine/storage/configdrive/pom.xml b/engine/storage/configdrive/pom.xml new file mode 100644 index 000000000000..2d9652ff4db0 --- /dev/null +++ b/engine/storage/configdrive/pom.xml @@ -0,0 +1,43 @@ + + + 4.0.0 + cloud-engine-storage-configdrive + Apache CloudStack Framework - Storage Config Drive Component + + org.apache.cloudstack + cloudstack-framework + 4.11.1.0-SNAPSHOT + ../pom.xml + + + + + org.apache.cloudstack + cloud-api + ${project.version} + + + org.apache.cloudstack + cloud-core + ${project.version} + + + + diff --git a/engine/storage/configdrive/src/org/apache/cloudstack/storage/configdrive/ConfigDrive.java b/engine/storage/configdrive/src/org/apache/cloudstack/storage/configdrive/ConfigDrive.java index 78af05ba6313..2d72bc1c3390 100644 --- a/engine/storage/configdrive/src/org/apache/cloudstack/storage/configdrive/ConfigDrive.java +++ b/engine/storage/configdrive/src/org/apache/cloudstack/storage/configdrive/ConfigDrive.java @@ -21,4 +21,7 @@ public class ConfigDrive { public final static String CONFIGDRIVEFILENAME = "configdrive.iso"; public final static String CONFIGDRIVEDIR = "configdrive"; + + public static final String cloudStackConfigDriveName = "/cloudstack/"; + public static final String openStackConfigDriveName = "/openstack/latest/"; } diff --git a/services/secondary-storage/server/pom.xml b/services/secondary-storage/server/pom.xml index 7c7cf9b68bff..87ac18661806 100644 --- a/services/secondary-storage/server/pom.xml +++ b/services/secondary-storage/server/pom.xml @@ -50,6 +50,11 @@ cloud-server ${project.version} + + org.apache.cloudstack + cloud-engine-storage-configdrive + ${project.version} + io.netty diff --git a/services/secondary-storage/server/src/org/apache/cloudstack/storage/resource/NfsSecondaryStorageResource.java b/services/secondary-storage/server/src/org/apache/cloudstack/storage/resource/NfsSecondaryStorageResource.java index da3ab32e2035..46aa1043d1de 100644 --- a/services/secondary-storage/server/src/org/apache/cloudstack/storage/resource/NfsSecondaryStorageResource.java +++ b/services/secondary-storage/server/src/org/apache/cloudstack/storage/resource/NfsSecondaryStorageResource.java @@ -106,6 +106,7 @@ import org.apache.cloudstack.storage.command.UploadStatusAnswer; import org.apache.cloudstack.storage.command.UploadStatusAnswer.UploadStatus; import org.apache.cloudstack.storage.command.UploadStatusCommand; +import org.apache.cloudstack.storage.configdrive.ConfigDrive; import org.apache.cloudstack.storage.template.DownloadManager; import org.apache.cloudstack.storage.template.DownloadManagerImpl; import org.apache.cloudstack.storage.template.DownloadManagerImpl.ZfsPathParser; @@ -200,8 +201,6 @@ public class NfsSecondaryStorageResource extends ServerResourceBase implements S private static final String TEMPLATE_ROOT_DIR = "template/tmpl"; private static final String VOLUME_ROOT_DIR = "volumes"; private static final String POST_UPLOAD_KEY_LOCATION = "/etc/cloudstack/agent/ms-psk"; - private static final String cloudStackConfigDriveName = "/cloudstack/"; - private static final String openStackConfigDriveName = "/openstack/latest/"; private static final Map updatableConfigData = Maps.newHashMap(); static { @@ -456,7 +455,7 @@ public Answer createConfigDriveIsoForVM(HandleConfigDriveIsoCommand cmd) { //create OpenStack files //create folder with empty files - File openStackFolder = new File(tempDirName + openStackConfigDriveName); + File openStackFolder = new File(tempDirName + ConfigDrive.openStackConfigDriveName); if (openStackFolder.exists() || openStackFolder.mkdirs()) { File vendorDataFile = new File(openStackFolder,"vendor_data.json"); try (FileWriter fw = new FileWriter(vendorDataFile); BufferedWriter bw = new BufferedWriter(fw)) { @@ -488,7 +487,7 @@ public Answer createConfigDriveIsoForVM(HandleConfigDriveIsoCommand cmd) { // create file with content in folder if (dataType != null && !dataType.isEmpty()) { //create folder - File typeFolder = new File(tempDirName + cloudStackConfigDriveName + dataType); + File typeFolder = new File(tempDirName + ConfigDrive.cloudStackConfigDriveName + dataType); if (typeFolder.exists() || typeFolder.mkdirs()) { if (StringUtils.isNotEmpty(content)) { File file = new File(typeFolder, fileName + ".txt"); @@ -603,11 +602,11 @@ private static JsonArray arrayOf(JsonElement... elements) { private String linkUserData(String tempDirName) { //Hard link the user_data.txt file with the user_data file in the OpenStack directory. - String userDataFilePath = tempDirName + cloudStackConfigDriveName + "userdata/user_data.txt"; + String userDataFilePath = tempDirName + ConfigDrive.cloudStackConfigDriveName + "userdata/user_data.txt"; if ((new File(userDataFilePath).exists())) { Script hardLink = new Script(!_inSystemVM, "ln", _timeout, s_logger); hardLink.add(userDataFilePath); - hardLink.add(tempDirName + openStackConfigDriveName + "user_data"); + hardLink.add(tempDirName + ConfigDrive.openStackConfigDriveName + "user_data"); s_logger.debug("execute command: " + hardLink.toString()); return hardLink.execute(); } From 3247ad5b559161e730122ea4d7d82cb3b5a7d533 Mon Sep 17 00:00:00 2001 From: Rohit Yadav Date: Thu, 17 May 2018 13:28:06 +0530 Subject: [PATCH 03/10] refactors, save metadata on update/reset APIs before rebooting VM is api is called when VM was running Signed-off-by: Rohit Yadav --- .../element/UserDataServiceProvider.java | 2 +- engine/storage/configdrive/pom.xml | 2 +- .../src/com/cloud/vm/UserVmManagerImpl.java | 41 +++++++++---------- 3 files changed, 22 insertions(+), 23 deletions(-) diff --git a/api/src/com/cloud/network/element/UserDataServiceProvider.java b/api/src/com/cloud/network/element/UserDataServiceProvider.java index 45ab0d877a3e..bf6d7e819e91 100644 --- a/api/src/com/cloud/network/element/UserDataServiceProvider.java +++ b/api/src/com/cloud/network/element/UserDataServiceProvider.java @@ -26,7 +26,7 @@ import com.cloud.vm.VirtualMachineProfile; public interface UserDataServiceProvider extends NetworkElement { - public boolean addPasswordAndUserdata(Network network, NicProfile nic, VirtualMachineProfile vm, DeployDestination dest, ReservationContext context) + boolean addPasswordAndUserdata(Network network, NicProfile nic, VirtualMachineProfile vm, DeployDestination dest, ReservationContext context) throws ConcurrentOperationException, InsufficientCapacityException, ResourceUnavailableException; boolean savePassword(Network network, NicProfile nic, VirtualMachineProfile vm) throws ResourceUnavailableException; diff --git a/engine/storage/configdrive/pom.xml b/engine/storage/configdrive/pom.xml index 2d9652ff4db0..2ea63f84d112 100644 --- a/engine/storage/configdrive/pom.xml +++ b/engine/storage/configdrive/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack-framework 4.11.1.0-SNAPSHOT - ../pom.xml + ../../pom.xml diff --git a/server/src/com/cloud/vm/UserVmManagerImpl.java b/server/src/com/cloud/vm/UserVmManagerImpl.java index ecdd1125aabc..9895cab22ac8 100644 --- a/server/src/com/cloud/vm/UserVmManagerImpl.java +++ b/server/src/com/cloud/vm/UserVmManagerImpl.java @@ -653,10 +653,6 @@ protected void runInContext() { } } - - - - @Override @ActionEvent(eventType = EventTypes.EVENT_VM_RESETPASSWORD, eventDescription = "resetting Vm password", async = true) public UserVm resetVMPassword(ResetVMPasswordCmd cmd, String password) throws ResourceUnavailableException, InsufficientCapacityException { @@ -733,6 +729,14 @@ private boolean resetVMPasswordInternal(Long vmId, String password) throws Resou s_logger.debug("Failed to reset password for the virtual machine; no need to reboot the vm"); return false; } else { + final UserVmVO userVm = _vmDao.findById(vmId); + _vmDao.loadDetails(userVm); + userVm.setPassword(password); + // update the password in vm_details table too + // Check if an SSH key pair was selected for the instance and if so + // use it to encrypt & save the vm password + encryptAndStorePassword(userVm, password); + if (vmInstance.getState() == State.Stopped) { s_logger.debug("Vm " + vmInstance + " is stopped, not rebooting it as a part of password reset"); return true; @@ -796,15 +800,7 @@ public UserVm resetVMSSHKey(ResetVMSSHKeyCmd cmd) throws ResourceUnavailableExce boolean result = resetVMSSHKeyInternal(vmId, sshPublicKey, password); - if (result) { - userVm.setDetail("SSH.PublicKey", sshPublicKey); - if (template != null && template.getEnablePassword()) { - userVm.setPassword(password); - //update the encrypted password in vm_details table too - encryptAndStorePassword(userVm, password); - } - _vmDao.saveDetails(userVm); - } else { + if (!result) { throw new CloudRuntimeException("Failed to reset SSH Key for the virtual machine "); } return userVm; @@ -842,6 +838,16 @@ private boolean resetVMSSHKeyInternal(Long vmId, String sshPublicKey, String pas s_logger.debug("Failed to reset SSH Key for the virtual machine; no need to reboot the vm"); return false; } else { + final UserVmVO userVm = _vmDao.findById(vmId); + _vmDao.loadDetails(userVm); + userVm.setDetail("SSH.PublicKey", sshPublicKey); + if (template.getEnablePassword()) { + userVm.setPassword(password); + //update the encrypted password in vm_details table too + encryptAndStorePassword(userVm, password); + } + _vmDao.saveDetails(userVm); + if (vmInstance.getState() == State.Stopped) { s_logger.debug("Vm " + vmInstance + " is stopped, not rebooting it as a part of SSH Key reset"); return true; @@ -6135,14 +6141,7 @@ public UserVm restoreVMInternal(Account caller, UserVmVO vm, Long newTemplateId) if (template.getEnablePassword()) { password = _mgr.generateRandomPassword(); boolean result = resetVMPasswordInternal(vmId, password); - if (result) { - vm.setPassword(password); - _vmDao.loadDetails(vm); - // update the password in vm_details table too - // Check if an SSH key pair was selected for the instance and if so - // use it to encrypt & save the vm password - encryptAndStorePassword(vm, password); - } else { + if (!result) { throw new CloudRuntimeException("VM reset is completed but failed to reset password for the virtual machine "); } } From 3db327108411e7c3dac4b9dec60591325ed6852b Mon Sep 17 00:00:00 2001 From: Rohit Yadav Date: Thu, 17 May 2018 14:00:05 +0530 Subject: [PATCH 04/10] reset pass should fail if VM is not stopped just like reset ssh public key Refactor code Signed-off-by: Rohit Yadav --- .../element/ConfigDriveNetworkElement.java | 174 ++++++------------ .../src/com/cloud/vm/UserVmManagerImpl.java | 7 +- 2 files changed, 65 insertions(+), 116 deletions(-) diff --git a/server/src/com/cloud/network/element/ConfigDriveNetworkElement.java b/server/src/com/cloud/network/element/ConfigDriveNetworkElement.java index 734365a43df5..1f525c1f0276 100644 --- a/server/src/com/cloud/network/element/ConfigDriveNetworkElement.java +++ b/server/src/com/cloud/network/element/ConfigDriveNetworkElement.java @@ -20,21 +20,18 @@ import java.util.List; import java.util.Map; import java.util.Set; -import javax.inject.Inject; -import org.apache.cloudstack.storage.configdrive.ConfigDrive; -import org.apache.log4j.Logger; +import javax.inject.Inject; -import org.apache.cloudstack.engine.orchestration.service.VolumeOrchestrationService; 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.to.TemplateObjectTO; -import com.cloud.agent.AgentManager; +import org.apache.log4j.Logger; + import com.cloud.agent.api.Answer; -import com.cloud.agent.api.AttachIsoAnswer; -import com.cloud.agent.api.AttachIsoCommand; import com.cloud.agent.api.HandleConfigDriveIsoCommand; import com.cloud.agent.api.to.DiskTO; import com.cloud.configuration.ConfigurationManager; @@ -42,7 +39,6 @@ import com.cloud.deploy.DeployDestination; import com.cloud.exception.ConcurrentOperationException; import com.cloud.exception.InsufficientCapacityException; -import com.cloud.exception.OperationTimedoutException; import com.cloud.exception.ResourceUnavailableException; import com.cloud.exception.UnsupportedServiceException; import com.cloud.host.Host; @@ -55,7 +51,6 @@ import com.cloud.network.NetworkModel; import com.cloud.network.Networks.TrafficType; import com.cloud.network.PhysicalNetworkServiceProvider; -import com.cloud.network.dao.NetworkDao; import com.cloud.offering.NetworkOffering; import com.cloud.service.dao.ServiceOfferingDao; import com.cloud.storage.Storage; @@ -63,46 +58,37 @@ import com.cloud.storage.dao.GuestOSCategoryDao; import com.cloud.storage.dao.GuestOSDao; import com.cloud.utils.component.AdapterBase; +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.UserVmManager; 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.DomainRouterDao; import com.cloud.vm.dao.UserVmDao; import com.cloud.vm.dao.UserVmDetailsDao; public class ConfigDriveNetworkElement extends AdapterBase implements NetworkElement, UserDataServiceProvider, StateListener, NetworkMigrationResponder { - private static final Logger s_logger = Logger.getLogger(ConfigDriveNetworkElement.class); + private static final Logger LOG = Logger.getLogger(ConfigDriveNetworkElement.class); private static final Map> capabilities = setCapabilities(); - @Inject - NetworkDao _networkConfigDao; @Inject NetworkModel _networkMgr; @Inject - UserVmManager _userVmMgr; - @Inject UserVmDao _userVmDao; @Inject UserVmDetailsDao _userVmDetailsDao; @Inject - DomainRouterDao _routerDao; - @Inject ConfigurationManager _configMgr; @Inject DataCenterDao _dcDao; @Inject - AgentManager _agentManager; - @Inject ServiceOfferingDao _serviceOfferingDao; @Inject NetworkModel _networkModel; @@ -116,8 +102,6 @@ public class ConfigDriveNetworkElement extends AdapterBase implements NetworkEle DataStoreManager _dataStoreMgr; @Inject EndPointSelector _ep; - @Inject - VolumeOrchestrationService _volumeMgr; private final static Integer CONFIGDRIVEDISKSEQ = 4; @@ -148,7 +132,8 @@ public boolean release(Network network, NicProfile nic, VirtualMachineProfile vm if (!nic.isDefaultNic()) { return true; } - // Remove form secondary storage + + // Remove from secondary storage DataStore secondaryStore = _dataStoreMgr.getImageStore(network.getDataCenterId()); String isoFile = "/" + ConfigDrive.CONFIGDRIVEDIR + "/" + vm.getInstanceName()+ "/" + ConfigDrive.CONFIGDRIVEFILENAME; @@ -157,7 +142,7 @@ public boolean release(Network network, NicProfile nic, VirtualMachineProfile vm // Delete the ISO on the secondary store EndPoint endpoint = _ep.select(secondaryStore); if (endpoint == null) { - s_logger.error(String.format("Secondary store: %s not available", secondaryStore.getName())); + LOG.error(String.format("Secondary store: %s not available", secondaryStore.getName())); return false; } Answer answer = endpoint.sendMessage(deleteCommand); @@ -206,37 +191,49 @@ public boolean canEnableIndividualServices() { } private String getSshKey(VirtualMachineProfile profile) { - UserVmDetailVO vmDetailSshKey = _userVmDetailsDao.findDetail(profile.getId(), "SSH.PublicKey"); + 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 { - String sshPublicKey = getSshKey(profile); return (canHandle(network.getTrafficType()) - && updateConfigDrive(profile, sshPublicKey, nic)) - && updateConfigDriveIso(network, profile, dest.getHost(), false); + && addConfigDriveData(profile, nic)) + && createConfigDriveIso(network, profile, dest.getHost()); } @Override - public boolean savePassword(Network network, NicProfile nic, VirtualMachineProfile profile) throws ResourceUnavailableException { - String sshPublicKey = getSshKey(profile); - if (!(canHandle(network.getTrafficType()) && updateConfigDrive(profile, sshPublicKey, nic))) return false; - return updateConfigDriveIso(network, profile, true); + 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"); + } + return canHandle(network.getTrafficType()); } @Override - public boolean saveSSHKey(Network network, NicProfile nic, VirtualMachineProfile vm, String sshPublicKey) throws ResourceUnavailableException { - if (!(canHandle(network.getTrafficType()) && updateConfigDrive(vm, sshPublicKey, nic))) return false; - return updateConfigDriveIso(network, vm, true); + 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"); + } + return canHandle(network.getTrafficType()); } @Override - public boolean saveUserData(Network network, NicProfile nic, VirtualMachineProfile profile) throws ResourceUnavailableException { - String sshPublicKey = getSshKey(profile); - if (!(canHandle(network.getTrafficType()) && updateConfigDrive(profile, sshPublicKey, nic))) return false; - return updateConfigDriveIso(network, profile, true); + 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()); } @Override @@ -266,12 +263,12 @@ public boolean postStateTransitionEvent(StateMachine2.Transition vmData = _networkModel.generateVmData(vm.getUserData(), serviceOffering, vm.getDataCenterId(), vm.getInstanceName(), vm.getHostName(), vm.getId(), - vm.getUuid(), nic.getIPv4Address(), publicKey, (String) profile.getParameter(VirtualMachineProfile.Param.VmPassword), isWindows); + final List 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; } - private Integer detachIso (DataStore secondaryStore, String instanceName, Long hostId) throws ResourceUnavailableException { - String isoPath = ConfigDrive.CONFIGDRIVEDIR + "/" + instanceName + "/" + ConfigDrive.CONFIGDRIVEFILENAME; - AttachIsoCommand isoCommand = new AttachIsoCommand(instanceName, secondaryStore.getUri() + "/" + isoPath, false, CONFIGDRIVEDISKSEQ, true); - isoCommand.setStoreUrl(secondaryStore.getUri()); - Answer attachIsoAnswer = null; - - try { - attachIsoAnswer = _agentManager.send(hostId, isoCommand); - } catch (OperationTimedoutException e) { - throw new ResourceUnavailableException("Detach ISO failed: " + e.getMessage(), ConfigDriveNetworkElement.class, 0L); - } - - if (!attachIsoAnswer.getResult()) { - throw new ResourceUnavailableException("Detach ISO failed: " + attachIsoAnswer.getDetails(), ConfigDriveNetworkElement.class, 0L); - } - - if (attachIsoAnswer instanceof AttachIsoAnswer) { - return ((AttachIsoAnswer)attachIsoAnswer).getDeviceKey(); - } else { - return CONFIGDRIVEDISKSEQ; - } - } - - private void attachIso (DataStore secondaryStore, String instanceName, Long hostId, Integer deviceKey) throws ResourceUnavailableException { - String isoPath = ConfigDrive.CONFIGDRIVEDIR + "/" + instanceName + "/" + ConfigDrive.CONFIGDRIVEFILENAME; - AttachIsoCommand isoCommand = new AttachIsoCommand(instanceName, secondaryStore.getUri() + "/" + isoPath, true); - isoCommand.setStoreUrl(secondaryStore.getUri()); - isoCommand.setDeviceKey(deviceKey); - Answer attachIsoAnswer = null; - try { - attachIsoAnswer = _agentManager.send(hostId, isoCommand); - } catch (OperationTimedoutException e) { - throw new ResourceUnavailableException("Attach ISO failed: " + e.getMessage() ,ConfigDriveNetworkElement.class,0L); - } - if (!attachIsoAnswer.getResult()) { - throw new ResourceUnavailableException("Attach ISO failed: " + attachIsoAnswer.getDetails(),ConfigDriveNetworkElement.class,0L); - } - } - } diff --git a/server/src/com/cloud/vm/UserVmManagerImpl.java b/server/src/com/cloud/vm/UserVmManagerImpl.java index 9895cab22ac8..4ac48c9cd318 100644 --- a/server/src/com/cloud/vm/UserVmManagerImpl.java +++ b/server/src/com/cloud/vm/UserVmManagerImpl.java @@ -677,6 +677,11 @@ public UserVm resetVMPassword(ResetVMPasswordCmd cmd, String password) throws Re throw new InvalidParameterValueException("Vm with id " + vmId + " is not in the right state"); } + if (userVm.getState() != State.Stopped) { + s_logger.error("vm is not in the right state: " + vmId); + throw new InvalidParameterValueException("Vm " + userVm + " should be stopped to do password reset"); + } + _accountMgr.checkAccess(caller, null, true, userVm); boolean result = resetVMPasswordInternal(vmId, password); @@ -4105,8 +4110,6 @@ public boolean finalizeVirtualMachineProfile(VirtualMachineProfile profile, Depl } } - - _templateMgr.prepareIsoForVmProfile(profile, dest); return true; } From 184de36c181f4783c06189f15bef5fcb98478757 Mon Sep 17 00:00:00 2001 From: Rohit Yadav Date: Thu, 17 May 2018 16:27:53 +0530 Subject: [PATCH 05/10] cfg: build iso on mgmt server and ship iso-data around Signed-off-by: Rohit Yadav --- .../api/HandleConfigDriveIsoCommand.java | 19 +- engine/storage/configdrive/pom.xml | 2 +- .../storage/configdrive/ConfigDrive.java | 1 + .../configdrive/ConfigDriveBuilder.java | 217 +++++++++++ .../configdrive/ConfigDriveBuilderTest.java | 38 ++ .../element/ConfigDriveNetworkElement.java | 16 +- .../ConfigDriveNetworkElementTest.java | 16 +- .../resource/NfsSecondaryStorageResource.java | 347 +++--------------- 8 files changed, 337 insertions(+), 319 deletions(-) create mode 100644 engine/storage/configdrive/src/org/apache/cloudstack/storage/configdrive/ConfigDriveBuilder.java create mode 100644 engine/storage/configdrive/test/org/apache/cloudstack/storage/configdrive/ConfigDriveBuilderTest.java diff --git a/core/src/com/cloud/agent/api/HandleConfigDriveIsoCommand.java b/core/src/com/cloud/agent/api/HandleConfigDriveIsoCommand.java index e026b7ad28b7..f3770edc5fc3 100644 --- a/core/src/com/cloud/agent/api/HandleConfigDriveIsoCommand.java +++ b/core/src/com/cloud/agent/api/HandleConfigDriveIsoCommand.java @@ -26,17 +26,23 @@ public class HandleConfigDriveIsoCommand extends Command { String isoFile; + String isoData; List vmData; String configDriveLabel; boolean create = false; - private boolean update = false; private DataStoreTO destStore; - public HandleConfigDriveIsoCommand(List vmData, String label, DataStoreTO destStore, String isoFile, boolean create, boolean update) { + public HandleConfigDriveIsoCommand(String isoFile, String isoData, DataStoreTO destStore, boolean create) { + this.isoFile = isoFile; + this.isoData = isoData; + this.destStore = destStore; + this.create = create; + } + + public HandleConfigDriveIsoCommand(List vmData, String label, DataStoreTO destStore, String isoFile, boolean create) { this.vmData = vmData; this.configDriveLabel = label; this.create = create; - this.update = update; this.destStore = destStore; this.isoFile = isoFile; @@ -47,6 +53,10 @@ public boolean executeInSequence() { return false; } + public String getIsoData() { + return isoData; + } + public List getVmData() { return vmData; } @@ -71,7 +81,4 @@ public String getIsoFile() { return isoFile; } - public boolean isUpdate() { - return update; - } } diff --git a/engine/storage/configdrive/pom.xml b/engine/storage/configdrive/pom.xml index 2ea63f84d112..dc3d118eb686 100644 --- a/engine/storage/configdrive/pom.xml +++ b/engine/storage/configdrive/pom.xml @@ -22,7 +22,7 @@ Apache CloudStack Framework - Storage Config Drive Component org.apache.cloudstack - cloudstack-framework + cloud-engine 4.11.1.0-SNAPSHOT ../../pom.xml diff --git a/engine/storage/configdrive/src/org/apache/cloudstack/storage/configdrive/ConfigDrive.java b/engine/storage/configdrive/src/org/apache/cloudstack/storage/configdrive/ConfigDrive.java index 2d72bc1c3390..156e2ffaf4d9 100644 --- a/engine/storage/configdrive/src/org/apache/cloudstack/storage/configdrive/ConfigDrive.java +++ b/engine/storage/configdrive/src/org/apache/cloudstack/storage/configdrive/ConfigDrive.java @@ -24,4 +24,5 @@ public class ConfigDrive { public static final String cloudStackConfigDriveName = "/cloudstack/"; public static final String openStackConfigDriveName = "/openstack/latest/"; + } diff --git a/engine/storage/configdrive/src/org/apache/cloudstack/storage/configdrive/ConfigDriveBuilder.java b/engine/storage/configdrive/src/org/apache/cloudstack/storage/configdrive/ConfigDriveBuilder.java new file mode 100644 index 000000000000..41be3b0265d0 --- /dev/null +++ b/engine/storage/configdrive/src/org/apache/cloudstack/storage/configdrive/ConfigDriveBuilder.java @@ -0,0 +1,217 @@ +// 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 org.apache.cloudstack.storage.configdrive; + +import static com.cloud.network.NetworkModel.CONFIGDATA_CONTENT; +import static com.cloud.network.NetworkModel.CONFIGDATA_DIR; +import static com.cloud.network.NetworkModel.CONFIGDATA_FILE; +import static com.cloud.network.NetworkModel.PASSWORD_FILE; +import static com.cloud.network.NetworkModel.USERDATA_FILE; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; + +import org.apache.commons.codec.binary.Base64; +import org.apache.commons.io.FileUtils; +import org.apache.commons.lang.StringUtils; +import org.apache.log4j.Logger; +import org.joda.time.Duration; + +import com.cloud.network.NetworkModel; +import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.utils.script.Script; +import com.google.common.base.Strings; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; + +public class ConfigDriveBuilder { + + public static final Logger LOG = Logger.getLogger(ConfigDriveBuilder.class); + + private static void writeFile(final File folder, final String file, final String content) { + if (folder == null || Strings.isNullOrEmpty(file)) { + return; + } + final File vendorDataFile = new File(folder, file); + try (final FileWriter fw = new FileWriter(vendorDataFile); final BufferedWriter bw = new BufferedWriter(fw)) { + bw.write(content); + } catch (IOException ex) { + throw new CloudRuntimeException("Failed to create config drive file " + file, ex); + } + } + + public static String fileToBase64String(final File isoFile) throws IOException { + byte[] encoded = Base64.encodeBase64(FileUtils.readFileToByteArray(isoFile)); + return new String(encoded, StandardCharsets.US_ASCII); + } + + public static File base64StringToFile(final String encodedIsoData, final String folder, final String fileName) throws IOException { + byte[] decoded = Base64.decodeBase64(encodedIsoData.getBytes(StandardCharsets.US_ASCII)); + Path destPath = Paths.get(folder, fileName); + return Files.write(destPath, decoded).toFile(); + } + + public static String buildConfigDrive(final List vmData, final String isoFileName, final String driveLabel) { + if (vmData == null) { + throw new CloudRuntimeException("No VM metadata provided"); + } + + Path tempDir = null; + String tempDirName = null; + try { + tempDir = Files.createTempDirectory(ConfigDrive.CONFIGDRIVEDIR); + tempDirName = tempDir.toString(); + + File openStackFolder = new File(tempDirName + ConfigDrive.openStackConfigDriveName); + if (openStackFolder.exists() || openStackFolder.mkdirs()) { + writeFile(openStackFolder, "vendor_data.json", "{}"); + writeFile(openStackFolder, "network_data.json", "{}"); + } else { + throw new CloudRuntimeException("Failed to create folder " + openStackFolder); + } + + JsonObject metaData = new JsonObject(); + for (String[] item : vmData) { + String dataType = item[CONFIGDATA_DIR]; + String fileName = item[CONFIGDATA_FILE]; + String content = item[CONFIGDATA_CONTENT]; + LOG.debug(String.format("[createConfigDriveIsoForVM] dataType=%s, filename=%s, content=%s", + dataType, fileName, (fileName.equals(PASSWORD_FILE)?"********":content))); + + // create file with content in folder + if (dataType != null && !dataType.isEmpty()) { + //create folder + File typeFolder = new File(tempDirName + ConfigDrive.cloudStackConfigDriveName + dataType); + if (typeFolder.exists() || typeFolder.mkdirs()) { + if (StringUtils.isNotEmpty(content)) { + File file = new File(typeFolder, fileName + ".txt"); + try { + if (fileName.equals(USERDATA_FILE)) { + // User Data is passed as a base64 encoded string + FileUtils.writeByteArrayToFile(file, Base64.decodeBase64(content)); + } else { + FileUtils.write(file, content, com.cloud.utils.StringUtils.getPreferredCharset()); + } + } catch (IOException ex) { + throw new CloudRuntimeException("Failed to create file ", ex); + } + } + } else { + throw new CloudRuntimeException("Failed to create folder: " + typeFolder); + } + + //now write the file to the OpenStack directory + metaData = buildOpenStackMetaData(metaData, dataType, fileName, content); + } + } + writeFile(openStackFolder, "meta_data.json", metaData.toString()); + + String linkResult = linkUserData(tempDirName); + if (linkResult != null) { + String errMsg = "Unable to create user_data link due to " + linkResult; + throw new CloudRuntimeException(errMsg); + } + + File tmpIsoStore = new File(tempDirName, new File(isoFileName).getName()); + Script command = new Script("/usr/bin/genisoimage", Duration.standardSeconds(300), LOG); + command.add("-o", tmpIsoStore.getAbsolutePath()); + command.add("-ldots"); + command.add("-allow-lowercase"); + command.add("-allow-multidot"); + command.add("-cache-inodes"); // Enable caching inode and device numbers to find hard links to files. + command.add("-l"); + command.add("-quiet"); + command.add("-J"); + command.add("-r"); + command.add("-V", driveLabel); + command.add(tempDirName); + LOG.debug("Executing config drive creation command: " + command.toString()); + String result = command.execute(); + if (result != null) { + String errMsg = "Unable to create iso file: " + isoFileName + " due to " + result; + LOG.warn(errMsg); + throw new CloudRuntimeException(errMsg); + } + return fileToBase64String(new File(tmpIsoStore.getAbsolutePath())); + } catch (IOException e) { + throw new CloudRuntimeException("Failed due to", e); + } finally { + try { + FileUtils.deleteDirectory(tempDir.toFile()); + } catch (IOException ioe) { + LOG.warn("Failed to delete ConfigDrive temporary directory: " + tempDirName, ioe); + } + } + } + + private static String linkUserData(String tempDirName) { + //Hard link the user_data.txt file with the user_data file in the OpenStack directory. + String userDataFilePath = tempDirName + ConfigDrive.cloudStackConfigDriveName + "userdata/user_data.txt"; + if ((new File(userDataFilePath).exists())) { + Script hardLink = new Script("ln", Duration.standardSeconds(300), LOG); + hardLink.add(userDataFilePath); + hardLink.add(tempDirName + ConfigDrive.openStackConfigDriveName + "user_data"); + LOG.debug("execute command: " + hardLink.toString()); + return hardLink.execute(); + } + return null; + } + + private static JsonArray arrayOf(JsonElement... elements) { + JsonArray array = new JsonArray(); + for (JsonElement element : elements) { + array.add(element); + } + return array; + } + + private static JsonObject buildOpenStackMetaData(JsonObject metaData, String dataType, String fileName, String content) { + if (dataType.equals(NetworkModel.METATDATA_DIR) && StringUtils.isNotEmpty(content)) { + //keys are a special case in OpenStack format + if (NetworkModel.PUBLIC_KEYS_FILE.equals(fileName)) { + String[] keyArray = content.replace("\\n", "").split(" "); + String keyName = "key"; + if (keyArray.length > 3 && StringUtils.isNotEmpty(keyArray[2])){ + keyName = keyArray[2]; + } + + JsonObject keyLegacy = new JsonObject(); + keyLegacy.addProperty("type", "ssh"); + keyLegacy.addProperty("data", content.replace("\\n", "")); + keyLegacy.addProperty("name", keyName); + metaData.add("keys", arrayOf(keyLegacy)); + + JsonObject key = new JsonObject(); + key.addProperty(keyName, content); + metaData.add("public_keys", key); + } else if (NetworkModel.openStackFileMapping.get(fileName) != null) { + metaData.addProperty(NetworkModel.openStackFileMapping.get(fileName), content); + } + } + return metaData; + } + +} diff --git a/engine/storage/configdrive/test/org/apache/cloudstack/storage/configdrive/ConfigDriveBuilderTest.java b/engine/storage/configdrive/test/org/apache/cloudstack/storage/configdrive/ConfigDriveBuilderTest.java new file mode 100644 index 000000000000..b420a94bd731 --- /dev/null +++ b/engine/storage/configdrive/test/org/apache/cloudstack/storage/configdrive/ConfigDriveBuilderTest.java @@ -0,0 +1,38 @@ +package org.apache.cloudstack.storage.configdrive; + +import java.util.Arrays; +import java.util.List; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +public class ConfigDriveBuilderTest { + @Before + public void setUp() throws Exception { + } + + @After + public void tearDown() throws Exception { + } + + @Test + public void testConfigDriveBuild() { + + List actualVmData = Arrays.asList( + new String[]{"userdata", "user_data", "c29tZSB1c2VyIGRhdGE="}, + new String[]{"metadata", "service-offering", "offering"}, + new String[]{"metadata", "availability-zone", "zone1"}, + new String[]{"metadata", "local-hostname", "hostname"}, + new String[]{"metadata", "local-ipv4", "192.168.111.111"}, + new String[]{"metadata", "public-hostname", "7.7.7.7"}, + new String[]{"metadata", "public-ipv4", "7.7.7.7"}, + new String[]{"metadata", "vm-id", "uuid"}, + new String[]{"metadata", "instance-id", "i-x-y"}, + new String[]{"metadata", "public-keys", "ssh-rsa some-key"}, + new String[]{"metadata", "cloud-identifier", String.format("CloudStack-{%s}", "uuid")}, + new String[]{"password", "vm_password", "password123"} + ); + String encodedString = ConfigDriveBuilder.buildConfigDrive(actualVmData, "i-x-y.iso", "config-2"); + } +} \ No newline at end of file diff --git a/server/src/com/cloud/network/element/ConfigDriveNetworkElement.java b/server/src/com/cloud/network/element/ConfigDriveNetworkElement.java index 1f525c1f0276..9508431e56bb 100644 --- a/server/src/com/cloud/network/element/ConfigDriveNetworkElement.java +++ b/server/src/com/cloud/network/element/ConfigDriveNetworkElement.java @@ -28,6 +28,7 @@ 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; @@ -138,7 +139,7 @@ public boolean release(Network network, NicProfile nic, VirtualMachineProfile vm String isoFile = "/" + ConfigDrive.CONFIGDRIVEDIR + "/" + vm.getInstanceName()+ "/" + ConfigDrive.CONFIGDRIVEFILENAME; HandleConfigDriveIsoCommand deleteCommand = new HandleConfigDriveIsoCommand(vm.getVmData(), - vm.getConfigDriveLabel(), secondaryStore.getTO(), isoFile, false, false); + vm.getConfigDriveLabel(), secondaryStore.getTO(), isoFile, false); // Delete the ISO on the secondary store EndPoint endpoint = _ep.select(secondaryStore); if (endpoint == null) { @@ -199,7 +200,7 @@ private String getSshKey(VirtualMachineProfile profile) { public boolean addPasswordAndUserdata(Network network, NicProfile nic, VirtualMachineProfile profile, DeployDestination dest, ReservationContext context) throws ConcurrentOperationException, InsufficientCapacityException, ResourceUnavailableException { return (canHandle(network.getTrafficType()) - && addConfigDriveData(profile, nic)) + && configureConfigDriveData(profile, nic)) && createConfigDriveIso(network, profile, dest.getHost()); } @@ -260,7 +261,7 @@ public boolean postStateTransitionEvent(StateMachine2.Transition recoveredVmData = recoverVmData(isoFile); - for (String[] vmDataEntry : cmd.getVmData()) { - if (updatableConfigData.containsKey(vmDataEntry[CONFIGDATA_FILE]) - && updatableConfigData.get(vmDataEntry[CONFIGDATA_FILE]).equals(vmDataEntry[CONFIGDATA_DIR])) { - updateVmData(recoveredVmData, vmDataEntry); - } - } - cmd.setVmData(recoveredVmData); - } catch (IOException e) { - return new Answer(cmd, e); + s_logger.debug("config drive iso already exists"); + } + Path tempDir = null; + try { + tempDir = java.nio.file.Files.createTempDirectory(ConfigDrive.CONFIGDRIVEDIR); + File tmpIsoFile = ConfigDriveBuilder.base64StringToFile(cmd.getIsoData(), tempDir.toAbsolutePath().toString(), ConfigDrive.CONFIGDRIVEFILENAME); + copyLocalToNfs(tmpIsoFile, new File(cmd.getIsoFile()), cmd.getDestStore()); + } catch (IOException | ConfigurationException e) { + return new Answer(cmd, false, "Failed due to exception: " + e.getMessage()); + } finally { + try { + if (tempDir != null) { + FileUtils.deleteDirectory(tempDir.toFile()); } + } catch (IOException ioe) { + s_logger.warn("Failed to delete ConfigDrive temporary directory: " + tempDir.toString(), ioe); } } - return createConfigDriveIsoForVM(cmd); + return new Answer(cmd, true, "Successfully saved config drive at secondary storage"); } else { DataStoreTO dstore = cmd.getDestStore(); if (dstore instanceof NfsTO) { @@ -382,237 +370,6 @@ private Answer execute(HandleConfigDriveIsoCommand cmd) { } } - private void updateVmData(List recoveredVmData, String[] vmDataEntry) { - for (String[] recoveredEntry : recoveredVmData) { - if (recoveredEntry[CONFIGDATA_DIR].equals(vmDataEntry[CONFIGDATA_DIR]) - && recoveredEntry[CONFIGDATA_FILE].equals(vmDataEntry[CONFIGDATA_FILE])) { - recoveredEntry[CONFIGDATA_CONTENT] = vmDataEntry[CONFIGDATA_CONTENT]; - return; - } - } - recoveredVmData.add(vmDataEntry); - } - - private List recoverVmData(File isoFile) throws IOException { - String tempDirName = null; - List recoveredVmData = Lists.newArrayList(); - boolean mounted = false; - try { - Path tempDir = java.nio.file.Files.createTempDirectory("ConfigDrive"); - tempDirName = tempDir.toString(); - - // Unpack the current config drive file - Script command = new Script(!_inSystemVM, "mount", _timeout, s_logger); - command.add("-o", "loop"); - command.add(isoFile.getAbsolutePath()); - command.add(tempDirName); - String result = command.execute(); - - if (result != null) { - String errMsg = "Unable to mount " + isoFile.getAbsolutePath() + " at " + tempDirName + " due to " + result; - s_logger.error(errMsg); - throw new IOException(errMsg); - } - mounted = true; - - - // Scan directory structure - for (File configDirectory: (new File(tempDirName, "cloudstack")).listFiles()){ - for (File configFile: configDirectory.listFiles()) { - recoveredVmData.add(new String[]{configDirectory.getName(), - Files.getNameWithoutExtension(configFile.getName()), - Files.readFirstLine(configFile, Charset.defaultCharset())}); - } - } - - } finally { - if (mounted) { - Script command = new Script(!_inSystemVM, "umount", _timeout, s_logger); - command.add(tempDirName); - String result = command.execute(); - if (result != null) { - s_logger.warn("Unable to umount " + tempDirName + " due to " + result); - } - } - try { - FileUtils.deleteDirectory(new File(tempDirName)); - } catch (IOException ioe) { - s_logger.warn("Failed to delete ConfigDrive temporary directory: " + tempDirName, ioe); - } - } - return recoveredVmData; - } - - public Answer createConfigDriveIsoForVM(HandleConfigDriveIsoCommand cmd) { - //create folder for the VM - if (cmd.getVmData() != null) { - - Path tempDir = null; - String tempDirName = null; - try { - tempDir = java.nio.file.Files.createTempDirectory("ConfigDrive"); - tempDirName = tempDir.toString(); - - //create OpenStack files - //create folder with empty files - File openStackFolder = new File(tempDirName + ConfigDrive.openStackConfigDriveName); - if (openStackFolder.exists() || openStackFolder.mkdirs()) { - File vendorDataFile = new File(openStackFolder,"vendor_data.json"); - try (FileWriter fw = new FileWriter(vendorDataFile); BufferedWriter bw = new BufferedWriter(fw)) { - bw.write("{}"); - } catch (IOException ex) { - s_logger.error("Failed to create file ", ex); - return new Answer(cmd, ex); - } - File networkDataFile = new File(openStackFolder, "network_data.json"); - try (FileWriter fw = new FileWriter(networkDataFile); BufferedWriter bw = new BufferedWriter(fw)) { - bw.write("{}"); - } catch (IOException ex) { - s_logger.error("Failed to create file ", ex); - return new Answer(cmd, ex); - } - } else { - s_logger.error("Failed to create folder " + openStackFolder); - return new Answer(cmd, false, "Failed to create folder " + openStackFolder); - } - - JsonObject metaData = new JsonObject(); - for (String[] item : cmd.getVmData()) { - String dataType = item[CONFIGDATA_DIR]; - String fileName = item[CONFIGDATA_FILE]; - String content = item[CONFIGDATA_CONTENT]; - s_logger.debug(String.format("[createConfigDriveIsoForVM] dataType=%s, filename=%s, content=%s", - dataType, fileName, (fileName.equals(PASSWORD_FILE)?"********":content))); - - // create file with content in folder - if (dataType != null && !dataType.isEmpty()) { - //create folder - File typeFolder = new File(tempDirName + ConfigDrive.cloudStackConfigDriveName + dataType); - if (typeFolder.exists() || typeFolder.mkdirs()) { - if (StringUtils.isNotEmpty(content)) { - File file = new File(typeFolder, fileName + ".txt"); - try { - if (fileName.equals(USERDATA_FILE)) { - // User Data is passed as a base64 encoded string - FileUtils.writeByteArrayToFile(file, Base64.decodeBase64(content)); - } else { - FileUtils.write(file, content, com.cloud.utils.StringUtils.getPreferredCharset()); - } - } catch (IOException ex) { - s_logger.error("Failed to create file ", ex); - return new Answer(cmd, ex); - } - } - } else { - s_logger.error("Failed to create folder " + typeFolder); - return new Answer(cmd, false, "Failed to create folder " + typeFolder); - } - - //now write the file to the OpenStack directory - metaData = constructOpenStackMetaData(metaData, dataType, fileName, content); - } - } - - File metaDataFile = new File(openStackFolder, "meta_data.json"); - try (FileWriter fw = new FileWriter(metaDataFile); BufferedWriter bw = new BufferedWriter(fw)) { - bw.write(metaData.toString()); - } catch (IOException ex) { - s_logger.error("Failed to create file ", ex); - return new Answer(cmd, ex); - } - - String linkResult = linkUserData(tempDirName); - if (linkResult != null) { - String errMsg = "Unable to create user_data link due to " + linkResult; - s_logger.warn(errMsg); - return new Answer(cmd, false, errMsg); - } - - File tmpIsoStore = new File(tempDirName, new File(cmd.getIsoFile()).getName()); - Script command = new Script(!_inSystemVM, "/usr/bin/genisoimage", _timeout, s_logger); - command.add("-o", tmpIsoStore.getAbsolutePath()); - command.add("-ldots"); - command.add("-allow-lowercase"); - command.add("-allow-multidot"); - command.add("-cache-inodes"); // Enable caching inode and device numbers to find hard links to files. - command.add("-l"); - command.add("-quiet"); - command.add("-J"); - command.add("-r"); - command.add("-V", cmd.getConfigDriveLabel()); - command.add(tempDirName); - s_logger.debug("execute command: " + command.toString()); - String result = command.execute(); - if (result != null) { - String errMsg = "Unable to create iso file: " + cmd.getIsoFile() + " due to " + result; - s_logger.warn(errMsg); - return new Answer(cmd, false, errMsg); - } - copyLocalToNfs(tmpIsoStore, new File(cmd.getIsoFile()), cmd.getDestStore()); - - } catch (IOException e) { - return new Answer(cmd, e); - } catch (ConfigurationException e) { - s_logger.warn("SecondStorageException ", e); - return new Answer(cmd, e); - } finally { - try { - FileUtils.deleteDirectory(tempDir.toFile()); - } catch (IOException ioe) { - s_logger.warn("Failed to delete ConfigDrive temporary directory: " + tempDirName, ioe); - } - } - } - return new Answer(cmd); - } - - JsonObject constructOpenStackMetaData(JsonObject metaData, String dataType, String fileName, String content) { - if (dataType.equals(NetworkModel.METATDATA_DIR) && StringUtils.isNotEmpty(content)) { - //keys are a special case in OpenStack format - if (NetworkModel.PUBLIC_KEYS_FILE.equals(fileName)) { - String[] keyArray = content.replace("\\n", "").split(" "); - String keyName = "key"; - if (keyArray.length > 3 && StringUtils.isNotEmpty(keyArray[2])){ - keyName = keyArray[2]; - } - - JsonObject keyLegacy = new JsonObject(); - keyLegacy.addProperty("type", "ssh"); - keyLegacy.addProperty("data", content.replace("\\n", "")); - keyLegacy.addProperty("name", keyName); - metaData.add("keys", arrayOf(keyLegacy)); - - JsonObject key = new JsonObject(); - key.addProperty(keyName, content); - metaData.add("public_keys", key); - } else if (NetworkModel.openStackFileMapping.get(fileName) != null) { - metaData.addProperty(NetworkModel.openStackFileMapping.get(fileName), content); - } - } - return metaData; - } - - private static JsonArray arrayOf(JsonElement... elements) { - JsonArray array = new JsonArray(); - for (JsonElement element : elements) { - array.add(element); - } - return array; - } - - private String linkUserData(String tempDirName) { - //Hard link the user_data.txt file with the user_data file in the OpenStack directory. - String userDataFilePath = tempDirName + ConfigDrive.cloudStackConfigDriveName + "userdata/user_data.txt"; - if ((new File(userDataFilePath).exists())) { - Script hardLink = new Script(!_inSystemVM, "ln", _timeout, s_logger); - hardLink.add(userDataFilePath); - hardLink.add(tempDirName + ConfigDrive.openStackConfigDriveName + "user_data"); - s_logger.debug("execute command: " + hardLink.toString()); - return hardLink.execute(); - } - return null; - } - protected void copyLocalToNfs(File localFile, File isoFile, DataStoreTO destData) throws ConfigurationException, IOException { String scriptsDir = "scripts/storage/secondary"; String createVolScr = Script.findScript(scriptsDir, "createvolume.sh"); From 4d4dd02cca9a156ad42c501a70c05ae18c0974c9 Mon Sep 17 00:00:00 2001 From: Rohit Yadav Date: Thu, 17 May 2018 20:09:45 +0530 Subject: [PATCH 06/10] implement config drive on primary storage for KVM + global setting Signed-off-by: Rohit Yadav --- .../api/HandleConfigDriveIsoCommand.java | 29 +-- .../com/cloud/vm/VirtualMachineManager.java | 11 +- .../cloud/vm/VirtualMachineManagerImpl.java | 5 +- .../storage/configdrive/ConfigDrive.java | 8 + .../configdrive/ConfigDriveBuilder.java | 7 +- ...ibvirtHandleConfigDriveCommandWrapper.java | 79 ++++++++ .../element/ConfigDriveNetworkElement.java | 171 +++++++++++------- .../ConfigDriveNetworkElementTest.java | 16 +- 8 files changed, 222 insertions(+), 104 deletions(-) create mode 100644 plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtHandleConfigDriveCommandWrapper.java diff --git a/core/src/com/cloud/agent/api/HandleConfigDriveIsoCommand.java b/core/src/com/cloud/agent/api/HandleConfigDriveIsoCommand.java index f3770edc5fc3..23858f3c61cc 100644 --- a/core/src/com/cloud/agent/api/HandleConfigDriveIsoCommand.java +++ b/core/src/com/cloud/agent/api/HandleConfigDriveIsoCommand.java @@ -19,16 +19,13 @@ package com.cloud.agent.api; -import java.util.List; - import com.cloud.agent.api.to.DataStoreTO; public class HandleConfigDriveIsoCommand extends Command { - String isoFile; + @LogLevel(LogLevel.Log4jLevel.Off) String isoData; - List vmData; - String configDriveLabel; + String isoFile; boolean create = false; private DataStoreTO destStore; @@ -39,15 +36,6 @@ public HandleConfigDriveIsoCommand(String isoFile, String isoData, DataStoreTO d this.create = create; } - public HandleConfigDriveIsoCommand(List vmData, String label, DataStoreTO destStore, String isoFile, boolean create) { - this.vmData = vmData; - this.configDriveLabel = label; - this.create = create; - this.destStore = destStore; - - this.isoFile = isoFile; - } - @Override public boolean executeInSequence() { return false; @@ -57,22 +45,10 @@ public String getIsoData() { return isoData; } - public List getVmData() { - return vmData; - } - - public void setVmData(List vmData) { - this.vmData = vmData; - } - public boolean isCreate() { return create; } - public String getConfigDriveLabel() { - return configDriveLabel; - } - public DataStoreTO getDestStore() { return destStore; } @@ -80,5 +56,4 @@ public DataStoreTO getDestStore() { public String getIsoFile() { return isoFile; } - } diff --git a/engine/api/src/com/cloud/vm/VirtualMachineManager.java b/engine/api/src/com/cloud/vm/VirtualMachineManager.java index 556ec86f0606..6041146b9de2 100644 --- a/engine/api/src/com/cloud/vm/VirtualMachineManager.java +++ b/engine/api/src/com/cloud/vm/VirtualMachineManager.java @@ -49,14 +49,17 @@ */ public interface VirtualMachineManager extends Manager { - static final ConfigKey ExecuteInSequence = new ConfigKey("Advanced", Boolean.class, "execute.in.sequence.hypervisor.commands", "false", + ConfigKey ExecuteInSequence = new ConfigKey<>("Advanced", Boolean.class, "execute.in.sequence.hypervisor.commands", "false", "If set to true, start, stop, reboot, copy and migrate commands will be serialized on the agent side. If set to false the commands are executed in parallel. Default value is false.", false); - static final ConfigKey VmConfigDriveLabel = new ConfigKey("Hidden", String.class, "vm.configdrive.label", "config-2", + ConfigKey VmConfigDriveLabel = new ConfigKey<>("Hidden", String.class, "vm.configdrive.label", "config-2", "The default label name for the config drive", false); - public interface Topics { - public static final String VM_POWER_STATE = "vm.powerstate"; + ConfigKey VmConfigDriveOnPrimaryPool = new ConfigKey<>("Advanced", Boolean.class, "vm.configdrive.primarypool.enabled", "false", + "If config drive need to be created and hosted on primary storage pool. Currently only supported for KVM.", true); + + interface Topics { + String VM_POWER_STATE = "vm.powerstate"; } /** diff --git a/engine/orchestration/src/com/cloud/vm/VirtualMachineManagerImpl.java b/engine/orchestration/src/com/cloud/vm/VirtualMachineManagerImpl.java index 5135c0fde79e..2623423a74ae 100755 --- a/engine/orchestration/src/com/cloud/vm/VirtualMachineManagerImpl.java +++ b/engine/orchestration/src/com/cloud/vm/VirtualMachineManagerImpl.java @@ -1105,10 +1105,11 @@ public void orchestrateStart(final String vmUuid, final Map[] getConfigKeys() { return new ConfigKey[] {ClusterDeltaSyncInterval, StartRetry, VmDestroyForcestop, VmOpCancelInterval, VmOpCleanupInterval, VmOpCleanupWait, VmOpLockStateRetry, - VmOpWaitInterval, ExecuteInSequence, VmJobCheckInterval, VmJobTimeout, VmJobStateReportInterval, VmConfigDriveLabel}; + VmOpWaitInterval, ExecuteInSequence, VmJobCheckInterval, VmJobTimeout, VmJobStateReportInterval, VmConfigDriveLabel, VmConfigDriveOnPrimaryPool}; } public List getStoragePoolAllocators() { diff --git a/engine/storage/configdrive/src/org/apache/cloudstack/storage/configdrive/ConfigDrive.java b/engine/storage/configdrive/src/org/apache/cloudstack/storage/configdrive/ConfigDrive.java index 156e2ffaf4d9..ec461991537d 100644 --- a/engine/storage/configdrive/src/org/apache/cloudstack/storage/configdrive/ConfigDrive.java +++ b/engine/storage/configdrive/src/org/apache/cloudstack/storage/configdrive/ConfigDrive.java @@ -25,4 +25,12 @@ public class ConfigDrive { public static final String cloudStackConfigDriveName = "/cloudstack/"; public static final String openStackConfigDriveName = "/openstack/latest/"; + /** + * This is the path to iso file relative to mount point + * @return config drive iso file path + */ + public static String createConfigDrivePath(final String instanceName) { + return ConfigDrive.CONFIGDRIVEDIR + "/" + instanceName + "/" + ConfigDrive.CONFIGDRIVEFILENAME; + } + } diff --git a/engine/storage/configdrive/src/org/apache/cloudstack/storage/configdrive/ConfigDriveBuilder.java b/engine/storage/configdrive/src/org/apache/cloudstack/storage/configdrive/ConfigDriveBuilder.java index 41be3b0265d0..d847aa1d1d76 100644 --- a/engine/storage/configdrive/src/org/apache/cloudstack/storage/configdrive/ConfigDriveBuilder.java +++ b/engine/storage/configdrive/src/org/apache/cloudstack/storage/configdrive/ConfigDriveBuilder.java @@ -155,7 +155,12 @@ public static String buildConfigDrive(final List vmData, final String LOG.warn(errMsg); throw new CloudRuntimeException(errMsg); } - return fileToBase64String(new File(tmpIsoStore.getAbsolutePath())); + File tmpIsoFile = new File(tmpIsoStore.getAbsolutePath()); + // Max allowed file size of config drive is 64MB: https://docs.openstack.org/project-install-guide/baremetal/draft/configdrive.html + if (tmpIsoFile.length() > (64L * 1024L * 1024L)) { + throw new CloudRuntimeException("Config drive file exceeds maximum allowed size of 64MB"); + } + return fileToBase64String(tmpIsoFile); } catch (IOException e) { throw new CloudRuntimeException("Failed due to", e); } finally { diff --git a/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtHandleConfigDriveCommandWrapper.java b/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtHandleConfigDriveCommandWrapper.java new file mode 100644 index 000000000000..2c2ee2d9a2a3 --- /dev/null +++ b/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtHandleConfigDriveCommandWrapper.java @@ -0,0 +1,79 @@ +// 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.hypervisor.kvm.resource.wrapper; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +import org.apache.cloudstack.storage.configdrive.ConfigDriveBuilder; +import org.apache.commons.io.FileUtils; +import org.apache.log4j.Logger; + +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.HandleConfigDriveIsoCommand; +import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource; +import com.cloud.hypervisor.kvm.storage.KVMStoragePool; +import com.cloud.hypervisor.kvm.storage.KVMStoragePoolManager; +import com.cloud.resource.CommandWrapper; +import com.cloud.resource.ResourceWrapper; +import com.cloud.storage.Storage; + +@ResourceWrapper(handles = HandleConfigDriveIsoCommand.class) +public final class LibvirtHandleConfigDriveCommandWrapper extends CommandWrapper { + private static final Logger LOG = Logger.getLogger(LibvirtHandleConfigDriveCommandWrapper.class); + + @Override + public Answer execute(final HandleConfigDriveIsoCommand command, final LibvirtComputingResource libvirtComputingResource) { + final KVMStoragePoolManager storagePoolMgr = libvirtComputingResource.getStoragePoolMgr(); + final KVMStoragePool pool = storagePoolMgr.getStoragePool(Storage.StoragePoolType.NetworkFilesystem, command.getDestStore().getUuid()); + if (pool == null) { + return new Answer(command, false, "Pool not found, config drive for KVM is only supported for NFS"); + } + + final String mountPoint = pool.getLocalPath(); + final Path isoPath = Paths.get(mountPoint, command.getIsoFile()); + final File isoFile = new File(mountPoint, command.getIsoFile()); + if (command.isCreate()) { + LOG.debug("Creating config drive: " + command.getIsoFile()); + if (command.getIsoData() == null) { + return new Answer(command, false, "Invalid config drive ISO data received"); + } + if(isoFile.exists()) { + LOG.debug("An old config drive iso already exists"); + } + try { + Files.createDirectories(isoPath.getParent()); + ConfigDriveBuilder.base64StringToFile(command.getIsoData(), mountPoint, command.getIsoFile()); + } catch (IOException e) { + return new Answer(command, false, "Failed due to exception: " + e.getMessage()); + } + } else { + try { + FileUtils.deleteDirectory(isoPath.getParent().toFile()); + } catch (IOException e) { + LOG.warn("Failed to delete config drive: " + isoPath.toAbsolutePath().toString()); + return new Answer(command, false, "Failed due to exception: " + e.getMessage()); + } + } + + return new Answer(command, true, "Successfully saved config drive at primary storage"); + } +} \ No newline at end of file diff --git a/server/src/com/cloud/network/element/ConfigDriveNetworkElement.java b/server/src/com/cloud/network/element/ConfigDriveNetworkElement.java index 9508431e56bb..a9956f250575 100644 --- a/server/src/com/cloud/network/element/ConfigDriveNetworkElement.java +++ b/server/src/com/cloud/network/element/ConfigDriveNetworkElement.java @@ -32,6 +32,7 @@ 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; @@ -42,7 +43,6 @@ import com.cloud.exception.InsufficientCapacityException; import com.cloud.exception.ResourceUnavailableException; import com.cloud.exception.UnsupportedServiceException; -import com.cloud.host.Host; import com.cloud.host.dao.HostDao; import com.cloud.network.Network; import com.cloud.network.Network.Capability; @@ -54,10 +54,13 @@ 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.dao.GuestOSCategoryDao; import com.cloud.storage.dao.GuestOSDao; +import com.cloud.storage.dao.VolumeDao; import com.cloud.utils.component.AdapterBase; import com.cloud.utils.exception.CloudRuntimeException; import com.cloud.utils.fsm.StateListener; @@ -98,8 +101,12 @@ public class ConfigDriveNetworkElement extends AdapterBase implements NetworkEle @Inject GuestOSDao _guestOSDao; @Inject + VolumeDao _volumeDao; + @Inject HostDao _hostDao; @Inject + AgentManager agentManager; + @Inject DataStoreManager _dataStoreMgr; @Inject EndPointSelector _ep; @@ -134,20 +141,12 @@ public boolean release(Network network, NicProfile nic, VirtualMachineProfile vm return true; } - // Remove from secondary storage - DataStore secondaryStore = _dataStoreMgr.getImageStore(network.getDataCenterId()); - - String isoFile = "/" + ConfigDrive.CONFIGDRIVEDIR + "/" + vm.getInstanceName()+ "/" + ConfigDrive.CONFIGDRIVEFILENAME; - HandleConfigDriveIsoCommand deleteCommand = new HandleConfigDriveIsoCommand(vm.getVmData(), - vm.getConfigDriveLabel(), secondaryStore.getTO(), isoFile, false); - // Delete the ISO on the secondary store - EndPoint endpoint = _ep.select(secondaryStore); - if (endpoint == null) { - LOG.error(String.format("Secondary store: %s not available", secondaryStore.getName())); + try { + return deleteConfigDriveIso(vm.getVirtualMachine()); + } catch (ResourceUnavailableException e) { + LOG.error("Failed to delete config drive due to: ", e); return false; } - Answer answer = endpoint.sendMessage(deleteCommand); - return answer.getResult(); } @Override @@ -201,7 +200,7 @@ public boolean addPasswordAndUserdata(Network network, NicProfile nic, VirtualMa throws ConcurrentOperationException, InsufficientCapacityException, ResourceUnavailableException { return (canHandle(network.getTrafficType()) && configureConfigDriveData(profile, nic)) - && createConfigDriveIso(network, profile, dest.getHost()); + && createConfigDriveIso(profile, dest); } @Override @@ -248,30 +247,22 @@ public boolean preStateTransitionEvent(VirtualMachine.State oldState, VirtualMac } @Override - public boolean postStateTransitionEvent(StateMachine2.Transition transition, VirtualMachine vo, boolean status, Object opaque) { + public boolean postStateTransitionEvent(StateMachine2.Transition transition, VirtualMachine vm, boolean status, Object opaque) { if (transition.getToState().equals(VirtualMachine.State.Expunging) && transition.getEvent().equals(VirtualMachine.Event.ExpungeOperation)) { - Nic nic = _networkModel.getDefaultNic(vo.getId()); + Nic nic = _networkModel.getDefaultNic(vm.getId()); + if (nic == null) { + return true; + } try { - if (nic != null) { - final Network network = _networkMgr.getNetwork(nic.getNetworkId()); - final UserDataServiceProvider userDataUpdateProvider = _networkModel.getUserDataUpdateProvider(network); - final Provider provider = userDataUpdateProvider.getProvider(); - if (provider.equals(Provider.ConfigDrive)) { - // Delete config drive ISO on destroy - DataStore secondaryStore = _dataStoreMgr.getImageStore(vo.getDataCenterId()); - String isoFile = "/" + ConfigDrive.CONFIGDRIVEDIR + "/" + vo.getInstanceName() + "/" + ConfigDrive.CONFIGDRIVEFILENAME; - HandleConfigDriveIsoCommand deleteCommand = new HandleConfigDriveIsoCommand(null, - null, secondaryStore.getTO(), isoFile, false); - EndPoint endpoint = _ep.select(secondaryStore); - if (endpoint == null) { - LOG.error(String.format("Secondary store: %s not available", secondaryStore.getName())); - return false; - } - Answer answer = endpoint.sendMessage(deleteCommand); - if (!answer.getResult()) { - LOG.error(String.format("Update ISO failed, details: %s", answer.getDetails())); - return false; - } + 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) {} @@ -283,8 +274,11 @@ public boolean postStateTransitionEvent(StateMachine2.Transition commandCaptor = ArgumentCaptor.forClass(HandleConfigDriveIsoCommand.class); - verify(endpoint, times(1)).sendMessage(commandCaptor.capture()); + verify(agentManager, times(1)).easySend(anyLong(), commandCaptor.capture()); HandleConfigDriveIsoCommand deleteCommand = commandCaptor.getValue(); assertThat(deleteCommand.isCreate(), is(false)); @@ -235,6 +231,7 @@ public void testGetCapabilities () { assertThat(_configDrivesNetworkElement.getCapabilities(), hasEntry(Network.Service.UserData, null)); } + /* @Test public void testAddPasswordAndUserdata() throws InsufficientCapacityException, ResourceUnavailableException { List actualVmData = getVmData(); @@ -300,7 +297,9 @@ public void testAddPasswordAndUserdataUuid() throws InsufficientCapacityExceptio new String[]{PASSWORD, "vm_password", PASSWORD} )); } + */ + /* private List getVmData() throws InsufficientCapacityException, ResourceUnavailableException { final Answer answer = mock(Answer.class); final UserVmDetailVO userVmDetailVO = mock(UserVmDetailVO.class); @@ -323,4 +322,5 @@ private List getVmData() throws InsufficientCapacityException, Resourc HandleConfigDriveIsoCommand result = commandCaptor.getValue(); return result.getVmData(); } + */ } From 0b7aac5cf45a48790f7dabd6c7d572a898db3f90 Mon Sep 17 00:00:00 2001 From: Rohit Yadav Date: Thu, 17 May 2018 21:01:59 +0530 Subject: [PATCH 07/10] minor fixes, don't log iso data etc. Signed-off-by: Rohit Yadav --- .../api/HandleConfigDriveIsoCommand.java | 7 ++++--- ...ibvirtHandleConfigDriveCommandWrapper.java | 4 ++-- .../element/ConfigDriveNetworkElement.java | 21 +++++++++++++------ 3 files changed, 21 insertions(+), 11 deletions(-) diff --git a/core/src/com/cloud/agent/api/HandleConfigDriveIsoCommand.java b/core/src/com/cloud/agent/api/HandleConfigDriveIsoCommand.java index 23858f3c61cc..3d8d8f7e10e3 100644 --- a/core/src/com/cloud/agent/api/HandleConfigDriveIsoCommand.java +++ b/core/src/com/cloud/agent/api/HandleConfigDriveIsoCommand.java @@ -24,9 +24,10 @@ public class HandleConfigDriveIsoCommand extends Command { @LogLevel(LogLevel.Log4jLevel.Off) - String isoData; - String isoFile; - boolean create = false; + private String isoData; + + private String isoFile; + private boolean create = false; private DataStoreTO destStore; public HandleConfigDriveIsoCommand(String isoFile, String isoData, DataStoreTO destStore, boolean create) { diff --git a/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtHandleConfigDriveCommandWrapper.java b/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtHandleConfigDriveCommandWrapper.java index 2c2ee2d9a2a3..5e4ef485123b 100644 --- a/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtHandleConfigDriveCommandWrapper.java +++ b/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtHandleConfigDriveCommandWrapper.java @@ -56,7 +56,7 @@ public Answer execute(final HandleConfigDriveIsoCommand command, final LibvirtCo if (command.getIsoData() == null) { return new Answer(command, false, "Invalid config drive ISO data received"); } - if(isoFile.exists()) { + if (isoFile.exists()) { LOG.debug("An old config drive iso already exists"); } try { @@ -74,6 +74,6 @@ public Answer execute(final HandleConfigDriveIsoCommand command, final LibvirtCo } } - return new Answer(command, true, "Successfully saved config drive at primary storage"); + return new Answer(command); } } \ No newline at end of file diff --git a/server/src/com/cloud/network/element/ConfigDriveNetworkElement.java b/server/src/com/cloud/network/element/ConfigDriveNetworkElement.java index a9956f250575..d9fc13f393da 100644 --- a/server/src/com/cloud/network/element/ConfigDriveNetworkElement.java +++ b/server/src/com/cloud/network/element/ConfigDriveNetworkElement.java @@ -58,6 +58,7 @@ 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; @@ -276,7 +277,7 @@ public boolean prepareMigration(NicProfile nic, Network network, VirtualMachineP LOG.trace(String.format("[prepareMigration] for vm: %s", vm.getInstanceName())); DataStore dataStore = _dataStoreMgr.getImageStore(network.getDataCenterId()); if (VirtualMachineManager.VmConfigDriveOnPrimaryPool.value()) { - dataStore = findDataStore(dest); + dataStore = findDataStore(vm, dest); } addConfigDriveDisk(vm, dataStore); return false; @@ -292,7 +293,7 @@ public void rollbackMigration(NicProfile nic, Network network, VirtualMachinePro public void commitMigration(NicProfile nic, Network network, VirtualMachineProfile vm, ReservationContext src, ReservationContext dst) { } - private DataStore findDataStore(DeployDestination dest) { + private DataStore findDataStore(VirtualMachineProfile profile, DeployDestination dest) { DataStore dataStore = null; if (VirtualMachineManager.VmConfigDriveOnPrimaryPool.value()) { for (final Volume volume : dest.getStorageForDisks().keySet()) { @@ -302,6 +303,12 @@ private DataStore findDataStore(DeployDestination dest) { break; } } + if (dataStore == null) { + final List 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()); } @@ -331,7 +338,7 @@ private Long findAgentId(VirtualMachineProfile profile, DeployDestination dest, } private boolean createConfigDriveIso(VirtualMachineProfile profile, DeployDestination dest) throws ResourceUnavailableException { - final DataStore dataStore = findDataStore(dest); + 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", @@ -358,9 +365,11 @@ private boolean deleteConfigDriveIso(final VirtualMachine vm) throws ResourceUna Long agentId = findAgentIdForImageStore(dataStore); if (VirtualMachineManager.VmConfigDriveOnPrimaryPool.value()) { - final Volume volume = _volumeDao.findByInstanceAndType(vm.getId(), Volume.Type.ROOT).get(0); - dataStore = _dataStoreMgr.getDataStore(volume.getPoolId(), DataStoreRole.Primary); - agentId = vm.getHostId(); + List 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) { From 8bb8e0a69e94a16069bafa7abe1e899b22defd5f Mon Sep 17 00:00:00 2001 From: Rohit Yadav Date: Thu, 17 May 2018 21:10:54 +0530 Subject: [PATCH 08/10] remove deps Signed-off-by: Rohit Yadav --- packaging/centos63/cloud.spec | 1 - packaging/centos7/cloud.spec | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/packaging/centos63/cloud.spec b/packaging/centos63/cloud.spec index 9655ba72d72c..87d2c1ec5780 100644 --- a/packaging/centos63/cloud.spec +++ b/packaging/centos63/cloud.spec @@ -130,7 +130,6 @@ Requires: perl Requires: libvirt-python Requires: qemu-img Requires: qemu-kvm -Requires: genisoimage Provides: cloud-agent Obsoletes: cloud-agent < 4.1.0 Obsoletes: cloud-agent-libs < 4.1.0 diff --git a/packaging/centos7/cloud.spec b/packaging/centos7/cloud.spec index 0a8292e1012b..96b679c89550 100644 --- a/packaging/centos7/cloud.spec +++ b/packaging/centos7/cloud.spec @@ -75,7 +75,7 @@ Requires: sudo Requires: /sbin/service Requires: /sbin/chkconfig Requires: /usr/bin/ssh-keygen -Requires: mkisofs +Requires: genisoimage Requires: mysql-connector-python Requires: ipmitool Requires: %{name}-common = %{_ver} @@ -111,7 +111,6 @@ Requires: perl Requires: libvirt-python Requires: qemu-img Requires: qemu-kvm -Requires: genisoimage Provides: cloud-agent Group: System Environment/Libraries %description agent From 40ace72591e35d70b2c065b5fcf59711cdbe575c Mon Sep 17 00:00:00 2001 From: Rohit Yadav Date: Fri, 18 May 2018 16:26:57 +0530 Subject: [PATCH 09/10] fix vm migration with config drive on primary storage Signed-off-by: Rohit Yadav --- .../resource/LibvirtComputingResource.java | 5 ++--- .../kvm/storage/KVMStoragePoolManager.java | 2 +- .../element/ConfigDriveNetworkElement.java | 19 +++++++++---------- 3 files changed, 12 insertions(+), 14 deletions(-) diff --git a/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java b/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java index fc5e5395b87f..424280cc66ca 100644 --- a/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java +++ b/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java @@ -2200,9 +2200,8 @@ public String getVolumePath(final Connect conn, final DiskTO volume) throws Libv final DataTO data = volume.getData(); final DataStoreTO store = data.getDataStore(); - if (volume.getType() == Volume.Type.ISO && data.getPath() != null) { - final NfsTO nfsStore = (NfsTO)store; - final String isoPath = nfsStore.getUrl() + File.separator + data.getPath(); + if (volume.getType() == Volume.Type.ISO && data.getPath() != null && (store instanceof NfsTO || store instanceof PrimaryDataStoreTO)) { + final String isoPath = store.getUrl().split("\\?")[0] + File.separator + data.getPath(); final int index = isoPath.lastIndexOf("/"); final String path = isoPath.substring(0, index); final String name = isoPath.substring(index + 1); diff --git a/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/storage/KVMStoragePoolManager.java b/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/storage/KVMStoragePoolManager.java index c61350806714..4d0523c9a063 100644 --- a/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/storage/KVMStoragePoolManager.java +++ b/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/storage/KVMStoragePoolManager.java @@ -265,7 +265,7 @@ public KVMStoragePool getStoragePoolByURI(String uri) { String uuid = null; String sourceHost = ""; StoragePoolType protocol = null; - if (storageUri.getScheme().equalsIgnoreCase("nfs")) { + if (storageUri.getScheme().equalsIgnoreCase("nfs") || storageUri.getScheme().equalsIgnoreCase("NetworkFilesystem")) { sourcePath = storageUri.getPath(); sourcePath = sourcePath.replace("//", "/"); sourceHost = storageUri.getHost(); diff --git a/server/src/com/cloud/network/element/ConfigDriveNetworkElement.java b/server/src/com/cloud/network/element/ConfigDriveNetworkElement.java index d9fc13f393da..dab4a14311c6 100644 --- a/server/src/com/cloud/network/element/ConfigDriveNetworkElement.java +++ b/server/src/com/cloud/network/element/ConfigDriveNetworkElement.java @@ -275,10 +275,7 @@ public boolean postStateTransitionEvent(StateMachine2.Transition Date: Fri, 18 May 2018 17:14:17 +0530 Subject: [PATCH 10/10] cleanup and fix tests Signed-off-by: Rohit Yadav --- .../configdrive/ConfigDriveBuilderTest.java | 47 +++++++-- .../ConfigDriveNetworkElementTest.java | 99 +++++-------------- 2 files changed, 61 insertions(+), 85 deletions(-) diff --git a/engine/storage/configdrive/test/org/apache/cloudstack/storage/configdrive/ConfigDriveBuilderTest.java b/engine/storage/configdrive/test/org/apache/cloudstack/storage/configdrive/ConfigDriveBuilderTest.java index b420a94bd731..50a4384d5c8d 100644 --- a/engine/storage/configdrive/test/org/apache/cloudstack/storage/configdrive/ConfigDriveBuilderTest.java +++ b/engine/storage/configdrive/test/org/apache/cloudstack/storage/configdrive/ConfigDriveBuilderTest.java @@ -1,24 +1,42 @@ +// 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 org.apache.cloudstack.storage.configdrive; +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.Arrays; import java.util.List; -import org.junit.After; -import org.junit.Before; +import org.apache.commons.io.FileUtils; +import org.junit.Assert; import org.junit.Test; public class ConfigDriveBuilderTest { - @Before - public void setUp() throws Exception { - } - @After - public void tearDown() throws Exception { + @Test + public void testConfigDriveIsoPath() throws IOException { + Assert.assertEquals(ConfigDrive.createConfigDrivePath("i-x-y"), "configdrive/i-x-y/configdrive.iso"); } @Test - public void testConfigDriveBuild() { - + public void testConfigDriveBuild() throws IOException { List actualVmData = Arrays.asList( new String[]{"userdata", "user_data", "c29tZSB1c2VyIGRhdGE="}, new String[]{"metadata", "service-offering", "offering"}, @@ -33,6 +51,15 @@ public void testConfigDriveBuild() { new String[]{"metadata", "cloud-identifier", String.format("CloudStack-{%s}", "uuid")}, new String[]{"password", "vm_password", "password123"} ); - String encodedString = ConfigDriveBuilder.buildConfigDrive(actualVmData, "i-x-y.iso", "config-2"); + + final Path tempDir = Files.createTempDirectory(ConfigDrive.CONFIGDRIVEDIR); + final String isoData = ConfigDriveBuilder.buildConfigDrive(actualVmData, "i-x-y.iso", "config-2"); + final File isoFile = ConfigDriveBuilder.base64StringToFile(isoData, tempDir.toAbsolutePath().toString(), ConfigDrive.CONFIGDRIVEFILENAME); + + Assert.assertTrue(isoFile.exists()); + Assert.assertTrue(isoFile.isFile()); + Assert.assertTrue(isoFile.length() > 0L); + + FileUtils.deleteDirectory(tempDir.toFile()); } } \ No newline at end of file diff --git a/server/test/com/cloud/network/element/ConfigDriveNetworkElementTest.java b/server/test/com/cloud/network/element/ConfigDriveNetworkElementTest.java index 044f69edb950..e964999ee9d6 100644 --- a/server/test/com/cloud/network/element/ConfigDriveNetworkElementTest.java +++ b/server/test/com/cloud/network/element/ConfigDriveNetworkElementTest.java @@ -23,6 +23,7 @@ import static org.junit.Assert.assertTrue; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyLong; +import static org.mockito.Matchers.anyString; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; @@ -32,12 +33,14 @@ import java.lang.reflect.Field; import java.util.Arrays; import java.util.List; +import java.util.Map; 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.framework.config.dao.ConfigurationDao; +import org.apache.cloudstack.storage.configdrive.ConfigDrive; import org.junit.Test; import org.mockito.ArgumentCaptor; import org.mockito.InjectMocks; @@ -48,6 +51,7 @@ import com.cloud.agent.AgentManager; import com.cloud.agent.api.Answer; import com.cloud.agent.api.HandleConfigDriveIsoCommand; +import com.cloud.dc.DataCenter; import com.cloud.dc.DataCenterVO; import com.cloud.dc.dao.DataCenterDao; import com.cloud.deploy.DeployDestination; @@ -73,8 +77,10 @@ import com.cloud.utils.fsm.NoTransitionException; import com.cloud.utils.fsm.StateListener; import com.cloud.utils.fsm.StateMachine2; +import com.cloud.utils.net.Ip; import com.cloud.vm.Nic; import com.cloud.vm.NicProfile; +import com.cloud.vm.UserVmDetailVO; import com.cloud.vm.UserVmVO; import com.cloud.vm.VirtualMachine; import com.cloud.vm.VirtualMachineProfile; @@ -82,6 +88,7 @@ import com.cloud.vm.dao.UserVmDao; import com.cloud.vm.dao.UserVmDetailsDao; import com.cloud.vm.dao.VMInstanceDao; +import com.google.common.collect.Maps; public class ConfigDriveNetworkElementTest { @@ -99,6 +106,7 @@ public class ConfigDriveNetworkElementTest { private final long SOID = 31L; private final long HOSTID = NETWORK_ID; + @Mock private DataCenter dataCenter; @Mock private ConfigurationDao _configDao; @Mock private DataCenterDao _dcDao; @Mock private DataStoreManager _dataStoreMgr; @@ -139,6 +147,7 @@ public void setUp() throws NoSuchFieldException, IllegalAccessException { _configDrivesNetworkElement._networkModel = _networkModel; when(_dataStoreMgr.getImageStore(DATACENTERID)).thenReturn(dataStore); + when(_ep.select(dataStore)).thenReturn(endpoint); when(_vmDao.findById(VMID)).thenReturn(virtualMachine); when(_dcDao.findById(DATACENTERID)).thenReturn(dataCenterVO); @@ -161,7 +170,9 @@ public void setUp() throws NoSuchFieldException, IllegalAccessException { when(virtualMachine.getInstanceName()).thenReturn(VMINSTANCENAME); when(virtualMachine.getUserData()).thenReturn(VMUSERDATA); when(virtualMachine.getHostName()).thenReturn(VMHOSTNAME); + when(dataCenter.getId()).thenReturn(DATACENTERID); when(deployDestination.getHost()).thenReturn(hostVO); + when(deployDestination.getDataCenter()).thenReturn(dataCenter); when(hostVO.getId()).thenReturn(HOSTID); when(nic.isDefaultNic()).thenReturn(true); when(nic.getNetworkId()).thenReturn(NETWORK_ID); @@ -220,7 +231,7 @@ public void testExpunge() throws NoTransitionException, NoSuchFieldException, Il @Test public void testRelease() { final Answer answer = mock(Answer.class); - when(endpoint.sendMessage(any(HandleConfigDriveIsoCommand.class))).thenReturn(answer); + when(agentManager.easySend(anyLong(), any(HandleConfigDriveIsoCommand.class))).thenReturn(answer); when(answer.getResult()).thenReturn(true); VirtualMachineProfile profile = new VirtualMachineProfileImpl(virtualMachine, null, serviceOfferingVO, null, null); assertTrue(_configDrivesNetworkElement.release(network, nicp, profile, null)); @@ -231,85 +242,21 @@ public void testGetCapabilities () { assertThat(_configDrivesNetworkElement.getCapabilities(), hasEntry(Network.Service.UserData, null)); } - /* - @Test - public void testAddPasswordAndUserdata() throws InsufficientCapacityException, ResourceUnavailableException { - List actualVmData = getVmData(); - - assertThat(actualVmData, containsInAnyOrder( - new String[]{"userdata", "user_data", VMUSERDATA}, - new String[]{"metadata", "service-offering", VMOFFERING}, - new String[]{"metadata", "availability-zone", ZONENAME}, - new String[]{"metadata", "local-hostname", VMHOSTNAME}, - new String[]{"metadata", "local-ipv4", "192.168.111.111"}, - new String[]{"metadata", "public-hostname", null}, - new String[]{"metadata", "public-ipv4", "192.168.111.111"}, - new String[]{"metadata", "vm-id", String.valueOf(VMID)}, - new String[]{"metadata", "instance-id", VMINSTANCENAME}, - new String[]{"metadata", "public-keys", PUBLIC_KEY}, - new String[]{"metadata", "cloud-identifier", String.format("CloudStack-{%s}", CLOUD_ID)}, - new String[]{PASSWORD, "vm_password", PASSWORD} - )); - } - - @Test - public void testAddPasswordAndUserdataStaticNat() throws InsufficientCapacityException, ResourceUnavailableException { - when(_ipAddressDao.findByAssociatedVmId(VMID)).thenReturn(publicIp); - when(publicIp.getAddress()).thenReturn(new Ip("7.7.7.7")); - - List actualVmData = getVmData(); - - assertThat(actualVmData, containsInAnyOrder( - new String[]{"userdata", "user_data", VMUSERDATA}, - new String[]{"metadata", "service-offering", VMOFFERING}, - new String[]{"metadata", "availability-zone", ZONENAME}, - new String[]{"metadata", "local-hostname", VMHOSTNAME}, - new String[]{"metadata", "local-ipv4", "192.168.111.111"}, - new String[]{"metadata", "public-hostname", "7.7.7.7"}, - new String[]{"metadata", "public-ipv4", "7.7.7.7"}, - new String[]{"metadata", "vm-id", String.valueOf(VMID)}, - new String[]{"metadata", "instance-id", VMINSTANCENAME}, - new String[]{"metadata", "public-keys", PUBLIC_KEY}, - new String[]{"metadata", "cloud-identifier", String.format("CloudStack-{%s}", CLOUD_ID)}, - new String[]{PASSWORD, "vm_password", PASSWORD} - )); - } - - @Test - public void testAddPasswordAndUserdataUuid() throws InsufficientCapacityException, ResourceUnavailableException { - when(virtualMachine.getUuid()).thenReturn("vm-uuid"); - - List actualVmData = getVmData(); - - assertThat(actualVmData, containsInAnyOrder( - new String[]{"userdata", "user_data", VMUSERDATA}, - new String[]{"metadata", "service-offering", VMOFFERING}, - new String[]{"metadata", "availability-zone", ZONENAME}, - new String[]{"metadata", "local-hostname", VMHOSTNAME}, - new String[]{"metadata", "local-ipv4", "192.168.111.111"}, - new String[]{"metadata", "public-hostname", null}, - new String[]{"metadata", "public-ipv4", "192.168.111.111"}, - new String[]{"metadata", "vm-id", "vm-uuid"}, - new String[]{"metadata", "instance-id", "vm-uuid"}, - new String[]{"metadata", "public-keys", PUBLIC_KEY}, - new String[]{"metadata", "cloud-identifier", String.format("CloudStack-{%s}", CLOUD_ID)}, - new String[]{PASSWORD, "vm_password", PASSWORD} - )); - } - */ - - /* - private List getVmData() throws InsufficientCapacityException, ResourceUnavailableException { + public void testAddPasswordAndUserData() throws Exception { final Answer answer = mock(Answer.class); final UserVmDetailVO userVmDetailVO = mock(UserVmDetailVO.class); - when(endpoint.sendMessage(any(HandleConfigDriveIsoCommand.class))).thenReturn(answer); + when(agentManager.easySend(anyLong(), any(HandleConfigDriveIsoCommand.class))).thenReturn(answer); when(answer.getResult()).thenReturn(true); when(network.getTrafficType()).thenReturn(Networks.TrafficType.Guest); when(virtualMachine.getState()).thenReturn(VirtualMachine.State.Stopped); + when(virtualMachine.getUuid()).thenReturn("vm-uuid"); when(userVmDetailVO.getValue()).thenReturn(PUBLIC_KEY); when(nicp.getIPv4Address()).thenReturn("192.168.111.111"); when(_userVmDetailsDao.findDetail(anyLong(), anyString())).thenReturn(userVmDetailVO); + when(_ipAddressDao.findByAssociatedVmId(VMID)).thenReturn(publicIp); + when(publicIp.getAddress()).thenReturn(new Ip("7.7.7.7")); + Map parms = Maps.newHashMap(); parms.put(VirtualMachineProfile.Param.VmPassword, PASSWORD); parms.put(VirtualMachineProfile.Param.VmSshPubKey, PUBLIC_KEY); @@ -318,9 +265,11 @@ private List getVmData() throws InsufficientCapacityException, Resourc network, nicp, profile, deployDestination, null)); ArgumentCaptor commandCaptor = ArgumentCaptor.forClass(HandleConfigDriveIsoCommand.class); - verify(endpoint, times(1)).sendMessage(commandCaptor.capture()); - HandleConfigDriveIsoCommand result = commandCaptor.getValue(); - return result.getVmData(); + verify(agentManager, times(1)).easySend(anyLong(), commandCaptor.capture()); + HandleConfigDriveIsoCommand createCommand = commandCaptor.getValue(); + + assertTrue(createCommand.isCreate()); + assertTrue(createCommand.getIsoData().length() > 0); + assertTrue(createCommand.getIsoFile().equals(ConfigDrive.createConfigDrivePath(profile.getInstanceName()))); } - */ }