JCLOUDS-1072: Add support for environment-specific run lists to Role

This commit is contained in:
rongallagher 2016-02-02 14:03:50 -05:00 committed by Ignasi Barrera
parent 2bd0550110
commit 27b3a844f8
4 changed files with 208 additions and 9 deletions

View File

@ -20,14 +20,18 @@ import static com.google.common.base.Preconditions.checkNotNull;
import static org.jclouds.chef.util.CollectionUtils.copyOfOrEmpty;
import java.beans.ConstructorProperties;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.jclouds.domain.JsonBall;
import org.jclouds.javax.annotation.Nullable;
import com.google.common.base.Function;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import com.google.gson.annotations.SerializedName;
/**
@ -44,6 +48,10 @@ public class Role {
private ImmutableMap.Builder<String, JsonBall> overrideAttributes = ImmutableMap.builder();
private ImmutableMap.Builder<String, JsonBall> defaultAttributes = ImmutableMap.builder();
private ImmutableList.Builder<String> runList = ImmutableList.builder();
// envRunList is a nested set of collections. The Immutable* classes in google collections don't appear to
// support this nested immutability, so the builder will utilize native collections as the envRunList is
// assembled. An immutable, nested map of collections will be assembled in the build() method.
private Map<String, List<String>> envRunList = new HashMap<String, List<String>>();
public Builder name(String name) {
this.name = checkNotNull(name, "name");
@ -85,8 +93,40 @@ public class Role {
return this;
}
public Builder envRunList(Map<String, List<String>> envRunList) {
this.envRunList.putAll(checkNotNull(envRunList, "envRunList"));
return this;
}
public Builder envRunList(String name, List<String> runList) {
this.envRunList.put(checkNotNull(name, "name"), checkNotNull(runList, "runList"));
return this;
}
public Builder envRunListElement(String name, String value) {
checkNotNull(name, "name");
checkNotNull(value, "value");
List<String> runList = this.envRunList.get(name);
if (runList == null) {
runList = new ArrayList<String>();
this.envRunList.put(name, runList);
}
runList.add(value);
return this;
}
public Role build() {
return new Role(name, description, defaultAttributes.build(), runList.build(), overrideAttributes.build());
// Assemble an immutable envRunList where each entry is an immutable list of entries.
Map<String, List<String>> immutableEnvRunList = Maps.transformValues(envRunList,
new Function<List<String>, List<String>>() {
@Override
public List<String> apply(List<String> input) {
return ImmutableList.copyOf(input);
}
});
return new Role(name, description, defaultAttributes.build(), runList.build(), overrideAttributes.build(),
immutableEnvRunList);
}
}
@ -98,6 +138,8 @@ public class Role {
private final Map<String, JsonBall> defaultAttributes;
@SerializedName("run_list")
private final List<String> runList;
@SerializedName("env_run_lists")
private Map<String, List<String>> envRunList;
// internal
@SerializedName("json_class")
@ -105,14 +147,17 @@ public class Role {
@SerializedName("chef_type")
private final String _chefType = "role";
@ConstructorProperties({ "name", "description", "default_attributes", "run_list", "override_attributes" })
@ConstructorProperties({ "name", "description", "default_attributes", "run_list", "override_attributes",
"env_run_lists" })
protected Role(String name, String description, @Nullable Map<String, JsonBall> defaultAttributes,
@Nullable List<String> runList, @Nullable Map<String, JsonBall> overrideAttributes) {
@Nullable List<String> runList, @Nullable Map<String, JsonBall> overrideAttributes,
@Nullable Map<String, List<String>> envRunList) {
this.name = name;
this.description = description;
this.defaultAttributes = copyOfOrEmpty(defaultAttributes);
this.runList = copyOfOrEmpty(runList);
this.overrideAttributes = copyOfOrEmpty(overrideAttributes);
this.envRunList = envRunList;
}
public String getName() {
@ -135,6 +180,10 @@ public class Role {
return runList;
}
public Map<String, List<String>> getEnvRunList() {
return envRunList;
}
@Override
public int hashCode() {
final int prime = 31;
@ -146,6 +195,7 @@ public class Role {
result = prime * result + ((name == null) ? 0 : name.hashCode());
result = prime * result + ((overrideAttributes == null) ? 0 : overrideAttributes.hashCode());
result = prime * result + ((runList == null) ? 0 : runList.hashCode());
result = prime * result + ((envRunList == null) ? 0 : envRunList.hashCode());
return result;
}
@ -193,13 +243,19 @@ public class Role {
return false;
} else if (!runList.equals(other.runList))
return false;
if (envRunList == null) {
if (other.envRunList != null)
return false;
} else if (!envRunList.equals(other.envRunList))
return false;
return true;
}
@Override
public String toString() {
return "Role [name=" + name + ", description=" + description + ", defaultAttributes=" + defaultAttributes
+ ", overrideAttributes=" + overrideAttributes + ", runList=" + runList + "]";
+ ", overrideAttributes=" + overrideAttributes + ", runList=" + runList
+ ", envRunList=" + this.envRunList + "]";
}
}

View File

@ -20,6 +20,7 @@ import static com.google.common.collect.Iterables.any;
import static com.google.common.collect.Iterables.isEmpty;
import static com.google.common.hash.Hashing.md5;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static org.jclouds.chef.domain.RoleTest.verifyRunListForEnvironment;
import static org.jclouds.util.Closeables2.closeQuietly;
import static org.jclouds.util.Predicates2.retry;
import static org.testng.Assert.assertEquals;
@ -243,12 +244,27 @@ public class ChefApiLiveTest extends BaseChefLiveTest {
@Test
public void testCreateRole() throws Exception {
String env1 = "env1";
String env2 = "env2";
String env1Alpha = "env1.alpha";
String env2Alpha = "env2.alpha";
String env2Bravo = "env2.bravo";
api.deleteRole(PREFIX);
api.createRole(Role.builder().name(PREFIX).runListElement("recipe[java]").build());
api.createRole(Role.builder().name(PREFIX).runListElement("recipe[java]")
.envRunListElement(env1, env1Alpha)
.envRunListElement(env2, env2Alpha)
.envRunListElement(env2, env2Bravo)
.build());
Role role = api.getRole(PREFIX);
assertNotNull(role, "Created role should not be null");
assertEquals(role.getName(), PREFIX);
assertEquals(role.getRunList(), Collections.singleton("recipe[java]"));
assertNotNull(role.getEnvRunList(), "envRunList should not be null");
assertEquals(role.getEnvRunList().size(), 2, "envRunList.size should be 2");
verifyRunListForEnvironment(role.getEnvRunList(), env1, env1Alpha);
verifyRunListForEnvironment(role.getEnvRunList(), env2, env2Alpha, env2Bravo);
}
@Test(dependsOnMethods = "testCreateRole")

View File

@ -357,13 +357,18 @@ public class ChefApiTest extends BaseRestAnnotationProcessingTest<ChefApi> {
public void testCreateRole() throws SecurityException, NoSuchMethodException, IOException {
Invokable<?, ?> method = method(ChefApi.class, "createRole", Role.class);
GeneratedHttpRequest httpRequest = processor.apply(Invocation.create(method,
ImmutableList.<Object> of(Role.builder().name("testrole").runListElement("recipe[java]").build())));
ImmutableList.<Object> of(Role.builder().name("testrole").runListElement("recipe[java]")
.envRunListElement("erl.one", "erl.one.alpha")
.envRunListElement("erl.one", "erl.one.bravo")
.envRunListElement("erl.two", "erl.two.alpha")
.envRunListElement("erl.two", "erl.two.bravo")
.build())));
assertRequestLineEquals(httpRequest, "POST http://localhost:4000/roles HTTP/1.1");
assertNonPayloadHeadersEqual(httpRequest, "Accept: application/json\nX-Chef-Version: " + ChefApiMetadata.DEFAULT_API_VERSION
+ "-test\n");
assertPayloadEquals(httpRequest, "{\"name\":\"testrole\",\"override_attributes\":{},\"default_attributes\":{},"
+ "\"run_list\":[\"recipe[java]\"],\"json_class\":\"Chef::Role\",\"chef_type\":\"role\"}",
+ "\"run_list\":[\"recipe[java]\"],\"env_run_lists\":{\"erl.one\":[\"erl.one.alpha\",\"erl.one.bravo\"],\"erl.two\":[\"erl.two.alpha\",\"erl.two.bravo\"]},\"json_class\":\"Chef::Role\",\"chef_type\":\"role\"}",
"application/json", false);
assertResponseParserClassEquals(method, httpRequest, ReleasePayloadAndReturn.class);
@ -377,13 +382,18 @@ public class ChefApiTest extends BaseRestAnnotationProcessingTest<ChefApi> {
public void testUpdateRole() throws SecurityException, NoSuchMethodException, IOException {
Invokable<?, ?> method = method(ChefApi.class, "updateRole", Role.class);
GeneratedHttpRequest httpRequest = processor.apply(Invocation.create(method,
ImmutableList.<Object> of(Role.builder().name("testrole").runListElement("recipe[java]").build())));
ImmutableList.<Object> of(Role.builder().name("testrole").runListElement("recipe[java]")
.envRunListElement("erl.one", "erl.one.alpha")
.envRunListElement("erl.one", "erl.one.bravo")
.envRunListElement("erl.two", "erl.two.alpha")
.envRunListElement("erl.two", "erl.two.bravo")
.build())));
assertRequestLineEquals(httpRequest, "PUT http://localhost:4000/roles/testrole HTTP/1.1");
assertNonPayloadHeadersEqual(httpRequest, "Accept: application/json\nX-Chef-Version: " + ChefApiMetadata.DEFAULT_API_VERSION
+ "-test\n");
assertPayloadEquals(httpRequest, "{\"name\":\"testrole\",\"override_attributes\":{},\"default_attributes\":{},"
+ "\"run_list\":[\"recipe[java]\"],\"json_class\":\"Chef::Role\",\"chef_type\":\"role\"}",
+ "\"run_list\":[\"recipe[java]\"],\"env_run_lists\":{\"erl.one\":[\"erl.one.alpha\",\"erl.one.bravo\"],\"erl.two\":[\"erl.two.alpha\",\"erl.two.bravo\"]},\"json_class\":\"Chef::Role\",\"chef_type\":\"role\"}",
"application/json", false);
assertResponseParserClassEquals(method, httpRequest, ParseJson.class);

View File

@ -0,0 +1,117 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.jclouds.chef.domain;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNotNull;
import static org.testng.Assert.assertNotEquals;
import static org.testng.Assert.assertTrue;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import org.testng.annotations.Test;
/**
* Tests behaviors of {@code Role}.
*/
@Test(groups = { "unit" })
public class RoleTest {
@Test(expectedExceptions = NullPointerException.class)
public void canNotAddEnvRunListMapThatIsNull() {
Role.builder().envRunList((Map<String, List<String>>) null);
}
@Test(expectedExceptions = NullPointerException.class)
public void canNotAddRunListForEnvironmentThatIsNull() {
Role.builder().envRunList("does.not.matter", (List<String>) null);
}
@Test(expectedExceptions = NullPointerException.class)
public void canNotUseNullEnvNameWhenAddingEnvRunListEntry() {
Role.builder().envRunListElement((String) null, "does.not.matter");
}
@Test(expectedExceptions = NullPointerException.class)
public void canNotUseNullEntryWhenAddingEnvRunListEntry() {
Role.builder().envRunListElement("does.not.matter", (String) null);
}
public void multipleEnvRunListsCanBePopulated() {
String env1 = "env1";
String env2 = "env2";
String env1Alpha = "env1.alpha";
String env2Alpha = "env2.alpha";
String env2Bravo = "env2.bravo";
Role role = Role.builder().envRunListElement(env1, env1Alpha).envRunListElement(env2, env2Alpha)
.envRunListElement(env2, env2Bravo).build();
Map<String, List<String>> envRunList = role.getEnvRunList();
assertNotNull(envRunList, "envRunList");
assertEquals(envRunList.size(), 2, "envRunList.size");
verifyRunListForEnvironment(envRunList, env1, env1Alpha);
verifyRunListForEnvironment(envRunList, env2, env2Alpha, env2Bravo);
}
@Test(expectedExceptions = UnsupportedOperationException.class)
public void envRunListOnNewlyBuiltRoleIsImmutable() {
String env = "env";
Role role = Role.builder().envRunListElement(env, env + "1").build();
role.getEnvRunList().put("does.not.matter", new ArrayList<String>());
}
@Test(expectedExceptions = UnsupportedOperationException.class)
public void envRunListEntriesOnNewlyBuiltRoleIsImmutable() {
String env = "env";
Role role = Role.builder().envRunListElement(env, env + "1").build();
role.getEnvRunList().get(env).add("does.not.matter");
}
public void rolesWithSameEnvRunListAreEqual() {
String env = "env";
String entry = "entry";
Role role1 = Role.builder().envRunListElement(env, entry).build();
Role role2 = Role.builder().envRunListElement(env, entry).build();
assertEquals(role1.hashCode(), role2.hashCode(), "hashCodes should be equal");
assertEquals(role1, role2, "role1 should equal role2");
assertEquals(role2, role1, "role2 should equal role1");
}
public void rolesWithDifferentEnvRunListAreNotEqual() {
String env = "env";
String entry = "entry";
Role role1 = Role.builder().envRunListElement(env, entry.toUpperCase()).build();
Role role2 = Role.builder().envRunListElement(env, entry.toLowerCase()).build();
assertNotEquals(role1.hashCode(), role2.hashCode(), "hashCodes should not be equal");
assertNotEquals(role1, role2, "role1 should not equal role2");
assertNotEquals(role2, role1, "role2 should not equal role1");
}
public static void verifyRunListForEnvironment(Map<String, List<String>> envRunList, String envName,
String... expectedEntries) {
assertTrue(envRunList.containsKey(envName), "envRunList contains " + envName);
assertEquals(envRunList.get(envName).size(), expectedEntries.length, "envRunList size for '" + envName);
assertTrue(envRunList.get(envName).containsAll(Arrays.asList(expectedEntries)), "envRunList for e1 contains "
+ Arrays.asList(expectedEntries));
}
}