feat: 新增获取k8s pod namespace container接口

pull/7450/head
feng626 2021-08-30 16:48:46 +08:00 committed by Jiangjie.Bai
parent 11fd9a6567
commit 100bfe0304
10 changed files with 309 additions and 45 deletions

View File

@ -1,6 +1,6 @@
# coding: utf-8 # coding: utf-8
# #
from django.shortcuts import get_object_or_404
from orgs.mixins.api import OrgBulkModelViewSet from orgs.mixins.api import OrgBulkModelViewSet
from rest_framework.decorators import action from rest_framework.decorators import action
from rest_framework.response import Response from rest_framework.response import Response

View File

@ -1,13 +1,21 @@
from urllib.parse import urlencode, parse_qsl
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from rest_framework.generics import get_object_or_404
from common.tree import TreeNode from common.tree import TreeNode
from orgs.models import Organization from orgs.models import Organization
from assets.models import SystemUser
from applications.utils import KubernetesClient, KubernetesTree
from perms.utils.application.permission import get_application_system_user_ids
from ..models import Application from ..models import Application
__all__ = ['SerializeApplicationToTreeNodeMixin'] __all__ = ['SerializeApplicationToTreeNodeMixin']
class SerializeApplicationToTreeNodeMixin: class SerializeApplicationToTreeNodeMixin:
@staticmethod @staticmethod
def filter_organizations(applications): def filter_organizations(applications):
organization_ids = set(applications.values_list('org_id', flat=True)) organization_ids = set(applications.values_list('org_id', flat=True))
@ -31,25 +39,47 @@ class SerializeApplicationToTreeNodeMixin:
}) })
return node return node
def serialize_applications_with_org(self, applications): def serialize_applications_with_org(self, applications, tree_id, parent_info, user):
tree_nodes = []
if not applications: if not applications:
return [] return tree_nodes
root_node = self.create_root_node()
tree_nodes = [root_node]
organizations = self.filter_organizations(applications)
for i, org in enumerate(organizations): if not tree_id:
# 组织节点 root_node = self.create_root_node()
org_node = org.as_tree_node(pid=root_node.id) tree_nodes.append(root_node)
tree_nodes.append(org_node) organizations = self.filter_organizations(applications)
org_applications = applications.filter(org_id=org.id) for i, org in enumerate(organizations):
count = org_applications.count() tree_id = urlencode({'org_id': str(org.id)})
org_node.name += '({})'.format(count) apps = applications.filter(org_id=org.id)
# 组织节点
org_node = org.as_tree_node(oid=tree_id, pid=root_node.id)
org_node.name += '({})'.format(apps.count())
tree_nodes.append(org_node)
category_type_nodes = Application.create_category_type_tree_nodes(
apps, tree_id, show_empty=False
)
tree_nodes += category_type_nodes
# 各应用节点 for app in apps:
apps_nodes = Application.create_tree_nodes( app_node = app.as_tree_node(tree_id, is_luna=True)
queryset=org_applications, root_node=org_node, tree_nodes.append(app_node)
show_empty=False return tree_nodes
)
tree_nodes += apps_nodes parent_info = dict(parse_qsl(parent_info))
pod_name = parent_info.get('pod')
app_id = parent_info.get('app_id')
namespace = parent_info.get('namespace')
system_user_id = parent_info.get('system_user_id')
if app_id and not any([pod_name, namespace, system_user_id]):
app = get_object_or_404(Application, id=app_id)
system_user_ids = get_application_system_user_ids(user, app)
system_users = SystemUser.objects.filter(id__in=system_user_ids).order_by('priority')
for system_user in system_users:
system_user_node = KubernetesTree(tree_id).as_system_user_tree_node(
system_user, parent_info
)
tree_nodes.append(system_user_node)
return tree_nodes
tree_nodes = KubernetesTree(tree_id).async_tree_node(parent_info)
return tree_nodes return tree_nodes

