diff options
74 files changed, 416 insertions, 352 deletions
diff --git a/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/impl/IdentityDocumentGenerator.java b/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/impl/IdentityDocumentGenerator.java index 644fc929884..efb2179ff6b 100644 --- a/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/impl/IdentityDocumentGenerator.java +++ b/athenz-identity-provider-service/src/main/java/com/yahoo/vespa/hosted/athenz/instanceproviderservice/impl/IdentityDocumentGenerator.java @@ -1,7 +1,6 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.hosted.athenz.instanceproviderservice.impl; -import com.yahoo.athenz.auth.util.Crypto; import com.yahoo.config.provision.Zone; import com.yahoo.vespa.hosted.athenz.instanceproviderservice.config.AthenzProviderServiceConfig; import com.yahoo.vespa.hosted.athenz.instanceproviderservice.impl.model.IdentityDocument; @@ -27,6 +26,7 @@ public class IdentityDocumentGenerator { private final String dnsSuffix; private final String providerService; private final String ztsUrl; + private final String providerDomain; public IdentityDocumentGenerator(AthenzProviderServiceConfig config, NodeRepository nodeRepository, Zone zone, KeyProvider keyProvider) { this.nodeRepository = nodeRepository; @@ -35,6 +35,7 @@ public class IdentityDocumentGenerator { this.dnsSuffix = config.certDnsSuffix(); this.providerService = config.serviceName(); this.ztsUrl = config.ztsUrl(); + this.providerDomain = config.domain(); } public String generateSignedIdentityDocument(String hostname) { @@ -59,7 +60,7 @@ public class IdentityDocumentGenerator { SignedIdentityDocument.DEFAULT_KEY_VERSION, identityDocument.providerUniqueId.asString(), dnsSuffix, - providerService, + providerDomain + "." + providerService, ztsUrl, SignedIdentityDocument.DEFAILT_DOCUMENT_VERSION ); diff --git a/clustercontroller-core/src/main/java/com/yahoo/vespa/clustercontroller/core/SystemStateBroadcaster.java b/clustercontroller-core/src/main/java/com/yahoo/vespa/clustercontroller/core/SystemStateBroadcaster.java index 5c035a0d5a7..024c5d21607 100644 --- a/clustercontroller-core/src/main/java/com/yahoo/vespa/clustercontroller/core/SystemStateBroadcaster.java +++ b/clustercontroller-core/src/main/java/com/yahoo/vespa/clustercontroller/core/SystemStateBroadcaster.java @@ -134,6 +134,7 @@ public class SystemStateBroadcaster { if (systemState == null) return false; if (!systemState.isOfficial()) { + log.log(LogLevel.INFO, String.format("Publishing cluster state version %d", systemState.getVersion())); systemState.setOfficial(true); } diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/search/ContainerSearch.java b/config-model/src/main/java/com/yahoo/vespa/model/container/search/ContainerSearch.java index 03c5a7e4a4b..db81191f0cf 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/container/search/ContainerSearch.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/container/search/ContainerSearch.java @@ -26,7 +26,6 @@ import java.util.*; /** * @author gjoranv * @author tonytv - * @since 5.1.9 */ public class ContainerSearch extends ContainerSubsystem<SearchChains> implements diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/TenantHandler.java b/configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/TenantHandler.java index b02d17a9372..46d007126c0 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/TenantHandler.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/TenantHandler.java @@ -37,7 +37,7 @@ public class TenantHandler extends HttpHandler { protected HttpResponse handlePUT(HttpRequest request) { TenantName tenantName = getAndValidateTenantFromRequest(request); try { - tenants.writeTenantPath(tenantName); + tenants.addTenant(tenantName); } catch (Exception e) { throw new InternalServerException(Exceptions.toMessageString(e)); } diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/tenant/Tenants.java b/configserver/src/main/java/com/yahoo/vespa/config/server/tenant/Tenants.java index 3ad920bbff2..cf05440a1d2 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/tenant/Tenants.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/tenant/Tenants.java @@ -28,7 +28,6 @@ import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; @@ -42,7 +41,7 @@ import java.util.logging.Logger; * implemented support for it). * * This instance is called from two different threads, the http handler threads and the zookeeper watcher threads. - * To create or delete a tenant, the handler calls {@link Tenants#writeTenantPath} and {@link Tenants#deleteTenant} methods. + * To create or delete a tenant, the handler calls {@link Tenants#addTenant} and {@link Tenants#deleteTenant} methods. * This will delete shared state from zookeeper, and return, so it does not mean a tenant is immediately deleted. * * Once a tenant is deleted from zookeeper, the zookeeper watcher thread will get notified on all configservers, and @@ -73,14 +72,13 @@ public class Tenants implements ConnectionStateListener, PathChildrenCacheListen /** - * New instance from the tenants in the given component registry's ZooKeeper. Will set watch when reading them. + * New instance from the tenants in the given component registry's ZooKeeper data. * * @param globalComponentRegistry a {@link com.yahoo.vespa.config.server.GlobalComponentRegistry} * @throws Exception is creating the Tenants instance fails */ @Inject public Tenants(GlobalComponentRegistry globalComponentRegistry, Metrics metrics) throws Exception { - // Note: unit tests may want to use the constructor below to avoid setting watch by calling readTenants(). this.globalComponentRegistry = globalComponentRegistry; this.curator = globalComponentRegistry.getCurator(); metricUpdater = metrics.getOrCreateMetricUpdater(Collections.emptyMap()); @@ -94,12 +92,12 @@ public class Tenants implements ConnectionStateListener, PathChildrenCacheListen this.directoryCache = curator.createDirectoryCache(tenantsPath.getAbsolute(), false, false, pathChildrenExecutor); directoryCache.start(); directoryCache.addListener(this); - tenantsChanged(readTenants()); + createTenants(); notifyTenantsLoaded(); } /** - * New instance containing the given tenants. This will not watch in ZooKeeper. + * New instance containing the given tenants. This will not create Zookeeper watches. For testing only * @param globalComponentRegistry a {@link com.yahoo.vespa.config.server.GlobalComponentRegistry} instance * @param metrics a {@link com.yahoo.vespa.config.server.monitoring.Metrics} instance * @param tenants a collection of {@link Tenant}s @@ -131,9 +129,9 @@ public class Tenants implements ConnectionStateListener, PathChildrenCacheListen return sessionTenants; } - public synchronized void addTenant(Tenant tenant) { - tenants.put(tenant.getName(), tenant); - metricUpdater.setTenants(tenants.size()); + public synchronized void addTenant(TenantName tenantName) throws Exception { + writeTenantPath(tenantName); + createTenant(tenantName); } /** @@ -141,7 +139,7 @@ public class Tenants implements ConnectionStateListener, PathChildrenCacheListen * * @return a set of tenant names */ - private Set<TenantName> readTenants() { + private Set<TenantName> readTenantsFromZooKeeper() { Set<TenantName> tenants = new LinkedHashSet<>(); for (String tenant : curator.getChildren(tenantsPath)) { tenants.add(TenantName.from(tenant)); @@ -149,10 +147,11 @@ public class Tenants implements ConnectionStateListener, PathChildrenCacheListen return tenants; } - synchronized void tenantsChanged(Set<TenantName> newTenants) throws Exception { - log.log(LogLevel.DEBUG, "Tenants changed: " + newTenants); - checkForRemovedTenants(newTenants); - checkForAddedTenants(newTenants); + synchronized void createTenants() throws Exception { + Set<TenantName> allTenants = readTenantsFromZooKeeper(); + log.log(LogLevel.DEBUG, "Create tenants, tenants found in zookeeper: " + allTenants); + checkForRemovedTenants(allTenants); + checkForAddedTenants(allTenants); metricUpdater.setTenants(tenants.size()); } @@ -170,24 +169,26 @@ public class Tenants implements ConnectionStateListener, PathChildrenCacheListen private void checkForAddedTenants(Set<TenantName> newTenants) throws Exception { ExecutorService executor = Executors.newFixedThreadPool(globalComponentRegistry.getConfigserverConfig().numParallelTenantLoaders()); - Map<TenantName, Tenant> addedTenants = new ConcurrentHashMap<>(); for (TenantName tenantName : newTenants) { // Note: the http handler will check if the tenant exists, and throw accordingly if (!tenants.containsKey(tenantName)) { executor.execute(() -> { - try { - Tenant tenant = TenantBuilder.create(globalComponentRegistry, tenantName, getTenantPath(tenantName)).build(); - notifyNewTenant(tenant); - addedTenants.put(tenantName, tenant); - } catch (Exception e) { - log.log(LogLevel.WARNING, "Error loading tenant '" + tenantName + "', skipping.", e); - } + createTenant(tenantName); }); } } executor.shutdown(); executor.awaitTermination(365, TimeUnit.DAYS); // Timeout should never happen - tenants.putAll(addedTenants); + } + + private void createTenant(TenantName tenantName) { + try { + Tenant tenant = TenantBuilder.create(globalComponentRegistry, tenantName, getTenantPath(tenantName)).build(); + notifyNewTenant(tenant); + tenants.put(tenantName, tenant); + } catch (Exception e) { + log.log(LogLevel.WARNING, "Error loading tenant '" + tenantName + "', skipping.", e); + } } /** @@ -239,7 +240,7 @@ public class Tenants implements ConnectionStateListener, PathChildrenCacheListen * @param name name of the tenant * @return this Tenants */ - public synchronized Tenants writeTenantPath(TenantName name) { + synchronized Tenants writeTenantPath(TenantName name) { Path tenantPath = getTenantPath(name); curator.createAtomically(tenantPath, tenantPath.append(Tenant.SESSIONS), tenantPath.append(Tenant.APPLICATIONS)); return this; @@ -316,7 +317,7 @@ public class Tenants implements ConnectionStateListener, PathChildrenCacheListen switch (event.getType()) { case CHILD_ADDED: case CHILD_REMOVED: - tenantsChanged(readTenants()); + createTenants(); break; } } diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/ConfigServerBootstrapTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/ConfigServerBootstrapTest.java index 88d7177a51e..e0d65055f21 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/ConfigServerBootstrapTest.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/ConfigServerBootstrapTest.java @@ -47,9 +47,9 @@ public class ConfigServerBootstrapTest extends TestWithTenant { public TemporaryFolder temporaryFolder = new TemporaryFolder(); @Before - public void setup() { - tenants.writeTenantPath(tenant1); - tenants.writeTenantPath(tenant2); + public void setup() throws Exception { + tenants.addTenant(tenant1); + tenants.addTenant(tenant2); applicationRepository = new ApplicationRepository(tenants, new SessionHandlerTest.MockProvisioner(), diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/ApplicationHandlerTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/ApplicationHandlerTest.java index afe51adee20..892c821950e 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/ApplicationHandlerTest.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/ApplicationHandlerTest.java @@ -24,8 +24,6 @@ import com.yahoo.vespa.config.server.TestComponentRegistry; import com.yahoo.vespa.config.server.application.ApplicationConvergenceChecker; import com.yahoo.vespa.config.server.application.HttpProxy; import com.yahoo.vespa.config.server.application.LogServerLogGrabber; -import com.yahoo.vespa.config.server.application.TenantApplications; -import com.yahoo.vespa.config.server.application.ZKTenantApplications; import com.yahoo.vespa.config.server.http.HandlerTest; import com.yahoo.vespa.config.server.http.HttpErrorResponse; import com.yahoo.vespa.config.server.http.StaticResponse; @@ -40,7 +38,6 @@ import com.yahoo.vespa.config.server.session.RemoteSession; import com.yahoo.vespa.config.server.session.SessionContext; import com.yahoo.vespa.config.server.session.SessionZooKeeperClient; import com.yahoo.vespa.config.server.tenant.Tenant; -import com.yahoo.vespa.config.server.tenant.TenantBuilder; import com.yahoo.vespa.config.server.tenant.Tenants; import com.yahoo.vespa.curator.mock.MockCurator; import com.yahoo.vespa.model.VespaModelFactory; @@ -279,7 +276,7 @@ public class ApplicationHandlerTest { tenant.getRemoteSessionRepo().addSession(new RemoteSession(tenant.getName(), sessionId, componentRegistry, new MockSessionZKClient(app), clock)); } - static Tenants addApplication(ApplicationId applicationId, long sessionId) throws Exception { + private static Tenants addApplication(ApplicationId applicationId, long sessionId) throws Exception { // This method is a good illustration of the spaghetti wiring resulting from no design // TODO: When this setup looks sane we have refactored sufficiently that there is a design @@ -289,18 +286,15 @@ public class ApplicationHandlerTest { Path sessionPath = tenantPath.append(Tenant.SESSIONS).append(String.valueOf(sessionId)); MockCurator curator = new MockCurator(); - GlobalComponentRegistry globalComponents = new TestComponentRegistry.Builder().curator(curator).build(); + GlobalComponentRegistry componentRegistry = new TestComponentRegistry.Builder() + .curator(curator) + .modelFactoryRegistry(new ModelFactoryRegistry( + Collections.singletonList(new VespaModelFactory(new NullConfigModelRegistry())))) + .build(); - Tenants tenants = new Tenants(globalComponents, Metrics.createTestMetrics()); // Creates the application path element in zk - tenants.writeTenantPath(tenantName); - TenantApplications tenantApplications = ZKTenantApplications.create(curator, - tenantPath.append(Tenant.APPLICATIONS), - new MockReloadHandler(), // TODO: Use the real one - tenantName); - Tenant tenant = TenantBuilder.create(globalComponents, applicationId.tenant(), tenantPath) - .withApplicationRepo(tenantApplications) - .build(); - tenants.addTenant(tenant); + Tenants tenants = new Tenants(componentRegistry, Metrics.createTestMetrics()); // Creates the application path element in zk + tenants.addTenant(tenantName); + Tenant tenant = tenants.getTenant(tenantName); tenant.getApplicationRepo().createPutApplicationTransaction(applicationId, sessionId).commit(); ApplicationPackage app = FilesApplicationPackage.fromFile(testApp); @@ -317,11 +311,7 @@ public class ApplicationHandlerTest { tenant.getRemoteSessionRepo().addSession( new RemoteSession(tenantName, sessionId, - new TestComponentRegistry.Builder() - .curator(curator) - .modelFactoryRegistry(new ModelFactoryRegistry( - Collections.singletonList(new VespaModelFactory(new NullConfigModelRegistry())))) - .build(), + componentRegistry, sessionClient, Clock.systemUTC())); return tenants; diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/ListTenantsTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/ListTenantsTest.java index 189314577e4..aa27dd0783c 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/ListTenantsTest.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/ListTenantsTest.java @@ -19,9 +19,9 @@ public class ListTenantsTest extends TenantTest { @Test public void testListTenants() throws Exception { - tenants.writeTenantPath(a); - tenants.writeTenantPath(b); - tenants.writeTenantPath(c); + tenants.addTenant(a); + tenants.addTenant(b); + tenants.addTenant(c); ListTenantsHandler listTenantsHandler = new ListTenantsHandler(testExecutor(), null, tenants); diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/TenantHandlerTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/TenantHandlerTest.java index cf79770ad20..ca545611e7f 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/TenantHandlerTest.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/TenantHandlerTest.java @@ -54,7 +54,7 @@ public class TenantHandlerTest extends TenantTest { @Test public void testGetExisting() throws Exception { - tenants.writeTenantPath(a); + tenants.addTenant(a); TenantGetResponse response = (TenantGetResponse) handler.handleGET( HttpRequest.createTestRequest("http://deploy.example.yahoo.com:80/application/v2/tenant/a", Method.GET)); assertResponseEquals(response, "{\"message\":\"Tenant 'a' exists.\"}"); diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/tenant/TenantsTestCase.java b/configserver/src/test/java/com/yahoo/vespa/config/server/tenant/TenantsTestCase.java index a77b7615e6d..ce16ac14f60 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/tenant/TenantsTestCase.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/tenant/TenantsTestCase.java @@ -3,8 +3,6 @@ package com.yahoo.vespa.config.server.tenant; import com.yahoo.config.model.test.MockApplicationPackage; import com.yahoo.config.provision.ApplicationId; -import com.yahoo.config.provision.ApplicationName; -import com.yahoo.config.provision.InstanceName; import com.yahoo.config.provision.TenantName; import com.yahoo.config.provision.Version; import com.yahoo.vespa.config.server.application.ApplicationSet; @@ -12,7 +10,6 @@ import com.yahoo.vespa.config.server.ServerCache; import com.yahoo.vespa.config.server.TestComponentRegistry; import com.yahoo.vespa.config.server.TestWithCurator; import com.yahoo.vespa.config.server.application.Application; -import com.yahoo.vespa.config.server.deploy.MockDeployer; import com.yahoo.vespa.config.server.monitoring.MetricUpdater; import com.yahoo.vespa.config.server.monitoring.Metrics; import com.yahoo.vespa.model.VespaModel; @@ -52,8 +49,8 @@ public class TenantsTestCase extends TestWithCurator { tenantListener.tenantsLoaded = false; tenants = new Tenants(globalComponentRegistry, Metrics.createTestMetrics()); assertTrue(tenantListener.tenantsLoaded); - tenants.writeTenantPath(tenant1); - tenants.writeTenantPath(tenant2); + tenants.addTenant(tenant1); + tenants.addTenant(tenant2); } @After @@ -79,7 +76,7 @@ public class TenantsTestCase extends TestWithCurator { @Test public void testTenantListenersNotified() throws Exception { - tenants.writeTenantPath(tenant3); + tenants.addTenant(tenant3); assertThat("tenant3 not the last created tenant. Tenants: " + tenants.getAllTenantNames() + ", /config/v2/tenants: " + readZKChildren("/config/v2/tenants"), tenantListener.tenantCreatedName, is(tenant3)); tenants.deleteTenant(tenant2); assertFalse(tenants.getAllTenantNames().contains(tenant2)); @@ -91,7 +88,7 @@ public class TenantsTestCase extends TestWithCurator { Set<TenantName> allTenants = tenants.getAllTenantNames(); assertTrue(allTenants.contains(tenant1)); assertTrue(allTenants.contains(tenant2)); - tenants.writeTenantPath(tenant3); + tenants.addTenant(tenant3); allTenants = tenants.getAllTenantNames(); assertTrue(allTenants.contains(tenant1)); assertTrue(allTenants.contains(tenant2)); @@ -100,7 +97,7 @@ public class TenantsTestCase extends TestWithCurator { @Test public void testPutAdd() throws Exception { - tenants.writeTenantPath(tenant3); + tenants.addTenant(tenant3); assertNotNull(globalComponentRegistry.getCurator().framework().checkExists().forPath(tenants.tenantZkPath(tenant3))); } @@ -115,29 +112,28 @@ public class TenantsTestCase extends TestWithCurator { public void testTenantsChanged() throws Exception { tenants.close(); // close the Tenants instance created in setupSession, we do not want to use one with a PatchChildrenCache listener tenants = new Tenants(globalComponentRegistry, Metrics.createTestMetrics(), new ArrayList<>()); - Set<TenantName> newTenants = new LinkedHashSet<>(); TenantName defaultTenant = TenantName.defaultName(); - newTenants.add(tenant2); - newTenants.add(defaultTenant); - tenants.tenantsChanged(newTenants); + tenants.writeTenantPath(tenant2); + tenants.writeTenantPath(defaultTenant); + tenants.createTenants(); Set<TenantName> allTenants = tenants.getAllTenantNames(); assertTrue(allTenants.contains(tenant2)); assertEquals("default", defaultTenant.value()); assertTrue(allTenants.contains(defaultTenant)); - assertFalse(allTenants.contains(tenant1)); - newTenants.clear(); - tenants.tenantsChanged(newTenants); + tenants.deleteTenant(tenant1); + tenants.deleteTenant(tenant2); + tenants.deleteTenant(defaultTenant); + tenants.createTenants(); allTenants = tenants.getAllTenantNames(); assertFalse(allTenants.contains(tenant1)); assertFalse(allTenants.contains(tenant2)); assertFalse(allTenants.contains(defaultTenant)); - newTenants.clear(); TenantName foo = TenantName.from("foo"); TenantName bar = TenantName.from("bar"); - newTenants.add(tenant2); - newTenants.add(foo); - newTenants.add(bar); - tenants.tenantsChanged(newTenants); + tenants.writeTenantPath(tenant2); + tenants.writeTenantPath(foo); + tenants.writeTenantPath(bar); + tenants.createTenants(); allTenants = tenants.getAllTenantNames(); assertTrue(allTenants.contains(tenant2)); assertTrue(allTenants.contains(foo)); diff --git a/container-di/src/main/scala/com/yahoo/container/di/Container.scala b/container-di/src/main/scala/com/yahoo/container/di/Container.scala index c82625bae3b..c6812c52242 100644 --- a/container-di/src/main/scala/com/yahoo/container/di/Container.scala +++ b/container-di/src/main/scala/com/yahoo/container/di/Container.scala @@ -183,6 +183,9 @@ class Container( val graph = new ComponentGraph(generation) val componentsConfig = getConfig(componentsConfigKey, configsIncludingBootstrapConfigs) + if (componentsConfig == null) + throw new ConfigurationRuntimeException( + "The set of all configs does not include a valid 'components' config. Config set: " + configsIncludingBootstrapConfigs.keySet) addNodes(componentsConfig, graph) injectNodes(componentsConfig, graph) diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/organization/DeploymentIssues.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/organization/DeploymentIssues.java index 7874fcd8c45..fdebcca6d83 100644 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/organization/DeploymentIssues.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/organization/DeploymentIssues.java @@ -1,6 +1,7 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.hosted.controller.api.integration.organization; +import com.yahoo.component.Version; import com.yahoo.config.provision.ApplicationId; import com.yahoo.vespa.hosted.controller.api.identifiers.PropertyId; @@ -19,7 +20,7 @@ public interface DeploymentIssues { IssueId fileUnlessOpen(Optional<IssueId> issueId, ApplicationId applicationId, User assignee); - IssueId fileUnlessOpen(Collection<ApplicationId> applicationIds); + IssueId fileUnlessOpen(Collection<ApplicationId> applicationIds, Version version); void escalateIfInactive(IssueId issueId, Optional<PropertyId> propertyId, Duration maxInactivity); diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/organization/MockOrganization.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/organization/MockOrganization.java index 4eaca8fb642..5584f8a95fb 100644 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/organization/MockOrganization.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/organization/MockOrganization.java @@ -85,7 +85,7 @@ public class MockOrganization implements Organization { @Override public List<? extends List<? extends User>> contactsFor(PropertyId propertyId) { - return properties.get(propertyId).contacts; + return properties.getOrDefault(propertyId, new PropertyInfo()).contacts; } @Override diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/stubs/LoggingDeploymentIssues.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/stubs/LoggingDeploymentIssues.java index 62dde3efe55..783a1267e64 100644 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/stubs/LoggingDeploymentIssues.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/stubs/LoggingDeploymentIssues.java @@ -3,6 +3,7 @@ package com.yahoo.vespa.hosted.controller.api.integration.stubs; import com.google.inject.Inject; +import com.yahoo.component.Version; import com.yahoo.config.provision.ApplicationId; import com.yahoo.vespa.hosted.controller.api.identifiers.PropertyId; import com.yahoo.vespa.hosted.controller.api.integration.organization.DeploymentIssues; @@ -62,9 +63,9 @@ public class LoggingDeploymentIssues implements DeploymentIssues { } @Override - public IssueId fileUnlessOpen(Collection<ApplicationId> applicationIds) { + public IssueId fileUnlessOpen(Collection<ApplicationId> applicationIds, Version version) { if ( ! platformIssue.get()) - log.info("These applications are all failing deployment:\n" + applicationIds); + log.info("These applications are all failing deployment to version " + version + ":\n" + applicationIds); platformIssue.set(true); return null; diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Application.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Application.java index 6f5116a1825..ee8e1b19e58 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Application.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/Application.java @@ -155,15 +155,18 @@ public class Application { outstandingChange); } - public Application withJobTriggering(JobType type, Optional<Change> change, Instant triggerTime, Controller controller) { + public Application withJobTriggering(long runId, JobType type, Optional<Change> change, String reason, + Instant triggerTime, Controller controller) { return new Application(id, deploymentSpec, validationOverrides, deployments, deploymentJobs.withTriggering(type, change, + runId, determineTriggerVersion(type, controller), determineTriggerRevision(type, controller), + reason, triggerTime), deploying, outstandingChange); diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/ApplicationController.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/ApplicationController.java index 1ff6802f0ab..3339e59d581 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/ApplicationController.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/ApplicationController.java @@ -296,14 +296,17 @@ public class ApplicationController { application = application.withProjectId(options.screwdriverBuildJob.get().screwdriverId.value()); if (application.deploying().isPresent() && application.deploying().get() instanceof Change.ApplicationChange) application = application.withDeploying(Optional.of(Change.ApplicationChange.of(revision))); - if ( ! triggeredWith(revision, application, jobType) && !canDeployDirectlyTo(zone, options) && jobType != null) { - // Triggering information is used to store which changes were made or attempted - // - For all applications, we don't have complete control over which revision is actually built, - // so we update it here with what we actually triggered if necessary + if ( ! canDeployDirectlyTo(zone, options) && jobType != null) { + // Update with (potentially) missing information about what we triggered + JobStatus.JobRun triggering = getOrCreateTriggering(application, version, jobType); application = application.with(application.deploymentJobs() - .withTriggering(jobType, application.deploying(), - version, Optional.of(revision), - clock.instant())); + .withTriggering(jobType, + application.deploying(), + triggering.id(), + version, + Optional.of(revision), + triggering.reason(), + triggering.at())); } // Delete zones not listed in DeploymentSpec, if allowed @@ -390,14 +393,21 @@ public class ApplicationController { return application; } - private boolean triggeredWith(ApplicationRevision revision, Application application, DeploymentJobs.JobType jobType) { - if (jobType == null) return false; + /** + * Returns the existing triggering of the given type from this application, + * or an incomplete one created in this method if none is present + * This is needed (only) in the case where some external entity triggers a job. + */ + private JobStatus.JobRun getOrCreateTriggering(Application application, Version version, DeploymentJobs.JobType jobType) { + if (jobType == null) return incompleteTriggeringEvent(version); JobStatus status = application.deploymentJobs().jobStatus().get(jobType); - if (status == null) return false; - if ( ! status.lastTriggered().isPresent()) return false; - JobStatus.JobRun triggered = status.lastTriggered().get(); - if ( ! triggered.revision().isPresent()) return false; - return triggered.revision().get().equals(revision); + if (status == null) return incompleteTriggeringEvent(version); + if ( ! status.lastTriggered().isPresent()) return incompleteTriggeringEvent(version); + return status.lastTriggered().get(); + } + + private JobStatus.JobRun incompleteTriggeringEvent(Version version) { + return new JobStatus.JobRun(-1, version, Optional.empty(), false, "", clock.instant()); } private DeployOptions withVersion(Version version, DeployOptions options) { diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/DeploymentJobs.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/DeploymentJobs.java index 68e0ce39c5c..8f654c66871 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/DeploymentJobs.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/DeploymentJobs.java @@ -59,21 +59,26 @@ public class DeploymentJobs { Map<JobType, JobStatus> status = new LinkedHashMap<>(this.status); status.compute(report.jobType(), (type, job) -> { if (job == null) job = JobStatus.initial(report.jobType()); - return job.withCompletion(report.jobError(), notificationTime, controller); + return job.withCompletion(report.buildNumber(), report.jobError(), notificationTime, controller); }); return new DeploymentJobs(Optional.of(report.projectId()), status, issueId); } public DeploymentJobs withTriggering(JobType jobType, Optional<Change> change, + long runId, Version version, Optional<ApplicationRevision> revision, + String reason, Instant triggerTime) { Map<JobType, JobStatus> status = new LinkedHashMap<>(this.status); status.compute(jobType, (type, job) -> { if (job == null) job = JobStatus.initial(jobType); - return job.withTriggering(version, revision, + return job.withTriggering(runId, + version, + revision, change.isPresent() && change.get() instanceof Change.VersionChange, + reason, triggerTime); }); return new DeploymentJobs(projectId, status, issueId); diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/JobStatus.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/JobStatus.java index 0b81a45922c..929bf186eea 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/JobStatus.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/JobStatus.java @@ -56,20 +56,22 @@ public class JobStatus { return new JobStatus(type, Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty()); } - public JobStatus withTriggering(Version version, Optional<ApplicationRevision> revision, boolean upgrade, - Instant triggerTime) { - return new JobStatus(type, jobError, Optional.of(new JobRun(version, revision, upgrade, triggerTime)), + public JobStatus withTriggering(long runId, Version version, Optional<ApplicationRevision> revision, + boolean upgrade, String reason, Instant triggerTime) { + return new JobStatus(type, jobError, Optional.of(new JobRun(runId, version, revision, upgrade, reason, triggerTime)), lastCompleted, firstFailing, lastSuccess); } - public JobStatus withCompletion(Optional<DeploymentJobs.JobError> jobError, Instant completionTime, Controller controller) { + public JobStatus withCompletion(long runId, Optional<DeploymentJobs.JobError> jobError, Instant completionTime, Controller controller) { Version version; Optional<ApplicationRevision> revision; boolean upgrade; + String reason; if (type == DeploymentJobs.JobType.component) { // not triggered by us version = controller.systemVersion(); revision = Optional.empty(); upgrade = false; + reason = "Application commit"; } else if (! lastTriggered.isPresent()) { throw new IllegalStateException("Got notified about completion of " + this + @@ -80,9 +82,10 @@ public class JobStatus { version = lastTriggered.get().version(); revision = lastTriggered.get().revision(); upgrade = lastTriggered.get().upgrade(); + reason = lastTriggered.get().reason(); } - JobRun thisCompletion = new JobRun(version, revision, upgrade, completionTime); + JobRun thisCompletion = new JobRun(runId, version, revision, upgrade, reason, completionTime); Optional<JobRun> firstFailing = this.firstFailing; if (jobError.isPresent() && ! this.firstFailing.isPresent()) @@ -168,31 +171,43 @@ public class JobStatus { /** Information about a particular triggering or completion of a run of a job. This is immutable. */ public static class JobRun { + private final long id; private final Version version; private final Optional<ApplicationRevision> revision; - private final Instant at; private final boolean upgrade; + private final String reason; + private final Instant at; - public JobRun(Version version, Optional<ApplicationRevision> revision, boolean upgrade, Instant at) { + public JobRun(long id, Version version, Optional<ApplicationRevision> revision, + boolean upgrade, String reason, Instant at) { Objects.requireNonNull(version, "version cannot be null"); Objects.requireNonNull(revision, "revision cannot be null"); + Objects.requireNonNull(reason, "Reason cannot be null"); Objects.requireNonNull(at, "at cannot be null"); + this.id = id; this.version = version; this.revision = revision; this.upgrade = upgrade; + this.reason = reason; this.at = at; } + + /** Returns the id of this run of this job, or -1 if not known */ + public long id() { return id; } /** Returns whether this job run was a Vespa upgrade */ public boolean upgrade() { return upgrade; } - /** The Vespa version used on this run */ + /** Returns the Vespa version used on this run */ public Version version() { return version; } - /** The application revision used for this run, or empty when not known */ + /** Returns the application revision used for this run, or empty when not known */ public Optional<ApplicationRevision> revision() { return revision; } - /** The time if this triggering or completion */ + /** Returns a human-readable reason for this particular job run */ + public String reason() { return reason; } + + /** Returns the time if this triggering or completion */ public Instant at() { return at; } @Override @@ -203,16 +218,17 @@ public class JobStatus { @Override public boolean equals(Object o) { if (this == o) return true; - if (!(o instanceof JobRun)) return false; + if ( ! (o instanceof JobRun)) return false; JobRun jobRun = (JobRun) o; - return upgrade == jobRun.upgrade && + return id == id && Objects.equals(version, jobRun.version) && Objects.equals(revision, jobRun.revision) && + upgrade == jobRun.upgrade && Objects.equals(at, jobRun.at); } @Override - public String toString() { return "job run of version " + (upgrade() ? "upgrade " : "") + version + " " + public String toString() { return "job run " + id + " of version " + (upgrade() ? "upgrade " : "") + version + " " + revision + " at " + at; } } diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTrigger.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTrigger.java index 6b37c20f2b5..11074e1a68e 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTrigger.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTrigger.java @@ -243,7 +243,7 @@ public class DeploymentTrigger { Application application = applications().require(applicationId); if (application.deploying().isPresent() && ! application.deploymentJobs().hasFailures()) throw new IllegalArgumentException("Could not start " + change + " on " + application + ": " + - application.deploying() + " is already in progress"); + application.deploying().get() + " is already in progress"); application = application.withDeploying(Optional.of(change)); if (change instanceof Change.ApplicationChange) application = application.withOutstandingChange(false); @@ -362,27 +362,27 @@ public class DeploymentTrigger { * @param jobType the type of the job to trigger, or null to trigger nothing * @param application the application to trigger the job for * @param first whether to trigger the job before other jobs - * @param force true to diable checks which should normally prevent this triggering from happening - * @param cause describes why the job is triggered + * @param force true to disable checks which should normally prevent this triggering from happening + * @param reason describes why the job is triggered * @return the application in the triggered state, if actually triggered. This *must* be stored by the caller */ public Application triggerAllowParallel(JobType jobType, Application application, - boolean first, boolean force, String cause, Lock lock) { + boolean first, boolean force, String reason, Lock lock) { if (jobType == null) return application; // we are passed null when the last job has been reached // Never allow untested changes to go through // Note that this may happen because a new change catches up and prevents an older one from continuing if ( ! application.deploymentJobs().isDeployableTo(jobType.environment(), application.deploying())) { log.warning(String.format("Want to trigger %s for %s with reason %s, but change is untested", jobType, - application, cause)); + application, reason)); return application; } if ( ! force && ! allowedTriggering(jobType, application)) return application; log.info(String.format("Triggering %s for %s, %s: %s", jobType, application, application.deploying().map(d -> "deploying " + d).orElse("restarted deployment"), - cause)); + reason)); buildSystem.addJob(application.id(), jobType, first); - return application.withJobTriggering(jobType, application.deploying(), clock.instant(), controller); + return application.withJobTriggering(-1, jobType, application.deploying(), reason, clock.instant(), controller); } /** Returns true if the given proposed job triggering should be effected */ diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentIssueReporter.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentIssueReporter.java index 48033645573..23068b9567f 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentIssueReporter.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentIssueReporter.java @@ -71,7 +71,8 @@ public class DeploymentIssueReporter extends Maintainer { .upgradingTo(controller().systemVersion()) .asList().stream() .map(Application::id) - .collect(Collectors.toList())); + .collect(Collectors.toList()), + controller().systemVersion()); } private boolean oldApplicationChangeFailuresIn(DeploymentJobs jobs) { diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializer.java index fa4341b8e25..3bd1abdf607 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializer.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializer.java @@ -78,10 +78,12 @@ public class ApplicationSerializer { private final String lastSuccessField = "lastSuccess"; // JobRun fields + private final String jobRunIdField = "id"; private final String versionField = "version"; private final String revisionField = "revision"; - private final String atField = "at"; private final String upgradeField = "upgrade"; + private final String reasonField = "reason"; + private final String atField = "at"; // ClusterInfo fields private final String clusterInfoField = "clusterInfo"; @@ -226,10 +228,12 @@ public class ApplicationSerializer { private void jobRunToSlime(Optional<JobStatus.JobRun> jobRun, Cursor parent, String jobRunObjectName) { if ( ! jobRun.isPresent()) return; Cursor object = parent.setObject(jobRunObjectName); + object.setLong(jobRunIdField, jobRun.get().id()); object.setString(versionField, jobRun.get().version().toString()); if ( jobRun.get().revision().isPresent()) toSlime(jobRun.get().revision().get(), object.setObject(revisionField)); object.setBool(upgradeField, jobRun.get().upgrade()); + object.setString(reasonField, jobRun.get().reason()); object.setLong(atField, jobRun.get().at().toEpochMilli()); } @@ -384,9 +388,11 @@ public class ApplicationSerializer { private Optional<JobStatus.JobRun> jobRunFromSlime(Inspector object) { if ( ! object.valid()) return Optional.empty(); - return Optional.of(new JobStatus.JobRun(new Version(object.field(versionField).asString()), + return Optional.of(new JobStatus.JobRun(optionalLong(object.field(jobRunIdField)).orElse(-1L), // TODO: Make non-optional after November 2017 + new Version(object.field(versionField).asString()), applicationRevisionFromSlime(object.field(revisionField)), object.field(upgradeField).asBool(), + optionalString(object.field(reasonField)).orElse(""), // TODO: Make non-optional after November 2017 Instant.ofEpochMilli(object.field(atField).asLong()))); } diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/ErrorResponse.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/ErrorResponse.java index a9643e21c00..4af833baa98 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/ErrorResponse.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/ErrorResponse.java @@ -11,6 +11,7 @@ import static com.yahoo.jdisc.Response.Status.FORBIDDEN; import static com.yahoo.jdisc.Response.Status.INTERNAL_SERVER_ERROR; import static com.yahoo.jdisc.Response.Status.METHOD_NOT_ALLOWED; import static com.yahoo.jdisc.Response.Status.NOT_FOUND; +import static com.yahoo.jdisc.Response.Status.UNAUTHORIZED; /** * A HTTP JSON response containing an error code and a message @@ -24,7 +25,8 @@ public class ErrorResponse extends SlimeJsonResponse { BAD_REQUEST, FORBIDDEN, METHOD_NOT_ALLOWED, - INTERNAL_SERVER_ERROR + INTERNAL_SERVER_ERROR, + UNAUTHORIZED } public ErrorResponse(int statusCode, String errorType, String message) { @@ -55,6 +57,10 @@ public class ErrorResponse extends SlimeJsonResponse { return new ErrorResponse(FORBIDDEN, errorCodes.FORBIDDEN.name(), message); } + public static ErrorResponse unauthorized(String message) { + return new ErrorResponse(UNAUTHORIZED, errorCodes.UNAUTHORIZED.name(), message); + } + public static ErrorResponse methodNotAllowed(String message) { return new ErrorResponse(METHOD_NOT_ALLOWED, errorCodes.METHOD_NOT_ALLOWED.name(), message); } diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java index 6ae9761b305..a259e221a1e 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java @@ -67,7 +67,6 @@ import com.yahoo.vespa.hosted.controller.application.DeploymentMetrics; import com.yahoo.vespa.hosted.controller.application.JobStatus; import com.yahoo.vespa.hosted.controller.application.SourceRevision; import com.yahoo.vespa.hosted.controller.athenz.AthenzClientFactory; -import com.yahoo.vespa.hosted.controller.athenz.AthenzPrincipal; import com.yahoo.vespa.hosted.controller.athenz.NToken; import com.yahoo.vespa.hosted.controller.athenz.ZmsException; import com.yahoo.vespa.hosted.controller.restapi.ErrorResponse; @@ -82,6 +81,7 @@ import com.yahoo.yolean.Exceptions; import javax.ws.rs.BadRequestException; import javax.ws.rs.ForbiddenException; +import javax.ws.rs.NotAuthorizedException; import java.io.IOException; import java.io.InputStream; import java.net.URI; @@ -140,6 +140,9 @@ public class ApplicationApiHandler extends LoggingRequestHandler { catch (ForbiddenException e) { return ErrorResponse.forbidden(Exceptions.toMessageString(e)); } + catch (NotAuthorizedException e) { + return ErrorResponse.unauthorized(Exceptions.toMessageString(e)); + } catch (NotExistsException e) { return ErrorResponse.notFoundError(Exceptions.toMessageString(e)); } @@ -780,25 +783,8 @@ public class ApplicationApiHandler extends LoggingRequestHandler { DeployAuthorizer deployAuthorizer = new DeployAuthorizer(controller.zoneRegistry(), athenzClientFactory); Tenant tenant = controller.tenants().tenant(new TenantId(tenantName)).orElseThrow(() -> new NotExistsException(new TenantId(tenantName))); Principal principal = authorizer.getPrincipal(request); - if (principal instanceof AthenzPrincipal) { - deployAuthorizer.throwIfUnauthorizedForDeploy(principal, - Environment.from(environment), - tenant, - applicationId); - } else { // In case of host-based principal - // TODO What about other user type principals like Bouncer? - log.log(LogLevel.WARNING, - "Using deprecated DeployAuthorizer.throwIfUnauthorizedForDeploy. Principal=" + principal); - UserId userId = new UserId(principal.getName()); - deployAuthorizer.throwIfUnauthorizedForDeploy( - Environment.from(environment), - userId, - tenant, - applicationId, - optional("screwdriverBuildJob", deployOptions).map(ScrewdriverId::new)); - } + deployAuthorizer.throwIfUnauthorizedForDeploy(principal, Environment.from(environment), tenant, applicationId); - // TODO: get rid of the json object DeployOptions deployOptionsJsonClass = new DeployOptions(screwdriverBuildJobFromSlime(deployOptions.field("screwdriverBuildJob")), optional("vespaVersion", deployOptions).map(Version::new), @@ -957,8 +943,10 @@ public class ApplicationApiHandler extends LoggingRequestHandler { } private void toSlime(JobStatus.JobRun jobRun, Cursor object) { + object.setLong("id", jobRun.id()); object.setString("version", jobRun.version().toFullString()); jobRun.revision().ifPresent(revision -> toSlime(revision, object.setObject("revision"))); + object.setString("reason", jobRun.reason()); object.setLong("at", jobRun.at().toEpochMilli()); } diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/Authorizer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/Authorizer.java index 93dc2541385..0c808e30c2a 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/Authorizer.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/Authorizer.java @@ -1,7 +1,6 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.hosted.controller.restapi.application; -import com.google.common.collect.ImmutableSet; import com.yahoo.config.provision.Environment; import com.yahoo.container.jdisc.HttpRequest; import com.yahoo.vespa.hosted.controller.Controller; @@ -12,19 +11,16 @@ import com.yahoo.vespa.hosted.controller.api.identifiers.UserGroup; import com.yahoo.vespa.hosted.controller.api.identifiers.UserId; import com.yahoo.vespa.hosted.controller.api.integration.entity.EntityService; import com.yahoo.vespa.hosted.controller.athenz.AthenzClientFactory; -import com.yahoo.vespa.hosted.controller.athenz.AthenzPrincipal; import com.yahoo.vespa.hosted.controller.athenz.AthenzUtils; import com.yahoo.vespa.hosted.controller.athenz.NToken; import com.yahoo.vespa.hosted.controller.common.ContextAttributes; import com.yahoo.vespa.hosted.controller.restapi.filter.NTokenRequestFilter; -import com.yahoo.vespa.hosted.controller.restapi.filter.UnauthenticatedUserPrincipal; import javax.ws.rs.ForbiddenException; import javax.ws.rs.HttpMethod; import javax.ws.rs.core.SecurityContext; import java.security.Principal; import java.util.Optional; -import java.util.Set; import java.util.logging.Logger; @@ -41,11 +37,6 @@ public class Authorizer { // Must be kept in sync with bouncer filter configuration. private static final String VESPA_HOSTED_ADMIN_ROLE = "10707.A"; - private static final Set<UserId> SCREWDRIVER_USERS = ImmutableSet.of(new UserId("screwdrv"), - new UserId("screwdriver"), - new UserId("sdrvtest"), - new UserId("screwdriver-test")); - private final Controller controller; private final AthenzClientFactory athenzClientFactory; private final EntityService entityService; @@ -92,17 +83,8 @@ public class Authorizer { } public boolean isSuperUser(HttpRequest request) { - // TODO Check membership of admin role in Vespa's Athenz domain - return isMemberOfVespaBouncerGroup(request) || isScrewdriverPrincipal(getPrincipal(request)); - } - - public static boolean isScrewdriverPrincipal(Principal principal) { - if (principal instanceof UnauthenticatedUserPrincipal) // Host-based authentication - return SCREWDRIVER_USERS.contains(new UserId(principal.getName())); - else if (principal instanceof AthenzPrincipal) - return ((AthenzPrincipal)principal).getDomain().equals(AthenzUtils.SCREWDRIVER_DOMAIN); - else - return false; + // TODO Replace check with membership of a dedicated 'hosted Vespa super-user' role in Vespa's Athenz domain + return isMemberOfVespaBouncerGroup(request); } private static ForbiddenException loggedForbiddenException(String message, Object... args) { @@ -149,6 +131,8 @@ public class Authorizer { return method.equals(HttpMethod.GET) || method.equals(HttpMethod.HEAD) || method.equals(HttpMethod.OPTIONS); } + @Deprecated + // TODO Remove this method. Stop using Bouncer for authorization and use Athenz instead private boolean isMemberOfVespaBouncerGroup(HttpRequest request) { Optional<SecurityContext> securityContext = securityContextOf(request); if ( ! securityContext.isPresent() ) throw Authorizer.loggedForbiddenException("User is not authenticated"); diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/DeployAuthorizer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/DeployAuthorizer.java index 7cf19629774..71126259417 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/DeployAuthorizer.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/DeployAuthorizer.java @@ -5,23 +5,19 @@ import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.Environment; import com.yahoo.vespa.hosted.controller.api.Tenant; import com.yahoo.vespa.hosted.controller.api.identifiers.AthenzDomain; -import com.yahoo.vespa.hosted.controller.api.identifiers.ScrewdriverId; -import com.yahoo.vespa.hosted.controller.api.identifiers.UserId; import com.yahoo.vespa.hosted.controller.api.integration.zone.ZoneRegistry; import com.yahoo.vespa.hosted.controller.athenz.ApplicationAction; import com.yahoo.vespa.hosted.controller.athenz.AthenzClientFactory; import com.yahoo.vespa.hosted.controller.athenz.AthenzPrincipal; import com.yahoo.vespa.hosted.controller.athenz.AthenzUtils; import com.yahoo.vespa.hosted.controller.athenz.ZmsException; -import com.yahoo.vespa.hosted.controller.restapi.filter.UnauthenticatedUserPrincipal; import javax.ws.rs.ForbiddenException; +import javax.ws.rs.NotAuthorizedException; import java.security.Principal; -import java.util.Optional; import java.util.logging.Logger; import static com.yahoo.vespa.hosted.controller.restapi.application.Authorizer.environmentRequiresAuthorization; -import static com.yahoo.vespa.hosted.controller.restapi.application.Authorizer.isScrewdriverPrincipal; /** * @author bjorncs @@ -43,36 +39,40 @@ public class DeployAuthorizer { Environment environment, Tenant tenant, ApplicationId applicationId) { - if (athenzCredentialsRequired(environment, tenant, applicationId, principal)) - checkAthenzCredentials(principal, tenant, applicationId); - } - - // TODO: inline when deployment via ssh is removed - private boolean athenzCredentialsRequired(Environment environment, Tenant tenant, ApplicationId applicationId, Principal principal) { - if (!environmentRequiresAuthorization(environment)) return false; - - if (! isScrewdriverPrincipal(principal)) - throw loggedForbiddenException( - "Principal '%s' is not a screwdriver principal, and does not have deploy access to application '%s'", - principal.getName(), applicationId.toShortString()); + if (!environmentRequiresAuthorization(environment)) { + return; + } - return tenant.isAthensTenant(); - } + if (principal == null) { + throw loggedUnauthorizedException("Principal not authenticated!"); + } + if (!(principal instanceof AthenzPrincipal)) { + throw loggedUnauthorizedException( + "Principal '%s' of type '%s' is not an Athenz principal, which is required for production deployments.", + principal.getName(), principal.getClass().getSimpleName()); + } - // TODO: inline when deployment via ssh is removed - private void checkAthenzCredentials(Principal principal, Tenant tenant, ApplicationId applicationId) { - AthenzDomain domain = tenant.getAthensDomain().get(); - if (! (principal instanceof AthenzPrincipal)) - throw loggedForbiddenException("Principal '%s' is not authenticated.", principal.getName()); + AthenzPrincipal athenzPrincipal = (AthenzPrincipal) principal; + AthenzDomain principalDomain = athenzPrincipal.getDomain(); - AthenzPrincipal athensPrincipal = (AthenzPrincipal)principal; - if ( ! hasDeployAccessToAthenzApplication(athensPrincipal, domain, applicationId)) + if (!principalDomain.equals(AthenzUtils.SCREWDRIVER_DOMAIN)) { throw loggedForbiddenException( - "Screwdriver principal '%1$s' does not have deploy access to '%2$s'. " + - "Either the application has not been created at " + zoneRegistry.getDashboardUri() + " or " + - "'%1$s' is not added to the application's deployer role in Athens domain '%3$s'.", - athensPrincipal, applicationId, tenant.getAthensDomain().get()); + "Principal '%s' is not a Screwdriver principal. Excepted principal with Athenz domain '%s', got '%s'.", + principal.getName(), AthenzUtils.SCREWDRIVER_DOMAIN.id(), principalDomain.id()); + } + + // NOTE: no fine-grained deploy authorization for non-Athenz tenants + if (tenant.isAthensTenant()) { + AthenzDomain tenantDomain = tenant.getAthensDomain().get(); + if (!hasDeployAccessToAthenzApplication(athenzPrincipal, tenantDomain, applicationId)) { + throw loggedForbiddenException( + "Screwdriver principal '%1$s' does not have deploy access to '%2$s'. " + + "Either the application has not been created at " + zoneRegistry.getDashboardUri() + " or " + + "'%1$s' is not added to the application's deployer role in Athenz domain '%3$s'.", + athenzPrincipal.toYRN(), applicationId, tenantDomain.id()); + } + } } private static ForbiddenException loggedForbiddenException(String message, Object... args) { @@ -81,23 +81,10 @@ public class DeployAuthorizer { return new ForbiddenException(formattedMessage); } - /** - * @deprecated Only usable for ssh. Use the method that takes Principal instead of UserId and screwdriverId. - */ - @Deprecated - public void throwIfUnauthorizedForDeploy(Environment environment, - UserId userId, - Tenant tenant, - ApplicationId applicationId, - Optional<ScrewdriverId> optionalScrewdriverId) { - Principal principal = new UnauthenticatedUserPrincipal(userId.id()); - - if (athenzCredentialsRequired(environment, tenant, applicationId, principal)) { - ScrewdriverId screwdriverId = optionalScrewdriverId.orElseThrow( - () -> loggedForbiddenException("Screwdriver id must be provided when deploying from Screwdriver.")); - principal = AthenzUtils.createPrincipal(screwdriverId); - checkAthenzCredentials(principal, tenant, applicationId); - } + private static NotAuthorizedException loggedUnauthorizedException(String message, Object... args) { + String formattedMessage = String.format(message, args); + log.info(formattedMessage); + return new NotAuthorizedException(formattedMessage); } private boolean hasDeployAccessToAthenzApplication(AthenzPrincipal principal, AthenzDomain domain, ApplicationId applicationId) { diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTest.java index 30d07bed50f..572e96edf1c 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTest.java @@ -104,12 +104,12 @@ public class ControllerTest { Optional<ApplicationRevision> revision = ((Change.ApplicationChange)tester.controller().applications().require(app1.id()).deploying().get()).revision(); assertTrue("Revision has been set during deployment", revision.isPresent()); assertStatus(JobStatus.initial(stagingTest) - .withTriggering(version1, revision, false, tester.clock().instant()) - .withCompletion(Optional.empty(), tester.clock().instant(), tester.controller()), app1.id(), tester.controller()); + .withTriggering(-1, version1, revision, false, "", tester.clock().instant()) + .withCompletion(-1, Optional.empty(), tester.clock().instant(), tester.controller()), app1.id(), tester.controller()); // Causes first deployment job to be triggered assertStatus(JobStatus.initial(productionCorpUsEast1) - .withTriggering(version1, revision, false, tester.clock().instant()), app1.id(), tester.controller()); + .withTriggering(-1, version1, revision, false, "", tester.clock().instant()), app1.id(), tester.controller()); tester.clock().advance(Duration.ofSeconds(1)); // production job (failing) @@ -117,9 +117,9 @@ public class ControllerTest { assertEquals(4, applications.require(app1.id()).deploymentJobs().jobStatus().size()); JobStatus expectedJobStatus = JobStatus.initial(productionCorpUsEast1) - .withTriggering(version1, revision, false, tester.clock().instant()) // Triggered first without revision info - .withCompletion(Optional.of(JobError.unknown), tester.clock().instant(), tester.controller()) - .withTriggering(version1, revision, false, tester.clock().instant()); // Re-triggering (due to failure) has revision info + .withTriggering(-1, version1, revision, false, "", tester.clock().instant()) // Triggered first without revision info + .withCompletion(-1, Optional.of(JobError.unknown), tester.clock().instant(), tester.controller()) + .withTriggering(-1, version1, revision, false, "", tester.clock().instant()); // Re-triggering (due to failure) has revision info assertStatus(expectedJobStatus, app1.id(), tester.controller()); @@ -139,20 +139,20 @@ public class ControllerTest { applications.notifyJobCompletion(mockReport(app1, component, true)); tester.deployAndNotify(app1, applicationPackage, true, false, systemTest); assertStatus(JobStatus.initial(systemTest) - .withTriggering(version1, revision, false, tester.clock().instant()) - .withCompletion(Optional.empty(), tester.clock().instant(), tester.controller()), app1.id(), tester.controller()); + .withTriggering(-1, version1, revision, false, "", tester.clock().instant()) + .withCompletion(-1, Optional.empty(), tester.clock().instant(), tester.controller()), app1.id(), tester.controller()); tester.deployAndNotify(app1, applicationPackage, true, stagingTest); // production job succeeding now tester.deployAndNotify(app1, applicationPackage, true, productionCorpUsEast1); expectedJobStatus = expectedJobStatus - .withTriggering(version1, revision, false, tester.clock().instant()) - .withCompletion(Optional.empty(), tester.clock().instant(), tester.controller()); + .withTriggering(-1, version1, revision, false, "", tester.clock().instant()) + .withCompletion(-1, Optional.empty(), tester.clock().instant(), tester.controller()); assertStatus(expectedJobStatus, app1.id(), tester.controller()); // causes triggering of next production job assertStatus(JobStatus.initial(productionUsEast3) - .withTriggering(version1, revision, false, tester.clock().instant()), + .withTriggering(-1, version1, revision, false, "", tester.clock().instant()), app1.id(), tester.controller()); tester.deployAndNotify(app1, applicationPackage, true, productionUsEast3); @@ -173,7 +173,10 @@ public class ControllerTest { } assertNotNull("Zone was not removed", applications.require(app1.id()).deployments().get(productionCorpUsEast1.zone(SystemName.main).get())); - assertNotNull("Deployment job was not removed", applications.require(app1.id()).deploymentJobs().jobStatus().get(productionCorpUsEast1)); + JobStatus jobStatus = applications.require(app1.id()).deploymentJobs().jobStatus().get(productionCorpUsEast1); + assertNotNull("Deployment job was not removed", jobStatus); + assertEquals(42, jobStatus.lastCompleted().get().id()); + assertEquals("stagingTest completed successfully in build 42", jobStatus.lastCompleted().get().reason()); // prod zone removal is allowed with override applicationPackage = new ApplicationPackageBuilder() diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentIssueReporterTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentIssueReporterTest.java index b4a2aa3fc19..afafe4be82b 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentIssueReporterTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentIssueReporterTest.java @@ -8,7 +8,6 @@ import com.yahoo.vespa.hosted.controller.Application; import com.yahoo.vespa.hosted.controller.api.integration.organization.IssueId; import com.yahoo.vespa.hosted.controller.api.integration.stubs.LoggingDeploymentIssues; import com.yahoo.vespa.hosted.controller.application.ApplicationPackage; -import com.yahoo.vespa.hosted.controller.application.Change; import com.yahoo.vespa.hosted.controller.deployment.ApplicationPackageBuilder; import com.yahoo.vespa.hosted.controller.deployment.DeploymentTester; import com.yahoo.vespa.hosted.controller.persistence.MockCuratorDb; diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializerTest.java index f7fd7cb6fb1..f65996feac8 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializerTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializerTest.java @@ -68,11 +68,11 @@ public class ApplicationSerializerTest { List<JobStatus> statusList = new ArrayList<>(); statusList.add(JobStatus.initial(DeploymentJobs.JobType.systemTest) - .withTriggering(Version.fromString("5.6.7"), Optional.empty(), true, Instant.ofEpochMilli(7)) - .withCompletion(Optional.empty(), Instant.ofEpochMilli(8), tester.controller())); + .withTriggering(37, Version.fromString("5.6.7"), Optional.empty(), true, "Test", Instant.ofEpochMilli(7)) + .withCompletion(30, Optional.empty(), Instant.ofEpochMilli(8), tester.controller())); statusList.add(JobStatus.initial(DeploymentJobs.JobType.stagingTest) - .withTriggering(Version.fromString("5.6.6"), Optional.empty(), true, Instant.ofEpochMilli(5)) - .withCompletion(Optional.of(JobError.unknown), Instant.ofEpochMilli(6), tester.controller())); + .withTriggering(12, Version.fromString("5.6.6"), Optional.empty(), true, "Test 2", Instant.ofEpochMilli(5)) + .withCompletion(11, Optional.of(JobError.unknown), Instant.ofEpochMilli(6), tester.controller())); DeploymentJobs deploymentJobs = new DeploymentJobs(projectId, statusList, Optional.empty()); diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java index b36a88ca1d4..eeeed08c2b8 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java @@ -577,7 +577,7 @@ public class ApplicationApiTest extends ControllerContainerTest { entity, Request.Method.POST, athenzUserDomain, "mytenant"), - "{\"error-code\":\"FORBIDDEN\",\"message\":\"Principal 'mytenant' is not a screwdriver principal, and does not have deploy access to application 'tenant1.application1'\"}", + "{\"error-code\":\"FORBIDDEN\",\"message\":\"Principal 'mytenant' is not a Screwdriver principal. Excepted principal with Athenz domain 'cd.screwdriver.project', got 'domain1'.\"}", 403); // Deleting an application for an Athens domain the user is not admin for is disallowed diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/application-without-change-multiple-deployments.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/application-without-change-multiple-deployments.json index a82bdaa454a..fe9c373b7d5 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/application-without-change-multiple-deployments.json +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/application-without-change-multiple-deployments.json @@ -4,11 +4,15 @@ "type": "component", "success": true, "lastCompleted": { + "id": 42, "version": "(ignore)", + "reason": "Application commit", "at": "(ignore)" }, "lastSuccess": { + "id": 42, "version": "(ignore)", + "reason": "Application commit", "at": "(ignore)" } }, @@ -16,6 +20,7 @@ "type": "system-test", "success": true, "lastTriggered": { + "id": -1, "version": "(ignore)", "revision": { "hash": "(ignore)", @@ -25,9 +30,11 @@ "gitCommit": "commit1" } }, + "reason": "component completed successfully in build 42", "at": "(ignore)" }, "lastCompleted": { + "id": 42, "version": "(ignore)", "revision": { "hash": "(ignore)", @@ -37,9 +44,11 @@ "gitCommit": "commit1" } }, + "reason": "component completed successfully in build 42", "at": "(ignore)" }, "lastSuccess": { + "id": 42, "version": "(ignore)", "revision": { "hash": "(ignore)", @@ -49,6 +58,7 @@ "gitCommit": "commit1" } }, + "reason": "component completed successfully in build 42", "at": "(ignore)" } }, @@ -56,6 +66,7 @@ "type": "staging-test", "success": true, "lastTriggered": { + "id": -1, "version": "(ignore)", "revision": { "hash": "(ignore)", @@ -65,9 +76,11 @@ "gitCommit": "commit1" } }, + "reason":"systemTest completed successfully in build 42", "at": "(ignore)" }, "lastCompleted": { + "id": 42, "version": "(ignore)", "revision": { "hash": "(ignore)", @@ -77,9 +90,11 @@ "gitCommit": "commit1" } }, + "reason":"systemTest completed successfully in build 42", "at": "(ignore)" }, "lastSuccess": { + "id": 42, "version": "(ignore)", "revision": { "hash": "(ignore)", @@ -89,6 +104,7 @@ "gitCommit": "commit1" } }, + "reason":"systemTest completed successfully in build 42", "at": "(ignore)" } }, @@ -96,6 +112,7 @@ "type": "production-us-west-1", "success": true, "lastTriggered": { + "id": -1, "version": "(ignore)", "revision": { "hash": "(ignore)", @@ -105,9 +122,11 @@ "gitCommit": "commit1" } }, + "reason":"stagingTest completed successfully in build 42", "at": "(ignore)" }, "lastCompleted": { + "id": 42, "version": "(ignore)", "revision": { "hash": "(ignore)", @@ -117,9 +136,11 @@ "gitCommit": "commit1" } }, + "reason":"stagingTest completed successfully in build 42", "at": "(ignore)" }, "lastSuccess": { + "id": 42, "version": "(ignore)", "revision": { "hash": "(ignore)", @@ -129,6 +150,7 @@ "gitCommit": "commit1" } }, + "reason":"stagingTest completed successfully in build 42", "at": "(ignore)" } }, @@ -136,6 +158,7 @@ "type": "production-us-east-3", "success": true, "lastTriggered": { + "id": -1, "version": "(ignore)", "revision": { "hash": "(ignore)", @@ -145,9 +168,11 @@ "gitCommit": "commit1" } }, + "reason":"productionUsWest1 completed successfully in build 42", "at": "(ignore)" }, "lastCompleted": { + "id": 42, "version": "(ignore)", "revision": { "hash": "(ignore)", @@ -157,9 +182,11 @@ "gitCommit": "commit1" } }, + "reason":"productionUsWest1 completed successfully in build 42", "at": "(ignore)" }, "lastSuccess": { + "id": 42, "version": "(ignore)", "revision": { "hash": "(ignore)", @@ -169,6 +196,7 @@ "gitCommit": "commit1" } }, + "reason":"productionUsWest1 completed successfully in build 42", "at": "(ignore)" } } diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/application.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/application.json index cc17e76642f..3dca8103ed7 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/application.json +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/application.json @@ -7,6 +7,7 @@ "type": "system-test", "success": true, "lastTriggered": { + "id": -1, "version": "(ignore)", "revision": { "hash": "(ignore)", @@ -16,9 +17,11 @@ "gitCommit": "commit1" } }, + "reason": "", "at": "(ignore)" }, "lastCompleted": { + "id": 42, "version": "(ignore)", "revision": { "hash": "(ignore)", @@ -28,9 +31,11 @@ "gitCommit": "commit1" } }, + "reason": "", "at": "(ignore)" }, "lastSuccess": { + "id": 42, "version": "(ignore)", "revision": { "hash": "(ignore)", @@ -40,6 +45,7 @@ "gitCommit": "commit1" } }, + "reason": "", "at": "(ignore)" } }, @@ -47,6 +53,7 @@ "type": "staging-test", "success": true, "lastTriggered": { + "id": -1, "version": "(ignore)", "revision": { "hash": "(ignore)", @@ -56,9 +63,11 @@ "gitCommit": "commit1" } }, + "reason": "systemTest completed successfully in build 42", "at": "(ignore)" }, "lastCompleted": { + "id": 42, "version": "(ignore)", "revision": { "hash": "(ignore)", @@ -68,9 +77,11 @@ "gitCommit": "commit1" } }, + "reason": "systemTest completed successfully in build 42", "at": "(ignore)" }, "lastSuccess": { + "id": 42, "version": "(ignore)", "revision": { "hash": "(ignore)", @@ -80,6 +91,7 @@ "gitCommit": "commit1" } }, + "reason": "systemTest completed successfully in build 42", "at": "(ignore)" } }, @@ -87,6 +99,7 @@ "type": "production-corp-us-east-1", "success": false, "lastTriggered": { + "id": -1, "version": "(ignore)", "revision": { "hash": "(ignore)", @@ -96,9 +109,11 @@ "gitCommit": "commit1" } }, + "reason": "Retrying as build 42 just started failing", "at": "(ignore)" }, "lastCompleted": { + "id": 42, "version": "(ignore)", "revision": { "hash": "(ignore)", @@ -108,9 +123,11 @@ "gitCommit": "commit1" } }, + "reason": "stagingTest completed successfully in build 42", "at": "(ignore)" }, "firstFailing": { + "id": 42, "version": "(ignore)", "revision": { "hash": "(ignore)", @@ -120,6 +137,7 @@ "gitCommit": "commit1" } }, + "reason": "stagingTest completed successfully in build 42", "at": "(ignore)" } } diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/opsdb-tenant-with-new-id-without-applications.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/opsdb-tenant-with-new-id-without-applications.json index f00b3c5bb1a..3f4b6017971 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/opsdb-tenant-with-new-id-without-applications.json +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/opsdb-tenant-with-new-id-without-applications.json @@ -5,5 +5,11 @@ "userGroup": "group1", "applications": [ + ], + "propertyUrl": "www.properties.tld/4321", + "contactsUrl": "www.contacts.tld/4321", + "issueCreationUrl": "www.issues.tld/4321", + "contacts": [ + ] } diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/tenant-without-applications.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/tenant-without-applications.json index 88ec5ec7d3d..69669b5dfb8 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/tenant-without-applications.json +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/tenant-without-applications.json @@ -5,4 +5,4 @@ "applications": [ ] -}
\ No newline at end of file +} diff --git a/jdisc_core/src/main/java/com/yahoo/jdisc/AbstractResource.java b/jdisc_core/src/main/java/com/yahoo/jdisc/AbstractResource.java index eddb321cce2..bc3887e2e5f 100644 --- a/jdisc_core/src/main/java/com/yahoo/jdisc/AbstractResource.java +++ b/jdisc_core/src/main/java/com/yahoo/jdisc/AbstractResource.java @@ -14,9 +14,9 @@ import java.util.logging.Level; import java.util.logging.Logger; /** - * <p>This class provides a thread-safe implementation of the {@link SharedResource} interface, and should be used for + * This class provides a thread-safe implementation of the {@link SharedResource} interface, and should be used for * all subclasses of {@link RequestHandler}, {@link ClientProvider} and {@link ServerProvider}. Once the reference count - * of this resource reaches zero, the {@link #destroy()} method is called.</p> + * of this resource reaches zero, the {@link #destroy()} method is called. * * @author Simon Thoresen */ diff --git a/jdisc_core/src/main/java/com/yahoo/jdisc/Container.java b/jdisc_core/src/main/java/com/yahoo/jdisc/Container.java index f98394d28cc..88e4d842bf5 100644 --- a/jdisc_core/src/main/java/com/yahoo/jdisc/Container.java +++ b/jdisc_core/src/main/java/com/yahoo/jdisc/Container.java @@ -26,23 +26,23 @@ import java.net.URI; * <p>The only way to <u>create</u> a new instance of this class is to 1) create and configure a {@link * ContainerBuilder}, and 2) pass that to the {@link ContainerActivator#activateContainer(ContainerBuilder)} method.</p> * - * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a> + * @author Simon Thoresen */ public interface Container extends SharedResource, Timer { /** - * <p>Attempts to find a {@link RequestHandler} in the current server- (if {@link Request#isServerRequest()} is + * Attempts to find a {@link RequestHandler} in the current server- (if {@link Request#isServerRequest()} is * <em>true</em>) or client- (if {@link Request#isServerRequest()} is <em>false</em>) {@link BindingSet} that - * matches the given {@link URI}. If no match can be found, this method returns null.</p> + * matches the given {@link URI}. If no match can be found, this method returns null. * * @param request The Request to match against the bound {@link RequestHandler}s. * @return The matching RequestHandler, or null if there is no match. */ - public RequestHandler resolveHandler(Request request); + RequestHandler resolveHandler(Request request); /** - * <p>Returns the appropriate instance for the given injection key. When feasible, avoid using this method in favor - * of having Guice inject your dependencies ahead of time.</p> + * Returns the appropriate instance for the given injection key. When feasible, avoid using this method in favor + * of having Guice inject your dependencies ahead of time. * * @param key The key of the instance to return. * @param <T> The class of the instance to return. @@ -50,11 +50,11 @@ public interface Container extends SharedResource, Timer { * @throws ConfigurationException If this injector cannot find or create the provider. * @throws ProvisionException If there was a runtime failure while providing an instance. */ - public <T> T getInstance(Key<T> key); + <T> T getInstance(Key<T> key); /** - * <p>Returns the appropriate instance for the given injection type. When feasible, avoid using this method in - * favor of having Guice inject your dependencies ahead of time.</p> + * Returns the appropriate instance for the given injection type. When feasible, avoid using this method in + * favor of having Guice inject your dependencies ahead of time. * * @param type The class object of the instance to return. * @param <T> The class of the instance to return. @@ -62,5 +62,6 @@ public interface Container extends SharedResource, Timer { * @throws ConfigurationException If this injector cannot find or create the provider. * @throws ProvisionException If there was a runtime failure while providing an instance. */ - public <T> T getInstance(Class<T> type); + <T> T getInstance(Class<T> type); + } diff --git a/jdisc_core/src/main/java/com/yahoo/jdisc/HeaderFields.java b/jdisc_core/src/main/java/com/yahoo/jdisc/HeaderFields.java index b8057370219..b24f0a24d20 100644 --- a/jdisc_core/src/main/java/com/yahoo/jdisc/HeaderFields.java +++ b/jdisc_core/src/main/java/com/yahoo/jdisc/HeaderFields.java @@ -6,11 +6,11 @@ import com.google.common.collect.ImmutableList; import java.util.*; /** - * <p>This is an encapsulation of the header fields that belong to either a {@link Request} or a {@link Response}. It is + * This is an encapsulation of the header fields that belong to either a {@link Request} or a {@link Response}. It is * a multimap from String to String, with some additional methods for convenience. The keys of this map are compared by - * ignoring their case, so that <tt>get("foo")</tt> returns the same entry as <tt>get("FOO")</tt>.</p> + * ignoring their case, so that <tt>get("foo")</tt> returns the same entry as <tt>get("FOO")</tt>. * - * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a> + * @author Simon Thoresen */ public class HeaderFields implements Map<String, List<String>> { @@ -302,4 +302,5 @@ public class HeaderFields implements Map<String, List<String>> { throw new UnsupportedOperationException(); } } + } diff --git a/jdisc_core/src/main/java/com/yahoo/jdisc/handler/AbstractContentOutputStream.java b/jdisc_core/src/main/java/com/yahoo/jdisc/handler/AbstractContentOutputStream.java index bc79acc9814..b12bf0d9fe6 100644 --- a/jdisc_core/src/main/java/com/yahoo/jdisc/handler/AbstractContentOutputStream.java +++ b/jdisc_core/src/main/java/com/yahoo/jdisc/handler/AbstractContentOutputStream.java @@ -6,7 +6,7 @@ import java.nio.ByteBuffer; import java.util.Objects; /** - * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen Hult</a> + * @author Simon Thoresen Hult */ abstract class AbstractContentOutputStream extends OutputStream { @@ -25,24 +25,24 @@ abstract class AbstractContentOutputStream extends OutputStream { } @Override - public final void write(byte[] buf, int offset, int length) { - Objects.requireNonNull(buf, "buf"); + public final void write(byte[] buffer, int offset, int length) { + Objects.requireNonNull(buffer, "buf"); if (current == null) { current = ByteBuffer.allocate(BUFFERSIZE + length); } int part = Math.min(length, current.remaining()); - current.put(buf, offset, part); + current.put(buffer, offset, part); if (current.remaining() == 0) { flush(); } if (part < length) { - write(buf, offset + part, length - part); + write(buffer, offset + part, length - part); } } @Override - public final void write(byte[] buf) { - write(buf, 0, buf.length); + public final void write(byte[] buffer) { + write(buffer, 0, buffer.length); } @Override @@ -65,4 +65,5 @@ abstract class AbstractContentOutputStream extends OutputStream { protected abstract void doFlush(ByteBuffer buf); protected abstract void doClose(); + } diff --git a/jdisc_core/src/main/java/com/yahoo/jdisc/handler/BindingNotFoundException.java b/jdisc_core/src/main/java/com/yahoo/jdisc/handler/BindingNotFoundException.java index 151870d57dc..deeccc0472c 100644 --- a/jdisc_core/src/main/java/com/yahoo/jdisc/handler/BindingNotFoundException.java +++ b/jdisc_core/src/main/java/com/yahoo/jdisc/handler/BindingNotFoundException.java @@ -11,7 +11,7 @@ import java.net.URI; * instance of this class will be thrown by the {@link Request#connect(ResponseHandler)} method when the current {@link * BindingSet} has not binding that matches the corresponding Request's URI. * - * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a> + * @author Simon Thoresen */ public final class BindingNotFoundException extends RuntimeException { @@ -35,4 +35,5 @@ public final class BindingNotFoundException extends RuntimeException { public URI uri() { return uri; } + } diff --git a/jdisc_core/src/main/java/com/yahoo/jdisc/handler/BlockingContentWriter.java b/jdisc_core/src/main/java/com/yahoo/jdisc/handler/BlockingContentWriter.java index ab03f282c33..3a2adf60ccf 100644 --- a/jdisc_core/src/main/java/com/yahoo/jdisc/handler/BlockingContentWriter.java +++ b/jdisc_core/src/main/java/com/yahoo/jdisc/handler/BlockingContentWriter.java @@ -6,12 +6,12 @@ import java.util.concurrent.ExecutionException; import java.util.Objects; /** - * <p>This class provides a blocking <em>write</em>-interface to a {@link ContentChannel}. Both {@link + * This class provides a blocking <em>write</em>-interface to a {@link ContentChannel}. Both {@link * #write(ByteBuffer)} and {@link #close()} methods provide an internal {@link CompletionHandler} to the decorated * {@link ContentChannel} calls, and wait for these to be called before returning. If {@link - * CompletionHandler#failed(Throwable)} is called, the corresponding Throwable is thrown to the caller.</p> + * CompletionHandler#failed(Throwable)} is called, the corresponding Throwable is thrown to the caller. * - * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a> + * @author Simon Thoresen * @see FastContentWriter */ public final class BlockingContentWriter { @@ -19,7 +19,7 @@ public final class BlockingContentWriter { private final ContentChannel channel; /** - * <p>Creates a new BlockingContentWriter that encapsulates a given {@link ContentChannel}.</p> + * Creates a new BlockingContentWriter that encapsulates a given {@link ContentChannel}. * * @param content The ContentChannel to encapsulate. * @throws NullPointerException If the <em>content</em> argument is null. @@ -30,7 +30,7 @@ public final class BlockingContentWriter { } /** - * <p>Writes to the underlying {@link ContentChannel} and waits for the operation to complete.</p> + * Writes to the underlying {@link ContentChannel} and waits for the operation to complete. * * @param buf The ByteBuffer to write. * @throws InterruptedException If the thread was interrupted while waiting. @@ -54,7 +54,7 @@ public final class BlockingContentWriter { } /** - * <p>Closes the underlying {@link ContentChannel} and waits for the operation to complete.</p> + * Closes the underlying {@link ContentChannel} and waits for the operation to complete. * * @throws InterruptedException If the thread was interrupted while waiting. * @throws RuntimeException If the operation failed to complete, see cause for details. @@ -75,4 +75,5 @@ public final class BlockingContentWriter { throw new RuntimeException(t); } } + } diff --git a/jdisc_core/src/main/java/com/yahoo/jdisc/handler/BufferedContentChannel.java b/jdisc_core/src/main/java/com/yahoo/jdisc/handler/BufferedContentChannel.java index a93e4430d20..406ee0ff6e5 100644 --- a/jdisc_core/src/main/java/com/yahoo/jdisc/handler/BufferedContentChannel.java +++ b/jdisc_core/src/main/java/com/yahoo/jdisc/handler/BufferedContentChannel.java @@ -7,12 +7,12 @@ import java.util.List; import java.util.Objects; /** - * <p>This class implements an unlimited, non-blocking content queue. All {@link ContentChannel} methods are implemented + * This class implements an unlimited, non-blocking content queue. All {@link ContentChannel} methods are implemented * by pushing to a thread-safe internal queue. All of the queued calls are forwarded to another ContentChannel when * {@link #connectTo(ContentChannel)} is called. Once connected, this class becomes a non-buffering proxy for the - * connected ContentChannel.</p> + * connected ContentChannel. * - * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a> + * @author Simon Thoresen */ public final class BufferedContentChannel implements ContentChannel { @@ -153,4 +153,5 @@ public final class BufferedContentChannel implements ContentChannel { this.buf = buf; } } + } diff --git a/jdisc_core/src/main/java/com/yahoo/jdisc/handler/CallableRequestDispatch.java b/jdisc_core/src/main/java/com/yahoo/jdisc/handler/CallableRequestDispatch.java index 88da723144a..cfbd4edcf8f 100644 --- a/jdisc_core/src/main/java/com/yahoo/jdisc/handler/CallableRequestDispatch.java +++ b/jdisc_core/src/main/java/com/yahoo/jdisc/handler/CallableRequestDispatch.java @@ -6,12 +6,12 @@ import com.yahoo.jdisc.Response; import java.util.concurrent.Callable; /** - * <p>This is a convenient subclass of {@link RequestDispatch} that implements the {@link Callable} interface. This + * This is a convenient subclass of {@link RequestDispatch} that implements the {@link Callable} interface. This * should be used in place of {@link RequestDispatch} if you intend to schedule its execution. Because {@link #call()} * does not return until a {@link Response} becomes available, you can use the <tt>Future</tt> return value of - * <tt>ExecutorService.submit(Callable)</tt> to wait for it.</p> + * <tt>ExecutorService.submit(Callable)</tt> to wait for it. * - * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen Hult</a> + * @author Simon Thoresen Hult */ public abstract class CallableRequestDispatch extends RequestDispatch implements Callable<Response> { @@ -19,4 +19,5 @@ public abstract class CallableRequestDispatch extends RequestDispatch implements public final Response call() throws Exception { return dispatch().get(); } + } diff --git a/jdisc_core/src/main/java/com/yahoo/jdisc/handler/CallableResponseDispatch.java b/jdisc_core/src/main/java/com/yahoo/jdisc/handler/CallableResponseDispatch.java index 0dc9b964798..0471e05a5f9 100644 --- a/jdisc_core/src/main/java/com/yahoo/jdisc/handler/CallableResponseDispatch.java +++ b/jdisc_core/src/main/java/com/yahoo/jdisc/handler/CallableResponseDispatch.java @@ -6,20 +6,20 @@ import com.yahoo.jdisc.Response; import java.util.concurrent.Callable; /** - * <p>This is a convenient subclass of {@link ResponseDispatch} that implements the {@link Callable} interface. This + * This is a convenient subclass of {@link ResponseDispatch} that implements the {@link Callable} interface. This * should be used in place of {@link ResponseDispatch} if you intend to schedule its execution. Because {@link #call()} * does not return until the entirety of the {@link Response} and its content have been consumed, you can use the - * <tt>Future</tt> return value of <tt>ExecutorService.submit(Callable)</tt> to wait for it to complete.</p> + * <tt>Future</tt> return value of <tt>ExecutorService.submit(Callable)</tt> to wait for it to complete. * - * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen Hult</a> + * @author Simon Thoresen Hult */ public abstract class CallableResponseDispatch extends ResponseDispatch implements Callable<Boolean> { private final ResponseHandler handler; /** - * <p>Constructs a new instances of this class over the given {@link ResponseHandler}. Invoking {@link #call()} will - * dispatch to this handler.</p> + * Constructs a new instances of this class over the given {@link ResponseHandler}. Invoking {@link #call()} will + * dispatch to this handler. * * @param handler The ResponseHandler to dispatch to. */ @@ -31,4 +31,5 @@ public abstract class CallableResponseDispatch extends ResponseDispatch implemen public final Boolean call() throws Exception { return dispatch(handler).get(); } + } diff --git a/jdisc_core/src/main/java/com/yahoo/jdisc/handler/CompletionHandler.java b/jdisc_core/src/main/java/com/yahoo/jdisc/handler/CompletionHandler.java index 134a6aaea47..4975f32adfe 100644 --- a/jdisc_core/src/main/java/com/yahoo/jdisc/handler/CompletionHandler.java +++ b/jdisc_core/src/main/java/com/yahoo/jdisc/handler/CompletionHandler.java @@ -18,7 +18,7 @@ import com.yahoo.jdisc.Container; * throughout its lifetime. This also means that the either {@link #completed()} or {@link #failed(Throwable)} MUST be * called in order to release that reference. Failure to do so will prevent the Container from ever shutting down. * - * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a> + * @author Simon Thoresen */ public interface CompletionHandler { @@ -27,7 +27,7 @@ public interface CompletionHandler { * release the internal {@link Container} reference. Failure to do so will prevent the Container from ever shutting * down. */ - public void completed(); + void completed(); /** * Invoked when an operation fails. Notice that you MUST call either this or {@link #completed()} to release the @@ -35,5 +35,6 @@ public interface CompletionHandler { * * @param t The exception to indicate why the I/O operation failed. */ - public void failed(Throwable t); + void failed(Throwable t); + } diff --git a/jdisc_core/src/main/java/com/yahoo/jdisc/handler/ContentChannel.java b/jdisc_core/src/main/java/com/yahoo/jdisc/handler/ContentChannel.java index 9fa78368f62..e01a3c312be 100644 --- a/jdisc_core/src/main/java/com/yahoo/jdisc/handler/ContentChannel.java +++ b/jdisc_core/src/main/java/com/yahoo/jdisc/handler/ContentChannel.java @@ -21,7 +21,7 @@ import java.nio.ByteBuffer; * requirement is regardless of any errors that may occur while calling any of its other methods or its derived {@link * CompletionHandler}s. * - * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a> + * @author Simon Thoresen */ public interface ContentChannel { @@ -33,7 +33,7 @@ public interface ContentChannel { * @param buf The {@link ByteBuffer} to schedule for write. No further calls can be made to this buffer. * @param handler The {@link CompletionHandler} to call after the write has been executed. */ - public void write(ByteBuffer buf, CompletionHandler handler); + void write(ByteBuffer buf, CompletionHandler handler); /** * Closes this ContentChannel. After a channel is closed, any further attempt to invoke {@link #write(ByteBuffer, @@ -45,5 +45,6 @@ public interface ContentChannel { * * @param handler The {@link CompletionHandler} to call after the close has been executed. */ - public void close(CompletionHandler handler); + void close(CompletionHandler handler); + } diff --git a/jdisc_core/src/main/java/com/yahoo/jdisc/handler/ContentInputStream.java b/jdisc_core/src/main/java/com/yahoo/jdisc/handler/ContentInputStream.java index ecc123dc095..3f89ba04aa5 100644 --- a/jdisc_core/src/main/java/com/yahoo/jdisc/handler/ContentInputStream.java +++ b/jdisc_core/src/main/java/com/yahoo/jdisc/handler/ContentInputStream.java @@ -2,15 +2,15 @@ package com.yahoo.jdisc.handler; /** - * <p>This class extends {@link UnsafeContentInputStream} and adds a finalizer to it that calls {@link #close()}. This - * has a performance impact, but ensures that an unclosed stream does not prevent shutdown.</p> + * This class extends {@link UnsafeContentInputStream} and adds a finalizer to it that calls {@link #close()}. This + * has a performance impact, but ensures that an unclosed stream does not prevent shutdown. * - * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a> + * @author Simon Thoresen */ public final class ContentInputStream extends UnsafeContentInputStream { /** - * <p>Constructs a new ContentInputStream that reads from the given {@link ReadableContentChannel}.</p> + * Constructs a new ContentInputStream that reads from the given {@link ReadableContentChannel}. * * @param content The content to read the stream from. */ @@ -26,4 +26,5 @@ public final class ContentInputStream extends UnsafeContentInputStream { super.finalize(); } } + } diff --git a/jdisc_core/src/main/java/com/yahoo/jdisc/handler/FastContentOutputStream.java b/jdisc_core/src/main/java/com/yahoo/jdisc/handler/FastContentOutputStream.java index 1b6af5d7fe3..1ccbfad83c9 100644 --- a/jdisc_core/src/main/java/com/yahoo/jdisc/handler/FastContentOutputStream.java +++ b/jdisc_core/src/main/java/com/yahoo/jdisc/handler/FastContentOutputStream.java @@ -18,7 +18,7 @@ import java.util.concurrent.TimeoutException; * <p>Please notice that the Future implementation of this class will NEVER complete unless {@link #close()} has been * called.</p> * - * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen Hult</a> + * @author Simon Thoresen Hult */ public class FastContentOutputStream extends AbstractContentOutputStream implements ListenableFuture<Boolean> { diff --git a/jdisc_core/src/main/java/com/yahoo/jdisc/handler/FastContentWriter.java b/jdisc_core/src/main/java/com/yahoo/jdisc/handler/FastContentWriter.java index 76cc060b961..86e9851629c 100644 --- a/jdisc_core/src/main/java/com/yahoo/jdisc/handler/FastContentWriter.java +++ b/jdisc_core/src/main/java/com/yahoo/jdisc/handler/FastContentWriter.java @@ -23,7 +23,7 @@ import java.util.concurrent.atomic.AtomicInteger; * <p>Please notice that the Future implementation of this class will NEVER complete unless {@link #close()} has been * called; please use try-with-resources to ensure that close() is called.</p> * - * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen Hult</a> + * @author Simon Thoresen Hult */ public class FastContentWriter implements ListenableFuture<Boolean>, AutoCloseable { diff --git a/jdisc_core/src/main/java/com/yahoo/jdisc/handler/FutureCompletion.java b/jdisc_core/src/main/java/com/yahoo/jdisc/handler/FutureCompletion.java index ec3fa4e3d78..e18c88382b6 100644 --- a/jdisc_core/src/main/java/com/yahoo/jdisc/handler/FutureCompletion.java +++ b/jdisc_core/src/main/java/com/yahoo/jdisc/handler/FutureCompletion.java @@ -11,7 +11,7 @@ import com.google.common.util.concurrent.AbstractFuture; * * <p>Notice that calling {@link #cancel(boolean)} throws an UnsupportedOperationException.</p> * - * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a> + * @author Simon Thoresen */ public final class FutureCompletion extends AbstractFuture<Boolean> implements CompletionHandler { diff --git a/jdisc_core/src/main/java/com/yahoo/jdisc/handler/FutureConjunction.java b/jdisc_core/src/main/java/com/yahoo/jdisc/handler/FutureConjunction.java index 15882e4fd1b..bda0f845af0 100644 --- a/jdisc_core/src/main/java/com/yahoo/jdisc/handler/FutureConjunction.java +++ b/jdisc_core/src/main/java/com/yahoo/jdisc/handler/FutureConjunction.java @@ -15,7 +15,7 @@ import java.util.concurrent.*; * simply create an instance of it and add operands to it using the {@link #addOperand(ListenableFuture)} method.</p> * TODO: consider rewriting usage of FutureConjunction to use CompletableFuture instead. * - * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a> + * @author Simon Thoresen */ public final class FutureConjunction implements ListenableFuture<Boolean> { @@ -94,4 +94,5 @@ public final class FutureConjunction implements ListenableFuture<Boolean> { } return ret; } + } diff --git a/jdisc_core/src/main/java/com/yahoo/jdisc/handler/FutureResponse.java b/jdisc_core/src/main/java/com/yahoo/jdisc/handler/FutureResponse.java index fea4f84416a..3360812864a 100644 --- a/jdisc_core/src/main/java/com/yahoo/jdisc/handler/FutureResponse.java +++ b/jdisc_core/src/main/java/com/yahoo/jdisc/handler/FutureResponse.java @@ -5,10 +5,10 @@ import com.google.common.util.concurrent.AbstractFuture; import com.yahoo.jdisc.Response; /** - * <p>This class provides an implementation of {@link ResponseHandler} that allows you to wait for a {@link Response} to - * be returned.</p> + * This class provides an implementation of {@link ResponseHandler} that allows you to wait for a {@link Response} to + * be returned. * - * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a> + * @author Simon Thoresen */ public final class FutureResponse extends AbstractFuture<Response> implements ResponseHandler { @@ -63,4 +63,5 @@ public final class FutureResponse extends AbstractFuture<Response> implements Re public final boolean isCancelled() { return false; } + } diff --git a/jdisc_core/src/main/java/com/yahoo/jdisc/handler/NullContent.java b/jdisc_core/src/main/java/com/yahoo/jdisc/handler/NullContent.java index a4476affcc5..a561823891f 100644 --- a/jdisc_core/src/main/java/com/yahoo/jdisc/handler/NullContent.java +++ b/jdisc_core/src/main/java/com/yahoo/jdisc/handler/NullContent.java @@ -13,7 +13,7 @@ import java.nio.ByteBuffer; * <p>A {@link RequestHandler}s that does not expect content can simply return the {@link #INSTANCE} of this class for * every invocation of its {@link RequestHandler#handleRequest(Request, ResponseHandler)}.</p> * - * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a> + * @author Simon Thoresen */ public final class NullContent implements ContentChannel { @@ -39,4 +39,5 @@ public final class NullContent implements ContentChannel { handler.completed(); } } + } diff --git a/jdisc_core/src/main/java/com/yahoo/jdisc/handler/OverloadException.java b/jdisc_core/src/main/java/com/yahoo/jdisc/handler/OverloadException.java index 20cab5eb249..ba6ff200a51 100644 --- a/jdisc_core/src/main/java/com/yahoo/jdisc/handler/OverloadException.java +++ b/jdisc_core/src/main/java/com/yahoo/jdisc/handler/OverloadException.java @@ -5,17 +5,14 @@ package com.yahoo.jdisc.handler; * An exception to signal abort current action, as the container is overloaded. * Just unroll state as cheaply as possible. * - * <p> - * The contract of OverloadException (for Jetty) is: - * </p> + * The contract of OverloadException is: * * <ol> - * <li>You must set the response yourself first, or you'll get 500 internal - * server error.</li> - * <li>You must throw it from handleRequest synchronously.</li> + * <li>You must set the response yourself first, or you'll get an internal server error. + * <li>You must throw it from handleRequest synchronously. * </ol> * - * @author <a href="mailto:steinar@yahoo-inc.com">Steinar Knutsen</a> + * @author Steinar Knutsen */ public class OverloadException extends RuntimeException { public OverloadException(String message, Throwable cause) { diff --git a/jdisc_core/src/main/java/com/yahoo/jdisc/handler/ReadableContentChannel.java b/jdisc_core/src/main/java/com/yahoo/jdisc/handler/ReadableContentChannel.java index 27edb619837..50cd2ab2e8c 100644 --- a/jdisc_core/src/main/java/com/yahoo/jdisc/handler/ReadableContentChannel.java +++ b/jdisc_core/src/main/java/com/yahoo/jdisc/handler/ReadableContentChannel.java @@ -15,7 +15,7 @@ import java.util.Queue; * a {@link BufferedContentChannel} up front, and {@link BufferedContentChannel#connectTo(ContentChannel) connect} that * to a ReadableContentChannel at the point where you decide to consume the data.</p> * - * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a> + * @author Simon Thoresen */ public final class ReadableContentChannel implements ContentChannel, Iterable<ByteBuffer> { diff --git a/jdisc_core/src/main/java/com/yahoo/jdisc/handler/RequestDeniedException.java b/jdisc_core/src/main/java/com/yahoo/jdisc/handler/RequestDeniedException.java index 8c7a0c7ca24..012ac92b057 100644 --- a/jdisc_core/src/main/java/com/yahoo/jdisc/handler/RequestDeniedException.java +++ b/jdisc_core/src/main/java/com/yahoo/jdisc/handler/RequestDeniedException.java @@ -11,7 +11,7 @@ import java.net.URI; * or {@link RequestHandler}. There is no automation in throwing an instance of this class, but all RequestHandlers are * encouraged to use this where appropriate.</p> * - * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a> + * @author Simon Thoresen */ public final class RequestDeniedException extends RuntimeException { diff --git a/jdisc_core/src/main/java/com/yahoo/jdisc/handler/RequestDispatch.java b/jdisc_core/src/main/java/com/yahoo/jdisc/handler/RequestDispatch.java index d986bc562ca..211c5474fe6 100644 --- a/jdisc_core/src/main/java/com/yahoo/jdisc/handler/RequestDispatch.java +++ b/jdisc_core/src/main/java/com/yahoo/jdisc/handler/RequestDispatch.java @@ -44,7 +44,7 @@ import java.util.List; * } * </pre> * - * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen Hult</a> + * @author Simon Thoresen Hult */ public abstract class RequestDispatch implements ListenableFuture<Response>, ResponseHandler { diff --git a/jdisc_core/src/main/java/com/yahoo/jdisc/handler/ResponseDispatch.java b/jdisc_core/src/main/java/com/yahoo/jdisc/handler/ResponseDispatch.java index 5fcc19de499..e5e6b752716 100644 --- a/jdisc_core/src/main/java/com/yahoo/jdisc/handler/ResponseDispatch.java +++ b/jdisc_core/src/main/java/com/yahoo/jdisc/handler/ResponseDispatch.java @@ -32,7 +32,7 @@ import java.util.concurrent.Future; * } * </pre> * - * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen Hult</a> + * @author Simon Thoresen Hult */ public abstract class ResponseDispatch extends ForwardingListenableFuture<Boolean> { diff --git a/jdisc_core/src/main/java/com/yahoo/jdisc/handler/ResponseHandler.java b/jdisc_core/src/main/java/com/yahoo/jdisc/handler/ResponseHandler.java index 0b44e68aa0f..8e22192570c 100644 --- a/jdisc_core/src/main/java/com/yahoo/jdisc/handler/ResponseHandler.java +++ b/jdisc_core/src/main/java/com/yahoo/jdisc/handler/ResponseHandler.java @@ -15,7 +15,7 @@ import com.yahoo.jdisc.service.ClientProvider; * corresponding Request, but rather leave that to the implementation of context-aware ResponseHandlers. By creating * light-weight ResponseHandlers on a per-Request basis, any necessary reference can be embedded within.</p> * - * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a> + * @author Simon Thoresen */ public interface ResponseHandler { diff --git a/jdisc_core/src/main/java/com/yahoo/jdisc/handler/ThreadedRequestHandler.java b/jdisc_core/src/main/java/com/yahoo/jdisc/handler/ThreadedRequestHandler.java index d794b2345ab..c50df27120f 100644 --- a/jdisc_core/src/main/java/com/yahoo/jdisc/handler/ThreadedRequestHandler.java +++ b/jdisc_core/src/main/java/com/yahoo/jdisc/handler/ThreadedRequestHandler.java @@ -40,7 +40,7 @@ import java.util.concurrent.TimeUnit; * } * </pre> * - * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a> + * @author Simon Thoresen */ public abstract class ThreadedRequestHandler extends AbstractRequestHandler { diff --git a/jdisc_core/src/main/java/com/yahoo/jdisc/handler/UnsafeContentInputStream.java b/jdisc_core/src/main/java/com/yahoo/jdisc/handler/UnsafeContentInputStream.java index 8fb83595ff2..c895e8fe1a5 100644 --- a/jdisc_core/src/main/java/com/yahoo/jdisc/handler/UnsafeContentInputStream.java +++ b/jdisc_core/src/main/java/com/yahoo/jdisc/handler/UnsafeContentInputStream.java @@ -13,7 +13,7 @@ import java.util.Objects; * always call {@link #close()} before discarding it. Failure to do so will prevent the Container from ever shutting * down.</p> * - * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a> + * @author Simon Thoresen */ public class UnsafeContentInputStream extends InputStream { diff --git a/jdisc_core/src/main/java/com/yahoo/jdisc/service/AbstractServerProvider.java b/jdisc_core/src/main/java/com/yahoo/jdisc/service/AbstractServerProvider.java index e8a841a8c1b..5e45fbab80b 100644 --- a/jdisc_core/src/main/java/com/yahoo/jdisc/service/AbstractServerProvider.java +++ b/jdisc_core/src/main/java/com/yahoo/jdisc/service/AbstractServerProvider.java @@ -12,7 +12,7 @@ import java.util.Objects; * essential {@link #start()} and {@link #close()} methods. It requires that the {@link CurrentContainer} is injected in * the constructor, since that interface is needed to dispatch {@link Request}s.</p> * - * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a> + * @author Simon Thoresen */ public abstract class AbstractServerProvider extends AbstractResource implements ServerProvider { diff --git a/jdisc_core/src/main/java/com/yahoo/jdisc/statistics/ActiveContainerMetrics.java b/jdisc_core/src/main/java/com/yahoo/jdisc/statistics/ActiveContainerMetrics.java index 9a5271fd417..440c9af6bf5 100644 --- a/jdisc_core/src/main/java/com/yahoo/jdisc/statistics/ActiveContainerMetrics.java +++ b/jdisc_core/src/main/java/com/yahoo/jdisc/statistics/ActiveContainerMetrics.java @@ -10,8 +10,10 @@ import com.yahoo.jdisc.core.ActiveContainer; * @author bjorncs */ public interface ActiveContainerMetrics { + String TOTAL_DEACTIVATED_CONTAINERS = "jdisc.deactivated_containers.total"; String DEACTIVATED_CONTAINERS_WITH_RETAINED_REFERENCES = "jdisc.deactivated_containers.with_retained_refs"; void emitMetrics(Metric metric); + } diff --git a/slobrok/src/tests/oldapi/mirror.cpp b/slobrok/src/tests/oldapi/mirror.cpp index e0904fe3a00..2004620d57b 100644 --- a/slobrok/src/tests/oldapi/mirror.cpp +++ b/slobrok/src/tests/oldapi/mirror.cpp @@ -54,14 +54,13 @@ MirrorOld::SpecList MirrorOld::lookup(const std::string & pattern) const { SpecList ret; - _lock.Lock(); + std::lock_guard<std::mutex> guard(_lock); SpecList::const_iterator end = _specs.end(); for (SpecList::const_iterator it = _specs.begin(); it != end; ++it) { if (match(it->first.c_str(), pattern.c_str())) { ret.push_back(*it); } } - _lock.Unlock(); return ret; } @@ -119,10 +118,11 @@ MirrorOld::PerformTask() std::string(s[idx]._str))); } - _lock.Lock(); - std::swap(specs, _specs); - _updates.add(); - _lock.Unlock(); + { + std::lock_guard<std::mutex> guard(_lock); + std::swap(specs, _specs); + _updates.add(); + } _specsGen.setFromInt(answer[2]._intval32); } _backOff.reset(); diff --git a/slobrok/src/tests/oldapi/mirror.h b/slobrok/src/tests/oldapi/mirror.h index de680641c89..fb150b5d64f 100644 --- a/slobrok/src/tests/oldapi/mirror.h +++ b/slobrok/src/tests/oldapi/mirror.h @@ -111,7 +111,7 @@ private: void RequestDone(FRT_RPCRequest *req) override; FRT_Supervisor &_orb; - mutable FastOS_Mutex _lock; + mutable std::mutex _lock; bool _reqDone; SpecList _specs; vespalib::GenCnt _specsGen; diff --git a/slobrok/src/vespa/slobrok/sbregister.cpp b/slobrok/src/vespa/slobrok/sbregister.cpp index 748ec198bc3..a2ec4a4a7b2 100644 --- a/slobrok/src/vespa/slobrok/sbregister.cpp +++ b/slobrok/src/vespa/slobrok/sbregister.cpp @@ -96,10 +96,9 @@ RegisterAPI::~RegisterAPI() void RegisterAPI::registerName(const vespalib::stringref & name) { - _lock.Lock(); + std::lock_guard<std::mutex> guard(_lock); for (uint32_t i = 0; i < _names.size(); ++i) { if (_names[i] == name) { - _lock.Unlock(); return; } } @@ -108,20 +107,18 @@ RegisterAPI::registerName(const vespalib::stringref & name) _pending.push_back(name); discard(_unreg, name); ScheduleNow(); - _lock.Unlock(); } void RegisterAPI::unregisterName(const vespalib::stringref & name) { - _lock.Lock(); + std::lock_guard<std::mutex> guard(_lock); _busy = true; discard(_names, name); discard(_pending, name); _unreg.push_back(name); ScheduleNow(); - _lock.Unlock(); } // handle any request that completed @@ -176,11 +173,12 @@ RegisterAPI::handleReconnect() // try next possible server. _target = _orb.GetTarget(_currSlobrok.c_str()); } - _lock.Lock(); - // now that we have a new connection, we need to - // immediately re-register everything. - _pending = _names; - _lock.Unlock(); + { + std::lock_guard<std::mutex> guard(_lock); + // now that we have a new connection, we need to + // immediately re-register everything. + _pending = _names; + } if (_target == 0) { // we have tried all possible servers. // start from the top after a delay, @@ -206,18 +204,19 @@ RegisterAPI::handlePending() bool unreg = false; bool reg = false; vespalib::string name; - _lock.Lock(); - // pop off the todo stack, unregister has priority - if (_unreg.size() > 0) { - name = _unreg.back(); - _unreg.pop_back(); - unreg = true; - } else if (_pending.size() > 0) { - name = _pending.back(); - _pending.pop_back(); - reg = true; + { + std::lock_guard<std::mutex> guard(_lock); + // pop off the todo stack, unregister has priority + if (_unreg.size() > 0) { + name = _unreg.back(); + _unreg.pop_back(); + unreg = true; + } else if (_pending.size() > 0) { + name = _pending.back(); + _pending.pop_back(); + reg = true; + } } - _lock.Unlock(); if (unreg) { // start a new unregister request @@ -239,12 +238,11 @@ RegisterAPI::handlePending() } else { // nothing more to do right now; schedule to re-register all // names after a long delay. - _lock.Lock(); + std::lock_guard<std::mutex> guard(_lock); _pending = _names; LOG(debug, "done, reschedule in 30s"); _busy = false; Schedule(30.0); - _lock.Unlock(); } } @@ -301,12 +299,11 @@ void RegisterAPI::RPCHooks::rpc_listNamesServed(FRT_RPCRequest *req) { FRT_Values &dst = *req->GetReturn(); - _owner._lock.Lock(); + std::lock_guard<std::mutex> guard(_owner._lock); FRT_StringValue *names = dst.AddStringArray(_owner._names.size()); for (uint32_t i = 0; i < _owner._names.size(); ++i) { dst.SetString(&names[i], _owner._names[i].c_str()); } - _owner._lock.Unlock(); } diff --git a/slobrok/src/vespa/slobrok/sbregister.h b/slobrok/src/vespa/slobrok/sbregister.h index de9f8ed8923..d8812c0f3d0 100644 --- a/slobrok/src/vespa/slobrok/sbregister.h +++ b/slobrok/src/vespa/slobrok/sbregister.h @@ -84,7 +84,7 @@ private: FRT_Supervisor &_orb; RPCHooks _hooks; - FastOS_Mutex _lock; + std::mutex _lock; bool _reqDone; bool _busy; SlobrokList _slobrokSpecs; diff --git a/vespalog/src/vespa/log/bufferedlogger.cpp b/vespalog/src/vespa/log/bufferedlogger.cpp index 106a9bc1dba..5c243b86f86 100644 --- a/vespalog/src/vespa/log/bufferedlogger.cpp +++ b/vespalog/src/vespa/log/bufferedlogger.cpp @@ -1,7 +1,6 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. #include "bufferedlogger.h" -#include <vespa/fastos/mutex.h> #include <boost/multi_index_container.hpp> #include <boost/multi_index/identity.hpp> #include <boost/multi_index/member.hpp> @@ -13,6 +12,7 @@ #include <sstream> #include <vector> #include <cstdarg> +#include <mutex> namespace ns_log { @@ -23,7 +23,7 @@ class BackingBuffer { public: std::unique_ptr<Timer> _timer; /** Lock needed to access cache. */ - mutable FastOS_Mutex _mutex; + mutable std::mutex _mutex; static uint64_t _countFactor; @@ -100,9 +100,8 @@ public: * need to empty buffer before new log messages arive. */ void trimCache() { - _mutex.Lock(); + std::lock_guard<std::mutex> guard(_mutex); trimCache(_timer->getTimestamp()); - _mutex.Unlock(); } /** @@ -240,7 +239,7 @@ BackingBuffer::logImpl(Logger& l, Logger::LogLevel level, { Entry entry(level, file, line, token, message, _timer->getTimestamp(), l); - _mutex.Lock(); + std::lock_guard<std::mutex> guard(_mutex); LogCacheFrontToken::iterator it1 = _cacheFront.get<1>().find(entry); LogCacheBackToken::iterator it2 = _cacheBack.get<1>().find(entry); if (it1 != _cacheFront.get<1>().end()) { @@ -257,13 +256,12 @@ BackingBuffer::logImpl(Logger& l, Logger::LogLevel level, _cacheFront.push_back(entry); } trimCache(entry._timestamp); - _mutex.Unlock(); } void BackingBuffer::flush() { - _mutex.Lock(); + std::lock_guard<std::mutex> guard(_mutex); for (LogCacheBack::const_iterator it = _cacheBack.begin(); it != _cacheBack.end(); ++it) { @@ -276,7 +274,6 @@ BackingBuffer::flush() log(*it); } _cacheFront.clear(); - _mutex.Unlock(); } void @@ -340,7 +337,7 @@ BackingBuffer::toString() const { std::ostringstream ost; ost << "Front log cache content:\n"; - _mutex.Lock(); + std::lock_guard<std::mutex> guard(_mutex); for (LogCacheFront::const_iterator it = _cacheFront.begin(); it != _cacheFront.end(); ++it) { @@ -352,7 +349,6 @@ BackingBuffer::toString() const { ost << " " << it->toString() << "\n"; } - _mutex.Unlock(); return ost.str(); } diff --git a/yolean/src/main/java/com/yahoo/yolean/chain/After.java b/yolean/src/main/java/com/yahoo/yolean/chain/After.java index 582613765cf..cb2caccc6df 100644 --- a/yolean/src/main/java/com/yahoo/yolean/chain/After.java +++ b/yolean/src/main/java/com/yahoo/yolean/chain/After.java @@ -8,16 +8,16 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** - * <p>The component that is annotated with this must be placed later than the components or phases providing names - * contained in the given list.</p> + * The component that is annotated with this must be placed later than the components or phases providing names + * contained in the given list. * * @author tonytv - * @since 5.1.18 */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Inherited public @interface After { - public abstract String[] value() default { }; + String[] value() default { }; + } diff --git a/yolean/src/main/java/com/yahoo/yolean/chain/Before.java b/yolean/src/main/java/com/yahoo/yolean/chain/Before.java index 00e6d6a20b9..108fbff2e21 100644 --- a/yolean/src/main/java/com/yahoo/yolean/chain/Before.java +++ b/yolean/src/main/java/com/yahoo/yolean/chain/Before.java @@ -8,16 +8,16 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** - * <p>The component that is annotated with this must be placed earlier than the components or phases providing names - * contained in the given list.</p> + * The component that is annotated with this must be placed earlier than the components or phases providing names + * contained in the given list. * * @author tonytv - * @since 5.1.18 */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Inherited public @interface Before { - public abstract String[] value() default { }; + String[] value() default { }; + } diff --git a/yolean/src/main/java/com/yahoo/yolean/chain/Chain.java b/yolean/src/main/java/com/yahoo/yolean/chain/Chain.java index b761d2f1834..537d242374d 100644 --- a/yolean/src/main/java/com/yahoo/yolean/chain/Chain.java +++ b/yolean/src/main/java/com/yahoo/yolean/chain/Chain.java @@ -95,4 +95,5 @@ public final class Chain<T> implements Iterable<T> { } return true; } + } diff --git a/yolean/src/main/java/com/yahoo/yolean/chain/ChainCycleException.java b/yolean/src/main/java/com/yahoo/yolean/chain/ChainCycleException.java index b4e0a6815b4..50eff4aabc4 100644 --- a/yolean/src/main/java/com/yahoo/yolean/chain/ChainCycleException.java +++ b/yolean/src/main/java/com/yahoo/yolean/chain/ChainCycleException.java @@ -21,4 +21,5 @@ public class ChainCycleException extends RuntimeException { public List<?> components() { return components; } + } diff --git a/yolean/src/main/java/com/yahoo/yolean/chain/Dependencies.java b/yolean/src/main/java/com/yahoo/yolean/chain/Dependencies.java index 0932a847889..fca0bac753d 100644 --- a/yolean/src/main/java/com/yahoo/yolean/chain/Dependencies.java +++ b/yolean/src/main/java/com/yahoo/yolean/chain/Dependencies.java @@ -182,4 +182,5 @@ public class Dependencies<T> { return new Order<>((U[])null, null, null); } } + } diff --git a/yolean/src/main/java/com/yahoo/yolean/chain/DirectedGraph.java b/yolean/src/main/java/com/yahoo/yolean/chain/DirectedGraph.java index 4a06badcbb0..cd1680b5722 100644 --- a/yolean/src/main/java/com/yahoo/yolean/chain/DirectedGraph.java +++ b/yolean/src/main/java/com/yahoo/yolean/chain/DirectedGraph.java @@ -103,4 +103,5 @@ class DirectedGraph { Collections.<T>emptyList() : list; } + } diff --git a/yolean/src/main/java/com/yahoo/yolean/chain/EnumeratedIdentitySet.java b/yolean/src/main/java/com/yahoo/yolean/chain/EnumeratedIdentitySet.java index ea94b22e3ec..6cd316fb5f5 100644 --- a/yolean/src/main/java/com/yahoo/yolean/chain/EnumeratedIdentitySet.java +++ b/yolean/src/main/java/com/yahoo/yolean/chain/EnumeratedIdentitySet.java @@ -180,4 +180,5 @@ class EnumeratedIdentitySet<T> implements Set<T> { return result; } + } diff --git a/yolean/src/main/java/com/yahoo/yolean/chain/Provides.java b/yolean/src/main/java/com/yahoo/yolean/chain/Provides.java index 277fe20c224..5b02d3bf4d3 100644 --- a/yolean/src/main/java/com/yahoo/yolean/chain/Provides.java +++ b/yolean/src/main/java/com/yahoo/yolean/chain/Provides.java @@ -12,12 +12,12 @@ import java.lang.annotation.Target; * and "after" the string provided here, to impose constraints on ordering.</p> * * @author tonytv - * @since 5.1.18 */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Inherited public @interface Provides { - public abstract String[] value() default { }; + String[] value() default { }; + } |