perf: 打算重构 asset application

pull/8605/head
ibuler 2022-04-02 18:35:46 +08:00
parent 54d1996507
commit 3de881fa19
31 changed files with 667 additions and 362 deletions

View File

@ -5,6 +5,7 @@ from django.utils.translation import ugettext_lazy as _
from common.utils import lazyproperty
from assets.models.base import BaseUser
from assets.models import SystemUser
class Account(BaseUser):
@ -108,3 +109,9 @@ class Account(BaseUser):
def __str__(self):
return self.smart_name
class ApplicationUser(SystemUser):
class Meta:
proxy = True
verbose_name = _('Application user')

View File

@ -1,216 +1,13 @@
from collections import defaultdict
from urllib.parse import urlencode, parse_qsl
from django.db import models
from django.utils.translation import ugettext_lazy as _
from django.conf import settings
from orgs.mixins.models import OrgModelMixin
from common.mixins import CommonModelMixin
from common.tree import TreeNode
from common.utils import is_uuid
from assets.models import Asset, SystemUser
from assets.models import Asset
from ..utils import KubernetesTree
from .. import const
class ApplicationTreeNodeMixin:
id: str
name: str
type: str
category: str
attrs: dict
@staticmethod
def create_tree_id(pid, type, v):
i = dict(parse_qsl(pid))
i[type] = v
tree_id = urlencode(i)
return tree_id
@classmethod
def create_choice_node(cls, c, id_, pid, tp, opened=False, counts=None,
show_empty=True, show_count=True):
count = counts.get(c.value, 0)
if count == 0 and not show_empty:
return None
label = c.label
if count is not None and show_count:
label = '{} ({})'.format(label, count)
data = {
'id': id_,
'name': label,
'title': label,
'pId': pid,
'isParent': bool(count),
'open': opened,
'iconSkin': '',
'meta': {
'type': tp,
'data': {
'name': c.name,
'value': c.value
}
}
}
return TreeNode(**data)
@classmethod
def create_root_tree_node(cls, queryset, show_count=True):
count = queryset.count() if show_count else None
root_id = 'applications'
root_name = _('Applications')
if count is not None and show_count:
root_name = '{} ({})'.format(root_name, count)
node = TreeNode(**{
'id': root_id,
'name': root_name,
'title': root_name,
'pId': '',
'isParent': True,
'open': True,
'iconSkin': '',
'meta': {
'type': 'applications_root',
}
})
return node
@classmethod
def create_category_tree_nodes(cls, pid, counts=None, show_empty=True, show_count=True):
nodes = []
categories = const.AppType.category_types_mapper().keys()
for category in categories:
if not settings.XPACK_ENABLED and const.AppCategory.is_xpack(category):
continue
i = cls.create_tree_id(pid, 'category', category.value)
node = cls.create_choice_node(
category, i, pid=pid, tp='category',
counts=counts, opened=False, show_empty=show_empty,
show_count=show_count
)
if not node:
continue
nodes.append(node)
return nodes
@classmethod
def create_types_tree_nodes(cls, pid, counts, show_empty=True, show_count=True):
nodes = []
temp_pid = pid
type_category_mapper = const.AppType.type_category_mapper()
types = const.AppType.type_category_mapper().keys()
for tp in types:
if not settings.XPACK_ENABLED and const.AppType.is_xpack(tp):
continue
category = type_category_mapper.get(tp)
pid = cls.create_tree_id(pid, 'category', category.value)
i = cls.create_tree_id(pid, 'type', tp.value)
node = cls.create_choice_node(
tp, i, pid, tp='type', counts=counts, opened=False,
show_empty=show_empty, show_count=show_count
)
pid = temp_pid
if not node:
continue
nodes.append(node)
return nodes
@staticmethod
def get_tree_node_counts(queryset):
counts = defaultdict(int)
values = queryset.values_list('type', 'category')
for i in values:
tp = i[0]
category = i[1]
counts[tp] += 1
counts[category] += 1
return counts
@classmethod
def create_category_type_tree_nodes(cls, queryset, pid, show_empty=True, show_count=True):
counts = cls.get_tree_node_counts(queryset)
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:
root_node = cls.create_root_tree_node(queryset, show_count=show_count)
tree_nodes.append(root_node)
tree_nodes += cls.create_category_type_tree_nodes(
queryset, root_node.id, show_empty=show_empty, show_count=show_count
)
# 应用的节点
for app in queryset:
if not settings.XPACK_ENABLED and const.AppType.is_xpack(app.type):
continue
node = app.as_tree_node(root_node.id)
tree_nodes.append(node)
return tree_nodes
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, k8s_as_tree=False):
if self.type == const.AppType.k8s and k8s_as_tree:
node = KubernetesTree(pid).as_tree_node(self)
else:
node = self._as_tree_node(pid)
return node
def _attrs_to_tree(self):
if self.category == const.AppCategory.db:
return self.attrs
return {}
def _as_tree_node(self, pid):
icon_skin_category_mapper = {
'remote_app': 'chrome',
'db': 'database',
'cloud': 'cloud'
}
icon_skin = icon_skin_category_mapper.get(self.category, 'file')
pid = self.create_app_tree_pid(pid)
node = TreeNode(**{
'id': str(self.id),
'name': self.name,
'title': self.name,
'pId': pid,
'isParent': False,
'open': False,
'iconSkin': icon_skin,
'meta': {
'type': 'application',
'data': {
'category': self.category,
'type': self.type,
'attrs': self._attrs_to_tree()
}
}
})
return node
from .tree import ApplicationTreeNodeMixin
class Application(CommonModelMixin, OrgModelMixin, ApplicationTreeNodeMixin):
@ -279,8 +76,3 @@ class Application(CommonModelMixin, OrgModelMixin, ApplicationTreeNodeMixin):
if raise_exception:
raise ValueError("Remote App not has asset attr")
class ApplicationUser(SystemUser):
class Meta:
proxy = True
verbose_name = _('Application user')

