Allows extensions to fall back on using an optional name in keystone/openstack

This commit is contained in:
Zack Shoylev 2015-12-02 16:02:43 -06:00
parent b64d05abb9
commit 415a8a6600
9 changed files with 116 additions and 29 deletions

View File

@ -92,15 +92,15 @@ public class KeystoneHttpApiModule extends HttpApiModule<KeystoneApi> {
}
// Allow providers to cleanly contribute their own aliases
public static MapBinder<URI, URI> aliasBinder(Binder binder) {
return MapBinder.newMapBinder(binder, URI.class, URI.class, Aliases.class).permitDuplicates();
public static MapBinder<URI, URI> namespaceAliasBinder(Binder binder) {
return MapBinder.newMapBinder(binder, URI.class, URI.class, NamespaceAliases.class).permitDuplicates();
}
@Override
protected void configure() {
bind(ImplicitOptionalConverter.class).to(PresentWhenExtensionAnnotationNamespaceEqualsAnyNamespaceInExtensionsSet.class);
super.configure();
aliasBinder(binder());
namespaceAliasBinder(binder());
}
@Provides

View File

@ -26,6 +26,6 @@ import javax.inject.Qualifier;
@Retention(value = RetentionPolicy.RUNTIME)
@Target(value = { ElementType.TYPE, ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD })
@Qualifier
public @interface Aliases {
public @interface NamespaceAliases {
}

View File

@ -18,6 +18,7 @@ package org.jclouds.openstack.v2_0.functions;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.collect.Iterables.any;
import static org.jclouds.openstack.v2_0.predicates.ExtensionPredicates.nameEquals;
import static org.jclouds.openstack.v2_0.predicates.ExtensionPredicates.namespaceOrAliasEquals;
import static org.jclouds.util.Optionals2.unwrapIfOptional;
@ -28,7 +29,7 @@ import java.util.Set;
import javax.inject.Inject;
import org.jclouds.openstack.keystone.v2_0.config.Aliases;
import org.jclouds.openstack.keystone.v2_0.config.NamespaceAliases;
import org.jclouds.openstack.v2_0.domain.Extension;
import org.jclouds.reflect.InvocationSuccess;
import org.jclouds.rest.functions.ImplicitOptionalConverter;
@ -39,7 +40,7 @@ import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Sets;
/**
* We use the annotation {@link org.jclouds.openstack.services.Extension} to bind a class that implements an extension
* We use the annotation {@link Extension} to bind a class that implements an extension
* API to an {@link Extension}.
*/
public class PresentWhenExtensionAnnotationNamespaceEqualsAnyNamespaceInExtensionsSet implements
@ -49,7 +50,7 @@ public class PresentWhenExtensionAnnotationNamespaceEqualsAnyNamespaceInExtensio
@Inject
PresentWhenExtensionAnnotationNamespaceEqualsAnyNamespaceInExtensionsSet(
LoadingCache<String, Set<? extends Extension>> extensions, @Aliases Map<URI, Set<URI>> aliases) {
LoadingCache<String, Set<? extends Extension>> extensions, @NamespaceAliases Map<URI, Set<URI>> aliases) {
this.extensions = extensions;
this.aliases = aliases == null ? ImmutableMap.<URI, Set<URI>> of() : ImmutableMap.copyOf(aliases);
}
@ -63,16 +64,29 @@ public class PresentWhenExtensionAnnotationNamespaceEqualsAnyNamespaceInExtensio
URI namespace = URI.create(ext.get().namespace());
List<Object> args = input.getInvocation().getArgs();
Set<URI> aliasesForNamespace = aliases.containsKey(namespace) ? aliases.get(namespace) : Sets.<URI> newHashSet();
String name = ext.get().name();
if (args.isEmpty()) {
if (any(extensions.getUnchecked(""), namespaceOrAliasEquals(namespace, aliasesForNamespace)))
return input.getResult();
// Could not find extension by namespace or namespace alias. Try to find it by name next:
if ( !"".equals(name)) {
if (any(extensions.getUnchecked(""), nameEquals(name)))
return input.getResult();
}
} else if (args.size() == 1) {
String arg0 = checkNotNull(args.get(0), "arg[0] in %s", input).toString();
if (any(extensions.getUnchecked(arg0), namespaceOrAliasEquals(namespace, aliasesForNamespace)))
return input.getResult();
// Could not find extension by namespace or namespace alias. Try to find it by name next:
if (!"".equals(name)) {
if (any(extensions.getUnchecked(arg0), nameEquals(name)))
return input.getResult();
}
} else {
throw new RuntimeException(String.format("expecting zero or one args %s", input));
}
return Optional.absent();
} else {
// No extension annotation, should check whether to return absent

View File

@ -33,7 +33,7 @@ public class ExtensionPredicates {
/**
* matches namespace of the given extension
*
*
* @param namespace
* ex {@code http://docs.openstack.org/ext/keypairs/api/v1.1}
* @return predicate that will match namespace of the given extension
@ -56,7 +56,7 @@ public class ExtensionPredicates {
/**
* matches alias of the given extension
*
*
* @param alias
* ex. {@code os-keypairs}
* @return predicate that will alias of the given extension
@ -75,13 +75,13 @@ public class ExtensionPredicates {
return "aliasEquals(" + alias + ")";
}
};
}
}
/**
* matches namespace of the given extension
*
*
* @param namespace
* ex {@code http://docs.openstack.org/ext/keypairs/api/v1.1}
* @param namespacesAliases
* @param namespaceAliases
* Collection of ex {@code http://docs.openstack.org/compute/ext/keypairs/api/v1.1}
* @return predicate that will match namespace of the given extension
*/
@ -102,4 +102,27 @@ public class ExtensionPredicates {
}
};
}
/**
* matches name of the given extension
*
* @param name
* ex {@code http://docs.openstack.org/ext/keypairs/api/v1.1}
* @return predicate that will match name of the given extension
*/
public static Predicate<Extension> nameEquals(final String name) {
checkNotNull(name, "extension name must be defined");
return new Predicate<Extension>() {
@Override
public boolean apply(Extension ext) {
return name.equals(ext.getName());
}
@Override
public String toString() {
return "nameEquals(" + name + ")";
}
};
}
}

