ONOS-7050 First stab at PI translation store

Change-Id: I7f48802b1f5d70fbe3e6cead2800855de18b9207
diff --git a/core/api/src/main/java/org/onosproject/net/flow/FlowRule.java b/core/api/src/main/java/org/onosproject/net/flow/FlowRule.java
index 27a604c..b62007f 100644
--- a/core/api/src/main/java/org/onosproject/net/flow/FlowRule.java
+++ b/core/api/src/main/java/org/onosproject/net/flow/FlowRule.java
@@ -18,12 +18,13 @@
 import org.onosproject.core.ApplicationId;
 import org.onosproject.core.GroupId;
 import org.onosproject.net.DeviceId;
+import org.onosproject.net.pi.service.PiTranslatable;
 
 /**
  * Represents a generalized match & action pair to be applied to an
  * infrastructure device.
  */
-public interface FlowRule {
+public interface FlowRule extends PiTranslatable {
 
     IndexTableId DEFAULT_TABLE = IndexTableId.of(0);
     int MAX_TIMEOUT = 60;
diff --git a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiActionGroup.java b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiActionGroup.java
index 1d9a94b..9084bea 100644
--- a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiActionGroup.java
+++ b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiActionGroup.java
@@ -34,7 +34,7 @@
  * Instance of an action group of a protocol-independent pipeline.
  */
 @Beta
-public final class PiActionGroup {
+public final class PiActionGroup implements PiEntity {
 
     private final PiActionGroupId id;
     private final PiActionGroupType type;
@@ -125,6 +125,11 @@
         return new Builder();
     }
 
+    @Override
+    public PiEntityType piEntityType() {
+        return PiEntityType.GROUP;
+    }
+
     /**
      * Builder of action groups.
      */
diff --git a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiEntity.java b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiEntity.java
new file mode 100644
index 0000000..c3d5a01
--- /dev/null
+++ b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiEntity.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.net.pi.runtime;
+
+import com.google.common.annotations.Beta;
+
+/**
+ * Abstraction of an entity of a protocol-independent that can be read or write
+ * at runtime.
+ */
+@Beta
+public interface PiEntity {
+
+    /**
+     * Returns the type of this entity.
+     *
+     * @return entity type
+     */
+    PiEntityType piEntityType();
+}
diff --git a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiEntityType.java b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiEntityType.java
new file mode 100644
index 0000000..e01e520
--- /dev/null
+++ b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiEntityType.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.net.pi.runtime;
+
+import com.google.common.annotations.Beta;
+
+/**
+ * Type of runtime entity of a protocol-independent pipeline.
+ */
+@Beta
+public enum PiEntityType {
+    /**
+     * Table entry.
+     */
+    TABLE_ENTRY,
+
+    /**
+     * Action profile group.
+     */
+    GROUP
+}
diff --git a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiTableEntry.java b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiTableEntry.java
index cd7e493..5770c6b 100644
--- a/core/api/src/main/java/org/onosproject/net/pi/runtime/PiTableEntry.java
+++ b/core/api/src/main/java/org/onosproject/net/pi/runtime/PiTableEntry.java
@@ -30,7 +30,7 @@
  * Instance of a table entry in a protocol-independent pipeline.
  */
 @Beta
-public final class PiTableEntry {
+public final class PiTableEntry implements PiEntity {
 
     public static final PiTableEntry EMTPY = new PiTableEntry();
 
@@ -160,6 +160,11 @@
         return new Builder();
     }
 
+    @Override
+    public PiEntityType piEntityType() {
+        return PiEntityType.TABLE_ENTRY;
+    }
+
     public static final class Builder {
 
         private PiTableId tableId;
diff --git a/core/api/src/main/java/org/onosproject/net/pi/service/PiTranslatable.java b/core/api/src/main/java/org/onosproject/net/pi/service/PiTranslatable.java
new file mode 100644
index 0000000..316c3ed
--- /dev/null
+++ b/core/api/src/main/java/org/onosproject/net/pi/service/PiTranslatable.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.net.pi.service;
+
+import com.google.common.annotations.Beta;
+
+/**
+ * Abstraction of protocol-dependent (PD) entity that can be translated to an
+ * equivalent protocol-independent (PI) one.
+ */
+@Beta
+public interface PiTranslatable {
+}
diff --git a/core/api/src/main/java/org/onosproject/net/pi/service/PiTranslatedEntity.java b/core/api/src/main/java/org/onosproject/net/pi/service/PiTranslatedEntity.java
new file mode 100644
index 0000000..8914eee
--- /dev/null
+++ b/core/api/src/main/java/org/onosproject/net/pi/service/PiTranslatedEntity.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.net.pi.service;
+
+import com.google.common.annotations.Beta;
+import org.onosproject.net.pi.model.PiPipeconfId;
+import org.onosproject.net.pi.runtime.PiEntity;
+import org.onosproject.net.pi.runtime.PiEntityType;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Representation of the result of a PD-to-PI translation.
+ */
+@Beta
+public final class PiTranslatedEntity {
+
+    private final PiTranslatable original;
+    private final PiEntity translated;
+    private final PiPipeconfId pipeconfId;
+    private final PiEntityType type;
+
+    public PiTranslatedEntity(PiTranslatable original, PiEntity translated,
+                              PiPipeconfId pipeconfId) {
+        this.original = checkNotNull(original);
+        this.translated = checkNotNull(translated);
+        this.pipeconfId = checkNotNull(pipeconfId);
+        this.type = checkNotNull(translated.piEntityType());
+    }
+
+    /**
+     * Returns the type of the translated entity.
+     *
+     * @return type of the translated entity
+     */
+    public final PiEntityType entityType() {
+        return type;
+    }
+
+    /**
+     * Returns the original PD entity.
+     *
+     * @return instance of PI translatable entity
+     */
+    public final PiTranslatable original() {
+        return original;
+    }
+
+    /**
+     * Returns the translated PI entity.
+     *
+     * @return PI entity
+     */
+    public final PiEntity translated() {
+        return translated;
+    }
+
+    /**
+     * The ID of the pipeconf for which this translation is valid. In other
+     * words, the PI entity is guaranteed to be functionally equivalent to the
+     * PD one when applied to a device configured with such pipeconf.
+     *
+     * @return PI pipeconf ID
+     */
+    public final PiPipeconfId pipeconfId() {
+        return pipeconfId;
+    }
+}
diff --git a/core/api/src/main/java/org/onosproject/net/pi/service/PiTranslationEvent.java b/core/api/src/main/java/org/onosproject/net/pi/service/PiTranslationEvent.java
new file mode 100644
index 0000000..4f59079
--- /dev/null
+++ b/core/api/src/main/java/org/onosproject/net/pi/service/PiTranslationEvent.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.net.pi.service;
+
+import com.google.common.annotations.Beta;
+import org.onosproject.event.AbstractEvent;
+
+/**
+ * Signals an event related to the translation of a protocol-dependent (PD)
+ * entity to a protocol-independent (PI) one.
+ */
+@Beta
+public final class PiTranslationEvent
+        extends AbstractEvent<PiTranslationEvent.Type, PiTranslatedEntity> {
+
+    /**
+     * Type of event.
+     */
+    public enum Type {
+        /**
+         * Signals that A PD entity has been translated to a PI one, and the
+         * mapping between the two entities has been learned by the system.
+         */
+        LEARNED,
+
+        /**
+         * Signals that a previously learned mapping between a PD entity and its
+         * PI counterpart has been removed.
+         */
+        FORGOT,
+    }
+
+    /**
+     * Creates a new translation event.
+     *
+     * @param type    type of event
+     * @param subject subject of event
+     */
+    public PiTranslationEvent(Type type, PiTranslatedEntity subject) {
+        super(type, subject);
+    }
+}
diff --git a/core/api/src/main/java/org/onosproject/net/pi/service/PiTranslationService.java b/core/api/src/main/java/org/onosproject/net/pi/service/PiTranslationService.java
index 210b7e0..6af3e83 100644
--- a/core/api/src/main/java/org/onosproject/net/pi/service/PiTranslationService.java
+++ b/core/api/src/main/java/org/onosproject/net/pi/service/PiTranslationService.java
@@ -20,40 +20,68 @@
 import org.onosproject.net.flow.FlowRule;
 import org.onosproject.net.group.Group;
 import org.onosproject.net.pi.model.PiPipeconf;
+import org.onosproject.net.pi.model.PiPipeconfId;
 import org.onosproject.net.pi.runtime.PiActionGroup;
 import org.onosproject.net.pi.runtime.PiTableEntry;
 
+import java.util.Optional;
+
 /**
- * A service to translate protocol-dependent entities to protocol-independent ones.
+ * A service to translate protocol-dependent (PD) entities to
+ * protocol-independent (PI) ones.
  */
 @Beta
 public interface PiTranslationService {
 
     /**
-     * Returns a PI table entry equivalent to the given flow rule for the given protocol-independent pipeline
-     * configuration.
+     * Returns a PI table entry equivalent to the given flow rule for the given
+     * protocol-independent pipeline configuration.
      *
      * @param rule     a flow rule
      * @param pipeconf a pipeline configuration
      * @return a table entry
      * @throws PiTranslationException if the flow rule cannot be translated
      */
-    PiTableEntry translateFlowRule(FlowRule rule, PiPipeconf pipeconf)
+    PiTableEntry translate(FlowRule rule, PiPipeconf pipeconf)
             throws PiTranslationException;
 
     /**
-     * Returns a PI action group equivalent to the given group for the given protocol-independent pipeline
-     * configuration.
+     * Returns a PI action group equivalent to the given group for the given
+     * protocol-independent pipeline configuration.
      *
      * @param group    a group
      * @param pipeconf a pipeline configuration
      * @return a PI action group
      * @throws PiTranslationException if the group cannot be translated
      */
-    PiActionGroup translateGroup(Group group, PiPipeconf pipeconf)
+    PiActionGroup translate(Group group, PiPipeconf pipeconf)
             throws PiTranslationException;
 
     /**
+     * Returns a flow rule previously translated to the given PI table entry,
+     * for the given pipeconf ID, if present. If not present it means that such
+     * flow rule was never translated in the first place.
+     *
+     * @param piTableEntry PI table entry
+     * @param pipeconfId   pipeconf ID
+     * @return optional flow rule
+     */
+    Optional<FlowRule> lookup(PiTableEntry piTableEntry,
+                              PiPipeconfId pipeconfId);
+
+    /**
+     * Returns a group previously translated to the given PI action group, for
+     * the given pipeconf ID, if present. If not present it means that such
+     * group was never translated in the first place.
+     *
+     * @param piActionGroup PI action group
+     * @param pipeconfId    pipeconf ID
+     * @return optional group
+     */
+    Optional<Group> lookup(PiActionGroup piActionGroup,
+                           PiPipeconfId pipeconfId);
+
+    /**
      * Signals that an error was encountered while translating an entity.
      */
     class PiTranslationException extends Exception {
diff --git a/core/api/src/main/java/org/onosproject/net/pi/service/PiTranslationStore.java b/core/api/src/main/java/org/onosproject/net/pi/service/PiTranslationStore.java
new file mode 100644
index 0000000..6e0dec7
--- /dev/null
+++ b/core/api/src/main/java/org/onosproject/net/pi/service/PiTranslationStore.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.net.pi.service;
+
+import com.google.common.annotations.Beta;
+import org.onosproject.net.pi.model.PiPipeconfId;
+import org.onosproject.net.pi.runtime.PiEntity;
+import org.onosproject.store.Store;
+
+/**
+ * PI translation service store abstraction that acts as a multi-language
+ * dictionary. For each pipeconf ID (language) it maintains a mapping between a
+ * protocol-dependent (PD) entity and an equivalent protocol-independent (PI)
+ * one.
+ */
+@Beta
+public interface PiTranslationStore
+        extends Store<PiTranslationEvent, PiTranslationStoreDelegate> {
+
+    /**
+     * Adds or update a mapping between the given PD entity (original) and the
+     * translated PI counterpart, for the given pipeconf ID.
+     *
+     * @param original   PD entity
+     * @param translated PI entity
+     * @param pipeconfId pipeconf ID
+     */
+    void addOrUpdate(PiTranslatable original, PiEntity translated,
+                     PiPipeconfId pipeconfId);
+
+    /**
+     * Removes a previously added mapping for the given PI entity and pipeconf
+     * ID.
+     *
+     * @param piEntity   PI entity
+     * @param pipeconfId pipeconf ID
+     */
+    void remove(PiEntity piEntity, PiPipeconfId pipeconfId);
+
+    /**
+     * Removes all previously learned mappings for the given pipeconf ID.
+     *
+     * @param pipeconfId pipeconf ID
+     */
+    void removeAll(PiPipeconfId pipeconfId);
+
+    /**
+     * Returns a PD entity for the given PI one and pipeconf ID. Returns null if
+     * this store does not contain a mapping between the two for the given
+     * pipeconf ID.
+     *
+     * @param piEntity   PI entity
+     * @param pipeconfId pipeconf ID
+     * @return PD entity or null
+     */
+    PiTranslatable lookup(PiEntity piEntity, PiPipeconfId pipeconfId);
+}
diff --git a/core/api/src/main/java/org/onosproject/net/pi/service/PiTranslationStoreDelegate.java b/core/api/src/main/java/org/onosproject/net/pi/service/PiTranslationStoreDelegate.java
new file mode 100644
index 0000000..0fe5c75
--- /dev/null
+++ b/core/api/src/main/java/org/onosproject/net/pi/service/PiTranslationStoreDelegate.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.net.pi.service;
+
+import com.google.common.annotations.Beta;
+import org.onosproject.store.StoreDelegate;
+
+/**
+ * PI translation service store delegate abstraction.
+ */
+@Beta
+public interface PiTranslationStoreDelegate
+        extends StoreDelegate<PiTranslationEvent> {
+}
diff --git a/core/net/src/main/java/org/onosproject/net/pi/impl/PiTranslationServiceImpl.java b/core/net/src/main/java/org/onosproject/net/pi/impl/PiTranslationServiceImpl.java
index 3cb0a09..b6d90f0 100644
--- a/core/net/src/main/java/org/onosproject/net/pi/impl/PiTranslationServiceImpl.java
+++ b/core/net/src/main/java/org/onosproject/net/pi/impl/PiTranslationServiceImpl.java
@@ -28,12 +28,17 @@
 import org.onosproject.net.flow.FlowRule;
 import org.onosproject.net.group.Group;
 import org.onosproject.net.pi.model.PiPipeconf;
+import org.onosproject.net.pi.model.PiPipeconfId;
 import org.onosproject.net.pi.runtime.PiActionGroup;
 import org.onosproject.net.pi.runtime.PiTableEntry;
+import org.onosproject.net.pi.service.PiTranslatable;
 import org.onosproject.net.pi.service.PiTranslationService;
+import org.onosproject.net.pi.service.PiTranslationStore;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import java.util.Optional;
+
 /**
  * Implementation of the protocol-independent translation service.
  */
@@ -48,6 +53,9 @@
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected DeviceService deviceService;
 
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    private PiTranslationStore translationStore;
+
     @Activate
     public void activate() {
         log.info("Started");
@@ -59,19 +67,43 @@
     }
 
     @Override
-    public PiTableEntry translateFlowRule(FlowRule rule, PiPipeconf pipeconf) throws PiTranslationException {
-        return PiFlowRuleTranslator.translate(rule, pipeconf, getDevice(rule.deviceId()));
+    public PiTableEntry translate(FlowRule rule, PiPipeconf pipeconf)
+            throws PiTranslationException {
+        final PiTableEntry piTableEntry = PiFlowRuleTranslator
+                .translate(rule, pipeconf, getDevice(rule.deviceId()));
+        translationStore.addOrUpdate(rule, piTableEntry, pipeconf.id());
+        return piTableEntry;
     }
 
     @Override
-    public PiActionGroup translateGroup(Group group, PiPipeconf pipeconf) throws PiTranslationException {
-        return PiGroupTranslator.translate(group, pipeconf, getDevice(group.deviceId()));
+    public Optional<FlowRule> lookup(PiTableEntry piTableEntry,
+                                     PiPipeconfId pipeconfId) {
+        final PiTranslatable original = translationStore
+                .lookup(piTableEntry, pipeconfId);
+        return original == null
+                ? Optional.empty()
+                : Optional.of((FlowRule) original);
+    }
+
+    @Override
+    public PiActionGroup translate(Group group, PiPipeconf pipeconf)
+            throws PiTranslationException {
+        return PiGroupTranslator.translate(group, pipeconf,
+                                           getDevice(group.deviceId()));
+    }
+
+    @Override
+    public Optional<Group> lookup(PiActionGroup piActionGroup,
+                                  PiPipeconfId pipeconfId) {
+        // TODO: implement learning and lookup of groups
+        return Optional.empty();
     }
 
     private Device getDevice(DeviceId deviceId) throws PiTranslationException {
         final Device device = deviceService.getDevice(deviceId);
         if (device == null) {
-            throw new PiTranslationException("Unable to get device " + deviceId);
+            throw new PiTranslationException(
+                    "Unable to get device " + deviceId);
         }
         return device;
     }
diff --git a/core/store/dist/src/main/java/org/onosproject/store/pi/impl/DistributedPiTranslationStore.java b/core/store/dist/src/main/java/org/onosproject/store/pi/impl/DistributedPiTranslationStore.java
new file mode 100644
index 0000000..f19c746
--- /dev/null
+++ b/core/store/dist/src/main/java/org/onosproject/store/pi/impl/DistributedPiTranslationStore.java
@@ -0,0 +1,189 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.store.pi.impl;
+
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.apache.felix.scr.annotations.Service;
+import org.onlab.util.KryoNamespace;
+import org.onosproject.net.pi.model.PiPipeconfId;
+import org.onosproject.net.pi.runtime.PiEntity;
+import org.onosproject.net.pi.service.PiTranslatable;
+import org.onosproject.net.pi.service.PiTranslatedEntity;
+import org.onosproject.net.pi.service.PiTranslationEvent;
+import org.onosproject.net.pi.service.PiTranslationStore;
+import org.onosproject.net.pi.service.PiTranslationStoreDelegate;
+import org.onosproject.store.AbstractStore;
+import org.onosproject.store.serializers.KryoNamespaces;
+import org.onosproject.store.service.EventuallyConsistentMap;
+import org.onosproject.store.service.EventuallyConsistentMapEvent;
+import org.onosproject.store.service.EventuallyConsistentMapListener;
+import org.onosproject.store.service.StorageService;
+import org.onosproject.store.service.WallClockTimestamp;
+import org.slf4j.Logger;
+
+import java.util.Objects;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Distributed implementation of PiTranslationStore.
+ */
+@Component(immediate = true)
+@Service
+public class DistributedPiTranslationStore
+        extends AbstractStore<PiTranslationEvent, PiTranslationStoreDelegate>
+        implements PiTranslationStore {
+
+    private static final String DIST_MAP_NAME = "onos-pi-translated-entities-map";
+    private final Logger log = getLogger(getClass());
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected StorageService storageService;
+
+    private EventuallyConsistentMap<PiTranslatedEntityKey, PiTranslatedEntity>
+            translatedEntities;
+
+    private final EventuallyConsistentMapListener<PiTranslatedEntityKey,
+            PiTranslatedEntity> entityMapListener = new InternalEntityMapListener();
+
+    @Activate
+    public void activate() {
+        translatedEntities = storageService
+                .<PiTranslatedEntityKey, PiTranslatedEntity>eventuallyConsistentMapBuilder()
+                .withName(DIST_MAP_NAME)
+                .withSerializer(KryoNamespace.newBuilder()
+                                        .register(KryoNamespaces.API)
+                                        .register(PiTranslatedEntityKey.class)
+                                        .build())
+                .withTimestampProvider((k, v) -> new WallClockTimestamp())
+                .build();
+        translatedEntities.addListener(entityMapListener);
+        log.info("Started");
+    }
+
+    @Deactivate
+    public void deactivate() {
+        translatedEntities.removeListener(entityMapListener);
+        translatedEntities = null;
+        log.info("Stopped");
+    }
+
+    @Override
+    public void addOrUpdate(PiTranslatable original, PiEntity translated,
+                            PiPipeconfId pipeconfId) {
+        translatedEntities.put(PiTranslatedEntityKey.of(pipeconfId, translated),
+                               new PiTranslatedEntity(original, translated, pipeconfId));
+    }
+
+    @Override
+    public void remove(PiEntity piEntity, PiPipeconfId pipeconfId) {
+        translatedEntities.remove(
+                PiTranslatedEntityKey.of(pipeconfId, piEntity));
+    }
+
+    @Override
+    public void removeAll(PiPipeconfId pipeconfId) {
+        // FIXME: this can be heavy, but we assume it won't be called that often
+        // How often we expect a pipeconf to be removed from the device/system?
+        final Set<PiTranslatedEntityKey> keysToRemove = translatedEntities
+                .keySet().parallelStream()
+                .filter(k -> k.pipeconfId.equals(pipeconfId))
+                .collect(Collectors.toSet());
+        keysToRemove.forEach(translatedEntities::remove);
+    }
+
+    @Override
+    public PiTranslatable lookup(PiEntity piEntity, PiPipeconfId pipeconfId) {
+        PiTranslatedEntity translatedEntity = translatedEntities
+                .get(PiTranslatedEntityKey.of(pipeconfId, piEntity));
+        return translatedEntity == null ? null : translatedEntity.original();
+    }
+
+
+    private class InternalEntityMapListener
+            implements EventuallyConsistentMapListener
+                               <PiTranslatedEntityKey, PiTranslatedEntity> {
+
+        @Override
+        public void event(EventuallyConsistentMapEvent<PiTranslatedEntityKey,
+                PiTranslatedEntity> event) {
+            final PiTranslationEvent.Type type;
+            switch (event.type()) {
+                case PUT:
+                    type = PiTranslationEvent.Type.LEARNED;
+                    break;
+                case REMOVE:
+                    type = PiTranslationEvent.Type.FORGOT;
+                    break;
+                default:
+                    throw new IllegalArgumentException(
+                            "Unknown event type " + event.type().name());
+            }
+            notifyDelegate(new PiTranslationEvent(type, event.value()));
+        }
+    }
+
+    /**
+     * Internal representation of a key that uniquely identifies a translated
+     * entity.
+     */
+    private static final class PiTranslatedEntityKey {
+
+        private final PiPipeconfId pipeconfId;
+        private final PiEntity piEntity;
+
+        private PiTranslatedEntityKey(PiPipeconfId pipeconfId,
+                                      PiEntity piEntity) {
+            this.pipeconfId = pipeconfId;
+            this.piEntity = piEntity;
+        }
+
+        public static PiTranslatedEntityKey of(PiPipeconfId pipeconfId,
+                                               PiEntity piEntity) {
+            return new PiTranslatedEntityKey(pipeconfId, piEntity);
+        }
+
+        public static PiTranslatedEntityKey of(PiTranslatedEntity entity) {
+            return new PiTranslatedEntityKey(entity.pipeconfId(),
+                                             entity.translated());
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(pipeconfId, piEntity);
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (this == obj) {
+                return true;
+            }
+            if (obj == null || getClass() != obj.getClass()) {
+                return false;
+            }
+            final PiTranslatedEntityKey other = (PiTranslatedEntityKey) obj;
+            return Objects.equals(this.pipeconfId, other.pipeconfId)
+                    && Objects.equals(this.piEntity, other.piEntity);
+        }
+    }
+}
diff --git a/core/store/serializers/src/main/java/org/onosproject/store/serializers/KryoNamespaces.java b/core/store/serializers/src/main/java/org/onosproject/store/serializers/KryoNamespaces.java
index cc3c05d..9c5cb82 100644
--- a/core/store/serializers/src/main/java/org/onosproject/store/serializers/KryoNamespaces.java
+++ b/core/store/serializers/src/main/java/org/onosproject/store/serializers/KryoNamespaces.java
@@ -227,6 +227,8 @@
 import org.onosproject.net.pi.runtime.PiControlMetadata;
 import org.onosproject.net.pi.runtime.PiCounterCellData;
 import org.onosproject.net.pi.runtime.PiCounterCellId;
+import org.onosproject.net.pi.runtime.PiEntity;
+import org.onosproject.net.pi.runtime.PiEntityType;
 import org.onosproject.net.pi.runtime.PiExactFieldMatch;
 import org.onosproject.net.pi.runtime.PiFieldMatch;
 import org.onosproject.net.pi.runtime.PiGroupKey;
@@ -239,6 +241,8 @@
 import org.onosproject.net.pi.runtime.PiTableEntry;
 import org.onosproject.net.pi.runtime.PiTernaryFieldMatch;
 import org.onosproject.net.pi.runtime.PiValidFieldMatch;
+import org.onosproject.net.pi.service.PiTranslatable;
+import org.onosproject.net.pi.service.PiTranslatedEntity;
 import org.onosproject.net.provider.ProviderId;
 import org.onosproject.net.region.DefaultRegion;
 import org.onosproject.net.region.Region;
@@ -637,6 +641,8 @@
                     PiControlMetadata.class,
                     PiCounterCellData.class,
                     PiCounterCellId.class,
+                    PiEntity.class,
+                    PiEntityType.class,
                     PiExactFieldMatch.class,
                     PiFieldMatch.class,
                     PiGroupKey.class,
@@ -649,6 +655,9 @@
                     PiTableEntry.class,
                     PiTernaryFieldMatch.class,
                     PiValidFieldMatch.class,
+                    // PI service
+                    PiTranslatedEntity.class,
+                    PiTranslatable.class,
                     // Other
                     PiCriterion.class,
                     PiInstruction.class
diff --git a/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/P4RuntimeFlowRuleProgrammable.java b/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/P4RuntimeFlowRuleProgrammable.java
index 5f95eda..b216fcf 100644
--- a/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/P4RuntimeFlowRuleProgrammable.java
+++ b/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/P4RuntimeFlowRuleProgrammable.java
@@ -242,7 +242,7 @@
             PiTableEntry piTableEntry;
 
             try {
-                piTableEntry = piTranslationService.translateFlowRule(rule, pipeconf);
+                piTableEntry = piTranslationService.translate(rule, pipeconf);
             } catch (PiTranslationService.PiTranslationException e) {
                 log.warn("Unable to translate flow rule: {} - {}", e.getMessage(), rule);
                 continue; // next rule
diff --git a/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/P4RuntimeGroupProgrammable.java b/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/P4RuntimeGroupProgrammable.java
index 464c4b6..a3139bc 100644
--- a/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/P4RuntimeGroupProgrammable.java
+++ b/drivers/p4runtime/src/main/java/org/onosproject/drivers/p4runtime/P4RuntimeGroupProgrammable.java
@@ -103,7 +103,7 @@
 
         PiActionGroup piActionGroup;
         try {
-            piActionGroup = piTranslationService.translateGroup(group, pipeconf);
+            piActionGroup = piTranslationService.translate(group, pipeconf);
         } catch (PiTranslationService.PiTranslationException e) {
             log.warn("Unable translate group, aborting group operation {}: {}", groupOp.opType(), e.getMessage());
             return;