diff --git a/services_setup/kube-master/setup-kubectl-master.sh b/services_setup/kube-master/setup-kubectl-master.sh deleted file mode 100644 index 109a9f61..00000000 --- a/services_setup/kube-master/setup-kubectl-master.sh +++ /dev/null @@ -1,136 +0,0 @@ -#!/usr/bin/env bash - -# -# This file is part of the eskimo project referenced at www.eskimo.sh. The licensing information below apply just as -# well to this individual file than to the Eskimo Project as a whole. -# -# Copyright 2019 - 2022 eskimo.sh / https://www.eskimo.sh - All rights reserved. -# Author : eskimo.sh / https://www.eskimo.sh -# -# Eskimo is available under a dual licensing model : commercial and GNU AGPL. -# If you did not acquire a commercial licence for Eskimo, you can still use it and consider it free software under the -# terms of the GNU Affero Public License. You can redistribute it and/or modify it under the terms of the GNU Affero -# Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) -# any later version. -# Compliance to each and every aspect of the GNU Affero Public License is mandatory for users who did no acquire a -# commercial license. -# -# Eskimo is distributed as a free software under GNU AGPL in the hope that it will be useful, but WITHOUT ANY -# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Affero Public License for more details. -# -# You should have received a copy of the GNU Affero Public License along with Eskimo. If not, -# see or write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, -# Boston, MA, 02110-1301 USA. -# -# You can be released from the requirements of the license by purchasing a commercial license. Buying such a -# commercial license is mandatory as soon as : -# - you develop activities involving Eskimo without disclosing the source code of your own product, software, -# platform, use cases or scripts. -# - you deploy eskimo as part of a commercial product, platform or software. -# For more information, please contact eskimo.sh at https://www.eskimo.sh -# -# The above copyright notice and this licensing notice shall be included in all copies or substantial portions of the -# Software. -# - -echoerr() { echo "$@" 1>&2; } - -SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" -. $SCRIPT_DIR/common.sh "$@" - -# CHange current folder to script dir (important !) -cd $SCRIPT_DIR || exit 199 - -if [[ ! -f /etc/k8s/env.sh ]]; then - echo "Could not find /etc/k8s/env.sh" - exit 1 -fi - -. /etc/k8s/env.sh - -sudo rm -Rf /tmp/kube_basemaster_setup -mkdir /tmp/kube_basemaster_setup -cd /tmp/kube_basemaster_setup - -# Defining topology variables -if [[ $SELF_NODE_NUMBER == "" ]]; then - echo " - No Self Node Number found in topology" - exit 1 -fi - -if [[ $SELF_IP_ADDRESS == "" ]]; then - echo " - No Self IP address found in topology for node $SELF_NODE_NUMBER" - exit 2 -fi - - - -set -e - - -echo " - Creating / checking eskimo kubernetes base config (master only)" - - -echo " + Create and install kubernetes-csr.json" -cat > kubernetes-csr.json < ca-config.json < kubernetes-csr.json < getEditableSettings() { public int getRelevantDependenciesCount() { return (int) dependencies.stream() - .filter(dep -> !dep.getMasterService().equals(getName()) && !dep.getMasterService().equals(NodesConfigWrapper.NODE_ID_FIELD)) + .filter(dep -> + !dep.getMasterService().equals(getName()) + && !dep.getMasterService().equals(NodesConfigWrapper.NODE_ID_FIELD) + && !dep.isDependentInstalledFirst() + ) .count(); } diff --git a/src/main/java/ch/niceideas/eskimo/services/ServicesDefinition.java b/src/main/java/ch/niceideas/eskimo/services/ServicesDefinition.java index 57b3b4f4..11ae55ed 100644 --- a/src/main/java/ch/niceideas/eskimo/services/ServicesDefinition.java +++ b/src/main/java/ch/niceideas/eskimo/services/ServicesDefinition.java @@ -327,6 +327,9 @@ public void afterPropertiesSet() throws Exception { Boolean depRestart = depObj.has("restart") ? depObj.getBoolean("restart") : null; dependency.setRestart(depRestart == null || depRestart); + Boolean dependentInstalledFirst = depObj.has("dependentInstalledFirst") ? depObj.getBoolean("dependentInstalledFirst") : null; + dependency.setDependentInstalledFirst(dependentInstalledFirst != null && dependentInstalledFirst); // false by default + String conditionalDependency = depObj.has("conditional") ? depObj.getString("conditional") : null; if (StringUtils.isNotBlank(conditionalDependency)) { dependency.setConditional (conditionalDependency); @@ -739,12 +742,12 @@ public int compareServices(Service one, Service other) { // need to browse dependencies for (Dependency dep : one.getDependencies()) { - if (dep.getMasterService().equals(other.getName())) { + if (dep.getMasterService().equals(other.getName()) && !dep.isDependentInstalledFirst()) { return 1; } } for (Dependency dep : other.getDependencies()) { - if (dep.getMasterService().equals(one.getName())) { + if (dep.getMasterService().equals(one.getName()) && !dep.isDependentInstalledFirst()) { return -1; } } diff --git a/src/main/java/ch/niceideas/eskimo/services/ServicesInstallationSorter.java b/src/main/java/ch/niceideas/eskimo/services/ServicesInstallationSorter.java index 4e3add0a..709747ae 100644 --- a/src/main/java/ch/niceideas/eskimo/services/ServicesInstallationSorter.java +++ b/src/main/java/ch/niceideas/eskimo/services/ServicesInstallationSorter.java @@ -85,7 +85,7 @@ public List> orderOperations ( operationForService.add(operation); } - // 2. Order by depencencies + // 2. Order by dependencies List services = groupedOperations.keySet().stream() .sorted((one, other) -> servicesDefinition.compareServices(one, other)) .map(service -> servicesDefinition.getService(service)) diff --git a/src/main/resources/services.json b/src/main/resources/services.json index 39d50386..89fa865b 100644 --- a/src/main/resources/services.json +++ b/src/main/resources/services.json @@ -384,7 +384,8 @@ "masterService": "kube-slave", "numberOfMasters": 1, "mandatory": true, - "restart": false + "restart": false, + "dependentInstalledFirst": true } ] }, diff --git a/src/test/java/ch/niceideas/eskimo/controlers/NodesConfigControllerTest.java b/src/test/java/ch/niceideas/eskimo/controlers/NodesConfigControllerTest.java index 1b151c82..fc6f7930 100644 --- a/src/test/java/ch/niceideas/eskimo/controlers/NodesConfigControllerTest.java +++ b/src/test/java/ch/niceideas/eskimo/controlers/NodesConfigControllerTest.java @@ -139,9 +139,9 @@ public NodesConfigWrapper resolveRanges(NodesConfigWrapper rawNodesConfig) throw assertEquals ("{\n" + " \"command\": {\n" + " \"restarts\": [\n" + + " {\"kube-master\": \"192.168.10.11\"},\n" + " {\"kube-slave\": \"192.168.10.11\"},\n" + " {\"kube-slave\": \"192.168.10.13\"},\n" + - " {\"kube-master\": \"192.168.10.11\"},\n" + " {\"spark-console\": \"(kubernetes)\"},\n" + " {\"logstash\": \"(kubernetes)\"},\n" + " {\"zeppelin\": \"(kubernetes)\"}\n" + diff --git a/src/test/java/ch/niceideas/eskimo/model/ServiceOperationsCommandTest.java b/src/test/java/ch/niceideas/eskimo/model/ServiceOperationsCommandTest.java index a15001c3..8ff6ecb0 100644 --- a/src/test/java/ch/niceideas/eskimo/model/ServiceOperationsCommandTest.java +++ b/src/test/java/ch/niceideas/eskimo/model/ServiceOperationsCommandTest.java @@ -117,10 +117,10 @@ public void testRestartMany() throws Exception { System.err.println (oc.toJSON()); assertTrue (new JSONObject( - "{\"restarts\":[{\"gluster\":\"192.168.10.11\"},{\"gluster\":\"192.168.10.13\"},{\"elasticsearch\":\"(kubernetes)\"},{\"kafka\":\"(kubernetes)\"},{\"kafka-manager\":\"(kubernetes)\"},{\"logstash\":\"(kubernetes)\"},{\"zeppelin\":\"(kubernetes)\"}]," + - "\"uninstallations\":[{\"etcd\":\"192.168.10.11\"},{\"kube-slave\":\"192.168.10.11\"},{\"zookeeper\":\"192.168.10.13\"}]," + - "\"installations\":[{\"zookeeper\":\"192.168.10.11\"},{\"etcd\":\"192.168.10.13\"}]}") - .similar(oc.toJSON())); + "{\"restarts\":[{\"gluster\":\"192.168.10.11\"},{\"gluster\":\"192.168.10.13\"},{\"elasticsearch\":\"(kubernetes)\"},{\"kafka\":\"(kubernetes)\"},{\"kafka-manager\":\"(kubernetes)\"},{\"logstash\":\"(kubernetes)\"},{\"zeppelin\":\"(kubernetes)\"}]," + + "\"uninstallations\":[{\"etcd\":\"192.168.10.11\"},{\"kube-slave\":\"192.168.10.11\"},{\"zookeeper\":\"192.168.10.13\"}]," + + "\"installations\":[{\"zookeeper\":\"192.168.10.11\"},{\"etcd\":\"192.168.10.13\"}]}") + .similar(oc.toJSON())); } @Test @@ -143,7 +143,7 @@ public void testRestartOnlySameNode() throws Exception { assertEquals(6, oc.getRestarts().size()); assertEquals ( - "etcd=192.168.10.11, kube-slave=192.168.10.11, kube-master=192.168.10.11, spark-console=(kubernetes), logstash=(kubernetes), zeppelin=(kubernetes)" + "etcd=192.168.10.11, kube-master=192.168.10.11, kube-slave=192.168.10.11, spark-console=(kubernetes), logstash=(kubernetes), zeppelin=(kubernetes)" , oc.getRestarts().stream() .map(operationId -> operationId.getService()+"="+operationId.getNode()) .collect(Collectors.joining(", "))); @@ -153,16 +153,16 @@ public void testRestartOnlySameNode() throws Exception { public void testMoveServices() throws Exception { ServicesInstallStatusWrapper savedServicesInstallStatus = new ServicesInstallStatusWrapper (new HashMap<>() {{ - put ("cerebro_installed_on_IP_KUBERNETES_NODE", "OK"); - put ("ntp_installed_on_IP_192-168-10-11", "OK"); - put ("etcd_installed_on_IP_192-168-10-11", "OK"); + put ("cerebro_installed_on_IP_KUBERNETES_NODE", "OK"); + put ("ntp_installed_on_IP_192-168-10-11", "OK"); + put ("etcd_installed_on_IP_192-168-10-11", "OK"); }}); NodesConfigWrapper nodesConfig = new NodesConfigWrapper(new HashMap<>() {{ - put ("node_id1", "192.168.10.11"); - put ("node_id2", "192.168.10.13"); - put ("etcd2", "on"); - put ("ntp2", "on"); + put ("node_id1", "192.168.10.11"); + put ("node_id2", "192.168.10.13"); + put ("etcd2", "on"); + put ("ntp2", "on"); }} ); ServiceOperationsCommand oc = ServiceOperationsCommand.create(def, nrr, savedServicesInstallStatus, nodesConfig); @@ -255,7 +255,7 @@ public void testRecoverUninstallationWhenNodeDownMiddleUninstall() throws Except savedServicesInstallStatus.setValueForPath("zookeeper_installed_on_IP_192-168-10-11", "OK"); savedServicesInstallStatus.setValueForPath("etcd_installed_on_IP_192-168-10-13", "OK"); savedServicesInstallStatus.getJSONObject().remove("etcd_installed_on_IP_192-168-10-11"); - + // flag all restarts savedServicesInstallStatus.setValueForPath("etcd_installed_on_IP_192-168-10-11", "restart"); savedServicesInstallStatus.setValueForPath("kube-master_installed_on_IP_192-168-10-13", "restart"); @@ -268,7 +268,9 @@ public void testRecoverUninstallationWhenNodeDownMiddleUninstall() throws Except System.err.println (oc2.toJSON()); assertTrue (new JSONObject( - "{\"restarts\":[{\"zookeeper\":\"192.168.10.11\"},{\"gluster\":\"192.168.10.11\"},{\"gluster\":\"192.168.10.13\"},{\"etcd\":\"192.168.10.11\"},{\"kube-slave\":\"192.168.10.13\"},{\"kube-master\":\"192.168.10.11\"},{\"kube-master\":\"192.168.10.13\"},{\"elasticsearch\":\"(kubernetes)\"},{\"cerebro\":\"(kubernetes)\"},{\"spark-console\":\"(kubernetes)\"},{\"kafka\":\"(kubernetes)\"},{\"kafka-manager\":\"(kubernetes)\"},{\"logstash\":\"(kubernetes)\"},{\"zeppelin\":\"(kubernetes)\"}],\"uninstallations\":[{\"etcd\":\"192.168.10.11\"},{\"kube-master\":\"192.168.10.13\"},{\"kube-slave\":\"192.168.10.11\"},{\"zookeeper\":\"192.168.10.13\"}],\"installations\":[]}") + "{\"restarts\":[{\"zookeeper\":\"192.168.10.11\"},{\"gluster\":\"192.168.10.11\"},{\"gluster\":\"192.168.10.13\"},{\"etcd\":\"192.168.10.11\"},{\"kube-master\":\"192.168.10.11\"},{\"kube-master\":\"192.168.10.13\"},{\"kube-slave\":\"192.168.10.13\"},{\"elasticsearch\":\"(kubernetes)\"},{\"cerebro\":\"(kubernetes)\"},{\"spark-console\":\"(kubernetes)\"},{\"kafka\":\"(kubernetes)\"},{\"kafka-manager\":\"(kubernetes)\"},{\"logstash\":\"(kubernetes)\"},{\"zeppelin\":\"(kubernetes)\"}]," + + "\"uninstallations\":[{\"etcd\":\"192.168.10.11\"},{\"kube-master\":\"192.168.10.13\"},{\"kube-slave\":\"192.168.10.11\"},{\"zookeeper\":\"192.168.10.13\"}]," + + "\"installations\":[]}") .similar(oc2.toJSON())); } @@ -314,7 +316,10 @@ public void testRecoverUninstallationWhenNodeDownAfterUninstall() throws Excepti System.err.println (oc2.toJSON()); - assertTrue (new JSONObject("{\"restarts\":[{\"etcd\":\"192.168.10.13\"},{\"kube-slave\":\"192.168.10.13\"},{\"kube-master\":\"192.168.10.11\"},{\"kube-master\":\"192.168.10.13\"},{\"elasticsearch\":\"(kubernetes)\"},{\"cerebro\":\"(kubernetes)\"},{\"spark-console\":\"(kubernetes)\"},{\"kafka\":\"(kubernetes)\"},{\"kafka-manager\":\"(kubernetes)\"},{\"logstash\":\"(kubernetes)\"},{\"zeppelin\":\"(kubernetes)\"}],\"uninstallations\":[{\"kube-master\":\"192.168.10.13\"}],\"installations\":[]}") + assertTrue (new JSONObject("{" + + "\"restarts\":[{\"etcd\":\"192.168.10.13\"},{\"kube-master\":\"192.168.10.11\"},{\"kube-master\":\"192.168.10.13\"},{\"kube-slave\":\"192.168.10.13\"},{\"elasticsearch\":\"(kubernetes)\"},{\"cerebro\":\"(kubernetes)\"},{\"spark-console\":\"(kubernetes)\"},{\"kafka\":\"(kubernetes)\"},{\"kafka-manager\":\"(kubernetes)\"},{\"logstash\":\"(kubernetes)\"},{\"zeppelin\":\"(kubernetes)\"}]," + + "\"uninstallations\":[{\"kube-master\":\"192.168.10.13\"}]," + + "\"installations\":[]}") .similar(oc2.toJSON())); } @@ -365,7 +370,10 @@ public void testRecoverUninstallationWhenNodeDownMiddleRestart() throws Exceptio System.err.println (oc2.toJSON()); - assertTrue (new JSONObject("{\"restarts\":[{\"kube-slave\":\"192.168.10.13\"},{\"kube-master\":\"192.168.10.11\"},{\"elasticsearch\":\"(kubernetes)\"},{\"cerebro\":\"(kubernetes)\"},{\"spark-console\":\"(kubernetes)\"},{\"kafka\":\"(kubernetes)\"},{\"kafka-manager\":\"(kubernetes)\"},{\"logstash\":\"(kubernetes)\"},{\"zeppelin\":\"(kubernetes)\"}],\"uninstallations\":[{\"kube-master\":\"192.168.10.13\"}],\"installations\":[]}") + assertTrue (new JSONObject("{" + + "\"restarts\":[{\"kube-master\":\"192.168.10.11\"},{\"kube-slave\":\"192.168.10.13\"},{\"elasticsearch\":\"(kubernetes)\"},{\"cerebro\":\"(kubernetes)\"},{\"spark-console\":\"(kubernetes)\"},{\"kafka\":\"(kubernetes)\"},{\"kafka-manager\":\"(kubernetes)\"},{\"logstash\":\"(kubernetes)\"},{\"zeppelin\":\"(kubernetes)\"}]," + + "\"uninstallations\":[{\"kube-master\":\"192.168.10.13\"}]," + + "\"installations\":[]}") .similar(oc2.toJSON())); } diff --git a/src/test/java/ch/niceideas/eskimo/services/ServiceInstallationSorterTest.java b/src/test/java/ch/niceideas/eskimo/services/ServiceInstallationSorterTest.java index 0e8b505b..d27f37fd 100644 --- a/src/test/java/ch/niceideas/eskimo/services/ServiceInstallationSorterTest.java +++ b/src/test/java/ch/niceideas/eskimo/services/ServiceInstallationSorterTest.java @@ -79,7 +79,7 @@ public void testNoMixUpOfKubeAndNonKube() throws Exception { new String[] {"kube-master", "kube-slave", "gluster", "cerebro", "kibana"}, StandardSetupHelpers.getStandard2NodesInstallStatus(), StandardSetupHelpers.getStandard2NodesSetup() - ); + ); List> orderedRestart = restartCommand.getRestartsInOrder( sis, StandardSetupHelpers.getStandard2NodesSetup() @@ -94,18 +94,18 @@ public void testNoMixUpOfKubeAndNonKube() throws Exception { assertEquals("192.168.10.13", group1.get(1).getNode()); List group2 = orderedRestart.get(1); - assertEquals(2, group2.size()); - assertEquals("kube-slave", group2.get(0).getService()); + assertEquals(1, group2.size()); + assertEquals("kube-master", group2.get(0).getService()); assertEquals("192.168.10.11", group2.get(0).getNode()); - assertEquals("kube-slave", group2.get(1).getService()); - assertEquals("192.168.10.13", group2.get(1).getNode()); - List group3 = orderedRestart.get(2); - assertEquals(1, group3.size()); - assertEquals("kube-master", group3.get(0).getService()); + assertEquals(2, group3.size()); + assertEquals("kube-slave", group3.get(0).getService()); assertEquals("192.168.10.11", group3.get(0).getNode()); + assertEquals("kube-slave", group3.get(1).getService()); + assertEquals("192.168.10.13", group3.get(1).getNode()); + List group4 = orderedRestart.get(3); assertEquals(1, group4.size()); assertEquals("cerebro", group4.get(0).getService()); @@ -136,16 +136,16 @@ public void testNominal() throws Exception { assertEquals("192.168.10.11", group1.get(1).getNode()); List group4 = orderedInstall.get(4); - assertEquals(2, group4.size()); - assertEquals("kube-slave", group4.get(0).getService()); + assertEquals(1, group4.size()); + assertEquals("kube-master", group4.get(0).getService()); assertEquals("192.168.10.11", group4.get(0).getNode()); - assertEquals("kube-slave", group4.get(1).getService()); - assertEquals("192.168.10.13", group4.get(1).getNode()); List group5 = orderedInstall.get(5); - assertEquals(1, group5.size()); - assertEquals("kube-master", group5.get(0).getService()); + assertEquals(2, group5.size()); + assertEquals("kube-slave", group5.get(0).getService()); assertEquals("192.168.10.11", group5.get(0).getNode()); + assertEquals("kube-slave", group5.get(1).getService()); + assertEquals("192.168.10.13", group5.get(1).getNode()); } diff --git a/src/test/resources/OperationsMonitoringServiceTest/expected-status-interrupted.json b/src/test/resources/OperationsMonitoringServiceTest/expected-status-interrupted.json index 17e90c9f..f5e85eeb 100644 --- a/src/test/resources/OperationsMonitoringServiceTest/expected-status-interrupted.json +++ b/src/test/resources/OperationsMonitoringServiceTest/expected-status-interrupted.json @@ -83,6 +83,10 @@ "operation": "Check--Install_Base-System_192-168-10-15", "label": "Check / Install of Base System on 192.168.10.15" }, + { + "operation": "installation_kube-master_192-168-10-15", + "label": "installation of kube-master on 192.168.10.15" + }, { "operation": "installation_kube-slave_192-168-10-15", "label": "installation of kube-slave on 192.168.10.15" @@ -91,10 +95,6 @@ "operation": "installation_kube-slave_192-168-10-13", "label": "installation of kube-slave on 192.168.10.13" }, - { - "operation": "installation_kube-master_192-168-10-15", - "label": "installation of kube-master on 192.168.10.15" - }, { "operation": "installation_spark-console_192-168-10-15", "label": "installation of spark-console on 192.168.10.15" diff --git a/src/test/resources/OperationsMonitoringServiceTest/expected-status.json b/src/test/resources/OperationsMonitoringServiceTest/expected-status.json index 7b4096ef..89c6d97a 100644 --- a/src/test/resources/OperationsMonitoringServiceTest/expected-status.json +++ b/src/test/resources/OperationsMonitoringServiceTest/expected-status.json @@ -83,6 +83,10 @@ "operation": "Check--Install_Base-System_192-168-10-15", "label": "Check / Install of Base System on 192.168.10.15" }, + { + "operation": "installation_kube-master_192-168-10-15", + "label": "installation of kube-master on 192.168.10.15" + }, { "operation": "installation_kube-slave_192-168-10-15", "label": "installation of kube-slave on 192.168.10.15" @@ -91,10 +95,6 @@ "operation": "installation_kube-slave_192-168-10-13", "label": "installation of kube-slave on 192.168.10.13" }, - { - "operation": "installation_kube-master_192-168-10-15", - "label": "installation of kube-master on 192.168.10.15" - }, { "operation": "installation_spark-console_192-168-10-15", "label": "installation of spark-console on 192.168.10.15" diff --git a/test_lab/integration_test/integration-test.sh b/test_lab/integration_test/integration-test.sh index f2556e55..efaff25e 100755 --- a/test_lab/integration_test/integration-test.sh +++ b/test_lab/integration_test/integration-test.sh @@ -313,7 +313,7 @@ __tmp_saved_dir=$(pwd) __dump_error() { __returned_to_saved_dir - cat /tmp/integration-test.log + echo "(Do 'cat /tmp/integration-test.log' to know more if script finishes in error)" } __returned_to_saved_dir() { @@ -365,7 +365,7 @@ __dp_build_box() { vagrant up $1 >> /tmp/integration-test.log 2>&1 echo_date " + Updating the appliance" - vagrant ssh -c "sudo bash -c 'if [[ -f \"/etc/debian_version\" ]]; then apt-get -y update && apt-get -y upgrade ; else yum update -y; fi'" $1 >> /tmp/integration-test.log 2>&1 + vagrant ssh -c "sudo bash -c 'if [[ -f \"/etc/debian_version\" ]]; then export DEBIAN_FRONTEND=noninteractive; apt-get -y update && apt-mark hold grub-pc && apt-get -y -o Dpkg::Options::=\"--force-confdef\" -o Dpkg::Options::=\"--force-confold\" dist-upgrade ; else yum update -y; fi'" $1 >> /tmp/integration-test.log 2>&1 if [[ $2 == "MASTER" ]]; then diff --git a/test_lab/vagrant/Vagrantfile b/test_lab/vagrant/Vagrantfile index 4e9192af..e5d6cf66 100644 --- a/test_lab/vagrant/Vagrantfile +++ b/test_lab/vagrant/Vagrantfile @@ -71,7 +71,7 @@ nodes = [ { :hostname => 'integration-test1', :box => 'sorabt/centos-stream-8', :ip => '192.168.56.51', :ram => 7500, :cpu => 2 }, { :hostname => 'integration-test2', :box => 'fedora/35-cloud-base', :ip => '192.168.56.52', :ram => 7500, :cpu => 2 }, { :hostname => 'integration-test3', :box => 'ubuntu/xenial64', :ip => '192.168.56.53', :ram => 7500, :cpu => 2 }, - { :hostname => 'integration-test4', :box => 'bento/debian-11', :ip => '192.168.56.54', :ram => 7500, :cpu => 2 }, + { :hostname => 'integration-test4', :box => 'bento/debian-11.4', :ip => '192.168.56.54', :ram => 7500, :cpu => 2 }, { :hostname => 'small-node1', :box => 'sorabt/centos-stream-8', :ip => '192.168.56.71', :ram => 7000, :cpu => 2 }, { :hostname => 'small-node2', :box => 'sorabt/centos-stream-8', :ip => '192.168.56.72', :ram => 7000, :cpu => 2 }, { :hostname => 'small-node3', :box => 'sorabt/centos-stream-8', :ip => '192.168.56.73', :ram => 7000, :cpu => 2 },