1 /*
2  * ConfigureDatabase.actor.cpp
3  *
4  * This source file is part of the FoundationDB open source project
5  *
6  * Copyright 2013-2018 Apple Inc. and the FoundationDB project authors
7  *
8  * Licensed under the Apache License, Version 2.0 (the "License");
9  * you may not use this file except in compliance with the License.
10  * You may obtain a copy of the License at
11  *
12  *     http://www.apache.org/licenses/LICENSE-2.0
13  *
14  * Unless required by applicable law or agreed to in writing, software
15  * distributed under the License is distributed on an "AS IS" BASIS,
16  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17  * See the License for the specific language governing permissions and
18  * limitations under the License.
19  */
20 
21 #include "fdbclient/NativeAPI.actor.h"
22 #include "fdbserver/TesterInterface.actor.h"
23 #include "fdbclient/ManagementAPI.actor.h"
24 #include "fdbserver/workloads/workloads.actor.h"
25 #include "fdbrpc/simulator.h"
26 #include "flow/actorcompiler.h"  // This must be the last #include.
27 
28 // "ssd" is an alias to the preferred type which skews the random distribution toward it but that's okay.
29 static const char* storeTypes[] = { "ssd", "ssd-1", "ssd-2", "memory", "memory-1", "memory-2" };
30 static const char* logTypes[] = { "log_engine:=1", "log_engine:=2", "log_spill:=1", "log_spill:=2", "log_version:=2", "log_version:=3" };
31 static const char* redundancies[] = { "single", "double", "triple" };
32 
generateRegions()33 std::string generateRegions() {
34 	std::string result;
35 	if(g_simulator.physicalDatacenters == 1 || (g_simulator.physicalDatacenters == 2 && g_random->random01() < 0.25) || g_simulator.physicalDatacenters == 3) {
36 		return " usable_regions=1 regions=\"\"";
37 	}
38 
39 	if(g_random->random01() < 0.25) {
40 		return format(" usable_regions=%d", g_random->randomInt(1,3));
41 	}
42 
43 	int primaryPriority = 1;
44 	int remotePriority = -1;
45 	double priorityType = g_random->random01();
46 	if(priorityType < 0.1) {
47 		primaryPriority = -1;
48 		remotePriority = 1;
49 	} else if(priorityType < 0.2) {
50 		remotePriority = 1;
51 		primaryPriority = 1;
52 	}
53 
54 	StatusObject primaryObj;
55 	StatusObject primaryDcObj;
56 	primaryDcObj["id"] = "0";
57 	primaryDcObj["priority"] = primaryPriority;
58 	StatusArray primaryDcArr;
59 	primaryDcArr.push_back(primaryDcObj);
60 
61 	StatusObject remoteObj;
62 	StatusObject remoteDcObj;
63 	remoteDcObj["id"] = "1";
64 	remoteDcObj["priority"] = remotePriority;
65 	StatusArray remoteDcArr;
66 	remoteDcArr.push_back(remoteDcObj);
67 
68 	if(g_simulator.physicalDatacenters > 3 && g_random->random01() < 0.5) {
69 		StatusObject primarySatelliteObj;
70 		primarySatelliteObj["id"] = "2";
71 		primarySatelliteObj["priority"] = 1;
72 		primarySatelliteObj["satellite"] = 1;
73 		primaryDcArr.push_back(primarySatelliteObj);
74 
75 		StatusObject remoteSatelliteObj;
76 		remoteSatelliteObj["id"] = "3";
77 		remoteSatelliteObj["priority"] = 1;
78 		remoteSatelliteObj["satellite"] = 1;
79 		remoteDcArr.push_back(remoteSatelliteObj);
80 
81 		if(g_simulator.physicalDatacenters > 5 && g_random->random01() < 0.5) {
82 			StatusObject primarySatelliteObjB;
83 			primarySatelliteObjB["id"] = "4";
84 			primarySatelliteObjB["priority"] = 1;
85 			primarySatelliteObjB["satellite"] = 1;
86 			primaryDcArr.push_back(primarySatelliteObjB);
87 
88 			StatusObject remoteSatelliteObjB;
89 			remoteSatelliteObjB["id"] = "5";
90 			remoteSatelliteObjB["priority"] = 1;
91 			remoteSatelliteObjB["satellite"] = 1;
92 			remoteDcArr.push_back(remoteSatelliteObjB);
93 
94 			int satellite_replication_type = g_random->randomInt(0,3);
95 			switch (satellite_replication_type) {
96 			case 0: {
97 				TEST( true );  // Simulated cluster using no satellite redundancy mode
98 				break;
99 			}
100 			case 1: {
101 				TEST( true );  // Simulated cluster using two satellite fast redundancy mode
102 				primaryObj["satellite_redundancy_mode"] = "two_satellite_fast";
103 				remoteObj["satellite_redundancy_mode"] = "two_satellite_fast";
104 				break;
105 			}
106 			case 2: {
107 				TEST( true );  // Simulated cluster using two satellite safe redundancy mode
108 				primaryObj["satellite_redundancy_mode"] = "two_satellite_safe";
109 				remoteObj["satellite_redundancy_mode"] = "two_satellite_safe";
110 				break;
111 			}
112 			default:
113 				ASSERT(false);  // Programmer forgot to adjust cases.
114 			}
115 		} else {
116 			int satellite_replication_type = g_random->randomInt(0,4);
117 			switch (satellite_replication_type) {
118 			case 0: {
119 				//FIXME: implement
120 				TEST( true );  // Simulated cluster using custom satellite redundancy mode
121 				break;
122 			}
123 			case 1: {
124 				TEST( true );  // Simulated cluster using no satellite redundancy mode
125 				break;
126 			}
127 			case 2: {
128 				TEST( true );  // Simulated cluster using single satellite redundancy mode
129 				primaryObj["satellite_redundancy_mode"] = "one_satellite_single";
130 				remoteObj["satellite_redundancy_mode"] = "one_satellite_single";
131 				break;
132 			}
133 			case 3: {
134 				TEST( true );  // Simulated cluster using double satellite redundancy mode
135 				primaryObj["satellite_redundancy_mode"] = "one_satellite_double";
136 				remoteObj["satellite_redundancy_mode"] = "one_satellite_double";
137 				break;
138 			}
139 			default:
140 				ASSERT(false);  // Programmer forgot to adjust cases.
141 			}
142 		}
143 
144 		if (g_random->random01() < 0.25) {
145 			int logs = g_random->randomInt(1,7);
146 			primaryObj["satellite_logs"] = logs;
147 			remoteObj["satellite_logs"] = logs;
148 		}
149 
150 		int remote_replication_type = g_random->randomInt(0, 4);
151 		switch (remote_replication_type) {
152 		case 0: {
153 			//FIXME: implement
154 			TEST( true );  // Simulated cluster using custom remote redundancy mode
155 			break;
156 		}
157 		case 1: {
158 			TEST( true );  // Simulated cluster using default remote redundancy mode
159 			break;
160 		}
161 		case 2: {
162 			TEST( true );  // Simulated cluster using single remote redundancy mode
163 			result += " remote_single";
164 			break;
165 		}
166 		case 3: {
167 			TEST( true );  // Simulated cluster using double remote redundancy mode
168 			result += " remote_double";
169 			break;
170 		}
171 		default:
172 			ASSERT(false);  // Programmer forgot to adjust cases.
173 		}
174 
175 		result += format(" log_routers=%d", g_random->randomInt(1,7));
176 		result += format(" remote_logs=%d", g_random->randomInt(1,7));
177 	}
178 
179 	primaryObj["datacenters"] = primaryDcArr;
180 	remoteObj["datacenters"] = remoteDcArr;
181 
182 	StatusArray regionArr;
183 	regionArr.push_back(primaryObj);
184 
185 	if(g_random->random01() < 0.8) {
186 		regionArr.push_back(remoteObj);
187 		if(g_random->random01() < 0.25) {
188 			result += format(" usable_regions=%d", g_random->randomInt(1,3));
189 		}
190 	}
191 
192 	result += " regions=" + json_spirit::write_string(json_spirit::mValue(regionArr), json_spirit::Output_options::none);
193 	return result;
194 }
195 
196 
197 
198 struct ConfigureDatabaseWorkload : TestWorkload {
199 	double testDuration;
200 	int additionalDBs;
201 
202 	vector<Future<Void>> clients;
203 	PerfIntCounter retries;
204 
ConfigureDatabaseWorkloadConfigureDatabaseWorkload205 	ConfigureDatabaseWorkload( WorkloadContext const& wcx )
206 		: TestWorkload(wcx), retries("Retries")
207 	{
208 		testDuration = getOption( options, LiteralStringRef("testDuration"), 200.0 );
209 		g_simulator.usableRegions = 1;
210 	}
211 
descriptionConfigureDatabaseWorkload212 	virtual std::string description() { return "DestroyDatabaseWorkload"; }
213 
setupConfigureDatabaseWorkload214 	virtual Future<Void> setup( Database const& cx ) {
215 		return _setup( cx, this );
216 	}
217 
startConfigureDatabaseWorkload218 	virtual Future<Void> start( Database const& cx ) {
219 		return _start( this, cx );
220 	}
checkConfigureDatabaseWorkload221 	virtual Future<bool> check( Database const& cx ) {
222 		return true;
223 	}
224 
getMetricsConfigureDatabaseWorkload225 	virtual void getMetrics( vector<PerfMetric>& m ) {
226 		m.push_back( retries.getMetric() );
227 	}
228 
valueToUInt64ConfigureDatabaseWorkload229 	static inline uint64_t valueToUInt64( const StringRef& v ) {
230 		long long unsigned int x = 0;
231 		sscanf( v.toString().c_str(), "%llx", &x );
232 		return x;
233 	}
234 
getDatabaseNameConfigureDatabaseWorkload235 	static inline Standalone<StringRef> getDatabaseName( ConfigureDatabaseWorkload *self, int dbIndex ) {
236 		return StringRef(format("DestroyDB%d", dbIndex));
237 	}
238 
IssueConfigurationChangeConfigureDatabaseWorkload239 	static Future<ConfigurationResult::Type> IssueConfigurationChange( Database cx, const std::string& config, bool force ) {
240 		printf("Issuing configuration change: %s\n", config.c_str());
241 		return changeConfig(cx, config, force);
242 	}
243 
_setupConfigureDatabaseWorkload244 	ACTOR Future<Void> _setup( Database cx, ConfigureDatabaseWorkload *self ) {
245 		wait(success( changeConfig( cx, "single", true ) ));
246 		return Void();
247 	}
248 
_startConfigureDatabaseWorkload249 	ACTOR Future<Void> _start( ConfigureDatabaseWorkload *self, Database cx ) {
250 		if( self->clientId == 0 ) {
251 			self->clients.push_back( timeout( self->singleDB( self, cx ), self->testDuration, Void() ) );
252 			wait( waitForAll( self->clients ) );
253 		}
254 		return Void();
255 	}
256 
randomRoleNumberConfigureDatabaseWorkload257 	static int randomRoleNumber() {
258 		int i = g_random->randomInt(0,4);
259 		return i ? i : -1;
260 	}
261 
singleDBConfigureDatabaseWorkload262 	ACTOR Future<Void> singleDB( ConfigureDatabaseWorkload *self, Database cx ) {
263 		state Transaction tr;
264 		state int i;
265 		state bool firstFearless = false;
266 		loop {
267 			if(g_simulator.speedUpSimulation) {
268 				return Void();
269 			}
270 			state int randomChoice = g_random->randomInt(0, 7);
271 			if( randomChoice == 0 ) {
272 				double waitDuration = 3.0 * g_random->random01();
273 				//TraceEvent("ConfigureTestWaitAfter").detail("WaitDuration",waitDuration);
274 				wait( delay( waitDuration ) );
275 			}
276 			else if( randomChoice == 1 ) {
277 				tr = Transaction( cx );
278 				loop {
279 					try {
280 						tr.clear( normalKeys );
281 						wait( tr.commit() );
282 						break;
283 					} catch( Error &e ) {
284 						wait( tr.onError(e) );
285 					}
286 				}
287 			}
288 			else if( randomChoice == 2 ) {
289 				state double loadDuration = g_random->random01() * 10.0;
290 				state double startTime = now();
291 				state int amtLoaded = 0;
292 
293 				loop {
294 					if( now() - startTime > loadDuration )
295 						break;
296 					loop {
297 						tr = Transaction( cx );
298 						try {
299 							for( i = 0; i < 10; i++ ) {
300 								state Key randomKey( "ConfigureTest" + g_random->randomUniqueID().toString() );
301 								Optional<Value> val = wait( tr.get( randomKey ) );
302 								uint64_t nextVal = val.present() ? valueToUInt64( val.get() ) + 1 : 0;
303 								tr.set( randomKey, format( "%016llx", nextVal ) );
304 							}
305 							wait( tr.commit() );
306 							amtLoaded += 10;
307 							break;
308 						}
309 						catch( Error& e ) {
310 							wait( tr.onError( e ) );
311 							++self->retries;
312 						}
313 					}
314 					wait( delay( 0.1 ) );
315 				}
316 
317 				//TraceEvent("ConfigureTestLoadData").detail("LoadTime", now() - startTime).detail("AmountLoaded",amtLoaded);
318 			}
319 			else if( randomChoice == 3 ) {
320 				//TraceEvent("ConfigureTestConfigureBegin").detail("NewConfig", newConfig);
321 				int maxRedundancies = sizeof(redundancies)/sizeof(redundancies[0]);
322 				if(g_simulator.physicalDatacenters == 2 || g_simulator.physicalDatacenters > 3) {
323 					maxRedundancies--; //There are not enough machines for triple replication in fearless configurations
324 				}
325 				int redundancy = g_random->randomInt(0, maxRedundancies);
326 				std::string config = redundancies[redundancy];
327 
328 				if(config == "triple" && g_simulator.physicalDatacenters == 3) {
329 					config = "three_data_hall ";
330 				}
331 
332 				config += generateRegions();
333 
334 				if (g_random->random01() < 0.5) config += " logs=" + format("%d", randomRoleNumber());
335 				if (g_random->random01() < 0.5) config += " proxies=" + format("%d", randomRoleNumber());
336 				if (g_random->random01() < 0.5) config += " resolvers=" + format("%d", randomRoleNumber());
337 
338 				wait(success( IssueConfigurationChange( cx, config, false ) ));
339 
340 				//TraceEvent("ConfigureTestConfigureEnd").detail("NewConfig", newConfig);
341 			}
342 			else if( randomChoice == 4 ) {
343 				//TraceEvent("ConfigureTestQuorumBegin").detail("NewQuorum", s);
344 				auto ch = autoQuorumChange();
345 				if (g_random->randomInt(0,2))
346 					ch = nameQuorumChange( format("NewName%d", g_random->randomInt(0,100)), ch );
347 				wait(success( changeQuorum( cx, ch ) ));
348 				//TraceEvent("ConfigureTestConfigureEnd").detail("NewQuorum", s);
349 			}
350 			else if ( randomChoice == 5) {
351 				wait(success( IssueConfigurationChange( cx, storeTypes[g_random->randomInt( 0, sizeof(storeTypes)/sizeof(storeTypes[0]))], true ) ));
352 			}
353 			else if ( randomChoice == 6 ) {
354 				// Some configurations will be invalid, and that's fine.
355 				wait(success( IssueConfigurationChange( cx, logTypes[g_random->randomInt( 0, sizeof(logTypes)/sizeof(logTypes[0]))], false ) ));
356 			}
357 			else {
358 				ASSERT(false);
359 			}
360 		}
361 	}
362 };
363 
364 WorkloadFactory<ConfigureDatabaseWorkload> DestroyDatabaseWorkloadFactory("ConfigureDatabase");
365