Commit b97d5b09 authored by ibuler's avatar ibuler

[Feature] assets task 修改

parent 08e17884
# -*- coding: utf-8 -*-
#
ADMIN_USER_CONN_CACHE_KEY_PREFIX = "ADMIN_USER_CONN_"
SYSTEM_USER_CONN_CACHE_KEY_PREFIX = "SYSTEM_USER_CONN_"
PUSH_SYSTEM_USER_PERIOD_LOCK_KEY = "PUSH_SYSTEM_USER_PERIOD_KEY"
PUSH_SYSTEM_USER_PERIOD_TASK_NAME = "PUSH-SYSTEM-USER-PERIOD"
PUSH_SYSTEM_USER_TASK_NAME = "PUSH-SYSTEM-USER-TO-CLUSTER-{}"
PUSH_SYSTEM_USER_LOCK_KEY = "PUSH_SYSTEM_USER_TO_CLUSTER_LOCK_{}"
UPDATE_ASSETS_HARDWARE_TASK_NAME = 'UPDATE-ASSETS-HARDWARE-INFO'
UPDATE_ASSETS_HARDWARE_PERIOD_LOCK_KEY = "UPDATE_ASSETS_HARDWARE_PERIOD_LOCK_KEY"
TEST_ADMIN_USER_CONNECTABILITY_PEROID_KEY = "TEST_ADMIN_USER_CONNECTABILITY_KEY"
TEST_SYSTEM_USER_CONNECTABILITY_PEROID_KEY = "TEST_SYSTEM_USER_CONNECTABILITY_PEROID_KEY"
PUSH_SYSTEM_USER_PERIOD_KEY = "PUSH_SYSTEM_USER_PERIOD_KEY"
UPDATE_ASSETS_HARDWARE_PERIOD_TASK_NAME = 'UPDATE-ASSETS-HARDWARE-INFO-PERIOD'
UPDATE_ASSETS_HARDWARE_TASKS = [
{
'name': UPDATE_ASSETS_HARDWARE_TASK_NAME,
'action': {
'module': 'setup'
}
}
]
TEST_ADMIN_USER_CONN_PERIOD_LOCK_KEY = "TEST_ADMIN_USER_CONN_PERIOD_KEY"
TEST_ADMIN_USER_CONN_PERIOD_TASK_NAME = "TEST_ADMIN_USER_CONN_PERIOD_TASK"
TEST_ADMIN_USER_CONN_TASK_NAME = "TEST-ADMIN-USER-CONN-{}"
TEST_ADMIN_USER_CONN_LOCK_KEY = TEST_ADMIN_USER_CONN_TASK_NAME
ADMIN_USER_CONN_CACHE_KEY = "ADMIN_USER_CONN_{}"
TEST_ADMIN_USER_CONN_TASKS = [
{
"name": "TEST_ADMIN_CONNECTIVE",
"action": {
"module": "ping",
}
}
]
ASSET_ADMIN_CONN_CACHE_KEY = "ASSET_ADMIN_USER_CONN_{}"
TEST_ASSET_CONN_TASK_NAME = "ASSET_CONN_TEST_MANUAL"
TEST_SYSTEM_USER_CONN_PERIOD_LOCK_KEY = "TEST_SYSTEM_USER_CONN_PERIOD_KEY"
TEST_SYSTEM_USER_CONN_PERIOD_TASK_NAME = "TEST-SYSTEM-USER-CONN-PERIOD-TASK"
TEST_SYSTEM_USER_CONN_CACHE_KEY_PREFIX = "SYSTEM_USER_CONN_"
TEST_SYSTEM_USER_CONN_TASK_NAME = "TEST-ADMIN-USER-CONN-{}"
TEST_SYSTEM_USER_CONN_LOCK_KEY = "TEST_SYSTEM_USER_CONN_{}"
SYSTEM_USER_CONN_CACHE_KEY = "SYSTEM_USER_CONN_{}"
TEST_SYSTEM_USER_CONN_TASKS = [
{
"name": "TEST_SYSTEM_USER_CONNECTIVE",
"action": {
"module": "ping",
}
}
]
TASK_OPTIONS = {
'timeout': 60,
'forks': 10,
}
......@@ -9,7 +9,7 @@ from django.db import models
from django.utils.translation import ugettext_lazy as _
from django.core.cache import cache
from ..const import ADMIN_USER_CONN_CACHE_KEY_PREFIX
from ..const import ASSET_ADMIN_CONN_CACHE_KEY
from .cluster import Cluster
from .group import AssetGroup
from .user import AdminUser, SystemUser
......@@ -110,7 +110,7 @@ class Asset(models.Model):
@property
def is_connective(self):
val = cache.get(ADMIN_USER_CONN_CACHE_KEY_PREFIX + self.hostname)
val = cache.get(ASSET_ADMIN_CONN_CACHE_KEY.format(self.hostname))
if val == 1:
return True
else:
......
......@@ -5,7 +5,7 @@ from rest_framework_bulk.serializers import BulkListSerializer
from common.mixins import BulkSerializerMixin
from .models import AssetGroup, Asset, Cluster, AdminUser, SystemUser
from .tasks import SYSTEM_USER_CONN_CACHE_KEY_PREFIX, ADMIN_USER_CONN_CACHE_KEY_PREFIX
from .const import ADMIN_USER_CONN_CACHE_KEY, SYSTEM_USER_CONN_CACHE_KEY
class AssetGroupSerializer(BulkSerializerMixin, serializers.ModelSerializer):
......@@ -73,7 +73,7 @@ class AdminUserSerializer(serializers.ModelSerializer):
@staticmethod
def get_unreachable_amount(obj):
data = cache.get(ADMIN_USER_CONN_CACHE_KEY_PREFIX + obj.name)
data = cache.get(ADMIN_USER_CONN_CACHE_KEY.format(obj.name))
if data:
return len(data.get('dark'))
else:
......@@ -98,7 +98,7 @@ class SystemUserSerializer(serializers.ModelSerializer):
@staticmethod
def get_unreachable_amount(obj):
data = cache.get(SYSTEM_USER_CONN_CACHE_KEY_PREFIX + obj.name)
data = cache.get(SYSTEM_USER_CONN_CACHE_KEY.format(obj.name))
if data:
return len(data.get('dark'))
else:
......
This diff is collapsed.
......@@ -9,24 +9,22 @@ import chardet
from io import StringIO
from django.conf import settings
from django.core.exceptions import ImproperlyConfigured, FieldDoesNotExist
from django.utils.translation import ugettext_lazy as _
from django.views.generic import TemplateView, ListView, View
from django.views.generic.edit import CreateView, DeleteView, FormView, UpdateView
from django.urls import reverse_lazy
from django.views.generic.detail import DetailView, SingleObjectMixin
from django.http import HttpResponse, JsonResponse, HttpResponseRedirect, Http404
from django.views.decorators.csrf import csrf_protect, csrf_exempt
from django.views.generic.detail import DetailView
from django.http import HttpResponse, JsonResponse
from django.views.decorators.csrf import csrf_exempt
from django.utils.decorators import method_decorator
from django.core.cache import cache
from django.utils import timezone
from django.contrib.auth.mixins import LoginRequiredMixin
from django.shortcuts import get_object_or_404, redirect, reverse
from django.shortcuts import redirect
from common.mixins import JSONResponseMixin
from common.utils import get_object_or_none
from common.imexp import ModelExportView
from common.utils import get_object_or_none, get_logger
from .. import forms
from ..models import Asset, AssetGroup, AdminUser, Cluster, SystemUser
from ..hands import AdminUserRequiredMixin
......@@ -39,6 +37,7 @@ __all__ = [
'AssetModalListView', 'AssetDeleteView', 'AssetExportView',
'BulkImportAssetView',
]
logger = get_logger(__file__)
class AssetListView(AdminUserRequiredMixin, TemplateView):
......@@ -48,12 +47,11 @@ class AssetListView(AdminUserRequiredMixin, TemplateView):
context = {
'app': 'Assets',
'action': 'Asset list',
'groups': AssetGroup.objects.all(),
# 'groups': AssetGroup.objects.all(),
'system_users': SystemUser.objects.all(),
# 'form': forms.AssetBulkUpdateForm(),
}
kwargs.update(context)
return super(AssetListView, self).get_context_data(**kwargs)
return super().get_context_data(**kwargs)
class UserAssetListView(LoginRequiredMixin, TemplateView):
......@@ -64,10 +62,9 @@ class UserAssetListView(LoginRequiredMixin, TemplateView):
'app': 'Assets',
'action': 'Asset list',
'system_users': SystemUser.objects.all(),
'default_pk': '00000000-0000-0000-0000-000000000000',
}
kwargs.update(context)
return super(UserAssetListView, self).get_context_data(**kwargs)
return super().get_context_data(**kwargs)
class AssetCreateView(AdminUserRequiredMixin, CreateView):
......@@ -107,7 +104,7 @@ class AssetModalListView(AdminUserRequiredMixin, ListView):
'assets': assets
}
kwargs.update(context)
return super(AssetModalListView, self).get_context_data(**kwargs)
return super().get_context_data(**kwargs)
class AssetBulkUpdateView(AdminUserRequiredMixin, ListView):
......@@ -128,7 +125,7 @@ class AssetBulkUpdateView(AdminUserRequiredMixin, ListView):
)
else:
self.form = self.form_class()
return super(AssetBulkUpdateView, self).get(request, *args, **kwargs)
return super().get(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
form = self.form_class(request.POST)
......@@ -148,7 +145,7 @@ class AssetBulkUpdateView(AdminUserRequiredMixin, ListView):
'assets': Asset.objects.all(),
}
kwargs.update(context)
return super(AssetBulkUpdateView, self).get_context_data(**kwargs)
return super().get_context_data(**kwargs)
class AssetUpdateView(AdminUserRequiredMixin, UpdateView):
......@@ -166,8 +163,8 @@ class AssetUpdateView(AdminUserRequiredMixin, UpdateView):
return super(AssetUpdateView, self).get_context_data(**kwargs)
def form_invalid(self, form):
print(form.errors)
return super(AssetUpdateView, self).form_invalid(form)
logger.error(form.errors)
return super().form_invalid(form)
class AssetDeleteView(AdminUserRequiredMixin, DeleteView):
......@@ -196,11 +193,46 @@ class AssetDetailView(DetailView):
@method_decorator(csrf_exempt, name='dispatch')
class AssetExportView(ModelExportView):
filename_prefix = 'jumpserver'
redirect_url = reverse_lazy('assets:asset-export')
model = Asset
fields = ('hostname', 'ip')
class AssetExportView(View):
def get(self, request):
spm = request.GET.get('spm', '')
assets_id_default = [Asset.objects.first().id] if Asset.objects.first() else [1]
assets_id = cache.get(spm, assets_id_default)
fields = [
field for field in Asset._meta.fields
if field.name not in [
'date_created'
]
]
filename = 'assets-{}.csv'.format(
timezone.localtime(timezone.now()).strftime('%Y-%m-%d_%H-%M-%S'))
response = HttpResponse(content_type='text/csv')
response['Content-Disposition'] = 'attachment; filename="%s"' % filename
response.write(codecs.BOM_UTF8)
assets = Asset.objects.filter(id__in=assets_id)
writer = csv.writer(response, dialect='excel',
quoting=csv.QUOTE_MINIMAL)
header = [field.verbose_name for field in fields]
header.append(_('Asset groups'))
writer.writerow(header)
for asset in assets:
groups = ','.join([group.name for group in asset.groups.all()])
data = [getattr(asset, field.name) for field in fields]
data.append(groups)
writer.writerow(data)
return response
def post(self, request, *args, **kwargs):
try:
assets_id = json.loads(request.body).get('assets_id', [])
except ValueError:
return HttpResponse('Json object not valid', status=400)
spm = uuid.uuid4().hex
cache.set(spm, assets_id, 300)
url = reverse_lazy('assets:asset-export') + '?spm=%s' % spm
return JsonResponse({'redirect': url})
class BulkImportAssetView(AdminUserRequiredMixin, JSONResponseMixin, FormView):
......@@ -305,4 +337,3 @@ class BulkImportAssetView(AdminUserRequiredMixin, JSONResponseMixin, FormView):
}
return self.render_json_response(data)
This diff is collapsed.
# ~*~ coding: utf-8 ~*~
from ansible.plugins.callback import CallbackBase
from ansible.plugins.callback.default import CallbackModule
class AdHocResultCallback(CallbackBase):
class AdHocResultCallback(CallbackModule):
"""
Task result Callback
"""
def __init__(self, display=None):
def __init__(self, display=None, options=None):
# result_raw example: {
# "ok": {"hostname": {"task_name": {},...},..},
# "failed": {"hostname": {"task_name": {}..}, ..},
......@@ -20,9 +21,10 @@ class AdHocResultCallback(CallbackBase):
# }
self.results_raw = dict(ok={}, failed={}, unreachable={}, skipped={})
self.results_summary = dict(contacted=[], dark={})
super().__init__(display)
super().__init__()
def gather_result(self, t, res):
self._clean_results(res._result, res._task.action)
host = res._host.get_name()
task_name = res.task_name
task_result = res._result
......@@ -49,15 +51,19 @@ class AdHocResultCallback(CallbackBase):
def v2_runner_on_failed(self, result, ignore_errors=False):
self.gather_result("failed", result)
super().v2_runner_on_failed(result, ignore_errors=ignore_errors)
def v2_runner_on_ok(self, result):
self.gather_result("ok", result)
super().v2_runner_on_ok(result)
def v2_runner_on_skipped(self, result):
self.gather_result("skipped", result)
super().v2_runner_on_skipped(result)
def v2_runner_on_unreachable(self, result):
self.gather_result("unreachable", result)
super().v2_runner_on_unreachable(result)
class CommandResultCallback(AdHocResultCallback):
......
......@@ -21,37 +21,62 @@ class Task(models.Model):
One task can have some versions of adhoc, run a task only run the latest version adhoc
"""
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
name = models.CharField(max_length=128, blank=True, verbose_name=_('Name'))
name = models.CharField(max_length=128, unique=True, verbose_name=_('Name'))
is_deleted = models.BooleanField(default=False)
created_by = models.CharField(max_length=128, blank=True, default='')
date_created = models.DateTimeField(auto_now_add=True)
__latest_adhoc = None
@property
def short_id(self):
return str(self.id).split('-')[-1]
def __str__(self):
return self.name
@property
def latest_adhoc(self):
if not self.__latest_adhoc:
self.__latest_adhoc = self.get_latest_adhoc()
return self.__latest_adhoc
def get_latest_adhoc(self):
return self.adhoc.all().order_by('date_created').last()
@latest_adhoc.setter
def latest_adhoc(self, item):
self.__latest_adhoc = item
def get_latest_history(self):
return self.get_latest_adhoc().get_latest_history()
@property
def latest_history(self):
try:
return self.history.all().latest()
except AdHocRunHistory.DoesNotExist:
return None
def get_all_run_history(self):
adhocs = self.adhoc.all()
return AdHocRunHistory.objects.filter(adhoc__in=adhocs)
def get_latest_adhoc(self):
try:
return self.adhoc.all().latest()
except AdHoc.DoesNotExist:
return None
def get_all_run_times(self):
history_all = self.get_all_run_history()
total = len(history_all)
success = len([history for history in history_all if history.is_success])
failed = len([history for history in history_all if not history.is_success])
@property
def history_summary(self):
history = self.get_run_history()
total = len(history)
success = len([history for history in history if history.is_success])
failed = len([history for history in history if not history.is_success])
return {'total': total, 'success': success, 'failed': failed}
def get_run_history(self):
return self.history.all()
def run(self):
if self.latest_adhoc:
return self.latest_adhoc.run()
else:
return {'error': 'No adhoc'}
def __str__(self):
return self.name
class Meta:
db_table = 'ops_task'
get_latest_by = 'date_created'
class AdHoc(models.Model):
......@@ -103,6 +128,10 @@ class AdHoc(models.Model):
else:
return {}
def run(self):
from .utils import run_adhoc_object
return run_adhoc_object(self, **self.options)
@become.setter
def become(self, item):
"""
......@@ -130,14 +159,31 @@ class AdHoc(models.Model):
def short_id(self):
return str(self.id).split('-')[-1]
def get_latest_history(self):
return self.history.all().order_by('date_start').last()
@property
def latest_history(self):
try:
return self.history.all().latest()
except AdHocRunHistory.DoesNotExist:
return None
def __str__(self):
return "{} of {}".format(self.task.name, self.short_id)
def __eq__(self, other):
if not isinstance(other, self.__class__):
return False
fields_check = []
for field in self.__class__._meta.fields:
if field.name not in ['id', 'date_created']:
fields_check.append(field)
for field in fields_check:
if getattr(self, field.name) != getattr(other, field.name):
return False
return True
class Meta:
db_table = "ops_adhoc"
get_latest_by = 'date_created'
class AdHocRunHistory(models.Model):
......@@ -145,7 +191,8 @@ class AdHocRunHistory(models.Model):
AdHoc running history.
"""
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
adhoc = models.ForeignKey(AdHoc, related_name='history', on_delete=models.CASCADE)
task = models.ForeignKey(Task, related_name='history', on_delete=models.SET_NULL, null=True)
adhoc = models.ForeignKey(AdHoc, related_name='history', on_delete=models.SET_NULL, null=True)
date_start = models.DateTimeField(auto_now_add=True, verbose_name=_('Start time'))
date_finished = models.DateTimeField(blank=True, null=True, verbose_name=_('End time'))
timedelta = models.FloatField(default=0.0, verbose_name=_('Time'), null=True)
......@@ -179,3 +226,4 @@ class AdHocRunHistory(models.Model):
class Meta:
db_table = "ops_adhoc_history"
get_latest_by = 'date_start'
......@@ -52,19 +52,19 @@
<td class="text-center"><input type="checkbox" class="cbx-term"> </td>
<td class="text-center"><a href="{% url 'ops:task-detail' pk=object.id %}">{{ object.name }}</a></td>
<td class="text-center">
<span class="text-danger">{{ object.get_all_run_times.failed }}</span>/<span class="text-navy">{{ object.get_all_run_times.success}}</span>/{{ object.get_all_run_times.total}}
<span class="text-danger">{{ object.history_summary.failed }}</span>/<span class="text-navy">{{ object.history_summary.success}}</span>/{{ object.history_summary.total}}
</td>
<td class="text-center">{{ object.adhoc.all | length}}</td>
<td class="text-center">{{ object.get_latest_adhoc.hosts | length}}</td>
<td class="text-center">{{ object.latest_adhoc.hosts | length}}</td>
<td class="text-center">
{% if object.get_latest_history.is_success %}
{% if object.latest_history.is_success %}
<i class="fa fa-check text-navy"></i>
{% else %}
<i class="fa fa-times text-danger"></i>
{% endif %}
</td>
<td class="text-center">{{ object.get_latest_history.date_start }}</td>
<td class="text-center">{{ object.get_latest_history.timedelta|floatformat }} s</td>
<td class="text-center">{{ object.latest_history.date_start }}</td>
<td class="text-center">{{ object.latest_history.timedelta|floatformat }} s</td>
<td class="text-center">
<a href="{% url 'ops:task-run' pk=object.id %}" class="btn btn-xs btn-info">{% trans "Run" %}</a>
<a data-uid="{{ object.uuid }}" class="btn btn-xs btn-danger btn-del">{% trans "Delete" %}</a>
......
......@@ -21,10 +21,9 @@ def is_uuid(s):
return False
def record_adhoc(func):
def _deco(adhoc, **options):
record = AdHocRunHistory(adhoc=adhoc)
record = AdHocRunHistory(adhoc=adhoc, task=adhoc.task)
time_start = time.time()
try:
result = func(adhoc, **options)
......@@ -86,7 +85,7 @@ def run_adhoc_object(adhoc, **options):
name = adhoc.task.name
inventory = get_adhoc_inventory(adhoc)
runner = AdHocRunner(inventory)
for k, v in options:
for k, v in options.items():
runner.set_option(k, v)
try:
......@@ -113,42 +112,69 @@ def run_adhoc(hostname_list, pattern, tasks, name=None,
return runner.run(tasks, pattern, play_name=name)
def create_and_run_adhoc(hostname_list, pattern, tasks, name=None,
run_as_admin=False, run_as=None, become_info=None):
if name is None:
name = "Adhoc-task-{}-{}".format(
get_short_uuid_str(),
timezone.now().strftime("%Y-%m-%d %H:%M:%S"),
)
task = Task(name=name)
task.save()
adhoc = AdHoc(
task=task, pattern=pattern, name=name,
run_as_admin=run_as_admin, run_as=run_as
)
adhoc.hosts = hostname_list
adhoc.tasks = tasks
adhoc.become = become_info
adhoc.save()
def get_task_by_name(name):
task = get_object_or_none(Task, name=name)
# def create_and_run_adhoc(hostname_list, pattern, tasks, name=None,
# run_as_admin=False, run_as=None, become_info=None):
# if name is None:
# name = "Adhoc-task-{}-{}".format(
# get_short_uuid_str(),
# timezone.now().strftime("%Y-%m-%d %H:%M:%S"),
# )
# task = Task(name=name)
# task.save()
# adhoc = AdHoc(
# task=task, pattern=pattern, name=name,
# run_as_admin=run_as_admin, run_as=run_as
# )
# adhoc.hosts = hostname_list
# adhoc.tasks = tasks
# adhoc.become = become_info
# adhoc.save()
# def get_task_by_name(name):
# task = get_object_or_none(Task, name=name)
# return task
# def create_task(name, created_by=""):
# return Task.objects.create(name=name, created_by=created_by)
#
#
# def create_adhoc(task, hosts, tasks, pattern='all', options=None,
# run_as_admin=False, run_as="",
# become_info=None, created_by=""):
# adhoc = AdHoc(task=task, pattern=pattern, run_as_admin=run_as_admin,
# run_as=run_as, created_by=created_by)
# adhoc.hosts = hosts
# adhoc.tasks = tasks
# adhoc.options = options
# adhoc.become = become_info
# adhoc.save()
# return adhoc
def create_or_update_task(
task_name, hosts, tasks, pattern='all', options=None,
run_as_admin=False, run_as="", become_info=None,
created_by=None
):
task = get_object_or_none(Task, name=task_name)
if task is None:
task = Task(name=task_name, created_by=created_by)
task.save()
adhoc = task.get_latest_adhoc()
new_adhoc = AdHoc(task=task, pattern=pattern,
run_as_admin=run_as_admin,
run_as=run_as)
new_adhoc.hosts = hosts
new_adhoc.tasks = tasks
new_adhoc.options = options
new_adhoc.become = become_info
if not adhoc or adhoc != new_adhoc:
new_adhoc.save()
task.latest_adhoc = new_adhoc
return task
def create_task(name, created_by=""):
return Task.objects.create(name=name, created_by=created_by)
def create_adhoc(task, hosts, tasks, pattern='all', options=None,
run_as_admin=False, run_as="",
become_info=None, created_by=""):
adhoc = AdHoc(task=task, pattern=pattern, run_as_admin=run_as_admin,
run_as=run_as, created_by=created_by)
adhoc.hosts = hosts
adhoc.tasks = tasks
adhoc.options = options
adhoc.become = become_info
adhoc.save()
return adhoc
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment