Example #1
0
    def test_ca_cert_files_checks_are_skipped_under_dev_and_msi_install(self):
        # Skip those checks under dev and msi install because those final generated certs files
        # are not present under dev install

        from scalyr_agent.agent_main import ScalyrAgent
        from scalyr_agent.platform_controller import PlatformController

        # 1. Dev install (boths checks should be skipped)
        with mock.patch("scalyr_agent.__scalyr__.INSTALL_TYPE",
                        __scalyr__.DEV_INSTALL):

            # ca_cert_path file doesn't exist
            config = mock.Mock()
            config.scalyr_server = "foo.bar.com"
            config.compression_level = 1

            config.verify_server_certificate = True
            config.ca_cert_path = "/tmp/doesnt.exist"
            config.use_new_ingestion = False

            agent = ScalyrAgent(PlatformController())
            agent._ScalyrAgent__config = config

            self.assertTrue(create_client(config=config))

            # intermediate_certs_path file doesn't exist
            config.verify_server_certificate = True
            config.ca_cert_path = __file__
            config.intermediate_certs_path = "/tmp/doesnt.exist"

            self.assertTrue(create_client(config=config))

        # 2. MSI install (only intermediate_certs_path check should be skipped)
        with mock.patch("scalyr_agent.__scalyr__.INSTALL_TYPE",
                        __scalyr__.MSI_INSTALL):

            config = mock.Mock()
            config.scalyr_server = "foo.bar.com"
            config.compression_level = 1

            config.verify_server_certificate = True
            config.ca_cert_path = "/tmp/doesnt.exist"
            config.use_new_ingestion = False

            agent = ScalyrAgent(PlatformController())
            agent._ScalyrAgent__config = config

            expected_msg = (
                r'Invalid path "/tmp/doesnt.exist" specified for the "ca_cert_path" config '
                "option: file does not exist")

            self.assertRaisesRegexp(ValueError, expected_msg,
                                    functools.partial(create_client, config))

            # intermediate_certs_path file doesn't exist
            config.verify_server_certificate = True
            config.ca_cert_path = __file__
            config.intermediate_certs_path = "/tmp/doesnt.exist"

            self.assertTrue(create_client(config=config))
Example #2
0
    def __init__(self, stdin='/dev/null', stdout='/dev/null', stderr='/dev/null'):
        """Initializes the POSIX platform instance.
        """
        self.__stdin = stdin
        self.__stdout = stdout
        self.__stderr = stderr
        # The file name storing the pid.
        self.__pidfile = None
        # The pidfile specified on the commandline using the flags, if any.  This gives the user a chance to
        # specify the pidfile if the configuration cannot be read.
        self.__pidfile_from_options = None

        # The method to invoke when termination is requested.
        self.__termination_handler = None
        # The method to invoke when status is requested by another process.
        self.__status_handler = None
        self.__no_change_user = False
        # A list of log lines collected for debugging the initialization sequence.  The entries
        # are tuples of the line and a boolean indicating whether or not it is a debug entry.
        self.__init_log_lines = []
        self.__is_initializing = True

        # Whether or not to use the commandline in the pidfile to help protect against pid re-use.
        self.__verify_command_in_pidfile = False

        PlatformController.__init__(self)
Example #3
0
    def __init__(self,
                 stdin='/dev/null',
                 stdout='/dev/null',
                 stderr='/dev/null'):
        """Initializes the POSIX platform instance.
        """
        self.__stdin = stdin
        self.__stdout = stdout
        self.__stderr = stderr
        # The file name storing the pid.
        self.__pidfile = None
        # The pidfile specified on the commandline using the flags, if any.  This gives the user a chance to
        # specify the pidfile if the configuration cannot be read.
        self.__pidfile_from_options = None

        # The method to invoke when termination is requested.
        self.__termination_handler = None
        # The method to invoke when status is requested by another process.
        self.__status_handler = None
        self.__no_change_user = False
        # A list of log lines collected for debugging the initialization sequence.  The entries
        # are tuples of the line and a boolean indicating whether or not it is a debug entry.
        self.__init_log_lines = []
        self.__is_initializing = True

        # Whether or not to use the commandline in the pidfile to help protect against pid re-use.
        self.__verify_command_in_pidfile = False

        PlatformController.__init__(self)
