├── .gitignore ├── CHANGES.md ├── MANIFEST.in ├── README.md ├── cassandralauncher ├── README.md ├── __init__.py ├── cassandralauncher.py ├── clusterlauncher.conf ├── clusterlauncher.py ├── common.py ├── datastax_pssh ├── datastax_s3_restore ├── datastax_s3_store ├── datastax_ssh ├── ec2.py ├── rax.py └── s3cfg ├── demoservice ├── README.md └── demoservice.py ├── docs ├── new_features.md └── sample_runs.md ├── qa ├── run_packaging_smoke.sh └── test_generation.py ├── scripts ├── cassandralauncher └── imagelauncher └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | TODO 3 | CassandraLauncher.egg-info/ 4 | running.log 5 | dist/ 6 | cassandralauncher/nodelist 7 | -------------------------------------------------------------------------------- /CHANGES.md: -------------------------------------------------------------------------------- 1 | Changes 2 | ======= 3 | 4 | * 1.15-0: Regions besides us-east-1 now supported. 5 | * 1.16-0: DSE 2.0 support. QA folder with test generation 6 | and smoke testing scripts. 7 | * 1.17-0: Notify users when `ssh-keyscan` fails and workaround 8 | OpsCenter 2.1.0 bug with installing agents. 9 | * 1.18-0: Added datastax_pssh and s3 store/restore functionality. 10 | See [New Features](https://github.com/joaquincasares/cassandralauncher/tree/master/docs/new_features.md) for details. 11 | * 1.18-1: Added needed files to the manifest. 12 | * 1.18-2: Added needed files to setup.py. 13 | * 1.18-3: Display RAX authentication errors. 14 | * 1.18-4: Allows the --qa switch to run in parallel threads. 15 | * 1.18-5: Trying to fine tune the no RSA key edge case. 16 | * 1.18-7: Updated to the newest 2.4 AMI built on Ubuntu 12.04 LTS. (No other major changes made during this transition.) 17 | * 1.19-4: Be less aggressive with the security group. 18 | * 1.19-5: Remove handle requirement for imagelauncher. 19 | * 1.19-6: Stop installing OpsCenter agents and rely on datastax-agent logic on the AMI. 20 | 21 | Automated Features 22 | ================== 23 | 24 | The list of automated features include: 25 | 26 | * DataStax username and password memory 27 | * AWS API key memory 28 | * Instance size memory 29 | * Input validation 30 | * Automated RSA fingerprint checking 31 | * Automatic separate known_hosts file 32 | * Pre-built SSH strings 33 | * Automatic OpsCenter agent installation 34 | * Passwordless SSH around the cluster 35 | * datastax_ssh tool 36 | * Allows for SSH commands to easily be run across an entire cluster 37 | * nodelist file 38 | * An uploaded file onto the cluster for later, possible, use 39 | * Modified hosts file 40 | * Allows for easy jumping between machines, e.g. c0, c1, a0, a1, s0, ... 41 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.md 2 | include cassandralauncher/clusterlauncher.conf 3 | include cassandralauncher/datastax_s3_store 4 | include cassandralauncher/datastax_s3_restore 5 | include cassandralauncher/datastax_ssh 6 | include cassandralauncher/datastax_pssh 7 | include cassandralauncher/s3cfg 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Apache Cassandra / DataStax Community/Enterprise Launcher (and Plain Image Launcher) 2 | 3 | This project has two offerings. They are: 4 | 5 | 1. Cassandra Launcher - An easy to use, interactive command line interface that allows you to deterministically launch a DataStax Community or DataStax Enterprise cluster in under a minute. This component also comes with a fully scriptable interface for cluster launches straight from the command line. 6 | 2. Plain Image Launcher - A great tool that combines both Amazon's EC2 and Rackspace's Cloudservers into a single tool that allows for deterministicly easy clustering, keyless ssh, and interactive prompts for choosing your environment. No longer must you search for images IDs or wait past page reloads for a cluster since this is all done on the client side. 7 | 8 | ## Installation 9 | 10 | **Note:** This repository does not need to be cloned. Everything is already included in the pip commands. 11 | 12 | Make sure `python-setuptools` and `python-pip` are installed, then run: 13 | 14 | pip install cassandralauncher 15 | 16 | ## Upgrading 17 | 18 | pip install --upgrade cassandralauncher 19 | 20 | ## Setup 21 | 22 | Start the program once to copy the default `/usr/local/etc/cassandralauncher/clusterlauncher.conf` to `~/.clusterlauncher.conf`. 23 | 24 | Exit the program and edit `~/.clusterlauncher.conf` with your appropriate authentication parameters. 25 | 26 | ## Apache Cassandra / DataStax Community/Enterprise Launcher 27 | 28 | cassandralauncher 29 | 30 | ## EC2/RAX Plain Image Launcher 31 | 32 | imagelauncher 33 | 34 | ## To destroy 35 | 36 | Either run `cassandralauncher` or `imagelauncher` again. 37 | 38 | * With `cassandralauncher`: 39 | 40 | * Select Cluster, Confirm. 41 | 42 | * With `imagelauncher`: 43 | 44 | * Select EC2 or RAX, Destroy, Cluster, Confirm. 45 | 46 | _THIS MUST BE DONE! IF NOT YOUR AWS ACCOUNT WILL GET A HUGE BILL. TAKE IT FROM ME!_ 47 | 48 | **Disclaimer:** Even though these tools try their best to keep track of launched clusters, 49 | it is always best to ensure that all clusters are terminated periodically. This is especially 50 | true in cases where AWS/RAX was unable to tag the machine as they were launched. If the tools 51 | were unable to tag said machines, they will forever ignore them since we would rather not even 52 | present the option to kill a cluster these tools did not launch. 53 | 54 | ## More Documentation 55 | 56 | * [CHANGES](https://github.com/joaquincasares/cassandralauncher/tree/master/CHANGES.md) 57 | * [Sample Runs](https://github.com/joaquincasares/cassandralauncher/tree/master/docs/sample_runs.md) 58 | * [New Features](https://github.com/joaquincasares/cassandralauncher/tree/master/docs/new_features.md) 59 | 60 | ## Programmatically Launching Apache Cassandra / DataStax Community/Enterprise Clusters 61 | 62 | Run: 63 | 64 | cassandralauncher -h 65 | 66 | to display all options. `imagelauncher` does not have this functionality, but is easily scriptable at the Python level calling ec2.py or rax.py. See `clusterlauncher.py` for how to do this. 67 | 68 | ## FAQ 69 | 70 | My cluster is not done launching one (or several) of my nodes. What did I do wrong? 71 | 72 | Nothing. EC2 and Rackspace do this from time to time. You can either continue on to do 73 | basic testing, or terminate this cluster and try again. Using EC2 and Rackspace off it's 74 | peak hours helps in this scenario, in general. 75 | -------------------------------------------------------------------------------- /cassandralauncher/README.md: -------------------------------------------------------------------------------- 1 | # Cluster Launcher and Cassandra Launcher from Source 2 | 3 | ## Prereqs for source only downloads 4 | 5 | pip install boto 6 | pip install python-cloudservers 7 | 8 | ## Setup 9 | 10 | Same as pip install instructions. 11 | 12 | ## EC2/RAX Plain Cluster Launcher 13 | 14 | Run `./clusterlauncher.py` from this directory. 15 | 16 | ## Cassandra Launcher 17 | 18 | Run `./cassandralauncher.py` from this directory 19 | 20 | ## To destroy 21 | 22 | Same as pip installation instructions except the files are located in this directory: 23 | 24 | ./clusterlauncher.py 25 | ./cassandralauncher.py 26 | 27 | _THIS MUST BE DONE! IF NOT YOUR AWS ACCOUNT WILL GET A HUGE BILL. TAKE IT FROM ME!_ 28 | 29 | **Disclaimer:** Even though these tools try their best to keep track of launched clusters, 30 | it is always best to ensure that all clusters are terminated periodically. This is especially 31 | true in cases where AWS/RAX was unable to tag the machine as they were launched. If the tools 32 | were unable to tag said machines, they will forever ignore them since we would rather not even 33 | present the option to kill a cluster these tools did not launch. 34 | -------------------------------------------------------------------------------- /cassandralauncher/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | class CassandraLauncher: 4 | def __init__(self): 5 | import cassandralauncher 6 | cassandralauncher.run() 7 | 8 | class ClusterLauncher: 9 | def __init__(self): 10 | import clusterlauncher 11 | clusterlauncher.run() 12 | -------------------------------------------------------------------------------- /cassandralauncher/cassandralauncher.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import getpass 4 | import os 5 | import re 6 | import shlex 7 | import subprocess 8 | import sys 9 | import tempfile 10 | import time 11 | import urllib2 12 | 13 | import ec2 14 | import common 15 | 16 | 17 | # Globals 18 | private_ips = [] 19 | public_ips = [] 20 | 21 | config, KEY_PAIR, PEM_HOME, HOST_FILE, PEM_FILE = common.header() 22 | cli_options = {} 23 | 24 | 25 | ################################# 26 | # Execution and SSH commands 27 | 28 | def exe(command, wait=True): 29 | """Execute a subprocess command""" 30 | 31 | # Open a subprocess to run your command 32 | process = subprocess.Popen(shlex.split(str(command)), stdout=subprocess.PIPE, stderr=subprocess.PIPE) 33 | if wait: 34 | read = process.communicate() 35 | return read 36 | else: 37 | return process 38 | 39 | def create_ssh_cmd(sshuser, host): 40 | """SSH function that returns SSH string""" 41 | 42 | additional_options = '' 43 | if cli_options['CLI_qa']: 44 | additional_options = '-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no' 45 | 46 | connstring = "%s %s -i %s -o UserKnownHostsFile=%s %s@%s " % ( 47 | config.get('System', 'ssh'), 48 | additional_options, 49 | PEM_FILE, 50 | HOST_FILE, 51 | sshuser, host 52 | ) 53 | return connstring 54 | 55 | def exe_ssh_cmd(connstring, command, wait=True): 56 | """SSH function that executes SSH string + command""" 57 | 58 | return exe('{0} "{1}"'.format(connstring, command), wait) 59 | 60 | def scp_send(sshuser, host, sendfile, tolocation=''): 61 | """SCP send a file""" 62 | 63 | return exe("%s -i %s -o UserKnownHostsFile=%s %s %s@%s:%s" % ( 64 | config.get('System', 'scp'), 65 | PEM_FILE, 66 | HOST_FILE, 67 | sendfile, sshuser, host, tolocation 68 | )) 69 | 70 | ################################# 71 | 72 | ################################# 73 | # Keyless SSH Creation 74 | 75 | def confirm_authentication(username, password): 76 | repo_url = "http://debian.datastax.com/enterprise" 77 | 78 | # Configure HTTP authentication 79 | password_mgr = urllib2.HTTPPasswordMgrWithDefaultRealm() 80 | password_mgr.add_password(None, repo_url, username, password) 81 | handler = urllib2.HTTPBasicAuthHandler(password_mgr) 82 | opener = urllib2.build_opener(handler) 83 | 84 | # Try reading from the authenticated connection 85 | try: 86 | opener.open(repo_url) 87 | except Exception as inst: 88 | if "401" in str(inst): 89 | # Authentication failed 90 | return False 91 | raise 92 | return True 93 | 94 | ################################# 95 | 96 | ################################# 97 | # Install small tools 98 | 99 | def install_datastax_ssh(user): 100 | # Find the datastax_ssh original file 101 | datastax_ssh = os.path.join(os.path.dirname(__file__), 'datastax_ssh') 102 | if not os.path.exists(datastax_ssh): 103 | datastax_ssh = os.path.join('/etc', 'cassandralauncher', 'datastax_ssh') 104 | if not os.path.exists(datastax_ssh): 105 | datastax_ssh = os.path.join('/usr', 'local', 'etc', 'cassandralauncher', 'datastax_ssh') 106 | 107 | # Write the public IPs to the nodelist file 108 | with tempfile.NamedTemporaryFile() as tmp_file: 109 | tmp_file.write('\n'.join(public_ips)) 110 | tmp_file.flush() 111 | 112 | # Send files to the cluster 113 | for ip in public_ips: 114 | scp_send(user, ip, tmp_file.name, 'nodelist') 115 | 116 | scp_send(user, ip, datastax_ssh) 117 | scp_send(user, ip, datastax_ssh.replace('datastax_ssh', 'datastax_pssh')) 118 | 119 | exe_ssh_cmd(create_ssh_cmd(user, ip), 'chmod +x datastax_ssh; sudo mv datastax_ssh /usr/bin/') 120 | exe_ssh_cmd(create_ssh_cmd(user, ip), 'chmod +x datastax_pssh; sudo mv datastax_pssh /usr/bin/') 121 | 122 | exe_ssh_cmd(create_ssh_cmd(user, ip), 'sudo mkdir /etc/cassandralauncher; sudo mv nodelist /etc/cassandralauncher/nodelist') 123 | 124 | def upload_smoke_tests(user): 125 | # Find the datastax_ssh original file 126 | qa_file = os.path.join(os.path.dirname(__file__), '../', 'qa', 'run_packaging_smoke.sh') 127 | if not os.path.exists(qa_file): 128 | return 129 | 130 | # Send files to the cluster 131 | for ip in public_ips: 132 | scp_send(user, ip, qa_file, 'run_packaging_smoke.sh') 133 | exe_ssh_cmd(create_ssh_cmd(user, ip), 'chmod +x run_packaging_smoke.sh') 134 | # exe_ssh_cmd(create_ssh_cmd(user, ip), 'sudo ./run_packaging_smoke.sh hadoop pkg') 135 | # exe_ssh_cmd(create_ssh_cmd(user, ip), 'sudo ./run_packaging_smoke.sh search pkg') 136 | 137 | def install_hosts_appending(user): 138 | # Write the public IPs to a hosts file 139 | with tempfile.NamedTemporaryFile() as tmp_file: 140 | hosts_file = '' 141 | for i, ip in enumerate(private_ips): 142 | for node_type in node_types: 143 | if i in node_types[node_type]: 144 | i = node_types[node_type].index(i) 145 | break 146 | hosts_file += '{0:15} {1}\n'.format(ip, '{0}{1}'.format(node_type, i)) 147 | tmp_file.write(hosts_file) 148 | tmp_file.flush() 149 | 150 | # Send files to the cluster 151 | for ip in public_ips: 152 | scp_send(user, ip, tmp_file.name, 'hosts_file') 153 | exe_ssh_cmd(create_ssh_cmd(user, ip), "sudo su -c 'cat hosts_file >> /etc/hosts'") 154 | exe_ssh_cmd(create_ssh_cmd(user, ip), 'rm hosts_file') 155 | 156 | def setup_s3_store_and_restore(user): 157 | # Look for the configuration file in the same directory as the launcher 158 | s3cfg_default = os.path.join(os.path.dirname(__file__), 's3cfg') 159 | if not os.path.exists(s3cfg_default): 160 | # Look for the configuration file in the user's home directory 161 | s3cfg_default = os.path.join(os.path.expanduser('~'), '.s3cfg') 162 | if not os.path.exists(s3cfg_default): 163 | # Look for the configuration file in /etc/clusterlauncher 164 | s3cfg_default = os.path.join('/etc', 'cassandralauncher', 's3cfg') 165 | if not os.path.exists(s3cfg_default): 166 | # Look for the configuration file in /usr/local/etc/clusterlauncher 167 | s3cfg_default = os.path.join('/usr', 'local', 'etc', 'cassandralauncher', 's3cfg') 168 | with open(s3cfg_default) as f: 169 | s3cfg = f.read() 170 | 171 | s3cfg = s3cfg.replace('$ACCESS_KEY', check_cascading_options('aws_access_key_id')) 172 | s3cfg = s3cfg.replace('$SECRET_KEY', check_cascading_options('aws_secret_access_key')) 173 | 174 | with tempfile.NamedTemporaryFile() as tmp_file: 175 | tmp_file.write(s3cfg) 176 | tmp_file.flush() 177 | 178 | for ip in public_ips: 179 | if check_cascading_options('send_s3_credentials', optional=True, ignore_command_line=True): 180 | scp_send(user, ip, tmp_file.name, '.s3cfg') 181 | exe_ssh_cmd(create_ssh_cmd(user, ip), 'chmod 400 .s3cfg') 182 | 183 | scp_send(user, ip, os.path.join(os.path.dirname(__file__), 'datastax_s3_store'), 'datastax_s3_store') 184 | scp_send(user, ip, os.path.join(os.path.dirname(__file__), 'datastax_s3_restore'), 'datastax_s3_restore') 185 | exe_ssh_cmd(create_ssh_cmd(user, ip), 'chmod +x datastax_s3*; sudo mv datastax_s3* /usr/bin/') 186 | 187 | 188 | ################################# 189 | 190 | ################################# 191 | # Keyless SSH Creation 192 | 193 | def sshprompt(prompt, choices): 194 | """Helper for prompting that ensures user answer is included in choices. 195 | 'prompt' text is displayed after initial blank prompt, in the event that the answer is not included in the choices. 196 | """ 197 | 198 | # Repeat question until the input is a valid int 199 | while True: 200 | response = raw_input().lower() 201 | if response in choices: 202 | break 203 | print prompt, 204 | return response 205 | 206 | def prompt_rsa_fingerprints(ip, fingerprint): 207 | """Prompts the user to accept RSA fingerprints.""" 208 | 209 | # Generate and prompt 210 | securityText = [ 211 | "The authenticity of host '{0} ({0})' can't be established.", 212 | "RSA key fingerprint is {1}.", 213 | "Are you sure you want to continue connecting (yes/no/all)?" 214 | ] 215 | print "\n".join(securityText).format(ip, fingerprint), 216 | accept_rsa_fingerprints = sshprompt("Please type 'yes' or 'no' or 'all': ", ['yes', 'no', 'all']) 217 | 218 | # Allow one more chance if answered 'no' 219 | if accept_rsa_fingerprints == 'no': 220 | while True: 221 | confirmation = raw_input("Do you really want to cancel OpsCenter Agent Installation (y/n)? ").lower() 222 | if confirmation in ['y', 'n']: 223 | break 224 | if confirmation == 'y': 225 | sys.exit(1) 226 | else: 227 | print "\n".join(securityText), 228 | accept_rsa_fingerprints = sshprompt("Please type 'yes' or 'no' or 'all': ", ['yes', 'no', 'all']) 229 | if accept_rsa_fingerprints == 'no': 230 | sys.exit(1) 231 | 232 | return accept_rsa_fingerprints 233 | 234 | def create_keyless_ssh_ring(public_ips, user): 235 | """Create keyless SSH ring from primed servers""" 236 | 237 | print "Creating a keyless SSH ring..." 238 | 239 | # Send files to EC2 once 240 | kicker_conn = create_ssh_cmd(user, public_ips[0]) 241 | scp_send(user, public_ips[0], PEM_FILE) 242 | scp_send(user, public_ips[0], HOST_FILE) 243 | exe_ssh_cmd(kicker_conn, "mv %s .ssh/id_rsa; chmod 400 .ssh/id_rsa" % (KEY_PAIR + '.pem')) 244 | exe_ssh_cmd(kicker_conn, "mv %s .ssh/known_hosts; chmod 600 .ssh/known_hosts" % 'ds_known_hosts') 245 | 246 | # Transfer files from kicker node to all other nodes 247 | for node in public_ips[1:]: 248 | exe_ssh_cmd(kicker_conn, "scp .ssh/id_rsa %s:.ssh/id_rsa" % node) 249 | exe_ssh_cmd(kicker_conn, "scp .ssh/known_hosts %s:.ssh/known_hosts" % node) 250 | 251 | def prime_connections(public_ips, user): 252 | """Ask acceptance of RSA keys.""" 253 | 254 | print "Priming connections..." 255 | 256 | # Clear previous file, if not running parallel tests 257 | if not cli_options['CLI_qa']: 258 | with open(HOST_FILE, 'w') as f: 259 | f.write('') 260 | 261 | accept_rsa_fingerprints = config.get('Cassandra', 'accept_rsa_fingerprints') 262 | 263 | # Prompt for RSA fingerprint authentication for each IP 264 | for ip in public_ips: 265 | 266 | retries = 0 267 | while True: 268 | # Get public RSA key 269 | get_rsa_command = 'ssh-keyscan -t rsa {0}'.format(ip) 270 | rsa_key = exe(get_rsa_command)[0] 271 | 272 | if not rsa_key: 273 | if retries < 10: 274 | retries += 1 275 | time.sleep(2) 276 | continue 277 | 278 | sys.stderr.write('`{0}` failed to return a key. Please ensure this command works on your system.\n'.format(get_rsa_command)) 279 | sys.exit(1) 280 | 281 | with tempfile.NamedTemporaryFile() as tmp_file: 282 | tmp_file.write(rsa_key) 283 | tmp_file.flush() 284 | 285 | # Generate fingerprint 286 | fingerprint = exe('ssh-keygen -l -f {0}'.format(tmp_file.name))[0].split()[1] 287 | 288 | # Ensure that stderr didn't return the error message '* is not a public key file.' 289 | if fingerprint != 'is': 290 | break 291 | 292 | # If performing individual authentication, prompt 293 | if accept_rsa_fingerprints != 'all': 294 | accept_rsa_fingerprints = prompt_rsa_fingerprints(ip, fingerprint) 295 | 296 | # Append all public RSA keys into HOST_FILE 297 | with open(HOST_FILE, 'a') as f: 298 | f.write(rsa_key) 299 | 300 | # Clear authentication if not marked 'all' 301 | if accept_rsa_fingerprints != 'all': 302 | accept_rsa_fingerprints = '' 303 | 304 | create_keyless_ssh_ring(public_ips, user) 305 | 306 | ################################# 307 | 308 | ################################# 309 | # Installing OpsCenter Agents 310 | 311 | def start_priming(user): 312 | """Prime connections to all nodes""" 313 | 314 | # Give AWS some time to warm up 315 | wait = 10 316 | print "Waiting %s seconds for EC2 instances to warm up..." % wait 317 | time.sleep(wait) 318 | 319 | # Authenticate and ring cluster with keyless SSH 320 | prime_connections(public_ips, user) 321 | print 322 | 323 | ################################# 324 | 325 | ################################# 326 | # Log code for private stats 327 | 328 | def running_log(reservation, demotime): 329 | """Logs usage data for personal stats.""" 330 | 331 | loginfo = [ 332 | 'Running' if config.get('Cassandra', 'demo') == 'True' else 'Ignore', 333 | config.get('Shared', 'handle'), 334 | str(demotime), 335 | str(time.time()), 336 | str(reservation.id) 337 | ] 338 | logline = ",".join(loginfo) + '\n' 339 | 340 | with open('running.log', 'a') as f: 341 | f.write(logline) 342 | 343 | ################################# 344 | 345 | ################################# 346 | # Argument parsing 347 | 348 | # This holds the information for all options 349 | # and their metadata to allow easier command 350 | # line argument, configuration reading, and 351 | # raw_input prompting. 352 | 353 | options_tree = { 354 | 'handle': { 355 | 'Section': 'Shared', 356 | 'Prompt': 'ShortName (Handle)', 357 | 'Help': 'Your Personal Shortname' 358 | }, 359 | 'aws_access_key_id': { 360 | 'Section': 'EC2', 361 | 'Prompt': 'AWS Access Key ID', 362 | 'Help': 'AWS Access Key ID' 363 | }, 364 | 'aws_secret_access_key': { 365 | 'Section': 'EC2', 366 | 'Prompt': 'AWS Secret Access Key', 367 | 'Help': 'AWS Secret Access Key' 368 | }, 369 | 'security_public_inbound_source': { 370 | 'Section': 'EC2', 371 | 'Prompt': "Security Group Public Ports' Inbound Source", 372 | 'Help': "Specify the security group's inbound source for public ports in CIDR format. Multiple values can be comma-separated. (e.g. '0.0.0.0/0' or '10.0.0.0/8, 192.168.1.0/24')" 373 | }, 374 | 'send_s3_credentials': { 375 | 'Section': 'S3', 376 | 'Prompt': 'Send S3 Credentials', 377 | 'Action': 'store_true', 378 | 'Help': 'Specify if S3 Credentials get uploaded' 379 | }, 380 | 'clustername': { 381 | 'Section': 'Cassandra', 382 | 'Prompt': 'Cluster Name', 383 | 'Help': 'Name of the Cluster' 384 | }, 385 | 'totalnodes': { 386 | 'Section': 'Cassandra', 387 | 'Prompt': 'Total Nodes', 388 | 'Help': 'Number of Nodes in the Cluster' 389 | }, 390 | 'version': { 391 | 'Section': 'Cassandra', 392 | 'Prompt': 'Version:', 393 | 'Help': 'Community | Enterprise' 394 | }, 395 | 'installopscenter': { 396 | 'Section': 'Cassandra', 397 | 'Prompt': 'Install Opscenter', 398 | 'Help': 'True | False' 399 | }, 400 | 'release': { 401 | 'Section': 'Cassandra', 402 | 'Prompt': 'Specify Package Release', 403 | 'Help': 'Package Release' 404 | }, 405 | 'username': { 406 | 'Section': 'Cassandra', 407 | 'Prompt': 'DataStax Username', 408 | 'Help': 'DataStax Username' 409 | }, 410 | 'password': { 411 | 'Section': 'Cassandra', 412 | 'Prompt': 'Uses getpass()', 413 | 'Help': 'DataStax Password' 414 | }, 415 | 'analyticsnodes': { 416 | 'Section': 'Cassandra', 417 | 'Prompt': 'Analytics Nodes', 418 | 'Help': 'Number of Analytics Nodes' 419 | }, 420 | 'searchnodes': { 421 | 'Section': 'Cassandra', 422 | 'Prompt': 'Search Nodes', 423 | 'Help': 'Number of Search Nodes' 424 | }, 425 | 'cfsreplicationfactor': { 426 | 'Section': 'Cassandra', 427 | 'Prompt': 'CFS Replication Factor', 428 | 'Help': 'CFS Replication Factor' 429 | }, 430 | 'datastax_ami': { 431 | 'Section': 'Cassandra', 432 | 'Prompt': 'DataStax AMI ID', 433 | 'Help': 'DataStax AMI ID' 434 | }, 435 | 'dev': { 436 | 'Section': 'Cassandra', 437 | 'Prompt': 'Dev Branch', 438 | 'Action': 'store_true', 439 | 'Help': 'Dev Branch' 440 | }, 441 | 'demotime': { 442 | 'Section': 'Cassandra', 443 | 'Prompt': 'Time (in hours) for the cluster to live', 444 | 'Help': 'For use with DemoService' 445 | }, 446 | 'instance_type': { 447 | 'Section': 'EC2', 448 | 'Prompt': 'EC2 Instance Size:', 449 | 'Help': 'm1.large | m1.xlarge | m2.xlarge | m2.2xlarge | m2.4xlarge | hi1.4xlarge | hs1.8xlarge' 450 | }, 451 | 'opscenterinterface': { 452 | 'Section': 'OpsCenter', 453 | 'Prompt': 'NoPrompts', 454 | 'Help': 'Sets the OpsCenter interface port' 455 | }, 456 | 'noprompts':{ 457 | 'Section': 'CLI', 458 | 'Prompt': 'NoPrompts', 459 | 'Action': 'store_true', 460 | 'Help': 'Ensures that no user prompts will occur.' 461 | }, 462 | 'qa':{ 463 | 'Section': 'CLI', 464 | 'Prompt': 'QA', 465 | 'Action': 'store_true', 466 | 'Help': 'Upload QA scripts.' 467 | } 468 | } 469 | 470 | def type_checker(option, read_option, type_check, passive=False): 471 | """Ensures that the data read is of expected type.""" 472 | 473 | if type_check: 474 | try: 475 | read_option = type_check(read_option) 476 | except: 477 | if passive: 478 | return None 479 | sys.stderr.write('"{0}" was expected to be of {1}\n'.format(option, type_check)) 480 | sys.exit(1) 481 | return read_option 482 | 483 | def basic_option_checker(read_option, option, type_check, choices): 484 | """Performs basic checks on configuration and command line argument data.""" 485 | 486 | if read_option: 487 | read_option = type_checker(option, read_option, type_check) 488 | if choices: 489 | if any(read_option.lower() == choice.lower() for choice in choices): 490 | return read_option 491 | else: 492 | return read_option 493 | 494 | def check_cascading_options(option, type_check=False, choices=False, password=False, optional=False, ignore_command_line=False): 495 | """Reads from the command line arguments, then configuration file, then prompts 496 | the user for program options.""" 497 | section = options_tree[option]['Section'] 498 | 499 | # Read from sys.argv 500 | if not ignore_command_line: 501 | read_option = cli_options['{0}_{1}'.format(section, option)] 502 | read_option = basic_option_checker(read_option, option, type_check, choices) 503 | if read_option != None: 504 | return read_option 505 | 506 | # Read from configfile 507 | if config.has_option(section, option): 508 | read_option = config.get(section, option) 509 | read_option = basic_option_checker(read_option, option, type_check, choices) 510 | if read_option != None: 511 | return read_option 512 | 513 | if optional: 514 | return False 515 | 516 | # Exit(1) if you asked for --noprompts and didn't fill in all variables 517 | if cli_options['CLI_noprompts']: 518 | sys.stderr.write('Prompt occurred after specifying --noprompts.\n') 519 | sys.stderr.write('Missing/Invalid configuration for "--{0}".\n'.format(option)) 520 | sys.exit(1) 521 | 522 | # Prompt for password if special case 523 | if password: 524 | return getpass.getpass() 525 | 526 | # Prompt the user with raw_input or common.choose 527 | while True: 528 | prompt = options_tree[option]['Prompt'] 529 | if choices: 530 | response = common.choose(prompt, choices) 531 | else: 532 | response = raw_input('{0}: '.format(prompt)) 533 | response = type_checker(option, response, type_check, passive=True) 534 | if response != None: 535 | break 536 | 537 | # Set config to avoid double prompting later (doesn't actually write to disk) 538 | config.set(section, option, response) 539 | return response 540 | 541 | 542 | ################################# 543 | 544 | 545 | def main(): 546 | print "Using configuration file: %s" % config.get('Internal', 'last_location') 547 | print 548 | print "Welcome to DataStax' Cassandra Cluster Launcher!" 549 | print " The easiest way to get Apache Cassandra up and running in Amazon's EC2" 550 | print " in under 5 minutes!" 551 | print 552 | 553 | global cli_options 554 | cli_options = common.parse_cli_options(options_tree) 555 | 556 | # Required handle for log purposes and future shared EC2 purposes 557 | check_cascading_options('handle') 558 | 559 | # Prompt the user with any outstanding running clusters 560 | if (check_cascading_options('aws_access_key_id')[0] == '"' or check_cascading_options('aws_secret_access_key')[0] == '"' or 561 | check_cascading_options('aws_access_key_id')[0] == "'" or check_cascading_options('aws_secret_access_key')[0] == "'"): 562 | sys.stderr.write("None of the configurations should be wrapped in quotes.\n") 563 | sys.stderr.write(" EC2:aws_access_key_id or EC2:aws_secret_access_key appears to be.\n") 564 | sys.exit(1) 565 | 566 | if not cli_options['CLI_noprompts']: 567 | ec2.terminate_cluster(check_cascading_options('aws_access_key_id'), check_cascading_options('aws_secret_access_key'), config.get('EC2', 'placement'), check_cascading_options('handle'), prompt_continuation=True) 568 | 569 | start_time = time.time() 570 | 571 | # Get basic information for both Community and Enterprise clusters 572 | clustername = check_cascading_options('clustername') 573 | clustername = "'%s'" % clustername.replace("'", "") 574 | 575 | # Ensure totalnodes > 0 576 | ignore_command_line = False 577 | while True: 578 | totalnodes = check_cascading_options('totalnodes', int, ignore_command_line=ignore_command_line) 579 | if totalnodes > 0: 580 | break 581 | else: 582 | config.set('Cassandra', 'totalnodes') 583 | ignore_command_line = True 584 | 585 | version = check_cascading_options('version', choices=['Community', 'Enterprise']).title() 586 | user_data = '--clustername %s --totalnodes %s --version %s' % (clustername, totalnodes, version) 587 | 588 | if version == 'Enterprise': 589 | ignore_command_line = False 590 | while True: 591 | # Get additional information for Enterprise clusters 592 | username = check_cascading_options('username', ignore_command_line=ignore_command_line) 593 | password = check_cascading_options('password', password=True, ignore_command_line=ignore_command_line) 594 | 595 | print "Confirming credentials..." 596 | if confirm_authentication(username, password): 597 | break 598 | else: 599 | config.set('Cassandra', 'username') 600 | config.set('Cassandra', 'password') 601 | ignore_command_line = True 602 | print "Authentication to DataStax server failed. Please try again." 603 | 604 | # Check the number of Analytics Nodes that will launch 605 | ignore_command_line = False 606 | while True: 607 | analyticsnodes = check_cascading_options('analyticsnodes', int, ignore_command_line=ignore_command_line) 608 | if analyticsnodes <= totalnodes: 609 | break 610 | else: 611 | print "Overallocation of the chosen %d nodes" % (totalnodes) 612 | # Clear the previous cfsreplicationfactor 613 | config.set('Cassandra', 'analyticsnodes') 614 | ignore_command_line = True 615 | 616 | # Check the number of Search Nodes that will launch 617 | ignore_command_line = False 618 | while True: 619 | searchnodes = check_cascading_options('searchnodes', int, ignore_command_line=ignore_command_line) 620 | if analyticsnodes + searchnodes <= totalnodes: 621 | break 622 | else: 623 | print "Overallocation of the chosen %d nodes" % (totalnodes) 624 | # Clear the previous cfsreplicationfactor 625 | config.set('Cassandra', 'searchnodes') 626 | ignore_command_line = True 627 | 628 | user_data += ' --username %s --password %s --analyticsnodes %s --searchnodes %s' % (username, password, analyticsnodes, searchnodes) 629 | 630 | # If Hadoop enabled nodes are launching, check the CFS replication factor 631 | if analyticsnodes > 0: 632 | ignore_command_line = False 633 | while True: 634 | cfsreplicationfactor = check_cascading_options('cfsreplicationfactor', int, ignore_command_line=ignore_command_line) 635 | if 1 <= cfsreplicationfactor and cfsreplicationfactor <= analyticsnodes: 636 | break 637 | else: 638 | print "1 <= CFS Replication Factor <= Number of Analytics Nodes" 639 | # Clear the previous cfsreplicationfactor 640 | config.set('Cassandra', 'cfsreplicationfactor') 641 | ignore_command_line = True 642 | 643 | user_data += ' --cfsreplicationfactor %s' % (cfsreplicationfactor) 644 | print 645 | 646 | # Included for the experimental DemoService that requires demoservice.py to always be running 647 | demotime = -1 648 | if config.get('Cassandra', 'demo') == 'True': 649 | print "Your configuration file is set to launch a demo cluster for a specified time." 650 | demotime = check_cascading_options('demotime', float) 651 | print "If the demo service is running, this cluster will live for %s hour(s)." % demotime 652 | print 653 | 654 | if check_cascading_options('installopscenter', optional=True) == 'False': 655 | user_data += ' --opscenter no' 656 | 657 | if check_cascading_options('release', optional=True): 658 | user_data += ' --release %s' % check_cascading_options('release') 659 | 660 | opscenterinterface = 8888 661 | if check_cascading_options('opscenterinterface', optional=True): 662 | opscenterinterface = check_cascading_options('opscenterinterface') 663 | user_data += ' --opscenterinterface %s' % opscenterinterface 664 | 665 | if check_cascading_options('dev', optional=True): 666 | user_data += ' --forcecommit origin/dev-2.5' 667 | 668 | # DataStax AMI specific options and formatting 669 | image = check_cascading_options('datastax_ami', optional=True) 670 | if not image: 671 | image = 'ami-6139e708' 672 | 673 | tag = '{0} - DataStaxAMI Time: {1} Size: {2}'.format(check_cascading_options('handle'), time.strftime("%m-%d-%y %H:%M", time.localtime()), totalnodes) 674 | user = 'ubuntu' 675 | 676 | # Launch the cluster 677 | instance_type = check_cascading_options('instance_type', choices=['m1.large', 'm1.xlarge', 'm2.xlarge', 'm2.2xlarge', 'm2.4xlarge', 'hi1.4xlarge', 'hs1.8xlarge']) 678 | clusterinfo = ec2.create_cluster(check_cascading_options('aws_access_key_id'), 679 | check_cascading_options('aws_secret_access_key'), 680 | totalnodes, image, check_cascading_options('handle'), tag, KEY_PAIR, 681 | instance_type, config.get('EC2', 'placement'), PEM_HOME, 682 | user_data, cli_options['CLI_noprompts'], 683 | opscenterinterface, 684 | check_cascading_options('security_public_inbound_source', optional=True)) 685 | 686 | # Save IPs 687 | global private_ips 688 | global public_ips 689 | private_ips, public_ips, reservation = clusterinfo 690 | 691 | # Log clusterinfo 692 | running_log(reservation, demotime) 693 | 694 | if check_cascading_options('installopscenter', optional=True) != 'False': 695 | # Print OpsCenter url 696 | print "OpsCenter Address:" 697 | print "http://%s:%s" % (public_ips[0], opscenterinterface) 698 | print "Note: You must wait 60 seconds after Cassandra becomes active to access OpsCenter." 699 | print 700 | 701 | if cli_options['CLI_qa']: 702 | print "OPSCENTER_IP:%s" % public_ips[0] 703 | print "OPSCENTER_PORT:%s" % opscenterinterface 704 | 705 | start_priming(user) 706 | 707 | if version == 'Enterprise': 708 | global seed_index 709 | realtimenodes = totalnodes - analyticsnodes - searchnodes 710 | seed_index = [0, realtimenodes, realtimenodes + analyticsnodes] 711 | seed_index.reverse() 712 | 713 | global node_types 714 | node_type = 'c' 715 | node_types = {'c': [], 'a':[], 's':[]} 716 | 717 | print 'Primed Connection Strings:' 718 | for i, node in enumerate(public_ips): 719 | if version == 'Enterprise' and i in seed_index: 720 | if seed_index.index(i) == 0: 721 | print 'Search (Solr) Nodes:' 722 | node_type = 's' 723 | if seed_index.index(i) == 1: 724 | print 'Analytics (Hadoop) Nodes:' 725 | node_type = 'a' 726 | if seed_index.index(i) == 2: 727 | print 'Realtime (Cassandra) Nodes:' 728 | node_type = 'c' 729 | 730 | node_types[node_type].append(i) 731 | print ' {0} -i {1} -o UserKnownHostsFile={2} {3}@{4}'.format(config.get('System', 'ssh'), PEM_FILE, HOST_FILE, user, node) 732 | print 733 | 734 | print 'Installing DataStax SSH on the cluster...' 735 | install_datastax_ssh(user) 736 | 737 | if cli_options['CLI_qa']: 738 | print 'Uploading smoke tests on the cluster...' 739 | upload_smoke_tests(user) 740 | 741 | print 'Setting up the hosts file for the cluster...' 742 | install_hosts_appending(user) 743 | 744 | print 'Setting up datastax_s3_store and datastax_s3_restore capabilities...' 745 | setup_s3_store_and_restore(user) 746 | 747 | end_time = int(time.time() - start_time) 748 | print 'Total Elapsed Time: %s minutes %s seconds' % (end_time / 60, end_time % 60) 749 | print 750 | 751 | if not cli_options['CLI_noprompts']: 752 | ec2.terminate_cluster(check_cascading_options('aws_access_key_id'), check_cascading_options('aws_secret_access_key'), config.get('EC2', 'placement'), check_cascading_options('handle')) 753 | else: 754 | # Ensure the agents have time to start 755 | # installing before exiting the program 756 | time.sleep(10) 757 | 758 | def run(): 759 | try: 760 | main() 761 | except KeyboardInterrupt: 762 | print 763 | sys.stderr.write("Program Aborted.\n") 764 | print 765 | sys.exit(1) 766 | 767 | 768 | 769 | if __name__ == "__main__": 770 | run() 771 | -------------------------------------------------------------------------------- /cassandralauncher/clusterlauncher.conf: -------------------------------------------------------------------------------- 1 | [System] 2 | # Windows support: Download PuTTY and PSCP and modify the file paths or use Git for Windows' Shell. 3 | ssh = ssh 4 | scp = scp 5 | 6 | 7 | 8 | 9 | 10 | [Shared] 11 | # handle: Mine is jcasares. Brandon will probably use driftx, Nate: zznate. This is not EC2 related and is only used to identify your clusters. 12 | 13 | handle = 14 | 15 | 16 | [EC2] 17 | # aws_access_key_id: Found here: https://aws-portal.amazon.com/gp/aws/securityCredentials as Access Key ID. 18 | # aws_secret_access_key: Found here: https://aws-portal.amazon.com/gp/aws/securityCredentials as Secret Access Key. 19 | # instance_type: Can be one of the following options: m1.large, m1.xlarge, m2.xlarge, m2.2xlarge, m2.4xlarge, hi1.4xlarge, hs1.8xlarge. Leave blank to be prompted on each launch. 20 | # pem_home: The location on your machine that houses .pem 21 | 22 | # EC2 defaults (MUST change) 23 | aws_access_key_id = 24 | aws_secret_access_key = 25 | instance_type = m1.large 26 | 27 | # EC2 defaults (Advanced: Don't change) 28 | pem_home = ~/.ssh 29 | # key_pair = 30 | placement = us-east-1c 31 | 32 | # security_public_inbound_source: Change this if you want to restrict the inbound 33 | # traffic source for the public ports defined in the security group. 34 | # Specified in CIDR format. Multiple values can be separated with a comma. 35 | security_public_inbound_source = 0.0.0.0/0 36 | 37 | [S3] 38 | # send_s3_credentials: Send a preconfigured .s3cfg with your aws_access_key_id and aws_secret_access_key properly set under 400 permissions. For use with S3 store/restore capabilities. 39 | 40 | send_s3_credentials = False 41 | 42 | 43 | [Rax] 44 | # rax_user: Found here: https://manage.rackspacecloud.com/APIAccess.do 45 | # rax_api_key: Found here: https://manage.rackspacecloud.com/APIAccess.do 46 | # flavor: Can be one of the following options: 256, 512, 1GB, 2GB, 4GB, 8GB, 15.5GB, 30GB. Leave blank to be prompted on each launch. 47 | 48 | # Rackspace defaults (MUST change) 49 | rax_user = 50 | rax_api_key = 51 | flavor = 2GB 52 | 53 | # Rackspace defaults (Advanced: Don't change) 54 | user = root 55 | 56 | 57 | [Cassandra] 58 | # Fill all of these in to permanently have questions answered 59 | # Leave them blank to be prompted each time 60 | 61 | # accept_rsa_fingerprints: Set to 'all' to disable rsa fingerprint prompts 62 | 63 | accept_rsa_fingerprints = 64 | 65 | # Defaults for CassandraLauncher 66 | # clustername: The name of the cluster 67 | # totalnodes: The total nodes in the cluster 68 | # version: Community | Enterprise 69 | # installopscenter: Optionally disable OpsCenter 70 | # release: Optionally set release package version 71 | 72 | clustername = 73 | totalnodes = 74 | version = 75 | installopscenter = True 76 | release = 77 | 78 | # Defaults for CassandraLauncher: Enterprise Clusters 79 | # username: The DataStax username as provided at: http://www.datastax.com/download/enterprise 80 | # password: The DataStax password as provided at: http://www.datastax.com/download/enterprise 81 | # analyticsnodes: The amount of Analytics Nodes as opposed to Realtime or Search Nodes 82 | # searchnodes: The amount of Search Nodes as opposed to Realtime or Analytics Nodes 83 | # cfsreplicationfactor: The CassandraFS replication factor 84 | 85 | username = 86 | password = 87 | analyticsnodes = 88 | searchnodes = 89 | cfsreplicationfactor = 90 | 91 | # More settings (Advanced: Don't change) 92 | # datastax_ami: The DataStax AMI ID, e.g. ami-xx00xx00 93 | # demo: Set to 'True' if you are planning on running the demoservice to kill clusters after a set time. 94 | # demotime: Set to the time (in hours) for a cluster to live before being terminated if you wish not to be prompted each time. Cluster dies 3 minutes before hour mark to avoid additional AWS charges. 95 | 96 | datastax_ami = ami-05caca6c 97 | 98 | # DataStax HVM AMI 99 | # datastax_ami = ami-1dcaca74 100 | 101 | demo = False 102 | demotime = 103 | 104 | 105 | [Cluster] 106 | # low_security_ssh: Set to True will disable StrictHostKeyChecking and set UserKnownHostsFile to /dev/null for the ssh printouts in clusterlauncher. 107 | 108 | low_security_ssh = False 109 | -------------------------------------------------------------------------------- /cassandralauncher/clusterlauncher.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import os 4 | import time 5 | import sys 6 | 7 | import ec2 8 | import rax 9 | import common 10 | 11 | config, KEY_PAIR, PEM_HOME, HOST_FILE, PEM_FILE = common.header() 12 | 13 | # Cluster choice dataset 14 | clusterChoices = { 15 | 'EC2':{ 16 | 'Ubuntu':{ 17 | 'RightScale':{ 18 | '10.10':{ 19 | 'User': 'root', 20 | 'Manifest': 'RightImage_OSS_Ubuntu_Maverick_x86_64_5.5.9.7_RC3.manifest.xml', 21 | 'AMI': 'ami-46f0072f' 22 | }, 23 | '10.04':{ 24 | 'User': 'root', 25 | 'Manifest': 'RightImage_Ubuntu_10.04_x64_v5.6.8.1.manifest.xml', 26 | 'AMI': 'ami-70fb0a19' 27 | } 28 | }, 29 | 'Canonical':{ 30 | '11.10':{ 31 | 'User': 'ubuntu', 32 | 'Manifest': 'ubuntu-oneiric-11.10-amd64-server-20111205.manifest.xml', 33 | 'AMI': 'ami-c162a9a8' 34 | }, 35 | '11.04':{ 36 | 'User': 'ubuntu', 37 | 'Manifest': 'ubuntu-natty-11.04-amd64-server-20111003.manifest.xml', 38 | 'AMI': 'ami-71589518' 39 | }, 40 | '10.10':{ 41 | 'User': 'ubuntu', 42 | 'Manifest': 'ubuntu-maverick-10.10-amd64-server-20111001.manifest.xml', 43 | 'AMI': 'ami-1933fe70' 44 | }, 45 | '10.04':{ 46 | 'User': 'ubuntu', 47 | 'Manifest': 'ubuntu-lucid-10.04-amd64-server-20110930.manifest.xml', 48 | 'AMI': 'ami-1136fb78' 49 | }, 50 | } 51 | }, 52 | 'CentOS':{ 53 | 'RightScale':{ 54 | '5.6':{ 55 | 'User': 'root', 56 | 'Manifest': 'RightImage_CentOS_5.6_x64_v5.7.14.manifest.xml', 57 | 'AMI': 'ami-49e32320' 58 | }, 59 | '5.4':{ 60 | 'User': 'root', 61 | 'Manifest': 'RightImage_CentOS_5.4_x64_v5.6.8.1.manifest.xml', 62 | 'AMI': 'ami-9ae312f3' 63 | } 64 | } 65 | }, 66 | 'Debian':{ 67 | 'RightScale':{ 68 | '6.0':{ 69 | 'User': 'root', 70 | 'Manifest': 'rightimage_debian_6.0.1_amd64_20110406.1.manifest.xml', 71 | 'AMI': 'ami-c40df0ad' 72 | } 73 | } 74 | }, 75 | '~Custom AMI':'Null' 76 | }, 77 | 'Rackspace': { 78 | 'Ubuntu':{ 79 | '12.04 LTS':{ 80 | 'Image': 125 81 | }, 82 | '11.10':{ 83 | 'Image': 119 84 | }, 85 | '11.04':{ 86 | 'Image': 115 87 | }, 88 | '10.10 (deprecated)':{ 89 | 'Image': 69 90 | }, 91 | '10.04 LTS':{ 92 | 'Image': 112 93 | } 94 | }, 95 | 'CentOS':{ 96 | '6.3':{ 97 | 'Image': 127 98 | }, 99 | '6.2':{ 100 | 'Image': 122 101 | }, 102 | '6.0':{ 103 | 'Image': 118 104 | }, 105 | '5.8':{ 106 | 'Image': 121 107 | }, 108 | '5.6':{ 109 | 'Image': 114 110 | }, 111 | '5.5 (deprecated)':{ 112 | 'Image': 51 113 | }, 114 | '5.4 (deprecated)':{ 115 | 'Image': 187811 116 | } 117 | }, 118 | 'Debian':{ 119 | '6.0':{ 120 | 'Image': 104 121 | }, 122 | '5.0 (deprecated)':{ 123 | 'Image': 103 124 | } 125 | }, 126 | 'Fedora':{ 127 | '17':{ 128 | 'Image': 126 129 | }, 130 | '16':{ 131 | 'Image': 120 132 | }, 133 | '15 (deprecated)':{ 134 | 'Image': 116 135 | }, 136 | '14 (deprecated)':{ 137 | 'Image': 106 138 | }, 139 | '13 (deprecated)':{ 140 | 'Image': 53 141 | } 142 | } 143 | } 144 | } 145 | 146 | 147 | 148 | def printConnections(user, private_ips, public_ips, pem_file=False): 149 | # Print SSH commands 150 | for publicIP in public_ips: 151 | # Allow for a quicker SSH command into the cluster 152 | low_security_args = '' 153 | if config.getboolean('Cluster', 'low_security_ssh'): 154 | low_security_args = ' -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no' 155 | 156 | if pem_file: 157 | print '{0}{1} -i {2} {3}@{4}'.format(config.get('System', 'ssh'), low_security_args, pem_file, user, publicIP) 158 | else: 159 | print '{0}{1} {2}@{3}'.format(config.get('System', 'ssh'), low_security_args, user, publicIP) 160 | print 161 | 162 | # Print IPs (CSV) 163 | print "Public IPs:" 164 | print ", ".join(public_ips) 165 | print 166 | print "Private IPs:" 167 | print ", ".join(private_ips) 168 | print 169 | 170 | def main(): 171 | print "Using configuration file: %s" % config.get('Internal', 'last_location') 172 | print 173 | print "Welcome to the Plain Image Launcher!" 174 | print " The easiest way to interface with Amazon's EC2 and Rackspace's CloudServers" 175 | print " and produce a plain instance (or cluster) in under 5 minutes!" 176 | print 177 | 178 | if not config.get('Shared', 'handle'): 179 | sys.stderr.write("Ensure {0} is appropriately set.\n".format(config.get('Internal', 'last_location'))) 180 | sys.stderr.write(" 'Shared:handle' is missing.\n") 181 | sys.exit(1) 182 | 183 | cloud = common.choose("Choose your Cloud Testing Host: ", clusterChoices.keys()) 184 | 185 | # Ensure access keys are setup 186 | if cloud == 'EC2': 187 | if not config.get('EC2', 'aws_access_key_id') or not config.get('EC2', 'aws_secret_access_key'): 188 | sys.stderr.write("Ensure {0} is appropriately set.\n".format(config.get('Internal', 'last_location'))) 189 | sys.stderr.write(" 'EC2:aws_access_key_id|aws_secret_access_key' are missing.\n") 190 | sys.exit(1) 191 | if (config.get('EC2', 'aws_access_key_id')[0] == '"' or config.get('EC2', 'aws_secret_access_key')[0] == '"' or 192 | config.get('EC2', 'aws_access_key_id')[0] == "'" or config.get('EC2', 'aws_secret_access_key')[0] == "'"): 193 | sys.stderr.write("None of the configurations should be wrapped in quotes.\n") 194 | sys.stderr.write(" EC2:aws_access_key_id or EC2:aws_secret_access_key appears to be.\n") 195 | sys.exit(1) 196 | if cloud == 'Rackspace': 197 | if not config.get('Rax', 'rax_user') or not config.get('Rax', 'rax_api_key'): 198 | sys.stderr.write("Ensure {0} is appropriately set.\n".format(config.get('Internal', 'last_location'))) 199 | sys.stderr.write(" 'Rax:rax_user|rax_api_key' are missing.\n") 200 | sys.exit(1) 201 | if (config.get('Rax', 'rax_user')[0] == '"' or config.get('Rax', 'rax_api_key')[0] == '"' or 202 | config.get('Rax', 'rax_user')[0] == "'" or config.get('Rax', 'rax_api_key')[0] == "'"): 203 | sys.stderr.write("None of the configurations should be wrapped in quotes.\n") 204 | sys.stderr.write(" Rax:rax_user or Rax:rax_api_key appears to be.\n") 205 | sys.exit(1) 206 | 207 | action = common.choose("Choose your Cloud Command: ", ['Create', 'Destroy']) 208 | 209 | if action == 'Destroy': 210 | if cloud == 'EC2': 211 | ec2.terminate_cluster(config.get('EC2', 'aws_access_key_id'), config.get('EC2', 'aws_secret_access_key'), config.get('EC2', 'placement'), config.get('Shared', 'handle')) 212 | if cloud == 'Rackspace': 213 | rax.terminate_cluster(config.get('Rax', 'rax_user'), config.get('Rax', 'rax_api_key'), config.get('Shared', 'handle')) 214 | sys.exit() 215 | 216 | reservation_size = common.typed_input(int, "Choose your Cluster Size: ") 217 | print 218 | 219 | operating_system = common.choose("Choose your Testing Operating System: " , clusterChoices[cloud].keys()) 220 | 221 | if cloud == 'EC2': 222 | if operating_system == '~Custom AMI': 223 | image = raw_input("Enter the AMI ID: ") 224 | user = raw_input("Enter the AMI's default user: ") 225 | tag = "{3} - Time: {2} {0} Size: {1}".format(image, reservation_size, time.strftime("%m-%d-%y %H:%M", time.localtime()), config.get('Shared', 'handle')) 226 | else: 227 | provider = common.choose("Choose your Image Provider: ", clusterChoices[cloud][operating_system].keys()) 228 | version = common.choose("Choose your Operating System Version: ", clusterChoices[cloud][operating_system][provider].keys()) 229 | 230 | image = clusterChoices[cloud][operating_system][provider][version]['AMI'] 231 | user = clusterChoices[cloud][operating_system][provider][version]['User'] 232 | 233 | tag = "{5} - {0} Time: {4} {1} {2} Size: {3}".format(provider, operating_system, version, reservation_size, time.strftime("%m-%d-%y %H:%M", time.localtime()), config.get('Shared', 'handle')) 234 | 235 | user_data = raw_input("Enter EC2 user data: ") 236 | print 237 | 238 | instance_type = config.get('EC2', 'instance_type') 239 | if not instance_type: 240 | instance_type = common.choose('Choose your Instance Size:', ['m1.large', 'm1.xlarge', 'm2.xlarge', 'm2.2xlarge', 'm2.4xlarge']) 241 | 242 | clusterinfo = ec2.create_cluster(config.get('EC2', 'aws_access_key_id'), config.get('EC2', 'aws_secret_access_key'), 243 | reservation_size, image, 'imagelauncher', tag, KEY_PAIR, 244 | instance_type, config.get('EC2', 'placement'), PEM_HOME, user_data) 245 | private_ips, public_ips, reservation = clusterinfo 246 | 247 | printConnections(user, private_ips, public_ips, PEM_FILE) 248 | 249 | if cloud == 'Rackspace': 250 | version = common.choose("Choose your Operating System Version: ", clusterChoices[cloud][operating_system].keys()) 251 | 252 | image = clusterChoices[cloud][operating_system][version]['Image'] 253 | tag = "{0} Time {4} {1} {2} Size {3}".format(config.get('Shared', 'handle'), operating_system, version, reservation_size, time.strftime("%m-%d-%y %H:%M", time.localtime())).replace(' ', '-').replace(':', '_').replace('.', '_') 254 | 255 | flavor_array = ['256', '512', '1GB', '2GB', '4GB', '8GB', '15.5GB', '30GB'] 256 | flavor_choice = config.get('Rax', 'flavor') 257 | if not flavor_choice: 258 | flavor_choice = common.choose("Choose your Instance Size: ", flavor_array, sort=False) 259 | 260 | flavor_dict = {} 261 | for i, flavor in enumerate(flavor_array): 262 | flavor_dict[flavor] = i + 1 263 | flavor_dict[str(i + 1)] = i + 1 # For backward compliance 264 | flavor = flavor_dict[flavor_choice] 265 | 266 | private_ips, public_ips = rax.create_cluster(config.get('Rax', 'rax_user'), config.get('Rax', 'rax_api_key'), 267 | reservation_size, image, tag, flavor) 268 | 269 | printConnections(config.get('Rax', 'user'), private_ips, public_ips) 270 | 271 | def run(): 272 | try: 273 | start_time = time.time() 274 | main() 275 | end_time = int(time.time() - start_time) 276 | print 'Total Elapsed Time: %s minutes %s seconds' % (end_time / 60, end_time % 60) 277 | except KeyboardInterrupt: 278 | print 279 | sys.stderr.write("Program Aborted.\n") 280 | print 281 | sys.exit(1) 282 | 283 | 284 | 285 | if __name__ == "__main__": 286 | run() 287 | -------------------------------------------------------------------------------- /cassandralauncher/common.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import os 4 | import shutil 5 | import sys 6 | 7 | from optparse import OptionParser 8 | 9 | def header(): 10 | """Setup an array of information before starting the program.""" 11 | 12 | # Read authentication file 13 | import ConfigParser 14 | config = ConfigParser.RawConfigParser() 15 | 16 | # If $CLUSTERLAUNCHER_CONF is set, we will assume success or total failure 17 | if os.getenv('CLUSTERLAUNCHER_CONF'): 18 | configfile = os.getenv('CLUSTERLAUNCHER_CONF') 19 | if not os.path.exists(configfile): 20 | sys.stderr.write("ERROR: environ['CLUSTERLAUNCHER_CONF'] is set incorrectly to a file that does not exists.\n") 21 | sys.stderr.write("Please remove the enviornment variable or set it correctly.\n") 22 | sys.exit(1) 23 | else: 24 | # Look for the configuration file in the same directory as the launcher 25 | configfile = os.path.join(os.path.dirname(__file__), 'clusterlauncher.conf') 26 | if not os.path.exists(configfile): 27 | # Look for the configuration file in the user's home directory 28 | configfile = os.path.join(os.path.expanduser('~'), '.clusterlauncher.conf') 29 | if not os.path.exists(configfile): 30 | # Look for the configuration file in /etc/clusterlauncher 31 | defaultfile = os.path.join('/etc', 'cassandralauncher', 'clusterlauncher.conf') 32 | if not os.path.exists(configfile): 33 | # Look for the configuration file in /usr/local/etc/clusterlauncher 34 | defaultfile = os.path.join('/usr', 'local', 'etc', 'cassandralauncher', 'clusterlauncher.conf') 35 | configfile = os.path.join(os.path.expanduser('~'), '.clusterlauncher.conf') 36 | shutil.copyfile(defaultfile, configfile) 37 | 38 | # Exit the program to alert the user that the conf file must be properly set with authentications 39 | # before continuing 40 | sys.stderr.write("A copy of the default configuration file located at:\n") 41 | sys.stderr.write(' %s\n' % defaultfile) 42 | sys.stderr.write("was now copied to:\n") 43 | sys.stderr.write(' %s\n' % configfile) 44 | sys.stderr.write("Please ensure that all default settings are correct and filled in before continuing.\n") 45 | sys.exit(1) 46 | 47 | if not os.path.exists(configfile): 48 | # Exit since we still have not found the configuration file 49 | sys.stderr.write("Please setup your authentication configurations. Order of importance:\n") 50 | sys.stderr.write(" The location as set by $CLUSTERLAUNCHER_CONF.\n") 51 | sys.stderr.write(" {0}\n".format(os.path.join(os.path.dirname(__file__), 'clusterlauncher.conf'))) 52 | sys.stderr.write(" {0}\n".format(os.path.join(os.path.expanduser('~'), '.clusterlauncher.conf'))) 53 | sys.stderr.write(" {0}\n".format(os.path.join('/etc', 'cassandralauncher', 'clusterlauncher.conf'))) 54 | sys.stderr.write(" {0}\n".format(os.path.join('/usr', 'local', 'etc', 'cassandralauncher', 'clusterlauncher.conf'))) 55 | sys.exit(1) 56 | 57 | # Read configuration file 58 | config.read(configfile) 59 | config.add_section('Internal') 60 | config.set('Internal', 'last_location', configfile) 61 | 62 | # Constantly referenced filenames 63 | KEY_PAIR = config.get('EC2', 'key_pair') if config.has_option('EC2', 'key_pair') else 'DataStaxLauncher' 64 | KEY_PAIR = KEY_PAIR.replace('.pem', '') 65 | 66 | PEM_HOME = os.path.expanduser(config.get('EC2', 'pem_home')) 67 | HOST_FILE = os.path.join(PEM_HOME, 'ds_known_hosts') 68 | PEM_FILE = os.path.join(PEM_HOME, KEY_PAIR) + '.pem' 69 | 70 | # Ensure filesystem access 71 | if not os.path.exists(PEM_HOME): 72 | try: 73 | os.makedirs(PEM_HOME) 74 | except: 75 | sys.stderr.write("Failed to create %s. Please ensure the parent directory has write permissions.\n" % PEM_HOME) 76 | sys.exit(1) 77 | if not os.access(PEM_HOME, os.W_OK): 78 | sys.stderr.write("Please ensure %s is writeable. Nothing was launched.\n" % PEM_HOME) 79 | sys.exit(1) 80 | 81 | return [config, KEY_PAIR, PEM_HOME, HOST_FILE, PEM_FILE] 82 | 83 | def printConnections(user, private_ips, public_ips): 84 | """Helper method for printing ssh commands.""" 85 | 86 | # Print SSH commands 87 | for publicIP in public_ips: 88 | print '{0} {1}@{2}'.format(config.get('System', 'ssh'), user, publicIP) 89 | print 90 | 91 | # Print IPs (CSV) 92 | print "Private IPs:" 93 | print ", ".join(private_ips) 94 | print "Public IPs:" 95 | print ", ".join(public_ips) 96 | print 97 | 98 | def typed_input(type_check, question=''): 99 | """Like raw_input, but reprompts to ensure strict type checking.""" 100 | 101 | toReturn = "" 102 | 103 | # Repeat question until the input is an int 104 | while not isinstance(toReturn, type_check): 105 | try: 106 | toReturn = type_check(raw_input(question)) 107 | except ValueError: 108 | toReturn = "" 109 | 110 | # Return the int 111 | return toReturn 112 | 113 | def choose(question, choices, noneOption=False, sort=True): 114 | """Like raw_input, but prompts options and reprompts until answer is valid.""" 115 | 116 | print question 117 | 118 | # Sort dictionary keys 119 | if sort: 120 | choices.sort() 121 | 122 | # Add an option for adding None to the list as 0 123 | if noneOption: 124 | choices = ['None'] + choices 125 | 126 | if len(choices) == 0: 127 | print "No choices available." 128 | print 129 | sys.exit() 130 | 131 | # List the options 132 | for i, choice in enumerate(choices): 133 | print " [{0}] {1}".format(i, choice) 134 | 135 | if len(choices) == 1: 136 | index = 0 137 | print '%s automatically chosen.' % choices[0] 138 | else: 139 | # Repeat question until the input is a valid int 140 | index = "" 141 | while not isinstance(index, int) or index >= len(choices): 142 | try: 143 | index = int(raw_input()) 144 | except ValueError: 145 | index = "" 146 | 147 | # Clear a line and return the choice 148 | print 149 | return choices[index] 150 | 151 | def parse_cli_options(options_tree): 152 | """Allows for a simplified way to parse complex argument types. 153 | In particular, when coming from different sources.""" 154 | 155 | parser = OptionParser() 156 | 157 | # Keys in options_tree dictionary are the arguments 158 | options = options_tree.keys() 159 | options.sort() 160 | 161 | for option in options: 162 | # All options are created from said dictionary 163 | section = options_tree[option]['Section'] 164 | help = options_tree[option]['Help'] 165 | action = options_tree[option]['Action'] if 'Action' in options_tree[option] else 'store' 166 | parser.add_option('--{0}'.format(option), action=action, 167 | dest='{0}_{1}'.format(section, option), 168 | help=help) 169 | 170 | (options, args) = parser.parse_args() 171 | return vars(options) 172 | -------------------------------------------------------------------------------- /cassandralauncher/datastax_pssh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | ### Script provided by DataStax. 3 | 4 | import os 5 | import random 6 | import subprocess 7 | import shlex 8 | import sys 9 | 10 | 11 | commands = [ 12 | 'Enter a custom command...', 13 | 'nodetool -h localhost ring', 14 | 'datastax_s3_store', 15 | 'datastax_s3_restore', 16 | 'sudo /etc/init.d/cassandra start', 17 | 'sudo /etc/init.d/cassandra stop', 18 | 'sudo /etc/init.d/cassandra restart', 19 | 'sudo /etc/init.d/dse start', 20 | 'sudo /etc/init.d/dse stop', 21 | 'sudo /etc/init.d/dse restart' 22 | ] 23 | 24 | # Function to execute commands and print traces of the command and output for debugging/logging purposes 25 | def exe(command, log=True): 26 | # Executes command and wait for completion 27 | process = subprocess.Popen(shlex.split(command), stderr=subprocess.PIPE, stdout=subprocess.PIPE) 28 | read = process.communicate() 29 | 30 | # Prints output to stdout 31 | print read[0] 32 | print read[1] 33 | 34 | # return process 35 | return read 36 | 37 | def datastax_ssh(command): 38 | exe('parallel-ssh --hosts /etc/cassandralauncher/nodelist --user ubuntu --print %s' % command) 39 | 40 | try: 41 | print "Welcome to DataStax' Parallel SSH Utility!" 42 | print 43 | 44 | selection = False 45 | while not selection: 46 | print "Choose a command to run across the cluster:" 47 | for i, command in enumerate(commands): 48 | print " %s. %s" % (i, command) 49 | 50 | try: 51 | selection = int(raw_input("")) 52 | print 53 | 54 | if selection == 0: 55 | selection = raw_input("Please input your command: ") 56 | print 57 | else: 58 | if selection in [2, 3]: 59 | number = random.randint(0, 100) 60 | if raw_input("Enter the number '%s' to verify you wish to run `%s` clusterwide: " % (number, commands[selection])) != str(number): 61 | sys.exit(1) 62 | else: 63 | print "Performing command: %s..." % commands[selection] 64 | 65 | selection = commands[selection] 66 | except KeyboardInterrupt: 67 | raise 68 | except: 69 | print "Invalid selection. Please try again." 70 | print 71 | selection = False 72 | 73 | datastax_ssh(selection) 74 | # Catch, log, and display pretty KeyboardInterrupts 75 | except KeyboardInterrupt: 76 | print 77 | pass 78 | -------------------------------------------------------------------------------- /cassandralauncher/datastax_s3_restore: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import ConfigParser 4 | import os 5 | import re 6 | import shlex 7 | import subprocess 8 | import yaml 9 | 10 | configfile = '/home/ubuntu/.s3cfg' 11 | 12 | def exe(command, wait=True): 13 | """Execute a subprocess command""" 14 | 15 | # Open a subprocess to run your command 16 | process = subprocess.Popen(shlex.split(str(command)), stdout=subprocess.PIPE, stderr=subprocess.PIPE) 17 | if wait: 18 | read = process.communicate() 19 | return read 20 | else: 21 | return process 22 | 23 | def s3_restore(): 24 | print "Stopping the node..." 25 | exe('sudo service dse stop') 26 | exe('sudo service cassandra stop') 27 | 28 | print "Clearing current data..." 29 | exe('sudo rm -rf %s/*' % os.path.join(root_data_dir, 'data')) 30 | 31 | print "Downloading data..." 32 | response = exe('sudo s3cmd sync --delete-removed s3://%s-%s/%s/%s/ %s' % ( 33 | bucket_name, access_key, 34 | cluster_name, initial_token, 35 | os.path.join(root_data_dir, 'data/') 36 | )) 37 | if response[1]: 38 | print response[1] 39 | 40 | print "Restarting the node..." 41 | exe('sudo service dse restart') 42 | exe('sudo service cassandra restart') 43 | 44 | 45 | # Read access_key 46 | config = ConfigParser.RawConfigParser() 47 | config.read(configfile) 48 | access_key = config.get('default', 'access_key') 49 | bucket_name = config.get('datastax', 'bucket_name') if config.has_option('default', 'bucket_name') else 'datastax_s3_storage' 50 | root_data_dir = config.get('datastax', 'root_data_dir') if config.has_option('default', 'root_data_dir') else '/raid0/cassandra' 51 | 52 | # Read cluster_name and initial_token 53 | with open('/etc/dse/cassandra/cassandra.yaml') as f: 54 | dataMap = yaml.load(f) 55 | cluster_name = re.sub(r'\W+', '_', dataMap['cluster_name']) 56 | initial_token = dataMap['initial_token'] 57 | 58 | s3_restore() 59 | -------------------------------------------------------------------------------- /cassandralauncher/datastax_s3_store: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import ConfigParser 4 | import os 5 | import re 6 | import shlex 7 | import subprocess 8 | import yaml 9 | 10 | configfile = '/home/ubuntu/.s3cfg' 11 | 12 | def exe(command, wait=True): 13 | """Execute a subprocess command""" 14 | 15 | # Open a subprocess to run your command 16 | process = subprocess.Popen(shlex.split(str(command)), stdout=subprocess.PIPE, stderr=subprocess.PIPE) 17 | if wait: 18 | read = process.communicate() 19 | return read 20 | else: 21 | return process 22 | 23 | def s3_store(): 24 | print "Draining the node..." 25 | exe('nodetool -h localhost drain') 26 | 27 | 28 | # Create the bucket 29 | exe('s3cmd mb s3://%s-%s' % (bucket_name, access_key)) 30 | 31 | print "Uploading data..." 32 | response = exe('sudo s3cmd sync --delete-removed %s s3://%s-%s/%s/%s/' % ( 33 | os.path.join(root_data_dir, 'data/'), 34 | bucket_name, access_key, 35 | cluster_name, initial_token 36 | )) 37 | if response[1]: 38 | print response[1] 39 | 40 | print "Stopping the node..." 41 | exe('sudo service dse stop') 42 | exe('sudo service cassandra stop') 43 | 44 | 45 | # Read access_key 46 | config = ConfigParser.RawConfigParser() 47 | config.read(configfile) 48 | access_key = config.get('default', 'access_key') 49 | bucket_name = config.get('datastax', 'bucket_name') if config.has_option('default', 'bucket_name') else 'datastax_s3_storage' 50 | root_data_dir = config.get('datastax', 'root_data_dir') if config.has_option('default', 'root_data_dir') else '/raid0/cassandra' 51 | 52 | # Read cluster_name and initial_token 53 | with open('/etc/dse/cassandra/cassandra.yaml') as f: 54 | dataMap = yaml.load(f) 55 | cluster_name = re.sub(r'\W+', '_', dataMap['cluster_name']) 56 | initial_token = dataMap['initial_token'] 57 | 58 | s3_store() 59 | -------------------------------------------------------------------------------- /cassandralauncher/datastax_ssh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | ### Script provided by DataStax. 3 | 4 | import os 5 | import subprocess 6 | import shlex 7 | import sys 8 | 9 | 10 | commands = [ 11 | 'Enter a custom command...', 12 | 'nodetool -h localhost ring', 13 | 'sudo tail /var/log/cassandra/output.log', 14 | 'sudo tail /var/log/cassandra/system.log', 15 | 'sudo /etc/init.d/cassandra start', 16 | 'sudo /etc/init.d/cassandra stop', 17 | 'sudo /etc/init.d/cassandra restart', 18 | 'sudo /etc/init.d/dse start', 19 | 'sudo /etc/init.d/dse stop', 20 | 'sudo /etc/init.d/dse restart' 21 | ] 22 | 23 | # Function to execute commands and print traces of the command and output for debugging/logging purposes 24 | def exe(command, log=True): 25 | # Executes command and wait for completion 26 | process = subprocess.Popen(shlex.split(command), stderr=subprocess.PIPE, stdout=subprocess.PIPE) 27 | read = process.communicate() 28 | 29 | # Prints output to stdout 30 | print read[0] 31 | print read[1] 32 | 33 | # return process 34 | return read 35 | 36 | # SSH function that returns SSH string 37 | def create_ssh_cmd(host, sshuser): 38 | return "ssh -t %s@%s " % (sshuser, host) 39 | 40 | # SSH function that executes SSH string + command 41 | def exe_ssh_cmd(connstring, command, printmsg=False): 42 | exe_command = '%s "%s"' % (connstring, command) 43 | output = exe(exe_command) 44 | if printmsg and output[1]: 45 | print 46 | print printmsg 47 | print "Command that failed:" 48 | print ' %s' % (exe_command) 49 | print "Error:" 50 | print output[1] 51 | return output 52 | 53 | def datastax_ssh(command): 54 | current_path = '' 55 | if os.path.dirname(sys.argv[0]): 56 | current_path = os.path.dirname(sys.argv[0]) 57 | 58 | with open(os.path.join(current_path, '/etc/cassandralauncher/nodelist'), 'r') as f: 59 | nodelist = f.readlines() 60 | 61 | sshuser = 'ubuntu' 62 | 63 | for ip in nodelist: 64 | if ip.strip(): 65 | sshcommand = create_ssh_cmd(ip, sshuser) 66 | 67 | print "Performing '%s' on %s..." % (command, ip.strip()) 68 | exe_ssh_cmd(sshcommand, command) 69 | 70 | try: 71 | print "Welcome to DataStax' SSH Utility!" 72 | print 73 | 74 | selection = False 75 | while not selection: 76 | print "Choose a command to run across the cluster:" 77 | for i, command in enumerate(commands): 78 | print " %s. %s" % (i, command) 79 | 80 | try: 81 | selection = int(raw_input("")) 82 | print 83 | 84 | if selection == 0: 85 | selection = raw_input("Please input your command: ") 86 | print 87 | else: 88 | selection = commands[selection] 89 | except KeyboardInterrupt: 90 | raise 91 | except: 92 | print "Invalid selection. Please try again." 93 | print 94 | selection = False 95 | 96 | datastax_ssh(selection) 97 | # Catch, log, and display pretty KeyboardInterrupts 98 | except KeyboardInterrupt: 99 | print 100 | pass 101 | -------------------------------------------------------------------------------- /cassandralauncher/ec2.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import os 4 | import stat 5 | import sys 6 | import time 7 | 8 | try: 9 | import boto 10 | import boto.ec2 11 | except: 12 | sys.stderr.write("'boto' is not installed on your system. Please run:\n") 13 | sys.stderr.write(" pip install boto\n") 14 | sys.stderr.write("and try again.\n") 15 | sys.exit(1) 16 | 17 | from xml.dom.minidom import parseString 18 | 19 | import common 20 | 21 | def authorize(security_group, port, realm, port_end_range=False, public_inbound_source='0.0.0.0/0'): 22 | 23 | # Setup single ports, unless noted 24 | if not port_end_range: 25 | port_end_range = port 26 | 27 | # Catch errors from overpopulating 28 | try: 29 | # Open ports publicly 30 | if realm == 'public': 31 | sources = {s.strip() for s in public_inbound_source.split(',')} 32 | security_group.authorize('tcp', port, port_end_range, list(sources)) 33 | # Open ports privately 34 | elif realm == 'private': 35 | security_group.authorize('tcp', port, port_end_range, src_group=security_group) 36 | # Error out if code was changed 37 | else: 38 | sys.stderr.write('Unknown realm assigned!\n') 39 | sys.exit(1) 40 | except boto.exception.EC2ResponseError: 41 | # Continue since ports were probably trying to be overwritten 42 | pass 43 | 44 | def deauthorize(security_group, port, port_end_range=False): 45 | 46 | # Setup single ports, unless noted 47 | if not port_end_range: 48 | port_end_range = port 49 | 50 | # Catch errors from overpopulating 51 | try: 52 | # Check first if there are rules to revoke 53 | for rule in security_group.rules: 54 | if (rule.ip_protocol == 'tcp' and int(rule.from_port) == port and 55 | int(rule.to_port) == port_end_range): 56 | for grant in rule.grants: 57 | security_group.revoke('tcp', port, port_end_range, cidr_ip=grant.cidr_ip, 58 | src_group=(grant if grant.group_id else None)) 59 | break 60 | 61 | except boto.exception.EC2ResponseError: 62 | # Continue since ports were probably trying to be overwritten 63 | pass 64 | 65 | def print_boto_error(): 66 | """Attempts to extract the XML from boto errors to present plain errors with no stacktraces.""" 67 | 68 | try: 69 | quick_summary, null, xml = str(sys.exc_info()[1]).split('\n') 70 | error_msg = parseString(xml).getElementsByTagName('Response')[0].getElementsByTagName('Errors')[0].getElementsByTagName('Error')[0] 71 | print 72 | sys.stderr.write('AWS Error: {0}\n'.format(quick_summary)) 73 | sys.stderr.write('{0}: {1}\n'.format(error_msg.getElementsByTagName('Code')[0].firstChild.data, error_msg.getElementsByTagName('Message')[0].firstChild.data)) 74 | print 75 | except: 76 | # Raise the exception if parsing failed 77 | raise 78 | sys.exit(1) 79 | 80 | 81 | 82 | def create_cluster(aws_access_key_id, aws_secret_access_key, reservation_size, image, handle, tag, key_pair, instance_type, placement, pem_home, user_data=None, noprompts=False, opscenterinterface=False, security_public_inbound_source='0.0.0.0/0'): 83 | 84 | # Connect to EC2 85 | print 'Starting an EC2 cluster of type {0} with image {1}...'.format(instance_type, image) 86 | conn = boto.ec2.connect_to_region(placement[:-1], aws_access_key_id=aws_access_key_id, 87 | aws_secret_access_key=aws_secret_access_key) 88 | 89 | # Ensure PEM key is created 90 | try: 91 | print "Ensuring %s pem key exists on AWS..." % key_pair 92 | key = conn.get_all_key_pairs(keynames=[key_pair])[0] 93 | 94 | print "Ensuring %s pem key exists on filesystem..." % key_pair 95 | # Print a warning message if the pem file can't be found 96 | pem_file = os.path.join(pem_home, key_pair + '.pem') 97 | if os.path.isfile(pem_file): 98 | print "Ensuring %s pem key's permissions are acceptable..." % key_pair 99 | # Print a warning message is the pem file does not have the correct file permissions 100 | permissions = os.stat(pem_file).st_mode 101 | if not (bool(permissions & stat.S_IRUSR) and # Ensure owner can read 102 | not bool(permissions & stat.S_IRWXG) and # Ensure that group has no permissions 103 | not bool(permissions & stat.S_IRWXO)): # Ensure that others have no permissions 104 | 105 | sys.stderr.write("WARNING: The pem file has permissions that are too open!\n\n") 106 | 107 | if noprompts: 108 | sys.exit(1) 109 | if raw_input("Do you wish to reset file permissions? [y/N] ").lower() == 'y': 110 | try: 111 | # Reset permissions 112 | os.chmod(pem_file, 0400) 113 | except: 114 | sys.stderr.write("Unable to reset file permissions!\n") 115 | if raw_input("Do you wish to continue? [y/N] ").lower() != 'y': 116 | sys.exit(1) 117 | else: 118 | print "File permissions unchanged." 119 | if raw_input("Do you wish to continue? [y/N] ").lower() != 'y': 120 | sys.exit(1) 121 | else: 122 | sys.stderr.write("WARNING: The created pem file no longer exists at %s!\n" % pem_file) 123 | sys.stderr.write(" Please copy it out of the previous 'pem_home' directory.\n\n") 124 | 125 | if noprompts or raw_input("Do you wish to continue? [y/N] ").lower() != 'y': 126 | sys.exit(1) 127 | 128 | except boto.exception.EC2ResponseError, e: 129 | if e.code == 'InvalidKeyPair.NotFound': 130 | print 'Creating keypair...' 131 | key = conn.create_key_pair(key_pair) 132 | try: 133 | key.save(pem_home) 134 | except: 135 | conn.delete_key_pair(key_pair) 136 | print "Saving keypair failed! Removing keypair from AWS and exiting. Nothing was launched.\n" 137 | raise 138 | else: 139 | raise 140 | 141 | # Check if DataStax security group exists 142 | ds_security_group = False 143 | for security_group in conn.get_all_security_groups(): 144 | if security_group.name == 'DataStax': 145 | ds_security_group = security_group 146 | break 147 | 148 | # Create the DataStax security group if it doesn't exist 149 | if not ds_security_group: 150 | ds_security_group = conn.create_security_group('DataStax', 'Security group for running DataStax products') 151 | 152 | # Ensure all Security settings are active 153 | print "Configuring ports..." 154 | 155 | if not security_public_inbound_source: 156 | security_public_inbound_source = '0.0.0.0/0' 157 | 158 | authorize(ds_security_group, 22, 'public', public_inbound_source=security_public_inbound_source) # SSH 159 | authorize(ds_security_group, 8012, 'public', public_inbound_source=security_public_inbound_source) # Hadoop Job Tracker client port 160 | if opscenterinterface: 161 | authorize(ds_security_group, int(opscenterinterface), 'public', public_inbound_source=security_public_inbound_source) # OpsCenter website port 162 | else: 163 | authorize(ds_security_group, 8888, 'public', public_inbound_source=security_public_inbound_source) # OpsCenter website port 164 | authorize(ds_security_group, 8983, 'public', public_inbound_source=security_public_inbound_source) # Portfolio and Solr default port 165 | authorize(ds_security_group, 50030, 'public', public_inbound_source=security_public_inbound_source) # Hadoop Job Tracker website port 166 | authorize(ds_security_group, 50060, 'public', public_inbound_source=security_public_inbound_source) # Hadoop Task Tracker website port 167 | 168 | authorize(ds_security_group, 22, 'private') # SSH between nodes 169 | authorize(ds_security_group, 7000, 'private') # Cassandra intra-node port 170 | authorize(ds_security_group, 7199, 'private') # JMX initial port 171 | authorize(ds_security_group, 9160, 'private') # Cassandra client port 172 | authorize(ds_security_group, 9290, 'private') # Hadoop Job Tracker Thrift port 173 | authorize(ds_security_group, 50031, 'private') # Hadoop job tracker port 174 | authorize(ds_security_group, 61620, 'private') # OpsCenter Agent port 175 | authorize(ds_security_group, 61621, 'private') # OpsCenter Agent port 176 | authorize(ds_security_group, 1024, 'private', 65535) # JMX reconnection ports 177 | 178 | try: 179 | # Create the EC2 cluster 180 | print 'Launching cluster...' 181 | start_time = time.time() 182 | try: 183 | reservation = conn.run_instances(image, 184 | min_count=reservation_size, 185 | max_count=reservation_size, 186 | instance_type=instance_type, 187 | key_name=key_pair, 188 | placement=placement, 189 | security_groups=['DataStax'], user_data=user_data) 190 | except boto.exception.EC2ResponseError: 191 | print_boto_error() 192 | 193 | # Sleep so Amazon recognizes the new instance 194 | print 'Waiting for EC2 cluster to instantiate...' 195 | time.sleep(12) 196 | print ' Nodes that have been allocated by EC2:' 197 | launching = set(reservation.instances) 198 | running = set() 199 | while launching: 200 | time.sleep(3) 201 | for instance in launching: 202 | if instance.update() == 'running': 203 | print ' Node %s' % (instance.ami_launch_index) 204 | running.add(instance) 205 | launching.difference_update(running) 206 | print 207 | 208 | print "Cluster booted successfully!" 209 | end_time = int(time.time() - start_time) 210 | print ' Elapsed Time: %s minutes %s seconds' % (end_time / 60, end_time % 60) 211 | print 212 | 213 | # Tag the instances in this reservation 214 | for instance in reservation.instances: 215 | conn.create_tags([instance.id], {'Name': tag, 'Initializer': 'DataStax', 'provisioner': 'cassandralauncher', 'user': handle}) 216 | except (Exception, KeyboardInterrupt, EOFError) as err: 217 | print "\n\nERROR: Tags were never applied to started instances!!! Make sure you TERMINATE instances here:" 218 | print " https://console.aws.amazon.com/ec2/home?region=us-east-1#s=Instances\n" 219 | 220 | # Ensure the user acknowledges pricing danger 221 | while True: 222 | try: 223 | while raw_input('Acknowledge warning [Type OK]: ').lower() != 'ok': 224 | pass 225 | except KeyboardInterrupt: 226 | print 227 | pass 228 | raise err 229 | 230 | # Collect ip addresses 231 | private_ips = range(len(reservation.instances)) 232 | public_ips = range(len(reservation.instances)) 233 | for instance in reservation.instances: 234 | idx = int(instance.ami_launch_index) 235 | private_ips[idx] = instance.private_ip_address 236 | public_ips[idx] = instance.ip_address 237 | 238 | return [private_ips, public_ips, reservation] 239 | 240 | def terminate_cluster(aws_access_key_id, aws_secret_access_key, placement, search_term, prompt_continuation=False): 241 | 242 | # Grab all the infomation for clusters spawn by this tool that are still alive 243 | ds_reservations = {} 244 | conn = boto.ec2.connect_to_region(placement[:-1], aws_access_key_id=aws_access_key_id, 245 | aws_secret_access_key=aws_secret_access_key) 246 | 247 | try: 248 | reservations = conn.get_all_instances() 249 | except boto.exception.EC2ResponseError: 250 | print_boto_error() 251 | 252 | for reservation in reservations: 253 | if 'Initializer' in reservation.instances[0].tags and reservation.instances[0].tags['Initializer'] == 'DataStax' and reservation.instances[0].update() == 'running': 254 | if 'Name' in reservation.instances[0].tags \ 255 | and not reservation.instances[0].tags['Name'] in ds_reservations \ 256 | and search_term.lower() in reservation.instances[0].tags['Name'].lower(): 257 | 258 | ds_reservations[reservation.instances[0].tags['Name']] = { 259 | 'Reservation': reservation 260 | } 261 | 262 | if not ds_reservations.keys(): 263 | print "No existing clusters currently running!" 264 | print 265 | return 266 | 267 | # Prompt for cluster to destroy 268 | selection = common.choose("Choose the cluster to destroy (if you wish):", ds_reservations.keys(), noneOption=True) 269 | 270 | # Return if you do not with to kill a cluster 271 | if selection == 'None': 272 | return 273 | 274 | # Confirm cluster termination 275 | print "Confirm you wish to terminate {0} by pressing 'y'.".format(selection) 276 | if raw_input().lower() != 'y': 277 | print "Cluster was not terminated." 278 | print 279 | sys.exit() 280 | print 281 | 282 | # The actual termination command 283 | for instance in ds_reservations[selection]['Reservation'].instances: 284 | conn.terminate_instances([instance.id]) 285 | 286 | print "Termination command complete." 287 | print 288 | 289 | if prompt_continuation: 290 | if raw_input('Do you wish to launch another cluster? [Y/n] ').lower() == 'n': 291 | sys.exit(0) 292 | print 293 | -------------------------------------------------------------------------------- /cassandralauncher/rax.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import os 4 | import re 5 | import time 6 | import sys 7 | 8 | try: 9 | from cloudservers import CloudServers 10 | from cloudservers import Image 11 | except: 12 | sys.stderr.write("'cloudservers' is not installed on your system. Please run:\n") 13 | sys.stderr.write(" pip install python-cloudservers\n") 14 | sys.stderr.write("and try again.\n") 15 | sys.exit(1) 16 | 17 | import common 18 | 19 | def create_cluster(rax_user, rax_api_key, reservation_size, image, tag, flavor): 20 | 21 | # Create the Rackspace cluster 22 | print 'Starting a Rackspace cluster of flavor {0} with image {1}...'.format(flavor, image) 23 | cloudservers = CloudServers(rax_user, rax_api_key) 24 | servers = [] 25 | print 'Launching cluster...' 26 | start_time = time.time() 27 | for i in range(reservation_size): 28 | transfer_files = { 29 | '/root/.ssh/authorized_keys' : open(os.path.expanduser("~/.ssh/id_rsa.pub"), 'r') 30 | # '/root/.ssh/id_rsa.pub' : open(os.path.expanduser("~/.ssh/id_rsa.pub"), 'r') 31 | # '/root/.ssh/id_rsa' : open(os.path.expanduser("~/.ssh/id_rsa"), 'r') 32 | } 33 | 34 | try: 35 | server = cloudservers.servers.create( 36 | image=image, 37 | flavor=flavor, 38 | name=tag, 39 | files=transfer_files 40 | ) 41 | servers.append(server) 42 | except: 43 | sys.stderr.write('Rackspace authentication failed. Please check your Rackspace authentication and try again.\n') 44 | sys.exit(1) 45 | 46 | print 'Waiting for cluster...' 47 | time.sleep(10) 48 | print ' Nodes that have been allocated by Rackspace:' 49 | for i, server in enumerate(servers): 50 | while cloudservers.servers.get(server.id).status != "ACTIVE": 51 | time.sleep(3) 52 | print ' Node %s' % (i) 53 | print 54 | 55 | print "Cluster booted successfully!" 56 | end_time = int(time.time() - start_time) 57 | print ' Elapsed Time: %s minutes %s seconds' % (end_time / 60, end_time % 60) 58 | print 59 | 60 | # Print SSH commands 61 | public_ips = [] 62 | private_ips = [] 63 | for server in servers: 64 | public_ips.append(server.public_ip) 65 | private_ips.append(server.private_ip) 66 | print 67 | 68 | return [private_ips, public_ips] 69 | 70 | def terminate_cluster(rax_user, rax_api_key, search_term): 71 | 72 | # Grab all the infomation for clusters spawn by this tool that are still alive 73 | ds_reservations = {} 74 | cloudservers = CloudServers(rax_user, rax_api_key) 75 | try: 76 | serverlist = cloudservers.servers.list() 77 | except: 78 | sys.stderr.write('Rackspace authentication failed. Please check your Rackspace authentication and try again.\n') 79 | sys.exit(1) 80 | 81 | p = re.compile("{0}.*".format(search_term)) 82 | 83 | for server in serverlist: 84 | if p.match(server.name): 85 | try: 86 | if cloudservers.servers.get(server.id).status == "ACTIVE": 87 | if not server.name in ds_reservations: 88 | ds_reservations[server.name] = { 89 | 'Servers': [] 90 | } 91 | ds_reservations[server.name]['Servers'].append(server) 92 | except: 93 | # Server was recently shut down 94 | pass 95 | 96 | # Prompt for cluster to destroy 97 | selection = common.choose("Choose the cluster to destroy:", ds_reservations.keys()) 98 | 99 | # Confirm cluster termination 100 | print "Confirm you wish to terminate {0} by pressing 'y'.".format(selection) 101 | if raw_input().lower() != 'y': 102 | print "Cluster was not terminated." 103 | print 104 | sys.exit() 105 | print 106 | 107 | # The actual termination command 108 | for server in ds_reservations[selection]['Servers']: 109 | try: 110 | server.delete() 111 | except: 112 | print "This server appears to have already been shut down. Please give it some time for the API to adjust." 113 | 114 | print "Termination command complete." 115 | print 116 | 117 | # NOTES: 118 | # cloudservers = CloudServers(user, apiKey) 119 | # 120 | # Find Flavor List 121 | # for i in cloudservers.flavors.list(): 122 | # print "ID: %s = %s" % (i.id, i.name) 123 | # 124 | # Find Image List 125 | # for i in cloudservers.images.list(): 126 | # print "ID: %s = %s" % (i.id, i.name) 127 | -------------------------------------------------------------------------------- /cassandralauncher/s3cfg: -------------------------------------------------------------------------------- 1 | [default] 2 | access_key = $ACCESS_KEY 3 | acl_public = False 4 | bucket_location = US 5 | cloudfront_host = cloudfront.amazonaws.com 6 | cloudfront_resource = /2008-06-30/distribution 7 | default_mime_type = binary/octet-stream 8 | delete_removed = False 9 | dry_run = False 10 | encoding = UTF-8 11 | encrypt = False 12 | force = False 13 | get_continue = False 14 | gpg_command = /usr/bin/gpg 15 | gpg_decrypt = %(gpg_command)s -d --verbose --no-use-agent --batch --yes --passphrase-fd %(passphrase_fd)s -o %(output_file)s %(input_file)s 16 | gpg_encrypt = %(gpg_command)s -c --verbose --no-use-agent --batch --yes --passphrase-fd %(passphrase_fd)s -o %(output_file)s %(input_file)s 17 | gpg_passphrase = $SECRET_KEY 18 | guess_mime_type = True 19 | host_base = s3.amazonaws.com 20 | host_bucket = %(bucket)s.s3.amazonaws.com 21 | human_readable_sizes = False 22 | list_md5 = False 23 | preserve_attrs = True 24 | progress_meter = True 25 | proxy_host = 26 | proxy_port = 0 27 | recursive = False 28 | recv_chunk = 4096 29 | secret_key = $SECRET_KEY 30 | send_chunk = 4096 31 | simpledb_host = sdb.amazonaws.com 32 | skip_existing = False 33 | urlencoding_mode = normal 34 | use_https = False 35 | verbosity = WARNING 36 | 37 | [datastax] 38 | root_data_dir = '/raid0/cassandra' 39 | bucket_name = 'datastax_s3_storage' 40 | -------------------------------------------------------------------------------- /demoservice/README.md: -------------------------------------------------------------------------------- 1 | # Cassandra Launcher Demo Service 2 | 3 | Allows for a service to run over ./running.log from the instantiation directory to ensure that clusters running too long are terminated. 4 | 5 | For use in conjuncture with demo=True in clusterlauncher.conf under [Cassandra]. 6 | 7 | We do _not_ guarantee any successful operations with this program and should not be held liable for over-running clusters. This is merely an experimental tool which works well in our enviornment. Nothing more. 8 | 9 | ## Setup 10 | 11 | Run: 12 | 13 | nohup ./demoservice.py & 14 | -------------------------------------------------------------------------------- /demoservice/demoservice.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import boto 4 | import common 5 | import time 6 | from decimal import Decimal, ROUND_UP 7 | 8 | config, KEY_PAIR, PEM_HOME, HOST_FILE, PEM_FILE = common.header() 9 | sleepTime = 1 * 60 # 1 minute 10 | conn = None 11 | runningFile = 'running.log' 12 | 13 | 14 | def updateLog(oldline, newline): 15 | with open(runningFile, 'r') as f: 16 | runninglog = f.read() 17 | 18 | runninglog = runninglog.replace(oldline, newline) 19 | 20 | with open(runningFile, 'w') as f: 21 | f.write(runninglog) 22 | 23 | def getReservationByID(aws_access_key_id, aws_secret_access_key, reservationId): 24 | global conn 25 | if not conn: 26 | conn = boto.connect_ec2(aws_access_key_id, aws_secret_access_key) 27 | reservations = conn.get_all_instances() 28 | 29 | for reservation in reservations: 30 | if reservationId == reservation.id: 31 | return reservation 32 | 33 | def checkAndCloseExpiredInstances(): 34 | try: 35 | with open(runningFile, 'r') as f: 36 | runninglog = f.read().strip().split('\n') 37 | except: 38 | # Wait until runninglog is created 39 | return 40 | 41 | # Wait until the log is populated 42 | if not len(runninglog): 43 | return 44 | 45 | for line in runninglog: 46 | status, user, ttl, birthstamp, reservationId = line.split(',') 47 | ttl = float(Decimal(ttl).quantize(Decimal('1.'), rounding=ROUND_UP)) 48 | ttl = ttl * 60 * 60 - (3 * 60) 49 | birthstamp = float(birthstamp) 50 | 51 | if status == 'Running': 52 | if time.time() > birthstamp + ttl: 53 | # Find and create a list for all instances under this reservation 54 | reservation = getReservationByID(config.get('EC2', 'aws_access_key_id'), 55 | config.get('EC2', 'aws_secret_access_key'), reservationId) 56 | instanceList = [] 57 | for instance in reservation.instances: 58 | instanceList.append(instance.id) 59 | 60 | # Terminate these instances 61 | conn.terminate_instances(instanceList) 62 | 63 | # Update log from running a termination on these instances 64 | updateLog(line, line.replace('Running', 'Stopped')) 65 | try: 66 | while True: 67 | checkAndCloseExpiredInstances() 68 | time.sleep(sleepTime) 69 | except: 70 | print "DataStax Cluster Launcher Demo Service exiting..." 71 | import traceback 72 | traceback.print_exc() 73 | with open('error.log', 'w') as f: 74 | f.write(traceback.format_exc()) 75 | -------------------------------------------------------------------------------- /docs/new_features.md: -------------------------------------------------------------------------------- 1 | ## OpsCenter Agents 2 | 3 | OpsCenter Agents are now preinstalled with all clusters that are OpsCenter enabled. No longer do you have to enter credentials via the OpsCenter GUI. 4 | 5 | ## Nodelist 6 | 7 | If you ever need a list of your Cassandra nodes, simply look at `/etc/cassandralauncher/nodelist`. 8 | 9 | ## DataStax SSH Client 10 | 11 | Now you can simply run the DataStax SSH Client by executing: `datastax_ssh`. This will connect you to all machines by serially executing your command of choice on all machines in your cluster. 12 | 13 | ## Modified /etc/hosts 14 | 15 | Now you can simply run an `ssh c0` or `ssh a0` command and easily jump from node to node from within your cluster. 16 | 17 | A prefix of `c` is reserved for Cassandra nodes, `a` for Analytics nodes, and `s` for Search nodes. The numerical suffix refers to their order within the ring. View `/etc/hosts` on the node for more details. 18 | 19 | ## DataStax Parallel SSH Client 20 | 21 | Now you can simply run the DataStax Parallel SSH Client by executing: `datastax_pssh`. This will connect you to all machines and executing your command of choice on all machines in your cluster in parallel. 22 | 23 | ## S3 Store and Restore 24 | 25 | Now you can use the `datastax_s3_store` and `datastax_s3_restore` commands to simply upload and download your `cassandra/data` directory to and from S3. Think of it as an "experimental EBS functionality for S3." 26 | 27 | *Note*: _Do not count on this feature to save important data. This application is only for trivial data in development environments._ 28 | 29 | The files are stored in `s3://datastax_s3_storage-//` using the `~/.s3cfg` file. 30 | 31 | Using the `datastax_pssh` command to store and restore an entire cluster is the recommended way to use this feature. 32 | 33 | To automatically preconfigure `s3cmd`, which `datastax_s3_store` and `datastax_s3_restore` rely on, add/change this on your `clusterlauncher.conf`: 34 | 35 | [S3] 36 | send_s3_credentials = True 37 | 38 | That will send .s3cfg preconfigured with your aws_access_key_id and aws_secret_access_key, properly set under 400 permissions, to your home directory. 39 | -------------------------------------------------------------------------------- /docs/sample_runs.md: -------------------------------------------------------------------------------- 1 | ## Sample Run for Cassandra Launcher 2 | 3 | host1:~ joaquin$ cassandralauncher 4 | Using configuration file: /root/.clusterlauncher.conf 5 | 6 | Welcome to DataStax' Cassandra Cluster Launcher! 7 | The easiest way to get Apache Cassandra up and running in Amazon's EC2 8 | in under 5 minutes! 9 | 10 | No existing clusters currently running! 11 | 12 | Cluster Name: Test Cluster 13 | Total Nodes: 6 14 | Version: 15 | [0] Community 16 | [1] Enterprise 17 | 1 18 | 19 | Confirming credentials... 20 | Analytics Nodes: 2 21 | Search Nodes: 2 22 | 23 | Starting an EC2 cluster of type m1.large with image ami-53f42a3a... 24 | Ensuring DataStax pem key exists on AWS... 25 | Ensuring DataStax pem key exists on filesystem... 26 | Ensuring DataStax pem key's permissions are acceptable... 27 | Configuring ports... 28 | Launching cluster... 29 | Waiting for EC2 cluster to instantiate... 30 | Nodes that have been allocated by EC2: 31 | Node 0 32 | Node 1 33 | Node 2 34 | Node 3 35 | Node 4 36 | Node 5 37 | 38 | Cluster booted successfully! 39 | Elapsed Time: 1 minutes 37 seconds 40 | 41 | OpsCenter Address: 42 | http://184.73.30.118:8888 43 | Note: You must wait 60 seconds after Cassandra becomes active to access OpsCenter. 44 | 45 | Waiting 10 seconds for EC2 instances to warm up... 46 | Priming connections... 47 | The authenticity of host '184.73.30.118 (184.73.30.118)' can't be established. 48 | RSA key fingerprint is 8c:6a:e6:db:7e:0b:4b:f9:cc:bf:0b:16:a8:89:f2:35. 49 | Are you sure you want to continue connecting (yes/no/all)? all 50 | Creating a keyless SSH ring... 51 | 52 | Primed Connection Strings: 53 | Realtime (Cassandra) Nodes: 54 | ssh -i /Users/joaquin/DataStaxLauncher.pem -o UserKnownHostsFile=/Users/joaquin/ds_known_hosts ubuntu@184.73.30.118 55 | ssh -i /Users/joaquin/DataStaxLauncher.pem -o UserKnownHostsFile=/Users/joaquin/ds_known_hosts ubuntu@50.17.89.186 56 | Analytics (Hadoop) Nodes: 57 | ssh -i /Users/joaquin/DataStaxLauncher.pem -o UserKnownHostsFile=/Users/joaquin/ds_known_hosts ubuntu@23.20.74.0 58 | ssh -i /Users/joaquin/DataStaxLauncher.pem -o UserKnownHostsFile=/Users/joaquin/ds_known_hosts ubuntu@23.20.252.3 59 | Search (Solr) Nodes: 60 | ssh -i /Users/joaquin/DataStaxLauncher.pem -o UserKnownHostsFile=/Users/joaquin/ds_known_hosts ubuntu@107.20.117.244 61 | ssh -i /Users/joaquin/DataStaxLauncher.pem -o UserKnownHostsFile=/Users/joaquin/ds_known_hosts ubuntu@23.20.83.5 62 | 63 | Installing DataStax SSH on the cluster... 64 | Setting up the hosts file for the cluster... 65 | Waiting for the agent tarball to be created (This can take up to 4 minutes)... 66 | If taking longer, ctrl-C and login to AMI to see error logs. 67 | Installing OpsCenter Agents... 68 | 69 | Total Elapsed Time: 4 minutes 3 seconds 70 | 71 | Choose the cluster to destroy (if you wish): 72 | [0] None 73 | [1] jcasares - DataStaxAMI Time: 02-22-12 18:06 Size: 4 74 | 1 75 | 76 | Confirm you wish to terminate jcasares - DataStaxAMI Time: 02-22-12 18:06 Size: 4 by pressing 'y'. 77 | y 78 | 79 | Termination command complete. 80 | 81 | ## Sample Run for Plain Image Launcher 82 | 83 | host1:~ joaquin$ imagelauncher 84 | Using configuration file: /root/.clusterlauncher.conf 85 | 86 | Welcome to the Plain Image Launcher! 87 | The easiest way to interface with Amazon's EC2 and Rackspace's CloudServers 88 | and produce a plain instance (or cluster) in under 5 minutes! 89 | 90 | Choose your Cloud Testing Host: 91 | [0] EC2 92 | [1] Rackspace 93 | 1 94 | 95 | Choose your Cloud Command: 96 | [0] Create 97 | [1] Destroy 98 | 0 99 | 100 | Choose your Cluster Size: 3 101 | 102 | Choose your Testing Operating System: 103 | [0] CentOS 104 | [1] Debian 105 | [2] Fedora 106 | [3] Ubuntu 107 | 0 108 | 109 | Choose your Operating System Version: 110 | [0] 5.4 (deprecated) 111 | [1] 5.5 (deprecated) 112 | [2] 5.6 113 | [3] 6.0 114 | 2 115 | 116 | Starting a Rackspace cluster of flavor 4 with image 114... 117 | Launching cluster... 118 | Waiting for cluster... 119 | Nodes that have been allocated by Rackspace: 120 | Node 0 121 | Node 1 122 | Node 2 123 | 124 | Cluster booted successfully! 125 | Elapsed Time: 4 minutes 42 seconds 126 | 127 | 128 | ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no root@50.57.232.174 129 | ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no root@50.57.186.82 130 | ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no root@50.57.225.96 131 | 132 | Public IPs: 133 | 50.57.232.174, 50.57.186.82, 50.57.225.96 134 | 135 | Private IPs: 136 | 10.182.113.188, 10.182.113.192, 10.182.113.194 137 | 138 | Total Elapsed Time: 4 minutes 48 seconds 139 | 140 | 141 | 142 | host1:~ joaquin$ imagelauncher 143 | Using configuration file: /root/.clusterlauncher.conf 144 | 145 | Welcome to the Plain Image Launcher! 146 | The easiest way to interface with Amazon's EC2 and Rackspace's CloudServers 147 | and produce a plain instance (or cluster) in under 5 minutes! 148 | 149 | Choose your Cloud Testing Host: 150 | [0] EC2 151 | [1] Rackspace 152 | 1 153 | 154 | Choose your Cloud Command: 155 | [0] Create 156 | [1] Destroy 157 | 1 158 | 159 | Choose the cluster to destroy: 160 | [0] jcasares-Time-02-22-12-1822-CentOS-56-Size-3 161 | jcasares-Time-02-22-12-1822-CentOS-56-Size-3 automatically chosen. 162 | 163 | Confirm you wish to terminate jcasares-Time-02-22-12-1822-CentOS-56-Size-3 by pressing 'y'. 164 | y 165 | 166 | Termination command complete. 167 | 168 | -------------------------------------------------------------------------------- /qa/run_packaging_smoke.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | test=$1 4 | install_type=$2 5 | 6 | if [ -z "$test" ] || [ -z $install_type ]; then 7 | echo "Usage: $0 " 8 | exit 9 | fi 10 | 11 | ts=$(date +%m%d%y_%H%M%S) 12 | output=/tmp/${0}-$(date +%m%d%y_%H%M%S).output 13 | CWD=`pwd` 14 | 15 | 16 | if [ $install_type == 'pkg' ]; then 17 | SUDO="sudo" 18 | 19 | DSE_CMD="$SUDO dse " 20 | DSETOOL_CMD="$SUDO dsetool " 21 | NODETOOL_CMD="$SUDO nodetool " 22 | CQLSH_CMD="$SUDO cqlsh " 23 | CASS_CLI_COMMAND="$SUDO cassandra-cli -h `hostname`" 24 | 25 | 26 | if [ $test == "dsc" ]; then 27 | DEMO_DIR=/usr/share/dsc-demos 28 | PRICER_CMD="$SUDO $DEMO_DIR/portfolio_manager/bin/pricer " 29 | else 30 | DEMO_DIR=/usr/share/dse-demos 31 | PRICER_CMD="$SUDO $DEMO_DIR/portfolio_manager/bin/pricer " 32 | HADOOP_EXAMPLES="/usr/share/dse/hadoop/lib/hadoop-examples-*.jar" 33 | fi 34 | 35 | elif [ $install_type == 'tar' ]; then 36 | SUDO="" 37 | 38 | DSE_HOME=$HOME/dse 39 | DSE_CMD="$SUDO $DSE_HOME/bin/dse " 40 | DSETOOL_CMD="$SUDO $DSE_HOME/bin/dsetool " 41 | NODETOOL_CMD="$SUDO $DSE_HOME/bin/nodetool " 42 | CQLSH_CMD="$SUDO $DSE_HOME/bin/cqlsh " 43 | CASS_CLI_COMMAND="$SUDO $DSE_HOME/bin/cassandra-cli -h localhost" 44 | 45 | DEMO_DIR=$DSE_HOME/demos 46 | PRICER_CMD="$SUDO $DEMO_DIR/portfolio_manager/bin/pricer " 47 | HADOOP_EXAMPLES="$DSE_HOME/resources/hadoop/hadoop-examples-*.jar" 48 | 49 | fi 50 | 51 | 52 | function run_cmd () { 53 | run_cmd=$1 54 | echo 55 | echo "--------------- Running Command: $run_cmd ---------------" 56 | echo 57 | ! $run_cmd | $SUDO tee $output 58 | 59 | echo 60 | echo 61 | echo "*** Return Code of Command: $? ***" 62 | echo "*** Checking Output for failures ***" 63 | egrep "ERROR | WARNING | WARN | FAIL | FATAL| Exception" $output 64 | 65 | $SUDO rm $output 66 | 67 | echo 68 | echo 69 | echo 70 | sleep 1 71 | } 72 | 73 | function dse_utilities { 74 | cmdlist[0]="$DSETOOL_CMD ring" 75 | cmdlist[1]="$DSETOOL_CMD jobtracker" 76 | cmdlist[2]="$DSE_CMD hive --service help" 77 | 78 | for ((i=0; i<${#cmdlist[@]} ;i++)); 79 | do 80 | cmd=${cmdlist[$i]} 81 | run_cmd "$cmd" 82 | done 83 | } 84 | 85 | function nodetool_test { 86 | cmd="$NODETOOL_CMD -h `hostname` ring" 87 | run_cmd "$cmd" 88 | sleep 2 89 | } 90 | 91 | 92 | function run_portfolio_demo { 93 | cmdlist[0]="$PRICER_CMD -o INSERT_PRICES" 94 | cmdlist[1]="$PRICER_CMD -o UPDATE_PORTFOLIOS" 95 | cmdlist[2]="$PRICER_CMD -o INSERT_HISTORICAL_PRICES -n 100" 96 | cmdlist[3]="$DSE_CMD hive -f 10_day_loss.q" 97 | 98 | for ((i=0; i<${#cmdlist[@]} ;i++)); 99 | do 100 | cd ${DEMO_DIR}/portfolio_manager 101 | 102 | cmd=${cmdlist[$i]} 103 | run_cmd "$cmd" 104 | done 105 | } 106 | 107 | function run_terasort { 108 | 109 | cmdlist[0]="$DSE_CMD hadoop jar $HADOOP_EXAMPLES teragen 30 /tmp/terasort-input-${ts}" 110 | cmdlist[1]="$DSE_CMD hadoop jar $HADOOP_EXAMPLES terasort /tmp/terasort-input-${ts} /tmp/terasort-output-${ts}" 111 | cmdlist[2]="$DSE_CMD hadoop jar $HADOOP_EXAMPLES teravalidate /tmp/terasort-output-${ts} /tmp/terasort-validate-${ts}" 112 | cmdlist[3]="$DSE_CMD hadoop fs -lsr cfs:///" 113 | 114 | for ((i=0; i<${#cmdlist[@]} ;i++)); 115 | do 116 | cmd=${cmdlist[$i]} 117 | run_cmd "$cmd" 118 | done 119 | } 120 | 121 | function run_cql_queries { 122 | echo 123 | echo "--------------- Running test: cqlsh ---------------" 124 | echo 125 | 126 | $CQLSH_CMD localhost < 1: 142 | print 'cd %s' % sys.argv[1] 143 | print 'cassandralauncher/cassandralauncher.py ', 144 | switches = case.keys() 145 | switches.sort() 146 | for switch in switches: 147 | if switch == 'clustername': 148 | print '--{0} "{1}"'.format(switch, case[switch]), 149 | else: 150 | print '--{0} {1}'.format(switch, case[switch]), 151 | print '--qa --noprompts' 152 | print 153 | 154 | print 'Manually test just --version, --clustername, --totalnodes' 155 | print 156 | -------------------------------------------------------------------------------- /scripts/cassandralauncher: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import cassandralauncher 4 | cassandralauncher.CassandraLauncher() 5 | -------------------------------------------------------------------------------- /scripts/imagelauncher: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import cassandralauncher 4 | cassandralauncher.ClusterLauncher() 5 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import sys 4 | 5 | if sys.version_info < (2, 6): 6 | sys.stderr.write('Please install (and use) Python2.6, or greater, to run setup.py.\n') 7 | sys.exit(1) 8 | 9 | try: 10 | from setuptools import setup 11 | except: 12 | from distutils.core import setup 13 | 14 | long_description = """CassandraLauncher contains two parts: 15 | 16 | 1. `cassandralauncher` is accessible straight from the command line and launches a Cassandra cluster for you. 17 | 2. `imagelauncher` is also accessible from the command line and provides a user interface for AWS and RAX. 18 | 19 | Both run off of a config: clusterlauncher.conf. 20 | """ 21 | 22 | setup(name='CassandraLauncher', 23 | version='1.21', 24 | description='Command line utilities for launching Cassandra clusters in EC2', 25 | long_description=long_description, 26 | author='Joaquin Casares', 27 | author_email='joaquin.casares AT gmail.com', 28 | url='http://www.github.com/joaquincasares/cassandralauncher', 29 | packages=['cassandralauncher'], 30 | scripts=['scripts/cassandralauncher', 'scripts/imagelauncher'], 31 | package_data={'': ['README.md']}, 32 | data_files=[('/usr/local/etc/cassandralauncher', [ 33 | 'cassandralauncher/clusterlauncher.conf', 34 | 'cassandralauncher/datastax_s3_store', 35 | 'cassandralauncher/datastax_s3_restore', 36 | 'cassandralauncher/datastax_ssh', 37 | 'cassandralauncher/datastax_pssh', 38 | 'cassandralauncher/s3cfg' 39 | ])], 40 | keywords="apache cassandra solr pig hive hadoop search analytics datastax cluster clustertools cloud cloudservers rackspace ec2 aws on-demand", 41 | install_requires=["boto", "python-cloudservers"] 42 | ) 43 | --------------------------------------------------------------------------------