def stop_cluster(user_name): user_info = get_user_info(dynamodb_client, user_name, user_table) if 'Item' not in user_info: return _make_reply(_http_status(user_info), { "status": Status.USER_NOT_FOUND, "error": "%s does not exist" % user_name }) if 'cfn_id' not in user_info['Item']: return _make_reply(_http_status(user_info), { 'status': Status.NO_STACK, 'error': '%s does not have a stack' % user_name }) cfn_id = user_info['Item']['cfn_id']['S'] stack_params = get_stack_params(cfn_client, cfn_id) for param in stack_params: if param['ParameterKey'] == 'ClusterSize': param.update(ParameterValue='0') if param['ParameterKey'] == 'AdminPassword': param.pop('ParameterValue', None) param['UsePreviousValue']=True response = cfn_client.update_stack( StackName = cfn_id, UsePreviousTemplate = True, Parameters = stack_params, Capabilities=IamCapabilities, RoleARN=cfn_role_arn ) return _make_reply(_http_status(response), {'status': Status.OK})
def deduct_credit(user_name): # For expServer to invoke - it only has access to deduct credit. # No other configurable params - to avoid potential securty issue with juypter # To-do auth logic to make sure the caller is updating his credit only user_info = get_user_info(dynamodb_client, user_name, user_table) if 'Item' in user_info and 'cfn_id' in user_info['Item']: cfn_id = user_info['Item']['cfn_id']['S'] else: return _make_reply( _http_status(user_info), { 'status': Status.NO_STACK, 'error': '%s does not have a stack' % user_name }) stack_info = get_stack_info(cfn_client, cfn_id) if 'errorCode' in stack_info: return _make_reply( stack_info['errorCode'], { 'status': Status.STACK_NOT_FOUND, 'error': 'Stack %s not found' % cfn_id }) elif 'size' not in stack_info or 'type' not in stack_info or stack_info[ 'size'] == 0: return _make_reply(200, { 'status': Status.NO_RUNNING_CLUSTER, 'error': 'No running cluster' }) credit_change = str(-1 * get_price(stack_info['type'], stack_info['size'])) return update_credit(user_name, credit_change)
def get_stack_info(client, cfn_id): cluster_url = None try: response = client.describe_stacks(StackName=cfn_id) except Exception as e: return {'errorCode': 400} if 'Stacks' not in response or len(response['Stacks']) == 0: return {'errorCode': _http_status(response)} stack_info = response['Stacks'][0] ret_struct = {} for param in stack_info['Parameters']: if 'ParameterValue' in param: if param['ParameterKey'] == 'InstanceType': ret_struct['type'] = param['ParameterValue'] elif param['ParameterKey'] == 'ClusterSize': ret_struct['size'] = int(param['ParameterValue']) for output in stack_info['Outputs']: if output['OutputKey'] == 'S3Bucket': ret_struct['s3_url'] = output['OutputValue'] elif output['OutputKey'] == 'VanityURL': ret_struct['cluster_url'] = output['OutputValue'] elif 'cluster_url' not in ret_struct and output['OutputKey'] == 'URL': ret_struct['cluster_url'] = output['OutputValue'] ret_struct['stack_status'] = stack_info['StackStatus'] return ret_struct
def get_stack_params(client, cfn_id): try: response = client.describe_stacks(StackName=cfn_id) except Exception as e: return {'errorCode': 400} if 'Stacks' not in response or len(response['Stacks']) == 0: return {'errorCode': _http_status(response)} stack_params = response['Stacks'][0]['Parameters'] return stack_params
def get_cluster(user_name): user_info = get_user_info(dynamodb_client, user_name, user_table) if 'Item' not in user_info: response = init_user(dynamodb_client, user_name, default_credit, user_table, billing_table) return _make_reply(_http_status(response), { 'status': Status.OK, 'isPending': False }) elif 'cfn_id' not in user_info['Item']: return _make_reply(200, { 'status': Status.OK, 'isPending': False }) cfn_id = user_info['Item']['cfn_id']['S'] stack_info = get_stack_info(cfn_client, cfn_id) if 'errorCode' in stack_info: if _http_status(reset_user_cfn(dynamodb_client, user_name, user_table)) != 200: error = 'Stack %s not found and failed to clean user table' % cfn_id else: error = 'Stack %s not found' % cfn_id return _make_reply(stack_info['errorCode'], { 'status': Status.STACK_NOT_FOUND, 'error': error }) # To-do more detailed stack status else: # in progresss if stack_info['stack_status'].endswith('IN_PROGRESS'): return _make_reply(200, { 'status': Status.OK, 'isPending': True, 'isStarting': False if stack_info['size'] == 0 else True }) #updated completed, then check cluster status elif stack_info['stack_status'] == 'UPDATE_COMPLETE': cluster_status = check_cluster_status(user_name, stack_info) credit_change = str(-1 * get_price(stack_info['type'], stack_info['size'])) return _make_reply(200, cluster_status) #error(more detailed failure check) else: return _make_reply(200, { 'status': Status.STACK_ERROR, 'error': 'Stack has error: %s' % stack_info['stack_status'], })
def get_credit(user_name): response = dynamodb_client.query( TableName=billing_table, ScanIndexForward=True, ProjectionExpression='credit_change', KeyConditionExpression='user_name = :uname', ExpressionAttributeValues={':uname': { 'S': user_name }}) credit = 0 if 'Items' in response and len(response['Items']) > 0: for row in response['Items']: credit += float(row['credit_change']['N']) else: return _make_reply( _http_status(response), { 'status': Status.NO_CREDIT_HISTORY, 'error': 'No credit history for user: %s' % user_name }) while 'LastEvaluatedKey' in response: response = dynamodb_client.query( TableName=billing_table, ScanIndexForward=True, ExclusiveStartKey=response['LastEvaluatedKey'], ProjectionExpression='credit_change', KeyConditionExpression='user_name = :uname', ExpressionAttributeValues={':uname': { 'S': user_name }}) if 'Items' in response and len(response['Items']) > 0: for row in response['Items']: credit += float(row['credit_change']['N']) return _make_reply(_http_status(response), { 'status': Status.OK, 'credits': credit, })
def update_credit(user_name, credit_change): transaction = { 'user_name': { 'S': user_name }, 'timestamp': { 'N': str(round(time.time() * 1000)) }, 'credit_change': { 'N': credit_change } } response = dynamodb_client.put_item(TableName=billing_table, Item=transaction) return _make_reply(_http_status(response), {'status': Status.OK})
def get_bucket(user_name): response = get_user_info(dynamodb_client, user_name, user_table) if 'Item' not in response: return _make_reply( _http_status(response), { 'status': Status.USER_NOT_FOUND, 'error': "%s does not exist" % user_name }) cfn_id = response['Item']['cfn_id']['S'] stack_info = get_stack_info(cfn_client, cfn_id) if 'errorCode' in stack_info: return _make_reply( stack_info['errorCode'], { 'status': Status.STACK_NOT_FOUND, 'error': 'Stack %s not found' % cfn_id }) if 's3_url' not in stack_info: return _make_reply( 200, { 'status': Status.S3_BUCKET_NOT_EXIST, 'error': 'Cloud not find s3 given stack %s' % cfn_id }) return stack_info['s3_url']
def start_cluster(user_name, cluster_params): # if the user has a cfn stack response = get_user_info(dynamodb_client, user_name, user_table) if 'Item' not in response: return _make_reply(_http_status(response), { 'status': Status.USER_NOT_FOUND, 'error': '%s does not exist' % user_name }) user_info = response['Item'] parameters = [] is_new = False # whether this is a new user tags = cfn_id = cluster_type = response = None if 'cfn_id' in user_info: cfn_id = user_info['cfn_id']['S'] else: #or we give him an available one stack_info = get_available_stack(user_name) if stack_info is None: return _make_reply(200, { 'status': Status.NO_AVAILABLE_STACK, 'error': 'No available stack at this moment' }) cfn_id = stack_info['cfn_id'] tags = stack_info['tags'] is_new = True if 'type' in cluster_params and cluster_params['type'] in cluster_type_table: cluster_type = cluster_type_table[cluster_params['type']] else: # default to use 'XS' cluster_type = cluster_type_table['XS'] stack_params = get_stack_params(cfn_client, cfn_id) for param in stack_params: if param['ParameterKey'] == 'ClusterSize': param.update(ParameterValue=cluster_type['clusterSize']) if param['ParameterKey'] == 'InstanceType': param.update(ParameterValue=cluster_type['instanceType']) if param['ParameterKey'] == 'ImageId' and 'AMI' in cluster_params: param.update(ParameterValue=cluster_params['AMI']) if param['ParameterKey'] == 'AdminPassword': param.pop('ParameterValue', None) param['UsePreviousValue']=True is_test_cluster = check_test_cluster(cfn_id) if is_test_cluster is None: error = 'Stack %s not found' % cfn_id return _make_reply(200, { 'status': Status.STACK_NOT_FOUND, 'error': error }) elif is_test_cluster: if is_new == False: response = cfn_client.update_stack( StackName=cfn_id, UsePreviousTemplate=True, Parameters=stack_params, Capabilities=IamCapabilities, RoleARN=cfn_role_arn ) else: cfn_client.update_stack( StackName=cfn_id, UsePreviousTemplate=True, Parameters=stack_params , Capabilities=IamCapabilities, RoleARN=cfn_role_arn, Tags=tags ) updates = { 'cfn_id': { 'S': cfn_id } } response = update_user_info(dynamodb_client, user_info, updates, user_table) else: template = ssm_client.get_parameter(Name=ssm_key)['Parameter']['Value'] if is_new == False: response = cfn_client.update_stack( StackName=cfn_id, TemplateURL=template, UsePreviousTemplate=False, Parameters=stack_params, Capabilities=IamCapabilities, RoleARN=cfn_role_arn ) else: cfn_client.update_stack( StackName=cfn_id, TemplateURL=template, UsePreviousTemplate=False, Parameters=stack_params , Capabilities=IamCapabilities, RoleARN=cfn_role_arn, Tags=tags ) updates = { 'cfn_id': { 'S': cfn_id } } response = update_user_info(dynamodb_client, user_info, updates, user_table) return _make_reply(_http_status(response), { 'status': Status.OK })