Example #4
0
    def test_ca_cert_files_checks_are_skipped_under_dev_and_msi_install(self):
        # Skip those checks under dev and msi install because those final generated certs files
        # are not present under dev install
        import scalyr_agent.agent_main

        from scalyr_agent.agent_main import ScalyrAgent
        from scalyr_agent.platform_controller import PlatformController

        # 1. Dev install (boths checks should be skipped)
        scalyr_agent.agent_main.INSTALL_TYPE = DEV_INSTALL

        # ca_cert_path file doesn't exist
        config = mock.Mock()
        config.scalyr_server = "foo.bar.com"

        config.verify_server_certificate = True
        config.ca_cert_path = "/tmp/doesnt.exist"

        agent = ScalyrAgent(PlatformController())
        agent._ScalyrAgent__config = config

        self.assertTrue(agent._ScalyrAgent__create_client())

        # intermediate_certs_path file doesn't exist
        config.verify_server_certificate = True
        config.ca_cert_path = __file__
        config.intermediate_certs_path = "/tmp/doesnt.exist"

        self.assertTrue(agent._ScalyrAgent__create_client())

        # 2. MSI install (only intermediate_certs_path check should be skipped)
        scalyr_agent.agent_main.INSTALL_TYPE = MSI_INSTALL

        config = mock.Mock()
        config.scalyr_server = "foo.bar.com"

        config.verify_server_certificate = True
        config.ca_cert_path = "/tmp/doesnt.exist"

        agent = ScalyrAgent(PlatformController())
        agent._ScalyrAgent__config = config

        expected_msg = (
            r'Invalid path "/tmp/doesnt.exist" specified for the "ca_cert_path" config '
            "option: file does not exist"
        )

        self.assertRaisesRegexp(
            ValueError, expected_msg, agent._ScalyrAgent__create_client
        )

        # intermediate_certs_path file doesn't exist
        config.verify_server_certificate = True
        config.ca_cert_path = __file__
        config.intermediate_certs_path = "/tmp/doesnt.exist"

        self.assertTrue(agent._ScalyrAgent__create_client())
Example #5
0
    def test_create_client_ca_file_and_intermediate_certs_file_doesnt_exist(self):
        from scalyr_agent.agent_main import ScalyrAgent
        from scalyr_agent.platform_controller import PlatformController

        # 1. file doesn't exist but cert verification is disabled
        config = mock.Mock()
        config.scalyr_server = "foo.bar.com"

        config.verify_server_certificate = False
        config.ca_cert_path = "/tmp/doesnt.exist"

        agent = ScalyrAgent(PlatformController())
        agent._ScalyrAgent__config = config

        self.assertTrue(agent._ScalyrAgent__create_client())

        # ca_cert_path file doesn't exist
        config.verify_server_certificate = True
        config.ca_cert_path = "/tmp/doesnt.exist"

        agent = ScalyrAgent(PlatformController())
        agent._ScalyrAgent__config = config

        expected_msg = (
            r'Invalid path "/tmp/doesnt.exist" specified for the "ca_cert_path" config '
            "option: file does not exist"
        )

        self.assertRaisesRegexp(
            ValueError, expected_msg, agent._ScalyrAgent__create_client
        )

        # intermediate_certs_path file doesn't exist
        config.verify_server_certificate = True
        config.ca_cert_path = __file__
        config.intermediate_certs_path = "/tmp/doesnt.exist"

        agent = ScalyrAgent(PlatformController())
        agent._ScalyrAgent__config = config

        expected_msg = (
            r'Invalid path "/tmp/doesnt.exist" specified for the '
            '"intermediate_certs_path" config option: file does not exist'
        )

        self.assertRaisesRegexp(
            ValueError, expected_msg, agent._ScalyrAgent__create_client
        )
    def __init__(self):
        """Initializes the Windows platform instance."""
        # The method to invoke when termination is requested.
        self.__termination_handler = None
        # The method to invoke when status is requested by another process.
        self.__status_handler = None
        # The file path to the configuration.  We need to stash this so it is available when start is invoked.
        self.__config_file_path = None

        # The local domain Administrators name.
        self.__local_administrators = "%s\\Administrators" % win32api.GetComputerName(
        )

        self.__no_change_user = False

        # Controls whether or not we warn the user via stdout that we are about to escalate to Administrator privileges.
        self.__no_escalation_warning = False

        PlatformController.__init__(self)
Example #7
0
    def __init__(self,
                 stdin='/dev/null',
                 stdout='/dev/null',
                 stderr='/dev/null'):
        """Initializes the POSIX platform instance.
        """
        self.__stdin = stdin
        self.__stdout = stdout
        self.__stderr = stderr
        # The file name storing the pid.
        self.__pidfile = None
        # The pidfile specified on the commandline using the flags, if any.  This gives the user a chance to
        # specify the pidfile if the configuration cannot be read.
        self.__pidfile_from_options = None

        # The method to invoke when termination is requested.
        self.__termination_handler = None
        # The method to invoke when status is requested by another process.
        self.__status_handler = None
        self.__no_change_user = False
        PlatformController.__init__(self)
Example #8
0
    def config_object(self):  # type: () -> Configuration
        """
        Get config object from the config file.
        """
        platform = PlatformController.new_platform()
        platform._install_type = self._installation_type
        default_types = platform.default_paths

        config = Configuration(six.text_type(self._agent_config_path),
                               default_types, None)
        config.parse()
        return config
    def __init__(self, install_type, stdin='/dev/null', stdout='/dev/null', stderr='/dev/null'):
        """Initializes the POSIX platform instance.

        @param install_type: One of the constants describing the install type, such as PACKAGE_INSTALL, TARBALL_INSTALL,
            or DEV_INSTALL.

        @type install_type: int
        """
        self.__stdin = stdin
        self.__stdout = stdout
        self.__stderr = stderr
        # The file name storing the pid.
        self.__pidfile = None
        # The pidfile specified on the commandline using the flags, if any.  This gives the user a chance to
        # specify the pidfile if the configuration cannot be read.
        self.__pidfile_from_options = None

        # The method to invoke when termination is requested.
        self.__termination_handler = None
        # The method to invoke when status is requested by another process.
        self.__status_handler = None
        PlatformController.__init__(self, install_type)
