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.util;
019
020import java.net.InetAddress;
021
022import java.net.UnknownHostException;
023import java.util.ArrayList;
024import java.util.Collection;
025import java.util.HashSet;
026import java.util.LinkedList;
027import java.util.List;
028import java.util.Set;
029
030import org.apache.commons.logging.Log;
031import org.apache.commons.logging.LogFactory;
032import org.apache.commons.net.util.SubnetUtils;
033
034import com.google.common.annotations.VisibleForTesting;
035import com.google.common.net.InetAddresses;
036
037/**
038 * Container class which holds a list of ip/host addresses and 
039 * answers membership queries.
040 *
041 * Accepts list of ip addresses, ip addreses in CIDR format and/or 
042 * host addresses.
043 */
044
045public class MachineList {
046  
047  public static final Log LOG = LogFactory.getLog(MachineList.class);
048  public static final String WILDCARD_VALUE = "*";
049
050  /**
051   * InetAddressFactory is used to obtain InetAddress from host.
052   * This class makes it easy to simulate host to ip mappings during testing.
053   *
054   */
055  public static class InetAddressFactory {
056
057    static final InetAddressFactory S_INSTANCE = new InetAddressFactory();
058
059    public InetAddress getByName (String host) throws UnknownHostException {
060      return InetAddress.getByName(host);
061    }
062  }
063
064  private final boolean all;
065  private final Set<String> ipAddresses;
066  private final List<SubnetUtils.SubnetInfo> cidrAddresses;
067  private final Set<String> hostNames;
068  private final InetAddressFactory addressFactory;
069
070  /**
071   * 
072   * @param hostEntries comma separated ip/cidr/host addresses
073   */
074  public MachineList(String hostEntries) {
075    this(StringUtils.getTrimmedStringCollection(hostEntries));
076  }
077
078  /**
079   *
080   * @param hostEntries collection of separated ip/cidr/host addresses
081   */
082  public MachineList(Collection<String> hostEntries) {
083    this(hostEntries, InetAddressFactory.S_INSTANCE);
084  }
085
086  /**
087   * Accepts a collection of ip/cidr/host addresses
088   * 
089   * @param hostEntries
090   * @param addressFactory addressFactory to convert host to InetAddress
091   */
092  public MachineList(Collection<String> hostEntries, InetAddressFactory addressFactory) {
093    this.addressFactory = addressFactory;
094    if (hostEntries != null) {
095      if ((hostEntries.size() == 1) && (hostEntries.contains(WILDCARD_VALUE))) {
096        all = true; 
097        ipAddresses = null; 
098        hostNames = null; 
099        cidrAddresses = null; 
100      } else {
101        all = false;
102        Set<String> ips = new HashSet<String>();
103        List<SubnetUtils.SubnetInfo> cidrs = new LinkedList<SubnetUtils.SubnetInfo>();
104        Set<String> hosts = new HashSet<String>();
105        for (String hostEntry : hostEntries) {
106          //ip address range
107          if (hostEntry.indexOf("/") > -1) {
108            try {
109              SubnetUtils subnet = new SubnetUtils(hostEntry);
110              subnet.setInclusiveHostCount(true);
111              cidrs.add(subnet.getInfo());
112            } catch (IllegalArgumentException e) {
113              LOG.warn("Invalid CIDR syntax : " + hostEntry);
114              throw e;
115            }
116          } else if (InetAddresses.isInetAddress(hostEntry)) { //ip address
117            ips.add(hostEntry);
118          } else { //hostname
119            hosts.add(hostEntry);
120          }
121        }
122        ipAddresses = (ips.size() > 0) ? ips : null;
123        cidrAddresses = (cidrs.size() > 0) ? cidrs : null;
124        hostNames = (hosts.size() > 0) ? hosts : null;
125      }
126    } else {
127      all = false; 
128      ipAddresses = null;
129      hostNames = null; 
130      cidrAddresses = null; 
131    }
132  }
133  /**
134   * Accepts an ip address and return true if ipAddress is in the list
135   * @param ipAddress
136   * @return true if ipAddress is part of the list
137   */
138  public boolean includes(String ipAddress) {
139    
140    if (all) {
141      return true;
142    }
143    
144    //check in the set of ipAddresses
145    if ((ipAddresses != null) && ipAddresses.contains(ipAddress)) {
146      return true;
147    }
148    
149    //iterate through the ip ranges for inclusion
150    if (cidrAddresses != null) {
151      for(SubnetUtils.SubnetInfo cidrAddress : cidrAddresses) {
152        if(cidrAddress.isInRange(ipAddress)) {
153          return true;
154        }
155      }
156    }
157    
158    //check if the ipAddress matches one of hostnames
159    if (hostNames != null) {
160      //convert given ipAddress to hostname and look for a match
161      InetAddress hostAddr;
162      try {
163        hostAddr = addressFactory.getByName(ipAddress);
164        if ((hostAddr != null) && hostNames.contains(hostAddr.getCanonicalHostName())) {
165          return true;
166        }
167      } catch (UnknownHostException e) {
168        //ignore the exception and proceed to resolve the list of hosts
169      }
170
171      //loop through host addresses and convert them to ip and look for a match
172      for (String host : hostNames) {
173        try {
174          hostAddr = addressFactory.getByName(host);
175        } catch (UnknownHostException e) {
176          continue;
177        }
178        if (hostAddr.getHostAddress().equals(ipAddress)) {
179          return true;
180        }
181      }
182    }
183    return false;
184  }
185
186  /**
187   * returns the contents of the MachineList as a Collection<String>
188   * This can be used for testing 
189   * @return contents of the MachineList
190   */
191  @VisibleForTesting
192  public Collection<String> getCollection() {
193    Collection<String> list = new ArrayList<String>();
194    if (all) {
195      list.add("*"); 
196    } else {
197      if (ipAddresses != null) {
198        list.addAll(ipAddresses);
199      }
200      if (hostNames != null) {
201        list.addAll(hostNames);
202      }
203      if (cidrAddresses != null) {
204        for(SubnetUtils.SubnetInfo cidrAddress : cidrAddresses) {
205          list.add(cidrAddress.getCidrSignature());
206        }
207      }
208    }
209    return list;
210  }
211}