/* * Copyright 2012 Nodeable Inc * * 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.streamreduce; import com.google.common.collect.ImmutableSet; import com.google.inject.Module; import com.streamreduce.connections.AuthType; import com.streamreduce.connections.CloudProvider; import com.streamreduce.core.model.Account; import com.streamreduce.core.model.Connection; import com.streamreduce.core.model.ConnectionCredentials; import com.streamreduce.core.model.Role; import com.streamreduce.core.model.User; import com.streamreduce.core.service.UserService; import com.streamreduce.core.service.exception.UserNotFoundException; import com.streamreduce.rest.dto.response.ConnectionResponseDTO; import com.streamreduce.rest.resource.ErrorMessage; import com.streamreduce.security.Roles; import com.streamreduce.util.ConnectionUtils; import net.sf.json.JSONObject; import org.apache.commons.io.IOUtils; import org.apache.http.Header; import org.apache.http.HttpResponse; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpDelete; import org.apache.http.client.methods.HttpEntityEnclosingRequestBase; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpHead; import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpPut; import org.apache.http.client.methods.HttpRequestBase; import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.params.HttpProtocolParams; import org.codehaus.jackson.map.ObjectMapper; import org.codehaus.jackson.map.type.TypeFactory; import org.codehaus.jackson.type.JavaType; import org.jclouds.aws.ec2.reference.AWSEC2Constants; import org.jclouds.compute.ComputeService; import org.jclouds.compute.ComputeServiceContextFactory; import org.jclouds.compute.domain.NodeMetadata; import org.jclouds.scriptbuilder.domain.Statement; import org.jclouds.scriptbuilder.statements.login.AdminAccess; import org.jclouds.sshj.config.SshjSshClientModule; import org.junit.After; import org.junit.Before; import org.springframework.beans.factory.annotation.Autowired; import javax.ws.rs.core.MediaType; import java.io.IOException; import java.util.Properties; import java.util.ResourceBundle; import java.util.Set; import java.util.UUID; import static com.google.common.collect.Iterables.getOnlyElement; import static org.jclouds.compute.options.TemplateOptions.Builder.runScript; import static org.junit.Assert.assertEquals; public abstract class AbstractInContainerTestCase extends AbstractServiceTestCase { public final ResourceBundle applicationProperties = ResourceBundle.getBundle("application"); public final ResourceBundle cloudProperties = ResourceBundle.getBundle("cloud"); public final ResourceBundle gitHubProperties = ResourceBundle.getBundle("github"); public final ResourceBundle jiraProperties = ResourceBundle.getBundle("jira"); public final ResourceBundle serverProperties = ResourceBundle.getBundle("server"); public final String accountBaseUrl = getPublicApiUrlBase() + "/account"; public final String adminBaseUrl = getPrivateUrlBase() + "/admin"; public final String connectionsBaseUrl = getPublicApiUrlBase() + "/connections"; public final String messagesBaseUrl = getPublicApiUrlBase() + "/messages"; public final String imgBaseUrl = getPublicUrlBase() + "/gateway/custom"; public final String inventoryItemBaseUrl = getPublicApiUrlBase() + "/inventory"; public final String usersBaseUrl = getPublicApiUrlBase() + "/user"; public final String testJcloudsInstanceGroup = "nodeable-test"; public final String testUsername = "in_container_test_user@nodeable.com"; public enum AuthTokenType { API, GATEWAY } protected Account testAccount; protected User testUser; @Autowired protected UserService userService; @Override @Before public void setUp() throws Exception { super.setUp(); setUp(true); } protected void setUp(boolean startAMQ) throws Exception { // Create the test account and user // sometimes we fail to delete this... so don't just try it willy nilly. User user = null; try { user = userService.getUser(testUsername); testUser = user; testAccount = testUser.getAccount(); } catch (UserNotFoundException unfe) { } if (user == null) { testAccount = new Account.Builder() .name("In Container Test Account") .description("This account is for testing in container.") .url("http://nodeable.com") .build(); testAccount = userService.createAccount(testAccount); Set<Role> roles = userService.getAdminRoles(); roles.add(applicationManager.getSecurityService().findRole(Roles.ADMIN_ROLE)); testUser = new User.Builder() .username(testUsername) .password(testUsername) .accountLocked(false) .fullname("In Container Test User") .userStatus(User.UserStatus.ACTIVATED) .account(testAccount) .roles(roles) .accountOriginator(true) .alias(UUID.randomUUID().toString()) .build(); testUser = userService.createUser(testUser); } // TODO: Fix problem where local SecurityService doesn't see users logged in via RESTful API } @Override @After public void tearDown() throws Exception { // Delete the test account and user if (userService != null) { userService.deleteUser(testUser); userService.deleteAccount(testAccount.getId()); } super.tearDown(); } /** * Returns the user created for this test run. * * @return the user */ public User getTestUser() { return testUser; } /** * Returns the base url for private endpoints. * * @return the private endpoint base url */ public String getPrivateUrlBase() { String hostname = serverProperties.getString("server.host"); String port = serverProperties.getString("server.port.private"); return "http://" + hostname + ":" + port; } /** * Returns the base url for public endpoints. * * @return the public endpoint base url */ public String getPublicUrlBase() { String hostname = serverProperties.getString("server.host"); String port = serverProperties.getString("server.port.public"); return "http://" + hostname + ":" + port; } /** * Returns the base url for public API endpoints. * * @return the public api endpoint base url */ public String getPublicApiUrlBase() { return getPublicUrlBase() + "/api"; } /** * Logs in the given user and returns the user's authentication token. * * @param username the username to login as * @param password the password for the username * @return the user's authentication token * @throws Exception if something goes wrong */ public String login(String username, String password) throws Exception { HttpClient httpClient = new DefaultHttpClient(); // Set the User-Agent to be safe httpClient.getParams().setParameter(HttpProtocolParams.USER_AGENT, Constants.NODEABLE_HTTP_USER_AGENT); HttpPost post = new HttpPost(getPublicUrlBase() + "/authentication/login"); // HttpState state = httpClient.getState(); String authnToken; try { // Login is done via Basic Authentication at this time // state.setCredentials(new AuthScope(null, AuthScope.ANY_PORT, null, AuthScope.ANY_SCHEME), // new UsernamePasswordCredentials(username, password)); HttpResponse httpReponse = httpClient.execute(post); Header authHeader = httpReponse.getFirstHeader(Constants.NODEABLE_AUTH_TOKEN); if (authHeader != null) { authnToken = httpReponse.getFirstHeader(Constants.NODEABLE_AUTH_TOKEN).getValue(); } else { String response = IOUtils.toString(httpReponse.getEntity().getContent()); try { ErrorMessage em = jsonToObject(response, TypeFactory.defaultInstance().constructType(ErrorMessage.class)); throw new Exception(em.getErrorMessage()); } catch (Exception e) { throw new Exception("Unable to login: " + response); } } } finally { post.releaseConnection(); } return authnToken; } /** * Logs a user out. * * @param authnToken the user's authentication url * @throws Exception if something goes wrong */ public void logout(String authnToken) throws Exception { makeRequest(getPublicUrlBase() + "/api/user/logout", "GET", null, authnToken); } public String makeRequest(String url, String method, Object data, String authnToken) throws Exception { return makeRequest(url, method, data, authnToken, AuthTokenType.API); } /** * Makes a request to the given url using the given method and possibly submitting * the given data. If you need the request to be an authenticated request, pass in * your authentication token as well. * * @param url the url for the request * @param method the method to use for the request * @param data the data, if any, for the request * @param authnToken the token retrieved during authentication * @param type API or GATEWAY, tells us the auth token key to use * @return the response as string (This is either the response payload or the status code if no payload is sent) * @throws Exception if something goes wrong */ public String makeRequest(String url, String method, Object data, String authnToken, AuthTokenType type) throws Exception { HttpClient httpClient = new DefaultHttpClient(); HttpRequestBase request; String actualPayload; // Set the User-Agent to be safe httpClient.getParams().setParameter(HttpProtocolParams.USER_AGENT, Constants.NODEABLE_HTTP_USER_AGENT); // Create the request object if (method.equals("DELETE")) { request = new HttpDelete(url); } else if (method.equals("GET")) { request = new HttpGet(url); } else if (method.equals("POST")) { request = new HttpPost(url); } else if (method.equals("PUT")) { request = new HttpPut(url); } else if (method.equals("HEAD")) { request = new HttpHead(url); } else { throw new IllegalArgumentException("The method you specified is not supported."); } // Put data into the request for POST and PUT requests if (method.equals("POST") || method.equals("PUT")) { HttpEntityEnclosingRequestBase eeMethod = (HttpEntityEnclosingRequestBase) request; String requestBody = data instanceof JSONObject ? ((JSONObject) data).toString() : new ObjectMapper().writeValueAsString(data); eeMethod.setEntity(new StringEntity(requestBody, MediaType.APPLICATION_JSON, "UTF-8")); } // Add the authentication token to the request if (authnToken != null) { if (type.equals(AuthTokenType.API)) { request.addHeader(Constants.NODEABLE_AUTH_TOKEN, authnToken); } else if (type.equals(AuthTokenType.GATEWAY)) { request.addHeader(Constants.NODEABLE_API_KEY, authnToken); } else { throw new Exception("Unsupported Type of " + type + " for authToken " + authnToken); } } // Execute the request try { HttpResponse response = httpClient.execute(request); String payload = IOUtils.toString(response.getEntity().getContent()); // To work around HEAD, where we cannot receive a payload, and other scenarios where the payload could // be empty, let's stuff the response status code into the response. actualPayload = payload != null && payload.length() > 0 ? payload : Integer.toString(response.getStatusLine().getStatusCode()); } finally { request.releaseConnection(); } return actualPayload; } /** * Returns an object from the passed in JSON string based on the JavaType passed in. * * @param <T> Variable type based on the JavaType passed in * @param json the JSON string to parse * @param type the type to return * @return the parsed object * @throws Exception if something goes wrong */ @SuppressWarnings("unchecked") public <T> T jsonToObject(String json, JavaType type) throws Exception { ObjectMapper om = new ObjectMapper(); try { return (T) om.readValue(json, type); } catch (Exception e) { try { ErrorMessage em = om.readValue(json, ErrorMessage.class); throw new Exception(em.getErrorMessage()); } catch (IOException e2) { throw new IOException(e.getMessage() + ": " + json); } } } /** * Creates a dummy AWS EC2 node used for testing inventory. * * @param cloud the cloud to create the node in/with * @param securityGroup the security group to create the node in/with * @return the created node * @throws Exception if something goes wrong */ public NodeMetadata createDummyAWSEC2Node(ConnectionResponseDTO cloud, String securityGroup) throws Exception { ComputeService computeService = getComputeService(cloud); // Create a node Statement bootInstructions = AdminAccess.standard(); NodeMetadata newNode = getOnlyElement(computeService.createNodesInGroup(securityGroup, 1, runScript(bootInstructions))); return newNode; } public ComputeService getComputeService(ConnectionResponseDTO cloud) { CloudProvider provider = (CloudProvider) ConnectionUtils.getProviderFromId(CloudProvider.TYPE, cloud.getProviderId()); Properties overrides = new Properties(); // Choose from only Amazon provided AMIs overrides.setProperty(AWSEC2Constants.PROPERTY_EC2_AMI_QUERY, "owner-id=137112412989;state=available;image-type=machine"); overrides.setProperty(AWSEC2Constants.PROPERTY_EC2_CC_AMI_QUERY, ""); // Inject the SSH implementation Iterable<Module> modules = ImmutableSet.<Module>of(new SshjSshClientModule()); return new ComputeServiceContextFactory().createContext(provider.getComputeId(), cloudProperties.getString("nodeable.aws.accessKeyId"), cloudProperties.getString("nodeable.aws.secretKey"), modules, overrides).getComputeService(); } public void refreshCloudInventoryItemCache(Connection cloud, String authnToken) throws Exception { assertEquals("200", makeRequest(connectionsBaseUrl + "/" + cloud.getId() + "/inventory/refresh", "POST", null, authnToken)); } public ConnectionResponseDTO createConnection(String authnToken, String alias, String description, ConnectionCredentials credentials, String providerId, String url, String type, AuthType authType ) throws Exception { JSONObject json = new JSONObject(); JSONObject credentialsJSON = new JSONObject(); credentialsJSON.put("credential", credentials.getCredential()); credentialsJSON.put("identity", credentials.getIdentity()); json.put("alias", alias); json.put("description", description); json.put("credentials", credentialsJSON); json.put("providerId", providerId); json.put("url", url); json.put("type", type); json.put("authType", authType); return jsonToObject(makeRequest(connectionsBaseUrl, "POST", json, authnToken), TypeFactory.defaultInstance().constructType(ConnectionResponseDTO.class)); } }