├── .gitignore ├── Dockerfile ├── LICENSE ├── README.md ├── account ├── __init__.py ├── admin.py ├── apps.py ├── migrations │ └── __init__.py ├── models.py ├── tests.py ├── urls.py └── views │ ├── __init__.py │ └── login.py ├── build_and_run.sh ├── classes ├── __init__.py ├── ansible_api.py ├── config.py ├── crypto.py ├── get_ip_show_type.py ├── kprocess.py ├── kth_test.py ├── kthread.py ├── my_cmd.py ├── my_concurrent.py ├── my_mq.py ├── my_redis.py └── mysql_db.py ├── cmdb ├── __init__.py ├── admin.py ├── apps.py ├── migrations │ ├── 0001_initial.py │ ├── 0002_auto_20181215_1626.py │ ├── 0003_cmdbansiblesshinfo_cmdbproductinfo.py │ ├── 0004_cmdbusersshauth.py │ └── __init__.py ├── models.py ├── tests.py ├── urls.py └── views │ ├── __init__.py │ ├── cmdb_pool.py │ ├── product_info.py │ ├── tree.py │ └── user_auth.py ├── conf ├── crypto.ini ├── ip_type.ini ├── mq.ini ├── mysql.ini ├── settings.ini └── sshkey_global.conf ├── db_job ├── __init__.py ├── admin.py ├── apps.py ├── migrations │ ├── 0001_initial.py │ ├── 0002_dbjobdbbackuphistory.py │ └── __init__.py ├── models.py ├── tests.py ├── urls.py └── views │ ├── __init__.py │ ├── db_backup.py │ └── db_instance.py ├── log └── .gitkeep ├── manage.py ├── ops_django ├── __init__.py ├── celery.py ├── settings.py ├── urls.py └── wsgi.py ├── ops_job ├── __init__.py ├── admin.py ├── apps.py ├── migrations │ ├── 0001_initial.py │ └── __init__.py ├── models.py ├── tasks.py ├── tests.py ├── urls.py └── views │ ├── __init__.py │ ├── script_cron.py │ ├── script_edit.py │ ├── script_executor.py │ └── script_history.py ├── requirements.txt └── start.sh /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | *.log 3 | *.pyc 4 | db.sqlite3 -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # use base python image with python 2.7 2 | FROM python:2.7 3 | 4 | # set timezone 5 | RUN cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && echo 'Asia/Shanghai' >/etc/timezone 6 | 7 | # install mysql-client 8 | RUN echo "deb http://ftp.cn.debian.org/debian/ stretch main" > /etc/apt/sources.list 9 | RUN echo "deb http://ftp.cn.debian.org/debian/ stretch-updates main" >> /etc/apt/sources.list 10 | RUN echo "deb http://ftp.cn.debian.org/debian-security stretch/updates main" >> /etc/apt/sources.list 11 | RUN apt-get update 12 | RUN apt-get install -y mysql-client 13 | RUN apt-get install -y openssh-server 14 | 15 | RUN sed -i 's/^#.*StrictHostKeyChecking ask/StrictHostKeyChecking no/g' /etc/ssh/ssh_config 16 | 17 | RUN service ssh restart 18 | 19 | # env 20 | ENV RUN_MODE=DEPLOY 21 | ENV C_FORCE_ROOT=true 22 | 23 | COPY ./requirements.txt /app/ 24 | 25 | WORKDIR /app/ 26 | RUN pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple 27 | 28 | # add project to the image 29 | ADD . /app/ 30 | 31 | ADD ./sshkeys/ /root/.ssh/ 32 | 33 | WORKDIR /app/ 34 | # RUN server after docker is up 35 | ENTRYPOINT sh start.sh -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 brickli 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | > 请结合前端工程 [ops-vue](https://github.com/BrickQiJayLee/ops-vue) 使用 2 | 3 | # 部署 4 | 1.安装依赖包: 5 | requirement.txt 6 | 7 | ##### 需要安装mysql,rabbitmq 8 | 9 | 10 | 2.数据库, mq相关: 11 | ---- 12 | >#### 初始化数据库: 13 | >>1). 执行 create database databasename 14 | >>2). conf目录下写配置 15 | 16 | >#### 初始化rabbitmq: 17 | >>1). rabbitmqctl add_user myuser mypassword #创建用户 18 | >>2). rabbitmqctl set_user_tags myuser administrator #为用户设置管理员标签 19 | >>3). rabbitmqctl add_vhost vhost #设置虚拟环境 20 | >>4). rabbitmqctl set_permissions -p vhost myuser ".*" ".*" ".*" #为用户在虚拟环境下设置所有权限 21 | >>2). conf目录下配置服务 22 | 23 | >#### sql导入数据 24 | >#### python manage.py runserver 25 | 26 | 3.celery任务调度配置: 27 | ---- 28 | >1). 初始化celery: python manage.py migrate djcelery 29 | >2). 启动celery调度: python manage.py celery beat 30 | >3). 启动celery任务worker: python manage.py celery worker --loglevel=info 31 | 32 | #### 注: 整套服务需要跑至少3个进程 django server进程, 任务调度进程, 任务调度worker进程(可多开) -------------------------------------------------------------------------------- /account/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brick-li/ops-django/3244fbe4eb782ea07ac38606e5c63b9d2e5525da/account/__init__.py -------------------------------------------------------------------------------- /account/admin.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.contrib import admin 5 | 6 | # Register your models here. 7 | -------------------------------------------------------------------------------- /account/apps.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.apps import AppConfig 5 | 6 | 7 | class AccountConfig(AppConfig): 8 | name = 'account' 9 | -------------------------------------------------------------------------------- /account/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brick-li/ops-django/3244fbe4eb782ea07ac38606e5c63b9d2e5525da/account/migrations/__init__.py -------------------------------------------------------------------------------- /account/models.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models 5 | 6 | # Create your models here. 7 | -------------------------------------------------------------------------------- /account/tests.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.test import TestCase 5 | 6 | # Create your tests here. 7 | -------------------------------------------------------------------------------- /account/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import url 2 | from account.views import login 3 | 4 | urlpatterns = [ 5 | # login 6 | url(r'^/user_login$', login.user_login, name="account-user-login"), 7 | url(r'^/user_logout$', login.user_logout, name="account-user-logout"), 8 | url(r'^/check_login', login.check_login, name="account-check-login"), 9 | ] -------------------------------------------------------------------------------- /account/views/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brick-li/ops-django/3244fbe4eb782ea07ac38606e5c63b9d2e5525da/account/views/__init__.py -------------------------------------------------------------------------------- /account/views/login.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | 3 | 4 | from django.http import HttpResponse 5 | from django.contrib.auth import authenticate, login 6 | from django.contrib.sessions.models import Session 7 | import json 8 | from django.views.decorators.csrf import csrf_exempt 9 | from django.conf import settings 10 | 11 | 12 | def check_login(session_id): 13 | """ 14 | 比对数据库sessionid 15 | :param session_select: 16 | :param session_id: 17 | :return: 18 | """ 19 | return session_id in [ str(Session.objects.first()) ] 20 | 21 | @csrf_exempt 22 | def user_login(request): 23 | user = authenticate(username=request.POST.get('username'), password=request.POST.get('password')) 24 | if user is not None: 25 | if not user.is_active: 26 | request.session.set_expiry(3600*24) 27 | HttpResponse(json.dumps({"result": "success", "username": request.user.username, 28 | "session_id": request.session.session_key})) 29 | else: 30 | login(request, user) 31 | return HttpResponse(json.dumps({"result": "success", "username": request.user.username, 32 | "session_id": request.session.session_key})) 33 | else: 34 | return HttpResponse(json.dumps({"result": "failed", "info": "密码或用户名错误"})) 35 | 36 | @csrf_exempt 37 | def check_login(request): 38 | """ 39 | 检查是否登陆 40 | :param session_select: 41 | :param session_id: 42 | :return: 43 | """ 44 | import datetime 45 | now = datetime.datetime.now() 46 | sessions = list(Session.objects.filter(expire_date__gt=now).values()) 47 | sessions_list = [i['session_key'] for i in sessions] 48 | if request.META.get("HTTP_SESSIONID", None) in sessions_list: 49 | return True 50 | else: 51 | return False 52 | 53 | @csrf_exempt 54 | def user_logout(request): 55 | Session.objects.filter(session_key=request.META.get("HTTP_SESSIONID", '')).delete() 56 | return HttpResponse(json.dumps({"result":"success", "info": "退出成功"})) 57 | 58 | 59 | class LoginRequireMiddleWare(object): 60 | """ 61 | 登陆中间件 62 | """ 63 | def __init__(self, get_response): 64 | self.get_response = get_response 65 | 66 | 67 | def __call__(self, request): 68 | # 判断是否需要忽略登陆 69 | ignore = False 70 | for prefix in settings.AUTH_FREE_URL_PREFIX: 71 | if request.path.startswith(prefix): 72 | ignore = True 73 | break 74 | if ignore is False and not check_login(request): 75 | # 未登陆且未授权免登陆 76 | return HttpResponse(json.dumps({"result":"needLogin"})) 77 | # 判断结束,正常请求 78 | response = self.get_response(request) 79 | return response 80 | 81 | def process_exception(self, request, exception): 82 | """ automatically handle exception in get response """ 83 | pass 84 | 85 | 86 | -------------------------------------------------------------------------------- /build_and_run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # clear last build 4 | [[ `docker ps -a | grep -w ops_django | wc -l` -ne 0 ]] && docker rm -f ops_django 5 | 6 | # if clean last build, open it 7 | [[ `docker images | grep -w ops_django | wc -l` -ne 0 ]] && docker rmi ops_django 8 | 9 | [ -d sshkeys ] || mkdir sshkeys 10 | 11 | cp -rf ~/.ssh/* ./sshkeys/ 12 | 13 | docker build . -t ops_django 14 | 15 | docker run -d --network ops_web --name ops_django -p 8081:8000 ops_django 16 | 17 | -------------------------------------------------------------------------------- /classes/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brick-li/ops-django/3244fbe4eb782ea07ac38606e5c63b9d2e5525da/classes/__init__.py -------------------------------------------------------------------------------- /classes/ansible_api.py: -------------------------------------------------------------------------------- 1 | # coding:utf-8 2 | #!/usr/bin/env python 3 | from collections import namedtuple 4 | from ansible.parsing.dataloader import DataLoader 5 | from ansible.vars.manager import VariableManager 6 | from ansible.inventory.manager import InventoryManager 7 | from ansible.executor.task_queue_manager import TaskQueueManager 8 | from ansible.executor.playbook_executor import PlaybookExecutor 9 | from ansible.plugins.callback import CallbackBase 10 | from ansible.errors import AnsibleError 11 | from ansible.playbook.play import Play 12 | import traceback 13 | import mysql_db, get_ip_show_type, config, crypto 14 | import os, json, sys, time 15 | import tempfile 16 | import random 17 | import logging 18 | 19 | _logger = logging.getLogger(__name__) 20 | 21 | from ops_django.settings import RUN_MODE 22 | 23 | 24 | 25 | """ 26 | 2019-01-25 更新: 废弃ansible 2.2 27 | """ 28 | 29 | 30 | class ResultCallback(CallbackBase): 31 | """结果回调""" 32 | def __init__(self, *args, **kwargs): 33 | self.history_id = kwargs.get('history_id', None) 34 | try: 35 | kwargs.pop('history_id') 36 | except Exception: 37 | pass 38 | super(ResultCallback, self).__init__(*args, **kwargs) 39 | self.host_ok = {} 40 | self.host_unreachable = {} 41 | self.host_failed = {} 42 | 43 | self.ret = { 44 | 'host_ok': {}, 45 | 'host_unreachable': {}, 46 | 'host_failed': {} 47 | } 48 | 49 | def update_result(self): 50 | c = config.config('mysql.ini') 51 | db_name = c.getOption(RUN_MODE, 'dbname') 52 | with mysql_db.conn_db(db_name, RUN_MODE) if RUN_MODE == 'DEPLOY' else mysql_db.conn_db(db_name, 53 | "LOCAL") as _db: 54 | data = [( 55 | json.dumps(self.ret), 56 | self.history_id 57 | )] 58 | try: 59 | _db.executemany("update ops_job_script_history set result=%s where id=%s", data) 60 | except: 61 | print traceback.format_exc() 62 | 63 | def v2_runner_on_unreachable(self, result): 64 | self.host_unreachable[result._host.get_name()] = result 65 | if not self.history_id is None: 66 | self.update_result() 67 | self.ret['host_unreachable'][result._host.name] = result._result 68 | 69 | def v2_runner_on_ok(self, result, *args, **kwargs): 70 | if result._result.get('stderr_lines', None): 71 | self.host_ok[result._host.get_name()] = result 72 | if not self.history_id is None: 73 | self.update_result() 74 | self.ret['host_failed'][result._host.name] = result._result 75 | else: 76 | self.host_ok[result._host.get_name()] = result 77 | if not self.history_id is None: 78 | self.update_result() 79 | self.ret['host_ok'][result._host.name] = result._result 80 | 81 | def v2_runner_on_failed(self, result, *args, **kwargs): 82 | self.host_failed[result._host.get_name()] = result 83 | if not self.history_id is None: 84 | self.update_result() 85 | self.ret['host_failed'][result._host.name] = result._result 86 | 87 | def runner_on_skipped(self, result, *args, **kwargs): 88 | self.host_failed[result._host.get_name()] = result 89 | if not self.history_id is None: 90 | self.update_result() 91 | self.ret['host_failed'][result._host.name] = result._result 92 | 93 | def runner_on_async_failed(self, result, *args, **kwargs): 94 | self.host_failed[result._host.get_name()] = result 95 | if not self.history_id is None: 96 | self.update_result() 97 | self.ret['host_failed'][result._host.name] = result._result 98 | 99 | 100 | def AnsibleTempSource(): 101 | ''' 102 | 数据库获取ansible resource 103 | :return: 104 | ''' 105 | c = config.config('mysql.ini') 106 | db_name = c.getOption(RUN_MODE, 'dbname') 107 | ip_show_type = get_ip_show_type.get_show_type() 108 | with mysql_db.conn_db(db_name, RUN_MODE) if RUN_MODE == 'DEPLOY' else mysql_db.conn_db(db_name, "LOCAL") as _db: 109 | ssh_info = _db.select("select * from cmdb_ansible_ssh_info;") 110 | ips = [i['outer_addr_ip' if ip_show_type == 'outer_ip' else 'inner_addr_ip'] for i in ssh_info] 111 | # 查询没有记录ansible信息的主机,使用默认配置 112 | sql = "select * from cmdb_tree_node where node_name not in ('%s') and node_type='ip';" % "','".join(ips) 113 | other_host = _db.select(sql) 114 | other_host = [i['node_name'] for i in other_host] 115 | 116 | resource = list() 117 | for i in ssh_info: 118 | try: 119 | resource.append({ 120 | "hostname": i['outer_addr_ip'] if ip_show_type == 'outer_ip' else i['inner_addr_ip'], 121 | "port": 22 if not i['ansible_ssh_port'] else int(i['ansible_ssh_port']), 122 | "username": "root" if not i['ansible_ssh_user'] else i['ansible_ssh_user'], 123 | "password": '' if i['ansible_sudo_pass'] == '' else crypto.passwd_deaes(i['ansible_sudo_pass']), 124 | "ip": i['outer_addr_ip'] if ip_show_type == 'outer_ip' else i['inner_addr_ip'], 125 | }) 126 | for ip in other_host: 127 | resource.append({ 128 | "hostname": ip, 129 | "port": 22, 130 | "username": "root", 131 | "password": '', 132 | "ip": ip, 133 | }) 134 | except Exception: 135 | print traceback.format_exc() 136 | raise AnsibleError 137 | return resource 138 | 139 | 140 | class AnsibleTempFile(): 141 | def __init__(self): 142 | ''' 143 | 数据库获取ansible变量到临时文件 144 | :return: 145 | ''' 146 | c = config.config('mysql.ini') 147 | db_name = c.getOption(RUN_MODE, 'dbname') 148 | ip_show_type = get_ip_show_type.get_show_type() 149 | with mysql_db.conn_db(db_name, RUN_MODE) if RUN_MODE == 'DEPLOY' else mysql_db.conn_db(db_name, "LOCAL") as _db: 150 | ssh_info = _db.select("select * from cmdb_ansible_ssh_info;") 151 | inv_list = list() 152 | for i in ssh_info: 153 | str = i['outer_addr_ip'] if ip_show_type == 'outer_ip' else i['inner_addr_ip'] 154 | str = "%s ansible_ssh_user=%s" % (str, i['ansible_ssh_user'] if i['ansible_ssh_user'] != '' else 'root') 155 | str = "%s%s" % (str, " ansible_ssh_port={0}".format(i['ansible_ssh_port']) if i['ansible_ssh_port'] != '' else '') 156 | str = "%s%s" % (str, " ansible_sudo_pass={0}".format(crypto.passwd_deaes(i['ansible_sudo_pass'])) if i['ansible_sudo_pass'] != '' else '') 157 | inv_list.append(str) 158 | inv = '[AllResource]' 159 | inv = "%s\n%s" % (inv, '\n'.join(inv_list)) 160 | self.temp = tempfile.mktemp() 161 | with open(self.temp, "wb") as f: 162 | f.write(inv) 163 | f.close() 164 | 165 | def get_tmp_file(self): 166 | """ 167 | 获得临时文件 168 | :return: 169 | """ 170 | return self.temp 171 | 172 | def remove_tmp_file(self): 173 | """ 174 | 删除临时文件 175 | :return: 176 | """ 177 | try: 178 | os.remove(self.temp) 179 | except: 180 | pass 181 | 182 | 183 | class AnsibleApi(object): 184 | 185 | def __init__(self, become=False, become_method=None, become_user=None, history_id=None): 186 | self.become = become 187 | self.become_method = become_method 188 | self.become_user = become_user 189 | self.inventory = None 190 | self.variable_manager = None 191 | self.loader = None 192 | self.options = None 193 | self.passwords = None 194 | self.history_id = history_id 195 | self.callback = ResultCallback(history_id=self.history_id) 196 | self.__initializeData() 197 | 198 | def __initializeData(self): 199 | ''' 200 | 创建参数,为保证每个参数都被设置,ansible使用可命名元组 201 | ''' 202 | '''初始化loader类''' 203 | self.loader = DataLoader() # 用于读取与解析yaml和json文件 204 | self.passwords = dict(vault_pass='secret') 205 | self.Options = namedtuple('Options', 206 | ['connection', 'module_path', 'forks', 'become', 'become_method', 'become_user', 207 | 'check', 'diff']) 208 | self.options = self.Options(connection='ssh', module_path='/to/mymodules', forks=30, become=self.become, 209 | become_method=self.become_method, become_user=self.become_user, check=False, diff=False) 210 | 211 | self.tmp_file_handler = AnsibleTempFile() 212 | self.tmp_source = self.tmp_file_handler.get_tmp_file() 213 | self.inventory = InventoryManager(loader=self.loader, sources=self.tmp_source) 214 | self.variable_manager = VariableManager(loader=self.loader, inventory=self.inventory) 215 | 216 | def run(self, host_list, module_name, module_args): 217 | """ 218 | run module from andible ad-hoc. 219 | module_name: ansible module_name 220 | module_args: ansible module args 221 | """ 222 | # create play with tasks 223 | try: 224 | '''run after _init_task''' 225 | play_source = dict( 226 | name="Ansible Play", 227 | hosts=host_list, 228 | gather_facts='no', 229 | tasks=[dict(action=dict(module=module_name, args=module_args))] 230 | ) 231 | play = Play().load(play_source, variable_manager=self.variable_manager, loader=self.loader) 232 | tqm = None 233 | try: 234 | tqm = TaskQueueManager( 235 | inventory=self.inventory, 236 | variable_manager=self.variable_manager, 237 | loader=self.loader, 238 | options=self.options, 239 | passwords=self.passwords, 240 | stdout_callback=self.callback 241 | # Use our custom callback instead of the ``default`` callback plugin, which prints to stdout 242 | ) 243 | tqm._stdout_callback = self.callback 244 | tqm.run(play) 245 | except Exception, e: 246 | print traceback.format_exc() 247 | _logger.error("ansible error: %s, %s " % (e, traceback.format_exc())) 248 | finally: 249 | # Remove ansible tmp file 250 | self.tmp_file_handler.remove_tmp_file() 251 | if tqm is not None: 252 | tqm.cleanup() 253 | except(Exception): 254 | print traceback.format_exc() 255 | print self.callback 256 | #raise AnsibleError 257 | 258 | def run_playbook(self, host_list, role_name, role_uuid, temp_param): 259 | """ 260 | run ansible palybook 261 | """ 262 | try: 263 | self.callback = ResultCallback(history_id=self.history_id) 264 | filenames = ['' + '/handlers/ansible/v1_0/sudoers.yml'] # playbook的路径 265 | template_file = '' # 模板文件的路径 266 | if not os.path.exists(template_file): 267 | sys.exit() 268 | 269 | extra_vars = {} # 额外的参数 sudoers.yml以及模板中的参数,它对应ansible-playbook test.yml --extra-vars "host='aa' name='cc' " 270 | host_list_str = ','.join([item for item in host_list]) 271 | extra_vars['host_list'] = host_list_str 272 | extra_vars['username'] = role_name 273 | extra_vars['template_dir'] = template_file 274 | extra_vars['command_list'] = temp_param.get('cmdList') 275 | extra_vars['role_uuid'] = 'role-%s' % role_uuid 276 | self.variable_manager.extra_vars = extra_vars 277 | # actually run it 278 | executor = PlaybookExecutor( 279 | playbooks=filenames, inventory=self.inventory, variable_manager=self.variable_manager, 280 | loader=self.loader, 281 | options=self.options, passwords=self.passwords, 282 | ) 283 | executor._tqm._stdout_callback = self.callback 284 | executor.run() 285 | except Exception as e: 286 | print "error:", e.message 287 | 288 | def _get_result(self): 289 | return self.callback.ret 290 | 291 | class AnsiInterface(AnsibleApi): 292 | def __init__(self, *args, **kwargs): 293 | super(AnsiInterface, self).__init__(*args, **kwargs) 294 | 295 | def copy_file(self, host_list, src=None, dest=None): 296 | """ 297 | copy file 298 | """ 299 | module_args = "src=%s dest=%s"%(src, dest) 300 | self.run(host_list, 'copy', module_args) 301 | result = self._get_result() 302 | return result 303 | 304 | def make_dir(self, host_list, dir): 305 | ''' 306 | file 307 | ''' 308 | module_args = 'path=%s state=directory' % dir 309 | self.run(host_list, 'file', module_args) 310 | result = self._get_result() 311 | return result 312 | 313 | def exec_shell(self, host_list, sh): 314 | """ 315 | commands 316 | """ 317 | self.run(host_list, 'shell', sh) 318 | result = self._get_result() 319 | return result 320 | 321 | def exec_command(self, host_list, cmds): 322 | """ 323 | commands 324 | """ 325 | self.run(host_list, 'command', cmds) 326 | result = self._get_result() 327 | return result 328 | 329 | def exec_script(self, host_list, path): 330 | """ 331 | 在远程主机执行shell命令或者.sh脚本 332 | """ 333 | self.run(host_list, 'shell', path) 334 | result = self._get_result() 335 | return result 336 | 337 | def sync_authorized_key(self, host_list, keyargs): 338 | """ 339 | 同步远程主机authkey 340 | """ 341 | self.run(host_list, 'authorized_key', keyargs) 342 | result = self._get_result() 343 | return result 344 | 345 | def local_actions(self, keyargs): 346 | """ 347 | 同步远程主机authkey 348 | """ 349 | self.run([], 'local_action', keyargs) 350 | result = self._get_result() 351 | return result 352 | 353 | class ansible_script_temp_file(): 354 | def __init__(self, script_name): 355 | ''' 356 | 获取脚本内容 357 | ''' 358 | c = config.config('mysql.ini') 359 | db_name = c.getOption(RUN_MODE, 'dbname') 360 | with mysql_db.conn_db(db_name, RUN_MODE) if RUN_MODE == 'DEPLOY' else mysql_db.conn_db(db_name, 361 | "LOCAL") as _db: 362 | script = _db.select("select * from ops_job_job_script_info where script_name='%s';" % script_name) 363 | if not len(script) == 1: 364 | print "Found no script as %s" % script_name 365 | raise Exception 366 | else: 367 | self.script_content = script[0]['script_content'] 368 | self.temp = tempfile.mktemp() 369 | with open(self.temp, "wb") as f: 370 | f.write(self.script_content.encode('UTF-8')) 371 | 372 | def get_tmp_file(self): 373 | return self.temp 374 | 375 | def remove_tmp_file(self): 376 | os.remove(self.temp) 377 | 378 | def __enter__(self): 379 | return self 380 | 381 | def __exit__(self, exc_type, exc_val, exc_tb): 382 | self.remove_tmp_file() 383 | 384 | class asnible_args_temp_file(): 385 | def __init__(self, content): 386 | ''' 387 | 脚本参数文件 388 | :param content: 389 | ''' 390 | self.temp = tempfile.mktemp() 391 | with open(self.temp, "wb") as f: 392 | f.write(content.encode('UTF-8')) 393 | 394 | def get_tmp_file(self): 395 | return self.temp 396 | 397 | def remove_tmp_file(self): 398 | os.remove(self.temp) 399 | 400 | def __enter__(self): 401 | return self 402 | 403 | def __exit__(self, exc_type, exc_val, exc_tb): 404 | self.remove_tmp_file() 405 | 406 | 407 | def exec_script_all_type(self, host_list, script_name, script_args='', args_type='normal'): 408 | """ 409 | 在远程主机执行shell命令或者.sh脚本 410 | """ 411 | 412 | TmpFileName = "%s%s%s" % (int(time.time()), int(random.random()*1000), int(random.random()*1000)) 413 | AnsibleTmpPath = "/tmp/ansible_script_tmp/" 414 | if args_type == 'file': 415 | with self.asnible_args_temp_file(script_args) as args_file_handler: 416 | args_file = args_file_handler.get_tmp_file() 417 | args_file_name = TmpFileName 418 | script_args = "%s%s" % (AnsibleTmpPath, args_file_name) 419 | self.run(host_list, 'file', 'path=%s state=directory mode=0777' % AnsibleTmpPath) 420 | self.run(host_list, 'copy', 'src=%s dest=%s%s owner=root group=root mode=0755' % (args_file, AnsibleTmpPath, args_file_name)) 421 | 422 | with self.ansible_script_temp_file(script_name) as script_file_handler: 423 | script_file = script_file_handler.get_tmp_file() 424 | TmpFileName = "%s.sh" % TmpFileName 425 | self.run(host_list, 'file', 'path=%s state=directory mode=0777' % AnsibleTmpPath) 426 | self.run(host_list, 'shell', 'chmod 777 %s' % AnsibleTmpPath) 427 | self.run(host_list, 'shell', '(cd %s && find . -type f -mtime +3 -exec rm {} \;) || echo "no need to clean"' % AnsibleTmpPath) 428 | self.run(host_list, 'copy', 'src=%s dest=%s%s owner=root group=root mode=0755' % (script_file, AnsibleTmpPath, TmpFileName)) 429 | self.run(host_list, 'shell', 'chmod 0755 %s%s' % (AnsibleTmpPath, TmpFileName)) 430 | self.run(host_list, 'shell', 'cd %s && ./%s %s' % (AnsibleTmpPath, TmpFileName, script_args)) 431 | result = self._get_result() 432 | return result 433 | -------------------------------------------------------------------------------- /classes/config.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | #-*- coding:utf8 -*- 3 | 4 | import ConfigParser,os,re,io 5 | 6 | class config: 7 | def __init__(self, filename): 8 | self.config = ConfigParser.RawConfigParser(allow_no_value=True) 9 | dir = os.path.dirname(os.path.abspath(__file__)) 10 | path = "%s/../conf/" % dir 11 | filepath = path + filename 12 | if not os.path.exists(filepath): 13 | raise Exception("ERROR: %s该配置文件不存在!"%filepath) 14 | f = open(filepath) 15 | content = f.read() 16 | self.config.readfp(io.BytesIO(str(content))) 17 | f.close() 18 | 19 | def checkArg(self, str,info): 20 | '''检查参数是否为空''' 21 | if check.nullCheck(str) : 22 | raise Exception(info) 23 | 24 | def checkSection(self, section): 25 | '''检查配置文件的section是否存在''' 26 | if not self.config.has_section(section): 27 | raise Exception("没有%s该section"%section) 28 | 29 | def getOption(self,section,option,type="str",default=None): 30 | '''检查配置文件的option是否存在''' 31 | returnStr = "" 32 | if not self.config.has_option(section,option): 33 | #如果对应section中没有找到option则到通用的section中查找option 34 | if not self.config.has_option("common",option): 35 | if default != None: 36 | return default 37 | else: 38 | raise Exception("没有%s该option"%option) 39 | #return None 40 | else: 41 | if type == "bool": 42 | returnStr = self.config.getboolean("common",option) 43 | elif type == "int": 44 | returnStr = self.config.getint("common",option) 45 | elif type == "float": 46 | returnStr = self.config.getfloat("common",option) 47 | else: 48 | returnStr = self.config.get("common",option) 49 | else: 50 | if type == "bool": 51 | returnStr = self.config.getboolean(section,option) 52 | elif type == "int": 53 | returnStr = self.config.getint(section,option) 54 | elif type == "float": 55 | returnStr = self.config.getfloat(section,option) 56 | else: 57 | returnStr = self.config.get(section,option) 58 | return returnStr 59 | 60 | 61 | -------------------------------------------------------------------------------- /classes/crypto.py: -------------------------------------------------------------------------------- 1 | # coding: utf8 2 | import sys 3 | from Crypto.Cipher import AES 4 | from binascii import b2a_hex, a2b_hex 5 | from classes import config 6 | 7 | class prpcrypt(): 8 | def __init__(self, key): 9 | self.key = key 10 | self.mode = AES.MODE_CBC 11 | 12 | # 加密函数,如果text不是16的倍数【加密文本text必须为16的倍数!】,那就补足为16的倍数 13 | def encrypt(self, text): 14 | cryptor = AES.new(self.key, self.mode, self.key) 15 | # 这里密钥key 长度必须为16(AES-128)、24(AES-192)、或32(AES-256)Bytes 长度.目前AES-128足够用 16 | length = 16 17 | count = len(text) 18 | add = length - (count % length) 19 | text = text + ('\0' * add) 20 | self.ciphertext = cryptor.encrypt(text) 21 | # 因为AES加密时候得到的字符串不一定是ascii字符集的,输出到终端或者保存时候可能存在问题 22 | # 所以这里统一把加密后的字符串转化为16进制字符串 23 | return b2a_hex(self.ciphertext) 24 | 25 | # 解密后,去掉补足的空格用strip() 去掉 26 | def decrypt(self, text): 27 | cryptor = AES.new(self.key, self.mode, self.key) 28 | plain_text = cryptor.decrypt(a2b_hex(text)) 29 | return plain_text.rstrip('\0') 30 | 31 | 32 | 33 | def passwd_aes(db_passwd): 34 | ''' 35 | AES加密密码 36 | :return: 37 | ''' 38 | _config = config.config("../conf/crypto.ini") 39 | crypto_str = _config.getOption(section="crypto", option="key_str") 40 | pc = prpcrypt(crypto_str) # 初始化密钥 41 | e = pc.encrypt(db_passwd) 42 | return e 43 | 44 | def passwd_deaes(db_passwd_aes): 45 | _config = config.config("../conf/crypto.ini") 46 | print 47 | crypto_str = _config.getOption(section="crypto", option="key_str") 48 | pc = prpcrypt(crypto_str) # 初始化密钥 49 | d = pc.decrypt(db_passwd_aes) 50 | return d 51 | 52 | -------------------------------------------------------------------------------- /classes/get_ip_show_type.py: -------------------------------------------------------------------------------- 1 | # coding:utf-8 2 | #!/usr/bin/env python 3 | 4 | 5 | from classes import config 6 | import traceback 7 | 8 | def get_show_type(): 9 | try: 10 | c = config.config("ip_type.ini") 11 | #c.getOption('SHOWTYPE', 'ipshowtype') 12 | return str(c.getOption('SHOWTYPE', 'ipshowtype')) 13 | except Exception: 14 | print("Env_type get error: %s" % traceback.format_exc()) 15 | return 'outer_ip' -------------------------------------------------------------------------------- /classes/kprocess.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | 3 | import signal 4 | import logging 5 | 6 | _logger = logging.getLogger(__name__) 7 | 8 | def callback(): 9 | print "kprocess time out error" 10 | 11 | class TimeOutException(Exception): 12 | pass 13 | 14 | def setTimeout(num, callback=callback): 15 | def wraper(func): 16 | def handle(signum, frame): 17 | raise TimeOutException("multiprocess out of time: %ss!" % num) 18 | 19 | def toDo(*args, **kwargs): 20 | try: 21 | signal.signal(signal.SIGALRM, handle) 22 | signal.alarm(num) # 开启闹钟信号 23 | rs = func(*args, **kwargs) 24 | signal.alarm(0) # 关闭闹钟信号 25 | return rs 26 | except TimeOutException, e: 27 | callback() 28 | 29 | return toDo 30 | return wraper -------------------------------------------------------------------------------- /classes/kth_test.py: -------------------------------------------------------------------------------- 1 | import kthread, time 2 | 3 | @kthread.timeout(1) 4 | def test(): 5 | time.sleep(2) 6 | print "success" 7 | 8 | 9 | if __name__ == '__main__': 10 | test() -------------------------------------------------------------------------------- /classes/kthread.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | import sys 3 | import threading 4 | 5 | class KThread(threading.Thread): 6 | """A subclass of threading.Thread, with a kill() 7 | method. 8 | 9 | Come from: 10 | Kill a thread in Python: 11 | http://mail.python.org/pipermail/python-list/2004-May/260937.html 12 | """ 13 | 14 | def __init__(self, *args, **kwargs): 15 | threading.Thread.__init__(self, *args, **kwargs) 16 | self.killed = False 17 | 18 | def start(self): 19 | """Start the thread.""" 20 | self.__run_backup = self.run 21 | self.run = self.__run # Force the Thread to install our trace. 22 | threading.Thread.start(self) 23 | 24 | def __run(self): 25 | """Hacked run function, which installs the 26 | trace.""" 27 | sys.settrace(self.globaltrace) 28 | self.__run_backup() 29 | self.run = self.__run_backup 30 | 31 | def globaltrace(self, frame, why, arg): 32 | if why == 'call': 33 | return self.localtrace 34 | else: 35 | return None 36 | 37 | def localtrace(self, frame, why, arg): 38 | if self.killed: 39 | if why == 'line': 40 | raise SystemExit() 41 | return self.localtrace 42 | 43 | def kill(self): 44 | self.killed = True 45 | 46 | 47 | class Timeout(Exception): 48 | """function run timeout""" 49 | 50 | 51 | def timeout(seconds): 52 | """超时装饰器,指定超时时间 53 | 若被装饰的方法在指定的时间内未返回,则抛出Timeout异常""" 54 | 55 | def timeout_decorator(func): 56 | """真正的装饰器""" 57 | 58 | def _new_func(oldfunc, result, oldfunc_args, oldfunc_kwargs): 59 | result.append(oldfunc(*oldfunc_args, **oldfunc_kwargs)) 60 | 61 | def _(*args, **kwargs): 62 | result = [] 63 | new_kwargs = { # create new args for _new_func, because we want to get the func return val to result list 64 | 'oldfunc': func, 65 | 'result': result, 66 | 'oldfunc_args': args, 67 | 'oldfunc_kwargs': kwargs 68 | } 69 | thd = KThread(target=_new_func, args=(), kwargs=new_kwargs) 70 | thd.start() 71 | thd.join(seconds) 72 | alive = thd.isAlive() 73 | thd.kill() # kill the child thread 74 | if alive: 75 | raise Timeout(u'Function Run Timeout, timeout %d seconds.' % seconds) 76 | else: 77 | return result[0] 78 | 79 | _.__name__ = func.__name__ 80 | _.__doc__ = func.__doc__ 81 | return _ 82 | 83 | return timeout_decorator -------------------------------------------------------------------------------- /classes/my_cmd.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | #-*- coding:utf8 -*- 3 | 4 | import commands 5 | 6 | def cmd(cmdStr): 7 | status, out = commands.getstatusoutput(cmdStr) 8 | if status != 0 : 9 | print "[%s] ERROR:%s"%(cmdStr, out) 10 | return 1 11 | else: 12 | return out -------------------------------------------------------------------------------- /classes/my_concurrent.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | 3 | 4 | # python2适用 5 | import threading 6 | from pathos import multiprocessing 7 | #from multiprocessing import Pool, TimeoutError 8 | #import multiprocessing 9 | 10 | def div_list(ls,n): 11 | ''' 12 | 尽量平分列表 13 | :param ls: 14 | ls - 传入的列表对象 15 | n - 需要划分为多少个子列表 16 | :param n: 17 | :return: 18 | ''' 19 | if not isinstance(ls, list) or not isinstance(n, int): 20 | print("not list") 21 | raise AttributeError 22 | ls_len = len(ls) 23 | if n<=0 or ls_len==0: 24 | print("num is wrong or list is empty") 25 | raise AttributeError 26 | elif n >= ls_len: 27 | return [[i] for i in ls] 28 | else: 29 | j = ls_len/n 30 | k = ls_len%n 31 | ### j,j,j,...(前面有n-1个j),j+k 32 | #步长j,次数n-1 33 | ls_return = [] 34 | for i in xrange(0,(n-1)*j,j): 35 | ls_return.append(ls[i:i+j]) 36 | #算上末尾的j+k 37 | ls_return.append(ls[(n-1)*j:]) 38 | return ls_return 39 | 40 | # 多线程 41 | class MyMultiThread(): 42 | def __init__(self): 43 | self.runlist = list() 44 | 45 | def multi_thread_Add(self, func, name, *args, **kwargs): 46 | t = threading.Thread(target=func, name=name, args=args, kwargs=kwargs) 47 | self.runlist.append(t) 48 | 49 | def multi_thread_start(self): 50 | for t in self.runlist: 51 | t.start() 52 | 53 | def multi_thread_wait(self): 54 | for t in self.runlist: 55 | t.join() 56 | 57 | # 多进程 58 | class MyMultiProcess(): 59 | ''' 60 | django数据库操作不能使用,与父进程冲突,应该使用subprocess 61 | ''' 62 | def __init__(self, processes): 63 | self._pool = multiprocessing.Pool(processes=processes) 64 | self.result = list() 65 | self.p_list = list() 66 | 67 | def multi_process_add(self, func, *args, **kwargs): 68 | self.p_list.append(self._pool.apply_async(func, args=args, kwds=kwargs)) 69 | 70 | def multi_process_wait(self): 71 | ''' 72 | 执行并且 73 | 等待子进程执行完成并获取结果 74 | :return: 执行结果 75 | ''' 76 | self._pool.close() 77 | self._pool.join() 78 | 79 | def get_result(self): 80 | for p in self.p_list: 81 | self.result.append(p.get()) 82 | return self.result 83 | -------------------------------------------------------------------------------- /classes/my_mq.py: -------------------------------------------------------------------------------- 1 | # _*_coding:utf-8_*_ 2 | import pika 3 | from config import config 4 | 5 | 6 | def mq_client(): 7 | _config = config("../conf/mq.ini") 8 | user = _config.getOption("rabbit_mq", "user") 9 | passwd = _config.getOption("rabbit_mq", "passwd") 10 | ip = _config.getOption("rabbit_mq", "ip") 11 | port = _config.getOption("rabbit_mq", "port") 12 | credentials = pika.PlainCredentials(user, passwd) 13 | connection = pika.BlockingConnection(pika.ConnectionParameters( 14 | ip,port,'/',credentials)) 15 | channel = connection.channel() 16 | 17 | channel.queue_declare(queue='balance') 18 | return channel 19 | 20 | 21 | -------------------------------------------------------------------------------- /classes/my_redis.py: -------------------------------------------------------------------------------- 1 | #-*- coding:utf8 -*- 2 | 3 | import sys 4 | sys.path.append("..") 5 | import my_redis 6 | from classes import config 7 | 8 | def redis_handler(config_section, db): 9 | c = config.config("config_db.ini") 10 | ip = c.getOption(config_section, "ip") 11 | password = c.getOption(config_section, "password") 12 | port = c.getOption(config_section, "port") 13 | r = my_redis.Redis(host=ip, password=password, port=port, db=db) 14 | return r -------------------------------------------------------------------------------- /classes/mysql_db.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | import sys 3 | 4 | sys.path.append("..") 5 | from classes import config 6 | import MySQLdb 7 | # from DBUtils.PooledDB import PooledDB 8 | import logging 9 | import traceback 10 | 11 | 12 | logger = logging.getLogger(__name__) 13 | 14 | def dict_fetch_all(cursor): 15 | "Returns all rows from a cursor as a dict" 16 | desc = cursor.description 17 | return [ 18 | dict(zip([col[0] for col in desc], row)) 19 | for row in cursor.fetchall() 20 | ] 21 | 22 | 23 | def change_query_data_tostring(query_data): 24 | ''' 25 | 将数据库取出来的数据转化为字符,因为datetime不支持json 26 | :param query_data: 27 | :return: 28 | ''' 29 | for data in query_data: 30 | keys = data.keys() 31 | try: 32 | for key in keys: 33 | try: 34 | if "blob" in key: 35 | data.pop(key) 36 | else: 37 | data[key] = str(data[key]).encode("utf-8") 38 | except: 39 | data.pop(key) 40 | except: 41 | pass 42 | return query_data 43 | 44 | 45 | class conn_db: 46 | __pool = {} 47 | def __init__(self, db, config_section): 48 | """ 49 | 数据库构造函数,从连接池中取出连接,并生成操作游标 50 | """ 51 | try: 52 | c = config.config("../conf/mysql.ini") 53 | user = c.getOption(config_section, "username") 54 | pwd = c.getOption(config_section, "password") 55 | host = c.getOption(config_section, "host") 56 | port = c.getOption(config_section, "port", "int") 57 | self.connect = self.db = MySQLdb.connect(host=host, user=user, passwd=pwd, db=db, port=port, charset='utf8') 58 | except Exception, e: 59 | print traceback.format_exc() 60 | #logger.error("connect database error - %s" % str(e)) 61 | return 62 | 63 | def execute(self, sql, param=()): 64 | try: 65 | cursor1 = self.connect.cursor() 66 | index = cursor1.execute(sql, param) 67 | self.connect.commit() 68 | cursor1.close() 69 | return index 70 | except Exception, e: 71 | print traceback.format_exc() 72 | self.connect.rollback() 73 | #logger.error("execute sql error - %s" % str(e)) 74 | return -1 75 | 76 | def execute_returnid(self, sql, param=()): 77 | try: 78 | cursor1 = self.connect.cursor() 79 | cursor1.execute(sql, param) 80 | self.connect.commit() 81 | index = int(cursor1.lastrowid) 82 | return index 83 | except Exception, e: 84 | self.connect.rollback() 85 | print traceback.format_exc() 86 | self.connect.rollback() 87 | #logger.error("execute sql error - %s" % str(e)) 88 | return -1 89 | 90 | def executemany(self, sql, param=()): 91 | try: 92 | cursor1 = self.connect.cursor() 93 | index = cursor1.executemany(sql, param) 94 | self.connect.commit() 95 | cursor1.close() 96 | return index 97 | except Exception, e: 98 | print traceback.format_exc() 99 | self.connect.rollback() 100 | #logger.error("execute sql error - %s" % str(e)) 101 | return -1 102 | 103 | def select(self, sql, param=()): 104 | try: 105 | cursor1 = self.connect.cursor() 106 | cursor1.execute(sql, param) 107 | self.connect.commit() 108 | get_data = dict_fetch_all(cursor1) 109 | cursor1.close() 110 | return get_data 111 | except Exception, e: 112 | print traceback.format_exc() 113 | #logger.error("execute sql error - %s" % str(e)) 114 | return -1 115 | 116 | def close(self): 117 | self.connect.close() 118 | 119 | def __enter__(self): 120 | return self 121 | 122 | def __exit__(self, exc_type, exc_val, exc_tb): 123 | self.connect.close() 124 | -------------------------------------------------------------------------------- /cmdb/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brick-li/ops-django/3244fbe4eb782ea07ac38606e5c63b9d2e5525da/cmdb/__init__.py -------------------------------------------------------------------------------- /cmdb/admin.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.contrib import admin 5 | 6 | # Register your models here. 7 | -------------------------------------------------------------------------------- /cmdb/apps.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.apps import AppConfig 5 | 6 | 7 | class CmdbConfig(AppConfig): 8 | name = 'cmdb' 9 | -------------------------------------------------------------------------------- /cmdb/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.17 on 2018-12-15 13:58 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | initial = True 11 | 12 | dependencies = [ 13 | ] 14 | 15 | operations = [ 16 | migrations.CreateModel( 17 | name='CmdbPool', 18 | fields=[ 19 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 20 | ('inner_addr_ip', models.CharField(db_column='Inner_Addr_IP', max_length=32)), 21 | ('outer_addr_ip', models.CharField(blank=True, db_column='Outer_Addr_IP', max_length=32, null=True)), 22 | ('operating_system', models.CharField(blank=True, db_column='Operating_System', max_length=32, null=True)), 23 | ('status', models.CharField(blank=True, db_column='Status', max_length=32, null=True)), 24 | ('region', models.CharField(blank=True, db_column='Region', max_length=32, null=True)), 25 | ('available_zone', models.CharField(blank=True, db_column='Available_Zone', max_length=32, null=True)), 26 | ('cpu_info', models.IntegerField(blank=True, db_column='CPU_Info', null=True)), 27 | ('memory_info', models.IntegerField(blank=True, db_column='Memory_Info', null=True)), 28 | ('expire_time', models.DateTimeField(blank=True, db_column='Expire_Time', null=True)), 29 | ('create_time', models.DateTimeField(auto_now_add=True, db_column='Create_Time')), 30 | ('update_time', models.DateTimeField(auto_now=True, db_column='Update_Time', null=True)), 31 | ], 32 | options={ 33 | 'db_table': 'cmdb_pool', 34 | }, 35 | ), 36 | ] 37 | -------------------------------------------------------------------------------- /cmdb/migrations/0002_auto_20181215_1626.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.17 on 2018-12-15 16:26 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('cmdb', '0001_initial'), 12 | ] 13 | 14 | operations = [ 15 | migrations.CreateModel( 16 | name='CmdbTreeNode', 17 | fields=[ 18 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 19 | ('product_id', models.IntegerField(blank=True, null=True)), 20 | ('node_name', models.CharField(blank=True, max_length=32, null=True)), 21 | ('environment', models.CharField(blank=True, max_length=32, null=True)), 22 | ('service_info', models.TextField(blank=True, null=True)), 23 | ('depth', models.IntegerField(blank=True, null=True)), 24 | ('father_id', models.IntegerField(blank=True, null=True)), 25 | ('create_time', models.DateTimeField(auto_now_add=True, db_column='Create_Time', null=True)), 26 | ('update_time', models.DateTimeField(auto_now=True, db_column='Update_Time', null=True)), 27 | ('node_type', models.CharField(blank=True, max_length=32, null=True)), 28 | ('tag', models.CharField(blank=True, max_length=32, null=True)), 29 | ], 30 | options={ 31 | 'db_table': 'cmdb_tree_node', 32 | }, 33 | ), 34 | migrations.AlterField( 35 | model_name='cmdbpool', 36 | name='create_time', 37 | field=models.DateTimeField(auto_now_add=True, db_column='Create_Time', null=True), 38 | ), 39 | ] 40 | -------------------------------------------------------------------------------- /cmdb/migrations/0003_cmdbansiblesshinfo_cmdbproductinfo.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.17 on 2018-12-16 07:05 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('cmdb', '0002_auto_20181215_1626'), 12 | ] 13 | 14 | operations = [ 15 | migrations.CreateModel( 16 | name='CmdbAnsibleSshInfo', 17 | fields=[ 18 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 19 | ('inner_addr_ip', models.CharField(max_length=32)), 20 | ('outer_addr_ip', models.CharField(max_length=32)), 21 | ('ansible_ssh_user', models.CharField(max_length=32)), 22 | ('ansible_ssh_port', models.CharField(max_length=32)), 23 | ('ansible_sudo_pass', models.CharField(max_length=255)), 24 | ], 25 | options={ 26 | 'db_table': 'cmdb_ansible_ssh_info', 27 | }, 28 | ), 29 | migrations.CreateModel( 30 | name='CmdbProductInfo', 31 | fields=[ 32 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 33 | ('product_id', models.IntegerField()), 34 | ('product_name', models.CharField(max_length=32)), 35 | ('develop', models.TextField(blank=True, null=True)), 36 | ('ops', models.TextField(blank=True, null=True)), 37 | ('test', models.TextField(blank=True, null=True)), 38 | ], 39 | options={ 40 | 'db_table': 'cmdb_product_info', 41 | }, 42 | ), 43 | ] 44 | -------------------------------------------------------------------------------- /cmdb/migrations/0004_cmdbusersshauth.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.17 on 2018-12-16 08:40 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('cmdb', '0003_cmdbansiblesshinfo_cmdbproductinfo'), 12 | ] 13 | 14 | operations = [ 15 | migrations.CreateModel( 16 | name='CmdbUserSshAuth', 17 | fields=[ 18 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 19 | ('username', models.CharField(max_length=32)), 20 | ('user_key', models.TextField(blank=True, null=True)), 21 | ('related_node', models.TextField(blank=True, null=True)), 22 | ('status', models.CharField(blank=True, max_length=32, null=True)), 23 | ('create_time', models.DateTimeField(auto_now_add=True, db_column='Create_Time', null=True)), 24 | ('update_time', models.DateTimeField(auto_now=True, db_column='Update_Time', null=True)), 25 | ('special_ip', models.TextField(blank=True, null=True)), 26 | ], 27 | options={ 28 | 'db_table': 'cmdb_user_ssh_auth', 29 | }, 30 | ), 31 | ] 32 | -------------------------------------------------------------------------------- /cmdb/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brick-li/ops-django/3244fbe4eb782ea07ac38606e5c63b9d2e5525da/cmdb/migrations/__init__.py -------------------------------------------------------------------------------- /cmdb/models.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models 5 | 6 | # Create your models here. 7 | class CmdbPool(models.Model): #设备资源池 8 | inner_addr_ip = models.CharField(db_column='Inner_Addr_IP', max_length=32) # Field name made lowercase. 9 | outer_addr_ip = models.CharField(db_column='Outer_Addr_IP', max_length=32, blank=True, null=True) # Field name made lowercase. 10 | operating_system = models.CharField(db_column='Operating_System', max_length=32, blank=True, null=True) # Field name made lowercase. 11 | status = models.CharField(db_column='Status', max_length=32, blank=True, null=True) # Field name made lowercase. 12 | region = models.CharField(db_column='Region', max_length=32, blank=True, null=True) # Field name made lowercase. 13 | available_zone = models.CharField(db_column='Available_Zone', max_length=32, blank=True, null=True) # Field name made lowercase. 14 | cpu_info = models.IntegerField(db_column='CPU_Info', blank=True, null=True) # Field name made lowercase. 15 | memory_info = models.IntegerField(db_column='Memory_Info', blank=True, null=True) # Field name made lowercase. 16 | expire_time = models.DateTimeField(db_column='Expire_Time', blank=True, null=True) # Field name made lowercase. 17 | create_time = models.DateTimeField(db_column='Create_Time', blank=True, null=True, auto_now_add=True) # Field name made lowercase. 18 | update_time = models.DateTimeField(db_column='Update_Time', blank=True, null=True, auto_now=True) # Field name made lowercase. 19 | 20 | class Meta: 21 | #managed = False 22 | db_table = 'cmdb_pool' 23 | 24 | class CmdbTreeNode(models.Model): #业务目录结构 25 | product_id = models.IntegerField(blank=True, null=True) 26 | node_name = models.CharField(max_length=32, blank=True, null=True) 27 | environment = models.CharField(max_length=32, blank=True, null=True) 28 | service_info = models.TextField(blank=True, null=True) 29 | depth = models.IntegerField(blank=True, null=True) 30 | father_id = models.IntegerField(blank=True, null=True) 31 | create_time = models.DateTimeField(db_column='Create_Time', blank=True, null=True, auto_now_add=True) # Field name made lowercase. 32 | update_time = models.DateTimeField(db_column='Update_Time', blank=True, null=True, auto_now=True) # Field name made lowercase. 33 | node_type = models.CharField(max_length=32, blank=True, null=True) 34 | tag = models.CharField(max_length=32, blank=True, null=True) 35 | 36 | class Meta: 37 | #managed = False 38 | db_table = 'cmdb_tree_node' 39 | 40 | class CmdbProductInfo(models.Model): 41 | product_id = models.IntegerField() 42 | product_name = models.CharField(max_length=32) 43 | develop = models.TextField(blank=True, null=True) 44 | ops = models.TextField(blank=True, null=True) 45 | test = models.TextField(blank=True, null=True) 46 | 47 | class Meta: 48 | #managed = False 49 | db_table = 'cmdb_product_info' 50 | 51 | 52 | class CmdbAnsibleSshInfo(models.Model): 53 | inner_addr_ip = models.CharField(max_length=32) # ip 54 | outer_addr_ip = models.CharField(max_length=32) # ip 55 | ansible_ssh_user = models.CharField(max_length=32) # login user 56 | ansible_ssh_port = models.CharField(max_length=32) # ssh port 57 | ansible_sudo_pass = models.CharField(max_length=255) # su pass 58 | 59 | class Meta: 60 | #managed = False 61 | db_table = 'cmdb_ansible_ssh_info' 62 | 63 | class CmdbUserSshAuth(models.Model): 64 | username = models.CharField(max_length=32) 65 | user_key = models.TextField(blank=True, null=True) 66 | related_node = models.TextField(blank=True, null=True) 67 | status = models.CharField(max_length=32, blank=True, null=True) 68 | create_time = models.DateTimeField(db_column='Create_Time', blank=True, null=True, 69 | auto_now_add=True) # Field name made lowercase. 70 | update_time = models.DateTimeField(db_column='Update_Time', blank=True, null=True, 71 | auto_now=True) # Field name made lowercase. 72 | special_ip = models.TextField(blank=True, null=True) # special 73 | 74 | class Meta: 75 | #managed = False 76 | db_table = 'cmdb_user_ssh_auth' -------------------------------------------------------------------------------- /cmdb/tests.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.test import TestCase 5 | 6 | # Create your tests here. 7 | -------------------------------------------------------------------------------- /cmdb/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import url 2 | from cmdb.views import cmdb_pool, product_info, tree, user_auth 3 | 4 | urlpatterns = [ 5 | # CMDB pool 6 | url(r'^/cmdb_pool/cmdb_pool', cmdb_pool.cmdb_pool, name="cmdb-pool"), 7 | url(r'^/cmdb_pool/cmdb_update', cmdb_pool.cmdb_update, name="cmdb-pool-update"), 8 | url(r'^/cmdb_pool/cmdb_delete', cmdb_pool.cmdb_delete, name="cmdb-pool-delete"), 9 | 10 | # CMDB product info 11 | url(r'^/product_info/product_list', product_info.product_list, name="cmdb-product-list"), 12 | url(r'^/product_info/commit_product', product_info.commit_product, name="cmdb-product-commit-product"), 13 | url(r'^/product_info/delete_product', product_info.delete_product, name="cmdb-product-delete-product"), 14 | 15 | # CMDB tree info 16 | url(r'^/tree/tree_info', tree.tree_info, name="cmdb-tree-info"), 17 | url(r'^/tree/get_node_info', tree.get_node_info, name="cmdb-get-tree-node-info"), 18 | url(r'^/tree/change_father_node', tree.change_father_node, name="cmdb-change-father-node"), 19 | url(r'^/tree/get_unused_ip_from_cmdb_pool', tree.get_unused_ip_from_cmdb_pool, name="cmdb-get-unused-ip-from-cmdb-pool"), 20 | url(r'^/tree/get_prod_list', tree.get_prod_list, name="cmdb-get-product-list"), 21 | url(r'^/tree/save_node_change', tree.save_node_change, name="cmdb-save-node-change"), 22 | url(r'^/tree/create_node', tree.create_node, name="cmdb-create-node"), 23 | url(r'^/tree/delete_node', tree.delete_node, name="cmdb-delete-tree-node"), 24 | 25 | 26 | # CMDB user ssh key 27 | url(r'^/user_auth/get_user_info', user_auth.get_user_info, name="cmdb-user-auth-info"), 28 | url(r'^/user_auth/tree_node_list', user_auth.tree_node_list, name="cmdb-user-auth-tree-node-list"), 29 | url(r'^/user_auth/commit_user_auth', user_auth.commit_user_auth, name="cmdb-user-auth-commit-user-auth"), 30 | url(r'^/user_auth/delete_user', user_auth.delete_user_auth, name="cmdb-user-auth-delete-user"), 31 | url(r'^/user_auth/sync_auth', user_auth.sync_auth, name="cmdb-sync-auth"), 32 | ] -------------------------------------------------------------------------------- /cmdb/views/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brick-li/ops-django/3244fbe4eb782ea07ac38606e5c63b9d2e5525da/cmdb/views/__init__.py -------------------------------------------------------------------------------- /cmdb/views/cmdb_pool.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | 3 | # Create your views here. 4 | 5 | import traceback 6 | import logging 7 | from django.http import HttpResponse 8 | from django.views.decorators.csrf import csrf_exempt 9 | # from django.contrib.auth.decorators import login_required 10 | # from django.db import transaction 11 | from django.db.models import Q 12 | from cmdb.models import CmdbPool, CmdbTreeNode 13 | import json 14 | # from account.views.login import check_login 15 | # from django.contrib.auth.decorators import login_required 16 | 17 | _logger = logging.getLogger(__name__) 18 | 19 | 20 | @csrf_exempt 21 | def cmdb_pool(request): 22 | cmdb_info = list(CmdbPool.objects.all().values()) 23 | cmdb_info_table = [{u"id": int(i['id']), u"inner_addr_ip": i['inner_addr_ip'], 24 | u"outer_addr_ip": i['outer_addr_ip'], 25 | u"operating_system": i['operating_system'], 26 | u"status": i['status'], u"region":i['region'],u"available_zone":i['available_zone']} for i in cmdb_info ] 27 | return HttpResponse(json.dumps({"result":"success", "data": cmdb_info_table})) 28 | 29 | @csrf_exempt 30 | def cmdb_update(request): 31 | data_id = request.POST.get('id') 32 | data_val = { 33 | "available_zone": request.POST.get('available_zone'), 34 | "inner_addr_ip": request.POST.get('inner_addr_ip'), 35 | "outer_addr_ip": request.POST.get('outer_addr_ip'), 36 | "operating_system": request.POST.get('operating_system'), 37 | "region": request.POST.get('region') 38 | } 39 | # check if exists 40 | if data_id in [None, '']: 41 | if CmdbPool.objects.filter(Q(inner_addr_ip=data_val['inner_addr_ip']) | Q(outer_addr_ip=data_val['outer_addr_ip'])): 42 | return HttpResponse(json.dumps({"result": "failed", "info": "资源已经存在"})) 43 | CmdbPool.objects.create(**data_val) 44 | else: 45 | if CmdbPool.objects.filter(~Q(id=data_id), Q(inner_addr_ip=data_val['inner_addr_ip']) | Q(outer_addr_ip=data_val['outer_addr_ip'])): 46 | return HttpResponse(json.dumps({"result": "failed", "info": "资源已经存在"})) 47 | CmdbPool.objects.filter(id=data_id).update(**data_val) 48 | return HttpResponse(json.dumps({"result": "success", "info": "提交成功"})) 49 | 50 | @csrf_exempt 51 | def cmdb_delete(request): 52 | try: 53 | data_id = request.POST.get('id') 54 | inner_ip = request.POST.get('inner_ip') 55 | outer_ip = request.POST.get('outer_ip') 56 | try: 57 | if CmdbTreeNode.objects.get(Q(node_name=inner_ip)|Q(node_name=outer_ip)): 58 | return HttpResponse(json.dumps({"result": "failed", "info": "在业务模型中还未移除该资源"})) 59 | except Exception: 60 | pass 61 | CmdbPool.objects.filter(id=data_id).delete() 62 | return HttpResponse(json.dumps({"result": "success", "info": "删除成功"})) 63 | except Exception: 64 | print traceback.format_exc() 65 | return HttpResponse(json.dumps({"result": "failed", "info": "删除失败"})) -------------------------------------------------------------------------------- /cmdb/views/product_info.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | 3 | # Create your views here. 4 | 5 | import traceback 6 | import logging, json 7 | from django.http import HttpResponse 8 | from django.views.decorators.csrf import csrf_exempt 9 | from cmdb.models import CmdbProductInfo 10 | 11 | _logger = logging.getLogger(__name__) 12 | 13 | @csrf_exempt 14 | def commit_product(request): 15 | try: 16 | product_id = request.POST.get("product_id") 17 | product_name = request.POST.get("product_name") 18 | cmdb_productinfo, created = CmdbProductInfo.objects.get_or_create(product_id=product_id, product_name=product_name) 19 | cmdb_productinfo.develop = request.POST.get("develop") 20 | cmdb_productinfo.ops = request.POST.get("ops") 21 | cmdb_productinfo.test = request.POST.get("test") 22 | cmdb_productinfo.save() 23 | return HttpResponse(json.dumps({"result": "success", "info": "提交成功"})) 24 | except Exception: 25 | print traceback.format_exc() 26 | _logger.error(traceback.format_exc()) 27 | return HttpResponse(json.dumps({"result": "failed", "info": "提交失败"})) 28 | 29 | @csrf_exempt 30 | def delete_product(request): 31 | try: 32 | product_id = request.POST.get("product_id") 33 | product_name = request.POST.get("product_name") 34 | print CmdbProductInfo.objects.filter(product_id=product_id, product_name=product_name).values() 35 | CmdbProductInfo.objects.filter(product_id=product_id, product_name=product_name).delete() 36 | return HttpResponse(json.dumps({"result": "success", "info": "删除成功"})) 37 | except Exception: 38 | print traceback.format_exc() 39 | _logger.error(traceback.format_exc()) 40 | return HttpResponse(json.dumps({"result": "failed", "info": "删除失败"})) 41 | 42 | @csrf_exempt 43 | def product_list(request): 44 | try: 45 | return HttpResponse(json.dumps({"result": "success", "data": list(CmdbProductInfo.objects.all().values())})) 46 | except Exception: 47 | print traceback.format_exc() 48 | _logger.error(traceback.format_exc()) 49 | return HttpResponse(json.dumps({"result": "failed", "info": "查询列表失败"})) 50 | 51 | def get_product_name_byid(ProductId): 52 | cmdb_product_info = CmdbProductInfo.objects.filter(product_id=ProductId).first() 53 | if cmdb_product_info: 54 | return cmdb_product_info.product_name 55 | else: 56 | return '未指定业务' 57 | 58 | def get_product_id(product_name): 59 | cmdb_product_info = CmdbProductInfo.objects.filter(product_name=product_name).first() 60 | if cmdb_product_info: 61 | return cmdb_product_info.product_id 62 | else: 63 | return -1 -------------------------------------------------------------------------------- /cmdb/views/tree.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | 3 | # Create your views here. 4 | 5 | import traceback 6 | import logging 7 | from django.utils import timezone 8 | from django.http import HttpResponse 9 | from django.shortcuts import render_to_response 10 | from django.views.decorators.csrf import csrf_exempt 11 | from django.contrib.auth.decorators import login_required 12 | from django.db import transaction 13 | from django.db.models import Q 14 | from cmdb.views.product_info import get_product_name_byid 15 | from cmdb.models import CmdbPool, CmdbTreeNode, CmdbProductInfo, CmdbAnsibleSshInfo 16 | import json,time,datetime 17 | from classes import crypto, get_ip_show_type 18 | import getopt 19 | 20 | 21 | _logger = logging.getLogger(__name__) 22 | 23 | ####################tree API############################ 24 | 25 | def get_ips_by_set_module(module_args, normal_params=None, file_params=None): 26 | ''' 27 | 根据set和module查询ip地址 28 | :return: 29 | ''' 30 | opts, ags = getopt.getopt(module_args.split(), '-h-s:-m:-e:-H:') 31 | sets = None 32 | modules = None 33 | env = None 34 | ip = None 35 | _ips = [] 36 | for _opt_name, _opt_value in opts: 37 | if _opt_name in ('-h'): 38 | return {'result': 'success', 39 | 'info': 'module_args:\n -s "set1,set2" \n-m "module1,module2" \n-e "环境类型:正式环境,测试环境"\n--root 强制以root身份执行\n--所有参数可使用,分隔多个参数'} 40 | if _opt_name in ('-s'): 41 | sets = _opt_value 42 | if _opt_name in ('-m'): 43 | modules = _opt_value 44 | if _opt_name in ('-e'): 45 | env = _opt_value 46 | if _opt_name in ('-H'): 47 | ip = _opt_value 48 | if sets is None and ip is None: # 无参数 49 | if normal_params is None and file_params is None: 50 | return [] 51 | else: 52 | return [], [] 53 | if ip is not None: 54 | _ips += list(CmdbTreeNode.objects.filter(depth=3, node_name__in=ip.split(',')).values()) 55 | if sets is not None: 56 | sets = sets.split(',') 57 | modules_in_sets = CmdbTreeNode.objects.filter(depth=1, node_name__in=sets).values('id') if env is None else \ 58 | CmdbTreeNode.objects.filter(depth=1, node_name__in=sets, environment=env.split(',')).values('id') 59 | sets_id = [i['id'] for i in modules_in_sets] 60 | modules_in_sets = CmdbTreeNode.objects.filter(depth=2, father_id__in=sets_id).values('id') if modules is None else \ 61 | CmdbTreeNode.objects.filter(depth=2, father_id__in=sets_id, node_name__in=modules.split(',')).values('id') 62 | modules_id = [i['id'] for i in modules_in_sets] 63 | _ips += list(CmdbTreeNode.objects.filter(depth=3, father_id__in=modules_id).values()) 64 | 65 | ret = list() 66 | ips = list() 67 | if normal_params is not None: 68 | ips = [i['node_name'] for i in _ips] 69 | ret = [{'ip': i['node_name'], 'params': normal_params} for i in _ips] 70 | if file_params is not None: 71 | opts, ags = getopt.getopt(file_params.split(), '', ['service', 'custom=']) 72 | for item in _ips: 73 | data = '' 74 | for _opt_name, _opt_value in opts: 75 | if _opt_name in ('--service'): 76 | if item['service_info']: 77 | data = "%s%s " % (data, item['service_info']) 78 | else: 79 | data = "%s%s " % (data, '') 80 | if _opt_name in ('--custom'): 81 | data = "%s%s " % (data, _opt_value) 82 | ret.append({ 83 | 'ip': item['node_name'], 84 | 'params': data 85 | }) 86 | ips.append(item['node_name']) 87 | if normal_params is None and file_params is None: 88 | return [i['node_name'] for i in _ips] 89 | return ips, ret 90 | 91 | 92 | def get_tree_options(module_args): 93 | """ 94 | 获取tree信息 95 | :return: 96 | """ 97 | opts, ags = getopt.getopt(module_args.split(), '-h-s:-m:-e') 98 | 99 | def get_prod_id_by_name(prod_name): 100 | prod_info = list(CmdbProductInfo.objects.filter(product_name=prod_name).values("product_id")) 101 | if len(prod_info) == 0: 102 | return -1 103 | return prod_info[0]['product_id'] 104 | 105 | ######################################################## 106 | 107 | @login_required 108 | def resource_total(request): 109 | cmdb_info = CmdbPool.objects.all().values() 110 | 111 | cmdb_info_table = [{u"id": int(i['id']), u"inner_addr_ip": i['inner_addr_ip'], 112 | u"outer_addr_ip": i['outer_addr_ip'], 113 | u"operating_system": i['operating_system'], 114 | u"status": i['status'], u"region":i['region']} for i in cmdb_info ] 115 | return render_to_response("resource/total.html", {"page":"page1", "table_data": json.dumps(cmdb_info_table)}) 116 | 117 | 118 | 119 | #@login_required 120 | @csrf_exempt 121 | def tree_info(request): 122 | tree_root = datefield_to_str(list(CmdbTreeNode.objects.filter(father_id=0, depth=1).values())) 123 | treeDataRoot = [ 124 | {'id': i['id'], 125 | 'depth': i['depth'], 126 | 'node_id': i['id'], 127 | 'label': i['node_name'], 128 | 'environment': i['environment'], 129 | 'product_id':i['product_id'], 130 | 'product_name':get_product_name_byid(i['product_id']), 131 | 'update_time': i['update_time'], 132 | 'node_type': i['node_type'], 133 | 'father_id': i['father_id'], 134 | 'children': [] 135 | } for i in tree_root ] 136 | tree_root_ids = [i['id'] for i in tree_root] 137 | tree_second = datefield_to_str((list(CmdbTreeNode.objects.filter(father_id__in=tree_root_ids).values()))) 138 | treeDataSecond = [ 139 | {'id': i['id'], 140 | 'depth': i['depth'], 141 | 'node_id': i['id'], 142 | 'label': i['node_name'], 143 | 'environment': i['environment'], 144 | 'product_id': i['product_id'], 145 | 'product_name': get_product_name_byid(i['product_id']), 146 | 'update_time': i['update_time'], 147 | 'node_type': i['node_type'], 148 | 'father_id': i['father_id'], 149 | 'children': [] 150 | } for i in tree_second] 151 | tree_second_ids = [i['id'] for i in tree_second] 152 | tree_third = datefield_to_str(list(CmdbTreeNode.objects.filter(father_id__in=tree_second_ids).values())) 153 | treeDataThird = [ 154 | {'id': i['id'], 155 | 'depth': i['depth'], 156 | 'node_id': i['id'], 157 | 'label': i['node_name'], 158 | 'environment': i['environment'], 159 | 'product_id': i['product_id'], 160 | 'product_name': get_product_name_byid(i['product_id']), 161 | 'update_time': i['update_time'], 162 | 'node_type': i['node_type'], 163 | 'father_id': i['father_id'], 164 | } for i in tree_third] 165 | 166 | for third in treeDataThird: 167 | for second in treeDataSecond: 168 | if third['father_id'] == second['node_id']: 169 | second['children'].append(third) #填充2层 170 | for second in treeDataSecond: 171 | for root in treeDataRoot: #填充根 172 | if second['father_id'] == root['node_id']: 173 | root['children'].append(second) 174 | # create tree node 175 | return HttpResponse(json.dumps({"result": "success", "data": treeDataRoot})) 176 | 177 | 178 | @csrf_exempt 179 | # 点击时拉取node信息 180 | def get_node_info(request): 181 | node_id = request.POST.get("node_id") 182 | node_info = list(CmdbTreeNode.objects.filter(id=node_id).values()) 183 | node_info = datefield_to_str(node_info) 184 | if len(node_info) == 0: 185 | node_info = {} 186 | else: 187 | node_info = node_info[0] 188 | #定义页面显示的内容 189 | lines = [ 190 | "product_id", 191 | "product_name", 192 | "node_type", 193 | "environment", 194 | "service_info", 195 | "create_time", 196 | ] 197 | node_info['product_name'] = get_product_name_byid(node_info.get('product_id', 0)) 198 | if node_info.get("depth", -1) == 3: 199 | from django.db.models import Q 200 | try: 201 | outter_ip = CmdbPool.objects.get(Q(inner_addr_ip=node_info['node_name'])|Q(outer_addr_ip=node_info['node_name'])) 202 | except CmdbPool.DoesNotExist: 203 | outter_ip = None 204 | if outter_ip: 205 | node_info['inner_addr_ip'] = outter_ip.inner_addr_ip 206 | node_info['outer_addr_ip'] = outter_ip.outer_addr_ip 207 | else: 208 | node_info['inner_addr_ip'] = '' 209 | node_info['outer_addr_ip'] = '' 210 | try: 211 | ansible_ssh_info = CmdbAnsibleSshInfo.objects.get( 212 | Q(inner_addr_ip=node_info['node_name'])|Q(outer_addr_ip=node_info['node_name'])) 213 | except CmdbAnsibleSshInfo.DoesNotExist: 214 | ansible_ssh_info = None 215 | node_info['ansible_ssh_user'] = '' 216 | node_info['ansible_ssh_port'] = '' 217 | node_info['ansible_sudo_pass'] = '' 218 | if ansible_ssh_info: 219 | node_info['ansible_ssh_user'] = ansible_ssh_info.ansible_ssh_user 220 | node_info['ansible_ssh_port'] = ansible_ssh_info.ansible_ssh_port 221 | node_info['ansible_sudo_pass'] = '' if ansible_ssh_info.ansible_sudo_pass == '' else ansible_ssh_info.ansible_sudo_pass 222 | lines.append('inner_addr_ip') 223 | lines.append('outer_addr_ip') 224 | lines.append('ansible_ssh_user') 225 | lines.append('ansible_ssh_port') 226 | lines.append('ansible_sudo_pass') 227 | if node_info['service_info']: 228 | node_info['service_info'] = node_info['service_info'].split(';') 229 | else: 230 | node_info['service_info'] = list() 231 | return HttpResponse(json.dumps({"result":"success", "data": {"node_info": node_info, "lines": lines}})) 232 | 233 | @csrf_exempt 234 | def change_father_node(request): 235 | try: 236 | node_id = request.POST.get('node_id', None) 237 | father_id = request.POST.get('father_id', None) 238 | 239 | father_node = CmdbTreeNode.objects.get(id=father_id) 240 | env = father_node.environment 241 | product_id = father_node.product_id 242 | CmdbTreeNode.objects.filter(id=node_id).update(father_id=father_id, environment=env, product_id=product_id) 243 | return HttpResponse(json.dumps({"result":"success", "info": "修改父节点成功"})) 244 | except Exception: 245 | print traceback.format_exc() 246 | return HttpResponse(json.dumps({"result": "failed", "info": "修改父节点失败"})) 247 | 248 | @csrf_exempt 249 | def get_unused_ip_from_cmdb_pool(request): 250 | ''' 251 | 获取资源池空闲机 252 | :return: 253 | ''' 254 | inuse_info = CmdbTreeNode.objects.filter(node_type='ip').values() 255 | inuse_ip = [ i['node_name'] for i in inuse_info ] 256 | unuse_info = list(CmdbPool.objects.exclude(Q(inner_addr_ip__in=inuse_ip)|Q(outer_addr_ip__in=inuse_ip)).values()) 257 | 258 | #print unuse_info 查询节点ip展示方式,外网ip或者内网ip 259 | show_type = get_ip_show_type.get_show_type() 260 | if show_type == 'outer_ip': 261 | unuse_ip = [ {"label": i['outer_addr_ip'], "value": i['outer_addr_ip']} for i in unuse_info ] 262 | else: 263 | unuse_ip = [{"label": i['inner_addr_ip'], "value": i['inner_addr_ip']} for i in unuse_info] 264 | 265 | return HttpResponse(json.dumps({"result": "success", "data": unuse_ip})) 266 | 267 | @csrf_exempt 268 | def get_prod_list(request): 269 | ''' 270 | 拉取所有product信息 271 | :return: 272 | ''' 273 | cmdb_product_info = CmdbProductInfo.objects.all().values() 274 | print [ {'value':i['product_id'], 'lable': i['product_name']} for i in cmdb_product_info ] 275 | return HttpResponse(json.dumps({"result": "success", "data": [ {'value':i['product_id'], 'label': i['product_name']} for i in cmdb_product_info ]})) 276 | 277 | 278 | 279 | @csrf_exempt 280 | def save_node_change(request): 281 | ''' 282 | 修改节点信息 283 | :param request: 284 | :return: 285 | ''' 286 | try: 287 | rowid = request.POST.get('rowid') 288 | rowKey = request.POST.get('rowKey') 289 | rowValue = request.POST.get('rowValue') 290 | if rowKey == "ansible_sudo_pass": 291 | if rowValue == '': 292 | rowValue = '' 293 | else: 294 | rowValue = crypto.passwd_aes(rowValue) 295 | updateinfo = { 296 | rowKey: rowValue 297 | } 298 | if rowKey.startswith("ansible"): 299 | print rowid 300 | node_info = CmdbTreeNode.objects.get(id=rowid) 301 | if node_info is None: 302 | return HttpResponse(json.dumps({"result": "failed", "info": "节点不存在"})) 303 | IpInfo = CmdbPool.objects.get( 304 | Q(inner_addr_ip=node_info.node_name) | Q(outer_addr_ip=node_info.node_name)) 305 | if node_info is None: 306 | return HttpResponse(json.dumps({"result": "failed", "info": "ip不在资源池中"})) 307 | inner_addr_ip = IpInfo.inner_addr_ip 308 | outer_addr_ip = IpInfo.outer_addr_ip 309 | CmdbAnsibleSshInfoDb, created = CmdbAnsibleSshInfo.objects.get_or_create(inner_addr_ip=inner_addr_ip, outer_addr_ip=outer_addr_ip) 310 | CmdbAnsibleSshInfoDb.save() 311 | _logger.info(CmdbAnsibleSshInfo.objects.filter(inner_addr_ip=inner_addr_ip, outer_addr_ip=outer_addr_ip).values()) 312 | CmdbAnsibleSshInfo.objects.filter(inner_addr_ip=inner_addr_ip, outer_addr_ip=outer_addr_ip).update(**updateinfo) 313 | else: 314 | CmdbTreeNode.objects.filter(id=rowid).update(**updateinfo) 315 | return HttpResponse(json.dumps({"result": "success", "info": "保存成功"})) 316 | except Exception: 317 | _logger.error("Error while save change, %s" % traceback.format_exc()) 318 | print traceback.format_exc() 319 | return HttpResponse(json.dumps({"result": "failed", "info": "保存失败"})) 320 | 321 | @transaction.atomic # 数据库事务 322 | @csrf_exempt 323 | def create_node(request): 324 | """ 325 | 创建节点 326 | :param request: 327 | :return: 328 | """ 329 | update_time = timezone.make_aware(datetime.datetime.strptime("02/03/2014 12:00 UTC", "%d/%m/%Y %H:%M %Z"), timezone.get_default_timezone()) 330 | time_now = str(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(time.time()))) 331 | node_type = request.POST.get('node_type') 332 | if node_type == 'ip': 333 | ip = request.POST.get('node_name').strip() 334 | if not check_ip_exist(ip): 335 | return HttpResponse(json.dumps({"result": "failed","info":"创建的ip不存在资源库中,请先加入资源库"})) 336 | product_id = int(request.POST.get("product_id", "-1")) 337 | print product_id 338 | CmdbTreeNode.objects.create(product_id=product_id, node_name=request.POST.get("node_name", "-1"), 339 | environment=request.POST.get("environment", "-1"),depth=request.POST.get("depth"), 340 | father_id=request.POST.get('father_id'),node_type=request.POST.get("node_type")) 341 | created_id = CmdbTreeNode.objects.latest('id').id 342 | product_name = get_prod_name(request.POST.get("product_id", -1)) 343 | return HttpResponse(json.dumps({"result": "success", "info":"添加成功", "data": {"created_id": created_id, "updatetime": time_now, "product_name": product_name}})) 344 | 345 | @csrf_exempt 346 | def delete_node(request): 347 | """ 348 | 删除节点 349 | :param request: 350 | :return: 351 | """ 352 | node_id = int(request.POST.get("node_id", -1)) 353 | tree_all = list(CmdbTreeNode.objects.all().values()) 354 | nodeids = [node_id] 355 | traverse_node(node_id, tree_all, nodeids) #获取所有需要删除的节点 356 | if not node_id == -1: 357 | CmdbTreeNode.objects.filter(id__in=nodeids).delete() 358 | return HttpResponse(json.dumps({"result": "success", "info": "删除成功"})) 359 | 360 | 361 | # 递归查找所有子节点 362 | def traverse_node(father_id, tree_all, nodeids): 363 | for i in tree_all: 364 | if father_id == i['father_id']: 365 | nodeids.append(i['id']) 366 | traverse_node(i['id'], tree_all, nodeids) 367 | else: 368 | pass 369 | 370 | 371 | def datefield_to_str(date_data): 372 | for i in date_data: 373 | if i.has_key("create_time"): 374 | i["create_time"] = i["create_time"].strftime("%Y-%m-%d %H:%M:%S") 375 | if i.has_key("update_time"): 376 | i["update_time"] = i["update_time"].strftime("%Y-%m-%d %H:%M:%S") 377 | return date_data 378 | 379 | def get_prod_name(prod_id): 380 | prod_info = list(CmdbProductInfo.objects.filter(product_id=prod_id).values("product_name")) 381 | if len(prod_info) == 0: 382 | return "未指定业务" 383 | return prod_info[0]['product_name'] 384 | 385 | 386 | 387 | def check_ip_exist(ip): 388 | ''' 389 | 查询ip是否存在资源库 390 | :return: 391 | ''' 392 | exist = list(CmdbPool.objects.filter(Q(inner_addr_ip=ip)|Q(outer_addr_ip=ip)).values()) 393 | if not len(exist) == 0: 394 | return True 395 | else: 396 | return False 397 | 398 | def sync_node_name(): 399 | ''' 400 | 启动同步node name 为外网或者内网ip 由配置决定 401 | :return: 402 | ''' 403 | ip_show_type = get_ip_show_type.get_show_type() 404 | nodes = CmdbTreeNode.objects.filter(node_type='ip') 405 | ip_show_type = 'outer_addr_ip' if ip_show_type=='outer_ip' else 'inner_addr_ip' 406 | for _res in CmdbPool.objects.all().values(): 407 | node_f = nodes.filter(Q(node_name=_res['outer_addr_ip'])|Q(node_name=_res['inner_addr_ip'])) 408 | if node_f: 409 | node_f.update(node_name=_res[ip_show_type]) 410 | 411 | 412 | sync_node_name() -------------------------------------------------------------------------------- /cmdb/views/user_auth.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | 3 | # Create your views here. 4 | from __future__ import absolute_import 5 | from django.http import HttpResponse 6 | from django.utils import timezone 7 | import datetime 8 | from django.views.decorators.csrf import csrf_exempt 9 | import traceback 10 | import json 11 | import os 12 | from django.db.models import Q 13 | import logging 14 | from classes import ansible_api, my_concurrent 15 | from cmdb.models import CmdbTreeNode, CmdbUserSshAuth 16 | 17 | _logger = logging.getLogger(__name__) 18 | 19 | 20 | # 获取本机公钥 21 | USERHOME = os.environ['HOME'] 22 | with open("%s/.ssh/id_rsa.pub" % USERHOME) as f: 23 | LOCALSSHKEY = f.read().strip() 24 | 25 | #############common############ 26 | 27 | def datefield_to_str(date_data): 28 | ''' 29 | mysql date格式转换为字符串格式 30 | :param date_data: 31 | :return: 32 | ''' 33 | for i in date_data: 34 | if i.has_key("create_time") and i.get('create_time', None) is not None: 35 | i["create_time"] = i["create_time"].strftime("%Y-%m-%d %H:%M:%S") 36 | if i.has_key("update_time") and i.get('update_time', None) is not None: 37 | i["update_time"] = i["update_time"].strftime("%Y-%m-%d %H:%M:%S") 38 | return date_data 39 | 40 | def get_id_by_name(name): 41 | ''' 42 | 根据节点名称获取id 43 | :param name: 44 | :return: 45 | ''' 46 | try: 47 | ids = [i['id'] for i in list(CmdbTreeNode.objects.filter(~Q(node_type='ip'), node_name=name).values())] 48 | return ids 49 | except(Exception): 50 | return None 51 | 52 | 53 | # 递归查找所有子节点 54 | def traverse_node(father_id, tree_all, nodeids): 55 | for i in tree_all: 56 | if father_id == i['father_id']: 57 | nodeids.append(i['id']) 58 | traverse_node(i['id'], tree_all, nodeids) 59 | else: 60 | pass 61 | 62 | def get_app_ip_by_father(father_ids): 63 | ''' 64 | 根据父节点找所有ip 65 | :param father_id: 66 | :return: 67 | ''' 68 | tree_all = list(CmdbTreeNode.objects.all().values()) 69 | all_ip = list() 70 | for father_id in father_ids: 71 | nodeids = list() 72 | traverse_node(father_id, tree_all, nodeids) 73 | all_ip += [i['node_name'] for i in list(CmdbTreeNode.objects.filter(id__in=nodeids, node_type='ip').values())] 74 | return list(set(all_ip)) 75 | 76 | def get_all_node(): 77 | nodes = datefield_to_str(list(CmdbTreeNode.objects.filter(node_type='app').values())) 78 | return nodes 79 | 80 | def get_keys_sync_info(): 81 | ''' 82 | 获取每个ip对应的用户key 83 | :return ip:user_key, 关联用户id 84 | ''' 85 | user_key_info = list(CmdbUserSshAuth.objects.all().values()) 86 | '''获取所有关联的模块''' 87 | all_apps = [i['related_node'] for i in user_key_info] 88 | while '' in all_apps: 89 | all_apps.remove('') 90 | all_apps = list(set((','.join(all_apps)).split(','))) 91 | keys_sync_info = dict() 92 | for i in all_apps: 93 | if not keys_sync_info.has_key(i): 94 | keys_sync_info[i] = {'ip':[]} #保存IP 95 | father_ids = get_id_by_name(i) 96 | print father_ids 97 | set_ips = get_app_ip_by_father(father_ids) 98 | print set_ips 99 | for n in set_ips: 100 | if not n in keys_sync_info[i]['ip']: 101 | keys_sync_info[i]['ip'].append(n) 102 | ip_keys = dict() #保存ip对应key信息 103 | for i in user_key_info: 104 | user_id = i['id'] 105 | user_key = i['user_key'] 106 | related_nodes = i['related_node'].split(',') 107 | for node in related_nodes: 108 | if keys_sync_info.has_key(node): 109 | for ip in keys_sync_info[node]['ip']: 110 | if not ip_keys.has_key(ip): 111 | ip_keys[ip] = list() 112 | ip_keys[ip].append(user_key) 113 | 114 | #special ip 115 | for item in user_key_info: 116 | special_ip = item.get('special_ip') 117 | key = item.get('user_key') 118 | if special_ip == '': 119 | continue 120 | elif special_ip is None: 121 | continue 122 | else: 123 | special_ips = special_ip.split(',') 124 | for ip in special_ips: 125 | if ip_keys.has_key(ip.strip()): 126 | ip_keys[ip.strip()].append(key) 127 | else: 128 | ip_keys[ip.strip()] = [key] 129 | 130 | #去重 131 | for k, v in ip_keys.items(): 132 | ip_keys[k] = list(set(ip_keys[k])) 133 | ret_ip_keys = list() 134 | for ip, keys in ip_keys.items(): 135 | keys_str = get_base_auth_key() if len(keys) == 0 else u"%s\n%s" % ('\n'.join(keys), get_base_auth_key()) 136 | if len(keys_str.split('\n')) <= 3: 137 | print("length of keys less than 3, plase check") 138 | _logger.error("length of keys less than 3, plase check") 139 | return None, None 140 | ret_ip_keys.append([ip, keys_str]) 141 | return ret_ip_keys 142 | 143 | def change_user_stat(status='synced'): 144 | ''' 145 | 改变用户状态 146 | :return: 147 | ''' 148 | CmdbUserSshAuth.objects.all().update(status=status) 149 | 150 | 151 | 152 | 153 | def get_base_auth_key(): 154 | ''' 155 | 获取没台管理的主机都需要添加的sshkey 156 | :return: 157 | ''' 158 | with open("conf/sshkey_global.conf", 'r') as f: 159 | FileKey = f.read() 160 | return "%s\n%s" % (FileKey, LOCALSSHKEY) 161 | 162 | def sync_auth_keys(keys_synv_info): 163 | ''' 164 | 实际执行同步user key的方法 165 | :return: 166 | ''' 167 | try: 168 | ip = keys_synv_info[0] 169 | keys = keys_synv_info[1] 170 | ansible_interface = ansible_api.AnsiInterface(become=True, become_method='sudo', become_user='root') 171 | ansible_interface.sync_authorized_key(ip, 'user=root key="%s" exclusive=True state=present' % keys) 172 | ansible_interface.sync_authorized_key(ip, 'user={{ ansible_ssh_user }} key="%s" exclusive=True state=present' % keys) 173 | result = ansible_interface._get_result() 174 | if ip not in (list(set(result['host_unreachable'].keys() + result['host_ok'].keys() + result['host_failed'].keys()))): 175 | result['host_failed'][ip] = "sync failed, mabe password error" 176 | return result 177 | 178 | except Exception: 179 | try: 180 | ip = keys_synv_info[0] 181 | except: 182 | ip = None 183 | _logger.error("sync failed: %s" % traceback.format_exc()) 184 | return { 185 | 'host_ok': '', 186 | 'host_unreachable': '', 187 | 'host_failed': {ip: "failed: %s" % traceback.format_exc()} 188 | } 189 | 190 | 191 | @csrf_exempt 192 | def sync_auth(request): 193 | ''' 194 | 同步用户key到服务器 195 | :param modules: 196 | :return: 197 | ''' 198 | try: 199 | keys_synv_info = get_keys_sync_info() # 根据需要同步的模块去查询每个ip对应要加的key 200 | # print keys_synv_info 201 | if not keys_synv_info or len(keys_synv_info) == 0: 202 | return HttpResponse({'result': "没有相关ip,或key获取失败,请检查日志"}) 203 | # 定义并发runlist 204 | 205 | # 开始并发处理 206 | keys_sync_handler = my_concurrent.MyMultiProcess(10) 207 | # sync_auth_keys(keys_synv_info) 208 | for i in keys_synv_info: 209 | keys_sync_handler.multi_process_add(sync_auth_keys, i) 210 | keys_sync_handler.multi_process_wait() 211 | result = keys_sync_handler.get_result() 212 | print result 213 | g_res = dict() 214 | for _res in result: 215 | __res = _res 216 | for k,v in __res.items(): 217 | if not g_res.has_key(k): 218 | g_res[k] = dict() 219 | if v: 220 | for _ip,r in v.items(): 221 | if _ip not in g_res[k].keys(): 222 | g_res[k][_ip] = [] 223 | g_res[k][_ip].append(r) 224 | 225 | # 设置用户已同步 226 | change_user_stat(status='synced') 227 | except(Exception): 228 | _logger.error(traceback.format_exc()) 229 | g_res = "sync failed" 230 | return HttpResponse(json.dumps({'result': 'failed', 'info': g_res})) 231 | if g_res['host_failed'] or g_res['host_unreachable']: 232 | failed_ip = g_res['host_failed'].keys() + g_res['host_unreachable'].keys() 233 | return HttpResponse(json.dumps({'result': 'failed', 'info': '失败IP: %s' % failed_ip})) 234 | return HttpResponse(json.dumps({'result': 'success', 'info': '同步成功'})) 235 | 236 | @csrf_exempt 237 | def delete_user_auth(request): 238 | ''' 239 | 清除用户登陆 240 | :param request: 241 | :return: 242 | ''' 243 | try: 244 | user_auth_name = request.POST.get('delete_user_name', 'None') 245 | CmdbUserSshAuth.objects.filter(username=user_auth_name).delete() 246 | return HttpResponse(json.dumps({'result': 'success','info':'用户key已删除,请点击同步'})) 247 | except Exception: 248 | _logger.error("delete_user_auth:%s" % traceback.format_exc()) 249 | return HttpResponse(json.dumps({'result': 'failed', 'info':'删除失败'})) 250 | 251 | @csrf_exempt 252 | def get_user_info(request): 253 | ''' 254 | 展示所有用户信息 255 | :param request: 256 | :return: 257 | ''' 258 | user_key_auth = datefield_to_str(list(CmdbUserSshAuth.objects.all().values())) 259 | return HttpResponse(json.dumps({"result":"success", "data": {"user_key_auth": user_key_auth, 260 | "nodes": get_all_node()}})) 261 | 262 | @csrf_exempt 263 | def tree_node_list(request): 264 | tree_node = datefield_to_str(list(CmdbTreeNode.objects.filter(depth=1,father_id=0).values())) 265 | return HttpResponse(json.dumps({"result": "success", "data": tree_node})) 266 | 267 | @csrf_exempt 268 | def commit_user_auth(request): 269 | UserAuthInfo, created = CmdbUserSshAuth.objects.get_or_create(username=request.POST.get('username')) 270 | UserAuthInfo.username = request.POST.get('username') 271 | UserAuthInfo.user_key = request.POST.get('user_key') 272 | UserAuthInfo.related_node = request.POST.get('related_node_selected') 273 | UserAuthInfo.status = request.POST.get('status') 274 | if created: 275 | UserAuthInfo.create_time = timezone.make_aware(datetime.datetime.strptime("02/03/2014 12:00 UTC", "%d/%m/%Y %H:%M %Z"), \ 276 | timezone.get_default_timezone()) 277 | UserAuthInfo.update_time = timezone.make_aware(datetime.datetime.strptime("02/03/2014 12:00 UTC", "%d/%m/%Y %H:%M %Z"), \ 278 | timezone.get_default_timezone()) 279 | UserAuthInfo.special_ip = request.POST.get('special_ip') 280 | 281 | UserAuthInfo.save() 282 | return HttpResponse(json.dumps({"result": "success", "info": "提交成功"})) -------------------------------------------------------------------------------- /conf/crypto.ini: -------------------------------------------------------------------------------- 1 | [crypto] 2 | key_str = 89c14211d2a1cd56 -------------------------------------------------------------------------------- /conf/ip_type.ini: -------------------------------------------------------------------------------- 1 | [SHOWTYPE] 2 | ipshowtype = inner_ip -------------------------------------------------------------------------------- /conf/mq.ini: -------------------------------------------------------------------------------- 1 | [DEPLOY] 2 | username = prod_mq_user 3 | password = pass 4 | host = mq_host 5 | port = mq_port 6 | vhost = 7 | 8 | [LOCAL] 9 | username = local_user 10 | password = 111111 11 | host = 127.0.0.1 12 | port = 5672 13 | vhost = vhost 14 | 15 | [DEV] 16 | username = local_user 17 | password = pass 18 | host = 127.0.0.1 19 | port = 5672 20 | vhost = vhost -------------------------------------------------------------------------------- /conf/mysql.ini: -------------------------------------------------------------------------------- 1 | [DEPLOY] 2 | username = prod_user 3 | dbname = ops 4 | password = pass 5 | host = db_ip 6 | port = db_port 7 | 8 | [LOCAL] 9 | username = root 10 | password = pass 11 | dbname = ops_dev 12 | host = 127.0.0.1 13 | port = 3306 14 | 15 | [DEV] 16 | username = root 17 | password = pass 18 | dbname = ops_dev 19 | host = 127.0.0.1 20 | port = 3306 -------------------------------------------------------------------------------- /conf/settings.ini: -------------------------------------------------------------------------------- 1 | [AllowHost] 2 | iplist = 127.0.0.1,localhost 3 | 4 | [SECRET_KEY] 5 | secret_key = your secretkey in settings.py -------------------------------------------------------------------------------- /conf/sshkey_global.conf: -------------------------------------------------------------------------------- 1 | 此文件记录每台主机都需要添加的共有pub key,每行一个 -------------------------------------------------------------------------------- /db_job/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brick-li/ops-django/3244fbe4eb782ea07ac38606e5c63b9d2e5525da/db_job/__init__.py -------------------------------------------------------------------------------- /db_job/admin.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.contrib import admin 5 | 6 | # Register your models here. 7 | -------------------------------------------------------------------------------- /db_job/apps.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.apps import AppConfig 5 | 6 | 7 | class DbJobConfig(AppConfig): 8 | name = 'db_job' 9 | -------------------------------------------------------------------------------- /db_job/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.17 on 2018-12-16 08:55 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | initial = True 11 | 12 | dependencies = [ 13 | ] 14 | 15 | operations = [ 16 | migrations.CreateModel( 17 | name='DbJobDbInstance', 18 | fields=[ 19 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 20 | ('db_master', models.CharField(db_column='db_master', max_length=32)), 21 | ('db_slave', models.CharField(db_column='db_slave', max_length=32)), 22 | ('db_product', models.CharField(db_column='db_product', max_length=32)), 23 | ('db_product_id', models.CharField(db_column='db_product_id', max_length=32)), 24 | ('db_env', models.CharField(db_column='db_env', max_length=100)), 25 | ('db_mark', models.CharField(db_column='db_mark', max_length=100)), 26 | ('db_passwd', models.CharField(db_column='db_passwd', max_length=32)), 27 | ('db_container_name', models.CharField(db_column='db_container_name', max_length=100)), 28 | ('db_container_name_slave', models.CharField(db_column='db_container_name_slave', default='', max_length=100)), 29 | ('db_user_name', models.CharField(db_column='db_user_name', max_length=32)), 30 | ('db_service_type', models.CharField(db_column='db_service_type', default='container', max_length=32)), 31 | ('db_service_name', models.CharField(db_column='db_service_name', default='', max_length=32)), 32 | ('db_service_name_slave', models.CharField(db_column='db_service_name_slave', default='', max_length=100)), 33 | ], 34 | options={ 35 | 'db_table': 'db_job_db_instance', 36 | }, 37 | ), 38 | ] 39 | -------------------------------------------------------------------------------- /db_job/migrations/0002_dbjobdbbackuphistory.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.17 on 2018-12-16 09:25 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('db_job', '0001_initial'), 12 | ] 13 | 14 | operations = [ 15 | migrations.CreateModel( 16 | name='DbJobDbBackupHistory', 17 | fields=[ 18 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 19 | ('db_instance_id', models.CharField(db_column='db_instance_id', max_length=32)), 20 | ('dbs', models.TextField(blank=True, db_column='dbs', null=True)), 21 | ('tables', models.TextField(blank=True, db_column='tables', null=True)), 22 | ('result', models.TextField(blank=True, db_column='result', null=True)), 23 | ('dtEventTime', models.DateTimeField(blank=True, db_column='dtEventTime', null=True)), 24 | ], 25 | options={ 26 | 'db_table': 'db_job_db_backup_history', 27 | }, 28 | ), 29 | ] 30 | -------------------------------------------------------------------------------- /db_job/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brick-li/ops-django/3244fbe4eb782ea07ac38606e5c63b9d2e5525da/db_job/migrations/__init__.py -------------------------------------------------------------------------------- /db_job/models.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models 5 | 6 | # Create your models here. 7 | 8 | class DbJobDbInstance(models.Model): 9 | db_master = models.CharField(db_column='db_master', max_length=32) # Field name made lowercase. 10 | db_slave = models.CharField(db_column='db_slave', max_length=32) # Field name made lowercase. 11 | db_product = models.CharField(db_column='db_product', max_length=32) # Field name made lowercase. 12 | db_product_id = models.CharField(db_column='db_product_id', max_length=32) # Field name made lowercase. 13 | db_env = models.CharField(db_column='db_env', max_length=100) # Field name made lowercase. 14 | db_mark = models.CharField(db_column='db_mark', max_length=100) # Field name made lowercase. 15 | db_passwd = models.CharField(db_column='db_passwd', max_length=32) # Field name made lowercase. 16 | db_container_name = models.CharField(db_column='db_container_name', max_length=100) # Field name made lowercase. 17 | db_container_name_slave = models.CharField(db_column='db_container_name_slave', default='', max_length=100) # Field name made lowercase. 18 | db_user_name = models.CharField(db_column='db_user_name', max_length=32) # Field name made lowercase. 19 | db_service_type = models.CharField(db_column='db_service_type', default='container', max_length=32) # service type container/service 20 | db_service_name = models.CharField(db_column='db_service_name', default='', max_length=32) # Field name made lowercase. 21 | db_service_name_slave = models.CharField(db_column='db_service_name_slave', default='', max_length=100) # Field name made lowercase. 22 | 23 | class Meta: 24 | #managed = False 25 | db_table = 'db_job_db_instance' 26 | 27 | class DbJobDbBackupHistory(models.Model): 28 | db_instance_id = models.CharField(db_column='db_instance_id', max_length=32) # Field name made lowercase. 29 | dbs = models.TextField(db_column='dbs', blank=True, null=True) # Field name made lowercase. 30 | tables = models.TextField(db_column='tables', blank=True, null=True) # Field name made lowercase. 31 | result = models.TextField(db_column='result', blank=True, null=True) # Field name made lowercase. 32 | dtEventTime = models.DateTimeField(db_column='dtEventTime', blank=True, null=True) # Field name made lowercase. 33 | 34 | class Meta: 35 | #managed = False 36 | db_table = 'db_job_db_backup_history' -------------------------------------------------------------------------------- /db_job/tests.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.test import TestCase 5 | 6 | # Create your tests here. 7 | -------------------------------------------------------------------------------- /db_job/urls.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | 3 | from django.conf.urls import url 4 | 5 | from db_job.views import db_instance, db_backup #db_registry, db_backup, 6 | #from django.views.decorators.csrf import csrf_exempt 7 | 8 | urlpatterns = [ 9 | # DB实例注册 10 | url(r'^/db_instance/db_registry', db_instance.db_registry, 11 | name="db-job-db-instance-registry"), 12 | url(r'^/db_instance/commit_db_instance', db_instance.commit_db_instance, 13 | name="db-job-db-instance-commit-db-instance"), 14 | url(r'^/db_instance/delete_instance', db_instance.delete_instance, 15 | name="db-job-db-instance-delete-instance"), 16 | 17 | # DB备份 18 | url(r'^/db_backup/backup_list', db_backup.show_db_backup_history, name="db-job-db-backup-backup-list"), 19 | url(r'^/db_backup/backup_excute_page', db_backup.backup_excute_page, name="db-job-db-backup-backup-excute-page"), 20 | url(r'^/db_backup/db_backup_start', db_backup.db_backup_start, name="db-backup-start"), 21 | 22 | ] -------------------------------------------------------------------------------- /db_job/views/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brick-li/ops-django/3244fbe4eb782ea07ac38606e5c63b9d2e5525da/db_job/views/__init__.py -------------------------------------------------------------------------------- /db_job/views/db_backup.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | 3 | # Create your views here. 4 | 5 | from django.http import HttpResponse 6 | from django.views.decorators.csrf import csrf_exempt 7 | from classes import ansible_api, my_concurrent, crypto, mysql_db, config 8 | import logging 9 | import traceback 10 | from db_job.models import DbJobDbInstance, DbJobDbBackupHistory 11 | import json 12 | from ops_django.settings import RUN_MODE 13 | import time 14 | from operator import itemgetter 15 | 16 | _logger = logging.getLogger(__name__) 17 | 18 | import commands 19 | 20 | def my_cmd(cmdStr): 21 | status, out = commands.getstatusoutput(cmdStr.encode('utf-8')) 22 | if status != 0: 23 | print type(out) 24 | print out 25 | return 1 26 | else: 27 | return out 28 | 29 | def datefield_to_str(date_data): 30 | ''' 31 | mysql date格式转换为字符串格式 32 | :param date_data: 33 | :return: 34 | ''' 35 | for i in date_data: 36 | if i.has_key("create_time") and i.get('create_time', None) is not None: 37 | i["create_time"] = i["create_time"].strftime("%Y-%m-%d %H:%M:%S") 38 | if i.has_key("update_time") and i.get('update_time', None) is not None: 39 | i["update_time"] = i["update_time"].strftime("%Y-%m-%d %H:%M:%S") 40 | if i.has_key("dtEventTime") and i.get('dtEventTime', None) is not None: 41 | i["dtEventTime"] = i["dtEventTime"].strftime("%Y-%m-%d %H:%M:%S") 42 | return date_data 43 | 44 | #####################数据库备份##################### 45 | @csrf_exempt 46 | #@login_required(login_url='/login/user_login') 47 | def backup_excute_page(request): 48 | ''' 49 | 数据库备份页面 50 | :param request: 51 | :return: 52 | ''' 53 | all_db_resource = list(DbJobDbInstance.objects.all().values()) 54 | db_instances = {} 55 | for i in all_db_resource: 56 | db_instances[i['db_mark']] = i 57 | db_marks = [i['db_mark'] for i in all_db_resource] 58 | 59 | return HttpResponse(json.dumps({"result": "success", "data": {"db_info": db_instances, "db_marks": db_marks}})) 60 | 61 | 62 | 63 | def get_mysql_container_name_and_passwd(db_instance): 64 | ''' 65 | 获取mysql密码和container name 66 | :return: 67 | ''' 68 | db_resource = DbJobDbInstance.objects.filter(db_mark=db_instance).values() 69 | if not len(db_resource) == 1: 70 | return False, '', '', '' 71 | else: 72 | return True, db_resource[0]['db_container_name'], db_resource[0]['db_user_name'], db_resource[0]['db_passwd'], db_resource[0]['id'] 73 | 74 | 75 | def db_backup_job(job_list, db_ip_port): 76 | ''' 77 | 执行db备份方法 78 | :return: 79 | ''' 80 | try: 81 | db_ip = db_ip_port.strip().split(':')[0] 82 | db_port = db_ip_port.strip().split(':')[1] 83 | if job_list['db'] is None or job_list['table'] is None: 84 | return json.dumps({"result": "failed", "info": "%s db or table is none" % db_ip_port}) 85 | else: 86 | # print job_list 87 | db_instance_id = job_list['db_instance_id'] 88 | db_instance_name = job_list['db_mark'] 89 | db_container_name = job_list['db_container_name_slave'] if job_list['db_container_name_slave'] else\ 90 | job_list['db_container_name'] 91 | db_service_type = job_list['db_service_type'] 92 | db_passwd = crypto.passwd_deaes(job_list['db_passwd']) 93 | db_user_name = job_list['db_user_name'] 94 | db = job_list['db'] 95 | table = job_list['table'] 96 | ansible_interface = ansible_api.AnsiInterface(become=True, become_method='sudo', become_user='root') 97 | time_now = time.strftime("%Y%m%d%H%M%S", time.localtime(time.time())) 98 | 99 | if db_service_type == 'container': 100 | # mk backup dir 101 | ansible_interface.make_dir(db_ip, '/data/mysqlbackup/ state=directory') 102 | ansible_interface.make_dir(db_ip, '/data/mysqlbackup/%s state=directory' % db_instance_name) 103 | ansible_interface.make_dir(db_ip, 104 | '/data/mysqlbackup/%s/%s_%s state=directory' % (db_instance_name, db, table)) 105 | cmd = "docker exec %s bash -c 'mysqldump --defaults-extra-file=/etc/my.cnf -h127.0.0.1 " \ 106 | "-u%s -P%s -p%s --opt --skip-lock-tables %s %s' > /data/mysqlbackup/%s/%s_%s/%s.sql" \ 107 | % (db_container_name, db_user_name, db_port, db_passwd, db, table, db_instance_name, 108 | db, table, time_now) 109 | result = ansible_interface.exec_shell(db_ip, cmd) 110 | elif db_service_type in ['service', 'RDS']: 111 | # mk backup dir 112 | my_cmd('if [[ ! -d /data/mysqlbackup/ ]];then mkdir /data/mysqlbackup/; fi') 113 | my_cmd('if [[ ! -d /data/mysqlbackup/%s ]];then mkdir /data/mysqlbackup/%s; fi' % (db_instance_name, db_instance_name)) 114 | my_cmd('if [[ ! -d /data/mysqlbackup/%s/%s_%s ]];then mkdir /data/mysqlbackup/%s/%s_%s; fi' % (db_instance_name, db, table, db_instance_name, db, table)) 115 | cmd = "mysqldump -h%s " \ 116 | "-u%s -P%s -p%s --opt --skip-lock-tables %s %s > /data/mysqlbackup/%s/%s_%s/%s.sql" \ 117 | % (db_ip, db_user_name, db_port, db_passwd, db, table, db_instance_name, 118 | db, table, time_now) 119 | _result = my_cmd(cmd) 120 | result = { 121 | "host_failed": {}, 122 | "host_ok": {}, 123 | "host_unreachable": {} 124 | } 125 | if _result == 1: 126 | result['host_failed'] = { 127 | db_ip: { 128 | 'stderr': u'备份失败' 129 | } 130 | } 131 | else: 132 | result['host_ok'] = {db_ip: {}} 133 | else: 134 | return json.dumps({"result": "failed", "info": u"没有该服务类型对应命令"}) 135 | 136 | # parse result 137 | if result['host_failed']: 138 | _ret = {"result": "failed", "info": "ip: %s, db: %s, failed_info: %s" % (db_ip, db, result['host_failed'][db_ip]['stderr'])} # 执行结果放入Queue 139 | elif result['host_unreachable']: 140 | _ret = {"result": "failed", "info": " ip: %s, ureachable: %s" % (db_ip, result['host_unreachable'])} # 执行结果放入Queue 141 | elif result['host_ok']: 142 | ok_ip = result['host_ok'].keys() 143 | _ret = {"result": "success", "info": "ip: %s, backup_file: %s" % (','.join(ok_ip), "/data/mysqlbackup/%s/%s_%s/%s.sql" % (db_instance_name, 144 | db, table, time_now))} 145 | dteventtime = str(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(time.time()))) 146 | data = [( 147 | dteventtime, 148 | db_instance_id, 149 | db, 150 | table, 151 | "/data/mysqlbackup/%s/%s_%s/%s.sql" % (db_instance_name, db, table, time_now) 152 | )] 153 | c = config.config('mysql.ini') 154 | db_name = c.getOption(RUN_MODE, 'dbname') 155 | with mysql_db.conn_db(db_name, RUN_MODE) as _db: 156 | sql = "insert into db_job_db_backup_history (`dtEventTime`,`db_instance_id`,`dbs`,`tables`,`result`) values (%s,%s,%s,%s,%s)" 157 | _db.executemany(sql, data) 158 | return json.dumps(_ret) 159 | else: 160 | _ret = {"result": "failed", 161 | "info": "ip: %s, db: %s, failed_info: %s" % (db_ip, db, u"没有执行结果,请检查密码")} 162 | return json.dumps(_ret) 163 | except Exception: 164 | try: 165 | table = job_list.get('table', 'None') 166 | db = job_list.get('db', 'None') 167 | db_ip = db_ip_port.strip().split(':')[0] 168 | except: 169 | table = 'None' 170 | db = 'None' 171 | db_ip = 'None' 172 | _logger.error(traceback.format_exc()) 173 | return json.dumps({"result": "failed", "info": "ip: %s, db: %s,table: %s, failed_info: %s" % (db_ip, db, table, traceback.format_exc())}) 174 | 175 | 176 | #@login_required(login_url='/login/user_login') 177 | @csrf_exempt 178 | def db_backup_start(request): 179 | try: 180 | db_instance = request.POST.get('db_instance', None) 181 | dbs = request.POST.get('dbs', None) 182 | tables = request.POST.get('tables', None) 183 | selectdb = json.loads(request.POST.get('selectdb', None)) 184 | if selectdb['db_slave'] == '' or selectdb['db_slave'] is None: 185 | db_ip_port = selectdb['db_master'] 186 | else: 187 | db_ip_port = selectdb['db_slave'] 188 | if None in [db_instance, dbs, tables]: 189 | return HttpResponse(json.dumps({"result": "failed", "info": "DB信息有误,请对比注册表"})) 190 | backup_list = [] 191 | for db in list(set(dbs.strip().split("\n"))): 192 | for table in list(set(tables.strip().split('\n'))): 193 | backup_list.append({'db':db.strip(), 'table': table.strip()}) 194 | # 解开dbinstance信息 195 | for item in backup_list: 196 | item['db_instance_id'] = selectdb['id'] 197 | item['db_instance_name'] = selectdb['db_mark'] 198 | item['db_product'] = selectdb['db_product'] 199 | item['db_service_name_slave'] = selectdb['db_service_name_slave'] 200 | item['db_container_name'] = selectdb['db_container_name'] 201 | item['db_mark'] = selectdb['db_mark'] 202 | item['db_service_type'] = selectdb['db_service_type'] 203 | item['db_master'] = selectdb['db_master'] 204 | item['db_env'] = selectdb['db_env'] 205 | item['db_passwd'] = selectdb['db_passwd'] 206 | item['db_service_name'] = selectdb['db_service_name'] 207 | item['db_product_id'] = selectdb['db_product_id'] 208 | item['db_slave'] = selectdb['db_slave'] 209 | item['db_container_name_slave'] = selectdb['db_container_name_slave'] 210 | item['db_user_name'] = selectdb['db_user_name'] 211 | # 开始并发处理 212 | db_backup_multi_process = my_concurrent.MyMultiProcess(10) 213 | for i in backup_list: 214 | db_backup_multi_process.multi_process_add(db_backup_job, i, db_ip_port) 215 | db_backup_multi_process.multi_process_wait() # 等待执行完成 216 | result = db_backup_multi_process.get_result() 217 | 218 | g_res = {"failed":[], "success": []} 219 | for _res in result: 220 | __res = json.loads(_res) 221 | if __res['result'] == "failed": 222 | g_res["failed"].append(__res['info']) 223 | elif __res['result'] == "success": 224 | g_res["success"].append(__res['info']) 225 | if g_res["failed"]: 226 | return HttpResponse(json.dumps({"result": "failed", "data": g_res})) 227 | return HttpResponse(json.dumps({"result": "success", "data": g_res})) 228 | except Exception: 229 | _logger.error(traceback.format_exc()) 230 | g_res = "500 内部错误 %s"%traceback.format_exc() 231 | return HttpResponse(json.dumps({"result": "failed", "data": g_res})) 232 | 233 | #####################数据库备份历史##################### 234 | @csrf_exempt 235 | def show_db_backup_history(request): 236 | all_history = list(DbJobDbBackupHistory.objects.exclude(dtEventTime=None).values()) 237 | all_history_by_time = sorted(all_history, key=itemgetter('dtEventTime')) 238 | all_history_by_time.reverse() 239 | for i in all_history_by_time: 240 | instance_name = DbJobDbInstance.objects.filter(id=i['db_instance_id']).values() 241 | if len(instance_name) == 0: 242 | i['instance_name'] = '' 243 | else: 244 | i['instance_name'] = instance_name[0]['db_mark'] 245 | all_history_by_time = datefield_to_str(all_history_by_time) 246 | time_now = time.strftime("%Y-%m-%d", time.localtime(time.time())) 247 | last_week = time.strftime("%Y-%m-%d", time.localtime(time.time() - 7*24*60*60)) 248 | last_month = time.strftime("%Y-%m-%d", time.localtime(time.time() - 7*24*60*60*30)) 249 | date_filter = [ 250 | {"text": str(time_now), "value": str(time_now)}, 251 | {"text": str(last_week), "value": str(last_week)}, 252 | {"text": str(last_month), "value": str(last_month)}, 253 | ] 254 | print date_filter 255 | return HttpResponse(json.dumps({"result": "success","data":all_history_by_time, "date_filter": date_filter})) -------------------------------------------------------------------------------- /db_job/views/db_instance.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | 3 | # Create your views here. 4 | 5 | from django.http import HttpResponse 6 | from django.views.decorators.csrf import csrf_exempt 7 | from classes import crypto 8 | from cmdb.views.tree import get_prod_id_by_name 9 | from cmdb.models import CmdbProductInfo 10 | from db_job.models import DbJobDbInstance 11 | import json 12 | import logging 13 | import traceback 14 | 15 | 16 | _logger = logging.getLogger(__name__) 17 | 18 | 19 | ################数据库实例注册################ 20 | @csrf_exempt 21 | def add_db_instance(request): 22 | ''' 23 | 新增实例 24 | :param request: 25 | :return: 26 | ''' 27 | try: 28 | db_mark = request.POST.get('db_mark', None) 29 | db_job_instance,created = DbJobDbInstance.objects.get_or_create(db_mark=db_mark) 30 | if not created: 31 | return HttpResponse(json.dumps({"result":"failed", "info": "实例名重复"})) 32 | db_job_instance.db_master = request.POST.get('db_master', None) 33 | db_job_instance.db_slave = request.POST.get('db_slave', None) 34 | db_job_instance.db_product = request.POST.get('db_product', None).split('_')[0] 35 | try: 36 | db_job_instance.db_product_id = list(CmdbProductInfo.objects.filter(product_name=request.POST.get('db_product', None).split('_')[0]).values("product_id"))[0]['product_id'] 37 | except Exception: 38 | db_job_instance.db_product_id = -1 39 | db_job_instance.db_env = request.POST.get('db_env', None) 40 | db_job_instance.db_mark = request.POST.get('db_mark', None) 41 | db_job_instance.db_user_name = request.POST.get('db_user_name', None) 42 | db_job_instance.db_container_name = request.POST.get('db_container_name', None) 43 | db_job_instance.db_service_type = request.POST.get('db_service_type', None) 44 | db_job_instance.db_service_name = request.POST.get('db_service_name', None) 45 | db_job_instance.db_passwd = crypto.passwd_aes(request.POST.get('db_passwd', None)) # 加密密码 46 | db_job_instance.save() 47 | return HttpResponse(json.dumps({"result":"success"})) 48 | except Exception: 49 | print traceback.format_exc() 50 | _logger.error(traceback.format_exc()) 51 | return HttpResponse(json.dumps({"result":"failed", "info":"数据库操作异常"})) 52 | 53 | @csrf_exempt 54 | def delete_instance(request): 55 | ''' 56 | 删除db resource 57 | :param request: 58 | :return: 59 | ''' 60 | try: 61 | id = request.POST.get('id') 62 | DbJobDbInstance.objects.filter(id=id).delete() 63 | return HttpResponse(json.dumps({"result": "success", "info": "已成功删除"})) 64 | except Exception: 65 | print traceback.format_exc() 66 | _logger.error(traceback.format_exc()) 67 | return HttpResponse(json.dumps({"result":"failed", "info": "删除失败"})) 68 | 69 | 70 | 71 | def resource(): 72 | data = DbJobDbInstance.objects.all().values() 73 | data = [{u"id": int(i['id']), u"db_master": i['db_master'], 74 | u"db_slave": i['db_slave'], 75 | u"db_product": i['db_product'], 76 | u"db_product_id": i['db_product_id'], u"db_env": i['db_env'], u"db_mark": i['db_mark'], u"db_passwd": i['db_passwd'], 77 | u"db_container_name": i['db_container_name'],u"db_container_name_slave": i['db_container_name_slave'], u"db_user_name": i['db_user_name'], u"db_service_type": i['db_service_type'], 78 | u"db_service_name": i['db_service_name'], u"db_service_name_slave": i['db_service_name_slave']} for i in data] 79 | return data 80 | 81 | 82 | @csrf_exempt 83 | def commit_db_instance(request): 84 | ''' 85 | 修改或新增db实例 86 | :param request: 87 | :return: 88 | ''' 89 | db_id = request.POST.get("id", -1) 90 | pass_change = request.POST.get("pass_change", 0) 91 | db_info = { 92 | "db_master": request.POST.get("db_master"), 93 | "db_slave": request.POST.get('db_slave', None), 94 | "db_product": request.POST.get("db_product"), 95 | "db_product_id": get_prod_id_by_name(request.POST.get("db_product")), 96 | "db_env": request.POST.get("db_env"), 97 | "db_mark": request.POST.get("db_mark"), 98 | "db_container_name": request.POST.get("db_container_name"), 99 | "db_container_name_slave": request.POST.get("db_container_name_slave"), 100 | "db_user_name": request.POST.get("db_user_name"), 101 | "db_service_type": request.POST.get("db_service_type"), 102 | "db_service_name": request.POST.get("db_service_name"), 103 | "db_service_name_slave": request.POST.get("db_service_name_slave"), 104 | } 105 | if int(pass_change) == 1: 106 | db_info["db_passwd"] = crypto.passwd_aes(request.POST.get("db_passwd")) 107 | db_instance = DbJobDbInstance.objects.filter(db_mark=db_info['db_mark']).values() 108 | if db_instance and db_id in [None, '', '-1', '0', -1, 0]: 109 | return HttpResponse(json.dumps({"result": "failed", "info": "实例名重复"})) 110 | if db_id in [None, '', '-1', '0', -1, 0]: 111 | try: 112 | DbJobDbInstance.objects.create(**db_info) 113 | except Exception: 114 | _logger.error(traceback.format_exc()) 115 | return HttpResponse(json.dumps({"result": "failed", "info": "获取db实例失败"})) 116 | else: 117 | DbJobDbInstance.objects.filter(id=db_id).update(**db_info) 118 | return HttpResponse(json.dumps({"result": "success", "info": "提交成功"})) 119 | 120 | 121 | 122 | @csrf_exempt 123 | def db_registry(request): 124 | ''' 125 | 注册数据库主页面 126 | :param request: 127 | :return: 128 | ''' 129 | data = resource() 130 | elseData = { 131 | "app_info": [ {'value': i['product_name'], 'lable':i['product_name']} for i in CmdbProductInfo.objects.all().values() ], 132 | "env_info": [ 133 | { 134 | 'value': 'test', 135 | 'lable': 'test' 136 | }, 137 | { 138 | 'value': 'prod', 139 | 'lable': 'prod' 140 | } 141 | ], 142 | "service_type": [ 143 | { 144 | 'value': 'container', 145 | 'lable': 'container' 146 | }, 147 | { 148 | 'value': 'service', 149 | 'lable': 'service' 150 | }, 151 | { 152 | 'value': 'RDS', 153 | 'lable': 'RDS' 154 | } 155 | ], 156 | } 157 | return HttpResponse(json.dumps({"result": "success", "data": data, "elseData": elseData})) 158 | -------------------------------------------------------------------------------- /log/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brick-li/ops-django/3244fbe4eb782ea07ac38606e5c63b9d2e5525da/log/.gitkeep -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "ops_django.settings") 7 | try: 8 | from django.core.management import execute_from_command_line 9 | except ImportError: 10 | # The above import may fail for some other reason. Ensure that the 11 | # issue is really that Django is missing to avoid masking other 12 | # exceptions on Python 2. 13 | try: 14 | import django 15 | except ImportError: 16 | raise ImportError( 17 | "Couldn't import Django. Are you sure it's installed and " 18 | "available on your PYTHONPATH environment variable? Did you " 19 | "forget to activate a virtual environment?" 20 | ) 21 | raise 22 | execute_from_command_line(sys.argv) 23 | -------------------------------------------------------------------------------- /ops_django/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brick-li/ops-django/3244fbe4eb782ea07ac38606e5c63b9d2e5525da/ops_django/__init__.py -------------------------------------------------------------------------------- /ops_django/celery.py: -------------------------------------------------------------------------------- 1 | # coding:utf8 2 | from __future__ import absolute_import 3 | 4 | import os 5 | 6 | from celery import Celery, platforms 7 | from django.conf import settings 8 | 9 | 10 | platforms.C_FORCE_ROOT = True 11 | # set the default Django settings module for the 'celery' program. 12 | 13 | # yourprojectname代表你工程的名字,在下面替换掉 14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'ops_django.settings') 15 | 16 | app = Celery('ops_django') 17 | 18 | # Using a string here means the worker will not have to 19 | # pickle the object when using Windows. 20 | app.config_from_object('django.conf:settings') 21 | app.autodiscover_tasks(lambda: settings.INSTALLED_APPS) 22 | 23 | 24 | @app.task(bind=True) 25 | def debug_task(self): 26 | print('Request: {0!r}'.format(self.request)) -------------------------------------------------------------------------------- /ops_django/settings.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | """ 3 | Django settings for ops_django project. 4 | 5 | Generated by 'django-admin startproject' using Django 1.11.17. 6 | 7 | For more information on this file, see 8 | https://docs.djangoproject.com/en/1.11/topics/settings/ 9 | 10 | For the full list of settings and their values, see 11 | https://docs.djangoproject.com/en/1.11/ref/settings/ 12 | """ 13 | 14 | import os 15 | from classes import config 16 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 17 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 18 | 19 | 20 | # Quick-start development settings - unsuitable for production 21 | # See https://docs.djangoproject.com/en/1.11/howto/deployment/checklist/ 22 | 23 | 24 | settings_c = config.config("settings.ini") 25 | # SECURITY WARNING: keep the secret key used in production secret! 26 | SECRET_KEY = settings_c.getOption('SECRET_KEY','secret_key') 27 | 28 | # SECURITY WARNING: don't run with debug turned on in production! 29 | DEBUG = True 30 | 31 | SITE_ID = 1 32 | 33 | ALLOWED_HOSTS = settings_c.getOption('AllowHost', 'iplist').strip().split(',') 34 | 35 | # Application definition 36 | 37 | INSTALLED_APPS = [ 38 | 'django.contrib.sites', 39 | 'django.contrib.admin', 40 | 'django.contrib.auth', 41 | 'django.contrib.contenttypes', 42 | 'django.contrib.sessions', 43 | 'django.contrib.messages', 44 | 'django.contrib.staticfiles', 45 | 'account', 46 | 'djcelery', 47 | 'cmdb', 48 | 'db_job', 49 | 'ops_job', 50 | ] 51 | 52 | 53 | # 免登陆使用的接口 54 | AUTH_FREE_URL_PREFIX = ['/account/user_login', '/admin', '/ops_job/script/monitor', '/ops_job/script/script_http'] 55 | 56 | MIDDLEWARE = [ 57 | 'django.middleware.security.SecurityMiddleware', 58 | 'django.contrib.sessions.middleware.SessionMiddleware', 59 | 'corsheaders.middleware.CorsMiddleware', 60 | 'django.middleware.common.CommonMiddleware', 61 | 'django.middleware.csrf.CsrfViewMiddleware', 62 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 63 | 'django.contrib.messages.middleware.MessageMiddleware', 64 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 65 | 'account.views.login.LoginRequireMiddleWare', 66 | ] 67 | 68 | # solve CROS 69 | CORS_ALLOW_CREDENTIALS = True 70 | CORS_ORIGIN_ALLOW_ALL = True 71 | CORS_ORIGIN_WHITELIST = ( 72 | '*' 73 | ) 74 | CORS_ALLOW_METHODS = ( 75 | 'DELETE', 76 | 'GET', 77 | 'OPTIONS', 78 | 'PATCH', 79 | 'POST', 80 | 'PUT', 81 | 'VIEW', 82 | ) 83 | CORS_ALLOW_HEADERS = ( 84 | 'XMLHttpRequest', 85 | 'X_FILENAME', 86 | 'accept-encoding', 87 | 'authorization', 88 | 'content-type', 89 | 'dnt', 90 | 'origin', 91 | 'user-agent', 92 | 'x-csrftoken', 93 | 'x-requested-with', 94 | 'Pragma', 95 | 'sessionid', 96 | ) 97 | 98 | ROOT_URLCONF = 'ops_django.urls' 99 | 100 | TEMPLATES = [ 101 | { 102 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 103 | 'DIRS': [], 104 | 'APP_DIRS': True, 105 | 'OPTIONS': { 106 | 'context_processors': [ 107 | 'django.template.context_processors.debug', 108 | 'django.template.context_processors.request', 109 | 'django.contrib.auth.context_processors.auth', 110 | 'django.contrib.messages.context_processors.messages', 111 | ], 112 | }, 113 | }, 114 | ] 115 | 116 | WSGI_APPLICATION = 'ops_django.wsgi.application' 117 | 118 | 119 | # RUN_MODE 120 | RUN_MODE = os.getenv('RUN_MODE','DEV') 121 | 122 | # Database 123 | # https://docs.djangoproject.com/en/1.11/ref/settings/#databases 124 | db_c = config.config("mysql.ini") 125 | DB_HOST = db_c.getOption(RUN_MODE, 'host') 126 | DB_USER = db_c.getOption(RUN_MODE, 'username') 127 | DB_NAME = db_c.getOption(RUN_MODE, 'dbname') 128 | DB_PASS = db_c.getOption(RUN_MODE, 'password') 129 | DB_PORT = db_c.getOption(RUN_MODE, 'port') 130 | DATABASES = { 131 | 'default': { 132 | 'ENGINE': 'django.db.backends.mysql', 133 | 'NAME':DB_NAME, 134 | 'HOST':DB_HOST, 135 | 'PORT':DB_PORT, 136 | 'USER':DB_USER, 137 | 'PASSWORD':DB_PASS, 138 | } 139 | } 140 | 141 | # mq setting 142 | # celery setting 143 | import djcelery 144 | djcelery.setup_loader() 145 | 146 | mq_c = config.config("mq.ini") 147 | MQ_HOST = mq_c.getOption(RUN_MODE, 'host') 148 | MQ_USER = mq_c.getOption(RUN_MODE, 'username') 149 | MQ_PASS = mq_c.getOption(RUN_MODE, 'password') 150 | MQ_PORT = mq_c.getOption(RUN_MODE, 'port') 151 | MQ_VHOST = mq_c.getOption(RUN_MODE, 'vhost') 152 | CELERYD_CONCURRENCY = 200 153 | CELERYBEAT_SCHEDULER = 'djcelery.schedulers.DatabaseScheduler' 154 | BROKER_URL = 'amqp://%s:%s@%s:%s' % (MQ_USER, MQ_PASS, MQ_HOST, MQ_PORT) 155 | if MQ_VHOST != '': 156 | BROKER_URL = "%s/%s" % (BROKER_URL, MQ_VHOST) 157 | 158 | 159 | # Password validation 160 | # https://docs.djangoproject.com/en/1.11/ref/settings/#auth-password-validators 161 | 162 | AUTH_PASSWORD_VALIDATORS = [ 163 | { 164 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', 165 | }, 166 | { 167 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 168 | }, 169 | { 170 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', 171 | }, 172 | { 173 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', 174 | }, 175 | ] 176 | 177 | 178 | # Internationalization 179 | # https://docs.djangoproject.com/en/1.11/topics/i18n/ 180 | 181 | LANGUAGE_CODE = 'en-us' 182 | 183 | TIME_ZONE = 'Asia/Shanghai' 184 | 185 | USE_TZ = False 186 | 187 | 188 | USE_I18N = True 189 | 190 | USE_L10N = True 191 | 192 | 193 | 194 | # Static files (CSS, JavaScript, Images) 195 | # https://docs.djangoproject.com/en/1.11/howto/static-files/ 196 | 197 | STATIC_URL = '/static/' 198 | 199 | 200 | 201 | # Logger 202 | BASE_LOG_DIR = os.path.join(BASE_DIR, "log") 203 | LOGGING = { 204 | 'version': 1, 205 | 'disable_existing_loggers': False, 206 | 'formatters': { 207 | # 详细的日志格式 208 | 'standard': { 209 | 'format': '[%(asctime)s][%(threadName)s:%(thread)d][task_id:%(name)s][%(filename)s:%(lineno)d]' 210 | '[%(levelname)s][%(message)s]' 211 | }, 212 | # 简单的日志格式 213 | 'simple': { 214 | 'format': '[%(levelname)s][%(asctime)s][%(filename)s:%(lineno)d]%(message)s' 215 | }, 216 | }, 217 | 'filters': { 218 | 'require_debug_true': { 219 | '()': 'django.utils.log.RequireDebugTrue', 220 | } 221 | }, 222 | 'handlers': { 223 | 'console': { 224 | 'level': 'DEBUG', 225 | 'filters': ['require_debug_true'], # 只有在Django debug为True时才在屏幕打印日志 226 | 'class': 'logging.StreamHandler', # 227 | 'formatter': 'standard' 228 | }, 229 | 'default': { 230 | 'level': 'INFO', 231 | 'class': 'logging.handlers.RotatingFileHandler', # 保存到文件,自动切 232 | 'filename': os.path.join(BASE_LOG_DIR, "info.log"), # 日志文件 233 | 'maxBytes': 1024 * 1024 * 50, # 日志大小 50M 234 | 'backupCount': 3, # 最多备份几个 235 | 'formatter': 'standard', 236 | 'encoding': 'utf-8', 237 | }, 238 | # 专门用来记错误日志 239 | 'error': { 240 | 'level': 'ERROR', 241 | 'class': 'logging.handlers.RotatingFileHandler', # 保存到文件,自动切 242 | 'filename': os.path.join(BASE_LOG_DIR, "error.log"), # 日志文件 243 | 'maxBytes': 1024 * 1024 * 50, # 日志大小 50M 244 | 'backupCount': 5, 245 | 'formatter': 'standard', 246 | 'encoding': 'utf-8', 247 | }, 248 | }, 249 | 'loggers': { 250 | # 默认的logger应用如下配置 251 | '': { 252 | 'handlers': ['default', 'console', 'error'], # 上线之后可以把'console'移除 253 | 'level': 'DEBUG', 254 | 'propagate': True, # 向不向更高级别的logger传递 255 | }, 256 | }, 257 | } -------------------------------------------------------------------------------- /ops_django/urls.py: -------------------------------------------------------------------------------- 1 | """ops_django URL Configuration 2 | 3 | The `urlpatterns` list routes URLs to views. For more information please see: 4 | https://docs.djangoproject.com/en/1.11/topics/http/urls/ 5 | Examples: 6 | Function views 7 | 1. Add an import: from my_app import views 8 | 2. Add a URL to urlpatterns: url(r'^$', views.home, name='home') 9 | Class-based views 10 | 1. Add an import: from other_app.views import Home 11 | 2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home') 12 | Including another URLconf 13 | 1. Import the include() function: from django.conf.urls import url, include 14 | 2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls')) 15 | """ 16 | from django.conf.urls import url, include 17 | from django.contrib import admin 18 | 19 | urlpatterns = [ 20 | url(r'^admin/', admin.site.urls), 21 | ] 22 | 23 | urlpatterns += [ 24 | url(r'^cmdb', include('cmdb.urls')), 25 | url(r'^db_job', include('db_job.urls')), 26 | url(r'^ops_job', include('ops_job.urls')), 27 | url(r'^account', include('account.urls')), 28 | ] -------------------------------------------------------------------------------- /ops_django/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for ops_django project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/1.11/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "ops_django.settings") 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /ops_job/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brick-li/ops-django/3244fbe4eb782ea07ac38606e5c63b9d2e5525da/ops_job/__init__.py -------------------------------------------------------------------------------- /ops_job/admin.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.contrib import admin 5 | 6 | # Register your models here. 7 | -------------------------------------------------------------------------------- /ops_job/apps.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.apps import AppConfig 5 | 6 | 7 | class OpsJobConfig(AppConfig): 8 | name = 'ops_job' 9 | -------------------------------------------------------------------------------- /ops_job/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.17 on 2018-12-16 09:47 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | initial = True 11 | 12 | dependencies = [ 13 | ] 14 | 15 | operations = [ 16 | migrations.CreateModel( 17 | name='OpsJobJobScriptInfo', 18 | fields=[ 19 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 20 | ('job_name', models.CharField(max_length=32)), 21 | ('script_name', models.CharField(max_length=32)), 22 | ('script_content', models.TextField(default='')), 23 | ('drop_status', models.IntegerField(blank=True, default=0, null=True)), 24 | ('mTime', models.DateTimeField(auto_now=True, db_column='m_time', null=True)), 25 | ], 26 | options={ 27 | 'db_table': 'ops_job_job_script_info', 28 | }, 29 | ), 30 | migrations.CreateModel( 31 | name='OpsJobScriptHistory', 32 | fields=[ 33 | ('id', models.AutoField(primary_key=True, serialize=False)), 34 | ('job_name', models.CharField(max_length=32)), 35 | ('result', models.TextField(default='')), 36 | ('dtEventTime', models.DateTimeField(auto_now=True, db_column='dtEventTime', null=True)), 37 | ('exec_status', models.IntegerField(blank=True, default=0, null=True)), 38 | ('ip_list', models.TextField(default='')), 39 | ], 40 | options={ 41 | 'db_table': 'ops_job_script_history', 42 | }, 43 | ), 44 | ] 45 | -------------------------------------------------------------------------------- /ops_job/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brick-li/ops-django/3244fbe4eb782ea07ac38606e5c63b9d2e5525da/ops_job/migrations/__init__.py -------------------------------------------------------------------------------- /ops_job/models.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models 5 | 6 | # Create your models here. 7 | 8 | 9 | # 脚本库 10 | class OpsJobJobScriptInfo(models.Model): 11 | job_name = models.CharField(max_length=32) # job name 12 | script_name = models.CharField(max_length=32) # script_name 13 | script_content = models.TextField(default='') # script_content 14 | drop_status = models.IntegerField(blank=True, null=True, default=0) # drop_status 15 | mTime = models.DateTimeField(db_column='m_time', blank=True, null=True, auto_now=True) # mTime 16 | 17 | class Meta: 18 | # managed = False 19 | db_table = 'ops_job_job_script_info' 20 | 21 | # 脚本执行历史 22 | class OpsJobScriptHistory(models.Model): 23 | id = models.AutoField(primary_key=True) 24 | job_name = models.CharField(max_length=32) # job name 25 | result = models.TextField(default='') # result 26 | dtEventTime = models.DateTimeField(db_column='dtEventTime', blank=True, null=True, auto_now=True, db_index=True) # dtEventTime 27 | exec_status = models.IntegerField(blank=True, null=True, default=0) #0 exec success, 1 execing 28 | ip_list = models.TextField(default='') #ip_list 29 | 30 | class Meta: 31 | # managed = False 32 | db_table = 'ops_job_script_history' -------------------------------------------------------------------------------- /ops_job/tasks.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from __future__ import absolute_import 3 | import django 4 | django.setup() 5 | import logging 6 | from celery import shared_task 7 | import json 8 | import traceback 9 | from ops_job.views.script_executor import get_job_name, get_history_id, exec_scripts 10 | 11 | _logger = logging.getLogger("ops_scripts") 12 | 13 | 14 | @shared_task() 15 | def celery_scripts(*args, **kwargs): 16 | ''' 17 | 执行脚本的celery方法 18 | :return: 19 | ''' 20 | try: 21 | import urllib, urllib2 22 | script_job_name = get_job_name(kwargs.get('script_name')) 23 | celery_kwargs = { 24 | "args_type": 'file' if kwargs['args_type'] == '2' else 'normal', 25 | "script_job_name": script_job_name, 26 | "is_root": kwargs['is_root'], 27 | "script_args": kwargs['script_args'], 28 | "module_args": kwargs['module_args'], 29 | "history_id": get_history_id(script_job_name) #创建任务 30 | } 31 | http_args = { 32 | 'kwargs': json.dumps(celery_kwargs) 33 | } 34 | # 执行任务 35 | script_urlencode = urllib.urlencode(http_args) 36 | requrl = "http://127.0.0.1:8000/ops_job/script/script_http" 37 | req = urllib2.Request(url=requrl, data=script_urlencode) 38 | res_data = urllib2.urlopen(req) 39 | ret = json.loads(res_data.read()) 40 | if ret.get('result', None) == 'failed': 41 | _logger.info("Script celery 500 error, %s" % ret['result']) 42 | elif ret.get('result', None) == 'success': 43 | if ret['data']['host_failed'] or ret['data']['host_unreachable']: 44 | failed_ip = list() 45 | if ret['data']['host_failed']: 46 | failed_ip += ret['data']['host_failed'].keys() 47 | if ret['data']['host_unreachable']: 48 | failed_ip += ret['data']['host_unreachable'].keys() 49 | if failed_ip: 50 | failed_ips = ','.join(failed_ip) 51 | else: 52 | failed_ips = 'no ip' 53 | _logger.info("Script celery Ansible Failed IP: %s" % failed_ips) 54 | try: 55 | _logger.info("failed_info: %s" % json.dumps(ret)) 56 | except(Exception): 57 | _logger.error("Send Failed IP failed : %s" % traceback.format_exc()) 58 | except Exception: 59 | _logger.error("task execute failed, %s" % traceback.format_exc()) 60 | pass -------------------------------------------------------------------------------- /ops_job/tests.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.test import TestCase 5 | 6 | # Create your tests here. 7 | -------------------------------------------------------------------------------- /ops_job/urls.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | 3 | from django.conf.urls import url 4 | from ops_job.views import script_edit 5 | from ops_job.views import script_executor, script_cron, script_history 6 | 7 | urlpatterns = [ 8 | # 脚本编辑 9 | url(r'^/script$', script_executor.exec_scripts, name="ops-job-script"), 10 | url(r'^/script/exector_create$', script_executor.exector_create, name="ops-job-exector-create$"), 11 | url(r'^/script/list$', script_edit.get_scripts_list, name="ops-job-script-list"), 12 | url(r'^/script/edit', script_edit.edit_script, name="ops-job-script-edit"), 13 | url(r'^/script/delete$', script_edit.delete_script, name="ops-job-script-delete"), 14 | url(r'^/script/updatescript', script_edit.updatescript, name="ops-job-script-updatescript"), 15 | 16 | # 作业执行历史 17 | url(r'^/script/script_history$', script_history.get_script_history, name="ops-job-script-scripthistory"), 18 | url(r'^/script/script_history_detail', script_history.script_history_detail, name="ops-job-script-scripthistorydetail"), 19 | 20 | # 定时作业 21 | url(r'^/script/cronlist', script_cron.get_cron_list, name="ops-job-script-cronlist"), 22 | # 服务监控 23 | url(r'^/script/get_task_list', script_cron.get_task_list, name="ops-job-script-task-list"), 24 | url(r'^/script/delete_cron_job', script_cron.delete_cron_job, name="ops-job-delete-cron-job"), 25 | url(r'^/script/commitcron', script_cron.commitcron, name="ops-job-script-commitcron"), 26 | url(r'^/script/enablecron', script_cron.enablecron, name="ops-job-script-enablecron"), 27 | url(r'^/script/script_http$', script_executor.scrpits_http, name="ops-job-script-http"), 28 | 29 | # 获取脚本参数 30 | url(r'^/script/get_args', script_executor.get_args, name="ops-job-script-get-args"), 31 | ] -------------------------------------------------------------------------------- /ops_job/views/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brick-li/ops-django/3244fbe4eb782ea07ac38606e5c63b9d2e5525da/ops_job/views/__init__.py -------------------------------------------------------------------------------- /ops_job/views/script_cron.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | from django.http import HttpResponse 4 | from django.views.decorators.csrf import csrf_exempt 5 | import json, os, time 6 | import traceback 7 | from djcelery.models import PeriodicTask, CrontabSchedule 8 | #from django.db import transaction 9 | from ops_job.models import OpsJobJobScriptInfo 10 | from .script_edit import get_job_name, get_script_name 11 | 12 | def datefield_to_str(date_data): 13 | ''' 14 | mysql date格式转换为字符串格式 15 | :param date_data: 16 | :return: 17 | ''' 18 | for i in date_data: 19 | if i.has_key("date_changed") and i.get('date_changed', None) is not None: 20 | i["date_changed"] = i["date_changed"].strftime("%Y-%m-%d %H:%M:%S") 21 | if i.has_key("last_run_at") and i.get('last_run_at', None) is not None: 22 | i["last_run_at"] = i["last_run_at"].strftime("%Y-%m-%d %H:%M:%S") 23 | return date_data 24 | 25 | @csrf_exempt 26 | def get_cron_list(request): 27 | cron_list = list(PeriodicTask.objects.all().values()) 28 | cron_list = datefield_to_str(cron_list) 29 | crons = list(CrontabSchedule.objects.all().values()) 30 | crons_dir = {} 31 | for item in crons: 32 | crons_dir[str(item['id'])] = "%s %s %s %s %s" % (item['minute'], item['hour'], item['day_of_month'], item['month_of_year'], item['day_of_week']) 33 | cron_list_ret = [{ 34 | "task_name": i['name'], 35 | "job_name": get_job_name(json.loads(i['kwargs']).get('script_name','无')), 36 | "script_name": json.loads(i['kwargs']).get('script_name','none'), 37 | "crontab": crons_dir.get(str(i['crontab_id']), ""), 38 | "is_root": True if json.loads(i['kwargs']).get('is_root', '0') == '1' else False, 39 | "script_args": json.loads(i['kwargs']).get('script_args', '无'), 40 | "module_args": json.loads(i['kwargs']).get('module_args', '无'), 41 | "args_type": json.loads(i['kwargs']).get('args_type', '1'), 42 | "args_type_str": '直接传参' if json.loads(i['kwargs']).get('args_type', '1') == '1' else '文件传参', 43 | "enabled": i['enabled'] 44 | } for i in cron_list if i['name'] != 'celery.backend_cleanup'] 45 | return HttpResponse(json.dumps({ 46 | "result": "success", 47 | "data": cron_list_ret 48 | })) 49 | 50 | 51 | 52 | @csrf_exempt 53 | def commitcron(request): 54 | try: 55 | task_name = request.POST.get('task_name') 56 | old_name = request.POST.get('old_name') 57 | enabled = True if request.POST.get('enabled') == 'true' else False 58 | _crontab = request.POST.get("crontab").strip() 59 | args_type = request.POST.get("args_type") 60 | try: 61 | crontab_time = { 62 | 'day_of_week': _crontab.split()[4], # 周 63 | 'month_of_year': _crontab.split()[3], # 月 64 | 'day_of_month': _crontab.split()[2], # 日 65 | 'hour': _crontab.split()[1], # 时 66 | 'minute': _crontab.split()[0], # 分 67 | } 68 | # 检查crontab是否已经存在 69 | crons = CrontabSchedule.objects.filter(**crontab_time).first() 70 | if crons is None: 71 | CrontabSchedule.objects.create(**crontab_time) 72 | except Exception: 73 | return HttpResponse(json.dumps({"result": "failed", "info": "定时表达式错误"})) 74 | search_name = task_name if task_name == old_name else old_name 75 | if search_name is None: 76 | search_name = task_name 77 | # 查找任务 78 | task, created = PeriodicTask.objects.get_or_create(name=search_name) 79 | crontab = CrontabSchedule.objects.filter(**crontab_time).first() 80 | task.name = task_name # name 81 | task.crontab = crontab # 设置crontab 82 | task.enabled = enabled # enabled 83 | task.task = "ops_job.tasks.celery_scripts" # tasks 84 | kwargs = { 85 | "task_name": task_name, 86 | "script_name": get_script_name(request.POST.get('job_name', '')), 87 | "args_type": request.POST.get('args_type', '1'), 88 | "is_root": request.POST.get('is_root'), 89 | "script_args": '' if request.POST.get('script_args', '') in ['无', ''] else request.POST.get( 90 | 'script_args', ''), 91 | "module_args": '' if request.POST.get('module_args', '') in ['无', ''] else request.POST.get( 92 | 'module_args', ''), 93 | } 94 | task.kwargs = json.dumps(kwargs) 95 | task.save() 96 | return HttpResponse(json.dumps({"result": "success", "info": "已修改定时任务"})) 97 | except Exception: 98 | print traceback.format_exc() 99 | return HttpResponse(json.dumps({"result": "failed", "info": "内部错误"})) 100 | 101 | 102 | def timeStampToTime(timestamp): 103 | return time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(int(timestamp))) 104 | 105 | @csrf_exempt 106 | def get_task_list(request): 107 | ''' 108 | 获取脚本作业列表,用于创建定时任务 109 | :return: 110 | ''' 111 | OpsJobJobScriptInfos = OpsJobJobScriptInfo.objects.filter(drop_status=0).values() 112 | tks = [i['job_name'] for i in OpsJobJobScriptInfos] 113 | return HttpResponse(json.dumps({"result": "success", "tks": tks})) 114 | 115 | 116 | @csrf_exempt 117 | def enablecron(request): 118 | try: 119 | job_name = request.POST.get('task_name', None) 120 | enabled = request.POST.get('enabled', False) 121 | if not job_name: 122 | return HttpResponse(json.dumps({"result": "failed", "info": "未获取到job name"})) 123 | else: 124 | periodic_task, created = PeriodicTask.objects.get_or_create(name=job_name) 125 | if created == True: 126 | return HttpResponse(json.dumps({"result": "failed", "info": "提交失败!任务不存在"})) 127 | periodic_task.enabled = True if enabled == 'true' else False 128 | periodic_task.save() 129 | if enabled == 'true': 130 | ret_info = "启用成功!" 131 | else: 132 | ret_info = "已停用!" 133 | return HttpResponse(json.dumps({"result": "success", "info": ret_info})) 134 | except Exception: 135 | print traceback.format_exc() 136 | return HttpResponse(json.dumps({"result": "failed", "info": "出错了,请到后台查看"})) 137 | 138 | 139 | @csrf_exempt 140 | def delete_cron_job(request): 141 | ''' 142 | 删除crontab任务 143 | :param request: 144 | :return: 145 | ''' 146 | task_name = request.POST.get('task_name') 147 | PeriodicTask.objects.filter(name=task_name).delete() 148 | return HttpResponse(json.dumps({"result": "success", "info": "出错了,请到后台查看"})) -------------------------------------------------------------------------------- /ops_job/views/script_edit.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | from django.http import HttpResponse 4 | from django.views.decorators.csrf import csrf_exempt 5 | import os, time, logging 6 | import json, commands 7 | import traceback 8 | import time 9 | from ops_job.models import OpsJobJobScriptInfo 10 | 11 | _logger = logging.getLogger(__name__) 12 | 13 | def cmd(cmdStr): 14 | status, out = commands.getstatusoutput(cmdStr) 15 | if status != 0 : 16 | print "[%s] ERROR:%s"%(cmdStr, out) 17 | return 1 18 | else: 19 | return out 20 | 21 | def datefield_to_str(date_data): 22 | ''' 23 | mysql date格式转换为字符串格式 24 | :param date_data: 25 | :return: 26 | ''' 27 | for i in date_data: 28 | if i.has_key("mTime") and i.get('mTime', None) is not None: 29 | i["mTime"] = i["mTime"].strftime("%Y-%m-%d %H:%M:%S") 30 | return date_data 31 | 32 | # Create your views here. 33 | def timeStampToTime(timestamp): 34 | return time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(int(timestamp))) 35 | 36 | def get_job_name(script_name): 37 | OpsJobJobScriptInfos = OpsJobJobScriptInfo.objects.filter(script_name=script_name,drop_status=0).first() 38 | if OpsJobJobScriptInfos is None: 39 | return '未命名' 40 | else: 41 | return OpsJobJobScriptInfos.job_name 42 | 43 | def get_script_name(job_name): 44 | OpsJobJobScriptInfos = OpsJobJobScriptInfo.objects.filter(job_name=job_name,drop_status=0).first() 45 | if OpsJobJobScriptInfos is None: 46 | return '未找到脚本名' 47 | else: 48 | return OpsJobJobScriptInfos.script_name 49 | 50 | @csrf_exempt 51 | def get_scripts_list(request): 52 | OpsJobJobScriptInfos = list(OpsJobJobScriptInfo.objects.filter(drop_status=0).values()) 53 | OpsJobJobScriptInfos = datefield_to_str(OpsJobJobScriptInfos) 54 | file_list = list() 55 | for item in OpsJobJobScriptInfos: 56 | file_list.append({'script_job_name': item['job_name'], 'script_name': item['script_name'], 'mtime': item['mTime']}) 57 | return HttpResponse(json.dumps({'data': file_list})) 58 | 59 | @csrf_exempt 60 | def edit_script(request): 61 | script_name = request.POST.get('script_name', None) 62 | script_type = script_name.split('.')[-1] 63 | script_type_list = {'py': 'python', 64 | 'sh': 'shell', 65 | 'pl': 'perl'} 66 | script_type = script_type_list.get(script_type, 'shell') 67 | script_info = OpsJobJobScriptInfo.objects.filter(script_name=script_name,drop_status=0).first() 68 | script_content = '' if script_info is None else script_info.script_content 69 | script_name = '' if script_info is None else script_info.script_name 70 | script_job_name = '' if script_info is None else script_info.job_name 71 | return HttpResponse(json.dumps({ 'data': { 72 | "script_content": script_content, 73 | "script_type": script_type, 74 | "script_name": script_name, 75 | "script_job_name": script_job_name 76 | }})) 77 | 78 | 79 | @csrf_exempt 80 | def updatescript(request): 81 | script_name = request.POST.get("script_name", None) 82 | script_content = request.POST.get("script_content", None) 83 | job_name = request.POST.get("script_job_name", None) 84 | # old_job_name = request.POST.get("old_job_name", None) 85 | old_script_name = request.POST.get("old_script_name", None) 86 | search_name = old_script_name if not script_name == old_script_name else script_name 87 | #curPath = os.path.abspath(os.path.dirname(__file__)) 88 | #if not script_name == old_script_name and old_script_name != '': 89 | # script_path = "%s/script/%s" % (curPath, old_script_name) 90 | # os.remove(script_path) #删除旧文件 91 | #new_script_path = "%s/script/%s" % (curPath, script_name) 92 | #with open(new_script_path, 'w') as f: #创建新文件或修改原文件 93 | # f.write(script_content.encode('utf-8')) 94 | #cmd("cd %s/script && git add . && git commit -m 'remove script: %s, update script: %s' && git push" % 95 | # (curPath, old_script_name, script_name)) 96 | #更新script.list 97 | job_script_info,created = OpsJobJobScriptInfo.objects.get_or_create(script_name=search_name,drop_status=0) 98 | job_script_info.script_name = script_name 99 | job_script_info.job_name = job_name 100 | job_script_info.mTime = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(int(time.time()))) 101 | job_script_info.script_content = script_content.encode('utf-8') 102 | job_script_info.save() 103 | #job_script_info.script_name = script_name 104 | return HttpResponse(json.dumps({"result": "success", "info":"commit success"})) 105 | 106 | @csrf_exempt 107 | def delete_script(request): 108 | try: 109 | script_name = request.POST.get("script_name", None) 110 | OpsJobJobScriptInfo.objects.filter(script_name=script_name).update(drop_status=1) 111 | return HttpResponse(json.dumps({"result": "success", "info": "脚本已删除"})) 112 | except Exception: 113 | return HttpResponse(json.dumps({"result": "failed", "info": "脚本删除失败: %s" % traceback.format_exc()})) 114 | -------------------------------------------------------------------------------- /ops_job/views/script_executor.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | from __future__ import absolute_import 3 | from django.http import HttpResponse 4 | #import traceback 5 | import json 6 | import traceback 7 | from django.views.decorators.csrf import csrf_exempt 8 | from classes import ansible_api 9 | from cmdb.views.tree import get_ips_by_set_module 10 | from ops_job.models import OpsJobScriptHistory 11 | from .script_edit import get_job_name, get_script_name 12 | import logging 13 | 14 | _logger = logging.getLogger(__name__) 15 | 16 | 17 | def get_history_id(task_name): 18 | ''' 19 | 创建任务并获得history_id''' 20 | _history = OpsJobScriptHistory() 21 | _history.job_name = task_name 22 | _history.result = json.dumps({}) 23 | _history.exec_status = 1 24 | _history.save() 25 | _history_id = _history.id 26 | return _history_id 27 | 28 | @csrf_exempt 29 | def exector_create(request): 30 | ''' 31 | 创建任务记录 32 | :return: 33 | ''' 34 | task_name = request.POST.get("task_name") 35 | _history_id = get_history_id(task_name) 36 | return HttpResponse(json.dumps({"result": "success", "data": {"history_id": _history_id}})) 37 | 38 | def exec_scripts(kwargs): 39 | ''' 40 | 执行脚本统一入口 41 | :return: 42 | ''' 43 | try: 44 | args_type = kwargs.get("args_type", None) 45 | script_name = kwargs.get("script_name", None) 46 | job_name = kwargs.get("job_name", None) 47 | history_id = kwargs.get("history_id", None) 48 | if history_id is None: 49 | return {'result': 'failed', 'info': u'请先创建任务'} 50 | if script_name is None or script_name == '': 51 | job_name = kwargs.get("job_name", None) 52 | script_name = get_script_name(job_name) 53 | elif job_name is None or job_name == '': 54 | job_name = get_job_name(script_name) 55 | else: 56 | pass 57 | if script_name is None and job_name is None: 58 | _logger.error('%s script %s"' % (script_name, u"传入名称参数错误")) 59 | script_args = kwargs.get("script_args", '') 60 | module_args = kwargs.get("module_args", None) #模块参数 例: "-s vms_test -m env1" 根据参数获取对应ip地址 61 | specific_ip = kwargs.get("specific_ip", []) #指定ip地址 62 | is_root = kwargs.get("is_root", 0) #指定ip地址 63 | if script_name is None: 64 | _logger.error('%s script %s"' % (script_name, u"未指定作业")) 65 | return {'result': 'failed', 'info':u'未指定作业'} 66 | if module_args is not None and module_args != '': 67 | ips = get_ips_by_set_module(module_args) 68 | ips = ips + specific_ip 69 | else: 70 | return {'result': 'failed', 'info': u'未指定ip'} 71 | if args_type == 'normal': 72 | script_args_req = script_args 73 | elif args_type == 'file': 74 | _, script_args_req = get_ips_by_set_module(module_args, file_params=script_args) 75 | script_args_req = ["%s %s" % (i['ip'], i['params']) for i in script_args_req] 76 | script_args_req = '\n'.join(script_args_req) 77 | else: 78 | return {'result': 'failed', 'info': u'参数错误'} 79 | if is_root == '1': 80 | ansible_interface = ansible_api.AnsiInterface(become=True, become_method='sudo', become_user='root', history_id=history_id) 81 | result = ansible_interface.exec_script_all_type(ips, script_name, script_args=script_args_req, args_type=args_type) 82 | else: 83 | ansible_interface = ansible_api.AnsiInterface(history_id=history_id) 84 | result = ansible_interface.exec_script_all_type(ips, script_name, script_args=script_args_req, args_type=args_type) 85 | #result = ansible_interface._get_result() 86 | _history = OpsJobScriptHistory.objects.get(id=history_id) 87 | _history.result = json.dumps(result) 88 | _history.ip_list = json.dumps(ips) 89 | _history.exec_status = 0 90 | _history.save() 91 | g_res = result 92 | return {"result": "success", "data": g_res} 93 | except Exception: 94 | _logger.error(traceback.format_exc()) 95 | 96 | @csrf_exempt 97 | def get_args(request): 98 | """ 99 | 点击获取脚本参数 100 | :param request: 101 | :return: 102 | """ 103 | try: 104 | args_type = request.POST.get("args_type") 105 | module_args = request.POST.get("module_args") 106 | script_args = request.POST.get("script_args") 107 | if args_type == 'normal': 108 | ips, args = get_ips_by_set_module(module_args, normal_params=script_args) 109 | elif args_type == 'file': 110 | ips, args = get_ips_by_set_module(module_args, file_params=script_args) 111 | else: 112 | return HttpResponse(json.dumps({"result": "failed", "info": "未获取到ip列表"})) 113 | return HttpResponse(json.dumps({"result": "success", "data": args})) 114 | except Exception: 115 | _logger.error(traceback.format_exc()) 116 | return HttpResponse(json.dumps({"result": "failed", "info": "获取参数有误"})) 117 | 118 | @csrf_exempt 119 | def script_http(request): 120 | try: 121 | kwargs = json.loads(request.POST.get("kwargs", None)) 122 | ret = exec_scripts(kwargs) 123 | return HttpResponse(json.dumps(ret)) 124 | except Exception: 125 | _logger.error(traceback.format_exc()) 126 | print traceback.format_exc() -------------------------------------------------------------------------------- /ops_job/views/script_history.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | from django.http import HttpResponse 4 | from django.views.decorators.csrf import csrf_exempt 5 | import json, logging 6 | import traceback 7 | from djcelery.models import PeriodicTask, CrontabSchedule 8 | #from django.db import transaction 9 | from ops_job.models import OpsJobScriptHistory 10 | from operator import itemgetter 11 | 12 | 13 | _logger = logging.getLogger(__name__) 14 | 15 | 16 | @csrf_exempt 17 | def get_script_history(request): 18 | startTime = request.POST.get('startTime', None) 19 | endTime = request.POST.get('endTime', None) 20 | # print startTime, endTime 21 | if not endTime or not startTime: 22 | return HttpResponse(json.dumps({"result": "failed", "info": "请指定时间"})) 23 | script_history = OpsJobScriptHistory.objects.filter(dtEventTime__gte=startTime, dtEventTime__lte=endTime).values() 24 | if not script_history: 25 | return HttpResponse(json.dumps({"result": "success", "data": []})) 26 | data = list() 27 | for item in script_history: 28 | job_name = item['job_name'] 29 | ip_list = item['ip_list'] 30 | ip_list = '[]' if ip_list == '' else ip_list 31 | if ip_list == '': 32 | ip_list = [] 33 | exec_status = item['exec_status'] 34 | try: 35 | _result = json.loads(item['result']) 36 | except: 37 | _logger.error("result not a json") 38 | continue 39 | host_ok_list = _result.get('host_ok', {}) 40 | host_ok_ip_count = len(host_ok_list) 41 | host_failed_list = _result.get('host_failed', {}) 42 | host_failed_ip_count = len(host_failed_list) 43 | host_unreachable_list = _result.get('host_unreachable', {}) 44 | host_unreachable_ip_count = len(host_unreachable_list) 45 | for i in [host_ok_list, host_failed_list, host_unreachable_list]: 46 | for k, v in dict(i).items(): 47 | if v.has_key('stdout'): 48 | del v['stdout'] 49 | if v.has_key('stderr'): 50 | v['stderr'] = v['stderr'].split('\n') 51 | detail = { 52 | "成功IP:": host_ok_list, 53 | "失败IP:": host_failed_list, 54 | "不可达IP:": host_unreachable_list, 55 | "未执行IP": list(set(ip_list) ^ set(host_ok_list.keys() + host_failed_list.keys() + host_unreachable_list.keys())), 56 | "IP列表:": ip_list 57 | } 58 | dteventtime = item['dtEventTime'] 59 | data.append({ 60 | "id": item['id'], 61 | "job_name": job_name, 62 | "host_ok_ip_count": host_ok_ip_count, 63 | "host_failed_ip_count": host_failed_ip_count, 64 | "host_unreachable_ip_count": host_unreachable_ip_count, 65 | "detail": detail, 66 | "exec_status": False if exec_status == 0 else True, 67 | "dteventtime": dteventtime.strftime("%Y-%m-%d %H:%M:%S") 68 | }) 69 | data = sorted(data, key=lambda i: i['dteventtime']) #排序 70 | data.reverse() 71 | return HttpResponse(json.dumps({"result": "success", "data": data})) 72 | 73 | @csrf_exempt 74 | def script_history_detail(request): 75 | try: 76 | history_id = request.GET.get("history_id", None) 77 | if not history_id: 78 | return HttpResponse(json.dumps({"result": "failed", "data": {"ExecComplete":True, "info": "传入记录ID错误"}})) 79 | 80 | script_history = OpsJobScriptHistory.objects.filter(id=history_id).first() 81 | if not script_history: 82 | return HttpResponse(json.dumps({"result": "failed", "data": "未查到执行历史记录"})) 83 | #job_name = script_history.job_name 84 | #dteventtime = script_history.dtEventTime 85 | ip_list = script_history.ip_list 86 | ip_list = '[]' if ip_list == '' else ip_list 87 | exec_status = script_history.exec_status 88 | _result = json.loads(script_history.result) 89 | host_ok_list = _result.get('host_ok', {}) 90 | host_ok_ip_count = len(host_ok_list) 91 | host_failed_list = _result.get('host_failed', {}) 92 | host_failed_ip_count = len(host_failed_list) 93 | host_unreachable_list = _result.get('host_unreachable', {}) 94 | host_unreachable_ip_count = len(host_unreachable_list) 95 | 96 | for i in [host_ok_list, host_failed_list, host_unreachable_list]: 97 | for k, v in dict(i).items(): 98 | if v.has_key('stdout'): del v['stdout'] 99 | if v.has_key('stderr'): v['stderr'] = v['stderr'].split('\n') 100 | detail = { 101 | "成功IP[%s]:" % host_ok_ip_count: host_ok_list, 102 | "失败IP[%s]:" % host_failed_ip_count: host_failed_list, 103 | "不可达IP[%s]:" % host_unreachable_ip_count: host_unreachable_list, 104 | "未执行IP": list(set(json.loads(ip_list)) ^ set(host_ok_list.keys() + host_failed_list.keys() + host_unreachable_list.keys())), 105 | "IP列表:": ip_list, 106 | "ExecComplete": True if exec_status == 0 else False 107 | } 108 | return HttpResponse(json.dumps({ 109 | "result": "success", 110 | "data": detail 111 | })) 112 | except Exception: 113 | print traceback.format_exc() 114 | return HttpResponse(json.dumps({ 115 | "result": "failed", 116 | "data": {"ExecComplete": True, "info": "查询失败,已停止"} 117 | })) -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | django==1.11.17 2 | mysql-python==1.2.5 3 | django-cors-headers==2.4.0 4 | pycrypto==2.6.1 5 | DBUtils==1.3 6 | ansible==2.7.6 7 | pathos==0.2.2.1 8 | django-celery==3.2.2 -------------------------------------------------------------------------------- /start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | 4 | nohup python manage.py celery beat & 5 | nohup python manage.py celery worker & 6 | 7 | cd /app 8 | python manage.py runserver 0.0.0.0:8000 9 | --------------------------------------------------------------------------------