Example #10
0
    def setUp(self):
        super(AgentMainStatusHandlerTestCase, self).setUp()

        self.data_path = tempfile.mkdtemp(suffix="agent-data-path")
        self.status_format_file = os.path.join(self.data_path, STATUS_FORMAT_FILE)

        default_paths = mock.Mock()
        default_paths.agent_data_path = self.data_path
        default_paths.agent_log_path = "agent.log"

        config_file = os.path.join(BASE_DIR, "fixtures/configs/agent1.json")
        config = Configuration(config_file, default_paths, None)
        config.parse()

        self.agent = ScalyrAgent(PlatformController())
        self.agent._ScalyrAgent__config = config
Example #11
0
    def _get_default_paths(self):  # type: () -> Dict[six.text_type, Path]
        """
        Get default path for essential directories and files of the agent.  Those paths are fetched from 'PlatformController'.
        """
        # create new 'PlatformController' instance. Since this code is executed on the same machine with agent,
        # platform setting and paths should match.
        platform = PlatformController.new_platform()
        # change install type of the controller to needed one.
        platform._install_type = self._installation_type

        default_types = platform.default_paths

        result = dict()
        for k, v in default_types.__dict__.items():
            result[k] = Path(v)

        return result
Example #12
0
def run_standalone_monitor(
    monitor_module,
    monitor_python_path,
    monitor_config,
    monitor_sample_interval,
    monitor_debug_level,
    global_config_path,
):
    """Runs a single plugin monitor instance.

    @param monitor_module: The name of the python module implementing the monitor.
    @param monitor_python_path: The python path to search to find the module.
    @param monitor_config: The monitor configuration object.
    @param monitor_sample_interval: The default to use for the sample interval.
    @param monitor_debug_level: The debug level to use for logging.
    @param global_config_path:  The path to the agent.json global configuration file to use, or None if none was
        supplied.
    """
    scalyr_logging.set_log_destination(use_stdout=True)
    scalyr_logging.set_log_level(monitor_debug_level)

    log.log(scalyr_logging.DEBUG_LEVEL_1, "Attempting to run module %s",
            monitor_module)

    try:
        # Needs to be json_lib.parse because it is parsing configuration
        parsed_config = scalyr_util.json_scalyr_config_decode(monitor_config)
        log.log(scalyr_logging.DEBUG_LEVEL_1,
                "Parsed configuration successfully")
    except JsonParseException as e:
        print(
            "Failed to parse the monitor configuration as valid JSON: %s",
            six.text_type(e),
            file=sys.stderr,
        )
        return 1

    parsed_config["module"] = monitor_module
    if "id" not in parsed_config:
        parsed_config["id"] = ""

    # noinspection PyUnusedLocal
    def handle_shutdown_signal(signum, frame):
        print("Signal received, stopping monitor...", file=sys.stdout)
        monitor.stop()

    for sig in (signal.SIGTERM, signal.SIGINT):
        signal.signal(sig, handle_shutdown_signal)

    try:
        if global_config_path is not None:
            controller = PlatformController.new_platform()
            paths = controller.default_paths
            global_config = Configuration(global_config_path, paths, log)
            global_config.parse()
        else:
            global_config = None
        monitor = MonitorsManager.build_monitor(
            parsed_config,
            monitor_python_path,
            float(monitor_sample_interval),
            global_config,
        )
        log.log(scalyr_logging.DEBUG_LEVEL_1, "Constructed monitor")
        monitor.open_metric_log()
        log.log(scalyr_logging.DEBUG_LEVEL_1, "Starting monitor")
        monitor.start()

        while monitor.isAlive():
            time.sleep(0.1)
    except BadMonitorConfiguration as e:
        print("Invalid monitor configuration: %s" % six.text_type(e),
              file=sys.stderr)

    return 0
