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