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 */
018package org.apache.hadoop.hbase.master.procedure;
019
020import java.io.IOException;
021import java.util.Optional;
022import java.util.function.Supplier;
023import java.util.regex.Pattern;
024import org.apache.hadoop.hbase.DoNotRetryIOException;
025import org.apache.hadoop.hbase.NamespaceDescriptor;
026import org.apache.hadoop.hbase.TableName;
027import org.apache.hadoop.hbase.constraint.ConstraintException;
028import org.apache.hadoop.hbase.master.MasterServices;
029import org.apache.hadoop.hbase.procedure2.Procedure;
030import org.apache.hadoop.hbase.procedure2.ProcedureException;
031import org.apache.hadoop.hbase.procedure2.ProcedureExecutor;
032import org.apache.hadoop.hbase.rsgroup.RSGroupInfo;
033import org.apache.hadoop.hbase.security.User;
034import org.apache.hadoop.hbase.util.NonceKey;
035import org.apache.hadoop.security.UserGroupInformation;
036import org.apache.yetus.audience.InterfaceAudience;
037import org.apache.yetus.audience.InterfaceStability;
038
039import org.apache.hadoop.hbase.shaded.protobuf.generated.RPCProtos.UserInformation;
040
041@InterfaceAudience.Private
042@InterfaceStability.Evolving
043public final class MasterProcedureUtil {
044
045  private MasterProcedureUtil() {
046  }
047
048  public static UserInformation toProtoUserInfo(User user) {
049    UserInformation.Builder userInfoPB = UserInformation.newBuilder();
050    userInfoPB.setEffectiveUser(user.getName());
051    if (user.getUGI().getRealUser() != null) {
052      userInfoPB.setRealUser(user.getUGI().getRealUser().getUserName());
053    }
054    return userInfoPB.build();
055  }
056
057  public static User toUserInfo(UserInformation userInfoProto) {
058    if (userInfoProto.hasEffectiveUser()) {
059      String effectiveUser = userInfoProto.getEffectiveUser();
060      if (userInfoProto.hasRealUser()) {
061        String realUser = userInfoProto.getRealUser();
062        UserGroupInformation realUserUgi = UserGroupInformation.createRemoteUser(realUser);
063        return User.create(UserGroupInformation.createProxyUser(effectiveUser, realUserUgi));
064      }
065      return User.create(UserGroupInformation.createRemoteUser(effectiveUser));
066    }
067    return null;
068  }
069
070  /**
071   * Helper Runnable used in conjunction with submitProcedure() to deal with submitting procs with
072   * nonce. See submitProcedure() for an example.
073   */
074  public static abstract class NonceProcedureRunnable {
075    private final MasterServices master;
076    private final NonceKey nonceKey;
077    private Long procId;
078
079    public NonceProcedureRunnable(final MasterServices master, final long nonceGroup,
080      final long nonce) {
081      this.master = master;
082      this.nonceKey = getProcedureExecutor().createNonceKey(nonceGroup, nonce);
083    }
084
085    protected NonceKey getNonceKey() {
086      return nonceKey;
087    }
088
089    protected MasterServices getMaster() {
090      return master;
091    }
092
093    protected ProcedureExecutor<MasterProcedureEnv> getProcedureExecutor() {
094      return master.getMasterProcedureExecutor();
095    }
096
097    protected long getProcId() {
098      return procId != null ? procId.longValue() : -1;
099    }
100
101    protected long setProcId(final long procId) {
102      this.procId = procId;
103      return procId;
104    }
105
106    protected abstract void run() throws IOException;
107
108    protected abstract String getDescription();
109
110    protected long submitProcedure(final Procedure<MasterProcedureEnv> proc) {
111      assert procId == null : "submitProcedure() was already called, running procId=" + procId;
112      procId = getProcedureExecutor().submitProcedure(proc, nonceKey);
113      return procId;
114    }
115  }
116
117  /**
118   * Helper used to deal with submitting procs with nonce. Internally the
119   * NonceProcedureRunnable.run() will be called only if no one else registered the nonce. any
120   * Exception thrown by the run() method will be collected/handled and rethrown. <code>
121   * long procId = MasterProcedureUtil.submitProcedure(
122   *      new NonceProcedureRunnable(procExec, nonceGroup, nonce) {
123   *   {@literal @}Override
124   *   public void run() {
125   *     cpHost.preOperation();
126   *     submitProcedure(new MyProc());
127   *     cpHost.postOperation();
128   *   }
129   * });
130   * </code>
131   */
132  public static long submitProcedure(final NonceProcedureRunnable runnable) throws IOException {
133    final ProcedureExecutor<MasterProcedureEnv> procExec = runnable.getProcedureExecutor();
134    final long procId = procExec.registerNonce(runnable.getNonceKey());
135    if (procId >= 0) return procId; // someone already registered the nonce
136    try {
137      runnable.run();
138    } catch (IOException e) {
139      procExec.setFailureResultForNonce(runnable.getNonceKey(), runnable.getDescription(),
140        procExec.getEnvironment().getRequestUser(), e);
141      throw e;
142    } finally {
143      procExec.unregisterNonceIfProcedureWasNotSubmitted(runnable.getNonceKey());
144    }
145    return runnable.getProcId();
146  }
147
148  /**
149   * Pattern used to validate a Procedure WAL file name see
150   * {@link #validateProcedureWALFilename(String)} for description.
151   * @deprecated Since 2.3.0, will be removed in 4.0.0. We do not use this style of procedure wal
152   *             file name any more.
153   */
154  @Deprecated
155  private static final Pattern PATTERN = Pattern.compile(".*pv2-\\d{20}.log");
156
157  /**
158   * A Procedure WAL file name is of the format: pv-&lt;wal-id&gt;.log where wal-id is 20 digits.
159   * @param filename name of the file to validate
160   * @return <tt>true</tt> if the filename matches a Procedure WAL, <tt>false</tt> otherwise
161   */
162  public static boolean validateProcedureWALFilename(String filename) {
163    return PATTERN.matcher(filename).matches();
164  }
165
166  /**
167   * Return the priority for the given table. Now meta table is 3, other system tables are 2, and
168   * user tables are 1.
169   */
170  public static int getTablePriority(TableName tableName) {
171    if (TableName.isMetaTableName(tableName)) {
172      return 3;
173    } else if (tableName.isSystemTable()) {
174      return 2;
175    } else {
176      return 1;
177    }
178  }
179
180  /**
181   * Return the priority for the given procedure. For now we only have two priorities, 100 for
182   * server carrying meta, and 1 for others.
183   */
184  public static int getServerPriority(ServerProcedureInterface proc) {
185    return proc.hasMetaTableRegion() ? 100 : 1;
186  }
187
188  /**
189   * This is a version of unwrapRemoteIOException that can do DoNotRetryIOE. We need to throw DNRIOE
190   * to clients if a failed Procedure else they will keep trying. The default
191   * proc.getException().unwrapRemoteException doesn't have access to DNRIOE from the procedure2
192   * module.
193   */
194  public static IOException unwrapRemoteIOException(Procedure<?> proc) {
195    Exception e = proc.getException().unwrapRemoteException();
196    // Do not retry ProcedureExceptions!
197    return (e instanceof ProcedureException)
198      ? new DoNotRetryIOException(e)
199      : proc.getException().unwrapRemoteIOException();
200  }
201
202  /**
203   * Do not allow creating new tables/namespaces which has an empty rs group, expect the default rs
204   * group. Notice that we do not check for online servers, as this is not stable because region
205   * servers can die at any time.
206   */
207  public static void checkGroupNotEmpty(RSGroupInfo rsGroupInfo, Supplier<String> forWhom)
208    throws ConstraintException {
209    if (rsGroupInfo == null || rsGroupInfo.getName().equals(RSGroupInfo.DEFAULT_GROUP)) {
210      // we do not have a rs group config or we explicitly set the rs group to default, then no need
211      // to check.
212      return;
213    }
214    if (rsGroupInfo.getServers().isEmpty()) {
215      throw new ConstraintException(
216        "No servers in the rsgroup " + rsGroupInfo.getName() + " for " + forWhom.get());
217    }
218  }
219
220  @FunctionalInterface
221  public interface RSGroupGetter {
222    RSGroupInfo get(String groupName) throws IOException;
223  }
224
225  public static RSGroupInfo checkGroupExists(RSGroupGetter getter, Optional<String> optGroupName,
226    Supplier<String> forWhom) throws IOException {
227    if (optGroupName.isPresent()) {
228      String groupName = optGroupName.get();
229      RSGroupInfo group = getter.get(groupName);
230      if (group == null) {
231        throw new ConstraintException(
232          "Region server group " + groupName + " for " + forWhom.get() + " does not exit");
233      }
234      return group;
235    }
236    return null;
237  }
238
239  public static Optional<String> getNamespaceGroup(NamespaceDescriptor namespaceDesc) {
240    return Optional
241      .ofNullable(namespaceDesc.getConfigurationValue(RSGroupInfo.NAMESPACE_DESC_PROP_GROUP));
242  }
243}