Skip to content

Commit

Permalink
feat: 卸载agent时停止插件并清理agent安装及运行过程中产生的目录(closed #2490)
Browse files Browse the repository at this point in the history
  • Loading branch information
chalice-1831 committed Nov 24, 2024
1 parent f14f01b commit 8fe2b7c
Show file tree
Hide file tree
Showing 16 changed files with 469 additions and 12 deletions.
8 changes: 8 additions & 0 deletions apps/backend/agent/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -297,3 +297,11 @@ def install_other_agent(cls, extra_agent_version: str, node_type: str = NodeType
act.component.inputs.extra_agent_version = Var(type=Var.PLAIN, value=extra_agent_version)
act.component.inputs.node_type = Var(type=Var.PLAIN, value=node_type)
return act

@classmethod
def stop_plugins(cls):
"""停止插件"""
act = AgentServiceActivity(
component_code=components.StopPluginsComponent.code, name=components.StopPluginsComponent.name
)
return act
7 changes: 7 additions & 0 deletions apps/backend/components/collections/agent_new/components.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
from .render_and_push_gse_config import RenderAndPushGseConfigService
from .restart import RestartService
from .run_upgrade_command import RunUpgradeCommandService
from .stop_plugins import StopPluginsService
from .unbind_host_agent import UnBindHostAgentService
from .update_install_info import UpdateInstallInfoService
from .update_process_status import UpdateProcessStatusService
Expand Down Expand Up @@ -221,3 +222,9 @@ class InstallOtherAgentComponent(Component):
name = _("安装额外Agent")
code = "install_other_agent"
bound_service = InstallOtherAgentService


class StopPluginsComponent(Component):
name = _("停止插件")
code = "stop_plugins"
bound_service = StopPluginsService
86 changes: 86 additions & 0 deletions apps/backend/components/collections/agent_new/stop_plugins.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
# -*- coding: utf-8 -*-
"""
TencentBlueKing is pleased to support the open source community by making 蓝鲸智云-节点管理(BlueKing-BK-NODEMAN) available.
Copyright (C) 2017-2022 THL A29 Limited, a Tencent company. All rights reserved.
Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License.
You may obtain a copy of the License at https://opensource.org/licenses/MIT
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.
"""

from collections import defaultdict
from typing import List

from django.conf import settings

from apps.core.concurrent.retry import RetryHandler
from apps.node_man import constants, models
from apps.utils.batch_request import request_multi_thread
from common.api import NodeApi
from common.api.exception import DataAPIException

from ..base import CommonData
from ..subsubscription import SubSubscriptionBaseService
from .base import AgentBaseService


class StopPluginsService(SubSubscriptionBaseService, AgentBaseService):
@staticmethod
@RetryHandler(interval=1, retry_times=1, exception_types=[DataAPIException])
def call_create_subscription_api(params):
return NodeApi.create_subscription(params)

@classmethod
def create_subscriptions(cls, common_data: CommonData) -> List[int]:

host_ids_group_by_os = defaultdict(list)
for host in common_data.host_id_obj_map.values():
host_ids_group_by_os[host.os_type.lower()].append(host.bk_host_id)

installed_running_plugin_names = models.ProcessStatus.objects.filter(
status=constants.ProcStateType.RUNNING,
bk_host_id__in=common_data.bk_host_ids,
proc_type=constants.ProcType.PLUGIN,
).values_list("name", flat=True)

plugin_name__os_type_set = set(
models.Packages.objects.filter(
project__in=installed_running_plugin_names, os__in=host_ids_group_by_os.keys()
).values_list("project", "os")
)
params_list = []
for (plugin_name, os_type) in plugin_name__os_type_set:
params_list.append(
{
"params": {
"run_immediately": True,
"category": models.Subscription.CategoryType.ONCE,
"bk_username": settings.SYSTEM_USE_API_ACCOUNT,
"scope": {
"node_type": models.Subscription.NodeType.INSTANCE,
"object_type": models.Subscription.ObjectType.HOST,
"nodes": [{"bk_host_id": bk_host_id} for bk_host_id in host_ids_group_by_os[os_type]],
},
"steps": [
{
"id": plugin_name,
"type": "PLUGIN",
"config": {
"job_type": constants.JobType.MAIN_STOP_PLUGIN,
"plugin_name": plugin_name,
"plugin_version": "latest",
"config_templates": [
{"name": "{}.conf".format(plugin_name), "version": "latest", "is_main": True}
],
},
"params": {"context": {}},
}
],
}
}
)
subscription_ids = request_multi_thread(
cls.call_create_subscription_api, params_list, get_data=lambda x: [x["subscription_id"]]
)
return subscription_ids
2 changes: 2 additions & 0 deletions apps/backend/subscription/steps/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -497,6 +497,7 @@ class UninstallAgent(AgentAction):
def _generate_activities(self, agent_manager: AgentManager):
activities = [
agent_manager.query_password(),
agent_manager.stop_plugins(),
agent_manager.uninstall_agent(),
agent_manager.get_agent_status(expect_status=constants.ProcStateType.UNKNOWN),
agent_manager.update_process_status(status=constants.ProcStateType.NOT_INSTALLED),
Expand All @@ -515,6 +516,7 @@ class UninstallProxy(AgentAction):

def _generate_activities(self, agent_manager: AgentManager):
activities = [
agent_manager.stop_plugins(),
agent_manager.uninstall_proxy(),
agent_manager.get_agent_status(expect_status=constants.ProcStateType.UNKNOWN, name=_("查询Proxy状态")),
agent_manager.update_process_status(status=constants.ProcStateType.NOT_INSTALLED),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
# -*- coding: utf-8 -*-
"""
TencentBlueKing is pleased to support the open source community by making 蓝鲸智云-节点管理(BlueKing-BK-NODEMAN) available.
Copyright (C) 2017-2022 THL A29 Limited, a Tencent company. All rights reserved.
Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License.
You may obtain a copy of the License at https://opensource.org/licenses/MIT
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.
"""
from typing import List

import mock

from apps.backend.api.constants import POLLING_INTERVAL
from apps.backend.components.collections.agent_new.components import (
StopPluginsComponent,
)
from apps.mock_data import common_unit
from apps.node_man import models
from apps.node_man.tests.utils import NodeApi
from pipeline.component_framework.test import (
ComponentTestCase,
ExecuteAssertion,
ScheduleAssertion,
)

from . import utils


class StopPluginsTestCase(utils.AgentServiceBaseTestCase):
CASE_NAME = "测试子订阅停止插件成功"
NODEMAN_API_MOCK_PATHS = [
"common.api.NodeApi",
"apps.backend.components.collections.subsubscription.NodeApi",
"apps.backend.components.collections.agent_new.stop_plugins.NodeApi",
]
SUBSCRIPTION_ID = common_unit.subscription.DEFAULT_SUBSCRIPTION_ID
EXECUTE_RESULT = True
IS_FINISHED = True
SCHEDULE_RESULT = True
EXTRA_OUTPUT = {}

@staticmethod
def init_plugins():
models.GsePluginDesc.objects.create(**common_unit.plugin.GSE_PLUGIN_DESC_MODEL_DATA)
models.Packages.objects.create(**common_unit.plugin.PACKAGES_MODEL_DATA)
models.ProcessStatus.objects.create(**common_unit.plugin.PROC_STATUS_MODEL_DATA)

def start_patch(self):
class CustomNodeApi(NodeApi):
@staticmethod
def create_subscription(params):
return {"subscription_id": self.SUBSCRIPTION_ID}

@staticmethod
def get_subscription_task_status(params):
task_results = NodeApi.get_subscription_task_status(params)
for task_result in task_results:
task_result["instance_info"]["host"]["bk_host_id"] = self.obj_factory.bk_host_ids[0]
return task_results

for nodeman_api_mock_path in self.NODEMAN_API_MOCK_PATHS:
mock.patch(nodeman_api_mock_path, CustomNodeApi).start()

def setUp(self) -> None:
self.init_plugins()
super().setUp()
self.start_patch()

def fetch_succeeded_sub_inst_ids(self) -> List[int]:
return self.common_inputs["subscription_instance_ids"]

def component_cls(self):
return StopPluginsComponent

def cases(self):
return [
ComponentTestCase(
name=self.CASE_NAME,
inputs=self.common_inputs,
parent_data={},
execute_assertion=ExecuteAssertion(
success=self.EXECUTE_RESULT,
outputs={
"subscription_ids": [self.SUBSCRIPTION_ID],
"all_subscription_ids": [self.SUBSCRIPTION_ID],
"succeeded_subscription_instance_ids": self.fetch_succeeded_sub_inst_ids(),
},
),
schedule_assertion=ScheduleAssertion(
success=self.SCHEDULE_RESULT,
schedule_finished=self.IS_FINISHED,
outputs={
"subscription_ids": [self.SUBSCRIPTION_ID],
"all_subscription_ids": [self.SUBSCRIPTION_ID],
"succeeded_subscription_instance_ids": self.fetch_succeeded_sub_inst_ids(),
**self.EXTRA_OUTPUT,
},
),
execute_call_assertion=None,
)
]

@classmethod
def tearDownClass(cls):
mock.patch.stopall()
super().tearDownClass()


class StopPluginsNoSubTestCase(StopPluginsTestCase):
CASE_NAME = "测试无需创建子订阅"

@staticmethod
def init_plugins():
pass

def cases(self):
return [
ComponentTestCase(
name=self.CASE_NAME,
inputs=self.common_inputs,
parent_data={},
execute_assertion=ExecuteAssertion(
success=self.EXECUTE_RESULT,
outputs={
"subscription_ids": [],
"all_subscription_ids": [],
"succeeded_subscription_instance_ids": self.fetch_succeeded_sub_inst_ids(),
},
),
schedule_assertion=[],
)
]


class StopPluginsFailedTestCase(StopPluginsTestCase):
CASE_NAME = "测试子订阅停止插件失败"
SUBSCRIPTION_ID = common_unit.subscription.FAILED_SUBSCRIPTION_ID
SCHEDULE_RESULT = False
EXTRA_OUTPUT = {"succeeded_subscription_instance_ids": []}


class StopPluginsCreateSubTaskFailedTestCase(StopPluginsTestCase):
CASE_NAME = "测试子订阅任务创建失败"
SUBSCRIPTION_ID = common_unit.subscription.CREATE_TASK_FAILED_SUBSCRIPTION_ID
SCHEDULE_RESULT = False
EXTRA_OUTPUT = {"succeeded_subscription_instance_ids": [], "subscription_ids": []}


class StopPluginsRunningTestCase(StopPluginsTestCase):
CASE_NAME = "测试子订阅停止插件运行中"
SUBSCRIPTION_ID = common_unit.subscription.RUNNING_SUBSCRIPTION_ID
IS_FINISHED = False
EXTRA_OUTPUT = {"polling_time": POLLING_INTERVAL}
14 changes: 13 additions & 1 deletion script_tools/agent_tools/agent2/setup_agent.sh
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,16 @@ remove_crontab () {
fi
}

remove_directory () {
for dir in "$@"; do
if [ -d "$dir" ]; then
log remove_directory - "trying to remove directory [${dir}]"
rm -rf "$dir"
log remove_directory - "directory [${dir}] removed"
fi
done
}

setup_startup_scripts () {
check_rc_file
local rcfile=$RC_LOCAL_FILE
Expand Down Expand Up @@ -480,7 +490,8 @@ remove_agent () {

if [[ "$REMOVE" == "TRUE" ]]; then
unregister_agent_id
clean_up_agent_directory
remove_directory "$GSE_HOME" "$GSE_AGENT_RUN_DIR" "$GSE_AGENT_DATA_DIR" "$GSE_AGENT_LOG_DIR"

log remove_agent DONE "agent removed"
exit 0
fi
Expand Down Expand Up @@ -903,6 +914,7 @@ done
PKG_NAME=${NAME}-${VERSION}.tgz
COMPLETE_DOWNLOAD_URL="${DOWNLOAD_URL}/agent/linux/${CPU_ARCH}"
GSE_AGENT_CONFIG_PATH="${AGENT_SETUP_PATH}/etc/${GSE_AGENT_CONFIG}"
GSE_HOME=$(dirname ${AGENT_SETUP_PATH})

LOG_FILE="$TMP_DIR"/nm.${0##*/}.$TASK_ID
DEBUG_LOG_FILE=${TMP_DIR}/nm.${0##*/}.${TASK_ID}.debug
Expand Down
14 changes: 13 additions & 1 deletion script_tools/agent_tools/agent2/setup_agent.zsh
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,16 @@ remove_crontab () {
fi
}

remove_directory () {
for dir in "$@"; do
if [ -d "$dir" ]; then
log remove_directory - "trying to remove directory [${dir}]"
rm -rf "$dir"
log remove_directory - "directory [${dir}] removed"
fi
done
}

get_daemon_file () {
DAEMON_FILE_PATH="/Library/LaunchDaemons/"
DAEMON_FILE_NAME="com.tencent.$(echo ${AGENT_SETUP_PATH%*/} | tr '/' '.' | awk -F '.' '{print $(NF-1)"."$NF}').Daemon.plist"
Expand Down Expand Up @@ -492,7 +502,7 @@ remove_agent () {

if [[ "$REMOVE" == "TRUE" ]]; then
unregister_agent_id
clean_up_agent_directory
remove_directory "$GSE_HOME" "$GSE_AGENT_RUN_DIR" "$GSE_AGENT_DATA_DIR" "$GSE_AGENT_LOG_DIR"
log remove_agent DONE "agent removed"
exit 0
fi
Expand Down Expand Up @@ -850,6 +860,7 @@ check_env () {
CLOUD_ID=0
TMP_DIR=/tmp
AGENT_SETUP_PATH="/usr/local/gse/${NODE_TYPE}"

CURR_PID=$$
OVERIDE=false
REMOVE=false
Expand Down Expand Up @@ -914,6 +925,7 @@ done
PKG_NAME=${NAME}-${VERSION}.tgz
COMPLETE_DOWNLOAD_URL="${DOWNLOAD_URL}/agent/darwin/${CPU_ARCH}"
GSE_AGENT_CONFIG_PATH="${AGENT_SETUP_PATH}/etc/${GSE_AGENT_CONFIG}"
GSE_HOME=$(dirname ${AGENT_SETUP_PATH})

LOG_FILE="$TMP_DIR"/nm.${0##*/}.$TASK_ID
DEBUG_LOG_FILE=${TMP_DIR}/nm.${0##*/}.${TASK_ID}.debug
Expand Down
13 changes: 12 additions & 1 deletion script_tools/agent_tools/agent2/setup_proxy.sh
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,16 @@ remove_crontab () {
fi
}

remove_directory () {
for dir in "$@"; do
if [ -d "$dir" ]; then
log remove_directory - "trying to remove directory [${dir}]"
rm -rf "$dir"
log remove_directory - "directory [${dir}] removed"
fi
done
}

setup_startup_scripts () {
check_rc_file
local rcfile=$RC_LOCAL_FILE
Expand Down Expand Up @@ -520,7 +530,7 @@ remove_proxy () {

if [[ "$REMOVE" == "TRUE" ]]; then
unregister_agent_id SKIP
clean_up_proxy_directory
remove_directory "$GSE_HOME" "$GSE_AGENT_RUN_DIR" "$GSE_AGENT_DATA_DIR" "$GSE_AGENT_LOG_DIR"
log remove_proxy DONE "proxy removed"
exit 0
else
Expand Down Expand Up @@ -896,6 +906,7 @@ DEBUG_LOG_FILE=${TMP_DIR}/nm.${0##*/}.${TASK_ID}.debug
PKG_NAME=${NAME}-${VERSION}.tgz
COMPLETE_DOWNLOAD_URL="${DOWNLOAD_URL}/agent/linux/${CPU_ARCH}/"
GSE_AGENT_CONFIG_PATH="${AGENT_SETUP_PATH}/etc/${GSE_AGENT_CONFIG}"
GSE_HOME=$(dirname ${AGENT_SETUP_PATH})

# redirect STDOUT & STDERR to DEBUG
exec &> >(tee "$DEBUG_LOG_FILE")
Expand Down
Loading

0 comments on commit 8fe2b7c

Please sign in to comment.