1 package org.kuali.common.devops.logic;
2
3 import static com.google.common.collect.Iterables.filter;
4 import static com.google.common.collect.Lists.newArrayList;
5 import static com.google.common.collect.Maps.newHashMap;
6 import static com.google.common.collect.Maps.newTreeMap;
7 import static com.google.common.collect.Sets.newHashSet;
8 import static java.lang.String.format;
9 import static org.apache.commons.io.FileUtils.readFileToString;
10 import static org.apache.commons.io.FileUtils.write;
11 import static org.apache.commons.lang3.StringUtils.removeEnd;
12 import static org.kuali.common.devops.archive.sweep.Functions.hostnameToKey;
13 import static org.kuali.common.devops.logic.Auth.getDNSMECredentials;
14 import static org.kuali.common.dns.dnsme.DNSME.DNSME_REST_API_URL_PRODUCTION;
15 import static org.kuali.common.dns.model.DnsRecordType.CNAME;
16 import static org.kuali.common.util.FormatUtils.getCount;
17 import static org.kuali.common.util.FormatUtils.getTime;
18 import static org.kuali.common.util.base.Exceptions.illegalState;
19 import static org.kuali.common.util.log.Loggers.newLogger;
20
21 import java.io.File;
22 import java.io.IOException;
23 import java.util.List;
24 import java.util.Map;
25 import java.util.Properties;
26 import java.util.Set;
27
28 import org.kuali.common.core.json.api.JsonService;
29 import org.kuali.common.core.json.jackson.JacksonJsonService;
30 import org.kuali.common.dns.api.DnsService;
31 import org.kuali.common.dns.dnsme.DNSMadeEasyDnsService;
32 import org.kuali.common.dns.dnsme.model.DNSMadeEasyCredentials;
33 import org.kuali.common.dns.dnsme.model.DNSMadeEasyServiceContext;
34 import org.kuali.common.dns.model.SimpleDnsRecord;
35 import org.kuali.common.util.file.CanonicalFile;
36 import org.kuali.common.util.property.ImmutableProperties;
37 import org.slf4j.Logger;
38
39 import com.google.common.base.Function;
40 import com.google.common.base.Predicate;
41 import com.google.common.base.Stopwatch;
42 import com.google.common.collect.BiMap;
43 import com.google.common.collect.HashBiMap;
44 import com.google.common.collect.HashMultiset;
45 import com.google.common.collect.ImmutableBiMap;
46 import com.google.common.collect.ImmutableList;
47 import com.google.common.collect.ImmutableMap;
48 import com.google.common.collect.Multiset;
49 import com.google.common.collect.Ordering;
50 import com.google.common.collect.Sets;
51
52 public class DNS {
53
54 private static final Logger logger = newLogger();
55 private static final String DOMAIN = "kuali.org";
56 private static final File CACHE = new CanonicalFile("./target/cache/dns/records.json");
57
58
59
60
61
62
63
64
65
66 public static Map<String, String> getAliasMap(boolean refresh) {
67 List<SimpleDnsRecord> records = getDnsRecords(refresh);
68 info("records -> %s", getCount(records));
69 return asCNAMEAliasFQDNMap(records);
70 }
71
72 private static List<SimpleDnsRecord> getDnsRecords(boolean refresh) {
73 if (refresh || !CACHE.exists()) {
74 info("domain -> %s", DOMAIN);
75 List<SimpleDnsRecord> records = queryProvider();
76 store(records);
77 return records;
78 } else {
79 return load();
80 }
81 }
82
83
84
85
86
87
88
89
90
91
92
93 public static BiMap<String, String> getCanonicalMap(boolean refresh) {
94 Map<String, String> map = newHashMap(getAliasMap(refresh));
95 removeAllKeysWithDuplicateValues(map);
96 BiMap<String, String> aliases = HashBiMap.create(map);
97 BiMap<String, String> canonical = aliases.inverse();
98 info("using -> %s uniquely aliased CNAME records", getCount(canonical));
99 return ImmutableBiMap.copyOf(canonical);
100 }
101
102 protected static <T> void removeAllKeysWithDuplicateValues(Map<?, T> map) {
103 int oldSize = map.size();
104 Set<T> duplicates = getDuplicateValues(map);
105 Set<?> keys = newHashSet(map.keySet());
106 for (Object key : keys) {
107 T value = map.get(key);
108 if (duplicates.contains(value)) {
109 debug("remove -> %s", key);
110 map.remove(key);
111 }
112 }
113 int newSize = map.size();
114 int removed = oldSize - newSize;
115 info("removed -> %s CNAME records with duplicate aliases", getCount(removed));
116 }
117
118 protected static <T> Set<T> getDuplicateValues(Map<?, T> map) {
119 Multiset<T> multi = HashMultiset.create();
120 multi.addAll(map.values());
121 Set<T> duplicates = Sets.newHashSet();
122 Set<T> elements = multi.elementSet();
123 for (T element : elements) {
124 if (multi.count(element) > 1) {
125 duplicates.add(element);
126 }
127 }
128 return duplicates;
129 }
130
131
132
133
134 protected static List<SimpleDnsRecord> queryProvider() {
135 DNSMadeEasyCredentials credentials = getDNSMECredentials();
136 String url = DNSME_REST_API_URL_PRODUCTION;
137 String domain = DOMAIN;
138 DNSMadeEasyServiceContext context = new DNSMadeEasyServiceContext(credentials, url, domain);
139 DnsService dns = new DNSMadeEasyDnsService(context);
140 return dns.getRecords();
141 }
142
143
144
145
146
147
148 protected static Map<String, String> asCNAMEAliasFQDNMap(List<SimpleDnsRecord> records) {
149 List<SimpleDnsRecord> filtered = newArrayList(filter(records, CNAMEPredicate.INSTANCE));
150 Map<String, String> map = newTreeMap();
151 for (SimpleDnsRecord record : filtered) {
152 String alias = record.getName() + "." + DOMAIN;
153 String cname = removeEnd(record.getValue(), ".");
154 map.put(alias, cname);
155 }
156 int removed = records.size() - filtered.size();
157 info("removed -> %s records that were not CNAME's", getCount(removed));
158 return ImmutableMap.copyOf(map);
159 }
160
161 protected static List<SimpleDnsRecord> load() {
162 JsonService json = new JacksonJsonService();
163 try {
164 info("load -> %s", CACHE);
165 String content = readFileToString(CACHE);
166 return ImmutableList.copyOf(json.readString(content, SimpleDnsRecord[].class));
167 } catch (IOException e) {
168 throw illegalState(e);
169 }
170 }
171
172 protected static void store(List<SimpleDnsRecord> records) {
173 JsonService json = new JacksonJsonService();
174 Function<SimpleDnsRecord, String> sorter = ReverseHostnameFunction.INSTANCE;
175 List<SimpleDnsRecord> sorted = Ordering.natural().onResultOf(sorter).sortedCopy(records);
176 String data = json.writeString(sorted);
177 try {
178 info("create -> %s", CACHE);
179 write(CACHE, data);
180 } catch (IOException e) {
181 throw illegalState(e);
182 }
183 }
184
185 protected static Properties convert(Map<String, String> map) {
186 Properties props = new Properties();
187 props.putAll(map);
188 return ImmutableProperties.copyOf(props);
189 }
190
191 protected static Map<String, String> convert(Properties props) {
192 Map<String, String> map = newTreeMap();
193 for (String key : props.stringPropertyNames()) {
194 map.put(key, props.getProperty(key));
195 }
196 return ImmutableMap.copyOf(map);
197 }
198
199 private enum CNAMEPredicate implements Predicate<SimpleDnsRecord> {
200 INSTANCE;
201
202 @Override
203 public boolean apply(SimpleDnsRecord record) {
204 return record.getType().equals(CNAME);
205 }
206 }
207
208 private enum ReverseHostnameFunction implements Function<SimpleDnsRecord, String> {
209 INSTANCE;
210
211 @Override
212 public String apply(SimpleDnsRecord record) {
213 return hostnameToKey().apply(record.getName());
214 }
215 }
216
217 protected static void elapsed(Stopwatch sw) {
218 info("elapsed -> %s", getTime(sw));
219 }
220
221 protected static void debug(String msg, Object... args) {
222 logger.debug((args == null || args.length == 0) ? msg : format(msg, args));
223 }
224
225 protected static void info(String msg, Object... args) {
226 logger.info((args == null || args.length == 0) ? msg : format(msg, args));
227 }
228
229 }