From b44d8ae0a56ec58f2a99c6fa7e67d04105c155fd Mon Sep 17 00:00:00 2001 From: Nick Korsakov Date: Wed, 2 Nov 2022 20:58:10 +0100 Subject: [PATCH] Add option to select http client The plugin depends on network library yavijava https://github.com/jenkinsci/vsphere-cloud-plugin/blob/master/pom.xml#L95-L96 The yavijava supports two types of http clients: `CloseableHttpClient` `HttpURLConnection` - is default The HttpURLConnection client's behaviour can be impacted by system-wide cookie handler CookieManager: https://docs.oracle.com/javase/8/docs/api/java/net/CookieManager.html The major problem is that the CookieManager overlaps logic provided by HttpURLConnection to setup http header `Cookie`. Global CookieManager know nothing about logic of vsphere-cloud-plugin and fill all http headers with stored cookies, as result vsphere center rejects login requests with error: ``` ServerFaultCode Cannot complete login due to an incorrect user name or password. vim.fault.InvalidLoginWithReason 1 vim.fault.InvalidLoginReasonRegisterFailed Login failed because the session could not be re-registered. ``` The vsphere-cloud-plugin does not use CookieManager, but there is no guarantee that other plugins avoid it. Example: https://github.com/jenkinsci/parameterized-remote-trigger-plugin/blob/master/src/main/java/org/jenkinsci/plugins/ParameterizedRemoteTrigger/RemoteBuildConfiguration.java#L158-L159 As workaroud we can switch to CloseableHttpClient http client. Issue in Jira: https://issues.jenkins.io/browse/JENKINS-69999 --- .../org/jenkinsci/plugins/vSphereCloud.java | 10 ++- .../vsphere/VSphereConnectionConfig.java | 78 ++++++++++++++++++- .../plugins/vsphere/tools/VSphere.java | 2 + .../VSphereConnectionConfig/config.groovy | 4 + .../help-httpClientClassName.html | 8 ++ .../plugins/vsphere/tools/expected_output.yml | 1 + 6 files changed, 96 insertions(+), 7 deletions(-) create mode 100644 src/main/resources/org/jenkinsci/plugins/vsphere/VSphereConnectionConfig/help-httpClientClassName.html diff --git a/src/main/java/org/jenkinsci/plugins/vSphereCloud.java b/src/main/java/org/jenkinsci/plugins/vSphereCloud.java index ddb33052..0984bb24 100644 --- a/src/main/java/org/jenkinsci/plugins/vSphereCloud.java +++ b/src/main/java/org/jenkinsci/plugins/vSphereCloud.java @@ -172,7 +172,7 @@ public vSphereCloud(VSphereConnectionConfig vsConnectionConfig, String vsDescrip public Object readResolve() throws IOException { if (vsConnectionConfig == null) { - vsConnectionConfig = new VSphereConnectionConfig(vsHost, null); + vsConnectionConfig = new VSphereConnectionConfig(vsHost, null, null); } if (this.templates != null) { for (vSphereCloudSlaveTemplate template : templates) { @@ -630,9 +630,11 @@ public static List findAllVsphereClouds(String jobName) { } } - for (Cloud cloud : Jenkins.getInstance().clouds) { - if (cloud instanceof vSphereCloud) { - vSphereClouds.add((vSphereCloud) cloud); + if (Jenkins.getInstance() != null) { + for (Cloud cloud : Jenkins.getInstance().clouds) { + if (cloud instanceof vSphereCloud) { + vSphereClouds.add((vSphereCloud) cloud); + } } } return vSphereClouds; diff --git a/src/main/java/org/jenkinsci/plugins/vsphere/VSphereConnectionConfig.java b/src/main/java/org/jenkinsci/plugins/vsphere/VSphereConnectionConfig.java index dc160c73..610f5b14 100644 --- a/src/main/java/org/jenkinsci/plugins/vsphere/VSphereConnectionConfig.java +++ b/src/main/java/org/jenkinsci/plugins/vsphere/VSphereConnectionConfig.java @@ -28,6 +28,8 @@ import com.cloudbees.plugins.credentials.common.StandardUsernamePasswordCredentials; import com.cloudbees.plugins.credentials.domains.DomainRequirement; import com.cloudbees.plugins.credentials.domains.HostnameRequirement; +import com.vmware.vim25.ws.ApacheHttpClient; +import com.vmware.vim25.ws.WSClient; import hudson.Extension; import hudson.Util; import hudson.model.AbstractDescribableImpl; @@ -37,11 +39,14 @@ import hudson.util.ListBoxModel; import hudson.util.Secret; import java.util.Collections; +import java.util.List; + import edu.umd.cs.findbugs.annotations.CheckForNull; import edu.umd.cs.findbugs.annotations.NonNull; import jenkins.model.Jenkins; import org.apache.commons.lang.StringUtils; +import org.jenkinsci.plugins.vSphereCloud; import org.jenkinsci.plugins.vsphere.tools.VSphere; import org.kohsuke.stapler.AncestorInPath; import org.kohsuke.stapler.DataBoundConstructor; @@ -58,16 +63,71 @@ public class VSphereConnectionConfig extends AbstractDescribableImpl httpClientNameToClass(String httpClientClassName) { + if (httpClientClassName.equals(HttpClientClassName.ApacheHttpClientClass.name)) { + return ApacheHttpClient.class; + } + return WSClient.class; + } + + private static String httpClientClassToName(Class httpClientClass) { + if (httpClientClass == ApacheHttpClient.class) { + return HttpClientClassName.ApacheHttpClientClass.name; + } + return HttpClientClassName.WSClientClass.name; + } + + private String httpClientClassName; + + private static Class httpClientClass; + + public static Class setupGlobalHttpClientClass() { + if (VSphereConnectionConfig.httpClientClass == null) { + VSphereConnectionConfig.httpClientClass = WSClient.class; + List clouds = vSphereCloud.findAllVsphereClouds(null); + if (clouds.size() > 0) { + VSphereConnectionConfig.httpClientClass + = httpClientNameToClass(clouds.get(0).getVsConnectionConfig().httpClientClassName); + } + } + return VSphereConnectionConfig.httpClientClass; + } + + public static String getHttpClientClassName() { + return httpClientClassToName(setupGlobalHttpClientClass()); + } + + @DataBoundSetter + public void setHttpClientClassName(String httpClientClassName) { + this.httpClientClassName = (httpClientClassName == null) ? getHttpClientClassName() : httpClientClassName; + for (vSphereCloud cloud : vSphereCloud.findAllVsphereClouds(null)) { + cloud.getVsConnectionConfig().httpClientClassName = this.httpClientClassName; + } + VSphereConnectionConfig.httpClientClass = httpClientNameToClass(this.httpClientClassName); + } + @DataBoundConstructor - public VSphereConnectionConfig(String vsHost, String credentialsId) { + public VSphereConnectionConfig(String vsHost, String credentialsId, String httpClientClassName) { this.vsHost = vsHost; this.credentialsId = credentialsId; + setHttpClientClassName(httpClientClassName); } /** Full constructor for internal use, initializes all fields */ public VSphereConnectionConfig(String vsHost, boolean allowUntrustedCertificate, String credentialsId) { - this(vsHost, credentialsId); + this(vsHost, credentialsId, null); setAllowUntrustedCertificate(allowUntrustedCertificate); } @@ -119,6 +179,18 @@ public String getDisplayName() { return "N/A"; } + public ListBoxModel doFillHttpClientClassNameItems(@AncestorInPath AbstractFolder containingFolderOrNull) { + throwUnlessUserHasPermissionToConfigureCloud(containingFolderOrNull); + + ListBoxModel items = new ListBoxModel(new ListBoxModel.Option("Use HttpURLConnection to connect to the vSphere cloud", + VSphereConnectionConfig.HttpClientClassName.WSClientClass.name, + VSphereConnectionConfig.HttpClientClassName.WSClientClass.name.equals(getHttpClientClassName())), + new ListBoxModel.Option("Use CloseableHttpClient to connect to the vSphere cloud", + VSphereConnectionConfig.HttpClientClassName.ApacheHttpClientClass.name, + VSphereConnectionConfig.HttpClientClassName.ApacheHttpClientClass.name.equals(getHttpClientClassName()))); + return items; + } + public FormValidation doCheckVsHost(@QueryParameter String value) { if (value!=null && value.length() != 0) { if (!value.startsWith("https://")) { diff --git a/src/main/java/org/jenkinsci/plugins/vsphere/tools/VSphere.java b/src/main/java/org/jenkinsci/plugins/vsphere/tools/VSphere.java index 422bf991..aefd4693 100644 --- a/src/main/java/org/jenkinsci/plugins/vsphere/tools/VSphere.java +++ b/src/main/java/org/jenkinsci/plugins/vsphere/tools/VSphere.java @@ -24,6 +24,7 @@ import java.util.logging.Logger; import java.util.logging.Level; +import com.vmware.vim25.ws.ClientCreator; import edu.umd.cs.findbugs.annotations.CheckForNull; import edu.umd.cs.findbugs.annotations.NonNull; @@ -71,6 +72,7 @@ public class VSphere { private VSphere(@NonNull String url, boolean ignoreCert, @NonNull String user, @CheckForNull String pw) throws VSphereException { try { + ClientCreator.clientClass = VSphereConnectionConfig.setupGlobalHttpClientClass(); this.url = new URL(url); final ServiceInstance serviceInstance = new ServiceInstance(this.url, user, pw, ignoreCert); final ServerConnection serverConnection = serviceInstance.getServerConnection(); diff --git a/src/main/resources/org/jenkinsci/plugins/vsphere/VSphereConnectionConfig/config.groovy b/src/main/resources/org/jenkinsci/plugins/vsphere/VSphereConnectionConfig/config.groovy index 6bf07bf0..e23744aa 100644 --- a/src/main/resources/org/jenkinsci/plugins/vsphere/VSphereConnectionConfig/config.groovy +++ b/src/main/resources/org/jenkinsci/plugins/vsphere/VSphereConnectionConfig/config.groovy @@ -7,6 +7,10 @@ f.entry(title:_("vSphere Host"), field:"vsHost") { f.textbox() } +f.entry(title:_("Change Http Client"), field:"httpClientClassName") { + f.select() +} + f.entry(title:_("Disable SSL Check"), field:"allowUntrustedCertificate") { f.checkbox() } diff --git a/src/main/resources/org/jenkinsci/plugins/vsphere/VSphereConnectionConfig/help-httpClientClassName.html b/src/main/resources/org/jenkinsci/plugins/vsphere/VSphereConnectionConfig/help-httpClientClassName.html new file mode 100644 index 00000000..0d325ef4 --- /dev/null +++ b/src/main/resources/org/jenkinsci/plugins/vsphere/VSphereConnectionConfig/help-httpClientClassName.html @@ -0,0 +1,8 @@ +
+The vSphere Cloud plugin depends on +yavijava +library, which supports two types of http clients: +CloseableHttpClient +and +HttpURLConnection +
diff --git a/src/test/resources/org/jenkinsci/plugins/vsphere/tools/expected_output.yml b/src/test/resources/org/jenkinsci/plugins/vsphere/tools/expected_output.yml index ea40ad9e..051025d1 100644 --- a/src/test/resources/org/jenkinsci/plugins/vsphere/tools/expected_output.yml +++ b/src/test/resources/org/jenkinsci/plugins/vsphere/tools/expected_output.yml @@ -30,5 +30,6 @@ useSnapshot: true waitForVMTools: true vsConnectionConfig: + httpClientClassName: "WSClient" vsHost: "https://company-vsphere" vsDescription: "Company vSphere"