diff --git a/agent/src/main/java/com/cloud/agent/Agent.java b/agent/src/main/java/com/cloud/agent/Agent.java index b4474032b9ef..7fec460124ac 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 @@ private Answer setupAgentCertificate(final SetupCertificateCommand cmd) { 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 3e6bb1a579c9..6471742e8feb 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 4e4b0d4d9277..bfc4479d7f69 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 @@ IpAddress allocatePortableIP(Account ipOwner, int regionId, Long zoneId, Long ne 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 8bec51990470..c4ae4e964cf5 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 enum RedundantState { 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 4e993a71968e..088239708f19 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 Pair, Integer> listVpcs(Long id, String vpcName, Stri */ 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 b266269d0865..dc15521ac80e 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.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 @@ VirtualMachine upgradeSystemVM(ScaleSystemVMCmd cmd) throws ResourceUnavailableE 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 607851d36fe4..643060cee12d 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 000000000000..a0c066ce0e8f --- /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 59879562b02c..d4db584149a2 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 Boolean getMakeRedundant() { 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 aade0c2886a5..ac22e6691595 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 Boolean getMakeredundant() { 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 66f403074493..a5fa2bd08c2a 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 void setHealthChecksFailed(boolean healthChecksFailed) { 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 000000000000..125c8b566658 --- /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 aae0006174e0..052e06184f85 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 000000000000..1065768d6ef2 --- /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 000000000000..29c5baca7f03 --- /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 834a11c8d6eb..a76d555b002b 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 54b9d38a9625..8a265a78123b 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 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 @@ private Answer execute(final SetupKeyStoreCommand cmd) { } 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 @@ private Answer execute(AggregationControlCommand cmd) { } 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 16ac00ed176b..1602a78d9a47 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.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 9a9cf3bbc09f..08f56d4f117f 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 b590e5d40fdc..7537e07a6b6b 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 7611df820c80..f8032bf4b0e7 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.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 @@ static String getEncodedMsPublicKey(String pubKey) { } 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 9a08aebc8ab0..bab36f4703b9 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 @@ NicProfile createNicForVm(Network network, NicProfile requested, ReservationCont 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 a17e92a97c0a..64e4ad891544 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 void advanceExpunge(final String vmUuid) throws ResourceUnavailableExcept 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 @@ protected void advanceExpunge(VMInstanceVO vm) throws ResourceUnavailableExcepti 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 @@ private void addAllExpungeCommandsFromList(List cmdList, Commands cmds, 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 void orchestrateStart(final String vmUuid, 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 void orchestrateStart(final String vmUuid, 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 f422a0138944..aeaecb42c6d2 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 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 boolean canUpdateInSequence(Network network, boolean forced) { 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 boolean startNetwork(final long networkId, final DeployDestination dest, } @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 boolean restartNetwork(final Long networkId, final Account callerAccount, } 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 @@ private boolean rollingRestartRouters(final NetworkVO network, final NetworkOffe 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 @@ private boolean rollingRestartRouters(final NetworkVO network, final NetworkOffe 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 1eb3fdd20bbc..0ddaf00643fc 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 void registerTemplate(Pair hypervisorA /** * 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 8bd973af531c..1a619734ff39 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 void setUpdateState(UpdateState updateState) { 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 36ff74b11b37..dcf6505ce221 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 c4fd2f6c974d..8e965b210beb 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 85e751788691..63cdc042b26f 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 @@ protected void init() { 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 List listByNetworkAndPodAndRole(final long networkId, fin } @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 d597fed0e8c6..e6fd3708ca8d 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 4288463d2eb6..431dbee93024 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 099ad59b3d58..893b7b56cd89 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 bf6ef6671092..9f2dc3782197 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 3a0f3100f7d6..97be335b8455 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 ExecutionResult createFileInVR(final String routerIp, final String path, 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 boolean configure(final String name, final Map params) th _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 int compare(final DiskTO arg0, final DiskTO arg1) { } 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 000000000000..691d34fd7096 --- /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 f151255d5cdc..bdb86f061e96 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.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 Answer execute(final StartCommand command, final LibvirtComputingResource break; } } + + try { + File pemFile = new File(LibvirtComputingResource.SSHPRVKEYPATH); + FileUtil.scpPatchFiles(controlIp, "/tmp/", Integer.parseInt(LibvirtComputingResource.DEFAULTDOMRSSHPORT), pemFile, LibvirtComputingResource.systemVmPatchFiles, LibvirtComputingResource.BASEPATH); + if (!virtRouterResource.isSystemVMSetup(vmName, controlIp)) { + String errMsg = "Failed to patch systemVM"; + s_logger.error(errMsg); + return new StartAnswer(command, 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(command, String.format("%s due to: %s", errMsg, e.getMessage())); + } } } diff --git a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResourceTest.java b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResourceTest.java index 3606f22296b1..0da2373c1f55 100644 --- a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResourceTest.java +++ b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResourceTest.java @@ -54,6 +54,7 @@ import javax.xml.xpath.XPathExpressionException; import javax.xml.xpath.XPathFactory; +import com.cloud.utils.ssh.SshHelper; import org.apache.cloudstack.storage.command.AttachAnswer; import org.apache.cloudstack.storage.command.AttachCommand; import org.apache.cloudstack.utils.linux.CPUStat; @@ -212,7 +213,7 @@ import org.libvirt.VcpuInfo; @RunWith(PowerMockRunner.class) -@PrepareForTest(value = {MemStat.class}) +@PrepareForTest(value = {MemStat.class, SshHelper.class}) @PowerMockIgnore({"javax.xml.*", "org.w3c.dom.*", "org.apache.xerces.*"}) public class LibvirtComputingResourceTest { @@ -5280,7 +5281,9 @@ public void testStartCommandUriException() { } @Test - public void testStartCommand() { + public void testStartCommand() throws Exception { + PowerMockito.mockStatic(SshHelper.class); + 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()); final VirtualMachineTO vmSpec = Mockito.mock(VirtualMachineTO.class); final com.cloud.host.Host host = Mockito.mock(com.cloud.host.Host.class); final boolean executeInSequence = false; @@ -5332,6 +5335,7 @@ public void testStartCommand() { when(nic.getType()).thenReturn(TrafficType.Control); when(libvirtComputingResource.getVirtRouterResource()).thenReturn(virtRouterResource); when(virtRouterResource.connect(controlIp, 1, 5000)).thenReturn(true); + when(virtRouterResource.isSystemVMSetup(vmName, controlIp)).thenReturn(true); } catch (final InternalErrorException e) { fail(e.getMessage()); } catch (final LibvirtException e) { @@ -5354,7 +5358,9 @@ public void testStartCommand() { } @Test - public void testStartCommandIsolationEc2() { + public void testStartCommandIsolationEc2() throws Exception { + PowerMockito.mockStatic(SshHelper.class); + 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()); final VirtualMachineTO vmSpec = Mockito.mock(VirtualMachineTO.class); final com.cloud.host.Host host = Mockito.mock(com.cloud.host.Host.class); final boolean executeInSequence = false; @@ -5410,6 +5416,7 @@ public void testStartCommandIsolationEc2() { when(nic.getType()).thenReturn(TrafficType.Control); when(libvirtComputingResource.getVirtRouterResource()).thenReturn(virtRouterResource); when(virtRouterResource.connect(controlIp, 1, 5000)).thenReturn(true); + when(virtRouterResource.isSystemVMSetup(vmName, controlIp)).thenReturn(true); } catch (final InternalErrorException e) { fail(e.getMessage()); } catch (final LibvirtException e) { diff --git a/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/vmware/resource/VmwareResource.java b/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/vmware/resource/VmwareResource.java index 7cca51a6d004..2180152ab067 100644 --- a/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/vmware/resource/VmwareResource.java +++ b/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/vmware/resource/VmwareResource.java @@ -47,6 +47,11 @@ import javax.naming.ConfigurationException; import javax.xml.datatype.XMLGregorianCalendar; +import com.cloud.agent.api.PatchSystemVmAnswer; +import com.cloud.agent.api.PatchSystemVmCommand; +import com.cloud.resource.ServerResourceBase; +import com.cloud.utils.FileUtil; +import com.cloud.utils.validation.ChecksumUtil; import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.storage.command.CopyCommand; import org.apache.cloudstack.storage.command.StorageSubSystemCommand; @@ -361,10 +366,11 @@ import com.vmware.vim25.VmwareDistributedVirtualSwitchPvlanSpec; import com.vmware.vim25.VmwareDistributedVirtualSwitchVlanIdSpec; -public class VmwareResource implements StoragePoolResource, ServerResource, VmwareHostService, VirtualRouterDeployer { +public class VmwareResource extends ServerResourceBase implements StoragePoolResource, ServerResource, VmwareHostService, VirtualRouterDeployer { private static final Logger s_logger = Logger.getLogger(VmwareResource.class); public static final String VMDK_EXTENSION = ".vmdk"; private static final String EXECUTING_RESOURCE_COMMAND = "Executing resource command %s: [%s]."; + public static final String BASEPATH = "/usr/share/cloudstack-common/vms/"; private static final Random RANDOM = new Random(System.nanoTime()); @@ -466,7 +472,9 @@ public Answer executeRequest(Command cmd) { mbean.addProp("Name", cmd.getClass().getSimpleName()); Class 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 Answer executeRequest(Command cmd) { 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 @@ protected StartAnswer execute(StartCommand cmd) { 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 @@ protected StartAnswer execute(StartCommand cmd) { 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 @@ protected StartAnswer execute(StartCommand cmd) { 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 @@ private String getNetworkNamePrefix(NicTO nicTo) throws Exception { } } + 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 @@ protected Answer execute(PrepareForMigrationCommand cmd) { 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 String getName() { return _name; } + @Override + protected String getDefaultScriptsDir() { + return null; + } + @Override public boolean start() { return true; @@ -7355,7 +7471,6 @@ private List relocateVirtualMachine(final VmwareHypervisorHost h 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 a95c07bdd196..6bed711b5f07 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.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 @@ * 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 String toString() { 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 String toString() { 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 @@ protected StorageSubsystemCommandHandler buildStorageHandler() { 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 synchronized Network configureTunnelNetwork(final Connection conn, final } } - 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 String connect(final Connection conn, final String vmName, final String i return null; } try { - Thread.sleep(_sleep); + Thread.sleep(sleep); } catch (final InterruptedException e) { } } @@ -974,8 +987,25 @@ public ExecutionResult createFileInVR(final String routerIp, final String path, 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 VBD createPatchVbd(final Connection conn, final String vmName, final VM v _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 VBD createPatchVbd(final Connection conn, final String vmName, final VM v 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 VM createVmFromTemplate(final Connection conn, final VirtualMachineTO vmS 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 boolean setupServer(final Connection conn, final Host host) { 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 cf34a8fb30cd..ec7d844dfbc7 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 Answer execute(final CheckSshCommand command, final CitrixResourceBase ci } try { - final String result = citrixResourceBase.connect(conn, command.getName(), privateIp, cmdPort); + final String result = citrixResourceBase.connect(conn, command.getName(), privateIp, cmdPort, 0); if (result != null) { return new CheckSshAnswer(command, "Can not ping System vm " + vmName + "due to:" + result); } diff --git a/plugins/hypervisors/xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixNetworkElementCommandWrapper.java b/plugins/hypervisors/xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixNetworkElementCommandWrapper.java index da2bf1e126e2..3e763155fbdc 100644 --- a/plugins/hypervisors/xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixNetworkElementCommandWrapper.java +++ b/plugins/hypervisors/xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixNetworkElementCommandWrapper.java @@ -25,10 +25,11 @@ import com.cloud.hypervisor.xenserver.resource.CitrixResourceBase; import com.cloud.resource.CommandWrapper; import com.cloud.resource.ResourceWrapper; +import org.apache.log4j.Logger; @ResourceWrapper(handles = NetworkElementCommand.class) public final class CitrixNetworkElementCommandWrapper 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 000000000000..718daec72926 --- /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 236d8db3858a..a87458326e5b 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 Answer execute(final RebootRouterCommand command, final CitrixResourceBas final Answer answer = wrapper.execute(rebootCommand, citrixResourceBase); if (answer.getResult()) { - final String cnct = citrixResourceBase.connect(conn, command.getVmName(), command.getPrivateIpAddress()); + final String cnct = citrixResourceBase.connect(conn, command.getVmName(), command.getPrivateIpAddress(), 0); citrixResourceBase.networkUsage(conn, command.getPrivateIpAddress(), "create", null); if (cnct == null) { diff --git a/plugins/hypervisors/xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixStartCommandWrapper.java b/plugins/hypervisors/xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixStartCommandWrapper.java index ecd1b0d718c9..d1b5224f28f2 100644 --- a/plugins/hypervisors/xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixStartCommandWrapper.java +++ b/plugins/hypervisors/xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/wrapper/xenbase/CitrixStartCommandWrapper.java @@ -25,6 +25,8 @@ import java.util.Map; import java.util.Set; +import com.cloud.agent.resource.virtualnetwork.VirtualRoutingResource; +import org.apache.commons.lang3.StringUtils; import org.apache.log4j.Logger; import com.cloud.agent.api.Answer; @@ -180,6 +182,35 @@ public Answer execute(final StartCommand command, final CitrixResourceBase citri state = VmPowerState.RUNNING; + if (vmSpec.getType() != VirtualMachine.Type.User) { + String controlIp = null; + for (final NicTO nic : vmSpec.getNics()) { + if (nic.getType() == Networks.TrafficType.Control) { + controlIp = nic.getIp(); + break; + } + } + + String result2 = citrixResourceBase.connect(conn, vmName, controlIp, 1000); + if (StringUtils.isEmpty(result2)) { + s_logger.info(String.format("Connected to SystemVM: %s", vmName)); + } + + try { + citrixResourceBase.copyPatchFilesToVR(controlIp, "/tmp/"); + VirtualRoutingResource vrResource = citrixResourceBase.getVirtualRoutingResource(); + if (!vrResource.isSystemVMSetup(vmName, controlIp)) { + String errMsg = "Failed to patch systemVM"; + s_logger.error(errMsg); + return new StartAnswer(command, 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(command, String.format("%s due to: %s", errMsg, e.getMessage())); + } + } + final StartAnswer startAnswer = new StartAnswer(command); startAnswer.setIqnToData(iqnToData); diff --git a/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/KubernetesClusterManagerImpl.java b/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/KubernetesClusterManagerImpl.java index ce1e5113a90c..535375502934 100644 --- a/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/KubernetesClusterManagerImpl.java +++ b/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/KubernetesClusterManagerImpl.java @@ -444,7 +444,8 @@ private void validateDockerRegistryParams(final String dockerRegistryUserName, if (!((dockerRegistryUserName != null && !dockerRegistryUserName.isEmpty()) && (dockerRegistryPassword != null && !dockerRegistryPassword.isEmpty()) && (dockerRegistryUrl != null && !dockerRegistryUrl.isEmpty()))) { - throw new InvalidParameterValueException("All the docker private registry parameters (username, password, url, email) required are specified"); + + throw new InvalidParameterValueException("All the docker private registry parameters (username, password, url) required are specified"); } try { @@ -787,7 +788,7 @@ public void doInTransactionWithoutResult(TransactionStatus status) { addKubernetesClusterDetailIfIsNotEmpty(details, kubernetesClusterId, ApiConstants.DOCKER_REGISTRY_PASSWORD, dockerRegistryPassword, false); addKubernetesClusterDetailIfIsNotEmpty(details, kubernetesClusterId, ApiConstants.DOCKER_REGISTRY_URL, dockerRegistryUrl, true); - details.add(new KubernetesClusterDetailsVO(kubernetesClusterId, ApiConstants.USERNAME, "admin", true)); + details.add(new KubernetesClusterDetailsVO(kubernetesCluster.getId(), ApiConstants.USERNAME, "admin", true)); SecureRandom random = new SecureRandom(); String randomPassword = new BigInteger(130, random).toString(32); details.add(new KubernetesClusterDetailsVO(kubernetesClusterId, ApiConstants.PASSWORD, randomPassword, false)); diff --git a/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/actionworkers/KubernetesClusterActionWorker.java b/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/actionworkers/KubernetesClusterActionWorker.java index 048eb0b66695..ba0615261ba4 100644 --- a/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/actionworkers/KubernetesClusterActionWorker.java +++ b/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/actionworkers/KubernetesClusterActionWorker.java @@ -87,6 +87,7 @@ import java.util.Set; import java.util.stream.Collectors; + public class KubernetesClusterActionWorker { public static final String CLUSTER_NODE_VM_USER = "cloud"; diff --git a/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/actionworkers/KubernetesClusterResourceModifierActionWorker.java b/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/actionworkers/KubernetesClusterResourceModifierActionWorker.java index c76c6079faf4..81db87a7c7de 100644 --- a/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/actionworkers/KubernetesClusterResourceModifierActionWorker.java +++ b/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/actionworkers/KubernetesClusterResourceModifierActionWorker.java @@ -76,6 +76,7 @@ import com.cloud.vm.VirtualMachine; import com.cloud.vm.VmDetailConstants; import com.cloud.vm.dao.VMInstanceDao; + import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.BaseCmd; import org.apache.cloudstack.api.command.user.firewall.CreateFirewallRuleCmd; @@ -187,6 +188,7 @@ protected String updateKubeConfigWithRegistryDetails(String k8sConfig) { registryUrl = detail.getValue(); } } + if (StringUtils.isNoneEmpty(registryUsername, registryPassword, registryUrl)) { // Update runcmd in the cloud-init configuration to run a script that updates the containerd config with provided registry details String runCmd = "- bash -x /opt/bin/setup-containerd"; diff --git a/plugins/integrations/kubernetes-service/src/main/resources/conf/k8s-control-node-add.yml b/plugins/integrations/kubernetes-service/src/main/resources/conf/k8s-control-node-add.yml index 89138a58d08b..335ae9194e6d 100644 --- a/plugins/integrations/kubernetes-service/src/main/resources/conf/k8s-control-node-add.yml +++ b/plugins/integrations/kubernetes-service/src/main/resources/conf/k8s-control-node-add.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/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 d7a63048a65b..76adc5a7d296 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 d253f3e27803..86966245c83e 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 49ff10d6bc68..c315570fec7d 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 000000000000..9371f1c74128 --- /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 a2b57bff07e2..9e4e7f246b31 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 dd03ded95927..de5feb06d21d 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 d3c5db052458..1edd35ad628a 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 862aa2e9c110..8bb1eadd7034 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 b6f7cdbb5c1b..16b1ce71b922 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 4546796f9b36..bb09d255601e 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 bea0cf9bfd17..2652c30cd05e 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 db137c9826f3..f18a325f05bf 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 db137c9826f3..f18a325f05bf 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 c05d232c0e7c..747fb07c5840 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 44baedc933b1..f6f777efe3f9 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 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 JsonElement serialize(ResponseObject responseObj, Type typeOfResponseObj, 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 0ed234c51e10..70a20c647419 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 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 DomainRouterResponse newDomainRouterResponse(DomainRouterJoinVO router, A routerResponse.setState(router.getState()); routerResponse.setIsRedundantRouter(router.isRedundantRouter()); routerResponse.setScriptsVersion(router.getScriptsVersion()); + routerResponse.setSoftwareVersion(router.getSoftwareVersion()); if (router.getRedundantState() != null) { routerResponse.setRedundantState(router.getRedundantState().toString()); } if (router.getTemplateVersion() != null) { String routerVersion = CloudStackVersion.trimRouterVersion(router.getTemplateVersion()); routerResponse.setVersion(routerVersion); - routerResponse.setRequiresUpgrade((CloudStackVersion.compare(routerVersion, NetworkOrchestrationService.MinVRVersion.valueIn(router.getDataCenterId())) < 0)); + boolean isTempVersionLower = (CloudStackVersion.compare(routerVersion, NetworkOrchestrationService.MinVRVersion.valueIn(router.getDataCenterId())) < 0); + if (!isTempVersionLower) { + routerResponse.setRequiresUpgrade(false); + } else { + boolean requiresUpgrade = true; + String currentCodeVersion = this.getClass().getPackage().getImplementationVersion(); + if (StringUtils.isNotEmpty(currentCodeVersion)) { + currentCodeVersion = CloudStackVersion.parse(currentCodeVersion).toString(); + String routerSoftwareVersion = router.getSoftwareVersion(); + if (StringUtils.isNotEmpty(routerSoftwareVersion)) { + requiresUpgrade = !(currentCodeVersion.equals(routerSoftwareVersion)); + } + } + routerResponse.setRequiresUpgrade(requiresUpgrade); + } } else { routerResponse.setVersion("UNKNOWN"); routerResponse.setRequiresUpgrade(true); diff --git a/server/src/main/java/com/cloud/api/query/vo/DomainRouterJoinVO.java b/server/src/main/java/com/cloud/api/query/vo/DomainRouterJoinVO.java index c29f9b5e2cb8..51171ff6913a 100644 --- a/server/src/main/java/com/cloud/api/query/vo/DomainRouterJoinVO.java +++ b/server/src/main/java/com/cloud/api/query/vo/DomainRouterJoinVO.java @@ -251,6 +251,9 @@ public class DomainRouterJoinVO extends BaseViewVO implements ControlledViewEnti @Enumerated(value = EnumType.STRING) private VirtualRouter.Role role; + @Column(name = "software_version") + private String softwareVersion; + public DomainRouterJoinVO() { } @@ -534,4 +537,8 @@ public VirtualRouter.Role getRole() { public Class 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 ff384bc845c9..4b02e7f42261 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 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 @@ protected ConsoleProxyManagerImpl() { @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 boolean finalizeVirtualMachineProfile(VirtualMachineProfile profile, Depl 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 50356d05c61d..4f1fcb26a7c5 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.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 @@ private void setupAgentSecurity(final Connection sshConnection, final String age 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 @@ private void setupAgentSecurity(final Connection sshConnection, final String age "/etc/cloudstack/agent/%s", KeyStoreUtils.KS_SETUP_SCRIPT, KeyStoreUtils.KS_FILENAME, - PasswordGenerator.generateRandomPassword(16), + keystorePassword, validityPeriod, KeyStoreUtils.CSR_FILENAME)); @@ -186,21 +185,22 @@ private void setupAgentSecurity(final Connection sshConnection, final String age 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 HostVO createHostVOForConnectedAgent(HostVO host, StartupCommand[] cmd) { 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 9b7476509085..3a87cee0b116 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 boolean deleteNetwork(long networkId, boolean forced) { @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 @@ private NetworkVO getNetworkVO(Long networkId, String errMsgFormat) { } @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 boolean restartNetwork(NetworkVO network, boolean cleanup, boolean makeRe } 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 boolean restartNetwork(RestartNetworkCmd cmd) throws ConcurrentOperationE 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 9e4a5571b44e..ea008e4c4ca0 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 abstract VirtualRouter destroyRouter(long routerId, Account caller, * @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 dfe331008451..d9e0833b8968 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 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 VirtualRouter destroyRouter(final long routerId, final Account caller, fi @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 e561acca3936..cd676aa62925 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.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.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 @@ public class VirtualNetworkApplianceManagerImpl extends ManagerBase implements V @Inject protected CommandSetupHelper _commandSetupHelper; @Inject protected RouterDeploymentDefinitionBuilder _routerDeploymentManagerBuilder; + @Inject private ManagementServer mgr; private int _routerRamSize; private int _routerCpuMHz; @@ -1269,7 +1272,7 @@ private boolean restartVpcInDomainRouter(DomainRouterJoinVO router, User user) { ActionEventUtils.onActionEvent(User.UID_SYSTEM, Account.ACCOUNT_ID_SYSTEM, Domain.ROOT_DOMAIN, EventTypes.EVENT_ROUTER_HEALTH_CHECKS, "Recreating router " + router.getUuid() + " by restarting VPC " + router.getVpcUuid()); - return vpcService.restartVpc(router.getVpcId(), true, false, user); + return vpcService.restartVpc(router.getVpcId(), true, false, false, user); } catch (Exception e) { s_logger.error("Failed to restart VPC for router recreation " + router.getVpcName() + " ,router " + router.getUuid(), e); @@ -1293,7 +1296,7 @@ private boolean restartGuestNetworkInDomainRouter(DomainRouterJoinVO router, Use ActionEventUtils.onActionEvent(User.UID_SYSTEM, Account.ACCOUNT_ID_SYSTEM, Domain.ROOT_DOMAIN, EventTypes.EVENT_ROUTER_HEALTH_CHECKS, "Recreating router " + router.getUuid() + " by restarting network " + router.getNetworkUuid()); - return networkService.restartNetwork(router.getNetworkId(), true, false, user); + return networkService.restartNetwork(router.getNetworkId(), true, false, false, user); } catch (Exception e) { s_logger.error("Failed to restart network " + router.getNetworkName() + " for router recreation " + router.getNetworkName(), e); @@ -2679,6 +2682,11 @@ public boolean finalizeStart(final VirtualMachineProfile profile, final long hos final GetDomRVersionAnswer versionAnswer = (GetDomRVersionAnswer) cmds.getAnswer("getDomRVersion"); router.setTemplateVersion(versionAnswer.getTemplateVersion()); router.setScriptsVersion(versionAnswer.getScriptsVersion()); + String codeVersion = mgr.getVersion(); + if (StringUtils.isNotEmpty(codeVersion)) { + codeVersion = CloudStackVersion.parse(codeVersion).toString(); + } + router.setSoftwareVersion(codeVersion); _routerDao.persist(router, guestNetworks); final List routerNics = _nicDao.listByVmId(profile.getId()); @@ -3209,7 +3217,7 @@ public List upgradeRouterTemplate(final UpgradeRouterTemplateCmd cmd) { private List 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 7c86a8874819..6120b4680237 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 boolean restartVpc(final RestartVPCCmd cmd) throws ConcurrentOperationExc 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 boolean restartVpc(Long vpcId, boolean cleanUp, boolean makeRedundant, Us 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 boolean restartVpc(Long vpcId, boolean cleanUp, boolean makeRedundant, Us } } - 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 cbf20d405daa..4bd314578617 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 void doInTransactionWithoutResult(TransactionStatus status) { 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 @@ protected void copyPrivateKeyToHosts(String publicKeyPath, String privKeyPath) { 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 4e58a4f55765..7ecb66503732 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.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 26d11eafb9bd..4daca16be5b9 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.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.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.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.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.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 @@ 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 void setAffinityGroupProcessors(final List affini public ManagementServerImpl() { setRunLevel(ComponentLifecycle.RUN_LEVEL_APPLICATION_MAINLOOP); + setStateMachine(); } public List getUserAuthenticators() { @@ -1010,6 +1036,10 @@ public boolean configure(final String name, final Map params) th 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 Pair, Integer> searchForIPAddresses(final ListP 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 Pair, Integer> searchForIPAddresses(final ListP } 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 Pair, Integer> searchForIPAddresses(final ListP 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 List> getCommands() { 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 Map listCapabilities(final ListCapabilitiesCmd cmd) { 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 void cleanupVMReservations() { _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 8c006a2cf0f9..549f16429780 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 boolean finalizeCommandsOnStart(Commands cmds, VirtualMachineProfile prof } @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 1e5d8127053b..2e7e756c49a8 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 boolean provisionCertificate(final Host host, final Boolean reconnect, fi } 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 String generateKeyStoreAndCsr(final Host host, final Map 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 4941561a9296..0ba9dfa3ab9a 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 boolean deleteNetwork(long networkId, boolean forced) { } @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 NetworkProfile convertNetworkToNetworkProfile(long networkId) { * @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 22dc29be60dd..908819abb8b4 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.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 @@ private static void configProxy(Properties conf) { 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 static void startWithContext(Properties conf, Object context, byte[] ksBi 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 fa42d000f337..10229ae17db2 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 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 boolean finalizeVirtualMachineProfile(VirtualMachineProfile profile, Depl 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 boolean finalizeVirtualMachineProfile(VirtualMachineProfile profile, Depl } 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 2af52767cc39..cfaf5e7cd44a 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 cb20aaf7c060..31a1f3b8f597 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 000000000000..373cd9ed66fa --- /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 000000000000..bfe0f64495aa --- /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 2335d649a950..4720237543fb 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 ac672a265399..0b5df04ded2b 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 d0ebd0b68146..d76079b69e49 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 + 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 - if [ -f $privkey ]; then - cp -f $privkey /root/.ssh/ - chmod go-rwx /root/.ssh/authorized_keys + 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 +} - return 0 +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 c7b01c25bd4a..cad8af8c0156 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 3f00f3da43a1..8006f6bb2445 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 9161aeb37ee4..27dbf3e8f628 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 ae16b4bcb7c4..52132ccea96f 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 ac801b200e6a..a130674d1e8a 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 000000000000..a1ac48e46b56 --- /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 04929302f513..ba5c394d9f24 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 e8f6edf0ceb5..d7113c493028 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 13ed5c5d0ae0..3b21ed5cae17 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 f97fb161f47f..bfb062188254 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 000000000000..62e0dd6f1a87 --- /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 7fe4ae8a5e4e..69e971b74715 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 67bb03491e49..115d340fe846 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 eb8cca525d14..2db966de86bd 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", @@ -2221,6 +2226,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`", @@ -2580,6 +2586,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 7899d88fb2ae..31456bf30e78 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 b09d3092ce22..1c27a960af65 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 77c9d3d851fa..cf5145637ed5 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 7d723267d727..9808f72fa51d 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 e0d631f06198..b8aa99d2937d 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 0945dbe1d322..252350bfa807 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 84781a203ccd..30ab08174c0e 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 c55dd74e21bf..3a44127fe140 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 @@ 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 50fcae0e8205..fb57f256690e 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 String call() { 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 bce61fd4629c..7a73e8675a70 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 static void scpTo(String host, int port, String user, File pemKeyFile, St 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 static void scpTo(String host, int port, String user, File pemKeyFile, St } } + 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 000000000000..b1b675bca0a0 --- /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 8bc942c36a0a..eb92e68f3d0e 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.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 static String getHashValueFromChecksumValue(String checksum) { 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 c6f8d21918c4..e78d14adbb2d 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 @@ 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 000000000000..70ad39cd5851 --- /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 000000000000..08cc389621ee --- /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"); + } + } +}