Expose entity details in MDSAL 11/97111/14
authorRobert Varga <robert.varga@pantheon.tech>
Mon, 9 Aug 2021 16:00:28 +0000 (18:00 +0200)
committerRobert Varga <nite@hq.sk>
Wed, 25 Aug 2021 08:16:04 +0000 (08:16 +0000)
Our previous implementation was very open about it state, which was
completely visible through RESTCONF (and from datastore).

We have a number of tools which need information relating to where each
entity lives -- and to that effect we want to expose a read-only API.

This patch instroduces odl-akka-eos model, which exposes a few RPCs,
which in turn provide information formerly visible from the datastore.

JIRA: CONTROLLER-1992
Change-Id: I85da8941f84ca9e109c4dec61fb07a697e937639
Signed-off-by: Robert Varga <robert.varga@pantheon.tech>
12 files changed:
opendaylight/md-sal/eos-dom-akka/pom.xml
opendaylight/md-sal/eos-dom-akka/src/main/java/org/opendaylight/controller/eos/akka/AkkaEntityOwnershipService.java
opendaylight/md-sal/eos-dom-akka/src/main/java/org/opendaylight/controller/eos/akka/owner/supervisor/OwnerSupervisor.java
opendaylight/md-sal/eos-dom-akka/src/main/java/org/opendaylight/controller/eos/akka/owner/supervisor/command/AbstractEntityRequest.java [new file with mode: 0644]
opendaylight/md-sal/eos-dom-akka/src/main/java/org/opendaylight/controller/eos/akka/owner/supervisor/command/GetEntitiesReply.java [new file with mode: 0644]
opendaylight/md-sal/eos-dom-akka/src/main/java/org/opendaylight/controller/eos/akka/owner/supervisor/command/GetEntitiesRequest.java [new file with mode: 0644]
opendaylight/md-sal/eos-dom-akka/src/main/java/org/opendaylight/controller/eos/akka/owner/supervisor/command/GetEntityOwnerReply.java [new file with mode: 0644]
opendaylight/md-sal/eos-dom-akka/src/main/java/org/opendaylight/controller/eos/akka/owner/supervisor/command/GetEntityOwnerRequest.java [new file with mode: 0644]
opendaylight/md-sal/eos-dom-akka/src/main/java/org/opendaylight/controller/eos/akka/owner/supervisor/command/GetEntityReply.java [new file with mode: 0644]
opendaylight/md-sal/eos-dom-akka/src/main/java/org/opendaylight/controller/eos/akka/owner/supervisor/command/GetEntityRequest.java [new file with mode: 0644]
opendaylight/md-sal/eos-dom-akka/src/main/java/org/opendaylight/controller/eos/akka/owner/supervisor/command/OwnerSupervisorRequest.java [new file with mode: 0644]
opendaylight/md-sal/eos-dom-akka/src/main/yang/odl-akka-eos.yang [new file with mode: 0644]

index a6e68e8a255f2f5284237f245bfbc44dcd676c91..7eda688b5fbf224042f1fb019edff4a8b6cd92b2 100644 (file)
@@ -13,9 +13,9 @@
     <modelVersion>4.0.0</modelVersion>
     <parent>
         <groupId>org.opendaylight.controller</groupId>
     <modelVersion>4.0.0</modelVersion>
     <parent>
         <groupId>org.opendaylight.controller</groupId>
-        <artifactId>bundle-parent</artifactId>
+        <artifactId>mdsal-parent</artifactId>
         <version>4.0.2-SNAPSHOT</version>
         <version>4.0.2-SNAPSHOT</version>
-        <relativePath>../../../bundle-parent</relativePath>
+        <relativePath>../bundle-parent</relativePath>
     </parent>
 
     <artifactId>eos-dom-akka</artifactId>
     </parent>
 
     <artifactId>eos-dom-akka</artifactId>
             <groupId>org.opendaylight.mdsal</groupId>
             <artifactId>mdsal-eos-dom-api</artifactId>
         </dependency>
             <groupId>org.opendaylight.mdsal</groupId>
             <artifactId>mdsal-eos-dom-api</artifactId>
         </dependency>
+        <dependency>
+            <groupId>org.opendaylight.mdsal</groupId>
+            <artifactId>mdsal-binding-api</artifactId>
+        </dependency>
         <dependency>
             <groupId>org.opendaylight.yangtools</groupId>
             <artifactId>concepts</artifactId>
         <dependency>
             <groupId>org.opendaylight.yangtools</groupId>
             <artifactId>concepts</artifactId>
