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
019
020package org.apache.oozie.service;
021
022import java.text.SimpleDateFormat;
023import java.util.Date;
024import java.util.concurrent.TimeUnit;
025
026import org.apache.curator.RetryPolicy;
027import org.apache.curator.framework.recipes.atomic.AtomicValue;
028import org.apache.curator.framework.recipes.atomic.DistributedAtomicLong;
029import org.apache.curator.framework.recipes.atomic.PromotedToLock;
030import org.apache.curator.retry.RetryNTimes;
031import org.apache.oozie.ErrorCode;
032import org.apache.oozie.lock.LockToken;
033import org.apache.oozie.util.XLog;
034import org.apache.oozie.util.ZKUtils;
035
036import com.google.common.annotations.VisibleForTesting;
037
038/**
039 * Service that provides distributed job id sequence via ZooKeeper. Requires that a ZooKeeper ensemble is available. The
040 * sequence path will be located under a ZNode named "job_id_sequence" under the namespace (see {@link ZKUtils}). The
041 * sequence will be reset to 0, once max is reached.
042 */
043
044public class ZKUUIDService extends UUIDService {
045
046    public static final String CONF_PREFIX = Service.CONF_PREFIX + "ZKUUIDService.";
047
048    public static final String CONF_SEQUENCE_MAX = CONF_PREFIX + "jobid.sequence.max";
049    public static final String LOCKS_NODE = "/SEQUENCE_LOCK";
050
051    public static final String ZK_SEQUENCE_PATH = "/job_id_sequence";
052
053    public static final long RESET_VALUE = 0L;
054    public static final int RETRY_COUNT = 3;
055
056    private final static XLog LOG = XLog.getLog(ZKUUIDService.class);
057
058    private ZKUtils zk;
059    private static Long maxSequence = 9999990L;
060
061    DistributedAtomicLong atomicIdGenerator;
062
063    public static final ThreadLocal<SimpleDateFormat> dt = new ThreadLocal<SimpleDateFormat>() {
064        @Override
065        protected SimpleDateFormat initialValue() {
066            return new SimpleDateFormat("yyMMddHHmmssSSS");
067        }
068    };
069
070
071    @Override
072    public void init(Services services) throws ServiceException {
073
074        super.init(services);
075        try {
076            zk = ZKUtils.register(this);
077            PromotedToLock.Builder lockBuilder = PromotedToLock.builder().lockPath(getPromotedLock())
078                    .retryPolicy(getRetryPolicy()).timeout(Service.lockTimeout, TimeUnit.MILLISECONDS);
079            atomicIdGenerator = new DistributedAtomicLong(zk.getClient(), ZK_SEQUENCE_PATH, getRetryPolicy(),
080                    lockBuilder.build());
081
082        }
083        catch (Exception ex) {
084            throw new ServiceException(ErrorCode.E1700, ex.getMessage(), ex);
085        }
086
087    }
088
089    /**
090     * Gets the unique id.
091     *
092     * @return the id
093     * @throws Exception the exception
094     */
095    @Override
096    protected String createSequence() {
097        String localStartTime = super.startTime;
098        long id = 0L;
099        try {
100            id = getZKSequence();
101        }
102        catch (Exception e) {
103            LOG.error("Error getting jobId, switching to old UUIDService", e);
104            id = super.getCounter();
105            localStartTime = dt.get().format(new Date());
106        }
107        return appendTimeToSequence(id, localStartTime);
108    }
109
110    protected synchronized long getZKSequence() throws Exception {
111        long id = getDistributedSequence();
112
113        if (id >= maxSequence) {
114            resetSequence();
115            id = getDistributedSequence();
116        }
117        return id;
118    }
119
120    @SuppressWarnings("finally")
121    private long getDistributedSequence() throws Exception {
122        if (atomicIdGenerator == null) {
123            throw new Exception("Sequence generator can't be null. Path : " + ZK_SEQUENCE_PATH);
124        }
125        AtomicValue<Long> value = null;
126        try {
127            value = atomicIdGenerator.increment();
128        }
129        catch (Exception e) {
130            throw new Exception("Exception incrementing UID for session ", e);
131        }
132        finally {
133            if (value != null && value.succeeded()) {
134                return value.preValue();
135            }
136            else {
137                throw new Exception("Exception incrementing UID for session ");
138            }
139        }
140    }
141
142    /**
143     * Once sequence is reached limit, reset to 0.
144     *
145     * @throws Exception
146     */
147    private  void resetSequence() throws Exception {
148        for (int i = 0; i < RETRY_COUNT; i++) {
149            AtomicValue<Long> value = atomicIdGenerator.get();
150            if (value.succeeded()) {
151                if (value.postValue() < maxSequence) {
152                    return;
153                }
154            }
155            // Acquire ZK lock, so that other host doesn't reset sequence.
156            LockToken lock = null;
157            try {
158                lock = Services.get().get(MemoryLocksService.class)
159                        .getWriteLock(ZKUUIDService.class.getName(), lockTimeout);
160            }
161            catch (InterruptedException e1) {
162                //ignore
163            }
164            try {
165                if (lock == null) {
166                    LOG.info("Lock is held by other system, will sleep and try again");
167                    Thread.sleep(1000);
168                    continue;
169                }
170                else {
171                    value = atomicIdGenerator.get();
172                    if (value.succeeded()) {
173                        if (value.postValue() < maxSequence) {
174                            return;
175                        }
176                    }
177                    try {
178                        atomicIdGenerator.forceSet(RESET_VALUE);
179                    }
180                    catch (Exception e) {
181                        LOG.info("Exception while resetting sequence, will try again");
182                        continue;
183                    }
184                    resetStartTime();
185                    return;
186                }
187            }
188            finally {
189                if (lock != null) {
190                    lock.release();
191                }
192            }
193        }
194        throw new Exception("Can't reset ID sequence in ZK. Retried " + RETRY_COUNT + " times");
195    }
196
197    @Override
198    public void destroy() {
199        if (zk != null) {
200            zk.unregister(this);
201        }
202        zk = null;
203        super.destroy();
204    }
205
206    public String getPromotedLock() {
207        if (ZKUtils.getZKNameSpace().startsWith("/")) {
208            return ZKUtils.getZKNameSpace() + LOCKS_NODE;
209
210        }
211        else {
212            return "/" + ZKUtils.getZKNameSpace() + LOCKS_NODE;
213        }
214    }
215
216    @VisibleForTesting
217    public void setMaxSequence(long sequence) {
218        maxSequence = sequence;
219    }
220
221    /**
222     * Retries 25 times with delay of 200ms
223     *
224     * @return RetryNTimes
225     */
226    private static RetryPolicy getRetryPolicy() {
227        return new RetryNTimes(25, 200);
228    }
229
230}