diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtGetVmIpAddressCommandWrapper.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtGetVmIpAddressCommandWrapper.java index 0f00a6e29bd..e050cb4e85d 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtGetVmIpAddressCommandWrapper.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtGetVmIpAddressCommandWrapper.java @@ -27,6 +27,7 @@ import com.cloud.agent.api.GetVmIpAddressCommand; import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource; import com.cloud.resource.CommandWrapper; import com.cloud.resource.ResourceWrapper; +import com.cloud.utils.Pair; import com.cloud.utils.net.NetUtils; import com.cloud.utils.script.Script; @@ -34,6 +35,26 @@ import com.cloud.utils.script.Script; public final class LibvirtGetVmIpAddressCommandWrapper extends CommandWrapper { + static String virsh_path = null; + static String virt_win_reg_path = null; + static String grep_path = null; + static String awk_path = null; + static String sed_path = null; + static String virt_ls_path = null; + static String virt_cat_path = null; + static String tail_path = null; + + static void init() { + virt_ls_path = Script.getExecutableAbsolutePath("virt-ls"); + virt_cat_path = Script.getExecutableAbsolutePath("virt-cat"); + virt_win_reg_path = Script.getExecutableAbsolutePath("virt-win-reg"); + tail_path = Script.getExecutableAbsolutePath("tail"); + grep_path = Script.getExecutableAbsolutePath("grep"); + awk_path = Script.getExecutableAbsolutePath("awk"); + sed_path = Script.getExecutableAbsolutePath("sed"); + virsh_path = Script.getExecutableAbsolutePath("virsh"); + } + @Override public Answer execute(final GetVmIpAddressCommand command, final LibvirtComputingResource libvirtComputingResource) { String ip = null; @@ -42,65 +63,113 @@ public final class LibvirtGetVmIpAddressCommandWrapper extends CommandWrapper commands = new ArrayList<>(); - final String virt_ls_path = Script.getExecutableAbsolutePath("virt-ls"); - final String virt_cat_path = Script.getExecutableAbsolutePath("virt-cat"); - final String virt_win_reg_path = Script.getExecutableAbsolutePath("virt-win-reg"); - final String tail_path = Script.getExecutableAbsolutePath("tail"); - final String grep_path = Script.getExecutableAbsolutePath("grep"); - final String awk_path = Script.getExecutableAbsolutePath("awk"); - final String sed_path = Script.getExecutableAbsolutePath("sed"); - if(!command.isWindows()) { - //List all dhcp lease files inside guestVm - commands.add(new String[]{virt_ls_path, sanitizedVmName, "/var/lib/dhclient/"}); - commands.add(new String[]{grep_path, ".*\\*.leases"}); - String leasesList = Script.executePipedCommands(commands, 0).second(); - if(leasesList != null) { - String[] leasesFiles = leasesList.split("\n"); - for(String leaseFile : leasesFiles){ - //Read from each dhclient lease file inside guest Vm using virt-cat libguestfs utility - commands = new ArrayList<>(); - commands.add(new String[]{virt_cat_path, sanitizedVmName, "/var/lib/dhclient/" + leaseFile}); - commands.add(new String[]{tail_path, "-16"}); - commands.add(new String[]{grep_path, "fixed-address"}); - commands.add(new String[]{awk_path, "{print $2}"}); - commands.add(new String[]{sed_path, "-e", "s/;//"}); - String ipAddr = Script.executePipedCommands(commands, 0).second(); - // Check if the IP belongs to the network - if((ipAddr != null) && NetUtils.isIpWithInCidrRange(ipAddr, networkCidr)) { - ip = ipAddr; - break; - } - logger.debug("GetVmIp: "+ vmName + " Ip: "+ipAddr+" does not belong to network "+networkCidr); - } - } - } else { - // For windows, read from guest Vm registry using virt-win-reg libguestfs ulitiy. Registry Path: HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Services\Tcpip\Parameters\Interfaces\\DhcpIPAddress - commands = new ArrayList<>(); - commands.add(new String[]{virt_win_reg_path, "--unsafe-printable-strings", sanitizedVmName, "HKEY_LOCAL_MACHINE\\SYSTEM\\ControlSet001\\Services\\Tcpip\\Parameters\\Interfaces"}); - commands.add(new String[]{grep_path, "DhcpIPAddress"}); - commands.add(new String[]{awk_path, "-F", ":", "{print $2}"}); - commands.add(new String[]{sed_path, "-e", "s/^\"//", "-e", "s/\"$//"}); - String ipList = Script.executePipedCommands(commands, 0).second(); - if(ipList != null) { - logger.debug("GetVmIp: "+ vmName + "Ips: "+ipList); - String[] ips = ipList.split("\n"); - for (String ipAddr : ips){ - // Check if the IP belongs to the network - if((ipAddr != null) && NetUtils.isIpWithInCidrRange(ipAddr, networkCidr)){ - ip = ipAddr; - break; - } - logger.debug("GetVmIp: "+ vmName + " Ip: "+ipAddr+" does not belong to network "+networkCidr); - } + + ip = ipFromDomIf(sanitizedVmName, networkCidr); + + if (ip == null) { + if(!command.isWindows()) { + ip = ipFromDhcpLeaseFile(sanitizedVmName, networkCidr); + } else { + ip = ipFromWindowsRegistry(sanitizedVmName, networkCidr); } } + if(ip != null){ result = true; logger.debug("GetVmIp: "+ vmName + " Found Ip: "+ip); + } else { + logger.warn("GetVmIp: "+ vmName + " IP not found."); } + return new Answer(command, result, ip); } + + private String ipFromDomIf(String sanitizedVmName, String networkCidr) { + String ip = null; + List commands = new ArrayList<>(); + commands.add(new String[]{virsh_path, "domifaddr", sanitizedVmName, "--source", "agent"}); + Pair response = executePipedCommands(commands, 0); + if (response != null) { + String output = response.second(); + String[] lines = output.split("\n"); + for (String line : lines) { + if (line.contains("ipv4")) { + String[] parts = line.split(" "); + String[] ipParts = parts[parts.length-1].split("/"); + if (ipParts.length > 1) { + if (NetUtils.isIpWithInCidrRange(ipParts[0], networkCidr)) { + ip = ipParts[0]; + break; + } + } + } + } + } else { + logger.error("ipFromDomIf: Command execution failed for VM: " + sanitizedVmName); + } + return ip; + } + + private String ipFromDhcpLeaseFile(String sanitizedVmName, String networkCidr) { + String ip = null; + List commands = new ArrayList<>(); + commands.add(new String[]{virt_ls_path, sanitizedVmName, "/var/lib/dhclient/"}); + commands.add(new String[]{grep_path, ".*\\*.leases"}); + Pair response = executePipedCommands(commands, 0); + + if(response != null && response.second() != null) { + String leasesList = response.second(); + String[] leasesFiles = leasesList.split("\n"); + for(String leaseFile : leasesFiles){ + commands = new ArrayList<>(); + commands.add(new String[]{virt_cat_path, sanitizedVmName, "/var/lib/dhclient/" + leaseFile}); + commands.add(new String[]{tail_path, "-16"}); + commands.add(new String[]{grep_path, "fixed-address"}); + commands.add(new String[]{awk_path, "{print $2}"}); + commands.add(new String[]{sed_path, "-e", "s/;//"}); + String ipAddr = executePipedCommands(commands, 0).second(); + if((ipAddr != null) && NetUtils.isIpWithInCidrRange(ipAddr, networkCidr)) { + ip = ipAddr; + break; + } + logger.debug("GetVmIp: "+ sanitizedVmName + " Ip: "+ipAddr+" does not belong to network "+networkCidr); + } + } else { + logger.error("ipFromDhcpLeaseFile: Command execution failed for VM: " + sanitizedVmName); + } + return ip; + } + + private String ipFromWindowsRegistry(String sanitizedVmName, String networkCidr) { + String ip = null; + List commands = new ArrayList<>(); + commands.add(new String[]{virt_win_reg_path, "--unsafe-printable-strings", sanitizedVmName, "HKEY_LOCAL_MACHINE\\SYSTEM\\ControlSet001\\Services\\Tcpip\\Parameters\\Interfaces"}); + commands.add(new String[]{grep_path, "DhcpIPAddress"}); + commands.add(new String[]{awk_path, "-F", ":", "{print $2}"}); + commands.add(new String[]{sed_path, "-e", "s/^\"//", "-e", "s/\"$//"}); + Pair pair = executePipedCommands(commands, 0); + if(pair != null && pair.second() != null) { + String ipList = pair.second(); + ipList = ipList.replaceAll("\"", ""); + logger.debug("GetVmIp: "+ sanitizedVmName + "Ips: "+ipList); + String[] ips = ipList.split("\n"); + for (String ipAddr : ips){ + if((ipAddr != null) && NetUtils.isIpWithInCidrRange(ipAddr, networkCidr)){ + ip = ipAddr; + break; + } + logger.debug("GetVmIp: "+ sanitizedVmName + " Ip: "+ipAddr+" does not belong to network "+networkCidr); + } + } else { + logger.error("ipFromWindowsRegistry: Command execution failed for VM: " + sanitizedVmName); + } + return ip; + } + + static Pair executePipedCommands(List commands, long timeout) { + return Script.executePipedCommands(commands, timeout); + } } diff --git a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtGetVmIpAddressCommandWrapperTest.java b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtGetVmIpAddressCommandWrapperTest.java new file mode 100644 index 00000000000..bd09fe03d49 --- /dev/null +++ b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtGetVmIpAddressCommandWrapperTest.java @@ -0,0 +1,320 @@ +// +// 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 static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.anyList; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.MockedStatic; +import org.mockito.MockitoAnnotations; + +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.GetVmIpAddressCommand; +import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource; +import com.cloud.utils.Pair; +import com.cloud.utils.script.Script; + +public class LibvirtGetVmIpAddressCommandWrapperTest { + + private static String VIRSH_DOMIF_OUTPUT = " Name MAC address Protocol Address\n" + // + "-------------------------------------------------------------------------------\n" + // + " lo 00:00:00:00:00:70 ipv4 127.0.0.1/8\n" + // + " eth0 02:0c:02:f9:00:80 ipv4 192.168.0.10/24\n" + // + " net1 b2:41:19:69:a4:90 N/A N/A\n" + // + " net2 52:a2:36:cf:d1:50 ipv4 10.244.6.93/32\n" + // + " net3 a6:1d:d3:52:d3:40 N/A N/A\n" + // + " net4 2e:9b:60:dc:49:30 N/A N/A\n" + // + " lxc5b7327203b6f 92:b2:77:0b:a9:20 N/A N/A\n"; + + @Before + public void setUp() { + MockitoAnnotations.openMocks(this); + } + + @Test + public void testExecuteWithValidVmName() { + LibvirtComputingResource libvirtComputingResource = mock(LibvirtComputingResource.class); + GetVmIpAddressCommand getVmIpAddressCommand = mock(GetVmIpAddressCommand.class); + LibvirtGetVmIpAddressCommandWrapper commandWrapper = new LibvirtGetVmIpAddressCommandWrapper(); + MockedStatic + + diff --git a/ui/src/views/infra/HostInfo.vue b/ui/src/views/infra/HostInfo.vue index 1a4e9ee9f44..259445154a0 100644 --- a/ui/src/views/infra/HostInfo.vue +++ b/ui/src/views/infra/HostInfo.vue @@ -86,14 +86,48 @@ - -
- {{ $t('label.powerstate') }} + +
- {{ host.outofbandmanagement.powerstate }} + {{ $t('label.oobm.username') }} +
+ {{ host.outofbandmanagement.username }} +
-
-
+ + +
+ {{ $t('label.oobm.powerstate') }} +
+ {{ host.outofbandmanagement.powerstate }} +
+
+
+ +
+ {{ $t('label.oobm.driver') }} +
+ {{ host.outofbandmanagement.driver }} +
+
+
+ +
+ {{ $t('label.oobm.address') }} +
+ {{ host.outofbandmanagement.address }} +
+
+
+ +
+ {{ $t('label.oobm.port') }} +
+ {{ host.outofbandmanagement.port }} +
+
+
+
{{ $t('label.haenable') }} diff --git a/ui/src/views/storage/CreateTemplate.vue b/ui/src/views/storage/CreateTemplate.vue index 13ce75777fb..65941d39a9d 100644 --- a/ui/src/views/storage/CreateTemplate.vue +++ b/ui/src/views/storage/CreateTemplate.vue @@ -43,7 +43,7 @@ v-model:value="form.displaytext" :placeholder="apiParams.displaytext.description" /> - + @@ -130,41 +130,40 @@ - - - - - - - {{ $t('label.passwordenabled') }} - - - - - {{ $t('label.isdynamicallyscalable') }} - - - - - {{ $t('label.requireshvm') }} - - - - - {{ $t('label.isfeatured') }} - - - - - {{ $t('label.ispublic') }} - - - - + + + + + + + + + + + + + + + + + + + +
@@ -234,7 +233,9 @@ export default { }, fetchData () { this.fetchOsTypes() - this.fetchSnapshotZones() + if (this.resource.intervaltype) { + this.fetchSnapshotZones() + } if ('listDomains' in this.$store.getters.apis) { this.fetchDomains() } @@ -300,21 +301,24 @@ export default { this.handleDomainChange(null) }) }, - handleDomainChange (domain) { + async handleDomainChange (domain) { this.domainid = domain this.form.account = null this.account = null if ('listAccounts' in this.$store.getters.apis) { - this.fetchAccounts() + await this.fetchAccounts() } }, fetchAccounts () { - api('listAccounts', { - domainid: this.domainid - }).then(response => { - this.accounts = response.listaccountsresponse.account || [] - }).catch(error => { - this.$notifyError(error) + return new Promise((resolve, reject) => { + api('listAccounts', { + domainid: this.domainid + }).then(response => { + this.accounts = response?.listaccountsresponse?.account || [] + resolve(this.accounts) + }).catch(error => { + this.$notifyError(error) + }) }) }, handleAccountChange (acc) { @@ -329,17 +333,22 @@ export default { this.formRef.value.validate().then(() => { const formRaw = toRaw(this.form) const values = this.handleRemoveFields(formRaw) - values.snapshotid = this.resource.id - if (values.groupenabled) { - const input = values.groupenabled - for (const index in input) { - const name = input[index] - values[name] = true + const params = {} + if (this.resource.intervaltype) { + params.snapshotid = this.resource.id + } else { + params.volumeid = this.resource.id + } + + for (const key in values) { + const input = values[key] + if (input === undefined) { + continue } - delete values.groupenabled + params[key] = input } this.loading = true - api('createTemplate', values).then(response => { + api('createTemplate', params).then(response => { this.$pollJob({ jobId: response.createtemplateresponse.jobid, title: this.$t('message.success.create.template'),