index fdb8a4d02f730224fb8668c104085bf6571cc652..ce60f6f83d26e85aa5f173f60b24ad97e43014ed 100644 (file)
@@ -23,6 +23,7 @@ import java.util.Set;
 import java.util.concurrent.CompletionStage;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.CompletionStage;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ExecutionException;
+import java.util.function.Function;
 import javax.annotation.PreDestroy;
 import javax.inject.Inject;
 import javax.inject.Singleton;
 import javax.annotation.PreDestroy;
 import javax.inject.Inject;
 import javax.inject.Singleton;
@@ -37,13 +38,21 @@ import org.opendaylight.controller.eos.akka.owner.checker.command.GetOwnershipSt
 import org.opendaylight.controller.eos.akka.owner.checker.command.StateCheckerCommand;
 import org.opendaylight.controller.eos.akka.owner.supervisor.command.ActivateDataCenter;
 import org.opendaylight.controller.eos.akka.owner.supervisor.command.DeactivateDataCenter;
 import org.opendaylight.controller.eos.akka.owner.checker.command.StateCheckerCommand;
 import org.opendaylight.controller.eos.akka.owner.supervisor.command.ActivateDataCenter;
 import org.opendaylight.controller.eos.akka.owner.supervisor.command.DeactivateDataCenter;
+import org.opendaylight.controller.eos.akka.owner.supervisor.command.GetEntitiesReply;
+import org.opendaylight.controller.eos.akka.owner.supervisor.command.GetEntitiesRequest;
+import org.opendaylight.controller.eos.akka.owner.supervisor.command.GetEntityOwnerReply;
+import org.opendaylight.controller.eos.akka.owner.supervisor.command.GetEntityOwnerRequest;
+import org.opendaylight.controller.eos.akka.owner.supervisor.command.GetEntityReply;
+import org.opendaylight.controller.eos.akka.owner.supervisor.command.GetEntityRequest;
 import org.opendaylight.controller.eos.akka.owner.supervisor.command.OwnerSupervisorCommand;
 import org.opendaylight.controller.eos.akka.owner.supervisor.command.OwnerSupervisorCommand;
+import org.opendaylight.controller.eos.akka.owner.supervisor.command.OwnerSupervisorReply;
 import org.opendaylight.controller.eos.akka.registry.candidate.command.CandidateRegistryCommand;
 import org.opendaylight.controller.eos.akka.registry.candidate.command.RegisterCandidate;
 import org.opendaylight.controller.eos.akka.registry.candidate.command.UnregisterCandidate;
 import org.opendaylight.controller.eos.akka.registry.listener.type.command.RegisterListener;
 import org.opendaylight.controller.eos.akka.registry.listener.type.command.TypeListenerRegistryCommand;
 import org.opendaylight.controller.eos.akka.registry.listener.type.command.UnregisterListener;
 import org.opendaylight.controller.eos.akka.registry.candidate.command.CandidateRegistryCommand;
 import org.opendaylight.controller.eos.akka.registry.candidate.command.RegisterCandidate;
 import org.opendaylight.controller.eos.akka.registry.candidate.command.UnregisterCandidate;
 import org.opendaylight.controller.eos.akka.registry.listener.type.command.RegisterListener;
 import org.opendaylight.controller.eos.akka.registry.listener.type.command.TypeListenerRegistryCommand;
 import org.opendaylight.controller.eos.akka.registry.listener.type.command.UnregisterListener;
+import org.opendaylight.mdsal.binding.api.RpcProviderService;
 import org.opendaylight.mdsal.eos.common.api.CandidateAlreadyRegisteredException;
 import org.opendaylight.mdsal.eos.common.api.EntityOwnershipState;
 import org.opendaylight.mdsal.eos.dom.api.DOMEntity;
 import org.opendaylight.mdsal.eos.common.api.CandidateAlreadyRegisteredException;
 import org.opendaylight.mdsal.eos.common.api.EntityOwnershipState;
 import org.opendaylight.mdsal.eos.dom.api.DOMEntity;
@@ -51,7 +60,18 @@ import org.opendaylight.mdsal.eos.dom.api.DOMEntityOwnershipCandidateRegistratio
 import org.opendaylight.mdsal.eos.dom.api.DOMEntityOwnershipListener;
 import org.opendaylight.mdsal.eos.dom.api.DOMEntityOwnershipListenerRegistration;
 import org.opendaylight.mdsal.eos.dom.api.DOMEntityOwnershipService;
 import org.opendaylight.mdsal.eos.dom.api.DOMEntityOwnershipListener;
 import org.opendaylight.mdsal.eos.dom.api.DOMEntityOwnershipListenerRegistration;
 import org.opendaylight.mdsal.eos.dom.api.DOMEntityOwnershipService;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.entity.owners.norev.GetEntitiesInput;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.entity.owners.norev.GetEntitiesOutput;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.entity.owners.norev.GetEntityInput;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.entity.owners.norev.GetEntityOutput;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.entity.owners.norev.GetEntityOwnerInput;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.entity.owners.norev.GetEntityOwnerOutput;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.entity.owners.norev.OdlEntityOwnersService;
