| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package org.apache.cassandra.locator; |
| |
| import java.io.DataInputStream; |
| import java.io.BufferedInputStream; |
| import java.io.BufferedReader; |
| import java.io.FileReader; |
| import java.io.IOException; |
| import java.io.File; |
| import java.net.HttpURLConnection; |
| import java.net.InetAddress; |
| import java.net.URL; |
| import java.net.URI; |
| import java.nio.charset.StandardCharsets; |
| import java.util.Map; |
| import java.util.regex.Pattern; |
| import java.util.regex.Matcher; |
| |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| import org.apache.cassandra.db.SystemKeyspace; |
| import org.apache.cassandra.exceptions.ConfigurationException; |
| import org.apache.cassandra.gms.ApplicationState; |
| import org.apache.cassandra.gms.EndpointState; |
| import org.apache.cassandra.gms.Gossiper; |
| import org.apache.cassandra.io.util.FileUtils; |
| import org.apache.cassandra.utils.FBUtilities; |
| import org.apache.cassandra.utils.JVMStabilityInspector; |
| |
| /** |
| * {@code |
| * A snitch that assumes a Cloudstack Zone follows the typical convention |
| * <country>-<location>-<availability zone> and uses the country/location |
| * tuple as a datacenter and the availability zone as a rack |
| * } |
| */ |
| |
| public class CloudstackSnitch extends AbstractNetworkTopologySnitch |
| { |
| protected static final Logger logger = LoggerFactory.getLogger(CloudstackSnitch.class); |
| protected static final String ZONE_NAME_QUERY_URI = "/latest/meta-data/availability-zone"; |
| |
| private Map<InetAddress, Map<String, String>> savedEndpoints; |
| |
| private static final String DEFAULT_DC = "UNKNOWN-DC"; |
| private static final String DEFAULT_RACK = "UNKNOWN-RACK"; |
| private static final String[] LEASE_FILES = |
| { |
| "file:///var/lib/dhcp/dhclient.eth0.leases", |
| "file:///var/lib/dhclient/dhclient.eth0.leases" |
| }; |
| |
| protected String csZoneDc; |
| protected String csZoneRack; |
| |
| public CloudstackSnitch() throws IOException, ConfigurationException |
| { |
| String endpoint = csMetadataEndpoint(); |
| String zone = csQueryMetadata(endpoint + ZONE_NAME_QUERY_URI); |
| String zone_parts[] = zone.split("-"); |
| |
| if (zone_parts.length != 3) |
| { |
| throw new ConfigurationException("CloudstackSnitch cannot handle invalid zone format: " + zone); |
| } |
| csZoneDc = zone_parts[0] + "-" + zone_parts[1]; |
| csZoneRack = zone_parts[2]; |
| } |
| |
| public String getRack(InetAddress endpoint) |
| { |
| if (endpoint.equals(FBUtilities.getBroadcastAddress())) |
| return csZoneRack; |
| EndpointState state = Gossiper.instance.getEndpointStateForEndpoint(endpoint); |
| if (state == null || state.getApplicationState(ApplicationState.RACK) == null) |
| { |
| if (savedEndpoints == null) |
| savedEndpoints = SystemKeyspace.loadDcRackInfo(); |
| if (savedEndpoints.containsKey(endpoint)) |
| return savedEndpoints.get(endpoint).get("rack"); |
| return DEFAULT_RACK; |
| } |
| return state.getApplicationState(ApplicationState.RACK).value; |
| } |
| |
| public String getDatacenter(InetAddress endpoint) |
| { |
| if (endpoint.equals(FBUtilities.getBroadcastAddress())) |
| return csZoneDc; |
| EndpointState state = Gossiper.instance.getEndpointStateForEndpoint(endpoint); |
| if (state == null || state.getApplicationState(ApplicationState.DC) == null) |
| { |
| if (savedEndpoints == null) |
| savedEndpoints = SystemKeyspace.loadDcRackInfo(); |
| if (savedEndpoints.containsKey(endpoint)) |
| return savedEndpoints.get(endpoint).get("data_center"); |
| return DEFAULT_DC; |
| } |
| return state.getApplicationState(ApplicationState.DC).value; |
| } |
| |
| String csQueryMetadata(String url) throws ConfigurationException, IOException |
| { |
| HttpURLConnection conn = null; |
| DataInputStream is = null; |
| |
| try |
| { |
| conn = (HttpURLConnection) new URL(url).openConnection(); |
| } |
| catch (Exception e) |
| { |
| throw new ConfigurationException("CloudstackSnitch cannot query wrong metadata URL: " + url); |
| } |
| try |
| { |
| conn.setRequestMethod("GET"); |
| if (conn.getResponseCode() != 200) |
| { |
| throw new ConfigurationException("CloudstackSnitch was unable to query metadata."); |
| } |
| |
| int cl = conn.getContentLength(); |
| byte[] b = new byte[cl]; |
| is = new DataInputStream(new BufferedInputStream(conn.getInputStream())); |
| is.readFully(b); |
| return new String(b, StandardCharsets.UTF_8); |
| } |
| finally |
| { |
| FileUtils.close(is); |
| conn.disconnect(); |
| } |
| } |
| |
| String csMetadataEndpoint() throws ConfigurationException |
| { |
| for (String lease_uri: LEASE_FILES) |
| { |
| try |
| { |
| File lease_file = new File(new URI(lease_uri)); |
| if (lease_file.exists()) |
| { |
| return csEndpointFromLease(lease_file); |
| } |
| } |
| catch (Exception e) |
| { |
| JVMStabilityInspector.inspectThrowable(e); |
| continue; |
| } |
| |
| } |
| |
| throw new ConfigurationException("No valid DHCP lease file could be found."); |
| } |
| |
| String csEndpointFromLease(File lease) throws ConfigurationException |
| { |
| String line; |
| String endpoint = null; |
| Pattern identifierPattern = Pattern.compile("^[ \t]*option dhcp-server-identifier (.*);$"); |
| |
| try (BufferedReader reader = new BufferedReader(new FileReader(lease))) |
| { |
| |
| while ((line = reader.readLine()) != null) |
| { |
| Matcher matcher = identifierPattern.matcher(line); |
| |
| if (matcher.find()) |
| { |
| endpoint = matcher.group(1); |
| break; |
| } |
| } |
| } |
| catch (Exception e) |
| { |
| throw new ConfigurationException("CloudstackSnitch cannot access lease file."); |
| } |
| |
| if (endpoint == null) |
| { |
| throw new ConfigurationException("No metadata server could be found in lease file."); |
| } |
| |
| return "http://" + endpoint; |
| } |
| } |