Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(clouddriver): Support sharding read-only requests by user #1641

Merged
merged 1 commit into from
Oct 4, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,13 @@ class DelegatingClouddriverService<T> {
}

T getService() {
SelectableService.Criteria criteria = new SelectableService.Criteria(null, null, null, null);
SelectableService.Criteria criteria = new SelectableService.Criteria(null, null, null, null, null);

ExecutionContext executionContext = ExecutionContext.get();
if (executionContext != null) {
criteria = new SelectableService.Criteria(
executionContext.getApplication(),
executionContext.getAuthenticatedUser(),
executionContext.getExecutionType(),
executionContext.getExecutionId(),
executionContext.getOrigin()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/*
* Copyright 2017 Netflix, 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.netflix.spinnaker.orca.clouddriver.config;

import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

public class ByAuthenticatedUserServiceSelector implements ServiceSelector {
private final Object service;
private final int priority;
private final List<Pattern> userPatterns;

public ByAuthenticatedUserServiceSelector(Object service, Integer priority, Map<String, Object> config) {
this.service = service;
this.priority = priority;

Collection<String> users = new HashSet(
((Map<String, String>) config.get("users")).values()
);
this.userPatterns = users.stream().map(Pattern::compile).collect(Collectors.toList());
}

@Override
public Object getService() {
return service;
}

@Override
public int getPriority() {
return priority;
}

@Override
public boolean supports(SelectableService.Criteria criteria) {
if (criteria.getAuthenticatedUser() == null) {
return false;
}

return userPatterns
.stream()
.anyMatch(userPattern -> userPattern.matcher(criteria.getAuthenticatedUser()).matches());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,19 +41,22 @@ public Object getService(Criteria criteria) {

public static class Criteria {
private final String application;
private final String authenticatedUser;
private final String executionType;
private final String executionId;
private final String origin;

public Criteria(String application, String executionType, String origin) {
this(application, executionType, null, origin);
public Criteria(String application, String authenticatedUser, String executionType, String origin) {
this(application, authenticatedUser, executionType, null, origin);
}

public Criteria(String application,
String authenticatedUser,
String executionType,
String executionId,
String origin) {
this.application = application;
this.authenticatedUser = authenticatedUser;
this.executionType = executionType;
this.executionId = executionId;
this.origin = origin;
Expand All @@ -63,6 +66,10 @@ public String getApplication() {
return application;
}

public String getAuthenticatedUser() {
return authenticatedUser;
}

public String getExecutionType() {
return executionType;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,26 +16,25 @@

package com.netflix.spinnaker.orca.clouddriver.config

import com.netflix.spinnaker.orca.clouddriver.InstanceService
import com.netflix.spinnaker.orca.clouddriver.KatoService
import com.netflix.spinnaker.orca.clouddriver.MortService
import com.netflix.spinnaker.orca.clouddriver.OortService
import spock.lang.Shared
import spock.lang.Specification
import spock.lang.Unroll;

class SelectableServiceSpec extends Specification {
@Shared
def mortService = Mock(MortService)
def mortService = "mort"

@Shared
def oortService = Mock(OortService)
def oortService = "oort"

@Shared
def katoService = Mock(KatoService)
def katoService = "kato"

@Shared
def instanceService = Mock(InstanceService)
def instanceService = "instance"

@Shared
def bakeryService = "bakery"

@Unroll
def "should lookup service by application or executionType"() {
Expand All @@ -45,6 +44,7 @@ class SelectableServiceSpec extends Specification {
new ByApplicationServiceSelector(mortService, 10, ["applicationPattern": ".*spindemo.*"]),
new ByExecutionTypeServiceSelector(oortService, 5, ["executionTypes": [0: "orchestration"]]),
new ByOriginServiceSelector(instanceService, 20, ["origin": "deck", "executionTypes": [0: "orchestration"]]),
new ByAuthenticatedUserServiceSelector(bakeryService, 25, ["users": [0: "user1@email.com", 1: ".*user2.*"]]),
new DefaultServiceSelector(katoService, 1, [:])
]
)
Expand All @@ -56,14 +56,17 @@ class SelectableServiceSpec extends Specification {
service == expectedService

where:
criteria || expectedService
new SelectableService.Criteria(null, null, null) || katoService // the default service selector
new SelectableService.Criteria("spindemo", "orchestration", "api") || mortService
new SelectableService.Criteria("1-spindemo-1", "orchestration", "api") || mortService
new SelectableService.Criteria("spindemo", "orchestration", "deck") || instanceService // origin selector is higher priority
new SelectableService.Criteria("spindemo", "pipeline", "deck") || mortService // fall back to application selector as origin selector does not support pipeline
new SelectableService.Criteria("spintest", "orchestration", "api") || oortService
new SelectableService.Criteria("spintest", "pipeline", "api") || katoService
criteria || expectedService
new SelectableService.Criteria(null, null, null, null) || katoService // the default service selector
new SelectableService.Criteria("spindemo", null, "orchestration", "api") || mortService
new SelectableService.Criteria("1-spindemo-1", null, "orchestration", "api") || mortService
new SelectableService.Criteria("spindemo", null, "orchestration", "deck") || instanceService // origin selector is higher priority
new SelectableService.Criteria("spindemo", null, "pipeline", "deck") || mortService // fall back to application selector as origin selector does not support pipeline
new SelectableService.Criteria("spintest", null, "orchestration", "api") || oortService
new SelectableService.Criteria("spintest", null, "pipeline", "api") || katoService
new SelectableService.Criteria("spintest", "user1@unsupported.com", "orchestration", "api") || oortService
new SelectableService.Criteria("spintest", "user1@email.com", "orchestration", "api") || bakeryService // user selector is highest priority
new SelectableService.Criteria("spintest", "user2@random.com", "orchestration", "api") || bakeryService // user selector supports regex patterns
}

def "should default to all execution types if none configured (by origin selector)"() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,14 @@ public class ExecutionContext {
private static final ThreadLocal<ExecutionContext> threadLocal = new ThreadLocal<>();

private final String application;
private final String authenticatedUser;
private final String executionType;
private final String executionId;
private final String origin;

public ExecutionContext(String application, String executionType, String executionId, String origin) {
public ExecutionContext(String application, String authenticatedUser, String executionType, String executionId, String origin) {
this.application = application;
this.authenticatedUser = authenticatedUser;
this.executionType = executionType;
this.executionId = executionId;
this.origin = origin;
Expand All @@ -47,6 +49,10 @@ public String getApplication() {
return application;
}

public String getAuthenticatedUser() {
return authenticatedUser;
}

public String getExecutionType() {
return executionType;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ interface AuthenticationAware {
try {
ExecutionContext.set(ExecutionContext(
getExecution().getApplication(),
currentUser.username,
getExecution().javaClass.simpleName.toLowerCase(),
getExecution().getId(),
getExecution().getOrigin()
Expand Down