2 * Copyright (c) 2016 Cisco Systems, Inc. and others. All rights reserved.
4 * This program and the accompanying materials are made available under the
5 * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6 * and is available at http://www.eclipse.org/legal/epl-v10.html
8 package org.opendaylight.mdsal.singleton.dom.impl;
10 import static com.google.common.base.Preconditions.checkArgument;
11 import static com.google.common.base.Verify.verify;
12 import static com.google.common.base.Verify.verifyNotNull;
13 import static java.util.Objects.requireNonNull;
15 import com.google.common.annotations.VisibleForTesting;
16 import com.google.common.base.Strings;
17 import com.google.common.util.concurrent.FutureCallback;
18 import com.google.common.util.concurrent.Futures;
19 import com.google.common.util.concurrent.ListenableFuture;
20 import com.google.common.util.concurrent.MoreExecutors;
21 import java.util.ArrayList;
22 import java.util.List;
23 import java.util.concurrent.ConcurrentHashMap;
24 import java.util.concurrent.ConcurrentMap;
25 import org.eclipse.jdt.annotation.NonNull;
26 import org.opendaylight.mdsal.eos.common.api.CandidateAlreadyRegisteredException;
27 import org.opendaylight.mdsal.eos.common.api.EntityOwnershipStateChange;
28 import org.opendaylight.mdsal.eos.dom.api.DOMEntity;
29 import org.opendaylight.mdsal.eos.dom.api.DOMEntityOwnershipListener;
30 import org.opendaylight.mdsal.eos.dom.api.DOMEntityOwnershipService;
31 import org.opendaylight.mdsal.singleton.common.api.ClusterSingletonService;
32 import org.opendaylight.mdsal.singleton.common.api.ClusterSingletonServiceProvider;
33 import org.opendaylight.mdsal.singleton.common.api.ClusterSingletonServiceRegistration;
34 import org.opendaylight.yangtools.concepts.Registration;
35 import org.slf4j.Logger;
36 import org.slf4j.LoggerFactory;
39 * Abstract class {@link AbstractClusterSingletonServiceProviderImpl} represents implementations of
40 * {@link ClusterSingletonServiceProvider} and it implements {@link DOMEntityOwnershipListener} for providing
41 * OwnershipChange for all registered {@link ClusterSingletonServiceGroup} entity candidate.
43 public abstract class AbstractClusterSingletonServiceProviderImpl
44 implements ClusterSingletonServiceProvider, DOMEntityOwnershipListener {
45 private static final Logger LOG = LoggerFactory.getLogger(AbstractClusterSingletonServiceProviderImpl.class);
48 static final @NonNull String SERVICE_ENTITY_TYPE = "org.opendaylight.mdsal.ServiceEntityType";
50 static final @NonNull String CLOSE_SERVICE_ENTITY_TYPE = "org.opendaylight.mdsal.AsyncServiceCloseEntityType";
52 private final ConcurrentMap<String, ClusterSingletonServiceGroup> serviceGroupMap = new ConcurrentHashMap<>();
53 private final DOMEntityOwnershipService entityOwnershipService;
55 /* EOS Entity Listeners Registration */
56 private Registration serviceEntityListenerReg;
57 private Registration asyncCloseEntityListenerReg;
62 * @param entityOwnershipService relevant EOS
64 protected AbstractClusterSingletonServiceProviderImpl(
65 final @NonNull DOMEntityOwnershipService entityOwnershipService) {
66 this.entityOwnershipService = requireNonNull(entityOwnershipService);
70 * This method must be called once on startup to initialize this provider.
72 public final void initializeProvider() {
73 LOG.debug("Initialization method for ClusterSingletonService Provider {}", this);
74 serviceEntityListenerReg = registerListener(SERVICE_ENTITY_TYPE, entityOwnershipService);
75 asyncCloseEntityListenerReg = registerListener(CLOSE_SERVICE_ENTITY_TYPE, entityOwnershipService);
79 public final synchronized ClusterSingletonServiceRegistration registerClusterSingletonService(
80 final ClusterSingletonService service) {
81 LOG.debug("Call registrationService {} method for ClusterSingletonService Provider {}", service, this);
83 final String serviceIdentifier = service.getIdentifier().getName();
84 checkArgument(!Strings.isNullOrEmpty(serviceIdentifier),
85 "ClusterSingletonService identifier may not be null nor empty");
87 final ClusterSingletonServiceGroup serviceGroup;
88 final var existing = serviceGroupMap.get(serviceIdentifier);
89 if (existing == null) {
90 serviceGroup = createGroup(serviceIdentifier, new ArrayList<>(1));
91 serviceGroupMap.put(serviceIdentifier, serviceGroup);
94 initializeOrRemoveGroup(serviceGroup);
95 } catch (CandidateAlreadyRegisteredException e) {
96 throw new IllegalArgumentException("Service group already registered", e);
99 serviceGroup = existing;
102 final var reg = new AbstractClusterSingletonServiceRegistration(service) {
104 protected void removeRegistration() {
105 // We need to bounce the unregistration through a ordered lock in order not to deal with asynchronous
106 // shutdown of the group and user registering it again.
107 AbstractClusterSingletonServiceProviderImpl.this.removeRegistration(serviceIdentifier, this);
111 serviceGroup.registerService(reg);
115 private ClusterSingletonServiceGroup createGroup(final String serviceIdentifier,
116 final List<ClusterSingletonServiceRegistration> services) {
117 return new ClusterSingletonServiceGroupImpl(serviceIdentifier, entityOwnershipService,
118 createEntity(SERVICE_ENTITY_TYPE, serviceIdentifier),
119 createEntity(CLOSE_SERVICE_ENTITY_TYPE, serviceIdentifier), services);
122 private void initializeOrRemoveGroup(final ClusterSingletonServiceGroup group)
123 throws CandidateAlreadyRegisteredException {
126 } catch (CandidateAlreadyRegisteredException e) {
127 serviceGroupMap.remove(group.getIdentifier(), group);
132 void removeRegistration(final String serviceIdentifier, final ClusterSingletonServiceRegistration reg) {
133 final PlaceholderGroup placeHolder;
134 final ListenableFuture<?> future;
135 synchronized (this) {
136 final var lookup = verifyNotNull(serviceGroupMap.get(serviceIdentifier));
137 future = lookup.unregisterService(reg);
138 if (future == null) {
142 // Close the group and replace it with a placeholder
143 LOG.debug("Closing service group {}", serviceIdentifier);
144 placeHolder = new PlaceholderGroup(lookup, future);
146 final String identifier = reg.getInstance().getIdentifier().getName();
147 verify(serviceGroupMap.replace(identifier, lookup, placeHolder));
148 LOG.debug("Replaced group {} with {}", serviceIdentifier, placeHolder);
150 lookup.closeClusterSingletonGroup();
153 future.addListener(() -> finishShutdown(placeHolder), MoreExecutors.directExecutor());
156 synchronized void finishShutdown(final PlaceholderGroup placeHolder) {
157 final var identifier = placeHolder.getIdentifier();
158 LOG.debug("Service group {} closed", identifier);
160 final var services = placeHolder.getServices();
161 if (services.isEmpty()) {
162 // No services, we are all done
163 if (serviceGroupMap.remove(identifier, placeHolder)) {
164 LOG.debug("Service group {} removed", placeHolder);
166 LOG.debug("Service group {} superseded by {}", placeHolder, serviceGroupMap.get(identifier));
171 // Placeholder is being retired, we are reusing its services as the seed for the group.
172 final var group = createGroup(identifier, services);
173 verify(serviceGroupMap.replace(identifier, placeHolder, group));
174 placeHolder.setSuccessor(group);
175 LOG.debug("Service group upgraded from {} to {}", placeHolder, group);
178 initializeOrRemoveGroup(group);
179 } catch (CandidateAlreadyRegisteredException e) {
180 LOG.error("Failed to register delayed group {}, it will remain inoperational", identifier, e);
185 public final void close() {
186 LOG.debug("Close method for ClusterSingletonService Provider {}", this);
188 if (serviceEntityListenerReg != null) {
189 serviceEntityListenerReg.close();
190 serviceEntityListenerReg = null;
193 final var futures = serviceGroupMap.values().stream()
194 .map(ClusterSingletonServiceGroup::closeClusterSingletonGroup)
196 final var future = Futures.allAsList(futures);
197 Futures.addCallback(future, new FutureCallback<>() {
199 public void onSuccess(final List<Object> result) {
204 public void onFailure(final Throwable throwable) {
205 LOG.warn("Unexpected problem by closing ClusterSingletonServiceProvider {}",
206 AbstractClusterSingletonServiceProviderImpl.this, throwable);
209 }, MoreExecutors.directExecutor());
213 public final void ownershipChanged(final DOMEntity entity, final EntityOwnershipStateChange change,
214 final boolean inJeopardy) {
215 LOG.debug("Ownership change for ClusterSingletonService Provider on {} {} inJeopardy={}", entity, change,
217 final var serviceIdentifier = getServiceIdentifierFromEntity(entity);
218 final var serviceHolder = serviceGroupMap.get(serviceIdentifier);
219 if (serviceHolder != null) {
220 serviceHolder.ownershipChanged(entity, change, inJeopardy);
222 LOG.debug("ClusterSingletonServiceGroup was not found for serviceIdentifier {}", serviceIdentifier);
227 * Method implementation registers the listener.
229 * @param entityType the type of the entity
230 * @param eos - EOS type
231 * @return a {@link Registration}
233 protected abstract Registration registerListener(String entityType, DOMEntityOwnershipService eos);
236 * Creates an extended {@link DOMEntity} instance.
238 * @param entityType the type of the entity
239 * @param entityIdentifier the identifier of the entity
240 * @return instance of Entity extended GenericEntity type
243 static final DOMEntity createEntity(final String entityType, final String entityIdentifier) {
244 return new DOMEntity(entityType, entityIdentifier);
248 * Method is responsible for parsing ServiceGroupIdentifier from E entity.
250 * @param entity instance of GenericEntity type
251 * @return ServiceGroupIdentifier parsed from entity key value.
253 protected abstract String getServiceIdentifierFromEntity(DOMEntity entity);
256 * Method is called async. from close method in end of Provider lifecycle.
258 final void cleanup() {
259 LOG.debug("Final cleaning ClusterSingletonServiceProvider {}", this);
260 if (asyncCloseEntityListenerReg != null) {
261 asyncCloseEntityListenerReg.close();
262 asyncCloseEntityListenerReg = null;
264 serviceGroupMap.clear();