<groupId>org.opendaylight.yangtools</groupId>
<artifactId>yang-common</artifactId>
</dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
</dependencies>
<scm>
--- /dev/null
+/*
+ * Copyright (c) 2015 Cisco Systems, Inc. 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.mdsal.common.api.clustering;
+
+import com.google.common.base.Preconditions;
+import javax.annotation.Nonnull;
+import org.opendaylight.yangtools.concepts.Path;
+
+/**
+ * Thrown when a Candidate has already been registered for a given Entity. This could be due to a component doing a
+ * duplicate registration or two different components within the same process trying to register a Candidate.
+ */
+public class CandidateAlreadyRegisteredException extends Exception {
+ private static final long serialVersionUID = 1L;
+
+ private final GenericEntity<?> entity;
+
+ public <T extends Path<T>> CandidateAlreadyRegisteredException(@Nonnull GenericEntity<T> entity) {
+ super(String.format("Candidate has already been registered for %s",
+ Preconditions.checkNotNull(entity, "entity should not be null")));
+ this.entity = entity;
+ }
+
+ /**
+ * @return the entity for which a Candidate has already been registered in the current process.
+ *
+ * @param <T> the instance identifier path type
+ */
+ @SuppressWarnings("unchecked")
+ @Nonnull
+ public <T extends Path<T>> GenericEntity<T> getEntity() {
+ return (GenericEntity<T>) entity;
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2015 Brocade Communications Systems, Inc. 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.mdsal.common.api.clustering;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableMap.Builder;
+import java.util.Map;
+
+/**
+ * Enumerates the ownership change states for an entity.
+ *
+ * @author Thomas Pantelis
+ */
+public enum EntityOwnershipChangeState {
+ /**
+ * The local process instance has been granted ownership.
+ */
+ LOCAL_OWNERSHIP_GRANTED(false, true, true),
+
+ /**
+ * The local process instance has lost ownership and another process instance is now the owner.
+ */
+ LOCAL_OWNERSHIP_LOST_NEW_OWNER(true, false, true),
+
+ /**
+ * The local process instance has lost ownership and there are no longer any candidates for the entity and
+ * thus has no owner.
+ */
+ LOCAL_OWNERSHIP_LOST_NO_OWNER(true, false, false),
+
+ /**
+ * Entity ownership has transitioned to another process instance and this instance was not the previous owner.
+ */
+ REMOTE_OWNERSHIP_CHANGED(false, false, true),
+
+ /**
+ * A remote process instance has lost ownership and there are no longer any candidates for the entity and
+ * thus has no owner.
+ */
+ REMOTE_OWNERSHIP_LOST_NO_OWNER(false, false, false);
+
+ private static final Map<Key, EntityOwnershipChangeState> BY_KEY;
+ static {
+ Builder<Key, EntityOwnershipChangeState> builder = ImmutableMap.<Key, EntityOwnershipChangeState>builder();
+ for(EntityOwnershipChangeState e: values()) {
+ builder.put(new Key(e.wasOwner, e.isOwner, e.hasOwner), e);
+ }
+
+ BY_KEY = builder.build();
+ }
+
+ private final boolean wasOwner;
+ private final boolean isOwner;
+ private final boolean hasOwner;
+
+ private EntityOwnershipChangeState(boolean wasOwner, boolean isOwner, boolean hasOwner) {
+ this.wasOwner = wasOwner;
+ this.isOwner = isOwner;
+ this.hasOwner = hasOwner;
+ }
+
+ /**
+ * Returns the previous ownership status of the entity for this process instance.
+ * @return true if this process was the owner of the entity at the time this notification was generated
+ */
+ public boolean wasOwner() {
+ return wasOwner;
+ }
+
+ /**
+ * Returns the current ownership status of the entity for this process instance.
+ * @return true if this process is now the owner of the entity
+ */
+ public boolean isOwner() {
+ return isOwner;
+ }
+
+ /**
+ * Returns the current ownership status of the entity across all process instances.
+ * @return true if the entity has an owner which may or may not be this process. If false, then
+ * the entity has no candidates and thus no owner.
+ */
+ public boolean hasOwner() {
+ return hasOwner;
+ }
+
+ @Override
+ public String toString() {
+ return name() + " [wasOwner=" + wasOwner + ", isOwner=" + isOwner + ", hasOwner=" + hasOwner + "]";
+ }
+
+ public static EntityOwnershipChangeState from(boolean wasOwner, boolean isOwner, boolean hasOwner) {
+ EntityOwnershipChangeState state = BY_KEY.get(new Key(wasOwner, isOwner, hasOwner));
+ Preconditions.checkArgument(state != null, "Invalid combination of wasOwner: %s, isOwner: %s, hasOwner: %s",
+ wasOwner, isOwner, hasOwner);
+ return state;
+ }
+
+ private static final class Key {
+ private final boolean wasOwner;
+ private final boolean isOwner;
+ private final boolean hasOwner;
+
+ Key(boolean wasOwner, boolean isOwner, boolean hasOwner) {
+ this.wasOwner = wasOwner;
+ this.isOwner = isOwner;
+ this.hasOwner = hasOwner;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + (hasOwner ? 1231 : 1237);
+ result = prime * result + (isOwner ? 1231 : 1237);
+ result = prime * result + (wasOwner ? 1231 : 1237);
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ Key other = (Key) obj;
+ return hasOwner == other.hasOwner && isOwner == other.isOwner && wasOwner == other.wasOwner;
+ }
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2015 Brocade Communications Systems, Inc. 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.mdsal.common.api.clustering;
+
+/**
+ * Enumerates the current ownership state for an entity.
+ *
+ * @author Thomas Pantelis
+ */
+public enum EntityOwnershipState {
+ /**
+ * The local process instance is the owner of the entity.
+ */
+ IS_OWNER,
+
+ /**
+ * A remote process instance is the owner of the entity.
+ */
+ OWNED_BY_OTHER,
+
+ /**
+ * The entity has no owner and thus no candidates.
+ */
+ NO_OWNER;
+
+ public static EntityOwnershipState from(boolean isOwner, boolean hasOwner) {
+ if(isOwner) {
+ return IS_OWNER;
+ } else if(hasOwner) {
+ return OWNED_BY_OTHER;
+ } else {
+ return NO_OWNER;
+ }
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2015 Brocade Communications Systems, Inc. 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.mdsal.common.api.clustering;
+
+import com.google.common.base.Preconditions;
+import java.io.Serializable;
+import javax.annotation.Nonnull;
+import org.opendaylight.yangtools.concepts.Identifiable;
+import org.opendaylight.yangtools.concepts.Path;
+
+/**
+ * A clustered Entity is something which is shared by multiple applications across a cluster. An Entity has a type
+ * and an identifier.
+ * <p>
+ * The type describes the type of the Entity where examples of a type maybe "openflow" or "netconf"
+ * etc. An Entity type could be tied to how exactly an application shares and "owns" an entity. For example we may want
+ * an application which deals with the openflow entity to be assigned ownership of that entity based on a first come
+ * first served basis. On the other hand for netconf entity types we may want applications to gain ownership based on
+ * a load balancing approach. While this mechanism of assigning a ownership acquisition strategy is not finalized the
+ * intention is that the entity type will play a role in determining the strategy and thus should be put in place.
+ * <p>
+ * The identifier is an instance identifier path. The reason for the choice of instance identifier path is because it
+ * can easily be used to represent a data node. For example an inventory node represents a shared entity and it is best
+ * referenced by its instance identifier path if the inventory node is stored in the data store.
+ * <p>
+ * Note that an entity identifier must conform to a valid yang schema. If there is no existing yang schema to
+ * represent an entity, the general-entity yang model can be used.
+ * <p>
+ *
+ * @author Thomas Pantelis
+ *
+ * @param <T> the entity identifier type
+ */
+public class GenericEntity<T extends Path<T>> implements Serializable, Identifiable<T> {
+ private static final long serialVersionUID = 1L;
+
+ private final String type;
+ private final T id;
+
+ protected GenericEntity(@Nonnull String type, @Nonnull T id) {
+ this.type = Preconditions.checkNotNull(type, "type should not be null");
+ this.id = Preconditions.checkNotNull(id, "id should not be null");
+ }
+
+ /**
+ * @return the id of entity.
+ */
+ @Nonnull
+ @Override
+ public final T getIdentifier() {
+ return id;
+ }
+
+ /**
+ * @return the type of entity.
+ */
+ @Nonnull
+ public final String getType(){
+ return type;
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ GenericEntity<T> entity = (GenericEntity<T>) o;
+
+ if (!id.equals(entity.id)) {
+ return false;
+ }
+
+ if (!type.equals(entity.type)) {
+ return false;
+ }
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ return 31 * type.hashCode() + id.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return getClass().getSimpleName() + " [type=" + type + ", id=" + id + "]";
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2015 Brocade Communications Systems, Inc. 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.mdsal.common.api.clustering;
+
+import org.opendaylight.yangtools.concepts.ObjectRegistration;
+import org.opendaylight.yangtools.concepts.Path;
+
+/**
+ * An interface that records a request to register a Candidate for a given Entity. Calling close on the
+ * registration will remove the Candidate from any future ownership considerations for that Entity.
+ *
+ * @author Thomas Pantelis
+ *
+ * @param <P> the instance identifier type
+ * @param <E> the GenericEntity type
+ */
+public interface GenericEntityOwnershipCandidateRegistration<P extends Path<P>, E extends GenericEntity<P>>
+ extends ObjectRegistration<E> {
+
+ /**
+ * Unregister the candidate
+ */
+ @Override
+ void close();
+}
--- /dev/null
+/*
+ * Copyright (c) 2015 Brocade Communications Systems, Inc. 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.mdsal.common.api.clustering;
+
+import com.google.common.base.Preconditions;
+import javax.annotation.Nonnull;
+import org.opendaylight.yangtools.concepts.Path;
+
+/**
+ * A DTO that encapsulates an ownership change for an entity.
+ *
+ * @author Thomas Pantelis
+ *
+ * @param <P> the instance identifier path type
+ * @param <E> the GenericEntity type
+ */
+public class GenericEntityOwnershipChange<P extends Path<P>, E extends GenericEntity<P>> {
+ private final E entity;
+ private final EntityOwnershipChangeState state;
+
+ public GenericEntityOwnershipChange(@Nonnull E entity, @Nonnull EntityOwnershipChangeState state) {
+ this.entity = Preconditions.checkNotNull(entity, "entity can't be null");
+ this.state = Preconditions.checkNotNull(state, "state can't be null");
+ }
+
+ /**
+ * Returns the entity whose ownership status changed.
+ * @return the entity
+ */
+ @Nonnull public E getEntity() {
+ return entity;
+ }
+
+ /**
+ * Returns the ownership change state.
+ * @return an EntityOwnershipChangeState enum
+ */
+ @Nonnull public EntityOwnershipChangeState getState() {
+ return state;
+ }
+
+ @Override
+ public String toString() {
+ return getClass().getSimpleName() + " [entity=" + entity + ", state=" + state + "]";
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2015 Brocade Communications Systems, Inc. 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.mdsal.common.api.clustering;
+
+import org.opendaylight.yangtools.concepts.Path;
+
+/**
+ * An interface for a class that listens for entity ownership changes.
+ *
+ * @author Thomas Pantelis
+ *
+ * @param <P> the instance identifier path type
+ * @param <C> the GenericEntityOwnershipChange type
+ */
+public interface GenericEntityOwnershipListener<P extends Path<P>,
+ C extends GenericEntityOwnershipChange<P, ? extends GenericEntity<P>>> {
+
+ /**
+ * A notification that is generated when the ownership status of an entity changes.
+ *
+ * @param ownershipChange contains the entity and its ownership change state
+ */
+ void ownershipChanged(C ownershipChange);
+}
--- /dev/null
+/*
+ * Copyright (c) 2015 Brocade Communications Systems, Inc. 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.mdsal.common.api.clustering;
+
+import javax.annotation.Nonnull;
+import org.opendaylight.yangtools.concepts.ObjectRegistration;
+import org.opendaylight.yangtools.concepts.Path;
+
+/**
+ * An interface that records a request to register a ownership status change listener for a given Entity.
+ * Calling close on the registration will unregister listeners and future ownership changes will not
+ * be delivered to the listener.
+ *
+ * @author Thomas Pantelis
+ *
+ * @param <P> the instance identifier path type
+ */
+public interface GenericEntityOwnershipListenerRegistration<P extends Path<P>,
+ L extends GenericEntityOwnershipListener<P, ? extends GenericEntityOwnershipChange<P, ? extends GenericEntity<P>>>>
+ extends ObjectRegistration<L> {
+
+ /**
+ * @return the entity type that the listener was registered for
+ */
+ @Nonnull String getEntityType();
+
+ /**
+ * Unregister the listener
+ */
+ @Override
+ void close();
+}
--- /dev/null
+/*
+ * Copyright (c) 2015 Brocade Communications Systems, Inc. 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.mdsal.common.api.clustering;
+
+import com.google.common.base.Optional;
+import javax.annotation.Nonnull;
+import org.opendaylight.yangtools.concepts.Path;
+
+/**
+ * <p>
+ * An interface that provides the means for a component/application to request ownership for a given
+ * Entity on the current cluster member. Entity ownership is always tied to a process and two components on the same
+ * process cannot register a candidate for a given Entity.
+ * </p>
+ * <p>
+ * A component/application may also register interest in the ownership status of an Entity. The listener would be
+ * notified whenever the ownership status changes.
+ * </p>
+ *
+ * @author Thomas Pantelis
+ *
+ * @param <P> the instance identifier path type
+ * @param <E> the GenericEntity type
+ */
+public interface GenericEntityOwnershipService<P extends Path<P>, E extends GenericEntity<P>,
+ L extends GenericEntityOwnershipListener<P, ? extends GenericEntityOwnershipChange<P, E>>> {
+
+ /**
+ * Registers a candidate for ownership of the given entity. Only one such request can be made per entity
+ * per process. If multiple requests for registering a candidate for a given entity are received in the
+ * current process a CandidateAlreadyRegisteredException will be thrown.
+ * <p>
+ * The registration is performed asynchronously and any registered entity ownership listener is
+ * notified of ownership status changes for the entity.
+ *
+ * @param entity the entity which the Candidate wants to own
+ * @return a registration object that can be used to unregister the Candidate
+ * @throws CandidateAlreadyRegisteredException if the candidate was already registered
+ */
+ GenericEntityOwnershipCandidateRegistration<P, E> registerCandidate(@Nonnull E entity)
+ throws CandidateAlreadyRegisteredException;
+
+ /**
+ * Registers a listener that is interested in ownership changes for entities of the given entity type. The
+ * listener is notified whenever its process instance is granted ownership of the entity and also whenever
+ * it loses ownership. On registration the listener will be notified of all entities its process instance
+ * currently owns at the time of registration.
+ *
+ * @param entityType the type of entities whose ownership status the Listener is interested in
+ * @param listener the listener that is interested in the entities
+ * @return a registration object that can be used to unregister the Listener
+ */
+ GenericEntityOwnershipListenerRegistration<P, L> registerListener(@Nonnull String entityType, @Nonnull L listener);
+
+ /**
+ * Gets the current ownership state information for an entity.
+ *
+ * @param forEntity the entity to query.
+ * @return an Optional EntityOwnershipState whose instance is present if the entity is found
+ */
+ Optional<EntityOwnershipState> getOwnershipState(@Nonnull E forEntity);
+}
--- /dev/null
+/*
+ * Copyright (c) 2015 Brocade Communications Systems, Inc. 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.mdsal.common.api.clustering;
+
+import static org.junit.Assert.assertEquals;
+import org.junit.Test;
+
+/**
+ * Unit tests for EntityOwnershipChangeState.
+ *
+ * @author Thomas Pantelis
+ */
+public class EntityOwnershipChangeStateTest {
+
+ @Test
+ public void testFromWithValid() {
+ assertEquals("from(false, true, true)", EntityOwnershipChangeState.LOCAL_OWNERSHIP_GRANTED,
+ EntityOwnershipChangeState.from(false, true, true));
+ assertEquals("from(true, false, true)", EntityOwnershipChangeState.LOCAL_OWNERSHIP_LOST_NEW_OWNER,
+ EntityOwnershipChangeState.from(true, false, true));
+ assertEquals("from(true, false, false)", EntityOwnershipChangeState.LOCAL_OWNERSHIP_LOST_NO_OWNER,
+ EntityOwnershipChangeState.from(true, false, false));
+ assertEquals("from(false, false, true)", EntityOwnershipChangeState.REMOTE_OWNERSHIP_CHANGED,
+ EntityOwnershipChangeState.from(false, false, true));
+ assertEquals("from(false, false, false)", EntityOwnershipChangeState.REMOTE_OWNERSHIP_LOST_NO_OWNER,
+ EntityOwnershipChangeState.from(false, false, false));
+ }
+
+ @Test(expected=IllegalArgumentException.class)
+ public void testFromWithInvalidFalseTrueFalse() {
+ EntityOwnershipChangeState.from(false, true, false);
+ }
+
+ @Test(expected=IllegalArgumentException.class)
+ public void testFromWithInvalidTrueTrueFalse() {
+ EntityOwnershipChangeState.from(true, true, false);
+ }
+
+ @Test(expected=IllegalArgumentException.class)
+ public void testFromWithInvalidTrueTrueTrue() {
+ EntityOwnershipChangeState.from(true, true, true);
+ }
+}
\ No newline at end of file