diff --git a/core/src/main/java/org/zstack/core/CoreGlobalProperty.java b/core/src/main/java/org/zstack/core/CoreGlobalProperty.java index d281dbb7016..828b06b0bf2 100755 --- a/core/src/main/java/org/zstack/core/CoreGlobalProperty.java +++ b/core/src/main/java/org/zstack/core/CoreGlobalProperty.java @@ -79,9 +79,6 @@ public class CoreGlobalProperty { public static List CHRONY_SERVERS; @GlobalProperty(name="management.server.vip") public static String MN_VIP; - @GlobalProperty(name = "management.server.prefer.ipv6", defaultValue = "false") - @AvailableValues(value = {"true", "false"}) - public static boolean MANAGEMENT_SERVER_PREFER_IPV6; @GlobalProperty(name = "simulatorsOn", defaultValue = "false") public static boolean SIMULATORS_ON; @GlobalProperty(name = "startMode", defaultValue = "") diff --git a/core/src/main/java/org/zstack/core/Platform.java b/core/src/main/java/org/zstack/core/Platform.java index b78925704ff..2a4ba9631a7 100755 --- a/core/src/main/java/org/zstack/core/Platform.java +++ b/core/src/main/java/org/zstack/core/Platform.java @@ -83,7 +83,6 @@ public class Platform { private static MessageSource messageSource; private static String encryptionKey = EncryptRSA.generateKeyString("ZStack open source"); private static final String MANAGEMENT_SERVER_IP_PROPERTY = "management.server.ip"; - private static final String MANAGEMENT_SERVER_PREFER_IPV6_PROPERTY = "management.server.prefer.ipv6"; private static final String ZSTACK_MANAGEMENT_SERVER_IP_ENV = "ZSTACK_MANAGEMENT_SERVER_IP"; private static final String IPV4_ADDRESS_COMMAND = "ip -4 add"; private static final String IPV6_ADDRESS_COMMAND = "ip -6 addr"; @@ -994,7 +993,7 @@ private static String getManagementServerIpInternal() { for (NetworkInterface iface : Collections.list(nets)) { String name = iface.getName(); if (defaultLine.contains(name)) { - ip = selectManagementServerIp(Collections.list(iface.getInetAddresses()), isManagementServerPreferIpv6()); + ip = selectManagementServerIp(Collections.list(iface.getInetAddresses())); } } } catch (SocketException e) { @@ -1070,7 +1069,74 @@ public static List getManagementServerIps() { return new ArrayList<>(ips); } - public static String selectManagementServerIp(Collection addresses, boolean preferIpv6) { + public static List getManagementServerIpsWithLocalFallback() { + LinkedHashSet ips = new LinkedHashSet<>(getManagementServerIps()); + ips.addAll(getLocalNonLoopbackIps()); + ips.remove(null); + return new ArrayList<>(ips); + } + + private static List getLocalNonLoopbackIps() { + List ips = new ArrayList<>(); + try { + Enumeration nets = NetworkInterface.getNetworkInterfaces(); + for (NetworkInterface iface : Collections.list(nets)) { + if (!iface.isUp()) { + continue; + } + for (InetAddress address : Collections.list(iface.getInetAddresses())) { + if (address.isLoopbackAddress() || address.isLinkLocalAddress()) { + continue; + } + ips.add(normalizeManagementIp(address.getHostAddress())); + } + } + } catch (SocketException e) { + logger.warn("failed to list local non-loopback IPs", e); + } + return ips; + } + + public static String getRouteSourceIp(String remoteIp) { + if (StringUtils.isBlank(remoteIp)) { + return null; + } + + remoteIp = normalizeManagementIp(remoteIp); + String family; + if (IPv6NetworkUtils.isIpv6Address(remoteIp)) { + family = "-6"; + } else if (NetworkUtils.isIpv4Address(remoteIp)) { + family = "-4"; + } else { + return null; + } + + Linux.ShellResult ret = Linux.shell(String.format("/sbin/ip %s route get %s", family, remoteIp)); + if (ret.getExitCode() != 0) { + logger.warn(String.format("failed to get route source IP for remote[%s], stdout[%s], stderr[%s]", + remoteIp, ret.getStdout(), ret.getStderr())); + return null; + } + + String[] tokens = ret.getStdout().trim().split("\\s+"); + for (int i = 0; i < tokens.length - 1; i++) { + if (!"src".equals(tokens[i])) { + continue; + } + String sourceIp = normalizeManagementIp(tokens[i + 1]); + if (IPv6NetworkUtils.isIpv6Address(remoteIp) && IPv6NetworkUtils.isIpv6Address(sourceIp)) { + return sourceIp; + } + if (NetworkUtils.isIpv4Address(remoteIp) && NetworkUtils.isIpv4Address(sourceIp)) { + return sourceIp; + } + } + + return null; + } + + public static String selectManagementServerIp(Collection addresses) { String ipv4 = null; String ipv6 = null; @@ -1087,22 +1153,9 @@ public static String selectManagementServerIp(Collection addresses, } } - if (preferIpv6 && ipv6 != null) { - return ipv6; - } - return ipv4 != null ? ipv4 : ipv6; } - public static boolean isManagementServerPreferIpv6() { - String propertyValue = System.getProperty(MANAGEMENT_SERVER_PREFER_IPV6_PROPERTY); - if (propertyValue != null) { - return Boolean.parseBoolean(propertyValue); - } - - return CoreGlobalProperty.MANAGEMENT_SERVER_PREFER_IPV6; - } - public static String formatJGroupsInitialHosts(String nodeIp, String peerIp, int port) { return String.format(JGROUPS_INITIAL_HOST_FORMAT, IPv6NetworkUtils.formatHostForUrl(nodeIp), port, diff --git a/core/src/main/java/org/zstack/core/ansible/CallBackNetworkChecker.java b/core/src/main/java/org/zstack/core/ansible/CallBackNetworkChecker.java index 031f624218c..5cf5665c06b 100644 --- a/core/src/main/java/org/zstack/core/ansible/CallBackNetworkChecker.java +++ b/core/src/main/java/org/zstack/core/ansible/CallBackNetworkChecker.java @@ -10,6 +10,7 @@ import org.zstack.utils.ssh.SshCmdHelper; import org.zstack.utils.ssh.SshException; import org.zstack.utils.ssh.SshResult; +import org.zstack.utils.network.IPv6NetworkUtils; import static org.zstack.core.Platform.operr; import static org.zstack.utils.StringDSL.ln; @@ -30,8 +31,11 @@ public class CallBackNetworkChecker implements AnsibleChecker { private String callbackIp = Platform.getManagementServerIp(); private int callBackPort = Platform.getManagementNodeServicePort(); - private static StringDSL.StringWrapper script = ln( - "cat /dev/null | nc {2} {1} || echo {0} | sudo -S nmap -sS -P0 -n -p {1} {2} 2>/dev/null | grep \"1 host up\"" + private static final String EMPTY_COMMAND_OPTION = ""; + private static final String IPV6_COMMAND_OPTION = "-6 "; + private static final String HOST_UP_PATTERN = "1 host up"; + private static final StringDSL.StringWrapper CALLBACK_CHECK_SCRIPT = ln( + "cat /dev/null | nc {3}{2} {1} || echo {0} | sudo -S nmap {4}-sS -P0 -n -p {1} {2} 2>/dev/null | grep \"{5}\"" ); @Override @@ -49,7 +53,7 @@ public void deleteDestFile() { * if failed, use nmap to try again. */ private ErrorCode useNcatAndNmapToTestConnection(Ssh ssh) { - String srcScript = script.format(SshCmdHelper.shellQuote(password), callBackPort, callbackIp); + String srcScript = buildCallbackCheckScript(SshCmdHelper.shellQuote(password), callBackPort, callbackIp); ssh.sudoCommand(srcScript); SshResult ret = ssh.run(); @@ -58,6 +62,13 @@ private ErrorCode useNcatAndNmapToTestConnection(Ssh ssh) { return null; } + public static String buildCallbackCheckScript(String password, int port, String callbackIp) { + String callbackHost = IPv6NetworkUtils.stripHostUrlBrackets(callbackIp); + String ipVersionOption = IPv6NetworkUtils.isIpv6Address(callbackHost) ? IPV6_COMMAND_OPTION : EMPTY_COMMAND_OPTION; + + return CALLBACK_CHECK_SCRIPT.format(password, port, callbackHost, ipVersionOption, ipVersionOption, HOST_UP_PATTERN); + } + @Override public ErrorCode stopAnsible() { if (CoreGlobalProperty.UNIT_TEST_ON || !AnsibleGlobalConfig.CHECK_MANAGEMENT_CALLBACK.value(Boolean.class)) { diff --git a/network/src/main/java/org/zstack/network/l3/L3NetworkApiInterceptor.java b/network/src/main/java/org/zstack/network/l3/L3NetworkApiInterceptor.java index ac88e09dbf8..f5717441a68 100755 --- a/network/src/main/java/org/zstack/network/l3/L3NetworkApiInterceptor.java +++ b/network/src/main/java/org/zstack/network/l3/L3NetworkApiInterceptor.java @@ -445,10 +445,6 @@ private void validateIpv6Range(IpRangeInventory ipr) { L3NetworkVO l3Vo = Q.New(L3NetworkVO.class).eq(L3NetworkVO_.uuid, ipr.getL3NetworkUuid()).find(); - if (l3Vo.getCategory().equals(L3NetworkCategory.System)) { - throw new ApiMessageInterceptionException(argerr(ORG_ZSTACK_NETWORK_L3_10034, "can not add ip range, because system network doesn't support ipv6 yet")); - } - List rangeVOS = Q.New(NormalIpRangeVO.class).eq(NormalIpRangeVO_.l3NetworkUuid, ipr.getL3NetworkUuid()).eq(NormalIpRangeVO_.ipVersion, IPv6Constants.IPv6).list(); if (rangeVOS != null && !rangeVOS.isEmpty()) { if (!rangeVOS.get(0).getAddressMode().equals(ipr.getAddressMode())) { diff --git a/plugin/applianceVm/src/main/java/org/zstack/appliancevm/ApplianceVmFacadeImpl.java b/plugin/applianceVm/src/main/java/org/zstack/appliancevm/ApplianceVmFacadeImpl.java index 99159d643bf..cf17c2df318 100755 --- a/plugin/applianceVm/src/main/java/org/zstack/appliancevm/ApplianceVmFacadeImpl.java +++ b/plugin/applianceVm/src/main/java/org/zstack/appliancevm/ApplianceVmFacadeImpl.java @@ -467,7 +467,7 @@ public Map prepareBootstrapInformation(VmInstanceSpec spec) { ret.put(ApplianceVmConstant.BootstrapParams.publicKey.toString(), publicKey); ret.put(BootstrapParams.uuid.toString(), spec.getVmInventory().getUuid()); putManagementNodeBootstrapParams(ret, - Platform.getManagementServerIps(), + Platform.getManagementServerIpsWithLocalFallback(), getVrManagementCidrs(mgmtNic), Platform.getManagementServerIp(), Platform.getManagementServerVip(), diff --git a/plugin/ceph/src/main/java/org/zstack/storage/ceph/primary/CephPrimaryStorageBase.java b/plugin/ceph/src/main/java/org/zstack/storage/ceph/primary/CephPrimaryStorageBase.java index 285e970de68..c5da3bc36b4 100755 --- a/plugin/ceph/src/main/java/org/zstack/storage/ceph/primary/CephPrimaryStorageBase.java +++ b/plugin/ceph/src/main/java/org/zstack/storage/ceph/primary/CephPrimaryStorageBase.java @@ -5869,7 +5869,7 @@ public MonMigrateIpInfo(String psUuid) { final String extraIps = CephMonSystemTags.EXTRA_IPS .getTokenByResourceUuid(mon.getUuid(), CephMonSystemTags.EXTRA_IPS_TOKEN); Optional.ofNullable(extraIps).ifPresent(it -> ips.addAll(Arrays.asList(it.split(",")))); - List cidrIps = NetworkUtils.filterIpv4sInCidr(ips, migrateCidr); + List cidrIps = NetworkUtils.filterIpsInCidr(ips, migrateCidr); if (!cidrIps.isEmpty()) { monMigrateIpMap.put(mon.getUuid(), cidrIps.get(0)); } diff --git a/plugin/kvm/src/main/java/org/zstack/kvm/KVMHost.java b/plugin/kvm/src/main/java/org/zstack/kvm/KVMHost.java index eae09b34930..36e53287727 100755 --- a/plugin/kvm/src/main/java/org/zstack/kvm/KVMHost.java +++ b/plugin/kvm/src/main/java/org/zstack/kvm/KVMHost.java @@ -2726,10 +2726,10 @@ public void done() { ); } - private String buildUrl(String path) { + public static String buildAgentUrl(String host, String path) { UriComponentsBuilder ub = UriComponentsBuilder.newInstance(); ub.scheme(KVMGlobalProperty.AGENT_URL_SCHEME); - ub.host(KVMHostUtils.formatHostForUrl(self.getManagementIp())); + ub.host(KVMHostUtils.formatHostForUrl(host)); ub.port(KVMGlobalProperty.AGENT_PORT); if (!"".equals(KVMGlobalProperty.AGENT_URL_ROOT_PATH)) { ub.path(KVMGlobalProperty.AGENT_URL_ROOT_PATH); @@ -2738,6 +2738,10 @@ private String buildUrl(String path) { return ub.build().toUriString(); } + private String buildUrl(String path) { + return buildAgentUrl(self.getManagementIp(), path); + } + private void executeAsyncHttpCall(final KVMHostAsyncHttpCallMsg msg, final NoErrorCompletion completion) { if (!msg.isNoStatusCheck()) { checkStatus(); @@ -3154,10 +3158,7 @@ public void run(final FlowTrigger trigger, Map data) { CleanVmFirmwareFlashCmd cmd = new CleanVmFirmwareFlashCmd(); cmd.vmUuid = vmUuid; - UriComponentsBuilder ub = UriComponentsBuilder.fromHttpUrl(baseUrl); - ub.host(dstHostMnIp); - ub.path(KVMConstant.CLEAN_FIRMWARE_FLASH); - String url = ub.build().toString(); + String url = buildAgentUrl(dstHostMnIp, KVMConstant.CLEAN_FIRMWARE_FLASH); new Http<>(url, cmd, AgentResponse.class).call(dstHostUuid, new ReturnValueCompletion(trigger) { @Override public void success(AgentResponse ret) { @@ -3227,9 +3228,9 @@ protected void scripts() { cmd.setDisks(diskMigrationMap); } - UriComponentsBuilder ub = UriComponentsBuilder.fromHttpUrl(migrateVmPath); - ub.host(migrateFromDestination ? dstHostMnIp : srcHostMnIp); - String migrateUrl = ub.build().toString(); + String migrateUrl = buildAgentUrl( + migrateFromDestination ? dstHostMnIp : srcHostMnIp, + KVMConstant.KVM_MIGRATE_VM_PATH); new Http<>(migrateUrl, cmd, MigrateVmResponse.class).call(migrateFromDestination ? dstHostUuid : srcHostUuid, new ReturnValueCompletion(trigger) { @Override public void success(MigrateVmResponse ret) { @@ -3268,10 +3269,7 @@ public void run(final FlowTrigger trigger, Map data) { cmd.vmUuid = vmUuid; cmd.hostManagementIp = dstHostMnIp; - UriComponentsBuilder ub = UriComponentsBuilder.fromHttpUrl(baseUrl); - ub.host(dstHostMnIp); - ub.path(KVMConstant.KVM_HARDEN_CONSOLE_PATH); - String url = ub.build().toString(); + String url = buildAgentUrl(dstHostMnIp, KVMConstant.KVM_HARDEN_CONSOLE_PATH); new Http<>(url, cmd, AgentResponse.class).call(dstHostUuid, new ReturnValueCompletion(trigger) { @Override public void success(AgentResponse ret) { @@ -3304,10 +3302,7 @@ public void run(final FlowTrigger trigger, Map data) { cmd.vmUuid = vmUuid; cmd.hostManagementIp = srcHostMnIp; - UriComponentsBuilder ub = UriComponentsBuilder.fromHttpUrl(baseUrl); - ub.host(srcHostMnIp); - ub.path(KVMConstant.KVM_DELETE_CONSOLE_FIREWALL_PATH); - String url = ub.build().toString(); + String url = buildAgentUrl(srcHostMnIp, KVMConstant.KVM_DELETE_CONSOLE_FIREWALL_PATH); new Http<>(url, cmd, AgentResponse.class).call(new ReturnValueCompletion(trigger) { @Override public void success(AgentResponse ret) { diff --git a/plugin/virtualRouterProvider/src/main/java/org/zstack/network/service/virtualrouter/VirtualRouterApiInterceptor.java b/plugin/virtualRouterProvider/src/main/java/org/zstack/network/service/virtualrouter/VirtualRouterApiInterceptor.java index ea2e5ba66bb..2713a4699cc 100755 --- a/plugin/virtualRouterProvider/src/main/java/org/zstack/network/service/virtualrouter/VirtualRouterApiInterceptor.java +++ b/plugin/virtualRouterProvider/src/main/java/org/zstack/network/service/virtualrouter/VirtualRouterApiInterceptor.java @@ -212,11 +212,6 @@ private void validate(APICreateVirtualRouterOfferingMsg msg) { throw new ApiMessageInterceptionException(argerr(ORG_ZSTACK_NETWORK_SERVICE_VIRTUALROUTER_10030, "management network[uuid:%s] is not in the same zone[uuid:%s] this offering is going to create", msg.getManagementNetworkUuid(), msg.getZoneUuid())); } - /* mgt network does not support ipv6 yet, TODO, will be implemented soon */ - if (mgtL3.getIpVersions().contains(IPv6Constants.IPv6) && !mgtL3.getIpVersions().contains(IPv6Constants.IPv4)) { - throw new ApiMessageInterceptionException(argerr(ORG_ZSTACK_NETWORK_SERVICE_VIRTUALROUTER_10031, "can not create virtual router offering, because management network doesn't support ipv6 yet")); - } - if (!CoreGlobalProperty.UNIT_TEST_ON) { checkIfManagementNetworkReachable(msg.getManagementNetworkUuid()); } diff --git a/plugin/virtualRouterProvider/src/main/java/org/zstack/network/service/virtualrouter/VirtualRouterManagerImpl.java b/plugin/virtualRouterProvider/src/main/java/org/zstack/network/service/virtualrouter/VirtualRouterManagerImpl.java index 3e435674ae7..b3a48429692 100755 --- a/plugin/virtualRouterProvider/src/main/java/org/zstack/network/service/virtualrouter/VirtualRouterManagerImpl.java +++ b/plugin/virtualRouterProvider/src/main/java/org/zstack/network/service/virtualrouter/VirtualRouterManagerImpl.java @@ -960,7 +960,7 @@ public String buildUrl(String mgmtNicIp, String subPath) { if (CoreGlobalProperty.UNIT_TEST_ON) { ub.host("localhost"); } else { - ub.host(mgmtNicIp); + ub.host(IPv6NetworkUtils.formatHostForUrl(mgmtNicIp)); } ub.port(VirtualRouterGlobalProperty.AGENT_PORT); @@ -978,6 +978,11 @@ public Map buildAgentCallbackUrlHeaders(String mgmtNicIp) { } private String selectManagementIpForAgent(String agentIp) { + String routeSourceIp = Platform.getRouteSourceIp(agentIp); + if (routeSourceIp != null) { + return routeSourceIp; + } + if (IPv6NetworkUtils.isIpv6Address(agentIp)) { return Platform.getManagementServerIps().stream() .filter(IPv6NetworkUtils::isIpv6Address) diff --git a/plugin/zbs/src/main/java/org/zstack/storage/zbs/MdsUri.java b/plugin/zbs/src/main/java/org/zstack/storage/zbs/MdsUri.java index d0c990d6826..0e46313d40a 100644 --- a/plugin/zbs/src/main/java/org/zstack/storage/zbs/MdsUri.java +++ b/plugin/zbs/src/main/java/org/zstack/storage/zbs/MdsUri.java @@ -5,6 +5,7 @@ import org.zstack.header.errorcode.ErrorCode; import org.zstack.header.errorcode.OperationFailureException; import org.zstack.header.exception.CloudRuntimeException; +import org.zstack.utils.network.IPv6NetworkUtils; import java.net.URI; import java.net.URISyntaxException; @@ -26,7 +27,7 @@ public class MdsUri { private String username; private String password; - private static final String MDS_URL_FORMAT = "sshUsername:sshPassword@hostname:[sshPort]/?[mdsPort=]"; + private static final String MDS_URL_FORMAT = "sshUsername:sshPassword@hostname[:sshPort]/?[mdsPort=], IPv6 hostname must be bracketed"; private static final Integer DEFAULT_MDS_PORT = 6666; private static final Integer DEFAULT_SSH_PORT = 22; @@ -80,7 +81,7 @@ public MdsUri(String url) { password = ssh[1]; URI uri = new URI(String.format("ssh://%s", rest)); - hostname = uri.getHost(); + hostname = IPv6NetworkUtils.stripHostUrlBrackets(uri.getHost()); if (hostname == null) { throw new OperationFailureException(operr(ORG_ZSTACK_STORAGE_ZBS_10004, "invalid mdsUrl[%s], hostname cannot be null. A valid mdsUrl is" + " in format of %s", url, MDS_URL_FORMAT) diff --git a/plugin/zbs/src/main/java/org/zstack/storage/zbs/ZbsAgentUrl.java b/plugin/zbs/src/main/java/org/zstack/storage/zbs/ZbsAgentUrl.java index ef42cef1193..34a98f8f8ae 100644 --- a/plugin/zbs/src/main/java/org/zstack/storage/zbs/ZbsAgentUrl.java +++ b/plugin/zbs/src/main/java/org/zstack/storage/zbs/ZbsAgentUrl.java @@ -1,21 +1,27 @@ package org.zstack.storage.zbs; -import org.springframework.web.util.UriComponentsBuilder; +import org.zstack.utils.network.IPv6NetworkUtils; /** * @author Xingwei Yu * @date 2024/3/27 17:39 */ public class ZbsAgentUrl { - public static String primaryStorageUrl(String ip, String path) { - UriComponentsBuilder ub = UriComponentsBuilder.newInstance(); - ub.scheme("http"); - ub.host(ip); - ub.port(ZbsGlobalProperty.PRIMARY_STORAGE_AGENT_PORT); - if (!"".equals(ZbsGlobalProperty.PRIMARY_STORAGE_AGENT_URL_ROOT_PATH)) { - ub.path(ZbsGlobalProperty.PRIMARY_STORAGE_AGENT_URL_ROOT_PATH); + private static void appendPath(StringBuilder sb, String path) { + if (path == null || path.isEmpty()) { + return; + } + + if (!path.startsWith("/")) { + sb.append("/"); } - ub.path(path); - return ub.build().toUriString(); + sb.append(path); + } + + public static String primaryStorageUrl(String ip, String path) { + StringBuilder sb = new StringBuilder(IPv6NetworkUtils.buildHttpUrl(ip, ZbsGlobalProperty.PRIMARY_STORAGE_AGENT_PORT)); + appendPath(sb, ZbsGlobalProperty.PRIMARY_STORAGE_AGENT_URL_ROOT_PATH); + appendPath(sb, path); + return sb.toString(); } } diff --git a/test/src/test/groovy/org/zstack/test/integration/console/ConsoleProxyCase.groovy b/test/src/test/groovy/org/zstack/test/integration/console/ConsoleProxyCase.groovy index 839cb50554a..da26544976d 100644 --- a/test/src/test/groovy/org/zstack/test/integration/console/ConsoleProxyCase.groovy +++ b/test/src/test/groovy/org/zstack/test/integration/console/ConsoleProxyCase.groovy @@ -2,6 +2,7 @@ package org.zstack.test.integration.console import org.springframework.http.HttpEntity import org.zstack.console.ConsoleGlobalConfig +import org.zstack.console.ConsoleManagerImpl import org.zstack.header.vm.VmInstanceConstant import org.zstack.core.CoreGlobalProperty import org.zstack.core.Platform @@ -16,6 +17,7 @@ import org.zstack.header.console.ConsoleProxyVO import org.zstack.header.console.ConsoleProxyVO_ import org.zstack.header.vm.KvmReportVmShutdownFromGuestEventMsg import org.zstack.sdk.ConsoleInventory +import org.zstack.sdk.ConsoleProxyAgentInventory import org.zstack.sdk.GarbageCollectorInventory import org.zstack.sdk.SessionInventory import org.zstack.sdk.VmInstanceInventory @@ -236,6 +238,32 @@ class ConsoleProxyCase extends SubCase { assert agent.consoleProxyOverriddenIp == "127.0.0.1" assert agent.consoleProxyPort == 4900 + String ipv6ConsoleProxyIp = "2001:db8::100" + updateConsoleProxyAgent { + uuid = agent.uuid + consoleProxyOverriddenIp = ipv6ConsoleProxyIp + consoleProxyPort = 4900 + } + + agent = dbf.reload(agent) + assert agent.consoleProxyOverriddenIp == ipv6ConsoleProxyIp + assert Platform.getGlobalProperties().get("consoleProxyOverriddenIp") == ipv6ConsoleProxyIp + assert CoreGlobalProperty.CONSOLE_PROXY_OVERRIDDEN_IP == ipv6ConsoleProxyIp + + List agents = queryConsoleProxyAgent { + conditions = ["uuid=${agent.uuid}".toString()] + } as List + assert agents[0].consoleProxyOverriddenIp == ipv6ConsoleProxyIp + def selectConsoleProxyHostname = ConsoleManagerImpl.class.getDeclaredMethod("selectConsoleProxyHostname", String.class, Boolean.TYPE, String.class) + selectConsoleProxyHostname.accessible = true + assert selectConsoleProxyHostname.invoke(null, ipv6ConsoleProxyIp, false, "127.0.0.1") == "[${ipv6ConsoleProxyIp}]" + + updateConsoleProxyAgent { + uuid = agent.uuid + consoleProxyOverriddenIp = "127.0.0.1" + consoleProxyPort = 4900 + } + // update console proxy agent by none admin account SessionInventory testAccountSession = logInByAccount { accountName = "test" diff --git a/test/src/test/groovy/org/zstack/test/integration/core/ManagementNetworkIpv6Case.groovy b/test/src/test/groovy/org/zstack/test/integration/core/ManagementNetworkIpv6Case.groovy index d8ecd0f741a..463a4599e84 100644 --- a/test/src/test/groovy/org/zstack/test/integration/core/ManagementNetworkIpv6Case.groovy +++ b/test/src/test/groovy/org/zstack/test/integration/core/ManagementNetworkIpv6Case.groovy @@ -2,8 +2,8 @@ package org.zstack.test.integration.core import org.zstack.appliancevm.ApplianceVmConstant import org.zstack.appliancevm.ApplianceVmFacadeImpl +import org.zstack.core.ansible.CallBackNetworkChecker import org.zstack.core.ansible.AnsibleRunner -import org.zstack.core.CoreGlobalProperty import org.zstack.core.Platform import org.zstack.core.agent.AgentManagerImpl import org.zstack.core.cloudbus.CloudBusImpl3 @@ -11,13 +11,17 @@ import org.zstack.core.rest.RESTFacadeImpl import org.zstack.console.ConsoleProxyBase import org.zstack.header.rest.RESTConstant import org.zstack.kvm.KVMConsoleHypervisorBackend +import org.zstack.kvm.KVMConstant import org.zstack.kvm.KVMHost +import org.zstack.kvm.KVMGlobalProperty import org.zstack.kvm.KvmHostIpmiPowerExecutor import org.zstack.network.l2.vxlan.vxlanNetworkPool.VxlanPoolApiInterceptor +import org.zstack.network.l2.vxlan.vxlanNetworkPool.VxlanSystemTags import org.zstack.storage.ceph.MonUri import org.zstack.storage.ceph.backup.CephBackupStorageMetaDataMaker import org.zstack.storage.primary.nfs.NfsApiParamChecker import org.zstack.testlib.SubCase +import org.zstack.utils.TagUtils import org.zstack.utils.URLBuilder import org.zstack.utils.ssh.SshShell import org.zstack.utils.network.IPv6Constants @@ -42,6 +46,10 @@ class ManagementNetworkIpv6Case extends SubCase { private static final String NFS_IPV6_URL = "[${IPV6}]:${NFS_EXPORT_PATH}" private static final String CEPH_IPV6_MON_URL = "root:password@[${IPV6}]:22/?monPort=6789" private static final String INVALID_VTEP_IP = "not-a-vtep-ip" + private static final String VXLAN_POOL_UUID = "235f904603a2416d83810ff1dd5850b8" + private static final String CLUSTER_UUID = "e9acb8d6a4b04eea89f14e91918deed7" + private static final String VXLAN_IPV4_CIDR = "192.168.100.0/24" + private static final String VXLAN_IPV6_CIDR = "fd00:172:24:249::/64" private static final String HOST_EXTRA_IPS = "10.0.0.10,${IPV6_2}" private static final String IPV4_ADDRESS_COMMAND_OUTPUT = """\ 2: eth0 @@ -74,8 +82,6 @@ class ManagementNetworkIpv6Case extends SubCase { @Override @Test void test() { - testPreferIpv6DefaultFalse() - testPreferIpv6SystemProperty() testSelectManagementServerIpDualStackPolicy() testSelectManagementServerIpSkipsLoopbackAndLinkLocal() testSelectApplianceVmManagementNodeIpByCidr() @@ -85,8 +91,11 @@ class ManagementNetworkIpv6Case extends SubCase { testConsoleVncUriIpv6() testConsoleProxyListenHostByProxyIpVersion() testCoreManagementUrlsIpv6() + testKvmAgentUrlsIpv6() testRestFacadeIpv6Urls() - testSshTargetUsesBracketedIpv6Host() + testSshTargetUsesRawIpv6Host() + testScpTargetUsesBracketedIpv6Host() + testCallbackCheckerUsesIpv6Options() testBuildHostPortIpv6() testBracketIpv6Idempotent() testNormalizeIpv6() @@ -102,39 +111,21 @@ class ManagementNetworkIpv6Case extends SubCase { testCephIpv6MonUrlParsing() testCephMetadataAgentUrlUsesBracketedIpv6Host() testVxlanVtepIpv6Validation() + testVxlanSystemTagMatchesIpv6Cidr() + testPatternedSystemTagParsesIpv6Token() testKvmExtraIpCidrSelection() testKvmIpmiAddressKeepsIpv6() testApplianceVmBootstrapParam() } - void testPreferIpv6DefaultFalse() { - assert !CoreGlobalProperty.MANAGEMENT_SERVER_PREFER_IPV6 - } - - void testPreferIpv6SystemProperty() { - String oldValue = System.getProperty("management.server.prefer.ipv6") - try { - System.setProperty("management.server.prefer.ipv6", "true") - assert Platform.isManagementServerPreferIpv6() - System.setProperty("management.server.prefer.ipv6", "false") - assert !Platform.isManagementServerPreferIpv6() - } finally { - if (oldValue == null) { - System.clearProperty("management.server.prefer.ipv6") - } else { - System.setProperty("management.server.prefer.ipv6", oldValue) - } - } - } - void testSelectManagementServerIpDualStackPolicy() { def ipv4 = InetAddress.getByName(IPV4) def ipv6 = InetAddress.getByName(IPV6) - assert Platform.selectManagementServerIp([ipv6, ipv4], false) == IPV4 - assert Platform.selectManagementServerIp([ipv4, ipv6], true) == IPV6 - assert Platform.selectManagementServerIp([ipv6], false) == IPV6 - assert Platform.selectManagementServerIp([ipv4], true) == IPV4 + assert Platform.selectManagementServerIp([ipv6, ipv4]) == IPV4 + assert Platform.selectManagementServerIp([ipv4, ipv6]) == IPV4 + assert Platform.selectManagementServerIp([ipv6]) == IPV6 + assert Platform.selectManagementServerIp([ipv4]) == IPV4 } void testSelectManagementServerIpSkipsLoopbackAndLinkLocal() { @@ -144,9 +135,9 @@ class ManagementNetworkIpv6Case extends SubCase { def loopbackIpv6 = InetAddress.getByName(LOOPBACK_IPV6) def linkLocalIpv6 = InetAddress.getByName(LINK_LOCAL_IPV6) - assert Platform.selectManagementServerIp([loopbackIpv4, ipv4], false) == IPV4 - assert Platform.selectManagementServerIp([loopbackIpv6, linkLocalIpv6, ipv6], true) == IPV6 - assert Platform.selectManagementServerIp([loopbackIpv4, loopbackIpv6, linkLocalIpv6], true) == null + assert Platform.selectManagementServerIp([loopbackIpv4, ipv4]) == IPV4 + assert Platform.selectManagementServerIp([loopbackIpv6, linkLocalIpv6, ipv6]) == IPV6 + assert Platform.selectManagementServerIp([loopbackIpv4, loopbackIpv6, linkLocalIpv6]) == null } void testSelectApplianceVmManagementNodeIpByCidr() { @@ -198,6 +189,15 @@ class ManagementNetworkIpv6Case extends SubCase { assert AnsibleRunner.buildPipUrl(IPV6, REST_PORT) == "http://[2001:db8::1]:8080/zstack/static/pypi/simple" } + void testKvmAgentUrlsIpv6() { + assert KVMHost.buildAgentUrl(IPV6, KVMConstant.KVM_MIGRATE_VM_PATH) == + "http://[2001:db8::1]:${KVMGlobalProperty.AGENT_PORT}/vm/migrate" + assert KVMHost.buildAgentUrl(IPV6, KVMConstant.CLEAN_FIRMWARE_FLASH) == + "http://[2001:db8::1]:${KVMGlobalProperty.AGENT_PORT}/clean/firmware/flash" + assert KVMHost.buildAgentUrl(IPV4, KVMConstant.KVM_MIGRATE_VM_PATH) == + "http://192.168.1.10:${KVMGlobalProperty.AGENT_PORT}/vm/migrate" + } + void testRestFacadeIpv6Urls() { assert RESTFacadeImpl.buildBaseUrl(IPV6, REST_PORT, null) == "http://[2001:db8::1]:8080" assert RESTFacadeImpl.buildBaseUrl(IPV6, REST_PORT, "zstack") == "http://[2001:db8::1]:8080/zstack" @@ -207,12 +207,29 @@ class ManagementNetworkIpv6Case extends SubCase { "http://[2001:db8::1]:8080/zstack${RESTConstant.COMMAND_CHANNEL_PATH}" } - void testSshTargetUsesBracketedIpv6Host() { + void testSshTargetUsesRawIpv6Host() { assert SshShell.formatSshTarget("root", IPV4) == "root@192.168.1.10" - assert SshShell.formatSshTarget("root", IPV6) == "root@[2001:db8::1]" + assert SshShell.formatSshTarget("root", IPV6) == "root@2001:db8::1" + assert SshShell.formatSshTarget("root", "[2001:db8::1]") == "root@2001:db8::1" assert SshShell.formatSshTarget("root", "host-01.example.com") == "root@host-01.example.com" } + void testScpTargetUsesBracketedIpv6Host() { + assert SshShell.formatScpTarget("root", IPV4) == "root@192.168.1.10" + assert SshShell.formatScpTarget("root", IPV6) == "root@[2001:db8::1]" + assert SshShell.formatScpTarget("root", "host-01.example.com") == "root@host-01.example.com" + } + + void testCallbackCheckerUsesIpv6Options() { + String ipv4Script = CallBackNetworkChecker.buildCallbackCheckScript("password", REST_PORT, IPV4) + assert ipv4Script.contains("nc ${IPV4} ${REST_PORT}") + assert ipv4Script.contains("nmap -sS -P0 -n -p ${REST_PORT} ${IPV4}") + + String ipv6Script = CallBackNetworkChecker.buildCallbackCheckScript("password", REST_PORT, IPV6) + assert ipv6Script.contains("nc -6 ${IPV6} ${REST_PORT}") + assert ipv6Script.contains("nmap -6 -sS -P0 -n -p ${REST_PORT} ${IPV6}") + } + void testBuildHostPortIpv6() { assert IPv6NetworkUtils.formatHostPort(IPV6, REST_PORT) == "[2001:db8::1]:8080" } @@ -220,6 +237,7 @@ class ManagementNetworkIpv6Case extends SubCase { void testBracketIpv6Idempotent() { assert IPv6NetworkUtils.formatHostForUrl(IPV6) == "[2001:db8::1]" assert IPv6NetworkUtils.formatHostForUrl("[2001:db8::1]") == "[2001:db8::1]" + assert IPv6NetworkUtils.stripHostUrlBrackets("[2001:db8::1]") == IPV6 } void testNormalizeIpv6() { @@ -247,6 +265,7 @@ class ManagementNetworkIpv6Case extends SubCase { void testIpv6NetworkCidr() { assert NetworkUtils.getNetworkAddressFromCidr("2001:db8::1/64") == "2001:db8::/64" + assert NetworkUtils.fmtCidr("2001:db8::1/64") == "2001:db8::/64" } void testIpInCidrDualStack() { @@ -254,6 +273,8 @@ class ManagementNetworkIpv6Case extends SubCase { assert NetworkUtils.isIpInCidr(IPV6, "2001:db8::/64") assert !NetworkUtils.isIpInCidr(IPV4, "2001:db8::/64") assert !NetworkUtils.isIpInCidr(IPV6, "192.168.1.0/24") + assert NetworkUtils.filterIpsInCidr([IPV4, IPV6], "192.168.1.0/24") == [IPV4] + assert NetworkUtils.filterIpsInCidr([IPV4, IPV6], "2001:db8::/64") == [IPV6] } void testManagementCidrCommandOutputParsing() { @@ -338,6 +359,39 @@ class ManagementNetworkIpv6Case extends SubCase { assert VxlanPoolApiInterceptor.normalizeVtepIp(" ${IPV6_FULL}\n") == IPV6 } + void testVxlanSystemTagMatchesIpv6Cidr() { + String ipv4Tag = VxlanSystemTags.VXLAN_POOL_CLUSTER_VTEP_CIDR.instantiateTag([ + (VxlanSystemTags.VXLAN_POOL_UUID_TOKEN): VXLAN_POOL_UUID, + (VxlanSystemTags.CLUSTER_UUID_TOKEN) : CLUSTER_UUID, + (VxlanSystemTags.VTEP_CIDR_TOKEN) : "{${VXLAN_IPV4_CIDR}}" + ]) + String ipv6Tag = VxlanSystemTags.VXLAN_POOL_CLUSTER_VTEP_CIDR.instantiateTag([ + (VxlanSystemTags.VXLAN_POOL_UUID_TOKEN): VXLAN_POOL_UUID, + (VxlanSystemTags.CLUSTER_UUID_TOKEN) : CLUSTER_UUID, + (VxlanSystemTags.VTEP_CIDR_TOKEN) : "{${VXLAN_IPV6_CIDR}}" + ]) + + assert VxlanSystemTags.VXLAN_POOL_CLUSTER_VTEP_CIDR.isMatch(ipv4Tag) + assert VxlanSystemTags.VXLAN_POOL_CLUSTER_VTEP_CIDR.isMatch(ipv6Tag) + + def tokens = VxlanSystemTags.VXLAN_POOL_CLUSTER_VTEP_CIDR.getTokensByTag(ipv6Tag) + assert tokens[VxlanSystemTags.VXLAN_POOL_UUID_TOKEN] == VXLAN_POOL_UUID + assert tokens[VxlanSystemTags.CLUSTER_UUID_TOKEN] == CLUSTER_UUID + assert tokens[VxlanSystemTags.VTEP_CIDR_TOKEN] == "{${VXLAN_IPV6_CIDR}}" + } + + void testPatternedSystemTagParsesIpv6Token() { + String extraIpsFormat = "extraips::{extraips}" + String extraIpsTag = "extraips::10.0.0.10,${IPV6_2}" + assert TagUtils.isMatch(extraIpsFormat, extraIpsTag) + assert TagUtils.parseIfMatch(extraIpsFormat, extraIpsTag)["extraips"] == "10.0.0.10,${IPV6_2}" + + String migrateCidrFormat = "cluster::migrate::network::cidr::{migrateCidr}" + String migrateCidrTag = "cluster::migrate::network::cidr::${VXLAN_IPV6_CIDR}" + assert TagUtils.isMatch(migrateCidrFormat, migrateCidrTag) + assert TagUtils.parseIfMatch(migrateCidrFormat, migrateCidrTag)["migrateCidr"] == VXLAN_IPV6_CIDR + } + void testKvmExtraIpCidrSelection() { assert KVMHost.selectIpInCidr(HOST_EXTRA_IPS, "10.0.0.0/24") == "10.0.0.10" assert KVMHost.selectIpInCidr(HOST_EXTRA_IPS, "2001:db8::/64") == IPV6_2 diff --git a/test/src/test/groovy/org/zstack/test/integration/storage/primary/addon/zbs/ZbsPrimaryStorageCase.groovy b/test/src/test/groovy/org/zstack/test/integration/storage/primary/addon/zbs/ZbsPrimaryStorageCase.groovy index e5bfba095e9..5be5ad24392 100644 --- a/test/src/test/groovy/org/zstack/test/integration/storage/primary/addon/zbs/ZbsPrimaryStorageCase.groovy +++ b/test/src/test/groovy/org/zstack/test/integration/storage/primary/addon/zbs/ZbsPrimaryStorageCase.groovy @@ -26,7 +26,9 @@ import org.zstack.header.storage.primary.PrimaryStorageHostStatus import org.zstack.storage.volume.VolumeGlobalConfig import org.zstack.storage.zbs.AddonInfo import org.zstack.storage.zbs.Config +import org.zstack.storage.zbs.ZbsAgentUrl import org.zstack.storage.zbs.ZbsConstants +import org.zstack.storage.zbs.ZbsGlobalProperty import org.zstack.storage.zbs.ZbsPrimaryStorageMdsBase import org.zstack.storage.zbs.ZbsStorageController import org.zstack.test.integration.storage.StorageTest @@ -184,6 +186,7 @@ class ZbsPrimaryStorageCase extends SubCase { testAddExternalPrimaryStorageWithMalformedJsonRejectedByInterceptor() testDataVolumeNegativeScenario() testDecodeMdsUriWithSpecialPassword() + testZbsMdsUriAndAgentUrlSupportIpv6() testMdsReconnectAfterMaximumPingFailures() } } @@ -816,6 +819,36 @@ class ZbsPrimaryStorageCase extends SubCase { assert uri.password == specialPassword } + void testZbsMdsUriAndAgentUrlSupportIpv6() { + def ipv6Uri = new MdsUri("root:password@[2001:db8::10]:2222/?mdsPort=7777") + assert ipv6Uri.hostname == "2001:db8::10" + assert ipv6Uri.sshPort == 2222 + assert ipv6Uri.mdsPort == 7777 + assert ZbsAgentUrl.primaryStorageUrl(ipv6Uri.hostname, ZbsPrimaryStorageMdsBase.PING_PATH) == + "http://[2001:db8::10]:${ZbsGlobalProperty.PRIMARY_STORAGE_AGENT_PORT}${ZbsPrimaryStorageMdsBase.PING_PATH}" + + def ipv4Uri = new MdsUri("root:password@172.24.249.182:2222/?mdsPort=7777") + assert ipv4Uri.hostname == "172.24.249.182" + assert ZbsAgentUrl.primaryStorageUrl(ipv4Uri.hostname, ZbsPrimaryStorageMdsBase.PING_PATH) == + "http://172.24.249.182:${ZbsGlobalProperty.PRIMARY_STORAGE_AGENT_PORT}${ZbsPrimaryStorageMdsBase.PING_PATH}" + + def endpoints = [ + "http://172.24.249.182:7763${ZbsPrimaryStorageMdsBase.PING_PATH}", + ZbsAgentUrl.primaryStorageUrl(ipv6Uri.hostname, ZbsPrimaryStorageMdsBase.PING_PATH) + ] + String ipv6PingUrl = "http://[2001:db8::10]:${ZbsGlobalProperty.PRIMARY_STORAGE_AGENT_PORT}${ZbsPrimaryStorageMdsBase.PING_PATH}" + assert endpoints.contains(ipv6PingUrl) + + String oldRootPath = ZbsGlobalProperty.PRIMARY_STORAGE_AGENT_URL_ROOT_PATH + try { + ZbsGlobalProperty.PRIMARY_STORAGE_AGENT_URL_ROOT_PATH = "zstack" + assert ZbsAgentUrl.primaryStorageUrl(ipv6Uri.hostname, ZbsPrimaryStorageMdsBase.PING_PATH) == + "http://[2001:db8::10]:${ZbsGlobalProperty.PRIMARY_STORAGE_AGENT_PORT}/zstack${ZbsPrimaryStorageMdsBase.PING_PATH}" + } finally { + ZbsGlobalProperty.PRIMARY_STORAGE_AGENT_URL_ROOT_PATH = oldRootPath + } + } + void testMdsReconnectAfterMaximumPingFailures() { env.cleanSimulatorAndMessageHandlers() Integer originalPingInterval = PrimaryStorageGlobalConfig.PING_INTERVAL.value().toInteger() diff --git a/utils/src/main/java/org/zstack/utils/TagUtils.java b/utils/src/main/java/org/zstack/utils/TagUtils.java index 823a7af6dc2..33e41aab68f 100755 --- a/utils/src/main/java/org/zstack/utils/TagUtils.java +++ b/utils/src/main/java/org/zstack/utils/TagUtils.java @@ -8,14 +8,20 @@ /** */ public class TagUtils { - public static Map parse(String fmt, String tag) { - List origins = new ArrayList(); - Collections.addAll(origins, tag.split("::")); + private static final String TAG_DELIMITER = "::"; + private static final char TOKEN_START = '{'; + private static final char TOKEN_END = '}'; + public static Map parse(String fmt, String tag) { List t = new ArrayList(); - Collections.addAll(t, fmt.split("::")); + t.addAll(splitTagFields(fmt)); + List origins = splitTagFieldsByFormat(t, tag); Map ret = new HashMap(); + if (origins == null) { + return ret; + } + for (int i=0;i origins = new ArrayList(); - Collections.addAll(origins, tag.split("::")); - List t = new ArrayList(); - Collections.addAll(t, fmt.split("::")); + t.addAll(splitTagFields(fmt)); - if (fmt.indexOf("::") == -1) { + if (fmt.indexOf(TAG_DELIMITER) == -1) { return fmt.equals(tag); } - if (origins.size() != t.size()) { + List origins = splitTagFieldsByFormat(t, tag); + if (origins == null || origins.size() != t.size()) { return false; } @@ -66,6 +70,99 @@ public static boolean isMatch(String fmt, String tag) { return true; } + private static List splitTagFieldsByFormat(List fmtFields, String tag) { + List fields = new ArrayList<>(); + int offset = 0; + + for (int i = 0; i < fmtFields.size(); i++) { + String fmtField = fmtFields.get(i); + boolean lastField = i == fmtFields.size() - 1; + if (isTokenField(fmtField)) { + if (lastField) { + fields.add(tag.substring(offset)); + offset = tag.length(); + continue; + } + + int end = indexOfDelimiterOutsideToken(tag, offset); + if (end < 0) { + return null; + } + fields.add(tag.substring(offset, end)); + offset = end + TAG_DELIMITER.length(); + continue; + } + + if (!tag.startsWith(fmtField, offset)) { + return null; + } + + fields.add(fmtField); + offset += fmtField.length(); + if (!lastField) { + if (!tag.startsWith(TAG_DELIMITER, offset)) { + return null; + } + offset += TAG_DELIMITER.length(); + } + } + + return offset == tag.length() ? fields : null; + } + + private static boolean isTokenField(String field) { + return field.startsWith(String.valueOf(TOKEN_START)) && field.endsWith(String.valueOf(TOKEN_END)); + } + + private static List splitTagFields(String tag) { + List fields = new ArrayList<>(); + StringBuilder field = new StringBuilder(); + int braceDepth = 0; + + for (int i = 0; i < tag.length(); i++) { + char current = tag.charAt(i); + if (current == TOKEN_START) { + braceDepth++; + } else if (current == TOKEN_END && braceDepth > 0) { + braceDepth--; + } + + if (braceDepth == 0 && tag.startsWith(TAG_DELIMITER, i)) { + fields.add(field.toString()); + field.setLength(0); + i += TAG_DELIMITER.length() - 1; + continue; + } + + field.append(current); + } + + fields.add(field.toString()); + while (!fields.isEmpty() && fields.get(fields.size() - 1).isEmpty()) { + fields.remove(fields.size() - 1); + } + + return fields; + } + + private static int indexOfDelimiterOutsideToken(String tag, int offset) { + int braceDepth = 0; + for (int i = offset; i < tag.length(); i++) { + char current = tag.charAt(i); + if (current == TOKEN_START) { + braceDepth++; + } else if (current == TOKEN_END && braceDepth > 0) { + braceDepth--; + } + + if (braceDepth == 0 && tag.startsWith(TAG_DELIMITER, i)) { + return i; + } + } + + return -1; + } + public static Map parseIfMatch(String fmt, String tag) { if (!isMatch(fmt, tag)) { return null; diff --git a/utils/src/main/java/org/zstack/utils/clouderrorcode/CloudOperationsErrorCode.java b/utils/src/main/java/org/zstack/utils/clouderrorcode/CloudOperationsErrorCode.java index 3a272354846..98fb901e470 100644 --- a/utils/src/main/java/org/zstack/utils/clouderrorcode/CloudOperationsErrorCode.java +++ b/utils/src/main/java/org/zstack/utils/clouderrorcode/CloudOperationsErrorCode.java @@ -13097,6 +13097,10 @@ public class CloudOperationsErrorCode { public static final String ORG_ZSTACK_VPC_HA_10020 = "ORG_ZSTACK_VPC_HA_10020"; + public static final String ORG_ZSTACK_VPC_HA_10021 = "ORG_ZSTACK_VPC_HA_10021"; + + public static final String ORG_ZSTACK_VPC_HA_10022 = "ORG_ZSTACK_VPC_HA_10022"; + public static final String ORG_ZSTACK_SSO_CAS_FILTER_10000 = "ORG_ZSTACK_SSO_CAS_FILTER_10000"; public static final String ORG_ZSTACK_SSO_CAS_FILTER_10001 = "ORG_ZSTACK_SSO_CAS_FILTER_10001"; diff --git a/utils/src/main/java/org/zstack/utils/network/IPv6NetworkUtils.java b/utils/src/main/java/org/zstack/utils/network/IPv6NetworkUtils.java index 5acc04ab0cb..355e43d3fe6 100644 --- a/utils/src/main/java/org/zstack/utils/network/IPv6NetworkUtils.java +++ b/utils/src/main/java/org/zstack/utils/network/IPv6NetworkUtils.java @@ -530,6 +530,18 @@ public static String formatHostForUrl(String host) { return isIpv6Address(host) ? String.format(URL_IPV6_HOST_FORMAT, host) : host; } + public static String stripHostUrlBrackets(String host) { + if (host == null) { + return null; + } + + if (host.startsWith(IPV6_BRACKET_PREFIX) && host.endsWith(IPV6_BRACKET_SUFFIX)) { + return host.substring(IPV6_BRACKET_PREFIX.length(), host.length() - IPV6_BRACKET_SUFFIX.length()); + } + + return host; + } + public static String buildHttpUrl(String host, int port) { return String.format(HTTP_URL_FORMAT, formatHostForUrl(host), port); } diff --git a/utils/src/main/java/org/zstack/utils/network/NetworkUtils.java b/utils/src/main/java/org/zstack/utils/network/NetworkUtils.java index 0bc3daef0c2..4cb6be81196 100755 --- a/utils/src/main/java/org/zstack/utils/network/NetworkUtils.java +++ b/utils/src/main/java/org/zstack/utils/network/NetworkUtils.java @@ -589,6 +589,19 @@ public static List filterIpv4sInCidr(List ipv4s, String cidr){ return results; } + public static List filterIpsInCidr(List ips, String cidr){ + DebugUtils.Assert(isCidr(cidr), String.format("%s is not a cidr", cidr)); + List results = new ArrayList<>(); + + for (String ip : ips) { + validateIp(ip); + if (isIpInCidr(ip, cidr)) { + results.add(ip); + } + } + return results; + } + public static boolean isIpRoutedByDefaultGateway(String ip) { ShellResult res = ShellUtils.runAndReturn(String.format("ip route get %s | grep -q \"via $(ip route | awk '/default/ {print $3}')\"", ip)); return res.isReturnCode(0); @@ -628,9 +641,8 @@ public static List getIpRangeFromIps(List ips){ } public static String fmtCidr(final String origin) { - // format "*.*.1.1/16" to "*.*.0.0/16" DebugUtils.Assert(isCidr(origin), String.format("%s is not a cidr", origin)); - return new SubnetUtils(origin).getInfo().getNetworkAddress() + "/" + origin.split("/")[1]; + return getNetworkAddressFromCidr(origin); } public static List getCidrsFromIpRange(String startIp, String endIp) { diff --git a/utils/src/main/java/org/zstack/utils/ssh/Ssh.java b/utils/src/main/java/org/zstack/utils/ssh/Ssh.java index 1ae3b00aea5..3f61ae0b2f8 100755 --- a/utils/src/main/java/org/zstack/utils/ssh/Ssh.java +++ b/utils/src/main/java/org/zstack/utils/ssh/Ssh.java @@ -345,7 +345,7 @@ public SshResult run() { @Override public String getCommand() { - String target = SshShell.formatSshTarget(username, hostname); + String target = SshShell.formatScpTarget(username, hostname); if (download) { return String.format("scp -P %d %s:%s %s", port, target, src, dst); } else { diff --git a/utils/src/main/java/org/zstack/utils/ssh/SshShell.java b/utils/src/main/java/org/zstack/utils/ssh/SshShell.java index 1c08aa48ab7..cde850eb1b8 100755 --- a/utils/src/main/java/org/zstack/utils/ssh/SshShell.java +++ b/utils/src/main/java/org/zstack/utils/ssh/SshShell.java @@ -39,6 +39,10 @@ private void checkParams() { } public static String formatSshTarget(String username, String hostname) { + return String.format(SSH_TARGET_FORMAT, username, IPv6NetworkUtils.stripHostUrlBrackets(hostname)); + } + + public static String formatScpTarget(String username, String hostname) { return String.format(SSH_TARGET_FORMAT, username, IPv6NetworkUtils.formatHostForUrl(hostname)); } @@ -95,32 +99,32 @@ public SshResult runScript(String script) { tempPasswordFile = File.createTempFile("zstack", "tmp"); writeSecretFile(tempPasswordFile, privateKey); ssh = ln( - "ssh -q -i {0} -o UserKnownHostsFile=/dev/null -o PasswordAuthentication=no -o StrictHostKeyChecking=no -p {1} -T {2}@{3} << 'EOF'", + "ssh -q -i {0} -o UserKnownHostsFile=/dev/null -o PasswordAuthentication=no -o StrictHostKeyChecking=no -p {1} -T {2} << 'EOF'", "s=`mktemp`", "cat << 'EOT' > $s", - "{4}", + "{3}", "EOT", "bash $s", "ret=$?", "rm -f $s", "exit $ret", "EOF" - ).format(tempPasswordFile.getAbsolutePath(), port, username, hostname, script); + ).format(tempPasswordFile.getAbsolutePath(), port, formatSshTarget(username, hostname), script); } else { tempPasswordFile = File.createTempFile("zstack", "tmp"); writeSecretFile(tempPasswordFile, password); ssh = ln( - "sshpass -f{0} ssh -q -o UserKnownHostsFile=/dev/null -o PubkeyAuthentication=no -o StrictHostKeyChecking=no -p {1} -T {2}@{3} << 'EOF'", + "sshpass -f{0} ssh -q -o UserKnownHostsFile=/dev/null -o PubkeyAuthentication=no -o StrictHostKeyChecking=no -p {1} -T {2} << 'EOF'", "s=`mktemp`", "cat << 'EOT' > $s", - "{4}", + "{3}", "EOT", "bash $s", "ret=$?", "rm -f $s", "exit $ret", "EOF" - ).format(tempPasswordFile.getAbsolutePath(), port, username, hostname, script); + ).format(tempPasswordFile.getAbsolutePath(), port, formatSshTarget(username, hostname), script); } if (logger.isTraceEnabled()) {