diff options
Diffstat (limited to 'framework/src/onos/core/net/src/main/java/org/onosproject/net/device/impl/DeviceManager.java')
-rw-r--r-- | framework/src/onos/core/net/src/main/java/org/onosproject/net/device/impl/DeviceManager.java | 765 |
1 files changed, 765 insertions, 0 deletions
diff --git a/framework/src/onos/core/net/src/main/java/org/onosproject/net/device/impl/DeviceManager.java b/framework/src/onos/core/net/src/main/java/org/onosproject/net/device/impl/DeviceManager.java new file mode 100644 index 00000000..b0b3abe2 --- /dev/null +++ b/framework/src/onos/core/net/src/main/java/org/onosproject/net/device/impl/DeviceManager.java @@ -0,0 +1,765 @@ +/* + * Copyright 2014-2015 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.net.device.impl; + +import com.google.common.collect.Lists; +import com.google.common.util.concurrent.Futures; + +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.onosproject.cluster.ClusterService; +import org.onosproject.cluster.NodeId; +import org.onosproject.net.provider.AbstractListenerProviderRegistry; +import org.onosproject.net.config.NetworkConfigEvent; +import org.onosproject.net.config.NetworkConfigListener; +import org.onosproject.net.config.NetworkConfigService; +import org.onosproject.net.config.basics.BasicDeviceConfig; +import org.onosproject.net.config.basics.OpticalPortConfig; +import org.onosproject.mastership.MastershipEvent; +import org.onosproject.mastership.MastershipListener; +import org.onosproject.mastership.MastershipService; +import org.onosproject.mastership.MastershipTerm; +import org.onosproject.mastership.MastershipTermService; +import org.onosproject.net.ConnectPoint; +import org.onosproject.net.Device; +import org.onosproject.net.Device.Type; +import org.onosproject.net.DeviceId; +import org.onosproject.net.MastershipRole; +import org.onosproject.net.Port; +import org.onosproject.net.PortNumber; +import org.onosproject.net.device.DefaultDeviceDescription; +import org.onosproject.net.device.DefaultPortDescription; +import org.onosproject.net.device.DeviceAdminService; +import org.onosproject.net.device.DeviceDescription; +import org.onosproject.net.device.DeviceEvent; +import org.onosproject.net.device.DeviceListener; +import org.onosproject.net.device.DeviceProvider; +import org.onosproject.net.device.DeviceProviderRegistry; +import org.onosproject.net.device.DeviceProviderService; +import org.onosproject.net.device.DeviceService; +import org.onosproject.net.device.DeviceStore; +import org.onosproject.net.device.DeviceStoreDelegate; +import org.onosproject.net.device.PortDescription; +import org.onosproject.net.device.PortStatistics; +import org.onosproject.net.provider.AbstractProviderService; +import org.slf4j.Logger; + +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +import static com.google.common.base.Preconditions.checkNotNull; +import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor; +import static org.onlab.util.Tools.groupedThreads; +import static org.onosproject.net.MastershipRole.*; +import static org.onosproject.security.AppGuard.checkPermission; +import static org.slf4j.LoggerFactory.getLogger; +import static org.onosproject.security.AppPermission.Type.*; + +/** + * Provides implementation of the device SB & NB APIs. + */ +@Component(immediate = true) +@Service +public class DeviceManager + extends AbstractListenerProviderRegistry<DeviceEvent, DeviceListener, DeviceProvider, DeviceProviderService> + implements DeviceService, DeviceAdminService, DeviceProviderRegistry { + + private static final String DEVICE_ID_NULL = "Device ID cannot be null"; + private static final String PORT_NUMBER_NULL = "Port number cannot be null"; + private static final String DEVICE_DESCRIPTION_NULL = "Device description cannot be null"; + private static final String PORT_DESCRIPTION_NULL = "Port description cannot be null"; + private static final String PORT_DESC_LIST_NULL = "Port description list cannot be null"; + + private final Logger log = getLogger(getClass()); + + private final DeviceStoreDelegate delegate = new InternalStoreDelegate(); + + private final MastershipListener mastershipListener = new InternalMastershipListener(); + private NodeId localNodeId; + + private ScheduledExecutorService backgroundService; + + private final NetworkConfigListener networkConfigListener = new InternalNetworkConfigListener(); + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected DeviceStore store; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected ClusterService clusterService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected MastershipService mastershipService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected MastershipTermService termService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected NetworkConfigService networkConfigService; + + @Activate + public void activate() { + backgroundService = newSingleThreadScheduledExecutor(groupedThreads("onos/device", "manager-background")); + localNodeId = clusterService.getLocalNode().id(); + + store.setDelegate(delegate); + eventDispatcher.addSink(DeviceEvent.class, listenerRegistry); + mastershipService.addListener(mastershipListener); + networkConfigService.addListener(networkConfigListener); + + backgroundService.scheduleWithFixedDelay(() -> { + try { + mastershipCheck(); + } catch (Exception e) { + log.error("Exception thrown during integrity check", e); + } + }, 1, 1, TimeUnit.MINUTES); + log.info("Started"); + } + + @Deactivate + public void deactivate() { + backgroundService.shutdown(); + networkConfigService.removeListener(networkConfigListener); + store.unsetDelegate(delegate); + mastershipService.removeListener(mastershipListener); + eventDispatcher.removeSink(DeviceEvent.class); + log.info("Stopped"); + } + + @Override + public int getDeviceCount() { + checkPermission(DEVICE_READ); + return store.getDeviceCount(); + } + + @Override + public Iterable<Device> getDevices() { + checkPermission(DEVICE_READ); + return store.getDevices(); + } + + @Override + public Iterable<Device> getAvailableDevices() { + checkPermission(DEVICE_READ); + return store.getAvailableDevices(); + } + + @Override + public Device getDevice(DeviceId deviceId) { + checkPermission(DEVICE_READ); + checkNotNull(deviceId, DEVICE_ID_NULL); + return store.getDevice(deviceId); + } + + @Override + public MastershipRole getRole(DeviceId deviceId) { + checkPermission(DEVICE_READ); + checkNotNull(deviceId, DEVICE_ID_NULL); + return mastershipService.getLocalRole(deviceId); + } + + @Override + public List<Port> getPorts(DeviceId deviceId) { + checkPermission(DEVICE_READ); + checkNotNull(deviceId, DEVICE_ID_NULL); + return store.getPorts(deviceId); + } + + @Override + public List<PortStatistics> getPortStatistics(DeviceId deviceId) { + checkPermission(DEVICE_READ); + checkNotNull(deviceId, DEVICE_ID_NULL); + return store.getPortStatistics(deviceId); + } + + @Override + public List<PortStatistics> getPortDeltaStatistics(DeviceId deviceId) { + checkPermission(DEVICE_READ); + checkNotNull(deviceId, DEVICE_ID_NULL); + return store.getPortDeltaStatistics(deviceId); + } + + @Override + public Port getPort(DeviceId deviceId, PortNumber portNumber) { + checkPermission(DEVICE_READ); + checkNotNull(deviceId, DEVICE_ID_NULL); + checkNotNull(portNumber, PORT_NUMBER_NULL); + return store.getPort(deviceId, portNumber); + } + + @Override + public boolean isAvailable(DeviceId deviceId) { + checkPermission(DEVICE_READ); + + checkNotNull(deviceId, DEVICE_ID_NULL); + return store.isAvailable(deviceId); + } + + // Check a device for control channel connectivity. + private boolean isReachable(DeviceId deviceId) { + if (deviceId == null) { + return false; + } + DeviceProvider provider = getProvider(deviceId); + if (provider != null) { + return provider.isReachable(deviceId); + } else { + log.debug("Provider not found for {}", deviceId); + return false; + } + } + + @Override + public void removeDevice(DeviceId deviceId) { + checkNotNull(deviceId, DEVICE_ID_NULL); + DeviceEvent event = store.removeDevice(deviceId); + if (event != null) { + log.info("Device {} administratively removed", deviceId); + post(event); + } + } + + @Override + protected DeviceProviderService createProviderService( + DeviceProvider provider) { + return new InternalDeviceProviderService(provider); + } + + /** + * Checks if all the reachable devices have a valid mastership role. + */ + private void mastershipCheck() { + log.debug("Checking mastership"); + for (Device device : getDevices()) { + final DeviceId deviceId = device.id(); + log.trace("Checking device {}", deviceId); + + if (!isReachable(deviceId)) { + continue; + } + + if (mastershipService.getLocalRole(deviceId) != NONE) { + continue; + } + + log.info("{} is reachable but did not have a valid role, reasserting", deviceId); + + // isReachable but was not MASTER or STANDBY, get a role and apply + // Note: NONE triggers request to MastershipService + reassertRole(deviceId, NONE); + } + } + + // Personalized device provider service issued to the supplied provider. + private class InternalDeviceProviderService + extends AbstractProviderService<DeviceProvider> + implements DeviceProviderService { + + InternalDeviceProviderService(DeviceProvider provider) { + super(provider); + } + + /** + * Apply role in reaction to provider event. + * + * @param deviceId device identifier + * @param newRole new role to apply to the device + * @return true if the request was sent to provider + */ + private boolean applyRole(DeviceId deviceId, MastershipRole newRole) { + + if (newRole.equals(MastershipRole.NONE)) { + //no-op + return true; + } + + DeviceProvider provider = provider(); + if (provider == null) { + log.warn("Provider for {} was not found. Cannot apply role {}", + deviceId, newRole); + return false; + } + provider.roleChanged(deviceId, newRole); + // not triggering probe when triggered by provider service event + + return true; + } + + @Override + public void deviceConnected(DeviceId deviceId, + DeviceDescription deviceDescription) { + checkNotNull(deviceId, DEVICE_ID_NULL); + checkNotNull(deviceDescription, DEVICE_DESCRIPTION_NULL); + checkValidity(); + + BasicDeviceConfig cfg = networkConfigService.getConfig(deviceId, BasicDeviceConfig.class); + if (!isAllowed(cfg)) { + log.warn("Device {} is not allowed", deviceId); + return; + } + // Generate updated description and establish my Role + deviceDescription = BasicDeviceOperator.combine(cfg, deviceDescription); + Futures.getUnchecked(mastershipService.requestRoleFor(deviceId) + .thenAccept(role -> { + log.info("Local role is {} for {}", role, deviceId); + applyRole(deviceId, role); + })); + + DeviceEvent event = store.createOrUpdateDevice(provider().id(), deviceId, + deviceDescription); + log.info("Device {} connected", deviceId); + if (event != null) { + log.trace("event: {} {}", event.type(), event); + post(event); + } + } + + @Override + public void deviceDisconnected(DeviceId deviceId) { + checkNotNull(deviceId, DEVICE_ID_NULL); + checkValidity(); + + log.info("Device {} disconnected from this node", deviceId); + + List<Port> ports = store.getPorts(deviceId); + List<PortDescription> descs = Lists.newArrayList(); + ports.forEach(port -> + descs.add(new DefaultPortDescription(port.number(), + false, port.type(), + port.portSpeed()))); + store.updatePorts(this.provider().id(), deviceId, descs); + try { + if (mastershipService.isLocalMaster(deviceId)) { + post(store.markOffline(deviceId)); + } + } catch (IllegalStateException e) { + log.warn("Failed to mark {} offline", deviceId); + // only the MASTER should be marking off-line in normal cases, + // but if I was the last STANDBY connection, etc. and no one else + // was there to mark the device offline, this instance may need to + // temporarily request for Master Role and mark offline. + + //there are times when this node will correctly have mastership, BUT + //that isn't reflected in the ClockManager before the device disconnects. + //we want to let go of the device anyways, so make sure this happens. + + // FIXME: Store semantics leaking out as IllegalStateException. + // Consider revising store API to handle this scenario. + CompletableFuture<MastershipRole> roleFuture = mastershipService.requestRoleFor(deviceId); + roleFuture.whenComplete((role, error) -> { + MastershipTerm term = termService.getMastershipTerm(deviceId); + // TODO: Move this type of check inside device clock manager, etc. + if (term != null && localNodeId.equals(term.master())) { + log.info("Retry marking {} offline", deviceId); + post(store.markOffline(deviceId)); + } else { + log.info("Failed again marking {} offline. {}", deviceId, role); + } + }); + } finally { + try { + //relinquish master role and ability to be backup. + mastershipService.relinquishMastership(deviceId).get(); + } catch (InterruptedException e) { + log.warn("Interrupted while reliquishing role for {}", deviceId); + Thread.currentThread().interrupt(); + } catch (ExecutionException e) { + log.error("Exception thrown while relinquishing role for {}", deviceId, e); + } + } + } + + @Override + public void updatePorts(DeviceId deviceId, + List<PortDescription> portDescriptions) { + checkNotNull(deviceId, DEVICE_ID_NULL); + checkNotNull(portDescriptions, PORT_DESC_LIST_NULL); + checkValidity(); + if (!mastershipService.isLocalMaster(deviceId)) { + // Never been a master for this device + // any update will be ignored. + log.trace("Ignoring {} port updates on standby node. {}", deviceId, portDescriptions); + return; + } + portDescriptions = portDescriptions.stream() + .map(e -> consolidate(deviceId, e)) + .collect(Collectors.toList()); + List<DeviceEvent> events = store.updatePorts(this.provider().id(), + deviceId, portDescriptions); + for (DeviceEvent event : events) { + post(event); + } + } + + @Override + public void portStatusChanged(DeviceId deviceId, + PortDescription portDescription) { + checkNotNull(deviceId, DEVICE_ID_NULL); + checkNotNull(portDescription, PORT_DESCRIPTION_NULL); + checkValidity(); + + if (!mastershipService.isLocalMaster(deviceId)) { + // Never been a master for this device + // any update will be ignored. + log.trace("Ignoring {} port update on standby node. {}", deviceId, + portDescription); + return; + } + portDescription = consolidate(deviceId, portDescription); + final DeviceEvent event = store.updatePortStatus(this.provider().id(), + deviceId, portDescription); + if (event != null) { + log.info("Device {} port {} status changed", deviceId, event.port().number()); + post(event); + } + } + + // merges the appropriate PortConfig with the description. + private PortDescription consolidate(DeviceId did, PortDescription desc) { + switch (desc.type()) { + case COPPER: + case VIRTUAL: + return desc; + default: + OpticalPortConfig opc = networkConfigService.getConfig( + new ConnectPoint(did, desc.portNumber()), OpticalPortConfig.class); + return OpticalPortOperator.combine(opc, desc); + } + } + + @Override + public void receivedRoleReply(DeviceId deviceId, MastershipRole requested, + MastershipRole response) { + // Several things can happen here: + // 1. request and response match + // 2. request and response don't match + // 3. MastershipRole and requested match (and 1 or 2 are true) + // 4. MastershipRole and requested don't match (and 1 or 2 are true) + // + // 2, 4, and 3 with case 2 are failure modes. + + // FIXME: implement response to this notification + + log.debug("got reply to a role request for {}: asked for {}, and got {}", + deviceId, requested, response); + + if (requested == null && response == null) { + // something was off with DeviceProvider, maybe check channel too? + log.warn("Failed to assert role [{}] onto Device {}", requested, deviceId); + mastershipService.relinquishMastership(deviceId); + return; + } + + if (Objects.equals(requested, response)) { + if (Objects.equals(requested, mastershipService.getLocalRole(deviceId))) { + return; + } else { + return; + // FIXME roleManager got the device to comply, but doesn't agree with + // the store; use the store's view, then try to reassert. + } + } else { + // we didn't get back what we asked for. Reelect someone else. + log.warn("Failed to assert role [{}] onto Device {}", response, deviceId); + if (response == MastershipRole.MASTER) { + mastershipService.relinquishMastership(deviceId); + // TODO: Shouldn't we be triggering event? + //final Device device = getDevice(deviceId); + //post(new DeviceEvent(DEVICE_MASTERSHIP_CHANGED, device)); + } + } + } + + @Override + public void updatePortStatistics(DeviceId deviceId, Collection<PortStatistics> portStatistics) { + checkNotNull(deviceId, DEVICE_ID_NULL); + checkNotNull(portStatistics, "Port statistics list cannot be null"); + checkValidity(); + + DeviceEvent event = store.updatePortStatistics(this.provider().id(), + deviceId, portStatistics); + post(event); + } + } + + // by default allowed, otherwise check flag + private boolean isAllowed(BasicDeviceConfig cfg) { + return (cfg == null || cfg.isAllowed()); + } + + // Applies the specified role to the device; ignores NONE + + /** + * Apply role to device and send probe if MASTER. + * + * @param deviceId device identifier + * @param newRole new role to apply to the device + * @return true if the request was sent to provider + */ + private boolean applyRoleAndProbe(DeviceId deviceId, MastershipRole newRole) { + if (newRole.equals(MastershipRole.NONE)) { + //no-op + return true; + } + + DeviceProvider provider = getProvider(deviceId); + if (provider == null) { + log.warn("Provider for {} was not found. Cannot apply role {}", deviceId, newRole); + return false; + } + provider.roleChanged(deviceId, newRole); + + if (newRole.equals(MastershipRole.MASTER)) { + // only trigger event when request was sent to provider + provider.triggerProbe(deviceId); + } + return true; + } + + /** + * Reaasert role for specified device connected to this node. + * + * @param did device identifier + * @param nextRole role to apply. If NONE is specified, + * it will ask mastership service for a role and apply it. + */ + private void reassertRole(final DeviceId did, + final MastershipRole nextRole) { + + MastershipRole myNextRole = nextRole; + if (myNextRole == NONE) { + mastershipService.requestRoleFor(did); + MastershipTerm term = termService.getMastershipTerm(did); + if (term != null && localNodeId.equals(term.master())) { + myNextRole = MASTER; + } else { + myNextRole = STANDBY; + } + } + + switch (myNextRole) { + case MASTER: + final Device device = getDevice(did); + if ((device != null) && !isAvailable(did)) { + //flag the device as online. Is there a better way to do this? + DefaultDeviceDescription deviceDescription + = new DefaultDeviceDescription(did.uri(), + device.type(), + device.manufacturer(), + device.hwVersion(), + device.swVersion(), + device.serialNumber(), + device.chassisId()); + DeviceEvent devEvent = + store.createOrUpdateDevice(device.providerId(), did, + deviceDescription); + post(devEvent); + } + // TODO: should apply role only if there is mismatch + log.debug("Applying role {} to {}", myNextRole, did); + if (!applyRoleAndProbe(did, MASTER)) { + log.warn("Unsuccessful applying role {} to {}", myNextRole, did); + // immediately failed to apply role + mastershipService.relinquishMastership(did); + // FIXME disconnect? + } + break; + case STANDBY: + log.debug("Applying role {} to {}", myNextRole, did); + if (!applyRoleAndProbe(did, STANDBY)) { + log.warn("Unsuccessful applying role {} to {}", myNextRole, did); + // immediately failed to apply role + mastershipService.relinquishMastership(did); + // FIXME disconnect? + } + break; + case NONE: + default: + // should never reach here + log.error("You didn't see anything. I did not exist."); + break; + } + } + + private void handleMastershipEvent(MastershipEvent event) { + if (event.type() != MastershipEvent.Type.MASTER_CHANGED) { + // Don't care if backup list changed. + return; + } + + final DeviceId did = event.subject(); + + // myRole suggested by MastershipService + MastershipRole myNextRole; + if (localNodeId.equals(event.roleInfo().master())) { + // confirm latest info + MastershipTerm term = termService.getMastershipTerm(did); + final boolean iHaveControl = term != null && localNodeId.equals(term.master()); + if (iHaveControl) { + myNextRole = MASTER; + } else { + myNextRole = STANDBY; + } + } else if (event.roleInfo().backups().contains(localNodeId)) { + myNextRole = STANDBY; + } else { + myNextRole = NONE; + } + + final boolean isReachable = isReachable(did); + if (!isReachable) { + // device is not connected to this node + if (myNextRole != NONE) { + log.warn("Node was instructed to be {} role for {}, " + + "but this node cannot reach the device. " + + "Relinquishing role. ", + myNextRole, did); + mastershipService.relinquishMastership(did); + } + return; + } + + // device is connected to this node: + if (store.getDevice(did) != null) { + reassertRole(did, myNextRole); + } else { + log.debug("Device is not yet/no longer in the store: {}", did); + } + } + + // Intercepts mastership events + private class InternalMastershipListener implements MastershipListener { + + @Override + public void event(MastershipEvent event) { + backgroundService.submit(() -> { + try { + handleMastershipEvent(event); + } catch (Exception e) { + log.warn("Failed to handle {}", event, e); + } + }); + } + } + + // Store delegate to re-post events emitted from the store. + private class InternalStoreDelegate implements DeviceStoreDelegate { + @Override + public void notify(DeviceEvent event) { + post(event); + } + } + + @Override + public Iterable<Device> getDevices(Type type) { + checkPermission(DEVICE_READ); + Set<Device> results = new HashSet<>(); + Iterable<Device> devices = store.getDevices(); + if (devices != null) { + devices.forEach(d -> { + if (type.equals(d.type())) { + results.add(d); + } + }); + } + return results; + } + + @Override + public Iterable<Device> getAvailableDevices(Type type) { + checkPermission(DEVICE_READ); + Set<Device> results = new HashSet<>(); + Iterable<Device> availableDevices = store.getAvailableDevices(); + if (availableDevices != null) { + availableDevices.forEach(d -> { + if (type.equals(d.type())) { + results.add(d); + } + }); + } + return results; + } + + private class InternalNetworkConfigListener implements NetworkConfigListener { + @Override + public boolean isRelevant(NetworkConfigEvent event) { + return (event.type() == NetworkConfigEvent.Type.CONFIG_ADDED + || event.type() == NetworkConfigEvent.Type.CONFIG_UPDATED) + && (event.configClass().equals(BasicDeviceConfig.class) + || event.configClass().equals(OpticalPortConfig.class)); + } + + @Override + public void event(NetworkConfigEvent event) { + DeviceEvent de = null; + if (event.configClass().equals(BasicDeviceConfig.class)) { + log.info("Detected Device network config event {}", event.type()); + DeviceId did = (DeviceId) event.subject(); + BasicDeviceConfig cfg = networkConfigService.getConfig(did, BasicDeviceConfig.class); + + if (!isAllowed(cfg)) { + kickOutBadDevice(did); + } else { + Device dev = getDevice(did); + DeviceDescription desc = (dev == null) ? null : BasicDeviceOperator.descriptionOf(dev); + desc = BasicDeviceOperator.combine(cfg, desc); + if (getProvider(did) != null) { + de = store.createOrUpdateDevice(getProvider(did).id(), did, desc); + } + } + } + if (event.configClass().equals(OpticalPortConfig.class)) { + ConnectPoint cpt = (ConnectPoint) event.subject(); + DeviceId did = cpt.deviceId(); + Port dpt = getPort(did, cpt.port()); + + if (dpt != null) { + OpticalPortConfig opc = networkConfigService.getConfig(cpt, OpticalPortConfig.class); + PortDescription desc = OpticalPortOperator.descriptionOf(dpt); + desc = OpticalPortOperator.combine(opc, desc); + if (getProvider(did) != null) { + de = store.updatePortStatus(getProvider(did).id(), did, desc); + } + } + } + + if (de != null) { + post(de); + } + } + + // checks if the specified device is allowed by the BasicDeviceConfig + // and if not, removes it + private void kickOutBadDevice(DeviceId deviceId) { + Device badDevice = getDevice(deviceId); + if (badDevice != null) { + removeDevice(deviceId); + } + } + } +} |