View File

@ -0,0 +1,13 @@
from django.db import models
from django.utils.translation import gettext_lazy as _
from .application import Application
class Database(Application):
host = models.CharField(max_length=1024, verbose_name=_('Host'))
port = models.IntegerField(verbose_name=_("Port"))
database = models.CharField(max_length=1024, blank=True, null=True, verbose_name=_("Database"))
class Meta:
verbose_name = _("Database")

View File

View File

@ -0,0 +1,208 @@
from collections import defaultdict
from urllib.parse import urlencode, parse_qsl
from django.utils.translation import ugettext_lazy as _
from django.conf import settings
from common.tree import TreeNode
from ..utils import KubernetesTree
from .. import const
class ApplicationTreeNodeMixin:
id: str
name: str
type: str
category: str
attrs: dict
@staticmethod
def create_tree_id(pid, type, v):
i = dict(parse_qsl(pid))
i[type] = v
tree_id = urlencode(i)
return tree_id
@classmethod
def create_choice_node(cls, c, id_, pid, tp, opened=False, counts=None,
show_empty=True, show_count=True):
count = counts.get(c.value, 0)
if count == 0 and not show_empty:
return None
label = c.label
if count is not None and show_count:
label = '{} ({})'.format(label, count)
data = {
'id': id_,
'name': label,
'title': label,
'pId': pid,
'isParent': bool(count),
'open': opened,
'iconSkin': '',
'meta': {
'type': tp,
'data': {
'name': c.name,
'value': c.value
}
}
}
return TreeNode(**data)
@classmethod
def create_root_tree_node(cls, queryset, show_count=True):
count = queryset.count() if show_count else None
root_id = 'applications'
root_name = _('Applications')
if count is not None and show_count:
root_name = '{} ({})'.format(root_name, count)
node = TreeNode(**{
'id': root_id,
'name': root_name,
'title': root_name,
'pId': '',
'isParent': True,
'open': True,
'iconSkin': '',
'meta': {
'type': 'applications_root',
}
})
return node
@classmethod
def create_category_tree_nodes(cls, pid, counts=None, show_empty=True, show_count=True):
nodes = []
categories = const.AppType.category_types_mapper().keys()
for category in categories:
if not settings.XPACK_ENABLED and const.AppCategory.is_xpack(category):
continue
i = cls.create_tree_id(pid, 'category', category.value)
node = cls.create_choice_node(
category, i, pid=pid, tp='category',
counts=counts, opened=False, show_empty=show_empty,
show_count=show_count
)
if not node:
continue
nodes.append(node)
return nodes
@classmethod
def create_types_tree_nodes(cls, pid, counts, show_empty=True, show_count=True):
nodes = []
temp_pid = pid
type_category_mapper = const.AppType.type_category_mapper()
types = const.AppType.type_category_mapper().keys()
for tp in types:
if not settings.XPACK_ENABLED and const.AppType.is_xpack(tp):
continue
category = type_category_mapper.get(tp)
pid = cls.create_tree_id(pid, 'category', category.value)
i = cls.create_tree_id(pid, 'type', tp.value)
node = cls.create_choice_node(
tp, i, pid, tp='type', counts=counts, opened=False,
show_empty=show_empty, show_count=show_count
)
pid = temp_pid
if not node:
continue
nodes.append(node)
return nodes
@staticmethod
def get_tree_node_counts(queryset):
counts = defaultdict(int)
values = queryset.values_list('type', 'category')
for i in values:
tp = i[0]
category = i[1]
counts[tp] += 1
counts[category] += 1
return counts
@classmethod
def create_category_type_tree_nodes(cls, queryset, pid, show_empty=True, show_count=True):
counts = cls.get_tree_node_counts(queryset)
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:
root_node = cls.create_root_tree_node(queryset, show_count=show_count)
tree_nodes.append(root_node)
tree_nodes += cls.create_category_type_tree_nodes(
queryset, root_node.id, show_empty=show_empty, show_count=show_count
)
# 应用的节点
for app in queryset:
if not settings.XPACK_ENABLED and const.AppType.is_xpack(app.type):
continue
node = app.as_tree_node(root_node.id)
tree_nodes.append(node)
return tree_nodes
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, k8s_as_tree=False):
if self.type == const.AppType.k8s and k8s_as_tree:
node = KubernetesTree(pid).as_tree_node(self)
else:
node = self._as_tree_node(pid)
return node
def _attrs_to_tree(self):
if self.category == const.AppCategory.db:
return self.attrs
return {}
def _as_tree_node(self, pid):
icon_skin_category_mapper = {
'remote_app': 'chrome',
'db': 'database',
'cloud': 'cloud'
}
icon_skin = icon_skin_category_mapper.get(self.category, 'file')
pid = self.create_app_tree_pid(pid)
node = TreeNode(**{
'id': str(self.id),
'name': self.name,
'title': self.name,
'pId': pid,
'isParent': False,
'open': False,
'iconSkin': icon_skin,
'meta': {
'type': 'application',
'data': {
'category': self.category,
'type': self.type,
'attrs': self._attrs_to_tree()
}
}
})
return node

