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