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",