Example #13
0
def upgrade_windows_install(config,
                            release_track="stable",
                            preserve_msi=False,
                            use_ui=True):
    """Performs an upgrade for an existing Scalyr Agent 2 that was previously installed using a Windows MSI install
    file.

    This will contact the Scalyr servers to see what the most up-to-date version of the agent is and, if necessary,
    download an MSI file.

    @param config: The configuration for this agent.
    @param release_track:  The release track to use when checking which version is the latest.
    @param preserve_msi:  Whether or not to delete the MSI file once the upgrade is finished.  Note, this
        argument is essentially ignored for now and we always leave the file because we cannot delete it with
        the current way we exec the msiexec process.
    @param use_ui:  Whether or not the msiexec upgrade command should be run with the UI.

    @rtype config: Configuration
    @rtype release_track: str
    @rtype preserve_msi: bool
    @rtype use_ui: bool

    @return: The exit status code.
    """
    # The URL path of the agent to upgrade to.
    url_path = None

    try:
        platform_controller = PlatformController.new_platform()
        my_default_paths = platform_controller.default_paths

        # Ensure agent was installed via MSI
        if MSI_INSTALL != platform_controller.install_type:
            raise UpgradeFailure(
                'The current agent was not installed via MSI, so you may not use the upgrade windows '
                'command.')

        # Ensure that the user has not changed the defaults for the config, data, and log directory.
        if my_default_paths.config_file_path != config.file_path:
            raise UpgradeFailure(
                'The agent is not using the default configuration file so you may not use the '
                'upgrade windows command.')
        if my_default_paths.agent_data_path != config.agent_data_path:
            raise UpgradeFailure(
                'The agent is not using the default data directory so you may not use the upgrade '
                'windows command.')
        if my_default_paths.agent_log_path != config.agent_log_path:
            raise UpgradeFailure(
                'The agent is not using the default log directory so you may not use the upgrade '
                'windows command.')

        # Determine if a newer version is available
        client = ScalyrClientSession(config.scalyr_server,
                                     config.api_key,
                                     SCALYR_VERSION,
                                     quiet=True,
                                     ca_file=config.ca_cert_path)
        status, size, response = client.perform_agent_version_check(
            release_track)

        if status.lower() != 'success':
            raise UpgradeFailure(
                'Failed to contact the Scalyr servers to check for latest update.  Error code '
                'was "%s"' % status)

        # TODO:  We shouldn't have to reparse response on JSON, but for now that, that's what the client library
        # does.
        data_payload = json_lib.parse(response)['data']

        if not data_payload['update_required']:
            print 'The latest version is already installed.'
            return 0

        print 'Attempting to upgrade agent from version %s to version %s.' % (
            SCALYR_VERSION, data_payload['current_version'])
        url_path = data_payload['urls']['win32']

        file_portion = url_path[url_path.rfind('/') + 1:]
        download_location = os.path.join(tempfile.gettempdir(), file_portion)

        try:
            try:
                print 'Downloading agent from %s.' % url_path
                urllib.urlretrieve(url_path, download_location)

                if not os.path.isfile(download_location):
                    raise UpgradeFailure(
                        'Failed to download installation package')

                if use_ui:
                    print(
                        'Executing upgrade.  Please follow the instructions in the subsequent dialog boxes to '
                        'complete the upgrade process.')
                else:
                    print(
                        'Executing upgrade.  It will finish in the background.'
                    )

                # Because this file, config_main.py, is part of the currently installed Scalyr Agent package, we have
                # to finish our use of it before the upgrade can proceed.  So, we just fork off the msiexec process
                # in detached mode and terminate this program.  This means we cannot report any errors that happen
                # here, but I don't see a way around this for now.
                # noinspection PyUnresolvedReferences
                from win32process import DETACHED_PROCESS
                upgrade_command = [
                    'msiexec.exe', '/i', "{}".format(download_location)
                ]
                if not use_ui:
                    upgrade_command.append('/qn')
                subprocess.Popen(upgrade_command,
                                 shell=False,
                                 stdin=None,
                                 stdout=None,
                                 stderr=None,
                                 close_fds=True,
                                 creationflags=DETACHED_PROCESS)

                return 0
            except IOError, error:
                raise UpgradeFailure(
                    'Could not download the installer, returned error %s' %
                    str(error))

        finally:
            # TODO:  Actually delete the temporary file.  We cannot right now since our execution finishes
            # before the msiexec process runs, but maybe we can do something like have a small shell script
            # that runs the upgrader and then deletes the file.  Something to consider post-alpha release.
            if preserve_msi:
                print 'Downloaded installer file has been left at %s' % download_location

    except UpgradeFailure, error:
        print >> sys.stderr
        print >> sys.stderr, 'The upgrade failed due to the following reason: %s' % error.message
        if url_path is not None:
            print >> sys.stderr, 'You may try downloading and running the installer file yourself.'
            print >> sys.stderr, 'The installer can be downloaded from %s' % url_path
        print >> sys.stderr, 'Please e-mail [email protected] for help resolving this issue.'
        return 1