View File

@ -1,4 +1,5 @@
from .mixin import *
from .platform import *
from .admin_user import *
from .asset import *
from .label import *

View File

@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
#
from rest_framework.viewsets import ModelViewSet
from rest_framework.generics import RetrieveAPIView, ListAPIView
from rest_framework.decorators import action
from django.shortcuts import get_object_or_404
from django.db.models import Q
@ -27,8 +27,7 @@ from ..filters import FilterAssetByNodeFilterBackend, LabelFilterBackend, IpInFi
logger = get_logger(__file__)
__all__ = [
'AssetViewSet', 'AssetPlatformRetrieveApi',
'AssetGatewayListApi', 'AssetPlatformViewSet',
'AssetTaskCreateApi', 'AssetsTaskCreateApi',
'AssetGatewayListApi', 'AssetTaskCreateApi', 'AssetsTaskCreateApi',
'AssetPermUserListApi', 'AssetPermUserPermissionsListApi',
'AssetPermUserGroupListApi', 'AssetPermUserGroupPermissionsListApi',
]
@ -52,7 +51,8 @@ class AssetViewSet(SuggestionMixin, FilterAssetByNodeMixin, OrgBulkModelViewSet)
ordering = ('hostname', )
serializer_classes = {
'default': serializers.AssetSerializer,
'suggestion': serializers.MiniAssetSerializer
'suggestion': serializers.MiniAssetSerializer,
'platform': serializers.PlatformSerializer
}
rbac_perms = {
'match': 'assets.match_asset'
@ -74,6 +74,10 @@ class AssetViewSet(SuggestionMixin, FilterAssetByNodeMixin, OrgBulkModelViewSet)
assets = serializer.save()
self.set_assets_node(assets)
@action(methods='GET', detail=True, url_path='platform')
def platform(self, request, *args, **kwargs):
pass
class AssetPlatformRetrieveApi(RetrieveAPIView):
queryset = Platform.objects.all()
@ -88,20 +92,6 @@ class AssetPlatformRetrieveApi(RetrieveAPIView):
return asset.platform
class AssetPlatformViewSet(ModelViewSet):
queryset = Platform.objects.all()
serializer_class = serializers.PlatformSerializer
filterset_fields = ['name', 'base']
search_fields = ['name']
def check_object_permissions(self, request, obj):
if request.method.lower() in ['delete', 'put', 'patch'] and obj.internal:
self.permission_denied(
request, message={"detail": "Internal platform"}
)
return super().check_object_permissions(request, obj)
class AssetsTaskMixin:
def perform_assets_task(self, serializer):
@ -246,7 +236,7 @@ class AssetPermUserGroupListApi(BaseAssetPermUserOrUserGroupListApi):
return user_groups
class BaseAssetPermUserOrUserGroupPermissionsListApiMixin(generics.ListAPIView):
class BasePermedAssetListApi(generics.ListAPIView):
model = AssetPermission
serializer_class = AssetPermissionSerializer
filterset_class = AssetPermissionFilter
@ -272,7 +262,7 @@ class BaseAssetPermUserOrUserGroupPermissionsListApiMixin(generics.ListAPIView):
return queryset
class AssetPermUserPermissionsListApi(BaseAssetPermUserOrUserGroupPermissionsListApiMixin):
class AssetPermUserPermissionsListApi(BasePermedAssetListApi):
def filter_queryset(self, queryset):
queryset = super().filter_queryset(queryset)
queryset = self.filter_user_related(queryset)
@ -291,7 +281,7 @@ class AssetPermUserPermissionsListApi(BaseAssetPermUserOrUserGroupPermissionsLis
return user
class AssetPermUserGroupPermissionsListApi(BaseAssetPermUserOrUserGroupPermissionsListApiMixin):
class AssetPermUserGroupPermissionsListApi(BasePermedAssetListApi):
def filter_queryset(self, queryset):
queryset = super().filter_queryset(queryset)
queryset = self.filter_user_group_related(queryset)

View File

@ -0,0 +1,21 @@
from rest_framework.viewsets import ModelViewSet
from assets.models import Platform
from assets.serializers import PlatformSerializer
__all__ = ['AssetPlatformViewSet']
class AssetPlatformViewSet(ModelViewSet):
queryset = Platform.objects.all()
serializer_class = PlatformSerializer
filterset_fields = ['name', 'base']
search_fields = ['name']
def check_object_permissions(self, request, obj):
if request.method.lower() in ['delete', 'put', 'patch'] and obj.internal:
self.permission_denied(
request, message={"detail": "Internal platform"}
)
return super().check_object_permissions(request, obj)

View File

@ -17,6 +17,6 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='asset',
name='cluster',
field=models.ForeignKey(default=assets.models.asset.default_cluster, on_delete=django.db.models.deletion.SET_DEFAULT, related_name='assets', to='assets.Cluster', verbose_name='Cluster'),
field=models.ForeignKey(default=assets.models.default_cluster, on_delete=django.db.models.deletion.SET_DEFAULT, related_name='assets', to='assets.Cluster', verbose_name='Cluster'),
),
]

