From b60daf7142c337d4a9a9401de6f0a59454be0662 Mon Sep 17 00:00:00 2001 From: ustcweizhou Date: Mon, 29 Apr 2019 08:51:08 +0200 Subject: [PATCH 1/3] server: Fix exception while update domain resource count (#3204) --- .../com/cloud/configuration/dao/ResourceCountDaoImpl.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/engine/schema/src/com/cloud/configuration/dao/ResourceCountDaoImpl.java b/engine/schema/src/com/cloud/configuration/dao/ResourceCountDaoImpl.java index 1de2b65aed1..82903104f90 100644 --- a/engine/schema/src/com/cloud/configuration/dao/ResourceCountDaoImpl.java +++ b/engine/schema/src/com/cloud/configuration/dao/ResourceCountDaoImpl.java @@ -276,17 +276,18 @@ public class ResourceCountDaoImpl extends GenericDaoBase } private long executeSqlCountComputingResourcesForAccount(long accountId, String sqlCountComputingResourcesAllocatedToAccount) { - try (TransactionLegacy tx = TransactionLegacy.currentTxn()) { + TransactionLegacy tx = TransactionLegacy.currentTxn(); + try { PreparedStatement pstmt = tx.prepareAutoCloseStatement(sqlCountComputingResourcesAllocatedToAccount); pstmt.setLong(1, accountId); ResultSet rs = pstmt.executeQuery(); if (!rs.next()) { - throw new CloudRuntimeException(String.format("An unexpected case happened while counting allocated computing resources for account: " + accountId)); + return 0L; } return rs.getLong("total"); } catch (SQLException e) { throw new CloudRuntimeException(e); } } -} \ No newline at end of file +} From f9b61bc737fbd51d8a55b3e94f404703bd3528e4 Mon Sep 17 00:00:00 2001 From: Anurag Awasthi <43956255+anuragaw@users.noreply.github.com> Date: Fri, 10 May 2019 23:40:38 +0530 Subject: [PATCH 2/3] orchestration: Allow VM that has never started to have volumes attached (#3276) With this patch b766bf7 we started tracking disks in attaching state so that other attach request can fail gracefully. However this missed the case where disks were in allocated state but attach was requested. For the use case where users want to attach disk in allocated state but not ready, we need to have allocated-attaching transition as well. We must take care of returning to the original state - allocated or ready - when attach request has completed. For the use case of unstarted vm's the disk must proceed as follows - "Allocated" -> Attaching -> Allocated. When VM is started, the disk is "created" and pool is assigned. For the use case of started VMs it's more trivial and disk proceeds as follows - Ready -> Attaching -> Ready. Test this by creating a VM with "startvm=false", create a disk and try attaching it in allocated state. It would give an exception on latest 4.11 but will be fixed on this patch. Signed-off-by: Rohit Yadav --- api/src/com/cloud/storage/Volume.java | 4 +- .../storage/volume/VolumeServiceImpl.java | 3 + .../cloud/storage/VolumeApiServiceImpl.java | 49 +++++----- test/integration/smoke/test_volumes.py | 94 +++++++++++++++---- 4 files changed, 108 insertions(+), 42 deletions(-) diff --git a/api/src/com/cloud/storage/Volume.java b/api/src/com/cloud/storage/Volume.java index 133a59df8a6..0e86ac01638 100644 --- a/api/src/com/cloud/storage/Volume.java +++ b/api/src/com/cloud/storage/Volume.java @@ -52,7 +52,7 @@ public interface Volume extends ControlledEntity, Identity, InternalIdentity, Ba UploadInProgress("Volume upload is in progress"), UploadError("Volume upload encountered some error"), UploadAbandoned("Volume upload is abandoned since the upload was never initiated within a specificed time"), - Attaching("The volume is attaching to a VM"); + Attaching("The volume is attaching to a VM from Ready state."); String _description; @@ -72,6 +72,8 @@ public interface Volume extends ControlledEntity, Identity, InternalIdentity, Ba static { s_fsm.addTransition(new StateMachine2.Transition(Allocated, Event.CreateRequested, Creating, null)); s_fsm.addTransition(new StateMachine2.Transition(Allocated, Event.DestroyRequested, Destroy, null)); + s_fsm.addTransition(new StateMachine2.Transition(Allocated, Event.OperationFailed, Allocated, null)); + s_fsm.addTransition(new StateMachine2.Transition(Allocated, Event.OperationSucceeded, Allocated, null)); s_fsm.addTransition(new StateMachine2.Transition(Creating, Event.OperationRetry, Creating, null)); s_fsm.addTransition(new StateMachine2.Transition(Creating, Event.OperationFailed, Allocated, null)); s_fsm.addTransition(new StateMachine2.Transition(Creating, Event.OperationSucceeded, Ready, null)); diff --git a/engine/storage/volume/src/org/apache/cloudstack/storage/volume/VolumeServiceImpl.java b/engine/storage/volume/src/org/apache/cloudstack/storage/volume/VolumeServiceImpl.java index d2979f7415d..4abb190dcf2 100644 --- a/engine/storage/volume/src/org/apache/cloudstack/storage/volume/VolumeServiceImpl.java +++ b/engine/storage/volume/src/org/apache/cloudstack/storage/volume/VolumeServiceImpl.java @@ -217,6 +217,9 @@ public class VolumeServiceImpl implements VolumeService { @Override public void revokeAccess(DataObject dataObject, Host host, DataStore dataStore) { DataStoreDriver dataStoreDriver = dataStore != null ? dataStore.getDriver() : null; + if (dataStoreDriver == null) { + return; + } if (dataStoreDriver instanceof PrimaryDataStoreDriver) { ((PrimaryDataStoreDriver)dataStoreDriver).revokeAccess(dataObject, host, dataStore); diff --git a/server/src/com/cloud/storage/VolumeApiServiceImpl.java b/server/src/com/cloud/storage/VolumeApiServiceImpl.java index 372ab3423af..085c6995348 100644 --- a/server/src/com/cloud/storage/VolumeApiServiceImpl.java +++ b/server/src/com/cloud/storage/VolumeApiServiceImpl.java @@ -1888,6 +1888,10 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic } } + if (volumePool == null) { + sendCommand = false; + } + Answer answer = null; if (sendCommand) { @@ -1921,12 +1925,13 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic _volsDao.detachVolume(volume.getId()); // volume.getPoolId() should be null if the VM we are detaching the disk from has never been started before - DataStore dataStore = volume.getPoolId() != null ? dataStoreMgr.getDataStore(volume.getPoolId(), DataStoreRole.Primary) : null; - - volService.revokeAccess(volFactory.getVolume(volume.getId()), host, dataStore); - - handleTargetsForVMware(hostId, volumePool.getHostAddress(), volumePool.getPort(), volume.get_iScsiName()); - + if (volume.getPoolId() != null) { + DataStore dataStore = dataStoreMgr.getDataStore(volume.getPoolId(), DataStoreRole.Primary); + volService.revokeAccess(volFactory.getVolume(volume.getId()), host, dataStore); + } + if (volumePool != null && hostId != null) { + handleTargetsForVMware(hostId, volumePool.getHostAddress(), volumePool.getPort(), volume.get_iScsiName()); + } return _volsDao.findById(volumeId); } else { @@ -2634,24 +2639,25 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic return !storeForExistingStoreScope.isSameScope(storeForNewStoreScope); } - private synchronized void checkAndSetAttaching(Long volumeId, Long hostId) { + private synchronized void checkAndSetAttaching(Long volumeId) { VolumeInfo volumeToAttach = volFactory.getVolume(volumeId); if (volumeToAttach.isAttachedVM()) { throw new CloudRuntimeException("volume: " + volumeToAttach.getName() + " is already attached to a VM: " + volumeToAttach.getAttachedVmName()); } - if (volumeToAttach.getState().equals(Volume.State.Ready)) { - volumeToAttach.stateTransit(Volume.Event.AttachRequested); - } else { - String error = null; - if (hostId == null) { - error = "Please try attach operation after starting VM once"; - } else { - error = "Volume: " + volumeToAttach.getName() + " is in " + volumeToAttach.getState() + ". It should be in Ready state"; - } - s_logger.error(error); - throw new CloudRuntimeException(error); + + if (Volume.State.Allocated.equals(volumeToAttach.getState())) { + return; } + + if (Volume.State.Ready.equals(volumeToAttach.getState())) { + volumeToAttach.stateTransit(Volume.Event.AttachRequested); + return; + } + + final String error = "Volume: " + volumeToAttach.getName() + " is in " + volumeToAttach.getState() + ". It should be in Ready or Allocated state"; + s_logger.error(error); + throw new CloudRuntimeException(error); } private VolumeVO sendAttachVolumeCommand(UserVmVO vm, VolumeVO volumeToAttach, Long deviceId) { @@ -2684,7 +2690,7 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic // volumeToAttachStoragePool should be null if the VM we are attaching the disk to has never been started before DataStore dataStore = volumeToAttachStoragePool != null ? dataStoreMgr.getDataStore(volumeToAttachStoragePool.getId(), DataStoreRole.Primary) : null; - checkAndSetAttaching(volumeToAttach.getId(), hostId); + checkAndSetAttaching(volumeToAttach.getId()); boolean attached = false; try { @@ -2773,9 +2779,10 @@ public class VolumeApiServiceImpl extends ManagerBase implements VolumeApiServic volumeToAttach = _volsDao.findById(volumeToAttach.getId()); - if (vm.getHypervisorType() == HypervisorType.KVM && volumeToAttachStoragePool.isManaged() && volumeToAttach.getPath() == null) { + if (vm.getHypervisorType() == HypervisorType.KVM && + volumeToAttachStoragePool != null && volumeToAttachStoragePool.isManaged() && + volumeToAttach.getPath() == null && volumeToAttach.get_iScsiName() != null) { volumeToAttach.setPath(volumeToAttach.get_iScsiName()); - _volsDao.update(volumeToAttach.getId(), volumeToAttach); } } diff --git a/test/integration/smoke/test_volumes.py b/test/integration/smoke/test_volumes.py index d40c0fd065f..c71411a25e6 100644 --- a/test/integration/smoke/test_volumes.py +++ b/test/integration/smoke/test_volumes.py @@ -398,32 +398,32 @@ class TestVolumes(cloudstackTestCase): # 3. disk should be attached to instance successfully self.debug( - "Attaching volume (ID: %s) to VM (ID: %s)" % ( - self.volume.id, - self.virtual_machine.id - )) + "Attaching volume (ID: %s) to VM (ID: %s)" % ( + self.volume.id, + self.virtual_machine.id + )) self.virtual_machine.attach_volume(self.apiClient, self.volume) self.attached = True list_volume_response = Volume.list( - self.apiClient, - id=self.volume.id - ) + self.apiClient, + id=self.volume.id + ) self.assertEqual( - isinstance(list_volume_response, list), - True, - "Check list response returns a valid list" - ) + isinstance(list_volume_response, list), + True, + "Check list response returns a valid list" + ) self.assertNotEqual( - list_volume_response, - None, - "Check if volume exists in ListVolumes" - ) + list_volume_response, + None, + "Check if volume exists in ListVolumes" + ) volume = list_volume_response[0] self.assertNotEqual( - volume.virtualmachineid, - None, - "Check if volume state (attached) is reflected" - ) + volume.virtualmachineid, + None, + "Check if volume state (attached) is reflected" + ) try: #Format the attached volume to a known fs format_volume_to_ext3(self.virtual_machine.get_ssh_client()) @@ -431,7 +431,7 @@ class TestVolumes(cloudstackTestCase): except Exception as e: self.fail("SSH failed for VM: %s - %s" % - (self.virtual_machine.ipaddress, e)) + (self.virtual_machine.ipaddress, e)) return @attr(tags = ["advanced", "advancedns", "smoke", "basic"], required_hardware="false") @@ -857,6 +857,60 @@ class TestVolumes(cloudstackTestCase): self.assertTrue(hasattr(root_volume, "podname")) self.assertEqual(root_volume.podname, list_pods.name) + @attr(tags = ["advanced", "advancedns", "smoke", "basic"], required_hardware="true") + def test_11_attach_volume_with_unstarted_vm(self): + """Attach a created Volume to a unstarted VM + """ + # Validate the following + # 1. Attach to a vm in startvm=false state works and vm can be started afterwards. + # 2. shows list of volumes + # 3. "Attach Disk" pop-up box will display with list of instances + # 4. disk should be attached to instance successfully + + test_vm = VirtualMachine.create( + self.apiclient, + self.services, + accountid=self.account.name, + domainid=self.account.domainid, + serviceofferingid=self.service_offering.id, + mode=self.services["mode"], + startvm=False + ) + + self.debug( + "Attaching volume (ID: %s) to VM (ID: %s)" % ( + self.volume.id, + test_vm.id + )) + test_vm.attach_volume(self.apiClient, self.volume) + test_vm.start(self.apiClient) + + list_volume_response = Volume.list( + self.apiClient, + id=self.volume.id + ) + self.assertEqual( + isinstance(list_volume_response, list), + True, + "Check list response returns a valid list" + ) + self.assertNotEqual( + list_volume_response, + None, + "Check if volume exists in ListVolumes" + ) + volume = list_volume_response[0] + self.assertNotEqual( + volume.virtualmachineid, + None, + "Check if volume state (attached) is reflected" + ) + + test_vm.detach_volume(self.apiClient, self.volume) + self.cleanup.append(test_vm) + + return + def wait_for_attributes_and_return_root_vol(self): def checkVolumeResponse(): list_volume_response = Volume.list( From 9ff819da2c5a66d3c2fb6a5c71fb626c7126c5f6 Mon Sep 17 00:00:00 2001 From: Rohit Yadav Date: Fri, 10 May 2019 23:42:19 +0530 Subject: [PATCH 3/3] systemvm: new qemu-guest-agent based patching for KVM (#3278) This introduces a new patching script for patching systemvms on KVM using qemu-guest-agent that runs inside the systemvm on startup. This also removes the vport device which was previously used by the legacy patching script and instead uses the modern and new uniform guest agent vport for host-guest communication. Also updates the sytemvmtemplate build config to use the latest Debian 9.9.0 iso. Signed-off-by: Rohit Yadav --- .../resource/LibvirtComputingResource.java | 22 +-- .../wrapper/LibvirtStartCommandWrapper.java | 7 +- .../resource/LibvirtDomainXMLParserTest.java | 22 +-- .../kvm/resource/LibvirtVMDefTest.java | 6 +- scripts/installer/windows/client.wxs | 2 +- scripts/vm/hypervisor/kvm/patch.sh | 80 ++++++++++ scripts/vm/hypervisor/kvm/patchviasocket.py | 76 --------- .../vm/hypervisor/kvm/test_patchviasocket.py | 144 ------------------ .../opt/cloud/bin/setup/cloud-early-config | 52 +++---- systemvm/debian/root/.ssh/authorized_keys | 1 - systemvm/pom.xml | 7 - .../scripts/configure_systemvm_services.sh | 2 +- .../appliance/systemvmtemplate/template.json | 4 +- 13 files changed, 123 insertions(+), 302 deletions(-) create mode 100755 scripts/vm/hypervisor/kvm/patch.sh delete mode 100755 scripts/vm/hypervisor/kvm/patchviasocket.py delete mode 100755 scripts/vm/hypervisor/kvm/test_patchviasocket.py delete mode 100644 systemvm/debian/root/.ssh/authorized_keys 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 652f6431f0e..f5f38edbb2e 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 @@ -47,7 +47,6 @@ import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; -import com.cloud.resource.RequestWrapper; import org.apache.cloudstack.storage.to.PrimaryDataStoreTO; import org.apache.cloudstack.storage.to.TemplateObjectTO; import org.apache.cloudstack.storage.to.VolumeObjectTO; @@ -146,6 +145,7 @@ import com.cloud.hypervisor.kvm.storage.KVMStorageProcessor; import com.cloud.network.Networks.BroadcastDomainType; import com.cloud.network.Networks.RouterPrivateIpStrategy; import com.cloud.network.Networks.TrafficType; +import com.cloud.resource.RequestWrapper; import com.cloud.resource.ServerResource; import com.cloud.resource.ServerResourceBase; import com.cloud.storage.JavaStorageLayer; @@ -199,7 +199,7 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv private String _modifyVlanPath; private String _versionstringpath; - private String _patchViaSocketPath; + private String _patchScriptPath; private String _createvmPath; private String _manageSnapshotPath; private String _resizeVolumePath; @@ -682,9 +682,9 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv throw new ConfigurationException("Unable to find versions.sh"); } - _patchViaSocketPath = Script.findScript(kvmScriptsDir + "/patch/", "patchviasocket.py"); - if (_patchViaSocketPath == null) { - throw new ConfigurationException("Unable to find patchviasocket.py"); + _patchScriptPath = Script.findScript(kvmScriptsDir, "patch.sh"); + if (_patchScriptPath == null) { + throw new ConfigurationException("Unable to find patch.sh"); } _heartBeatPath = Script.findScript(kvmScriptsDir, "kvmheartbeat.sh"); @@ -1362,13 +1362,13 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv } public boolean passCmdLine(final String vmName, final String cmdLine) throws InternalErrorException { - final Script command = new Script(_patchViaSocketPath, 5 * 1000, s_logger); + final Script command = new Script(_patchScriptPath, 30 * 1000, s_logger); String result; command.add("-n", vmName); - command.add("-p", cmdLine.replaceAll(" ", "%")); + command.add("-c", cmdLine); result = command.execute(); if (result != null) { - s_logger.error("passcmd failed:" + result); + s_logger.error("Passing cmdline failed:" + result); return false; } return true; @@ -2141,12 +2141,6 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv final SerialDef serial = new SerialDef("pty", null, (short)0); devices.addDevice(serial); - /* Add a VirtIO channel for SystemVMs for communication and provisioning */ - if (vmTO.getType() != VirtualMachine.Type.User) { - devices.addDevice(new ChannelDef(vmTO.getName() + ".vport", ChannelDef.ChannelType.UNIX, - new File(_qemuSocketsPath + "/" + vmTO.getName() + ".agent"))); - } - if (_rngEnable) { final RngDef rngDevice = new RngDef(_rngPath, _rngBackendModel, _rngRateBytes, _rngRatePeriod); devices.addDevice(rngDevice); diff --git a/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtStartCommandWrapper.java b/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtStartCommandWrapper.java index 5a75f078f9e..18abb1c77b4 100644 --- a/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtStartCommandWrapper.java +++ b/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtStartCommandWrapper.java @@ -115,7 +115,6 @@ public final class LibvirtStartCommandWrapper extends CommandWrapper" + "" + "" + - "" + - "" + - "" + - "" + - "
" + - "" + "" + "" + "" + @@ -215,14 +206,9 @@ public class LibvirtDomainXMLParserTest extends TestCase { assertEquals(channelType, channels.get(i).getChannelType()); } - /* SSVM provisioning port/channel */ - assertEquals(channelState, channels.get(0).getChannelState()); - assertEquals(new File(ssvmAgentPath), channels.get(0).getPath()); - assertEquals(ssvmAgentName, channels.get(0).getName()); - /* Qemu Guest Agent port/channel */ - assertEquals(new File(guestAgentPath), channels.get(1).getPath()); - assertEquals(guestAgentName, channels.get(1).getName()); + assertEquals(new File(guestAgentPath), channels.get(0).getPath()); + assertEquals(guestAgentName, channels.get(0).getName()); List ifs = parser.getInterfaces(); for (int i = 0; i < ifs.size(); i++) { diff --git a/plugins/hypervisors/kvm/test/com/cloud/hypervisor/kvm/resource/LibvirtVMDefTest.java b/plugins/hypervisors/kvm/test/com/cloud/hypervisor/kvm/resource/LibvirtVMDefTest.java index b391b94e740..6a06c11fca7 100644 --- a/plugins/hypervisors/kvm/test/com/cloud/hypervisor/kvm/resource/LibvirtVMDefTest.java +++ b/plugins/hypervisors/kvm/test/com/cloud/hypervisor/kvm/resource/LibvirtVMDefTest.java @@ -21,13 +21,13 @@ package com.cloud.hypervisor.kvm.resource; import java.io.File; -import junit.framework.TestCase; - import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.ChannelDef; import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.DiskDef; import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.SCSIDef; import com.cloud.utils.Pair; +import junit.framework.TestCase; + public class LibvirtVMDefTest extends TestCase { public void testInterfaceEtehrnet() { @@ -180,7 +180,7 @@ public class LibvirtVMDefTest extends TestCase { public void testChannelDef() { ChannelDef.ChannelType type = ChannelDef.ChannelType.UNIX; ChannelDef.ChannelState state = ChannelDef.ChannelState.CONNECTED; - String name = "v-136-VM.vport"; + String name = "v-136-VM.org.qemu.guest_agent.0"; File path = new File("/var/lib/libvirt/qemu/" + name); ChannelDef channelDef = new ChannelDef(name, type, state, path); diff --git a/scripts/installer/windows/client.wxs b/scripts/installer/windows/client.wxs index ee09744fc7b..c54b76cc475 100644 --- a/scripts/installer/windows/client.wxs +++ b/scripts/installer/windows/client.wxs @@ -1046,7 +1046,7 @@ - + diff --git a/scripts/vm/hypervisor/kvm/patch.sh b/scripts/vm/hypervisor/kvm/patch.sh new file mode 100755 index 00000000000..e7c79fd9a73 --- /dev/null +++ b/scripts/vm/hypervisor/kvm/patch.sh @@ -0,0 +1,80 @@ +#!/bin/bash +# 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. + +set -e + +# Get the VM name and cmdline +while getopts "n:c:h" opt; do + case ${opt} in + n ) + name=$OPTARG + ;; + c ) + cmdline=$(echo $OPTARG | base64 -w 0) + ;; + h ) + echo "Usage: $0 -n [VM name] -c [command line]" + exit 0 + ;; + esac +done + +SSHKEY_FILE="/root/.ssh/id_rsa.pub.cloud" +if [ ! -e $SSHKEY_FILE ]; then + echo "SSH public key file $SSHKEY_FILE not found!" + exit 1 +fi + +if ! which virsh > /dev/null; then + echo "Libvirt CLI 'virsh' not found" + exit 1 +fi + +# Read the SSH public key +sshkey=$(cat $SSHKEY_FILE | base64 -w 0) + +# Method to send and write payload inside the VM +send_file() { + local name=${1} + local path=${2} + local content=${@:3} + local fd=$(virsh qemu-agent-command $name "{\"execute\":\"guest-file-open\", \"arguments\":{\"path\":\"$path\",\"mode\":\"w+\"}}" | sed 's/[^:]*:\([^}]*\).*/\1/') + virsh qemu-agent-command $name "{\"execute\":\"guest-file-write\", \"arguments\":{\"handle\":$fd,\"buf-b64\":\"$content\"}}" > /dev/null + virsh qemu-agent-command $name "{\"execute\":\"guest-file-close\", \"arguments\":{\"handle\":$fd}}" > /dev/null +} + +# Wait for the guest agent to come online +while ! virsh qemu-agent-command $name '{"execute":"guest-ping"}' >/dev/null 2>&1 +do + sleep 0.1 +done + +# Test guest agent sanity +while [ "$(virsh qemu-agent-command $name '{"execute":"guest-sync","arguments":{"id":1234567890}}' 2>/dev/null)" != '{"return":1234567890}' ] +do + sleep 0.1 +done + +# Write ssh public key +send_file $name "/root/.ssh/authorized_keys" $sshkey + +# Fix ssh public key permission +virsh qemu-agent-command $name '{"execute":"guest-exec","arguments":{"path":"chmod","arg":["go-rwx","/root/.ssh/authorized_keys"]}}' > /dev/null + +# Write cmdline payload +send_file $name "/var/cache/cloud/cmdline" $cmdline diff --git a/scripts/vm/hypervisor/kvm/patchviasocket.py b/scripts/vm/hypervisor/kvm/patchviasocket.py deleted file mode 100755 index c971d5dcc58..00000000000 --- a/scripts/vm/hypervisor/kvm/patchviasocket.py +++ /dev/null @@ -1,76 +0,0 @@ -#!/usr/bin/env python -# 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. - -# -# This script connects to the system vm socket and writes the -# authorized_keys and cmdline data to it. The system VM then -# reads it from /dev/vport0p1 in cloud_early_config -# - -import argparse -import os -import socket - -SOCK_FILE = "/var/lib/libvirt/qemu/{name}.agent" -PUB_KEY_FILE = "/root/.ssh/id_rsa.pub.cloud" -MESSAGE = "pubkey:{key}\ncmdline:{cmdline}\n" - - -def send_to_socket(sock_file, key_file, cmdline): - if not os.path.exists(key_file): - print("ERROR: ssh public key not found on host at {0}".format(key_file)) - return 1 - - try: - with open(key_file, "r") as f: - pub_key = f.read() - except IOError as e: - print("ERROR: unable to open {0} - {1}".format(key_file, e.strerror)) - return 1 - - # Keep old substitution from perl code: - cmdline = cmdline.replace("%", " ") - - msg = MESSAGE.format(key=pub_key, cmdline=cmdline) - - if not os.path.exists(sock_file): - print("ERROR: {0} socket not found".format(sock_file)) - return 1 - - try: - s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) - s.connect(sock_file) - s.sendall(msg) - s.close() - except IOError as e: - print("ERROR: unable to connect to {0} - {1}".format(sock_file, e.strerror)) - return 1 - - return 0 # Success - - -if __name__ == "__main__": - parser = argparse.ArgumentParser(description="Send configuration to system VM socket") - parser.add_argument("-n", "--name", required=True, help="Name of VM") - parser.add_argument("-p", "--cmdline", required=True, help="Command line") - - arguments = parser.parse_args() - - socket_file = SOCK_FILE.format(name=arguments.name) - - exit(send_to_socket(socket_file, PUB_KEY_FILE, arguments.cmdline)) diff --git a/scripts/vm/hypervisor/kvm/test_patchviasocket.py b/scripts/vm/hypervisor/kvm/test_patchviasocket.py deleted file mode 100755 index 6b411d32246..00000000000 --- a/scripts/vm/hypervisor/kvm/test_patchviasocket.py +++ /dev/null @@ -1,144 +0,0 @@ -#!/usr/bin/env python -# 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. - -import patchviasocket - -import getpass -import os -import socket -import tempfile -import time -import threading -import unittest - -KEY_DATA = "I luv\nCloudStack\n" -CMD_DATA = "/run/this-for-me --please=TRUE! very%quickly" -NON_EXISTING_FILE = "must-not-exist" - - -def write_key_file(): - _, tmpfile = tempfile.mkstemp(".sck") - with open(tmpfile, "w") as f: - f.write(KEY_DATA) - return tmpfile - - -class SocketThread(threading.Thread): - def __init__(self): - super(SocketThread, self).__init__() - self._data = "" - self._folder = tempfile.mkdtemp(".sck") - self._file = os.path.join(self._folder, "socket") - self._ready = False - - def data(self): - return self._data - - def file(self): - return self._file - - def wait_until_ready(self): - while not self._ready: - time.sleep(0.050) - - def run(self): - TIMEOUT = 0.314 # Very short time for tests that don't write to socket. - MAX_SIZE = 10 * 1024 - - s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) - try: - s.bind(self._file) - s.listen(1) - s.settimeout(TIMEOUT) - try: - self._ready = True - client, address = s.accept() - self._data = client.recv(MAX_SIZE) - client.close() - except socket.timeout: - pass - finally: - s.close() - os.remove(self._file) - os.rmdir(self._folder) - - -class TestPatchViaSocket(unittest.TestCase): - def setUp(self): - self._key_file = write_key_file() - - self._unreadable = write_key_file() - os.chmod(self._unreadable, 0) - - self.assertFalse(os.path.exists(NON_EXISTING_FILE)) - self.assertNotEqual("root", getpass.getuser(), "must be non-root user (to test access denied errors)") - - def tearDown(self): - os.remove(self._key_file) - os.remove(self._unreadable) - - def test_write_to_socket(self): - reader = SocketThread() - reader.start() - reader.wait_until_ready() - self.assertEquals(0, patchviasocket.send_to_socket(reader.file(), self._key_file, CMD_DATA)) - reader.join() - data = reader.data() - self.assertIn(KEY_DATA, data) - self.assertIn(CMD_DATA.replace("%", " "), data) - self.assertNotIn("LUV", data) - self.assertNotIn("very%quickly", data) # Testing substitution - - def test_host_key_error(self): - reader = SocketThread() - reader.start() - reader.wait_until_ready() - self.assertEquals(1, patchviasocket.send_to_socket(reader.file(), NON_EXISTING_FILE, CMD_DATA)) - reader.join() # timeout - - def test_host_key_access_denied(self): - reader = SocketThread() - reader.start() - reader.wait_until_ready() - self.assertEquals(1, patchviasocket.send_to_socket(reader.file(), self._unreadable, CMD_DATA)) - reader.join() # timeout - - def test_nonexistant_socket_error(self): - reader = SocketThread() - reader.start() - reader.wait_until_ready() - self.assertEquals(1, patchviasocket.send_to_socket(NON_EXISTING_FILE, self._key_file, CMD_DATA)) - reader.join() # timeout - - def test_invalid_socket_error(self): - reader = SocketThread() - reader.start() - reader.wait_until_ready() - self.assertEquals(1, patchviasocket.send_to_socket(self._key_file, self._key_file, CMD_DATA)) - reader.join() # timeout - - def test_access_denied_socket_error(self): - reader = SocketThread() - reader.start() - reader.wait_until_ready() - self.assertEquals(1, patchviasocket.send_to_socket(self._unreadable, self._key_file, CMD_DATA)) - reader.join() # timeout - - -if __name__ == '__main__': - unittest.main() diff --git a/systemvm/debian/opt/cloud/bin/setup/cloud-early-config b/systemvm/debian/opt/cloud/bin/setup/cloud-early-config index 8ba7701de8a..6aa0314040c 100755 --- a/systemvm/debian/opt/cloud/bin/setup/cloud-early-config +++ b/systemvm/debian/opt/cloud/bin/setup/cloud-early-config @@ -90,41 +90,29 @@ get_boot_params() { sed -i "s/%/ /g" $CMDLINE ;; kvm) - VPORT=$(find /dev/virtio-ports -type l -name '*.vport' 2>/dev/null|head -1) - - if [ -z "$VPORT" ]; then - log_it "No suitable VirtIO port was found in /dev/virtio-ports" && exit 2 + # Use any old cmdline as backup only + if [ -s $CMDLINE ]; then + mv $CMDLINE $CMDLINE.old + log_it "Found a non-empty old cmdline file" fi - - if [ ! -e "$VPORT" ]; then - log_it "${VPORT} not loaded, perhaps guest kernel is too old." && exit 2 - fi - - local factor=2 - local progress=1 - for i in {1..5} - do - while read line; do - if [[ $line == cmdline:* ]]; then - cmd=${line//cmdline:/} - echo $cmd > $CMDLINE - elif [[ $line == pubkey:* ]]; then - pubkey=${line//pubkey:/} - echo $pubkey > /var/cache/cloud/authorized_keys - echo $pubkey > /root/.ssh/authorized_keys - fi - done < $VPORT - # In case of reboot we do not send the boot args again. - # So, no need to wait for them, as the boot args are already set at startup - if [ -s $CMDLINE ] - then - log_it "Found a non empty cmdline file. Will now exit the loop and proceed with configuration." - break; + systemctl enable --now qemu-guest-agent + # Wait for $CMDLINE file to be written by the qemu-guest-agent + for i in {1..60}; do + if [ -s $CMDLINE ]; then + log_it "Received a new non-empty cmdline file from qemu-guest-agent" + break fi - sleep ${progress}s - progress=$[ progress * factor ] + sleep 1 done - chmod go-rwx /root/.ssh/authorized_keys + # Use any old cmdline only when new cmdline is not received + if [ -e $CMDLINE.old ]; then + if [ -s $CMDLINE ]; then + rm $CMDLINE.old + else + mv $CMDLINE.old $CMDLINE + log_it "Using old cmdline file, VM was perhaps rebooted." + fi + fi ;; vmware) vmtoolsd --cmd 'machine.id.get' > $CMDLINE diff --git a/systemvm/debian/root/.ssh/authorized_keys b/systemvm/debian/root/.ssh/authorized_keys deleted file mode 100644 index c09f6379a34..00000000000 --- a/systemvm/debian/root/.ssh/authorized_keys +++ /dev/null @@ -1 +0,0 @@ -ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA2RIE3hgSAD8zULuyE7KDW9EKh2oVbNGY7iSL/VI5xHLISKh4e8ksTshWjlGBtrUCnuzR7y2BUxZ65RI8XkB1fEDxcOU4/0lVPvJYDSsGveXoOgpLwOtKRoGLgjFUGzBQlj2s6YaYQxoNTqtBVkDIH6ekPNq0Q38hRrFcsVIk1sFo5ejuvFxt2wx6APcFIQtHSNezEDO0GVUScDU1N1YEMMv1PU3M/SrcezkXrGl/efF3kWtY9L5xm7sojHMCCqsI38r8ogof67F7JdWRXM6Nl3VzkdCBzWGcyAl+cYfjzgOiBGXyAyYBk8qqzJjKwUOtdjfRvCyowA/0xBwMW1T7PQ== diff --git a/systemvm/pom.xml b/systemvm/pom.xml index 55fc12b9d95..a72e3e9aac7 100644 --- a/systemvm/pom.xml +++ b/systemvm/pom.xml @@ -87,12 +87,6 @@ agent.zip - - debian/root/.ssh - - authorized_keys - - @@ -167,7 +161,6 @@ systemvm.iso agent.zip cloud-scripts.tgz - authorized_keys diff --git a/tools/appliance/systemvmtemplate/scripts/configure_systemvm_services.sh b/tools/appliance/systemvmtemplate/scripts/configure_systemvm_services.sh index 48695014bd9..78d868d8b0f 100644 --- a/tools/appliance/systemvmtemplate/scripts/configure_systemvm_services.sh +++ b/tools/appliance/systemvmtemplate/scripts/configure_systemvm_services.sh @@ -19,7 +19,7 @@ set -e set -x -CLOUDSTACK_RELEASE=4.11.2 +CLOUDSTACK_RELEASE=4.11.3 function configure_apache2() { # Enable ssl, rewrite and auth diff --git a/tools/appliance/systemvmtemplate/template.json b/tools/appliance/systemvmtemplate/template.json index 1c62b1e0942..083a66c9cfa 100644 --- a/tools/appliance/systemvmtemplate/template.json +++ b/tools/appliance/systemvmtemplate/template.json @@ -38,8 +38,8 @@ "disk_interface": "virtio", "net_device": "virtio-net", - "iso_url": "https://cdimage.debian.org/debian-cd/current/amd64/iso-cd/debian-9.6.0-amd64-netinst.iso", - "iso_checksum": "fcd77acbd46f33e0a266faf284acc1179ab0a3719e4b8abebac555307aa978aa242d7052c8d41e1a5fc6d1b30bc6ca6d62269e71526b71c9d5199b13339f0e25", + "iso_url": "https://cdimage.debian.org/debian-cd/current/amd64/iso-cd/debian-9.9.0-amd64-netinst.iso", + "iso_checksum": "42d9818abc4a08681dc0638f07e7aeb35d0c44646ab1e5b05a31a71d76c99da52b6192db9a3e852171ac78c2ba6b110b337c0b562c7be3d32e86a105023a6a0c", "iso_checksum_type": "sha512", "vm_name": "systemvmtemplate",