+import org.opendaylight.yangtools.concepts.Registration;
+import org.opendaylight.yangtools.yang.binding.RpcOutput;
 import org.opendaylight.yangtools.yang.common.Empty;
 import org.opendaylight.yangtools.yang.common.Empty;
+import org.opendaylight.yangtools.yang.common.RpcResult;
+import org.opendaylight.yangtools.yang.common.RpcResultBuilder;
 import org.osgi.service.component.annotations.Activate;
 import org.osgi.service.component.annotations.Component;
 import org.osgi.service.component.annotations.Deactivate;
 import org.osgi.service.component.annotations.Activate;
 import org.osgi.service.component.annotations.Component;
 import org.osgi.service.component.annotations.Deactivate;
@@ -66,10 +86,12 @@ import org.slf4j.LoggerFactory;
  */
 @Singleton
 @Component(immediate = true, service = { DOMEntityOwnershipService.class, DataCenterControl.class })
  */
 @Singleton
 @Component(immediate = true, service = { DOMEntityOwnershipService.class, DataCenterControl.class })
-public class AkkaEntityOwnershipService implements DOMEntityOwnershipService, DataCenterControl, AutoCloseable {
+public class AkkaEntityOwnershipService implements DOMEntityOwnershipService, DataCenterControl, AutoCloseable,
+        OdlEntityOwnersService {
     private static final Logger LOG = LoggerFactory.getLogger(AkkaEntityOwnershipService.class);
     private static final String DATACENTER_PREFIX = "dc";
     private static final Duration DATACENTER_OP_TIMEOUT = Duration.ofSeconds(20);
     private static final Logger LOG = LoggerFactory.getLogger(AkkaEntityOwnershipService.class);
     private static final String DATACENTER_PREFIX = "dc";
     private static final Duration DATACENTER_OP_TIMEOUT = Duration.ofSeconds(20);
+    private static final Duration QUERY_TIMEOUT = Duration.ofSeconds(10);
 
     private final Set<DOMEntity> registeredEntities = ConcurrentHashMap.newKeySet();
     private final String localCandidate;
 
     private final Set<DOMEntity> registeredEntities = ConcurrentHashMap.newKeySet();
     private final String localCandidate;
@@ -83,6 +105,8 @@ public class AkkaEntityOwnershipService implements DOMEntityOwnershipService, Da
     private final ActorRef<StateCheckerCommand> ownerStateChecker;
     protected final ActorRef<OwnerSupervisorCommand> ownerSupervisor;
 
     private final ActorRef<StateCheckerCommand> ownerStateChecker;
     protected final ActorRef<OwnerSupervisorCommand> ownerSupervisor;
 
+    private Registration reg;
+
     @VisibleForTesting
     protected AkkaEntityOwnershipService(final ActorSystem actorSystem)
             throws ExecutionException, InterruptedException {
     @VisibleForTesting
     protected AkkaEntityOwnershipService(final ActorSystem actorSystem)
             throws ExecutionException, InterruptedException {
@@ -111,15 +135,21 @@ public class AkkaEntityOwnershipService implements DOMEntityOwnershipService, Da
 
     @Inject
     @Activate
 
     @Inject
     @Activate
-    public AkkaEntityOwnershipService(@Reference final ActorSystemProvider provider)
-            throws ExecutionException, InterruptedException {
-        this(provider.getActorSystem());
+    public AkkaEntityOwnershipService(@Reference final ActorSystemProvider actorProvider,
+            @Reference final RpcProviderService rpcProvider) throws ExecutionException, InterruptedException {
+        this(actorProvider.getActorSystem());
+
+        reg = rpcProvider.registerRpcImplementation(OdlEntityOwnersService.class, this);
     }
 
     @PreDestroy
     @Deactivate
     @Override
     public void close() throws InterruptedException, ExecutionException {
     }
 
     @PreDestroy
     @Deactivate
     @Override
     public void close() throws InterruptedException, ExecutionException {
+        if (reg != null) {
+            reg.close();
+            reg = null;
+        }
         AskPattern.ask(bootstrap, Terminate::new, Duration.ofSeconds(5), scheduler).toCompletableFuture().get();
     }
 
         AskPattern.ask(bootstrap, Terminate::new, Duration.ofSeconds(5), scheduler).toCompletableFuture().get();
     }
 
@@ -185,6 +215,27 @@ public class AkkaEntityOwnershipService implements DOMEntityOwnershipService, Da
             AskPattern.ask(ownerSupervisor, DeactivateDataCenter::new, DATACENTER_OP_TIMEOUT, scheduler));
     }
 
             AskPattern.ask(ownerSupervisor, DeactivateDataCenter::new, DATACENTER_OP_TIMEOUT, scheduler));
     }
 
