/*
* Copyright (c) 2016 Cisco Systems, Inc. and others. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v1.0 which accompanies this distribution,
* and is available at http://www.eclipse.org/legal/epl-v10.html
*/
package org.opendaylight.groupbasedpolicy.sxp_ise_adapter.impl;
import com.google.common.base.Function;
import com.google.common.util.concurrent.AsyncFunction;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.sun.jersey.api.client.Client;
import com.sun.jersey.api.client.WebResource;
import java.net.URI;
import java.security.GeneralSecurityException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathExpressionException;
import org.opendaylight.groupbasedpolicy.sxp_ise_adapter.impl.util.IseReplyUtil;
import org.opendaylight.groupbasedpolicy.sxp_ise_adapter.impl.util.RestClientFactory;
import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.config.groupbasedpolicy.sxp.integration.sxp.ise.adapter.model.rev160630.gbp.sxp.ise.adapter.IseSourceConfig;
import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.config.groupbasedpolicy.sxp.integration.sxp.ise.adapter.model.rev160630.gbp.sxp.ise.adapter.ise.source.config.ConnectionConfig;
import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.config.groupbasedpolicy.sxp.integration.sxp.ise.adapter.model.rev160630.gbp.sxp.ise.adapter.ise.source.config.connection.config.Header;
import org.opendaylight.yang.gen.v1.urn.opendaylight.sxp.database.rev160308.Sgt;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
/**
* Purpose: harvest sgt + names available via ise-rest-api
*/
public class GbpIseSgtHarvesterImpl implements GbpIseSgtHarvester {
private static final Logger LOG = LoggerFactory.getLogger(GbpIseSgtHarvesterImpl.class);
private final SgtInfoProcessor[] sgtInfoProcessors;
/**
* @param sgtInfoProcessors generator delegate
*/
public GbpIseSgtHarvesterImpl(final SgtInfoProcessor... sgtInfoProcessors) {
this.sgtInfoProcessors = sgtInfoProcessors;
}
@Override
public ListenableFuture<Collection<SgtInfo>> harvestAll(@Nonnull final IseContext iseContext) {
LOG.debug("ise-source: harvestAll {} -> {}", iseContext.getIseSourceConfig().getTenant(),
iseContext.getIseSourceConfig().getConnectionConfig().getIseRestUrl());
ListenableFuture<Collection<SgtInfo>> result;
try {
final IseSourceConfig iseSourceConfig = iseContext.getIseSourceConfig();
final ConnectionConfig connectionConfig = iseSourceConfig.getConnectionConfig();
final WebResource baseWebResource = createWebResource(connectionConfig);
final WebResource.Builder requestBuilder = RestClientFactory.createRequestBuilder(baseWebResource,
connectionConfig.getHeader(), RestClientFactory.PATH_ERS_CONFIG_SGT);
final String rawSgtSummary = IseReplyUtil.deliverResponse(requestBuilder);
final List<SgtInfo> sgtInfos = harvestDetails(rawSgtSummary, baseWebResource, connectionConfig, iseContext.getUuidToSgtMap());
ListenableFuture<Void> processingResult = Futures.immediateCheckedFuture(null);
for (SgtInfoProcessor processor : sgtInfoProcessors) {
processingResult = Futures.transform(processingResult, new AsyncFunction<Void, Void>() {
@Override
public ListenableFuture<Void> apply(final Void input) throws Exception {
LOG.debug("entering stg-info processor {}", processor.getClass().getSimpleName());
return processor.processSgtInfo(iseSourceConfig.getTenant(), sgtInfos);
}
});
}
result = Futures.transform(processingResult, new Function<Void, Collection<SgtInfo>>() {
@Nullable
@Override
public Collection<SgtInfo> apply(@Nullable final Void input) {
// update uuid map
for (SgtInfo sgtInfo : sgtInfos) {
iseContext.getUuidToSgtMap().put(sgtInfo.getUuid(), sgtInfo.getSgt().getValue());
}
//TODO: store harvest stats to DS/operational
// always success, otherwise there will be TransactionCommitFailedException thrown
return sgtInfos;
}
});
} catch (Exception e) {
LOG.debug("failed to harvest ise", e);
result = Futures.immediateFailedFuture(e);
}
return result;
}
private WebResource createWebResource(final ConnectionConfig connectionConfig) throws GeneralSecurityException {
final Client iseClient = RestClientFactory.createIseClient(connectionConfig);
return iseClient.resource(connectionConfig.getIseRestUrl().getValue());
}
private List<SgtInfo> harvestDetails(final String rawSgtSummary, final WebResource baseWebResource,
final ConnectionConfig connectionConfig, final Map<String, Integer> uuidToSgtMap) {
LOG.trace("rawSgtSummary: {}", rawSgtSummary);
final List<Future<SgtInfo>> sgtInfoFutureBag = new ArrayList<>();
// prepare worker pool
final ExecutorService pool = Executors.newFixedThreadPool(
10, new ThreadFactoryBuilder().setNameFormat("ise-sgt-worker-%d").build());
// parse sgtSummary
final XPath xpath = IseReplyUtil.setupXpath();
final InputSource inputSource = IseReplyUtil.createInputSource(rawSgtSummary);
try {
final NodeList sgtResources = IseReplyUtil.findAllSgtResourceNodes(xpath, inputSource);
final Collection<Node> sgtLinkNodes = IseReplyUtil.filterNewResourcesByID(uuidToSgtMap, xpath, sgtResources);
int counter = 0;
for (Node sgtLinkNode : sgtLinkNodes) {
final String sgtLinkHrefValue = sgtLinkNode.getNodeValue();
LOG.debug("found sgt resource: {}", sgtLinkHrefValue);
// submit all query tasks to pool
final int idx = counter++;
sgtInfoFutureBag.add(pool.submit(new Callable<SgtInfo>() {
@Override
public SgtInfo call() {
SgtInfo sgtInfo = null;
try {
sgtInfo = querySgtDetail(baseWebResource, connectionConfig.getHeader(), xpath, idx, sgtLinkHrefValue);
} catch (XPathExpressionException e) {
LOG.info("failed to parse sgt response for {}: {}", sgtLinkHrefValue, e.getMessage());
}
return sgtInfo;
}
}));
}
// stop pool
pool.shutdown();
final boolean terminated = pool.awaitTermination(1, TimeUnit.MINUTES);
if (! terminated) {
LOG.debug("NOT all sgt-detail queries succeeded - timed out");
pool.shutdownNow();
}
} catch (InterruptedException | XPathExpressionException e) {
LOG.warn("failed to query all-sgt details", e);
}
// harvest available details
return sgtInfoFutureBag.stream()
.map(this::gainSgtInfoSafely)
.filter(Objects::nonNull)
.collect(Collectors.toList());
}
private SgtInfo gainSgtInfoSafely(final Future<SgtInfo> response) {
SgtInfo result = null;
if (response.isDone() && ! response.isCancelled()) {
try {
result = response.get();
} catch (Exception e) {
LOG.debug("sgt-detail query failed even when future was DONE", e);
}
}
return result;
}
private SgtInfo querySgtDetail(final WebResource baseWebResource, final List<Header> headers, final XPath xpath,
final int idx, final String sgtLinkHrefValue) throws XPathExpressionException {
// query all sgt entries (serial-vise)
final URI hrefToSgtDetailUri = URI.create(sgtLinkHrefValue);
final WebResource.Builder requestBuilder = RestClientFactory.createRequestBuilder(baseWebResource, headers,
hrefToSgtDetailUri.getPath());
// time consuming operation - wait for rest response
final String rawSgtDetail = IseReplyUtil.deliverResponse(requestBuilder);
LOG.trace("rawSgtDetail: {}", rawSgtDetail);
// process response xml
final Node sgtNode = IseReplyUtil.findSgtDetailNode(xpath, rawSgtDetail);
final Node sgtName = IseReplyUtil.gainSgtName(xpath, sgtNode);
final Node sgtUuid = IseReplyUtil.gainSgtUuid(xpath, sgtNode);
final Node sgtValue = IseReplyUtil.gainSgtValue(xpath, sgtNode);
LOG.debug("sgt value [{}]: {} -> {}", idx, sgtValue, sgtName);
// store replies into list of SgtInfo
final Sgt sgt = new Sgt(Integer.parseInt(sgtValue.getNodeValue(), 10));
return new SgtInfo(sgt, sgtName.getNodeValue(), sgtUuid.getNodeValue());
}
}