View Javadoc
1   package org.metricshub.vcenter;
2   
3   /*-
4    * ╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲
5    * VCenter Java Client
6    * ჻჻჻჻჻჻
7    * Copyright (C) 2023 - 2025 MetricsHub
8    * ჻჻჻჻჻჻
9    * Licensed under the Apache License, Version 2.0 (the "License");
10   * you may not use this file except in compliance with the License.
11   * You may obtain a copy of the License at
12   *
13   *      http://www.apache.org/licenses/LICENSE-2.0
14   *
15   * Unless required by applicable law or agreed to in writing, software
16   * distributed under the License is distributed on an "AS IS" BASIS,
17   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18   * See the License for the specific language governing permissions and
19   * limitations under the License.
20   * ╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱
21   */
22  
23  import com.vmware.vim25.HostServiceTicket;
24  import com.vmware.vim25.InvalidLogin;
25  import com.vmware.vim25.mo.Datacenter;
26  import com.vmware.vim25.mo.Folder;
27  import com.vmware.vim25.mo.HostSystem;
28  import com.vmware.vim25.mo.InventoryNavigator;
29  import com.vmware.vim25.mo.ManagedEntity;
30  import com.vmware.vim25.mo.ServiceInstance;
31  import java.net.InetAddress;
32  import java.net.URL;
33  import java.util.Arrays;
34  import java.util.List;
35  import java.util.function.Consumer;
36  import java.util.function.Supplier;
37  import java.util.stream.Collectors;
38  
39  /**
40   * VCenterClient class for interacting with VMware vCenter server.
41   */
42  public final class VCenterClient {
43  
44  	private static final String ENTITY_HOST_SYSTEM = "HostSystem";
45  	private static final String ENTITY_DATA_CENTER = "Datacenter";
46  
47  	private static Supplier<Boolean> isDebugEnabled;
48  	private static Consumer<String> debug;
49  
50  	/**
51  	 * Private constructor to prevent instantiation of this class
52  	 */
53  	private VCenterClient() {}
54  
55  	/**
56  	 * Sets the debug methods to be used by the VCenterClient class.
57  	 * <p>
58  	 * The VCenterClient class may need to write debug information. However,
59  	 * depending on whether it runs in the context of MatsyaEngine or as a CLI,
60  	 * the debug will need to be handled differently.
61  	 * <p>
62  	 * In the case of MatsyaEngine, before making any further call to this class,
63  	 * use setDebug(MatsyaEngine::getDebugMode, MatsyaEngine::debug).
64  	 * <p>
65  	 * In the case of CLI, use setDebug() with your own methods (which will probably
66  	 * simply print to stdout).
67  	 *
68  	 * @param isDebugEnabledMethod The static method that returns a boolean whether the debug mode is enabled or not
69  	 * @param debugMethod The static method that will print the debug message somewhere
70  	 */
71  	public static void setDebug(final Supplier<Boolean> isDebugEnabledMethod, final Consumer<String> debugMethod) {
72  		isDebugEnabled = isDebugEnabledMethod;
73  		debug = debugMethod;
74  	}
75  
76  	/**
77  	 * Request an authentication certificate for the specified hostname from the specified
78  	 * VMware vCenter server. <p>
79  	 * The specified hostname must be registered in VMware vCenter so we can get an authentication
80  	 * "token" for it. <p>
81  	 * To get this token, we first need to authenticate against VMware vCenter (using the good old
82  	 * username and password mechanism). Then, we will be able to connect to the specified hostname
83  	 * VMware ESX just using this "token"
84  	 *
85  	 * @param vCenterName The hostname of IP address of the VMware vCenter system
86  	 * @param username Credentials to connect to VMware vCenter
87  	 * @param password Associated password
88  	 * @param hostname The hostname or IP address of the ESX host that we need an authentication token for
89  	 * @return The authentication token in the form of a String
90  	 * @throws InvalidLogin when the specified username/password is... well, invalid
91  	 * @throws Exception when anything else happens
92  	 */
93  	public static String requestCertificate(
94  		final String vCenterName,
95  		final String username,
96  		final String password,
97  		final String hostname
98  	) throws InvalidLogin, Exception {
99  		ServiceInstance serviceInstance = null;
100 
101 		try {
102 			// Connect to VCenter
103 			URL vCenterURL = new URL("https://" + vCenterName + "/sdk");
104 			debug.accept("Connecting to " + vCenterURL.toString() + "...");
105 			serviceInstance = new ServiceInstance(vCenterURL, username, password, true);
106 
107 			// Try to find the specified host in VCenter
108 
109 			HostSystem hostSystem = getHostSystemManagedEntity(serviceInstance, hostname);
110 
111 			if (hostSystem == null) {
112 				throw new Exception("Unable to find host " + hostname + " in VCenter " + vCenterName);
113 			} else {
114 				HostServiceTicket ticket = hostSystem.acquireCimServicesTicket();
115 				return ticket.getSessionId();
116 			}
117 		} finally {
118 			if (serviceInstance != null) {
119 				serviceInstance.getServerConnection().logout();
120 			}
121 		}
122 	}
123 
124 	/**
125 	 * Retrieve in VCenter the managed entity that corresponds to the specified systemName
126 	 * @param serviceInstance VCenter service instance
127 	 * @param systemName The host name to be found in VCenter
128 	 * @return The HostSystem instance that matches with specified systemName. <p>null if not found.
129 	 * @throws Exception
130 	 */
131 	private static HostSystem getHostSystemManagedEntity(final ServiceInstance serviceInstance, final String systemName)
132 		throws Exception {
133 		// Declarations
134 		String entityName;
135 		String shortEntityName;
136 		ManagedEntity[] managedEntities;
137 
138 		// Get the root folder (we'll search things from there)
139 		Folder rootFolder = serviceInstance.getRootFolder();
140 		if (rootFolder == null) {
141 			throw new Exception("Couldn't get the root folder");
142 		}
143 
144 		// First pass: Search for all managed entities in the root folder
145 		// Try an exact match of the managed entity name with the specified hostname (case insensitive, of course)
146 		InventoryNavigator inventoryNavigator = new InventoryNavigator(rootFolder);
147 		managedEntities = inventoryNavigator.searchManagedEntities(ENTITY_HOST_SYSTEM);
148 		if (managedEntities != null) {
149 			// We did get a list of managed entities, let's parse them
150 			for (ManagedEntity managedEntity : managedEntities) {
151 				entityName = managedEntity.getName();
152 				if (systemName.equalsIgnoreCase(entityName)) {
153 					// Found it! Return immediately the corresponding HostSystem object
154 					return (HostSystem) managedEntity;
155 				}
156 			}
157 
158 			// Second pass: try again, but compare with a "shortened" version of the hostname of the managed entities (i.e. what before the first dot)
159 			if (systemName.indexOf('.') == -1) {
160 				// Of course, we do this 2nd pass only if the specified system name doesn't have a dot, i.e. is a short name
161 				for (ManagedEntity managedEntity : managedEntities) {
162 					entityName = managedEntity.getName();
163 					int dotIndex = entityName.indexOf('.');
164 					if (dotIndex > 1) {
165 						shortEntityName = entityName.substring(0, dotIndex);
166 						if (systemName.equalsIgnoreCase(shortEntityName)) {
167 							// Found it! Return immediately the corresponding HostSystem object
168 							return (HostSystem) managedEntity;
169 						}
170 					}
171 				}
172 			}
173 
174 			// We're here, which means that we did get a list of managed entities, but none of them match with the specified hostname
175 			if (isDebugEnabled.get()) {
176 				String managedEntitiesString = "";
177 				for (ManagedEntity managedEntity : managedEntities) {
178 					managedEntitiesString = managedEntitiesString + " - " + managedEntity.getName() + "\n";
179 				}
180 				StringBuilder entityList = new StringBuilder();
181 				for (ManagedEntity managedEntity : managedEntities) {
182 					entityList.append(" - ").append(managedEntity.getName()).append("\n");
183 				}
184 				debug.accept(
185 					"VCenterClient: Couldn't find host " +
186 					systemName +
187 					" in the list of managed entities in VCenter " +
188 					serviceInstance.getServerConnection().getUrl().getHost() +
189 					":\n" +
190 					entityList
191 				);
192 				debug.accept("VCenterClient: Will now try with the IP address of " + systemName);
193 			}
194 		}
195 
196 		// Third pass: Try to find the host in another way, through the Datacenter entities
197 		managedEntities = inventoryNavigator.searchManagedEntities(ENTITY_DATA_CENTER);
198 		if (null == managedEntities || managedEntities.length == 0) {
199 			throw new Exception("No Datacenter-type managed entity");
200 		}
201 
202 		// And we will try that using the IP address instead of the host name
203 		InetAddress[] hostIPaddresses;
204 		try {
205 			hostIPaddresses = InetAddress.getAllByName(systemName);
206 		} catch (Exception e) {
207 			throw new Exception("Couldn't resolve " + systemName + " into a valid IP address");
208 		}
209 
210 		// Go through each datacenter
211 		for (ManagedEntity datacenterEntity : managedEntities) {
212 			// Go through each IP address of the specified system name
213 			for (InetAddress hostIPaddress : hostIPaddresses) {
214 				// Use the index to find the managed entity we want, by IP address
215 				ManagedEntity[] managedEntitiesInDatacenter = serviceInstance
216 					.getSearchIndex()
217 					.findAllByIp((Datacenter) datacenterEntity, hostIPaddress.getHostAddress(), false);
218 
219 				// If we got something, return immediately the corresponding HostSystem instance, of the first match!
220 				if (managedEntitiesInDatacenter != null && managedEntitiesInDatacenter.length != 0) {
221 					return (HostSystem) managedEntitiesInDatacenter[0];
222 				}
223 			}
224 		}
225 
226 		return null;
227 	}
228 
229 	/**
230 	 * Retrieve all managed entities of type "HostSystem" in the specified VCenter.
231 	 * @param vCenterName The hostname of IP address of the VMware vCenter system
232 	 * @param username Credentials to connect to VMware vCenter
233 	 * @param password Associated password
234 	 * @return The list of hostnames (or IP addresses) registered in the VCenter
235 	 * @throws InvalidLogin for bad credentials
236 	 * @throws Exception when anything else goes south
237 	 */
238 	public static List<String> getAllHostSystemManagedEntities(
239 		final String vCenterName,
240 		final String username,
241 		final String password
242 	) throws Exception {
243 		ServiceInstance serviceInstance = null;
244 
245 		try {
246 			// Connect to VCenter
247 			URL vCenterURL = new URL("https://" + vCenterName + "/sdk");
248 			debug.accept("Connecting to " + vCenterURL.toString() + "...");
249 			serviceInstance = new ServiceInstance(vCenterURL, username, password, true);
250 
251 			// Get the root folder (we'll search things from there)
252 			Folder rootFolder = serviceInstance.getRootFolder();
253 			if (rootFolder == null) {
254 				throw new Exception("Couldn't get the root folder");
255 			}
256 
257 			// InventoryNavigator allows us to get all managed entities
258 			InventoryNavigator inventoryNavigator = new InventoryNavigator(rootFolder);
259 
260 			// Get all entities of type "HostSystem" and return as a list of HostSystem
261 			return Arrays
262 				.stream(inventoryNavigator.searchManagedEntities(ENTITY_HOST_SYSTEM))
263 				.map(ManagedEntity::getName)
264 				.collect(Collectors.toList());
265 		} finally {
266 			if (serviceInstance != null) {
267 				serviceInstance.getServerConnection().logout();
268 			}
269 		}
270 	}
271 }