def delete_volume(self, aws, args): with print_scope('Retrieving data from AWS:', 'Done.\n'): # Get current user with step('Get user identity'): user = aws.get_user_identity() # Ensure that instance does not yet exist with step('Get volume details'): volume = aws.get_volumes_by_id(args.volume_id)[0] if not self.is_proper_volume(volume, user) and not args.force: raise CommandError('Volume {} is not owned by you. Use -f flag to force deletion.'.format(args.volume_id)) aws.delete_volume(args.volume_id) print('Volume {} is deleted.'.format(args.volume_id))
def list_volumes(self, aws, args): with print_scope('Retrieving data from AWS:', 'Done.\n'): if not args.all: # Get current user with step('Get user identity'): user = aws.get_user_identity() # Get list of volumes owned by user with step('Get list of proper volumes'): volumes = aws.get_volumes(self.get_proper_volume_filter(user)) else: # Get list of all volumes with step('Get list of volumes'): volumes = aws.get_volumes() # Filter tags of every volume volumes = (self.filter_tags(volume) for volume in volumes) # Pretty print list of volumes map(print_volume, volumes)
def get_portal_spec(args): # Get portal name and spec file portal_name = args.portal.rsplit('.', 1)[0] spec_filename = '{}.json'.format(portal_name) # Ensure spec file exists with step('Locate portal specification file'): if not path.exists(spec_filename): raise Exception( 'Could not find portal specification file `{}`.'.format( spec_filename)) # Parse portal spec file with step('Parse portal specification file', catch=[IOError, ValueError]): with open(spec_filename) as spec_file: portal_spec_data = json.load(spec_file) # Validate portal spec with step('Validate portal specification', catch=[ValidationError]): portal_spec = PortalSchema().load(portal_spec_data) return portal_spec, portal_name
def get_config(args): # Parse global config with step('Parse config file', catch=[IOError, ValueError]): config_path = args.config # If config file is not specified in arguments, look for it in default locations if config_path is None: for p in config_paths: if path.exists(p): config_path = p break else: raise ValueError('Could not find config file') with open(config_path) as config_file: config_data = json.load(config_file) # Validate global config with step('Validate config', catch=[ValidationError]): config = ConfigSchema().load(config_data) return config
def run(self): # Find, parse and validate configs with print_scope('Checking configuration:', 'Done.\n'): config = get_config(self._args) portal_spec, portal_name = get_portal_spec(self._args) # Create AWS client aws = AwsClient(config['aws_access_key'], config['aws_secret_key'], config['aws_region']) with print_scope('Retrieving data from AWS:', 'Done.\n'): # Get current user with step('Get user identity'): user = aws.get_user_identity() # Get spot instance with step('Get spot instance', error_message='Portal `{}` does not seem to be opened'.format(portal_name), catch=[RuntimeError]): spot_instance = common.get_spot_instance(aws, portal_name, user['Arn']) spot_fleet_request_id = \ filter(lambda tag: tag['Key'] == 'aws:ec2spot:fleet-request-id', spot_instance['Tags'])[0]['Value'] # Get spot instance with step('Get spot request', error_message='Portal `{}` does not seem to be opened'.format(portal_name), catch=[RuntimeError]): spot_fleet_request = common.get_spot_fleet_request(aws, spot_fleet_request_id) # TODO: print fleet and instance statistics # Cancel spot instance request aws.cancel_spot_fleet_request(spot_fleet_request_id) # Clean up volumes' tags volume_ids = [volume['Ebs']['VolumeId'] for volume in spot_instance['BlockDeviceMappings'] if not volume['Ebs']['DeleteOnTermination']] aws.remove_tags(volume_ids, 'mount-point') print('Portal `{}` has been closed.'.format(portal_name))
def run(self): # Find, parse and validate configs with print_scope('Checking configuration:', 'Done.\n'): config = get_config(self._args) portal_spec, portal_name = get_portal_spec(self._args) # Ensure there is at least one channel spec with step('Check specifications for channels', error_message= 'Portal specification does not contain any channel'): channels = portal_spec['channels'] if len(channels) == 0: raise Exception() # Create AWS client aws = AwsClient(config['aws_access_key'], config['aws_secret_key'], config['aws_region']) with print_scope('Retrieving data from AWS:', 'Done.\n'): # Get current user with step('Get user identity'): user = aws.get_user_identity() # Get spot instance with step('Get spot instance', error_message='Portal `{}` does not seem to be opened'. format(portal_name), catch=[RuntimeError]): spot_instance = common.get_spot_instance( aws, portal_name, user['Arn']) host_name = spot_instance['PublicDnsName'] # Print information about the channels with print_scope( 'Channels defined for portal `{}`:'.format(portal_name), ''): for i in range(len(channels)): channel = channels[i] with print_scope('Channel #{} ({}):'.format( i, channel['direction'].upper())): print('Local: {}'.format(channel['local_path'])) print('Remote: {}'.format(channel['remote_path'])) # Specify remote host for ssh env.user = portal_spec['spot_instance']['remote_user'] env.key_filename = [portal_spec['spot_instance']['identity_file']] env.hosts = [host_name] # Periodically sync files across all channels print('Syncing... (press ctrl+C to interrupt)') for channel in channels: is_upload = channel['direction'] == 'out' is_recursive = channel[ 'recursive'] if 'recursive' in channel else False delay = 1.0 if 'delay' in channel: delay = channel['delay'] run_periodically(sync_files, [ channel['local_path'], channel['remote_path'], is_upload, is_recursive ], delay)
def run(self): # Find, parse and validate configs with print_scope('Checking configuration:', 'Done.\n'): config = get_config(self._args) portal_spec, portal_name = get_portal_spec(self._args) instance_spec = portal_spec['spot_instance'] # Create AWS client aws = AwsClient(config['aws_access_key'], config['aws_secret_key'], config['aws_region']) with print_scope('Retrieving data from AWS:', 'Done.\n'): # Get current user with step('Get user identity'): user = aws.get_user_identity() # Ensure that instance does not yet exist with step('Check already running instances', error_message='Portal `{}` seems to be already opened'. format(portal_name), catch=[RuntimeError]): common.check_instance_not_exists(aws, portal_name, user['Arn']) # Ensure persistent volumes are available with step('Check volumes availability', catch=[RuntimeError]): volume_ids = [ volume_spec['volume_id'] for volume_spec in portal_spec['persistent_volumes'] ] common.check_volumes_availability(aws, volume_ids) # If subnet Id is not provided, pick the default subnet of the availability zone if 'subnet_id' not in instance_spec or not instance_spec[ 'subnet_id']: with step('Get subnet id', catch=[IndexError, KeyError]): subnets = aws.get_subnets( instance_spec['availability_zone']) instance_spec['subnet_id'] = subnets[0]['SubnetId'] # Make request for Spot instance instance_type = instance_spec['instance_type'] with print_scope('Requesting a Spot instance of type {}:'.format( instance_type)): request_config = aws_helpers.single_instance_spot_fleet_request( portal_spec, portal_name, user['Arn']) response = aws.request_spot_fleet(request_config) spot_fleet_request_id = response['SpotFleetRequestId'] # Wait for spot fleet request to be fulfilled print('Waiting for the Spot instance to be created...') print( '(usually it takes around a minute, but might take much longer)' ) begin_time = datetime.datetime.now() next_time = begin_time try: while True: # Repeat status request every N seconds if datetime.datetime.now() > next_time: spot_fleet_request = aws.get_spot_fleet_request( spot_fleet_request_id) next_time += datetime.timedelta(seconds=5) # Compute time spend in waiting elapsed = datetime.datetime.now() - begin_time # Check request state and activity status request_state = spot_fleet_request['SpotFleetRequestState'] if request_state == 'active': spot_request_status = spot_fleet_request[ 'ActivityStatus'] if spot_request_status == 'fulfilled': break else: print( 'Elapsed {}s. Spot request is {} and has status `{}`' .format(elapsed.seconds, request_state, spot_request_status), end='\r') else: print('Elapsed {}s. Spot request is {}'.format( elapsed.seconds, request_state), end='\r') sys.stdout.flush() # ensure stdout is flushed immediately. time.sleep(0.5) except KeyboardInterrupt: print('\n') print('Interrupting...') # Cancel spot instance request aws.cancel_spot_fleet_request(spot_fleet_request_id) raise CommandError('Spot request has been cancelled.') print('\nSpot instance is created in {} seconds.\n'.format( (datetime.datetime.now() - begin_time).seconds)) # Get id of the created instance spot_fleet_instances = aws.get_spot_fleet_instances( spot_fleet_request_id) instance_id = spot_fleet_instances[0]['InstanceId'] # Get information about the created instance instance_info = aws.get_instance(instance_id) # Make requests to attach persistent volumes with print_scope('Attaching persistent volumes:'): for volume_spec in portal_spec['persistent_volumes']: response = aws.attach_volume(instance_id, volume_spec['volume_id'], volume_spec['device']) # Check status code if response['State'] not in ['attaching', 'attached']: raise CommandError( 'Could not attach persistent volume `{}`'.format( volume_spec['volume_id'])) # Wait for persistent volumes to be attached print('Waiting for the persistent volumes to be attached...') begin_time = datetime.datetime.now() next_time = begin_time while True: # Repeat status request every N seconds if datetime.datetime.now() > next_time: volumes = aws.get_volumes_by_id(volume_ids) next_time += datetime.timedelta(seconds=1) # Compute time spend in waiting elapsed = datetime.datetime.now() - begin_time if all([ volume['Attachments'][0]['State'] == 'attached' for volume in volumes ]): break else: states = [ '{} - `{}`'.format(volume['VolumeId'], volume['Attachments'][0]['State']) for volume in volumes ] print('Elapsed {}s. States: {}'.format( elapsed.seconds, ', '.join(states)), end='\r') sys.stdout.flush() # ensure stdout is flushed immediately. time.sleep(0.5) print('\nPersistent volumes are attached in {} seconds.\n'.format( (datetime.datetime.now() - begin_time).seconds)) # Configure ssh connection via fabric env.user = instance_spec['remote_user'] env.key_filename = [instance_spec['identity_file']] env.hosts = instance_info['PublicDnsName'] env.connection_attempts = self._fabric_retry_limit with print_scope('Preparing the instance:', 'Instance is ready.\n'): # Mount persistent volumes for i in range(len(portal_spec['persistent_volumes'])): with step('Mount volume #{}'.format(i), error_message='Could not mount volume', catch=[RuntimeError]): volume_spec = portal_spec['persistent_volumes'][i] # Mount volume with hide('running', 'stdout'): execute(self.mount_volume, volume_spec['device'], volume_spec['mount_point'], instance_spec['remote_group'], instance_spec['remote_user']) # Store extra information in volume's tags aws.add_tags(volume_spec['volume_id'], {'mount-point': volume_spec['mount_point']}) # TODO: consider importing and executing custom fab tasks instead # Install extra python packages, if needed if 'extra_python_packages' in instance_spec and len( instance_spec['extra_python_packages']) > 0: with step('Install extra python packages', error_message='Could not install python packages', catch=[RuntimeError]): python_packages = instance_spec['extra_python_packages'] virtual_env = instance_spec['python_virtual_env'] with hide('running', 'stdout'): execute(self.install_python_packages, python_packages, virtual_env) # Print summary print('Portal `{}` is now opened.'.format(portal_name)) with print_scope('Summary:', ''): with print_scope('Instance:'): print('Id: {}'.format(instance_id)) print('Type: {}'.format( instance_info['InstanceType'])) print('Public IP: {}'.format( instance_info['PublicIpAddress'])) print('Public DNS name: {}'.format( instance_info['PublicDnsName'])) with print_scope('Persistent volumes:'): for volume_spec in portal_spec['persistent_volumes']: print('{}: {}'.format(volume_spec['device'], volume_spec['mount_point'])) # Print ssh command print('Use the following command to connect to the remote machine:') print('ssh -i "{}" {}@{}'.format(instance_spec['identity_file'], instance_spec['remote_user'], instance_info['PublicDnsName']))
def create_volume(self, aws, args): with print_scope('Retrieving data from AWS:', 'Done.\n'): # Get current user with step('Get user identity'): user = aws.get_user_identity() # Ensure that instance does not yet exist with step('Get Availability Zones'): availability_zones = aws.get_availability_zones() print('Creating new persistent volume.') # Get properties of the new volume name = args.name size = args.size availability_zone = args.zone snapshot_id = args.snapshot # Ask for name, if not provided if name is None: print('Enter name for the new volume (no name by default): ', end='') name = raw_input() or None # Ask for size, if not provide if args.size is None: print('Enter size of the new volume in Gb ({}): '.format(self._default_size), end='') size = raw_input() or self._default_size try: size = int(size) except ValueError as e: raise CommandError('Size has to be an integer.') # Check size parameter if size < self._min_size: raise CommandError('Specified size {}Gb is smaller than the lower limit of {}Gb.' .format(size, self._min_size)) elif size > self._max_size: raise CommandError('Specified size {}Gb is bigger than the upper limit of {}Gb.' .format(size, self._max_size)) # Ask for availability zone, if not provided if availability_zone is None: print('Enter availability zone for the new volume ({}): '.format(availability_zones[0]), end='') availability_zone = raw_input() or availability_zones[0] # Check availability zone if availability_zone not in availability_zones: raise CommandError('Unexpected availability zone "{}". Available zones are: {}.' .format(availability_zone, ', '.join(availability_zones))) # Set tags tags = {'Name': name, 'created-by': user['Arn'], self._proper_tag_key: self._proper_tag_value} # Add user-specified tags, if provided if args.tags is not None: tags.update(self.parse_tags(args.tags)) # Create volume volume_id = aws.create_volume(size, availability_zone, tags, snapshot_id) print('New persistent volume has been created.\nVolume id: {}'.format(volume_id))
def show_full_info(self): # Find, parse and validate configs with print_scope('Checking configuration:', 'Done.\n'): config = get_config(self._args) portal_spec, portal_name = get_portal_spec(self._args) # Create AWS client aws = AwsClient(config['aws_access_key'], config['aws_secret_key'], config['aws_region']) volumes = [] with print_scope('Retrieving data from AWS:', 'Done.\n'): # Get current user with step('Get user identity'): aws_user = aws.get_user_identity() # Get spot instance with step('Get spot instance', error_message='Portal `{}` does not seem to be opened'. format(portal_name), catch=[RuntimeError]): instance_info = aws.find_spot_instance(portal_name, aws_user['Arn']) # Get persistent volumes, if portal is opened if instance_info is not None: with step('Get volumes'): volume_ids = [ volume['Ebs']['VolumeId'] for volume in instance_info['BlockDeviceMappings'] if not volume['Ebs']['DeleteOnTermination'] ] volumes = aws.get_volumes_by_id(volume_ids) # Print status if instance_info is not None: with print_scope('Summary:', ''): print('Name: {}'.format(portal_name)) print('Status: open') with print_scope('Instance:', ''): print('Id: {}'.format( instance_info['InstanceId'])) print('Type: {}'.format( instance_info['InstanceType'])) print('Public IP: {}'.format( instance_info['PublicIpAddress'])) print('Public DNS name: {}'.format( instance_info['PublicDnsName'])) print('User: {}'.format( portal_spec['spot_instance']['remote_user'])) with print_scope('Persistent volumes:', ''): for i in range(len(volumes)): volume = volumes[i] with print_scope('Volume #{}:'.format(i), ''): self.print_volume_info(volume) # Print ssh command with print_scope( 'Use the following command to connect to the remote machine:' ): print('ssh -i "{}" {}@{}'.format( portal_spec['spot_instance']['identity_file'], portal_spec['spot_instance']['remote_user'], instance_info['PublicDnsName'])) else: with print_scope('Summary:'): print('Name: {}'.format(portal_name)) print('Status: close')