Пример #1
0
    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)
Пример #2
0
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))
Пример #3
0
    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
Пример #4
0
 def list_all_profiles(self):
     return load_profiles()
Пример #5
0
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))