Пример #1
0
def test_auto_stop_nonzero(monkeypatch):
    metricsHelper = MetricsHelper('us-west-2')

    def mock_get_metric_statistics(Dimensions=[{
        'Name': 'WorkspaceId',
        'Value': 'ws-xxxxxxxxx'
    }],
                                   Namespace='AWS/WorkSpaces',
                                   MetricName='Stopped',
                                   StartTime=startTime,
                                   EndTime=endTime,
                                   Period=3600,
                                   Statistics=['Minimum', 'Maximum']):
        return {
            'Datapoints': [{
                'Timestamp': timeStamp,
                'Minimum': 0.0,
                'Unit': 'Count'
            }, {
                'Timestamp': timeStamp,
                'Minimum': 1.0,
                'Unit': 'Count'
            }],
            'ResponseMetadata': {
                'HTTPStatusCode': 200
            }
        }

    monkeypatch.setattr(metricsHelper.client, 'get_metric_statistics',
                        mock_get_metric_statistics)

    billableTime = metricsHelper.get_billable_time('ws-xxxxxxxxx', 'AUTO_STOP',
                                                   startTime, endTime)
    assert type(billableTime) is int
    assert billableTime != 2
Пример #2
0
 def __init__(self, settings):
     self.settings = settings
     self.maxRetries = 20
     self.region = settings['region']
     self.hourlyLimits = settings['hourlyLimits']
     self.testEndOfMonth = settings['testEndOfMonth']
     self.isDryRun = settings['isDryRun']
     self.client = boto3.client('workspaces',
                                region_name=self.region,
                                config=botoConfig)
     self.metricsHelper = MetricsHelper(self.region)
     return
def test_get_billable_time():
    settings = 'us-east-1'
    workspaceID = 'ws-abc1234XYZ'
    startTime = '2020-04-01T00:00:00Z'
    endTime = '2020-04-02T20:35:58Z'
    metrics_helper = MetricsHelper(settings)
    client_stubber = Stubber(metrics_helper.client)

    response = {
        'Label': 'UserConnected',
        'Datapoints': [
            {'Timestamp': datetime.datetime(2020, 4, 2, 11, 0, tzinfo=tzutc()), 'Maximum': 1.0, 'Unit': 'Count'},
            {'Timestamp': datetime.datetime(2020, 4, 1, 7, 0, tzinfo=tzutc()), 'Maximum': 1.0, 'Unit': 'Count'},
            {'Timestamp': datetime.datetime(2020, 4, 2, 6, 0, tzinfo=tzutc()), 'Maximum': 1.0, 'Unit': 'Count'},
            {'Timestamp': datetime.datetime(2020, 4, 1, 2, 0, tzinfo=tzutc()), 'Maximum': 0.0, 'Unit': 'Count'},
            {'Timestamp': datetime.datetime(2020, 4, 2, 1, 0, tzinfo=tzutc()), 'Maximum': 0.0, 'Unit': 'Count'}
        ]
    }
    expected_params = {
        'Dimensions': [
            {
                'Name': 'WorkspaceId',
                'Value': workspaceID
            }
        ],
        'Namespace': 'AWS/WorkSpaces',
        'MetricName': 'UserConnected',
        'StartTime': startTime,
        'EndTime': endTime,
        'Period': 3600,
        'Statistics': ['Maximum']
    }

    client_stubber.add_response('get_metric_statistics', response, expected_params)
    client_stubber.activate()
    billable_time = metrics_helper.get_billable_time(workspaceID, startTime, endTime)
    assert billable_time == 3
