Skip to content

Commit

Permalink
Support label selectors in mock server
Browse files Browse the repository at this point in the history
This supports simple label selectors (eg foo=bar). The != operator would
go beyond the limits of the AttributeSet-based matching in
CrudDispatcher.

There was a test using labels in ConfigMapCrudTest, but it was only
passing because, at the point of execution, all resources match the
label selector. This change moves changes the order of assertions so
that the effect of the label selector is observable.

This should fix #1125, although I haven't tested that code directly.
  • Loading branch information
drigz committed Jul 27, 2018
1 parent eddf24b commit d592d2d
Show file tree
Hide file tree
Showing 4 changed files with 104 additions and 14 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@

* Fix #1156 : Watcher does not have correct authentication information in Openshift environment.

* Fix #1125 : ConfigMap labels are ignored when using mock KubernetesServer

* Fix #1144 : Get Request with OpenShift Mock Server Not Working

* Fix #1147: Cluster context was being ignored when loading the Config from a kubeconfig file
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
import io.fabric8.mockwebserver.crud.AttributeExtractor;
import io.fabric8.mockwebserver.crud.AttributeSet;
import io.fabric8.zjsonpatch.internal.guava.Strings;
import java.util.Map;
import okhttp3.HttpUrl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand All @@ -34,9 +36,11 @@ public class KubernetesAttributesExtractor implements AttributeExtractor<HasMeta

private static final Logger LOGGER = LoggerFactory.getLogger(KubernetesAttributesExtractor.class);

public static final String KEY = "key";
public static final String KIND = "kind";
public static final String NAME = "name";
public static final String NAMESPACE = "namespace";
public static final String VALUE = "value";

private static final String API_GROUP = "/o?api(s/[a-zA-Z0-9-_.]+)?";
private static final String VERSION_GROUP = "(/(?<version>[a-zA-Z0-9-_]+))?";
Expand All @@ -47,16 +51,29 @@ public class KubernetesAttributesExtractor implements AttributeExtractor<HasMeta

protected static final Pattern PATTERN = Pattern.compile(API_GROUP + VERSION_GROUP + NAMESPACE_GROUP + KIND_GROUP + NAME_GROUP + END_GROUP);

private static final String LABEL_KEY_PREFIX = "labels:";
private static final String KEY_GROUP = "(?<key>[a-zA-Z0-9-_./]+)";
// Matches a==b and a=b but not a!=b.
private static final String EQUALITY_GROUP = "(==|(?<!!)=)";
private static final String VALUE_GROUP = "(?<value>[a-zA-Z0-9-_.]+)";
private static final Pattern LABEL_REQUIREMENT_EQUALITY = Pattern.compile(KEY_GROUP + EQUALITY_GROUP + VALUE_GROUP);

private HttpUrl parseUrlFromPathAndQuery(String s) {
return HttpUrl.parse("http://localhost" + s);
}

@Override
public AttributeSet fromPath(String s) {
if (s == null || s.isEmpty()) {
return new AttributeSet();
}

//Get paths
Matcher m = PATTERN.matcher(s);
HttpUrl url = parseUrlFromPathAndQuery(s);
Matcher m = PATTERN.matcher(url.encodedPath());
if (m.matches()) {
AttributeSet set = extract(m);
set = AttributeSet.merge(set, extractQueryParameters(url));
LOGGER.debug("fromPath {} : {}", s, set);
return set;
}
Expand Down Expand Up @@ -89,15 +106,7 @@ public AttributeSet extract(String s) {
return extract(h);
}

//Get paths
Matcher m = PATTERN.matcher(s);
if (m.matches()) {
AttributeSet set = extract(m);
LOGGER.debug("extract {} : {}", s, set);
return set;
}
LOGGER.debug("extract {} : no attributes", s);
return new AttributeSet();
return fromPath(s);
}


Expand All @@ -115,6 +124,12 @@ public AttributeSet extract(HasMetadata o) {
if (!Strings.isNullOrEmpty(o.getMetadata().getNamespace())) {
attributes = attributes.add(new Attribute(NAMESPACE, o.getMetadata().getNamespace()));
}

if (o.getMetadata().getLabels() != null) {
for (Map.Entry<String, String> label : o.getMetadata().getLabels().entrySet()) {
attributes = attributes.add(new Attribute(LABEL_KEY_PREFIX + label.getKey(), label.getValue()));
}
}
return attributes;
}

Expand Down Expand Up @@ -165,6 +180,22 @@ else if (kind.endsWith("s")) {
return attributes;
}

private static AttributeSet extractQueryParameters(HttpUrl url) {
AttributeSet attributes = new AttributeSet();
String labelSelector = url.queryParameter("labelSelector");
if (labelSelector != null) {
for (String requirement : labelSelector.split(",")) {
Matcher m = LABEL_REQUIREMENT_EQUALITY.matcher(requirement);
if (m.matches()) {
attributes = attributes.add(new Attribute(LABEL_KEY_PREFIX + m.group(KEY), m.group(VALUE)));
} else {
LOGGER.warn("Ignoring unsupported label requirement: {}", requirement);
}
}
}
return attributes;
}