+
+    @Override
+    public ListenableFuture<RpcResult<GetEntitiesOutput>> getEntities(final GetEntitiesInput input) {
+        return toRpcFuture(AskPattern.ask(ownerSupervisor, GetEntitiesRequest::new, QUERY_TIMEOUT, scheduler),
+            GetEntitiesReply::toOutput);
+    }
+
+    @Override
+    public ListenableFuture<RpcResult<GetEntityOutput>> getEntity(final GetEntityInput input) {
+        return toRpcFuture(AskPattern.ask(ownerSupervisor,
+            (final ActorRef<GetEntityReply> replyTo) -> new GetEntityRequest(replyTo, input), QUERY_TIMEOUT, scheduler),
+            GetEntityReply::toOutput);
+    }
+
+    @Override
+    public ListenableFuture<RpcResult<GetEntityOwnerOutput>> getEntityOwner(final GetEntityOwnerInput input) {
+        return toRpcFuture(AskPattern.ask(ownerSupervisor,
+            (final ActorRef<GetEntityOwnerReply> replyTo) -> new GetEntityOwnerRequest(replyTo, input), QUERY_TIMEOUT,
+            scheduler), GetEntityOwnerReply::toOutput);
+    }
+
     void unregisterCandidate(final DOMEntity entity) {
         LOG.debug("Unregistering candidate for {}", entity);
 
     void unregisterCandidate(final DOMEntity entity) {
         LOG.debug("Unregistering candidate for {}", entity);
 
@@ -204,11 +255,25 @@ public class AkkaEntityOwnershipService implements DOMEntityOwnershipService, Da
         return runningContext;
     }
 
         return runningContext;
     }
 
