/* * Copyright (c) 2015 Intel, 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.ofoverlay; import java.util.HashSet; import java.util.List; import java.util.Map.Entry; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.Future; import org.opendaylight.controller.md.sal.binding.api.DataBroker; import org.opendaylight.controller.md.sal.binding.api.DataChangeListener; import org.opendaylight.controller.md.sal.binding.api.ReadOnlyTransaction; import org.opendaylight.controller.md.sal.common.api.data.AsyncDataBroker.DataChangeScope; import org.opendaylight.controller.md.sal.common.api.data.AsyncDataChangeEvent; import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType; import org.opendaylight.controller.sal.binding.api.RpcProviderRegistry; import org.opendaylight.groupbasedpolicy.api.sf.ChainActionDefinition; import org.opendaylight.groupbasedpolicy.util.IetfModelCodec; import org.opendaylight.sfc.provider.SfcProviderRpc; import org.opendaylight.sfc.provider.api.SfcProviderServiceChainAPI; import org.opendaylight.sfc.provider.api.SfcProviderServicePathAPI; import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.sfc.common.rev151017.SfcName; import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.sfc.rsp.rev140701.ReadRenderedServicePathFirstHopInputBuilder; import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.sfc.rsp.rev140701.ReadRenderedServicePathFirstHopOutput; import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.sfc.rsp.rev140701.rendered.service.path.first.hop.info.RenderedServicePathFirstHop; import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.sfc.sfc.rev140701.service.function.chain.grouping.ServiceFunctionChain; import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.sfc.sfp.rev140701.ServiceFunctionPaths; import org.opendaylight.yang.gen.v1.urn.cisco.params.xml.ns.yang.sfc.sfp.rev140701.service.function.paths.ServiceFunctionPath; import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.IpAddress; import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.common.rev140421.ActionDefinitionId; import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.policy.rev140421.SubjectFeatureDefinitions; import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.policy.rev140421.Tenants; import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.policy.rev140421.subject.feature.definitions.ActionDefinition; import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.policy.rev140421.subject.feature.definitions.ActionDefinitionKey; import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.policy.rev140421.subject.feature.instance.ParameterValue; import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.policy.rev140421.tenants.Tenant; import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.policy.rev140421.tenants.tenant.Policy; import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.policy.rev140421.tenants.tenant.policy.SubjectFeatureInstances; import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.policy.rev140421.tenants.tenant.policy.subject.feature.instances.ActionInstance; import org.opendaylight.yangtools.concepts.ListenerRegistration; import org.opendaylight.yangtools.yang.binding.DataObject; import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; import org.opendaylight.yangtools.yang.common.RpcResult; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.base.Optional; import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; /** * Manage the state exchanged with SFC * * For the Proof of Concept, this manages the * RenderedServicePathFirstHop elements that * are retrieved from SFC. * */ public class SfcManager implements AutoCloseable, DataChangeListener { private static final Logger LOG = LoggerFactory.getLogger(SfcManager.class); private final DataBroker dataBroker; private final ExecutorService executor; private final InstanceIdentifier<ActionInstance> allActionInstancesIid; private final ListenerRegistration<DataChangeListener> actionListener; /* * local cache of the RSP first hops that we've requested from SFC, * keyed by RSP name */ private final ConcurrentMap<String, RenderedServicePathFirstHop> rspMap; /* * TODO: these two String defs should move to the common * "chain" action, once we have it. */ // the chain action public static final String SFC_CHAIN_ACTION = "chain"; // the parameter used for storing the chain name public static final String SFC_CHAIN_NAME = "sfc-chain-name"; private static enum ActionState { ADD("add"), CHANGE("change"), DELETE("delete"); private String state; ActionState(String state) { this.state = state; } @Override public String toString() { return this.state; } } public SfcManager(DataBroker dataBroker, RpcProviderRegistry rpcRegistry, ExecutorService executor) { this.dataBroker = dataBroker; this.executor = executor; /* * Use thread-safe type only because we use an executor */ this.rspMap = new ConcurrentHashMap<String, RenderedServicePathFirstHop>(); /* * For now, listen to all changes in rules */ allActionInstancesIid = InstanceIdentifier.builder(Tenants.class) .child(Tenant.class) .child(Policy.class) .child(SubjectFeatureInstances.class) .child(ActionInstance.class) .build(); actionListener = dataBroker.registerDataChangeListener(LogicalDatastoreType.CONFIGURATION, allActionInstancesIid, this, DataChangeScope.ONE); LOG.debug("SfcManager: Started"); } public Set<IpAddress> getSfcSourceIps() { if (rspMap.isEmpty()) return null; Set<IpAddress> ipAddresses = new HashSet<IpAddress>(); for (RenderedServicePathFirstHop rsp: rspMap.values()) { if (rsp.getIp() != null) { ipAddresses.add(IetfModelCodec.ipAddress2010(rsp.getIp())); } } if (ipAddresses.isEmpty()) return null; return ipAddresses; } @Override public void onDataChanged( AsyncDataChangeEvent<InstanceIdentifier<?>, DataObject> actionInstanceNotification) { for (DataObject dao : actionInstanceNotification.getCreatedData().values()) { if (dao instanceof ActionInstance) { ActionInstance ai = (ActionInstance)dao; LOG.debug("New ActionInstance created"); executor.execute(new MatchActionDefTask(ai, null, ActionState.ADD)); } } for (InstanceIdentifier<?> iid : actionInstanceNotification.getRemovedPaths()) { DataObject old = actionInstanceNotification.getOriginalData().get(iid); if (old instanceof ActionInstance) { ActionInstance ai = (ActionInstance)old; executor.execute(new MatchActionDefTask(null, ai, ActionState.DELETE)); } } for (Entry<InstanceIdentifier<?>, DataObject> entry: actionInstanceNotification.getUpdatedData().entrySet()) { DataObject dao = entry.getValue(); if (dao instanceof ActionInstance) { ActionInstance nai = (ActionInstance)dao; ActionInstance oai = null; InstanceIdentifier<?> iid = entry.getKey(); DataObject orig = actionInstanceNotification.getOriginalData().get(iid); if (orig != null) { oai = (ActionInstance)orig; /* * We may have some cleanup here. If the reference to * the Action Definition changed, or if the Action Instance's * chain parameter then we're no longer * an action, and we may need to remove the RSP. */ } executor.execute(new MatchActionDefTask(nai, oai, ActionState.CHANGE)); } } } /** * Private internal class that gets the action definition * referenced by the instance. If the definition has an * action of "chain" (or whatever we decide to use * here), then we need to invoke the SFC API to go * get the chain information, which we'll eventually * use during policy resolution. * */ private class MatchActionDefTask implements Runnable, FutureCallback<Optional<ActionDefinition>> { private final ActionState state; private final ActionInstance actionInstance; private final ActionInstance originalInstance; private final InstanceIdentifier<ActionDefinition> adIid; private final ActionDefinitionId id; public MatchActionDefTask(ActionInstance actionInstance, ActionInstance originalInstance, ActionState state) { this.actionInstance = actionInstance; this.originalInstance = originalInstance; if (actionInstance != null) { this.id = actionInstance.getActionDefinitionId(); } else { this.id = null; } this.state = state; adIid = InstanceIdentifier.builder(SubjectFeatureDefinitions.class) .child(ActionDefinition.class, new ActionDefinitionKey(this.id)) .build(); } /** * Create read transaction with callback to look up * the Action Definition that the Action Instance * references. */ @Override public void run() { ReadOnlyTransaction rot = dataBroker.newReadOnlyTransaction(); ListenableFuture<Optional<ActionDefinition>> dao = rot.read(LogicalDatastoreType.OPERATIONAL, adIid); Futures.addCallback(dao, this, executor); } @Override public void onFailure(Throwable arg0) { LOG.error("Failure reading ActionDefinition {}", id.getValue()); } /** * An Action Definition exists - now we need to see * if the Action Definition is for a chain action, * and implement the appropriate behavior. If it's * not a chain action, then we can ignore it. * * @param dao */ @Override public void onSuccess(Optional<ActionDefinition> dao) { LOG.debug("Found ActionDefinition {}", id.getValue()); if (!dao.isPresent()) return; ActionDefinition ad = dao.get(); if (ad.getId().getValue().equals(ChainActionDefinition.ID.getValue())) { /* * We have the state we need: * 1) it's a "CHAIN" action * 2) the name is defined in the ActionInstance */ switch (state) { case ADD: /* * Go get the RSP First Hop */ getSfcChain(); break; case CHANGE: /* * We only care if the named chain changes */ changeSfcRsp(); break; case DELETE: /* * If the instance is deleted, we need to remove * it from our map. */ deleteSfcRsp(); break; default: break; } } } private ParameterValue getChainNameParameter(List<ParameterValue> pvl) { if (pvl == null) return null; for (ParameterValue pv: actionInstance.getParameterValue()) { if (pv.getName().getValue().equals(SFC_CHAIN_NAME)) { return pv; } } return null; } private void changeSfcRsp() { ParameterValue newPv = getChainNameParameter(actionInstance.getParameterValue()); ParameterValue origPv = getChainNameParameter(originalInstance.getParameterValue()); if (!newPv.getStringValue().equals(origPv.getStringValue())) { if (rspMap.containsKey(origPv.getStringValue())) { /* * Flow cleanup will happen as part of the * resolved policy * * TODO: can we guarantee that this * happens after we remove the RSP?). */ rspMap.remove(origPv.getStringValue()); } addSfcRsp(); } } private void deleteSfcRsp() { ParameterValue pv = getChainNameParameter(originalInstance.getParameterValue()); if (pv == null) return; rspMap.remove(pv.getStringValue()); } /** * Get the RenderedServicePathFirstHop from SFC * * TODO: what if SFC state isn't available at the time of * this call, but becomes available later? Do we want * or need some sort of notification handler for this? */ private void addSfcRsp() { ParameterValue pv = getChainNameParameter(actionInstance.getParameterValue()); if (pv == null) return; LOG.trace("Invoking RPC for chain {}", pv.getStringValue()); ReadRenderedServicePathFirstHopInputBuilder builder = new ReadRenderedServicePathFirstHopInputBuilder() .setName(pv.getStringValue()); // TODO: make async Future<RpcResult<ReadRenderedServicePathFirstHopOutput>> result = SfcProviderRpc.getSfcProviderRpc() .readRenderedServicePathFirstHop(builder.build()); try { RpcResult<ReadRenderedServicePathFirstHopOutput> output = result.get(); if (output.isSuccessful()) { LOG.trace("RPC for chain {} succeeded!", pv.getStringValue()); RenderedServicePathFirstHop rspFirstHop = output.getResult().getRenderedServicePathFirstHop(); /* * We won't retry installation in the map * because the presumption is it's either * the same object or contain the same * state. */ rspMap.putIfAbsent(pv.getStringValue(), rspFirstHop); } } catch (Exception e) { LOG.warn("Failed ReadRenderedServicePathFirstHop RPC: {}", e); // TODO: proper exception handling } } private void getSfcChain() { ParameterValue pv = getChainNameParameter(actionInstance.getParameterValue()); if (pv == null) return; LOG.trace("Invoking RPC for chain {}", pv.getStringValue()); SfcName chainName=new SfcName(pv.getStringValue()); ServiceFunctionChain chain = SfcProviderServiceChainAPI.readServiceFunctionChain(chainName); ServiceFunctionPaths paths = SfcProviderServicePathAPI.readAllServiceFunctionPaths(); for(ServiceFunctionPath path: paths.getServiceFunctionPath()) { if(path.getServiceChainName().equals(chainName)) { LOG.info("Found path {} for chain {}",path.getName(),path.getServiceChainName()); } } } } /** * Return the first hop information for the Rendered Service Path * * @param rspName the Rendered Service Path * @return the first hop information for the Rendered Service Path */ public RenderedServicePathFirstHop getRspFirstHop(String rspName) { return rspMap.get(rspName); } @Override public void close() throws Exception { if (actionListener != null) actionListener.close(); } }