Example #14
0
def upgrade_tarball_install(config, new_tarball, preserve_old_install):
    """Performs an upgrade for an existing Scalyr Agent 2 that was previously installed using the tarball method.

    @param config: The configuration for this agent.
    @param new_tarball: The path to file containing the new tarball to install.
    @param preserve_old_install: If True, will move the old install directory to a new location rather than deleting
        it.

    @return: The exit status code.
    """
    # Create a temporary directory hold the new install as we untar it and copy files into it.
    tmp_install_dir = tempfile.mkdtemp()

    # Some variables that capture some important state that we may need to unwind if we execute
    # out the installation along the way.
    #
    # If not None, then the directory we are currently holding the old installation directory in.
    preserve_dir = None
    # True if the agent was running when the install started.
    was_running = False
    # True if the agent was successfully restarted.
    was_restarted = False

    try:
        try:
            platform_controller = PlatformController.new_platform()
            my_default_paths = platform_controller.default_paths

            # Ensure that this is a tarball install
            if platform_controller.install_type != TARBALL_INSTALL:
                raise UpgradeFailure(
                    'The current agent was not installed using a tarball, so you may not use the '
                    'upgrade tarball command.')

            # Ensure that the user has not changed the defaults for the config, data, and log directory.
            if my_default_paths.config_file_path != config.file_path:
                raise UpgradeFailure(
                    'The agent is not using the default configuration file so you may not use the '
                    'upgrade tarball command.')
            if my_default_paths.agent_data_path != config.agent_data_path:
                raise UpgradeFailure(
                    'The agent is not using the default data directory so you may not use the upgrade '
                    'tarball command.')
            if my_default_paths.agent_log_path != config.agent_log_path:
                raise UpgradeFailure(
                    'The agent is not using the default log directory so you may not use the upgrade '
                    'tarball command.')

            # We rely on the current installation being included in the PATH variable.
            if spawn.find_executable('scalyr-agent-2-config') is None:
                raise UpgradeFailure(
                    'Could not locate the scalyr-agent-2-config command from the current '
                    'installation. Please ensure that the agent\'s bin directory is in the system\'s '
                    'PATH variable.')

            if not os.path.isfile(new_tarball):
                raise UpgradeFailure('The tarball file %s does not exist.' %
                                     new_tarball)

            file_name = os.path.basename(new_tarball)
            if re.match('^scalyr-agent-2\..*\.tar\.gz$', file_name) is None:
                raise UpgradeFailure(
                    'The supplied tarball file name does not match the expected format.'
                )
            tarball_directory = file_name[0:-7]

            # We will be installing in the same directory where scalyr-agent-2 is currently installed.
            install_directory = os.path.dirname(get_install_root())

            if not os.path.isdir(
                    os.path.join(install_directory, 'scalyr-agent-2')):
                raise UpgradeFailure(
                    'Could not determine the install directory.  Either the main directory is no '
                    'longer called scalyr-agent-2, or the directory structure has changed.'
                )

            # Compute the full paths to the scalyr-agent-2 directories for both the new install and old install.
            tmp_new_install_location = os.path.join(tmp_install_dir,
                                                    tarball_directory)
            old_install_location = os.path.join(install_directory,
                                                'scalyr-agent-2')

            # Untar the new package into the temp location.
            tar = tarfile.open(new_tarball, 'r:gz')
            for member in tar.getmembers():
                tar.extract(member, path=tmp_install_dir)

            # Check to see if the agent is running.  If so, stop it.
            was_running = run_command(
                'scalyr-agent-2 stop',
                grep_for='Agent has stopped',
                command_name='scalyr-agent-2 stop')[0] == 0

            # Copy the config, data, and log directories.
            for dir_name in ['config', 'log', 'data']:
                copy_dir_to_new_agent(old_install_location,
                                      tmp_new_install_location, dir_name)

            # Allow the new agent code to perform any actions it deems necessary.  We do the special commandline
            # here where to pass in both directories to the --upgrade-tarball-command
            result = subprocess.call([
                os.path.join(tmp_new_install_location, 'bin',
                             'scalyr-agent-2-config'), '--upgrade-tarball',
                '%s%s%s' %
                (old_install_location, os.pathsep, tmp_new_install_location)
            ])
            if result != 0:
                raise UpgradeFailure(
                    'New package failed to finish the upgrade process.')

            # Move the old install directory to a temporary location, so we can undo the next move if we need to.
            preserve_dir = tempfile.mkdtemp()
            shutil.move(old_install_location, preserve_dir)

            # Move the new install into place.
            success = False
            try:
                shutil.move(tmp_new_install_location, old_install_location)
                success = True
            finally:
                if not success:
                    # Move the old install back in place just to be safe.
                    shutil.move(os.path.join(preserve_dir, 'scalyr-agent-2'),
                                old_install_location)
                if success and not preserve_old_install:
                    shutil.rmtree(preserve_dir)
                    preserve_dir = None

            print 'New agent installed.'

            # Start the agent if it was previously running.
            if was_running:
                if run_command('scalyr-agent-2 start',
                               exit_on_fail=False,
                               command_name='scalyr-agent-2 start')[0] == 0:
                    print 'Agent has successfully restarted.'
                    print '  You may execute the following command for status details:  scalyr-agent-2 status -v'
                    was_restarted = True
                else:
                    raise UpgradeFailure(
                        'Could not start the agent.  Execute the following command for more details: '
                        'scalyr-agent-2 start')
            else:
                print 'Execute the following command to start the agent:  scalyr-agent-2 start'

            return 0

        except UpgradeFailure, error:
            print >> sys.stderr
            print >> sys.stderr, 'The upgrade failed due to the following reason: %s' % error.message
            return 1

    finally:
        # Delete the temporary directory.
        shutil.rmtree(tmp_install_dir)

        # Warn if we should have restarted the agent but did not.
        if was_running and not was_restarted:
            print ''
            print(
                'WARNING, due to failure, the agent may no longer be running.  Restart it with: scalyr-agent-2 '
                'start')

        # If there is still a preserve_directory, there must be a reason for it, so tell the user where it is.
        if preserve_dir is not None:
            print ''
            print 'The previous agent installation was left in \'%s\'' % preserve_dir
            print 'You should be sure to delete this directory once you no longer need it.'