private static HasMetadata toKubernetesResource(String s) {
try (InputStream stream = new ByteArrayInputStream(s.getBytes(StandardCharsets.UTF_8.name()))) {
return Serialization.unmarshal(stream);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
import io.fabric8.kubernetes.api.model.PodBuilder;
import io.fabric8.mockwebserver.crud.Attribute;
import io.fabric8.mockwebserver.crud.AttributeSet;
import java.util.HashMap;
import java.util.Map;
import org.junit.Assert;
import org.junit.Test;

Expand Down Expand Up @@ -84,6 +86,20 @@ public void shouldHandleResource() {

}

@Test
public void shouldHandleResourceWithLabel() {
KubernetesAttributesExtractor extractor = new KubernetesAttributesExtractor();
Map<String, String> labels = new HashMap<>();
labels.put("name", "myname");
Pod pod = new PodBuilder().withNewMetadata().withLabels(labels).endMetadata().build();

AttributeSet attributes = extractor.extract(pod);

AttributeSet expected = new AttributeSet();
expected = expected.add(new Attribute("labels:name", "myname"));
Assert.assertTrue("Expected " + attributes + " to match " + expected, attributes.matches(expected));
}

@Test
public void shouldHandleKindWithoutVersion() {
KubernetesAttributesExtractor extractor = new KubernetesAttributesExtractor();
Expand Down Expand Up @@ -151,4 +167,45 @@ public void shouldHandleCrds() {
Assert.assertTrue("Expected " + attributes + " to match " + expected, attributes.matches(expected));
}

@Test
public void shouldHandleLabelSelectorsWithOneLabel() {
KubernetesAttributesExtractor extractor = new KubernetesAttributesExtractor();
AttributeSet attributes = extractor.extract("/api/v1/namespaces/myns/pods/mypod?labelSelector=name%3Dmyname");

AttributeSet expected = new AttributeSet();
expected = expected.add(new Attribute("labels:name", "myname"));
Assert.assertTrue("Expected " + attributes + " to match " + expected, attributes.matches(expected));
}

@Test
public void shouldHandleLabelSelectorsWithDoubleEquals() {
KubernetesAttributesExtractor extractor = new KubernetesAttributesExtractor();
AttributeSet attributes = extractor.extract("/api/v1/namespaces/myns/pods/mypod?labelSelector=name%3D%3Dmyname");

AttributeSet expected = new AttributeSet();
expected = expected.add(new Attribute("labels:name", "myname"));
Assert.assertTrue("Expected " + attributes + " to match " + expected, attributes.matches(expected));
}

@Test
public void shouldHandleLabelSelectorsWithTwoLabels() {
KubernetesAttributesExtractor extractor = new KubernetesAttributesExtractor();
AttributeSet attributes = extractor.extract("/api/v1/namespaces/myns/pods/mypod?labelSelector=name%3Dmyname,age%3D37");

AttributeSet expected = new AttributeSet();
expected = expected.add(new Attribute("labels:name", "myname"));
expected = expected.add(new Attribute("labels:age", "37"));
Assert.assertTrue("Expected " + attributes + " to match " + expected, attributes.matches(expected));
}

@Test
public void shouldHandleLabelSelectorsWithADomain() {
KubernetesAttributesExtractor extractor = new KubernetesAttributesExtractor();
AttributeSet attributes = extractor.extract("/api/v1/namespaces/myns/pods/mypod?labelSelector=example.com/name%3Dmyname");

AttributeSet expected = new AttributeSet();
expected = expected.add(new Attribute("labels:example.com/name", "myname"));
Assert.assertTrue("Expected " + attributes + " to match " + expected, attributes.matches(expected));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,10 @@ public void testCrud() {
assertNotNull(aConfigMapList);
assertEquals(3, aConfigMapList.getItems().size());

aConfigMapList = client.configMaps().inAnyNamespace().withLabels(Collections.singletonMap("foo", "bar")).list();
assertNotNull(aConfigMapList);
assertEquals(2, aConfigMapList.getItems().size());

aConfigMapList = client.configMaps().inNamespace("ns1").list();
assertNotNull(aConfigMapList);
assertEquals(2, aConfigMapList.getItems().size());
Expand All @@ -76,10 +80,6 @@ public void testCrud() {
assertNotNull(aConfigMapList);
assertEquals(1, aConfigMapList.getItems().size());

aConfigMapList = client.configMaps().inAnyNamespace().withLabels(Collections.singletonMap("foo", "bar")).list();
assertNotNull(aConfigMapList);
assertEquals(2, aConfigMapList.getItems().size());

configmap2 = client.configMaps().inNamespace("ns1").withName("configmap2").edit().addToData("II", "TWO").done();
assertNotNull(configmap2);
assertEquals("TWO", configmap2.getData().get("II"));
Expand Down

0 comments on commit d592d2d

Please sign in to comment.