View File

@ -28,15 +28,27 @@ import javax.inject.Qualifier;
* the context of the extension, we must consider the <a href=
* "http://docs.openstack.org/api/openstack-compute/2/content/Extensions-d1e1444.html"
* >extensions call</a>.
*
*
* <br/>
* For our purposes, the minimal context of an extension is the type of the
* service it extends ex. {@link ServiceType#COMPUTE}, and its namespace ex. <a
* href
* ="http://docs.openstack.org/ext/keypairs/api/v1.1">http://docs.openstack.org
* /ext/keypairs/api/v1.1</a>.
*
*
*
* <br/><br/>
* A keystone extension example:<br/><br/>
* {<br/>
* "updated": "2014-12-03T00:00:00Z",<br/>
* "name": "DiskConfig",<br/>
* "links": [<br/>
*<br/>
* ],<br/>
* "namespace": "http://docs.openstack.org/compute/ext/fake_xml",<br/>
* "alias": "OS-DCF",<br/>
* "description": "Disk Management Extension."<br/>
* },<br/>
*<br/>
* @see ServiceType
* @see <a href=
* "http://docs.openstack.org/api/openstack-compute/2/content/Extensions-d1e1444.html"
@ -51,14 +63,14 @@ public @interface Extension {
/**
* the service type this is an extension of.
*
*
* <h3>note</h3>
*
*
* This isn't necessarily one of the built-in {@link ServiceType services},
* it could be an extension of a custom service.
*
*
* @return the service type this is an extension of.
*
*
*/
String of();
@ -66,9 +78,18 @@ public @interface Extension {
* namespace ex. <a href
* ="http://docs.openstack.org/ext/keypairs/api/v1.1">http
* ://docs.openstack.org /ext/keypairs/api/v1.1</a>.
*
*
* @return the namespace of the extension
*/
String namespace();
/**
* @return the name of the extension
*/
String name() default "";
/**
* @return the alias of the extension
*/
String alias() default "";
}

View File

@ -24,7 +24,7 @@ import java.util.List;
import java.util.Set;
import org.jclouds.date.internal.SimpleDateFormatDateService;
import org.jclouds.openstack.keystone.v2_0.config.Aliases;
import org.jclouds.openstack.keystone.v2_0.config.NamespaceAliases;
import org.jclouds.openstack.v2_0.ServiceType;
import org.jclouds.openstack.v2_0.domain.Extension;
import org.jclouds.reflect.Invocation;
@ -70,11 +70,19 @@ public class PresentWhenExtensionAnnotationNamespaceEqualsAnyNamespaceInExtensio
}
@org.jclouds.openstack.v2_0.services.Extension(of = ServiceType.COMPUTE, name = "Floating_ips", namespace = "http://docs.openstack.org/fake")
interface FloatingIPNamedApi {
}
interface NovaApi {
@Delegate
Optional<FloatingIPApi> getFloatingIPExtensionApi(String region);
@Delegate
Optional<FloatingIPNamedApi> getFloatingIPNamedExtensionApi(String region);
@Delegate
Optional<KeyPairApi> getKeyPairExtensionApi(String region);
@ -85,6 +93,11 @@ public class PresentWhenExtensionAnnotationNamespaceEqualsAnyNamespaceInExtensio
Invocation.create(method(NovaApi.class, "getFloatingIPExtensionApi", String.class), args), "foo");
}
InvocationSuccess getFloatingIPNamedExtension(List<Object> args) throws SecurityException, NoSuchMethodException {
return InvocationSuccess.create(
Invocation.create(method(NovaApi.class, "getFloatingIPNamedExtensionApi", String.class), args), "foo");
}
InvocationSuccess getKeyPairExtension(List<Object> args) throws SecurityException, NoSuchMethodException {
return InvocationSuccess.create(
Invocation.create(method(NovaApi.class, "getKeyPairExtensionApi", String.class), args), "foo");
@ -108,7 +121,7 @@ public class PresentWhenExtensionAnnotationNamespaceEqualsAnyNamespaceInExtensio
/**
* It is possible that the /extensions call returned the correct extension, but that the
* namespaces were different, for whatever reason. One way to address this is to have a multimap
* of the authoritative namespace to alternate onces, which could be wired up with guice
* of the authoritative namespace to alternate ones, which could be wired up with guice
*
*/
public void testPresentWhenAliasForExtensionMapsToNamespace() throws SecurityException, NoSuchMethodException {
@ -125,6 +138,22 @@ public class PresentWhenExtensionAnnotationNamespaceEqualsAnyNamespaceInExtensio
}
/**
* In devstack and going forward, namespaces are being deprecated. When namespace is missing (or replaced with a
* "fake" /fake namespace), allow matching by name and alias.
*
*/
public void testPresentWhenNameSpaceIsMissingAndMatchByNameOrAlias() throws SecurityException, NoSuchMethodException {
Extension floatingIpsWithFakeNamespace = floatingIps.toBuilder()
.namespace(URI.create("http://docs.openstack.org/ext/fake"))
.build();
Multimap<URI, URI> aliases = ImmutableMultimap.of();
assertEquals(whenExtensionsAndAliasesInRegionInclude("region", ImmutableSet.of(floatingIpsWithFakeNamespace), aliases).apply(
getFloatingIPNamedExtension(ImmutableList.<Object> of("region"))), Optional.of("foo"));
}
private PresentWhenExtensionAnnotationNamespaceEqualsAnyNamespaceInExtensionsSet whenExtensionsInRegionInclude(
String region, Extension... extensions) {
return whenExtensionsAndAliasesInRegionInclude(region, ImmutableSet.copyOf(extensions), ImmutableMultimap.<URI, URI> of());
@ -141,7 +170,7 @@ public class PresentWhenExtensionAnnotationNamespaceEqualsAnyNamespaceInExtensio
@Override
protected void configure() {
MapBinder<URI, URI> aliasBindings = MapBinder.newMapBinder(binder(),
URI.class, URI.class, Aliases.class).permitDuplicates();
URI.class, URI.class, NamespaceAliases.class).permitDuplicates();
for (URI key : aliases.keySet()) {
for (URI value : aliases.get(key)) {
aliasBindings.addBinding(key).toInstance(value);

View File

@ -40,7 +40,7 @@ import java.net.URI;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import static org.jclouds.openstack.keystone.v2_0.config.KeystoneHttpApiModule.aliasBinder;
import static org.jclouds.openstack.keystone.v2_0.config.KeystoneHttpApiModule.namespaceAliasBinder;
/**
* Configures the Nova connection.
@ -61,7 +61,7 @@ public class NovaHttpApiModule extends HttpApiModule<NovaApi> {
// Intentionally private so subclasses use the Guice multibindings to contribute their aliases
private void bindDefaultAliases() {
MapBinder<URI, URI> aliases = aliasBinder(binder());
MapBinder<URI, URI> aliases = namespaceAliasBinder(binder());
aliases.addBinding(URI.create(ExtensionNamespaces.SECURITY_GROUPS)).toInstance(
URI.create("http://docs.openstack.org/compute/ext/securitygroups/api/v1.1"));
aliases.addBinding(URI.create(ExtensionNamespaces.FLOATING_IPS)).toInstance(

View File

@ -16,7 +16,7 @@
*/
package org.jclouds.rackspace.cloudservers.uk.config;
import static org.jclouds.openstack.keystone.v2_0.config.KeystoneHttpApiModule.aliasBinder;
import static org.jclouds.openstack.keystone.v2_0.config.KeystoneHttpApiModule.namespaceAliasBinder;
import java.net.URI;
@ -35,7 +35,7 @@ public class CloudServersUKHttpApiModule extends NovaHttpApiModule {
@Override
protected void configure() {
super.configure();
MapBinder<URI, URI> aliases = aliasBinder(binder());
MapBinder<URI, URI> aliases = namespaceAliasBinder(binder());
aliases.addBinding(URI.create(ExtensionNamespaces.VOLUME_ATTACHMENTS)).toInstance(
URI.create("http://docs.openstack.org/compute/ext/volumes/api/v1.1"));
}

View File

@ -16,7 +16,7 @@
*/
package org.jclouds.rackspace.cloudservers.us.config;
import static org.jclouds.openstack.keystone.v2_0.config.KeystoneHttpApiModule.aliasBinder;
import static org.jclouds.openstack.keystone.v2_0.config.KeystoneHttpApiModule.namespaceAliasBinder;
import java.net.URI;
@ -36,7 +36,7 @@ public class CloudServersUSHttpApiModule extends NovaHttpApiModule {
@Override
protected void configure() {
super.configure();
MapBinder<URI, URI> aliases = aliasBinder(binder());
MapBinder<URI, URI> aliases = namespaceAliasBinder(binder());
aliases.addBinding(URI.create(ExtensionNamespaces.VOLUME_ATTACHMENTS)).toInstance(
URI.create("http://docs.openstack.org/compute/ext/volumes/api/v1.1"));
}