# Copyright 2019 Huawei Technologies Co., Ltd # # 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. # ============================================================================ """Stop mindinsight service.""" import os import sys import argparse import signal import getpass import psutil from mindinsight.conf import settings from mindinsight.utils.command import BaseCommand from mindinsight.utils.hook import HookUtils class PortAction(argparse.Action): """Port action class definition.""" MIN_PORT = 1 MAX_PORT = 65535 def __call__(self, parser, namespace, values, option_string=None): """ Inherited __call__ method from argparse.Action. Args: parser (ArgumentParser): Passed-in argument parser. namespace (Namespace): Namespace object to hold arguments. values (object): Argument values with type depending on argument definition. option_string (str): Optional string for specific argument name. Default: None. """ port = values if not self.MIN_PORT <= port <= self.MAX_PORT: parser.error(f'{option_string} should be chosen from {self.MIN_PORT} to {self.MAX_PORT}') setattr(namespace, self.dest, port) class Command(BaseCommand): """Stop command.""" name = 'stop' description = 'stop mindinsight service' cmd_regex = 'mindinsight.backend.application:APP' access_log_path = os.path.join('gunicorn', 'access.log') def add_arguments(self, parser): """ Add arguments to parser. Args: parser (ArgumentParser): Specify parser to which arguments are added. """ parser.add_argument( '--port', type=int, action=PortAction, help=""" Custom port ranging from %s to %s. Default value is %s """ % (PortAction.MIN_PORT, PortAction.MAX_PORT, settings.PORT)) def update_settings(self, args): """ Update settings. Args: args (Namespace): parsed arguments to hold customized parameters. """ if args.port is None: args.port = settings.PORT pid, workspace = self.get_process(args.port) settings.config_workspace(workspace) setattr(args, 'pid', pid) def run(self, args): """ Run to stop. Args: args (Namespace): Parsed arguments to hold customized parameters. """ port, pid = args.port, args.pid if not pid: msg = f'No mindinsight service started by current user found for port {port}' self.console.error(msg) sys.exit(1) self.logfile.info('Stop mindinsight with port %s and pid %s.', port, pid) process = psutil.Process(pid) processes_to_kill = [process] # Set recursive to True to kill grand children processes. for child in process.children(recursive=True): processes_to_kill.append(child) for proc in processes_to_kill: self.logfile.info('Stopping mindinsight process %s.', proc.pid) try: proc.send_signal(signal.SIGKILL) except psutil.Error as ex: self.logfile.warning("Stop process %s failed. Detail: %s.", proc.pid, str(ex)) for hook in HookUtils.instance().hooks(): hook.on_shutdown(self.logfile) self.console.info('Stop mindinsight service successfully') def get_process(self, port): """ Get mindinsight process Args: port (int): Specified port for mindinsight process. Returns: tuple, return mindinsight process pid and workspace. """ pid, workspace = 0, settings.WORKSPACE user = getpass.getuser() connections = psutil.net_connections() for connection in connections: if connection.status != 'LISTEN': continue if connection.laddr.port != port: continue try: process = psutil.Process(connection.pid) except psutil.NoSuchProcess: continue cmds = process.cmdline() if ' '.join(cmds).find(self.cmd_regex) == -1: continue if user != process.username(): continue gunicorn_master_process = process # The gunicorn master process might have grand children (eg forked by process pool). while True: parent_process = gunicorn_master_process.parent() if parent_process is None or parent_process.pid == 1: break parent_cmd = parent_process.cmdline() if ' '.join(parent_cmd).find(self.cmd_regex) == -1: break gunicorn_master_process = parent_process pid = gunicorn_master_process.pid for open_file in process.open_files(): if open_file.path.endswith(self.access_log_path): log_base_dir = open_file.path[:-len(self.access_log_path)] workspace = os.path.realpath(os.path.join(log_base_dir, os.pardir)) break break return pid, workspace