/** * Copyright 2015-2016 Red Hat, Inc, and individual contributors. * * 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.wildfly.swarm.topology.openshift.runtime; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.concurrent.atomic.AtomicReference; import com.openshift.restclient.IClient; import com.openshift.restclient.IOpenShiftWatchListener; import com.openshift.restclient.IWatcher; import com.openshift.restclient.ResourceKind; import com.openshift.restclient.model.IResource; import com.openshift.restclient.model.IService; import org.jboss.msc.inject.Injector; import org.jboss.msc.service.Service; import org.jboss.msc.service.ServiceName; import org.jboss.msc.service.StartContext; import org.jboss.msc.service.StartException; import org.jboss.msc.service.StopContext; import org.jboss.msc.value.InjectedValue; import org.wildfly.swarm.topology.runtime.Registration; import org.wildfly.swarm.topology.runtime.TopologyManager; /** * @author Bob McWhirter * @author Ken Finnigan */ public class ServiceWatcher implements Service<ServiceWatcher>, IOpenShiftWatchListener { public static final ServiceName SERVICE_NAME = OpenShiftTopologyConnector.SERVICE_NAME.append("service-watcher"); public static final int DEFAULT_HTTPS_PORT = 8443; private static final String TOPOLOGY_SOURCE_KEY = "openshift"; public Injector<IClient> getClientInjector() { return this.clientInjector; } public Injector<String> getNamespaceInjector() { return this.namespaceInjector; } public Injector<TopologyManager> getTopologyManagerInjector() { return this.topologyManagerInjector; } @Override public void start(StartContext context) throws StartException { startWatcher(); } @Override public void stop(StopContext context) { if (openShiftWatcher != null) { openShiftWatcher.stop(); } } @Override public ServiceWatcher getValue() throws IllegalStateException, IllegalArgumentException { return this; } private void startWatcher() { IClient client = clientInjector.getValue(); listenerState.set(ListenerState.STARTING); openShiftWatcher = client.watch(namespaceInjector.getValue(), this, ResourceKind.SERVICE); } @Override public void connected(List<IResource> resources) { this.listenerState.set(ListenerState.CONNECTED); resources.stream() .filter(p -> p.getKind().equals(ResourceKind.SERVICE)) .forEach(r -> { Set<Registration> regs = registrationsForService((IService) r); regs.forEach(topologyManagerInjector.getValue()::register); }); } @Override public void disconnected() { if (listenerState.get().equals(ListenerState.RESTARTING)) { // Restarting so ignore disconnect return; } // De-register all endpoints topologyManagerInjector.getValue().unregisterAll(TOPOLOGY_SOURCE_KEY); listenerState.set(ListenerState.DISCONNECTED); } @Override public void received(IResource resource, ChangeType change) { if (change.equals(ChangeType.ADDED)) { // Add new Service to topology IService service = (IService) resource; registrationsForService(service) .forEach(topologyManagerInjector.getValue()::register); } else if (change.equals(ChangeType.DELETED)) { // Remove Service from topology topologyManagerInjector.getValue().unregisterAll(TOPOLOGY_SOURCE_KEY, resource.getName()); } } @Override public void error(Throwable err) { // Log the error ?? restart(); } private void restart() { switch (listenerState.get()) { case STARTING: // Trace.debug("Returning early from restart. Already starting for project {0} and kind {1}", project.getName(), kind); case DISCONNECTED: // Trace.debug("Endpoint disconnected and skipping restart for project {0} and kind {1}", project.getName(), kind); return; default: } if (openShiftWatcher != null) { listenerState.set(ListenerState.RESTARTING); openShiftWatcher.stop(); } startWatcher(); } private Set<Registration> registrationsForService(IService service) { Set<Registration> newEntries = new HashSet<>(); // Only expose the service's default port and anything running on the https port service.getPorts() .stream() .filter(servicePort -> servicePort.getPort() == service.getPort() || servicePort.getPort() == DEFAULT_HTTPS_PORT) .forEach(servicePort -> { Registration registration = new Registration(TOPOLOGY_SOURCE_KEY, service.getName(), service.getName(), servicePort.getPort()); if (servicePort.getPort() == DEFAULT_HTTPS_PORT) { registration.addTags(Collections.singletonList("https")); } else if (servicePort.getPort() == service.getPort()) { registration.addTags(Collections.singletonList("http")); } newEntries.add(registration); }); return newEntries; } private enum ListenerState { STARTING, CONNECTED, RESTARTING, DISCONNECTED } private InjectedValue<IClient> clientInjector = new InjectedValue<>(); private InjectedValue<String> namespaceInjector = new InjectedValue<>(); private InjectedValue<TopologyManager> topologyManagerInjector = new InjectedValue<>(); private IWatcher openShiftWatcher; private AtomicReference<ListenerState> listenerState = new AtomicReference<>(ListenerState.DISCONNECTED); }