Skip to content

Commit

Permalink
Add option to select http client
Browse files Browse the repository at this point in the history
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 in
HttpURLConnection to fill 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:
```
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
	<soapenv:Body>
		<soapenv:Fault>
			<faultcode>ServerFaultCode</faultcode>
			<faultstring>Cannot complete login due to an incorrect user name or password.</faultstring>
			<detail>
				<InvalidLoginFault xmlns="urn:vim25" xsi:type="InvalidLogin">
					<faultMessage>
						<key>vim.fault.InvalidLoginWithReason</key>
						<arg>
							<key>1</key>
							<value xsi:type="xsd:string">vim.fault.InvalidLoginReasonRegisterFailed</value>
						</arg>
						<message>Login failed because the session could not be re-registered.</message>
					</faultMessage>
				</InvalidLoginFault>
			</detail>
		</soapenv:Fault>
	</soapenv:Body>
</soapenv:Envelope>
```

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
  • Loading branch information
lifemanship committed Nov 4, 2022
1 parent 0eea7ae commit ec1207e
Show file tree
Hide file tree
Showing 6 changed files with 96 additions and 7 deletions.
10 changes: 6 additions & 4 deletions src/main/java/org/jenkinsci/plugins/vSphereCloud.java
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -630,9 +630,11 @@ public static List<vSphereCloud> 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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -58,16 +63,71 @@ public class VSphereConnectionConfig extends AbstractDescribableImpl<VSphereConn
private final @CheckForNull String vsHost;
private /*final*/ boolean allowUntrustedCertificate;
private final @CheckForNull String credentialsId;


private enum HttpClientClassName {
ApacheHttpClientClass("ApacheHttpClient"),
WSClientClass("WSClient");

public final String name;

private HttpClientClassName(String name) {
this.name = name;
}
}

private static Class<?> 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<vSphereCloud> 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);
}

Expand Down Expand Up @@ -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://")) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<div>
The <b>vSphere Cloud</b> plugin depends on
<a href="https://github.com/yavijava/yavijava">yavijava</a>
library, which supports two types of http clients:
<a href="https://github.com/yavijava/yavijava/blob/gradle/src/main/java/com/vmware/vim25/ws/ApacheHttpClient.java">CloseableHttpClient</a>
and
<a href="https://github.com/yavijava/yavijava/blob/gradle/src/main/java/com/vmware/vim25/ws/WSClient.java">HttpURLConnection</a>
</div>
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,6 @@
useSnapshot: true
waitForVMTools: true
vsConnectionConfig:
httpClientClassName: "WSClient"
vsHost: "https://company-vsphere"
vsDescription: "Company vSphere"

0 comments on commit ec1207e

Please sign in to comment.