From 27b3a844f89e311b2e25fc27e83659b3637dec2b Mon Sep 17 00:00:00 2001 From: rongallagher Date: Tue, 2 Feb 2016 14:03:50 -0500 Subject: [PATCH] JCLOUDS-1072: Add support for environment-specific run lists to Role --- .../java/org/jclouds/chef/domain/Role.java | 64 +++++++++- .../org/jclouds/chef/ChefApiLiveTest.java | 18 ++- .../java/org/jclouds/chef/ChefApiTest.java | 18 ++- .../org/jclouds/chef/domain/RoleTest.java | 117 ++++++++++++++++++ 4 files changed, 208 insertions(+), 9 deletions(-) create mode 100644 apis/chef/src/test/java/org/jclouds/chef/domain/RoleTest.java diff --git a/apis/chef/src/main/java/org/jclouds/chef/domain/Role.java b/apis/chef/src/main/java/org/jclouds/chef/domain/Role.java index d51dc1e37f..ac37bec8d4 100644 --- a/apis/chef/src/main/java/org/jclouds/chef/domain/Role.java +++ b/apis/chef/src/main/java/org/jclouds/chef/domain/Role.java @@ -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 overrideAttributes = ImmutableMap.builder(); private ImmutableMap.Builder defaultAttributes = ImmutableMap.builder(); private ImmutableList.Builder 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> envRunList = new HashMap>(); public Builder name(String name) { this.name = checkNotNull(name, "name"); @@ -85,8 +93,40 @@ public class Role { return this; } + public Builder envRunList(Map> envRunList) { + this.envRunList.putAll(checkNotNull(envRunList, "envRunList")); + return this; + } + + public Builder envRunList(String name, List 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 runList = this.envRunList.get(name); + if (runList == null) { + runList = new ArrayList(); + 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> immutableEnvRunList = Maps.transformValues(envRunList, + new Function, List>() { + @Override + public List apply(List 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 defaultAttributes; @SerializedName("run_list") private final List runList; + @SerializedName("env_run_lists") + private Map> 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 defaultAttributes, - @Nullable List runList, @Nullable Map overrideAttributes) { + @Nullable List runList, @Nullable Map overrideAttributes, + @Nullable Map> 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> 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 + "]"; } } diff --git a/apis/chef/src/test/java/org/jclouds/chef/ChefApiLiveTest.java b/apis/chef/src/test/java/org/jclouds/chef/ChefApiLiveTest.java index c00e0d9854..9f46f782ce 100644 --- a/apis/chef/src/test/java/org/jclouds/chef/ChefApiLiveTest.java +++ b/apis/chef/src/test/java/org/jclouds/chef/ChefApiLiveTest.java @@ -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") diff --git a/apis/chef/src/test/java/org/jclouds/chef/ChefApiTest.java b/apis/chef/src/test/java/org/jclouds/chef/ChefApiTest.java index 99a2da05ea..b24a130c80 100644 --- a/apis/chef/src/test/java/org/jclouds/chef/ChefApiTest.java +++ b/apis/chef/src/test/java/org/jclouds/chef/ChefApiTest.java @@ -357,13 +357,18 @@ public class ChefApiTest extends BaseRestAnnotationProcessingTest { public void testCreateRole() throws SecurityException, NoSuchMethodException, IOException { Invokable method = method(ChefApi.class, "createRole", Role.class); GeneratedHttpRequest httpRequest = processor.apply(Invocation.create(method, - ImmutableList. of(Role.builder().name("testrole").runListElement("recipe[java]").build()))); + ImmutableList. 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 { public void testUpdateRole() throws SecurityException, NoSuchMethodException, IOException { Invokable method = method(ChefApi.class, "updateRole", Role.class); GeneratedHttpRequest httpRequest = processor.apply(Invocation.create(method, - ImmutableList. of(Role.builder().name("testrole").runListElement("recipe[java]").build()))); + ImmutableList. 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); diff --git a/apis/chef/src/test/java/org/jclouds/chef/domain/RoleTest.java b/apis/chef/src/test/java/org/jclouds/chef/domain/RoleTest.java new file mode 100644 index 0000000000..59f92bf310 --- /dev/null +++ b/apis/chef/src/test/java/org/jclouds/chef/domain/RoleTest.java @@ -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>) null); + } + + @Test(expectedExceptions = NullPointerException.class) + public void canNotAddRunListForEnvironmentThatIsNull() { + Role.builder().envRunList("does.not.matter", (List) 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> 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()); + } + + @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> 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)); + } +}