/*
* 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.renderer.ios_xe_provider.impl.manager;
import static org.opendaylight.groupbasedpolicy.renderer.ios_xe_provider.impl.manager.PolicyManagerImpl.DsAction.Create;
import static org.opendaylight.groupbasedpolicy.renderer.ios_xe_provider.impl.manager.PolicyManagerImpl.DsAction.Delete;
import com.google.common.base.Function;
import com.google.common.base.Preconditions;
import com.google.common.util.concurrent.AsyncFunction;
import com.google.common.util.concurrent.CheckedFuture;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import org.opendaylight.controller.md.sal.binding.api.DataBroker;
import org.opendaylight.controller.md.sal.binding.api.ReadWriteTransaction;
import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
import org.opendaylight.controller.md.sal.common.api.data.TransactionCommitFailedException;
import org.opendaylight.groupbasedpolicy.renderer.ios_xe_provider.api.manager.PolicyManager;
import org.opendaylight.groupbasedpolicy.renderer.ios_xe_provider.impl.util.PolicyManagerUtil;
import org.opendaylight.groupbasedpolicy.renderer.ios_xe_provider.impl.util.RendererPolicyUtil;
import org.opendaylight.groupbasedpolicy.renderer.ios_xe_provider.impl.util.StatusUtil;
import org.opendaylight.groupbasedpolicy.sxp.ep.provider.api.EPToSgtMapper;
import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.renderer.rev151103.RendererName;
import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.renderer.rev151103.Renderers;
import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.renderer.rev151103.renderers.Renderer;
import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.renderer.rev151103.renderers.RendererKey;
import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.renderer.rev151103.renderers.renderer.RendererPolicy;
import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.renderer.rev151103.renderers.renderer.RendererPolicyBuilder;
import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.renderer.rev151103.renderers.renderer.renderer.policy.Configuration;
import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.renderer.rev151103.renderers.renderer.renderer.policy.Status;
import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.renderer.rev151103.renderers.renderer.renderer.policy.StatusBuilder;
import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.renderer.rev151103.renderers.renderer.renderer.policy.configuration.endpoints.AddressEndpointWithLocation;
import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.renderer.rev151103.renderers.renderer.renderer.policy.configuration.renderer.endpoints.RendererEndpoint;
import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.renderer.rev151103.renderers.renderer.renderer.policy.configuration.renderer.endpoints.renderer.endpoint.PeerEndpoint;
import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.renderer.rev151103.renderers.renderer.renderer.policy.status.UnconfiguredEndpointsBuilder;
import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.config.ip.sgt.distribution.rev160715.IpSgtDistributionService;
import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.config.ip.sgt.distribution.rev160715.SendIpSgtBindingToPeerInputBuilder;
import org.opendaylight.yang.gen.v1.urn.opendaylight.sxp.database.rev160308.Sgt;
import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.NodeId;
import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class PolicyManagerImpl implements PolicyManager {
private static final Logger LOG = LoggerFactory.getLogger(PolicyManagerImpl.class);
public static final RendererName IOS_XE_RENDERER = new RendererName("ios-xe-renderer");
private static final String BASE_POLICY_MAP_NAME = "service-chains-";
private final DataBroker dataBroker;
private final NodeManager nodeManager;
private final EPToSgtMapper epToSgtMapper;
private final IpSgtDistributionService ipSgtDistributor;
public PolicyManagerImpl(final DataBroker dataBroker,
final NodeManager nodeManager, final EPToSgtMapper epToSgtMapper,
final IpSgtDistributionService ipSgtDistributor) {
this.dataBroker = Preconditions.checkNotNull(dataBroker);
this.nodeManager = Preconditions.checkNotNull(nodeManager);
this.epToSgtMapper = Preconditions.checkNotNull(epToSgtMapper);
this.ipSgtDistributor = Preconditions.checkNotNull(ipSgtDistributor);
}
@Override
@Nonnull
public ListenableFuture<Boolean> syncPolicy(@Nullable final Configuration dataAfter, @Nullable final Configuration dataBefore,
final long version) {
ListenableFuture<Optional<Status>> creationResult;
if (dataBefore == null && dataAfter != null) {
creationResult = syncEndpoints(dataAfter, Create);
} else if (dataBefore != null && dataAfter == null) {
creationResult = syncEndpoints(dataBefore, Delete);
} else {
final ListenableFuture<Optional<Status>> deletionResult = syncEndpoints(dataBefore, Delete);
creationResult = Futures.transform(deletionResult, new AsyncFunction<Optional<Status>, Optional<Status>>() {
@Nonnull
@Override
public ListenableFuture<Optional<Status>> apply(@Nonnull Optional<Status> deletionResult) throws Exception {
if (deletionResult.isPresent()) {
// Wait till task is done. Result is not used, delete case has no status
deletionResult.get();
}
return syncEndpoints(dataAfter, Create);
}
});
}
return Futures.transform(creationResult, new AsyncFunction<Optional<Status>, Boolean>() {
@Override
public ListenableFuture<Boolean> apply(@Nullable final Optional<Status> statusValue) throws Exception {
Preconditions.checkArgument(statusValue != null, "provided status must not be null");
return Futures.transform(reportPolicy(version, statusValue), new Function<Void, Boolean>() {
@Override
public Boolean apply(@Nullable final Void input) {
return Boolean.TRUE;
}
});
}
});
}
/**
* Resolve policy for all endpoint pairs
*
* @param dataAfter - data used while processing
* @param action - specifies whether data are intended for creating or removing of configuration
* @return status of policy resolution
*/
@Nonnull
private ListenableFuture<Optional<Status>> syncEndpoints(final Configuration dataAfter, final DsAction action) {
if (dataAfter.getRendererEndpoints() == null
|| dataAfter.getRendererEndpoints().getRendererEndpoint() == null) {
LOG.debug("No configuration obtained - skipping");
return Futures.immediateFuture(Optional.empty());
}
final PolicyConfigurationContext context = new PolicyConfigurationContext();
// Renderer endpoint
for (RendererEndpoint rendererEndpoint : dataAfter.getRendererEndpoints().getRendererEndpoint()) {
context.setCurrentRendererEP(rendererEndpoint);
if (dataAfter.getEndpoints() == null || dataAfter.getEndpoints().getAddressEndpointWithLocation() == null) {
final String info = "Renderer-endpoint: missing address-endpoint-with-location";
context.appendUnconfiguredRendererEP(StatusUtil.assembleFullyNotConfigurableRendererEP(context, info));
continue;
}
final List<AddressEndpointWithLocation> endpointsWithLocation = dataAfter.getEndpoints()
.getAddressEndpointWithLocation();
final InstanceIdentifier mountpointIid = PolicyManagerUtil.getMountpointIidFromAbsoluteLocation(rendererEndpoint, endpointsWithLocation);
final DataBroker mountpoint = nodeManager.getNodeMountPoint(mountpointIid);
if (mountpoint == null) {
final String info = String.format("No data-broker for mount-point [%s] available", mountpointIid);
context.appendUnconfiguredRendererEP(StatusUtil.assembleFullyNotConfigurableRendererEP(context, info));
continue;
}
final Optional<String> optionalManagementIpAddress = nodeManager.getNodeManagementIpByMountPointIid(mountpointIid);
if (! optionalManagementIpAddress.isPresent()) {
final String info = String.format("Can not create policyWriter, managementIpAddress for mountpoint %s is null",
mountpointIid);
context.appendUnconfiguredRendererEP(StatusUtil.assembleFullyNotConfigurableRendererEP(context, info));
continue;
}
final String managementIpAddress = optionalManagementIpAddress.get();
final String interfaceName = PolicyManagerUtil.getInterfaceNameFromAbsoluteLocation(rendererEndpoint, endpointsWithLocation);
final NodeId nodeId = nodeManager.getNodeIdByMountpointIid(mountpointIid);
if (interfaceName == null || nodeId == null) {
final String info = String.format("Cannot compose policy-map, missing value. Interface: %s, NodeId: %s", interfaceName, nodeId);
context.appendUnconfiguredRendererEP(StatusUtil.assembleFullyNotConfigurableRendererEP(context, info));
LOG.warn(info);
continue;
}
final String policyMapName = BASE_POLICY_MAP_NAME.concat(interfaceName);
final PolicyMapLocation policyMapLocation = new PolicyMapLocation(policyMapName, interfaceName, nodeId,
managementIpAddress, mountpoint);
context.setPolicyMapLocation(policyMapLocation);
// TODO: pull timeout for async ops from config
final long TIMEOUT = 10;
final TimeUnit UNIT = TimeUnit.SECONDS;
final SendIpSgtBindingToPeerInputBuilder ipSgtBindings = new SendIpSgtBindingToPeerInputBuilder();
ipSgtBindings.setBinding(new ArrayList<>());
final Sgt sourceSgt = PolicyManagerUtil.findSgtTag(epToSgtMapper, rendererEndpoint, dataAfter.getEndpoints()
.getAddressEndpointWithLocation(), TIMEOUT, UNIT);
final AddressEndpointWithLocation sourceEPAddressWithLocation = RendererPolicyUtil.lookupEndpoint(
rendererEndpoint, dataAfter.getEndpoints().getAddressEndpointWithLocation());
PolicyManagerUtil.createIpSgtBindingItem(sourceSgt, sourceEPAddressWithLocation).ifPresent(ipSgtBindings.getBinding()::add);
// Peer Endpoint
for (PeerEndpoint peerEndpoint : rendererEndpoint.getPeerEndpoint()) {
final Sgt destinationSgt = PolicyManagerUtil.findSgtTag(epToSgtMapper, peerEndpoint, dataAfter.getEndpoints()
.getAddressEndpointWithLocation(), TIMEOUT, UNIT);
final AddressEndpointWithLocation destinationEPAddressWithLocation = RendererPolicyUtil.lookupEndpoint(
peerEndpoint, dataAfter.getEndpoints().getAddressEndpointWithLocation());
PolicyManagerUtil.createIpSgtBindingItem(destinationSgt, destinationEPAddressWithLocation)
.ifPresent(ipSgtBindings.getBinding()::add);
if (sourceSgt == null || destinationSgt == null) {
final String info = String.format("Endpoint-policy: missing sgt value(sourceSgt=%s, destinationSgt=%s)",
sourceSgt, destinationSgt);
context.appendUnconfiguredRendererEP(
StatusUtil.assembleNotConfigurableRendererEPForPeer(context, peerEndpoint, info));
continue;
}
// Resolve policy between endpoints
if (action.equals(Create)) {
LOG.debug("Setting up policy between endpoint {}, sgt: {} and peer {}, sgt: {}", rendererEndpoint,
sourceSgt, peerEndpoint, destinationSgt);
PolicyManagerUtil.syncEndpointPairCreatePolicy(sourceSgt, destinationSgt, context, dataAfter,
peerEndpoint, dataBroker);
} else {
LOG.debug("Removing policy between endpoint {}, sgt: {} and peer {}, sgt: {}", rendererEndpoint,
sourceSgt, peerEndpoint, destinationSgt);
PolicyManagerUtil.syncEndpointPairRemovePolicy(sourceSgt, destinationSgt, context, dataAfter,
peerEndpoint);
}
}
ipSgtDistributor.sendIpSgtBindingToPeer(ipSgtBindings.build());
}
final ListenableFuture<List<Boolean>> cumulativeResult = context.getCumulativeResult();
return Futures.transform(cumulativeResult, new Function<List<Boolean>, Optional<Status>>() {
@Nullable
@Override
public Optional<Status> apply(@Nullable final List<Boolean> input) {
//TODO: inspect if all booleans are true
LOG.trace("considering all submits as successful - otherwise there will be exception");
final Status status = new StatusBuilder()
.setUnconfiguredEndpoints(new UnconfiguredEndpointsBuilder()
.setUnconfiguredRendererEndpoint(context.getUnconfiguredRendererEPBag())
.build())
.build();
return Optional.of(status);
}
});
}
private CheckedFuture<Void, TransactionCommitFailedException> reportPolicy(final long version,
@Nonnull final Optional<Status> statusValue) {
if (statusValue.isPresent()) {
LOG.warn("IOS-XE renderer: operation not successfully completed, check unconfigured policy in operational/renderer:renderers");
}
final ReadWriteTransaction readWriteTransaction = dataBroker.newReadWriteTransaction();
final InstanceIdentifier<RendererPolicy> iid = InstanceIdentifier.create(Renderers.class)
.child(Renderer.class, new RendererKey(IOS_XE_RENDERER))
.child(RendererPolicy.class);
final RendererPolicy rendererPolicy = new RendererPolicyBuilder()
.setVersion(version)
.setStatus(statusValue.orElse(null))
.build();
readWriteTransaction.merge(LogicalDatastoreType.OPERATIONAL, iid, rendererPolicy);
return readWriteTransaction.submit();
}
@Override
public void close() {
//NOOP
}
enum DsAction {Create, Delete}
public enum ActionCase {ALLOW, CHAIN}
/**
* Wrapper class - contains all necessary information to clearly localize policy-map/interface/node in network
*/
public static class PolicyMapLocation {
private final String policyMapName;
private final String interfaceName;
private final NodeId nodeId;
private final String managementIpAddress;
private final DataBroker mountpoint;
public PolicyMapLocation(final String policyMapName, final String interfaceName, final NodeId nodeId,
final String managementIpAddress, final DataBroker mountpoint) {
this.policyMapName = Preconditions.checkNotNull(policyMapName);
this.interfaceName = Preconditions.checkNotNull(interfaceName);
this.nodeId = Preconditions.checkNotNull(nodeId);
this.managementIpAddress = Preconditions.checkNotNull(managementIpAddress);
this.mountpoint = Preconditions.checkNotNull(mountpoint);
}
public String getPolicyMapName() {
return policyMapName;
}
public String getInterfaceName() {
return interfaceName;
}
public NodeId getNodeId() {
return nodeId;
}
public String getManagementIpAddress() {
return managementIpAddress;
}
public DataBroker getMountpoint() {
return mountpoint;
}
}
}