package com.sequenceiq.cloudbreak.client; import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.Collections; import java.util.concurrent.TimeUnit; import javax.ws.rs.NotAuthorizedException; import javax.ws.rs.client.Client; import javax.ws.rs.client.WebTarget; import javax.ws.rs.core.Form; import javax.ws.rs.core.MultivaluedHashMap; import javax.ws.rs.core.MultivaluedMap; import org.glassfish.jersey.client.proxy.WebResourceFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.sequenceiq.cloudbreak.api.CoreApi; import com.sequenceiq.cloudbreak.api.endpoint.AccountPreferencesEndpoint; import com.sequenceiq.cloudbreak.api.endpoint.BlueprintEndpoint; import com.sequenceiq.cloudbreak.api.endpoint.ClusterEndpoint; import com.sequenceiq.cloudbreak.api.endpoint.ConnectorEndpoint; import com.sequenceiq.cloudbreak.api.endpoint.ConstraintTemplateEndpoint; import com.sequenceiq.cloudbreak.api.endpoint.CredentialEndpoint; import com.sequenceiq.cloudbreak.api.endpoint.EventEndpoint; import com.sequenceiq.cloudbreak.api.endpoint.LdapConfigEndpoint; import com.sequenceiq.cloudbreak.api.endpoint.NetworkEndpoint; import com.sequenceiq.cloudbreak.api.endpoint.RdsConfigEndpoint; import com.sequenceiq.cloudbreak.api.endpoint.RecipeEndpoint; import com.sequenceiq.cloudbreak.api.endpoint.SecurityGroupEndpoint; import com.sequenceiq.cloudbreak.api.endpoint.SssdConfigEndpoint; import com.sequenceiq.cloudbreak.api.endpoint.StackEndpoint; import com.sequenceiq.cloudbreak.api.endpoint.SubscriptionEndpoint; import com.sequenceiq.cloudbreak.api.endpoint.TemplateEndpoint; import com.sequenceiq.cloudbreak.api.endpoint.TopologyEndpoint; import com.sequenceiq.cloudbreak.api.endpoint.UsageEndpoint; import com.sequenceiq.cloudbreak.api.endpoint.UserEndpoint; import com.sequenceiq.cloudbreak.api.endpoint.UtilEndpoint; import net.jodah.expiringmap.ExpirationPolicy; import net.jodah.expiringmap.ExpiringMap; public class CloudbreakClient { private static final Logger LOGGER = LoggerFactory.getLogger(CloudbreakClient.class); private static final Form EMPTY_FORM = new Form(); private static final String TOKEN_KEY = "TOKEN"; private static final double TOKEN_EXPIRATION_FACTOR = 0.9; private final ExpiringMap<String, String> tokenCache; private final Client client; private final IdentityClient identityClient; private final String cloudbreakAddress; private String user; private String password; private String secret; private WebTarget t; private EndpointWrapper<CredentialEndpoint> credentialEndpoint; private EndpointWrapper<TemplateEndpoint> templateEndpoint; private EndpointWrapper<TopologyEndpoint> topologyEndpoint; private EndpointWrapper<UsageEndpoint> usageEndpoint; private EndpointWrapper<UserEndpoint> userEndpoint; private EndpointWrapper<EventEndpoint> eventEndpoint; private EndpointWrapper<SecurityGroupEndpoint> securityGroupEndpoint; private EndpointWrapper<StackEndpoint> stackEndpoint; private EndpointWrapper<SubscriptionEndpoint> subscriptionEndpoint; private EndpointWrapper<NetworkEndpoint> networkEndpoint; private EndpointWrapper<RecipeEndpoint> recipeEndpoint; private EndpointWrapper<SssdConfigEndpoint> sssdConfigEndpoint; private EndpointWrapper<RdsConfigEndpoint> rdsConfigEndpoint; private EndpointWrapper<AccountPreferencesEndpoint> accountPreferencesEndpoint; private EndpointWrapper<BlueprintEndpoint> blueprintEndpoint; private EndpointWrapper<ClusterEndpoint> clusterEndpoint; private EndpointWrapper<ConnectorEndpoint> connectorEndpoint; private EndpointWrapper<ConstraintTemplateEndpoint> constraintTemplateEndpoint; private EndpointWrapper<UtilEndpoint> utilEndpoint; private EndpointWrapper<LdapConfigEndpoint> ldapConfigEndpoint; private CloudbreakClient(String cloudbreakAddress, String identityServerAddress, String user, String password, String clientId, ConfigKey configKey) { this.client = RestClientUtil.get(configKey); this.cloudbreakAddress = cloudbreakAddress; this.identityClient = new IdentityClient(identityServerAddress, clientId, configKey); this.user = user; this.password = password; this.tokenCache = configTokenCache(); refresh(); LOGGER.info("CloudbreakClient has been created with user / pass. cloudbreak: {}, identity: {}, clientId: {}, configKey: {}", cloudbreakAddress, identityServerAddress, clientId, configKey); } private CloudbreakClient(String cloudbreakAddress, String identityServerAddress, String secret, String clientId, ConfigKey configKey) { this.client = RestClientUtil.get(configKey); this.cloudbreakAddress = cloudbreakAddress; this.identityClient = new IdentityClient(identityServerAddress, clientId, configKey); this.secret = secret; this.tokenCache = configTokenCache(); refresh(); LOGGER.info("CloudbreakClient has been created with a secret. cloudbreak: {}, identity: {}, clientId: {}, configKey: {}", cloudbreakAddress, identityServerAddress, clientId, configKey); } private ExpiringMap<String, String> configTokenCache() { return ExpiringMap.builder().variableExpiration().expirationPolicy(ExpirationPolicy.CREATED).build(); } private void refresh() { refresh(false); } synchronized void refresh(boolean force) { String token = tokenCache.get(TOKEN_KEY); if (force || token == null) { AccessToken accessToken; if (secret != null) { accessToken = identityClient.getToken(secret); } else { accessToken = identityClient.getToken(user, password); } token = accessToken.getToken(); int exp = (int) (accessToken.getExpiresIn() * TOKEN_EXPIRATION_FACTOR); LOGGER.info("Token has been renewed and expires in {} seconds", exp); tokenCache.put(TOKEN_KEY, accessToken.getToken(), ExpirationPolicy.CREATED, exp, TimeUnit.SECONDS); renewEndpoints(token); } } private void renewEndpoints(String token) { MultivaluedMap<String, Object> headers = new MultivaluedHashMap<>(); headers.add("Authorization", "Bearer " + token); this.t = client.target(cloudbreakAddress).path(CoreApi.API_ROOT_CONTEXT); this.credentialEndpoint = newResource(this.credentialEndpoint, CredentialEndpoint.class, headers); this.templateEndpoint = newResource(this.templateEndpoint, TemplateEndpoint.class, headers); this.topologyEndpoint = newResource(this.topologyEndpoint, TopologyEndpoint.class, headers); this.usageEndpoint = newResource(this.usageEndpoint, UsageEndpoint.class, headers); this.eventEndpoint = newResource(this.eventEndpoint, EventEndpoint.class, headers); this.securityGroupEndpoint = newResource(this.securityGroupEndpoint, SecurityGroupEndpoint.class, headers); this.stackEndpoint = newResource(this.stackEndpoint, StackEndpoint.class, headers); this.subscriptionEndpoint = newResource(this.subscriptionEndpoint, SubscriptionEndpoint.class, headers); this.networkEndpoint = newResource(this.networkEndpoint, NetworkEndpoint.class, headers); this.recipeEndpoint = newResource(this.recipeEndpoint, RecipeEndpoint.class, headers); this.sssdConfigEndpoint = newResource(this.sssdConfigEndpoint, SssdConfigEndpoint.class, headers); this.rdsConfigEndpoint = newResource(this.rdsConfigEndpoint, RdsConfigEndpoint.class, headers); this.accountPreferencesEndpoint = newResource(this.accountPreferencesEndpoint, AccountPreferencesEndpoint.class, headers); this.blueprintEndpoint = newResource(this.blueprintEndpoint, BlueprintEndpoint.class, headers); this.clusterEndpoint = newResource(this.clusterEndpoint, ClusterEndpoint.class, headers); this.connectorEndpoint = newResource(this.connectorEndpoint, ConnectorEndpoint.class, headers); this.userEndpoint = newResource(this.userEndpoint, UserEndpoint.class, headers); this.constraintTemplateEndpoint = newResource(this.constraintTemplateEndpoint, ConstraintTemplateEndpoint.class, headers); this.utilEndpoint = newResource(this.utilEndpoint, UtilEndpoint.class, headers); this.ldapConfigEndpoint = newResource(this.ldapConfigEndpoint, LdapConfigEndpoint.class, headers); LOGGER.info("Endpoints have been renewed for CloudbreakClient"); } private <C> EndpointWrapper<C> newResource(EndpointWrapper<C> endpointWrapper, final Class<C> resourceInterface, MultivaluedMap<String, Object> headers) { EndpointWrapper<C> result = endpointWrapper; if (result == null) { result = new EndpointWrapper<C>(resourceInterface); } result.setEndpoint(WebResourceFactory.newResource(resourceInterface, t, false, headers, Collections.emptyList(), EMPTY_FORM)); return result; } public CredentialEndpoint credentialEndpoint() { refresh(); return credentialEndpoint.getEndpointProxy(); } public TemplateEndpoint templateEndpoint() { refresh(); return templateEndpoint.getEndpointProxy(); } public TopologyEndpoint topologyEndpoint() { refresh(); return topologyEndpoint.getEndpointProxy(); } public UsageEndpoint usageEndpoint() { refresh(); return usageEndpoint.getEndpointProxy(); } public UserEndpoint userEndpoint() { refresh(); return userEndpoint.getEndpointProxy(); } public EventEndpoint eventEndpoint() { refresh(); return eventEndpoint.getEndpointProxy(); } public SecurityGroupEndpoint securityGroupEndpoint() { refresh(); return securityGroupEndpoint.getEndpointProxy(); } public StackEndpoint stackEndpoint() { refresh(); return stackEndpoint.getEndpointProxy(); } public SubscriptionEndpoint subscriptionEndpoint() { refresh(); return subscriptionEndpoint.getEndpointProxy(); } public NetworkEndpoint networkEndpoint() { refresh(); return networkEndpoint.getEndpointProxy(); } public RecipeEndpoint recipeEndpoint() { refresh(); return recipeEndpoint.getEndpointProxy(); } public SssdConfigEndpoint sssdConfigEndpoint() { refresh(); return sssdConfigEndpoint.getEndpointProxy(); } public RdsConfigEndpoint rdsConfigEndpoint() { refresh(); return rdsConfigEndpoint.getEndpointProxy(); } public AccountPreferencesEndpoint accountPreferencesEndpoint() { refresh(); return accountPreferencesEndpoint.getEndpointProxy(); } public BlueprintEndpoint blueprintEndpoint() { refresh(); return blueprintEndpoint.getEndpointProxy(); } public ClusterEndpoint clusterEndpoint() { refresh(); return clusterEndpoint.getEndpointProxy(); } public ConnectorEndpoint connectorEndpoint() { refresh(); return connectorEndpoint.getEndpointProxy(); } public LdapConfigEndpoint ldapConfigEndpoint() { refresh(); return ldapConfigEndpoint.getEndpointProxy(); } public ConstraintTemplateEndpoint constraintTemplateEndpoint() { return constraintTemplateEndpoint.getEndpointProxy(); } public UtilEndpoint utilEndpoint() { refresh(); return utilEndpoint.getEndpointProxy(); } private class EndpointWrapper<C> { private Class<C> endpointType; private C endpoint; private C endPointProxy; private InvocationHandler invocationHandler = new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object result; try { result = method.invoke(endpoint, args); } catch (InvocationTargetException ite) { if (ite.getTargetException() instanceof NotAuthorizedException) { LOGGER.warn("Unauthorized request on {}.{}(): {}, refreshing the auth token and try again...", endpointType.getCanonicalName(), method.getName(), ite.getTargetException().getMessage()); refresh(true); try { result = method.invoke(endpoint, args); } catch (InvocationTargetException iite) { throw iite.getTargetException(); } } else { throw ite.getTargetException(); } } return result; } }; private EndpointWrapper(Class<C> endpointType) { endPointProxy = (C) Proxy.newProxyInstance(endpointType.getClassLoader(), new Class[]{endpointType}, invocationHandler); this.endpointType = endpointType; } private void setEndpoint(C endpoint) { this.endpoint = endpoint; } private C getEndpointProxy() { return endPointProxy; } } public static class CloudbreakClientBuilder { private final String cloudbreakAddress; private final String identityServerAddress; private final String clientId; private String user; private String password; private String secret; private boolean debug; private boolean secure = true; public CloudbreakClientBuilder(String cloudbreakAddress, String identityServerAddress, String clientId) { this.cloudbreakAddress = cloudbreakAddress; this.identityServerAddress = identityServerAddress; this.clientId = clientId; } public CloudbreakClientBuilder withCredential(String user, String password) { this.user = user; this.password = password; return this; } public CloudbreakClientBuilder withSecret(String secret) { this.secret = secret; return this; } public CloudbreakClientBuilder withDebug(boolean debug) { this.debug = debug; return this; } public CloudbreakClientBuilder withCertificateValidation(boolean secure) { this.secure = secure; return this; } public CloudbreakClient build() { ConfigKey configKey = new ConfigKey(secure, debug); if (secret != null) { return new CloudbreakClient(cloudbreakAddress, identityServerAddress, secret, clientId, configKey); } else { return new CloudbreakClient(cloudbreakAddress, identityServerAddress, user, password, clientId, configKey); } } } }