/* * Copyright (c) 2014 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.ovsdb.southbound.ovsdb.transact; import static org.opendaylight.ovsdb.lib.operations.Operations.op; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Queue; import java.util.Set; import javax.annotation.Nullable; import com.google.common.base.Predicate; import org.opendaylight.controller.md.sal.binding.api.DataObjectModification; import org.opendaylight.controller.md.sal.binding.api.DataTreeModification; import org.opendaylight.controller.md.sal.common.api.data.AsyncDataChangeEvent; import org.opendaylight.ovsdb.lib.notation.Mutation; import org.opendaylight.ovsdb.lib.notation.Mutator; import org.opendaylight.ovsdb.lib.notation.OvsdbSet; import org.opendaylight.ovsdb.lib.notation.UUID; import org.opendaylight.ovsdb.lib.operations.Insert; import org.opendaylight.ovsdb.lib.operations.Mutate; import org.opendaylight.ovsdb.lib.operations.Operation; import org.opendaylight.ovsdb.lib.operations.TransactionBuilder; import org.opendaylight.ovsdb.lib.schema.ColumnSchema; import org.opendaylight.ovsdb.lib.schema.GenericTableSchema; import org.opendaylight.ovsdb.lib.schema.TableSchema; import org.opendaylight.ovsdb.southbound.SouthboundConstants; import org.opendaylight.ovsdb.southbound.SouthboundMapper; import org.opendaylight.ovsdb.southbound.SouthboundUtil; import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.Node; import org.opendaylight.yangtools.yang.binding.ChildOf; import org.opendaylight.yangtools.yang.binding.DataObject; import org.opendaylight.yangtools.yang.binding.Identifiable; import org.opendaylight.yangtools.yang.binding.Identifier; import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; import org.opendaylight.yangtools.yang.binding.KeyedInstanceIdentifier; import com.google.common.base.Predicates; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Sets; public class TransactUtils { private static Predicate> hasDataBefore() { return new Predicate>() { @Override public boolean apply(@Nullable DataObjectModification input) { return input != null && input.getDataBefore() != null; } }; } private static Predicate> hasDataBeforeAndDataAfter() { return new Predicate>() { @Override public boolean apply(@Nullable DataObjectModification input) { return input != null && input.getDataBefore() != null && input.getDataAfter() != null; } }; } private static Predicate> hasNoDataBefore() { return new Predicate>() { @Override public boolean apply(@Nullable DataObjectModification input) { return input != null && input.getDataBefore() == null; } }; } private static Predicate> hasDataAfterAndMatchesFilter( final Predicate> filter) { return new Predicate>() { @Override public boolean apply(@Nullable DataObjectModification input) { return input != null && input.getDataAfter() != null && filter.apply(input); } }; } private static Predicate> matchesEverything() { return new Predicate>() { @Override public boolean apply(@Nullable DataObjectModification input) { return true; } }; } private static Predicate> modificationIsDeletion() { return new Predicate>() { @Override public boolean apply(@Nullable DataObjectModification input) { return input != null && input.getModificationType() == DataObjectModification .ModificationType.DELETE; } }; } private static Predicate> modificationIsDeletionAndHasDataBefore () { return new Predicate>() { @Override public boolean apply(@Nullable DataObjectModification input) { return input != null && input.getModificationType() == DataObjectModification .ModificationType.DELETE && input.getDataBefore() != null; } }; } public static Map,Node> extractNode( Map, DataObject> changes) { Map,Node> result = new HashMap<>(); if (changes != null) { for (Entry, DataObject> created : changes.entrySet()) { if (created.getValue() instanceof Node) { Node value = (Node) created.getValue(); Class type = created.getKey().getTargetType(); if (type.equals(Node.class)) { @SuppressWarnings("unchecked") // Actually checked above InstanceIdentifier iid = (InstanceIdentifier) created.getKey(); result.put(iid, value); } } } } return result; } public static Map,T> extractCreated( AsyncDataChangeEvent, DataObject> changes,Class klazz) { return extract(changes.getCreatedData(),klazz); } /** * Extract all the instances of {@code clazz} which were created in the given set of modifications. * * @param changes The changes to process. * @param clazz The class we're interested in. * @param The type of changes we're interested in. * @param The type of changes to process. * @return The created instances, mapped by instance identifier. */ public static Map, T> extractCreated( Collection> changes, Class clazz) { return extractCreatedOrUpdated(changes, clazz, hasNoDataBefore()); } /** * Extract all the instance of {@code clazz} which were created or updated in the given set of modifications, and * which satisfy the given filter. * * @param changes The changes to process. * @param clazz The class we're interested in. * @param filter The filter the changes must satisfy. * @param The type of changes we're interested in. * @param The type of changes to process. * @return The created or updated instances which satisfy the filter, mapped by instance identifier. */ public static Map, T> extractCreatedOrUpdated( Collection> changes, Class clazz, Predicate> filter) { Map, T> result = new HashMap<>(); for (Map.Entry, DataObjectModification> entry : extractDataObjectModifications(changes, clazz, hasDataAfterAndMatchesFilter(filter)).entrySet()) { result.put(entry.getKey(), entry.getValue().getDataAfter()); } return result; } public static Map,T> extractUpdated( AsyncDataChangeEvent,DataObject> changes,Class klazz) { return extract(changes.getUpdatedData(),klazz); } /** * Extract all the instances of {@code clazz} which were updated in the given set of modifications. * * @param changes The changes to process. * @param clazz The class we're interested in. * @param The type of changes we're interested in. * @param The type of changes to process. * @return The updated instances, mapped by instance identifier. */ public static Map, T> extractUpdated( Collection> changes, Class clazz) { return extractCreatedOrUpdated(changes, clazz, hasDataBeforeAndDataAfter()); } public static Map,T> extractCreatedOrUpdated( AsyncDataChangeEvent,DataObject> changes,Class klazz) { Map,T> result = extractUpdated(changes,klazz); result.putAll(extractCreated(changes,klazz)); return result; } /** * Extract all the instances of {@code clazz} which were created or updated in the given set of modifications. * * @param changes The changes to process. * @param clazz The class we're interested in. * @param The type of changes we're interested in. * @param The type of changes to process. * @return The created or updated instances, mapped by instance identifier. */ public static Map, T> extractCreatedOrUpdated( Collection> changes, Class clazz) { return extractCreatedOrUpdated(changes, clazz, matchesEverything()); } public static Map, T> extractCreatedOrUpdatedOrRemoved( AsyncDataChangeEvent, DataObject> changes, Class klazz) { Map,T> result = extractCreatedOrUpdated(changes,klazz); result.putAll(extractRemovedObjects(changes, klazz)); return result; } /** * Extract all the instances of {@code clazz} which were created, updated, or removed in the given set of * modifications. For instances which were created or updated, the new instances are returned; for instances * which were removed, the old instances are returned. * * @param changes The changes to process. * @param clazz The class we're interested in. * @param The type of changes we're interested in. * @param The type of changes to process. * @return The created, updated or removed instances, mapped by instance identifier. */ public static Map, T> extractCreatedOrUpdatedOrRemoved( Collection> changes, Class clazz) { Map, T> result = extractCreatedOrUpdated(changes, clazz); result.putAll(extractRemovedObjects(changes, clazz)); return result; } public static Map,T> extractOriginal( AsyncDataChangeEvent,DataObject> changes,Class klazz) { return extract(changes.getOriginalData(),klazz); } /** * Extract the original instances of class {@code clazz} in the given set of modifications. * * @param changes The changes to process. * @param clazz The class we're interested in. * @param The type of changes we're interested in. * @param The type of changes to process. * @return The original instances, mapped by instance identifier. */ public static Map, T> extractOriginal( Collection> changes, Class clazz) { Map, T> result = new HashMap<>(); for (Map.Entry, DataObjectModification> entry : extractDataObjectModifications(changes, clazz, hasDataBefore()).entrySet()) { result.put(entry.getKey(), entry.getValue().getDataBefore()); } return result; } public static Set> extractRemoved( AsyncDataChangeEvent,DataObject> changes,Class klazz) { Set> result = new HashSet<>(); if (changes != null && changes.getRemovedPaths() != null) { for (InstanceIdentifier iid : changes.getRemovedPaths()) { if (iid.getTargetType().equals(klazz)) { @SuppressWarnings("unchecked") // Actually checked above InstanceIdentifier iidn = (InstanceIdentifier)iid; result.add(iidn); } } } return result; } /** * Extract the instance identifier of removed instances of {@code clazz} from the given set of modifications. * * @param changes The changes to process. * @param clazz The class we're interested in. * @param The type of changes we're interested in. * @param The type of changes to process. * @return The instance identifiers of removed instances. */ public static Set> extractRemoved( Collection> changes, Class clazz) { return extractDataObjectModifications(changes, clazz, modificationIsDeletion()).keySet(); } /** * Extract all the modifications affecting instances of {@code clazz} which are present in the given set of * modifications and satisfy the given filter. * * @param changes The changes to process. * @param clazz The class we're interested in. * @param filter The filter the changes must satisfy. * @param The type of changes we're interested in. * @param The type of changes to process. * @return The modifications, mapped by instance identifier. */ private static Map, DataObjectModification> extractDataObjectModifications( Collection> changes, Class clazz, Predicate> filter) { List> dataObjectModifications = new ArrayList<>(); List> paths = new ArrayList<>(); if (changes != null) { for (DataTreeModification change : changes) { dataObjectModifications.add(change.getRootNode()); paths.add(change.getRootPath().getRootIdentifier()); } } return extractDataObjectModifications(dataObjectModifications, paths, clazz, filter); } /** * Extract all the modifications affecting instances of {@code clazz} which are present in the given set of * modifications and satisfy the given filter. * * @param changes The changes to process. * @param paths The paths of the changes. * @param clazz The class we're interested in. * @param filter The filter the changes must satisfy. * @param The type of changes we're interested in. * @return The modifications, mapped by instance identifier. */ private static Map, DataObjectModification> extractDataObjectModifications( Collection> changes, Collection> paths, Class clazz, Predicate> filter) { Map, DataObjectModification> result = new HashMap<>(); Queue> remainingChanges = new LinkedList<>(changes); Queue> remainingPaths = new LinkedList<>(paths); while (!remainingChanges.isEmpty()) { DataObjectModification change = remainingChanges.remove(); InstanceIdentifier path = remainingPaths.remove(); // Is the change relevant? if (clazz.isAssignableFrom(change.getDataType()) && filter.apply((DataObjectModification) change)) { result.put((InstanceIdentifier) path, (DataObjectModification) change); } // Add any children to the queue for (DataObjectModification child : change.getModifiedChildren()) { remainingChanges.add(child); remainingPaths.add(extendPath(path, child)); } } return result; } /** * Extends the given instance identifier path to include the given child. Augmentations are treated in the same way * as children; keyed children are handled correctly. * * @param path The current path. * @param child The child modification to include. * @return The extended path. */ private static & ChildOf, K extends Identifier, T extends DataObject> InstanceIdentifier extendPath( InstanceIdentifier path, DataObjectModification child) { Class item = (Class) child.getDataType(); if (child.getIdentifier() instanceof InstanceIdentifier.IdentifiableItem) { K key = (K) ((InstanceIdentifier.IdentifiableItem) child.getIdentifier()).getKey(); KeyedInstanceIdentifier extendedPath = path.child(item, key); return extendedPath; } else { InstanceIdentifier extendedPath = path.child(item); return extendedPath; } } public static Map, T> extractRemovedObjects( AsyncDataChangeEvent, DataObject> changes, Class klazz) { Set> iids = extractRemoved(changes, klazz); return Maps.filterKeys(extractOriginal(changes, klazz),Predicates.in(iids)); } /** * Extract the removed instances of {@code clazz} from the given set of modifications. * * @param changes The changes to process. * @param clazz The class we're interested in. * @param The type of changes we're interested in. * @param The type of changes to process. * @return The removed instances, keyed by instance identifier. */ public static Map, T> extractRemovedObjects( Collection> changes, Class clazz) { Map, T> result = new HashMap<>(); for (Map.Entry, DataObjectModification> entry : extractDataObjectModifications(changes, clazz, modificationIsDeletionAndHasDataBefore()).entrySet()) { result.put(entry.getKey(), entry.getValue().getDataBefore()); } return result; } public static Map,T> extract( Map, DataObject> changes, Class klazz) { Map,T> result = new HashMap<>(); if (changes != null) { for (Entry, DataObject> created : changes.entrySet()) { if (klazz.isInstance(created.getValue())) { @SuppressWarnings("unchecked") T value = (T) created.getValue(); Class type = created.getKey().getTargetType(); if (type.equals(klazz)) { @SuppressWarnings("unchecked") // Actually checked above InstanceIdentifier iid = (InstanceIdentifier) created.getKey(); result.put(iid, value); } } } } return result; } public static List extractInsert(TransactionBuilder transaction, GenericTableSchema schema) { List operations = transaction.getOperations(); List inserts = new ArrayList<>(); for (Operation operation : operations) { if (operation instanceof Insert && operation.getTableSchema().equals(schema)) { inserts.add((Insert) operation); } } return inserts; } /** * Extract the NamedUuid from the Insert. * If the Insert does not have a NamedUuid set, a random one will be * generated, set, and returned. * * @param insert - Insert from which to extract the NamedUuid * @return UUID - NamedUUID of the Insert */ public static UUID extractNamedUuid(Insert insert) { String uuidString = insert.getUuidName() != null ? insert.getUuidName() : SouthboundMapper.getRandomUUID(); insert.setUuidName(uuidString); return new UUID(uuidString); } public static > void stampInstanceIdentifier(TransactionBuilder transaction, InstanceIdentifier iid, TableSchema tableSchema, ColumnSchema> columnSchema) { transaction.add(stampInstanceIdentifierMutation(transaction,iid, tableSchema,columnSchema)); } public static > Mutate stampInstanceIdentifierMutation(TransactionBuilder transaction, InstanceIdentifier iid, TableSchema tableSchema, ColumnSchema> columnSchema) { Map externalIdsMap = ImmutableMap.of(SouthboundConstants.IID_EXTERNAL_ID_KEY, SouthboundUtil.serializeInstanceIdentifier(iid)); Mutate mutate = op.mutate(tableSchema) .addMutation(columnSchema, Mutator.INSERT, externalIdsMap); Mutation deleteIidMutation = new Mutation(columnSchema.getName(), Mutator.DELETE, OvsdbSet.fromSet(Sets.newHashSet(SouthboundConstants.IID_EXTERNAL_ID_KEY))); List mutations = Lists.newArrayList(Sets.newHashSet(deleteIidMutation)); mutations.addAll(mutate.getMutations()); mutate.setMutations(mutations); return mutate; } /** * This method builds a string by concatenating the 2 character * hexadecimal representation of each byte from the input byte array. * * For example: an input byte array containing: * bytes[0] = 'a' * bytes[1] = 'b' * bytes[2] = 'c' * bytes[3] = '-' * bytes[4] = '1' * bytes[5] = '2' * bytes[6] = '3' * returns the string "6162632d313233" * * @param bytes * The byte array to convert to string * @return The hexadecimal representation of the byte array. If bytes is * null, the string "" is returned */ public static String bytesToHexString(byte[] bytes) { if (bytes == null) { return ""; } StringBuffer buf = new StringBuffer(); for (int i = 0; i < bytes.length; i++) { short u8byte = (short) (bytes[i] & 0xff); String tmp = Integer.toHexString(u8byte); if (tmp.length() == 1) { buf.append("0"); } buf.append(tmp); } return buf.toString(); } }