Update Fixes from testing
[aaf/authz.git] / auth / auth-cass / src / main / java / org / onap / aaf / auth / dao / CassAccess.java
1 /**
2  * ============LICENSE_START====================================================
3  * org.onap.aaf
4  * ===========================================================================
5  * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved.
6  * ===========================================================================
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  * 
11  *      http://www.apache.org/licenses/LICENSE-2.0
12  * 
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  * ============LICENSE_END====================================================
19  *
20  */
21
22 package org.onap.aaf.auth.dao;
23
24 import java.io.IOException;
25 import java.util.ArrayList;
26 import java.util.List;
27
28 import org.onap.aaf.auth.env.AuthzEnv;
29 import org.onap.aaf.cadi.config.Config;
30 import org.onap.aaf.cadi.routing.GreatCircle;
31 import org.onap.aaf.misc.env.APIException;
32 import org.onap.aaf.misc.env.Env;
33 import org.onap.aaf.misc.env.util.Split;
34
35 import com.datastax.driver.core.Cluster;
36 import com.datastax.driver.core.Cluster.Builder;
37 import com.datastax.driver.core.SocketOptions;
38 import com.datastax.driver.core.policies.DCAwareRoundRobinPolicy;
39 import com.datastax.driver.core.policies.TokenAwarePolicy;
40
41 public class CassAccess {
42     public static final String KEYSPACE = "authz";
43     public static final String CASSANDRA_CLUSTERS = "cassandra.clusters";
44     public static final String CASSANDRA_CLUSTERS_PORT = "cassandra.clusters.port";
45     public static final String CASSANDRA_CLUSTERS_USER_NAME = "cassandra.clusters.user";
46     public static final String CASSANDRA_CLUSTERS_PASSWORD = "cassandra.clusters.password";
47     public static final String CASSANDRA_RESET_EXCEPTIONS = "cassandra.reset.exceptions";
48     private static final List<Resettable> resetExceptions = new ArrayList<>();
49     public static final String ERR_ACCESS_MSG = "Accessing Backend";
50     private static Builder cb = null;
51
52     /**
53      * To create DCAwareRoundRobing Policy:
54      *      Need Properties
55      *         LATITUDE (or AFT_LATITUDE)
56      *         LONGITUDE (or AFT_LONGITUDE)
57      *         CASSANDRA CLUSTERS with additional information:
58      *             machine:DC:lat:long,machine:DC:lat:long
59      * @param env
60      * @param prefix
61      * @return
62      * @throws APIException
63      * @throws IOException
64      */
65
66 //    @SuppressWarnings("deprecation")
67     public static synchronized Cluster cluster(Env env, String prefix) throws APIException, IOException {
68         if (cb == null) {
69             String pre;
70             if (prefix==null) {
71                 pre="";
72             } else {
73                 env.info().log("Cassandra Connection for ",prefix);
74                 pre = prefix+'.';
75             }
76             cb = Cluster.builder();
77             String str = env.getProperty(pre+CASSANDRA_CLUSTERS_PORT,env.getProperty(CASSANDRA_CLUSTERS_PORT,"9042"));
78             if (str!=null) {
79                 env.init().log("Cass Port = ",str );
80                 cb.withPort(Integer.parseInt(str));
81             }
82             str = env.getProperty(pre+CASSANDRA_CLUSTERS_USER_NAME,env.getProperty(CASSANDRA_CLUSTERS_USER_NAME,null));
83             if (str!=null) {
84                 env.init().log("Cass User = ",str );
85                 String epass = env.getProperty(pre + CASSANDRA_CLUSTERS_PASSWORD,env.getProperty(CASSANDRA_CLUSTERS_PASSWORD,null));
86                 if (epass==null) {
87                     throw new APIException("No Password configured for " + str);
88                 }
89                 //TODO Figure out way to ensure Decryptor setting in AuthzEnv
90                 if (env instanceof AuthzEnv) {
91                     cb.withCredentials(str,((AuthzEnv)env).decrypt(epass,true));
92                 } else {
93                     cb.withCredentials(str, env.decryptor().decrypt(epass));
94                 }
95             }
96     
97             str = env.getProperty(pre+CASSANDRA_RESET_EXCEPTIONS,env.getProperty(CASSANDRA_RESET_EXCEPTIONS,null));
98             if (str!=null) {
99                 env.init().log("Cass ResetExceptions = ",str );
100                 for (String ex : Split.split(',', str)) {
101                     resetExceptions.add(new Resettable(env,ex));
102                 }
103             }
104     
105             str = env.getProperty(Config.CADI_LATITUDE);
106             Double lat = str!=null && !str.isEmpty()?Double.parseDouble(str):null;
107             str = env.getProperty(Config.CADI_LONGITUDE);
108             Double lon = str!=null && !str.isEmpty()?Double.parseDouble(str):null;
109             if (lat == null || lon == null) {
110                 throw new APIException(Config.CADI_LATITUDE + " and/or " + Config.CADI_LONGITUDE + " are not set");
111             }
112             
113             env.init().printf("Service Latitude,Longitude = %f,%f",lat,lon);
114             
115             str = env.getProperty(pre+CASSANDRA_CLUSTERS,env.getProperty(CASSANDRA_CLUSTERS,"localhost"));
116             env.init().printf("Cass Clusters = '%s'\n",str );
117             String[] machs = Split.split(',', str);
118             String[] cpoints = new String[machs.length];
119             String bestDC = null;
120             int numInBestDC = 1;
121             double mlat, mlon,temp,distance = Double.MAX_VALUE;
122             for (int i=0;i<machs.length;++i) {
123                 String[] minfo = Split.split(':',machs[i]);
124                 if (minfo.length>0) {
125                     cpoints[i]=minfo[0];
126                 }
127                 
128                 if (minfo.length>3) {
129                     if (minfo[1].equals(bestDC)) {
130                         ++numInBestDC;
131                     } else {
132                         // Calc closest DC with Great Circle
133                         mlat = Double.parseDouble(minfo[2]);
134                         mlon = Double.parseDouble(minfo[3]);
135                         // Note: GreatCircle Distance is always >= 0.0 (not negative)
136                         if ((temp=GreatCircle.calc(lat, lon, mlat, mlon)) < distance) {
137                             distance = temp;
138                             if (bestDC==null || !bestDC.equals(minfo[1])) {
139                                 bestDC = minfo[1];
140                                 numInBestDC = 1;
141                             }
142                         }
143                     }
144                 }
145             }
146             
147             cb.addContactPoints(cpoints);
148             
149             if (bestDC!=null) {
150                 // 8/26/2016 Management has determined that Accuracy is preferred over speed in bad situations
151                 // Local DC Aware Load Balancing appears to have the highest normal performance, with the best
152                 // Degraded Accuracy
153                 DCAwareRoundRobinPolicy dcrrPolicy = DCAwareRoundRobinPolicy.builder()
154                     .withLocalDc(bestDC)
155                     .withUsedHostsPerRemoteDc(numInBestDC)
156                     .build();
157 //                cb.withLoadBalancingPolicy(new DCAwareRoundRobinPolicy(
158 //                        bestDC, numInBestDC, true /*allow LocalDC to look at other DCs for LOCAL_QUORUM */));
159                 cb.withLoadBalancingPolicy(new TokenAwarePolicy(dcrrPolicy));
160                 env.init().printf("Cassandra configured for DCAwareRoundRobinPolicy with best DC at %s with emergency remote of up to %d node(s)"
161                     ,bestDC, numInBestDC);
162             } else {
163                 env.init().printf("Cassandra is using Default Policy, which is not DC aware");
164             }
165         }
166         cb.withSocketOptions(new SocketOptions().setReadTimeoutMillis(6500000));
167         return cb.build();
168     }
169     
170     private static class Resettable {
171         private Class<? extends Exception> cls;
172         private List<String> messages;
173         
174         @SuppressWarnings("unchecked")
175         public Resettable(Env env, String propData) throws APIException {
176             if (propData!=null && propData.length()>1) {
177                 String[] split = Split.split(':', propData);
178                 if (split.length>0) {
179                     try {
180                         cls = (Class<? extends Exception>)Class.forName(split[0]);
181                     } catch (ClassNotFoundException e) {
182                         throw new APIException("Declared Cassandra Reset Exception, " + propData + ", cannot be ClassLoaded");
183                     }
184                 }
185                 if (split.length>1) {
186                     messages=new ArrayList<>();
187                     for (int i=1;i<split.length;++i) {
188                         String str = split[i];
189                         int start = str.startsWith("\"")?1:0;
190                         int end = str.length()-(str.endsWith("\"")?1:0);
191                         messages.add(split[i].substring(start, end));
192                     }
193                 } else {
194                     messages = null;
195                 }
196             }
197         }
198         
199         public boolean matches(Exception ex) {
200             if (ex.getClass().equals(cls)) {
201                 if (messages!=null) {
202                     String msg = ex.getMessage();
203                     for (String m : messages) {
204                         if (msg.contains(m)) {
205                             return true;
206                         }
207                     }
208                 }
209             }
210             return false;
211         }
212     }
213     
214     public static final boolean isResetException(Exception e) {
215         if (e==null) {
216             return true;
217         }
218         for (Resettable re : resetExceptions) {
219             if (re.matches(e)) {
220                 return false;
221             }
222         }
223         return true;
224     }
225 }