1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *     http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.accumulo.server.master.balancer;
18  
19  import static org.junit.Assert.assertEquals;
20  
21  import java.net.InetSocketAddress;
22  import java.util.ArrayList;
23  import java.util.Collections;
24  import java.util.HashMap;
25  import java.util.List;
26  import java.util.Map;
27  import java.util.Map.Entry;
28  import java.util.Set;
29  import java.util.SortedMap;
30  import java.util.TreeMap;
31  
32  import org.apache.accumulo.core.data.KeyExtent;
33  import org.apache.accumulo.core.master.thrift.TableInfo;
34  import org.apache.accumulo.core.master.thrift.TabletServerStatus;
35  import org.apache.accumulo.core.security.thrift.ThriftSecurityException;
36  import org.apache.accumulo.core.tabletserver.thrift.TabletStats;
37  import org.apache.accumulo.core.util.AddressUtil;
38  import org.apache.accumulo.server.master.state.TServerInstance;
39  import org.apache.accumulo.server.master.state.TabletMigration;
40  import org.apache.hadoop.io.Text;
41  import org.apache.thrift.TException;
42  import org.junit.Test;
43  
44  public class ChaoticLoadBalancerTest {
45    
46    class FakeTServer {
47      List<KeyExtent> extents = new ArrayList<KeyExtent>();
48      
49      TabletServerStatus getStatus(TServerInstance server) {
50        TabletServerStatus result = new TabletServerStatus();
51        result.tableMap = new HashMap<String,TableInfo>();
52        for (KeyExtent extent : extents) {
53          String table = extent.getTableId().toString();
54          TableInfo info = result.tableMap.get(table);
55          if (info == null)
56            result.tableMap.put(table, info = new TableInfo());
57          info.onlineTablets++;
58          info.recs = info.onlineTablets;
59          info.ingestRate = 123.;
60          info.queryRate = 456.;
61        }
62        return result;
63      }
64    }
65    
66    Map<TServerInstance,FakeTServer> servers = new HashMap<TServerInstance,FakeTServer>();
67    
68    class TestChaoticLoadBalancer extends ChaoticLoadBalancer {
69      
70      @Override
71      public List<TabletStats> getOnlineTabletsForTable(TServerInstance tserver, String table) throws ThriftSecurityException, TException {
72        List<TabletStats> result = new ArrayList<TabletStats>();
73        for (KeyExtent extent : servers.get(tserver).extents) {
74          if (extent.getTableId().toString().equals(table)) {
75            result.add(new TabletStats(extent.toThrift(), null, null, null, 0l, 0., 0., 0));
76          }
77        }
78        return result;
79      }
80    }
81    
82    @Test
83    public void testAssignMigrations() {
84      servers.clear();
85      servers.put(new TServerInstance(AddressUtil.parseAddress("127.0.0.1", 1234), "a"), new FakeTServer());
86      servers.put(new TServerInstance(AddressUtil.parseAddress("127.0.0.1", 1235), "b"), new FakeTServer());
87      servers.put(new TServerInstance(AddressUtil.parseAddress("127.0.0.1", 1236), "c"), new FakeTServer());
88      Map<KeyExtent,TServerInstance> metadataTable = new TreeMap<KeyExtent,TServerInstance>();
89      String table = "t1";
90      metadataTable.put(makeExtent(table, null, null), null);
91      table = "t2";
92      metadataTable.put(makeExtent(table, "a", null), null);
93      metadataTable.put(makeExtent(table, null, "a"), null);
94      table = "t3";
95      metadataTable.put(makeExtent(table, "a", null), null);
96      metadataTable.put(makeExtent(table, "b", "a"), null);
97      metadataTable.put(makeExtent(table, "c", "b"), null);
98      metadataTable.put(makeExtent(table, "d", "c"), null);
99      metadataTable.put(makeExtent(table, "e", "d"), null);
100     metadataTable.put(makeExtent(table, null, "e"), null);
101     
102     TestChaoticLoadBalancer balancer = new TestChaoticLoadBalancer();
103     
104     SortedMap<TServerInstance,TabletServerStatus> current = new TreeMap<TServerInstance,TabletServerStatus>();
105     for (Entry<TServerInstance,FakeTServer> entry : servers.entrySet()) {
106       current.put(entry.getKey(), entry.getValue().getStatus(entry.getKey()));
107     }
108     
109     Map<KeyExtent, TServerInstance> assignments = new HashMap<KeyExtent, TServerInstance>();
110     balancer.getAssignments(getAssignments(servers), metadataTable, assignments);
111     
112     assertEquals(assignments.size(), metadataTable.size());
113   }
114   
115   SortedMap<TServerInstance,TabletServerStatus> getAssignments(Map<TServerInstance,FakeTServer> servers) {
116     SortedMap<TServerInstance,TabletServerStatus> result = new TreeMap<TServerInstance,TabletServerStatus>();
117     for (Entry<TServerInstance,FakeTServer> entry : servers.entrySet()) {
118       result.put(entry.getKey(), entry.getValue().getStatus(entry.getKey()));
119     }
120     return result;
121   }
122   
123   @Test
124   public void testUnevenAssignment() {
125     servers.clear();
126     for (char c : "abcdefghijklmnopqrstuvwxyz".toCharArray()) {
127       String cString = Character.toString(c);
128       InetSocketAddress fakeAddress = AddressUtil.parseAddress("127.0.0.1", (int) c);
129       String fakeInstance = cString;
130       TServerInstance tsi = new TServerInstance(fakeAddress, fakeInstance);
131       FakeTServer fakeTServer = new FakeTServer();
132       servers.put(tsi, fakeTServer);
133       fakeTServer.extents.add(makeExtent(cString, null, null));
134     }
135     // Put more tablets on one server, but not more than the number of servers
136     Entry<TServerInstance,FakeTServer> first = servers.entrySet().iterator().next();
137     first.getValue().extents.add(makeExtent("newTable", "a", null));
138     first.getValue().extents.add(makeExtent("newTable", "b", "a"));
139     first.getValue().extents.add(makeExtent("newTable", "c", "b"));
140     first.getValue().extents.add(makeExtent("newTable", "d", "c"));
141     first.getValue().extents.add(makeExtent("newTable", "e", "d"));
142     first.getValue().extents.add(makeExtent("newTable", "f", "e"));
143     first.getValue().extents.add(makeExtent("newTable", "g", "f"));
144     first.getValue().extents.add(makeExtent("newTable", "h", "g"));
145     first.getValue().extents.add(makeExtent("newTable", "i", null));
146     TestChaoticLoadBalancer balancer = new TestChaoticLoadBalancer();
147     Set<KeyExtent> migrations = Collections.emptySet();
148     
149     // Just want to make sure it gets some migrations, randomness prevents guarantee of a defined amount, or even expected amount
150     List<TabletMigration> migrationsOut = new ArrayList<TabletMigration>();
151     while (migrationsOut.size() != 0) {
152       balancer.balance(getAssignments(servers), migrations, migrationsOut);
153     }
154   }
155   
156   private static KeyExtent makeExtent(String table, String end, String prev) {
157     return new KeyExtent(new Text(table), toText(end), toText(prev));
158   }
159   
160   private static Text toText(String value) {
161     if (value != null)
162       return new Text(value);
163     return null;
164   }
165   
166 }