Bug 5421 - Single cluster-wide service API 58/40858/8
authorVaclav Demcak <vdemcak@cisco.com>
Thu, 14 Apr 2016 09:32:29 +0000 (11:32 +0200)
committerRobert Varga <nite@hq.sk>
Wed, 13 Jul 2016 17:33:40 +0000 (17:33 +0000)
ClusterSingletonServices provides a functionality which alowe to have only
one fully instantiated service instance in a cluster at one time.

* Cluster Singleton API contract definitions
* adoc for ClusterSingletonService implementation

Change-Id: I89180c0a6b6427d6cb1087457aaf2978b8544dda
Signed-off-by: Vaclav Demcak <vdemcak@cisco.com>
25 files changed:
.gitignore
common/artifacts/pom.xml
pom.xml
singleton-service/mdsal-singleton-binding-api/pom.xml [new file with mode: 0644]
singleton-service/mdsal-singleton-binding-api/src/main/java/org/opendaylight/mdsal/singleton/binding/api/ClusterSingletonServiceProvider.java [new file with mode: 0644]
singleton-service/mdsal-singleton-common-api/pom.xml [new file with mode: 0644]
singleton-service/mdsal-singleton-common-api/src/main/java/org/opendaylight/mdsal/singleton/common/api/ClusterSingletonService.java [new file with mode: 0644]
singleton-service/mdsal-singleton-common-api/src/main/java/org/opendaylight/mdsal/singleton/common/api/ClusterSingletonServiceGroup.java [new file with mode: 0644]
singleton-service/mdsal-singleton-common-api/src/main/java/org/opendaylight/mdsal/singleton/common/api/ClusterSingletonServiceRegistration.java [new file with mode: 0644]
singleton-service/mdsal-singleton-common-api/src/main/java/org/opendaylight/mdsal/singleton/common/api/ClusterSingletonServiceRegistrationDelegator.java [new file with mode: 0644]
singleton-service/mdsal-singleton-common-api/src/main/java/org/opendaylight/mdsal/singleton/common/api/CommonClusterSingletonServiceProvider.java [new file with mode: 0644]
singleton-service/mdsal-singleton-common-api/src/main/java/org/opendaylight/mdsal/singleton/common/api/ServiceGroupIdentifier.java [new file with mode: 0644]
singleton-service/mdsal-singleton-common-api/src/site/asciidoc/01_doubleCandidateSimpleSequence.plantuml [new file with mode: 0644]
singleton-service/mdsal-singleton-common-api/src/site/asciidoc/02_classClusterSingletonService.plantuml [new file with mode: 0644]
singleton-service/mdsal-singleton-common-api/src/site/asciidoc/03_classClusterSingletonServiceGroup.plantuml [new file with mode: 0644]
singleton-service/mdsal-singleton-common-api/src/site/asciidoc/04_classClusterSingletonServiceProvider.plantuml [new file with mode: 0644]
singleton-service/mdsal-singleton-common-api/src/site/asciidoc/05_pluginOsgiLifeCycle.plantuml [new file with mode: 0644]
singleton-service/mdsal-singleton-common-api/src/site/asciidoc/06_baseAppSingleInstance.plantuml [new file with mode: 0644]
singleton-service/mdsal-singleton-common-api/src/site/asciidoc/07_processAppSingleInstSimply.plantuml [new file with mode: 0644]
singleton-service/mdsal-singleton-common-api/src/site/asciidoc/08_processAppSingleInst.plantuml [new file with mode: 0644]
singleton-service/mdsal-singleton-common-api/src/site/asciidoc/cluster-wide-services.adoc [new file with mode: 0644]
singleton-service/mdsal-singleton-common-api/src/site/site.xml [new file with mode: 0644]
singleton-service/mdsal-singleton-dom-api/pom.xml [new file with mode: 0644]
singleton-service/mdsal-singleton-dom-api/src/main/java/org/opendaylight/mdsal/singleton/dom/api/DOMClusterSingletonServiceProvider.java [new file with mode: 0644]
singleton-service/pom.xml [new file with mode: 0644]

index 86097664e443dbdd64812a29b3760b848b261473..b58365ae44c8f00e2989f7ca37212a747c258fd7 100644 (file)
@@ -11,6 +11,7 @@
 .settings
 target
 *.iml
+*.svg
 .idea
 bin
 xtend-gen
index 6678f5221e44754d6967bae9cc7402e5c74de22a..8688f95cb1b6cdb4b6ce76a1d77f5b183e2c6002 100644 (file)
                 <artifactId>mdsal-eos-binding-adapter</artifactId>
                 <version>${project.version}</version>
             </dependency>
