mirror of
https://github.com/apache/cloudstack.git
synced 2025-10-26 08:42:29 +01:00
Merge branch '4.19' into 4.20
This commit is contained in:
commit
48ed5e2417
@ -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<GetVmIpAddressCommand, Answer, LibvirtComputingResource> {
|
||||
|
||||
|
||||
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<Ge
|
||||
if (!NetUtils.verifyDomainNameLabel(vmName, true)) {
|
||||
return new Answer(command, result, ip);
|
||||
}
|
||||
|
||||
String sanitizedVmName = sanitizeBashCommandArgument(vmName);
|
||||
String networkCidr = command.getVmNetworkCidr();
|
||||
List<String[]> 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");
|
||||
|
||||
ip = ipFromDomIf(sanitizedVmName, networkCidr);
|
||||
|
||||
if (ip == null) {
|
||||
if(!command.isWindows()) {
|
||||
//List all dhcp lease files inside guestVm
|
||||
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<String[]> commands = new ArrayList<>();
|
||||
commands.add(new String[]{virsh_path, "domifaddr", sanitizedVmName, "--source", "agent"});
|
||||
Pair<Integer,String> 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<String[]> commands = new ArrayList<>();
|
||||
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) {
|
||||
Pair<Integer,String> response = executePipedCommands(commands, 0);
|
||||
|
||||
if(response != null && response.second() != null) {
|
||||
String leasesList = response.second();
|
||||
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
|
||||
String ipAddr = executePipedCommands(commands, 0).second();
|
||||
if((ipAddr != null) && NetUtils.isIpWithInCidrRange(ipAddr, networkCidr)) {
|
||||
ip = ipAddr;
|
||||
break;
|
||||
}
|
||||
logger.debug("GetVmIp: "+ vmName + " Ip: "+ipAddr+" does not belong to network "+networkCidr);
|
||||
}
|
||||
logger.debug("GetVmIp: "+ sanitizedVmName + " 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\<service>\DhcpIPAddress
|
||||
commands = new ArrayList<>();
|
||||
logger.error("ipFromDhcpLeaseFile: Command execution failed for VM: " + sanitizedVmName);
|
||||
}
|
||||
return ip;
|
||||
}
|
||||
|
||||
private String ipFromWindowsRegistry(String sanitizedVmName, String networkCidr) {
|
||||
String ip = null;
|
||||
List<String[]> 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);
|
||||
Pair<Integer,String> 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){
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
if(ip != null){
|
||||
result = true;
|
||||
logger.debug("GetVmIp: "+ vmName + " Found Ip: "+ip);
|
||||
}
|
||||
return new Answer(command, result, ip);
|
||||
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<Integer, String> executePipedCommands(List<String[]> commands, long timeout) {
|
||||
return Script.executePipedCommands(commands, timeout);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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<Script> scriptMock = mockStatic(Script.class);
|
||||
|
||||
when(getVmIpAddressCommand.getVmName()).thenReturn("validVmName");
|
||||
when(getVmIpAddressCommand.getVmNetworkCidr()).thenReturn("192.168.0.0/24");
|
||||
when(getVmIpAddressCommand.isWindows()).thenReturn(false);
|
||||
when(Script.executePipedCommands(anyList(), anyLong())).thenReturn(new Pair<>(0, VIRSH_DOMIF_OUTPUT));
|
||||
|
||||
Answer answer = commandWrapper.execute(getVmIpAddressCommand, libvirtComputingResource);
|
||||
|
||||
try {
|
||||
assertTrue(answer.getResult());
|
||||
assertEquals("192.168.0.10", answer.getDetails());
|
||||
} finally {
|
||||
scriptMock.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExecuteWithInvalidVmName() {
|
||||
LibvirtComputingResource libvirtComputingResource = mock(LibvirtComputingResource.class);
|
||||
GetVmIpAddressCommand getVmIpAddressCommand = mock(GetVmIpAddressCommand.class);
|
||||
LibvirtGetVmIpAddressCommandWrapper commandWrapper = new LibvirtGetVmIpAddressCommandWrapper();
|
||||
MockedStatic<Script> scriptMock = mockStatic(Script.class);
|
||||
|
||||
when(getVmIpAddressCommand.getVmName()).thenReturn("invalidVmName!");
|
||||
when(getVmIpAddressCommand.getVmNetworkCidr()).thenReturn("192.168.0.0/24");
|
||||
when(getVmIpAddressCommand.isWindows()).thenReturn(false);
|
||||
when(Script.executePipedCommands(anyList(), anyLong())).thenReturn(new Pair<>(0, VIRSH_DOMIF_OUTPUT));
|
||||
|
||||
Answer answer = commandWrapper.execute(getVmIpAddressCommand, libvirtComputingResource);
|
||||
|
||||
try {
|
||||
assertFalse(answer.getResult());
|
||||
assertNull(answer.getDetails());
|
||||
} finally {
|
||||
scriptMock.close();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExecuteWithWindowsVm() {
|
||||
LibvirtComputingResource libvirtComputingResource = mock(LibvirtComputingResource.class);
|
||||
GetVmIpAddressCommand getVmIpAddressCommand = mock(GetVmIpAddressCommand.class);
|
||||
LibvirtGetVmIpAddressCommandWrapper commandWrapper = new LibvirtGetVmIpAddressCommandWrapper();
|
||||
MockedStatic<Script> scriptMock = null;
|
||||
|
||||
try {
|
||||
scriptMock = mockStatic(Script.class);
|
||||
|
||||
when(getVmIpAddressCommand.getVmName()).thenReturn("validVmName");
|
||||
when(getVmIpAddressCommand.getVmNetworkCidr()).thenReturn("192.168.0.0/24");
|
||||
when(getVmIpAddressCommand.isWindows()).thenReturn(true);
|
||||
when(Script.executePipedCommands(anyList(), anyLong())).thenReturn(new Pair<>(0, "192.168.0.10"));
|
||||
|
||||
Answer answer = commandWrapper.execute(getVmIpAddressCommand, libvirtComputingResource);
|
||||
|
||||
assertTrue(answer.getResult());
|
||||
assertEquals("192.168.0.10", answer.getDetails());
|
||||
} finally {
|
||||
if (scriptMock != null)
|
||||
scriptMock.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExecuteWithNoIpFound() {
|
||||
LibvirtComputingResource libvirtComputingResource = mock(LibvirtComputingResource.class);
|
||||
GetVmIpAddressCommand getVmIpAddressCommand = mock(GetVmIpAddressCommand.class);
|
||||
LibvirtGetVmIpAddressCommandWrapper commandWrapper = new LibvirtGetVmIpAddressCommandWrapper();
|
||||
MockedStatic<Script> scriptMock = null;
|
||||
try {
|
||||
scriptMock = mockStatic(Script.class);
|
||||
|
||||
when(getVmIpAddressCommand.getVmName()).thenReturn("validVmName");
|
||||
when(getVmIpAddressCommand.getVmNetworkCidr()).thenReturn("192.168.0.0/24");
|
||||
when(getVmIpAddressCommand.isWindows()).thenReturn(false);
|
||||
when(Script.executePipedCommands(anyList(), anyLong())).thenReturn(new Pair<>(0, ""));
|
||||
|
||||
Answer answer = commandWrapper.execute(getVmIpAddressCommand, libvirtComputingResource);
|
||||
|
||||
assertFalse(answer.getResult());
|
||||
assertNull(answer.getDetails());
|
||||
} finally {
|
||||
if (scriptMock != null)
|
||||
scriptMock.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExecuteWithValidVmNameAndNoIpFound() {
|
||||
LibvirtComputingResource libvirtComputingResource = mock(LibvirtComputingResource.class);
|
||||
GetVmIpAddressCommand getVmIpAddressCommand = mock(GetVmIpAddressCommand.class);
|
||||
LibvirtGetVmIpAddressCommandWrapper commandWrapper = new LibvirtGetVmIpAddressCommandWrapper();
|
||||
MockedStatic<Script> scriptMock = null;
|
||||
try {
|
||||
scriptMock = mockStatic(Script.class);
|
||||
|
||||
when(getVmIpAddressCommand.getVmName()).thenReturn("validVmName");
|
||||
when(getVmIpAddressCommand.getVmNetworkCidr()).thenReturn("192.168.0.0/24");
|
||||
when(getVmIpAddressCommand.isWindows()).thenReturn(false);
|
||||
when(Script.executePipedCommands(anyList(), anyLong())).thenReturn(new Pair<>(0, ""));
|
||||
|
||||
Answer answer = commandWrapper.execute(getVmIpAddressCommand, libvirtComputingResource);
|
||||
|
||||
assertFalse(answer.getResult());
|
||||
assertNull(answer.getDetails());
|
||||
} finally {
|
||||
if (scriptMock != null)
|
||||
scriptMock.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExecuteWithValidVmNameAndIpFromDhcpLeaseFile() {
|
||||
LibvirtComputingResource libvirtComputingResource = mock(LibvirtComputingResource.class);
|
||||
GetVmIpAddressCommand getVmIpAddressCommand = mock(GetVmIpAddressCommand.class);
|
||||
LibvirtGetVmIpAddressCommandWrapper commandWrapper = new LibvirtGetVmIpAddressCommandWrapper();
|
||||
MockedStatic<Script> scriptMock = null;
|
||||
try {
|
||||
scriptMock = mockStatic(Script.class);
|
||||
when(getVmIpAddressCommand.getVmName()).thenReturn("validVmName");
|
||||
when(getVmIpAddressCommand.getVmNetworkCidr()).thenReturn("192.168.0.0/24");
|
||||
when(getVmIpAddressCommand.isWindows()).thenReturn(false);
|
||||
when(Script.executePipedCommands(anyList(), anyLong())).thenReturn(new Pair<>(0, "192.168.0.10"));
|
||||
|
||||
Answer answer = commandWrapper.execute(getVmIpAddressCommand, libvirtComputingResource);
|
||||
|
||||
assertTrue(answer.getResult());
|
||||
assertEquals("192.168.0.10", answer.getDetails());
|
||||
} finally {
|
||||
if (scriptMock != null)
|
||||
scriptMock.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExecuteWithValidVmNameAndIpFromWindowsRegistry() {
|
||||
LibvirtComputingResource libvirtComputingResource = mock(LibvirtComputingResource.class);
|
||||
GetVmIpAddressCommand getVmIpAddressCommand = mock(GetVmIpAddressCommand.class);
|
||||
LibvirtGetVmIpAddressCommandWrapper commandWrapper = new LibvirtGetVmIpAddressCommandWrapper();
|
||||
MockedStatic<Script> scriptMock = null;
|
||||
try {
|
||||
scriptMock = mockStatic(Script.class);
|
||||
when(getVmIpAddressCommand.getVmName()).thenReturn("validVmName");
|
||||
when(getVmIpAddressCommand.getVmNetworkCidr()).thenReturn("192.168.0.0/24");
|
||||
when(getVmIpAddressCommand.isWindows()).thenReturn(true);
|
||||
when(Script.executePipedCommands(anyList(), anyLong())).thenReturn(new Pair<>(0, "\"192.168.0.10\""));
|
||||
|
||||
Answer answer = commandWrapper.execute(getVmIpAddressCommand, libvirtComputingResource);
|
||||
|
||||
assertTrue(answer.getResult());
|
||||
assertEquals("192.168.0.10", answer.getDetails());
|
||||
} finally {
|
||||
if (scriptMock != null)
|
||||
scriptMock.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIpFromDomIfCommandExecutionFailure() {
|
||||
LibvirtComputingResource libvirtComputingResource = mock(LibvirtComputingResource.class);
|
||||
GetVmIpAddressCommand getVmIpAddressCommand = mock(GetVmIpAddressCommand.class);
|
||||
LibvirtGetVmIpAddressCommandWrapper commandWrapper = new LibvirtGetVmIpAddressCommandWrapper();
|
||||
MockedStatic<Script> scriptMock = null;
|
||||
try {
|
||||
scriptMock = mockStatic(Script.class);
|
||||
when(getVmIpAddressCommand.getVmName()).thenReturn("testVm");
|
||||
when(getVmIpAddressCommand.getVmNetworkCidr()).thenReturn("192.168.1.0/24");
|
||||
when(Script.executePipedCommands(anyList(), anyLong())).thenReturn(null);
|
||||
|
||||
Answer answer = commandWrapper.execute(getVmIpAddressCommand, libvirtComputingResource);
|
||||
|
||||
assertFalse(answer.getResult());
|
||||
assertNull(answer.getDetails());
|
||||
} finally {
|
||||
if (scriptMock != null)
|
||||
scriptMock.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIpFromDhcpLeaseFileCommandExecutionFailure() {
|
||||
LibvirtComputingResource libvirtComputingResource = mock(LibvirtComputingResource.class);
|
||||
GetVmIpAddressCommand getVmIpAddressCommand = mock(GetVmIpAddressCommand.class);
|
||||
LibvirtGetVmIpAddressCommandWrapper commandWrapper = new LibvirtGetVmIpAddressCommandWrapper();
|
||||
MockedStatic<Script> scriptMock = null;
|
||||
try {
|
||||
scriptMock = mockStatic(Script.class);
|
||||
when(getVmIpAddressCommand.getVmName()).thenReturn("testVm");
|
||||
when(getVmIpAddressCommand.getVmNetworkCidr()).thenReturn("192.168.1.0/24");
|
||||
when(Script.executePipedCommands(anyList(), anyLong())).thenReturn(null);
|
||||
|
||||
Answer answer = commandWrapper.execute(getVmIpAddressCommand, libvirtComputingResource);
|
||||
|
||||
assertFalse(answer.getResult());
|
||||
assertNull(answer.getDetails());
|
||||
} finally {
|
||||
if (scriptMock != null)
|
||||
scriptMock.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIpFromWindowsRegistryCommandExecutionFailure() {
|
||||
LibvirtComputingResource libvirtComputingResource = mock(LibvirtComputingResource.class);
|
||||
GetVmIpAddressCommand getVmIpAddressCommand = mock(GetVmIpAddressCommand.class);
|
||||
LibvirtGetVmIpAddressCommandWrapper commandWrapper = new LibvirtGetVmIpAddressCommandWrapper();
|
||||
MockedStatic<Script> scriptMock = null;
|
||||
try {
|
||||
scriptMock = mockStatic(Script.class);
|
||||
when(getVmIpAddressCommand.getVmName()).thenReturn("testVm");
|
||||
when(getVmIpAddressCommand.getVmNetworkCidr()).thenReturn("192.168.1.0/24");
|
||||
when(getVmIpAddressCommand.isWindows()).thenReturn(true);
|
||||
when(Script.executePipedCommands(anyList(), anyLong())).thenReturn(null);
|
||||
|
||||
Answer answer = commandWrapper.execute(getVmIpAddressCommand, libvirtComputingResource);
|
||||
|
||||
assertFalse(answer.getResult());
|
||||
assertNull(answer.getDetails());
|
||||
} finally {
|
||||
if (scriptMock != null)
|
||||
scriptMock.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInit() {
|
||||
MockedStatic<Script> scriptMock = null;
|
||||
try {
|
||||
scriptMock = mockStatic(Script.class);
|
||||
scriptMock.when(() -> Script.getExecutableAbsolutePath("virt-ls")).thenReturn("/usr/bin/virt-ls");
|
||||
scriptMock.when(() -> Script.getExecutableAbsolutePath("virt-cat")).thenReturn("/usr/bin/virt-cat");
|
||||
scriptMock.when(() -> Script.getExecutableAbsolutePath("virt-win-reg")).thenReturn("/usr/bin/virt-win-reg");
|
||||
scriptMock.when(() -> Script.getExecutableAbsolutePath("tail")).thenReturn("/usr/bin/tail");
|
||||
scriptMock.when(() -> Script.getExecutableAbsolutePath("grep")).thenReturn("/usr/bin/grep");
|
||||
scriptMock.when(() -> Script.getExecutableAbsolutePath("awk")).thenReturn("/usr/bin/awk");
|
||||
scriptMock.when(() -> Script.getExecutableAbsolutePath("sed")).thenReturn("/usr/bin/sed");
|
||||
scriptMock.when(() -> Script.getExecutableAbsolutePath("virsh")).thenReturn("/usr/bin/virsh");
|
||||
|
||||
LibvirtGetVmIpAddressCommandWrapper.init();
|
||||
|
||||
assertEquals("/usr/bin/virt-ls", LibvirtGetVmIpAddressCommandWrapper.virt_ls_path);
|
||||
assertEquals("/usr/bin/virt-cat", LibvirtGetVmIpAddressCommandWrapper.virt_cat_path);
|
||||
assertEquals("/usr/bin/virt-win-reg", LibvirtGetVmIpAddressCommandWrapper.virt_win_reg_path);
|
||||
assertEquals("/usr/bin/tail", LibvirtGetVmIpAddressCommandWrapper.tail_path);
|
||||
assertEquals("/usr/bin/grep", LibvirtGetVmIpAddressCommandWrapper.grep_path);
|
||||
assertEquals("/usr/bin/awk", LibvirtGetVmIpAddressCommandWrapper.awk_path);
|
||||
assertEquals("/usr/bin/sed", LibvirtGetVmIpAddressCommandWrapper.sed_path);
|
||||
assertEquals("/usr/bin/virsh", LibvirtGetVmIpAddressCommandWrapper.virsh_path);
|
||||
} finally {
|
||||
if (scriptMock != null)
|
||||
scriptMock.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -403,6 +403,8 @@ for example:
|
||||
except IOError as e:
|
||||
msg = "Failed to save management server secret key file %s due to %s, also please check the default umask"%(self.encryptionKeyFile, e.strerror)
|
||||
self.errorAndExit(msg)
|
||||
os.chmod(self.encryptionKeyFile, 0o640)
|
||||
shutil.chown(self.encryptionKeyFile, user=None, group="cloud")
|
||||
|
||||
def formatEncryptResult(value):
|
||||
return 'ENC(%s)'%value
|
||||
|
||||
@ -18,14 +18,15 @@
|
||||
import logging
|
||||
import random
|
||||
import time
|
||||
import socket
|
||||
|
||||
# All tests inherit from cloudstackTestCase
|
||||
from marvin.cloudstackTestCase import cloudstackTestCase
|
||||
|
||||
# Import Integration Libraries
|
||||
# base - contains all resources as entities and defines create, delete, list operations on them
|
||||
from marvin.lib.base import Account, DiskOffering, ServiceOffering, Snapshot, StoragePool, Template, User, \
|
||||
VirtualMachine, Volume
|
||||
from marvin.lib.base import Account, DiskOffering, ServiceOffering, Snapshot, StoragePool, Template, User
|
||||
from marvin.lib.base import VirtualMachine, Volume, VmSnapshot
|
||||
|
||||
# common - commonly used methods for all tests are listed here
|
||||
from marvin.lib.common import get_domain, get_template, get_zone, list_clusters, list_hosts, list_virtual_machines, \
|
||||
@ -97,8 +98,7 @@ class TestData:
|
||||
# hypervisor type to test
|
||||
hypervisor_type = kvm
|
||||
|
||||
def __init__(self):
|
||||
linstor_controller_url = "http://10.43.224.8"
|
||||
def __init__(self, linstor_controller_url):
|
||||
self.testdata = {
|
||||
TestData.kvm: {
|
||||
TestData.username: "admin",
|
||||
@ -197,7 +197,7 @@ class TestData:
|
||||
"resourceGroup": "acs-test-same"
|
||||
}
|
||||
},
|
||||
# Linstor storage pool on different ScaleIO storage instance
|
||||
# Linstor storage pool on different Linstor storage instance
|
||||
TestData.primaryStorageDistinctInstance: {
|
||||
"name": "Linstor-%d" % random.randint(0, 100),
|
||||
TestData.scope: "ZONE",
|
||||
@ -225,6 +225,44 @@ class TestData:
|
||||
},
|
||||
}
|
||||
|
||||
class ServiceReady:
|
||||
@classmethod
|
||||
def ready(cls, hostname: str, port: int) -> bool:
|
||||
try:
|
||||
s = socket.create_connection((hostname, port), timeout=1)
|
||||
s.close()
|
||||
return True
|
||||
except (ConnectionRefusedError, socket.timeout, OSError):
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def wait(
|
||||
cls,
|
||||
hostname,
|
||||
port,
|
||||
wait_interval = 5,
|
||||
timeout = 90,
|
||||
service_name = 'ssh') -> bool:
|
||||
"""
|
||||
Wait until the controller can be reached.
|
||||
:param hostname:
|
||||
:param port: port of the application
|
||||
:param wait_interval:
|
||||
:param timeout: time to wait until exit with False
|
||||
:param service_name: name of the service to wait
|
||||
:return:
|
||||
"""
|
||||
starttime = int(round(time.time() * 1000))
|
||||
while not cls.ready(hostname, port):
|
||||
if starttime + timeout * 1000 < int(round(time.time() * 1000)):
|
||||
raise RuntimeError("{s} {h} cannot be reached.".format(s=service_name, h=hostname))
|
||||
time.sleep(wait_interval)
|
||||
return True
|
||||
|
||||
@classmethod
|
||||
def wait_ssh_ready(cls, hostname, wait_interval = 1, timeout = 90):
|
||||
return cls.wait(hostname, 22, wait_interval, timeout, "ssh")
|
||||
|
||||
|
||||
class TestLinstorVolumes(cloudstackTestCase):
|
||||
_volume_vm_id_and_vm_id_do_not_match_err_msg = "The volume's VM ID and the VM's ID do not match."
|
||||
@ -239,7 +277,11 @@ class TestLinstorVolumes(cloudstackTestCase):
|
||||
cls.apiClient = testclient.getApiClient()
|
||||
cls.configData = testclient.getParsedTestDataConfig()
|
||||
cls.dbConnection = testclient.getDbConnection()
|
||||
cls.testdata = TestData().testdata
|
||||
|
||||
# first host has the linstor controller
|
||||
first_host = list_hosts(cls.apiClient)[0]
|
||||
|
||||
cls.testdata = TestData(first_host.ipaddress).testdata
|
||||
|
||||
# Get Resources from Cloud Infrastructure
|
||||
cls.zone = get_zone(cls.apiClient, zone_id=cls.testdata[TestData.zoneId])
|
||||
@ -326,7 +368,8 @@ class TestLinstorVolumes(cloudstackTestCase):
|
||||
serviceofferingid=cls.compute_offering.id,
|
||||
templateid=cls.template.id,
|
||||
domainid=cls.domain.id,
|
||||
startvm=False
|
||||
startvm=False,
|
||||
mode='basic',
|
||||
)
|
||||
|
||||
TestLinstorVolumes._start_vm(cls.virtual_machine)
|
||||
@ -394,7 +437,8 @@ class TestLinstorVolumes(cloudstackTestCase):
|
||||
serviceofferingid=self.compute_offering.id,
|
||||
templateid=self.template.id,
|
||||
domainid=self.domain.id,
|
||||
startvm=False
|
||||
startvm=False,
|
||||
mode='basic',
|
||||
)
|
||||
|
||||
TestLinstorVolumes._start_vm(test_virtual_machine)
|
||||
@ -887,8 +931,31 @@ class TestLinstorVolumes(cloudstackTestCase):
|
||||
"Check volume was deleted"
|
||||
)
|
||||
|
||||
@attr(tags=['basic'], required_hardware=False)
|
||||
def test_09_create_snapshot(self):
|
||||
"""Create snapshot of root disk"""
|
||||
self.virtual_machine.stop(self.apiClient)
|
||||
|
||||
volume = list_volumes(
|
||||
self.apiClient,
|
||||
virtualmachineid = self.virtual_machine.id,
|
||||
type = "ROOT",
|
||||
listall = True,
|
||||
)
|
||||
snapshot = Snapshot.create(
|
||||
self.apiClient,
|
||||
volume_id = volume[0].id,
|
||||
account=self.account.name,
|
||||
domainid=self.domain.id,
|
||||
)
|
||||
|
||||
self.assertIsNotNone(snapshot, "Could not create snapshot")
|
||||
|
||||
snapshot.delete(self.apiClient)
|
||||
|
||||
|
||||
@attr(tags=['advanced', 'migration'], required_hardware=False)
|
||||
def test_09_migrate_volume_to_same_instance_pool(self):
|
||||
def test_10_migrate_volume_to_same_instance_pool(self):
|
||||
"""Migrate volume to the same instance pool"""
|
||||
|
||||
if not self.testdata[TestData.migrationTests]:
|
||||
@ -906,7 +973,8 @@ class TestLinstorVolumes(cloudstackTestCase):
|
||||
serviceofferingid=self.compute_offering.id,
|
||||
templateid=self.template.id,
|
||||
domainid=self.domain.id,
|
||||
startvm=False
|
||||
startvm=False,
|
||||
mode='basic',
|
||||
)
|
||||
|
||||
TestLinstorVolumes._start_vm(test_virtual_machine)
|
||||
@ -1020,7 +1088,7 @@ class TestLinstorVolumes(cloudstackTestCase):
|
||||
test_virtual_machine.delete(self.apiClient, True)
|
||||
|
||||
@attr(tags=['advanced', 'migration'], required_hardware=False)
|
||||
def test_10_migrate_volume_to_distinct_instance_pool(self):
|
||||
def test_11_migrate_volume_to_distinct_instance_pool(self):
|
||||
"""Migrate volume to distinct instance pool"""
|
||||
|
||||
if not self.testdata[TestData.migrationTests]:
|
||||
@ -1038,7 +1106,8 @@ class TestLinstorVolumes(cloudstackTestCase):
|
||||
serviceofferingid=self.compute_offering.id,
|
||||
templateid=self.template.id,
|
||||
domainid=self.domain.id,
|
||||
startvm=False
|
||||
startvm=False,
|
||||
mode='basic',
|
||||
)
|
||||
|
||||
TestLinstorVolumes._start_vm(test_virtual_machine)
|
||||
@ -1151,6 +1220,132 @@ class TestLinstorVolumes(cloudstackTestCase):
|
||||
|
||||
test_virtual_machine.delete(self.apiClient, True)
|
||||
|
||||
@attr(tags=["basic"], required_hardware=False)
|
||||
def test_12_create_vm_snapshots(self):
|
||||
"""Test to create VM snapshots
|
||||
"""
|
||||
vm = TestLinstorVolumes._start_vm(self.virtual_machine)
|
||||
|
||||
try:
|
||||
# Login to VM and write data to file system
|
||||
self.debug("virt: {}".format(vm))
|
||||
ssh_client = self.virtual_machine.get_ssh_client(vm.ipaddress, retries=5)
|
||||
ssh_client.execute("echo 'hello world' > testfile")
|
||||
ssh_client.execute("sync")
|
||||
except Exception as exc:
|
||||
self.fail("SSH failed for Virtual machine {}: {}".format(self.virtual_machine.ssh_ip, exc))
|
||||
|
||||
time.sleep(10)
|
||||
memory_snapshot = False
|
||||
vm_snapshot = VmSnapshot.create(
|
||||
self.apiClient,
|
||||
self.virtual_machine.id,
|
||||
memory_snapshot,
|
||||
"VMSnapshot1",
|
||||
"test snapshot"
|
||||
)
|
||||
self.assertEqual(
|
||||
vm_snapshot.state,
|
||||
"Ready",
|
||||
"Check the snapshot of vm is ready!"
|
||||
)
|
||||
|
||||
@attr(tags=["basic"], required_hardware=False)
|
||||
def test_13_revert_vm_snapshots(self):
|
||||
"""Test to revert VM snapshots
|
||||
"""
|
||||
|
||||
result = None
|
||||
try:
|
||||
ssh_client = self.virtual_machine.get_ssh_client(reconnect=True)
|
||||
result = ssh_client.execute("rm -rf testfile")
|
||||
except Exception as exc:
|
||||
self.fail("SSH failed for Virtual machine %s: %s".format(self.virtual_machine.ipaddress, exc))
|
||||
|
||||
if result is not None and "No such file or directory" in str(result):
|
||||
self.fail("testfile not deleted")
|
||||
|
||||
time.sleep(5)
|
||||
|
||||
list_snapshot_response = VmSnapshot.list(
|
||||
self.apiClient,
|
||||
virtualmachineid=self.virtual_machine.id,
|
||||
listall=True)
|
||||
|
||||
self.assertEqual(
|
||||
isinstance(list_snapshot_response, list),
|
||||
True,
|
||||
"Check list response returns a valid list"
|
||||
)
|
||||
self.assertNotEqual(
|
||||
list_snapshot_response,
|
||||
None,
|
||||
"Check if snapshot exists in ListSnapshot"
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
list_snapshot_response[0].state,
|
||||
"Ready",
|
||||
"Check the snapshot of vm is ready!"
|
||||
)
|
||||
|
||||
self.virtual_machine.stop(self.apiClient, forced=True)
|
||||
|
||||
VmSnapshot.revertToSnapshot(
|
||||
self.apiClient,
|
||||
list_snapshot_response[0].id
|
||||
)
|
||||
|
||||
TestLinstorVolumes._start_vm(self.virtual_machine)
|
||||
|
||||
try:
|
||||
ssh_client = self.virtual_machine.get_ssh_client(reconnect=True)
|
||||
|
||||
result = ssh_client.execute("cat testfile")
|
||||
|
||||
except Exception as exc:
|
||||
self.fail("SSH failed for Virtual machine {}: {}".format(self.virtual_machine.ipaddress, exc))
|
||||
|
||||
self.assertEqual(
|
||||
"hello world",
|
||||
result[0],
|
||||
"Check the content is the same as originaly written"
|
||||
)
|
||||
|
||||
@attr(tags=["basic"], required_hardware=False)
|
||||
def test_14_delete_vm_snapshots(self):
|
||||
"""Test to delete vm snapshots
|
||||
"""
|
||||
|
||||
list_snapshot_response = VmSnapshot.list(
|
||||
self.apiClient,
|
||||
virtualmachineid=self.virtual_machine.id,
|
||||
listall=True)
|
||||
|
||||
self.assertEqual(
|
||||
isinstance(list_snapshot_response, list),
|
||||
True,
|
||||
"Check list response returns a valid list"
|
||||
)
|
||||
self.assertNotEqual(
|
||||
list_snapshot_response,
|
||||
None,
|
||||
"Check if snapshot exists in ListSnapshot"
|
||||
)
|
||||
VmSnapshot.deleteVMSnapshot(
|
||||
self.apiClient,
|
||||
list_snapshot_response[0].id)
|
||||
|
||||
time.sleep(5)
|
||||
|
||||
list_snapshot_response = VmSnapshot.list(
|
||||
self.apiClient,
|
||||
virtualmachineid=self.virtual_machine.id,
|
||||
listall=False)
|
||||
self.debug('list_snapshot_response -------------------- {}'.format(list_snapshot_response))
|
||||
|
||||
self.assertIsNone(list_snapshot_response, "snapshot is already deleted")
|
||||
|
||||
def _create_vm_using_template_and_destroy_vm(self, template):
|
||||
vm_name = "VM-%d" % random.randint(0, 100)
|
||||
|
||||
@ -1177,42 +1372,31 @@ class TestLinstorVolumes(cloudstackTestCase):
|
||||
|
||||
virtual_machine.delete(self.apiClient, True)
|
||||
|
||||
@staticmethod
|
||||
def _get_bytes_from_gb(number_in_gb):
|
||||
return number_in_gb * 1024 * 1024 * 1024
|
||||
|
||||
def _get_volume(self, volume_id):
|
||||
list_vols_response = list_volumes(self.apiClient, id=volume_id)
|
||||
return list_vols_response[0]
|
||||
|
||||
def _get_vm(self, vm_id):
|
||||
list_vms_response = list_virtual_machines(self.apiClient, id=vm_id)
|
||||
@classmethod
|
||||
def _get_vm(cls, vm_id):
|
||||
list_vms_response = list_virtual_machines(cls.apiClient, id=vm_id)
|
||||
return list_vms_response[0]
|
||||
|
||||
def _get_template_cache_name(self):
|
||||
if TestData.hypervisor_type == TestData.kvm:
|
||||
return TestData.templateCacheNameKvm
|
||||
|
||||
self.assert_(False, "Invalid hypervisor type")
|
||||
|
||||
@classmethod
|
||||
def _start_vm(cls, vm):
|
||||
vm_for_check = list_virtual_machines(
|
||||
cls.apiClient,
|
||||
id=vm.id
|
||||
)[0]
|
||||
vm_for_check = cls._get_vm(vm.id)
|
||||
|
||||
if vm_for_check.state == VirtualMachine.STOPPED:
|
||||
vm.start(cls.apiClient)
|
||||
|
||||
# For KVM, just give it 90 seconds to boot up.
|
||||
if TestData.hypervisor_type == TestData.kvm:
|
||||
time.sleep(90)
|
||||
vm_for_check = cls._get_vm(vm.id)
|
||||
ServiceReady.wait_ssh_ready(vm_for_check.ipaddress)
|
||||
return vm_for_check
|
||||
|
||||
@classmethod
|
||||
def _reboot_vm(cls, vm):
|
||||
vm_for_check = cls._get_vm(vm.id)
|
||||
vm.reboot(cls.apiClient)
|
||||
|
||||
# For KVM, just give it 90 seconds to boot up.
|
||||
if TestData.hypervisor_type == TestData.kvm:
|
||||
time.sleep(90)
|
||||
time.sleep(5)
|
||||
|
||||
ServiceReady.wait_ssh_ready(vm_for_check.ipaddress)
|
||||
|
||||
@ -2617,6 +2617,11 @@
|
||||
"label.zonewizard.traffictype.storage": "Storage: Traffic between primary and secondary storage servers, such as Instance Templates and Snapshots.",
|
||||
"label.buckets": "Buckets",
|
||||
"label.objectstorageid": "Object Storage Pool",
|
||||
"label.oobm.address": "Out-of-band management address",
|
||||
"label.oobm.driver": "Out-of-band management driver",
|
||||
"label.oobm.port": "Out-of-band management port",
|
||||
"label.oobm.powerstate": "Out-of-band management power state",
|
||||
"label.oobm.username": "Out-of-band management username",
|
||||
"label.bucket.update": "Update Bucket",
|
||||
"label.bucket.delete": "Delete Bucket",
|
||||
"label.quotagb": "Quota in GB",
|
||||
@ -3327,6 +3332,7 @@
|
||||
"message.no.description": "No description entered.",
|
||||
"message.offering.internet.protocol.warning": "WARNING: IPv6 supported Networks use static routing and will require upstream routes to be configured manually.",
|
||||
"message.offering.ipv6.warning": "Please refer documentation for creating IPv6 enabled Network/VPC offering <a href='http://docs.cloudstack.apache.org/en/latest/plugins/ipv6.html#isolated-network-and-vpc-tier'>IPv6 support in CloudStack - Isolated Networks and VPC Network Tiers</a>",
|
||||
"message.oobm.configured": "Successfully configured out-of-band management for host",
|
||||
"message.ovf.configurations": "OVF configurations available for the selected appliance. Please select the desired value. Incompatible compute offerings will get disabled.",
|
||||
"message.password.reset.failed": "Failed to reset password.",
|
||||
"message.password.reset.success": "Password has been reset successfully. Please login using your new credentials.",
|
||||
|
||||
@ -154,16 +154,8 @@ export default {
|
||||
message: 'label.outofbandmanagement.configure',
|
||||
docHelp: 'adminguide/hosts.html#out-of-band-management',
|
||||
dataView: true,
|
||||
post: true,
|
||||
args: ['hostid', 'address', 'port', 'username', 'password', 'driver'],
|
||||
mapping: {
|
||||
hostid: {
|
||||
value: (record) => { return record.id }
|
||||
},
|
||||
driver: {
|
||||
options: ['ipmitool', 'nestedcloudstack', 'redfish']
|
||||
}
|
||||
}
|
||||
popup: true,
|
||||
component: shallowRef(defineAsyncComponent(() => import('@/views/infra/ConfigureHostOOBM')))
|
||||
},
|
||||
{
|
||||
api: 'enableOutOfBandManagementForHost',
|
||||
|
||||
@ -258,25 +258,8 @@ export default {
|
||||
show: (record) => {
|
||||
return record.state === 'Ready' && (record.vmstate === 'Stopped' || !record.virtualmachineid)
|
||||
},
|
||||
args: (record, store) => {
|
||||
var fields = ['volumeid', 'name', 'displaytext', 'ostypeid', 'isdynamicallyscalable', 'requireshvm', 'passwordenabled']
|
||||
if (['Admin', 'DomainAdmin'].includes(store.userInfo.roletype)) {
|
||||
fields.push('domainid')
|
||||
fields.push('account')
|
||||
}
|
||||
if (['Admin'].includes(store.userInfo.roletype) || store.features.userpublictemplateenabled) {
|
||||
fields.push('ispublic')
|
||||
}
|
||||
if (['Admin'].includes(store.userInfo.roletype)) {
|
||||
fields.push('isfeatured')
|
||||
}
|
||||
return fields
|
||||
},
|
||||
mapping: {
|
||||
volumeid: {
|
||||
value: (record) => { return record.id }
|
||||
}
|
||||
}
|
||||
popup: true,
|
||||
component: shallowRef(defineAsyncComponent(() => import('@/views/storage/CreateTemplate.vue')))
|
||||
},
|
||||
{
|
||||
api: 'recoverVolume',
|
||||
|
||||
172
ui/src/views/infra/ConfigureHostOOBM.vue
Normal file
172
ui/src/views/infra/ConfigureHostOOBM.vue
Normal file
@ -0,0 +1,172 @@
|
||||
// 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.
|
||||
|
||||
<template>
|
||||
<div class="form-layout">
|
||||
<a-form
|
||||
:ref="formRef"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
@finish="handleSubmit"
|
||||
v-ctrl-enter="handleSubmit"
|
||||
class="form"
|
||||
layout="vertical"
|
||||
>
|
||||
<a-alert type="warning">
|
||||
<template #message>
|
||||
<span v-html="$t('label.outofbandmanagement.configure')" />
|
||||
</template>
|
||||
</a-alert>
|
||||
<div style="margin-top: 10px;">
|
||||
<a-form-item name="address" ref="address">
|
||||
<template #label>
|
||||
<tooltip-label :title="$t('label.address')" :tooltip="apiParams.address.description"/>
|
||||
</template>
|
||||
<a-input
|
||||
v-model:value="form.address"
|
||||
v-focus="true" />
|
||||
</a-form-item>
|
||||
<a-form-item name="port" ref="port">
|
||||
<template #label>
|
||||
<tooltip-label :title="$t('label.port')" :tooltip="apiParams.port.description"/>
|
||||
</template>
|
||||
<a-input
|
||||
v-model:value="form.port" />
|
||||
</a-form-item>
|
||||
<a-form-item name="username" ref="username">
|
||||
<template #label>
|
||||
<tooltip-label :title="$t('label.username')" :tooltip="apiParams.username.description"/>
|
||||
</template>
|
||||
<a-input
|
||||
v-model:value="form.username" />
|
||||
</a-form-item>
|
||||
<a-form-item name="password" ref="password">
|
||||
<template #label>
|
||||
<tooltip-label :title="$t('label.password')" :tooltip="apiParams.password.description"/>
|
||||
</template>
|
||||
<a-input-password
|
||||
v-model:value="form.password"
|
||||
:placeholder="apiParams.password.description"/>
|
||||
</a-form-item>
|
||||
<a-form-item name="driver" ref="driver">
|
||||
<template #label>
|
||||
<tooltip-label :title="$t('label.driver')" :tooltip="apiParams.driver.description"/>
|
||||
</template>
|
||||
<a-select
|
||||
v-model:value="form.driver"
|
||||
style="width: 100%;"
|
||||
optionFilterProp="value"
|
||||
:filterOption="(input, option) => {
|
||||
return option.value.toLowerCase().indexOf(input.toLowerCase()) >= 0
|
||||
}" >
|
||||
<a-select-option key="" label="">{{ }}</a-select-option>
|
||||
<a-select-option value="ipmitool">ipmitool</a-select-option>
|
||||
<a-select-option value="nestedcloudstack">nestedcloudstack</a-select-option>
|
||||
<a-select-option value="redfish">redfish</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</div>
|
||||
<div :span="24" class="action-button">
|
||||
<a-button @click="onCloseAction">{{ $t('label.cancel') }}</a-button>
|
||||
<a-button type="primary" @click="handleSubmit" ref="submit">{{ $t('label.ok') }}</a-button>
|
||||
</div>
|
||||
</a-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import TooltipLabel from '@/components/widgets/TooltipLabel'
|
||||
import { ref, reactive, toRaw } from 'vue'
|
||||
import { api } from '@/api'
|
||||
|
||||
export default {
|
||||
name: 'ConfigureHostOOBM',
|
||||
components: {
|
||||
TooltipLabel
|
||||
},
|
||||
props: {
|
||||
resource: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
}
|
||||
},
|
||||
beforeCreate () {
|
||||
this.apiParams = this.$getApiParams('configureOutOfBandManagement')
|
||||
},
|
||||
created () {
|
||||
this.initForm()
|
||||
},
|
||||
methods: {
|
||||
initForm () {
|
||||
this.formRef = ref()
|
||||
this.form = reactive({
|
||||
address: this.resource.outofbandmanagement.address || '',
|
||||
port: this.resource.outofbandmanagement.port || '',
|
||||
username: this.resource.outofbandmanagement.username || '',
|
||||
password: '',
|
||||
driver: this.resource.outofbandmanagement.driver || ''
|
||||
})
|
||||
this.rules = reactive({
|
||||
address: [{ required: true, message: this.$t('message.error.required.input') }],
|
||||
port: [{ required: true, message: this.$t('message.error.required.input') }],
|
||||
username: [{ required: true, message: this.$t('message.error.required.input') }],
|
||||
password: [{ required: true, message: this.$t('message.error.required.input') }],
|
||||
driver: [{ required: true, message: this.$t('message.error.required.input') }]
|
||||
})
|
||||
},
|
||||
handleSubmit (e) {
|
||||
e.preventDefault()
|
||||
this.formRef.value.validate().then(() => {
|
||||
const values = toRaw(this.form)
|
||||
const params = {
|
||||
hostid: this.resource.id,
|
||||
address: values.address,
|
||||
port: values.port,
|
||||
username: values.username,
|
||||
password: values.password,
|
||||
driver: values.driver
|
||||
}
|
||||
|
||||
api('configureOutOfBandManagement', {}, 'POST', params).then(_ => {
|
||||
this.$message.success(this.$t('message.oobm.configured'))
|
||||
this.$emit('refresh-data')
|
||||
this.onCloseAction()
|
||||
}).catch(error => {
|
||||
this.$notifyError(error)
|
||||
})
|
||||
})
|
||||
},
|
||||
onCloseAction () {
|
||||
this.$emit('close-action')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.form-layout {
|
||||
width: 30vw;
|
||||
|
||||
@media (min-width: 500px) {
|
||||
width: 450px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -86,14 +86,48 @@
|
||||
</div>
|
||||
</div>
|
||||
</a-list-item>
|
||||
<a-list-item v-if="host.outofbandmanagement">
|
||||
<span v-if="host?.outofbandmanagement?.enabled">
|
||||
<a-list-item>
|
||||
<div>
|
||||
<strong>{{ $t('label.powerstate') }}</strong>
|
||||
<strong>{{ $t('label.oobm.username') }}</strong>
|
||||
<div>
|
||||
{{ host.outofbandmanagement.username }}
|
||||
</div>
|
||||
</div>
|
||||
</a-list-item>
|
||||
<a-list-item>
|
||||
<div>
|
||||
<strong>{{ $t('label.oobm.powerstate') }}</strong>
|
||||
<div>
|
||||
{{ host.outofbandmanagement.powerstate }}
|
||||
</div>
|
||||
</div>
|
||||
</a-list-item>
|
||||
<a-list-item>
|
||||
<div>
|
||||
<strong>{{ $t('label.oobm.driver') }}</strong>
|
||||
<div>
|
||||
{{ host.outofbandmanagement.driver }}
|
||||
</div>
|
||||
</div>
|
||||
</a-list-item>
|
||||
<a-list-item>
|
||||
<div>
|
||||
<strong>{{ $t('label.oobm.address') }}</strong>
|
||||
<div>
|
||||
{{ host.outofbandmanagement.address }}
|
||||
</div>
|
||||
</div>
|
||||
</a-list-item>
|
||||
<a-list-item>
|
||||
<div>
|
||||
<strong>{{ $t('label.oobm.port') }}</strong>
|
||||
<div>
|
||||
{{ host.outofbandmanagement.port }}
|
||||
</div>
|
||||
</div>
|
||||
</a-list-item>
|
||||
</span>
|
||||
<a-list-item v-if="host.hostha">
|
||||
<div>
|
||||
<strong>{{ $t('label.haenable') }}</strong>
|
||||
|
||||
@ -43,7 +43,7 @@
|
||||
v-model:value="form.displaytext"
|
||||
:placeholder="apiParams.displaytext.description" />
|
||||
</a-form-item>
|
||||
<a-form-item ref="zoneid" name="zoneid">
|
||||
<a-form-item v-if="resource.intervaltype" ref="zoneid" name="zoneid">
|
||||
<template #label>
|
||||
<tooltip-label :title="$t('label.zoneid')" :tooltip="apiParams.zoneid.description"/>
|
||||
</template>
|
||||
@ -130,40 +130,39 @@
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-row :gutter="12">
|
||||
<a-col :md="24" :lg="24">
|
||||
<a-form-item ref="groupenabled" name="groupenabled">
|
||||
<a-checkbox-group
|
||||
v-model:value="form.groupenabled"
|
||||
style="width: 100%;"
|
||||
>
|
||||
<a-row>
|
||||
<a-col :span="12">
|
||||
<a-checkbox value="passwordenabled">
|
||||
{{ $t('label.passwordenabled') }}
|
||||
</a-checkbox>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-checkbox value="isdynamicallyscalable">
|
||||
{{ $t('label.isdynamicallyscalable') }}
|
||||
</a-checkbox>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-checkbox value="requireshvm">
|
||||
{{ $t('label.requireshvm') }}
|
||||
</a-checkbox>
|
||||
</a-col>
|
||||
<a-col :span="12" v-if="isAdminRole">
|
||||
<a-checkbox value="isfeatured">
|
||||
{{ $t('label.isfeatured') }}
|
||||
</a-checkbox>
|
||||
</a-col>
|
||||
<a-col :span="12" v-if="isAdminRole || $store.getters.features.userpublictemplateenabled">
|
||||
<a-checkbox value="ispublic">
|
||||
{{ $t('label.ispublic') }}
|
||||
</a-checkbox>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-checkbox-group>
|
||||
<a-col :md="24" :lg="12">
|
||||
<a-form-item ref="isdynamicallyscalable" name="isdynamicallyscalable">
|
||||
<template #label>
|
||||
<tooltip-label :title="$t('label.isdynamicallyscalable')" :tooltip="apiParams.isdynamicallyscalable.description"/>
|
||||
</template>
|
||||
<a-switch v-model:checked="form.isdynamicallyscalable" />
|
||||
</a-form-item>
|
||||
<a-form-item ref="requireshvm" name="requireshvm">
|
||||
<template #label>
|
||||
<tooltip-label :title="$t('label.requireshvm')" :tooltip="apiParams.requireshvm.description"/>
|
||||
</template>
|
||||
<a-switch v-model:checked="form.requireshvm" />
|
||||
</a-form-item>
|
||||
<a-form-item ref="passwordenabled" name="passwordenabled">
|
||||
<template #label>
|
||||
<tooltip-label :title="$t('label.passwordenabled')" :tooltip="apiParams.passwordenabled.description"/>
|
||||
</template>
|
||||
<a-switch v-model:checked="form.passwordenabled" />
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
ref="ispublic"
|
||||
name="ispublic"
|
||||
v-if="$store.getters.userInfo.roletype === 'Admin' || $store.getters.features.userpublictemplateenabled" >
|
||||
<template #label>
|
||||
<tooltip-label :title="$t('label.ispublic')" :tooltip="apiParams.ispublic.description"/>
|
||||
</template>
|
||||
<a-switch v-model:checked="form.ispublic" />
|
||||
</a-form-item>
|
||||
<a-form-item ref="isfeatured" name="isfeatured" v-if="$store.getters.userInfo.roletype === 'Admin'">
|
||||
<template #label>
|
||||
<tooltip-label :title="$t('label.isfeatured')" :tooltip="apiParams.isfeatured.description"/>
|
||||
</template>
|
||||
<a-switch v-model:checked="form.isfeatured" />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
@ -234,7 +233,9 @@ export default {
|
||||
},
|
||||
fetchData () {
|
||||
this.fetchOsTypes()
|
||||
if (this.resource.intervaltype) {
|
||||
this.fetchSnapshotZones()
|
||||
}
|
||||
if ('listDomains' in this.$store.getters.apis) {
|
||||
this.fetchDomains()
|
||||
}
|
||||
@ -300,22 +301,25 @@ 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 () {
|
||||
return new Promise((resolve, reject) => {
|
||||
api('listAccounts', {
|
||||
domainid: this.domainid
|
||||
}).then(response => {
|
||||
this.accounts = response.listaccountsresponse.account || []
|
||||
this.accounts = response?.listaccountsresponse?.account || []
|
||||
resolve(this.accounts)
|
||||
}).catch(error => {
|
||||
this.$notifyError(error)
|
||||
})
|
||||
})
|
||||
},
|
||||
handleAccountChange (acc) {
|
||||
if (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
|
||||
}
|
||||
delete values.groupenabled
|
||||
|
||||
for (const key in values) {
|
||||
const input = values[key]
|
||||
if (input === undefined) {
|
||||
continue
|
||||
}
|
||||
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'),
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user