def __init__(self, name : str, instance_id : bool = False, profile : str = None, key_pair : str = None, kp_dir : str = None, security_group : str = None, instance_profile : str = '', efs_mount : bool = False, firewall : tuple = None, image_id : str = None, price : float = None, region : str = None, scripts : list = None, username : str = None, filesystem : str = None, new_mount : bool = False, monitoring : bool = False): ''' A class to run, control and interact with spot instances. __________ parameters - name : str. name for spot instance launch group (will be used as identifier) - instance_id : bool. if True, consider instance id string (overrides --name) - profile : dict of settings for the spot instance - instance_profile : str. Instance profile with attached IAM roles - monitoring : bool, default True. set monitoring to True for the instance - filesystem : string, default <name>. Filesystem to connect to the instance. If you want a new EFS to be created with this name set efs_mount = True, if an efs with the same name exists then the instance will be connected to it. - image_id : Image ID from AWS. go to the launch-wizard to get the image IDs or use the boto3 client.describe_images() with Owners of Filters parameters to reduce wait time and find what you need. - instance_type : Get a list of instance types and prices at https://aws.amazon.com/ec2/spot/pricing/ - price : float. maximum price willing to pay for the instance. - region : string. AWS region - username : string. This will usually depend on the operating system of the image used. For a list of operating systems and defaul usernames check https://alestic.com/2014/01/ec2-ssh-username/ - key_pair : string. name of the keypair to use. Will search for `key_pair`.pem in the current directory - kp_dir : string. path name for where to store the key pair files - sec_group : string. name of the security group to use - efs_mount : bool. (for advanced use) If True, attach EFS mount. If no EFS mount with the name <filesystem> exists one is created. If filesystem is None the new EFS will have the same name as the instance - new_mount : bool. (for advanced use) If True, create a new mount target on the EFS, even if one exists. If False, will be set to True if file system is submitted but no mount target is detected. - firewall : str. Firewall settings ''' self.profile = None profiles=sutils.load_profiles() if instance_id: self.using_id = True else: self.using_id = False self.name = name self.client = None if profile is None: if not self.using_id: raise Exception('Must specify a profile') else: self.profile = profiles[list(profiles.keys())[0]] else: self.profile=copy.deepcopy(profiles[profile]) if key_pair is not None: self.profile['key_pair']=(key_pair, key_pair+'.pem') self.filesystem = None if filesystem is None: self.filesystem='' self.profile['efs_mount'] = False print('No EFS mount requested for this instance.') else: self.filesystem=filesystem self.profile['efs_mount'] = True print('Instance will be mounted on the '+self.filesystem+' elastic filesystem') if firewall is not None: self.profile['firewall']=firewall if image_id is not None: self.profile['image_id']=image_id if price is not None: self.profile['price']=price if region is not None: self.profile['region']=region if username is not None: self.profile['username']=username if security_group is not None: sg = iam_methods.retrieve_security_group(security_group, region=self.profile['region']) self.profile['security_group'] = (sg['GroupId'], self.sec_group) self.kp_dir = None if kp_dir is not None: self.kp_dir = kp_dir else: try: kp_dir = sutils.get_package_kp_dir() if kp_dir =='': raise Exception print('Default key-pair directory is "%s"' % kp_dir) self.kp_dir = kp_dir except: kp_dir = input('Please select a default directory in which to save your key-pairs: ') sutils.set_default_kp_dir(kp_dir) print('You can change the default key-pair directory using spot_connect.sutils.set_default_kp_dir(<dir>)' % kp_dir) self.kp_dir = kp_dir # Add a forward slash to the kp_dir if self.kp_dir[-1]!='/': self.kp_dir = self.kp_dir + '/' self.new_mount = new_mount self.monitoring = monitoring self.instance_profile = instance_profile print('', flush=True) print('#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#', flush=True) print('#~#~#~#~#~#~#~# Spotting '+self.name, flush=True) print('#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#', flush=True) print('', flush=True) self.filled_profile = None # Launch the Instance # Launch the instance using the name profile, instance profile and monitoring arguments try: # If a key pair and security group were not added provided, they wil be created using the name of the instance self.instance, self.profile = ec2_methods.get_spot_instance(self.name, self.profile, instance_profile=self.instance_profile, monitoring=self.monitoring, kp_dir=self.kp_dir, using_instance_id=self.using_id) # Launch or connect to the spot instance under the given name except Exception as e: raise e sys.exit(1) # Mount Elastic File System if self.profile['efs_mount']: print('Requesting EFS mount...') fs_name = self.filesystem try: self.mount_target, self.instance_dns, self.filesystem_dns = efs_methods.retrieve_efs_mount(fs_name, self.instance, new_mount=self.new_mount, region=self.profile['region']) except Exception as e: raise e sys.exit(1) print('Connecting instance to link EFS...') instance_methods.run_script(self.instance, self.profile['username'], bash_scripts.compose_mount_script(self.filesystem_dns), kp_dir=self.kp_dir, cmd=True) # Automatically Run Scripts st = time.time() if scripts is not None: for script in scripts: print('\nExecuting script "%s"...' % str(script)) try: if not instance_methods.run_script(self.instance, self.profile['username'], script, kp_dir=self.kp_dir): break except Exception as e: print(str(e)) print('Script %s failed with above error' % script) print('Time to run script: %s' % str(time.time()-st)) self.state = self.instance['State']['Name'] print('\nDone. Current instance state: '+self.state)
def main(): # Main execution profiles=sutils.load_profiles() parser = argparse.ArgumentParser(description='Launch spot instance') # Variable for naming/identifying the instance parser.add_argument('-n', '--name', help='name for spot instance launch group (will be used as identifier)', default='') parser.add_argument('-iid', '--instanceid', help='instance id string (overrides --name)', default='') parser.add_argument('-kp', '--keypair', help='name of the key pair to use (will default to KP-<name> if none is submitted)', default='') parser.add_argument('-sg', '--securitygroup', help='name of the security group to use (will default to SG-<name> if none is submitted)', default='') parser.add_argument('-ip', '--instanceprofile', help='instance profile with attached IAM roles', default='') parser.add_argument('-p', '--profile', help='profile with efsmount, firewall, imageid, price, region, script and username settings any of which can be set here).', default=list(profiles.keys())[0], choices=profiles.keys()) parser.add_argument('-em', '--efsmount', help='if True, will connect or create a filesystem (for internal use, if no filesystem name is submitted this will be False)', default=True) parser.add_argument('-fw', '--firewall', help='a tuple of len 4 with firewall settings', default='') parser.add_argument('-ami', '--imageid', help='the ID for the AMI image to use', default='') parser.add_argument('-prc', '--price', help='custom maximum price for the instance', default='') parser.add_argument('-reg', '--region', help='AWS Region to use', default='') parser.add_argument('-s', '--script', help='script path (equivalent to user-date run on connection)', default='') parser.add_argument('-un', '--username', help='username to use to log into the instance, default is ec2-user', default='') parser.add_argument('-f', '--filesystem', help='elastic file system creation token', default='') parser.add_argument('-nm', '--newmount', help='create a new mount target even if one exists (for internal use)', default=False) parser.add_argument('-u', '--upload', help='file or directory to upload', default='') parser.add_argument('-r', '--remotepath', help='directory on EC2 instance to upload via ordinary NFS', default='.') parser.add_argument('-a', '--activeprompt', help='if "True" leave an active shell open after running scripts', default=False) parser.add_argument('-t', '--terminate', help='terminate the instance after running everything', default=False) parser.add_argument('-m', '--monitoring', help='activate monitoring for the instance', default=True) args = parser.parse_args() profile = profiles[args.profile] if args.instanceid != '': spot_identifier = args.instanceid using_id = True elif args.name == '': raise Exception("Must submit a name <-n> or instance id <-iid>.") else: spot_identifier = args.name using_id = False print('', flush=True) print('#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#', flush=True) print('#~#~#~#~#~#~#~# Spotting '+spot_identifier, flush=True) print('#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#', flush=True) print('', flush=True) if args.keypair != '': profile['key_pair'] = (args.keypair, args.keypair+'.pem') if args.filesystem!='': print('Instance will be mounted on the '+args.filesystem+' elastic filesystem') profile['efs_mount'] = True elif args.filesystem=='': print('No EFS mount requested for this instance.') profile['efs_mount'] = False if args.firewall != '': profile['firewall_ingress'] = args.firewall if args.imageid != '': profile['image_id'] = args.imageid if args.price != '': profile['price'] = args.price if args.username != '': profile['username'] = args.username if args.region != '': profile['region'] = args.region if args.securitygroup != '': # Retrieve the security group sg = iam_methods.retrieve_security_group(args.securitygroup, region=profile['region']) # For the profile we need a tuple of the security group ID and the security group name. profile['security_group'] = (sg['GroupId'],args.securitygroup) try: kp_dir = sutils.get_package_kp_dir() if kp_dir =='': raise Exception print('Default key-pair directory is "%s"' % kp_dir) except: kp_dir = input('Please select a default directory in which to save your key-pairs: ') sutils.set_default_kp_dir(kp_dir) print('You can change the default key-pair directory using spot_connect.sutils.set_default_kp_dir(<dir>)' % kp_dir) # Add a forward slash to the kp_dir if kp_dir[-1]!='/': kp_dir = kp_dir + '/' # Launch the instance using the name profile, instance profile and monitoring arguments try: # If a key pair and security group were not added provided, they wil be created using the name of the instance instance, profile = ec2_methods.get_spot_instance(spot_identifier, profile, instance_profile=args.instanceprofile, monitoring=args.monitoring, kp_dir=kp_dir, using_instance_id=using_id) # Launch or connect to the spot instance under the given name except Exception as e: raise e sys.exit(1) # If a filesystem was provided and we want to mount an EFS if profile['efs_mount']: print('Requesting EFS mount...') fs_name = args.filesystem try: # Create and/or mount an EFS to the instance mount_target, instance_dns, filesystem_dns = efs_methods.retrieve_efs_mount(fs_name, instance, new_mount=args.newmount, region=profile['region']) except Exception as e: raise e sys.exit(1) print('Connecting to instance to link EFS...') instance_methods.run_script(instance, profile['username'], bash_scripts.compose_mount_script(filesystem_dns), kp_dir=kp_dir, cmd=True) st = time.time() if args.upload!='': files_to_upload = [] for file in args.upload.split(','): files_to_upload.append(os.path.abspath(file)) instance_methods.upload_to_ec2(instance, profile['username'], files_to_upload, remote_dir=args.remotepath) print('Time to Upload: %s' % str(time.time()-st)) st = time.time() scripts_to_run = [] if args.script!= '': for s in args.script.split(','): scripts_to_run.append(s) for script in profile['scripts'] + scripts_to_run: print('\nExecuting script "%s"...' % str(script)) try: if not instance_methods.run_script(instance, profile['username'], script): break except Exception as e: print(str(e)) print('Script %s failed with above error' % script) print('Time to Run Script: %s' % str(time.time()-st)) if args.activeprompt: instance_methods.active_shell(instance, profile['username']) if args.terminate: # If we want to terminate the instance instance_methods.terminate_instance(instance['InstanceId']) # termination overrrides everything else print('Instance %s has been terminated' % str(spot_identifier))
def launch_fleet(self, account_number, n_instances, profile, name=None, user_data=None, instance_profile='', monitoring=True, availability_zone=None, kp_dir=None, enable_nfs=True, enable_ds=True, return_fid=False): ''' Launch a spot fleet and store it in the LinkAWS.fleets dict attribute. Each item has as the key a fleet id and as the value a dictionary the key 'instances' with its respective instances and the key 'name' if a name was submitted. Use the refresh_fleet_instances command to update the instances in each fleet For parameter descriptions use: help(spot_connect.fleet_methods.launch_spot_fleet) The attribute ['instances'] key in each fleet is a response from describe_spot_fleet_instances of the format: [{'InstanceId': 'i-07bcc7d2aq23 'InstanceType': 't3.micro', 'SpotInstanceRequestId': 'sir-ad32k5j', 'InstanceHealth': 'healthy'}, {'InstanceId': 'i-0dbec856841', 'InstanceType': 't3.micro', 'SpotInstanceRequestId': 'sir-848rwg', 'InstanceHealth': 'healthy'}] ''' profiles = sutils.load_profiles() profile = profiles[profile] # Submit a request to launch a spot fleet with the given number of instances response = launch_spot_fleet(account_number, n_instances=n_instances, profile=profile, name=name, instance_profile=instance_profile, user_data=user_data, monitoring=monitoring, availability_zone=availability_zone, kp_dir=kp_dir, enable_nfs=enable_nfs, enable_ds=enable_ds) # Get the request id for the fleet spot_fleet_req_id = response['SpotFleetRequestId'] # Get a list of the instances associated with the fleet fleet_instances = get_fleet_instances(spot_fleet_req_id, region=profile['region']) # Assign the fleet and its instances to the self.fleets attribute self.fleets[spot_fleet_req_id] = {} self.fleets[spot_fleet_req_id]['instances'] = fleet_instances[ 'ActiveInstances'] self.fleets[spot_fleet_req_id]['region'] = profile['region'] if name is not None: self.fleets[spot_fleet_req_id]['name'] = name if return_fid: return spot_fleet_req_id
def list_all_profiles(self): return load_profiles()
def main(): # Main execution profiles = sutils.load_profiles() parser = argparse.ArgumentParser(description='Launch spot instance') parser.add_argument('-n', '--name', help='Name of the spot instance', required=True) parser.add_argument('-p', '--profile', help='Profile', default=list(profiles.keys())[0], choices=profiles.keys()) parser.add_argument('-s', '--script', help='Script path', default='') parser.add_argument('-f', '--filesystem', help='Elastic File System name', default='') parser.add_argument('-u', '--upload', help='File or directory to upload', default='') parser.add_argument( '-r', '--remotepath', help='Directory on EC2 instance to upload via ordinary NFS', default='.') parser.add_argument( '-a', '--activeprompt', help='If "True" leave an active shell open after running scripts', default=False) parser.add_argument('-t', '--terminate', help='Terminate the instance after running everything', default=False) parser.add_argument('-m', '--monitoring', help='Activate monitoring for the instance', default=True) parser.add_argument('-em', '--efsmount', help='if True, will connect or create a filesystem', default=True) parser.add_argument('-nm', '--newmount', help='Create a new mount target even if one exists', default=False) parser.add_argument('-ip', '--instanceprofile', help='Instance profile with attached IAM roles', default='') args = parser.parse_args() profile = profiles[args.profile] print('', flush=True) print('#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#', flush=True) print('#~#~#~#~#~#~#~# Launching ' + args.name, flush=True) print('#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#~#', flush=True) print('', flush=True) try: kp_dir = sutils.get_package_kp_dir() if kp_dir == '': raise Exception print('Default key-pair directory is "%s"' % kp_dir) except: kp_dir = input( 'Please select a default directory in which to save your key-pairs: ' ) sutils.set_default_kp_dir(kp_dir) print( 'You can change the default key-pair directory using spot_connect.sutils.set_default_kp_dir(<dir>)' % kp_dir) # Add a forward slash to the kp_dir if kp_dir[-1] != '/': kp_dir = kp_dir + '/' # Launch the instance using the name profile, instance profile and monitoring arguments try: instance, profile = instances.launch_spot_instance( args.name, profile, instance_profile=args.instanceprofile, monitoring=args.monitoring, kp_dir=kp_dir ) # Launch or connect to the spot instance under the given name except Exception as e: raise e sys.exit(1) if not args.efsmount: profile['efs_mount'] = False if profile['efs_mount']: # If no filesystem name is submitted then do not attach a file system if args.filesystem == '': print('No EFS mount requested for this instance.') pass # Otherwise create the file system and attach an efs mount. else: print('Profile requesting EFS mount...') fs_name = args.filesystem try: # Create and/or mount an EFS to the instance mount_target, instance_dns, filesystem_dns = elastic_file_systems.retrieve_efs_mount( fs_name, instance, new_mount=args.newmount, region=profile['region']) except Exception as e: raise e sys.exit(1) print('Connecting to instance to link EFS...') methods.run_script( instance, profile['username'], elastic_file_systems.compose_mount_script(filesystem_dns), cmd=True) st = time.time() if args.upload != '': files_to_upload = [] for file in args.upload.split(','): files_to_upload.append(os.path.abspath(file)) methods.upload_to_ec2(instance, profile['username'], files_to_upload, remote_dir=args.remotepath) print('Time to Upload: %s' % str(time.time() - st)) st = time.time() scripts_to_run = [] if args.script != '': for s in args.script.split(','): scripts_to_run.append(s) for script in profile['scripts'] + scripts_to_run: print('\nExecuting script "%s"...' % str(script)) try: if not methods.run_script(instance, profile['username'], script): break except Exception as e: print(str(e)) print('Script %s failed with above error' % script) print('Time to Run Scripts: %s' % str(time.time() - st)) if args.activeprompt: methods.active_shell(instance, profile['username']) if args.terminate: # If we want to terminate the instance methods.terminate_instance( instance['InstanceId']) # termination overrrides everything else print('Script %s has been terminated' % str(args.name))