View File

@ -50,7 +50,7 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='asset',
name='nodes',
field=models.ManyToManyField(default=assets.models.asset.default_node, related_name='assets', to='assets.Node', verbose_name='Nodes'),
field=models.ManyToManyField(default=assets.models.default_node, related_name='assets', to='assets.Node', verbose_name='Nodes'),
),
migrations.AddField(
model_name='systemuser',

View File

@ -34,7 +34,7 @@ class Migration(migrations.Migration):
model_name='asset',
name='platform',
field=models.ForeignKey(
default=assets.models.asset.Platform.default,
default=assets.models.Platform.default,
on_delete=django.db.models.deletion.PROTECT,
related_name='assets', to='assets.Platform',
verbose_name='Platform'),

View File

@ -0,0 +1,20 @@
# Generated by Django 3.1.14 on 2022-03-30 10:35
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('assets', '0089_auto_20220310_0616'),
]
operations = [
migrations.CreateModel(
name='Host',
fields=[
('asset_ptr', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, primary_key=True, serialize=False, to='assets.asset')),
],
),
]

View File

@ -0,0 +1,40 @@
# Generated by Django 3.1.14 on 2022-04-01 07:58
from django.db import migrations, models
import django.db.models.deletion
def migrate_to_host(apps, schema_editor):
asset_model = apps.get_model("assets", "Asset")
host_model = apps.get_model("assets", 'Host')
db_alias = schema_editor.connection.alias
created = 0
batch_size = 1000
while True:
start = created
end = created + batch_size
assets = asset_model.objects.using(db_alias).all()[start:end]
if not assets:
break
hosts = [host_model(asset_ptr=asset) for asset in assets]
host_model.objects.using(db_alias).bulk_create(hosts, ignore_conflicts=True)
created += len(hosts)
class Migration(migrations.Migration):
dependencies = [
('assets', '0090_add_host'),
]
operations = [
migrations.AlterField(
model_name='host',
name='asset_ptr',
field=models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='assets.asset'),
),
migrations.RunPython(migrate_to_host)
]

View File

@ -0,0 +1,42 @@
# Generated by Django 3.1.14 on 2022-04-02 09:09
from django.db import migrations, models
import django.db.models.deletion
import uuid
class Migration(migrations.Migration):
dependencies = [
('assets', '0091_auto_20220401_1558'),
]
operations = [
migrations.CreateModel(
name='HostInfo',
fields=[
('vendor', models.CharField(blank=True, max_length=64, null=True, verbose_name='Vendor')),
('model', models.CharField(blank=True, max_length=54, null=True, verbose_name='Model')),
('sn', models.CharField(blank=True, max_length=128, null=True, verbose_name='Serial number')),
('cpu_model', models.CharField(blank=True, max_length=64, null=True, verbose_name='CPU model')),
('cpu_count', models.IntegerField(null=True, verbose_name='CPU count')),
('cpu_cores', models.IntegerField(null=True, verbose_name='CPU cores')),
('cpu_vcpus', models.IntegerField(null=True, verbose_name='CPU vcpus')),
('memory', models.CharField(blank=True, max_length=64, null=True, verbose_name='Memory')),
('disk_total', models.CharField(blank=True, max_length=1024, null=True, verbose_name='Disk total')),
('disk_info', models.CharField(blank=True, max_length=1024, null=True, verbose_name='Disk info')),
('os', models.CharField(blank=True, max_length=128, null=True, verbose_name='OS')),
('os_version', models.CharField(blank=True, max_length=16, null=True, verbose_name='OS version')),
('os_arch', models.CharField(blank=True, max_length=16, null=True, verbose_name='OS arch')),
('hostname_raw', models.CharField(blank=True, max_length=128, null=True, verbose_name='Hostname raw')),
('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)),
('created_by', models.CharField(blank=True, max_length=32, null=True, verbose_name='Created by')),
('date_created', models.DateTimeField(auto_now_add=True, null=True, verbose_name='Date created')),
('date_updated', models.DateTimeField(auto_now=True, verbose_name='Date updated')),
('host', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='assets.host', related_name='info', verbose_name='Host')),
],
options={
'verbose_name': 'HostInfo',
},
),
]