Example #15
0
def upgrade_windows_install(config, release_track="stable", preserve_msi=False, use_ui=True):
    """Performs an upgrade for an existing Scalyr Agent 2 that was previously installed using a Windows MSI install
    file.

    This will contact the Scalyr servers to see what the most up-to-date version of the agent is and, if necessary,
    download an MSI file.

    @param config: The configuration for this agent.
    @param release_track:  The release track to use when checking which version is the latest.
    @param preserve_msi:  Whether or not to delete the MSI file once the upgrade is finished.  Note, this
        argument is essentially ignored for now and we always leave the file because we cannot delete it with
        the current way we exec the msiexec process.
    @param use_ui:  Whether or not the msiexec upgrade command should be run with the UI.

    @rtype config: Configuration
    @rtype release_track: str
    @rtype preserve_msi: bool
    @rtype use_ui: bool

    @return: The exit status code.
    """
    # The URL path of the agent to upgrade to.
    url_path = None

    try:
        platform_controller = PlatformController.new_platform()
        my_default_paths = platform_controller.default_paths

        # Ensure agent was installed via MSI
        if MSI_INSTALL != platform_controller.install_type:
            raise UpgradeFailure('The current agent was not installed via MSI, so you may not use the upgrade windows '
                                 'command.')

        # Ensure that the user has not changed the defaults for the config, data, and log directory.
        if my_default_paths.config_file_path != config.file_path:
            raise UpgradeFailure('The agent is not using the default configuration file so you may not use the '
                                 'upgrade windows command.')
        if my_default_paths.agent_data_path != config.agent_data_path:
            raise UpgradeFailure('The agent is not using the default data directory so you may not use the upgrade '
                                 'windows command.')
        if my_default_paths.agent_log_path != config.agent_log_path:
            raise UpgradeFailure('The agent is not using the default log directory so you may not use the upgrade '
                                 'windows command.')

        # Determine if a newer version is available
        client = ScalyrClientSession(config.scalyr_server, config.api_key, SCALYR_VERSION, quiet=True,
                                     ca_file=config.ca_cert_path)
        status, size, response = client.perform_agent_version_check(release_track)

        if status.lower() != 'success':
            raise UpgradeFailure('Failed to contact the Scalyr servers to check for latest update.  Error code '
                                 'was "%s"' % status)

        # TODO:  We shouldn't have to reparse response on JSON, but for now that, that's what the client library
        # does.
        data_payload = json_lib.parse(response)['data']

        if not data_payload['update_required']:
            print 'The latest version is already installed.'
            return 0

        print 'Attempting to upgrade agent from version %s to version %s.' % (SCALYR_VERSION,
                                                                              data_payload['current_version'])
        url_path = data_payload['urls']['win32']

        file_portion = url_path[url_path.rfind('/')+1:]
        download_location = os.path.join(tempfile.gettempdir(), file_portion)

        try:
            try:
                print 'Downloading agent from %s.' % url_path
                urllib.urlretrieve(url_path, download_location)

                if not os.path.isfile(download_location):
                    raise UpgradeFailure('Failed to download installation package')

                if use_ui:
                    print ('Executing upgrade.  Please follow the instructions in the subsequent dialog boxes to '
                           'complete the upgrade process.')
                else:
                    print ('Executing upgrade.  It will finish in the background.')

                # Because this file, config_main.py, is part of the currently installed Scalyr Agent package, we have
                # to finish our use of it before the upgrade can proceed.  So, we just fork off the msiexec process
                # in detached mode and terminate this program.  This means we cannot report any errors that happen
                # here, but I don't see a way around this for now.
                # noinspection PyUnresolvedReferences
                from win32process import DETACHED_PROCESS
                upgrade_command = ['msiexec.exe', '/i', "{}".format(download_location)]
                if not use_ui:
                    upgrade_command.append('/qn')
                subprocess.Popen(upgrade_command,
                                 shell=False, stdin=None, stdout=None, stderr=None, close_fds=True,
                                 creationflags=DETACHED_PROCESS)

                return 0
            except IOError, error:
                raise UpgradeFailure('Could not download the installer, returned error %s' % str(error))

        finally:
            # TODO:  Actually delete the temporary file.  We cannot right now since our execution finishes
            # before the msiexec process runs, but maybe we can do something like have a small shell script
            # that runs the upgrader and then deletes the file.  Something to consider post-alpha release.
            if preserve_msi:
                print 'Downloaded installer file has been left at %s' % download_location

    except UpgradeFailure, error:
        print >>sys.stderr
        print >>sys.stderr, 'The upgrade failed due to the following reason: %s' % error.message
        if url_path is not None:
            print >>sys.stderr, 'You may try downloading and running the installer file yourself.'
            print >>sys.stderr, 'The installer can be downloaded from %s' % url_path
        print >>sys.stderr, 'Please e-mail [email protected] for help resolving this issue.'
        return 1
