001/**
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *      http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018
019package org.apache.oozie.dependency.hcat;
020
021import java.io.IOException;
022import java.net.InetAddress;
023import java.net.URI;
024import java.net.URISyntaxException;
025import java.util.ArrayList;
026import java.util.Collection;
027import java.util.Collections;
028import java.util.HashMap;
029import java.util.HashSet;
030import java.util.Iterator;
031import java.util.List;
032import java.util.Map;
033import java.util.Map.Entry;
034import java.util.Set;
035import java.util.concurrent.ConcurrentHashMap;
036import java.util.concurrent.ConcurrentMap;
037import org.apache.hadoop.conf.Configuration;
038import org.apache.oozie.service.ConfigurationService;
039import org.apache.oozie.service.HCatAccessorService;
040import org.apache.oozie.service.Services;
041import org.apache.oozie.util.HCatURI;
042import org.apache.oozie.util.XLog;
043
044public class SimpleHCatDependencyCache implements HCatDependencyCache {
045
046    private static XLog LOG = XLog.getLog(SimpleHCatDependencyCache.class);
047    private static String DELIMITER = ";";
048
049    public static final String USE_CANONICAL_HOSTNAME ="oozie.service.HCatAccessorService.jms.use.canonical.hostname";
050    private boolean useCanonicalHostName = false;
051
052
053
054    /**
055     * Map of server;db;table - sorter partition key order (country;dt;state) - sorted partition
056     * value (us;20120101;CA) - Collection of waiting actions (actionID and original hcat uri as
057     * string).
058     */
059    private ConcurrentMap<String, ConcurrentMap<String, Map<String, Collection<WaitingAction>>>> missingDeps;
060
061    /**
062     * Map of actionIDs and collection of available URIs
063     */
064    private ConcurrentMap<String, Collection<String>> availableDeps;
065
066    /**
067     * Map of actionIDs and partitions for reverse-lookup in purging
068     */
069    private ConcurrentMap<String, ConcurrentMap<String, Collection<String>>> actionPartitionMap;
070
071    @Override
072    public void init(Configuration conf) {
073        missingDeps = new ConcurrentHashMap<String, ConcurrentMap<String, Map<String, Collection<WaitingAction>>>>();
074        availableDeps = new ConcurrentHashMap<String, Collection<String>>();
075        actionPartitionMap = new ConcurrentHashMap<String, ConcurrentMap<String, Collection<String>>>();
076        useCanonicalHostName = ConfigurationService.getBoolean(USE_CANONICAL_HOSTNAME);
077    }
078
079    @Override
080    public void addMissingDependency(HCatURI hcatURI, String actionID) {
081        String tableKey = canonicalizeHostname(hcatURI.getServer()) + DELIMITER + hcatURI.getDb() + DELIMITER
082                + hcatURI.getTable();
083        SortedPKV sortedPKV = new SortedPKV(hcatURI.getPartitionMap());
084        // Partition keys seperated by ;. For eg: date;country;state
085        String partKey = sortedPKV.getPartKeys();
086        // Partition values seperated by ;. For eg: 20120101;US;CA
087        String partVal = sortedPKV.getPartVals();
088        ConcurrentMap<String, Map<String, Collection<WaitingAction>>> partKeyPatterns = missingDeps.get(tableKey);
089        if (partKeyPatterns == null) {
090            partKeyPatterns = new ConcurrentHashMap<String, Map<String, Collection<WaitingAction>>>();
091            ConcurrentMap<String, Map<String, Collection<WaitingAction>>> existingMap = missingDeps.putIfAbsent(
092                    tableKey, partKeyPatterns);
093            if (existingMap != null) {
094                partKeyPatterns = existingMap;
095            }
096        }
097        ConcurrentMap<String, Collection<String>> partitionMap = actionPartitionMap.get(actionID);
098        if (partitionMap == null) {
099            partitionMap = new ConcurrentHashMap<String, Collection<String>>();
100            ConcurrentMap<String, Collection<String>> existingPartMap = actionPartitionMap.putIfAbsent(actionID,
101                    partitionMap);
102            if (existingPartMap != null) {
103                partitionMap = existingPartMap;
104            }
105        }
106        synchronized (partitionMap) {
107            Collection<String> partKeys = partitionMap.get(tableKey);
108            if (partKeys == null) {
109                partKeys = new ArrayList<String>();
110            }
111            partKeys.add(partKey);
112            partitionMap.put(tableKey, partKeys);
113        }
114        synchronized (partKeyPatterns) {
115            missingDeps.put(tableKey, partKeyPatterns); // To handle race condition with removal of partKeyPatterns
116            Map<String, Collection<WaitingAction>> partValues = partKeyPatterns.get(partKey);
117            if (partValues == null) {
118                partValues = new HashMap<String, Collection<WaitingAction>>();
119                partKeyPatterns.put(partKey, partValues);
120            }
121            Collection<WaitingAction> waitingActions = partValues.get(partVal);
122            if (waitingActions == null) {
123                waitingActions = new HashSet<WaitingAction>();
124                partValues.put(partVal, waitingActions);
125            }
126            waitingActions.add(new WaitingAction(actionID, hcatURI.toURIString()));
127        }
128    }
129
130    @Override
131    public boolean removeMissingDependency(HCatURI hcatURI, String actionID) {
132        String tableKey = canonicalizeHostname(hcatURI.getServer()) + DELIMITER + hcatURI.getDb() + DELIMITER
133                + hcatURI.getTable();
134        SortedPKV sortedPKV = new SortedPKV(hcatURI.getPartitionMap());
135        String partKey = sortedPKV.getPartKeys();
136        String partVal = sortedPKV.getPartVals();
137        Map<String, Map<String, Collection<WaitingAction>>> partKeyPatterns = missingDeps.get(tableKey);
138        if (partKeyPatterns == null) {
139            LOG.warn("Remove missing dependency - Missing table entry - uri={0}, actionID={1}",
140                    hcatURI.toURIString(), actionID);
141            return false;
142        }
143        ConcurrentMap<String, Collection<String>> partitionMap = actionPartitionMap.get(actionID);
144        if (partitionMap != null) {
145            synchronized (partitionMap) {
146                Collection<String> partKeys = partitionMap.get(tableKey);
147                if (partKeys != null) {
148                    partKeys.remove(partKey);
149                }
150                if (partKeys.size() == 0) {
151                    partitionMap.remove(tableKey);
152                }
153                if (partitionMap.size() == 0) {
154                    actionPartitionMap.remove(actionID);
155                }
156            }
157        }
158
159        synchronized(partKeyPatterns) {
160            Map<String, Collection<WaitingAction>> partValues = partKeyPatterns.get(partKey);
161            if (partValues == null) {
162                LOG.warn("Remove missing dependency - Missing partition pattern - uri={0}, actionID={1}",
163                        hcatURI.toURIString(), actionID);
164                return false;
165            }
166            Collection<WaitingAction> waitingActions = partValues.get(partVal);
167            if (waitingActions == null) {
168                LOG.warn("Remove missing dependency - Missing partition value - uri={0}, actionID={1}",
169                        hcatURI.toURIString(), actionID);
170                return false;
171            }
172            boolean removed = waitingActions.remove(new WaitingAction(actionID, hcatURI.toURIString()));
173            if (!removed) {
174                LOG.warn("Remove missing dependency - Missing action ID - uri={0}, actionID={1}",
175                        hcatURI.toURIString(), actionID);
176            }
177            if (waitingActions.isEmpty()) {
178                partValues.remove(partVal);
179                if (partValues.isEmpty()) {
180                    partKeyPatterns.remove(partKey);
181                }
182                if (partKeyPatterns.isEmpty()) {
183                    missingDeps.remove(tableKey);
184                    // Close JMS session. Stop listening on topic
185                    HCatAccessorService hcatService = Services.get().get(HCatAccessorService.class);
186                    hcatService.unregisterFromNotification(hcatURI);
187                }
188            }
189            return removed;
190        }
191    }
192
193    @Override
194    public Collection<String> getWaitingActions(HCatURI hcatURI) {
195        String tableKey = canonicalizeHostname(hcatURI.getServer()) + DELIMITER + hcatURI.getDb() + DELIMITER
196                + hcatURI.getTable();
197        SortedPKV sortedPKV = new SortedPKV(hcatURI.getPartitionMap());
198        String partKey = sortedPKV.getPartKeys();
199        String partVal = sortedPKV.getPartVals();
200        Map<String, Map<String, Collection<WaitingAction>>> partKeyPatterns = missingDeps.get(tableKey);
201        if (partKeyPatterns == null) {
202            return null;
203        }
204        Map<String, Collection<WaitingAction>> partValues = partKeyPatterns.get(partKey);
205        if (partValues == null) {
206            return null;
207        }
208        Collection<WaitingAction> waitingActions = partValues.get(partVal);
209        if (waitingActions == null) {
210            return null;
211        }
212        Collection<String> actionIDs = new ArrayList<String>();
213        URI uri = hcatURI.getURI();
214        String uriString = null;
215        try {
216            uriString = new URI(uri.getScheme(), canonicalizeHostname(uri.getAuthority()), uri.getPath(),
217                    uri.getQuery(), uri.getFragment()).toString();
218        }
219        catch (URISyntaxException e) {
220            uriString = hcatURI.toURIString();
221        }
222
223        for (WaitingAction action : waitingActions) {
224            if (action.getDependencyURI().equals(uriString)) {
225                actionIDs.add(action.getActionID());
226            }
227        }
228        return actionIDs;
229    }
230
231    @Override
232    public Collection<String> markDependencyAvailable(String server, String db, String table,
233            Map<String, String> partitions) {
234        String tableKey = canonicalizeHostname(server) + DELIMITER + db + DELIMITER + table;
235        Map<String, Map<String, Collection<WaitingAction>>> partKeyPatterns = missingDeps.get(tableKey);
236        if (partKeyPatterns == null) {
237            LOG.warn("Got partition available notification for " + tableKey
238                    + ". Unexpected and should not be listening to topic. Unregistering topic");
239            HCatAccessorService hcatService = Services.get().get(HCatAccessorService.class);
240            hcatService.unregisterFromNotification(server, db, table);
241            return null;
242        }
243        Collection<String> actionsWithAvailDep = new HashSet<String>();
244        List<String> partKeysToRemove = new ArrayList<String>();
245        StringBuilder partValSB = new StringBuilder();
246        synchronized (partKeyPatterns) {
247            // If partition patterns are date, date;country and date;country;state,
248            // construct the partition values for each pattern from the available partitions map and
249            // for the matching value in the dependency map, get the waiting actions.
250            for (Entry<String, Map<String, Collection<WaitingAction>>> entry : partKeyPatterns.entrySet()) {
251                String[] partKeys = entry.getKey().split(DELIMITER);
252                partValSB.setLength(0);
253                for (String key : partKeys) {
254                    partValSB.append(partitions.get(key)).append(DELIMITER);
255                }
256                partValSB.setLength(partValSB.length() - 1);
257
258                Map<String, Collection<WaitingAction>> partValues = entry.getValue();
259                String partVal = partValSB.toString();
260                Collection<WaitingAction> wActions = entry.getValue().get(partVal);
261                if (wActions == null)
262                    continue;
263                for (WaitingAction wAction : wActions) {
264                    String actionID = wAction.getActionID();
265                    actionsWithAvailDep.add(actionID);
266                    Collection<String> depURIs = availableDeps.get(actionID);
267                    if (depURIs == null) {
268                        depURIs = new ArrayList<String>();
269                        Collection<String> existing = availableDeps.putIfAbsent(actionID, depURIs);
270                        if (existing != null) {
271                            depURIs = existing;
272                        }
273                    }
274                    synchronized (depURIs) {
275                        depURIs.add(wAction.getDependencyURI());
276                        availableDeps.put(actionID, depURIs);
277                    }
278                }
279                partValues.remove(partVal);
280                if (partValues.isEmpty()) {
281                    partKeysToRemove.add(entry.getKey());
282                }
283            }
284            for (String partKey : partKeysToRemove) {
285                partKeyPatterns.remove(partKey);
286            }
287            if (partKeyPatterns.isEmpty()) {
288                missingDeps.remove(tableKey);
289                // Close JMS session. Stop listening on topic
290                HCatAccessorService hcatService = Services.get().get(HCatAccessorService.class);
291                hcatService.unregisterFromNotification(server, db, table);
292            }
293        }
294        return actionsWithAvailDep;
295    }
296
297    @Override
298    public Collection<String> getAvailableDependencyURIs(String actionID) {
299        Collection<String> available = availableDeps.get(actionID);
300        if (available !=  null) {
301            // Return a copy
302            available = new ArrayList<String>(available);
303        }
304        return available;
305    }
306
307    @Override
308    public boolean removeAvailableDependencyURIs(String actionID, Collection<String> dependencyURIs) {
309        if (!availableDeps.containsKey(actionID)) {
310            return false;
311        }
312        else {
313            Collection<String> availList = availableDeps.get(actionID);
314            if (!availList.removeAll(dependencyURIs)) {
315                return false;
316            }
317            synchronized (availList) {
318                if (availList.isEmpty()) {
319                    availableDeps.remove(actionID);
320                }
321            }
322        }
323        return true;
324    }
325
326    @Override
327    public void destroy() {
328        missingDeps.clear();
329        availableDeps.clear();
330    }
331
332    private static class SortedPKV {
333        private StringBuilder partKeys;
334        private StringBuilder partVals;
335
336        public SortedPKV(Map<String, String> partitions) {
337            this.partKeys = new StringBuilder();
338            this.partVals = new StringBuilder();
339            ArrayList<String> keys = new ArrayList<String>(partitions.keySet());
340            Collections.sort(keys);
341            for (String key : keys) {
342                this.partKeys.append(key).append(DELIMITER);
343                this.partVals.append(partitions.get(key)).append(DELIMITER);
344            }
345            this.partKeys.setLength(partKeys.length() - 1);
346            this.partVals.setLength(partVals.length() - 1);
347        }
348
349        public String getPartKeys() {
350            return partKeys.toString();
351        }
352
353        public String getPartVals() {
354            return partVals.toString();
355        }
356    }
357
358    private HCatURI removePartitions(String coordActionId, Collection<String> partKeys,
359            Map<String, Map<String, Collection<WaitingAction>>> partKeyPatterns) {
360        HCatURI hcatUri = null;
361        for (String partKey : partKeys) {
362            Map<String, Collection<WaitingAction>> partValues = partKeyPatterns.get(partKey);
363            Iterator<String> partValItr = partValues.keySet().iterator();
364            while (partValItr.hasNext()) {
365                String partVal = partValItr.next();
366                Collection<WaitingAction> waitingActions = partValues.get(partVal);
367                if (waitingActions != null) {
368                    Iterator<WaitingAction> waitItr = waitingActions.iterator();
369                    while (waitItr.hasNext()) {
370                        WaitingAction waction = waitItr.next();
371                        if (coordActionId.contains(waction.getActionID())) {
372                            waitItr.remove();
373                            if (hcatUri == null) {
374                                try {
375                                    hcatUri = new HCatURI(waction.getDependencyURI());
376                                }
377                                catch (URISyntaxException e) {
378                                    continue;
379                                }
380                            }
381                        }
382                    }
383                }
384                // delete partition value with no waiting actions
385                if (waitingActions.size() == 0) {
386                    partValItr.remove();
387                }
388            }
389            if (partValues.size() == 0) {
390                partKeyPatterns.remove(partKey);
391            }
392        }
393        return hcatUri;
394    }
395
396    @Override
397    public void removeNonWaitingCoordActions(Set<String> coordActions) {
398        HCatAccessorService hcatService = Services.get().get(HCatAccessorService.class);
399        for (String coordActionId : coordActions) {
400            LOG.info("Removing non waiting coord action {0} from partition dependency map", coordActionId);
401            synchronized (actionPartitionMap) {
402                Map<String, Collection<String>> partitionMap = actionPartitionMap.get(coordActionId);
403                if (partitionMap != null) {
404                    Iterator<String> tableItr = partitionMap.keySet().iterator();
405                    while (tableItr.hasNext()) {
406                        String tableKey = tableItr.next();
407                        HCatURI hcatUri = null;
408                        Map<String, Map<String, Collection<WaitingAction>>> partKeyPatterns = missingDeps.get(tableKey);
409                        if (partKeyPatterns != null) {
410                            synchronized (partKeyPatterns) {
411                                Collection<String> partKeys = partitionMap.get(tableKey);
412                                if (partKeys != null) {
413                                    hcatUri = removePartitions(coordActionId, partKeys, partKeyPatterns);
414                                }
415                            }
416                            if (partKeyPatterns.size() == 0) {
417                                tableItr.remove();
418                                if (hcatUri != null) {
419                                    // Close JMS session. Stop listening on topic
420                                    hcatService.unregisterFromNotification(hcatUri);
421                                }
422                            }
423                        }
424                    }
425                }
426                actionPartitionMap.remove(coordActionId);
427            }
428        }
429    }
430
431    @Override
432    public void removeCoordActionWithDependenciesAvailable(String coordAction) {
433        actionPartitionMap.remove(coordAction);
434    }
435
436    public String canonicalizeHostname(String name) {
437        return canonicalizeHostname(name, useCanonicalHostName);
438    }
439
440    public static String canonicalizeHostname(String name, boolean useCanonicalHostName) {
441        if (useCanonicalHostName) {
442            String hostname = name;
443            String port = null;
444            if (name.contains(":")) {
445                hostname = name.split(":")[0];
446                port = name.split(":")[1];
447            }
448            try {
449                InetAddress address = InetAddress.getByName(hostname);
450                String canonicalHostName = address.getCanonicalHostName();
451                if (null != port) {
452                    return canonicalHostName + ":" + port;
453                }
454                return canonicalHostName;
455            }
456            catch (IOException ex) {
457                LOG.error(ex);
458                return name;
459            }
460        }
461        else {
462            return name;
463        }
464    }
465}