View File

@ -0,0 +1,51 @@
# Generated by Django 3.1.14 on 2022-04-02 08:27
from django.utils import timezone
from django.db import migrations
def migrate_hardware(apps, *args):
host_model = apps.get_model('assets', 'Host')
asset_model = apps.get_model('assets', 'Asset')
hardware_model = apps.get_model('assets', 'HostInfo')
created = 0
batch_size = 1000
excludes = ['id', 'host', 'date_updated']
fields = [f.name for f in hardware_model._meta.fields]
fields = [name for name in fields if name not in excludes]
while True:
start = created
end = created + batch_size
hosts = host_model.objects.all()[start:end]
asset_ids = [h.asset_ptr_id for h in hosts]
assets = asset_model.objects.filter(id__in=asset_ids)
asset_mapper = {a.id: a for a in assets}
if not hosts:
break
hardware_list = []
for host in hosts:
hardware = hardware_model()
asset = asset_mapper[host.asset_ptr_id]
hardware.host = host
hardware.date_updated = timezone.now()
for name in fields:
setattr(hardware, name, getattr(asset, name))
hardware_list.append(hardware)
hardware_model.objects.bulk_create(hardware_list, ignore_conflicts=True)
created += len(hardware_list)
class Migration(migrations.Migration):
dependencies = [
('assets', '0092_hardware'),
]
operations = [
migrations.RunPython(migrate_hardware)
]

View File

@ -0,0 +1,69 @@
# Generated by Django 3.1.14 on 2022-04-02 09:36
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('assets', '0093_auto_20220403_1627'),
]
operations = [
migrations.RemoveField(
model_name='asset',
name='cpu_cores',
),
migrations.RemoveField(
model_name='asset',
name='cpu_count',
),
migrations.RemoveField(
model_name='asset',
name='cpu_model',
),
migrations.RemoveField(
model_name='asset',
name='cpu_vcpus',
),
migrations.RemoveField(
model_name='asset',
name='disk_info',
),
migrations.RemoveField(
model_name='asset',
name='disk_total',
),
migrations.RemoveField(
model_name='asset',
name='hostname_raw',
),
migrations.RemoveField(
model_name='asset',
name='memory',
),
migrations.RemoveField(
model_name='asset',
name='model',
),
migrations.RemoveField(
model_name='asset',
name='os',
),
migrations.RemoveField(
model_name='asset',
name='os_arch',
),
migrations.RemoveField(
model_name='asset',
name='os_version',
),
migrations.RemoveField(
model_name='asset',
name='sn',
),
migrations.RemoveField(
model_name='asset',
name='vendor',
),
]

View File

@ -1,4 +1,5 @@
from .base import *
from .platform import *
from .asset import *
from .label import Label
from .user import *

View File

@ -0,0 +1,2 @@
from .common import *
from .host import *

View File

View File