Пример #4
0
class WorkspacesHelper(object):
    def __init__(self, settings):
        self.settings = settings
        self.maxRetries = 20
        self.region = settings['region']
        self.hourlyLimits = settings['hourlyLimits']
        self.testEndOfMonth = settings['testEndOfMonth']
        self.isDryRun = settings['isDryRun']
        self.client = boto3.client('workspaces',
                                   region_name=self.region,
                                   config=botoConfig)
        self.metricsHelper = MetricsHelper(self.region)
        return

    '''
    returns {
        workspaceID: str,
        billableTime: int,
        hourlyThreshold: int,
        optimizationResult: str,
        initialMode: str,
        newMode: str,
        bundleType: str
    }
    '''

    def process_workspace(self, workspace):

        workspaceID = workspace['WorkspaceId']
        log.debug('workspaceID: %s', workspaceID)

        workspaceBundleType = self.get_bundle_type(workspace)
        log.debug('workspaceBundleType: %s', workspaceBundleType)

        workspaceRunningMode = workspace['WorkspaceProperties']['RunningMode']
        log.debug('workspaceRunningMode: %s', workspaceRunningMode)

        hourlyThreshold = self.get_hourly_threshold(workspaceBundleType)

        billableTime = 0

        if self.check_for_skip_tag(workspaceID) == True:
            log.info('Skipping WorkSpace %s due to Skip_Convert tag',
                     workspaceID)
            optimizationResult = 'S'
        else:

            billableTime = self.metricsHelper.get_billable_time(
                workspaceID, workspaceRunningMode, self.settings['startTime'],
                self.settings['endTime'])

            print billableTime

            optimizationResult = self.compare_usage_metrics(
                workspaceID, billableTime, hourlyThreshold,
                workspaceRunningMode)

        return {
            'workspaceID': workspaceID,
            'billableTime': billableTime,
            'hourlyThreshold': hourlyThreshold,
            'optimizationResult': optimizationResult['resultCode'],
            'newMode': optimizationResult['newMode'],
            'bundleType': workspaceBundleType,
            'initialMode': workspaceRunningMode
        }

    '''
    returns str
    '''

    def get_bundle_type(self, workspace):
        describeBundle = self.client.describe_workspace_bundles(
            BundleIds=[workspace['BundleId']])

        return describeBundle['Bundles'][0]['ComputeType']['Name']

    '''
    returns int
    '''

    def get_hourly_threshold(self, bundleType):
        if bundleType in self.hourlyLimits:
            return int(self.hourlyLimits[bundleType])
        else:
            return None

    '''
    returns {
        Workspaces: [obj...],
        NextToken: str
    }
    '''

    def get_workspaces_page(self, directoryID, nextToken):
        for i in range(0, self.maxRetries):
            try:
                if nextToken == 'None':
                    result = self.client.describe_workspaces(
                        DirectoryId=directoryID)
                else:
                    result = self.client.describe_workspaces(
                        DirectoryId=directoryID, NextToken=nextToken)

                return result
            except botocore.exceptions.ClientError as e:
                log.error(e)
                if i >= self.maxRetries - 1:
                    log.error('ExceededMaxRetries')
                else:
                    time.sleep(i / 10)

    '''
    returns bool
    '''

    def check_for_skip_tag(self, workspaceID):
        tags = self.get_tags(workspaceID)

        for tagPair in tags:
            if tagPair['Key'] == 'Skip_Convert':
                return True

        return False

    '''
    returns [
        {
            'Key': 'str',
            'Value': 'str'
        }, ...
    ]
    '''

    def get_tags(self, workspaceID):
        for i in range(0, self.maxRetries):
            try:
                workspaceTags = self.client.describe_tags(
                    ResourceId=workspaceID)
                log.debug(workspaceTags)

                return workspaceTags['TagList']

            except botocore.exceptions.ClientError as e:
                log.error(e)
                if i >= self.maxRetries - 1:
                    log.error('ExceededMaxRetries')
                else:
                    time.sleep(i / 10)

    '''
    returns str
    '''

    def modify_workspace_properties(self, workspaceID, newRunningMode,
                                    isDryRun):
        for i in range(0, self.maxRetries):
            log.debug('modifyWorkspaceProperties')
            try:
                if isDryRun == False:
                    wsModWS = self.client.modify_workspace_properties(
                        WorkspaceId=workspaceID,
                        WorkspaceProperties={'RunningMode': newRunningMode})
                else:
                    log.info(
                        'Skipping modifyWorkspaceProperties for Workspace %s due to dry run',
                        workspaceID)

                if newRunningMode == 'ALWAYS_ON':
                    result = '-M-'
                elif newRunningMode == 'AUTO_STOP':
                    result = '-H-'

                return result

            except botocore.exeptions.ClientError as e:
                if i >= self.maxRetries - 1:
                    result = '-E-'
                else:
                    time.sleep(i / 10)
        return result

    '''
    returns {
        'resultCode': str,
        'newMode': str
    }
    '''

    def compare_usage_metrics(self, workspaceID, billableTime, hourlyThreshold,
                              workspaceRunningMode):

        if hourlyThreshold == None:
            return {'resultCode': '-S-', 'newMode': workspaceRunningMode}

        # If the Workspace is in Auto Stop (hourly)
        if workspaceRunningMode == 'AUTO_STOP':
            log.debug('workspaceRunningMode {} == AUTO_STOP'.format(
                workspaceRunningMode))

            # If billable time is over the threshold for this bundle type
            if billableTime > hourlyThreshold:
                log.debug('billableTime {} > hourlyThreshold {}'.format(
                    billableTime, hourlyThreshold))

                # Change the workspace to ALWAYS_ON
                resultCode = self.modify_workspace_properties(
                    workspaceID, 'ALWAYS_ON', self.isDryRun)
                newMode = 'ALWAYS_ON'

            # Otherwise, report no change for the Workspace
            elif billableTime <= hourlyThreshold:
                log.debug('billableTime {} <= hourlyThreshold {}'.format(
                    billableTime, hourlyThreshold))
                resultCode = '-N-'
                newMode = 'AUTO_STOP'

        # Or if the Workspace is Always On (monthly)
        elif workspaceRunningMode == 'ALWAYS_ON':
            log.debug('workspaceRunningMode {} == ALWAYS_ON'.format(
                workspaceRunningMode))

            # Only perform metrics gathering for ALWAYS_ON Workspaces at the end of the month.
            if self.testEndOfMonth == True:
                log.debug('testEndOfMonth {} == True'.format(
                    self.testEndOfMonth))

                # If billable time is under the threshold for this bundle type
                if billableTime < hourlyThreshold:
                    log.debug('billableTime {} < hourlyThreshold {}'.format(
                        billableTime, hourlyThreshold))

                    # Change the workspace to AUTO_STOP
                    resultCode = self.modify_workspace_properties(
                        workspaceID, 'AUTO_STOP', self.isDryRun)
                    newMode = 'AUTO_STOP'

                # Otherwise, report no change for the Workspace
                elif billableTime >= hourlyThreshold:
                    log.debug('billableTime {} >= hourlyThreshold {}'.format(
                        billableTime, hourlyThreshold))
                    resultCode = '-N-'
                    newMode = 'ALWAYS_ON'

            elif self.testEndOfMonth == False:
                log.debug('testEndOfMonth {} == False'.format(
                    self.testEndOfMonth))
                resultCode = '-N-'
                newMode = 'ALWAYS_ON'

        # Otherwise, we don't know what it is so skip.
        else:
            log.warning(
                'workspaceRunningMode {} is unrecognized for workspace {}'.
                format(workspaceRunningMode, workspaceID))
            resultCode = '-S-'
            newMode = workspaceRunningMode

        return {'resultCode': resultCode, 'newMode': newMode}
