This is an automated email from the ASF dual-hosted git repository. pearl11594 pushed a commit to branch 4.20 in repository https://gitbox.apache.org/repos/asf/cloudstack.git
commit 48ed5e24175b9cf7ad797ba39be5ae51c37f1552 Merge: 3aabedd4473 b41acf28d4c Author: Pearl Dsilva <pearl1...@gmail.com> AuthorDate: Tue Mar 4 09:17:16 2025 -0500 Merge branch '4.19' into 4.20 .../LibvirtGetVmIpAddressCommandWrapper.java | 167 +++++++---- .../LibvirtGetVmIpAddressCommandWrapperTest.java | 320 +++++++++++++++++++++ setup/bindir/cloud-setup-databases.in | 2 + .../plugins/linstor/test_linstor_volumes.py | 252 +++++++++++++--- ui/public/locales/en.json | 6 + ui/src/config/section/infra/hosts.js | 12 +- ui/src/config/section/storage.js | 21 +- ui/src/views/infra/ConfigureHostOOBM.vue | 172 +++++++++++ ui/src/views/infra/HostInfo.vue | 46 ++- ui/src/views/storage/CreateTemplate.vue | 113 ++++---- 10 files changed, 941 insertions(+), 170 deletions(-) diff --cc plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtGetVmIpAddressCommandWrapper.java index 0f00a6e29bd,0dd52ddfb10..e050cb4e85d --- 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 @@@ -33,7 -36,28 +34,27 @@@ import com.cloud.utils.script.Script @ResourceWrapper(handles = GetVmIpAddressCommand.class) public final class LibvirtGetVmIpAddressCommandWrapper extends CommandWrapper<GetVmIpAddressCommand, Answer, LibvirtComputingResource> { - private static final Logger s_logger = Logger.getLogger(LibvirtGetVmIpAddressCommandWrapper.class); + 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 -66,113 +63,113 @@@ if (!NetUtils.verifyDomainNameLabel(vmName, true)) { return new Answer(command, result, ip); } + String sanitizedVmName = sanitizeBashCommandArgument(vmName); String networkCidr = command.getVmNetworkCidr(); + + ip = ipFromDomIf(sanitizedVmName, networkCidr); + + if (ip == null) { + if(!command.isWindows()) { + ip = ipFromDhcpLeaseFile(sanitizedVmName, networkCidr); + } else { + ip = ipFromWindowsRegistry(sanitizedVmName, networkCidr); + } + } + + if(ip != null){ + result = true; - s_logger.debug("GetVmIp: "+ vmName + " Found Ip: "+ip); ++ logger.debug("GetVmIp: "+ vmName + " Found Ip: "+ip); + } else { - s_logger.warn("GetVmIp: "+ vmName + " IP not found."); ++ 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<>(); - 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; + 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; + } } - 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\<service>\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); - s_logger.error("ipFromDomIf: Command execution failed for VM: " + sanitizedVmName); ++ 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"}); + 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){ + 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; } - s_logger.debug("GetVmIp: "+ sanitizedVmName + " Ip: "+ipAddr+" does not belong to network "+networkCidr); ++ logger.debug("GetVmIp: "+ sanitizedVmName + " Ip: "+ipAddr+" does not belong to network "+networkCidr); } + } else { - s_logger.error("ipFromDhcpLeaseFile: Command execution failed for VM: " + sanitizedVmName); ++ logger.error("ipFromDhcpLeaseFile: Command execution failed for VM: " + sanitizedVmName); } - if(ip != null){ - result = true; - logger.debug("GetVmIp: "+ vmName + " Found Ip: "+ip); + 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/\"$//"}); + Pair<Integer,String> pair = executePipedCommands(commands, 0); + if(pair != null && pair.second() != null) { + String ipList = pair.second(); + ipList = ipList.replaceAll("\"", ""); - s_logger.debug("GetVmIp: "+ sanitizedVmName + "Ips: "+ipList); ++ 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; + } - s_logger.debug("GetVmIp: "+ sanitizedVmName + " Ip: "+ipAddr+" does not belong to network "+networkCidr); ++ logger.debug("GetVmIp: "+ sanitizedVmName + " Ip: "+ipAddr+" does not belong to network "+networkCidr); + } + } else { - s_logger.error("ipFromWindowsRegistry: Command execution failed for VM: " + sanitizedVmName); ++ logger.error("ipFromWindowsRegistry: Command execution failed for VM: " + sanitizedVmName); } - return new Answer(command, result, ip); + return ip; + } + + static Pair<Integer, String> executePipedCommands(List<String[]> commands, long timeout) { + return Script.executePipedCommands(commands, timeout); } } diff --cc ui/public/locales/en.json index dfdac5963bd,c1c88da62d3..0a67c67ba9a --- a/ui/public/locales/en.json +++ b/ui/public/locales/en.json @@@ -3327,10 -3071,8 +3332,11 @@@ "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.", +"message.path": "Path : ", "message.path.description": "NFS: exported path from the server. VMFS: /datacenter name/datastore name. SharedMountPoint: path where primary storage is mounted, such as /mnt/primary.", "message.please.confirm.remove.ssh.key.pair": "Please confirm that you want to remove this SSH key pair.", "message.please.confirm.remove.user.data": "Please confirm that you want to remove this Userdata", diff --cc ui/src/config/section/storage.js index 37f2a4b55e0,a869dfb6e80..a86650874ba --- a/ui/src/config/section/storage.js +++ b/ui/src/config/section/storage.js @@@ -256,27 -254,12 +256,10 @@@ export default label: 'label.action.create.template.from.volume', dataView: true, show: (record) => { - return !['Destroy', 'Destroyed', 'Expunging', 'Expunged', 'Migrating', 'Uploading', 'UploadError', 'Creating'].includes(record.state) && - ((record.type === 'ROOT' && record.vmstate === 'Stopped') || - (record.type !== 'ROOT' && !record.virtualmachineid && !['Allocated', 'Uploaded'].includes(record.state))) + 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',