summaryrefslogtreecommitdiffstats
path: root/controller-server
diff options
context:
space:
mode:
authorValerij Fredriksen <valerij92@gmail.com>2021-01-20 14:08:52 +0100
committerValerij Fredriksen <valerij92@gmail.com>2021-01-20 14:08:52 +0100
commit9a768c486a9bbbb9c36f2d218ebf4d8de3dc590f (patch)
tree3930f41475f69b7fbb3188d2f02f7bf8d815362a /controller-server
parentc68838b159e51eeb6810a1d8689d09f6494a9554 (diff)
Store last login info in tenant
Diffstat (limited to 'controller-server')
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/LockedTenant.java47
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/TenantSerializer.java43
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/tenant/AthenzTenant.java6
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/tenant/CloudTenant.java6
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/tenant/LastLoginInfo.java55
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/tenant/Tenant.java9
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/TenantSerializerTest.java22
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java3
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/filter/SignatureFilterTest.java3
9 files changed, 164 insertions, 30 deletions
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/LockedTenant.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/LockedTenant.java
index d27c585050b..e45bda0708e 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/LockedTenant.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/LockedTenant.java
@@ -13,6 +13,7 @@ import com.yahoo.vespa.hosted.controller.api.identifiers.PropertyId;
import com.yahoo.vespa.hosted.controller.api.integration.organization.Contact;
import com.yahoo.vespa.hosted.controller.tenant.AthenzTenant;
import com.yahoo.vespa.hosted.controller.tenant.CloudTenant;
+import com.yahoo.vespa.hosted.controller.tenant.LastLoginInfo;
import com.yahoo.vespa.hosted.controller.tenant.Tenant;
import com.yahoo.vespa.hosted.controller.tenant.TenantInfo;
@@ -33,10 +34,12 @@ public abstract class LockedTenant {
final TenantName name;
final Instant createdAt;
+ final LastLoginInfo lastLoginInfo;
- private LockedTenant(TenantName name, Instant createdAt) {
+ private LockedTenant(TenantName name, Instant createdAt, LastLoginInfo lastLoginInfo) {
this.name = requireNonNull(name);
this.createdAt = requireNonNull(createdAt);
+ this.lastLoginInfo = requireNonNull(lastLoginInfo);
}
static LockedTenant of(Tenant tenant, Lock lock) {
@@ -50,6 +53,8 @@ public abstract class LockedTenant {
/** Returns a read-only copy of this */
public abstract Tenant get();
+ public abstract LockedTenant with(LastLoginInfo lastLoginInfo);
+
@Override
public String toString() {
return "tenant '" + name + "'";
@@ -65,8 +70,8 @@ public abstract class LockedTenant {
private final Optional<Contact> contact;
private Athenz(TenantName name, AthenzDomain domain, Property property, Optional<PropertyId> propertyId,
- Optional<Contact> contact, Instant createdAt) {
- super(name, createdAt);
+ Optional<Contact> contact, Instant createdAt, LastLoginInfo lastLoginInfo) {
+ super(name, createdAt, lastLoginInfo);
this.domain = domain;
this.property = property;
this.propertyId = propertyId;
@@ -74,28 +79,33 @@ public abstract class LockedTenant {
}
private Athenz(AthenzTenant tenant) {
- this(tenant.name(), tenant.domain(), tenant.property(), tenant.propertyId(), tenant.contact(), tenant.createdAt());
+ this(tenant.name(), tenant.domain(), tenant.property(), tenant.propertyId(), tenant.contact(), tenant.createdAt(), tenant.lastLoginInfo());
}
@Override
public AthenzTenant get() {
- return new AthenzTenant(name, domain, property, propertyId, contact, createdAt);
+ return new AthenzTenant(name, domain, property, propertyId, contact, createdAt, lastLoginInfo);
}
public Athenz with(AthenzDomain domain) {
- return new Athenz(name, domain, property, propertyId, contact, createdAt);
+ return new Athenz(name, domain, property, propertyId, contact, createdAt, lastLoginInfo);
}
public Athenz with(Property property) {
- return new Athenz(name, domain, property, propertyId, contact, createdAt);
+ return new Athenz(name, domain, property, propertyId, contact, createdAt, lastLoginInfo);
}
public Athenz with(PropertyId propertyId) {
- return new Athenz(name, domain, property, Optional.of(propertyId), contact, createdAt);
+ return new Athenz(name, domain, property, Optional.of(propertyId), contact, createdAt, lastLoginInfo);
}
public Athenz with(Contact contact) {
- return new Athenz(name, domain, property, propertyId, Optional.of(contact), createdAt);
+ return new Athenz(name, domain, property, propertyId, Optional.of(contact), createdAt, lastLoginInfo);
+ }
+
+ @Override
+ public LockedTenant with(LastLoginInfo lastLoginInfo) {
+ return new Athenz(name, domain, property, propertyId, contact, createdAt, lastLoginInfo);
}
}
@@ -108,20 +118,20 @@ public abstract class LockedTenant {
private final BiMap<PublicKey, Principal> developerKeys;
private final TenantInfo info;
- private Cloud(TenantName name, Instant createdAt, Optional<Principal> creator, BiMap<PublicKey, Principal> developerKeys, TenantInfo info) {
- super(name, createdAt);
+ private Cloud(TenantName name, Instant createdAt, LastLoginInfo lastLoginInfo, Optional<Principal> creator, BiMap<PublicKey, Principal> developerKeys, TenantInfo info) {
+ super(name, createdAt, lastLoginInfo);
this.developerKeys = ImmutableBiMap.copyOf(developerKeys);
this.creator = creator;
this.info = info;
}
private Cloud(CloudTenant tenant) {
- this(tenant.name(), tenant.createdAt(), Optional.empty(), tenant.developerKeys(), tenant.info());
+ this(tenant.name(), tenant.createdAt(), tenant.lastLoginInfo(), Optional.empty(), tenant.developerKeys(), tenant.info());
}
@Override
public CloudTenant get() {
- return new CloudTenant(name, createdAt, creator, developerKeys, info);
+ return new CloudTenant(name, createdAt, lastLoginInfo, creator, developerKeys, info);
}
public Cloud withDeveloperKey(PublicKey key, Principal principal) {
@@ -129,17 +139,22 @@ public abstract class LockedTenant {
if (keys.containsKey(key))
throw new IllegalArgumentException("Key " + KeyUtils.toPem(key) + " is already owned by " + keys.get(key));
keys.put(key, principal);
- return new Cloud(name, createdAt, creator, keys, info);
+ return new Cloud(name, createdAt, lastLoginInfo, creator, keys, info);
}
public Cloud withoutDeveloperKey(PublicKey key) {
BiMap<PublicKey, Principal> keys = HashBiMap.create(developerKeys);
keys.remove(key);
- return new Cloud(name, createdAt, creator, keys, info);
+ return new Cloud(name, createdAt, lastLoginInfo, creator, keys, info);
}
public Cloud withInfo(TenantInfo newInfo) {
- return new Cloud(name, createdAt, creator, developerKeys, newInfo);
+ return new Cloud(name, createdAt, lastLoginInfo, creator, developerKeys, newInfo);
+ }
+
+ @Override
+ public LockedTenant with(LastLoginInfo lastLoginInfo) {
+ return new Cloud(name, createdAt, lastLoginInfo, creator, developerKeys, info);
}
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/TenantSerializer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/TenantSerializer.java
index 79dbcc23299..3b5b01d16aa 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/TenantSerializer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/TenantSerializer.java
@@ -18,6 +18,7 @@ import com.yahoo.vespa.hosted.controller.api.integration.organization.Contact;
import com.yahoo.vespa.hosted.controller.api.integration.organization.BillingInfo;
import com.yahoo.vespa.hosted.controller.tenant.AthenzTenant;
import com.yahoo.vespa.hosted.controller.tenant.CloudTenant;
+import com.yahoo.vespa.hosted.controller.tenant.LastLoginInfo;
import com.yahoo.vespa.hosted.controller.tenant.Tenant;
import com.yahoo.vespa.hosted.controller.tenant.TenantInfo;
import com.yahoo.vespa.hosted.controller.tenant.TenantInfoAddress;
@@ -28,7 +29,9 @@ import java.security.Principal;
import java.security.PublicKey;
import java.time.Instant;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
import java.util.Optional;
import java.util.function.Supplier;
@@ -66,6 +69,7 @@ public class TenantSerializer {
private static final String productCodeField = "productCode";
private static final String pemDeveloperKeysField = "pemDeveloperKeys";
private static final String tenantInfoField = "info";
+ private static final String lastLoginInfoField = "lastLoginInfo";
public Slime toSlime(Tenant tenant) {
Slime slime = new Slime();
@@ -73,6 +77,7 @@ public class TenantSerializer {
tenantObject.setString(nameField, tenant.name().value());
tenantObject.setString(typeField, valueOf(tenant.type()));
tenantObject.setLong(createdAtField, tenant.createdAt().toEpochMilli());
+ toSlime(tenant.lastLoginInfo(), tenantObject.setObject(lastLoginInfoField));
switch (tenant.type()) {
case athenz: toSlime((AthenzTenant) tenant, tenantObject); break;
@@ -116,6 +121,13 @@ public class TenantSerializer {
billingInfoObject.setString(productCodeField, billingInfo.productCode());
}
+ private void toSlime(LastLoginInfo lastLoginInfo, Cursor lastLoginInfoObject) {
+ for (LastLoginInfo.UserLevel userLevel: LastLoginInfo.UserLevel.values()) {
+ lastLoginInfo.get(userLevel).ifPresent(lastLoginAt ->
+ lastLoginInfoObject.setLong(valueOf(userLevel), lastLoginAt.toEpochMilli()));
+ }
+ }
+
public Tenant tenantFrom(Slime slime, Supplier<Instant> tenantCreateTimeSupplier) {
Inspector tenantObject = slime.get();
Tenant.Type type = typeOf(tenantObject.field(typeField).asString());
@@ -134,16 +146,18 @@ public class TenantSerializer {
Optional<PropertyId> propertyId = SlimeUtils.optionalString(tenantObject.field(propertyIdField)).map(PropertyId::new);
Optional<Contact> contact = contactFrom(tenantObject.field(contactField));
Instant createdAt = SlimeUtils.optionalLong(tenantObject.field(createdAtField)).map(Instant::ofEpochMilli).orElseGet(tenantCreateTimeSupplier);
- return new AthenzTenant(name, domain, property, propertyId, contact, createdAt);
+ LastLoginInfo lastLoginInfo = lastLoginInfoFromSlime(tenantObject.field(lastLoginInfoField));
+ return new AthenzTenant(name, domain, property, propertyId, contact, createdAt, lastLoginInfo);
}
private CloudTenant cloudTenantFrom(Inspector tenantObject, Supplier<Instant> tenantCreateTimeSupplier) {
TenantName name = TenantName.from(tenantObject.field(nameField).asString());
Instant createdAt = SlimeUtils.optionalLong(tenantObject.field(createdAtField)).map(Instant::ofEpochMilli).orElseGet(tenantCreateTimeSupplier);
+ LastLoginInfo lastLoginInfo = lastLoginInfoFromSlime(tenantObject.field(lastLoginInfoField));
Optional<Principal> creator = SlimeUtils.optionalString(tenantObject.field(creatorField)).map(SimplePrincipal::new);
BiMap<PublicKey, Principal> developerKeys = developerKeysFromSlime(tenantObject.field(pemDeveloperKeysField));
TenantInfo info = tenantInfoFromSlime(tenantObject.field(tenantInfoField));
- return new CloudTenant(name, createdAt, creator, developerKeys, info);
+ return new CloudTenant(name, createdAt, lastLoginInfo, creator, developerKeys, info);
}
private BiMap<PublicKey, Principal> developerKeysFromSlime(Inspector array) {
@@ -186,6 +200,13 @@ public class TenantSerializer {
.withAddress(tenantInfoAddressFromSlime(billingObject.field("address")));
}
+ private LastLoginInfo lastLoginInfoFromSlime(Inspector lastLoginInfoObject) {
+ Map<LastLoginInfo.UserLevel, Instant> lastLoginByUserLevel = new HashMap<>();
+ lastLoginInfoObject.traverse((String name, Inspector value) ->
+ lastLoginByUserLevel.put(userLevelOf(name), Instant.ofEpochMilli(value.asLong())));
+ return new LastLoginInfo(lastLoginByUserLevel);
+ }
+
void toSlime(TenantInfo info, Cursor parentCursor) {
if (info.isEmpty()) return;
Cursor infoCursor = parentCursor.setObject("info");
@@ -283,4 +304,22 @@ public class TenantSerializer {
default: throw new IllegalArgumentException("Unexpected tenant type '" + type + "'.");
}
}
+
+ private static LastLoginInfo.UserLevel userLevelOf(String value) {
+ switch (value) {
+ case "user": return LastLoginInfo.UserLevel.user;
+ case "developer": return LastLoginInfo.UserLevel.developer;
+ case "administrator": return LastLoginInfo.UserLevel.administrator;
+ default: throw new IllegalArgumentException("Unknown user level '" + value + "'.");
+ }
+ }
+
+ private static String valueOf(LastLoginInfo.UserLevel userLevel) {
+ switch (userLevel) {
+ case user: return "user";
+ case developer: return "developer";
+ case administrator: return "administrator";
+ default: throw new IllegalArgumentException("Unexpected user level '" + userLevel + "'.");
+ }
+ }
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/tenant/AthenzTenant.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/tenant/AthenzTenant.java
index 7c0e78337ee..7fa46031c98 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/tenant/AthenzTenant.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/tenant/AthenzTenant.java
@@ -27,8 +27,8 @@ public class AthenzTenant extends Tenant {
* Use {@link #create(TenantName, AthenzDomain, Property, Optional, Instant)}.
* */
public AthenzTenant(TenantName name, AthenzDomain domain, Property property, Optional<PropertyId> propertyId,
- Optional<Contact> contact, Instant createdAt) {
- super(name, createdAt, contact);
+ Optional<Contact> contact, Instant createdAt, LastLoginInfo lastLoginInfo) {
+ super(name, createdAt, lastLoginInfo, contact);
this.domain = Objects.requireNonNull(domain, "domain must be non-null");
this.property = Objects.requireNonNull(property, "property must be non-null");
this.propertyId = Objects.requireNonNull(propertyId, "propertyId must be non-null");
@@ -62,7 +62,7 @@ public class AthenzTenant extends Tenant {
/** Create a new Athenz tenant */
public static AthenzTenant create(TenantName name, AthenzDomain domain, Property property,
Optional<PropertyId> propertyId, Instant createdAt) {
- return new AthenzTenant(requireName(name), domain, property, propertyId, Optional.empty(), createdAt);
+ return new AthenzTenant(requireName(name), domain, property, propertyId, Optional.empty(), createdAt, LastLoginInfo.EMPTY);
}
@Override
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/tenant/CloudTenant.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/tenant/CloudTenant.java
index 2ed1ddc40e1..5d0bb780c81 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/tenant/CloudTenant.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/tenant/CloudTenant.java
@@ -23,8 +23,9 @@ public class CloudTenant extends Tenant {
private final TenantInfo info;
/** Public for the serialization layer — do not use! */
- public CloudTenant(TenantName name, Instant createdAt, Optional<Principal> creator, BiMap<PublicKey, Principal> developerKeys, TenantInfo info) {
- super(name, createdAt, Optional.empty());
+ public CloudTenant(TenantName name, Instant createdAt, LastLoginInfo lastLoginInfo, Optional<Principal> creator,
+ BiMap<PublicKey, Principal> developerKeys, TenantInfo info) {
+ super(name, createdAt, lastLoginInfo, Optional.empty());
this.creator = creator;
this.developerKeys = developerKeys;
this.info = Objects.requireNonNull(info);
@@ -34,6 +35,7 @@ public class CloudTenant extends Tenant {
public static CloudTenant create(TenantName tenantName, Instant createdAt, Principal creator) {
return new CloudTenant(requireName(tenantName),
createdAt,
+ LastLoginInfo.EMPTY,
Optional.ofNullable(creator),
ImmutableBiMap.of(), TenantInfo.EMPTY);
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/tenant/LastLoginInfo.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/tenant/LastLoginInfo.java
new file mode 100644
index 00000000000..15f2f97e7d1
--- /dev/null
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/tenant/LastLoginInfo.java
@@ -0,0 +1,55 @@
+// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.controller.tenant;
+
+import java.time.Instant;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+
+/**
+ * @author freva
+ */
+public class LastLoginInfo {
+
+ public static final LastLoginInfo EMPTY = new LastLoginInfo(Map.of());
+
+ private final Map<UserLevel, Instant> lastLoginByUserLevel;
+
+ public LastLoginInfo(Map<UserLevel, Instant> lastLoginByUserLevel) {
+ this.lastLoginByUserLevel = Map.copyOf(lastLoginByUserLevel);
+ }
+
+ public Optional<Instant> get(UserLevel userLevel) {
+ return Optional.ofNullable(lastLoginByUserLevel.get(userLevel));
+ }
+
+ /**
+ * Returns new instance with updated last login time if the given {@code loginAt} timestamp is after the current
+ * for the given {@code userLevel}, otherwise returns this
+ */
+ public LastLoginInfo withLastLoginIfLater(UserLevel userLevel, Instant loginAt) {
+ Instant lastLogin = lastLoginByUserLevel.getOrDefault(userLevel, Instant.EPOCH);
+ if (loginAt.isAfter(lastLogin)) {
+ Map<UserLevel, Instant> lastLoginByUserLevel = new HashMap<>(this.lastLoginByUserLevel);
+ lastLoginByUserLevel.put(userLevel, loginAt);
+ return new LastLoginInfo(lastLoginByUserLevel);
+ }
+ return this;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ LastLoginInfo lastLoginInfo = (LastLoginInfo) o;
+ return lastLoginByUserLevel.equals(lastLoginInfo.lastLoginByUserLevel);
+ }
+
+ @Override
+ public int hashCode() {
+ return lastLoginByUserLevel.hashCode();
+ }
+
+ public enum UserLevel { user, developer, administrator };
+}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/tenant/Tenant.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/tenant/Tenant.java
index 7687796c697..f8b54e7eff3 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/tenant/Tenant.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/tenant/Tenant.java
@@ -17,11 +17,13 @@ public abstract class Tenant {
private final TenantName name;
private final Instant createdAt;
+ private final LastLoginInfo lastLoginInfo;
private final Optional<Contact> contact;
- Tenant(TenantName name, Instant createdAt, Optional<Contact> contact) {
+ Tenant(TenantName name, Instant createdAt, LastLoginInfo lastLoginInfo, Optional<Contact> contact) {
this.name = name;
this.createdAt = createdAt;
+ this.lastLoginInfo = lastLoginInfo;
this.contact = contact;
}
@@ -35,6 +37,11 @@ public abstract class Tenant {
return createdAt;
}
+ /** Returns login information for this tenant */
+ public LastLoginInfo lastLoginInfo() {
+ return lastLoginInfo;
+ }
+
/** Contact information for this tenant */
public Optional<Contact> contact() {
return contact;
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/TenantSerializerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/TenantSerializerTest.java
index c26e4db7996..4fcf4f344e3 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/TenantSerializerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/TenantSerializerTest.java
@@ -13,6 +13,7 @@ import com.yahoo.vespa.hosted.controller.api.integration.organization.Contact;
import com.yahoo.vespa.hosted.controller.api.role.SimplePrincipal;
import com.yahoo.vespa.hosted.controller.tenant.AthenzTenant;
import com.yahoo.vespa.hosted.controller.tenant.CloudTenant;
+import com.yahoo.vespa.hosted.controller.tenant.LastLoginInfo;
import com.yahoo.vespa.hosted.controller.tenant.TenantInfo;
import com.yahoo.vespa.hosted.controller.tenant.TenantInfoAddress;
import com.yahoo.vespa.hosted.controller.tenant.TenantInfoBillingContact;
@@ -21,8 +22,9 @@ import org.junit.Test;
import java.net.URI;
import java.security.PublicKey;
import java.time.Instant;
-import java.util.Collections;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
import java.util.Optional;
import static org.junit.Assert.assertEquals;
@@ -79,7 +81,8 @@ public class TenantSerializerTest {
new Property("property1"),
Optional.of(new PropertyId("1")),
Optional.of(contact()),
- Instant.EPOCH);
+ Instant.EPOCH,
+ lastLoginInfo(321L, 654L, 987L));
AthenzTenant serialized = (AthenzTenant) serializer.tenantFrom(serializer.toSlime(tenant), () -> { throw new UnsupportedOperationException(); });
assertEquals(tenant.contact(), serialized.contact());
}
@@ -88,6 +91,7 @@ public class TenantSerializerTest {
public void cloud_tenant() {
CloudTenant tenant = new CloudTenant(TenantName.from("elderly-lady"),
Instant.ofEpochMilli(1234L),
+ lastLoginInfo(123L, 456L, null),
Optional.of(new SimplePrincipal("foobar-user")),
ImmutableBiMap.of(publicKey, new SimplePrincipal("joe"),
otherPublicKey, new SimplePrincipal("jane")),
@@ -103,6 +107,7 @@ public class TenantSerializerTest {
public void cloud_tenant_with_info() {
CloudTenant tenant = new CloudTenant(TenantName.from("elderly-lady"),
Instant.EPOCH,
+ lastLoginInfo(null, 789L, 654L),
Optional.of(new SimplePrincipal("foobar-user")),
ImmutableBiMap.of(publicKey, new SimplePrincipal("joe"),
otherPublicKey, new SimplePrincipal("jane")),
@@ -156,18 +161,25 @@ public class TenantSerializerTest {
assertEquals(fullInfo, roundTripInfo);
}
- private Contact contact() {
+ private static Contact contact() {
return new Contact(
URI.create("http://contact1.test"),
URI.create("http://property1.test"),
URI.create("http://issue-tracker-1.test"),
List.of(
- Collections.singletonList("person1"),
- Collections.singletonList("person2")
+ List.of("person1"),
+ List.of("person2")
),
"queue",
Optional.empty()
);
}
+ private static LastLoginInfo lastLoginInfo(Long user, Long developer, Long administrator) {
+ Map<LastLoginInfo.UserLevel, Instant> lastLogins = new HashMap<>();
+ Optional.ofNullable(user).map(Instant::ofEpochMilli).ifPresent(i -> lastLogins.put(LastLoginInfo.UserLevel.user, i));
+ Optional.ofNullable(developer).map(Instant::ofEpochMilli).ifPresent(i -> lastLogins.put(LastLoginInfo.UserLevel.developer, i));
+ Optional.ofNullable(administrator).map(Instant::ofEpochMilli).ifPresent(i -> lastLogins.put(LastLoginInfo.UserLevel.administrator, i));
+ return new LastLoginInfo(lastLogins);
+ }
}
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 32f43f57152..2dfcd331d4f 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
@@ -67,6 +67,7 @@ import com.yahoo.vespa.hosted.controller.routing.GlobalRouting;
import com.yahoo.vespa.hosted.controller.security.AthenzCredentials;
import com.yahoo.vespa.hosted.controller.security.AthenzTenantSpec;
import com.yahoo.vespa.hosted.controller.tenant.AthenzTenant;
+import com.yahoo.vespa.hosted.controller.tenant.LastLoginInfo;
import com.yahoo.vespa.hosted.controller.versions.VespaVersion;
import com.yahoo.yolean.Exceptions;
import org.junit.Before;
@@ -1237,7 +1238,7 @@ public class ApplicationApiTest extends ControllerContainerTest {
// Create legacy tenant name containing underscores
tester.controller().curator().writeTenant(new AthenzTenant(TenantName.from("my_tenant"), ATHENZ_TENANT_DOMAIN,
- new Property("property1"), Optional.empty(), Optional.empty(), Instant.EPOCH));
+ new Property("property1"), Optional.empty(), Optional.empty(), Instant.EPOCH, LastLoginInfo.EMPTY));
// POST (add) a Athenz tenant with dashes duplicates existing one with underscores
tester.assertResponse(request("/application/v4/tenant/my-tenant", POST)
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/filter/SignatureFilterTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/filter/SignatureFilterTest.java
index d1f8fbebdb6..2183563cb61 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/filter/SignatureFilterTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/filter/SignatureFilterTest.java
@@ -17,6 +17,7 @@ import com.yahoo.vespa.hosted.controller.api.role.SimplePrincipal;
import com.yahoo.vespa.hosted.controller.application.TenantAndApplicationId;
import com.yahoo.vespa.hosted.controller.restapi.ApplicationRequestToDiscFilterRequestWrapper;
import com.yahoo.vespa.hosted.controller.tenant.CloudTenant;
+import com.yahoo.vespa.hosted.controller.tenant.LastLoginInfo;
import com.yahoo.vespa.hosted.controller.tenant.TenantInfo;
import org.junit.Before;
import org.junit.Test;
@@ -69,6 +70,7 @@ public class SignatureFilterTest {
tester.curator().writeTenant(new CloudTenant(appId.tenant(),
Instant.EPOCH,
+ LastLoginInfo.EMPTY,
Optional.empty(),
ImmutableBiMap.of(),
TenantInfo.EMPTY));
@@ -110,6 +112,7 @@ public class SignatureFilterTest {
// Signed request gets a developer role when a matching developer key is stored for the tenant.
tester.curator().writeTenant(new CloudTenant(appId.tenant(),
Instant.EPOCH,
+ LastLoginInfo.EMPTY,
Optional.empty(),
ImmutableBiMap.of(publicKey, () -> "user"),
TenantInfo.EMPTY));