02fba5e9d6cbc91a8a65a47063d17e2fe9443283
[groupbasedpolicy.git] / renderers / ofoverlay / src / main / java / org / opendaylight / groupbasedpolicy / renderer / ofoverlay / PolicyManager.java
1 /*
2  * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8
9 package org.opendaylight.groupbasedpolicy.renderer.ofoverlay;
10
11 import java.io.Closeable;
12 import java.io.IOException;
13 import java.util.ArrayList;
14 import java.util.Collection;
15 import java.util.HashMap;
16 import java.util.List;
17 import java.util.Map;
18 import java.util.concurrent.Callable;
19 import java.util.concurrent.CompletionService;
20 import java.util.concurrent.ExecutionException;
21 import java.util.concurrent.ExecutorCompletionService;
22 import java.util.concurrent.ScheduledExecutorService;
23 import java.util.concurrent.TimeUnit;
24
25 import org.opendaylight.controller.md.sal.binding.api.DataBroker;
26 import org.opendaylight.controller.md.sal.binding.api.DataTreeChangeListener;
27 import org.opendaylight.controller.md.sal.binding.api.DataTreeIdentifier;
28 import org.opendaylight.controller.md.sal.binding.api.DataTreeModification;
29 import org.opendaylight.controller.md.sal.binding.api.ReadWriteTransaction;
30 import org.opendaylight.controller.md.sal.binding.api.WriteTransaction;
31 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
32 import org.opendaylight.groupbasedpolicy.dto.EgKey;
33 import org.opendaylight.groupbasedpolicy.dto.EpKey;
34 import org.opendaylight.groupbasedpolicy.renderer.ofoverlay.endpoint.EndpointManager;
35 import org.opendaylight.groupbasedpolicy.renderer.ofoverlay.flow.DestinationMapper;
36 import org.opendaylight.groupbasedpolicy.renderer.ofoverlay.flow.EgressNatMapper;
37 import org.opendaylight.groupbasedpolicy.renderer.ofoverlay.flow.ExternalMapper;
38 import org.opendaylight.groupbasedpolicy.renderer.ofoverlay.flow.FlowUtils;
39 import org.opendaylight.groupbasedpolicy.renderer.ofoverlay.flow.GroupTable;
40 import org.opendaylight.groupbasedpolicy.renderer.ofoverlay.flow.IngressNatMapper;
41 import org.opendaylight.groupbasedpolicy.renderer.ofoverlay.flow.OfTable;
42 import org.opendaylight.groupbasedpolicy.renderer.ofoverlay.flow.PolicyEnforcer;
43 import org.opendaylight.groupbasedpolicy.renderer.ofoverlay.flow.PortSecurity;
44 import org.opendaylight.groupbasedpolicy.renderer.ofoverlay.flow.SourceMapper;
45 import org.opendaylight.groupbasedpolicy.renderer.ofoverlay.node.SwitchListener;
46 import org.opendaylight.groupbasedpolicy.renderer.ofoverlay.node.SwitchManager;
47 import org.opendaylight.groupbasedpolicy.renderer.ofoverlay.sfcutils.SfcIidFactory;
48 import org.opendaylight.groupbasedpolicy.util.DataStoreHelper;
49 import org.opendaylight.groupbasedpolicy.util.IidFactory;
50 import org.opendaylight.groupbasedpolicy.util.SingletonTask;
51 import org.opendaylight.yang.gen.v1.urn.ericsson.params.xml.ns.yang.sfc.of.renderer.rev151123.SfcOfRendererConfig;
52 import org.opendaylight.yang.gen.v1.urn.ericsson.params.xml.ns.yang.sfc.of.renderer.rev151123.SfcOfRendererConfigBuilder;
53 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.tables.Table;
54 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.tables.TableBuilder;
55 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.ofoverlay.rev140528.OfOverlayConfig.LearningMode;
56 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.renderer.rev151103.renderers.renderer.interests.followed.tenants.followed.tenant.FollowedEndpointGroup;
57 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.renderer.rev151103.renderers.renderer.interests.followed.tenants.followed.tenant.FollowedEndpointGroupBuilder;
58 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.resolved.policy.rev150828.ResolvedPolicies;
59 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.resolved.policy.rev150828.resolved.policies.ResolvedPolicy;
60 import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeId;
61 import org.opendaylight.yang.gen.v1.urn.opendaylight.table.types.rev131026.TableId;
62 import org.opendaylight.yangtools.concepts.ListenerRegistration;
63 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
64 import org.slf4j.Logger;
65 import org.slf4j.LoggerFactory;
66
67 import com.google.common.base.Function;
68 import com.google.common.base.Optional;
69 import com.google.common.collect.ImmutableList;
70 import com.google.common.util.concurrent.AsyncFunction;
71 import com.google.common.util.concurrent.Futures;
72 import com.google.common.util.concurrent.ListenableFuture;
73
74 /**
75  * Manage policies on switches by subscribing to updates from the
76  * policy resolver and information about endpoints from the endpoint
77  * registry
78  */
79 public class PolicyManager
80         implements SwitchListener, EndpointListener, DataTreeChangeListener<ResolvedPolicy>, Closeable {
81
82     private static final Logger LOG = LoggerFactory.getLogger(PolicyManager.class);
83
84     private Map<InstanceIdentifier<Table>, TableBuilder> previousGbpFlows  = new HashMap<>();
85
86     private short tableOffset;
87     private static final short TABLEID_PORTSECURITY = 0;
88     private static final short TABLEID_INGRESS_NAT = 1;
89     private static final short TABLEID_SOURCE_MAPPER = 2;
90     private static final short TABLEID_DESTINATION_MAPPER = 3;
91     private static final short TABLEID_POLICY_ENFORCER = 4;
92     private static final short TABLEID_EGRESS_NAT = 5;
93     private static final short TABLEID_EXTERNAL_MAPPER = 6;
94     private static final short TABLEID_SFC_INGRESS = 7;
95     private static final short TABLEID_SFC_EGRESS = 0;
96
97     private final SwitchManager switchManager;
98     private final EndpointManager endpointManager;
99
100     private final ListenerRegistration<PolicyManager> registerDataTreeChangeListener;
101
102     private final ScheduledExecutorService executor;
103     private final SingletonTask flowUpdateTask;
104     private final DataBroker dataBroker;
105
106     /**
107      * The delay before triggering the flow update task in response to an
108      * event in milliseconds.
109      */
110     private final static int FLOW_UPDATE_DELAY = 250;
111
112     public PolicyManager(DataBroker dataBroker, SwitchManager switchManager, EndpointManager endpointManager,
113             ScheduledExecutorService executor, short tableOffset) {
114         super();
115         this.switchManager = switchManager;
116         this.executor = executor;
117         this.dataBroker = dataBroker;
118         this.tableOffset = tableOffset;
119         try {
120             // to validate against model
121             verifyMaxTableId(tableOffset);
122         } catch (IllegalArgumentException e) {
123             throw new IllegalArgumentException("Failed to start OF-Overlay renderer\n."
124                     + "Max. table ID would be out of range. Check config-subsystem.\n{}", e);
125         }
126
127         if (dataBroker != null) {
128             registerDataTreeChangeListener = dataBroker.registerDataTreeChangeListener(
129                     new DataTreeIdentifier<>(LogicalDatastoreType.OPERATIONAL,
130                             InstanceIdentifier.builder(ResolvedPolicies.class).child(ResolvedPolicy.class).build()),
131                     this);
132         } else {
133             registerDataTreeChangeListener = null;
134             LOG.error("DataBroker is null. Listener for {} was not registered.",
135                     ResolvedPolicy.class.getCanonicalName());
136         }
137         if (switchManager != null)
138             switchManager.registerListener(this);
139         this.endpointManager = endpointManager;
140         endpointManager.registerListener(this);
141
142         if (!setSfcTableOffset(TABLEID_SFC_INGRESS, TABLEID_SFC_EGRESS)) {
143             LOG.error("Could not set SFC Ingress Table offset.");
144         }
145         flowUpdateTask = new SingletonTask(executor, new FlowUpdateTask());
146         scheduleUpdate();
147
148         LOG.debug("Initialized OFOverlay policy manager");
149     }
150
151     private boolean setSfcTableOffset(short tableidSfcIngress, short tableidSfcEgress) {
152         SfcOfRendererConfig sfcOfRendererConfig = new SfcOfRendererConfigBuilder()
153             .setSfcOfTableOffset(tableidSfcIngress).setSfcOfAppEgressTableOffset(tableidSfcEgress).build();
154         WriteTransaction wTx = dataBroker.newWriteOnlyTransaction();
155         wTx.put(LogicalDatastoreType.CONFIGURATION, SfcIidFactory.sfcOfRendererConfigIid(), sfcOfRendererConfig);
156         return DataStoreHelper.submitToDs(wTx);
157     }
158
159     private List<? extends OfTable> createFlowPipeline(OfContext ofCtx) {
160         // TODO - PORTSECURITY is kept in table 0.
161         // According to openflow spec,processing on vSwitch always starts from table 0.
162         // Packets will be droped if table 0 is empty.
163         // Alternative workaround - table-miss flow entries in table 0.
164         return ImmutableList.of(new PortSecurity(ofCtx, (short) 0), new GroupTable(ofCtx),
165                 new IngressNatMapper(ofCtx, getTABLEID_INGRESS_NAT()),
166                 new SourceMapper(ofCtx, getTABLEID_SOURCE_MAPPER()),
167                 new DestinationMapper(ofCtx, getTABLEID_DESTINATION_MAPPER()),
168                 new PolicyEnforcer(ofCtx, getTABLEID_POLICY_ENFORCER()),
169                 new EgressNatMapper(ofCtx, getTABLEID_EGRESS_NAT()),
170                 new ExternalMapper(ofCtx, getTABLEID_EXTERNAL_MAPPER()));
171     }
172
173     /**
174      * @param tableOffset the new offset value
175      * @return {@link ListenableFuture} to indicate that tables have been synced
176      */
177     public ListenableFuture<Void> changeOpenFlowTableOffset(final short tableOffset) {
178         try {
179             verifyMaxTableId(tableOffset);
180         } catch (IllegalArgumentException e) {
181             LOG.error("Cannot update table offset. Max. table ID would be out of range.\n{}", e);
182             // TODO - invalid offset value remains in conf DS
183             // It's not possible to validate offset value by using constrains in model,
184             // because number of tables in pipeline varies.
185             return Futures.immediateFuture(null);
186         }
187         List<Short> tableIDs = getTableIDs();
188         this.tableOffset = tableOffset;
189         return Futures.transform(removeUnusedTables(tableIDs), new Function<Void, Void>() {
190
191             @Override
192             public Void apply(Void tablesRemoved) {
193                 scheduleUpdate();
194                 return null;
195             }
196         });
197     }
198
199     /**
200      * @param tableIDs - IDs of tables to delete
201      * @return ListenableFuture<Void> - which will be filled when clearing is done
202      */
203     private ListenableFuture<Void> removeUnusedTables(final List<Short> tableIDs) {
204         List<ListenableFuture<Void>> checkList = new ArrayList<>();
205         final ReadWriteTransaction rwTx = dataBroker.newReadWriteTransaction();
206         for (Short tableId : tableIDs) {
207             for (NodeId nodeId : switchManager.getReadySwitches()) {
208                 final InstanceIdentifier<Table> tablePath = FlowUtils.createTablePath(nodeId, tableId);
209                 checkList.add(deleteTableIfExists(rwTx, tablePath));
210             }
211         }
212         ListenableFuture<List<Void>> allAsListFuture = Futures.allAsList(checkList);
213         return Futures.transform(allAsListFuture, new AsyncFunction<List<Void>, Void>() {
214
215             @Override
216             public ListenableFuture<Void> apply(List<Void> readyToSubmit) {
217                 return rwTx.submit();
218             }
219         });
220     }
221
222     private List<Short> getTableIDs() {
223         List<Short> tableIds = new ArrayList<>();
224         tableIds.add(getTABLEID_PORTSECURITY());
225         tableIds.add(getTABLEID_INGRESS_NAT());
226         tableIds.add(getTABLEID_SOURCE_MAPPER());
227         tableIds.add(getTABLEID_DESTINATION_MAPPER());
228         tableIds.add(getTABLEID_POLICY_ENFORCER());
229         tableIds.add(getTABLEID_EGRESS_NAT());
230         tableIds.add(getTABLEID_EXTERNAL_MAPPER());
231         return tableIds;
232     }
233
234     private ListenableFuture<Void> deleteTableIfExists(final ReadWriteTransaction rwTx,
235             final InstanceIdentifier<Table> tablePath) {
236         return Futures.transform(rwTx.read(LogicalDatastoreType.CONFIGURATION, tablePath),
237                 new Function<Optional<Table>, Void>() {
238
239                     @Override
240                     public Void apply(Optional<Table> optTable) {
241                         if (optTable.isPresent()) {
242                             rwTx.delete(LogicalDatastoreType.CONFIGURATION, tablePath);
243                         }
244                         return null;
245                     }
246                 });
247     }
248
249     // **************
250     // SwitchListener
251     // **************
252
253     public short getTABLEID_PORTSECURITY() {
254         return (short) (tableOffset + TABLEID_PORTSECURITY);
255     }
256
257     public short getTABLEID_INGRESS_NAT() {
258         return (short) (tableOffset + TABLEID_INGRESS_NAT);
259     }
260
261     public short getTABLEID_SOURCE_MAPPER() {
262         return (short) (tableOffset + TABLEID_SOURCE_MAPPER);
263     }
264
265     public short getTABLEID_DESTINATION_MAPPER() {
266         return (short) (tableOffset + TABLEID_DESTINATION_MAPPER);
267     }
268
269     public short getTABLEID_POLICY_ENFORCER() {
270         return (short) (tableOffset + TABLEID_POLICY_ENFORCER);
271     }
272
273     public short getTABLEID_EGRESS_NAT() {
274         return (short) (tableOffset + TABLEID_EGRESS_NAT);
275     }
276
277     public short getTABLEID_EXTERNAL_MAPPER() {
278         return (short) (tableOffset + TABLEID_EXTERNAL_MAPPER);
279     }
280
281     public short getTABLEID_SFC_EGRESS() {
282         return TABLEID_SFC_EGRESS;
283     }
284
285     public short getTABLEID_SFC_INGRESS() {
286         return TABLEID_SFC_INGRESS;
287     }
288
289     public TableId verifyMaxTableId(short tableOffset) {
290         return new TableId((short) (tableOffset + TABLEID_EXTERNAL_MAPPER));
291     }
292
293     @Override
294     public void switchReady(final NodeId nodeId) {
295         scheduleUpdate();
296     }
297
298     @Override
299     public void switchRemoved(NodeId sw) {
300         // XXX TODO purge switch flows
301         scheduleUpdate();
302     }
303
304     @Override
305     public void switchUpdated(NodeId sw) {
306         scheduleUpdate();
307     }
308
309     // ****************
310     // EndpointListener
311     // ****************
312
313     @Override
314     public void endpointUpdated(EpKey epKey) {
315         scheduleUpdate();
316     }
317
318     @Override
319     public void nodeEndpointUpdated(NodeId nodeId, EpKey epKey) {
320         scheduleUpdate();
321     }
322
323     @Override
324     public void groupEndpointUpdated(EgKey egKey, EpKey epKey) {
325         // TODO a renderer should remove followed-EPG and followed-tenant at some point
326         if (dataBroker == null) {
327             LOG.error("DataBroker is null. Cannot write followed-epg {}", epKey);
328             return;
329         }
330         WriteTransaction wTx = dataBroker.newWriteOnlyTransaction();
331         FollowedEndpointGroup followedEpg = new FollowedEndpointGroupBuilder().setId(egKey.getEgId()).build();
332         wTx.put(LogicalDatastoreType.OPERATIONAL, IidFactory.followedEndpointgroupIid(OFOverlayRenderer.RENDERER_NAME,
333                 egKey.getTenantId(), egKey.getEgId()), followedEpg, true);
334         DataStoreHelper.submitToDs(wTx);
335         scheduleUpdate();
336     }
337
338     // **************
339     // DataTreeChangeListener<ResolvedPolicy>
340     // **************
341
342     @Override
343     public void onDataTreeChanged(Collection<DataTreeModification<ResolvedPolicy>> changes) {
344         scheduleUpdate();
345     }
346
347     // *************
348     // PolicyManager
349     // *************
350
351     /**
352      * Set the learning mode to the specified value
353      *
354      * @param learningMode the learning mode to set
355      */
356     public void setLearningMode(LearningMode learningMode) {
357         // No-op for now
358     }
359
360     // **************
361     // Implementation
362     // **************
363
364     private void scheduleUpdate() {
365         if (switchManager != null) {
366             LOG.trace("Scheduling flow update task");
367             flowUpdateTask.reschedule(FLOW_UPDATE_DELAY, TimeUnit.MILLISECONDS);
368         }
369     }
370
371     /**
372      * Update the flows on a particular switch
373      */
374     private class SwitchFlowUpdateTask implements Callable<Void> {
375
376         private final OfWriter ofWriter;
377
378         public SwitchFlowUpdateTask(OfWriter ofWriter) {
379             this.ofWriter = ofWriter;
380         }
381
382         @Override
383         public Void call() throws Exception {
384             OfContext ofCtx = new OfContext(dataBroker, PolicyManager.this, switchManager, endpointManager, executor);
385             if (ofCtx.getCurrentPolicy() == null)
386                 return null;
387             List<? extends OfTable> flowPipeline = createFlowPipeline(ofCtx);
388             for (NodeId node : switchManager.getReadySwitches()) {
389                 for (OfTable table : flowPipeline) {
390                     try {
391                         table.sync(node, ofWriter);
392                     } catch (Exception e) {
393                         LOG.error("Failed to write Openflow table {}", table.getClass().getSimpleName(), e);
394                     }
395                 }
396             }
397             return null;
398         }
399     }
400
401     /**
402      * Update all flows on all switches as needed. Note that this will block
403      * one of the threads on the executor.
404      */
405     private class FlowUpdateTask implements Runnable {
406
407         @Override
408         public void run() {
409             LOG.debug("Beginning flow update task");
410
411             CompletionService<Void> ecs = new ExecutorCompletionService<>(executor);
412
413             OfWriter ofWriter = new OfWriter();
414
415             SwitchFlowUpdateTask swut = new SwitchFlowUpdateTask(ofWriter);
416             ecs.submit(swut);
417
418             try {
419                 ecs.take().get();
420                 // Current gbp flow must be independent, find out where this run() ends,
421                 // set flows to one field and reset another
422                 Map<InstanceIdentifier<Table>, TableBuilder> actualGbpFlows = new HashMap<>();
423                 actualGbpFlows.putAll(ofWriter.commitToDataStore(dataBroker, previousGbpFlows));
424                 previousGbpFlows = actualGbpFlows;
425             } catch (InterruptedException | ExecutionException e) {
426                 LOG.error("Failed to update flow tables", e);
427             }
428             LOG.debug("Flow update completed");
429         }
430     }
431
432     @Override
433     public void close() throws IOException {
434         if (registerDataTreeChangeListener != null)
435             registerDataTreeChangeListener.close();
436         // TODO unregister classifier and action instance validators
437     }
438
439 }