class WorkspacesHelper(object):
    def __init__(self, settings):
        self.settings = settings
        self.maxRetries = 20
        self.region = settings['region']
        self.hourlyLimits = settings['hourlyLimits']
        self.testEndOfMonth = settings['testEndOfMonth']
        self.isDryRun = settings['isDryRun']
        self.client = boto3.client('workspaces',
                                   region_name=self.region,
                                   config=botoConfig)
        self.metricsHelper = MetricsHelper(self.region)

        return

    '''
    returns str
    '''

    def append_entry(self, oldCsv, result):
        s = ','
        csv = oldCsv + s.join(
            (result['workspaceID'], result['directoryID'], result['userName'],
             result['computerName'], str(result['billableTime']),
             result['lastConnectionTime'], str(result['hourlyThreshold']),
             result['optimizationResult'], result['bundleType'],
             result['initialMode'], result['newMode'] + '\n'))

        return csv

    '''
    returns str
    '''

    def expand_csv(self, rawCSV):
        csv = rawCSV.replace(',-M-', ',ToMonthly').replace(
            ',-H-',
            ',ToHourly').replace(',-E-', ',Exceeded MaxRetries').replace(
                ',-N-', ',No Change').replace(',-S-', ',Skipped')
        return csv

    '''
    returns {
        workspaceID: str,
        directoryID: str,
        userName: str
        computerName: str
        billableTime: int,
        lastConnectionTime: str,
        hourlyThreshold: int,
        optimizationResult: str,
        initialMode: str,
        newMode: str,
        bundleType: str
    }
    '''

    def process_workspace(self, workspace):
        workspaceID = workspace['WorkspaceId']
        log.debug('workspaceID: %s', workspaceID)

        workspaceRunningMode = workspace['WorkspaceProperties']['RunningMode']
        log.debug('workspaceRunningMode: %s', workspaceRunningMode)

        workspaceBundleType = workspace['WorkspaceProperties'][
            'ComputeTypeName']
        log.debug('workspaceBundleType: %s', workspaceBundleType)

        billableTime = self.metricsHelper.get_billable_time(
            workspaceID, self.settings['startTime'], self.settings['endTime'])

        lastKnownUserConnectionTimestamp = self.client.describe_workspaces_connection_status(
            WorkspaceIds=[workspaceID])['WorkspacesConnectionStatus'][0].get(
                'LastKnownUserConnectionTimestamp')
        if lastKnownUserConnectionTimestamp is None:
            lastConnectionTime = 'Unavailable'
        else:
            lastConnectionTime = str(lastKnownUserConnectionTimestamp)

        if self.check_for_skip_tag(workspaceID) == True:
            log.info('Skipping WorkSpace %s due to Skip_Convert tag',
                     workspaceID)
            optimizationResult = {
                'resultCode': '-S-',
                'newMode': workspaceRunningMode
            }
            hourlyThreshold = "n/a"
        else:
            hourlyThreshold = self.get_hourly_threshold(workspaceBundleType)

            optimizationResult = self.compare_usage_metrics(
                workspaceID, billableTime, hourlyThreshold,
                workspaceRunningMode)

        return {
            'workspaceID': workspaceID,
            'directoryID': workspace['DirectoryId'],
            'userName': workspace['UserName'],
            'computerName': workspace['ComputerName'],
            'billableTime': billableTime,
            'lastConnectionTime': lastConnectionTime,
            'hourlyThreshold': hourlyThreshold,
            'optimizationResult': optimizationResult['resultCode'],
            'newMode': optimizationResult['newMode'],
            'bundleType': workspaceBundleType,
            'initialMode': workspaceRunningMode
        }

    '''
    returns str
    '''
    '''
    returns int
    '''

    def get_hourly_threshold(self, bundleType):
        if bundleType in self.hourlyLimits:
            return int(self.hourlyLimits[bundleType])
        else:
            return None

    '''
    returns {
        Workspaces: [obj...],
        NextToken: str
    }
    '''

    def get_workspaces_page(self, directoryID, nextToken):
        try:
            if nextToken == 'None':
                result = self.client.describe_workspaces(
                    DirectoryId=directoryID)
            else:
                result = self.client.describe_workspaces(
                    DirectoryId=directoryID, NextToken=nextToken)

            return result
        except botocore.exceptions.ClientError as e:
            log.error(e)

    '''
    returns bool
    '''

    def check_for_skip_tag(self, workspaceID):
        tags = self.get_tags(workspaceID)

        # Added for case insensitive matching.  Works with standard alphanumeric tags

        for tagPair in tags:
            if tagPair['Key'].lower() == 'Skip_Convert'.lower():
                return True

        return False

    '''
    returns [
        {
            'Key': 'str',
            'Value': 'str'
        }, ...
    ]
    '''

    def get_tags(self, workspaceID):
        try:
            workspaceTags = self.client.describe_tags(ResourceId=workspaceID)
            log.debug(workspaceTags)

            return workspaceTags['TagList']

        except botocore.exceptions.ClientError as e:
            log.error(e)

    '''
    returns str
    '''

    def modify_workspace_properties(self, workspaceID, newRunningMode,
                                    isDryRun):
        log.debug('modifyWorkspaceProperties')
        try:
            if isDryRun == False:
                wsModWS = self.client.modify_workspace_properties(
                    WorkspaceId=workspaceID,
                    WorkspaceProperties={'RunningMode': newRunningMode})
            else:
                log.info(
                    'Skipping modifyWorkspaceProperties for Workspace %s due to dry run',
                    workspaceID)

            if newRunningMode == 'ALWAYS_ON':
                result = '-M-'
            elif newRunningMode == 'AUTO_STOP':
                result = '-H-'

            return result

        except botocore.exceptions.ClientError as e:
            log.error('Exceeded retries for %s due to error: %s', workspaceID,
                      e)

        return result

    '''
    returns {
        'resultCode': str,
        'newMode': str
    }
    '''

    def compare_usage_metrics(self, workspaceID, billableTime, hourlyThreshold,
                              workspaceRunningMode):

        if hourlyThreshold == None:
            return {'resultCode': '-S-', 'newMode': workspaceRunningMode}

        # If the Workspace is in Auto Stop (hourly)
        if workspaceRunningMode == 'AUTO_STOP':
            log.debug('workspaceRunningMode {} == AUTO_STOP'.format(
                workspaceRunningMode))

            # If billable time is over the threshold for this bundle type
            if billableTime > hourlyThreshold:
                log.debug('billableTime {} > hourlyThreshold {}'.format(
                    billableTime, hourlyThreshold))

                # Change the workspace to ALWAYS_ON
                resultCode = self.modify_workspace_properties(
                    workspaceID, 'ALWAYS_ON', self.isDryRun)
                newMode = 'ALWAYS_ON'

            # Otherwise, report no change for the Workspace
            elif billableTime <= hourlyThreshold:
                log.debug('billableTime {} <= hourlyThreshold {}'.format(
                    billableTime, hourlyThreshold))
                resultCode = '-N-'
                newMode = 'AUTO_STOP'

        # Or if the Workspace is Always On (monthly)
        elif workspaceRunningMode == 'ALWAYS_ON':
            log.debug('workspaceRunningMode {} == ALWAYS_ON'.format(
                workspaceRunningMode))

            # Only perform metrics gathering for ALWAYS_ON Workspaces at the end of the month.
            if self.testEndOfMonth == True:
                log.debug('testEndOfMonth {} == True'.format(
                    self.testEndOfMonth))

                # If billable time is under the threshold for this bundle type
                if billableTime <= hourlyThreshold:
                    log.debug('billableTime {} < hourlyThreshold {}'.format(
                        billableTime, hourlyThreshold))

                    # Change the workspace to AUTO_STOP
                    resultCode = self.modify_workspace_properties(
                        workspaceID, 'AUTO_STOP', self.isDryRun)
                    newMode = 'AUTO_STOP'

                # Otherwise, report no change for the Workspace
                elif billableTime > hourlyThreshold:
                    log.debug('billableTime {} >= hourlyThreshold {}'.format(
                        billableTime, hourlyThreshold))
                    resultCode = '-N-'
                    newMode = 'ALWAYS_ON'

            elif self.testEndOfMonth == False:
                log.debug('testEndOfMonth {} == False'.format(
                    self.testEndOfMonth))
                resultCode = '-N-'
                newMode = 'ALWAYS_ON'

        # Otherwise, we don't know what it is so skip.
        else:
            log.warning(
                'workspaceRunningMode {} is unrecognized for workspace {}'.
                format(workspaceRunningMode, workspaceID))
            resultCode = '-S-'
            newMode = workspaceRunningMode

        return {'resultCode': resultCode, 'newMode': newMode}