View File

@ -1,4 +1,5 @@
from collections import defaultdict from collections import defaultdict
from urllib.parse import urlencode, parse_qsl
from django.db import models from django.db import models
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
@ -7,6 +8,8 @@ from orgs.mixins.models import OrgModelMixin
from common.mixins import CommonModelMixin from common.mixins import CommonModelMixin
from common.tree import TreeNode from common.tree import TreeNode
from assets.models import Asset, SystemUser from assets.models import Asset, SystemUser
from ..utils import KubernetesTree
from .. import const from .. import const
@ -16,6 +19,13 @@ class ApplicationTreeNodeMixin:
type: str type: str
category: str category: str
@staticmethod
def create_tree_id(pid, type, v):
i = dict(parse_qsl(pid))
i[type] = v
tree_id = urlencode(i)
return tree_id
@classmethod @classmethod
def create_choice_node(cls, c, id_, pid, tp, opened=False, counts=None, def create_choice_node(cls, c, id_, pid, tp, opened=False, counts=None,
show_empty=True, show_count=True): show_empty=True, show_count=True):
@ -65,13 +75,13 @@ class ApplicationTreeNodeMixin:
return node return node
@classmethod @classmethod
def create_category_tree_nodes(cls, root_node, counts=None, show_empty=True, show_count=True): def create_category_tree_nodes(cls, pid, counts=None, show_empty=True, show_count=True):
nodes = [] nodes = []
categories = const.AppType.category_types_mapper().keys() categories = const.AppType.category_types_mapper().keys()
for category in categories: for category in categories:
i = root_node.id + '_' + category.value i = cls.create_tree_id(pid, 'category', category.value)
node = cls.create_choice_node( node = cls.create_choice_node(
category, i, pid=root_node.id, tp='category', category, i, pid=pid, tp='category',
counts=counts, opened=False, show_empty=show_empty, counts=counts, opened=False, show_empty=show_empty,
show_count=show_count show_count=show_count
) )
@ -81,17 +91,20 @@ class ApplicationTreeNodeMixin:
return nodes return nodes
@classmethod @classmethod
def create_types_tree_nodes(cls, root_node, counts, show_empty=True, show_count=True): def create_types_tree_nodes(cls, pid, counts, show_empty=True, show_count=True):
nodes = [] nodes = []
temp_pid = pid
type_category_mapper = const.AppType.type_category_mapper() type_category_mapper = const.AppType.type_category_mapper()
for tp in const.AppType.type_category_mapper().keys(): types = const.AppType.type_category_mapper().keys()
for tp in types:
category = type_category_mapper.get(tp) category = type_category_mapper.get(tp)
pid = root_node.id + '_' + category.value pid = cls.create_tree_id(pid, 'category', category.value)
i = root_node.id + '_' + tp.value i = cls.create_tree_id(pid, 'type', tp.value)
node = cls.create_choice_node( node = cls.create_choice_node(
tp, i, pid, tp='type', counts=counts, opened=False, tp, i, pid, tp='type', counts=counts, opened=False,
show_empty=show_empty, show_count=show_count show_empty=show_empty, show_count=show_count
) )
pid = temp_pid
if not node: if not node:
continue continue
nodes.append(node) nodes.append(node)
@ -109,40 +122,63 @@ class ApplicationTreeNodeMixin:
return counts return counts
@classmethod @classmethod
def create_tree_nodes(cls, queryset, root_node=None, show_empty=True, show_count=True): def create_category_type_tree_nodes(cls, queryset, pid, show_empty=True, show_count=True):
counts = cls.get_tree_node_counts(queryset) counts = cls.get_tree_node_counts(queryset)
tree_nodes = [] tree_nodes = []
# 类别的节点
tree_nodes += cls.create_category_tree_nodes(
pid, counts, show_empty=show_empty,
show_count=show_count
)
# 类型的节点
tree_nodes += cls.create_types_tree_nodes(
pid, counts, show_empty=show_empty,
show_count=show_count
)
return tree_nodes
@classmethod
def create_tree_nodes(cls, queryset, root_node=None, show_empty=True, show_count=True):
tree_nodes = []
# 根节点有可能是组织名称 # 根节点有可能是组织名称
if root_node is None: if root_node is None:
root_node = cls.create_root_tree_node(queryset, show_count=show_count) root_node = cls.create_root_tree_node(queryset, show_count=show_count)
tree_nodes.append(root_node) tree_nodes.append(root_node)
# 类别的节点 tree_nodes += cls.create_category_type_tree_nodes(
tree_nodes += cls.create_category_tree_nodes( queryset, root_node.id, show_empty=show_empty, show_count=show_count
root_node, counts, show_empty=show_empty,
show_count=show_count
)
# 类型的节点
tree_nodes += cls.create_types_tree_nodes(
root_node, counts, show_empty=show_empty,
show_count=show_count
) )
# 应用的节点 # 应用的节点
for app in queryset: for app in queryset:
pid = root_node.id + '_' + app.type node = app.as_tree_node(root_node.id)
tree_nodes.append(app.as_tree_node(pid)) tree_nodes.append(node)
return tree_nodes return tree_nodes
def as_tree_node(self, pid): def create_app_tree_pid(self, root_id):
pid = self.create_tree_id(root_id, 'category', self.category)
pid = self.create_tree_id(pid, 'type', self.type)
return pid
def as_tree_node(self, pid, is_luna=False):
if is_luna and self.type == const.AppType.k8s:
node = KubernetesTree(pid).as_tree_node(self)
else:
node = self._as_tree_node(pid)
return node
def _as_tree_node(self, pid):
icon_skin_category_mapper = { icon_skin_category_mapper = {
'remote_app': 'chrome', 'remote_app': 'chrome',
'db': 'database', 'db': 'database',
'cloud': 'cloud' 'cloud': 'cloud'
} }
icon_skin = icon_skin_category_mapper.get(self.category, 'file') icon_skin = icon_skin_category_mapper.get(self.category, 'file')
pid = self.create_app_tree_pid(pid)
node = TreeNode(**{ node = TreeNode(**{
'id': str(self.id), 'id': str(self.id),
'name': self.name, 'name': self.name,

View File

@ -0,0 +1,4 @@
# -*- coding: utf-8 -*-
#
from .kubernetes_util import *

View File

@ -0,0 +1,186 @@
# -*- coding: utf-8 -*-
from urllib3.exceptions import MaxRetryError
from urllib.parse import urlencode
from kubernetes.client import api_client
from kubernetes.client.api import core_v1_api
from kubernetes import client
from kubernetes.client.exceptions import ApiException
from rest_framework.generics import get_object_or_404
from common.utils import get_logger
from common.tree import TreeNode
from assets.models import SystemUser
from .. import const
logger = get_logger(__file__)
class KubernetesClient:
def __init__(self, url, token):
self.url = url
self.token = token
def get_api(self):
configuration = client.Configuration()
configuration.host = self.url
configuration.verify_ssl = False
configuration.api_key = {"authorization": "Bearer " + self.token}
c = api_client.ApiClient(configuration=configuration)
api = core_v1_api.CoreV1Api(c)
return api
def get_namespace_list(self):
api = self.get_api()
namespace_list = []
for ns in api.list_namespace().items:
namespace_list.append(ns.metadata.name)
return namespace_list
def get_services(self):
api = self.get_api()
ret = api.list_service_for_all_namespaces(watch=False)
for i in ret.items:
print("%s \t%s \t%s \t%s \t%s \n" % (
i.kind, i.metadata.namespace, i.metadata.name, i.spec.cluster_ip, i.spec.ports))
def get_pod_info(self, namespace, pod):
api = self.get_api()
resp = api.read_namespaced_pod(namespace=namespace, name=pod)
return resp
def get_pod_logs(self, namespace, pod):
api = self.get_api()
log_content = api.read_namespaced_pod_log(pod, namespace, pretty=True, tail_lines=200)
return log_content
def get_pods(self):
api = self.get_api()
try:
ret = api.list_pod_for_all_namespaces(watch=False, _request_timeout=(3, 3))
except MaxRetryError:
logger.warning('Kubernetes connection timed out')
return
except ApiException as e:
if e.status == 401:
logger.warning('Kubernetes User not authenticated')
else:
logger.warning(e)
return
data = {}
for i in ret.items:
namespace = i.metadata.namespace
pod_info = {
'pod_name': i.metadata.name,
'containers': [j.name for j in i.spec.containers]
}
if namespace in data:
data[namespace].append(pod_info)
else:
data[namespace] = [pod_info, ]
return data
@staticmethod
def get_kubernetes_data(app_id, system_user_id):
from ..models import Application
app = get_object_or_404(Application, id=app_id)
system_user = get_object_or_404(SystemUser, id=system_user_id)
k8s = KubernetesClient(app.attrs['cluster'], system_user.token)
return k8s.get_pods()
class KubernetesTree:
def __init__(self, tree_id):
self.tree_id = tree_id
def as_tree_node(self, app):
pid = app.create_app_tree_pid(self.tree_id)
app_id = str(app.id)
parent_info = {'app_id': app_id}
node = self.create_tree_node(
app_id, pid, app.name, 'k8s', parent_info
)
return node
def as_system_user_tree_node(self, system_user, parent_info):
from ..models import ApplicationTreeNodeMixin
system_user_id = str(system_user.id)
username = system_user.username
username = username if username else '*'
name = f'{system_user.name}({username})'
pid = urlencode({'app_id': self.tree_id})
i = ApplicationTreeNodeMixin.create_tree_id(pid, 'system_user_id', system_user_id)
parent_info.update({'system_user_id': system_user_id})
node = self.create_tree_node(
i, pid, name, 'system_user', parent_info, icon='user-tie'
)
return node
def as_namespace_pod_tree_node(self, name, meta, type, counts=0, is_container=False):
from ..models import ApplicationTreeNodeMixin
i = ApplicationTreeNodeMixin.create_tree_id(self.tree_id, type, name)
meta.update({type: name})
name = name if is_container else f'{name}({counts})'
node = self.create_tree_node(
i, self.tree_id, name, type, meta, icon='cloud', is_container=is_container
)
return node
@staticmethod
def create_tree_node(id_, pid, name, identity, parent_info, icon='', is_container=False):
node = TreeNode(**{
'id': id_,
'name': name,
'title': name,
'pId': pid,
'isParent': not is_container,
'open': False,
'iconSkin': icon,
'parentInfo': urlencode(parent_info),
'meta': {
'type': 'application',
'data': {
'category': const.AppCategory.cloud,
'type': const.AppType.k8s,
'identity': identity
}
}
})
return node
def async_tree_node(self, parent_info):
pod_name = parent_info.get('pod')
app_id = parent_info.get('app_id')
namespace = parent_info.get('namespace')
system_user_id = parent_info.get('system_user_id')
tree_nodes = []
data = KubernetesClient.get_kubernetes_data(app_id, system_user_id)
if not data:
return tree_nodes
if pod_name:
for container in next(
filter(
lambda x: x['pod_name'] == pod_name, data[namespace]
)
)['containers']:
container_node = self.as_namespace_pod_tree_node(
container, parent_info, 'container', is_container=True
)
tree_nodes.append(container_node)
elif namespace:
for pod in data[namespace]:
pod_nodes = self.as_namespace_pod_tree_node(
pod['pod_name'], parent_info, 'pod', len(pod['containers'])
)
tree_nodes.append(pod_nodes)
elif system_user_id:
for namespace, pods in data.items():
namespace_node = self.as_namespace_pod_tree_node(
namespace, parent_info, 'namespace', len(pods)
)
tree_nodes.append(namespace_node)
return tree_nodes

View File

@ -13,6 +13,7 @@ class TreeNode:
pId = "" pId = ""
open = False open = False
iconSkin = "" iconSkin = ""
parentInfo = ''
meta = {} meta = {}
_tree = None _tree = None
@ -95,6 +96,7 @@ class TreeNodeSerializer(serializers.Serializer):
name = serializers.CharField(max_length=128) name = serializers.CharField(max_length=128)
title = serializers.CharField(max_length=128) title = serializers.CharField(max_length=128)
pId = serializers.CharField(max_length=128) pId = serializers.CharField(max_length=128)
parentInfo = serializers.CharField(max_length=4096, allow_blank=True)
isParent = serializers.BooleanField(default=False) isParent = serializers.BooleanField(default=False)
open = serializers.BooleanField(default=False) open = serializers.BooleanField(default=False)
iconSkin = serializers.CharField(max_length=128, allow_blank=True) iconSkin = serializers.CharField(max_length=128, allow_blank=True)

View File

@ -234,9 +234,9 @@ class Organization(models.Model):
with tmp_to_org(self): with tmp_to_org(self):
return resource_model.objects.all().count() return resource_model.objects.all().count()
def as_tree_node(self, pid, opened=True): def as_tree_node(self, oid, pid, opened=True):
node = TreeNode(**{ node = TreeNode(**{
'id': str(self.id), 'id': oid,
'name': self.name, 'name': self.name,
'title': self.name, 'title': self.name,
'pId': pid, 'pId': pid,

View File

@ -13,7 +13,6 @@ from rest_framework.generics import (
from orgs.utils import tmp_to_root_org from orgs.utils import tmp_to_root_org
from applications.models import Application from applications.models import Application
from perms.utils.application.permission import ( from perms.utils.application.permission import (
has_application_system_permission,
get_application_system_user_ids, get_application_system_user_ids,
validate_permission, validate_permission,
) )

View File

@ -54,10 +54,15 @@ class ApplicationsAsTreeMixin(SerializeApplicationToTreeNodeMixin):
将应用序列化成树的结构返回 将应用序列化成树的结构返回
""" """
serializer_class = TreeNodeSerializer serializer_class = TreeNodeSerializer
user: None
def list(self, request, *args, **kwargs): def list(self, request, *args, **kwargs):
tree_id = request.query_params.get('tree_id', None)
parent_info = request.query_params.get('parentInfo', None)
queryset = self.filter_queryset(self.get_queryset()) queryset = self.filter_queryset(self.get_queryset())
tree_nodes = self.serialize_applications_with_org(queryset) tree_nodes = self.serialize_applications_with_org(
queryset, tree_id, parent_info, self.user
)
serializer = self.get_serializer(tree_nodes, many=True) serializer = self.get_serializer(tree_nodes, many=True)
return Response(data=serializer.data) return Response(data=serializer.data)

View File

@ -59,7 +59,7 @@ PyNaCl==1.2.1
python-dateutil==2.6.1 python-dateutil==2.6.1
#python-gssapi==0.6.4 #python-gssapi==0.6.4
pytz==2018.3 pytz==2018.3
PyYAML==5.4 PyYAML==6.0
redis==3.5.3 redis==3.5.3
requests==2.25.1 requests==2.25.1
jms-storage==0.0.40 jms-storage==0.0.40
@ -125,3 +125,5 @@ pyzipper==0.3.5
python3-saml==1.12.0 python3-saml==1.12.0
python-keystoneclient==4.3.0 python-keystoneclient==4.3.0
pymssql==2.1.5 pymssql==2.1.5
kubernetes==21.7.0
websocket-client==1.2.3