+            <dependency>
+                <groupId>org.opendaylight.mdsal</groupId>
+                <artifactId>mdsal-singleton-common-api</artifactId>
+                <version>${project.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.opendaylight.mdsal</groupId>
+                <artifactId>mdsal-singleton-dom-api</artifactId>
+                <version>${project.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.opendaylight.mdsal</groupId>
+                <artifactId>mdsal-singleton-binding-api</artifactId>
+                <version>${project.version}</version>
+            </dependency>
         </dependencies>
     </dependencyManagement>
 
diff --git a/pom.xml b/pom.xml
index 55f417939aaefff6761d2065311c294f25b8a29e..5e34c5ab6710a1cacba95b665913e7e74c95e081 100644 (file)
--- a/pom.xml
+++ b/pom.xml
@@ -37,6 +37,7 @@
       <module>binding2</module>
       <module>model</module>
       <module>entityownership</module>
+      <module>singleton-service</module>
     </modules>
 
     <build>
diff --git a/singleton-service/mdsal-singleton-binding-api/pom.xml b/singleton-service/mdsal-singleton-binding-api/pom.xml
new file mode 100644 (file)
index 0000000..6a690b5
--- /dev/null
@@ -0,0 +1,73 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+  <parent>
+    <groupId>org.opendaylight.mdsal</groupId>
+    <artifactId>singleton-service</artifactId>
+    <version>2.1.0-SNAPSHOT</version>
+  </parent>
+
+  <artifactId>mdsal-singleton-binding-api</artifactId>
+  <packaging>bundle</packaging>
+
+  <dependencies>
+    <dependency>
+      <groupId>org.opendaylight.mdsal</groupId>
+      <artifactId>mdsal-eos-binding-api</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.opendaylight.mdsal</groupId>
+      <artifactId>mdsal-singleton-common-api</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>com.google.guava</groupId>
+      <artifactId>guava</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.opendaylight.yangtools</groupId>
+      <artifactId>concepts</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.opendaylight.yangtools</groupId>
+      <artifactId>util</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.opendaylight.yangtools</groupId>
+      <artifactId>yang-common</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.opendaylight.yangtools</groupId>
+      <artifactId>mockito-configuration</artifactId>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+
+  <scm>
+    <connection>scm:git:http://git.opendaylight.org/gerrit/mdsal.git</connection>
+    <developerConnection>scm:git:ssh://git.opendaylight.org:29418/mdsal.git</developerConnection>
+    <tag>HEAD</tag>
+    <url>https://wiki.opendaylight.org/view/MD-SAL:Main</url>
+  </scm>
+
+  <!--
+      Maven Site Configuration
+
+      The following configuration is necessary for maven-site-plugin to
+      correctly identify the correct deployment path for OpenDaylight Maven
+      sites.
+  -->
+  <url>${odl.site.url}/${project.groupId}/${stream}/${project.artifactId}/</url>
+
+  <distributionManagement>
+    <site>
+      <id>opendaylight-site</id>
+      <url>${nexus.site.url}/${project.artifactId}/</url>
+    </site>
+  </distributionManagement>
+
+</project>
diff --git a/singleton-service/mdsal-singleton-binding-api/src/main/java/org/opendaylight/mdsal/singleton/binding/api/ClusterSingletonServiceProvider.java b/singleton-service/mdsal-singleton-binding-api/src/main/java/org/opendaylight/mdsal/singleton/binding/api/ClusterSingletonServiceProvider.java
new file mode 100644 (file)
index 0000000..75121c5
--- /dev/null
@@ -0,0 +1,21 @@
+/*
+ * Copyright (c) 2016 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.singleton.binding.api;
+
+import org.opendaylight.mdsal.eos.binding.api.EntityOwnershipListener;
+import org.opendaylight.mdsal.singleton.common.api.CommonClusterSingletonServiceProvider;
+
+/**
+ * Binding version of {@link ClusterSingletonServiceProvider}
+ */
+public interface ClusterSingletonServiceProvider
+        extends CommonClusterSingletonServiceProvider, EntityOwnershipListener {
+
+    // Marker interface for blueprint
+}
diff --git a/singleton-service/mdsal-singleton-common-api/pom.xml b/singleton-service/mdsal-singleton-common-api/pom.xml
new file mode 100644 (file)
index 0000000..6a41c8e
--- /dev/null
@@ -0,0 +1,69 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+  <parent>
+    <groupId>org.opendaylight.mdsal</groupId>
+    <artifactId>singleton-service</artifactId>
+    <version>2.1.0-SNAPSHOT</version>
+  </parent>
+
+  <artifactId>mdsal-singleton-common-api</artifactId>
+  <packaging>bundle</packaging>
+
+  <dependencies>
+    <dependency>
+      <groupId>org.opendaylight.mdsal</groupId>
+      <artifactId>mdsal-eos-common-api</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>com.google.guava</groupId>
+      <artifactId>guava</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.opendaylight.yangtools</groupId>
+      <artifactId>concepts</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.opendaylight.yangtools</groupId>
+      <artifactId>util</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.opendaylight.yangtools</groupId>
+      <artifactId>yang-common</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.opendaylight.yangtools</groupId>
+      <artifactId>mockito-configuration</artifactId>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+
+  <scm>
+    <connection>scm:git:http://git.opendaylight.org/gerrit/mdsal.git</connection>
+    <developerConnection>scm:git:ssh://git.opendaylight.org:29418/mdsal.git</developerConnection>
+    <tag>HEAD</tag>
+    <url>https://wiki.opendaylight.org/view/MD-SAL:Main</url>
+  </scm>
+
+  <!--
+      Maven Site Configuration
+
+      The following configuration is necessary for maven-site-plugin to
+      correctly identify the correct deployment path for OpenDaylight Maven
+      sites.
+  -->
+  <url>${odl.site.url}/${project.groupId}/${stream}/${project.artifactId}/</url>
+
+  <distributionManagement>
+    <site>
+      <id>opendaylight-site</id>
+      <url>${nexus.site.url}/${project.artifactId}/</url>
+    </site>
+  </distributionManagement>
+
+</project>
diff --git a/singleton-service/mdsal-singleton-common-api/src/main/java/org/opendaylight/mdsal/singleton/common/api/ClusterSingletonService.java b/singleton-service/mdsal-singleton-common-api/src/main/java/org/opendaylight/mdsal/singleton/common/api/ClusterSingletonService.java
new file mode 100644 (file)
index 0000000..2329f8b
--- /dev/null
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2016 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.singleton.common.api;
+
+import com.google.common.util.concurrent.ListenableFuture;
+import org.opendaylight.yangtools.concepts.Identifiable;
+
+/**
+ * {@link ClusterSingletonService} interface represents a single cluster service instance. It has to implement
+ * every service (RPCs or Applications) which would like to be instantiated on same Cluster Node. Grouping is
+ * realized by ServiceGroupIdentifier. Servicies with same ServiceGroupIdentifier have to run on same Cluster
+ * Node. ServiceGroupIdentifier must not change during whole {@link ClusterSingletonService} lifecycle.
+ */
+public interface ClusterSingletonService extends Identifiable<ServiceGroupIdentifier> {
+
+    /**
+     * This method is invoked to instantiate an underlying service instance when
+     * ownership has been granted for the service entity.
+     */
+    void instantiateServiceInstance();
+
+    /**
+     * This method is invoked to close the underlying service instance when ownership has been lost
+     * for the service entity. If the act of closing the instance may perform blocking operations or
+     * take some time, it should be done asynchronously to avoid blocking the current thread.
+     *
+     * @return a ListenableFuture that is completed when the underlying instance close operation is complete.
+     */
+    ListenableFuture<Void> closeServiceInstance();
+
+}
diff --git a/singleton-service/mdsal-singleton-common-api/src/main/java/org/opendaylight/mdsal/singleton/common/api/ClusterSingletonServiceGroup.java b/singleton-service/mdsal-singleton-common-api/src/main/java/org/opendaylight/mdsal/singleton/common/api/ClusterSingletonServiceGroup.java
new file mode 100644 (file)
index 0000000..9e1cdcf
--- /dev/null
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 2016 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.singleton.common.api;
+
+import com.google.common.util.concurrent.ListenableFuture;
+import java.util.List;
+import org.opendaylight.mdsal.eos.common.api.GenericEntity;
+import org.opendaylight.mdsal.eos.common.api.GenericEntityOwnershipChange;
+import org.opendaylight.yangtools.concepts.Path;
+
+/**
+ * {@link ClusterSingletonServiceGroup} maintains a group of {@link ClusterSingletonService}
+ * instancies. All EntityOwnershipChange notifications have to applied to all registered
+ * services at the same time in the same manner.
+ * So this interface represents a single cluster service group instance." - remove this
+ * sentence. All registered services have only one instantiated service instance in a cluster
+ * at one time on same Cluster Node. This is realized via a double candidate approach where
+ * a service group instance maintains a candidate registration for ownership of the service
+ * entity in the cluster and also a registration that acts as a guard to ensure a service
+ * group instance has fully closed prior to relinquishing service ownership. To achieve
+ * ownership of the service group, a service group candidate must hold ownership
+ * of both these entities.
+ *
+ * @param <P> the instance identifier path type
+ * @param <E> the GenericEntity type
+ * @param <C> the GenericEntityOwnershipChange type
+ */
+interface ClusterSingletonServiceGroup<P extends Path<P>, E extends GenericEntity<P>,
+                                       C extends GenericEntityOwnershipChange<P, E>> {
+
+    /**
+     * This method must be called once on startup to initialize this group and
+     * register the relevant group entity candidate. It means create relevant
+     * Group Entity Candidate Registration.
+     */
+    void initializationClusterSingletonGroup();
+
+    /**
+     * This method registers a service instance for this service group. If the local node has
+     * ownership of the service group, the {@link ClusterSingletonService#instantiateServiceInstance()}
+     * method is called. Otherwise, the method is called once the local node gains ownership.
+     *
+     * @param service instance
+     * @return closable {@link ClusterSingletonServiceRegistration}
+     */
+    ClusterSingletonServiceRegistration registerService(ClusterSingletonService service);
+
+    /**
+     * Method provides possibility to restart some service from group without change
+     * leadership for whole group. {@link ClusterSingletonServiceRegistration#close()}
+     * implementation has to call this service.
+     * Candidates are signed for group, so unregistration for group with one service
+     * has to trigger new election only otherwise we can see same behavior as on server
+     * without clustering.
+     *
+     * @param service instance
+     */
+    void unregisterService(ClusterSingletonService service);
+
+    /**
+     * Method implementation has to apply ownershipChange for all registred services.
+     *
+     * @param ownershipChange change role for ClusterSingletonServiceGroup
+     */
+    void ownershipChanged(final C ownershipChange);
+
+    /**
+     * Closes this service group. All registered service providers are also closed.
+     *
+     * @return {@link ListenableFuture} in list for all Future from closing {@link ClusterSingletonService}
+     */
+    ListenableFuture<List<Void>> closeClusterSingletonGroup();
+
+}
+
diff --git a/singleton-service/mdsal-singleton-common-api/src/main/java/org/opendaylight/mdsal/singleton/common/api/ClusterSingletonServiceRegistration.java b/singleton-service/mdsal-singleton-common-api/src/main/java/org/opendaylight/mdsal/singleton/common/api/ClusterSingletonServiceRegistration.java
new file mode 100644 (file)
index 0000000..831c185
--- /dev/null
@@ -0,0 +1,21 @@
+/*
+ * Copyright (c) 2016 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.singleton.common.api;
+
+/**
+ * {@link ClusterSingletonServiceRegistration} interface contains a marker for
+ * holding ClusterSingletonService registration and posibility to close it
+ * via {@link AutoCloseable} interface.
+ */
+public interface ClusterSingletonServiceRegistration extends AutoCloseable {
+
+    /**
+     * Empty body for mark a ClusterSingletonService Registration instance
+     */
+}
diff --git a/singleton-service/mdsal-singleton-common-api/src/main/java/org/opendaylight/mdsal/singleton/common/api/ClusterSingletonServiceRegistrationDelegator.java b/singleton-service/mdsal-singleton-common-api/src/main/java/org/opendaylight/mdsal/singleton/common/api/ClusterSingletonServiceRegistrationDelegator.java
new file mode 100644 (file)
index 0000000..e062a4e
--- /dev/null
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2016 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.singleton.common.api;
+
+import com.google.common.base.Preconditions;
+import com.google.common.util.concurrent.ListenableFuture;
+
+/**
+ * Package protected help class represent a Delegator for {@link ClusterSingletonService}
+ * instance and {@link ClusterSingletonServiceRegistration} implementation.
+ * Close registration means remove {@link ClusterSingletonService} instance from internal
+ * ClusterSingletonServiceGroup list reference.
+ *
+ * Close {@link ClusterSingletonServiceRegistration} is prepared for a possible restart
+ * service or application in osgi container. Any another servicies from group can not be
+ * stoped.
+ */
+class ClusterSingletonServiceRegistrationDelegator
+        implements ClusterSingletonServiceRegistration, ClusterSingletonService {
+
+    private final ClusterSingletonService service;
+    private final ClusterSingletonServiceGroup<?, ?, ?> group;
+
+    ClusterSingletonServiceRegistrationDelegator(final ClusterSingletonService service,
+            final ClusterSingletonServiceGroup<?, ?, ?> group) {
+        this.service = Preconditions.checkNotNull(service);
+        this.group = Preconditions.checkNotNull(group);
+    }
+
+    @Override
+    public void close() throws Exception {
+        group.unregisterService(this);
+    }
+
+    @Override
+    public void instantiateServiceInstance() {
+        service.instantiateServiceInstance();
+    }
+
+    @Override
+    public ListenableFuture<Void> closeServiceInstance() {
+        return service.closeServiceInstance();
+    }
+
+    @Override
+    public ServiceGroupIdentifier getIdentifier() {
+        return service.getIdentifier();
+    }
+
+    public String getServiceGroupIdentifier() {
+        return service.getIdentifier().getValue();
+    }
+}
\ No newline at end of file
diff --git a/singleton-service/mdsal-singleton-common-api/src/main/java/org/opendaylight/mdsal/singleton/common/api/CommonClusterSingletonServiceProvider.java b/singleton-service/mdsal-singleton-common-api/src/main/java/org/opendaylight/mdsal/singleton/common/api/CommonClusterSingletonServiceProvider.java
new file mode 100644 (file)
index 0000000..4f35640
--- /dev/null
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2016 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.singleton.common.api;
+
+/**
+ * {@link CommonClusterSingletonServiceProvider} provides a functionality to register and group services
+ * {@link ClusterSingletonService} by service group identifier. Services could be Applications or RPCs.
+ * Provider provides a functionality which allows to have only one fully instantiated service instance
+ * in a cluster at one time and service group means to have all service instances for the same group
+ * situated on same Cluster Node. This is realized via a double candidate approach where a service
+ * group instance maintains a candidate registration for ownership of the service group entity in the
+ * cluster and also a registration that acts as a guard to ensure a service group instance has fully
+ * closed prior to relinquishing service group ownership. To achieve ownership of the service group,
+ * a service group candidate must hold ownership of both these entities.
+ */
+public interface CommonClusterSingletonServiceProvider extends AutoCloseable {
+
+    /**
+     * Method registers {@link ClusterSingletonService} to Provider.
+     *
+     * @param service ClusterSingletonService instance
+     * @return {@link AutoCloseable} registration
+     */
+    ClusterSingletonServiceRegistration registerClusterSingletonService(ClusterSingletonService service);
+
+}
diff --git a/singleton-service/mdsal-singleton-common-api/src/main/java/org/opendaylight/mdsal/singleton/common/api/ServiceGroupIdentifier.java b/singleton-service/mdsal-singleton-common-api/src/main/java/org/opendaylight/mdsal/singleton/common/api/ServiceGroupIdentifier.java
new file mode 100644 (file)
index 0000000..c858a70
--- /dev/null
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2016 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.singleton.common.api;
+
+import org.opendaylight.yangtools.util.AbstractStringIdentifier;
+
+/**
+ * Identifier represents a service group competence. It's based on String.
+ */
+public class ServiceGroupIdentifier extends AbstractStringIdentifier<ServiceGroupIdentifier> {
+
+    /**
+     * Method create immutable instance of {@link ServiceGroupIdentifier}
+     *
+     * @param serviceGroupIdentifier
+     * @return {@link ServiceGroupIdentifier} instance
+     */
+    public static ServiceGroupIdentifier create(final String serviceGroupIdentifier) {
+        return new ServiceGroupIdentifier(serviceGroupIdentifier);
+    }
+
+    protected ServiceGroupIdentifier(final String string) {
+        super(string);
+    }
+
+    private static final long serialVersionUID = 6853612584804702662L;
+
+}
diff --git a/singleton-service/mdsal-singleton-common-api/src/site/asciidoc/01_doubleCandidateSimpleSequence.plantuml b/singleton-service/mdsal-singleton-common-api/src/site/asciidoc/01_doubleCandidateSimpleSequence.plantuml
new file mode 100644 (file)
index 0000000..32c9602
--- /dev/null
@@ -0,0 +1,84 @@
+["plantuml", "01_doubleCandidateSimpleSequence", "svg"]
+
+------------
+@startuml
+
+  autonumber "<font color=red><b>[00]"
+
+  participant "OSGi Container" as osgi
+  participant "Application" as app
+  participant "Application Body" as body
+  participant "EntitiOwnerhsipService" as eos
+
+  alt "Initialization"
+    osgi -> app : <<init>>
+    activate osgi
+      activate app
+        app -> eos : registerListener()
+        activate eos
+          app <-- eos
+        deactivate eos
+      deactivate app
+      app -> eos : registerCandidate(MainEntity)
+      activate app
+        activate eos
+          app <-- eos
+        deactivate eos
+        osgi <-- app
+      deactivate app
+    deactivate osgi
+
+  else "Try to take Leadership"
+    eos -> app : ownershipChanged(isOwner=true) [MainEntity]
+    activate eos
+      activate app
+        app -> eos : registerCandidate(CloseGuardEntity)
+        app <-- eos
+        eos <-- app
+      deactivate app
+    deactivate eos
+
+  else "Take Leadership"
+    eos -> app : ownershipChanged(isOwner=true) [CloseGuardEntity]
+    activate eos
+    activate app
+      app -> body : <<init>>
+      activate body
+        ...
+        app <-- body
+      deactivate body
+    eos <-- app
+    deactivate app
+    deactivate eos
+
+  else "Lost Leadership"
+    alt "CloseGuardEntity is not registered (initial phase)"
+      eos -> app : ownershipChanged(isOwner=false, wasOwner=false) [MainEntity]
+      activate eos
+      activate app
+        eos <-- app
+      deactivate app
+      deactivate eos
+
+    else "CloseGuardEntity is registered (realy lost leadership)"
+      eos -> app : ownershipChanged(isOwner=false, wasOwner=true) [MainEntity]
+      activate eos
+      activate app
+        app -\ body : close
+      deactivate app
+      activate body
+        ...
+        app \-- body
+      deactivate body
+      activate app
+        app -> eos :closeGuardCandidateRegistration.close
+        app <-- eos
+        eos <-- app
+      deactivate app
+      deactivate eos
+
+    end
+  end
+
+@enduml
+------------
diff --git a/singleton-service/mdsal-singleton-common-api/src/site/asciidoc/02_classClusterSingletonService.plantuml b/singleton-service/mdsal-singleton-common-api/src/site/asciidoc/02_classClusterSingletonService.plantuml
new file mode 100644 (file)
index 0000000..858043d
--- /dev/null
@@ -0,0 +1,28 @@
+["plantuml", "02_classClusterSingletonService", "svg"]
+
+------------
+@startuml
+
+interface "AutoCloseable" as auto {
+  +void close();
+}
+
+interface "ClusterSingletonService" as csService {
+  +void instantiateServiceInstance();
+  +ListenableFuture<Void> closeServiceInstance();
+  +String getServiceGroupIdentifier();
+}
+
+interface "ClusterSingletonServiceRegistration" as cssRegistration {
+}
+
+interface "ClusterSingletonServiceProvider" as cssProvider {
+  +ClusterSingletonServiceRegistration registerClusterSingletonService(ClusterSingletonService);
+}
+
+cssProvider --|> auto
+cssRegistration --|> auto
+
+
+@enduml
+------------
diff --git a/singleton-service/mdsal-singleton-common-api/src/site/asciidoc/03_classClusterSingletonServiceGroup.plantuml b/singleton-service/mdsal-singleton-common-api/src/site/asciidoc/03_classClusterSingletonServiceGroup.plantuml
new file mode 100644 (file)
index 0000000..1ece350
--- /dev/null
@@ -0,0 +1,39 @@
+["plantuml", "03_classClusterSingletonServiceGroup", "svg"]
+
+------------
+@startuml
+
+interface "AutoCloseable" as auto {
+  +void close();
+}
+
+interface "ClusterSingletonService" as csService {
+  +void instantiateServiceInstance();
+  +ListenableFuture<Void> closeServiceInstance();
+  +String getServiceGroupIdentifier();
+}
+
+interface "ClusterSingletonServiceGroup" as cssGroup {
+  +void initializationClusterSingletonGroup()
+  +ClusterSingletonServiceRegistration registerService(ClusterSingletonService);
+  +void unregisterService(ClusterSingletonService);
+  +void ownershipChanged(final OwnershipChange ownershipChange);
+  +ListenableFuture<List<Void>> closingClusterSingletonGroup();
+}
+
+interface "ClusterSingletonServiceRegistration" as cssRegistration {
+}
+
+interface "ClusterSingletonServiceProvider" as cssProvider {
+  +ClusterSingletonServiceRegistration registerClusterSingletonService(ClusterSingletonService);
+}
+
+cssProvider --|> auto
+cssRegistration --|> auto
+
+csService - cssProvider : registerClusterSingletonService
+cssProvider - cssGroup : registerClusterSingletonService
+
+
+@enduml
+------------
diff --git a/singleton-service/mdsal-singleton-common-api/src/site/asciidoc/04_classClusterSingletonServiceProvider.plantuml b/singleton-service/mdsal-singleton-common-api/src/site/asciidoc/04_classClusterSingletonServiceProvider.plantuml
new file mode 100644 (file)
index 0000000..49a0fde
--- /dev/null
@@ -0,0 +1,48 @@
+["plantuml", "04_classClusterSingletonServiceProvider", "svg"]
+
+------------
+@startuml
+
+  interface "AutoCloseable" as auto {
+    +void close();
+  }
+
+  interface "GenericEntityOwnershipListener" as eosList {
+    +void ownershipChanged(EntityOwnershipChange ownershipChange);
+  }
+
+  interface "ClusterSingletonServiceProvider" as cssProvider {
+    ClusterSingletonServiceRegistration registerClusterSingletonService(ClusterSingletonService);
+  }
+
+  abstract "AbstractClusterSingletonServiceProviderImpl" as acssProviderImpl {
+    +final void initializeProvider();
+    +final ClusterSingletonServiceRegistration registerClusterSingletonService(ClusterSingletonService);
+    +final void ownershipChanged(OwnershipChange);
+    +final void close();
+    #abstract EntityOwnershipListenerRegistration registerListener(String entityType, GenericEntityOwnershipService service);
+    #abstract GenericEntity createEntity(String entityType, String entityIdentifier);
+    #abstract String getServiceIdentifierFromEntity(GenericEntity entity);
+    #final void cleaningProvider(Throwable t);
+  }
+
+  class "DOMClusterSingletonServiceProviderImpl" as domCssProviderImpl {
+    #final DOMEntity createEntity(String entityType, String entityIdentifier);
+    #final DOMEntityOwnershipListenerRegistration registerListener(String type, DOMEntityOwnershipService eos);
+    #final String getServiceIdentifierFromEntity(DOMEntity entity);
+  }
+
+  class "ClusterSingletonServiceProviderImpl" as cssProviderImpl {
+    #final Entity createEntity(String type, String ident);
+    #final EntityOwnershipListenerRegistration registerListener(String type, EntityOwnershipService eos);
+    #final String getServiceIdentifierFromEntity(Entity entity);
+  }
+
+  cssProvider --|> auto
+  acssProviderImpl --|> cssProvider
+  acssProviderImpl --|> eosList
+  cssProviderImpl --|> acssProviderImpl
+  domCssProviderImpl --|> acssProviderImpl
+
+@enduml
+------------
diff --git a/singleton-service/mdsal-singleton-common-api/src/site/asciidoc/05_pluginOsgiLifeCycle.plantuml b/singleton-service/mdsal-singleton-common-api/src/site/asciidoc/05_pluginOsgiLifeCycle.plantuml
new file mode 100644 (file)
index 0000000..a2c95cf
--- /dev/null
@@ -0,0 +1,34 @@
+["plantuml", "05_pluginOsgiLifeCycle", "svg"]
+
+------------
+@startuml
+
+
+  (Start) as (s)
+  (Installed) as (inst)
+  (Resolved) as (res)
+  (Uninstalled) as (uninst)
+
+  (Starting) as (st)
+  (Active) as (act)
+  (Stopping) as (stop)
+
+  (inst) -[hidden]> (st)
+  (res) -[hidden]> (act)
+  (uninst) -[hidden]> (stop)
+
+  (s) --|> (inst)
+  (inst) --|> (res)
+  (inst) <|-- (res)
+  (res) --|> (uninst)
+  (inst) --|> (uninst)
+
+  (st) --|> (act)
+  (act) --|> (stop) : stop
+
+  (res) --|> (st) : start
+  (stop) --|> (res)
+
+
+@enduml
+------------
diff --git a/singleton-service/mdsal-singleton-common-api/src/site/asciidoc/06_baseAppSingleInstance.plantuml b/singleton-service/mdsal-singleton-common-api/src/site/asciidoc/06_baseAppSingleInstance.plantuml
new file mode 100644 (file)
index 0000000..456094d
--- /dev/null
@@ -0,0 +1,28 @@
+["plantuml", "06_baseAppSingleInstance", "svg"]
+
+------------
+@startuml
+
+  cloud "Cluster" as cluster {
+    component "EntitiOwnershipService" as eos
+  }
+
+  package "OSGi Container" as osgi {
+    component "ClusterSingletonServiceProvider" as cssProvider
+  }
+
+  package "ODL app Module" as app {
+    component "ODL app provider" as appProvider
+    component "ODL app body" as appBody
+  }
+
+  osgi -> app : <<init>>
+  osgi <-- app : getClusterSingletonServiceProvider
+  appProvider --> cssProvider : registerClusterSingletonService
+  cssProvider --> eos : register candidate
+  cssProvider <-- eos : get EOS Role
+  cssProvider --> appBody : <<init>> for Master only
+
+
+@enduml
+------------
diff --git a/singleton-service/mdsal-singleton-common-api/src/site/asciidoc/07_processAppSingleInstSimply.plantuml b/singleton-service/mdsal-singleton-common-api/src/site/asciidoc/07_processAppSingleInstSimply.plantuml
new file mode 100644 (file)
index 0000000..b58c667
--- /dev/null
@@ -0,0 +1,78 @@
+["plantuml", "07_processAppSingleInstSimply", "svg"]
+
+------------
+@startuml
+
+
+  autonumber "<font color=red><b>[00]"
+
+  participant "OSGi container" as osgi
+  participant "AbstractModule<ODL app>" as module
+  participant "ODL app ProviderImpl" as provider
+  participant "ODL app Body" as body
+  participant "ClusterSingletonServiceProvider" as cssProvider
+  participant "EntityOwnershipService" as eos
+
+  activate osgi
+    osgi -> module : <<init>>
+    activate module
+      module -> provider : initializeProvider()
+      activate provider
+        provider -> cssProvider : registerClusterSingletonService()
+        activate cssProvider
+          cssProvider -> eos : registerCandidate
+          activate eos
+            cssProvider <-- eos
+          deactivate eos
+          provider <-- cssProvider
+        deactivate cssProvider
+        module <-- provider
+      deactivate provider
+      osgi <-- module
+    deactivate module
+  deactivate osgi
+
+  alt "Get Role Master"
+    activate eos
+    cssProvider <- eos : ownershipChanged(isOwner=true)
+      activate cssProvider
+        cssProvider -> provider : instantiateServiceInstance()
+        activate provider
+          provider -> body : <<init>>
+          activate body
+            provider <-- body
+          deactivate body
+          cssProvider <-- provider
+        deactivate provider
+        cssProvider --> eos
+      deactivate cssProvider
+    deactivate eos
+  else "Get Role Slave"
+    alt "Was Master"
+      cssProvider <- eos : ownershipChanged(isOwner=false, wasOwner=true)
+      activate eos
+        activate cssProvider
+          cssProvider -> provider : closeServiceInstance()
+          activate provider
+            provider -> body : close
+            activate body
+              provider <-- body : close
+            deactivate body
+            cssProvider <-- provider
+          deactivate provider
+          cssProvider --> eos
+        deactivate cssProvider
+      deactivate eos
+    else "Was Not Master"
+      cssProvider <- eos : ownershipChanged(isOwner=false, wasOwner=false)
+      activate eos
+        activate cssProvider
+          cssProvider --> eos
+        deactivate cssProvider
+      deactivate eos
+    end
+  end
+
+
+@enduml
+------------
diff --git a/singleton-service/mdsal-singleton-common-api/src/site/asciidoc/08_processAppSingleInst.plantuml b/singleton-service/mdsal-singleton-common-api/src/site/asciidoc/08_processAppSingleInst.plantuml
new file mode 100644 (file)
index 0000000..2ae1abd
--- /dev/null
@@ -0,0 +1,272 @@
+["plantuml", "08_processAppSingleInst", "svg"]
+
+------------
+@startuml
+
+
+  autonumber "<font color=red><b>[00]"
+
+  participant "OSGi container" as osgi
+  participant "AbstractModule<ODL app>" as module
+  participant "ODL ServiceProvider\nextends ClusterSingletonService" as provider
+  participant "ODL app Body" as body
+  participant "ClusterSingletonServiceProvider" as cssProvider
+  participant "ClusterSingletonServiceGroup" as cssGroup
+  participant "EntityOwnershipService" as eos
+
+  == Initialization ==
+
+  activate osgi
+    osgi -> module : <<init>>
+    activate module
+      module -> provider : initializeProvider()
+      activate provider
+        provider -> cssProvider : registerClusterSingletonService()
+        activate cssProvider
+  alt "New Group"
+          cssProvider -> cssGroup : <<init>>
+          activate cssProvider
+            activate cssGroup
+              cssProvider <-- cssGroup
+            deactivate cssGroup
+          deactivate cssProvider
+          cssProvider -> cssGroup : initializationClusterSingletonGroup()
+          activate cssProvider
+            activate cssGroup
+              cssGroup -> eos : registerCandidate
+              activate eos
+                cssGroup <-- eos
+              deactivate eos
+              cssProvider <-- cssGroup
+            deactivate cssGroup
+          deactivate cssProvider
+  else "Group is exist"
+          cssProvider -> cssGroup : registerService
+          activate cssProvider
+            activate cssGroup
+              cssProvider <-- cssGroup
+            deactivate cssGroup
+          deactivate cssProvider
+  end
+        provider <-- cssProvider
+        deactivate cssProvider
+        module <-- provider
+      deactivate provider
+      osgi <-- module
+    deactivate module
+  deactivate osgi
+
+  alt "Get MainCandidate Role Master"
+    eos -> eos : <<getRole(MainCandidate)>>
+    note over eos
+      hasOwner=false call election
+      hasOwner=true return Slave for every new registration
+    end note
+    activate eos
+      cssProvider <- eos : ownershipChanged(MainEntity, isOwner=true)
+      activate cssProvider
+        cssProvider -> cssGroup : ownerchipChanged(MainEntity, isOwner=true)
+        activate cssGroup
+          cssGroup -> eos : registerCandidate(CloseGuardEntity)
+          activate eos
+            cssGroup <-- eos
+          deactivate eos
+          cssProvider <-- cssGroup
+        deactivate cssGroup
+        cssProvider --> eos
+      deactivate cssProvider
+    deactivate eos
+    eos -> eos : newElection
+    note over eos
+      initialization phase does not have close guard
+      candidate registration so it starts with election
+    end note
+    activate eos
+      cssProvider <- eos : ownershipChanged(CloseGuardEntity, isOwner=true)
+      activate cssProvider
+        cssProvider -> cssGroup : ownershipChanged(CloseGuardEntity, isOwner=true)
+        activate cssGroup
+          cssGroup -> provider : instantiateServiceInstance()
+          activate provider
+            provider -> body : <<init>>
+            activate body
+              ...
+              provider <-- body
+            deactivate body
+            cssGroup <-- provider
+          deactivate provider
+          cssProvider <-- cssGroup
+        deactivate cssGroup
+        cssProvider --> eos
+      deactivate cssProvider
+    deactivate eos
+
+  else "Get MainCandidate Role Slave"
+    eos -> eos : getRole
+    activate eos
+      cssProvider <- eos : ownershipChanged(MainEntity, isOwner=false)
+      activate cssProvider
+        cssProvider -> cssGroup
+        activate cssGroup
+          note over cssGroup : "NOOP"
+          cssProvider <-- cssGroup
+        deactivate cssGroup
+        cssProvider --> eos
+      deactivate cssProvider
+    deactivate eos
+  end
+
+  == Repetition ==
+
+  alt "Get MainCandidate Role Master"
+    eos -> eos : <<newElection>>
+    activate eos
+      cssProvider <- eos : ownershipChanged(MainEntity, isOwner=true)
+      activate cssProvider
+        cssProvider -> cssGroup : ownershipChanged(MainEntity, isOwner=true)
+        activate cssGroup
+          cssGroup -> eos : registerCandidate(CloseGuardEntity)
+          activate eos
+            cssGroup <-- eos
+          deactivate eos
+          cssProvider <-- cssGroup
+        deactivate cssGroup
+        cssProvider --> eos
+      deactivate cssProvider
+    deactivate eos
+    eos -> eos : getRole
+    note over eos
+      hasOwner=true so EOS returns Slave and we have to
+      wait for unregistration old Leader of CloseGuard Candidate
+    end note
+    activate eos
+      cssProvider <-eos : ownershipChanged(CloseGuardEntity, isOwner=false)
+      activate cssProvider
+        cssProvider -> cssGroup : ownershipChanged(CloseGuardEntity, isOwner=false)
+        activate cssGroup
+          note over cssGroup : "NOOP"
+            cssProvider <-cssGroup
+        deactivate cssGroup
+      cssProvider --> eos
+      deactivate cssProvider
+    deactivate eos
+
+  else "Get MainCandidate Role Slave"
+    eos -> eos : <<newElection>>
+    activate eos
+      cssProvider <- eos : ownershipChanged(MainEntity, isOwner=false)
+      activate cssProvider
+        cssProvider -> cssGroup : ownershipChanged(MainEntity, isOwner=false)
+        activate cssGroup
+          cssGroup -> provider : closeServiceInstance()
+          activate provider
+            provider -\ body : <<close>>
+            activate body
+              ...
+              note over body : "close could be async. operation"
+              provider \-- body
+            deactivate body
+            cssGroup <-- provider
+          deactivate provider
+          cssGroup -> eos : unregisterCandidate(CloseGuardEntity)
+          activate eos
+            cssGroup <-- eos
+          deactivate eos
+          cssProvider <-- cssGroup
+        deactivate cssGroup
+        cssProvider --> eos
+      deactivate cssProvider
+    deactivate eos
+
+  else "Get CloseGuardCandidate Role Master"
+    eos -> eos : <<newElection>>
+    activate eos
+      cssProvider <- eos : ownershipChanged(CloseGuardEntity, isOwner=true)
+      activate cssProvider
+        cssProvider -> cssGroup : ownershipChanged(CloseGuardEntity, isOwner=true)
+        activate cssGroup
+          cssGroup -> provider : instantiateServiceInstance()
+          activate provider
+            provider -> body : <<init>>
+            activate body
+              ...
+              provider <-- body
+            deactivate body
+            cssGroup <-- provider
+          deactivate provider
+          cssProvider <-- cssGroup
+        deactivate cssGroup
+        cssProvider --> eos
+      deactivate cssProvider
+    deactivate eos
+
+  end
+
+  == Termination ==
+
+  activate osgi
+    osgi -> module : <<close>>
+    activate module
+      module -> provider : close()
+      activate provider
+        provider -> cssGroup : closeRegistration()
+        activate cssGroup
+          cssGroup -> eos : unregisterCandidate(MainEntity)
+          activate eos
+            cssGroup <-- eos
+          deactivate eos
+          provider <-- cssGroup
+        deactivate cssGroup
+        module <-- provider
+      deactivate provider
+      osgi <-- module
+    deactivate module
+  deactivate osgi
+
+  alt "Get MainCandidate Leader Role Slave"
+    activate eos
+      cssProvider <- eos : ownersipChanged(MainEntity, wasOwner=true)
+      activate cssProvider
+        cssProvider -> cssGroup : ownershipChanged(MainEntity, wasOwner=true)
+        activate cssGroup
+          cssGroup -> provider : closeServiceInstance()
+          activate provider
+            provider -\ body : <<close>>
+          deactivate provider
+        deactivate cssGroup
+      deactivate cssProvider
+    deactivate eos
+    activate body
+      note over body : "close could be async. operation"
+      ...
+      provider \-- body
+    deactivate body
+    activate provider
+      provider -> cssGroup : unregisterCandidate(CloseGuardEntity)
+      activate cssGroup
+        cssGroup -> eos : unregisterCandidate(CloseGuardEntity)
+        activate eos
+          cssGroup <-- eos
+        deactivate eos
+        provider <-- cssGroup
+      deactivate cssGroup
+    deactivate provider
+
+  else "Get MainCandidate NotLeader Role Slave"
+    activate eos
+      cssProvider <- eos : ownershipChange(MainEntity, wasOwner=false)
+      activate cssProvider
+        cssProvider -> cssGroup
+        activate cssGroup
+          note over cssGroup : "NOOP"
+          cssProvider <-- cssGroup
+        deactivate cssGroup
+        cssProvider --> eos
+      deactivate cssProvider
+    deactivate eos
+
+  end
+
+
+@enduml
+------------
diff --git a/singleton-service/mdsal-singleton-common-api/src/site/asciidoc/cluster-wide-services.adoc b/singleton-service/mdsal-singleton-common-api/src/site/asciidoc/cluster-wide-services.adoc
new file mode 100644 (file)
index 0000000..053b619
--- /dev/null
@@ -0,0 +1,240 @@
+= Cluster Wide Services
+
+----
+The existing OpenDaylight service deployment model assumes symmetric
+clusters, where all services are activated on all nodes in the cluster.
+However, many services require that there is a single active service
+instance per cluster. Examples are global MD-SAL RPC services, or
+services that use centralized data processing, or the OpenFlow Topology
+Manager, which needs to interact with all OF switches connected to a
+clustered controller and determine how the switches are interconnected.
+We call such services 'singleton services'.
+
+A developer of a singleton service must create logic that determines
+the active service instance, manages service failovers and ensures that
+a service instance always runs in the surviving partition of a cluster.
+This logic would have to interact with the Entity Ownership Service (EOS)
+and it is not easy to get it right. Leaving it to individual services
+would mean that each service would design and implement essentially the
+same functionality, each with its own behavior, engineering and issues.
+----
+
+== General Cluster Singleton Service Approach
+The main idea represents a single cluster service instance. The Entity
+Ownership Service (EOS) represents the base Leadership choice for one
+Entity instance. So we are able to move candidate election to the EOS.
+Every Cluster Singleton service *type* must have its own Entity and every
+Cluster Singleton service *instance* must have its own Entity Candidate.
+Every registered Entity Candidate should be notified about its actual role
+in the cluster.
+
+To ensure that there is only one active (i.e. fully instantiated) service
+instance in the cluster at any given time, we use the "double-candidate"
+approach: a service instance maintains not only a candidate registration
+for the ownership of the service's Entity in the cluster, but also an
+additonal (guard) ownership registration that ensures a full shutdown of
+the service instance before the overall ownership of the service is
+relinquished. To achieve the overall ownership of a singleton service,
+a service candidate must hold ownership of both these entities (see the
+sequence diagram below).
+
+.Double Candidate Solution (Async. Close Guard)
+include::01_doubleCandidateSimpleSequence.plantuml[]
+
+The double-candidate approach prevents the shutdown of a service with
+outstanding asynchronous operations, such as unfinished MD-SAL Data
+Store transactions. The **main entity** candidate is focused on the
+actual role of the service in the cluster; the **close guard entity**
+candidate is a guard that tracks the outstanding asynchronous operations.
+Every new Leader must register its own **close guard entity** candidate.
+A Leader that wishes to relinquish its leadership must close its
+**close guard entity** candidate. This is typically done in the last
+step of the shutdown procedure. When the old Leader relinquishes its
+**close guard entity** ownership, the new Leader will take the leadership
+for the **close guard entity** candidate (it has to hold ownership for
+both candidate signatures). That is the marker to full start cluster
+node application instance and old leader stops successfully. Figure 1
+shows the entire sequence.
+
+IMPORTANT: Double candidate approach (async. close guard) prerequisite is "actual ownership doesn't change by new candidate registration".
+
+=== Cluster Singleton Service
+Double candidate solution is relevant for all services and we don't need to implement same code for every instances. So we are able to hide whole EOS interaction for user and we can encapsulate it to some "ODL Cluster Singleton Service Provider" parent.
+
+.Class Diagram Cluster Singleton Service
+include::02_classClusterSingletonService.plantuml[]
+
+=== Cluster Singleton Service Grouping
+Sometimes we wish to have couple of services to run on same Cluster Node. So Double candidate EOS interaction could by realized for a list of ClusterSingletonService instances.
+
+.Class Diagram Cluster Singleton Service Group
+include::03_classClusterSingletonServiceGroup.plantuml[]
+
+
+=== Cluster Singleton Service Provider
+Provider implementation is realized as stay alone service which has to be instantiated for every ClusterNode and it has to be available for every depend applications. So class diagram looks as next.
+
+.Class Diagram Cluster Singleton Service Provider
+include::04_classClusterSingletonServiceProvider.plantuml[]
+
+=== Cluster Singleton Service RPC implementation sample
+We'd like to show a grouping RPC service sample. RPC services don't need be a part of same project.
+
+[source,java]
+----
+public class SampleClusterSingletonServiceRPC_1 implements ClusterSingletonService, AutoCloseable {
+
+    /* Property contains an entity name guard for all instances of this group of services */
+    private static final String CLUSTER_SERVICE_GROUP_IDENTIFIER = "sample-service-group";
+
+    private ClusterSingletonServiceRegistration registration;
+
+    public SampleClusterSingletonServiceRPC_1(final ClusterSingletonServiceProvider provider) {
+        Preconditions.checkArgument(provider != null);
+        this.registration = provider.registerClusterSingletonService(this);
+    }
+
+    @Override
+    public void instantiateServiceInstance() {
+        // TODO : implement start service functionality
+    }
+
+    @Override
+    public ListenableFuture<Void> closeServiceInstance() {
+        // TODO : implement sync. or async. stop service functionality
+        return Futures.immediateFuture(null);
+    }
+
+    @Override
+    public String getServiceGroupIdentifier() {
+        return CLUSTER_SERVICE_GROUP_IDENTIFIER;
+    }
+
+    @Override
+    public void close() throws Exception {
+        if (registration != null) {
+            registration.close();
+            registration = null;
+        }
+    }
+
+}
+
+public class SampleClusterSingletonServiceRPC_2 implements ClusterSingletonService, AutoCloseable {
+
+    /* Property contains an entity name guard for all instances of this group of services */
+    private static final String CLUSTER_SERVICE_GROUP_IDENTIFIER = "sample-service-group";
+
+    private ClusterSingletonServiceRegistration registration;
+
+    public SampleClusterSingletonServiceRPC_1(final ClusterSingletonServiceProvider provider) {
+        Preconditions.checkArgument(provider != null);
+        this.registration = provider.registerClusterSingletonService(this);
+    }
+
+    @Override
+    public void instantiateServiceInstance() {
+        // TODO : implement start service functionality
+    }
+
+    @Override
+    public ListenableFuture<Void> closeServiceInstance() {
+        // TODO : implement sync. or async. stop service functionality
+        return Futures.immediateFuture(null);
+    }
+
+    @Override
+    public String getServiceGroupIdentifier() {
+        return CLUSTER_SERVICE_GROUP_IDENTIFIER;
+    }
+
+    @Override
+    public void close() throws Exception {
+        if (registration != null) {
+            registration.close();
+            registration = null;
+        }
+    }
+
+}
+----
+
+Both RPCs are instantiated for some ClusterNode and RPCs have only one instance in whole Cluster.
+
+=== Cluster Singleton Application
+OSGi module application could be understand like service too. So we would like to focus for OSGi container like a application loader. Every OSGi app has own lifecycle which should be adapting to use EOS and only master could be loading fully. We wish to encapsulate EOS interaction in an ODL application Loader.
+
+.Life cycle of plug-ins in OSGi
+include::05_pluginOsgiLifeCycle.plantuml[]
+
+==== Application Module instantiation
+Every "ODL app." has Provider class which is instantiated in __AbstractModule<ODL app>__ class. Module has method __createInstance()__ which start an application Provider. So application provider has to implement __ClusterSingletonService__ interface and the application provider initialization (or constructor) has to register itself to ClusterSingletonServiceProvider. The application Provider body will be initialized by leader ClusterNode election for master only.
+
+.Base Cluster-wide app instantiation
+include::06_baseAppSingleInstance.plantuml[]
+
+So we are able to hide whole EOS interaction for user and encapsulate inside "ClusterSingletonServiceProvider". Application/service needs only implement relevant interface and registrates itself to provider.
+
+Simplified sequence diagram (without double candidate) is displayed in next picture:
+
+.Simply Cluster-wide app instantiation (without double candidate)
+include::07_processAppSingleInstSimply.plantuml[]
+
+Full sequence implementation diagram for __AbstractClusterProjectProvider__ is displayed in next picture:
+
+.Cluster-wide app instantiation
+include::08_processAppSingleInst.plantuml[]
+
+[source,java]
+----
+public class ClusterSingletonProjectSample implements ClusterSingletonService, AutoCloseable {
+
+    /* Property contains an entity name guard for all instances of this group of services */
+    private static final String CLUSTER_SERVICE_GROUP_IDENTIFIER = "sample-service-group";
+
+    private ClusterSingletonServiceRegistration registration;
+
+    public ClusterSingletonProjectSample(final ClusterSingletonServiceProvider provider) {
+        Preconditions.checkArgument(provider != null);
+        this.registration = provider.registerClusterSingletonService(this);
+    }
+
+    @Override
+    public void instantiateServiceInstance() {
+        // TODO : implement start project functionality
+
+    }
+
+    @Override
+    public ListenableFuture<Void> closeServiceInstance() {
+        // TODO : implement sync. or async. stop project functionality
+        return Futures.immediateFuture(null);
+    }
+
+    @Override
+    public String getServiceGroupIdentifier() {
+        return CLUSTER_SERVICE_GROUP_IDENTIFIER;
+    }
+
+    @Override
+    public void close() throws Exception {
+        if (registration != null) {
+            registration.close();
+            registration = null;
+        }
+    }
+
+}
+
+public class ApplicationModule extends ProjectAbstractModule<? extends AbstractStatisticsManagerModule> {
+
+    ...
+
+    @Override
+    public java.lang.AutoCloseable createInstance() {
+        AbstractServiceProvider projectProvider =
+            new ClusterSingletonProjectSample(getClusterSingletonServiceProviderDependency());
+        return projectProvider;
+    }
+}
+----
diff --git a/singleton-service/mdsal-singleton-common-api/src/site/site.xml b/singleton-service/mdsal-singleton-common-api/src/site/site.xml
new file mode 100644 (file)
index 0000000..9f08c27
--- /dev/null
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project name="mdsal">
+    <body>
+        <menu name="Overview">
+          <item name="Javadocs" href="apidocs/index.html" />
+        </menu>
+        <menu name="Cluster &amp; Wide &amp; Service &amp; Design">
+          <item name="Cluster Wide Service Design" href="cluster-wide-services.html" />
+        </menu>
+    </body>
+</project>
diff --git a/singleton-service/mdsal-singleton-dom-api/pom.xml b/singleton-service/mdsal-singleton-dom-api/pom.xml
new file mode 100644 (file)
index 0000000..70ad1a7
--- /dev/null
@@ -0,0 +1,73 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+  <parent>
+    <groupId>org.opendaylight.mdsal</groupId>
+    <artifactId>singleton-service</artifactId>
+    <version>2.1.0-SNAPSHOT</version>
+  </parent>
+
+  <artifactId>mdsal-singleton-dom-api</artifactId>
+  <packaging>bundle</packaging>
+
+  <dependencies>
+    <dependency>
+      <groupId>org.opendaylight.mdsal</groupId>
+      <artifactId>mdsal-eos-dom-api</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.opendaylight.mdsal</groupId>
+      <artifactId>mdsal-singleton-common-api</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>com.google.guava</groupId>
+      <artifactId>guava</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.opendaylight.yangtools</groupId>
+      <artifactId>concepts</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.opendaylight.yangtools</groupId>
+      <artifactId>util</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.opendaylight.yangtools</groupId>
+      <artifactId>yang-common</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.opendaylight.yangtools</groupId>
+      <artifactId>mockito-configuration</artifactId>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+
+  <scm>
+    <connection>scm:git:http://git.opendaylight.org/gerrit/mdsal.git</connection>
+    <developerConnection>scm:git:ssh://git.opendaylight.org:29418/mdsal.git</developerConnection>
+    <tag>HEAD</tag>
+    <url>https://wiki.opendaylight.org/view/MD-SAL:Main</url>
+  </scm>
+
+  <!--
+      Maven Site Configuration
+
+      The following configuration is necessary for maven-site-plugin to
+      correctly identify the correct deployment path for OpenDaylight Maven
+      sites.
+  -->
+  <url>${odl.site.url}/${project.groupId}/${stream}/${project.artifactId}/</url>
+
+  <distributionManagement>
+    <site>
+      <id>opendaylight-site</id>
+      <url>${nexus.site.url}/${project.artifactId}/</url>
+    </site>
+  </distributionManagement>
+
+</project>
diff --git a/singleton-service/mdsal-singleton-dom-api/src/main/java/org/opendaylight/mdsal/singleton/dom/api/DOMClusterSingletonServiceProvider.java b/singleton-service/mdsal-singleton-dom-api/src/main/java/org/opendaylight/mdsal/singleton/dom/api/DOMClusterSingletonServiceProvider.java
new file mode 100644 (file)
index 0000000..6539507
--- /dev/null
@@ -0,0 +1,21 @@
+/*
+ * Copyright (c) 2016 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.singleton.dom.api;
+
+import org.opendaylight.mdsal.eos.dom.api.DOMEntityOwnershipListener;
+import org.opendaylight.mdsal.singleton.common.api.CommonClusterSingletonServiceProvider;
+
+/**
+ * DOM version of {@link CommonClusterSingletonServiceProvider}
+ */
+public interface DOMClusterSingletonServiceProvider
+        extends CommonClusterSingletonServiceProvider, DOMEntityOwnershipListener {
+
+    // Marker interface for blueprint
+}
diff --git a/singleton-service/pom.xml b/singleton-service/pom.xml
new file mode 100644 (file)
index 0000000..65137d8
--- /dev/null
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright (c) 2016 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
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+    <parent>
+      <groupId>org.opendaylight.mdsal</groupId>
+      <artifactId>mdsal-parent</artifactId>
+      <version>2.1.0-SNAPSHOT</version>
+      <relativePath>../common/parent</relativePath>
+    </parent>
+
+    <modelVersion>4.0.0</modelVersion>
+    <artifactId>singleton-service</artifactId>
+    <version>2.1.0-SNAPSHOT</version>
+    <packaging>pom</packaging>
+    <name>${project.artifactId}</name> <!-- Used by Sonar to set project name -->
+    <description>${project.artifactId}</description>
+
+    <modules>
+      <module>mdsal-singleton-common-api</module>
+      <module>mdsal-singleton-dom-api</module>
+      <module>mdsal-singleton-binding-api</module>
+    </modules>
+
+
+  <!--
+      Maven Site Configuration
+
+      The following configuration is necessary for maven-site-plugin to
+      correctly identify the correct deployment path for OpenDaylight Maven
+      sites.
+  -->
+  <url>${odl.site.url}/${project.groupId}/${stream}/${project.artifactId}/</url>
+
+  <distributionManagement>
+    <site>
+      <id>opendaylight-site</id>
+      <url>${nexus.site.url}/${project.artifactId}/</url>
+    </site>
+  </distributionManagement>
+
+</project>