Example #16
0
    def test_skipped_bytes_warnings(self):
        from scalyr_agent.agent_main import ScalyrAgent
        from scalyr_agent.platform_controller import PlatformController

        with mock.patch("scalyr_agent.__scalyr__.INSTALL_TYPE",
                        __scalyr__.DEV_INSTALL):

            config = mock.Mock()
            config.scalyr_server = "foo.bar.com"
            config.server_attributes = {"serverHost": "test"}
            config.additional_file_paths = []
            config.compression_level = 1
            config.copying_manager_stats_log_interval = 60
            config.parsed_max_send_rate_enforcement = 12345

            config.verify_server_certificate = True
            config.ca_cert_path = "/tmp/doesnt.exist"

            platform_controller = PlatformController()
            platform_controller.get_usage_info = AgentMainTestCase.fake_get_useage_info
            agent = ScalyrAgent(platform_controller)
            agent._ScalyrAgent__config = config

            client = create_client(config)

            def get_worker_session_statuses_mock(*args, **kwargs):
                return [client]

            with mock.patch.object(agent,
                                   "_ScalyrAgent__copying_manager") as m:

                m.generate_status = mock.MagicMock(return_value=None)
                m.get_worker_session_statuses = get_worker_session_statuses_mock
                base_stats = agent_status.OverallStats()
                base_stats.total_bytes_skipped = 500
                test = agent._ScalyrAgent__calculate_overall_stats(
                    base_stats, copy_manager_warnings=True)
                self.assertIsNotNone(test)
                self.assertLogFileContainsLineRegex(".*" + re.escape(
                    "Warning, skipping copying log lines.  Only copied 0.0 MB/s log bytes when 0.0 MB/s were generated over the last 1.0 minutes. This may be due to max_send_rate_enforcement. Log upload has been delayed 0.0 seconds in the last 1.0 minutes  This may be desired (due to excessive bytes from a problematic log file).  Please contact [email protected] for additional help."
                ))

            config = mock.Mock()
            config.scalyr_server = "foo.bar.com"
            config.server_attributes = {"serverHost": "test"}
            config.additional_file_paths = []
            config.compression_level = 1
            config.copying_manager_stats_log_interval = 60
            config.parsed_max_send_rate_enforcement = None

            config.verify_server_certificate = True
            config.ca_cert_path = "/tmp/doesnt.exist"

            platform_controller = PlatformController()
            platform_controller.get_usage_info = AgentMainTestCase.fake_get_useage_info
            agent = ScalyrAgent(platform_controller)
            agent._ScalyrAgent__config = config

            client = create_client(config)

            with mock.patch.object(agent,
                                   "_ScalyrAgent__copying_manager") as m:
                m.generate_status = mock.MagicMock(return_value=None)
                m.get_worker_session_statuses = get_worker_session_statuses_mock

                base_stats.total_bytes_skipped = 1000
                test = agent._ScalyrAgent__calculate_overall_stats(
                    base_stats, copy_manager_warnings=True)
                self.assertIsNotNone(test)
                with open(self.agent_log_path, "r") as f:
                    print((f.read()))
                self.assertLogFileContainsLineRegex(".*" + re.escape(
                    "Warning, skipping copying log lines.  Only copied 0.0 MB/s log bytes when 0.0 MB/s were generated over the last 1.0 minutes.  This may be desired (due to excessive bytes from a problematic log file).  Please contact [email protected] for additional help."
                ))
Example #17
0
    parsed_config['module'] = monitor_module
    if 'id' not in parsed_config:
        parsed_config['id'] = ''

    # noinspection PyUnusedLocal
    def handle_shutdown_signal(signum, frame):
        print >>sys.stdout, 'Signal received, stopping monitor...'
        monitor.stop()

    for sig in (signal.SIGTERM, signal.SIGINT):
        signal.signal(sig, handle_shutdown_signal)

    try:
        if global_config_path is not None:
            controller = PlatformController.new_platform()
            paths = controller.default_paths
            global_config = Configuration( global_config_path, paths )
            global_config.parse(logger=log)
        else:
            global_config = None
        monitor = MonitorsManager.build_monitor(parsed_config, monitor_python_path, float(monitor_sample_interval),
                                                global_config )
        log.log(scalyr_logging.DEBUG_LEVEL_1, 'Constructed monitor')
        monitor.open_metric_log()
        log.log(scalyr_logging.DEBUG_LEVEL_1, 'Starting monitor')
        monitor.start()

        while monitor.isAlive():
            time.sleep(0.1)
    except BadMonitorConfiguration, e:
