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.thrift;
019
020import java.nio.ByteBuffer;
021import java.security.PrivilegedExceptionAction;
022import java.text.NumberFormat;
023import java.util.ArrayList;
024import java.util.HashMap;
025import java.util.List;
026import java.util.Map;
027import javax.security.auth.Subject;
028import javax.security.auth.login.LoginContext;
029import javax.security.sasl.Sasl;
030import org.apache.hadoop.hbase.thrift.generated.AlreadyExists;
031import org.apache.hadoop.hbase.thrift.generated.ColumnDescriptor;
032import org.apache.hadoop.hbase.thrift.generated.Hbase;
033import org.apache.hadoop.hbase.thrift.generated.Mutation;
034import org.apache.hadoop.hbase.thrift.generated.TCell;
035import org.apache.hadoop.hbase.thrift.generated.TRowResult;
036import org.apache.hadoop.hbase.util.Bytes;
037import org.apache.hadoop.hbase.util.ClientUtils;
038import org.apache.yetus.audience.InterfaceAudience;
039
040import org.apache.hbase.thirdparty.org.apache.thrift.protocol.TBinaryProtocol;
041import org.apache.hbase.thirdparty.org.apache.thrift.protocol.TProtocol;
042import org.apache.hbase.thirdparty.org.apache.thrift.transport.TSaslClientTransport;
043import org.apache.hbase.thirdparty.org.apache.thrift.transport.TSocket;
044import org.apache.hbase.thirdparty.org.apache.thrift.transport.TTransport;
045
046/**
047 * See the instructions under hbase-examples/README.txt
048 */
049@InterfaceAudience.Private
050public class DemoClient {
051
052  static protected int port;
053  static protected String host;
054
055  private static boolean secure = false;
056  private static String serverPrincipal = "hbase";
057
058  public static void main(String[] args) throws Exception {
059    if (args.length < 2 || args.length > 4 || (args.length > 2 && !isBoolean(args[2]))) {
060      System.out.println("Invalid arguments!");
061      System.out.println("Usage: DemoClient host port [secure=false [server-principal=hbase] ]");
062
063      System.exit(-1);
064    }
065
066    port = Integer.parseInt(args[1]);
067    host = args[0];
068
069    if (args.length > 2) {
070      secure = Boolean.parseBoolean(args[2]);
071    }
072
073    if (args.length == 4) {
074      serverPrincipal = args[3];
075    }
076
077    final DemoClient client = new DemoClient();
078    Subject.doAs(getSubject(), new PrivilegedExceptionAction<Void>() {
079      @Override
080      public Void run() throws Exception {
081        client.run();
082        return null;
083      }
084    });
085  }
086
087  private static boolean isBoolean(String s) {
088    return Boolean.TRUE.toString().equalsIgnoreCase(s)
089      || Boolean.FALSE.toString().equalsIgnoreCase(s);
090  }
091
092  DemoClient() {
093  }
094
095  // Helper to translate strings to UTF8 bytes
096  private byte[] bytes(String s) {
097    return Bytes.toBytes(s);
098  }
099
100  private void run() throws Exception {
101    TTransport transport = new TSocket(host, port);
102    if (secure) {
103      Map<String, String> saslProperties = new HashMap<>();
104      saslProperties.put(Sasl.QOP, "auth-conf,auth-int,auth");
105      /*
106       * The Thrift server the DemoClient is trying to connect to must have a matching principal,
107       * and support authentication. The HBase cluster must be secure, allow proxy user.
108       */
109      transport = new TSaslClientTransport("GSSAPI", null, serverPrincipal, // Thrift server user
110                                                                            // name, should be an
111                                                                            // authorized proxy
112                                                                            // user.
113        host, // Thrift server domain
114        saslProperties, null, transport);
115    }
116
117    transport.open();
118
119    TProtocol protocol = new TBinaryProtocol(transport, true, true);
120    Hbase.Client client = new Hbase.Client(protocol);
121
122    ByteBuffer demoTable = ByteBuffer.wrap(bytes("demo_table"));
123    ByteBuffer disabledTable = ByteBuffer.wrap(bytes("disabled_table"));
124
125    // Scan all tables, look for the demo table and delete it.
126    System.out.println("scanning tables...");
127
128    for (ByteBuffer name : client.getTableNames()) {
129      System.out.println("  found: " + ClientUtils.utf8(name));
130
131      if (name.equals(demoTable) || name.equals(disabledTable)) {
132        if (client.isTableEnabled(name)) {
133          System.out.println("    disabling table: " + ClientUtils.utf8(name));
134          client.disableTable(name);
135        }
136
137        System.out.println("    deleting table: " + ClientUtils.utf8(name));
138        client.deleteTable(name);
139      }
140    }
141
142    // Create the demo table with two column families, entry: and unused:
143    List<ColumnDescriptor> columns = new ArrayList<>(2);
144    ColumnDescriptor col = new ColumnDescriptor();
145    col.name = ByteBuffer.wrap(bytes("entry:"));
146    col.timeToLive = Integer.MAX_VALUE;
147    col.maxVersions = 10;
148    columns.add(col);
149    col = new ColumnDescriptor();
150    col.name = ByteBuffer.wrap(bytes("unused:"));
151    col.timeToLive = Integer.MAX_VALUE;
152    columns.add(col);
153
154    System.out.println("creating table: " + ClientUtils.utf8(demoTable.array()));
155    try {
156      client.createTable(demoTable, columns);
157      client.createTable(disabledTable, columns);
158    } catch (AlreadyExists ae) {
159      System.out.println("WARN: " + ae.message);
160    }
161
162    System.out.println("column families in " + ClientUtils.utf8(demoTable.array()) + ": ");
163    Map<ByteBuffer, ColumnDescriptor> columnMap = client.getColumnDescriptors(demoTable);
164    for (ColumnDescriptor col2 : columnMap.values()) {
165      System.out.println(
166        "  column: " + ClientUtils.utf8(col2.name.array()) + ", maxVer: " + col2.maxVersions);
167    }
168
169    if (client.isTableEnabled(disabledTable)) {
170      System.out.println("disabling table: " + ClientUtils.utf8(disabledTable.array()));
171      client.disableTable(disabledTable);
172    }
173
174    System.out.println("list tables with enabled statuses : ");
175    Map<ByteBuffer, Boolean> statusMap = client.getTableNamesWithIsTableEnabled();
176    for (Map.Entry<ByteBuffer, Boolean> entry : statusMap.entrySet()) {
177      System.out.println(" Table: " + ClientUtils.utf8(entry.getKey().array()) + ", is enabled: "
178        + entry.getValue());
179    }
180
181    Map<ByteBuffer, ByteBuffer> dummyAttributes = null;
182    boolean writeToWal = false;
183
184    // Test UTF-8 handling
185    byte[] invalid = { (byte) 'f', (byte) 'o', (byte) 'o', (byte) '-', (byte) 0xfc, (byte) 0xa1,
186      (byte) 0xa1, (byte) 0xa1, (byte) 0xa1 };
187    byte[] valid = { (byte) 'f', (byte) 'o', (byte) 'o', (byte) '-', (byte) 0xE7, (byte) 0x94,
188      (byte) 0x9F, (byte) 0xE3, (byte) 0x83, (byte) 0x93, (byte) 0xE3, (byte) 0x83, (byte) 0xBC,
189      (byte) 0xE3, (byte) 0x83, (byte) 0xAB };
190
191    ArrayList<Mutation> mutations;
192    // non-utf8 is fine for data
193    mutations = new ArrayList<>(1);
194    mutations.add(new Mutation(false, ByteBuffer.wrap(bytes("entry:foo")), ByteBuffer.wrap(invalid),
195      writeToWal));
196    client.mutateRow(demoTable, ByteBuffer.wrap(bytes("foo")), mutations, dummyAttributes);
197
198    // this row name is valid utf8
199    mutations = new ArrayList<>(1);
200    mutations.add(
201      new Mutation(false, ByteBuffer.wrap(bytes("entry:foo")), ByteBuffer.wrap(valid), writeToWal));
202    client.mutateRow(demoTable, ByteBuffer.wrap(valid), mutations, dummyAttributes);
203
204    // non-utf8 is now allowed in row names because HBase stores values as binary
205    mutations = new ArrayList<>(1);
206    mutations.add(new Mutation(false, ByteBuffer.wrap(bytes("entry:foo")), ByteBuffer.wrap(invalid),
207      writeToWal));
208    client.mutateRow(demoTable, ByteBuffer.wrap(invalid), mutations, dummyAttributes);
209
210    // Run a scanner on the rows we just created
211    List<ByteBuffer> columnNames = new ArrayList<>();
212    columnNames.add(ByteBuffer.wrap(bytes("entry")));
213
214    System.out.println("Starting scanner...");
215    int scanner =
216      client.scannerOpen(demoTable, ByteBuffer.wrap(bytes("")), columnNames, dummyAttributes);
217
218    while (true) {
219      List<TRowResult> entry = client.scannerGet(scanner);
220
221      if (entry.isEmpty()) {
222        break;
223      }
224
225      printRow(entry);
226    }
227    System.out.println("Scanner finished...");
228
229    // Run some operations on a bunch of rows
230    for (int i = 100; i >= 0; --i) {
231      // format row keys as "00000" to "00100"
232      NumberFormat nf = NumberFormat.getInstance();
233      nf.setMinimumIntegerDigits(5);
234      nf.setGroupingUsed(false);
235      byte[] row = bytes(nf.format(i));
236
237      mutations = new ArrayList<>(1);
238      mutations.add(new Mutation(false, ByteBuffer.wrap(bytes("unused:")),
239        ByteBuffer.wrap(bytes("DELETE_ME")), writeToWal));
240      client.mutateRow(demoTable, ByteBuffer.wrap(row), mutations, dummyAttributes);
241      printRow(client.getRow(demoTable, ByteBuffer.wrap(row), dummyAttributes));
242      client.deleteAllRow(demoTable, ByteBuffer.wrap(row), dummyAttributes);
243
244      // sleep to force later timestamp
245      try {
246        Thread.sleep(50);
247      } catch (InterruptedException e) {
248        // no-op
249      }
250
251      mutations = new ArrayList<>(2);
252      mutations.add(new Mutation(false, ByteBuffer.wrap(bytes("entry:num")),
253        ByteBuffer.wrap(bytes("0")), writeToWal));
254      mutations.add(new Mutation(false, ByteBuffer.wrap(bytes("entry:foo")),
255        ByteBuffer.wrap(bytes("FOO")), writeToWal));
256      client.mutateRow(demoTable, ByteBuffer.wrap(row), mutations, dummyAttributes);
257      printRow(client.getRow(demoTable, ByteBuffer.wrap(row), dummyAttributes));
258
259      mutations = new ArrayList<>(2);
260      Mutation m = new Mutation();
261      m.column = ByteBuffer.wrap(bytes("entry:foo"));
262      m.isDelete = true;
263      mutations.add(m);
264      m = new Mutation();
265      m.column = ByteBuffer.wrap(bytes("entry:num"));
266      m.value = ByteBuffer.wrap(bytes("-1"));
267      mutations.add(m);
268      client.mutateRow(demoTable, ByteBuffer.wrap(row), mutations, dummyAttributes);
269      printRow(client.getRow(demoTable, ByteBuffer.wrap(row), dummyAttributes));
270
271      mutations = new ArrayList<>();
272      mutations.add(new Mutation(false, ByteBuffer.wrap(bytes("entry:num")),
273        ByteBuffer.wrap(bytes(Integer.toString(i))), writeToWal));
274      mutations.add(new Mutation(false, ByteBuffer.wrap(bytes("entry:sqr")),
275        ByteBuffer.wrap(bytes(Integer.toString(i * i))), writeToWal));
276      client.mutateRow(demoTable, ByteBuffer.wrap(row), mutations, dummyAttributes);
277      printRow(client.getRow(demoTable, ByteBuffer.wrap(row), dummyAttributes));
278
279      // sleep to force later timestamp
280      try {
281        Thread.sleep(50);
282      } catch (InterruptedException e) {
283        // no-op
284      }
285
286      mutations.clear();
287      m = new Mutation();
288      m.column = ByteBuffer.wrap(bytes("entry:num"));
289      m.value = ByteBuffer.wrap(bytes("-999"));
290      mutations.add(m);
291      m = new Mutation();
292      m.column = ByteBuffer.wrap(bytes("entry:sqr"));
293      m.isDelete = true;
294      // shouldn't override latest
295      client.mutateRowTs(demoTable, ByteBuffer.wrap(row), mutations, 1, dummyAttributes);
296      printRow(client.getRow(demoTable, ByteBuffer.wrap(row), dummyAttributes));
297
298      List<TCell> versions = client.getVer(demoTable, ByteBuffer.wrap(row),
299        ByteBuffer.wrap(bytes("entry:num")), 10, dummyAttributes);
300      printVersions(ByteBuffer.wrap(row), versions);
301
302      if (versions.isEmpty()) {
303        System.out.println("FATAL: wrong # of versions");
304        System.exit(-1);
305      }
306
307      List<TCell> result = client.get(demoTable, ByteBuffer.wrap(row),
308        ByteBuffer.wrap(bytes("entry:foo")), dummyAttributes);
309
310      if (!result.isEmpty()) {
311        System.out.println("FATAL: shouldn't get here");
312        System.exit(-1);
313      }
314
315      System.out.println();
316    }
317
318    // scan all rows/columnNames
319    columnNames.clear();
320
321    for (ColumnDescriptor col2 : client.getColumnDescriptors(demoTable).values()) {
322      System.out.println("column with name: " + ClientUtils.utf8(col2.name));
323      System.out.println(col2);
324      // remove the trailing ':' from the family name
325      col2.name.limit(col2.name.limit() - 1);
326      columnNames.add(col2.name.slice());
327    }
328
329    System.out.println("Starting scanner...");
330    scanner = client.scannerOpenWithStop(demoTable, ByteBuffer.wrap(bytes("00020")),
331      ByteBuffer.wrap(bytes("00040")), columnNames, dummyAttributes);
332
333    while (true) {
334      List<TRowResult> entry = client.scannerGet(scanner);
335
336      if (entry.isEmpty()) {
337        System.out.println("Scanner finished...");
338        break;
339      }
340
341      printRow(entry);
342    }
343
344    transport.close();
345  }
346
347  private void printVersions(ByteBuffer row, List<TCell> versions) {
348    StringBuilder rowStr = new StringBuilder();
349
350    for (TCell cell : versions) {
351      rowStr.append(ClientUtils.utf8(cell.value.array()));
352      rowStr.append("; ");
353    }
354
355    System.out.println("row: " + ClientUtils.utf8(row) + ", values: " + rowStr);
356  }
357
358  private void printRow(TRowResult rowResult) {
359    ClientUtils.printRow(rowResult);
360  }
361
362  private void printRow(List<TRowResult> rows) {
363    for (TRowResult rowResult : rows) {
364      printRow(rowResult);
365    }
366  }
367
368  static Subject getSubject() throws Exception {
369    if (!secure) {
370      return new Subject();
371    }
372
373    LoginContext context = ClientUtils.getLoginContext();
374    context.login();
375    return context.getSubject();
376  }
377}