View Javadoc

1   /**
2    *
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *     http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing, software
14   * distributed under the License is distributed on an "AS IS" BASIS,
15   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   * See the License for the specific language governing permissions and
17   * limitations under the License.
18   */
19  package org.apache.hadoop.hbase.thrift;
20  
21  import java.io.UnsupportedEncodingException;
22  import java.nio.ByteBuffer;
23  import java.nio.charset.CharacterCodingException;
24  import java.nio.charset.Charset;
25  import java.nio.charset.CharsetDecoder;
26  import java.security.PrivilegedExceptionAction;
27  import java.text.NumberFormat;
28  import java.util.ArrayList;
29  import java.util.HashMap;
30  import java.util.List;
31  import java.util.Map;
32  import java.util.SortedMap;
33  import java.util.TreeMap;
34  
35  import javax.security.auth.Subject;
36  import javax.security.auth.login.AppConfigurationEntry;
37  import javax.security.auth.login.Configuration;
38  import javax.security.auth.login.LoginContext;
39  import javax.security.sasl.Sasl;
40  
41  import org.apache.hadoop.hbase.thrift.generated.AlreadyExists;
42  import org.apache.hadoop.hbase.thrift.generated.ColumnDescriptor;
43  import org.apache.hadoop.hbase.thrift.generated.Hbase;
44  import org.apache.hadoop.hbase.thrift.generated.Mutation;
45  import org.apache.hadoop.hbase.thrift.generated.TCell;
46  import org.apache.hadoop.hbase.thrift.generated.TRowResult;
47  import org.apache.thrift.protocol.TBinaryProtocol;
48  import org.apache.thrift.protocol.TProtocol;
49  import org.apache.thrift.transport.TSaslClientTransport;
50  import org.apache.thrift.transport.TSocket;
51  import org.apache.thrift.transport.TTransport;
52  
53  /**
54   * See the instructions under hbase-examples/README.txt
55   */
56  public class DemoClient {
57  
58      static protected int port;
59      static protected String host;
60      CharsetDecoder decoder = null;
61  
62      private static boolean secure = false;
63  
64      public static void main(String[] args) throws Exception {
65  
66          if (args.length < 2 || args.length > 3) {
67  
68              System.out.println("Invalid arguments!");
69              System.out.println("Usage: DemoClient host port [secure=false]");
70  
71              System.exit(-1);
72          }
73  
74          port = Integer.parseInt(args[1]);
75          host = args[0];
76          if (args.length > 2) {
77            secure = Boolean.parseBoolean(args[2]);
78          }
79  
80          final DemoClient client = new DemoClient();
81          Subject.doAs(getSubject(),
82            new PrivilegedExceptionAction<Void>() {
83              @Override
84              public Void run() throws Exception {
85                client.run();
86                return null;
87              }
88            });
89      }
90  
91      DemoClient() {
92          decoder = Charset.forName("UTF-8").newDecoder();
93      }
94  
95      // Helper to translate byte[]'s to UTF8 strings
96      private String utf8(byte[] buf) {
97          try {
98              return decoder.decode(ByteBuffer.wrap(buf)).toString();
99          } catch (CharacterCodingException e) {
100             return "[INVALID UTF-8]";
101         }
102     }
103 
104     // Helper to translate strings to UTF8 bytes
105     private byte[] bytes(String s) {
106         try {
107             return s.getBytes("UTF-8");
108         } catch (UnsupportedEncodingException e) {
109             e.printStackTrace();
110             return null;
111         }
112     }
113 
114     private void run() throws Exception {
115         TTransport transport = new TSocket(host, port);
116         if (secure) {
117           Map<String, String> saslProperties = new HashMap<String, String>();
118           saslProperties.put(Sasl.QOP, "auth-conf,auth-int,auth");
119           /**
120            * The Thrift server the DemoClient is trying to connect to
121            * must have a matching principal, and support authentication.
122            *
123            * The HBase cluster must be secure, allow proxy user.
124            */
125           transport = new TSaslClientTransport("GSSAPI", null,
126             "hbase", // Thrift server user name, should be an authorized proxy user.
127             host, // Thrift server domain
128             saslProperties, null, transport);
129         }
130 
131         transport.open();
132 
133         TProtocol protocol = new TBinaryProtocol(transport, true, true);
134         Hbase.Client client = new Hbase.Client(protocol);
135 
136         byte[] t = bytes("demo_table");
137 
138         //
139         // Scan all tables, look for the demo table and delete it.
140         //
141         System.out.println("scanning tables...");
142         for (ByteBuffer name : client.getTableNames()) {
143             System.out.println("  found: " + utf8(name.array()));
144             if (utf8(name.array()).equals(utf8(t))) {
145                 if (client.isTableEnabled(name)) {
146                     System.out.println("    disabling table: " + utf8(name.array()));
147                     client.disableTable(name);
148                 }
149                 System.out.println("    deleting table: " + utf8(name.array()));
150                 client.deleteTable(name);
151             }
152         }
153 
154         //
155         // Create the demo table with two column families, entry: and unused:
156         //
157         ArrayList<ColumnDescriptor> columns = new ArrayList<ColumnDescriptor>();
158         ColumnDescriptor col;
159         col = new ColumnDescriptor();
160         col.name = ByteBuffer.wrap(bytes("entry:"));
161         col.timeToLive = Integer.MAX_VALUE;
162         col.maxVersions = 10;
163         columns.add(col);
164         col = new ColumnDescriptor();
165         col.name = ByteBuffer.wrap(bytes("unused:"));
166         col.timeToLive = Integer.MAX_VALUE;
167         columns.add(col);
168 
169         System.out.println("creating table: " + utf8(t));
170         try {
171             client.createTable(ByteBuffer.wrap(t), columns);
172         } catch (AlreadyExists ae) {
173             System.out.println("WARN: " + ae.message);
174         }
175 
176         System.out.println("column families in " + utf8(t) + ": ");
177         Map<ByteBuffer, ColumnDescriptor> columnMap = client.getColumnDescriptors(ByteBuffer.wrap(t));
178         for (ColumnDescriptor col2 : columnMap.values()) {
179             System.out.println("  column: " + utf8(col2.name.array()) + ", maxVer: " + Integer.toString(col2.maxVersions));
180         }
181 
182         Map<ByteBuffer, ByteBuffer> dummyAttributes = null;
183         boolean writeToWal = false;
184 
185         //
186         // Test UTF-8 handling
187         //
188         byte[] invalid = {(byte) 'f', (byte) 'o', (byte) 'o', (byte) '-',
189             (byte) 0xfc, (byte) 0xa1, (byte) 0xa1, (byte) 0xa1, (byte) 0xa1};
190         byte[] valid = {(byte) 'f', (byte) 'o', (byte) 'o', (byte) '-',
191             (byte) 0xE7, (byte) 0x94, (byte) 0x9F, (byte) 0xE3, (byte) 0x83,
192             (byte) 0x93, (byte) 0xE3, (byte) 0x83, (byte) 0xBC, (byte) 0xE3,
193             (byte) 0x83, (byte) 0xAB};
194 
195         ArrayList<Mutation> mutations;
196         // non-utf8 is fine for data
197         mutations = new ArrayList<Mutation>();
198         mutations.add(new Mutation(false, ByteBuffer.wrap(bytes("entry:foo")),
199             ByteBuffer.wrap(invalid), writeToWal));
200         client.mutateRow(ByteBuffer.wrap(t), ByteBuffer.wrap(bytes("foo")),
201             mutations, dummyAttributes);
202 
203 
204         // this row name is valid utf8
205         mutations = new ArrayList<Mutation>();
206         mutations.add(new Mutation(false, ByteBuffer.wrap(bytes("entry:foo")), ByteBuffer.wrap(valid), writeToWal));
207         client.mutateRow(ByteBuffer.wrap(t), ByteBuffer.wrap(valid), mutations, dummyAttributes);
208 
209         // non-utf8 is now allowed in row names because HBase stores values as binary
210 
211         mutations = new ArrayList<Mutation>();
212         mutations.add(new Mutation(false, ByteBuffer.wrap(bytes("entry:foo")), ByteBuffer.wrap(invalid), writeToWal));
213         client.mutateRow(ByteBuffer.wrap(t), ByteBuffer.wrap(invalid), mutations, dummyAttributes);
214 
215 
216         // Run a scanner on the rows we just created
217         ArrayList<ByteBuffer> columnNames = new ArrayList<ByteBuffer>();
218         columnNames.add(ByteBuffer.wrap(bytes("entry:")));
219 
220         System.out.println("Starting scanner...");
221         int scanner = client.scannerOpen(ByteBuffer.wrap(t), ByteBuffer.wrap(bytes("")), columnNames, dummyAttributes);
222 
223         while (true) {
224             List<TRowResult> entry = client.scannerGet(scanner);
225             if (entry.isEmpty()) {
226                 break;
227             }
228             printRow(entry);
229         }
230 
231         //
232         // Run some operations on a bunch of rows
233         //
234         for (int i = 100; i >= 0; --i) {
235             // format row keys as "00000" to "00100"
236             NumberFormat nf = NumberFormat.getInstance();
237             nf.setMinimumIntegerDigits(5);
238             nf.setGroupingUsed(false);
239             byte[] row = bytes(nf.format(i));
240 
241             mutations = new ArrayList<Mutation>();
242             mutations.add(new Mutation(false, ByteBuffer.wrap(bytes("unused:")), ByteBuffer.wrap(bytes("DELETE_ME")), writeToWal));
243             client.mutateRow(ByteBuffer.wrap(t), ByteBuffer.wrap(row), mutations, dummyAttributes);
244             printRow(client.getRow(ByteBuffer.wrap(t), ByteBuffer.wrap(row), dummyAttributes));
245             client.deleteAllRow(ByteBuffer.wrap(t), ByteBuffer.wrap(row), dummyAttributes);
246 
247             // sleep to force later timestamp
248             try {
249                 Thread.sleep(50);
250             } catch (InterruptedException e) {
251                 // no-op
252             }
253 
254             mutations = new ArrayList<Mutation>();
255             mutations.add(new Mutation(false, ByteBuffer.wrap(bytes("entry:num")), ByteBuffer.wrap(bytes("0")), writeToWal));
256             mutations.add(new Mutation(false, ByteBuffer.wrap(bytes("entry:foo")), ByteBuffer.wrap(bytes("FOO")), writeToWal));
257             client.mutateRow(ByteBuffer.wrap(t), ByteBuffer.wrap(row), mutations, dummyAttributes);
258             printRow(client.getRow(ByteBuffer.wrap(t), ByteBuffer.wrap(row), dummyAttributes));
259 
260             Mutation m;
261             mutations = new ArrayList<Mutation>();
262             m = new Mutation();
263             m.column = ByteBuffer.wrap(bytes("entry:foo"));
264             m.isDelete = true;
265             mutations.add(m);
266             m = new Mutation();
267             m.column = ByteBuffer.wrap(bytes("entry:num"));
268             m.value = ByteBuffer.wrap(bytes("-1"));
269             mutations.add(m);
270             client.mutateRow(ByteBuffer.wrap(t), ByteBuffer.wrap(row), mutations, dummyAttributes);
271             printRow(client.getRow(ByteBuffer.wrap(t), ByteBuffer.wrap(row), dummyAttributes));
272 
273             mutations = new ArrayList<Mutation>();
274             mutations.add(new Mutation(false, ByteBuffer.wrap(bytes("entry:num")), ByteBuffer.wrap(bytes(Integer.toString(i))), writeToWal));
275             mutations.add(new Mutation(false, ByteBuffer.wrap(bytes("entry:sqr")), ByteBuffer.wrap(bytes(Integer.toString(i * i))), writeToWal));
276             client.mutateRow(ByteBuffer.wrap(t), ByteBuffer.wrap(row), mutations, dummyAttributes);
277             printRow(client.getRow(ByteBuffer.wrap(t), 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             client.mutateRowTs(ByteBuffer.wrap(t), ByteBuffer.wrap(row), mutations, 1, dummyAttributes); // shouldn't override latest
295             printRow(client.getRow(ByteBuffer.wrap(t), ByteBuffer.wrap(row), dummyAttributes));
296 
297             List<TCell> versions = client.getVer(ByteBuffer.wrap(t), ByteBuffer.wrap(row), ByteBuffer.wrap(bytes("entry:num")), 10, dummyAttributes);
298             printVersions(ByteBuffer.wrap(row), versions);
299             if (versions.isEmpty()) {
300                 System.out.println("FATAL: wrong # of versions");
301                 System.exit(-1);
302             }
303 
304             List<TCell> result = client.get(ByteBuffer.wrap(t), ByteBuffer.wrap(row), ByteBuffer.wrap(bytes("entry:foo")), dummyAttributes);
305             if (!result.isEmpty()) {
306                 System.out.println("FATAL: shouldn't get here");
307                 System.exit(-1);
308             }
309 
310             System.out.println("");
311         }
312 
313         // scan all rows/columnNames
314 
315         columnNames.clear();
316         for (ColumnDescriptor col2 : client.getColumnDescriptors(ByteBuffer.wrap(t)).values()) {
317             System.out.println("column with name: " + new String(col2.name.array()));
318             System.out.println(col2.toString());
319 
320             columnNames.add(col2.name);
321         }
322 
323         System.out.println("Starting scanner...");
324         scanner = client.scannerOpenWithStop(ByteBuffer.wrap(t), ByteBuffer.wrap(bytes("00020")), ByteBuffer.wrap(bytes("00040")), columnNames, dummyAttributes);
325 
326         while (true) {
327             List<TRowResult> entry = client.scannerGet(scanner);
328             if (entry.isEmpty()) {
329                 System.out.println("Scanner finished");
330                 break;
331             }
332             printRow(entry);
333         }
334 
335         transport.close();
336     }
337 
338     private void printVersions(ByteBuffer row, List<TCell> versions) {
339         StringBuilder rowStr = new StringBuilder();
340         for (TCell cell : versions) {
341             rowStr.append(utf8(cell.value.array()));
342             rowStr.append("; ");
343         }
344         System.out.println("row: " + utf8(row.array()) + ", values: " + rowStr);
345     }
346 
347     private void printRow(TRowResult rowResult) {
348         // copy values into a TreeMap to get them in sorted order
349 
350         TreeMap<String, TCell> sorted = new TreeMap<String, TCell>();
351         for (Map.Entry<ByteBuffer, TCell> column : rowResult.columns.entrySet()) {
352             sorted.put(utf8(column.getKey().array()), column.getValue());
353         }
354 
355         StringBuilder rowStr = new StringBuilder();
356         for (SortedMap.Entry<String, TCell> entry : sorted.entrySet()) {
357             rowStr.append(entry.getKey());
358             rowStr.append(" => ");
359             rowStr.append(utf8(entry.getValue().value.array()));
360             rowStr.append("; ");
361         }
362         System.out.println("row: " + utf8(rowResult.row.array()) + ", cols: " + rowStr);
363     }
364 
365     private void printRow(List<TRowResult> rows) {
366         for (TRowResult rowResult : rows) {
367             printRow(rowResult);
368         }
369     }
370 
371     static Subject getSubject() throws Exception {
372       if (!secure) return new Subject();
373 
374       /*
375        * To authenticate the DemoClient, kinit should be invoked ahead.
376        * Here we try to get the Kerberos credential from the ticket cache.
377        */
378       LoginContext context = new LoginContext("", new Subject(), null,
379         new Configuration() {
380           @Override
381           public AppConfigurationEntry[] getAppConfigurationEntry(String name) {
382             Map<String, String> options = new HashMap<String, String>();
383             options.put("useKeyTab", "false");
384             options.put("storeKey", "false");
385             options.put("doNotPrompt", "true");
386             options.put("useTicketCache", "true");
387             options.put("renewTGT", "true");
388             options.put("refreshKrb5Config", "true");
389             options.put("isInitiator", "true");
390             String ticketCache = System.getenv("KRB5CCNAME");
391             if (ticketCache != null) {
392               options.put("ticketCache", ticketCache);
393             }
394             options.put("debug", "true");
395 
396             return new AppConfigurationEntry[]{
397                 new AppConfigurationEntry("com.sun.security.auth.module.Krb5LoginModule",
398                     AppConfigurationEntry.LoginModuleControlFlag.REQUIRED,
399                     options)};
400           }
401         });
402       context.login();
403       return context.getSubject();
404     }
405 }