/*************************************************************************** * Copyright (c) 2012-2015 VMware, Inc. All Rights Reserved. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. ***************************************************************************/ package com.vmware.aurora.vc.vcservice; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.Writer; import java.lang.reflect.UndeclaredThrowableException; import java.net.URI; import java.net.URISyntaxException; import java.security.cert.Certificate; import java.security.cert.CertificateEncodingException; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.Map; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.RejectedExecutionHandler; import java.util.concurrent.ThreadFactory; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import javax.net.ssl.SSLException; import javax.net.ssl.TrustManager; import com.vmware.bdd.security.tls.TlsClientConfiguration; import com.vmware.bdd.utils.Constants; import org.apache.commons.codec.binary.Base64; import org.apache.commons.httpclient.Header; import org.apache.commons.httpclient.HttpClient; import org.apache.commons.httpclient.methods.PostMethod; import org.apache.commons.httpclient.methods.RequestEntity; import org.apache.commons.httpclient.methods.StringRequestEntity; import org.apache.commons.httpclient.protocol.Protocol; import org.apache.commons.httpclient.protocol.ProtocolSocketFactory; import org.apache.log4j.Logger; import org.springframework.scheduling.concurrent.CustomizableThreadFactory; import com.vmware.aurora.exception.VcException; import com.vmware.aurora.global.Configuration; import com.vmware.aurora.security.CmsKeyStore; import com.vmware.aurora.security.ThumbprintTrustManager; import com.vmware.aurora.util.AuAssert; import com.vmware.aurora.vc.vcservice.VcService.MyThreadPoolExecutor.MyBlockingQueue; import com.vmware.vim.binding.impl.vim.DescriptionImpl; import com.vmware.vim.binding.impl.vim.ExtensionImpl; import com.vmware.vim.binding.impl.vim.KeyValueImpl; import com.vmware.vim.binding.impl.vim.ext.ExtendedProductInfoImpl; import com.vmware.vim.binding.impl.vim.ext.ManagedEntityInfoImpl; import com.vmware.vim.binding.impl.vim.ext.SolutionManagerInfoImpl; import com.vmware.vim.binding.vim.*; import com.vmware.vim.binding.vim.AuthorizationManager.Role; import com.vmware.vim.binding.vim.Extension.PrivilegeInfo; import com.vmware.vim.binding.vim.alarm.AlarmManager; import com.vmware.vim.binding.vim.event.Event; import com.vmware.vim.binding.vim.event.Event.EventSeverity; import com.vmware.vim.binding.vim.ext.ExtendedProductInfo; import com.vmware.vim.binding.vim.ext.ManagedEntityInfo; import com.vmware.vim.binding.vim.ext.SolutionManagerInfo; import com.vmware.vim.binding.vim.option.OptionManager; import com.vmware.vim.binding.vim.option.OptionValue; import com.vmware.vim.binding.vim.version.version10; import com.vmware.vim.binding.vmodl.ManagedObject; import com.vmware.vim.binding.vmodl.ManagedObjectReference; import com.vmware.vim.binding.vmodl.query.PropertyCollector; import com.vmware.vim.vmomi.client.Client; import com.vmware.vim.vmomi.client.http.HttpClientConfiguration; import com.vmware.vim.vmomi.client.http.HttpConfiguration; import com.vmware.vim.vmomi.client.http.ThumbprintVerifier; import com.vmware.vim.vmomi.client.http.impl.HttpConfigurationImpl; import com.vmware.vim.vmomi.core.exception.CertificateValidationException; import com.vmware.vim.vmomi.core.exception.InternalException; import com.vmware.vim.vmomi.core.types.VmodlTypeMap; /** * <code>VcService</code> maintains connection and session with VC server. */ public class VcService { private static final Logger logger = Logger.getLogger(VcService.class); private static final String SERENGETI_EXTENSION_REGISTERED = "serengeti.extension.registered"; private static final Class<?> version = version10.class; private static final int SESSION_TIME_OUT = Configuration.getInt( "vc.session_time_out", 120000); private static final String SERENGETI_PRIVILEGE_GROUP_NAME = "Serengeti"; private static final String SERENGETI_PRIVILEGE_ID = "Serengeti.access"; private static final String SERENGETI_PERMISSION_ROLE = "BDE users"; /* * The following fields are VC login info, initialized once. */ static private boolean configured = false; static private boolean vcExtensionRegistered = false; static private String vcHost; static private int vcPort; static private String evsURL; static private String evsToken; static private String vcThumbprint; static private String extKey; static private String userName; static private String password; static private String locale; private final String serviceName; // Internally used session name. private final boolean useExecutor; // Whether this VcService uses ThreadPoolExecutor. private final int timeoutMillis; // HTTP timeout private ThumbprintVerifier getThumbprintVerifier() { return new ThumbprintVerifier() { @Override public Result verify(String thumbprint) { //tempo solution, when connect to another VC, disable certificate verification. if(Configuration.getBoolean(Constants.CONNECT_TO_ANOTHER_VC, false)) { logger.info("the BDE server is configured to connect to another vCenter, whose certificate will not be verified."); return Result.MATCH; } if (thumbprint.equalsIgnoreCase(vcThumbprint)) { return Result.MATCH; } else { return Result.MISMATCH; } } @Override public void onSuccess(X509Certificate[] chain, String thumbprint, Result verifyResult, boolean trustedChain, boolean verifiedAssertions) throws SSLException { } }; } /** * XXX This class is created to debug PR 848988. * @author mchen */ static class MyThreadPoolExecutor extends ThreadPoolExecutor { static class MyRejectHandler implements RejectedExecutionHandler { public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) { RejectedExecutionException e = new RejectedExecutionException(); logger.info("rejecting runnable " + r, e); logger.info((executor.isShutdown()? " shutdown " : " running ") + ",tasks=" + executor.getTaskCount() + ",poolSize=" + executor.getPoolSize() + ",maxPoolSize=" + executor.getMaximumPoolSize() + ",active=" + executor.getActiveCount() + ",queue=" + executor.getQueue() + ",qSize=" + executor.getQueue().size() + ",qCapacity=" + executor.getQueue().remainingCapacity()); throw e; } static public MyRejectHandler getInstance() { return new MyRejectHandler(); } } @SuppressWarnings("serial") static class MyBlockingQueue<E> extends LinkedBlockingQueue<E> { public MyBlockingQueue() { super(); } @Override public boolean offer(E e) { int oldSize = size(); int oldRemainingCapacity = remainingCapacity(); boolean retVal = super.offer(e); if (!retVal) { logger.info("offer failed: size=" + size() + ",capacity=" + remainingCapacity() + ",oldSize=" + oldSize + ",oldRemainingCapacity=" + oldRemainingCapacity); } return retVal; } } public MyThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, MyBlockingQueue<Runnable> workQueue, ThreadFactory threadFactory) { super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, MyRejectHandler.getInstance()); } } /* * References to VC service objects. */ private class ServiceContents { private final long genCount; // generation count of this service instance private Client vmomiClient; private ThreadPoolExecutor executor; private HttpConfiguration httpConfig; private ServiceInstance instance = null; private ServiceInstanceContent instanceContent = null; private SessionManager sessionManager = null; private FileManager fileManager = null; private VirtualDiskManager vmdkManager = null; private OvfManager ovfManager = null; private PerformanceManager perfManager = null; private TaskManager taskManager = null; private OptionManager optionManager = null; private PropertyCollector propertyCollector = null; private ExtensionManager extensionManager = null; private AlarmManager alarmManager = null; private AuthorizationManager authorizationManager = null; /* * A map that caches all managed object proxy objects. */ private final Map<ManagedObjectReference, ManagedObject> moMap = new HashMap<ManagedObjectReference, ManagedObject>(); /* * Login VC session and get service objects. */ private ServiceContents(long genCount) throws Exception { this.genCount = genCount; long startNanos = System.nanoTime(); String sessionTicket = loginAndGetSessionTicket(); ManagedObjectReference svcRef = new ManagedObjectReference(); boolean done = false; svcRef.setType("ServiceInstance"); svcRef.setValue("ServiceInstance"); try { initVmomiClient(); instance = vmomiClient.createStub(ServiceInstance.class, svcRef); /* * Establish session. */ instanceContent = instance.retrieveContent(); sessionManager = vmomiClient.createStub(SessionManager.class, instanceContent.getSessionManager()); /* * login for the user */ if (sessionTicket != null) { try { sessionManager.loginBySessionTicket(sessionTicket); } catch (Exception e) { logger.error("failed to use VC session ticket", e); throw e; } } else if (userName != null) { logger.info("try to login to VC using username and password"); sessionManager.login(userName, password, locale); } else { throw VcException.LOGIN_ERROR(); } fileManager = getManagedObject(instanceContent.getFileManager()); vmdkManager = getManagedObject(instanceContent.getVirtualDiskManager()); taskManager = getManagedObject(instanceContent.getTaskManager()); ovfManager = getManagedObject(instanceContent.getOvfManager()); perfManager = getManagedObject(instanceContent.getPerfManager()); optionManager = getManagedObject(instanceContent.getSetting()); propertyCollector = getManagedObject(instanceContent.getPropertyCollector()); extensionManager = getManagedObject(instanceContent.getExtensionManager()); alarmManager = getManagedObject(instanceContent.getAlarmManager()); authorizationManager = getManagedObject(instanceContent.getAuthorizationManager()); logger.info("VC login on behalf of {" + Thread.currentThread().getName() + ":" + serviceName + "{" + genCount + "}} " + TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNanos) + "ms"); done = true; } finally { // Clean up if we cannot proceed. if (!done) { cleanup(); } } } /** * Initialize a new vmomiClient, including all required resources, thread * pool, etc. Thread names will include service name, generation count and * a thread number in the pool as in "Thread[vcService{2}-1]". * @throws Exception */ private void initVmomiClient() throws URISyntaxException { String threadNamePrefix = serviceName + "{" + genCount + "}-"; CustomizableThreadFactory threadFactory = new CustomizableThreadFactory(threadNamePrefix); threadFactory.setDaemon(true); if (useExecutor) { executor = new MyThreadPoolExecutor(1, 1, 10, TimeUnit.SECONDS, new MyBlockingQueue<Runnable>(), threadFactory); } else { executor = null; } URI uri; String serviceUrl = getServiceUrl(); try { uri = new URI(serviceUrl); } catch (URISyntaxException e) { logger.error("Bad VC URL " + serviceUrl + e); AuAssert.check(false); throw e; } httpConfig = new HttpConfigurationImpl(); httpConfig.setTimeoutMs(timeoutMillis); httpConfig.setThumbprintVerifier(getThumbprintVerifier()); TlsClientConfiguration tlsClientConfiguration = new TlsClientConfiguration(); httpConfig.setEnabledProtocols(tlsClientConfiguration.getSslProtocols()); if (useExecutor) { vmomiClient = Client.Factory.createClient(uri, version, executor, httpConfig); } else { HttpClientConfiguration clientConfig = HttpClientConfiguration.Factory.newInstance(); clientConfig.setHttpConfiguration(httpConfig); vmomiClient = Client.Factory.createClient(uri, version, clientConfig); } } /* * Get & insert a managed object proxy from moMap cache. */ private synchronized <T extends ManagedObject> T getManagedObjectInt(ManagedObjectReference moRef) { @SuppressWarnings("unchecked") T obj = (T)moMap.get(moRef); if (obj != null) { return obj; } // The VmodlTypeMap.getVmodlType() is applicable for any vmodl type and is // not specific to ManagedObject. We need to cast to Class<T> because VLSI // core has changed the interface. @SuppressWarnings({ "unchecked"}) Class<T> clazz = (Class<T>) VmodlTypeMap.Factory.getTypeMap() .getVmodlType(moRef.getType()).getTypeClass(); obj = vmomiClient.<T>createStub(clazz, moRef); moMap.put(moRef, obj); return obj; } /* * Get a managed object proxy from moMap cache. */ private <T extends ManagedObject> T getManagedObject(ManagedObjectReference moRef) { @SuppressWarnings("unchecked") T obj = (T)moMap.get(moRef); if (obj != null) { return obj; } return this.<T>getManagedObjectInt(moRef); } /** * Logout out of vc and tear down all consumed resources. */ private void cleanup() { moMap.clear(); VcContext.getVcCleaner().logout(serviceName, vmomiClient, sessionManager, executor, httpConfig); } public int getInstanceId() { OptionValue[] options = optionManager.getSetting(); for (OptionValue option : options) { if (option.getKey().equals("instance.id")) { return (Integer)option.getValue(); } } throw VcException.SETTING_ERROR(); } void dumpStatus() { logger.info(serviceName + "{" + genCount + "}:" + (executor.isShutdown()? " shutdown " : " running ") + "tasks=" + executor.getTaskCount() + ",qSize=" + executor.getQueue().size() + ",qCapacity=" + executor.getQueue().remainingCapacity()); } } /* * Because VC sessions are shared by multiple threads, * we use a generation counter to keep track of the current * login-session to prevent multiple logout calls on * the same session. There will be exactly one VC session manager * logout and clearing of service contents per VC login. */ private volatile long curGenCount = 0; /* * VC connection objects. If a connection failed or was closed, * always establish new instances. */ ServiceContents service = null; private void setService(ServiceContents service, VcConnectionStatusChangeEvent eventType) { AuAssert.check(Thread.holdsLock(this)); /* * Always log an event for an initial attempt: both VcContext is * initialized for the first time and this session gets its first life. * Otherwise, log an an event only if a service state change has been * detected to avoid event storms when VC is persistently down. */ if (eventType != null && ((VcContext.getGenCount() == 1 && curGenCount == 1) || this.service != service)) { VcContext.triggerEvent(eventType, serviceName); } this.service = service; } private static void initVcConfig() { /* * The following configs should be set in * aurora-cms-transient.properties by evs_init.py * on every CMS boot. */ vcHost = Configuration.getString("vim.host"); vcPort = Configuration.getInt("vim.port", 443); evsURL = Configuration.getString("vim.evs_url"); evsToken = Configuration.getString("vim.evs_token"); vcThumbprint = Configuration.getString("vim.thumbprint", null); /* * Extension key is based on CMS instance identifier. */ extKey = "com.vmware.aurora.vcext.instance-" + Configuration.getCmsInstanceId(); // represent if extension service is already registered vcExtensionRegistered = Configuration.getBoolean(SERENGETI_EXTENSION_REGISTERED, false); /* * The following are not set in config files by default. * They can be hard coded manually. */ userName = Configuration.getString("vim.username", null); password = Configuration.getString("vim.password", null); locale = Configuration.getString("vim.locale", "en"); configured = true; } /** * Create a VC service instance. The following is done: * - Load the VLSI vmodl context. * - Create a VC client object with VLSI with VC URL, * HTTP configurations and a thread pool. * * After initialization, VC session will be established. * * @param serviceName internal name */ public VcService(String serviceName, boolean useExecutor, int timeoutMillis) throws VcException { if (!configured) { initVcConfig(); } this.serviceName = serviceName; this.useExecutor = useExecutor; this.timeoutMillis = timeoutMillis; synchronized(this) { // Connect to VC. try { initVcSession(); } catch (Exception e) { logger.error("Cannot establish session to VC " + vcHost + ":" + vcPort + " on startup,", e); } } } public VcService(String serviceName) { this(serviceName, true, SESSION_TIME_OUT); } public String getServiceName() { return serviceName; } public String getServiceUrl() { return "https://" + vcHost + ":" + vcPort + "/sdk"; } public String getClientSessionId() { return getServiceContents().vmomiClient.getBinding().getSession().getId(); } /** * Returns true if a connection to VC has already been established */ public boolean isConnected() { return service != null; } /* * Login once using VC extension key (stored in cms keystore) * and retrieve the session ticket. */ private String loginAndGetSessionTicket() { URI sdkUri = null; if (!vcExtensionRegistered) { return null; } try { sdkUri = new URI("https://sdkTunnel:8089/sdk/vimService"); } catch (URISyntaxException e) { logger.error(e); return null; } HttpConfigurationImpl httpConfig = new HttpConfigurationImpl(); httpConfig.setTimeoutMs(SESSION_TIME_OUT); httpConfig.setKeyStore(CmsKeyStore.getKeyStore()); httpConfig.setDefaultProxy(vcHost, Configuration.getInt("vim.port.http", 80), "http"); httpConfig.getKeyStoreConfig().setKeyAlias(CmsKeyStore.VC_EXT_KEY); httpConfig.getKeyStoreConfig().setKeyPassword(CmsKeyStore.getVCExtPassword()); httpConfig.setThumbprintVerifier(getThumbprintVerifier()); TlsClientConfiguration tlsClientConfiguration = new TlsClientConfiguration(); httpConfig.setEnabledProtocols(tlsClientConfiguration.getSslProtocols()); HttpClientConfiguration clientConfig = HttpClientConfiguration.Factory.newInstance(); clientConfig.setHttpConfiguration(httpConfig); Client client = Client.Factory.createClient(sdkUri, version, clientConfig); SessionManager sm = null; try { ManagedObjectReference svcRef = new ManagedObjectReference(); svcRef.setType("ServiceInstance"); svcRef.setValue("ServiceInstance"); ServiceInstance si = client.createStub(ServiceInstance.class, svcRef); sm = client.createStub(SessionManager.class, si.getContent().getSessionManager()); sm.loginExtensionByCertificate(extKey, "en"); String ticket = sm.acquireSessionTicket(null); logger.info("got session ticket using extension"); return ticket; } catch (Exception e) { logger.error("failed to get session ticket using extension", e); return null; } finally { VcContext.getVcCleaner().logout("VcExtensionLogin", client, sm, null, httpConfig); } } /** * Initializes the VC session. Each session gets a separate vmomi client * (http session cookie is kept in vmomi client). * * Result: * _serviceInstance & other fields initialized if no exception is thrown. */ private void initVcSession() throws VcException { try { /* * Make sure our VC extension has been registered. */ boolean justRegistered = false; if (!vcExtensionRegistered) { registerExtensionVService(); justRegistered = true; } if (vcExtensionRegistered) { String ticket = null; try { ticket = loginAndGetSessionTicket(); } catch (Exception e) { logger.debug("Got exception during login"); } if (ticket == null) { logger.info("Failed to login using certificate, try to regist extension once again"); vcExtensionRegistered = false; } } if (!vcExtensionRegistered) { registerExtensionVService(); justRegistered = true; } /* * attach to the new VC session */ setService(new ServiceContents(++curGenCount), VcConnectionStatusChangeEvent.VC_SESSION_CREATED); /* Context callback to communicate session reset. */ VcContext.serviceReset(this); //tempo solution, when connect to another VC, disable certificate verification. if(Configuration.getBoolean(Constants.CONNECT_TO_ANOTHER_VC, false)) { logger.info("the BDE server is configured to connect to another vCenter, so not configure Extension Service."); } else { /* * Now that we have a valid VMOMI connection, set properties for our extension */ if (vcExtensionRegistered && justRegistered) { configureExtensionVService(); } } } catch (Exception ex) { if (service != null) { service.cleanup(); } setService(null, VcConnectionStatusChangeEvent.VC_SESSION_CREATION_FAILURE); if (ex instanceof VcException) { throw (VcException)ex; } else if (ex instanceof UndeclaredThrowableException) { UndeclaredThrowableException e = (UndeclaredThrowableException)ex; if (e.getUndeclaredThrowable() instanceof CertificateValidationException) { throw VcException.LOGIN_ERROR(e.getUndeclaredThrowable()); } } else if (ex instanceof InternalException) { InternalException e = (InternalException)ex; if (e.getCause() instanceof CertificateValidationException) { throw VcException.LOGIN_ERROR(e.getCause()); } } throw VcException.LOGIN_ERROR(ex); } } /** * Returns a PEM representation of a certificate * * @param cert A non-null certificate * @return The PEM representation * @throws CertificateEncodingException, if certificate is malformed */ private static String CertificateToPem(Certificate cert) throws CertificateEncodingException { byte[] base64 = Base64.encodeBase64Chunked(cert.getEncoded()); StringBuffer sb = new StringBuffer(); sb.append("-----BEGIN CERTIFICATE-----\n"); sb.append(new String(base64)); sb.append("-----END CERTIFICATE-----"); return sb.toString(); } /** * This sends a URL POST request to the Extension vService guest API to * register a new extension. Upon success, set vcExtensionRegistered to true. * Note that the extension will not be fully configured until we log in to VC * as this extension and make some VMODL calls to finish the job. * * Note also that we only need to do this once per CMS install, not once per * CMS startup, but it doesn't seem to hurt to do it every time. * * @synchronized for preventing concurrent call to register EVS. */ private static synchronized void registerExtensionVService() { if (vcExtensionRegistered) { return; } logger.debug("Register extension vService at: " + evsURL + " token=" + evsToken); Writer output = null; BufferedReader input = null; try { /** * Initialize our own trust manager */ ThumbprintTrustManager thumbprintTrustManager = new ThumbprintTrustManager(); thumbprintTrustManager.add(vcThumbprint); TrustManager[] trustManagers = new TrustManager[]{thumbprintTrustManager}; HttpClient httpClient = new HttpClient(); TlsSocketFactory tlsSocketFactory = new TlsSocketFactory(trustManagers); Protocol.registerProtocol("https", new Protocol("https", (ProtocolSocketFactory)tlsSocketFactory, 443)); PostMethod method = new PostMethod(evsURL); method.setRequestHeader("evs-token", evsToken); Certificate cert = CmsKeyStore.getCertificate(CmsKeyStore.VC_EXT_KEY); String evsSchema = "http://www.vmware.com/schema/vservice/ExtensionVService"; String payload = "<RegisterExtension xmlns=\"" + evsSchema + "\">\n" + " <Key>" + extKey + "</Key>\n" + " <Certificate>\n" + CertificateToPem(cert) + "\n" + " </Certificate>\n" + "</RegisterExtension>\n"; RequestEntity requestEntity = new StringRequestEntity(payload, "text/plain", "UTF-8"); method.setRequestEntity(requestEntity); int statusCode = httpClient.executeMethod(method); logger.info("status code: " + statusCode); for (Header e: method.getResponseHeaders()) { logger.debug("Response Header: " + e.getName() + " :" + e.getValue()); } input = new BufferedReader( new InputStreamReader(method.getResponseBodyAsStream())); for (String str = input.readLine(); str != null; str = input.readLine()) { logger.debug("Response: " + str); } if(statusCode == 200) { vcExtensionRegistered = true; logger.info("Extension registration request sent successfully"); } else { logger.error("Extension registration request sent error"); } } catch (Exception e) { logger.error("Failed Extension registration to " + evsURL, e); } finally { Configuration.setBoolean(SERENGETI_EXTENSION_REGISTERED, true); Configuration.save(); if (output != null) { try { output.close(); } catch (IOException e) { logger.error("Failed to close output Writer", e); } } if (input != null) { try { input.close(); } catch (IOException e) { logger.error("Failed to close input Reader", e); } } } } private void configureExtensionVService() throws Exception { ExtensionManager em = service.extensionManager; Extension us = em.findExtension(extKey); AuAssert.check(us != null); // Describe Aurora itself Description desc = new DescriptionImpl(); desc.setLabel("VMware Serengeti Management Server"); desc.setSummary("VMware Serengeti Management Server, instance " + Configuration.getCmsInstanceId()); us.setDescription(desc); us.setCompany("VMware, Inc."); us.setVersion(Configuration.getNonEmptyString("serengeti.version")); us.setShownInSolutionManager(true); ExtendedProductInfo extInfo = new ExtendedProductInfoImpl(); extInfo.setCompanyUrl("http://www.vmware.com"); us.setExtendedProductInfo(extInfo); // XXX: Set health info, any other fields? // Describe the entities we manage (DBVM) ManagedEntityInfo info = new ManagedEntityInfoImpl(); info.setType("hadoop node"); info.setDescription("VMware Serengeti - Node Template"); //info.setSmallIconUrl("https://*:443/some-16x16.png"); ManagedEntityInfo[] infos = new ManagedEntityInfo[1]; infos[0] = info; us.setManagedEntityInfo(infos); // Generate ResourceInfo Extension.ResourceInfo extensionResourceInfo = new ExtensionImpl.ResourceInfoImpl(); extensionResourceInfo.setLocale("en"); extensionResourceInfo.setModule("extension"); KeyValue localizedExt[] = new KeyValue[6]; localizedExt[0] = new KeyValueImpl(); localizedExt[0].setKey(us.getKey() + ".label"); localizedExt[0].setValue(us.getDescription().getLabel()); localizedExt[1] = new KeyValueImpl(); localizedExt[1].setKey(us.getKey() + ".summary"); localizedExt[1].setValue(us.getDescription().getSummary()); localizedExt[2] = new KeyValueImpl(); localizedExt[2].setKey("privilege.Serengeti.label"); localizedExt[2].setValue("Big Data Extensions"); localizedExt[3] = new KeyValueImpl(); localizedExt[3].setKey("privilege.Serengeti.summary"); localizedExt[3].setValue("Big Data Extensions related privileges"); localizedExt[4] = new KeyValueImpl(); localizedExt[4].setKey("privilege.Serengeti.access.label"); localizedExt[4].setValue("Access"); localizedExt[5] = new KeyValueImpl(); localizedExt[5].setKey("privilege.Serengeti.access.summary"); localizedExt[5].setValue("Access Big Data Extensions"); extensionResourceInfo.setData(localizedExt); // Generate event type specifications Extension.ResourceInfo eventResourceInfo = new ExtensionImpl.ResourceInfoImpl(); eventResourceInfo.setLocale("en"); eventResourceInfo.setModule("event"); class KeyValueList extends ArrayList<KeyValue> { public void add(String key, String value) { KeyValue pair = new KeyValueImpl(); pair.setKey(key); pair.setValue(value); super.add(pair); } }; KeyValueList resourceInfo = new KeyValueList(); ArrayList<Extension.EventTypeInfo> eventTypes = new ArrayList<Extension.EventTypeInfo>(); for (EventSeverity severity : Event.EventSeverity.values()) { resourceInfo.add("com.vmware.vhadoop.vhm.vc.events."+severity.name()+".label", "BDE notification"); resourceInfo.add("com.vmware.vhadoop.vhm.vc.events."+severity.name()+".summary", "BDE notification"); resourceInfo.add("com.vmware.vhadoop.vhm.vc.events."+severity.name()+".category", severity.name()); resourceInfo.add("com.vmware.vhadoop.vhm.vc.events."+severity.name()+".fullFormat", "{message}"); resourceInfo.add("com.vmware.vhadoop.vhm.vc.events."+severity.name()+".formatOnVm", "BDE notification"); Extension.EventTypeInfo event = new ExtensionImpl.EventTypeInfoImpl(); event.setEventID("com.vmware.vhadoop.vhm.vc.events."+severity.name()); event.setEventTypeSchema("<EventType><eventTypeID>com.vmware.vhadoop.vhm.vc.events."+severity.name()+"</eventTypeID><description>Status update for a Big Data Extensions compute VM</description><arguments/></EventType>"); eventTypes.add(event); } eventResourceInfo.setData(resourceInfo.toArray(new KeyValue[0])); us.setResourceList(new Extension.ResourceInfo[] {extensionResourceInfo, eventResourceInfo}); us.setEventList(eventTypes.toArray(new Extension.EventTypeInfo[0])); us.setShownInSolutionManager(true); SolutionManagerInfo sm = new SolutionManagerInfoImpl(); sm.setSmallIconUrl("http://www.vmware.com"); us.setSolutionManagerInfo(sm); //register a privilege for serengeti web client PrivilegeInfo loginBDE = new ExtensionImpl.PrivilegeInfoImpl(); loginBDE.setPrivGroupName(SERENGETI_PRIVILEGE_GROUP_NAME); loginBDE.setPrivID(SERENGETI_PRIVILEGE_ID); Collection<PrivilegeInfo> privileges = new ArrayList<PrivilegeInfo>(); privileges.add(loginBDE); us.setPrivilegeList(privileges.toArray(new PrivilegeInfo[0])); // Push this info into VC em.updateExtension(us); //register a serengeti role for serengeti web client users AuthorizationManager am = service.authorizationManager; Role[] roles = am.getRoleList(); for(int i=0; i < roles.length; i++){ if(SERENGETI_PERMISSION_ROLE.equals(roles[i].getName())){ break; } if(i == roles.length-1){ logger.info("add a serengeti role:"+SERENGETI_PERMISSION_ROLE); am.addRole(SERENGETI_PERMISSION_ROLE, new String[]{SERENGETI_PRIVILEGE_ID}); } } } /** * Synchronized creation of new VC session. * @return the VC service object. * @throws Exception */ private synchronized ServiceContents getServiceContentsLocked() throws VcException { if (!isConnected()) { initVcSession(); } AuAssert.check(service != null); return service; } /* * If already logged into VC, do nothing. * Otherwise, try to instantiate a new {\ServiceContents} object * and start a new login session. * * This function speculatively fetches the session without locks * and fall back on synchronized method. */ private ServiceContents getServiceContents() throws VcException { ServiceContents svc = service; if (svc == null) { return getServiceContentsLocked(); } return svc; } /** * Dump debug info of the current service. */ public void dumpServiceStatus() { ServiceContents svc = service; if (svc != null) { svc.dumpStatus(); } else { logger.info("vcservice not connected last genCount=" + curGenCount); } } /** * Get the generation count of the current service context, if any. * @return Generation count, or null. */ public Long getServiceGenCount() { ServiceContents svc = service; if (svc != null) { return svc.genCount; } else { return null; } } /** * Clean up of the current VC service session. Physical vc logout * is deferred to VcCleanup thread. This call cannot fail. * @param generation count to match the session. * @param forced True if we always logout regardless of generation count. */ private synchronized void clearServiceContents(long generation, boolean forced, VcConnectionStatusChangeEvent eventType) { if (isConnected()) { if (forced || service.genCount == generation) { /* Report disconnect event before async logout to avoid event reorder. */ service.cleanup(); setService(null, eventType); AuAssert.check(!isConnected()); } } } /** * Drop connection on the floor without cleaning up. * Used for TESTING ONLY to drop VC connection. */ public synchronized void dropConnection() { try { if(isConnected()) { /* For testing, this is synchronous, no VcCleaner. */ VcContext.triggerEvent(VcConnectionStatusChangeEvent.VC_SESSION_DISCONNECTED, serviceName); service.sessionManager.logout(); } } catch (Exception e) { logger.info("Got exception trying to drop connection" + e); } } /** * Connect to VC if a session has not been established. * @return generation number for the VC session */ public long login() throws VcException { return getServiceContents().genCount; } /** * Force logout of the current VC session. */ public void logout() { clearServiceContents(-1, true, VcConnectionStatusChangeEvent.VC_SESSION_DISCONNECTED); } /** * Same as above, but allows to trigger a custom event. Used, for example, * during CMS shutdown. * @param eventType */ public void logout(VcConnectionStatusChangeEvent eventType) { clearServiceContents(-1, true, eventType); } /** * Logs out the session from VC server iff the genCount of the * current VC service matches the login generation of the caller. * @param generation Last known VC login generation count by the caller. */ public void logout(long generation) { clearServiceContents(generation, false, VcConnectionStatusChangeEvent.VC_SESSION_DISCONNECTED); } /** * Reestablish connection by logging in and out. * @param generation Last known VC login generation count * @return new VC login generation count * @throws VcException */ public long reconnect(long generation) throws VcException { logout(generation); return login(); } public ServiceInstance getServiceInstance() throws VcException { return getServiceContents().instance; } public ServiceInstanceContent getServiceInstanceContent() throws VcException { return getServiceContents().instanceContent; } public FileManager getFileManager() throws VcException { return getServiceContents().fileManager; } public VirtualDiskManager getVirtualDiskManager() throws VcException { return getServiceContents().vmdkManager; } public TaskManager getTaskManager() throws VcException { return getServiceContents().taskManager; } public OvfManager getOvfManager() throws VcException { return getServiceContents().ovfManager; } public PerformanceManager getPerfManager() throws VcException { return getServiceContents().perfManager; } public AlarmManager getAlarmManager() throws VcException { return getServiceContents().alarmManager; } public int getInstanceId() { return getServiceContents().getInstanceId(); } /** * @return default property collector for this client. * @throws VcException */ public PropertyCollector getPropertyCollector() throws VcException { return getServiceContents().propertyCollector; } /** * Given a <code>ManagedObjectReference</code> instance, uses it to fetch the * mapping <code>ManagedObject</code> * * @param moRef * The <code>ManagedObjectReference</code> instance. * @return ManagedObject instance */ public <T extends ManagedObject> T getManagedObject(ManagedObjectReference moRef) { return getServiceContents().<T>getManagedObject(moRef); } public String getExtensionKey() { return extKey; } public ExtensionManager getExtensionManager() { return getServiceContents().extensionManager; } public SessionManager getSessionManager() { return getServiceContents().sessionManager; } public String getLocale() { return locale; } }