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.Futures;
18 import com.google.common.util.concurrent.ListenableFuture;
19 import com.google.common.util.concurrent.MoreExecutors;
20 import java.util.ArrayList;
21 import java.util.List;
22 import java.util.concurrent.ConcurrentHashMap;
23 import java.util.concurrent.ConcurrentMap;
24 import java.util.concurrent.ExecutionException;
25 import javax.annotation.PreDestroy;
26 import javax.inject.Inject;
27 import javax.inject.Singleton;
28 import org.eclipse.jdt.annotation.NonNull;
29 import org.opendaylight.mdsal.eos.common.api.CandidateAlreadyRegisteredException;
30 import org.opendaylight.mdsal.eos.common.api.EntityOwnershipStateChange;
31 import org.opendaylight.mdsal.eos.dom.api.DOMEntity;
32 import org.opendaylight.mdsal.eos.dom.api.DOMEntityOwnershipListener;
33 import org.opendaylight.mdsal.eos.dom.api.DOMEntityOwnershipService;
34 import org.opendaylight.mdsal.singleton.common.api.ClusterSingletonService;
35 import org.opendaylight.mdsal.singleton.common.api.ClusterSingletonServiceProvider;
36 import org.opendaylight.yangtools.concepts.Registration;
37 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
38 import org.osgi.service.component.annotations.Activate;
39 import org.osgi.service.component.annotations.Component;
40 import org.osgi.service.component.annotations.Deactivate;
41 import org.osgi.service.component.annotations.Reference;
42 import org.slf4j.Logger;
43 import org.slf4j.LoggerFactory;
46 * Implementation of {@link ClusterSingletonServiceProvider} working on top a {@link DOMEntityOwnershipService}.
49 @Component(service = ClusterSingletonServiceProvider.class)
50 public final class EOSClusterSingletonServiceProvider
51 implements ClusterSingletonServiceProvider, DOMEntityOwnershipListener, AutoCloseable {
52 private static final Logger LOG = LoggerFactory.getLogger(EOSClusterSingletonServiceProvider.class);
55 static final @NonNull String SERVICE_ENTITY_TYPE = "org.opendaylight.mdsal.ServiceEntityType";
57 static final @NonNull String CLOSE_SERVICE_ENTITY_TYPE = "org.opendaylight.mdsal.AsyncServiceCloseEntityType";
59 private final ConcurrentMap<String, ServiceGroup> serviceGroupMap = new ConcurrentHashMap<>();
60 private final DOMEntityOwnershipService entityOwnershipService;
62 /* EOS Entity Listeners Registration */
63 private Registration serviceEntityListenerReg;
64 private Registration asyncCloseEntityListenerReg;
68 public EOSClusterSingletonServiceProvider(@Reference final DOMEntityOwnershipService entityOwnershipService) {
69 this.entityOwnershipService = requireNonNull(entityOwnershipService);
70 serviceEntityListenerReg = entityOwnershipService.registerListener(SERVICE_ENTITY_TYPE, this);
71 asyncCloseEntityListenerReg = entityOwnershipService.registerListener(CLOSE_SERVICE_ENTITY_TYPE, this);
72 LOG.info("Cluster Singleton Service started");
78 public synchronized void close() throws ExecutionException, InterruptedException {
79 if (serviceEntityListenerReg == null) {
84 LOG.info("Cluster Singleton Service stopping");
85 serviceEntityListenerReg.close();
86 serviceEntityListenerReg = null;
88 final var future = Futures.allAsList(serviceGroupMap.values().stream()
89 .map(ServiceGroup::closeClusterSingletonGroup)
92 LOG.debug("Waiting for service groups to stop");
95 asyncCloseEntityListenerReg.close();
96 asyncCloseEntityListenerReg = null;
97 serviceGroupMap.clear();
100 LOG.info("Cluster Singleton Service stopped");
104 public synchronized Registration registerClusterSingletonService(final ClusterSingletonService service) {
105 LOG.debug("Call registrationService {} method for ClusterSingletonService Provider {}", service, this);
107 final String serviceIdentifier = service.getIdentifier().getName();
108 checkArgument(!Strings.isNullOrEmpty(serviceIdentifier),
109 "ClusterSingletonService identifier may not be null nor empty");
111 final ServiceGroup serviceGroup;
112 final var existing = serviceGroupMap.get(serviceIdentifier);
113 if (existing == null) {
114 serviceGroup = createGroup(serviceIdentifier, new ArrayList<>(1));
115 serviceGroupMap.put(serviceIdentifier, serviceGroup);
118 initializeOrRemoveGroup(serviceGroup);
119 } catch (CandidateAlreadyRegisteredException e) {
120 throw new IllegalArgumentException("Service group already registered", e);
123 serviceGroup = existing;
126 final var reg = new ServiceRegistration(service) {
128 protected void removeRegistration() {
129 // We need to bounce the unregistration through a ordered lock in order not to deal with asynchronous
130 // shutdown of the group and user registering it again.
131 EOSClusterSingletonServiceProvider.this.removeRegistration(serviceIdentifier, this);
135 serviceGroup.registerService(reg);
139 private ServiceGroup createGroup(final String serviceIdentifier, final List<ServiceRegistration> services) {
140 return new ActiveServiceGroup(serviceIdentifier, entityOwnershipService,
141 createEntity(SERVICE_ENTITY_TYPE, serviceIdentifier),
142 createEntity(CLOSE_SERVICE_ENTITY_TYPE, serviceIdentifier), services);
145 private void initializeOrRemoveGroup(final ServiceGroup group) throws CandidateAlreadyRegisteredException {
148 } catch (CandidateAlreadyRegisteredException e) {
149 serviceGroupMap.remove(group.getIdentifier(), group);
154 private void removeRegistration(final String serviceIdentifier, final ServiceRegistration reg) {
155 final PlaceholderServiceGroup placeHolder;
156 final ListenableFuture<?> future;
157 synchronized (this) {
158 final var lookup = verifyNotNull(serviceGroupMap.get(serviceIdentifier));
159 future = lookup.unregisterService(reg);
160 if (future == null) {
164 // Close the group and replace it with a placeholder
165 LOG.debug("Closing service group {}", serviceIdentifier);
166 placeHolder = new PlaceholderServiceGroup(lookup, future);
168 final var identifier = reg.getInstance().getIdentifier().getName();
169 verify(serviceGroupMap.replace(identifier, lookup, placeHolder));
170 LOG.debug("Replaced group {} with {}", serviceIdentifier, placeHolder);
172 lookup.closeClusterSingletonGroup();
175 future.addListener(() -> finishShutdown(placeHolder), MoreExecutors.directExecutor());
178 private synchronized void finishShutdown(final PlaceholderServiceGroup placeHolder) {
179 final var identifier = placeHolder.getIdentifier();
180 LOG.debug("Service group {} closed", identifier);
182 final var services = placeHolder.getServices();
183 if (services.isEmpty()) {
184 // No services, we are all done
185 if (serviceGroupMap.remove(identifier, placeHolder)) {
186 LOG.debug("Service group {} removed", placeHolder);
188 LOG.debug("Service group {} superseded by {}", placeHolder, serviceGroupMap.get(identifier));
193 // Placeholder is being retired, we are reusing its services as the seed for the group.
194 final var group = createGroup(identifier, services);
195 verify(serviceGroupMap.replace(identifier, placeHolder, group));
196 placeHolder.setSuccessor(group);
197 LOG.debug("Service group upgraded from {} to {}", placeHolder, group);
200 initializeOrRemoveGroup(group);
201 } catch (CandidateAlreadyRegisteredException e) {
202 LOG.error("Failed to register delayed group {}, it will remain inoperational", identifier, e);
207 public void ownershipChanged(final DOMEntity entity, final EntityOwnershipStateChange change,
208 final boolean inJeopardy) {
209 LOG.debug("Ownership change for ClusterSingletonService Provider on {} {} inJeopardy={}", entity, change,
212 final var serviceIdentifier = getServiceIdentifierFromEntity(entity);
213 final var serviceHolder = serviceGroupMap.get(serviceIdentifier);
214 if (serviceHolder != null) {
215 serviceHolder.ownershipChanged(entity, change, inJeopardy);
217 LOG.debug("ClusterSingletonServiceGroup was not found for serviceIdentifier {}", serviceIdentifier);
222 static String getServiceIdentifierFromEntity(final DOMEntity entity) {
223 final var yii = entity.getIdentifier();
224 final var niiwp = (NodeIdentifierWithPredicates) yii.getLastPathArgument();
225 return niiwp.values().iterator().next().toString();
229 * Creates an extended {@link DOMEntity} instance.
231 * @param entityType the type of the entity
232 * @param entityIdentifier the identifier of the entity
233 * @return instance of Entity extended GenericEntity type
236 static DOMEntity createEntity(final String entityType, final String entityIdentifier) {
237 return new DOMEntity(entityType, entityIdentifier);