+    private static <R extends OwnerSupervisorReply, O extends RpcOutput> ListenableFuture<RpcResult<O>> toRpcFuture(
+            final CompletionStage<R> stage, final Function<R, O> outputFunction) {
+
+        final SettableFuture<RpcResult<O>> future = SettableFuture.create();
+        stage.whenComplete((reply, failure) -> {
+            if (failure != null) {
+                future.setException(failure);
+            } else {
+                future.set(RpcResultBuilder.success(outputFunction.apply(reply)).build());
+            }
+        });
+        return future;
+    }
+
     private static ListenableFuture<Empty> toListenableFuture(final String op, final CompletionStage<?> stage) {
         final SettableFuture<Empty> future = SettableFuture.create();
         stage.whenComplete((reply, failure) -> {
             if (failure != null) {
     private static ListenableFuture<Empty> toListenableFuture(final String op, final CompletionStage<?> stage) {
         final SettableFuture<Empty> future = SettableFuture.create();
         stage.whenComplete((reply, failure) -> {
             if (failure != null) {
-                LOG.warn("{} DataCenter has failed", op, failure);
+                LOG.warn("{} DataCenter failed", op, failure);
                 future.setException(failure);
             } else {
                 LOG.debug("{} DataCenter successful", op);
                 future.setException(failure);
             } else {
                 LOG.debug("{} DataCenter successful", op);
index 047040a65dee43dd69d6edfa3019cae9c8c9cdfe..5210f43dc66cbb9ade382f301cd8c42a2ca9844f 100644 (file)
@@ -40,9 +40,16 @@ import java.util.Map;
 import java.util.Set;
 import java.util.function.BiPredicate;
 import java.util.stream.Collectors;
 import java.util.Set;
 import java.util.function.BiPredicate;
 import java.util.stream.Collectors;
+import org.opendaylight.controller.eos.akka.owner.supervisor.command.AbstractEntityRequest;
 import org.opendaylight.controller.eos.akka.owner.supervisor.command.CandidatesChanged;
 import org.opendaylight.controller.eos.akka.owner.supervisor.command.DataCenterDeactivated;
 import org.opendaylight.controller.eos.akka.owner.supervisor.command.DeactivateDataCenter;
 import org.opendaylight.controller.eos.akka.owner.supervisor.command.CandidatesChanged;
 import org.opendaylight.controller.eos.akka.owner.supervisor.command.DataCenterDeactivated;
 import org.opendaylight.controller.eos.akka.owner.supervisor.command.DeactivateDataCenter;
+import org.opendaylight.controller.eos.akka.owner.supervisor.command.GetEntitiesReply;
+import org.opendaylight.controller.eos.akka.owner.supervisor.command.GetEntitiesRequest;
+import org.opendaylight.controller.eos.akka.owner.supervisor.command.GetEntityOwnerReply;
+import org.opendaylight.controller.eos.akka.owner.supervisor.command.GetEntityOwnerRequest;
+import org.opendaylight.controller.eos.akka.owner.supervisor.command.GetEntityReply;
+import org.opendaylight.controller.eos.akka.owner.supervisor.command.GetEntityRequest;
 import org.opendaylight.controller.eos.akka.owner.supervisor.command.MemberDownEvent;
 import org.opendaylight.controller.eos.akka.owner.supervisor.command.MemberReachableEvent;
 import org.opendaylight.controller.eos.akka.owner.supervisor.command.MemberUnreachableEvent;
 import org.opendaylight.controller.eos.akka.owner.supervisor.command.MemberDownEvent;
 import org.opendaylight.controller.eos.akka.owner.supervisor.command.MemberReachableEvent;
 import org.opendaylight.controller.eos.akka.owner.supervisor.command.MemberUnreachableEvent;
@@ -156,6 +163,9 @@ public final class OwnerSupervisor extends AbstractBehavior<OwnerSupervisorComma
                 .onMessage(MemberDownEvent.class, this::onPeerDown)
                 .onMessage(MemberReachableEvent.class, this::onPeerReachable)
                 .onMessage(MemberUnreachableEvent.class, this::onPeerUnreachable)
                 .onMessage(MemberDownEvent.class, this::onPeerDown)
                 .onMessage(MemberReachableEvent.class, this::onPeerReachable)
                 .onMessage(MemberUnreachableEvent.class, this::onPeerUnreachable)
+                .onMessage(GetEntitiesRequest.class, this::onGetEntities)
+                .onMessage(GetEntityRequest.class, this::onGetEntity)
+                .onMessage(GetEntityOwnerRequest.class, this::onGetEntityOwner)
                 .build();
     }
 
                 .build();
     }
 
@@ -275,8 +285,6 @@ public final class OwnerSupervisor extends AbstractBehavior<OwnerSupervisorComma
                                        final BiPredicate<DOMEntity, String> predicate) {
         LOG.debug("Reassigning owners for {}", entities);
         for (final DOMEntity entity : entities) {
                                        final BiPredicate<DOMEntity, String> predicate) {
         LOG.debug("Reassigning owners for {}", entities);
         for (final DOMEntity entity : entities) {
-
-
             if (predicate.test(entity, oldOwner)) {
                 ownerToEntity.remove(oldOwner, entity);
                 assignOwnerFor(entity);
             if (predicate.test(entity, oldOwner)) {
                 ownerToEntity.remove(oldOwner, entity);
                 assignOwnerFor(entity);
@@ -356,6 +364,22 @@ public final class OwnerSupervisor extends AbstractBehavior<OwnerSupervisorComma
         return this;
     }
 
         return this;
     }
 
+    private Behavior<OwnerSupervisorCommand> onGetEntities(final GetEntitiesRequest request) {
+        request.getReplyTo().tell(new GetEntitiesReply(currentOwners, currentCandidates));
+        return this;
+    }
+
+    private Behavior<OwnerSupervisorCommand> onGetEntity(final GetEntityRequest request) {
+        final DOMEntity entity = extractEntity(request);
+        request.getReplyTo().tell(new GetEntityReply(currentOwners.get(entity), currentCandidates.get(entity)));
+        return this;
+    }
+
+    private Behavior<OwnerSupervisorCommand> onGetEntityOwner(final GetEntityOwnerRequest request) {
+        request.getReplyTo().tell(new GetEntityOwnerReply(currentOwners.get(extractEntity(request))));
+        return this;
+    }
+
     private void handleReachableEvent(final Set<String> roles) {
         if (roles.contains(dataCenter)) {
             activeMembers.add(extractRole(roles));
     private void handleReachableEvent(final Set<String> roles) {
         if (roles.contains(dataCenter)) {
             activeMembers.add(extractRole(roles));
@@ -406,6 +430,10 @@ public final class OwnerSupervisor extends AbstractBehavior<OwnerSupervisorComma
         return members;
     }
 
         return members;
     }
 
+    private static DOMEntity extractEntity(final AbstractEntityRequest<?> request) {
+        return new DOMEntity(request.getType().getValue(), request.getName().getValue());
+    }
+
     private static String extractRole(final Member member) {
         return extractRole(member.getRoles());
     }
     private static String extractRole(final Member member) {
         return extractRole(member.getRoles());
     }
diff --git a/opendaylight/md-sal/eos-dom-akka/src/main/java/org/opendaylight/controller/eos/akka/owner/supervisor/command/AbstractEntityRequest.java b/opendaylight/md-sal/eos-dom-akka/src/main/java/org/opendaylight/controller/eos/akka/owner/supervisor/command/AbstractEntityRequest.java
new file mode 100644 (file)
index 0000000..b3b4c8c
--- /dev/null
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2021 PANTHEON.tech, s.r.o. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.eos.akka.owner.supervisor.command;
+
+import akka.actor.typed.ActorRef;
+import org.eclipse.jdt.annotation.NonNull;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.entity.owners.norev.EntityId;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.entity.owners.norev.EntityName;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.entity.owners.norev.EntityType;
+
+public abstract class AbstractEntityRequest<T extends OwnerSupervisorReply> extends OwnerSupervisorRequest<T> {
+    private static final long serialVersionUID = 1L;
+
+    private final @NonNull EntityType type;
+    private final @NonNull EntityName name;
+
+    AbstractEntityRequest(final ActorRef<T> replyTo, final EntityId entity) {
+        super(replyTo);
+        this.type = entity.requireType();
+        this.name = entity.requireName();
+    }
+
+    public final @NonNull EntityType getType() {
+        return type;
+    }
+
+    public final @NonNull EntityName getName() {
+        return name;
+    }
+}
diff --git a/opendaylight/md-sal/eos-dom-akka/src/main/java/org/opendaylight/controller/eos/akka/owner/supervisor/command/GetEntitiesReply.java b/opendaylight/md-sal/eos-dom-akka/src/main/java/org/opendaylight/controller/eos/akka/owner/supervisor/command/GetEntitiesReply.java
new file mode 100644 (file)
index 0000000..ea8a5a8
--- /dev/null
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) 2021 PANTHEON.tech, s.r.o. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.eos.akka.owner.supervisor.command;
+
+import static com.google.common.base.Verify.verify;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSetMultimap;
+import com.google.common.collect.ImmutableSetMultimap.Builder;
+import com.google.common.collect.Iterables;
+import java.io.Serializable;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.stream.Collectors;
+import org.eclipse.jdt.annotation.NonNull;
+import org.opendaylight.mdsal.eos.dom.api.DOMEntity;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.entity.owners.norev.EntityName;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.entity.owners.norev.EntityType;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.entity.owners.norev.GetEntitiesOutput;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.entity.owners.norev.GetEntitiesOutputBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.entity.owners.norev.NodeName;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.entity.owners.norev.get.entities.output.EntitiesBuilder;
+import org.opendaylight.yangtools.yang.binding.util.BindingMap;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
+
+public final class GetEntitiesReply extends OwnerSupervisorReply implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    private final ImmutableSetMultimap<DOMEntity, String> candidates;
+    private final ImmutableMap<DOMEntity, String> owners;
+
+    public GetEntitiesReply(final Map<DOMEntity, String> owners, final Map<DOMEntity, Set<String>> candidates) {
+        final Builder<DOMEntity, String> builder = ImmutableSetMultimap.builder();
+        for (Entry<DOMEntity, Set<String>> entry : candidates.entrySet()) {
+            builder.putAll(entry.getKey(), entry.getValue());
+        }
+        this.candidates = builder.build();
+        this.owners = ImmutableMap.copyOf(owners);
+    }
+
+    public @NonNull GetEntitiesOutput toOutput() {
+        final Set<DOMEntity> entities = new HashSet<>();
+        entities.addAll(owners.keySet());
+        entities.addAll(candidates.keySet());
+
+        return new GetEntitiesOutputBuilder()
+            .setEntities(entities.stream()
+                .map(entity -> {
+                    final EntitiesBuilder eb = new EntitiesBuilder()
+                        .setType(new EntityType(entity.getType()))
+                        .setName(extractName(entity))
+                        .setCandidateNodes(candidates.get(entity).stream()
+                            .map(NodeName::new).collect(Collectors.toUnmodifiableList()));
+
+                    final String owner = owners.get(entity);
+                    if (owner != null) {
+                        eb.setOwnerNode(new NodeName(owner));
+                    }
+                    return eb.build();
+                })
+                .collect(BindingMap.toMap()))
+            .build();
+    }
+
+    private static EntityName extractName(final DOMEntity entity) {
+        final PathArgument last = entity.getIdentifier().getLastPathArgument();
+        verify(last instanceof NodeIdentifierWithPredicates, "Unexpected last argument %s", last);
+        final Object value = Iterables.getOnlyElement(((NodeIdentifierWithPredicates) last).values());
+        verify(value instanceof String, "Unexpected predicate value %s", value);
+        return new EntityName((String) value);
+    }
+}
diff --git a/opendaylight/md-sal/eos-dom-akka/src/main/java/org/opendaylight/controller/eos/akka/owner/supervisor/command/GetEntitiesRequest.java b/opendaylight/md-sal/eos-dom-akka/src/main/java/org/opendaylight/controller/eos/akka/owner/supervisor/command/GetEntitiesRequest.java
new file mode 100644 (file)
index 0000000..fdea832
--- /dev/null
@@ -0,0 +1,18 @@
+/*
+ * Copyright (c) 2021 PANTHEON.tech, s.r.o. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.eos.akka.owner.supervisor.command;
+
+import akka.actor.typed.ActorRef;
+
+public final class GetEntitiesRequest extends OwnerSupervisorRequest<GetEntitiesReply> {
+    private static final long serialVersionUID = 1L;
+
+    public GetEntitiesRequest(final ActorRef<GetEntitiesReply> replyTo) {
+        super(replyTo);
+    }
+}
diff --git a/opendaylight/md-sal/eos-dom-akka/src/main/java/org/opendaylight/controller/eos/akka/owner/supervisor/command/GetEntityOwnerReply.java b/opendaylight/md-sal/eos-dom-akka/src/main/java/org/opendaylight/controller/eos/akka/owner/supervisor/command/GetEntityOwnerReply.java
new file mode 100644 (file)
index 0000000..282fc41
--- /dev/null
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2021 PANTHEON.tech, s.r.o. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.eos.akka.owner.supervisor.command;
+
+import java.io.Serializable;
+import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.Nullable;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.entity.owners.norev.GetEntityOwnerOutput;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.entity.owners.norev.GetEntityOwnerOutputBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.entity.owners.norev.NodeName;
+
+public final class GetEntityOwnerReply extends OwnerSupervisorReply implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    private final String owner;
+
+    public GetEntityOwnerReply(final @Nullable String owner) {
+        this.owner = owner;
+    }
+
+    public @NonNull GetEntityOwnerOutput toOutput() {
+        final GetEntityOwnerOutputBuilder builder = new GetEntityOwnerOutputBuilder();
+        if (owner != null) {
+            builder.setOwnerNode(new NodeName(owner));
+        }
+        return builder.build();
+    }
+}
diff --git a/opendaylight/md-sal/eos-dom-akka/src/main/java/org/opendaylight/controller/eos/akka/owner/supervisor/command/GetEntityOwnerRequest.java b/opendaylight/md-sal/eos-dom-akka/src/main/java/org/opendaylight/controller/eos/akka/owner/supervisor/command/GetEntityOwnerRequest.java
new file mode 100644 (file)
index 0000000..6b341d3
--- /dev/null
@@ -0,0 +1,19 @@
+/*
+ * Copyright (c) 2021 PANTHEON.tech, s.r.o. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.eos.akka.owner.supervisor.command;
+
+import akka.actor.typed.ActorRef;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.entity.owners.norev.EntityId;
+
+public final class GetEntityOwnerRequest extends AbstractEntityRequest<GetEntityOwnerReply> {
+    private static final long serialVersionUID = 1L;
+
+    public GetEntityOwnerRequest(final ActorRef<GetEntityOwnerReply> replyTo, final EntityId entity) {
+        super(replyTo, entity);
+    }
+}
diff --git a/opendaylight/md-sal/eos-dom-akka/src/main/java/org/opendaylight/controller/eos/akka/owner/supervisor/command/GetEntityReply.java b/opendaylight/md-sal/eos-dom-akka/src/main/java/org/opendaylight/controller/eos/akka/owner/supervisor/command/GetEntityReply.java
new file mode 100644 (file)
index 0000000..6fae25c
--- /dev/null
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2021 PANTHEON.tech, s.r.o. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.eos.akka.owner.supervisor.command;
+
+import com.google.common.collect.ImmutableSet;
+import java.io.Serializable;
+import java.util.Set;
+import java.util.stream.Collectors;
+import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.Nullable;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.entity.owners.norev.GetEntityOutput;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.entity.owners.norev.GetEntityOutputBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.entity.owners.norev.NodeName;
+
+public final class GetEntityReply extends OwnerSupervisorReply implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    private final ImmutableSet<String> candidates;
+    private final String owner;
+
+    public GetEntityReply(final @Nullable String owner, final @Nullable Set<String> candidates) {
+        this.owner = owner;
+        this.candidates = candidates == null ? ImmutableSet.of() : ImmutableSet.copyOf(candidates);
+    }
+
+    public @NonNull GetEntityOutput toOutput() {
+        final GetEntityOutputBuilder builder = new GetEntityOutputBuilder();
+        if (owner != null) {
+            builder.setOwnerNode(new NodeName(owner));
+        }
+        return builder
+            .setCandidateNodes(candidates.stream().map(NodeName::new).collect(Collectors.toUnmodifiableList()))
+            .build();
+    }
+}
diff --git a/opendaylight/md-sal/eos-dom-akka/src/main/java/org/opendaylight/controller/eos/akka/owner/supervisor/command/GetEntityRequest.java b/opendaylight/md-sal/eos-dom-akka/src/main/java/org/opendaylight/controller/eos/akka/owner/supervisor/command/GetEntityRequest.java
new file mode 100644 (file)
index 0000000..91b73c8
--- /dev/null
@@ -0,0 +1,19 @@
+/*
+ * Copyright (c) 2021 PANTHEON.tech, s.r.o. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.eos.akka.owner.supervisor.command;
+
+import akka.actor.typed.ActorRef;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.entity.owners.norev.EntityId;
+
+public final class GetEntityRequest extends AbstractEntityRequest<GetEntityReply> {
+    private static final long serialVersionUID = 1L;
+
+    public GetEntityRequest(final ActorRef<GetEntityReply> replyTo, final EntityId entity) {
+        super(replyTo, entity);
+    }
+}
diff --git a/opendaylight/md-sal/eos-dom-akka/src/main/java/org/opendaylight/controller/eos/akka/owner/supervisor/command/OwnerSupervisorRequest.java b/opendaylight/md-sal/eos-dom-akka/src/main/java/org/opendaylight/controller/eos/akka/owner/supervisor/command/OwnerSupervisorRequest.java
new file mode 100644 (file)
index 0000000..ab822a7
--- /dev/null
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2021 PANTHEON.tech, s.r.o. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.eos.akka.owner.supervisor.command;
+
+import static java.util.Objects.requireNonNull;
+
+import akka.actor.typed.ActorRef;
+import java.io.Serializable;
+import org.eclipse.jdt.annotation.NonNull;
+
+abstract class OwnerSupervisorRequest<T extends OwnerSupervisorReply> extends OwnerSupervisorCommand
+        implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    private final @NonNull ActorRef<T> replyTo;
+
+    OwnerSupervisorRequest(final ActorRef<T> replyTo) {
+        this.replyTo = requireNonNull(replyTo);
+    }
+
+    public final @NonNull ActorRef<T> getReplyTo() {
+        return replyTo;
+    }
+}
diff --git a/opendaylight/md-sal/eos-dom-akka/src/main/yang/odl-akka-eos.yang b/opendaylight/md-sal/eos-dom-akka/src/main/yang/odl-akka-eos.yang
new file mode 100644 (file)
index 0000000..b4f16b9
--- /dev/null
@@ -0,0 +1,93 @@
+module odl-entity-owners {
+  namespace urn:opendaylight:params:xml:ns:yang:controller:entity-owners;
+  prefix entity-owners;
+
+  organization 'OpenDaylight Project';
+  description "An initial cut at modeling entity ownership status information
+               in a way which is not dependent on the datastore.
+
+               This model is considered experimental and
+               implementation-specific. It can change incompatibly between
+               OpenDaylight releases.";
+
+  typedef entity-type {
+    type string {
+      length 1..max;
+      // FIXME: it would be nice to have a pattern here, or even better
+      //        if we turn this into an extensible enum (i.e. identityref)
+    }
+  }
+
+  typedef entity-name {
+    type string {
+      length 1..max;
+    }
+  }
+
+  typedef node-name {
+    type string {
+      length 1..max;
+    }
+  }
+
+  grouping entity-id {
+    leaf type {
+      type entity-type;
+      mandatory true;
+    }
+    leaf name {
+      type entity-name;
+      mandatory true;
+    }
+  }
+
+  grouping owner {
+    leaf owner-node {
+      type node-name;
+    }
+  }
+
+  grouping candidates {
+    leaf-list candidate-nodes {
+      type node-name;
+      ordered-by user;
+      min-elements 1;
+    }
+  }
+
+  grouping details {
+     uses owner;
+     uses candidates;
+  }
+
+  rpc get-entities {
+    output {
+      list entities {
+        key 'type name';
+        uses entity-id;
+        uses details;
+      }
+    }
+  }
+
+  rpc get-entity {
+    input {
+      uses entity-id;
+    }
+
+    output {
+      uses details;
+    }
+  }
+
+  rpc get-entity-owner {
+    input {
+      uses entity-id;
+    }
+
+    output {
+      uses owner;
+    }
+  }
+}
+