diff --git a/agent/src/main/java/com/cloud/agent/Agent.java b/agent/src/main/java/com/cloud/agent/Agent.java index b4474032b9e..7fec460124a 100644 --- a/agent/src/main/java/com/cloud/agent/Agent.java +++ b/agent/src/main/java/com/cloud/agent/Agent.java @@ -763,8 +763,10 @@ public class Agent implements HandlerFactory, IAgentControl { throw new CloudRuntimeException("Unable to save received agent client and ca certificates", e); } + String ksPassphrase = _shell.getPersistentProperty(null, KeyStoreUtils.KS_PASSPHRASE_PROPERTY); Script script = new Script(_keystoreCertImportPath, 300000, s_logger); script.add(agentFile.getAbsolutePath()); + script.add(ksPassphrase); script.add(keyStoreFile); script.add(KeyStoreUtils.AGENT_MODE); script.add(certFile); diff --git a/api/src/main/java/com/cloud/event/EventTypes.java b/api/src/main/java/com/cloud/event/EventTypes.java index 3e6bb1a579c..6471742e8fe 100644 --- a/api/src/main/java/com/cloud/event/EventTypes.java +++ b/api/src/main/java/com/cloud/event/EventTypes.java @@ -637,6 +637,9 @@ public class EventTypes { // Storage Policies public static final String EVENT_IMPORT_VCENTER_STORAGE_POLICIES = "IMPORT.VCENTER.STORAGE.POLICIES"; + // SystemVM + public static final String EVENT_LIVE_PATCH_SYSTEMVM = "LIVE.PATCH.SYSTEM.VM"; + static { // TODO: need a way to force author adding event types to declare the entity details as well, with out braking @@ -1048,6 +1051,7 @@ public class EventTypes { entityEventDetails.put(EVENT_IMPORT_VCENTER_STORAGE_POLICIES, "StoragePolicies"); entityEventDetails.put(EVENT_IMAGE_STORE_DATA_MIGRATE, ImageStore.class); + entityEventDetails.put(EVENT_LIVE_PATCH_SYSTEMVM, "SystemVMs"); } public static String getEntityForEvent(String eventName) { diff --git a/api/src/main/java/com/cloud/network/NetworkService.java b/api/src/main/java/com/cloud/network/NetworkService.java index 4e4b0d4d927..bfc4479d7f6 100644 --- a/api/src/main/java/com/cloud/network/NetworkService.java +++ b/api/src/main/java/com/cloud/network/NetworkService.java @@ -81,7 +81,7 @@ public interface NetworkService { boolean deleteNetwork(long networkId, boolean forced); - boolean restartNetwork(Long networkId, boolean cleanup, boolean makeRedundant, User user) throws ConcurrentOperationException, ResourceUnavailableException, InsufficientCapacityException; + boolean restartNetwork(Long networkId, boolean cleanup, boolean makeRedundant, boolean livePatch, User user) throws ConcurrentOperationException, ResourceUnavailableException, InsufficientCapacityException; boolean restartNetwork(RestartNetworkCmd cmd) throws ConcurrentOperationException, ResourceUnavailableException, InsufficientCapacityException; diff --git a/api/src/main/java/com/cloud/network/router/VirtualRouter.java b/api/src/main/java/com/cloud/network/router/VirtualRouter.java index 8bec5199047..c4ae4e964cf 100644 --- a/api/src/main/java/com/cloud/network/router/VirtualRouter.java +++ b/api/src/main/java/com/cloud/network/router/VirtualRouter.java @@ -52,4 +52,6 @@ public interface VirtualRouter extends VirtualMachine { Long getVpcId(); String getTemplateVersion(); + + String getScriptsVersion(); } diff --git a/api/src/main/java/com/cloud/network/vpc/VpcService.java b/api/src/main/java/com/cloud/network/vpc/VpcService.java index 4e993a71968..088239708f1 100644 --- a/api/src/main/java/com/cloud/network/vpc/VpcService.java +++ b/api/src/main/java/com/cloud/network/vpc/VpcService.java @@ -137,7 +137,7 @@ public interface VpcService { */ boolean restartVpc(RestartVPCCmd cmd) throws ConcurrentOperationException, ResourceUnavailableException, InsufficientCapacityException; - boolean restartVpc(Long networkId, boolean cleanup, boolean makeRedundant, User user) throws ConcurrentOperationException, ResourceUnavailableException, InsufficientCapacityException; + boolean restartVpc(Long networkId, boolean cleanup, boolean makeRedundant, boolean livePatch, User user) throws ConcurrentOperationException, ResourceUnavailableException, InsufficientCapacityException; /** * Returns a Private gateway found in the VPC by id diff --git a/api/src/main/java/com/cloud/server/ManagementService.java b/api/src/main/java/com/cloud/server/ManagementService.java index b266269d086..dc15521ac80 100644 --- a/api/src/main/java/com/cloud/server/ManagementService.java +++ b/api/src/main/java/com/cloud/server/ManagementService.java @@ -40,6 +40,7 @@ import org.apache.cloudstack.api.command.admin.resource.ListCapacityCmd; import org.apache.cloudstack.api.command.admin.resource.UploadCustomCertificateCmd; import org.apache.cloudstack.api.command.admin.systemvm.DestroySystemVmCmd; import org.apache.cloudstack.api.command.admin.systemvm.ListSystemVMsCmd; +import org.apache.cloudstack.api.command.admin.systemvm.PatchSystemVMCmd; import org.apache.cloudstack.api.command.admin.systemvm.RebootSystemVmCmd; import org.apache.cloudstack.api.command.admin.systemvm.ScaleSystemVMCmd; import org.apache.cloudstack.api.command.admin.systemvm.StopSystemVmCmd; @@ -428,5 +429,6 @@ public interface ManagementService { void cleanupVMReservations(); + Pair patchSystemVM(PatchSystemVMCmd cmd); } diff --git a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java index 607851d36fe..643060cee12 100644 --- a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java +++ b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java @@ -36,6 +36,7 @@ public class ApiConstants { public static final String USER_API_KEY = "userapikey"; public static final String APPLIED = "applied"; public static final String LIST_LB_VMIPS = "lbvmips"; + public static final String LIVE_PATCH = "livepatch"; public static final String AVAILABLE = "available"; public static final String BACKUP_ID = "backupid"; public static final String BACKUP_OFFERING_NAME = "backupofferingname"; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/systemvm/PatchSystemVMCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/systemvm/PatchSystemVMCmd.java new file mode 100644 index 00000000000..a0c066ce0e8 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/systemvm/PatchSystemVMCmd.java @@ -0,0 +1,108 @@ +// 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.api.command.admin.systemvm; + +import com.cloud.event.EventTypes; +import com.cloud.user.Account; +import com.cloud.utils.Pair; +import com.cloud.vm.VirtualMachine; +import org.apache.cloudstack.acl.RoleType; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.ApiErrorCode; +import org.apache.cloudstack.api.BaseAsyncCmd; +import org.apache.cloudstack.api.BaseCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.SuccessResponse; +import org.apache.cloudstack.api.response.SystemVmResponse; +import org.apache.cloudstack.context.CallContext; +import org.apache.log4j.Logger; + +@APICommand(name = PatchSystemVMCmd.APINAME, description = "Attempts to live patch systemVMs - CPVM, SSVM ", + responseObject = SuccessResponse.class, requestHasSensitiveInfo = false, + responseHasSensitiveInfo = false, authorized = { RoleType.Admin }, since = "4.17.0") +public class PatchSystemVMCmd extends BaseAsyncCmd { + public static final Logger s_logger = Logger.getLogger(PatchSystemVMCmd.class.getName()); + public static final String APINAME = "patchSystemVm"; + + ///////////////////////////////////////////////////// + //////////////// API parameters ///////////////////// + ///////////////////////////////////////////////////// + @Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = SystemVmResponse.class, + description = "patches systemVM - CPVM/SSVM with the specified ID") + private Long id; + + @Parameter(name = ApiConstants.FORCED, type = CommandType.BOOLEAN, + description = "If true, initiates copy of scripts and restart of the agent, even if the scripts version matches." + + "To be used with ID parameter only") + private Boolean force; + + ///////////////////////////////////////////////////// + /////////////////// Accessors /////////////////////// + ///////////////////////////////////////////////////// + + + public Long getId() { + return id; + } + + public boolean isForced() { + return force != null && force; + } + + ///////////////////////////////////////////////////// + /////////////// API Implementation/////////////////// + ///////////////////////////////////////////////////// + + @Override + public String getEventType() { + return EventTypes.EVENT_LIVE_PATCH_SYSTEMVM; + } + + @Override + public String getEventDescription() { + return String.format("Attempting to live patch System VM with Id: %s ", this._uuidMgr.getUuid(VirtualMachine.class, getId())); + } + + @Override + public String getCommandName() { + return APINAME.toLowerCase() + BaseCmd.RESPONSE_SUFFIX; + } + + @Override + public long getEntityOwnerId() { + Account account = CallContext.current().getCallingAccount(); + if (account != null) { + return account.getId(); + } + + return Account.ACCOUNT_ID_SYSTEM; + } + + @Override + public void execute() { + Pair patched = _mgr.patchSystemVM(this); + if (patched.first()) { + final SuccessResponse response = new SuccessResponse(getCommandName()); + response.setDisplayText(patched.second()); + setResponseObject(response); + } else { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, patched.second()); + } + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/network/RestartNetworkCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/network/RestartNetworkCmd.java index 59879562b02..d4db584149a 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/network/RestartNetworkCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/network/RestartNetworkCmd.java @@ -60,6 +60,11 @@ public class RestartNetworkCmd extends BaseAsyncCmd { @Parameter(name = ApiConstants.MAKEREDUNDANT, type = CommandType.BOOLEAN, required = false, description = "Turn the network into a network with redundant routers.", since = "4.11.1") private Boolean makeRedundant = false; + @Parameter(name = ApiConstants.LIVE_PATCH, type = CommandType.BOOLEAN, required = false, + description = "Live patches the router software before restarting it. This parameter will only work when 'cleanup' is false.", + since = "4.17.0") + private Boolean livePatch = false; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -81,6 +86,8 @@ public class RestartNetworkCmd extends BaseAsyncCmd { return makeRedundant; } + public Boolean getLivePatch() { return livePatch; } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/vpc/RestartVPCCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/vpc/RestartVPCCmd.java index aade0c2886a..ac22e669159 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/vpc/RestartVPCCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vpc/RestartVPCCmd.java @@ -54,6 +54,11 @@ public class RestartVPCCmd extends BaseAsyncCmd { @Parameter(name = ApiConstants.MAKEREDUNDANT, type = CommandType.BOOLEAN, required = false, description = "Turn a single VPC into a redundant one.") private Boolean makeredundant = false; + @Parameter(name = ApiConstants.LIVE_PATCH, type = CommandType.BOOLEAN, required = false, + description = "Live patches the router software before restarting it. This parameter will only work when 'cleanup' is false.", + since = "4.17.0") + private Boolean livePatch = false; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -70,6 +75,8 @@ public class RestartVPCCmd extends BaseAsyncCmd { return makeredundant; } + public Boolean getLivePatch() { return livePatch; } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// diff --git a/api/src/main/java/org/apache/cloudstack/api/response/DomainRouterResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/DomainRouterResponse.java index 66f40307449..a5fa2bd08c2 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/DomainRouterResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/DomainRouterResponse.java @@ -233,6 +233,10 @@ public class DomainRouterResponse extends BaseResponseWithAnnotations implements @Param(description = "Last executed health check result for the router", responseObject = RouterHealthCheckResultResponse.class, since = "4.14") List healthCheckResults; + @SerializedName("softwareversion") + @Param(description = "the version of the code / software in the router") + private String softwareVersion; + public DomainRouterResponse() { nics = new LinkedHashSet(); } @@ -490,4 +494,12 @@ public class DomainRouterResponse extends BaseResponseWithAnnotations implements public void setHealthCheckResults(List healthCheckResults) { this.healthCheckResults = healthCheckResults; } + + public String getSoftwareVersion() { + return softwareVersion; + } + + public void setSoftwareVersion(String softwareVersion) { + this.softwareVersion = softwareVersion; + } } diff --git a/api/src/test/java/org/apache/cloudstack/api/command/admin/systemvm/PatchSystemVMCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/admin/systemvm/PatchSystemVMCmdTest.java new file mode 100644 index 00000000000..125c8b56665 --- /dev/null +++ b/api/src/test/java/org/apache/cloudstack/api/command/admin/systemvm/PatchSystemVMCmdTest.java @@ -0,0 +1,92 @@ +// 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.A +package org.apache.cloudstack.api.command.admin.systemvm; + +import com.cloud.server.ManagementService; +import com.cloud.user.Account; +import com.cloud.utils.Pair; +import org.apache.cloudstack.context.CallContext; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PowerMockIgnore; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; +import org.springframework.test.util.ReflectionTestUtils; + +@RunWith(PowerMockRunner.class) +@PrepareForTest(CallContext.class) +@PowerMockIgnore({"javax.xml.*", "org.w3c.dom.*", "org.apache.xerces.*", "org.xml.*"}) +public class PatchSystemVMCmdTest { + + @Mock + private ManagementService _mgr; + + @InjectMocks + PatchSystemVMCmd cmd = new PatchSystemVMCmd(); + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + } + + @Test + public void patchValidSystemVM() { + ReflectionTestUtils.setField(cmd, "id", 1L); + Pair successResponse = new Pair<>(true, ""); + Mockito.doReturn(successResponse).when(_mgr).patchSystemVM(cmd); + try { + cmd.execute(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + @Test + public void patchInvalidSystemVM() { + ReflectionTestUtils.setField(cmd, "id", null); + Pair failureResponse = new Pair<>(false, "Please provide a valid ID of a system VM to be patched"); + Mockito.doReturn(failureResponse).when(_mgr).patchSystemVM(cmd); + try { + cmd.execute(); + } catch (Exception e) { + Assert.assertEquals(failureResponse.second(), e.getMessage()); + } + } + + @Test + public void validateArgsForPatchSystemVMApi() { + PowerMockito.mockStatic(CallContext.class); + CallContext callContextMock = PowerMockito.mock(CallContext.class); + PowerMockito.when(CallContext.current()).thenReturn(callContextMock); + Account accountMock = PowerMockito.mock(Account.class); + PowerMockito.when(callContextMock.getCallingAccount()).thenReturn(accountMock); + Mockito.when(accountMock.getId()).thenReturn(2L); + ReflectionTestUtils.setField(cmd, "id", 1L); + Assert.assertEquals((long)cmd.getId(), 1L); + Assert.assertFalse(cmd.isForced()); + Assert.assertEquals(cmd.getEntityOwnerId(), 2L); + + + } +} diff --git a/client/pom.xml b/client/pom.xml index aae0006174e..052e06184f8 100644 --- a/client/pom.xml +++ b/client/pom.xml @@ -890,32 +890,6 @@ - - - maven-antrun-plugin - 1.7 - - - - copy-systemvm - process-resources - - run - - - - - - - - - - - - - - diff --git a/core/src/main/java/com/cloud/agent/api/PatchSystemVmAnswer.java b/core/src/main/java/com/cloud/agent/api/PatchSystemVmAnswer.java new file mode 100644 index 00000000000..1065768d6ef --- /dev/null +++ b/core/src/main/java/com/cloud/agent/api/PatchSystemVmAnswer.java @@ -0,0 +1,44 @@ +// 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.agent.api; + +public class PatchSystemVmAnswer extends Answer { + + String templateVersion; + String scriptsVersion; + + public PatchSystemVmAnswer() { + } + + public PatchSystemVmAnswer(PatchSystemVmCommand cmd, String details, String templateVersion, String scriptsVersion) { + super(cmd, true, details); + this.templateVersion = templateVersion; + this.scriptsVersion = scriptsVersion; + } + + public PatchSystemVmAnswer(PatchSystemVmCommand cmd, String details) { + super(cmd, false, details); + } + + public String getTemplateVersion() { + return this.templateVersion; + } + + public String getScriptsVersion() { + return this.scriptsVersion; + } +} diff --git a/core/src/main/java/com/cloud/agent/api/PatchSystemVmCommand.java b/core/src/main/java/com/cloud/agent/api/PatchSystemVmCommand.java new file mode 100644 index 00000000000..29c5baca7f0 --- /dev/null +++ b/core/src/main/java/com/cloud/agent/api/PatchSystemVmCommand.java @@ -0,0 +1,58 @@ +// 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.agent.api; + +import java.util.HashMap; +import java.util.Map; + +public class PatchSystemVmCommand extends Command { + boolean forced; + HashMap accessDetails = new HashMap(0); + + public boolean isForced() { + return forced; + } + + public void setForced(boolean forced) { + this.forced = forced; + } + + public void setAccessDetail(final Map details) { + if (details == null) { + return; + } + for (final Map.Entry detail : details.entrySet()) { + if (detail == null) { + continue; + } + setAccessDetail(detail.getKey(), detail.getValue()); + } + } + + public void setAccessDetail(final String name, final String value) { + accessDetails.put(name, value); + } + + public String getAccessDetail(final String name) { + return accessDetails.get(name); + } + + @Override + public boolean executeInSequence() { + return false; + } +} diff --git a/core/src/main/java/com/cloud/agent/resource/virtualnetwork/VRScripts.java b/core/src/main/java/com/cloud/agent/resource/virtualnetwork/VRScripts.java index 834a11c8d6e..a76d555b002 100644 --- a/core/src/main/java/com/cloud/agent/resource/virtualnetwork/VRScripts.java +++ b/core/src/main/java/com/cloud/agent/resource/virtualnetwork/VRScripts.java @@ -41,6 +41,7 @@ public class VRScripts { public static final String IP_ALIAS_CONFIG = "ip_aliases.json"; public static final String LOAD_BALANCER_CONFIG = "load_balancer.json"; + public static final String SYSTEM_VM_PATCHED = "patched.sh"; public final static String CONFIG_CACHE_LOCATION = "/var/cache/cloud/"; public final static Duration VR_SCRIPT_EXEC_TIMEOUT = Duration.standardMinutes(10); public final static Duration CONNECTION_TIMEOUT = Duration.standardMinutes(1); diff --git a/core/src/main/java/com/cloud/agent/resource/virtualnetwork/VirtualRoutingResource.java b/core/src/main/java/com/cloud/agent/resource/virtualnetwork/VirtualRoutingResource.java index 54b9d38a962..8a265a78123 100644 --- a/core/src/main/java/com/cloud/agent/resource/virtualnetwork/VirtualRoutingResource.java +++ b/core/src/main/java/com/cloud/agent/resource/virtualnetwork/VirtualRoutingResource.java @@ -34,6 +34,7 @@ import java.util.concurrent.locks.ReentrantLock; import javax.naming.ConfigurationException; +import com.cloud.utils.PasswordGenerator; import org.apache.cloudstack.ca.SetupCertificateAnswer; import org.apache.cloudstack.ca.SetupCertificateCommand; import org.apache.cloudstack.ca.SetupKeyStoreCommand; @@ -174,11 +175,12 @@ public class VirtualRoutingResource { } private Answer execute(final SetupCertificateCommand cmd) { - final String args = String.format("/usr/local/cloud/systemvm/conf/agent.properties " + + final String args = String.format("/usr/local/cloud/systemvm/conf/agent.properties %s " + "/usr/local/cloud/systemvm/conf/%s %s " + "/usr/local/cloud/systemvm/conf/%s \"%s\" " + "/usr/local/cloud/systemvm/conf/%s \"%s\" " + "/usr/local/cloud/systemvm/conf/%s \"%s\"", + PasswordGenerator.generateRandomPassword(16), KeyStoreUtils.KS_FILENAME, KeyStoreUtils.SSH_MODE, KeyStoreUtils.CERT_FILENAME, @@ -582,4 +584,23 @@ public class VirtualRoutingResource { } return new Answer(cmd, false, "Fail to recognize aggregation action " + action.toString()); } + + public boolean isSystemVMSetup(String vmName, String controlIp) throws InterruptedException { + if (vmName.startsWith("s-") || vmName.startsWith("v-")) { + ScriptConfigItem scriptConfigItem = new ScriptConfigItem(VRScripts.SYSTEM_VM_PATCHED, "/opt/cloud/bin/keystore*"); + ExecutionResult result = new ExecutionResult(false, ""); + int retries = 0; + while (!result.isSuccess() && retries < 600) { + result = applyConfigToVR(controlIp, scriptConfigItem, VRScripts.VR_SCRIPT_EXEC_TIMEOUT); + if (result.isSuccess()) { + return true; + } + + retries++; + Thread.sleep(1000); + } + return false; + } + return true; + } } diff --git a/core/src/main/java/com/cloud/resource/ServerResource.java b/core/src/main/java/com/cloud/resource/ServerResource.java index 16ac00ed176..1602a78d9a4 100644 --- a/core/src/main/java/com/cloud/resource/ServerResource.java +++ b/core/src/main/java/com/cloud/resource/ServerResource.java @@ -26,12 +26,19 @@ import com.cloud.agent.api.PingCommand; import com.cloud.agent.api.StartupCommand; import com.cloud.host.Host; import com.cloud.utils.component.Manager; +import org.apache.cloudstack.utils.security.KeyStoreUtils; /** * ServerResource is a generic container to execute commands sent */ public interface ServerResource extends Manager { + String[] systemVmPatchFiles = new String[] { "agent.zip", "cloud-scripts.tgz", "patch-sysvms.sh" }; + String[] certificateFiles = new String[] {KeyStoreUtils.CERT_FILENAME, KeyStoreUtils.CACERT_FILENAME, KeyStoreUtils.PKEY_FILENAME}; + + String SSHKEYSPATH = "/root/.ssh"; + String SSHPRVKEYPATH = SSHKEYSPATH +"/id_rsa.cloud"; + /** * @return Host.Type type of the computing server we have. */ diff --git a/debian/cloudstack-common.install b/debian/cloudstack-common.install index 9a9cf3bbc09..08f56d4f117 100644 --- a/debian/cloudstack-common.install +++ b/debian/cloudstack-common.install @@ -15,7 +15,6 @@ # specific language governing permissions and limitations # under the License. -/usr/share/cloudstack-common/vms/systemvm.iso /usr/share/cloudstack-common/scripts/installer/* /usr/share/cloudstack-common/scripts/network/* /usr/share/cloudstack-common/scripts/storage/* @@ -29,6 +28,7 @@ /usr/share/cloudstack-common/scripts/vm/hypervisor/vmware/* /usr/share/cloudstack-common/scripts/vm/hypervisor/xenserver/* /usr/share/cloudstack-common/lib/* +/usr/share/cloudstack-common/vms/* /usr/bin/cloudstack-set-guest-password /usr/bin/cloudstack-set-guest-sshkey /usr/share/pyshared diff --git a/debian/rules b/debian/rules index b590e5d40fd..7537e07a6b6 100755 --- a/debian/rules +++ b/debian/rules @@ -117,11 +117,13 @@ override_dh_auto_install: mkdir $(DESTDIR)/usr/share/$(PACKAGE)-common/scripts mkdir $(DESTDIR)/usr/share/$(PACKAGE)-common/setup mkdir $(DESTDIR)/usr/share/$(PACKAGE)-common/lib + mkdir $(DESTDIR)/usr/share/$(PACKAGE)-common/vms cp -r scripts/installer $(DESTDIR)/usr/share/$(PACKAGE)-common/scripts cp -r scripts/network $(DESTDIR)/usr/share/$(PACKAGE)-common/scripts cp -r scripts/storage $(DESTDIR)/usr/share/$(PACKAGE)-common/scripts cp -r scripts/util $(DESTDIR)/usr/share/$(PACKAGE)-common/scripts cp -r scripts/vm $(DESTDIR)/usr/share/$(PACKAGE)-common/scripts + cp -r systemvm/dist/* $(DESTDIR)/usr/share/$(PACKAGE)-common/vms install -D client/target/utilities/bin/cloud-migrate-databases $(DESTDIR)/usr/bin/cloudstack-migrate-databases install -D client/target/utilities/bin/cloud-set-guest-password $(DESTDIR)/usr/bin/cloudstack-set-guest-password install -D client/target/utilities/bin/cloud-set-guest-sshkey $(DESTDIR)/usr/bin/cloudstack-set-guest-sshkey @@ -129,7 +131,7 @@ override_dh_auto_install: install -D client/target/utilities/bin/cloud-setup-management $(DESTDIR)/usr/bin/cloudstack-setup-management install -D client/target/utilities/bin/cloud-setup-encryption $(DESTDIR)/usr/bin/cloudstack-setup-encryption install -D client/target/utilities/bin/cloud-sysvmadm $(DESTDIR)/usr/bin/cloudstack-sysvmadm - install -D systemvm/dist/systemvm.iso $(DESTDIR)/usr/share/$(PACKAGE)-common/vms/systemvm.iso + install -D systemvm/dist/* $(DESTDIR)/usr/share/$(PACKAGE)-common/vms/ # We need jasypt for cloud-install-sys-tmplt, so this is a nasty hack to get it into the right place install -D agent/target/dependencies/jasypt-1.9.3.jar $(DESTDIR)/usr/share/$(PACKAGE)-common/lib diff --git a/engine/api/src/main/java/com/cloud/vm/VirtualMachineGuru.java b/engine/api/src/main/java/com/cloud/vm/VirtualMachineGuru.java index 7611df820c8..f8032bf4b0e 100644 --- a/engine/api/src/main/java/com/cloud/vm/VirtualMachineGuru.java +++ b/engine/api/src/main/java/com/cloud/vm/VirtualMachineGuru.java @@ -20,7 +20,14 @@ import com.cloud.agent.api.Answer; import com.cloud.agent.manager.Commands; import com.cloud.deploy.DeployDestination; import com.cloud.exception.ResourceUnavailableException; +import com.cloud.utils.PasswordGenerator; +import com.cloud.utils.exception.CloudRuntimeException; +import org.apache.cloudstack.ca.CAManager; +import org.apache.cloudstack.framework.ca.Certificate; +import org.apache.cloudstack.utils.security.CertUtils; +import org.apache.cloudstack.utils.security.KeyStoreUtils; +import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.Base64; @@ -72,4 +79,22 @@ public interface VirtualMachineGuru { } return base64EncodedPublicKey; } + + public static String getEncodedString(String certificate) { + return Base64.getEncoder().encodeToString(certificate.replace("\n", KeyStoreUtils.CERT_NEWLINE_ENCODER).replace(" ", KeyStoreUtils.CERT_SPACE_ENCODER).getBytes(StandardCharsets.UTF_8)); + } + + static void appendCertificateDetails(StringBuilder buf, Certificate certificate) { + try { + buf.append(" certificate=").append(getEncodedString(CertUtils.x509CertificateToPem(certificate.getClientCertificate()))); + buf.append(" cacertificate=").append(getEncodedString(CertUtils.x509CertificatesToPem(certificate.getCaCertificates()))); + if (certificate.getPrivateKey() != null) { + buf.append(" privatekey=").append(getEncodedString(CertUtils.privateKeyToPem(certificate.getPrivateKey()))); + } + } catch (IOException e) { + throw new CloudRuntimeException("Failed to transform X509 cert to PEM format", e); + } + buf.append(" keystore_password=").append(getEncodedString(PasswordGenerator.generateRandomPassword(16))); + buf.append(" validity=").append(CAManager.CertValidityPeriod.value()); + } } diff --git a/engine/api/src/main/java/org/apache/cloudstack/engine/orchestration/service/NetworkOrchestrationService.java b/engine/api/src/main/java/org/apache/cloudstack/engine/orchestration/service/NetworkOrchestrationService.java index 9a08aebc8ab..bab36f4703b 100644 --- a/engine/api/src/main/java/org/apache/cloudstack/engine/orchestration/service/NetworkOrchestrationService.java +++ b/engine/api/src/main/java/org/apache/cloudstack/engine/orchestration/service/NetworkOrchestrationService.java @@ -253,7 +253,7 @@ public interface NetworkOrchestrationService { NetworkProfile convertNetworkToNetworkProfile(long networkId); - boolean restartNetwork(Long networkId, Account callerAccount, User callerUser, boolean cleanup) throws ConcurrentOperationException, ResourceUnavailableException, + boolean restartNetwork(Long networkId, Account callerAccount, User callerUser, boolean cleanup, boolean livePatch) throws ConcurrentOperationException, ResourceUnavailableException, InsufficientCapacityException; boolean shutdownNetworkElementsAndResources(ReservationContext context, boolean b, Network network); diff --git a/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java b/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java index a17e92a97c0..64e4ad89154 100755 --- a/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java +++ b/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java @@ -552,7 +552,7 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac advanceExpunge(vm); } - private boolean expungeCommandCanBypassHostMaintenance(VirtualMachine vm) { + private boolean isValidSystemVMType(VirtualMachine vm) { return VirtualMachine.Type.SecondaryStorageVm.equals(vm.getType()) || VirtualMachine.Type.ConsoleProxy.equals(vm.getType()); } @@ -604,7 +604,7 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac final Commands cmds = new Commands(Command.OnError.Stop); for (final Command volumeExpungeCommand : volumeExpungeCommands) { - volumeExpungeCommand.setBypassHostMaintenance(expungeCommandCanBypassHostMaintenance(vm)); + volumeExpungeCommand.setBypassHostMaintenance(isValidSystemVMType(vm)); cmds.addCommand(volumeExpungeCommand); } @@ -690,7 +690,7 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac return; } for (final Command command : cmdList) { - command.setBypassHostMaintenance(expungeCommandCanBypassHostMaintenance(vm)); + command.setBypassHostMaintenance(isValidSystemVMType(vm)); if (s_logger.isTraceEnabled()) { s_logger.trace(String.format("Adding expunge command [%s] for VM [%s]", command.toString(), vm.toString())); } @@ -1192,8 +1192,12 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac handlePath(vmTO.getDisks(), vm.getHypervisorType()); Commands cmds = new Commands(Command.OnError.Stop); + final Map sshAccessDetails = _networkMgr.getSystemVMAccessDetails(vm); + final Map ipAddressDetails = new HashMap<>(sshAccessDetails); + ipAddressDetails.remove(NetworkElementCommand.ROUTER_NAME); - cmds.addCommand(new StartCommand(vmTO, dest.getHost(), getExecuteInSequence(vm.getHypervisorType()))); + StartCommand command = new StartCommand(vmTO, dest.getHost(), getExecuteInSequence(vm.getHypervisorType())); + cmds.addCommand(command); vmGuru.finalizeDeployment(cmds, vmProfile, dest, ctx); @@ -1247,12 +1251,16 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac final Host vmHost = _hostDao.findById(destHostId); if (vmHost != null && (VirtualMachine.Type.ConsoleProxy.equals(vm.getType()) || VirtualMachine.Type.SecondaryStorageVm.equals(vm.getType())) && caManager.canProvisionCertificates()) { - final Map sshAccessDetails = _networkMgr.getSystemVMAccessDetails(vm); for (int retries = 3; retries > 0; retries--) { try { - setupAgentSecurity(vmHost, sshAccessDetails, vm); + final Certificate certificate = caManager.issueCertificate(null, Arrays.asList(vm.getHostName(), vm.getInstanceName()), + new ArrayList<>(ipAddressDetails.values()), CAManager.CertValidityPeriod.value(), null); + final boolean result = caManager.deployCertificate(vmHost, certificate, false, sshAccessDetails); + if (!result) { + s_logger.error("Failed to setup certificate for system vm: " + vm.getInstanceName()); + } return; - } catch (final AgentUnavailableException | OperationTimedoutException e) { + } catch (final Exception e) { s_logger.error("Retrying after catching exception while trying to secure agent for systemvm id=" + vm.getId(), e); } } diff --git a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkOrchestrator.java b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkOrchestrator.java index f422a013894..aeaecb42c6d 100644 --- a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkOrchestrator.java +++ b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkOrchestrator.java @@ -38,6 +38,7 @@ import java.util.stream.Collectors; import javax.inject.Inject; import javax.naming.ConfigurationException; +import com.cloud.server.ManagementServer; import org.apache.cloudstack.acl.ControlledEntity.ACLType; import org.apache.cloudstack.annotation.AnnotationService; import org.apache.cloudstack.annotation.dao.AnnotationDao; @@ -306,7 +307,7 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra @Inject VMNetworkMapDao _vmNetworkMapDao; @Inject - DomainRouterDao _routerDao; + DomainRouterDao routerDao; @Inject RemoteAccessVpnDao _remoteAccessVpnDao; @Inject @@ -320,6 +321,8 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra @Inject private AnnotationDao annotationDao; @Inject + public ManagementServer mgr; + @Inject NetworkPermissionDao networkPermissionDao; List networkGurus; @@ -1625,8 +1628,8 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra throw new UnsupportedOperationException("Cannot update the network resources in sequence when providers other than virtualrouter are used"); } //check if routers are in correct state before proceeding with the update - List routers = _routerDao.listByNetworkAndRole(network.getId(), VirtualRouter.Role.VIRTUAL_ROUTER); - for (DomainRouterVO router : routers) { + List routers = routerDao.listByNetworkAndRole(network.getId(), VirtualRouter.Role.VIRTUAL_ROUTER); + for (DomainRouterVO router : routers){ if (router.getRedundantState() == VirtualRouter.RedundantState.UNKNOWN) { if (!forced) { throw new CloudRuntimeException("Domain router: " + router.getInstanceName() + " is in unknown state, Cannot update network. set parameter forced to true for forcing an update"); @@ -3310,7 +3313,7 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra } @Override - public boolean restartNetwork(final Long networkId, final Account callerAccount, final User callerUser, final boolean cleanup) throws ConcurrentOperationException, ResourceUnavailableException, + public boolean restartNetwork(final Long networkId, final Account callerAccount, final User callerUser, final boolean cleanup, final boolean livePatch) throws ConcurrentOperationException, ResourceUnavailableException, InsufficientCapacityException { boolean status = true; boolean restartRequired = false; @@ -3329,6 +3332,24 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra } setRestartRequired(network, restartRequired); return status; + } else if (livePatch) { + List domainRouters = routerDao.listByNetworkAndRole(network.getId(), VirtualRouter.Role.VIRTUAL_ROUTER, VirtualRouter.Role.INTERNAL_LB_VM); + for (DomainRouterVO router: domainRouters) { + try { + VMInstanceVO instanceVO = _vmDao.findById(router.getId()); + if (instanceVO == null) { + s_logger.info("Did not find a virtual router instance for the network"); + continue; + } + Pair patched = mgr.updateSystemVM(instanceVO, true); + if (patched.first()) { + s_logger.info(String.format("Successfully patched router %s", router)); + } + } catch (CloudRuntimeException e) { + throw new CloudRuntimeException(String.format("Failed to live patch router: %s", router), e); + } + + } } s_logger.debug("Implementing the network " + network + " elements and resources as a part of network restart without cleanup"); @@ -3438,10 +3459,10 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra return false; } s_logger.debug("Performing rolling restart of routers of network " + network); - destroyExpendableRouters(_routerDao.findByNetwork(network.getId()), context); + destroyExpendableRouters(routerDao.findByNetwork(network.getId()), context); final List providersToImplement = getNetworkProviders(network.getId()); - final List oldRouters = _routerDao.findByNetwork(network.getId()); + final List oldRouters = routerDao.findByNetwork(network.getId()); // Deploy a new router if (oldRouters.size() > 0) { @@ -3474,7 +3495,7 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra implementNetworkElementsAndResources(dest, context, network, offering); } - return areRoutersRunning(_routerDao.findByNetwork(network.getId())); + return areRoutersRunning(routerDao.findByNetwork(network.getId())); } private void setRestartRequired(final NetworkVO network, final boolean restartRequired) { diff --git a/engine/schema/src/main/java/com/cloud/upgrade/SystemVmTemplateRegistration.java b/engine/schema/src/main/java/com/cloud/upgrade/SystemVmTemplateRegistration.java index 1eb3fdd20bb..0ddaf00643f 100644 --- a/engine/schema/src/main/java/com/cloud/upgrade/SystemVmTemplateRegistration.java +++ b/engine/schema/src/main/java/com/cloud/upgrade/SystemVmTemplateRegistration.java @@ -681,8 +681,8 @@ public class SystemVmTemplateRegistration { /** * This method parses the metadata file consisting of the systemVM templates information - * @return the version of the systemvm template that is to be used. This is done to in order - * to fallback on the latest available version of the systemVM template when there does not + * @return the version of the systemvm template that is to be used. This is done in order + * to fallback on the latest available version of the systemVM template when there doesn't * exist a template corresponding to the current code version. */ public static String parseMetadataFile() { diff --git a/engine/schema/src/main/java/com/cloud/vm/DomainRouterVO.java b/engine/schema/src/main/java/com/cloud/vm/DomainRouterVO.java index 8bd973af531..1a619734ff3 100644 --- a/engine/schema/src/main/java/com/cloud/vm/DomainRouterVO.java +++ b/engine/schema/src/main/java/com/cloud/vm/DomainRouterVO.java @@ -74,6 +74,9 @@ public class DomainRouterVO extends VMInstanceVO implements VirtualRouter { @Enumerated(EnumType.STRING) private UpdateState updateState; + @Column(name= "software_version") + private String softwareVersion; + public DomainRouterVO(final long id, final long serviceOfferingId, final long elementId, final String name, final long templateId, final HypervisorType hypervisorType, final long guestOSId, final long domainId, final long accountId, final long userId, final boolean isRedundantRouter, final RedundantState redundantState, final boolean haEnabled, final boolean stopPending, final Long vpcId) { @@ -211,4 +214,12 @@ public class DomainRouterVO extends VMInstanceVO implements VirtualRouter { public String getName() { return instanceName; } + + public String getSoftwareVersion() { + return softwareVersion; + } + + public void setSoftwareVersion(String softwareVersion) { + this.softwareVersion = softwareVersion; + } } diff --git a/engine/schema/src/main/java/com/cloud/vm/dao/ConsoleProxyDaoImpl.java b/engine/schema/src/main/java/com/cloud/vm/dao/ConsoleProxyDaoImpl.java index 36ff74b11b3..dcf6505ce22 100644 --- a/engine/schema/src/main/java/com/cloud/vm/dao/ConsoleProxyDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/vm/dao/ConsoleProxyDaoImpl.java @@ -52,7 +52,7 @@ public class ConsoleProxyDaoImpl extends GenericDaoBase im + " AS runningVm ON c.id = runningVm.proxy_id WHERE i.state='Running' " + " GROUP BY c.id"; // - // query SQL for returnning running VM count at data center basis + // query SQL for returning running VM count at data center basis // private static final String DATACENTER_VM_MATRIX = "SELECT d.id, d.name, count(v.id) AS count" + " FROM data_center AS d LEFT JOIN vm_instance AS v ON v.data_center_id=d.id " diff --git a/engine/schema/src/main/java/com/cloud/vm/dao/DomainRouterDao.java b/engine/schema/src/main/java/com/cloud/vm/dao/DomainRouterDao.java index c4fd2f6c974..8e965b210be 100644 --- a/engine/schema/src/main/java/com/cloud/vm/dao/DomainRouterDao.java +++ b/engine/schema/src/main/java/com/cloud/vm/dao/DomainRouterDao.java @@ -121,7 +121,7 @@ public interface DomainRouterDao extends GenericDao { List listByNetworkAndPodAndRole(long networkId, long podId, Role role); - List listByNetworkAndRole(long networkId, Role role); + List listByNetworkAndRole(long networkId, Role... roles); List listByElementId(long elementId); diff --git a/engine/schema/src/main/java/com/cloud/vm/dao/DomainRouterDaoImpl.java b/engine/schema/src/main/java/com/cloud/vm/dao/DomainRouterDaoImpl.java index 85e75178869..63cdc042b26 100644 --- a/engine/schema/src/main/java/com/cloud/vm/dao/DomainRouterDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/vm/dao/DomainRouterDaoImpl.java @@ -79,7 +79,7 @@ public class DomainRouterDaoImpl extends GenericDaoBase im AllFieldsSearch = createSearchBuilder(); AllFieldsSearch.and("dc", AllFieldsSearch.entity().getDataCenterId(), Op.EQ); AllFieldsSearch.and("account", AllFieldsSearch.entity().getAccountId(), Op.EQ); - AllFieldsSearch.and("role", AllFieldsSearch.entity().getRole(), Op.EQ); + AllFieldsSearch.and("role", AllFieldsSearch.entity().getRole(), Op.IN); AllFieldsSearch.and("domainId", AllFieldsSearch.entity().getDomainId(), Op.EQ); AllFieldsSearch.and("host", AllFieldsSearch.entity().getHostId(), Op.EQ); AllFieldsSearch.and("lastHost", AllFieldsSearch.entity().getLastHostId(), Op.EQ); @@ -338,10 +338,10 @@ public class DomainRouterDaoImpl extends GenericDaoBase im } @Override - public List listByNetworkAndRole(final long networkId, final Role role) { + public List listByNetworkAndRole(final long networkId, final Role... roles) { final SearchCriteria sc = AllFieldsSearch.create(); sc.setJoinParameters("networkRouter", "networkId", networkId); - sc.setParameters("role", role); + sc.setParameters("role", (Object[])roles); return listBy(sc); } diff --git a/engine/schema/src/main/resources/META-INF/db/schema-41610to41700.sql b/engine/schema/src/main/resources/META-INF/db/schema-41610to41700.sql index d597fed0e8c..e6fd3708ca8 100644 --- a/engine/schema/src/main/resources/META-INF/db/schema-41610to41700.sql +++ b/engine/schema/src/main/resources/META-INF/db/schema-41610to41700.sql @@ -558,6 +558,44 @@ FROM LEFT JOIN `user_vm_details` `custom_ram_size` ON (((`custom_ram_size`.`vm_id` = `vm_instance`.`id`) AND (`custom_ram_size`.`name` = 'memory')))); +INSERT INTO `cloud`.`role_permissions` (`uuid`, `role_id`, `rule`, `permission`, `sort_order`) SELECT UUID(), 3, 'listConfigurations', 'ALLOW', (SELECT MAX(`sort_order`)+1 FROM `cloud`.`role_permissions`) ON DUPLICATE KEY UPDATE rule=rule; +INSERT INTO `cloud`.`role_permissions` (`uuid`, `role_id`, `rule`, `permission`, `sort_order`) SELECT UUID(), 3, 'updateConfiguration', 'ALLOW', (SELECT MAX(`sort_order`)+1 FROM `cloud`.`role_permissions`) ON DUPLICATE KEY UPDATE rule=rule; + +-- table for network permissions +CREATE TABLE `cloud`.`network_permissions` ( + `id` bigint unsigned NOT NULL auto_increment, + `network_id` bigint unsigned NOT NULL, + `account_id` bigint unsigned NOT NULL, + PRIMARY KEY (`id`), + INDEX `i_network_permission_network_id`(`network_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +INSERT INTO `cloud`.`user_vm_details`(`vm_id`, `name`, `value`) + SELECT `user_vm_details`.`vm_id`, 'SSH.KeyPairNames', `ssh_keypairs`.`keypair_name` + FROM `cloud`.`user_vm_details` + INNER JOIN `cloud`.`ssh_keypairs` ON ssh_keypairs.public_key = user_vm_details.value + INNER JOIN `cloud`.`vm_instance` ON vm_instance.id = user_vm_details.vm_id + WHERE ssh_keypairs.account_id = vm_instance.account_id; + +ALTER TABLE `cloud`.`kubernetes_cluster` ADD COLUMN `security_group_id` bigint unsigned DEFAULT NULL, +ADD CONSTRAINT `fk_kubernetes_cluster__security_group_id` FOREIGN KEY `fk_kubernetes_cluster__security_group_id`(`security_group_id`) REFERENCES `security_group`(`id`) ON DELETE CASCADE; + +-- PR#5984 Create table to persist VM stats. +DROP TABLE IF EXISTS `cloud`.`vm_stats`; +CREATE TABLE `cloud`.`vm_stats` ( + `id` bigint unsigned NOT NULL auto_increment COMMENT 'id', + `vm_id` bigint unsigned NOT NULL, + `mgmt_server_id` bigint unsigned NOT NULL, + `timestamp` datetime NOT NULL, + `vm_stats_data` text NOT NULL, + PRIMARY KEY (`id`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- PR#5984 Update name for global configuration vm.stats.increment.metrics +Update configuration set name='vm.stats.increment.metrics' where name='vm.stats.increment.metrics.in.memory'; + +ALTER TABLE `cloud`.`domain_router` ADD COLUMN `software_version` varchar(100) COMMENT 'Software version'; + DROP VIEW IF EXISTS `cloud`.`domain_router_view`; CREATE VIEW `cloud`.`domain_router_view` AS select @@ -629,7 +667,8 @@ CREATE VIEW `cloud`.`domain_router_view` AS domain_router.is_redundant_router is_redundant_router, domain_router.redundant_state redundant_state, domain_router.stop_pending stop_pending, - domain_router.role role + domain_router.role role, + domain_router.software_version software_version from `cloud`.`domain_router` inner join @@ -659,39 +698,4 @@ CREATE VIEW `cloud`.`domain_router_view` AS left join `cloud`.`async_job` ON async_job.instance_id = vm_instance.id and async_job.instance_type = 'DomainRouter' - and async_job.job_status = 0; -INSERT INTO `cloud`.`role_permissions` (`uuid`, `role_id`, `rule`, `permission`, `sort_order`) SELECT UUID(), 3, 'listConfigurations', 'ALLOW', (SELECT MAX(`sort_order`)+1 FROM `cloud`.`role_permissions`) ON DUPLICATE KEY UPDATE rule=rule; -INSERT INTO `cloud`.`role_permissions` (`uuid`, `role_id`, `rule`, `permission`, `sort_order`) SELECT UUID(), 3, 'updateConfiguration', 'ALLOW', (SELECT MAX(`sort_order`)+1 FROM `cloud`.`role_permissions`) ON DUPLICATE KEY UPDATE rule=rule; - --- table for network permissions -CREATE TABLE `cloud`.`network_permissions` ( - `id` bigint unsigned NOT NULL auto_increment, - `network_id` bigint unsigned NOT NULL, - `account_id` bigint unsigned NOT NULL, - PRIMARY KEY (`id`), - INDEX `i_network_permission_network_id`(`network_id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8; - -INSERT INTO `cloud`.`user_vm_details`(`vm_id`, `name`, `value`) - SELECT `user_vm_details`.`vm_id`, 'SSH.KeyPairNames', `ssh_keypairs`.`keypair_name` - FROM `cloud`.`user_vm_details` - INNER JOIN `cloud`.`ssh_keypairs` ON ssh_keypairs.public_key = user_vm_details.value - INNER JOIN `cloud`.`vm_instance` ON vm_instance.id = user_vm_details.vm_id - WHERE ssh_keypairs.account_id = vm_instance.account_id; - -ALTER TABLE `cloud`.`kubernetes_cluster` ADD COLUMN `security_group_id` bigint unsigned DEFAULT NULL, -ADD CONSTRAINT `fk_kubernetes_cluster__security_group_id` FOREIGN KEY `fk_kubernetes_cluster__security_group_id`(`security_group_id`) REFERENCES `security_group`(`id`) ON DELETE CASCADE; - --- PR#5984 Create table to persist VM stats. -DROP TABLE IF EXISTS `cloud`.`vm_stats`; -CREATE TABLE `cloud`.`vm_stats` ( - `id` bigint unsigned NOT NULL auto_increment COMMENT 'id', - `vm_id` bigint unsigned NOT NULL, - `mgmt_server_id` bigint unsigned NOT NULL, - `timestamp` datetime NOT NULL, - `vm_stats_data` text NOT NULL, - PRIMARY KEY (`id`) - ) ENGINE=InnoDB DEFAULT CHARSET=utf8; - --- PR#5984 Update name for global configuration vm.stats.increment.metrics -Update configuration set name='vm.stats.increment.metrics' where name='vm.stats.increment.metrics.in.memory'; + and async_job.job_status = 0; \ No newline at end of file diff --git a/packaging/centos7/cloud.spec b/packaging/centos7/cloud.spec index 4288463d2eb..431dbee9302 100644 --- a/packaging/centos7/cloud.spec +++ b/packaging/centos7/cloud.spec @@ -236,7 +236,7 @@ mkdir -p ${RPM_BUILD_ROOT}%{_datadir}/%{name}-common/vms mkdir -p ${RPM_BUILD_ROOT}%{python_sitearch}/ mkdir -p ${RPM_BUILD_ROOT}/usr/bin cp -r scripts/* ${RPM_BUILD_ROOT}%{_datadir}/%{name}-common/scripts -install -D systemvm/dist/systemvm.iso ${RPM_BUILD_ROOT}%{_datadir}/%{name}-common/vms/systemvm.iso +install -D systemvm/dist/* ${RPM_BUILD_ROOT}%{_datadir}/%{name}-common/vms/ install python/lib/cloud_utils.py ${RPM_BUILD_ROOT}%{python_sitearch}/cloud_utils.py cp -r python/lib/cloudutils ${RPM_BUILD_ROOT}%{python_sitearch}/ python3 -m py_compile ${RPM_BUILD_ROOT}%{python_sitearch}/cloud_utils.py @@ -627,7 +627,9 @@ pip3 install --upgrade urllib3 %dir %attr(0755,root,root) %{_datadir}/%{name}-common/vms %attr(0755,root,root) %{_datadir}/%{name}-common/scripts %attr(0755,root,root) /usr/bin/cloudstack-sccs -%attr(0644, root, root) %{_datadir}/%{name}-common/vms/systemvm.iso +%attr(0644, root, root) %{_datadir}/%{name}-common/vms/agent.zip +%attr(0644, root, root) %{_datadir}/%{name}-common/vms/cloud-scripts.tgz +%attr(0644, root, root) %{_datadir}/%{name}-common/vms/patch-sysvms.sh %attr(0644,root,root) %{python_sitearch}/cloud_utils.py %attr(0644,root,root) %{python_sitearch}/__pycache__/* %attr(0644,root,root) %{python_sitearch}/cloudutils/* diff --git a/packaging/centos8/cloud.spec b/packaging/centos8/cloud.spec index 099ad59b3d5..893b7b56cd8 100644 --- a/packaging/centos8/cloud.spec +++ b/packaging/centos8/cloud.spec @@ -229,7 +229,7 @@ mkdir -p ${RPM_BUILD_ROOT}%{_datadir}/%{name}-common/vms mkdir -p ${RPM_BUILD_ROOT}%{python_sitearch}/ mkdir -p ${RPM_BUILD_ROOT}/usr/bin cp -r scripts/* ${RPM_BUILD_ROOT}%{_datadir}/%{name}-common/scripts -install -D systemvm/dist/systemvm.iso ${RPM_BUILD_ROOT}%{_datadir}/%{name}-common/vms/systemvm.iso +install -D systemvm/dist/* ${RPM_BUILD_ROOT}%{_datadir}/%{name}-common/vms/ install python/lib/cloud_utils.py ${RPM_BUILD_ROOT}%{python_sitearch}/cloud_utils.py cp -r python/lib/cloudutils ${RPM_BUILD_ROOT}%{python_sitearch}/ python3 -m py_compile ${RPM_BUILD_ROOT}%{python_sitearch}/cloud_utils.py @@ -615,7 +615,9 @@ pip install --upgrade /usr/share/cloudstack-marvin/Marvin-*.tar.gz %dir %attr(0755,root,root) %{_datadir}/%{name}-common/vms %attr(0755,root,root) %{_datadir}/%{name}-common/scripts %attr(0755,root,root) /usr/bin/cloudstack-sccs -%attr(0644, root, root) %{_datadir}/%{name}-common/vms/systemvm.iso +%attr(0644, root, root) %{_datadir}/%{name}-common/vms/agent.zip +%attr(0644, root, root) %{_datadir}/%{name}-common/vms/cloud-scripts.tgz +%attr(0644, root, root) %{_datadir}/%{name}-common/vms/patch-sysvms.sh %attr(0644,root,root) %{python_sitearch}/cloud_utils.py %attr(0644,root,root) %{python_sitearch}/__pycache__/* %attr(0644,root,root) %{python_sitearch}/cloudutils/* diff --git a/packaging/suse15/cloud.spec b/packaging/suse15/cloud.spec index bf6ef667109..9f2dc378219 100644 --- a/packaging/suse15/cloud.spec +++ b/packaging/suse15/cloud.spec @@ -231,7 +231,7 @@ mkdir -p ${RPM_BUILD_ROOT}%{_datadir}/%{name}-common/vms mkdir -p ${RPM_BUILD_ROOT}%{python_sitearch}/ mkdir -p ${RPM_BUILD_ROOT}/usr/bin cp -r scripts/* ${RPM_BUILD_ROOT}%{_datadir}/%{name}-common/scripts -install -D systemvm/dist/systemvm.iso ${RPM_BUILD_ROOT}%{_datadir}/%{name}-common/vms/systemvm.iso +install -D systemvm/dist/* ${RPM_BUILD_ROOT}%{_datadir}/%{name}-common/vms/ install python/lib/cloud_utils.py ${RPM_BUILD_ROOT}%{python_sitearch}/cloud_utils.py cp -r python/lib/cloudutils ${RPM_BUILD_ROOT}%{python_sitearch}/ python3 -m py_compile ${RPM_BUILD_ROOT}%{python_sitearch}/cloud_utils.py @@ -609,7 +609,9 @@ pip install --upgrade /usr/share/cloudstack-marvin/Marvin-*.tar.gz %dir %attr(0755,root,root) %{_datadir}/%{name}-common/vms %attr(0755,root,root) %{_datadir}/%{name}-common/scripts %attr(0755,root,root) /usr/bin/cloudstack-sccs -%attr(0644, root, root) %{_datadir}/%{name}-common/vms/systemvm.iso +%attr(0644, root, root) %{_datadir}/%{name}-common/vms/agent.zip +%attr(0644, root, root) %{_datadir}/%{name}-common/vms/cloud-scripts.tgz +%attr(0644, root, root) %{_datadir}/%{name}-common/vms/patch-sysvms.sh %attr(0644,root,root) %{python_sitearch}/cloud_utils.py %attr(0644,root,root) %{python_sitearch}/__pycache__/* %attr(0644,root,root) %{python_sitearch}/cloudutils/* diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java index 3a0f3100f7d..97be335b845 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java @@ -283,6 +283,7 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv private static final String AARCH64 = "aarch64"; public static final String RESIZE_NOTIFY_ONLY = "NOTIFYONLY"; + public static final String BASEPATH = "/usr/share/cloudstack-common/vms/"; private String _modifyVlanPath; private String _versionstringpath; @@ -309,8 +310,6 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv private static final int NUMMEMSTATS =2; private KVMHAMonitor _monitor; - public static final String SSHKEYSPATH = "/root/.ssh"; - public static final String SSHPRVKEYPATH = SSHKEYSPATH + File.separator + "id_rsa.cloud"; public static final String SSHPUBKEYPATH = SSHKEYSPATH + File.separator + "id_rsa.pub.cloud"; public static final String DEFAULTDOMRSSHPORT = "3922"; @@ -412,7 +411,7 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv s_powerStatesTable.put(DomainState.VIR_DOMAIN_SHUTDOWN, PowerState.PowerOff); } - private VirtualRoutingResource _virtRouterResource; + public VirtualRoutingResource _virtRouterResource; private String _pingTestPath; @@ -472,7 +471,7 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv try { SshHelper.scpTo(routerIp, 3922, "root", permKey, null, path, content.getBytes(), filename, null); } catch (final Exception e) { - s_logger.warn("Fail to create file " + path + filename + " in VR " + routerIp, e); + s_logger.warn("Failed to create file " + path + filename + " in VR " + routerIp, e); details = e.getMessage(); success = false; } @@ -1171,20 +1170,6 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv _storagePoolMgr = new KVMStoragePoolManager(_storage, _monitor); - _sysvmISOPath = (String)params.get("systemvm.iso.path"); - if (_sysvmISOPath == null) { - final String[] isoPaths = {"/usr/share/cloudstack-common/vms/systemvm.iso"}; - for (final String isoPath : isoPaths) { - if (_storage.exists(isoPath)) { - _sysvmISOPath = isoPath; - break; - } - } - if (_sysvmISOPath == null) { - s_logger.debug("Can't find system vm ISO"); - } - } - final Map bridges = new HashMap(); params.put("libvirt.host.bridges", bridges); @@ -2942,14 +2927,12 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv } if (vmSpec.getType() != VirtualMachine.Type.User) { - if (_sysvmISOPath != null) { - final DiskDef iso = new DiskDef(); - iso.defISODisk(_sysvmISOPath); - if (_guestCpuArch != null && _guestCpuArch.equals("aarch64")) { - iso.setBusType(DiskDef.DiskBus.SCSI); - } - vm.getDevices().addDevice(iso); + final DiskDef iso = new DiskDef(); + iso.defISODisk(_sysvmISOPath); + if (_guestCpuArch != null && _guestCpuArch.equals("aarch64")) { + iso.setBusType(DiskDef.DiskBus.SCSI); } + vm.getDevices().addDevice(iso); } // For LXC, find and add the root filesystem, rbd data disks diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtPatchSystemVmCommandWrapper.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtPatchSystemVmCommandWrapper.java new file mode 100644 index 00000000000..691d34fd709 --- /dev/null +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtPatchSystemVmCommandWrapper.java @@ -0,0 +1,112 @@ +// 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 com.cloud.agent.api.Answer; +import com.cloud.agent.api.PatchSystemVmAnswer; +import com.cloud.agent.api.PatchSystemVmCommand; +import com.cloud.agent.api.routing.NetworkElementCommand; +import com.cloud.agent.resource.virtualnetwork.VRScripts; +import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource; +import com.cloud.resource.CommandWrapper; +import com.cloud.resource.ResourceWrapper; +import com.cloud.utils.ExecutionResult; +import com.cloud.utils.FileUtil; +import com.cloud.utils.Pair; +import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.utils.ssh.SshHelper; +import com.cloud.utils.validation.ChecksumUtil; +import org.apache.commons.lang3.StringUtils; +import org.apache.log4j.Logger; + +import java.io.File; + +@ResourceWrapper(handles = PatchSystemVmCommand.class) +public class LibvirtPatchSystemVmCommandWrapper extends CommandWrapper { + private static final Logger s_logger = Logger.getLogger(LibvirtPatchSystemVmCommandWrapper.class); + private static int sshPort = Integer.parseInt(LibvirtComputingResource.DEFAULTDOMRSSHPORT); + private static File pemFile = new File(LibvirtComputingResource.SSHPRVKEYPATH); + + @Override + public Answer execute(PatchSystemVmCommand cmd, LibvirtComputingResource serverResource) { + final String controlIp = cmd.getAccessDetail(NetworkElementCommand.ROUTER_IP); + final String sysVMName = cmd.getAccessDetail(NetworkElementCommand.ROUTER_NAME); + ExecutionResult result; + try { + result = getSystemVmVersionAndChecksum(serverResource, controlIp); + } catch (CloudRuntimeException e) { + return new PatchSystemVmAnswer(cmd, e.getMessage()); + } + + final String[] lines = result.getDetails().split("&"); + // TODO: do we fail, or patch anyway?? + if (lines.length != 2) { + return new PatchSystemVmAnswer(cmd, result.getDetails()); + } + + String scriptChecksum = lines[1].trim(); + String checksum = ChecksumUtil.calculateCurrentChecksum(sysVMName, "vms/cloud-scripts.tgz").trim(); + + if (!StringUtils.isEmpty(checksum) && checksum.equals(scriptChecksum) && !cmd.isForced()) { + String msg = String.format("No change in the scripts checksum, not patching systemVM %s", sysVMName); + s_logger.info(msg); + return new PatchSystemVmAnswer(cmd, msg, lines[0], lines[1]); + } + + Pair patchResult = null; + try { + FileUtil.scpPatchFiles(controlIp, "/tmp/", sshPort, pemFile, serverResource.systemVmPatchFiles, LibvirtComputingResource.BASEPATH); + patchResult = SshHelper.sshExecute(controlIp, sshPort, "root", + pemFile, null, "/tmp/patch-sysvms.sh", 10000, 10000, 600000); + } catch (Exception e) { + return new PatchSystemVmAnswer(cmd, e.getMessage()); + } + + if (patchResult.first()) { + String scriptVersion = lines[1]; + if (StringUtils.isNotEmpty(patchResult.second())) { + String res = patchResult.second().replace("\n", " "); + String[] output = res.split(":"); + if (output.length != 2) { + s_logger.warn("Failed to get the latest script version"); + } else { + scriptVersion = output[1].split(" ")[0]; + } + } + return new PatchSystemVmAnswer(cmd, String.format("Successfully patched systemVM %s ", sysVMName), lines[0], scriptVersion); + } + return new PatchSystemVmAnswer(cmd, patchResult.second()); + } + + private ExecutionResult getSystemVmVersionAndChecksum(LibvirtComputingResource serverResource, String controlIp) { + ExecutionResult result; + try { + result = serverResource.executeInVR(controlIp, VRScripts.VERSION, null); + if (!result.isSuccess()) { + String errMsg = String.format("GetSystemVMVersionCmd on %s failed, message %s", controlIp, result.getDetails()); + s_logger.error(errMsg); + throw new CloudRuntimeException(errMsg); + } + } catch (final Exception e) { + final String msg = "GetSystemVMVersionCmd failed due to " + e; + s_logger.error(msg, e); + throw new CloudRuntimeException(msg, e); + } + return result; + } +} + diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtStartCommandWrapper.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtStartCommandWrapper.java index f151255d5cd..bdb86f061e9 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtStartCommandWrapper.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtStartCommandWrapper.java @@ -19,8 +19,10 @@ package com.cloud.hypervisor.kvm.resource.wrapper; +import java.io.File; import java.net.URISyntaxException; +import com.cloud.utils.FileUtil; import org.apache.log4j.Logger; import org.libvirt.Connect; import org.libvirt.DomainInfo.DomainState; @@ -34,8 +36,8 @@ import com.cloud.agent.api.to.VirtualMachineTO; import com.cloud.agent.resource.virtualnetwork.VirtualRoutingResource; import com.cloud.exception.InternalErrorException; import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource; -import com.cloud.hypervisor.kvm.resource.LibvirtVMDef; import com.cloud.hypervisor.kvm.resource.LibvirtKvmAgentHook; +import com.cloud.hypervisor.kvm.resource.LibvirtVMDef; import com.cloud.hypervisor.kvm.storage.KVMStoragePoolManager; import com.cloud.network.Networks.TrafficType; import com.cloud.resource.CommandWrapper; @@ -115,6 +117,20 @@ public final class LibvirtStartCommandWrapper extends CommandWrapper clz = cmd.getClass(); - if (cmd instanceof NetworkElementCommand) { + if (clz == PatchSystemVmCommand.class) { + answer = execute((PatchSystemVmCommand) cmd); + } else if (cmd instanceof NetworkElementCommand) { return _vrResource.executeRequest((NetworkElementCommand) cmd); } else if (clz == ReadyCommand.class) { answer = execute((ReadyCommand) cmd); @@ -631,6 +639,77 @@ public class VmwareResource implements StoragePoolResource, ServerResource, Vmwa return answer; } + private ExecutionResult getSystemVmVersionAndChecksum(String controlIp) { + ExecutionResult result; + try { + result = executeInVR(controlIp, VRScripts.VERSION, null); + if (!result.isSuccess()) { + String errMsg = String.format("GetSystemVMVersionCmd on %s failed, message %s", controlIp, result.getDetails()); + s_logger.error(errMsg); + throw new CloudRuntimeException(errMsg); + } + } catch (final Exception e) { + final String msg = "GetSystemVMVersionCmd failed due to " + e; + s_logger.error(msg, e); + throw new CloudRuntimeException(msg, e); + } + return result; + } + + private Answer execute(PatchSystemVmCommand cmd) { + String controlIp = cmd.getAccessDetail((NetworkElementCommand.ROUTER_IP)); + String sysVMName = cmd.getAccessDetail(NetworkElementCommand.ROUTER_NAME); + String homeDir = System.getProperty("user.home"); + File pemFile = new File(homeDir + "/.ssh/id_rsa"); + ExecutionResult result; + try { + result = getSystemVmVersionAndChecksum(controlIp); + FileUtil.scpPatchFiles(controlIp, "/tmp/", DefaultDomRSshPort, pemFile, systemVmPatchFiles, BASEPATH); + } catch (CloudRuntimeException e) { + return new PatchSystemVmAnswer(cmd, e.getMessage()); + } + + final String[] lines = result.getDetails().split("&"); + // TODO: do we fail, or patch anyway?? + if (lines.length != 2) { + return new PatchSystemVmAnswer(cmd, result.getDetails()); + } + + String scriptChecksum = lines[1].trim(); + String checksum = ChecksumUtil.calculateCurrentChecksum(sysVMName, "vms/cloud-scripts.tgz").trim(); + + if (!org.apache.commons.lang3.StringUtils.isEmpty(checksum) && checksum.equals(scriptChecksum) && !cmd.isForced()) { + String msg = String.format("No change in the scripts checksum, not patching systemVM %s", sysVMName); + s_logger.info(msg); + return new PatchSystemVmAnswer(cmd, msg, lines[0], lines[1]); + } + + Pair patchResult = null; + try { + patchResult = SshHelper.sshExecute(controlIp, DefaultDomRSshPort, "root", + pemFile, null, "/tmp/patch-sysvms.sh", 10000, 10000, 600000); + } catch (Exception e) { + return new PatchSystemVmAnswer(cmd, e.getMessage()); + } + + String scriptVersion = lines[1]; + if (StringUtils.isNotEmpty(patchResult.second())) { + String res = patchResult.second().replace("\n", " "); + String[] output = res.split(":"); + if (output.length != 2) { + s_logger.warn("Failed to get the latest script version"); + } else { + scriptVersion = output[1].split(" ")[0]; + } + + } + if (patchResult.first()) { + return new PatchSystemVmAnswer(cmd, String.format("Successfully patched systemVM %s ", sysVMName), lines[0], scriptVersion); + } + return new PatchSystemVmAnswer(cmd, patchResult.second()); + + } + private Answer execute(SetupPersistentNetworkCommand cmd) { VmwareHypervisorHost host = getHyperHost(getServiceContext()); String hostname = null; @@ -2124,7 +2203,6 @@ public class VmwareResource implements StoragePoolResource, ServerResource, Vmwa String msg = "secondary storage for dc " + _dcId + " is not ready yet?"; throw new Exception(msg); } - mgr.prepareSecondaryStorageStore(secStoreUrl, secStoreId); ManagedObjectReference morSecDs = prepareSecondaryDatastoreOnHost(secStoreUrl); if (morSecDs == null) { @@ -2135,7 +2213,7 @@ public class VmwareResource implements StoragePoolResource, ServerResource, Vmwa deviceConfigSpecArray[i] = new VirtualDeviceConfigSpec(); Pair isoInfo = VmwareHelper.prepareIsoDevice(vmMo, - String.format("[%s] systemvm/%s", secDsMo.getName(), mgr.getSystemVMIsoFileNameOnDatastore()), secDsMo.getMor(), true, true, ideUnitNumber++, i + 1); + null, secDsMo.getMor(), true, true, ideUnitNumber++, i + 1); deviceConfigSpecArray[i].setDevice(isoInfo.first()); if (isoInfo.second()) { if (s_logger.isDebugEnabled()) @@ -2487,6 +2565,32 @@ public class VmwareResource implements StoragePoolResource, ServerResource, Vmwa startAnswer.setIqnToData(iqnToData); + if (vmSpec.getType() != VirtualMachine.Type.User) { + String controlIp = getControlIp(nics); + // check if the router is up? + for (int count = 0; count < 60; count++) { + final boolean result = _vrResource.connect(controlIp, 1, 5000); + if (result) { + break; + } + } + + try { + String homeDir = System.getProperty("user.home"); + File pemFile = new File(homeDir + "/.ssh/id_rsa"); + FileUtil.scpPatchFiles(controlIp, "/tmp/", DefaultDomRSshPort, pemFile, systemVmPatchFiles, BASEPATH); + if (!_vrResource.isSystemVMSetup(vmInternalCSName, controlIp)) { + String errMsg = "Failed to patch systemVM"; + s_logger.error(errMsg); + return new StartAnswer(cmd, errMsg); + } + } catch (Exception e) { + String errMsg = "Failed to scp files to system VM. Patching of systemVM failed"; + s_logger.error(errMsg, e); + return new StartAnswer(cmd, String.format("%s due to: %s", errMsg, e.getMessage())); + } + } + // Since VM was successfully powered-on, if there was an existing VM in a different cluster that was unregistered, delete all the files associated with it. if (existingVmName != null && existingVmFileLayout != null) { List vmDatastoreNames = new ArrayList(); @@ -3878,6 +3982,17 @@ public class VmwareResource implements StoragePoolResource, ServerResource, Vmwa } } + private String getControlIp(NicTO[] nicTOs) { + String controlIpAddress = null; + for (NicTO nic : nicTOs) { + if ((TrafficType.Management == nic.getType() || TrafficType.Control == nic.getType()) && nic.getIp() != null) { + controlIpAddress = nic.getIp(); + break; + } + } + return controlIpAddress; + } + private VirtualMachineMO takeVmFromOtherHyperHost(VmwareHypervisorHost hyperHost, String vmName) throws Exception { VirtualMachineMO vmMo = hyperHost.findVmOnPeerHyperHost(vmName); @@ -4399,10 +4514,6 @@ public class VmwareResource implements StoragePoolResource, ServerResource, Vmwa throw new Exception(msg); } - if (vm.getType() != VirtualMachine.Type.User) { - mgr.prepareSecondaryStorageStore(secStoreUrl, secStoreId); - } - ManagedObjectReference morSecDs = prepareSecondaryDatastoreOnHost(secStoreUrl); if (morSecDs == null) { String msg = "Failed to prepare secondary storage on host, secondary store url: " + secStoreUrl; @@ -6685,6 +6796,11 @@ public class VmwareResource implements StoragePoolResource, ServerResource, Vmwa return _name; } + @Override + protected String getDefaultScriptsDir() { + return null; + } + @Override public boolean start() { return true; @@ -7355,7 +7471,6 @@ public class VmwareResource implements StoragePoolResource, ServerResource, Vmwa String msg = "secondary storage for dc " + _dcId + " is not ready yet?"; throw new Exception(msg); } - mgr.prepareSecondaryStorageStore(secStoreUrl, secStoreId); ManagedObjectReference morSecDs = prepareSecondaryDatastoreOnSpecificHost(secStoreUrl, targetHyperHost); if (morSecDs == null) { throw new Exception(String.format("Failed to prepare secondary storage on host, secondary store url: %s", secStoreUrl)); diff --git a/plugins/hypervisors/xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/CitrixResourceBase.java b/plugins/hypervisors/xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/CitrixResourceBase.java index a95c07bdd19..6bed711b5f0 100644 --- a/plugins/hypervisors/xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/CitrixResourceBase.java +++ b/plugins/hypervisors/xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/CitrixResourceBase.java @@ -51,6 +51,7 @@ import javax.naming.ConfigurationException; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; +import com.cloud.resource.ServerResourceBase; import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.diagnostics.CopyToSecondaryStorageAnswer; import org.apache.cloudstack.diagnostics.CopyToSecondaryStorageCommand; @@ -179,7 +180,7 @@ import com.xensource.xenapi.XenAPIObject; * before you do any changes in this code here. * */ -public abstract class CitrixResourceBase implements ServerResource, HypervisorResource, VirtualRouterDeployer { +public abstract class CitrixResourceBase extends ServerResourceBase implements ServerResource, HypervisorResource, VirtualRouterDeployer { /** * used to describe what type of resource a storage device is of */ @@ -215,6 +216,7 @@ public abstract class CitrixResourceBase implements ServerResource, HypervisorRe private final static String VM_NAME_ISO_SUFFIX = "-ISO"; private final static String VM_FILE_ISO_SUFFIX = ".iso"; + public final static int DEFAULTDOMRSSHPORT = 3922; private static final XenServerConnectionPool ConnPool = XenServerConnectionPool.getInstance(); // static min values for guests on xenserver @@ -225,6 +227,8 @@ public abstract class CitrixResourceBase implements ServerResource, HypervisorRe protected static final HashMap s_powerStatesTable; public static final String XS_TOOLS_ISO_AFTER_70 = "guest-tools.iso"; + public static final String BASEPATH = "/opt/xensource/packages/resources/"; + protected static final String PLATFORM_CORES_PER_SOCKET_KEY = "cores-per-socket"; static { @@ -339,6 +343,11 @@ public abstract class CitrixResourceBase implements ServerResource, HypervisorRe return new StorageSubsystemCommandHandlerBase(processor); } + @Override + protected String getDefaultScriptsDir() { + return null; + } + public String callHostPlugin(final Connection conn, final String plugin, final String cmd, final String... params) { final Map args = new HashMap(); String msg; @@ -904,11 +913,15 @@ public abstract class CitrixResourceBase implements ServerResource, HypervisorRe } } - public String connect(final Connection conn, final String vmname, final String ipAddress) { - return connect(conn, vmname, ipAddress, 3922); + public String connect(final Connection conn, final String vmname, final String ipAddress, int sleep) { + return connect(conn, vmname, ipAddress, DEFAULTDOMRSSHPORT, sleep); } - public String connect(final Connection conn, final String vmName, final String ipAddress, final int port) { + public String connect(final Connection conn, final String vmName, final String ipAddress, final int port, int sleep) { + if (sleep == 0) { + sleep = _sleep; + } + for (int i = 0; i <= _retry; i++) { try { final Set vms = VM.getByNameLabel(conn, vmName); @@ -929,7 +942,7 @@ public abstract class CitrixResourceBase implements ServerResource, HypervisorRe return null; } try { - Thread.sleep(_sleep); + Thread.sleep(sleep); } catch (final InterruptedException e) { } } @@ -974,8 +987,25 @@ public abstract class CitrixResourceBase implements ServerResource, HypervisorRe s_logger.warn("scp VR config file into host " + _host.getIp() + " failed with exception " + e.getMessage().toString()); } - final String rc = callHostPlugin(conn, "vmops", "createFileInDomr", "domrip", routerIp, "srcfilepath", hostPath + filename, "dstfilepath", path); - s_logger.debug("VR Config file " + filename + " got created in VR, ip " + routerIp + " with content \n" + content); + final String rc = callHostPlugin(conn, "vmops", "createFileInDomr", "domrip", routerIp, "srcfilepath", hostPath + filename, "dstfilepath", path, "cleanup", "true"); + s_logger.debug("VR Config file " + filename + " got created in VR, IP: " + routerIp + " with content \n" + content); + + return new ExecutionResult(rc.startsWith("succ#"), rc.substring(5)); + } + + public ExecutionResult copyPatchFilesToVR(final String routerIp, final String path) { + final Connection conn = getConnection(); + final String hostPath = "/opt/xensource/packages/resources/"; + String rc = ""; + for (String file: systemVmPatchFiles) { + rc = callHostPlugin(conn, "vmops", "createFileInDomr", "domrip", routerIp, "srcfilepath", hostPath.concat(file), "dstfilepath", path, "cleanup", "false"); + if (rc.startsWith("fail#")) { + s_logger.error(String.format("Failed to scp file %s required for patching the systemVM", file)); + break; + } + } + + s_logger.debug("VR Config files at " + hostPath + " got created in VR, IP: " + routerIp); return new ExecutionResult(rc.startsWith("succ#"), rc.substring(5)); } @@ -1092,9 +1122,6 @@ public abstract class CitrixResourceBase implements ServerResource, HypervisorRe _host.setSystemvmisouuid(vdi.getRecord(conn).uuid); } } - if (_host.getSystemvmisouuid() == null) { - throw new CloudRuntimeException("can not find systemvmiso"); - } } final VBD.Record cdromVBDR = new VBD.Record(); @@ -1104,10 +1131,8 @@ public abstract class CitrixResourceBase implements ServerResource, HypervisorRe cdromVBDR.userdevice = "3"; cdromVBDR.mode = Types.VbdMode.RO; cdromVBDR.type = Types.VbdType.CD; - final VBD cdromVBD = VBD.create(conn, cdromVBDR); - cdromVBD.insert(conn, VDI.getByUuid(conn, _host.getSystemvmisouuid())); - return cdromVBD; + return VBD.create(conn, cdromVBDR); } protected boolean createSecondaryStorageFolder(final Connection conn, final String remoteMountPath, final String newFolder, final String nfsVersion) { @@ -1396,7 +1421,7 @@ public abstract class CitrixResourceBase implements ServerResource, HypervisorRe final DiskTO[] disks = vmSpec.getDisks(); for (final DiskTO disk : disks) { if (disk.getType() == Volume.Type.ISO) { - final TemplateObjectTO iso = (TemplateObjectTO)disk.getData(); + final TemplateObjectTO iso = (TemplateObjectTO) disk.getData(); final String osType = iso.getGuestOsType(); if (osType != null) { final String isoGuestOsName = getGuestOsType(vmSpec.getPlatformEmulator()); @@ -4893,7 +4918,7 @@ public abstract class CitrixResourceBase implements ServerResource, HypervisorRe throw new CloudRuntimeException("Unable to authenticate"); } - final String cmd = "mkdir -p /opt/cloud/bin /var/log/cloud"; + final String cmd = "mkdir -p /opt/cloud/bin /var/log/cloud /opt/xensource/packages/resources/"; if (!SSHCmdHelper.sshExecuteCmd(sshConnection, cmd)) { throw new CloudRuntimeException("Cannot create directory /opt/cloud/bin on XenServer hosts"); } diff --git a/plugins/hypervisors/xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixCheckSshCommandWrapper.java b/plugins/hypervisors/xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixCheckSshCommandWrapper.java index cf34a8fb30c..ec7d844dfbc 100644 --- a/plugins/hypervisors/xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixCheckSshCommandWrapper.java +++ b/plugins/hypervisors/xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixCheckSshCommandWrapper.java @@ -46,7 +46,7 @@ public final class CitrixCheckSshCommandWrapper extends CommandWrapper { - + private static final Logger s_logger = Logger.getLogger(CitrixNetworkElementCommandWrapper.class); @Override public Answer execute(final NetworkElementCommand command, final CitrixResourceBase citrixResourceBase) { final VirtualRoutingResource routingResource = citrixResourceBase.getVirtualRoutingResource(); diff --git a/plugins/hypervisors/xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixPatchSystemVmCommandWrapper.java b/plugins/hypervisors/xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixPatchSystemVmCommandWrapper.java new file mode 100644 index 00000000000..718daec7292 --- /dev/null +++ b/plugins/hypervisors/xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixPatchSystemVmCommandWrapper.java @@ -0,0 +1,111 @@ +// 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.xenserver.resource.wrapper.xenbase; + +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.PatchSystemVmAnswer; +import com.cloud.agent.api.PatchSystemVmCommand; +import com.cloud.agent.api.routing.NetworkElementCommand; +import com.cloud.agent.resource.virtualnetwork.VRScripts; +import com.cloud.hypervisor.xenserver.resource.CitrixResourceBase; +import com.cloud.resource.CommandWrapper; +import com.cloud.resource.ResourceWrapper; +import com.cloud.utils.ExecutionResult; +import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.utils.validation.ChecksumUtil; +import com.xensource.xenapi.Connection; +import org.apache.commons.lang3.StringUtils; +import org.apache.log4j.Logger; + +import java.io.File; + +@ResourceWrapper(handles = PatchSystemVmCommand.class) +public class CitrixPatchSystemVmCommandWrapper extends CommandWrapper { + private static final Logger s_logger = Logger.getLogger(CitrixPatchSystemVmCommandWrapper.class); + private static int sshPort = CitrixResourceBase.DEFAULTDOMRSSHPORT; + private static File pemFile = new File(CitrixResourceBase.SSHPRVKEYPATH); + + @Override + public Answer execute(PatchSystemVmCommand command, CitrixResourceBase serverResource) { + final String controlIp = command.getAccessDetail(NetworkElementCommand.ROUTER_IP); + final String sysVMName = command.getAccessDetail(NetworkElementCommand.ROUTER_NAME); + final Connection conn = serverResource.getConnection(); + + ExecutionResult result; + try { + result = getSystemVmVersionAndChecksum(serverResource, controlIp); + } catch (CloudRuntimeException e) { + return new PatchSystemVmAnswer(command, e.getMessage()); + } + + final String[] lines = result.getDetails().split("&"); + // TODO: do we fail, or patch anyway?? + if (lines.length != 2) { + return new PatchSystemVmAnswer(command, result.getDetails()); + } + + String scriptChecksum = lines[1].trim(); + String checksum = ChecksumUtil.calculateCurrentChecksum(sysVMName, "vms/cloud-scripts.tgz").trim(); + if (!StringUtils.isEmpty(checksum) && checksum.equals(scriptChecksum) && !command.isForced()) { + String msg = String.format("No change in the scripts checksum, not patching systemVM %s", sysVMName); + s_logger.info(msg); + return new PatchSystemVmAnswer(command, msg, lines[0], lines[1]); + } + + String patchResult = null; + try { + serverResource.copyPatchFilesToVR(controlIp, "/tmp/"); + patchResult = serverResource.callHostPlugin(conn, "vmops", "runPatchScriptInDomr", "domrip", controlIp); + } catch (Exception e) { + return new PatchSystemVmAnswer(command, e.getMessage()); + } + + if (patchResult.startsWith("succ#")) { + String scriptVersion = lines[1]; + String res = patchResult.replace("\n", " "); + String[] output = res.split(":"); + if (output.length != 2) { + s_logger.warn("Failed to get the latest script version"); + } else { + scriptVersion = output[1].split(" ")[0]; + } + + return new PatchSystemVmAnswer(command, String.format("Successfully patched systemVM %s ", sysVMName), lines[0], scriptVersion); + } + return new PatchSystemVmAnswer(command, patchResult.substring(5)); + + } + + private ExecutionResult getSystemVmVersionAndChecksum(CitrixResourceBase serverResource, String controlIp) { + ExecutionResult result; + try { + result = serverResource.executeInVR(controlIp, VRScripts.VERSION, null); + if (!result.isSuccess()) { + String errMsg = String.format("GetSystemVMVersionCmd on %s failed, message %s", controlIp, result.getDetails()); + s_logger.error(errMsg); + throw new CloudRuntimeException(errMsg); + } + } catch (final Exception e) { + final String msg = "GetSystemVMVersionCmd failed due to " + e; + s_logger.error(msg, e); + throw new CloudRuntimeException(msg, e); + } + return result; + } + + +} diff --git a/plugins/hypervisors/xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixRebootRouterCommandWrapper.java b/plugins/hypervisors/xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixRebootRouterCommandWrapper.java index 236d8db3858..a87458326e5 100644 --- a/plugins/hypervisors/xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixRebootRouterCommandWrapper.java +++ b/plugins/hypervisors/xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixRebootRouterCommandWrapper.java @@ -40,7 +40,7 @@ public final class CitrixRebootRouterCommandWrapper extends CommandWrapper /etc/containerd/config.toml - - sed -i '/\[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options\]/a '"\\ SystemdCgroup=true"'' /etc/containerd/config.toml + - sed -i 's/SystemdCgroup = false/SystemdCgroup = true/g' /etc/containerd/config.toml - systemctl daemon-reload - systemctl restart containerd - until [ -f /etc/systemd/system/deploy-kube-system.service ]; do sleep 5; done diff --git a/plugins/integrations/kubernetes-service/src/main/resources/conf/k8s-control-node.yml b/plugins/integrations/kubernetes-service/src/main/resources/conf/k8s-control-node.yml index d7a63048a65..76adc5a7d29 100644 --- a/plugins/integrations/kubernetes-service/src/main/resources/conf/k8s-control-node.yml +++ b/plugins/integrations/kubernetes-service/src/main/resources/conf/k8s-control-node.yml @@ -289,7 +289,6 @@ write_files: echo "Restarting containerd service" systemctl restart containerd - - path: /etc/systemd/system/setup-kube-system.service permissions: '0755' owner: root:root @@ -320,7 +319,7 @@ write_files: runcmd: - chown -R cloud:cloud /home/cloud/.ssh - containerd config default > /etc/containerd/config.toml - - sed -i '/\[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options\]/a '"\\ SystemdCgroup=true"'' /etc/containerd/config.toml + - sed -i 's/SystemdCgroup = false/SystemdCgroup = true/g' /etc/containerd/config.toml - systemctl daemon-reload - systemctl restart containerd - until [ -f /etc/systemd/system/deploy-kube-system.service ]; do sleep 5; done diff --git a/plugins/integrations/kubernetes-service/src/main/resources/conf/k8s-node.yml b/plugins/integrations/kubernetes-service/src/main/resources/conf/k8s-node.yml index d253f3e2780..86966245c83 100644 --- a/plugins/integrations/kubernetes-service/src/main/resources/conf/k8s-node.yml +++ b/plugins/integrations/kubernetes-service/src/main/resources/conf/k8s-node.yml @@ -262,7 +262,7 @@ write_files: runcmd: - chown -R cloud:cloud /home/cloud/.ssh - containerd config default > /etc/containerd/config.toml - - sed -i '/\[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options\]/a '"\\ SystemdCgroup=true"'' /etc/containerd/config.toml + - sed -i 's/SystemdCgroup = false/SystemdCgroup = true/g' /etc/containerd/config.toml - systemctl daemon-reload - systemctl restart containerd - until [ -f /etc/systemd/system/deploy-kube-system.service ]; do sleep 5; done diff --git a/pom.xml b/pom.xml index 49ff10d6bc6..c315570fec7 100644 --- a/pom.xml +++ b/pom.xml @@ -49,7 +49,7 @@ UTF-8 UTF-8 - 4.16.1.0 + 4.17.0.0 apachecloudstack https://sonarcloud.io diff --git a/scripts/installer/export-templates.sh b/scripts/installer/export-templates.sh new file mode 100644 index 00000000000..9371f1c7412 --- /dev/null +++ b/scripts/installer/export-templates.sh @@ -0,0 +1,192 @@ +#!/usr/bin/env 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. + +METADATA_FILE="metadata.ini" +IMAGE_PATH=${3:-"/usr/share/cloudstack-management/templates/systemvm/"} +TEMPLATE_VERSION=$(awk -F "=" '/version/ {print $2}' ${IMAGE_PATH}${METADATA_FILE} | xargs) +TEMPLATE_PATH="/usr/share/cloudstack-management/templates/systemvm/" +VERSION="${TEMPLATE_VERSION%.*}" +PREFIX=${4:-"systemvmtemplate-$VERSION"} +CLEANUP=${2:-1} +TEMP_IMAGE_PATH="/tmp/sysvm_convert/" + +initial_setup() { + mkdir -p $TEMP_IMAGE_PATH + cp -r $IMAGE_PATH/* $TEMP_IMAGE_PATH + cd $TEMP_IMAGE_PATH + if [ ! -f ${TEMP_IMAGE_PATH}${PREFIX}-kvm.qcow2 ]; then + bzip2 -dc $PREFIX-kvm.qcow2.bz2 > $PREFIX-kvm.qcow2 + fi +} + +export_vmware() { + initial_setup + # Export for KVM + virt-sparsify $PREFIX-kvm.qcow2 --compress -o compat=0.10 $PREFIX-kvm-temp.qcow2 + # Export for VMware + qemu-img convert -f qcow2 -O vmdk -o adapter_type=lsilogic,subformat=streamOptimized,compat6 $PREFIX-kvm-temp.qcow2 $PREFIX-vmware.vmdk + size=$(stat --printf="%s" $PREFIX-vmware.vmdk) + +cat < $PREFIX-vmware.ovf + + + + + + + + Virtual disk information + + + + A virtual machine + $PREFIX-vmware + + The kind of installed guest operating system + + + Virtual hardware requirements + + Virtual Hardware Family + 0 + $PREFIX-vmware + vmx-11 + + + hertz * 10^6 + Number of Virtual CPUs + 1 virtual CPU(s) + 1 + 3 + 1 + + + byte * 2^20 + Memory Size + 256MB of memory + 2 + 4 + 256 + + + 0 + SCSI Controller + scsiController0 + 3 + lsilogic + 6 + + + 0 + IDE Controller + ideController0 + 4 + 5 + + + 0 + false + cdrom0 + 5 + 4 + 15 + + + 0 + disk0 + ovf:/disk/vmdisk1 + 6 + 3 + 17 + + + false + video + 7 + 24 + + + + + + false + vmci + 8 + vmware.vmci + 1 + + + + + + + + A human-readable annotation + $PREFIX-vmware + + + +EOF +cat < $PREFIX-vmware.mf +SHA1($PREFIX-vmware.ovf)= $(sha1sum $PREFIX-vmware.ovf|awk '{print $1}') +SHA1($PREFIX-vmware.vmdk)= $(sha1sum $PREFIX-vmware.vmdk |awk '{print $1}') +EOF + + tar -cvf $PREFIX-vmware.ova $PREFIX-vmware.ovf $PREFIX-vmware.mf $PREFIX-vmware.vmdk + checksum=$(md5sum $PREFIX-vmware.ova | awk '{print $1}') + sed -i '/^\['"vmware"']/,/^\[/{s/^checksum[[:space:]]*=.*/checksum = '"$checksum"'/}' ./$METADATA_FILE + rm -rf *.mf *.ovf *.vmdk + sudo cp $TEMP_IMAGE_PATH/$PREFIX-vmware.ova $TEMP_IMAGE_PATH/metadata.ini $IMAGE_PATH + cleanup +} + +export_xen() { + # Export for XenServer/XCP-ng + initial_setup + qemu-img convert -f qcow2 -O vpc $PREFIX-kvm.qcow2 $PREFIX-xen.vhd + bzip2 $PREFIX-xen.vhd + checksum=$(md5sum $PREFIX-xen.vhd.bz2 | awk '{print $1}') + sed -i '/^\['"xenserver"']/,/^\[/{s/^checksum[[:space:]]*=.*/checksum = '"$checksum"'/}' $METADATA_FILE + rm -rf $PREFIX-xen.vhd + sudo cp $TEMP_IMAGE_PATH/$PREFIX-xen* $TEMP_IMAGE_PATH/metadata.ini $IMAGE_PATH + cleanup +} + +cleanup() { + cd /var/cloudstack/management/ + if [ $CLEANUP == 1 ]; then + cd /var/cloudstack/management/ + rm -rf $TEMP_IMAGE_PATH + fi +} + +if [ "$#" -lt 1 ] ; then + echo "Usage: $0 [cleanup: 0/1; default: 1] [imagepath: default:/usr/share/cloudstack-management/templates/systemvm/] [templateprefix: default:systemvmtemplate-$VERSION]" >&2 + exit 1 +fi + +if [ $1 == "vmware" ]; then + echo "exporting vmware template" + export_vmware +elif [ $1 == "xenserver" ]; then + echo "exporting xenserver template" + export_xen +else + echo "Conversion of template to $1's compatible format not supported " +fi + diff --git a/scripts/util/keystore-cert-import b/scripts/util/keystore-cert-import index a2b57bff07e..9e4e7f246b3 100755 --- a/scripts/util/keystore-cert-import +++ b/scripts/util/keystore-cert-import @@ -17,19 +17,34 @@ # under the License. PROPS_FILE="$1" -KS_FILE="$2" -MODE="$3" -CERT_FILE="$4" -CERT=$(echo "$5" | tr '^' '\n' | tr '~' ' ') -CACERT_FILE="$6" -CACERT=$(echo "$7" | tr '^' '\n' | tr '~' ' ') -PRIVKEY_FILE="$8" -PRIVKEY=$(echo "$9" | tr '^' '\n' | tr '~' ' ') +KS_PASS="$2" +KS_FILE="$3" +MODE="$4" +CERT_FILE="$5" +CERT=$(echo "$6" | tr '^' '\n' | tr '~' ' ') +CACERT_FILE="$7" +CACERT=$(echo "$8" | tr '^' '\n' | tr '~' ' ') +PRIVKEY_FILE="$9" +PRIVKEY=$(echo "${10}" | tr '^' '\n' | tr '~' ' ') ALIAS="cloud" SYSTEM_FILE="/var/cache/cloud/cmdline" LIBVIRTD_FILE="/etc/libvirt/libvirtd.conf" +if [ ! -f "$LIBVIRTD_FILE" ]; then + # Re-use existing password or use the one provided + while [ ! -d /usr/local/cloud/systemvm/conf ]; do sleep 1; done + if [ -f "$PROPS_FILE" ]; then + OLD_PASS=$(sed -n '/keystore.passphrase/p' "$PROPS_FILE" 2>/dev/null | sed 's/keystore.passphrase=//g' 2>/dev/null) + if [ ! -z "${OLD_PASS// }" ]; then + KS_PASS="$OLD_PASS" + else + sed -i "/keystore.passphrase.*/d" $PROPS_FILE 2> /dev/null || true + echo "keystore.passphrase=$KS_PASS" >> $PROPS_FILE + fi + fi +fi + # Find keystore password KS_PASS=$(sed -n '/keystore.passphrase/p' "$PROPS_FILE" 2>/dev/null | sed 's/keystore.passphrase=//g' 2>/dev/null) @@ -41,11 +56,17 @@ fi # Import certificate if [ ! -z "${CERT// }" ]; then echo "$CERT" > "$CERT_FILE" +elif [ ! -f "$CERT_FILE" ]; then + echo "Cannot find certificate file: $CERT_FILE, exiting" + exit fi # Import ca certs if [ ! -z "${CACERT// }" ]; then echo "$CACERT" > "$CACERT_FILE" +elif [ ! -f "$CACERT_FILE" ]; then + echo "Cannot find ca certificate file: $CACERT_FILE, exiting!" + exit fi # Import cacerts into the keystore @@ -64,6 +85,11 @@ fi # Import private key if available if [ ! -z "${PRIVKEY// }" ]; then echo "$PRIVKEY" > "$PRIVKEY_FILE" +else + > "$PRIVKEY_FILE" +fi + +if [ -f "$PRIVKEY_FILE" ] && [ -s "$PRIVKEY_FILE" ]; then # Re-initialize keystore when private key is provided keytool -delete -noprompt -alias "$ALIAS" -keystore "$KS_FILE" -storepass "$KS_PASS" 2>/dev/null || true openssl pkcs12 -export -name "$ALIAS" -in "$CERT_FILE" -inkey "$PRIVKEY_FILE" -out "$KS_FILE.p12" -password pass:"$KS_PASS" > /dev/null 2>&1 diff --git a/scripts/vm/hypervisor/xenserver/vmops b/scripts/vm/hypervisor/xenserver/vmops index dd03ded9592..de5feb06d21 100755 --- a/scripts/vm/hypervisor/xenserver/vmops +++ b/scripts/vm/hypervisor/xenserver/vmops @@ -235,17 +235,32 @@ def createFileInDomr(session, args): src_filepath = args['srcfilepath'] dst_path = args['dstfilepath'] domrip = args['domrip'] + cleanup = 'true' if 'cleanup' not in args else args['cleanup'] txt="" try: target = "root@" + domrip + ":" + dst_path txt = util.pread2(['scp','-P','3922','-q','-o','StrictHostKeyChecking=no','-i','/root/.ssh/id_rsa.cloud',src_filepath, target]) - util.pread2(['rm',src_filepath]) + if cleanup == 'true' or not cleanup: + util.pread2(['rm',src_filepath]) txt = 'succ#' + txt except: logging.debug("failed to copy file " + src_filepath + " from host to VR with ip " + domrip) txt = 'fail#' + txt return txt +@echo +def runPatchScriptInDomr(session, args): + domrip = args['domrip'] + txt="" + try: + target = "root@" + domrip + txt = util.pread2(['ssh','-p','3922','-i','/root/.ssh/id_rsa.cloud', target, "/bin/bash","/tmp/patch-sysvms.sh"]) + txt = 'succ#' + txt + except: + logging.debug("failed to run patch script in systemVM with IP: " + domrip) + txt = 'fail#' + txt + return txt + @echo def deleteFile(session, args): file_path = args["filepath"] @@ -1588,4 +1603,5 @@ if __name__ == "__main__": "cleanup_rules":cleanup_rules, "createFileInDomr":createFileInDomr, "kill_copy_process":kill_copy_process, - "secureCopyToHost":secureCopyToHost}) + "secureCopyToHost":secureCopyToHost, + "runPatchScriptInDomr": runPatchScriptInDomr}) diff --git a/scripts/vm/hypervisor/xenserver/xcposs/patch b/scripts/vm/hypervisor/xenserver/xcposs/patch index d3c5db05245..1edd35ad628 100644 --- a/scripts/vm/hypervisor/xenserver/xcposs/patch +++ b/scripts/vm/hypervisor/xenserver/xcposs/patch @@ -31,7 +31,9 @@ vmops=..,0755,/usr/lib/xcp/plugins ovsgre=..,0755,/usr/lib/xcp/plugins ovstunnel=..,0755,/usr/lib/xcp/plugins vmopsSnapshot=..,0755,/usr/lib/xcp/plugins -systemvm.iso=../../../../../vms,0644,/usr/share/xcp/packages/iso/ +agent.zip=../../../../../vms,0644,/opt/xensource/packages/resources/ +cloud-scripts.tgz=../../../../../vms,0644,/opt/xensource/packages/resources/ +patch-sysvms.sh=../../../../../vms,0644,/opt/xensource/packages/resources/ id_rsa.cloud=../../../systemvm,0600,/root/.ssh network_info.sh=..,0755,/opt/cloud/bin setupxenserver.sh=..,0755,/opt/cloud/bin diff --git a/scripts/vm/hypervisor/xenserver/xcpserver/patch b/scripts/vm/hypervisor/xenserver/xcpserver/patch index 862aa2e9c11..8bb1eadd703 100644 --- a/scripts/vm/hypervisor/xenserver/xcpserver/patch +++ b/scripts/vm/hypervisor/xenserver/xcpserver/patch @@ -31,7 +31,9 @@ NFSSR.py=/opt/xensource/sm vmops=..,0755,/etc/xapi.d/plugins ovstunnel=..,0755,/etc/xapi.d/plugins vmopsSnapshot=..,0755,/etc/xapi.d/plugins -systemvm.iso=../../../../../vms,0644,/opt/xensource/packages/iso +agent.zip=../../../../../vms,0644,/opt/xensource/packages/resources/ +cloud-scripts.tgz=../../../../../vms,0644,/opt/xensource/packages/resources/ +patch-sysvms.sh=../../../../../vms,0644,/opt/xensource/packages/resources/ id_rsa.cloud=../../../systemvm,0600,/root/.ssh network_info.sh=..,0755,/opt/cloud/bin setupxenserver.sh=..,0755,/opt/cloud/bin diff --git a/scripts/vm/hypervisor/xenserver/xenserver56/patch b/scripts/vm/hypervisor/xenserver/xenserver56/patch index b6f7cdbb5c1..16b1ce71b92 100644 --- a/scripts/vm/hypervisor/xenserver/xenserver56/patch +++ b/scripts/vm/hypervisor/xenserver/xenserver56/patch @@ -30,7 +30,9 @@ NFSSR.py=/opt/xensource/sm vmops=..,0755,/etc/xapi.d/plugins vmopsSnapshot=..,0755,/etc/xapi.d/plugins cloudstack_pluginlib.py=..,0755,/etc/xapi.d/plugins -systemvm.iso=../../../../../vms,0644,/opt/xensource/packages/iso +agent.zip=../../../../../vms,0644,/opt/xensource/packages/resources/ +cloud-scripts.tgz=../../../../../vms,0644,/opt/xensource/packages/resources/ +patch-sysvms.sh=../../../../../vms,0644,/opt/xensource/packages/resources/ id_rsa.cloud=../../../systemvm,0600,/root/.ssh network_info.sh=..,0755,/opt/cloud/bin setupxenserver.sh=..,0755,/opt/cloud/bin diff --git a/scripts/vm/hypervisor/xenserver/xenserver56fp1/patch b/scripts/vm/hypervisor/xenserver/xenserver56fp1/patch index 4546796f9b3..bb09d255601 100644 --- a/scripts/vm/hypervisor/xenserver/xenserver56fp1/patch +++ b/scripts/vm/hypervisor/xenserver/xenserver56fp1/patch @@ -30,7 +30,9 @@ NFSSR.py=/opt/xensource/sm vmops=..,0755,/etc/xapi.d/plugins vmopsSnapshot=..,0755,/etc/xapi.d/plugins cloudstack_pluginlib.py=..,0755,/etc/xapi.d/plugins -systemvm.iso=../../../../../vms,0644,/opt/xensource/packages/iso +agent.zip=../../../../../vms,0644,/opt/xensource/packages/resources/ +cloud-scripts.tgz=../../../../../vms,0644,/opt/xensource/packages/resources/ +patch-sysvms.sh=../../../../../vms,0644,/opt/xensource/packages/resources/ id_rsa.cloud=../../../systemvm,0600,/root/.ssh network_info.sh=..,0755,/opt/cloud/bin setupxenserver.sh=..,0755,/opt/cloud/bin diff --git a/scripts/vm/hypervisor/xenserver/xenserver60/patch b/scripts/vm/hypervisor/xenserver/xenserver60/patch index bea0cf9bfd1..2652c30cd05 100644 --- a/scripts/vm/hypervisor/xenserver/xenserver60/patch +++ b/scripts/vm/hypervisor/xenserver/xenserver60/patch @@ -34,7 +34,9 @@ cloudstack_plugins.conf=..,0644,/etc/xensource cloudstack_pluginlib.py=..,0755,/etc/xapi.d/plugins ovstunnel=..,0755,/etc/xapi.d/plugins vmopsSnapshot=..,0755,/etc/xapi.d/plugins -systemvm.iso=../../../../../vms,0644,/opt/xensource/packages/iso +agent.zip=../../../../../vms,0644,/opt/xensource/packages/resources/ +cloud-scripts.tgz=../../../../../vms,0644,/opt/xensource/packages/resources/ +patch-sysvms.sh=../../../../../vms,0644,/opt/xensource/packages/resources/ id_rsa.cloud=../../../systemvm,0600,/root/.ssh network_info.sh=..,0755,/opt/cloud/bin setupxenserver.sh=..,0755,/opt/cloud/bin diff --git a/scripts/vm/hypervisor/xenserver/xenserver62/patch b/scripts/vm/hypervisor/xenserver/xenserver62/patch index db137c9826f..f18a325f05b 100644 --- a/scripts/vm/hypervisor/xenserver/xenserver62/patch +++ b/scripts/vm/hypervisor/xenserver/xenserver62/patch @@ -35,7 +35,9 @@ cloudstack_plugins.conf=..,0644,/etc/xensource cloudstack_pluginlib.py=..,0755,/etc/xapi.d/plugins ovstunnel=..,0755,/etc/xapi.d/plugins cloud-plugin-storage=..,0755,/etc/xapi.d/plugins -systemvm.iso=../../../../../vms,0644,/opt/xensource/packages/iso +agent.zip=../../../../../vms,0644,/opt/xensource/packages/resources/ +cloud-scripts.tgz=../../../../../vms,0644,/opt/xensource/packages/resources/ +patch-sysvms.sh=../../../../../vms,0644,/opt/xensource/packages/resources/ id_rsa.cloud=../../../systemvm,0600,/root/.ssh network_info.sh=..,0755,/opt/cloud/bin setupxenserver.sh=..,0755,/opt/cloud/bin diff --git a/scripts/vm/hypervisor/xenserver/xenserver65/patch b/scripts/vm/hypervisor/xenserver/xenserver65/patch index db137c9826f..f18a325f05b 100644 --- a/scripts/vm/hypervisor/xenserver/xenserver65/patch +++ b/scripts/vm/hypervisor/xenserver/xenserver65/patch @@ -35,7 +35,9 @@ cloudstack_plugins.conf=..,0644,/etc/xensource cloudstack_pluginlib.py=..,0755,/etc/xapi.d/plugins ovstunnel=..,0755,/etc/xapi.d/plugins cloud-plugin-storage=..,0755,/etc/xapi.d/plugins -systemvm.iso=../../../../../vms,0644,/opt/xensource/packages/iso +agent.zip=../../../../../vms,0644,/opt/xensource/packages/resources/ +cloud-scripts.tgz=../../../../../vms,0644,/opt/xensource/packages/resources/ +patch-sysvms.sh=../../../../../vms,0644,/opt/xensource/packages/resources/ id_rsa.cloud=../../../systemvm,0600,/root/.ssh network_info.sh=..,0755,/opt/cloud/bin setupxenserver.sh=..,0755,/opt/cloud/bin diff --git a/scripts/vm/systemvm/injectkeys.sh b/scripts/vm/systemvm/injectkeys.sh index c05d232c0e7..747fb07c584 100755 --- a/scripts/vm/systemvm/injectkeys.sh +++ b/scripts/vm/systemvm/injectkeys.sh @@ -24,14 +24,8 @@ set -e TMP=/tmp -MOUNTPATH=${HOME}/systemvm_mnt -TMPDIR=${TMP}/cloud/systemvm umask 022 -clean_up() { - $SUDO umount $MOUNTPATH -} - copy_priv_key() { local newprivkey=$1 diff -q $newprivkey $(dirname $0)/id_rsa.cloud && return 0 @@ -45,8 +39,6 @@ then SUDO="sudo -n " fi -$SUDO mkdir -p $MOUNTPATH - [ $# -ne 1 ] && echo "Usage: $(basename $0) " && exit 3 newprivkey=$1 [ ! -f $newprivkey ] && echo "$(basename $0): Could not open $newprivkey" && exit 3 diff --git a/server/src/main/java/com/cloud/api/ResponseObjectTypeAdapter.java b/server/src/main/java/com/cloud/api/ResponseObjectTypeAdapter.java index 44baedc933b..f6f777efe3f 100644 --- a/server/src/main/java/com/cloud/api/ResponseObjectTypeAdapter.java +++ b/server/src/main/java/com/cloud/api/ResponseObjectTypeAdapter.java @@ -22,6 +22,7 @@ import java.lang.reflect.Type; import org.apache.cloudstack.api.ResponseObject; import org.apache.cloudstack.api.response.ExceptionResponse; import org.apache.cloudstack.api.response.SuccessResponse; +import org.apache.commons.lang3.StringUtils; import org.apache.log4j.Logger; import com.google.gson.JsonElement; @@ -38,6 +39,9 @@ public class ResponseObjectTypeAdapter implements JsonSerializer if (responseObj instanceof SuccessResponse) { obj.addProperty("success", ((SuccessResponse)responseObj).getSuccess()); + if (!StringUtils.isEmpty(((SuccessResponse) responseObj).getDisplayText())) { + obj.addProperty("details", ((SuccessResponse)responseObj).getDisplayText()); + } return obj; } else if (responseObj instanceof ExceptionResponse) { obj.addProperty("errorcode", ((ExceptionResponse)responseObj).getErrorCode()); diff --git a/server/src/main/java/com/cloud/api/query/dao/DomainRouterJoinDaoImpl.java b/server/src/main/java/com/cloud/api/query/dao/DomainRouterJoinDaoImpl.java index 0ed234c51e1..70a20c64741 100644 --- a/server/src/main/java/com/cloud/api/query/dao/DomainRouterJoinDaoImpl.java +++ b/server/src/main/java/com/cloud/api/query/dao/DomainRouterJoinDaoImpl.java @@ -24,6 +24,7 @@ import javax.inject.Inject; import org.apache.cloudstack.annotation.AnnotationService; import org.apache.cloudstack.annotation.dao.AnnotationDao; import org.apache.cloudstack.context.CallContext; +import org.apache.commons.lang3.StringUtils; import org.apache.log4j.Logger; import org.springframework.stereotype.Component; @@ -90,13 +91,28 @@ public class DomainRouterJoinDaoImpl extends GenericDaoBase getEntityType() { return VirtualMachine.class; } + + public String getSoftwareVersion() { + return softwareVersion; + } } diff --git a/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyManagerImpl.java b/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyManagerImpl.java index ff384bc845c..4b02e7f4226 100644 --- a/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyManagerImpl.java +++ b/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyManagerImpl.java @@ -29,9 +29,12 @@ import java.util.Map; import javax.inject.Inject; import javax.naming.ConfigurationException; +import com.cloud.utils.PasswordGenerator; import org.apache.cloudstack.agent.lb.IndirectAgentLB; +import org.apache.cloudstack.ca.CAManager; import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService; +import org.apache.cloudstack.framework.ca.Certificate; import org.apache.cloudstack.framework.config.ConfigKey; import org.apache.cloudstack.framework.config.Configurable; import org.apache.cloudstack.framework.config.dao.ConfigurationDao; @@ -221,6 +224,10 @@ public class ConsoleProxyManagerImpl extends ManagerBase implements ConsoleProxy private VirtualMachineManager virtualMachineManager; @Inject private IndirectAgentLB indirectAgentLB; + @Inject + private CAManager caManager; + @Inject + private NetworkOrchestrationService networkMgr; private ConsoleProxyListener consoleProxyListener; @@ -1204,7 +1211,11 @@ public class ConsoleProxyManagerImpl extends ManagerBase implements ConsoleProxy @Override public boolean finalizeVirtualMachineProfile(VirtualMachineProfile profile, DeployDestination dest, ReservationContext context) { - + final Map sshAccessDetails = networkMgr.getSystemVMAccessDetails(profile.getVirtualMachine()); + final Map ipAddressDetails = new HashMap<>(sshAccessDetails); + ipAddressDetails.remove("router.name"); + final Certificate certificate = caManager.issueCertificate(null, Arrays.asList(profile.getHostName(), profile.getInstanceName()), + new ArrayList<>(ipAddressDetails.values()), CAManager.CertValidityPeriod.value(), null); ConsoleProxyVO vm = consoleProxyDao.findById(profile.getId()); Map details = userVmDetailsDao.listDetailsKeyPairs(vm.getId()); vm.setDetails(details); @@ -1276,7 +1287,7 @@ public class ConsoleProxyManagerImpl extends ManagerBase implements ConsoleProxy if (dc.getDns2() != null) { buf.append(" dns2=").append(dc.getDns2()); } - + buf.append(" keystore_password=").append(VirtualMachineGuru.getEncodedString(PasswordGenerator.generateRandomPassword(16))); String bootArgs = buf.toString(); if (s_logger.isDebugEnabled()) { s_logger.debug("Boot Args for " + profile + ": " + bootArgs); diff --git a/server/src/main/java/com/cloud/hypervisor/kvm/discoverer/LibvirtServerDiscoverer.java b/server/src/main/java/com/cloud/hypervisor/kvm/discoverer/LibvirtServerDiscoverer.java index 50356d05c61..4f1fcb26a7c 100644 --- a/server/src/main/java/com/cloud/hypervisor/kvm/discoverer/LibvirtServerDiscoverer.java +++ b/server/src/main/java/com/cloud/hypervisor/kvm/discoverer/LibvirtServerDiscoverer.java @@ -16,26 +16,6 @@ // under the License. package com.cloud.hypervisor.kvm.discoverer; -import java.net.InetAddress; -import java.net.URI; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.UUID; - -import javax.inject.Inject; -import javax.naming.ConfigurationException; - -import org.apache.cloudstack.agent.lb.IndirectAgentLB; -import org.apache.cloudstack.ca.CAManager; -import org.apache.cloudstack.ca.SetupCertificateCommand; -import org.apache.cloudstack.direct.download.DirectDownloadManager; -import org.apache.cloudstack.framework.ca.Certificate; -import org.apache.cloudstack.utils.security.KeyStoreUtils; -import org.apache.log4j.Logger; - import com.cloud.agent.AgentManager; import com.cloud.agent.Listener; import com.cloud.agent.api.AgentControlAnswer; @@ -68,6 +48,24 @@ import com.cloud.utils.StringUtils; import com.cloud.utils.exception.CloudRuntimeException; import com.cloud.utils.ssh.SSHCmdHelper; import com.trilead.ssh2.Connection; +import org.apache.cloudstack.agent.lb.IndirectAgentLB; +import org.apache.cloudstack.ca.CAManager; +import org.apache.cloudstack.ca.SetupCertificateCommand; +import org.apache.cloudstack.direct.download.DirectDownloadManager; +import org.apache.cloudstack.framework.ca.Certificate; +import org.apache.cloudstack.utils.security.KeyStoreUtils; +import org.apache.log4j.Logger; + +import javax.inject.Inject; +import javax.naming.ConfigurationException; +import java.net.InetAddress; +import java.net.URI; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; import static com.cloud.configuration.ConfigurationManagerImpl.ADD_HOST_ON_SERVICE_RESTART_KVM; @@ -163,6 +161,7 @@ public abstract class LibvirtServerDiscoverer extends DiscovererBase implements validityPeriod = 1; } + String keystorePassword = PasswordGenerator.generateRandomPassword(16); final SSHCmdHelper.SSHCmdResult keystoreSetupResult = SSHCmdHelper.sshExecuteCmdWithResult(sshConnection, String.format("sudo /usr/share/cloudstack-common/scripts/util/%s " + "/etc/cloudstack/agent/agent.properties " + @@ -171,7 +170,7 @@ public abstract class LibvirtServerDiscoverer extends DiscovererBase implements "/etc/cloudstack/agent/%s", KeyStoreUtils.KS_SETUP_SCRIPT, KeyStoreUtils.KS_FILENAME, - PasswordGenerator.generateRandomPassword(16), + keystorePassword, validityPeriod, KeyStoreUtils.CSR_FILENAME)); @@ -186,21 +185,22 @@ public abstract class LibvirtServerDiscoverer extends DiscovererBase implements final SetupCertificateCommand certificateCommand = new SetupCertificateCommand(certificate); final SSHCmdHelper.SSHCmdResult setupCertResult = SSHCmdHelper.sshExecuteCmdWithResult(sshConnection, - String.format("sudo /usr/share/cloudstack-common/scripts/util/%s " + - "/etc/cloudstack/agent/agent.properties " + - "/etc/cloudstack/agent/%s %s " + - "/etc/cloudstack/agent/%s \"%s\" " + - "/etc/cloudstack/agent/%s \"%s\" " + - "/etc/cloudstack/agent/%s \"%s\"", - KeyStoreUtils.KS_IMPORT_SCRIPT, - KeyStoreUtils.KS_FILENAME, - KeyStoreUtils.SSH_MODE, - KeyStoreUtils.CERT_FILENAME, - certificateCommand.getEncodedCertificate(), - KeyStoreUtils.CACERT_FILENAME, - certificateCommand.getEncodedCaCertificates(), - KeyStoreUtils.PKEY_FILENAME, - certificateCommand.getEncodedPrivateKey())); + String.format("sudo /usr/share/cloudstack-common/scripts/util/%s " + + "/etc/cloudstack/agent/agent.properties %s " + + "/etc/cloudstack/agent/%s %s " + + "/etc/cloudstack/agent/%s \"%s\" " + + "/etc/cloudstack/agent/%s \"%s\" " + + "/etc/cloudstack/agent/%s \"%s\"", + KeyStoreUtils.KS_IMPORT_SCRIPT, + keystorePassword, + KeyStoreUtils.KS_FILENAME, + KeyStoreUtils.SSH_MODE, + KeyStoreUtils.CERT_FILENAME, + certificateCommand.getEncodedCertificate(), + KeyStoreUtils.CACERT_FILENAME, + certificateCommand.getEncodedCaCertificates(), + KeyStoreUtils.PKEY_FILENAME, + certificateCommand.getEncodedPrivateKey())); if (setupCertResult != null && !setupCertResult.isSuccess()) { throw new CloudRuntimeException("Failed to setup certificate in the KVM agent's keystore file, please see logs and configure manually!"); @@ -471,7 +471,7 @@ public abstract class LibvirtServerDiscoverer extends DiscovererBase implements String hostOs = ssCmd.getHostDetails().get("Host.OS"); if (!hostOsInCluster.equalsIgnoreCase(hostOs)) { throw new IllegalArgumentException("Can't add host: " + firstCmd.getPrivateIpAddress() + " with hostOS: " + hostOs + " into a cluster," + - "in which there are " + hostOsInCluster + " hosts added"); + "in which there are " + hostOsInCluster + " hosts added"); } } diff --git a/server/src/main/java/com/cloud/network/NetworkServiceImpl.java b/server/src/main/java/com/cloud/network/NetworkServiceImpl.java index 9b747650908..3a87cee0b11 100644 --- a/server/src/main/java/com/cloud/network/NetworkServiceImpl.java +++ b/server/src/main/java/com/cloud/network/NetworkServiceImpl.java @@ -2345,9 +2345,9 @@ public class NetworkServiceImpl extends ManagerBase implements NetworkService, C @Override @ActionEvent(eventType = EventTypes.EVENT_NETWORK_RESTART, eventDescription = "restarting network", async = true) - public boolean restartNetwork(Long networkId, boolean cleanup, boolean makeRedundant, User user) throws ConcurrentOperationException, ResourceUnavailableException, InsufficientCapacityException { + public boolean restartNetwork(Long networkId, boolean cleanup, boolean makeRedundant, boolean livePatch, User user) throws ConcurrentOperationException, ResourceUnavailableException, InsufficientCapacityException { NetworkVO network = getNetworkVO(networkId, "Network with specified id doesn't exist"); - return restartNetwork(network, cleanup, makeRedundant, user); + return restartNetwork(network, cleanup, makeRedundant, livePatch, user); } private NetworkVO getNetworkVO(Long networkId, String errMsgFormat) { @@ -2359,7 +2359,7 @@ public class NetworkServiceImpl extends ManagerBase implements NetworkService, C } @ActionEvent(eventType = EventTypes.EVENT_NETWORK_RESTART, eventDescription = "restarting network", async = true) - public boolean restartNetwork(NetworkVO network, boolean cleanup, boolean makeRedundant, User user) throws ConcurrentOperationException, ResourceUnavailableException, InsufficientCapacityException { + public boolean restartNetwork(NetworkVO network, boolean cleanup, boolean makeRedundant, boolean livePatch, User user) throws ConcurrentOperationException, ResourceUnavailableException, InsufficientCapacityException { // Don't allow to restart network if it's not in Implemented/Setup state if (!(network.getState() == Network.State.Implemented || network.getState() == Network.State.Setup)) { @@ -2384,9 +2384,11 @@ public class NetworkServiceImpl extends ManagerBase implements NetworkService, C } cleanup = true; } - + if (cleanup) { + livePatch = false; + } long id = network.getId(); - boolean success = _networkMgr.restartNetwork(id, callerAccount, user, cleanup); + boolean success = _networkMgr.restartNetwork(id, callerAccount, user, cleanup, livePatch); if (success) { s_logger.debug(String.format("Network id=%d is restarted successfully.",id)); } else { @@ -2406,8 +2408,9 @@ public class NetworkServiceImpl extends ManagerBase implements NetworkService, C throwInvalidIdException("Cannot restart a VPC tier with cleanup, please restart the whole VPC.", network.getUuid(), "network tier"); } boolean makeRedundant = cmd.getMakeRedundant(); + boolean livePatch = cmd.getLivePatch(); User callerUser = _accountMgr.getActiveUser(CallContext.current().getCallingUserId()); - return restartNetwork(network, cleanup, makeRedundant, callerUser); + return restartNetwork(network, cleanup, makeRedundant, livePatch, callerUser); } @Override diff --git a/server/src/main/java/com/cloud/network/router/NetworkHelper.java b/server/src/main/java/com/cloud/network/router/NetworkHelper.java index 9e4a5571b44..ea008e4c4ca 100644 --- a/server/src/main/java/com/cloud/network/router/NetworkHelper.java +++ b/server/src/main/java/com/cloud/network/router/NetworkHelper.java @@ -65,6 +65,7 @@ public interface NetworkHelper { * @return */ public abstract boolean checkRouterVersion(VirtualRouter router); + public abstract boolean checkRouterTemplateVersion(VirtualRouter router); public abstract List startRouters( RouterDeploymentDefinition routerDeploymentDefinition) diff --git a/server/src/main/java/com/cloud/network/router/NetworkHelperImpl.java b/server/src/main/java/com/cloud/network/router/NetworkHelperImpl.java index dfe33100845..d9e0833b896 100644 --- a/server/src/main/java/com/cloud/network/router/NetworkHelperImpl.java +++ b/server/src/main/java/com/cloud/network/router/NetworkHelperImpl.java @@ -27,6 +27,7 @@ import java.util.Map; import javax.annotation.PostConstruct; import javax.inject.Inject; +import com.cloud.utils.validation.ChecksumUtil; import org.apache.cloudstack.api.ApiConstants; import org.apache.log4j.Logger; @@ -269,6 +270,27 @@ public class NetworkHelperImpl implements NetworkHelper { @Override public boolean checkRouterVersion(final VirtualRouter router) { + if (!VirtualNetworkApplianceManager.RouterVersionCheckEnabled.value()) { + // Router version check is disabled. + return true; + } + if (router.getTemplateVersion() == null) { + return false; + } + final long dcid = router.getDataCenterId(); + String routerVersion = CloudStackVersion.trimRouterVersion(router.getTemplateVersion()); + String currentCheckSum = ChecksumUtil.calculateCurrentChecksum(router.getName(), "vms/cloud-scripts.tgz"); + String routerChecksum = router.getScriptsVersion() == null ? "" : router.getScriptsVersion(); + boolean routerVersionMatch = CloudStackVersion.compare(routerVersion, NetworkOrchestrationService.MinVRVersion.valueIn(dcid)) >= 0; + if (routerVersionMatch) { + return true; + } + boolean routerCheckSumMatch = currentCheckSum.equals(routerChecksum); + return routerCheckSumMatch; + } + + @Override + public boolean checkRouterTemplateVersion(final VirtualRouter router) { if (!VirtualNetworkApplianceManager.RouterVersionCheckEnabled.value()) { // Router version check is disabled. return true; diff --git a/server/src/main/java/com/cloud/network/router/VirtualNetworkApplianceManagerImpl.java b/server/src/main/java/com/cloud/network/router/VirtualNetworkApplianceManagerImpl.java index e561acca393..cd676aa6292 100644 --- a/server/src/main/java/com/cloud/network/router/VirtualNetworkApplianceManagerImpl.java +++ b/server/src/main/java/com/cloud/network/router/VirtualNetworkApplianceManagerImpl.java @@ -46,6 +46,7 @@ import javax.inject.Inject; import javax.naming.ConfigurationException; import com.cloud.offering.DiskOffering; +import com.cloud.server.ManagementServer; import org.apache.cloudstack.alert.AlertService; import org.apache.cloudstack.alert.AlertService.AlertType; import org.apache.cloudstack.api.command.admin.router.RebootRouterCmd; @@ -66,6 +67,7 @@ import org.apache.cloudstack.managed.context.ManagedContextRunnable; import org.apache.cloudstack.network.router.deployment.RouterDeploymentDefinitionBuilder; import org.apache.cloudstack.network.topology.NetworkTopology; import org.apache.cloudstack.network.topology.NetworkTopologyContext; +import org.apache.cloudstack.utils.CloudStackVersion; import org.apache.cloudstack.utils.identity.ManagementServerNode; import org.apache.cloudstack.utils.usage.UsageUtils; import org.apache.commons.lang3.StringUtils; @@ -364,6 +366,7 @@ Configurable, StateListener routerNics = _nicDao.listByVmId(profile.getId()); @@ -3209,7 +3217,7 @@ Configurable, StateListener rebootRouters(final List routers) { final List jobIds = new ArrayList(); for (final DomainRouterVO router : routers) { - if (!_nwHelper.checkRouterVersion(router)) { + if (!_nwHelper.checkRouterTemplateVersion(router)) { s_logger.debug("Upgrading template for router: " + router.getId()); final Map params = new HashMap(); params.put("ctxUserId", "1"); diff --git a/server/src/main/java/com/cloud/network/vpc/VpcManagerImpl.java b/server/src/main/java/com/cloud/network/vpc/VpcManagerImpl.java index 7c86a887481..6120b468023 100644 --- a/server/src/main/java/com/cloud/network/vpc/VpcManagerImpl.java +++ b/server/src/main/java/com/cloud/network/vpc/VpcManagerImpl.java @@ -1718,13 +1718,14 @@ public class VpcManagerImpl extends ManagerBase implements VpcManager, VpcProvis final long vpcId = cmd.getId(); final boolean cleanUp = cmd.getCleanup(); final boolean makeRedundant = cmd.getMakeredundant(); + final boolean livePatch = cmd.getLivePatch(); final User callerUser = _accountMgr.getActiveUser(CallContext.current().getCallingUserId()); - return restartVpc(vpcId, cleanUp, makeRedundant, callerUser); + return restartVpc(vpcId, cleanUp, makeRedundant, livePatch, callerUser); } @Override @ActionEvent(eventType = EventTypes.EVENT_VPC_RESTART, eventDescription = "restarting vpc") - public boolean restartVpc(Long vpcId, boolean cleanUp, boolean makeRedundant, User user) throws ConcurrentOperationException, ResourceUnavailableException, InsufficientCapacityException { + public boolean restartVpc(Long vpcId, boolean cleanUp, boolean makeRedundant, boolean livePatch, User user) throws ConcurrentOperationException, ResourceUnavailableException, InsufficientCapacityException { Vpc vpc = getActiveVpc(vpcId); if (vpc == null) { final InvalidParameterValueException ex = new InvalidParameterValueException("Unable to find Enabled VPC by id specified"); @@ -1767,7 +1768,11 @@ public class VpcManagerImpl extends ManagerBase implements VpcManager, VpcProvis return true; } - restartVPCNetworks(vpcId, callerAccount, user, cleanUp); + if (cleanUp) { + livePatch = false; + } + + restartVPCNetworks(vpcId, callerAccount, user, cleanUp, livePatch); s_logger.debug("Starting VPC " + vpc + " as a part of VPC restart process without cleanup"); if (!startVpc(vpcId, false)) { @@ -1785,11 +1790,11 @@ public class VpcManagerImpl extends ManagerBase implements VpcManager, VpcProvis } } - private void restartVPCNetworks(long vpcId, Account callerAccount, User callerUser, boolean cleanUp) throws InsufficientCapacityException, ResourceUnavailableException { + private void restartVPCNetworks(long vpcId, Account callerAccount, User callerUser, boolean cleanUp, boolean livePatch) throws InsufficientCapacityException, ResourceUnavailableException { List networks = _ntwkModel.listNetworksByVpc(vpcId); for (Network network: networks) { - if (network.isRestartRequired()) { - _ntwkMgr.restartNetwork(network.getId(), callerAccount, callerUser, cleanUp); + if (network.isRestartRequired() || livePatch) { + _ntwkMgr.restartNetwork(network.getId(), callerAccount, callerUser, cleanUp, livePatch); } } } diff --git a/server/src/main/java/com/cloud/server/ConfigurationServerImpl.java b/server/src/main/java/com/cloud/server/ConfigurationServerImpl.java index cbf20d405da..4bd31457861 100644 --- a/server/src/main/java/com/cloud/server/ConfigurationServerImpl.java +++ b/server/src/main/java/com/cloud/server/ConfigurationServerImpl.java @@ -667,7 +667,6 @@ public class ConfigurationServerImpl extends ManagerBase implements Configuratio s_logger.info("Keypairs already in database, updating local copy"); updateKeyPairsOnDisk(homeDir); } - s_logger.info("Going to update systemvm iso with generated keypairs if needed"); try { copyPrivateKeyToHosts(pubkeyfile.getAbsolutePath(), privkeyfile.getAbsolutePath()); } catch (CloudRuntimeException e) { @@ -742,13 +741,10 @@ public class ConfigurationServerImpl extends ManagerBase implements Configuratio s_logger.info("Trying to copy private keys to hosts"); String injectScript = getInjectScript(); String scriptPath = Script.findScript("", injectScript); - String systemVmIsoPath = Script.findScript("", "vms/systemvm.iso"); if (scriptPath == null) { throw new CloudRuntimeException("Unable to find key inject script " + injectScript); } - if (systemVmIsoPath == null) { - throw new CloudRuntimeException("Unable to find systemvm iso vms/systemvm.iso"); - } + Script command = null; if(isOnWindows()) { command = new Script("python", s_logger); diff --git a/server/src/main/java/com/cloud/server/ManagementServer.java b/server/src/main/java/com/cloud/server/ManagementServer.java index 4e58a4f5576..7ecb6650373 100644 --- a/server/src/main/java/com/cloud/server/ManagementServer.java +++ b/server/src/main/java/com/cloud/server/ManagementServer.java @@ -22,6 +22,7 @@ import com.cloud.storage.GuestOSHypervisorVO; import com.cloud.storage.GuestOSVO; import com.cloud.utils.Pair; import com.cloud.utils.component.PluggableService; +import com.cloud.vm.VMInstanceVO; import com.cloud.vm.VirtualMachine; /** @@ -67,4 +68,6 @@ public interface ManagementServer extends ManagementService, PluggableService { public long getMemoryOrCpuCapacityByHost(Long hostId, short capacityType); + Pair updateSystemVM(VMInstanceVO systemVM, boolean forced); + } diff --git a/server/src/main/java/com/cloud/server/ManagementServerImpl.java b/server/src/main/java/com/cloud/server/ManagementServerImpl.java index 26d11eafb9b..4daca16be5b 100644 --- a/server/src/main/java/com/cloud/server/ManagementServerImpl.java +++ b/server/src/main/java/com/cloud/server/ManagementServerImpl.java @@ -43,6 +43,22 @@ import javax.crypto.spec.SecretKeySpec; import javax.inject.Inject; import javax.naming.ConfigurationException; +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.Command; +import com.cloud.agent.api.PatchSystemVmAnswer; +import com.cloud.agent.api.PatchSystemVmCommand; +import com.cloud.agent.api.routing.NetworkElementCommand; +import com.cloud.agent.manager.Commands; +import com.cloud.dc.DomainVlanMapVO; +import com.cloud.dc.dao.DomainVlanMapDao; +import com.cloud.exception.AgentUnavailableException; +import com.cloud.network.Networks; +import com.cloud.utils.db.UUIDManager; +import com.cloud.utils.fsm.StateMachine2; +import com.cloud.vm.DomainRouterVO; +import com.cloud.vm.NicVO; +import com.cloud.vm.dao.DomainRouterDao; +import com.cloud.vm.dao.NicDao; import org.apache.cloudstack.acl.ControlledEntity; import org.apache.cloudstack.acl.SecurityChecker; import org.apache.cloudstack.affinity.AffinityGroupProcessor; @@ -226,6 +242,7 @@ import org.apache.cloudstack.api.command.admin.swift.ListSwiftsCmd; import org.apache.cloudstack.api.command.admin.systemvm.DestroySystemVmCmd; import org.apache.cloudstack.api.command.admin.systemvm.ListSystemVMsCmd; import org.apache.cloudstack.api.command.admin.systemvm.MigrateSystemVMCmd; +import org.apache.cloudstack.api.command.admin.systemvm.PatchSystemVMCmd; import org.apache.cloudstack.api.command.admin.systemvm.RebootSystemVmCmd; import org.apache.cloudstack.api.command.admin.systemvm.ScaleSystemVMCmd; import org.apache.cloudstack.api.command.admin.systemvm.StartSystemVMCmd; @@ -585,6 +602,7 @@ import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreDao; import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreVO; import org.apache.cloudstack.storage.datastore.db.VolumeDataStoreDao; import org.apache.cloudstack.storage.datastore.db.VolumeDataStoreVO; +import org.apache.cloudstack.utils.CloudStackVersion; import org.apache.cloudstack.utils.identity.ManagementServerNode; import org.apache.commons.codec.binary.Base64; import org.apache.commons.collections.CollectionUtils; @@ -614,7 +632,6 @@ import com.cloud.consoleproxy.ConsoleProxyManager; import com.cloud.dc.AccountVlanMapVO; import com.cloud.dc.ClusterVO; import com.cloud.dc.DataCenterVO; -import com.cloud.dc.DomainVlanMapVO; import com.cloud.dc.HostPodVO; import com.cloud.dc.Pod; import com.cloud.dc.PodVlanMapVO; @@ -624,7 +641,6 @@ import com.cloud.dc.VlanVO; import com.cloud.dc.dao.AccountVlanMapDao; import com.cloud.dc.dao.ClusterDao; import com.cloud.dc.dao.DataCenterDao; -import com.cloud.dc.dao.DomainVlanMapDao; import com.cloud.dc.dao.HostPodDao; import com.cloud.dc.dao.PodVlanMapDao; import com.cloud.dc.dao.VlanDao; @@ -769,11 +785,13 @@ import com.cloud.vm.dao.VMInstanceDao; public class ManagementServerImpl extends ManagerBase implements ManagementServer, Configurable { public static final Logger s_logger = Logger.getLogger(ManagementServerImpl.class.getName()); + protected StateMachine2 _stateMachine; static final ConfigKey vmPasswordLength = new ConfigKey("Advanced", Integer.class, "vm.password.length", "6", "Specifies the length of a randomly generated password", false); static final ConfigKey sshKeyLength = new ConfigKey("Advanced", Integer.class, "ssh.key.length", "2048", "Specifies custom SSH key length (bit)", true, ConfigKey.Scope.Global); static final ConfigKey humanReadableSizes = new ConfigKey("Advanced", Boolean.class, "display.human.readable.sizes", "true", "Enables outputting human readable byte sizes to logs and usage records.", false, ConfigKey.Scope.Global); public static final ConfigKey customCsIdentifier = new ConfigKey("Advanced", String.class, "custom.cs.identifier", UUID.randomUUID().toString().split("-")[0].substring(4), "Custom identifier for the cloudstack installation", true, ConfigKey.Scope.Global); + private static final VirtualMachine.Type []systemVmTypes = { VirtualMachine.Type.SecondaryStorageVm, VirtualMachine.Type.ConsoleProxy}; @Inject public AccountManager _accountMgr; @@ -836,7 +854,7 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe @Inject private StoragePoolJoinDao _poolJoinDao; @Inject - private NetworkDao _networkDao; + private NetworkDao networkDao; @Inject private StorageManager _storageMgr; @Inject @@ -909,10 +927,17 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe private AnnotationDao annotationDao; @Inject private DomainVlanMapDao _domainVlanMapDao; + @Inject + private NicDao nicDao; + @Inject + DomainRouterDao routerDao; + @Inject + public UUIDManager uuidMgr; private LockControllerListener _lockControllerListener; private final ScheduledExecutorService _eventExecutor = Executors.newScheduledThreadPool(1, new NamedThreadFactory("EventChecker")); private final ScheduledExecutorService _alertExecutor = Executors.newScheduledThreadPool(1, new NamedThreadFactory("AlertChecker")); + private static final int patchCommandTimeout = 600000; private Map _configs; @@ -952,6 +977,7 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe public ManagementServerImpl() { setRunLevel(ComponentLifecycle.RUN_LEVEL_APPLICATION_MAINLOOP); + setStateMachine(); } public List getUserAuthenticators() { @@ -1010,6 +1036,10 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe return true; } + private void setStateMachine() { + _stateMachine = VirtualMachine.State.getStateMachine(); + } + @Override public boolean start() { s_logger.info("Startup CloudStack management server..."); @@ -2197,9 +2227,9 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe if (ip == null) { throw new InvalidParameterValueException("Please specify a valid ipaddress id"); } - network = _networkDao.findById(ip.getSourceNetworkId()); + network = networkDao.findById(ip.getSourceNetworkId()); } else { - network = _networkDao.findById(networkId); + network = networkDao.findById(networkId); } if (network == null || network.getGuestType() != Network.GuestType.Shared) { throw new InvalidParameterValueException("Please specify a valid network id"); @@ -2271,7 +2301,7 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe } if (associatedNetworkId != null) { - _accountMgr.checkAccess(caller, null, false, _networkDao.findById(associatedNetworkId)); + _accountMgr.checkAccess(caller, null, false, networkDao.findById(associatedNetworkId)); sc.setParameters("associatedNetworkIdEq", associatedNetworkId); } if (vpcId != null) { @@ -2292,7 +2322,7 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe owner = _accountMgr.finalizeOwner(CallContext.current().getCallingAccount(), cmd.getAccountName(), cmd.getDomainId(), null); } if (associatedNetworkId != null) { - NetworkVO guestNetwork = _networkDao.findById(associatedNetworkId); + NetworkVO guestNetwork = networkDao.findById(associatedNetworkId); if (zoneId == null) { zoneId = guestNetwork.getDataCenterId(); } else if (zoneId != guestNetwork.getDataCenterId()) { @@ -3580,6 +3610,7 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe cmdList.add(UploadResourceIconCmd.class); cmdList.add(DeleteResourceIconCmd.class); cmdList.add(ListResourceIconCmd.class); + cmdList.add(PatchSystemVMCmd.class); cmdList.add(ListGuestVlansCmd.class); // Out-of-band management APIs for admins @@ -3986,7 +4017,7 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe boolean elasticLoadBalancerEnabled = false; boolean KVMSnapshotEnabled = false; String supportELB = "false"; - final List networks = _networkDao.listSecurityGroupEnabledNetworks(); + final List networks = networkDao.listSecurityGroupEnabledNetworks(); if (networks != null && !networks.isEmpty()) { securityGroupsEnabled = true; final String elbEnabled = _configDao.getValue(Config.ElasticLoadBalancerEnabled.key()); @@ -4717,6 +4748,104 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe _dpMgr.cleanupVMReservations(); } + @Override + public Pair patchSystemVM(PatchSystemVMCmd cmd) { + Long systemVmId = cmd.getId(); + boolean forced = cmd.isForced(); + + if (systemVmId == null) { + throw new InvalidParameterValueException("Please provide a valid ID of a system VM to be patched"); + } + + final VMInstanceVO systemVm = _vmInstanceDao.findByIdTypes(systemVmId, systemVmTypes); + if (systemVm == null) { + throw new InvalidParameterValueException(String.format("Unable to find SystemVm with id %s. patchSystemVm API can be used to patch CPVM / SSVM only.", systemVmId)); + } + + return updateSystemVM(systemVm, forced); + } + + + private String getControlIp(final long systemVmId) { + String controlIpAddress = null; + final List nics = nicDao.listByVmId(systemVmId); + for (final NicVO n : nics) { + final NetworkVO nc = networkDao.findById(n.getNetworkId()); + if (nc != null && nc.getTrafficType() == Networks.TrafficType.Control) { + controlIpAddress = n.getIPv4Address(); + // router will have only one control IP + break; + } + } + + if (controlIpAddress == null) { + s_logger.warn(String.format("Unable to find systemVm's control ip in its attached NICs!. systemVmId: %s", systemVmId)); + VMInstanceVO systemVM = _vmInstanceDao.findById(systemVmId); + return systemVM.getPrivateIpAddress(); + } + + return controlIpAddress; + } + + public Pair updateSystemVM(VMInstanceVO systemVM, boolean forced) { + String msg = String.format("Unable to patch SystemVM: %s as it is not in Running state. Please destroy and recreate the SystemVM.", systemVM); + if (systemVM.getState() != State.Running) { + s_logger.error(msg); + return new Pair<>(false, msg); + } + return patchSystemVm(systemVM, forced); + } + + private boolean updateRouterDetails(Long routerId, String scriptVersion, String templateVersion) { + DomainRouterVO router = routerDao.findById(routerId); + if (router == null) { + throw new CloudRuntimeException(String.format("Failed to find router with id: %s", routerId)); + } + + router.setTemplateVersion(templateVersion); + router.setScriptsVersion(scriptVersion); + String codeVersion = getVersion(); + if (StringUtils.isNotEmpty(codeVersion)) { + codeVersion = CloudStackVersion.parse(codeVersion).toString(); + } + router.setSoftwareVersion(codeVersion); + return routerDao.update(routerId, router); + } + + private Pair patchSystemVm(VMInstanceVO systemVM, boolean forced) { + PatchSystemVmAnswer answer; + final PatchSystemVmCommand command = new PatchSystemVmCommand(); + command.setAccessDetail(NetworkElementCommand.ROUTER_IP, getControlIp(systemVM.getId())); + command.setAccessDetail(NetworkElementCommand.ROUTER_NAME, systemVM.getInstanceName()); + command.setForced(forced); + try { + Commands cmds = new Commands(Command.OnError.Stop); + cmds.addCommand(command); + Answer[] answers = _agentMgr.send(systemVM.getHostId(), cmds, patchCommandTimeout); + answer = (PatchSystemVmAnswer) answers[0]; + if (!answer.getResult()) { + String errMsg = String.format("Failed to patch systemVM %s due to %s", systemVM.getInstanceName(), answer.getDetails()); + s_logger.error(errMsg); + return new Pair<>(false, errMsg); + } + } catch (AgentUnavailableException | OperationTimedoutException e) { + String errMsg = "SystemVM live patch failed"; + s_logger.error(errMsg, e); + return new Pair<>(false, String.format("%s due to: %s", errMsg, e.getMessage())); + } + s_logger.info(String.format("Successfully patched system VM %s", systemVM.getInstanceName())); + List routerTypes = new ArrayList<>(); + routerTypes.add(VirtualMachine.Type.DomainRouter); + routerTypes.add(VirtualMachine.Type.InternalLoadBalancerVm); + if (routerTypes.contains(systemVM.getType())) { + boolean updated = updateRouterDetails(systemVM.getId(), answer.getScriptsVersion(), answer.getTemplateVersion()); + if (!updated) { + s_logger.warn("Failed to update router's script and template version details"); + } + } + return new Pair<>(true, answer.getDetails()); + } + public List getStoragePoolAllocators() { return _storagePoolAllocators; } diff --git a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java index 8c006a2cf0f..549f1642978 100644 --- a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java +++ b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java @@ -4934,7 +4934,7 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir } @Override - public boolean finalizeStart(VirtualMachineProfile profile, long hostId, Commands cmds, ReservationContext context) { + public boolean finalizeStart(VirtualMachineProfile profile, long hostId, Commands cmds, ReservationContext context) { UserVmVO vm = _vmDao.findById(profile.getId()); Answer[] answersToCmds = cmds.getAnswers(); diff --git a/server/src/main/java/org/apache/cloudstack/ca/CAManagerImpl.java b/server/src/main/java/org/apache/cloudstack/ca/CAManagerImpl.java index 1e5d8127053..2e7e756c49a 100644 --- a/server/src/main/java/org/apache/cloudstack/ca/CAManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/ca/CAManagerImpl.java @@ -184,11 +184,14 @@ public class CAManagerImpl extends ManagerBase implements CAManager { } CallContext.current().setEventDetails("host id: " + host.getId()); CallContext.current().putContextParameter(Host.class, host.getUuid()); - final String csr; + String csr = null; + try { - csr = generateKeyStoreAndCsr(host, null); - if (StringUtils.isEmpty(csr)) { - return false; + if (host.getType() != Host.Type.ConsoleProxy && host.getType() != Host.Type.SecondaryStorageVM) { + csr = generateKeyStoreAndCsr(host, null); + if (StringUtils.isEmpty(csr)) { + return false; + } } final Certificate certificate = issueCertificate(csr, Arrays.asList(host.getName(), host.getPrivateIpAddress()), Arrays.asList(host.getPrivateIpAddress(), host.getPublicIpAddress(), host.getStorageIpAddress()), CAManager.CertValidityPeriod.value(), caProvider); return deployCertificate(host, certificate, reconnect, null); @@ -209,6 +212,11 @@ public class CAManagerImpl extends ManagerBase implements CAManager { return answer.getCsr(); } + private boolean isValidSystemVMType(Host.Type type) { + return Host.Type.SecondaryStorageVM.equals(type) || + Host.Type.ConsoleProxy.equals(type); + } + @Override public boolean deployCertificate(final Host host, final Certificate certificate, final Boolean reconnect, final Map sshAccessDetails) throws AgentUnavailableException, OperationTimedoutException { diff --git a/server/src/test/java/com/cloud/vpc/MockNetworkManagerImpl.java b/server/src/test/java/com/cloud/vpc/MockNetworkManagerImpl.java index 4941561a929..0ba9dfa3ab9 100644 --- a/server/src/test/java/com/cloud/vpc/MockNetworkManagerImpl.java +++ b/server/src/test/java/com/cloud/vpc/MockNetworkManagerImpl.java @@ -231,7 +231,7 @@ public class MockNetworkManagerImpl extends ManagerBase implements NetworkOrches } @Override - public boolean restartNetwork(Long networkId, boolean cleanup, boolean makeRedundant, User user) throws ConcurrentOperationException, ResourceUnavailableException, InsufficientCapacityException { + public boolean restartNetwork(Long networkId, boolean cleanup, boolean makeRedundant, boolean livePatch, User user) throws ConcurrentOperationException, ResourceUnavailableException, InsufficientCapacityException { return false; } @@ -777,7 +777,7 @@ public class MockNetworkManagerImpl extends ManagerBase implements NetworkOrches * @see com.cloud.network.NetworkManager#restartNetwork(java.lang.Long, com.cloud.user.Account, com.cloud.user.User, boolean) */ @Override - public boolean restartNetwork(Long networkId, Account callerAccount, User callerUser, boolean cleanup) throws ConcurrentOperationException, + public boolean restartNetwork(Long networkId, Account callerAccount, User callerUser, boolean cleanup, boolean livePatch) throws ConcurrentOperationException, ResourceUnavailableException, InsufficientCapacityException { // TODO Auto-generated method stub return false; diff --git a/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxy.java b/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxy.java index 22dc29be60d..908819abb8b 100644 --- a/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxy.java +++ b/services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxy.java @@ -31,6 +31,7 @@ import java.util.Map; import java.util.Properties; import java.util.concurrent.Executor; +import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.StringUtils; import org.apache.log4j.xml.DOMConfigurator; import org.eclipse.jetty.websocket.api.Session; @@ -74,6 +75,7 @@ public class ConsoleProxy { static boolean standaloneStart = false; static String encryptorPassword = "Dummy"; + static final String[] skipProperties = new String[]{"certificate", "cacertificate", "keystore_password", "privatekey"}; private static void configLog4j() { final ClassLoader loader = Thread.currentThread().getContextClassLoader(); @@ -109,6 +111,9 @@ public class ConsoleProxy { s_logger.info("Configure console proxy..."); for (Object key : conf.keySet()) { s_logger.info("Property " + (String)key + ": " + conf.getProperty((String)key)); + if (!ArrayUtils.contains(skipProperties, key)) { + s_logger.info("Property " + (String)key + ": " + conf.getProperty((String)key)); + } } String s = conf.getProperty("consoleproxy.httpListenPort"); @@ -247,7 +252,9 @@ public class ConsoleProxy { if (conf != null) { for (Object key : conf.keySet()) { - s_logger.info("Context property " + (String)key + ": " + conf.getProperty((String)key)); + if (!ArrayUtils.contains(skipProperties, key)) { + s_logger.info("Context property " + (String) key + ": " + conf.getProperty((String) key)); + } } } diff --git a/services/secondary-storage/controller/src/main/java/org/apache/cloudstack/secondarystorage/SecondaryStorageManagerImpl.java b/services/secondary-storage/controller/src/main/java/org/apache/cloudstack/secondarystorage/SecondaryStorageManagerImpl.java index fa42d000f33..10229ae17db 100644 --- a/services/secondary-storage/controller/src/main/java/org/apache/cloudstack/secondarystorage/SecondaryStorageManagerImpl.java +++ b/services/secondary-storage/controller/src/main/java/org/apache/cloudstack/secondarystorage/SecondaryStorageManagerImpl.java @@ -30,12 +30,15 @@ import java.util.Map; import javax.inject.Inject; import javax.naming.ConfigurationException; +import com.cloud.utils.PasswordGenerator; import org.apache.cloudstack.agent.lb.IndirectAgentLB; +import org.apache.cloudstack.ca.CAManager; import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService; 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.ZoneScope; +import org.apache.cloudstack.framework.ca.Certificate; import org.apache.cloudstack.framework.config.ConfigKey; import org.apache.cloudstack.framework.config.Configurable; import org.apache.cloudstack.framework.config.dao.ConfigurationDao; @@ -245,6 +248,8 @@ public class SecondaryStorageManagerImpl extends ManagerBase implements Secondar private ImageStoreDetailsUtil imageStoreDetailsUtil; @Inject private IndirectAgentLB indirectAgentLB; + @Inject + private CAManager caManager; private long _capacityScanInterval = DEFAULT_CAPACITY_SCAN_INTERVAL_IN_MILLISECONDS; private int _secStorageVmMtuSize; @@ -1070,6 +1075,12 @@ public class SecondaryStorageManagerImpl extends ManagerBase implements Secondar return false; } + final Map sshAccessDetails = _networkMgr.getSystemVMAccessDetails(profile.getVirtualMachine()); + final Map ipAddressDetails = new HashMap<>(sshAccessDetails); + ipAddressDetails.remove("router.name"); + final Certificate certificate = caManager.issueCertificate(null, Arrays.asList(profile.getHostName(), profile.getInstanceName()), + new ArrayList<>(ipAddressDetails.values()), CAManager.CertValidityPeriod.value(), null); + StringBuilder buf = profile.getBootArgsBuilder(); buf.append(" template=domP type=secstorage"); buf.append(" host=").append(com.cloud.utils.StringUtils.toCSVList(indirectAgentLB.getManagementServerList(dest.getHost().getId(), dest.getDataCenter().getId(), null))); @@ -1157,7 +1168,7 @@ public class SecondaryStorageManagerImpl extends ManagerBase implements Secondar } String nfsVersion = imageStoreDetailsUtil != null ? imageStoreDetailsUtil.getNfsVersion(secStore.getId()) : null; buf.append(" nfsVersion=").append(nfsVersion); - + buf.append(" keystore_password=").append(VirtualMachineGuru.getEncodedString(PasswordGenerator.generateRandomPassword(16))); String bootArgs = buf.toString(); if (s_logger.isDebugEnabled()) { s_logger.debug(String.format("Boot args for machine profile [%s]: [%s].", profile.toString(), bootArgs)); diff --git a/systemvm/debian/etc/systemd/system/cloud-early-config.service b/systemvm/debian/etc/systemd/system/cloud-early-config.service index 2af52767cc3..cfaf5e7cd44 100644 --- a/systemvm/debian/etc/systemd/system/cloud-early-config.service +++ b/systemvm/debian/etc/systemd/system/cloud-early-config.service @@ -2,11 +2,8 @@ Description=CloudStack post-boot patching service using cmdline DefaultDependencies=no -Before=network-pre.target -Wants=network-pre.target - -Requires=local-fs.target -After=local-fs.target +Requires=local-fs.target cloud-preinit.service +After=local-fs.target cloud-preinit.service [Install] WantedBy=multi-user.target diff --git a/systemvm/debian/etc/systemd/system/cloud-postinit.service b/systemvm/debian/etc/systemd/system/cloud-postinit.service index cb20aaf7c06..31a1f3b8f59 100644 --- a/systemvm/debian/etc/systemd/system/cloud-postinit.service +++ b/systemvm/debian/etc/systemd/system/cloud-postinit.service @@ -1,7 +1,6 @@ [Unit] Description=CloudStack post-patching init script After=cloud-early-config.service network.target local-fs.target -Before=ssh.service [Install] WantedBy=multi-user.target diff --git a/systemvm/debian/etc/systemd/system/cloud-preinit.service b/systemvm/debian/etc/systemd/system/cloud-preinit.service new file mode 100644 index 00000000000..373cd9ed66f --- /dev/null +++ b/systemvm/debian/etc/systemd/system/cloud-preinit.service @@ -0,0 +1,18 @@ +[Unit] +Description=CloudStack service to initialize interfaces +DefaultDependencies=no + +Before=network-pre.target +Wants=network-pre.target + +Requires=local-fs.target +After=local-fs.target + +[Install] +WantedBy=multi-user.target + +[Service] +Type=oneshot +ExecStart=/opt/cloud/bin/setup/init.sh +RemainAfterExit=true +TimeoutStartSec=5min diff --git a/systemvm/debian/opt/cloud/bin/patched.sh b/systemvm/debian/opt/cloud/bin/patched.sh new file mode 100644 index 00000000000..bfe0f64495a --- /dev/null +++ b/systemvm/debian/opt/cloud/bin/patched.sh @@ -0,0 +1,19 @@ +#!/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. + +ls -lrt $1 \ No newline at end of file diff --git a/systemvm/debian/opt/cloud/bin/setup/bootstrap.sh b/systemvm/debian/opt/cloud/bin/setup/bootstrap.sh index 2335d649a95..4720237543f 100755 --- a/systemvm/debian/opt/cloud/bin/setup/bootstrap.sh +++ b/systemvm/debian/opt/cloud/bin/setup/bootstrap.sh @@ -15,7 +15,7 @@ # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. - +set -x PATH="/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin" CMDLINE=/var/cache/cloud/cmdline @@ -29,124 +29,6 @@ log_it() { log_action_msg "$@" } -hypervisor() { - if [ -d /proc/xen ]; then - mount -t xenfs none /proc/xen - $(dmesg | grep -q "Xen HVM") - if [ $? -eq 0 ]; then # 1=PV,0=HVM - echo "xen-hvm" && return 0 - else - echo "xen-pv" && return 0 - fi - fi - - [ -x /usr/sbin/virt-what ] && local facts=( $(virt-what) ) - if [ "$facts" != "" ]; then - # Xen HVM is recognized as Hyperv when Viridian extensions are enabled - if [ "${facts[-1]}" == "xen-domU" ] && [ "${facts[0]}" == "hyperv" ]; then - echo "xen-hvm" && return 0 - else - echo ${facts[-1]} && return 0 - fi - fi - - grep -q QEMU /proc/cpuinfo && echo "kvm" && return 0 - grep -q QEMU /var/log/messages && echo "kvm" && return 0 - - vmware-checkvm &> /dev/null && echo "vmware" && return 0 - - echo "unknown" && return 1 -} - -config_guest() { - [ ! -d /proc/xen ] && sed -i 's/^vc/#vc/' /etc/inittab && telinit q - [ -d /proc/xen ] && sed -i 's/^#vc/vc/' /etc/inittab && telinit q - - systemctl daemon-reload - - case $HYPERVISOR in - xen-pv|xen-domU) - systemctl stop ntpd - systemctl disable ntpd - systemctl enable xe-daemon - systemctl start xe-daemon - - cat /proc/cmdline > $CMDLINE - sed -i "s/%/ /g" $CMDLINE - ;; - xen-hvm) - systemctl stop ntpd - systemctl disable ntpd - systemctl enable xe-daemon - systemctl start xe-daemon - - if [ ! -f /usr/bin/xenstore-read ]; then - log_it "ERROR: xentools not installed, cannot found xenstore-read" && exit 5 - fi - /usr/bin/xenstore-read vm-data/cloudstack/init > $CMDLINE - sed -i "s/%/ /g" $CMDLINE - ;; - kvm) - # Configure kvm hotplug support - if grep -E 'CONFIG_HOTPLUG_PCI=y|CONFIG_HOTPLUG_PCI_ACPI=y' /boot/config-`uname -r`; then - log_it "acpiphp and pci_hotplug module already compiled in" - else - modprobe acpiphp 2> /dev/null && log_it "acpiphp module loaded" || true - modprobe pci_hotplug 2> /dev/null && log_it "pci_hotplug module loaded" || true - fi - - sed -i -e "/^s0:2345:respawn.*/d" /etc/inittab - sed -i -e "/6:23:respawn/a\s0:2345:respawn:/sbin/getty -L 115200 ttyS0 vt102" /etc/inittab - systemctl enable qemu-guest-agent - systemctl start 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" - # Remove old configuration files in /etc/cloudstack if VR is booted from cloudstack - rm -rf /etc/cloudstack/*.json - log_it "Booting from cloudstack, remove old configuration files in /etc/cloudstack/" - break - fi - sleep 1 - done - if [ ! -s $CMDLINE ]; then - log_it "Failed to receive the cmdline file via the qemu-guest-agent" - fi - ;; - vmware) - # system time sync'd with host via vmware tools - systemctl stop ntpd - systemctl disable ntpd - systemctl enable open-vm-tools - systemctl start open-vm-tools - - vmtoolsd --cmd 'machine.id.get' > $CMDLINE - ;; - virtualpc|hyperv) - # Hyper-V is recognized as virtualpc hypervisor type. Boot args are passed using KVP Daemon - systemctl enable hyperv-daemons.hv-fcopy-daemon.service hyperv-daemons.hv-kvp-daemon.service hyperv-daemons.hv-vss-daemon.service - systemctl start hyperv-daemons.hv-fcopy-daemon.service hyperv-daemons.hv-kvp-daemon.service hyperv-daemons.hv-vss-daemon.service - sleep 5 - cp -f /var/opt/hyperv/.kvp_pool_0 $CMDLINE - cat /dev/null > /var/opt/hyperv/.kvp_pool_0 - ;; - virtualbox) - # Virtualbox is used to test the virtual router - # get the commandline from a dmistring (yes, hacky!) - dmidecode | grep cmdline | sed 's/^.*cmdline://' > $CMDLINE - RV=$? - if [ $RV -ne 0 ] ; then - log_it "Failed to get cmdline from a virtualbox dmi property" - fi - ;; - esac - - # Find and export guest type - export TYPE=$(grep -Po 'type=\K[a-zA-Z]*' $CMDLINE) -} - patch_systemvm() { local patchfile=$1 local backupfolder="/tmp/.conf.backup" @@ -158,6 +40,8 @@ patch_systemvm() { fi rm /usr/local/cloud/systemvm -rf mkdir -p /usr/local/cloud/systemvm + ls -lrt $patchfile + echo "All" | unzip $patchfile -d /usr/local/cloud/systemvm >$logfile 2>&1 find /usr/local/cloud/systemvm/ -name \*.sh | xargs chmod 555 if [ -f $backupfolder/cloud.jks ]; then @@ -171,7 +55,7 @@ patch_systemvm() { } patch() { - local PATCH_MOUNT=/media/cdrom + local PATCH_MOUNT=/tmp/ local logfile="/var/log/patchsystemvm.log" if [ "$TYPE" == "consoleproxy" ] || [ "$TYPE" == "secstorage" ] && [ -f ${PATCH_MOUNT}/agent.zip ] && [ -f /var/cache/cloud/patch.required ] @@ -188,11 +72,7 @@ patch() { rm -f /var/cache/cloud/patch.required chmod -x /etc/systemd/system/cloud*.service systemctl daemon-reload - umount $PATCH_MOUNT || true - if [ -f /mnt/cmdline ]; then - cat /mnt/cmdline > $CMDLINE - fi return 0 } @@ -212,11 +92,7 @@ config_sysctl() { bootstrap() { log_it "Bootstrapping systemvm appliance" - export HYPERVISOR=$(hypervisor) - [ $? -ne 0 ] && log_it "Failed to detect hypervisor type, bailing out" && exit 10 - log_it "Starting guest services for $HYPERVISOR" - - config_guest + export TYPE=$(grep -Po 'type=\K[a-zA-Z]*' $CMDLINE) patch config_sysctl diff --git a/systemvm/debian/opt/cloud/bin/setup/cksnode.sh b/systemvm/debian/opt/cloud/bin/setup/cksnode.sh index ac672a26539..0b5df04ded2 100755 --- a/systemvm/debian/opt/cloud/bin/setup/cksnode.sh +++ b/systemvm/debian/opt/cloud/bin/setup/cksnode.sh @@ -28,6 +28,7 @@ setup_k8s_node() { # set default ssh port and restart sshd service sed -i 's/3922/22/g' /etc/ssh/sshd_config + systemctl restart ssh # Prevent root login > /root/.ssh/authorized_keys @@ -39,7 +40,6 @@ setup_k8s_node() { log_it "Swap disabled" log_it "Setting up interfaces" - setup_common eth0 setup_system_rfc1918_internal log_it "Setting up entry in hosts" diff --git a/systemvm/debian/opt/cloud/bin/setup/cloud-early-config b/systemvm/debian/opt/cloud/bin/setup/cloud-early-config index d0ebd0b6814..d76079b69e4 100755 --- a/systemvm/debian/opt/cloud/bin/setup/cloud-early-config +++ b/systemvm/debian/opt/cloud/bin/setup/cloud-early-config @@ -15,7 +15,7 @@ # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. - +set -x PATH="/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin" # Clear boot up flag, it would be created by rc.local after boot up done @@ -31,52 +31,74 @@ log_it() { log_action_msg "$@" } +validate_checksums() { + local oldmd5= + [ -f ${1} ] && oldmd5=$(cat ${1}) + local newmd5= + [ -f ${2} ] && newmd5=$(md5sum ${2} | awk '{print $1}') + log_it "Scripts checksum detected: oldmd5=$oldmd5 newmd5=$newmd5" >> /dev/null 2>&1 + echo "oldmd5='${oldmd5}'; newmd5='${newmd5}'" +} + patch() { - local PATCH_MOUNT=/media/cdrom - local patchfile=$PATCH_MOUNT/cloud-scripts.tgz + local PATCH_MOUNT=/tmp + local PATCH_SCRIPTS=cloud-scripts.tgz + local oldpatchfile=/usr/share/cloud/$PATCH_SCRIPTS + local patchfile=$PATCH_MOUNT/$PATCH_SCRIPTS local privkey=$PATCH_MOUNT/authorized_keys local md5file=/var/cache/cloud/cloud-scripts-signature - local cdrom_dev= mkdir -p $PATCH_MOUNT - if [ -e /dev/xvdd ]; then - cdrom_dev=/dev/xvdd - elif [ -e /dev/cdrom ]; then - cdrom_dev=/dev/cdrom - elif [ -e /dev/cdrom1 ]; then - cdrom_dev=/dev/cdrom1 - elif [ -e /dev/cdrom2 ]; then - cdrom_dev=/dev/cdrom2 - elif [ -e /dev/cdrom3 ]; then - cdrom_dev=/dev/cdrom3 - fi - if [ -f /var/cache/cloud/authorized_keys ]; then privkey=/var/cache/cloud/authorized_keys fi - if [ -n "$cdrom_dev" ]; then - mount -o ro $cdrom_dev $PATCH_MOUNT - local oldmd5= - [ -f ${md5file} ] && oldmd5=$(cat ${md5file}) - local newmd5= - [ -f ${patchfile} ] && newmd5=$(md5sum ${patchfile} | awk '{print $1}') - log_it "Scripts checksum detected: oldmd5=$oldmd5 newmd5=$newmd5" - if [ "$oldmd5" != "$newmd5" ] && [ -f ${patchfile} ] && [ "$newmd5" != "" ] - then - tar xzf $patchfile -C / - echo ${newmd5} > ${md5file} - log_it "Patched scripts using $patchfile" - touch /var/cache/cloud/patch.required - fi - - if [ -f $privkey ]; then - cp -f $privkey /root/.ssh/ - chmod go-rwx /root/.ssh/authorized_keys - fi + eval $(validate_checksums $md5file $oldpatchfile) + if [ "$oldmd5" == "$newmd5" ] && [ -d /usr/local/cloud/systemvm ] && [ "$(ls -A /usr/local/cloud/systemvm)" ]; then + log_it "Checksum matches, no need to patch" + return 0 fi - return 0 + CMDLINE=/var/cache/cloud/cmdline + export TYPE=$(grep -Po 'type=\K[a-zA-Z]*' $CMDLINE) + retry=60 + local patched=false + if [ "$TYPE" != "cksnode" ]; then + while [ $retry -gt 0 ] + do + if [ -f $patchfile ]; then + eval $(validate_checksums $md5file $patchfile) + if [ "$oldmd5" != "$newmd5" ] && [ -f ${patchfile} ] && [ "$newmd5" != "" ] + then + tar xzf $patchfile -C / + echo ${newmd5} > ${md5file} + log_it "Patched scripts using $patchfile" + touch /var/cache/cloud/patch.required + fi + + if [ -f $privkey ]; then + cp -f $privkey /root/.ssh/ + chmod go-rwx /root/.ssh/authorized_keys + fi + patched=true + break + fi + + sleep 2 + retry=$(($retry-1)) + log_it "Could not find patch file, retrying" + done + + if [ $retry -eq 0 ] && [ "$patched" == "false" ]; then + return 2 + fi + return 0 + fi +} + +cleanup() { + rm -rf /tmp/agent.zip + mv /tmp/cloud-scripts.tgz /usr/share/cloud/cloud-scripts.tgz } start() { @@ -99,6 +121,7 @@ start() { patch sync /opt/cloud/bin/setup/bootstrap.sh + cleanup log_it "Finished setting up systemvm" exit 0 diff --git a/systemvm/debian/opt/cloud/bin/setup/common.sh b/systemvm/debian/opt/cloud/bin/setup/common.sh index c7b01c25bd4..cad8af8c015 100755 --- a/systemvm/debian/opt/cloud/bin/setup/common.sh +++ b/systemvm/debian/opt/cloud/bin/setup/common.sh @@ -15,7 +15,7 @@ # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. - +set -x PATH="/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin" . /lib/lsb/init-functions @@ -767,6 +767,21 @@ parse_cmd_line() { authorized_key) export AUTHORIZED_KEYS=$VALUE ;; + keystore_password) + export KEYSTORE_PSSWD=$VALUE + ;; + validity) + export KS_VALIDITY=$VALUE + ;; + certificate) + export CERTIFICATE=$VALUE + ;; + cacertificate) + export CACERTIFICATE=$VALUE + ;; + privatekey) + export PRIVATEKEY=$VALUE + ;; esac done echo -e "\n\t}\n}" >> ${CHEF_TMP_FILE} diff --git a/systemvm/debian/opt/cloud/bin/setup/consoleproxy.sh b/systemvm/debian/opt/cloud/bin/setup/consoleproxy.sh index 3f00f3da43a..8006f6bb244 100755 --- a/systemvm/debian/opt/cloud/bin/setup/consoleproxy.sh +++ b/systemvm/debian/opt/cloud/bin/setup/consoleproxy.sh @@ -25,7 +25,6 @@ setup_console_proxy() { echo "haproxy dnsmasq apache2 nfs-common portmap" > /var/cache/cloud/disabled_svcs mkdir -p /var/log/cloud - setup_common eth0 eth1 eth2 setup_system_rfc1918_internal log_it "Setting up entry in hosts" @@ -33,21 +32,11 @@ setup_console_proxy() { public_ip=`getPublicIp` echo "$public_ip $NAME" >> /etc/hosts - log_it "Applying iptables rules" - cp /etc/iptables/iptables-consoleproxy /etc/iptables/rules.v4 - - log_it "Configuring sshd" - local hyp=$HYPERVISOR - if [ "$hyp" == "vmware" ] || [ "$hyp" == "hyperv" ]; then - setup_sshd $ETH1_IP "eth1" - else - setup_sshd $ETH0_IP "eth0" - fi - disable_rpfilter enable_fwding 0 enable_irqbalance 0 rm -f /etc/logrotate.d/cloud + } setup_console_proxy diff --git a/systemvm/debian/opt/cloud/bin/setup/dhcpsrvr.sh b/systemvm/debian/opt/cloud/bin/setup/dhcpsrvr.sh index 9161aeb37ee..27dbf3e8f62 100755 --- a/systemvm/debian/opt/cloud/bin/setup/dhcpsrvr.sh +++ b/systemvm/debian/opt/cloud/bin/setup/dhcpsrvr.sh @@ -25,7 +25,6 @@ dhcpsrvr_svcs() { setup_dhcpsrvr() { log_it "Setting up dhcp server system vm" - setup_common eth0 eth1 setup_dnsmasq setup_apache2 $ETH0_IP @@ -36,18 +35,10 @@ setup_dhcpsrvr() { enable_irqbalance 0 enable_fwding 0 - cp /etc/iptables/iptables-router /etc/iptables/rules.v4 - #Only allow DNS service for current network sed -i "s/-A INPUT -i eth0 -p udp -m udp --dport 53 -j ACCEPT/-A INPUT -i eth0 -p udp -m udp --dport 53 -s $DHCP_RANGE\/$CIDR_SIZE -j ACCEPT/g" /etc/iptables/rules.v4 sed -i "s/-A INPUT -i eth0 -p tcp -m tcp --dport 53 -j ACCEPT/-A INPUT -i eth0 -p tcp -m tcp --dport 53 -s $DHCP_RANGE\/$CIDR_SIZE -j ACCEPT/g" /etc/iptables/rules.v4 - if [ "$SSHONGUEST" == "true" ] - then - setup_sshd $ETH0_IP "eth0" - else - setup_sshd $ETH1_IP "eth1" - fi } dhcpsrvr_svcs diff --git a/systemvm/debian/opt/cloud/bin/setup/elbvm.sh b/systemvm/debian/opt/cloud/bin/setup/elbvm.sh index ae16b4bcb7c..52132ccea96 100755 --- a/systemvm/debian/opt/cloud/bin/setup/elbvm.sh +++ b/systemvm/debian/opt/cloud/bin/setup/elbvm.sh @@ -25,20 +25,11 @@ elbvm_svcs() { setup_elbvm() { log_it "Setting up Elastic Load Balancer system vm" - setup_common eth0 eth1 sed -i /$NAME/d /etc/hosts public_ip=$ETH2_IP [ "$ETH2_IP" == "0.0.0.0" ] || [ "$ETH2_IP" == "" ] && public_ip=$ETH0_IP echo "$public_ip $NAME" >> /etc/hosts - cp /etc/iptables/iptables-elbvm /etc/iptables/rules.v4 - if [ "$SSHONGUEST" == "true" ] - then - setup_sshd $ETH0_IP "eth0" - else - setup_sshd $ETH1_IP "eth1" - fi - enable_fwding 0 enable_irqbalance 0 } diff --git a/systemvm/debian/opt/cloud/bin/setup/ilbvm.sh b/systemvm/debian/opt/cloud/bin/setup/ilbvm.sh index ac801b200e6..a130674d1e8 100755 --- a/systemvm/debian/opt/cloud/bin/setup/ilbvm.sh +++ b/systemvm/debian/opt/cloud/bin/setup/ilbvm.sh @@ -25,7 +25,6 @@ ilbvm_svcs() { setup_ilbvm() { log_it "Setting up Internal Load Balancer system vm" - setup_common eth0 eth1 #eth0 = guest network, eth1=control network sed -i /$NAME/d /etc/hosts diff --git a/systemvm/debian/opt/cloud/bin/setup/init.sh b/systemvm/debian/opt/cloud/bin/setup/init.sh new file mode 100644 index 00000000000..a1ac48e46b5 --- /dev/null +++ b/systemvm/debian/opt/cloud/bin/setup/init.sh @@ -0,0 +1,217 @@ +#!/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 -x +PATH="/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin" +CMDLINE=/var/cache/cloud/cmdline + +hypervisor() { + if [ -d /proc/xen ]; then + mount -t xenfs none /proc/xen + $(dmesg | grep -q "Xen HVM") + if [ $? -eq 0 ]; then # 1=PV,0=HVM + echo "xen-hvm" && return 0 + else + echo "xen-pv" && return 0 + fi + fi + + [ -x /usr/sbin/virt-what ] && local facts=( $(virt-what) ) + if [ "$facts" != "" ]; then + # Xen HVM is recognized as Hyperv when Viridian extensions are enabled + if [ "${facts[-1]}" == "xen-domU" ] && [ "${facts[0]}" == "hyperv" ]; then + echo "xen-hvm" && return 0 + else + echo ${facts[-1]} && return 0 + fi + fi + + grep -q QEMU /proc/cpuinfo && echo "kvm" && return 0 + grep -q QEMU /var/log/messages && echo "kvm" && return 0 + + vmware-checkvm &> /dev/null && echo "vmware" && return 0 + + echo "unknown" && return 1 +} + +config_guest() { + [ ! -d /proc/xen ] && sed -i 's/^vc/#vc/' /etc/inittab && telinit q + [ -d /proc/xen ] && sed -i 's/^#vc/vc/' /etc/inittab && telinit q + + systemctl daemon-reload + + case $HYPERVISOR in + xen-pv|xen-domU) + systemctl stop ntpd + systemctl disable ntpd + systemctl enable xe-daemon + systemctl start xe-daemon + + cat /proc/cmdline > $CMDLINE + sed -i "s/%/ /g" $CMDLINE + ;; + xen-hvm) + systemctl stop ntpd + systemctl disable ntpd + systemctl enable xe-daemon + systemctl start xe-daemon + + if [ ! -f /usr/bin/xenstore-read ]; then + log_it "ERROR: xentools not installed, cannot found xenstore-read" && exit 5 + fi + /usr/bin/xenstore-read vm-data/cloudstack/init > $CMDLINE + sed -i "s/%/ /g" $CMDLINE + ;; + kvm) + # Configure kvm hotplug support + if grep -E 'CONFIG_HOTPLUG_PCI=y|CONFIG_HOTPLUG_PCI_ACPI=y' /boot/config-`uname -r`; then + log_it "acpiphp and pci_hotplug module already compiled in" + else + modprobe acpiphp 2> /dev/null && log_it "acpiphp module loaded" || true + modprobe pci_hotplug 2> /dev/null && log_it "pci_hotplug module loaded" || true + fi + + sed -i -e "/^s0:2345:respawn.*/d" /etc/inittab + sed -i -e "/6:23:respawn/a\s0:2345:respawn:/sbin/getty -L 115200 ttyS0 vt102" /etc/inittab + systemctl enable qemu-guest-agent + systemctl start 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" + # Remove old configuration files in /etc/cloudstack if VR is booted from cloudstack + rm -rf /etc/cloudstack/*.json + log_it "Booting from cloudstack, remove old configuration files in /etc/cloudstack/" + break + fi + sleep 1 + done + if [ ! -s $CMDLINE ]; then + log_it "Failed to receive the cmdline file via the qemu-guest-agent" + fi + ;; + vmware) + # system time sync'd with host via vmware tools + systemctl stop ntpd + systemctl disable ntpd + systemctl enable open-vm-tools + systemctl start open-vm-tools + + vmtoolsd --cmd 'machine.id.get' > $CMDLINE + ;; + virtualpc|hyperv) + # Hyper-V is recognized as virtualpc hypervisor type. Boot args are passed using KVP Daemon + systemctl enable hyperv-daemons.hv-fcopy-daemon.service hyperv-daemons.hv-kvp-daemon.service hyperv-daemons.hv-vss-daemon.service + systemctl start hyperv-daemons.hv-fcopy-daemon.service hyperv-daemons.hv-kvp-daemon.service hyperv-daemons.hv-vss-daemon.service + sleep 5 + cp -f /var/opt/hyperv/.kvp_pool_0 $CMDLINE + cat /dev/null > /var/opt/hyperv/.kvp_pool_0 + ;; + virtualbox) + # Virtualbox is used to test the virtual router + # get the commandline from a dmistring (yes, hacky!) + dmidecode | grep cmdline | sed 's/^.*cmdline://' > $CMDLINE + RV=$? + if [ $RV -ne 0 ] ; then + log_it "Failed to get cmdline from a virtualbox dmi property" + fi + ;; + esac + + if [ -f /mnt/cmdline ]; then + cat /mnt/cmdline > $CMDLINE + fi + + # Find and export guest type + export TYPE=$(grep -Po 'type=\K[a-zA-Z]*' $CMDLINE) +} + +setup_interface_sshd() { + + if [ "$TYPE" != "cksnode" ]; then + log_it "Applying iptables rules" + if [ "$TYPE" != "dhcpsrvr" ]; then + cp /etc/iptables/iptables-$TYPE /etc/iptables/rules.v4 + else + cp /etc/iptables/iptables-router /etc/iptables/rules.v4 + fi + fi + + if [ "$TYPE" == "consoleproxy" ] || [ "$TYPE" == "secstorage" ]; then + setup_common eth0 eth1 eth2 + log_it "Configuring sshd" + local hyp=$HYPERVISOR + if [ "$hyp" == "vmware" ] || [ "$hyp" == "hyperv" ]; then + setup_sshd $ETH1_IP "eth1" + else + setup_sshd $ETH0_IP "eth0" + fi + + elif [ "$TYPE" == "router" ]; then + if [ -n "$ETH2_IP" ]; then + setup_common eth0 eth1 eth2 + + if [ -n "$EXTRA_PUBNICS" ]; then + for ((i = 3; i < 3 + $EXTRA_PUBNICS; i++)); do + setup_interface "$i" "0.0.0.0" "255.255.255.255" $GW "force" + done + fi + else + setup_common eth0 eth1 + if [ -n "$EXTRA_PUBNICS" ]; then + for ((i = 2; i < 2 + $EXTRA_PUBNICS; i++)); do + setup_interface "$i" "0.0.0.0" "255.255.255.255" $GW "force" + done + fi + fi + setup_sshd $ETH1_IP "eth1" + + elif [ "$TYPE" == "vpcrouter" ]; then + setup_interface "0" $ETH0_IP $ETH0_MASK $GW + setup_sshd $ETH0_IP "eth0" + + elif [ "$TYPE" == "ilbvm" ]; then + setup_common eth0 eth1 + setup_sshd $ETH1_IP "eth1" + + elif [ "$TYPE" == "elbvm" ] || [ "$TYPE" == "dhcpsrvr" ]; then + setup_common eth0 eth1 + if [ "$SSHONGUEST" == "true" ]; then + setup_sshd $ETH0_IP "eth0" + else + setup_sshd $ETH1_IP "eth1" + fi + elif [ "$TYPE" == "cksnode" ]; then + setup_common eth0 + fi + + systemctl restart systemd-journald + # Patch known systemd/sshd memory leak - https://github.com/systemd/systemd/issues/8015#issuecomment-476160981 + echo '@include null' >> /etc/pam.d/systemd-user + # Enable and Start SSH + systemctl enable --now --no-block ssh +} + +export HYPERVISOR=$(hypervisor) +[ $? -ne 0 ] && log_it "Failed to detect hypervisor type, bailing out" && exit 10 +log_it "Starting guest services for $HYPERVISOR" + +config_guest +source /opt/cloud/bin/setup/common.sh +setup_interface_sshd \ No newline at end of file diff --git a/systemvm/debian/opt/cloud/bin/setup/postinit.sh b/systemvm/debian/opt/cloud/bin/setup/postinit.sh index 04929302f51..ba5c394d9f2 100755 --- a/systemvm/debian/opt/cloud/bin/setup/postinit.sh +++ b/systemvm/debian/opt/cloud/bin/setup/postinit.sh @@ -23,17 +23,11 @@ log_it() { log_action_msg "$@" } -# Eject cdrom if any -CMDLINE=/var/cache/cloud/cmdline -export TYPE=$(grep -Po 'type=\K[a-zA-Z]*' $CMDLINE) -if [ "$TYPE" != "cksnode" ]; then - eject || true -fi - # Restart journald for setting changes to apply systemctl restart systemd-journald -TYPE=$(grep -Po 'type=\K[a-zA-Z]*' /var/cache/cloud/cmdline) +CMDLINE=/var/cache/cloud/cmdline +TYPE=$(grep -Po 'type=\K[a-zA-Z]*' $CMDLINE) if [ "$TYPE" == "router" ] || [ "$TYPE" == "vpcrouter" ] || [ "$TYPE" == "dhcpsrvr" ] then if [ -x /opt/cloud/bin/update_config.py ] @@ -71,10 +65,4 @@ then ip6tables-restore < $ipv6 fi -# Patch known systemd/sshd memory leak - https://github.com/systemd/systemd/issues/8015#issuecomment-476160981 -echo '@include null' >> /etc/pam.d/systemd-user - -# Enable and Start SSH -systemctl enable --now --no-block ssh - date > /var/cache/cloud/boot_up_done diff --git a/systemvm/debian/opt/cloud/bin/setup/router.sh b/systemvm/debian/opt/cloud/bin/setup/router.sh index e8f6edf0ceb..d7113c49302 100755 --- a/systemvm/debian/opt/cloud/bin/setup/router.sh +++ b/systemvm/debian/opt/cloud/bin/setup/router.sh @@ -43,23 +43,6 @@ setup_router() { oldmd5= [ -f "/etc/udev/rules.d/70-persistent-net.rules" ] && oldmd5=$(md5sum "/etc/udev/rules.d/70-persistent-net.rules" | awk '{print $1}') - if [ -n "$ETH2_IP" ]; then - setup_common eth0 eth1 eth2 - - if [ -n "$EXTRA_PUBNICS" ]; then - for ((i = 3; i < 3 + $EXTRA_PUBNICS; i++)); do - setup_interface "$i" "0.0.0.0" "255.255.255.255" $GW "force" - done - fi - else - setup_common eth0 eth1 - if [ -n "$EXTRA_PUBNICS" ]; then - for ((i = 2; i < 2 + $EXTRA_PUBNICS; i++)); do - setup_interface "$i" "0.0.0.0" "255.255.255.255" $GW "force" - done - fi - fi - log_it "Checking udev NIC assignment order changes" if [ "$NIC_MACS" != "" ] then @@ -88,8 +71,6 @@ setup_router() { enable_fwding 1 enable_rpsrfs 1 enable_passive_ftp 1 - cp /etc/iptables/iptables-router /etc/iptables/rules.v4 - setup_sshd $ETH1_IP "eth1" # Only allow DNS service for current network sed -i "s/-A INPUT -i eth0 -p udp -m udp --dport 53 -j ACCEPT/-A INPUT -i eth0 -p udp -m udp --dport 53 -s $DHCP_RANGE\/$CIDR_SIZE -j ACCEPT/g" /etc/iptables/rules.v4 diff --git a/systemvm/debian/opt/cloud/bin/setup/secstorage.sh b/systemvm/debian/opt/cloud/bin/setup/secstorage.sh index 13ed5c5d0ae..3b21ed5cae1 100755 --- a/systemvm/debian/opt/cloud/bin/setup/secstorage.sh +++ b/systemvm/debian/opt/cloud/bin/setup/secstorage.sh @@ -25,7 +25,6 @@ setup_secstorage() { echo "conntrackd keepalived haproxy dnsmasq" > /var/cache/cloud/disabled_svcs mkdir -p /var/log/cloud - setup_common eth0 eth1 eth2 setup_storage_network setup_system_rfc1918_internal @@ -37,14 +36,6 @@ setup_secstorage() { log_it "Applying iptables rules" cp /etc/iptables/iptables-secstorage /etc/iptables/rules.v4 - log_it "Configuring sshd" - local hyp=$HYPERVISOR - if [ "$hyp" == "vmware" ] || [ "$hyp" == "hyperv" ]; then - setup_sshd $ETH1_IP "eth1" - else - setup_sshd $ETH0_IP "eth0" - fi - log_it "Configuring apache2" setup_apache2 $ETH2_IP diff --git a/systemvm/debian/opt/cloud/bin/setup/vpcrouter.sh b/systemvm/debian/opt/cloud/bin/setup/vpcrouter.sh index f97fb161f47..bfb06218825 100755 --- a/systemvm/debian/opt/cloud/bin/setup/vpcrouter.sh +++ b/systemvm/debian/opt/cloud/bin/setup/vpcrouter.sh @@ -29,7 +29,6 @@ setup_vpcrouter() { auto lo eth0 iface lo inet loopback EOF - setup_interface "0" $ETH0_IP $ETH0_MASK $GW echo $NAME > /etc/hostname echo 'AVAHI_DAEMON_DETECT_LOCAL=0' > /etc/default/avahi-daemon @@ -86,7 +85,6 @@ EOF enable_fwding 1 enable_passive_ftp 1 cp /etc/iptables/iptables-vpcrouter /etc/iptables/rules.v4 - setup_sshd $ETH0_IP "eth0" cp /etc/vpcdnsmasq.conf /etc/dnsmasq.conf cp /etc/cloud-nic.rules /etc/udev/rules.d/cloud-nic.rules echo "" > /etc/dnsmasq.d/dhcphosts.txt diff --git a/systemvm/patch-sysvms.sh b/systemvm/patch-sysvms.sh new file mode 100644 index 00000000000..62e0dd6f1a8 --- /dev/null +++ b/systemvm/patch-sysvms.sh @@ -0,0 +1,132 @@ +#!/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. + +PATH="/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin" +backupfolder=/tmp/bkpup_live_patch +logfile="/var/log/livepatchsystemvm.log" +newpath="/tmp/" +CMDLINE=/var/cache/cloud/cmdline +md5file=/var/cache/cloud/cloud-scripts-signature +svcfile=/var/cache/cloud/enabled_svcs +TYPE=$(grep -Po 'type=\K[a-zA-Z]*' $CMDLINE) +patchfailed=0 +backuprestored=0 + +backup_old_package() { + mkdir -p $backupfolder + if [ -d /usr/local/cloud/systemvm/conf/ ]; then + echo "Backing up keystore file and certificates" > $logfile 2>&1 + mkdir -p $backupfolder/conf + cp -r /usr/local/cloud/systemvm/conf/* $backupfolder/conf + fi + if [ -d /usr/local/cloud/systemvm/ ]; then + echo "Backing up agent package" >> $logfile 2>&1 + cd /usr/local/cloud/systemvm/ + zip -r $backupfolder/agent.zip * >> $logfile 2>&1 2>&1 + cd - + fi + cp $md5file $backupfolder + echo "Backing up cloud-scripts file" >> $logfile 2>&1 + tar -zcvf $backupfolder/cloud-scripts.tgz /etc/ /var/ /opt/ /root/ >> $logfile 2>&1 +} + +restore_backup() { + echo "Restoring cloud scripts" >> $logfile 2>&1 + tar -xvf $backupfolder/cloud-scripts.tar -C / >> $logfile 2>&1 + echo "Restoring agent package" >> $logfile 2>&1 + if [ -f $backupfolder/agent.zip ]; then + unzip $backupfolder/agent.zip -d /usr/local/cloud/systemvm/ >> $logfile 2>&1 + echo "Restore keystore file and certificates" >> $logfile 2>&1 + mkdir -p "/usr/local/cloud/systemvm/conf/" + cp -r $backupfolder/conf/* /usr/local/cloud/systemvm/conf/ + fi + backuprestored=1 + restart_services + cp $backupfolder/cloud-scripts-signature $md5file +} + +update_checksum() { + newmd5=$(md5sum $1 | awk '{print $1}') + echo "checksum: " ${newmd5} >> $logfile 2>&1 + echo ${newmd5} > ${md5file} +} + +restart_services() { + systemctl daemon-reload + while IFS= read -r line + do + for svc in ${line}; do + systemctl is-active --quiet "$svc" + if [ $? -eq 0 ]; then + systemctl restart "$svc" + systemctl is-active --quiet "$svc" + if [ $? -gt 0 ]; then + echo "Failed to start "$svc" service. Patch Failed. Retrying again" >> $logfile 2>&1 + if [ $backuprestored == 0 ]; then + restore_backup + fi + patchfailed=1 + break + fi + fi + done + if [ $patchfailed == 1 ]; then + return + fi + done < "$svcfile" +} + +cleanup_systemVM() { + rm -rf $backupfolder + mv "$newpath"cloud-scripts.tgz /usr/share/cloud/cloud-scripts.tgz + rm -rf "$newpath""agent.zip" "$newpath""patch-sysvms.sh" +} + +patch_systemvm() { + rm -rf /usr/local/cloud/systemvm + + if [ "$TYPE" == "consoleproxy" ] || [ "$TYPE" == "secstorage" ]; then + echo "All" | unzip $newpath/agent.zip -d /usr/local/cloud/systemvm >> $logfile 2>&1 + mkdir -p /usr/local/cloud/systemvm + find /usr/local/cloud/systemvm/ -name \*.sh | xargs chmod 555 + fi + echo "Extracting cloud scripts" >> $logfile 2>&1 + tar -xvf $newpath/cloud-scripts.tgz -C / >> $logfile 2>&1 + + if [ -f $backupfolder/conf/cloud.jks ]; then + cp -r $backupfolder/conf/* /usr/local/cloud/systemvm/conf/ + echo "Restored keystore file and certs using backup" >> $logfile 2>&1 + fi + + update_checksum $newpath/cloud-scripts.tgz + + if [ "$TYPE" == "consoleproxy" ] || [ "$TYPE" == "secstorage" ] || [[ "$TYPE" == *router ]]; then + restart_services + fi +} + + +backup_old_package +patch_systemvm +cleanup_systemVM + +if [ $patchfailed == 0 ]; then + echo "version:$(cat ${md5file}) " +fi + +exit $patchfailed diff --git a/systemvm/pom.xml b/systemvm/pom.xml index 7fe4ae8a5e4..69e971b7471 100644 --- a/systemvm/pom.xml +++ b/systemvm/pom.xml @@ -88,6 +88,12 @@ agent.zip + + ${basedir} + + patch-sysvms.sh + + @@ -138,31 +144,6 @@ - - org.codehaus.mojo - exec-maven-plugin - 1.2.1 - - - package - - exec - - - - - ${mkisofs} - dist - - -quiet - -r - -o - systemvm.iso - agent.zip - cloud-scripts.tgz - - - diff --git a/tools/appliance/systemvmtemplate/scripts/configure_systemvm_services.sh b/tools/appliance/systemvmtemplate/scripts/configure_systemvm_services.sh index 67bb03491e4..115d340fe84 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.16.1 +CLOUDSTACK_RELEASE=4.17.0 function configure_apache2() { # Enable ssl, rewrite and auth @@ -52,7 +52,6 @@ function configure_cacerts() { # Add LetsEncrypt ca-cert wget https://letsencrypt.org/certs/lets-encrypt-r3.der wget https://letsencrypt.org/certs/isrgrootx1.der - keytool -trustcacerts -keystore /etc/ssl/certs/java/cacerts -storepass changeit -noprompt -importcert -alias letsencryptauthorityr3 -file lets-encrypt-r3.der keytool -trustcacerts -keystore /etc/ssl/certs/java/cacerts -storepass changeit -noprompt -importcert -alias letsencryptauthorityx1 -file isrgrootx1.der rm -f lets-encrypt-r3.der isrgrootx1.der @@ -71,6 +70,7 @@ function install_cloud_scripts() { chmod -x /etc/systemd/system/* || true systemctl daemon-reload + systemctl enable cloud-preinit systemctl enable cloud-early-config systemctl enable cloud-postinit } diff --git a/ui/public/locales/en.json b/ui/public/locales/en.json index 8e2f52658ad..70439a20428 100644 --- a/ui/public/locales/en.json +++ b/ui/public/locales/en.json @@ -46,6 +46,7 @@ "label.accept": "Accept", "label.accept.project.invitation": "Accept project invitation", "label.access": "Access", +"label.acess.kubernetes.nodes": "Access Kubernetes Nodes", "label.accesskey": "Access Key", "label.acess.kubernetes.nodes": "Access Kubernetes Nodes", "label.account": "Account", @@ -231,6 +232,8 @@ "label.action.migrate.systemvm": "Migrate System VM", "label.action.migrate.systemvm.processing": "Migrating System VM....", "label.action.migrate.systemvm.to.ps": "Migrate system VM to another primary storage", +"label.action.patch.systemvm": "Patch System VM", +"label.action.patch.systemvm.processing": "Patching System VM....", "label.action.project.add.account": "Add Account to Project", "label.action.project.add.user": "Add User to Project", "label.action.reboot.instance": "Reboot Instance", @@ -1329,6 +1332,7 @@ "label.list.nodes": "List nodes", "label.list.pods": "List pods", "label.list.services": "List services", +"label.livepatch": "Live Patch network's router(s)", "label.load.balancer": "Load Balancer", "label.load.balancing.policies": "Load balancing policies", "label.loadbalancerinstance": "Assigned VMs", @@ -2081,6 +2085,7 @@ "label.snmpcommunity": "SNMP Community", "label.snmpport": "SNMP Port", "label.sockettimeout": "Socket Timeout", +"label.softwareversion": "Software Version", "label.source.based": "SourceBased", "label.source.nat.supported": "SourceNAT Supported", "label.sourcecidr": "Source CIDR", @@ -2222,6 +2227,7 @@ "label.templatesubject": "Subject", "label.templatetotal": "Template", "label.templatetype": "Template Type", +"label.templateversion": "Template Version", "label.tftp.dir": "TFTP Directory", "label.tftpdir": "Tftp root directory", "label.theme.alert": "The setting is only visible to the current browser. To apply the setting, please download the JSON file and replace its content in the `theme` section of the `config.json` file under the path: `/public/config.json`", @@ -2581,6 +2587,8 @@ "message.action.host.enable.maintenance.mode": "Enabling maintenance mode will cause a live migration of all running instances on this host to any available host.", "message.action.instance.reset.password": "Please confirm that you want to change the ROOT password for this virtual machine.", "message.action.manage.cluster": "Please confirm that you want to manage the cluster.", +"message.action.patch.router": "Please confirm that you want to live patch the router.
This operation is equivalent updating the router packages and restarting the network without cleanup.", +"message.action.patch.systemvm": "Please confirm that you want to patch the System VM.", "message.action.primarystorage.enable.maintenance.mode": "Warning: placing the primary storage into maintenance mode will cause all VMs using volumes from it to be stopped. Do you want to continue?", "message.action.reboot.instance": "Please confirm that you want to reboot this instance.", "message.action.reboot.router": "All services provided by this virtual router will be interrupted. Please confirm that you want to reboot this router.", diff --git a/ui/src/components/view/ListView.vue b/ui/src/components/view/ListView.vue index 9134c18488e..4822abfe24c 100644 --- a/ui/src/components/view/ListView.vue +++ b/ui/src/components/view/ListView.vue @@ -288,6 +288,12 @@ {{ text }} {{ text }} + + diff --git a/ui/src/config/section/infra/ilbvms.js b/ui/src/config/section/infra/ilbvms.js index b09d3092ce2..1c27a960af6 100644 --- a/ui/src/config/section/infra/ilbvms.js +++ b/ui/src/config/section/infra/ilbvms.js @@ -22,8 +22,8 @@ export default { icon: 'share-alt-outlined', permission: ['listInternalLoadBalancerVMs'], params: { projectid: '-1' }, - columns: ['name', 'state', 'publicip', 'guestnetworkname', 'vpcname', 'version', 'hostname', 'account', 'zonename', 'requiresupgrade'], - details: ['name', 'id', 'version', 'requiresupgrade', 'guestnetworkname', 'vpcname', 'publicip', 'guestipaddress', 'linklocalip', 'serviceofferingname', 'networkdomain', 'isredundantrouter', 'redundantstate', 'hostname', 'account', 'zonename', 'created'], + columns: ['name', 'state', 'publicip', 'guestnetworkname', 'vpcname', 'version', 'softwareversion', 'hostname', 'account', 'zonename', 'requiresupgrade'], + details: ['name', 'id', 'version', 'softwareversion', 'requiresupgrade', 'guestnetworkname', 'vpcname', 'publicip', 'guestipaddress', 'linklocalip', 'serviceofferingname', 'networkdomain', 'isredundantrouter', 'redundantstate', 'hostname', 'account', 'zonename', 'created'], actions: [ { api: 'startInternalLoadBalancerVM', diff --git a/ui/src/config/section/infra/routers.js b/ui/src/config/section/infra/routers.js index 77c9d3d851f..cf5145637ed 100644 --- a/ui/src/config/section/infra/routers.js +++ b/ui/src/config/section/infra/routers.js @@ -24,9 +24,13 @@ export default { docHelp: 'adminguide/systemvm.html#virtual-router', permission: ['listRouters'], params: { projectid: '-1' }, - columns: ['name', 'state', 'publicip', 'guestnetworkname', 'vpcname', 'redundantstate', 'version', 'hostname', 'account', 'zonename', 'requiresupgrade'], + columns: () => { + var columns = ['name', 'state', 'publicip', 'guestnetworkname', 'vpcname', 'redundantstate', 'softwareversion', 'hostname', 'account', 'zonename', 'requiresupgrade'] + columns.splice(6, 0, { field: 'version', customTitle: 'templateversion' }) + return columns + }, searchFilters: ['name', 'zoneid', 'podid', 'clusterid'], - details: ['name', 'id', 'version', 'requiresupgrade', 'guestnetworkname', 'vpcname', 'publicip', 'guestipaddress', 'linklocalip', 'serviceofferingname', 'networkdomain', 'isredundantrouter', 'redundantstate', 'hostname', 'account', 'zonename', 'created'], + details: ['name', 'id', 'version', 'softwareversion', 'requiresupgrade', 'guestnetworkname', 'vpcname', 'publicip', 'guestipaddress', 'linklocalip', 'serviceofferingname', 'networkdomain', 'isredundantrouter', 'redundantstate', 'hostname', 'account', 'zonename', 'created'], resourceType: 'VirtualRouter', tabs: [{ name: 'details', @@ -84,6 +88,32 @@ export default { popup: true, groupMap: (selection, values) => { return selection.map(x => { return { id: x, forced: values.forced } }) } }, + { + api: 'restartNetwork', + icon: 'diff-outlined', + label: 'label.action.patch.systemvm', + message: 'message.action.patch.router', + dataView: true, + hidden: (record) => { return record.state === 'Running' }, + mapping: { + id: { + value: (record) => { return record.guestnetworkid } + }, + livepatch: { + value: (record) => { return true } + } + }, + groupAction: true, + popup: true, + groupMap: (selection, values, record) => { + return selection.map(x => { + const data = record.filter(y => { return y.id === x }) + return { + id: data[0].guestnetworkid, livepatch: true + } + }) + } + }, { api: 'scaleSystemVm', icon: 'arrows-alt-outlined', @@ -112,8 +142,8 @@ export default { message: 'message.confirm.upgrade.router.newer.template', docHelp: 'adminguide/systemvm.html#upgrading-virtual-routers', dataView: true, - groupAction: true, - show: (record) => { return record.requiresupgrade } + groupAction: true + // show: (record) => { return record.requiresupgrade } }, { api: 'migrateSystemVm', diff --git a/ui/src/config/section/infra/systemVms.js b/ui/src/config/section/infra/systemVms.js index 7d723267d72..9808f72fa51 100644 --- a/ui/src/config/section/infra/systemVms.js +++ b/ui/src/config/section/infra/systemVms.js @@ -137,6 +137,18 @@ export default { }, response: (result) => { return result && result.diagnostics && result.diagnostics.url ? `Please click the link to download the retrieved diagnostics:

${result.diagnostics.url}

` : 'Invalid response' } }, + { + api: 'patchSystemVm', + icon: 'diff-outlined', + label: 'label.action.patch.systemvm', + message: 'message.action.patch.systemvm', + dataView: true, + show: (record) => { return ['Running'].includes(record.state) }, + args: ['forced'], + groupAction: true, + popup: true, + groupMap: (selection) => { return selection.map(x => { return { id: x } }) } + }, { api: 'destroySystemVm', icon: 'delete-outlined', diff --git a/ui/src/config/section/network.js b/ui/src/config/section/network.js index c25c451c5e4..2421c024c87 100644 --- a/ui/src/config/section/network.js +++ b/ui/src/config/section/network.js @@ -104,7 +104,14 @@ export default { label: 'label.restart.network', message: 'message.restart.network', dataView: true, - args: (record) => record.vpcid == null ? ['cleanup'] : [], // if it is a tier in a VPC and so it has a vpc do not allow "cleanup + args: (record) => { + var fields = [] + if (record.vpcid == null) { + fields.push('cleanup') + } + fields.push('livepatch') + return fields + }, show: (record) => record.type !== 'L2', groupAction: true, popup: true, @@ -196,6 +203,7 @@ export default { if (!record.redundantvpcrouter) { fields.push('makeredundant') } + fields.push('livepatch') return fields }, groupAction: true, diff --git a/ui/src/core/lazy_lib/icons_use.js b/ui/src/core/lazy_lib/icons_use.js index 0945dbe1d32..252350bfa80 100644 --- a/ui/src/core/lazy_lib/icons_use.js +++ b/ui/src/core/lazy_lib/icons_use.js @@ -65,6 +65,7 @@ import { DoubleRightOutlined, DownOutlined, DownloadOutlined, + DiffOutlined, DragOutlined, EditOutlined, EnvironmentOutlined, @@ -207,6 +208,7 @@ export default { app.component('DoubleRightOutlined', DoubleRightOutlined) app.component('DownOutlined', DownOutlined) app.component('DownloadOutlined', DownloadOutlined) + app.component('DiffOutlined', DiffOutlined) app.component('DragOutlined', DragOutlined) app.component('EditOutlined', EditOutlined) app.component('EnvironmentOutlined', EnvironmentOutlined) diff --git a/ui/src/views/AutogenView.vue b/ui/src/views/AutogenView.vue index 84781a203cc..30ab08174c0 100644 --- a/ui/src/views/AutogenView.vue +++ b/ui/src/views/AutogenView.vue @@ -589,6 +589,8 @@ export default { var objIndex = 0 if (this.$route.path.includes('/template') || this.$route.path.includes('/iso')) { objIndex = selectedItems.findIndex(obj => (obj.zoneid === tempResource[r])) + } else if (this.$route.path.includes('/router')) { + objIndex = selectedItems.findIndex(obj => (obj.guestnetworkid === tempResource[r])) } else { objIndex = selectedItems.findIndex(obj => (obj.id === tempResource[r] || obj.username === tempResource[r] || obj.name === tempResource[r])) } diff --git a/utils/src/main/java/com/cloud/utils/FileUtil.java b/utils/src/main/java/com/cloud/utils/FileUtil.java index c55dd74e21b..3a44127fe14 100644 --- a/utils/src/main/java/com/cloud/utils/FileUtil.java +++ b/utils/src/main/java/com/cloud/utils/FileUtil.java @@ -21,12 +21,39 @@ package com.cloud.utils; import java.io.File; import java.io.IOException; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; +import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.utils.ssh.SshHelper; import org.apache.commons.io.FileUtils; +import org.apache.log4j.Logger; public class FileUtil { + private static final Logger s_logger = Logger.getLogger(FileUtil.class); public static void copyfile(File source, File destination) throws IOException { FileUtils.copyFile(source, destination); } + + public static void scpPatchFiles(String controlIp, String destPath, int sshPort, File pemFile, String[] files, String basePath) { + String errMsg = "Failed to scp files to system VM"; + List srcFiles = Arrays.asList(files); + srcFiles = srcFiles.stream() + .map(file -> basePath + file) // Using Lambda notation to update the entries + .collect(Collectors.toList()); + String[] newSrcFiles = srcFiles.toArray(new String[0]); + for (int retries = 3; retries > 0; retries--) { + try { + SshHelper.scpTo(controlIp, sshPort, "root", pemFile, null, + destPath, newSrcFiles, "0755"); + return; + } catch (Exception e) { + errMsg += ", retrying"; + s_logger.error(errMsg); + } + } + throw new CloudRuntimeException(errMsg); + } } diff --git a/utils/src/main/java/com/cloud/utils/script/Script.java b/utils/src/main/java/com/cloud/utils/script/Script.java index 50fcae0e820..fb57f256690 100644 --- a/utils/src/main/java/com/cloud/utils/script/Script.java +++ b/utils/src/main/java/com/cloud/utils/script/Script.java @@ -319,7 +319,7 @@ public class Script implements Callable { try { _logger.trace("Checking exit value of process"); _process.exitValue(); - _logger.trace("Script ran within the allotted time"); + _logger.trace("Script ran within the allocated time"); } catch (IllegalThreadStateException e) { _logger.warn("Interrupting script."); _isTimeOut = true; diff --git a/utils/src/main/java/com/cloud/utils/ssh/SshHelper.java b/utils/src/main/java/com/cloud/utils/ssh/SshHelper.java index bce61fd4629..7a73e8675a7 100644 --- a/utils/src/main/java/com/cloud/utils/ssh/SshHelper.java +++ b/utils/src/main/java/com/cloud/utils/ssh/SshHelper.java @@ -52,6 +52,12 @@ public class SshHelper { scpTo(host, port, user, pemKeyFile, password, remoteTargetDirectory, localFile, fileMode, DEFAULT_CONNECT_TIMEOUT, DEFAULT_KEX_TIMEOUT); } + public static void scpTo(String host, int port, String user, File pemKeyFile, String password, String remoteTargetDirectory, String[] localFiles, String fileMode) + throws Exception { + + scpTo(host, port, user, pemKeyFile, password, remoteTargetDirectory, localFiles, fileMode, DEFAULT_CONNECT_TIMEOUT, DEFAULT_KEX_TIMEOUT); + } + public static void scpTo(String host, int port, String user, File pemKeyFile, String password, String remoteTargetDirectory, byte[] data, String remoteFileName, String fileMode) throws Exception { @@ -118,6 +124,42 @@ public class SshHelper { } } + public static void scpTo(String host, int port, String user, File pemKeyFile, String password, String remoteTargetDirectory, String[] localFiles, String fileMode, + int connectTimeoutInMs, int kexTimeoutInMs) throws Exception { + + com.trilead.ssh2.Connection conn = null; + com.trilead.ssh2.SCPClient scpClient = null; + + try { + conn = new com.trilead.ssh2.Connection(host, port); + conn.connect(null, connectTimeoutInMs, kexTimeoutInMs); + + if (pemKeyFile == null) { + if (!conn.authenticateWithPassword(user, password)) { + String msg = "Failed to authentication SSH user " + user + " on host " + host; + s_logger.error(msg); + throw new Exception(msg); + } + } else { + if (!conn.authenticateWithPublicKey(user, pemKeyFile, password)) { + String msg = "Failed to authentication SSH user " + user + " on host " + host; + s_logger.error(msg); + throw new Exception(msg); + } + } + + scpClient = conn.createSCPClient(); + + if (fileMode != null) + scpClient.put(localFiles, remoteTargetDirectory, fileMode); + else + scpClient.put(localFiles, remoteTargetDirectory); + } finally { + if (conn != null) + conn.close(); + } + } + public static void scpTo(String host, int port, String user, File pemKeyFile, String password, String remoteTargetDirectory, byte[] data, String remoteFileName, String fileMode, int connectTimeoutInMs, int kexTimeoutInMs) throws Exception { diff --git a/utils/src/main/java/com/cloud/utils/validation/ChecksumUtil.java b/utils/src/main/java/com/cloud/utils/validation/ChecksumUtil.java new file mode 100644 index 00000000000..b1b675bca0a --- /dev/null +++ b/utils/src/main/java/com/cloud/utils/validation/ChecksumUtil.java @@ -0,0 +1,34 @@ +// 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.utils.validation; + +import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.utils.script.Script; +import org.apache.cloudstack.utils.security.DigestHelper; + +import java.io.File; + +public class ChecksumUtil { + public static String calculateCurrentChecksum(String name, String path) { + String cloudScriptsPath = Script.findScript("", path); + if (cloudScriptsPath == null) { + throw new CloudRuntimeException(String.format("Unable to find cloudScripts path, cannot update SystemVM %s", name)); + } + String md5sum = DigestHelper.calculateChecksum(new File(cloudScriptsPath)); + return md5sum; + } +} diff --git a/utils/src/main/java/org/apache/cloudstack/utils/security/DigestHelper.java b/utils/src/main/java/org/apache/cloudstack/utils/security/DigestHelper.java index 8bc942c36a0..eb92e68f3d0 100644 --- a/utils/src/main/java/org/apache/cloudstack/utils/security/DigestHelper.java +++ b/utils/src/main/java/org/apache/cloudstack/utils/security/DigestHelper.java @@ -17,6 +17,7 @@ package org.apache.cloudstack.utils.security; import com.cloud.utils.exception.CloudRuntimeException; +import org.apache.commons.codec.digest.DigestUtils; import org.apache.commons.lang3.StringUtils; import org.apache.log4j.Logger; @@ -32,7 +33,7 @@ import java.util.HashMap; import java.util.Map; public class DigestHelper { - public static final Logger s_logger = Logger.getLogger(DigestHelper.class.getName()); + public static final Logger LOGGER = Logger.getLogger(DigestHelper.class.getName()); public static ChecksumValue digest(String algorithm, InputStream is) throws NoSuchAlgorithmException, IOException { MessageDigest digest = MessageDigest.getInstance(algorithm); ChecksumValue checksum = null; @@ -139,10 +140,10 @@ public class DigestHelper { public static String calculateChecksum(File file) { try (InputStream is = Files.newInputStream(Paths.get(file.getPath()))) { - return org.apache.commons.codec.digest.DigestUtils.md5Hex(is); + return DigestUtils.md5Hex(is); } catch (IOException e) { String errMsg = "Failed to calculate template checksum"; - s_logger.error(errMsg, e); + LOGGER.error(errMsg); throw new CloudRuntimeException(errMsg, e); } } diff --git a/utils/src/main/java/org/apache/cloudstack/utils/security/KeyStoreUtils.java b/utils/src/main/java/org/apache/cloudstack/utils/security/KeyStoreUtils.java index c6f8d21918c..e78d14adbb2 100644 --- a/utils/src/main/java/org/apache/cloudstack/utils/security/KeyStoreUtils.java +++ b/utils/src/main/java/org/apache/cloudstack/utils/security/KeyStoreUtils.java @@ -26,6 +26,7 @@ import com.cloud.utils.PropertiesUtil; public class KeyStoreUtils { public static final String KS_SETUP_SCRIPT = "keystore-setup"; public static final String KS_IMPORT_SCRIPT = "keystore-cert-import"; + public static final String KS_SYSTEMVM_IMPORT_SCRIPT = "keystore-cert-import-sysvm"; public static final String AGENT_PROPSFILE = "agent.properties"; public static final String KS_PASSPHRASE_PROPERTY = "keystore.passphrase"; diff --git a/utils/src/test/java/com/cloud/utils/FileUtilTest.java b/utils/src/test/java/com/cloud/utils/FileUtilTest.java new file mode 100644 index 00000000000..70ad39cd585 --- /dev/null +++ b/utils/src/test/java/com/cloud/utils/FileUtilTest.java @@ -0,0 +1,69 @@ +// 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.utils; + +import com.cloud.utils.ssh.SshHelper; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mockito; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PowerMockIgnore; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + +import java.io.File; + +import static org.mockito.ArgumentMatchers.nullable; + +@PrepareForTest(value = {SshHelper.class}) +@RunWith(PowerMockRunner.class) +@PowerMockIgnore({"com.sun.org.apache.xerces.*", "javax.xml.parsers.*", "javax.xml.*", "org.w3c.dom.*", "org.xml.*"}) +public class FileUtilTest { + + @Test + public void successfulScpTest() throws Exception { + PowerMockito.mockStatic(SshHelper.class); + String basePath = "/tmp"; + String[] files = new String[] { "file1.txt" }; + int sshPort = 3922; + String controlIp = "10.0.0.10"; + String destPath = "/home/cloud/"; + File pemFile = new File("/root/.ssh/id_rsa"); + PowerMockito.doNothing().when(SshHelper.class, "scpTo", Mockito.anyString(), Mockito.anyInt(), Mockito.anyString(), Mockito.any(File.class), nullable(String.class), Mockito.anyString(), Mockito.any(String[].class), Mockito.anyString()); + FileUtil.scpPatchFiles(controlIp, destPath, sshPort, pemFile, files, basePath); + } + + @Test + public void FailingScpFilesTest() throws Exception { + PowerMockito.mockStatic(SshHelper.class); + String basePath = "/tmp"; + String[] files = new String[] { "file1.txt" }; + int sshPort = 3922; + String controlIp = "10.0.0.10"; + String destPath = "/home/cloud/"; + File pemFile = new File("/root/.ssh/id_rsa"); + PowerMockito.doThrow(new Exception()).when(SshHelper.class, "scpTo", Mockito.anyString(), Mockito.anyInt(), Mockito.anyString(), Mockito.any(File.class), Mockito.anyString(), Mockito.anyString(), Mockito.any(String[].class), Mockito.anyString()); + try { + FileUtil.scpPatchFiles(controlIp, destPath, sshPort, pemFile, files, basePath); + } catch (Exception e) { + Assert.assertEquals("Failed to scp files to system VM", e.getMessage()); + } + + } + +} diff --git a/utils/src/test/java/com/cloud/utils/validation/ChecksumUtilTest.java b/utils/src/test/java/com/cloud/utils/validation/ChecksumUtilTest.java new file mode 100644 index 00000000000..08cc389621e --- /dev/null +++ b/utils/src/test/java/com/cloud/utils/validation/ChecksumUtilTest.java @@ -0,0 +1,62 @@ +// 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.utils.validation; + +import com.cloud.utils.script.Script; +import org.apache.cloudstack.utils.security.DigestHelper; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mockito; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PowerMockIgnore; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + +import java.io.File; + +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +@PrepareForTest(value = {Script.class, DigestHelper.class}) +@RunWith(PowerMockRunner.class) +@PowerMockIgnore({"com.sun.org.apache.xerces.*", "javax.xml.parsers.*", "javax.xml.*", "org.w3c.dom.*", "org.xml.*"}) +public class ChecksumUtilTest { + + @Test + public void invalidFileForCheckSumValidationTest() { + PowerMockito.mockStatic(Script.class); + Mockito.when(Script.findScript(Mockito.anyString(), Mockito.anyString())).thenReturn(null); + try { + ChecksumUtil.calculateCurrentChecksum(Mockito.anyString(), Mockito.anyString()); + } catch (Exception e) { + assertTrue(e.getMessage().contains("Unable to find cloudScripts path, cannot update SystemVM")); + } + } + + @Test + public void generateChecksumTest() { + PowerMockito.mockStatic(Script.class); + PowerMockito.mockStatic(DigestHelper.class); + Mockito.when(Script.findScript(Mockito.anyString(), Mockito.anyString())).thenReturn("/dummyPath"); + Mockito.when(DigestHelper.calculateChecksum(Mockito.any(File.class))).thenReturn("dummy-checksum"); + try { + ChecksumUtil.calculateCurrentChecksum(Mockito.anyString(), Mockito.anyString()); + } catch (Exception e) { + fail("Failed to generate checksum"); + } + } +}