@ -11,18 +11,17 @@ from django.db import models
from django.utils.translation import ugettext_lazy as _
from rest_framework.exceptions import ValidationError
from common.fields.model import JsonDictTextField
from common.utils import lazyproperty
from orgs.mixins.models import OrgModelMixin, OrgManager
from ..platform import Platform
from ..base import AbsConnectivity
from .base import AbsConnectivity
__all__ = ['Asset', 'ProtocolsMixin', 'Platform', 'AssetQuerySet']
__all__ = ['Asset', 'ProtocolsMixin', 'AssetQuerySet', 'default_node', 'default_cluster']
logger = logging.getLogger(__name__)
def default_cluster():
from .cluster import Cluster
from assets.models import Cluster
name = "Default"
defaults = {"name": name}
cluster, created = Cluster.objects.get_or_create(
@ -33,7 +32,7 @@ def default_cluster():
def default_node():
try:
from .node import Node
from assets.models import Node
root = Node.org_root()
return Node.objects.filter(id=root.id)
except:
@ -106,7 +105,7 @@ class NodesRelationMixin:
_all_nodes_keys = None
def get_nodes(self):
from .node import Node
from assets.models import Node
nodes = self.nodes.all()
if not nodes:
nodes = Node.objects.filter(id=Node.org_root().id)
@ -122,104 +121,25 @@ class NodesRelationMixin:
return nodes
class Platform(models.Model):
CHARSET_CHOICES = (
('utf8', 'UTF-8'),
('gbk', 'GBK'),
)
BASE_CHOICES = (
('Linux', 'Linux'),
('Unix', 'Unix'),
('MacOS', 'MacOS'),
('BSD', 'BSD'),
('Windows', 'Windows'),
('Other', 'Other'),
)
name = models.SlugField(verbose_name=_("Name"), unique=True, allow_unicode=True)
base = models.CharField(choices=BASE_CHOICES, max_length=16, default='Linux', verbose_name=_("Base"))
charset = models.CharField(default='utf8', choices=CHARSET_CHOICES, max_length=8, verbose_name=_("Charset"))
meta = JsonDictTextField(blank=True, null=True, verbose_name=_("Meta"))
internal = models.BooleanField(default=False, verbose_name=_("Internal"))
comment = models.TextField(blank=True, null=True, verbose_name=_("Comment"))
@classmethod
def default(cls):
linux, created = cls.objects.get_or_create(
defaults={'name': 'Linux'}, name='Linux'
)
return linux.id
def is_windows(self):
return self.base.lower() in ('windows',)
def is_unixlike(self):
return self.base.lower() in ("linux", "unix", "macos", "bsd")
def __str__(self):
return self.name
class Meta:
verbose_name = _("Platform")
# ordering = ('name',)
class AbsHardwareInfo(models.Model):
# Collect
vendor = models.CharField(max_length=64, null=True, blank=True, verbose_name=_('Vendor'))
model = models.CharField(max_length=54, null=True, blank=True, verbose_name=_('Model'))
sn = models.CharField(max_length=128, null=True, blank=True, verbose_name=_('Serial number'))
cpu_model = models.CharField(max_length=64, null=True, blank=True, verbose_name=_('CPU model'))
cpu_count = models.IntegerField(null=True, verbose_name=_('CPU count'))
cpu_cores = models.IntegerField(null=True, verbose_name=_('CPU cores'))
cpu_vcpus = models.IntegerField(null=True, verbose_name=_('CPU vcpus'))
memory = models.CharField(max_length=64, null=True, blank=True, verbose_name=_('Memory'))
disk_total = models.CharField(max_length=1024, null=True, blank=True, verbose_name=_('Disk total'))
disk_info = models.CharField(max_length=1024, null=True, blank=True, verbose_name=_('Disk info'))
os = models.CharField(max_length=128, null=True, blank=True, verbose_name=_('OS'))
os_version = models.CharField(max_length=16, null=True, blank=True, verbose_name=_('OS version'))
os_arch = models.CharField(max_length=16, blank=True, null=True, verbose_name=_('OS arch'))
hostname_raw = models.CharField(max_length=128, blank=True, null=True, verbose_name=_('Hostname raw'))
class Meta:
abstract = True
@property
def cpu_info(self):
info = ""
if self.cpu_model:
info += self.cpu_model
if self.cpu_count and self.cpu_cores:
info += "{}*{}".format(self.cpu_count, self.cpu_cores)
return info
@property
def hardware_info(self):
if self.cpu_count:
return '{} Core {} {}'.format(
self.cpu_vcpus or self.cpu_count * self.cpu_cores,
self.memory, self.disk_total
)
else:
return ''
class Asset(AbsConnectivity, AbsHardwareInfo, ProtocolsMixin, NodesRelationMixin, OrgModelMixin):
class Asset(AbsConnectivity, ProtocolsMixin, NodesRelationMixin, OrgModelMixin):
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
ip = models.CharField(max_length=128, verbose_name=_('IP'), db_index=True)
hostname = models.CharField(max_length=128, verbose_name=_('Hostname'))
ip = models.CharField(max_length=128, verbose_name=_('IP'), db_index=True)
protocol = models.CharField(max_length=128, default=ProtocolsMixin.Protocol.ssh,
choices=ProtocolsMixin.Protocol.choices, verbose_name=_('Protocol'))
port = models.IntegerField(default=22, verbose_name=_('Port'))
protocols = models.CharField(max_length=128, default='ssh/22', blank=True, verbose_name=_("Protocols"))
platform = models.ForeignKey(Platform, default=Platform.default, on_delete=models.PROTECT, verbose_name=_("Platform"), related_name='assets')
domain = models.ForeignKey("assets.Domain", null=True, blank=True, related_name='assets', verbose_name=_("Domain"), on_delete=models.SET_NULL)
nodes = models.ManyToManyField('assets.Node', default=default_node, related_name='assets', verbose_name=_("Nodes"))
platform = models.ForeignKey(Platform, default=Platform.default, on_delete=models.PROTECT,
verbose_name=_("Platform"), related_name='assets')
domain = models.ForeignKey("assets.Domain", null=True, blank=True, related_name='assets',
verbose_name=_("Domain"), on_delete=models.SET_NULL)
nodes = models.ManyToManyField('assets.Node', default=default_node, related_name='assets',
verbose_name=_("Nodes"))
is_active = models.BooleanField(default=True, verbose_name=_('Is active'))
# Auth
admin_user = models.ForeignKey('assets.SystemUser', on_delete=models.SET_NULL, null=True, verbose_name=_("Admin user"), related_name='admin_assets')
admin_user = models.ForeignKey('assets.SystemUser', on_delete=models.SET_NULL, null=True,
verbose_name=_("Admin user"), related_name='admin_assets')
# Some information
public_ip = models.CharField(max_length=128, blank=True, null=True, verbose_name=_('Public IP'))
@ -236,7 +156,7 @@ class Asset(AbsConnectivity, AbsHardwareInfo, ProtocolsMixin, NodesRelationMixin
return '{0.hostname}({0.ip})'.format(self)
def set_admin_user_relation(self):
from .authbook import AuthBook
from assets.models import AuthBook
if not self.admin_user:
return
if self.admin_user.type != 'admin':
@ -333,7 +253,7 @@ class Asset(AbsConnectivity, AbsHardwareInfo, ProtocolsMixin, NodesRelationMixin
return names
def as_node(self):
from .node import Node
from assets.models import Node
fake_node = Node()
fake_node.id = self.id
fake_node.key = self.id
@ -372,7 +292,7 @@ class Asset(AbsConnectivity, AbsHardwareInfo, ProtocolsMixin, NodesRelationMixin
return tree_node
def get_all_system_users(self):
from .user import SystemUser
from assets.models import SystemUser
system_user_ids = SystemUser.assets.through.objects.filter(asset=self)\
.values_list('systemuser_id', flat=True)
system_users = SystemUser.objects.filter(id__in=system_user_ids)

View File

@ -0,0 +1,9 @@
from django.db import models
from django.utils.translation import ugettext_lazy as _
from .common import Asset
class Database(Asset):
database = models.CharField(max_length=1024, verbose_name=_("Database"), blank=True)

View File

@ -0,0 +1,56 @@
from django.utils.translation import gettext_lazy as _
from django.db import models
from common.mixins.models import CommonModelMixin
from .common import Asset
class Host(Asset):
pass
class HostInfo(CommonModelMixin):
host = models.OneToOneField(Host, related_name='info', on_delete=models.CASCADE,
verbose_name=_("Host"), unique=True)
# Collect
vendor = models.CharField(max_length=64, null=True, blank=True, verbose_name=_('Vendor'))
model = models.CharField(max_length=54, null=True, blank=True, verbose_name=_('Model'))
sn = models.CharField(max_length=128, null=True, blank=True, verbose_name=_('Serial number'))
cpu_model = models.CharField(max_length=64, null=True, blank=True, verbose_name=_('CPU model'))
cpu_count = models.IntegerField(null=True, verbose_name=_('CPU count'))
cpu_cores = models.IntegerField(null=True, verbose_name=_('CPU cores'))
cpu_vcpus = models.IntegerField(null=True, verbose_name=_('CPU vcpus'))
memory = models.CharField(max_length=64, null=True, blank=True, verbose_name=_('Memory'))
disk_total = models.CharField(max_length=1024, null=True, blank=True, verbose_name=_('Disk total'))
disk_info = models.CharField(max_length=1024, null=True, blank=True, verbose_name=_('Disk info'))
os = models.CharField(max_length=128, null=True, blank=True, verbose_name=_('OS'))
os_version = models.CharField(max_length=16, null=True, blank=True, verbose_name=_('OS version'))
os_arch = models.CharField(max_length=16, blank=True, null=True, verbose_name=_('OS arch'))
hostname_raw = models.CharField(max_length=128, blank=True, null=True, verbose_name=_('Hostname raw'))
@property
def cpu_info(self):
info = ""
if self.cpu_model:
info += self.cpu_model
if self.cpu_count and self.cpu_cores:
info += "{}*{}".format(self.cpu_count, self.cpu_cores)
return info
@property
def hardware_info(self):
if self.cpu_count:
return '{} Core {} {}'.format(
self.cpu_vcpus or self.cpu_count * self.cpu_cores,
self.memory, self.disk_total
)
else:
return ''
def __str__(self):
return '{} of {}'.format(self.hardware_info, self.host.hostname)
class Meta:
verbose_name = _("HostInfo")

View File

View File

View File

@ -0,0 +1,56 @@
from django.db import models
from django.utils.translation import gettext_lazy as _
from common.fields.model import JsonDictTextField
__all__ = ['Platform']
class Category(models.TextChoices):
Host = 'host', _('Host')
Network = 'network', _('Network device')
Database = 'database', _('Database')
RemoteApp = 'remote_app', _('Microsoft remote app')
Cloud = 'cloud', _("Cloud")
class Platform(models.Model):
CHARSET_CHOICES = (
('utf8', 'UTF-8'),
('gbk', 'GBK'),
)
BASE_CHOICES = (
('Linux', 'Linux'),
('Unix', 'Unix'),
('MacOS', 'MacOS'),
('BSD', 'BSD'),
('Windows', 'Windows'),
('Other', 'Other'),
)
name = models.SlugField(verbose_name=_("Name"), unique=True, allow_unicode=True)
base = models.CharField(choices=BASE_CHOICES, max_length=16, default='Linux', verbose_name=_("Base"))
charset = models.CharField(default='utf8', choices=CHARSET_CHOICES, max_length=8, verbose_name=_("Charset"))
meta = JsonDictTextField(blank=True, null=True, verbose_name=_("Meta"))
internal = models.BooleanField(default=False, verbose_name=_("Internal"))
comment = models.TextField(blank=True, null=True, verbose_name=_("Comment"))
@classmethod
def default(cls):
linux, created = cls.objects.get_or_create(
defaults={'name': 'Linux'}, name='Linux'
)
return linux.id
def is_windows(self):
return self.base.lower() in ('windows',)
def is_unixlike(self):
return self.base.lower() in ("linux", "unix", "macos", "bsd")
def __str__(self):
return self.name
class Meta:
verbose_name = _("Platform")
# ordering = ('name',)

View File

@ -0,0 +1 @@
from .common import *

View File

@ -5,7 +5,7 @@ from django.core.validators import RegexValidator
from django.utils.translation import ugettext_lazy as _
from orgs.mixins.serializers import BulkOrgResourceModelSerializer
from ..models import Asset, Node, Platform, SystemUser
from ...models import Asset, Node, Platform, SystemUser
__all__ = [
'AssetSerializer', 'AssetSimpleSerializer', 'MiniAssetSerializer',
@ -82,12 +82,6 @@ class AssetSerializer(BulkOrgResourceModelSerializer):
'protocol', 'port', 'protocols', 'is_active',
'public_ip', 'number', 'comment',
]
fields_hardware = [
'vendor', 'model', 'sn', 'cpu_model', 'cpu_count',
'cpu_cores', 'cpu_vcpus', 'memory', 'disk_total', 'disk_info',
'os', 'os_version', 'os_arch', 'hostname_raw',
'cpu_info', 'hardware_info',
]
fields_fk = [
'domain', 'domain_display', 'platform', 'admin_user', 'admin_user_display'
]
@ -95,16 +89,13 @@ class AssetSerializer(BulkOrgResourceModelSerializer):
'nodes', 'nodes_display', 'labels', 'labels_display',
]
read_only_fields = [
'connectivity', 'date_verified', 'cpu_info', 'hardware_info',
'created_by', 'date_created',
'connectivity', 'date_verified', 'created_by', 'date_created',
]
fields = fields_small + fields_hardware + fields_fk + fields_m2m + read_only_fields
fields = fields_small + fields_fk + fields_m2m + read_only_fields
extra_kwargs = {
'protocol': {'write_only': True},
'port': {'write_only': True},
'hardware_info': {'label': _('Hardware info'), 'read_only': True},
'admin_user_display': {'label': _('Admin user display'), 'read_only': True},
'cpu_info': {'label': _('CPU info')},
}
def get_fields(self):

View File

@ -0,0 +1,19 @@
from rest_framework import serializers
from .common import AssetSerializer
from assets.models import HostInfo
class HardwareSerializer(serializers.ModelSerializer):
class Meta:
model = HostInfo
fields = [
'id', 'vendor', 'model', 'sn', 'cpu_model', 'cpu_count',
'cpu_cores', 'cpu_vcpus', 'memory', 'disk_total', 'disk_info',
'os', 'os_version', 'os_arch', 'hostname_raw',
'cpu_info', 'hardware_info', 'date_updated'
]
class HostSerializer(AssetSerializer):
hardware_info = HardwareSerializer(read_only=True)

View File

@ -2,31 +2,31 @@ from rest_framework.viewsets import GenericViewSet, ModelViewSet, ReadOnlyModelV
from rest_framework_bulk import BulkModelViewSet
from ..mixins.api import (
RelationMixin, AllowBulkDestroyMixin, CommonMixin
RelationMixin, AllowBulkDestroyMixin, CommonApiMixin
)
class JMSGenericViewSet(CommonMixin, GenericViewSet):
class JMSGenericViewSet(CommonApiMixin, GenericViewSet):
pass
class JMSViewSet(CommonMixin, ViewSet):
class JMSViewSet(CommonApiMixin, ViewSet):
pass
class JMSModelViewSet(CommonMixin, ModelViewSet):
class JMSModelViewSet(CommonApiMixin, ModelViewSet):
pass
class JMSReadOnlyModelViewSet(CommonMixin, ReadOnlyModelViewSet):
class JMSReadOnlyModelViewSet(CommonApiMixin, ReadOnlyModelViewSet):
pass
class JMSBulkModelViewSet(CommonMixin, AllowBulkDestroyMixin, BulkModelViewSet):
class JMSBulkModelViewSet(CommonApiMixin, AllowBulkDestroyMixin, BulkModelViewSet):
pass
class JMSBulkRelationModelViewSet(CommonMixin,
class JMSBulkRelationModelViewSet(CommonApiMixin,
RelationMixin,
AllowBulkDestroyMixin,
BulkModelViewSet):

View File

@ -13,7 +13,7 @@ from .queryset import QuerySetMixin
__all__ = [
'CommonApiMixin', 'PaginatedResponseMixin', 'RelationMixin', 'CommonMixin'
'CommonApiMixin', 'PaginatedResponseMixin', 'RelationMixin',
]
@ -82,15 +82,10 @@ class RelationMixin:
self.send_m2m_changed_signal(instance, 'post_remove')
class CommonApiMixin(SerializerMixin, ExtraFilterFieldsMixin, RenderToJsonMixin):
pass
class CommonMixin(SerializerMixin,
QuerySetMixin,
ExtraFilterFieldsMixin,
RenderToJsonMixin):
class CommonApiMixin(SerializerMixin, ExtraFilterFieldsMixin,
QuerySetMixin, RenderToJsonMixin):
pass

1
apps/xpack.bak Submodule

@ -0,0 +1 @@
Subproject commit 244ace5a95503ffaf41b73037692e1121f7c066f