Example #18
0
def upgrade_tarball_install(config, new_tarball, preserve_old_install):
    """Performs an upgrade for an existing Scalyr Agent 2 that was previously installed using the tarball method.

    @param config: The configuration for this agent.
    @param new_tarball: The path to file containing the new tarball to install.
    @param preserve_old_install: If True, will move the old install directory to a new location rather than deleting
        it.

    @return: The exit status code.
    """
    # Create a temporary directory hold the new install as we untar it and copy files into it.
    tmp_install_dir = tempfile.mkdtemp()

    # Some variables that capture some important state that we may need to unwind if we execute
    # out the installation along the way.
    #
    # If not None, then the directory we are currently holding the old installation directory in.
    preserve_dir = None
    # True if the agent was running when the install started.
    was_running = False
    # True if the agent was successfully restarted.
    was_restarted = False

    try:
        try:
            platform_controller = PlatformController.new_platform()
            my_default_paths = platform_controller.default_paths

            # Ensure that this is a tarball install
            if platform_controller.install_type != TARBALL_INSTALL:
                raise UpgradeFailure('The current agent was not installed using a tarball, so you may not use the '
                                     'upgrade tarball command.')

            # Ensure that the user has not changed the defaults for the config, data, and log directory.
            if my_default_paths.config_file_path != config.file_path:
                raise UpgradeFailure('The agent is not using the default configuration file so you may not use the '
                                     'upgrade tarball command.')
            if my_default_paths.agent_data_path != config.agent_data_path:
                raise UpgradeFailure('The agent is not using the default data directory so you may not use the upgrade '
                                     'tarball command.')
            if my_default_paths.agent_log_path != config.agent_log_path:
                raise UpgradeFailure('The agent is not using the default log directory so you may not use the upgrade '
                                     'tarball command.')

            # We rely on the current installation being included in the PATH variable.
            if spawn.find_executable('scalyr-agent-2-config') is None:
                raise UpgradeFailure('Could not locate the scalyr-agent-2-config command from the current '
                                     'installation. Please ensure that the agent\'s bin directory is in the system\'s '
                                     'PATH variable.')

            if not os.path.isfile(new_tarball):
                raise UpgradeFailure('The tarball file %s does not exist.' % new_tarball)

            file_name = os.path.basename(new_tarball)
            if re.match('^scalyr-agent-2\..*\.tar\.gz$', file_name) is None:
                raise UpgradeFailure('The supplied tarball file name does not match the expected format.')
            tarball_directory = file_name[0:-7]

            # We will be installing in the same directory where scalyr-agent-2 is currently installed.
            install_directory = os.path.dirname(get_install_root())

            if not os.path.isdir(os.path.join(install_directory, 'scalyr-agent-2')):
                raise UpgradeFailure('Could not determine the install directory.  Either the main directory is no '
                                     'longer called scalyr-agent-2, or the directory structure has changed.')

            # Compute the full paths to the scalyr-agent-2 directories for both the new install and old install.
            tmp_new_install_location = os.path.join(tmp_install_dir, tarball_directory)
            old_install_location = os.path.join(install_directory, 'scalyr-agent-2')

            # Untar the new package into the temp location.
            tar = tarfile.open(new_tarball, 'r:gz')
            for member in tar.getmembers():
                tar.extract(member, path=tmp_install_dir)

            # Check to see if the agent is running.  If so, stop it.
            was_running = run_command('scalyr-agent-2 stop', grep_for='Agent has stopped',
                                      command_name='scalyr-agent-2 stop')[0] == 0

            # Copy the config, data, and log directories.
            for dir_name in ['config', 'log', 'data']:
                copy_dir_to_new_agent(old_install_location, tmp_new_install_location, dir_name)

            # Allow the new agent code to perform any actions it deems necessary.  We do the special commandline
            # here where to pass in both directories to the --upgrade-tarball-command
            result = subprocess.call([os.path.join(tmp_new_install_location, 'bin', 'scalyr-agent-2-config'),
                                      '--upgrade-tarball', '%s%s%s' % (old_install_location, os.pathsep,
                                                                       tmp_new_install_location)])
            if result != 0:
                raise UpgradeFailure('New package failed to finish the upgrade process.')

            # Move the old install directory to a temporary location, so we can undo the next move if we need to.
            preserve_dir = tempfile.mkdtemp()
            shutil.move(old_install_location, preserve_dir)

            # Move the new install into place.
            success = False
            try:
                shutil.move(tmp_new_install_location, old_install_location)
                success = True
            finally:
                if not success:
                    # Move the old install back in place just to be safe.
                    shutil.move(os.path.join(preserve_dir, 'scalyr-agent-2'), old_install_location)
                if success and not preserve_old_install:
                    shutil.rmtree(preserve_dir)
                    preserve_dir = None

            print 'New agent installed.'

            # Start the agent if it was previously running.
            if was_running:
                if run_command('scalyr-agent-2 start', exit_on_fail=False, command_name='scalyr-agent-2 start')[0] == 0:
                    print 'Agent has successfully restarted.'
                    print '  You may execute the following command for status details:  scalyr-agent-2 status -v'
                    was_restarted = True
                else:
                    raise UpgradeFailure('Could not start the agent.  Execute the following command for more details: '
                                         'scalyr-agent-2 start')
            else:
                print 'Execute the following command to start the agent:  scalyr-agent-2 start'

            return 0

        except UpgradeFailure, error:
            print >>sys.stderr
            print >>sys.stderr, 'The upgrade failed due to the following reason: %s' % error.message
            return 1

    finally:
        # Delete the temporary directory.
        shutil.rmtree(tmp_install_dir)

        # Warn if we should have restarted the agent but did not.
        if was_running and not was_restarted:
            print ''
            print ('WARNING, due to failure, the agent may no longer be running.  Restart it with: scalyr-agent-2 '
                   'start')

        # If there is still a preserve_directory, there must be a reason for it, so tell the user where it is.
        if preserve_dir is not None:
            print ''
            print 'The previous agent installation was left in \'%s\'' % preserve_dir
            print 'You should be sure to delete this directory once you no longer need it.'