普通文本  |  273行  |  10.53 KB

#
# Copyright (C) 2017 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import datetime
import logging
import os
import shutil
import tempfile

from vts.utils.python.common import cmd_utils
from vts.utils.python.gcs import gcs_api_utils


def NotNoneStr(item):
    '''Convert a variable to string only if it is not None'''
    return str(item) if item is not None else None


class ReportFileUtil(object):
    '''Utility class for report file saving.

    Contains methods to save report files or read incremental parts of
    report files to a destination folder and get URLs.
    Used by profiling util, systrace util, and host log reporting.

    Attributes:
        _flatten_source_dir: bool, whether to flatten source directory
                             structure in destination directory. Current
                             implementation assumes no duplicated fine names
        _use_destination_date_dir: bool, whether to create date directory
                                   in destination directory
        _source_dir: string, source directory that contains report files
        _destination_dir: string, the GCS destination bucket name.
        _url_prefix: string, a prefix added to relative destination file paths.
                     If set to None, will use parent directory path.
        _use_gcs: bool, whether or not this ReportFileUtil is using GCS.
        _gcs_api_utils: GcsApiUtils object used by the ReportFileUtil object.
        _gcs_available: bool, whether or not the GCS agent is available.
    '''

    def __init__(self,
                 flatten_source_dir=False,
                 use_destination_date_dir=False,
                 source_dir=None,
                 destination_dir=None,
                 url_prefix=None,
                 gcs_key_path=None):
        """Initializes the ReportFileUtils object.

        Args:
            flatten_source_dir: bool, whether or not flatten the directory structure.
            use_destination_date_dir: bool, whether or not use date as part of name,
            source_dir: string, path to the source directory.
            destination_dir: string, path to the destination directory.
            url_prefix: string, prefix of the url used to upload the link to dashboard.
            gcs_key_path: string, path to the GCS key file.
        """
        source_dir = NotNoneStr(source_dir)
        destination_dir = NotNoneStr(destination_dir)
        url_prefix = NotNoneStr(url_prefix)

        self._flatten_source_dir = flatten_source_dir
        self._use_destination_date_dir = use_destination_date_dir
        self._source_dir = source_dir
        self._destination_dir = destination_dir
        self._url_prefix = url_prefix
        self._use_gcs = False

        if gcs_key_path is not None:
            self._use_gcs = True
            self._gcs_api_utils = gcs_api_utils.GcsApiUtils(
                gcs_key_path, destination_dir)
            self._gcs_available = self._gcs_api_utils.Enabled

    def _ConvertReportPath(self,
                           src_path,
                           root_dir=None,
                           new_file_name=None,
                           file_name_prefix=None):
        '''Convert report source file path to destination path and url.

        Args:
            src_path: string, source report file path.
            new_file_name: string, new file name to use on destination.
            file_name_prefix: string, prefix added to destination file name.
                              if new_file_name is set, prefix will be added
                              to new_file_name as well.

        Returns:
            tuple(string, string), containing destination path and url
        '''
        root_dir = NotNoneStr(root_dir)
        new_file_name = NotNoneStr(new_file_name)
        file_name_prefix = NotNoneStr(file_name_prefix)

        dir_path = os.path.dirname(src_path)

        relative_path = os.path.basename(src_path)
        if new_file_name:
            relative_path = new_file_name
        if file_name_prefix:
            relative_path = file_name_prefix + relative_path
        if not self._flatten_source_dir and root_dir:
            relative_path = os.path.join(
                os.path.relpath(dir_path, root_dir), relative_path)
        if self._use_destination_date_dir:
            now = datetime.datetime.now()
            date = now.strftime('%Y-%m-%d')
            relative_path = os.path.join(date, relative_path)

        if self._use_gcs:
            dest_path = relative_path
        else:
            dest_path = os.path.join(self._destination_dir, relative_path)

        url = dest_path
        if self._url_prefix is not None:
            url = self._url_prefix + relative_path
        return dest_path, url

    def _PushReportFile(self, src_path, dest_path):
        '''Push a report file to destination.

        Args:
            src_path: string, source path of report file
            dest_path: string, destination path of report file
        '''
        logging.info('Uploading log %s to %s.', src_path, dest_path)

        src_path = NotNoneStr(src_path)
        dest_path = NotNoneStr(dest_path)

        parent_dir = os.path.dirname(dest_path)
        if not os.path.exists(parent_dir):
            try:
                os.makedirs(parent_dir)
            except OSError as e:
                logging.exception(e)
        shutil.copy(src_path, dest_path)

    def _PushReportFileGcs(self, src_path, dest_path):
        """Upload args src file to the bucket in Google Cloud Storage.

        Args:
            src_path: string, source path of report file
            dest_path: string, destination path of report file
        """
        if not self._gcs_available:
            logging.error('Logs not being uploaded.')
            return

        logging.info('Uploading log %s to %s.', src_path, dest_path)

        src_path = NotNoneStr(src_path)
        dest_path = NotNoneStr(dest_path)

        # Copy snapshot to temp as GCS will not handle dynamic files.
        temp_dir = tempfile.mkdtemp()
        shutil.copy(src_path, temp_dir)
        src_path = os.path.join(temp_dir, os.path.basename(src_path))
        logging.debug('Snapshot of logs: %s', src_path)

        try:
            self._gcs_api_utils.UploadFile(src_path, dest_path)
        except IOError as e:
            logging.exception(e)
        finally:
            logging.debug('removing temporary directory')
            try:
                shutil.rmtree(temp_dir)
            except OSError as e:
                logging.exception(e)

    def SaveReport(self, src_path, new_file_name=None, file_name_prefix=None):
        '''Save report file to destination.

        Args:
            src_path: string, source report file path.
            new_file_name: string, new file name to use on destination.
            file_name_prefix: string, prefix added to destination file name.
                              if new_file_name is set, prefix will be added
                              to new_file_name as well.

        Returns:
            string, destination URL of saved report file.
            If url_prefix is set to None, will return destination path of
            report files. If error happens during read or write operation,
            this method will return None.
        '''
        src_path = NotNoneStr(src_path)
        new_file_name = NotNoneStr(new_file_name)
        file_name_prefix = NotNoneStr(file_name_prefix)

        try:
            dest_path, url = self._ConvertReportPath(
                src_path,
                new_file_name=new_file_name,
                file_name_prefix=file_name_prefix)
            if self._use_gcs:
                self._PushReportFileGcs(src_path, dest_path)
            else:
                self._PushReportFile(src_path, dest_path)

            return url
        except IOError as e:
            logging.exception(e)

    def SaveReportsFromDirectory(self,
                                 source_dir=None,
                                 file_name_prefix=None,
                                 file_path_filters=None,
                                 dryrun=False):
        '''Save report files from source directory to destination.

        Args:
            source_dir: string, source directory where report files are stored.
                        if None, class attribute source_dir will be used.
                        Default is None.
            file_name_prefix: string, prefix added to destination file name
            file_path_filter: function, a functions that return True (pass) or
                              False (reject) given original file path.
            dryrun: bool, whether to perform a dry run to get urls only.

        Returns:
            A list of string, containing destination URLs of saved report files.
            If url_prefix is set to None, will return destination path of
            report files. If error happens during read or write operation,
            this method will return None.
        '''
        source_dir = NotNoneStr(source_dir)
        file_name_prefix = NotNoneStr(file_name_prefix)
        if not source_dir:
            source_dir = self._source_dir

        try:
            urls = []

            for (dirpath, dirnames, filenames) in os.walk(
                    source_dir, followlinks=False):
                for filename in filenames:
                    src_path = os.path.join(dirpath, filename)
                    dest_path, url = self._ConvertReportPath(
                        src_path,
                        root_dir=source_dir,
                        file_name_prefix=file_name_prefix)

                    if file_path_filters and not file_path_filters(src_path):
                        continue

                    #TODO(yuexima): handle duplicated destination file names
                    if not dryrun:
                        if self._use_gcs:
                            self._PushReportFileGcs(src_path, dest_path)
                        else:
                            self._PushReportFile(src_path, dest_path)
                    urls.append(url)

            return urls
        except IOError as e:
            logging.exception(e)