/* * Copyright 2016-present Open Networking Laboratory * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.onosproject.store.region.impl; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Sets; import org.apache.felix.scr.annotations.Activate; import org.apache.felix.scr.annotations.Component; import org.apache.felix.scr.annotations.Deactivate; import org.apache.felix.scr.annotations.Reference; import org.apache.felix.scr.annotations.ReferenceCardinality; import org.apache.felix.scr.annotations.Service; import org.onlab.util.Identifier; import org.onosproject.cluster.NodeId; import org.onosproject.net.Annotations; import org.onosproject.net.DeviceId; import org.onosproject.net.region.DefaultRegion; import org.onosproject.net.region.Region; import org.onosproject.net.region.RegionEvent; import org.onosproject.net.region.RegionId; import org.onosproject.net.region.RegionStore; import org.onosproject.net.region.RegionStoreDelegate; import org.onosproject.store.AbstractStore; import org.onosproject.store.serializers.KryoNamespaces; import org.onosproject.store.service.ConsistentMap; import org.onosproject.store.service.MapEvent; import org.onosproject.store.service.MapEventListener; import org.onosproject.store.service.Serializer; import org.onosproject.store.service.StorageService; import org.slf4j.Logger; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; import static org.onlab.util.Tools.nullIsNotFound; import static org.onosproject.net.region.RegionEvent.Type.REGION_MEMBERSHIP_CHANGED; import static org.slf4j.LoggerFactory.getLogger; /** * Consistent store implementation for tracking region definitions and device * region affiliation. */ @Component(immediate = true) @Service public class DistributedRegionStore extends AbstractStore<RegionEvent, RegionStoreDelegate> implements RegionStore { private static final String NO_REGION = "Region does not exist"; private static final String DUPLICATE_REGION = "Region already exists"; private final Logger log = getLogger(getClass()); @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) protected StorageService storageService; private ConsistentMap<RegionId, Region> regionsRepo; private Map<RegionId, Region> regionsById; private ConsistentMap<RegionId, Set<DeviceId>> membershipRepo; private Map<RegionId, Set<DeviceId>> regionDevices; private Map<DeviceId, Region> regionsByDevice = new HashMap<>(); private final MapEventListener<RegionId, Region> listener = new InternalRegionListener(); private final MapEventListener<RegionId, Set<DeviceId>> membershipListener = new InternalMembershipListener(); @Activate protected void activate() { Serializer serializer = Serializer.using(Arrays.asList(KryoNamespaces.API), Identifier.class); regionsRepo = storageService.<RegionId, Region>consistentMapBuilder() .withSerializer(serializer) .withName("onos-regions") .withRelaxedReadConsistency() .build(); regionsRepo.addListener(listener); regionsById = regionsRepo.asJavaMap(); membershipRepo = storageService.<RegionId, Set<DeviceId>>consistentMapBuilder() .withSerializer(serializer) .withName("onos-region-devices") .withRelaxedReadConsistency() .build(); membershipRepo.addListener(membershipListener); regionDevices = membershipRepo.asJavaMap(); log.info("Started"); } @Deactivate protected void deactivate() { regionsRepo.removeListener(listener); membershipRepo.removeListener(membershipListener); regionsByDevice.clear(); log.info("Stopped"); } @Override public Set<Region> getRegions() { return ImmutableSet.copyOf(regionsById.values()); } @Override public Region getRegion(RegionId regionId) { return nullIsNotFound(regionsById.get(regionId), NO_REGION); } @Override public Region getRegionForDevice(DeviceId deviceId) { return regionsByDevice.get(deviceId); } @Override public Set<DeviceId> getRegionDevices(RegionId regionId) { Set<DeviceId> deviceIds = regionDevices.get(regionId); return deviceIds != null ? ImmutableSet.copyOf(deviceIds) : ImmutableSet.of(); } @Override public Region createRegion(RegionId regionId, String name, Region.Type type, Annotations annots, List<Set<NodeId>> masterNodeIds) { return regionsRepo.compute(regionId, (id, region) -> { checkArgument(region == null, DUPLICATE_REGION); return new DefaultRegion(regionId, name, type, annots, masterNodeIds); }).value(); } @Override public Region updateRegion(RegionId regionId, String name, Region.Type type, Annotations annots, List<Set<NodeId>> masterNodeIds) { return regionsRepo.compute(regionId, (id, region) -> { nullIsNotFound(region, NO_REGION); return new DefaultRegion(regionId, name, type, annots, masterNodeIds); }).value(); } @Override public void removeRegion(RegionId regionId) { membershipRepo.remove(regionId); regionsRepo.remove(regionId); } @Override public void addDevices(RegionId regionId, Collection<DeviceId> deviceIds) { // Devices can only be a member in one region. Remove the device if it // belongs to a different region than the region for which we are // attempting to add it. for (DeviceId deviceId : deviceIds) { Region region = getRegionForDevice(deviceId); if ((region != null) && (!regionId.id().equals(region.id().id()))) { Set<DeviceId> deviceIdSet1 = ImmutableSet.of(deviceId); removeDevices(region.id(), deviceIdSet1); } } membershipRepo.compute(regionId, (id, existingDevices) -> { if (existingDevices == null) { return ImmutableSet.copyOf(deviceIds); } else if (!existingDevices.containsAll(deviceIds)) { return ImmutableSet.<DeviceId>builder() .addAll(existingDevices) .addAll(deviceIds) .build(); } else { return existingDevices; } }); Region region = regionsById.get(regionId); deviceIds.forEach(deviceId -> regionsByDevice.put(deviceId, region)); } @Override public void removeDevices(RegionId regionId, Collection<DeviceId> deviceIds) { membershipRepo.compute(regionId, (id, existingDevices) -> { if (existingDevices == null || existingDevices.isEmpty()) { return ImmutableSet.of(); } else { return ImmutableSet.<DeviceId>builder() .addAll(Sets.difference(existingDevices, ImmutableSet.copyOf(deviceIds))) .build(); } }); deviceIds.forEach(deviceId -> regionsByDevice.remove(deviceId)); } /** * Listener class to map listener events to the region inventory events. */ private class InternalRegionListener implements MapEventListener<RegionId, Region> { @Override public void event(MapEvent<RegionId, Region> event) { Region region = null; RegionEvent.Type type = null; switch (event.type()) { case INSERT: type = RegionEvent.Type.REGION_ADDED; region = checkNotNull(event.newValue().value()); break; case UPDATE: type = RegionEvent.Type.REGION_UPDATED; region = checkNotNull(event.newValue().value()); break; case REMOVE: type = RegionEvent.Type.REGION_REMOVED; region = checkNotNull(event.oldValue().value()); break; default: log.error("Unsupported event type: " + event.type()); } notifyDelegate(new RegionEvent(type, region)); } } /** * Listener class to map listener events to the region membership events. */ private class InternalMembershipListener implements MapEventListener<RegionId, Set<DeviceId>> { @Override public void event(MapEvent<RegionId, Set<DeviceId>> event) { if (event.type() != MapEvent.Type.REMOVE) { Region r = regionsById.get(event.key()); if (r != null) { notifyDelegate(new RegionEvent(REGION_MEMBERSHIP_CHANGED, r, event.newValue().value())); } } } } }