12aa6f6afbdb01a5d14e39d2db4f5eeafe03b89f
[neutron.git] / transcriber / src / main / java / org / opendaylight / neutron / transcriber / AbstractTranscriberInterface.java
1 /*
2  * Copyright (c) 2015 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.neutron.transcriber;
10
11 import com.google.common.base.Optional;
12 import com.google.common.base.Preconditions;
13 import com.google.common.util.concurrent.CheckedFuture;
14 import java.lang.reflect.InvocationTargetException;
15 import java.lang.reflect.Method;
16 import java.lang.reflect.ParameterizedType;
17 import java.lang.reflect.Type;
18 import java.util.ArrayList;
19 import java.util.HashSet;
20 import java.util.List;
21 import java.util.Set;
22 import java.util.concurrent.ExecutionException;
23 import javax.annotation.PreDestroy;
24 import org.opendaylight.controller.md.sal.binding.api.DataBroker;
25 import org.opendaylight.controller.md.sal.binding.api.ReadOnlyTransaction;
26 import org.opendaylight.controller.md.sal.binding.api.ReadTransaction;
27 import org.opendaylight.controller.md.sal.binding.api.ReadWriteTransaction;
28 import org.opendaylight.controller.md.sal.binding.api.WriteTransaction;
29 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
30 import org.opendaylight.controller.md.sal.common.api.data.OptimisticLockFailedException;
31 import org.opendaylight.controller.md.sal.common.api.data.ReadFailedException;
32 import org.opendaylight.controller.md.sal.common.api.data.TransactionCommitFailedException;
33 import org.opendaylight.neutron.spi.INeutronAdminAttributes;
34 import org.opendaylight.neutron.spi.INeutronBaseAttributes;
35 import org.opendaylight.neutron.spi.INeutronCRUD;
36 import org.opendaylight.neutron.spi.INeutronObject;
37 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.types.rev130715.Uuid;
38 import org.opendaylight.yang.gen.v1.urn.opendaylight.neutron.attrs.rev150712.AdminAttributes;
39 import org.opendaylight.yang.gen.v1.urn.opendaylight.neutron.attrs.rev150712.BaseAttributes;
40 import org.opendaylight.yang.gen.v1.urn.opendaylight.neutron.attrs.rev150712.IdAttributes;
41 import org.opendaylight.yangtools.concepts.Builder;
42 import org.opendaylight.yangtools.yang.binding.Augmentable;
43 import org.opendaylight.yangtools.yang.binding.ChildOf;
44 import org.opendaylight.yangtools.yang.binding.DataObject;
45 import org.opendaylight.yangtools.yang.binding.Identifiable;
46 import org.opendaylight.yangtools.yang.binding.Identifier;
47 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
48 import org.slf4j.Logger;
49 import org.slf4j.LoggerFactory;
50
51 /**
52  * Base class of Transcriber converts data from/to neutron spi to/from md-sal yang model.
53  *
54  * <pre>
55  * {@code
56  * V -> U -> T
57  *        K: key
58  *
59  *
60  * T(MD-SAL model) <-> S (neutron spi): Neutron northbound
61  *                  -> fromMd()
62  *                 <-  toMd()
63  * }
64  * </pre>
65  *
66  * <p>
67  * Example
68  * T: Port
69  * U: Ports
70  * K: PortKey
71  * S: NeutronPort
72  * V: Neutron
73  *
74  * <p>
75  * @param <T> Target yang model
76  * @param <U> parent of T
77  * @param <K> key type to indentify T
78  * @param <S> Neutron-spi class
79  * @param <V> parent of U
80  */
81 public abstract class AbstractTranscriberInterface<
82         T extends DataObject & Identifiable<K> & ChildOf<? super U>,
83         U extends ChildOf<? super V> & Augmentable<U>,
84         K extends Identifier<T>, S extends INeutronObject<S>,
85         V extends DataObject>
86         implements AutoCloseable, INeutronCRUD<S> {
87
88     private static final Logger LOG = LoggerFactory.getLogger(AbstractTranscriberInterface.class);
89
90     // T extends DataObject & Identifiable<K> & ChildOf<? super U> as 0th type argument
91     private static final int MD_LIST_CLASS_TYPE_INDEX = 0;
92     // U extends ChildOf<? super Neutron> & Augmentable<U> as 1st type argument
93     private static final int MD_CONTAINER_CLASS_TYPE_INDEX = 1;
94     // V extends DataObject as 4th type argument
95     private static final int MD_PARENT_CLASS_TYPE_INDEX = 4;
96     // S extends INeutronObject<S> as 3rd type argument
97     private static final int NEUTRON_OBJECT_TYPE_INDEX = 3;
98
99     private static final int DEDASHED_UUID_LENGTH = 32;
100     private static final int DEDASHED_UUID_START = 0;
101     private static final int DEDASHED_UUID_DIV1 = 8;
102     private static final int DEDASHED_UUID_DIV2 = 12;
103     private static final int DEDASHED_UUID_DIV3 = 16;
104     private static final int DEDASHED_UUID_DIV4 = 20;
105
106     private static final int RETRY_MAX = 2;
107
108     private final DataBroker db;
109
110     private final Class<V> mdParentClass;
111     private final Class<U> mdContainerClass;
112     private final Class<T> mdListClass;
113
114     // Unfortunately odl yangtools doesn't model yang model "uses" as
115     // class/interface hierarchy. So we need to resort to use reflection
116     // to call setter method.
117     private final Class<? extends Builder<T>> builderClass;
118     private final Method setUuid;
119     private final Method setTenantId;
120     private final Method setProjectId;
121     private final Method setName;
122     private final Method setAdminStateUp;
123     private final Method setStatus;
124     private final Method setRevisionNumber;
125
126     protected Class<V> getMdParentClass(final Type[] types) {
127         @SuppressWarnings("unchecked")
128         Class<V> localMdParentClass = (Class<V>) types[MD_PARENT_CLASS_TYPE_INDEX];
129         return localMdParentClass;
130     }
131
132     protected AbstractTranscriberInterface(Class<? extends Builder<T>> builderClass, DataBroker db) {
133         this.db = Preconditions.checkNotNull(db);
134         this.builderClass = builderClass;
135
136         ParameterizedType parameterizedType = (ParameterizedType) getClass().getGenericSuperclass();
137         Type[] types = parameterizedType.getActualTypeArguments();
138         @SuppressWarnings("unchecked")
139         Class<T> localMdListClass = (Class<T>) types[MD_LIST_CLASS_TYPE_INDEX];
140         mdListClass = localMdListClass;
141         @SuppressWarnings("unchecked")
142         Class<U> localMdContainerClass = (Class<U>) types[MD_CONTAINER_CLASS_TYPE_INDEX];
143         mdContainerClass = localMdContainerClass;
144         mdParentClass = getMdParentClass(types);
145         @SuppressWarnings("unchecked")
146         Class<S> neutronObjectClass = (Class<S>) types[NEUTRON_OBJECT_TYPE_INDEX];
147         try {
148             setUuid = builderClass.getDeclaredMethod("setUuid", Uuid.class);
149             setTenantId = builderClass.getDeclaredMethod("setTenantId", Uuid.class);
150             setProjectId = builderClass.getDeclaredMethod("setProjectId", String.class);
151             setRevisionNumber = builderClass.getDeclaredMethod("setRevisionNumber", Long.class);
152             if (INeutronBaseAttributes.class.isAssignableFrom(neutronObjectClass)) {
153                 setName = builderClass.getDeclaredMethod("setName", String.class);
154             } else {
155                 setName = null;
156             }
157
158             if (INeutronAdminAttributes.class.isAssignableFrom(neutronObjectClass)) {
159                 setAdminStateUp = builderClass.getDeclaredMethod("setAdminStateUp", Boolean.class);
160                 setStatus = builderClass.getDeclaredMethod("setStatus", String.class);
161             } else {
162                 setAdminStateUp = null;
163                 setStatus = null;
164             }
165         } catch (NoSuchMethodException e) {
166             throw new IllegalArgumentException(e);
167         }
168     }
169
170     public DataBroker getDataBroker() {
171         Preconditions.checkNotNull(db);
172         return db;
173     }
174
175     private InstanceIdentifier<T> createInstanceIdentifier(T item) {
176         return InstanceIdentifier.create(mdParentClass).child(mdContainerClass).child(mdListClass, item.key());
177     }
178
179     private InstanceIdentifier<U> createInstanceIdentifier() {
180         return InstanceIdentifier.create(mdParentClass).child(mdContainerClass);
181     }
182
183     protected static <S1 extends INeutronObject<S1>, M extends IdAttributes, B extends Builder<M>>
184         B toMdIds(INeutronObject<S1> neutronObject, Class<B> builderClass) {
185         B builder;
186         try {
187             builder = builderClass.newInstance();
188
189             if (neutronObject.getID() != null) {
190                 final Method setUuid = builderClass.getMethod("setUuid", Uuid.class);
191                 setUuid.invoke(builder, toUuid(neutronObject.getID()));
192             }
193             if (neutronObject.getTenantID() != null && !neutronObject.getTenantID().isEmpty()) {
194                 final Method setTenantId = builderClass.getMethod("setTenantId", Uuid.class);
195                 setTenantId.invoke(builder, toUuid(neutronObject.getTenantID()));
196             }
197             if (neutronObject.getProjectID() != null) {
198                 final Method setProjectId = builderClass.getMethod("setProjectId", String.class);
199                 setProjectId.invoke(builder, neutronObject.getTenantID());
200             }
201             if (neutronObject.getRevisionNumber() != null) {
202                 final Method setRevisionNumber = builderClass.getMethod("setRevisionNumber", Long.class);
203                 setRevisionNumber.invoke(builder, neutronObject.getRevisionNumber());
204             }
205         } catch (IllegalAccessException | InstantiationException | InvocationTargetException
206                 | NoSuchMethodException e) {
207             throw new IllegalArgumentException(e);
208         }
209         return builder;
210     }
211
212     protected <S1 extends INeutronObject<S1>, M extends IdAttributes, B extends Builder<M>>
213         void toMdIds(INeutronObject<S1> neutronObject, B builder) {
214         try {
215             if (neutronObject.getID() != null) {
216                 setUuid.invoke(builder, toUuid(neutronObject.getID()));
217             } else {
218                 LOG.warn("Attempting to write neutron object {} without UUID", builderClass.getSimpleName());
219             }
220             if (neutronObject.getTenantID() != null && !neutronObject.getTenantID().isEmpty()) {
221                 setTenantId.invoke(builder, toUuid(neutronObject.getTenantID()));
222             }
223             if (neutronObject.getProjectID() != null) {
224                 setProjectId.invoke(builder, neutronObject.getTenantID());
225             }
226             if (neutronObject.getRevisionNumber() != null) {
227                 setRevisionNumber.invoke(builder, neutronObject.getRevisionNumber());
228             }
229         } catch (IllegalAccessException | InvocationTargetException e) {
230             throw new IllegalArgumentException(e);
231         }
232     }
233
234     protected <S1 extends INeutronObject<S1>>
235         void fromMdIds(IdAttributes idAttributes, INeutronObject<S1> answer) {
236         if (idAttributes.getUuid() != null) {
237             answer.setID(idAttributes.getUuid().getValue());
238         }
239         if (idAttributes.getTenantId() != null) {
240             answer.setTenantID(idAttributes.getTenantId());
241         }
242         if (idAttributes.getProjectId() != null) {
243             answer.setProjectID(idAttributes.getProjectId());
244         }
245         if (idAttributes.getRevisionNumber() != null) {
246             answer.setRevisionNumber(idAttributes.getRevisionNumber());
247         }
248     }
249
250     protected <S1 extends INeutronBaseAttributes<S1>, M extends BaseAttributes, B extends Builder<M>>
251         void toMdBaseAttributes(S1 neutronObject, B builder) {
252         toMdIds(neutronObject, builder);
253         try {
254             if (neutronObject.getName() != null) {
255                 setName.invoke(builder, neutronObject.getName());
256             }
257         } catch (IllegalAccessException | InvocationTargetException e) {
258             throw new IllegalArgumentException(e);
259         }
260     }
261
262     protected <S1 extends INeutronBaseAttributes<S1>>
263         void fromMdBaseAttributes(BaseAttributes baseAttributes, S1 answer) {
264         fromMdIds(baseAttributes, answer);
265         if (baseAttributes.getName() != null) {
266             answer.setName(baseAttributes.getName());
267         }
268     }
269
270     protected <S1 extends INeutronAdminAttributes<S1>, M extends BaseAttributes & AdminAttributes, B extends Builder<M>>
271         void toMdAdminAttributes(S1 neutronObject, B builder) {
272         toMdBaseAttributes(neutronObject, builder);
273         try {
274             if (neutronObject.getAdminStateUp() != null) {
275                 setAdminStateUp.invoke(builder, neutronObject.getAdminStateUp());
276             }
277             if (neutronObject.getStatus() != null) {
278                 setStatus.invoke(builder, neutronObject.getStatus());
279             }
280         } catch (IllegalAccessException | InvocationTargetException e) {
281             throw new IllegalArgumentException(e);
282         }
283     }
284
285     protected <M extends BaseAttributes & AdminAttributes, S1 extends INeutronAdminAttributes<S1>>
286         void fromMdAdminAttributes(M attr, S1 answer) {
287         fromMdBaseAttributes(attr, answer);
288         if (attr.isAdminStateUp() != null) {
289             answer.setAdminStateUp(attr.isAdminStateUp());
290         }
291         if (attr.getStatus() != null) {
292             answer.setStatus(attr.getStatus());
293         }
294     }
295
296     protected abstract T toMd(S neutronObject);
297
298     protected T toMd(String uuid) {
299         Builder<T> builder;
300         try {
301             builder = builderClass.newInstance();
302             setUuid.invoke(builder, toUuid(uuid));
303         } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
304             // should not happen.
305             throw new IllegalArgumentException(e);
306         }
307         return builder.build();
308     }
309
310     protected abstract S fromMd(T dataObject);
311
312     private <W extends DataObject> W readMd(InstanceIdentifier<W> path, ReadTransaction tx) {
313         Preconditions.checkNotNull(tx);
314         W result = null;
315         final CheckedFuture<Optional<W>,
316                 ReadFailedException> future = tx.read(LogicalDatastoreType.CONFIGURATION, path);
317         if (future != null) {
318             Optional<W> optional;
319             try {
320                 optional = future.checkedGet();
321                 if (optional.isPresent()) {
322                     result = optional.get();
323                 }
324             } catch (final ReadFailedException e) {
325                 LOG.warn("Failed to read {}", path, e);
326             }
327         }
328         return result;
329     }
330
331     protected <W extends DataObject> W readMd(InstanceIdentifier<W> path) {
332         try (ReadOnlyTransaction tx = getDataBroker().newReadOnlyTransaction()) {
333             return readMd(path, tx);
334         }
335     }
336
337     private void addMd(S neutronObject, WriteTransaction tx) throws InterruptedException, ExecutionException {
338         // TODO think about adding existence logic
339         updateMd(neutronObject, tx);
340     }
341
342     protected boolean addMd(S neutronObject) {
343         try {
344             final WriteTransaction tx = getDataBroker().newWriteOnlyTransaction();
345             addMd(neutronObject, tx);
346             return true;
347         } catch (InterruptedException | ExecutionException e) {
348             LOG.warn("Transaction failed", e);
349         }
350         return false;
351     }
352
353     private void updateMd(S neutronObject, WriteTransaction tx) throws InterruptedException, ExecutionException {
354         Preconditions.checkNotNull(tx);
355
356         final T item = toMd(neutronObject);
357         final InstanceIdentifier<T> iid = createInstanceIdentifier(item);
358         tx.put(LogicalDatastoreType.CONFIGURATION, iid, item, true);
359         final CheckedFuture<Void, TransactionCommitFailedException> future = tx.submit();
360         // Check if it's successfully committed, otherwise exception will be thrown.
361         future.get();
362     }
363
364     protected boolean updateMd(S neutronObject) {
365         int retries = RETRY_MAX;
366         while (retries-- >= 0) {
367             try {
368                 final WriteTransaction tx = getDataBroker().newWriteOnlyTransaction();
369                 updateMd(neutronObject, tx);
370                 return true;
371             } catch (InterruptedException | ExecutionException e) {
372                 if (e.getCause() instanceof OptimisticLockFailedException) {
373                     LOG.warn("Got OptimisticLockFailedException - {} {}", neutronObject, retries);
374                     continue;
375                 }
376                 // TODO: rethrow exception. don't mask exception
377                 LOG.error("Transaction failed", e);
378             }
379             break;
380         }
381         return false;
382     }
383
384     private void removeMd(T item, WriteTransaction tx) throws InterruptedException, ExecutionException {
385         Preconditions.checkNotNull(tx);
386         final InstanceIdentifier<T> iid = createInstanceIdentifier(item);
387         tx.delete(LogicalDatastoreType.CONFIGURATION, iid);
388         final CheckedFuture<Void, TransactionCommitFailedException> future = tx.submit();
389         // Check if it's successfully committed, otherwise exception will be thrown.
390         future.get();
391     }
392
393     protected boolean removeMd(T item) {
394         final ReadWriteTransaction tx = getDataBroker().newReadWriteTransaction();
395         try {
396             removeMd(item, tx);
397             return true;
398         } catch (InterruptedException | ExecutionException e) {
399             LOG.warn("Transaction failed", e);
400         }
401         return false;
402     }
403
404     protected static Uuid toUuid(String uuid) {
405         Preconditions.checkNotNull(uuid);
406         Uuid result;
407         try {
408             result = new Uuid(uuid);
409         } catch (final IllegalArgumentException e) {
410             // OK... someone didn't follow RFC 4122... lets try this the hard way
411             final String dedashed = uuid.replace("-", "");
412             if (dedashed.length() == DEDASHED_UUID_LENGTH) {
413                 final String redashed = dedashed.substring(DEDASHED_UUID_START, DEDASHED_UUID_DIV1) + "-"
414                         + dedashed.substring(DEDASHED_UUID_DIV1, DEDASHED_UUID_DIV2) + "-"
415                         + dedashed.substring(DEDASHED_UUID_DIV2, DEDASHED_UUID_DIV3) + "-"
416                         + dedashed.substring(DEDASHED_UUID_DIV3, DEDASHED_UUID_DIV4) + "-"
417                         + dedashed.substring(DEDASHED_UUID_DIV4, DEDASHED_UUID_LENGTH);
418                 result = new Uuid(redashed);
419             } else {
420                 throw e;
421             }
422         }
423         return result;
424     }
425
426     @Override
427     @PreDestroy
428     public void close() throws Exception {
429     }
430
431     @Override
432     public boolean exists(String uuid, ReadTransaction tx) {
433         Preconditions.checkNotNull(tx);
434         final T dataObject = readMd(createInstanceIdentifier(toMd(uuid)), tx);
435         return dataObject != null;
436     }
437
438     private S get(String uuid, ReadTransaction tx) {
439         Preconditions.checkNotNull(tx);
440         final T dataObject = readMd(createInstanceIdentifier(toMd(uuid)), tx);
441         if (dataObject == null) {
442             return null;
443         }
444         return fromMd(dataObject);
445     }
446
447     @Override
448     public S get(String uuid) {
449         try (ReadOnlyTransaction tx = getDataBroker().newReadOnlyTransaction()) {
450             return get(uuid, tx);
451         }
452     }
453
454     protected abstract List<T> getDataObjectList(U dataObjects);
455
456     private List<S> getAll(ReadTransaction tx) {
457         Preconditions.checkNotNull(tx);
458         final Set<S> allNeutronObjects = new HashSet<>();
459         final U dataObjects = readMd(createInstanceIdentifier(), tx);
460         if (dataObjects != null) {
461             for (final T dataObject : getDataObjectList(dataObjects)) {
462                 allNeutronObjects.add(fromMd(dataObject));
463             }
464         }
465         LOG.debug("Exiting _getAll, Found {} OpenStackFirewall", allNeutronObjects.size());
466         final List<S> ans = new ArrayList<>();
467         ans.addAll(allNeutronObjects);
468         return ans;
469     }
470
471     @Override
472     public List<S> getAll() {
473         try (ReadOnlyTransaction tx = getDataBroker().newReadOnlyTransaction()) {
474             return getAll(tx);
475         }
476     }
477
478     private Result add(S input, ReadWriteTransaction tx) throws InterruptedException, ExecutionException {
479         Preconditions.checkNotNull(tx);
480         if (exists(input.getID(), tx)) {
481             tx.cancel();
482             return Result.AlreadyExists;
483         }
484         addMd(input, tx);
485         return Result.Success;
486     }
487
488     @Override
489     public Result add(S input) {
490         int retries = RETRY_MAX;
491         while (retries-- >= 0) {
492             final ReadWriteTransaction tx = getDataBroker().newReadWriteTransaction();
493             try {
494                 return add(input, tx);
495             } catch (InterruptedException | ExecutionException e) {
496                 // TODO replace all this with org.opendaylight.genius.infra.RetryingManagedNewTransactionRunner
497                 if (e.getCause() instanceof OptimisticLockFailedException) {
498                     LOG.warn("Got OptimisticLockFailedException - {} {}", input, retries);
499                     continue;
500                 }
501                 // TODO: rethrow exception. don't mask exception
502                 LOG.error("Transaction failed", e);
503             }
504             break;
505         }
506         // TODO remove when re-throwing, and remove Result.Exception completely
507         return Result.Exception;
508     }
509
510     private boolean remove(String uuid, ReadWriteTransaction tx) throws InterruptedException, ExecutionException {
511         Preconditions.checkNotNull(tx);
512         if (!exists(uuid, tx)) {
513             tx.cancel();
514             return false;
515         }
516         removeMd(toMd(uuid), tx);
517         return true;
518     }
519
520     @Override
521     public boolean remove(String uuid) {
522         int retries = RETRY_MAX;
523         while (retries-- >= 0) {
524             final ReadWriteTransaction tx = getDataBroker().newReadWriteTransaction();
525             try {
526                 return remove(uuid, tx);
527             } catch (InterruptedException | ExecutionException e) {
528                 if (e.getCause() instanceof OptimisticLockFailedException) {
529                     LOG.warn("Got OptimisticLockFailedException - {} {}", uuid, retries);
530                     continue;
531                 }
532                 // TODO: rethrow exception. don't mask exception
533                 LOG.error("Transaction failed", e);
534             }
535             break;
536         }
537         return false;
538     }
539
540     private boolean update(String uuid, S delta, ReadWriteTransaction tx)
541             throws InterruptedException, ExecutionException {
542         Preconditions.checkNotNull(tx);
543         if (!exists(uuid, tx)) {
544             tx.cancel();
545             return false;
546         }
547         updateMd(delta, tx);
548         return true;
549     }
550
551     @Override
552     public boolean update(String uuid, S delta) {
553         int retries = RETRY_MAX;
554         while (retries-- >= 0) {
555             final ReadWriteTransaction tx = getDataBroker().newReadWriteTransaction();
556             try {
557                 return update(uuid, delta, tx);
558             } catch (InterruptedException | ExecutionException e) {
559                 if (e.getCause() instanceof OptimisticLockFailedException) {
560                     LOG.warn("Got OptimisticLockFailedException - {} {} {}", uuid, delta, retries);
561                     continue;
562                 }
563                 // TODO: rethrow exception. don't mask exception
564                 LOG.error("Transaction failed", e);
565             }
566             break;
567         }
568         return false;
569     }
570 }