├── README.md ├── geniusalt_cli ├── gnsalt └── sub │ ├── __init__.py │ ├── add.py │ ├── bind.py │ ├── common.py │ ├── config.py │ ├── delete.py │ ├── dispatcher.py │ ├── eset.py │ ├── include.py │ ├── lock.py │ ├── pdel.py │ ├── pset.py │ ├── push.py │ ├── scan.py │ ├── show.py │ └── usage.py ├── gnsalt_modules ├── blacklist │ ├── init.sls │ ├── instance │ │ ├── init.sls │ │ ├── set_wlist.sls │ │ └── templates │ │ │ └── temp.example.com.lua │ ├── mkdirs.sls │ └── pillar.json ├── http_proxy │ ├── init.sls │ ├── instance │ │ ├── app_proxy.sls │ │ ├── init.sls │ │ └── templates │ │ │ └── temp.http_proxy.conf │ ├── mkdirs.sls │ └── pillar.json ├── proxy_base │ ├── dyups_api.sls │ ├── files │ │ ├── dyups.conf │ │ ├── nginx.conf │ │ └── nginx_for_logrotate │ ├── init.sls │ ├── installer.sls │ └── mkdirs.sls ├── tcp_proxy │ ├── init.sls │ ├── instance │ │ ├── app_proxy.sls │ │ ├── init.sls │ │ └── templates │ │ │ └── temp.tcp_proxy.conf │ ├── mkdirs.sls │ └── pillar.json └── whitelist │ ├── init.sls │ ├── instance │ ├── init.sls │ ├── set_wlist.sls │ └── templates │ │ └── temp.example.com.lua │ ├── mkdirs.sls │ └── pillar.json └── hfnlb_project ├── geniusalt ├── __init__.py ├── api │ ├── __init__.py │ ├── api_ingress.py │ ├── auth.py │ └── urls.py ├── bin │ └── token_manager ├── check.py ├── config.py ├── migrations │ ├── 0001_initial.py │ ├── 0002_auto_20180312_1847.py │ └── __init__.py ├── models.py ├── operators │ ├── __init__.py │ ├── common.py │ ├── instance_db_operator.py │ ├── module_db_operator.py │ ├── node_db_operator.py │ ├── push_operator.py │ └── relation_operator.py └── views.py ├── hfnlb_project ├── __init__.py ├── settings.py ├── urls.py └── wsgi.py ├── manage.py ├── nlb_proxy ├── __init__.py ├── api │ ├── __init__.py │ ├── blacklist.py │ ├── clustering.py │ ├── common.py │ ├── http_proxy.py │ ├── iplist.py │ ├── login.py │ ├── tcp_proxy.py │ ├── urls.py │ ├── users.py │ └── whitelist.py ├── bin │ └── nlb_timer ├── config.py ├── daemon │ ├── ReadMe_for_daemon.md │ ├── __init__.py │ ├── apply_handler.py │ ├── daemon_mixin.py │ ├── health_check.py │ ├── init_handler.py │ └── nlb_timer.py ├── migrations │ ├── 0001_initial.py │ ├── 0002_httpproxy_tcpproxy.py │ ├── 0003_httpproxy_push_result.py │ ├── 0004_clustermember.py │ ├── 0005_auto_20180402_1608.py │ ├── 0006_nlbconfig.py │ ├── 0007_auto_20180404_1302.py │ ├── 0008_auto_20180404_1559.py │ ├── 0009_auto_20180423_1430.py │ ├── 0010_auto_20180507_1550.py │ ├── 0011_pushlog_push_time.py │ ├── 0012_tcpproxy_proxy_domain_name.py │ ├── 0013_auto_20180509_1646.py │ ├── 0014_auto_20180510_1439.py │ ├── 0015_auto_20180511_1415.py │ ├── 0016_auto_20180514_1120.py │ ├── 0017_auto_20180514_1431.py │ ├── 0018_blackliststrategy.py │ ├── 0019_auto_20180514_1628.py │ ├── 0020_auto_20180516_1630.py │ ├── 0021_auto_20180516_1720.py │ ├── 0022_auto_20180525_1645.py │ ├── 0023_clustermember_is_deleted.py │ └── __init__.py ├── models.py ├── scripts │ ├── configfiles │ │ ├── master │ │ ├── minion │ │ └── sysctl.conf │ ├── nlb_init.sh │ └── service_check.sh ├── static │ ├── 6f0a76321d30f3c8120915e57f7bd77e.ttf │ ├── css │ │ └── font-awesome.min.css │ ├── fonts │ │ ├── FontAwesome.otf │ │ ├── fontawesome-webfont.eot │ │ ├── fontawesome-webfont.svg │ │ ├── fontawesome-webfont.ttf │ │ ├── fontawesome-webfont.woff │ │ └── fontawesome-webfont.woff2 │ ├── img │ │ ├── 1111.png │ │ └── nlb_background.jpeg │ ├── index.html │ ├── index.js │ ├── index.js.map │ ├── index_used.html │ ├── login │ │ ├── 6f0a76321d30f3c8120915e57f7bd77e.ttf │ │ ├── index.html │ │ ├── index.js │ │ ├── index.js.map │ │ ├── index_login.html │ │ ├── manifest.js │ │ ├── manifest.js.map │ │ ├── vendor.js │ │ └── vendor.js.map │ ├── manifest.js │ ├── manifest.js.map │ ├── vendor.js │ └── vendor.js.map ├── tools.py └── views.py └── start.sh /README.md: -------------------------------------------------------------------------------- 1 | 简要说明 2 | =========== 3 | 4 | 这是一套基于openresty封装的Nginx负载均衡集群系统。用反向代理的方式,实现内部服务的负载均衡。 5 | 6 | 支持七层反代,四层反代,七层黑白名单访问控制,流量切换等功能,支持nginx集群自动初始化,自动扩容等功能。 7 | 8 | 底层用Django开发的一套API,web用vue.js开发。集群初始化走ssh通道。代理配置同步使用geniusalt来分发文件。 9 | 10 | 本系统定位在于给运维人员使用,暂无复杂权限管理机制,只有简单的用户增删的功能。 11 | 12 | 关于geniusalt请,参考项目:https://github.com/alan011/geniusalt-apiserver 13 | 14 | 系统构成由三部分组成: 15 | 16 | * api-server 17 | 18 | Django开发的一套API,封装了所有功能。 19 | 20 | * web界面 21 | 22 | Vue.js结合element-ui开发的一套简洁的界面。使用AJAX跟api-server对接。 23 | 24 | * 后台Daemon程序 25 | 26 | 第一个独立于api-server的python进程。需要单独启动。相当于一个timer程序,实现了集群自动初始化、自动下发配置的变更、集群健康检查等功能。 27 | 28 | 29 | 安装使用 30 | =========== 31 | 32 | 所有机器底层依赖: 33 | * OS: centos6.x 34 | * ssh: 请确保集群中所有机器可以通过统一的USER/PASSWD登录机器,并能免密使用sudo,以供集群管理自动完成初始化。 35 | * 底层依赖-1:openresty,请确保集群中所有机器可以通过yum安装openresty软件包。Daemon程序会自动安装,启动服务。 36 | * 底层依赖-2:saltstack,请确保集群中所有机器可以通过yum安装salt-master, salt-minion软件包。Daemon程序会自动安装,启动相关服务。 37 | 38 | master运行环境: 39 | * 语言: python-3.5 以上 40 | * 框架: django-1.11.x 41 | * pip3依赖包: PyYaml, jsonfield, paramiko, requests。若DB使用mysql,还需要安装'mysqlclient'. 42 | 43 | 浏览器:WEB端使用vue.js开发,Chrome开发测试环境,请使用Chrome浏览器。 44 | 45 | master安装方法: 46 | * 下载本项目所有源码,解压至一个目录。 47 | * 设置django配置文件: `hfnlb_project/hfnlb_project/settings.py`, 修改`ALLOWED_HOSTS`,`DATABASES`两个配置项,并根据DB的配置,创建相应的库。 48 | * 启动web服务:到`hfnlb_project/`目录下,启动`start.sh` 49 | * 启动Daemon程序:到`hfnlb_project/nlb_proxy/bin`目录下,执行`nohup ./nlb_timer &` 50 | 51 | 注意: 52 | * `start.sh`中默认启动`0.0.0.0:10080`,可根据自己需求修改。 53 | * `start.sh`这种启动方式,仅供测试、体验用。若要用到高并发环境中,请使用`nginx + uWSGI` 来启动web服务。 54 | 55 | 56 | 集群管理 57 | =========== 58 | 59 | 本系统提供了自动化的集群管理机制。意味着,除了master需手动安装外,其他成员均可自动完成初始化,应用配置自动同步。 60 | 61 | 但,以上的自动化集群管理,依赖几个前提条件: 62 | 63 | * 可用统一系统用户通过ssh登录到所有成员机器 64 | 65 | 集群中所有机器,请使用同一的系统 user/password,并且user能有无密sudo权限。 66 | 这样master可以通过ssh通道登录到目标机器上,执行成员机器的初始化工作。 67 | 这个系统用户,可以在界面中的“集群管理”,“初始化设置”中设定。 68 | 69 | * 确保可以通过yum安装所需软件包 70 | 71 | 各成员机器,所需系统软件包:salt-minion, openresty. 72 | 73 | * 确保nlb_timer程序已启动 74 | 75 | 满足以上条件后,要为此Nginx集群添加成员,在“集群管理”栏,添加IP列表即可。集群可自动完成初始化,自动同步已有应用配置。 76 | 77 | 注意:一般情况下,NLB集群上游会挂一个四层负载均衡器(比如F5, LVS等),新成员的IP需要管理员手动加到上游负载均衡池。目前还没有实现这块的自动添加功能。 78 | 79 | 80 | 接口说明 81 | ========== 82 | 83 | 本着前后端分离的开发原则,本系统将所有功能都先封装成了接口。 84 | 注意:所有接口都要求用户先登录才能正常使用。 85 | 86 | 87 | 七层代理管理接口 88 | ---------- 89 | 90 | URI: `/proxy/api/v1/httpproxy` 91 | 92 | 返回数据:所有接口均返回一个json字典,如下。 93 | ``` 94 | { "result":"SUCCESS", #表示接口执行结果,成功是“SUCCESS", 失败是“FAILED” 95 | "message":"<文本信息>", #一段返回的文本信息。 96 | ... #对GET方法,请求的结果,还会增加一个'data'字段,具体见下文。 97 | } 98 | ``` 99 | 100 | 接口调用逻辑: 101 | 102 | * 获取全部信息 103 | 104 | 方法:GET 105 | 106 | 参数:无 107 | 108 | 返回数据:在上文所说的json字典中会增加一个‘data’字段,其值是一个list,list是一个表征每条应用代理配置的属性与状态的字典。 109 | 110 | * 检索、过滤信息: 111 | 112 | 方法:GET 113 | 114 | 参数:search_value 115 | 116 | 返回数据:在上文所说的json字典中会增加一个‘data’字段,其值是一个list,list是一个表征每条应用代理配置的属性与状态的字典。 117 | 118 | 说明:用search_value参数来支持复杂检索模式。支持以下检索、过滤规则: 119 | 120 | * 全字段模糊搜索 121 | 122 | 支持以下字段的单值模糊匹配:域名(DOMAIN,包含代理域名和其他域名),应用(APP),应用端口(APP_PORT),监听端口(LISTEN_PORT),服务器(SERVER,包含应用服务器和热份服务器),用户(USER),描述(DESC)。 123 | 即用search_value的值去匹配数据库中的以上所有字段。 124 | 125 | * 指定字段进行模糊匹配:DOMAIN:<域名>&APP:<应用>... 126 | 127 | “:”用于模糊匹配。相对“全字段”而言,这里仅匹配指定的字段。 128 | 129 | * 指定字段进行精确匹配:DOMAIN=<域名>&APP=<应用>... 130 | 131 | “=”用于精确匹配。 132 | 133 | * 输入格式不满足第二、三条时,将默认触发第一条“全字段模糊搜索”。 134 | 135 | * 增 136 | 137 | 方法: POST 138 | 139 | 数据格式: Form表单 140 | 141 | post数据字段: 142 | 143 | ``` 144 | {"action":"add", #必填字段,表示要执行添加动作。 145 | "proxy_domain_name":"<反向代理域名>", #必填字段,用于servername. 146 | "app_name":"<被代理的应用名称>", #必填字段,用于命名upstream. 147 | "app_port":"<被代理的应用端口>", #必填字段,用于upstream中,定义后端机器的服务端口转发。 148 | "app_servers":"<被代理应用的运行服务器>" #必填字段,用于upstream中,定义后端机器成员。多个用'/'隔开。 149 | 150 | "app_servers_backup":"<被代理应用的运行服务器热备机器>" #可选字段,多个用'/'隔开,成员需是‘app_servers’的子集。 151 | "other_domain_names":"<额外的servername>" #可选字段,多个用'/'隔开 152 | "proxy_listen_ports":"<额外的nginx listen端口>" #可选字段,不指定的话,nginx默认监听80。多个用'/'隔开。 153 | "description":"<文字描述>" #文字描述 154 | } 155 | ``` 156 | 157 | * 删: 158 | 159 | 方法: POST 160 | 161 | 数据格式: Form表单 162 | 163 | post数据字段: 164 | 165 | ``` 166 | {"action":"delete", #必填字段,表示要执行删除动作。 167 | "id":"<应用代理数据库ID>" #必填字段 168 | } 169 | ``` 170 | 171 | * 改: 172 | 173 | 方法: POST 174 | 175 | 数据格式: Form表单 176 | 177 | post数据字段: 178 | 179 | ``` 180 | {"action":"edit", #必填字段,表示要执行修改动作。 181 | "id":"<应用代理数据库ID>", #必填字段 182 | ... #'add'方法中所陈述的字段,均支持修改。 183 | } 184 | ``` 185 | 186 | * 停用 187 | 188 | 方法: POST 189 | 190 | 数据格式: Form表单 191 | 192 | post数据字段: 193 | 194 | ``` 195 | {"action":"disable", #必填字段,表示要执行停用动作。 196 | "id":"<应用代理数据库ID>", #必填字段 197 | } 198 | ``` 199 | 200 | * 启用 201 | 202 | 方法: POST 203 | 204 | 数据格式: Form表单 205 | 206 | post数据字段: 207 | 208 | ``` 209 | {"action":"enable", #必填字段,表示要执行添加动作。 210 | "id":"<应用代理数据库ID>", #必填字段 211 | } 212 | ``` 213 | 214 | * 重新标记推送 215 | 216 | 方法: POST 217 | 218 | 数据格式: Form表单 219 | 220 | post数据字段: 221 | 222 | ``` 223 | {"action":"saltRepush", #必填字段,表示要将此条代理配置标记为未推送,让daemon程序重新触发推送逻辑。 224 | "id":"<应用代理数据库ID>", #必填字段 225 | } 226 | ``` 227 | 228 | * 获取推送日志 229 | 230 | 方法: POST 231 | 232 | 数据格式: Form表单 233 | 234 | post数据字段: 235 | 236 | ``` 237 | {"action":"getPushLog", #必填字段,用于获取此条代理配置的saltstack推送日志。 238 | "id":"<应用代理数据库ID>", #必填字段 239 | } 240 | ``` 241 | 242 | 243 | 244 | 四层代理管理接口 245 | ---------- 246 | 247 | URI: /proxy/api/v1/tcpproxy 248 | 249 | 250 | 访问IP管理接口 251 | ---------- 252 | 253 | URI: /proxy/api/v1/iplist 254 | 255 | 256 | 白名单管理接口 257 | ---------- 258 | 259 | URI: /proxy/api/v1/whitelist 260 | 261 | 262 | 黑名单管理接口 263 | ---------- 264 | 265 | URI: /proxy/api/v1/blacklist 266 | 267 | 268 | 集群管理接口 269 | ---------- 270 | 271 | URI: /proxy/api/v1/cluster 272 | 273 | 274 | 用户管理接口 275 | ---------- 276 | 277 | URI: /proxy/api/v1/users 278 | 279 | 280 | 用户登录接口 281 | ---------- 282 | 283 | URI: /proxy/api/v1/login 284 | 285 | 286 | 用户信息查询接口 287 | ---------- 288 | 289 | URI: /proxy/api/v1/userinfo 290 | 291 | 292 | Daemon程序说明 293 | ============ 294 | 295 | <未完待续> 296 | -------------------------------------------------------------------------------- /geniusalt_cli/gnsalt: -------------------------------------------------------------------------------- 1 | #!/usr/local/bin/python3 2 | import sys 3 | from os.path import dirname, abspath 4 | sys.path.append(dirname(abspath(__file__))) 5 | 6 | from sub import CommandDispatcher 7 | 8 | if __name__ == '__main__': 9 | if len(sys.argv) < 2 or 'help' in sys.argv or '-h' in sys.argv or '--help' in sys.argv: 10 | CommandDispatcher.usage() 11 | sys.exit(0) 12 | 13 | action = sys.argv[1] 14 | if not action in CommandDispatcher.action_supported: 15 | CommandDispatcher.usage() 16 | sys.exit(1) 17 | 18 | command = CommandDispatcher(action=action, args=sys.argv[2:]) 19 | command.run() 20 | -------------------------------------------------------------------------------- /geniusalt_cli/sub/__init__.py: -------------------------------------------------------------------------------- 1 | from .dispatcher import CommandDispatcher 2 | 3 | 4 | __all__ = [ 5 | 'CommandDispatcher' 6 | ] 7 | -------------------------------------------------------------------------------- /geniusalt_cli/sub/add.py: -------------------------------------------------------------------------------- 1 | from .common import Handler 2 | 3 | class Add(Handler): 4 | def add(self): 5 | supported_options = ['-n','-e','-m','-pr', '-po', '-i','-p','-mb'] 6 | if not self.argParse(supported_options): 7 | self.error(self.argParseError) 8 | if not (self.nodes or self.modules or self.instances): 9 | self.error("ERROR: Add command requires an object name, which can be specified with option '-n, -m or -i'.") 10 | if (self.nodes and self.modules) or (self.nodes and self.instances) or (self.modules and self.instances): 11 | self.error("ERROR: Only one option '-n, -m or -i' can be specified.") 12 | 13 | if self.nodes: 14 | for name in self.nodes: 15 | post_data = {'object':'node', 'name':name} 16 | if self.environment: 17 | post_data['environment'] = self.environment 18 | self.call(post_data) 19 | 20 | if self.modules: 21 | for name in self.modules: 22 | post_data = {'object':'module', 'name':name} 23 | if self.pillar_required: 24 | post_data['pillar_required'] = self.pillar_required 25 | if self.pillar_optional: 26 | post_data['pillar_optional'] = self.pillar_optional 27 | self.call(post_data) 28 | 29 | if self.instances: 30 | if not self.module_belong: 31 | self.error("ERROR: instance must belong to a module, use '-mb' to specify it.") 32 | for name in self.instances: 33 | post_data = {'object':'instance', 'name':name, 'module_belong': self.module_belong} 34 | if self.pillars: 35 | post_data['pillar'] = self.pillars 36 | if self.environment: 37 | post_data['environment'] = self.environment 38 | self.call(post_data) 39 | -------------------------------------------------------------------------------- /geniusalt_cli/sub/bind.py: -------------------------------------------------------------------------------- 1 | from .common import Handler 2 | 3 | class Bind(Handler): 4 | def bind_common(self): 5 | supported_options = ['-n', '-i', '-m'] 6 | if not self.argParse(supported_options): 7 | self.error(self.argParseError) 8 | if not self.nodes or not (self.instances or self.modules): 9 | self.error("ERROR: action '%s' is used to %s instances or modules to node objects. Option '-n' must be specified. At least one of Option '-i' or '-m' must also be specified." % (self.action, self.action)) 10 | 11 | post_data = {'object':'relation', 'nodes': self.nodes} 12 | if self.modules: 13 | post_data['bind_modules'] = self.modules 14 | if self.instances: 15 | post_data['bind_instances'] = self.instances 16 | self.call(post_data) 17 | def bind(self): 18 | self.bind_common() 19 | def unbind(self): 20 | self.bind_common() 21 | -------------------------------------------------------------------------------- /geniusalt_cli/sub/common.py: -------------------------------------------------------------------------------- 1 | import json, requests, re, yaml, sys 2 | from .config import * 3 | 4 | class APICaller(object): 5 | def post_caller(self, api_url, post_data=None, isjson=True): 6 | if isjson: 7 | try: 8 | r = requests.post(api_url, json = post_data) 9 | except: 10 | return "API ERROR: host not reachable.\n API URL: %s\n JSON: %s" % (api_url, str(post_data)) 11 | else: 12 | try: 13 | r = requests.post(api_url, data = post_data) 14 | except: 15 | return "API ERROR: host not reachable.\n API URL: %s\n DATA: %s" % (api_url, str(post_data)) 16 | 17 | if r.status_code == 200: 18 | # To check the data successfully returned by geniusalt_api. 19 | try: 20 | return_data = r.json() 21 | except ValueError: #This means data returned is a normal string, which cannot be used with r.json(). 22 | return r.text 23 | else: 24 | return return_data 25 | elif r.status_code == 500: 26 | return "API ERROR: API corrupt." 27 | else: 28 | if re.search('^ERROR:', r.text): 29 | return "API %s\nstatus code: %d" % (r.text, r.status_code) 30 | else: 31 | return "API ERROR: %s\nstatus code: %d" % (r.text, r.status_code) 32 | 33 | class Handler(APICaller): 34 | api = GENIUSALT_API 35 | auth_token = AUTH_TOKEN 36 | action = None 37 | args = None 38 | 39 | ### Object attributes parsed from args. 40 | nodes = None 41 | instances = None 42 | modules = None 43 | pillars = None 44 | pillar_required = None 45 | pillar_optional = None 46 | environment = None 47 | included_instances = None 48 | module_belong = None 49 | show_short = False 50 | show_instances = False 51 | push_checkself = False 52 | push_only_module = False 53 | 54 | ### action result 55 | api_return = None 56 | argParseError = '' 57 | 58 | def getOptionValue(self, opt): 59 | l = len(self.args) 60 | i = self.args.index(opt) 61 | val = None 62 | if i + 1 < l: 63 | if not re.search('^\-{1,2}\w+', self.args[i + 1]): 64 | val = self.args.pop(i + 1) 65 | self.args.pop(i) 66 | return val 67 | 68 | def splitNames(self, opt): 69 | names = [] 70 | opt_value = self.getOptionValue(opt) 71 | if opt_value: 72 | for i in opt_value.split(','): 73 | if i: 74 | names.append(i) 75 | if not names: 76 | names = None 77 | return names 78 | 79 | def argParse(self, supported_options): 80 | args = self.args.copy() 81 | for i in args: 82 | if i in supported_options: 83 | 84 | if i == '-p': 85 | tmp = self.splitNames(i) 86 | pillars = {} 87 | for i in tmp: 88 | if '=' in i: 89 | p_name = i.split('=')[0] 90 | if not p_name: 91 | self.argParseError = "ERROR: invalid data for option '-p': %s" % p_name 92 | return False 93 | p_value = i[i.index('=') + 1:] ### Means pillar value can be empty string. 94 | pillars[p_name] = p_value 95 | else: 96 | pillars[i] = None 97 | if pillars: 98 | self.pillars = pillars 99 | if i == '-mb': 100 | self.module_belong = self.getOptionValue(i) 101 | if i == '-e': 102 | self.environment = self.getOptionValue(i) 103 | if i == '-n': 104 | self.nodes = self.splitNames(i) 105 | if i == '-m': 106 | self.modules = self.splitNames(i) 107 | if i == '-i': 108 | self.instances = self.splitNames(i) 109 | if i == '-ii': 110 | self.included_instances = self.splitNames(i) 111 | if i == '-pr': 112 | self.pillar_required = self.splitNames(i) 113 | if i == '-po': 114 | self.pillar_optional = self.splitNames(i) 115 | if i == '--short': 116 | self.show_short = True 117 | while '--short' in self.args: 118 | self.args.remove('--short') 119 | if i == '--instance': 120 | self.show_instances = True 121 | while '--instance' in self.args: 122 | self.args.remove('--instance') 123 | if i == '--checkself': 124 | self.push_checkself = True 125 | while '--checkself' in self.args: 126 | self.args.remove('--checkself') 127 | if i == '--only-module': 128 | self.push_only_module = True 129 | while '--only-module' in self.args: 130 | self.args.remove('--only-module') 131 | 132 | if self.args: 133 | self.argParseError = "ERROR: invalid arguments '%s' found for action '%s'" % (' '.join(self.args), self.action) 134 | return False 135 | 136 | self.args = args 137 | return True 138 | 139 | @classmethod 140 | def error(cls, message, status=1, exit=True): 141 | print(message) 142 | if exit: 143 | sys.exit(status) 144 | 145 | def handleAPIReturn(self): 146 | if isinstance(self.api_return, str): 147 | print(self.api_return) 148 | elif isinstance(self.api_return, dict): 149 | if self.api_return.get('data'): 150 | for obj in self.api_return['data']: 151 | if obj.get('__showOrder__'): 152 | print(obj['name'] + ':' +'\n ', end='') 153 | print(yaml.dump([{i:obj[i]} for i in obj['__showOrder__'] if i != '__showOrder__'], default_flow_style=False).replace('\n','\n ')) 154 | else: 155 | print(obj['name']) 156 | if self.api_return.get('message'): 157 | print(self.api_return['message']) 158 | if self.api_return.get('pushlog'): 159 | for node in self.api_return['pushlog']: 160 | print("===> Push log for node: %s" % node) 161 | for line in self.api_return['pushlog'][node]: 162 | print(line) 163 | print() 164 | 165 | def call(self, post_data): 166 | self.post_data.update(post_data) 167 | self.api_return = self.post_caller(self.api, self.post_data) 168 | self.handleAPIReturn() 169 | -------------------------------------------------------------------------------- /geniusalt_cli/sub/config.py: -------------------------------------------------------------------------------- 1 | GENIUSALT_API = 'http://172.16.40.157:10080/geniusalt/api/v1/ingress' 2 | 3 | #AUTH_TOKEN = 'cLqsTdRi.1Mk17F2smGvvWJwHJgmffiLyPw3iruh6Dtt6ROnWoLPVH68mlWEYynnoAae4L18Z' 4 | AUTH_TOKEN = 'cZdQBNPp.Ybuk180mAxaTALPHgOSvZDb4v0DNH2cB7O0PxyhCxbakeoGZVwCKFFpSYZptxjT5' 5 | -------------------------------------------------------------------------------- /geniusalt_cli/sub/delete.py: -------------------------------------------------------------------------------- 1 | from .common import Handler 2 | 3 | class Delete(Handler): 4 | def delete(self): 5 | supported_options = ['-n', '-m', '-i'] 6 | 7 | if not self.argParse(supported_options): 8 | self.error(self.argParseError) 9 | 10 | if self.nodes: 11 | for name in self.nodes: 12 | post_data = {'object':'node', 'name':name} 13 | self.call(post_data) 14 | 15 | if self.modules: 16 | for name in self.modules: 17 | answer = input("Warning: all instances belong to module '%s' will be deleted cascadedly.\nDo you really want to do that? [Y/N]" % name) 18 | if answer == 'Y': 19 | post_data = {'object':'module', 'name':name} 20 | self.call(post_data) 21 | else: 22 | print("OK, To delete module '%s' aborted." % name) 23 | 24 | if self.instances: 25 | for name in self.instances: 26 | post_data = {'object':'instance', 'name':name} 27 | self.call(post_data) 28 | -------------------------------------------------------------------------------- /geniusalt_cli/sub/dispatcher.py: -------------------------------------------------------------------------------- 1 | from .usage import Usage 2 | from .scan import Scan 3 | from .add import Add 4 | from .delete import Delete 5 | from .show import Show 6 | from .pset import Pset 7 | from .pdel import Pdel 8 | from .eset import Eset 9 | from .bind import Bind 10 | from .lock import Lock 11 | from .include import Include 12 | from .push import Push 13 | 14 | class CommandDispatcher(Usage, Scan, Add, Show, Delete, Pset, Eset, Pdel, Lock, Bind, Include, Push): 15 | action_supported = ('scan', 'add', 'del','delete', 'show', 'pset','eset', 'pdel', 'lock', 'unlock','bind', 'unbind', 'include', 'exclude', 'push', 'showb') 16 | 17 | def __init__(self, action, args): 18 | self.action = action 19 | if self.action == 'del': 20 | self.action = 'delete' 21 | self.args = args 22 | self.post_data = {'auth_token':self.auth_token,'action':self.action} 23 | 24 | def run(self): 25 | getattr(self, self.action)() 26 | -------------------------------------------------------------------------------- /geniusalt_cli/sub/eset.py: -------------------------------------------------------------------------------- 1 | from .common import Handler 2 | 3 | class Eset(Handler): 4 | def eset(self): 5 | supported_options = ['-n' , '-e'] 6 | if not self.argParse(supported_options): 7 | self.error(self.argParseError) 8 | if not self.nodes or not self.environment: 9 | self.error("ERROR: action 'eset' can only be used to set environment of node objects. Option '-n' and '-e' must be specified.") 10 | 11 | for name in self.nodes: 12 | post_data = {'object':'node', 'name':name, 'environment':self.environment} 13 | self.call(post_data) 14 | -------------------------------------------------------------------------------- /geniusalt_cli/sub/include.py: -------------------------------------------------------------------------------- 1 | from .common import Handler 2 | 3 | class Include(Handler): 4 | def include_common(self): 5 | supported_options = ['-i' , '-ii'] 6 | if not self.argParse(supported_options): 7 | self.error(self.argParseError) 8 | if not self.instances or not self.included_instances: 9 | self.error("ERROR: action '%s' is used for an instances to %s other instances. Option '-i' and '-ii' must be specified." % (self.action, self.action)) 10 | post_data = {'object':'relation', 'instances': self.instances, 'included_instances':self.included_instances} 11 | self.call(post_data) 12 | def include(self): 13 | self.include_common() 14 | def exclude(self): 15 | self.include_common() 16 | -------------------------------------------------------------------------------- /geniusalt_cli/sub/lock.py: -------------------------------------------------------------------------------- 1 | from .common import Handler 2 | 3 | class Lock(Handler): 4 | def lock_common_method(self): 5 | supported_options = ['-n' , '-m', '-i'] 6 | if not self.argParse(supported_options): 7 | self.error(self.argParseError) 8 | if not (self.instances or self.nodes or self.modules): 9 | self.error("ERROR: action '%s' requires at least one of option '-i', '-m' or '-n'." % self.action) 10 | 11 | if self.nodes: 12 | for name in self.nodes: 13 | post_data = {'object':'node', 'name':name} 14 | self.call(post_data) 15 | if self.instances: 16 | for name in self.instances: 17 | post_data = {'object':'instance', 'name':name} 18 | self.call(post_data) 19 | if self.modules: 20 | for name in self.modules: 21 | post_data = {'object':'module', 'name':name} 22 | self.call(post_data) 23 | 24 | def lock(self): 25 | self.lock_common_method() 26 | 27 | def unlock(self): 28 | self.lock_common_method() 29 | -------------------------------------------------------------------------------- /geniusalt_cli/sub/pdel.py: -------------------------------------------------------------------------------- 1 | from .common import Handler 2 | 3 | class Pdel(Handler): 4 | def pdel(self): 5 | supported_options = ['-i' , '-p', '-e'] 6 | if not self.argParse(supported_options): 7 | self.error(self.argParseError) 8 | if not self.instances or not self.pillars: 9 | self.error("ERROR: action 'pdel' can only be used to delete pillars of instance objects. Option '-i' and '-p' must be specified, option '-e' can be optionally used to delete environment related pillars.") 10 | 11 | for name in self.instances: 12 | for pillar_name in self.pillars: 13 | post_data = {'object':'instance', 'name':name, 'pillar_name':pillar_name} 14 | if self.environment: 15 | post_data['environment'] = self.environment 16 | self.call(post_data) 17 | -------------------------------------------------------------------------------- /geniusalt_cli/sub/pset.py: -------------------------------------------------------------------------------- 1 | from .common import Handler 2 | 3 | class Pset(Handler): 4 | def pset(self): 5 | supported_options = ['-i' , '-p', '-e'] 6 | if not self.argParse(supported_options): 7 | self.error(self.argParseError) 8 | if not self.instances or not self.pillars: 9 | self.error("ERROR: action 'pset' can only be used to set pillars of instance objects. Option '-i' and '-p' must be specified, option '-e' can be optionally used to set environment related pillars.") 10 | 11 | for name in self.instances: 12 | post_data = {'object':'instance', 'name':name, 'pillar':self.pillars} 13 | if self.environment: 14 | post_data['environment'] = self.environment 15 | self.call(post_data) 16 | -------------------------------------------------------------------------------- /geniusalt_cli/sub/push.py: -------------------------------------------------------------------------------- 1 | from .common import Handler 2 | 3 | class Push(Handler): 4 | def push(self): 5 | supported_options = ['-n' , '-m', '-i', '--checkself', '--only-module'] 6 | if not self.argParse(supported_options): 7 | self.error(self.argParseError) 8 | if not self.nodes: 9 | self.error("ERROR: action 'push' requires node objects specified.") 10 | 11 | post_data = {'object':'push', 'nodes':self.nodes} 12 | if self.modules: 13 | post_data['bind_modules'] = self.modules 14 | if self.instances: 15 | post_data['bind_instances'] = self.instances 16 | if self.push_checkself: 17 | post_data['longTerms'] = ['--checkself'] 18 | if self.push_only_module: 19 | if post_data.get('longTerms'): 20 | post_Data['longTerms'].append('--only-module') 21 | else: 22 | post_data['longTerms'] = ['--only-module'] 23 | self.call(post_data) 24 | -------------------------------------------------------------------------------- /geniusalt_cli/sub/scan.py: -------------------------------------------------------------------------------- 1 | from .common import Handler 2 | 3 | class Scan(Handler): 4 | def scan(self): 5 | supported_options = ['-n', '-m'] 6 | 7 | if not self.argParse(supported_options): 8 | self.error(self.argParseError) 9 | if not ('-n' in self.args or '-m' in self.args): 10 | self.error("ERROR: Action 'scan' requires at least one option in '-n' or '-m'.") 11 | 12 | if '-n' in self.args: 13 | post_data = {'object': 'node'} 14 | self.call(post_data) 15 | 16 | if '-m' in self.args: 17 | post_data = {'object': 'module'} 18 | self.call(post_data) 19 | -------------------------------------------------------------------------------- /geniusalt_cli/sub/show.py: -------------------------------------------------------------------------------- 1 | from .common import Handler 2 | 3 | class Show(Handler): 4 | def show(self): 5 | supported_options = ['-n', '-m', '-i', '--short', '--instance'] 6 | if not self.argParse(supported_options): 7 | self.error(self.argParseError) 8 | 9 | post_data = {'longTerms': ['--commandline']} 10 | trigger = 0 11 | if '-n' in self.args: 12 | trigger += 1 13 | post_data['object'] = 'node' 14 | if self.show_short: 15 | post_data['longTerms'].append('--short') 16 | 17 | if self.nodes: 18 | for name in self.nodes: 19 | post_data['name'] = name 20 | self.call(post_data) 21 | else: 22 | self.call(post_data) 23 | 24 | if '-m' in self.args: 25 | trigger += 1 26 | post_data['object'] = 'module' 27 | if self.show_short: 28 | post_data['longTerms'].append('--short') 29 | elif self.show_instances: 30 | post_data['longTerms'].append('--instance') 31 | if self.modules: 32 | for name in self.modules: 33 | post_data['name'] = name 34 | self.call(post_data) 35 | else: 36 | self.call(post_data) 37 | 38 | if '-i' in self.args: 39 | trigger += 1 40 | post_data['object'] = 'instance' 41 | if self.show_short: 42 | post_data['longTerms'].append('--short') 43 | if self.instances: 44 | for name in self.instances: 45 | post_data['name'] = name 46 | self.call(post_data) 47 | else: 48 | self.call(post_data) 49 | 50 | if trigger == 0: 51 | for obj_type in ['node', 'module', 'instance']: 52 | post_data = {'longTerms': ['--commandline']} 53 | print("===> %ss:" % obj_type.replace(obj_type[0], obj_type[0].upper(), 1)) 54 | post_data['object'] = obj_type 55 | if self.show_short: 56 | post_data['longTerms'].append('--short') 57 | elif self.show_instances and obj_type == 'module': 58 | post_data['longTerms'].append('--instance') 59 | self.call(post_data) 60 | def showb(self): 61 | supported_options = ['-m', '-i', '--short'] 62 | if not self.argParse(supported_options): 63 | self.error(self.argParseError) 64 | if not (self.modules or self.instances): 65 | self.error("ERROR: Option '-m' or '-i' must be specified.") 66 | if self.modules and self.instances: 67 | self.error("ERROR: Option '-m' and '-i' cannot be specified at the same time.") 68 | 69 | post_data = {'longTerms': ['--commandline']} 70 | if self.show_short: 71 | post_data['longTerms'].append('--short') 72 | if self.modules: 73 | post_data['object'] = 'module' 74 | if len(self.modules) != 1: 75 | self.error("ERROR: Multi-objects specification is not allow with 'showb' command.") 76 | post_data['name'] = self.modules[0] 77 | self.call(post_data) 78 | if self.instances: 79 | post_data['object'] = 'instance' 80 | if len(self.instances) != 1: 81 | self.error("ERROR: Multi-objects specification is not allow with 'showb' command.") 82 | post_data['name'] = self.instances[0] 83 | self.call(post_data) 84 | -------------------------------------------------------------------------------- /geniusalt_cli/sub/usage.py: -------------------------------------------------------------------------------- 1 | class Usage(object): 2 | @staticmethod 3 | def usage(): 4 | print(""" 5 | =================================== 6 | Usage: 7 | gnsalt command [] [] 8 | 9 | ===> Command list: 10 | scan Auto add Modules or Nodes. This will also update a module if pillar.json has changed. 11 | Supported options: '-m', '-n' 12 | add To add a Module, Instance or Node. 13 | Supported options: '-n','-e','-m','-pr', '-po', '-i','-p','-mb' 14 | del/delete To delete Modules, Instances or Nodes. 15 | Supported options: '-n', '-m', '-i' 16 | show To show data of Modules, Instances or Nodes. 17 | Supported options: '-n', '-m', '-i', '--short', '--instance' 18 | pset To set pillar of instance. 19 | Supported options: '-i' , '-p', '-e' 20 | pdel To remove pillar from a instance. 21 | Supported options: '-i' , '-p', '-e' 22 | eset To set environment of a node. 23 | Supported options: '-n' , '-e' 24 | include To set instances to include other instances. 25 | Supported options: '-i' , '-ii' 26 | exclude To set instances to exclude included instances. 27 | Supported options: '-i' , '-ii' 28 | bind To set nodes to bind instances or modules. This is only set on DB level, not apply to the real hosts. 29 | Supported options: '-n', '-i', '-m' 30 | unbind To set nodes to unbind instances or modules. This is only set on DB level, not apply to the real hosts. 31 | Supported options: '-n', '-i', '-m' 32 | push To apply instances or modules to the real hosts(nodes). This will make real change on hosts. 33 | Supported options: '-n', '-m', '-i', '--checkself', '--only-module' 34 | If '-i' or '-m' objects specified, only the specified instances or modules will be applied, not the whole pillar bound to this node. 35 | lock To lock objects to prevent it to be pushed to a real host. 36 | If to push an locked object, An error will be given. 37 | Supported options: '-n', '-m', '-i' 38 | unlock To unlock locked objects. 39 | Supported options: '-n', '-m', '-i' 40 | showb To show nodes which has bound the specified instance or module. 41 | Supported options: '-i', '-m' 42 | 43 | ===> object options: 44 | -n node1,node2,... 45 | To specify 'Node' basic objects. 46 | 47 | -m mod1,mod2,... 48 | To specify 'Module' basic objects. 49 | 50 | -pr var1,var2,var3... 51 | To specify pillar variables required for a module. Only available when to 'add' a module manually. 52 | 53 | -po var1,var2,var3... 54 | To specify optional pillar variables for a module. Only available when to 'add' a module manually. 55 | 56 | -i instance1,instance2,... 57 | To specify 'Instance' basic objects. 58 | 59 | -mb mod 60 | To specify module_belong when to add a instance. Only available when to 'add' an instance. 61 | 62 | -ii instance1,instance2,... 63 | To specify instances to be included. Only available for 'include' command. 64 | If an instance which included others instances bound to a node, key '__include__' will be auto added as a pillar variable of this instance, which value is a dict included pillars of other instances. 65 | 66 | -p var1=value1,var2=value2,... 67 | To specify 'pillar' data. this requires that value of pillars could not contain characters like '=' and ','. 68 | When comes with 'pdel' command, value can be ignored in your command-line just like: 'pdel -p var1,var2,var3'. 69 | 70 | -e envronment 71 | To specify envronment for nodes or pillar. Do not support multi-values seporated by ','. 72 | 73 | ===> LongTerms: 74 | --short 75 | Available for 'show' and 'showb' command. 76 | To show only object names, not all attributes of object. 77 | 78 | --instance 79 | Only available when to 'show' modules. 80 | To show instances of a module. 81 | 82 | --only-module 83 | Only available for 'push' command. 84 | To push nodes with only module level. This means no pillar of any instance passed to saltstack. 85 | This LongTerm cannot used with '-i' option at the same time. 86 | 87 | --checkself 88 | Only available for 'push' command. 89 | If this LongTerm specified, pillars passed to saltstack will auto add with a module level key '__checkself__', which value is equal the whole pillar of the corresponding node. 90 | This is sometimes useful when you want to know the whole pillar of this node, but you also want to push only specified instances or modules to the real host, which will not give the whole pillar of this node to saltstack. 91 | 92 | ===================================""") 93 | -------------------------------------------------------------------------------- /gnsalt_modules/blacklist/init.sls: -------------------------------------------------------------------------------- 1 | include: 2 | - proxy_base.installer 3 | - proxy_base.mkdirs 4 | - proxy_base.dyups_api 5 | - whitelist.mkdirs 6 | -------------------------------------------------------------------------------- /gnsalt_modules/blacklist/instance/init.sls: -------------------------------------------------------------------------------- 1 | include: 2 | - whitelist.instance.set_wlist 3 | -------------------------------------------------------------------------------- /gnsalt_modules/blacklist/instance/set_wlist.sls: -------------------------------------------------------------------------------- 1 | {%- for instance in pillar["whitelist"].itervalues() %} 2 | {%- set white_list=instance["white_list"] %} 3 | {%- set domain_name=instance["proxy_domain_name"] %} 4 | 5 | {%- if instance.get('is_disabled') == '1' %} 6 | /etc/nginx/luafiles/{{ domain_name }}.lua: 7 | file.absent 8 | #- watch_in: 9 | # - service: nginx 10 | {%- else %} 11 | /etc/nginx/luafiles/{{ domain_name }}.lua: 12 | file.managed: 13 | - source: salt://whitelist/instance/templates/temp.example.com.lua 14 | - user: root 15 | - group: root 16 | - mode: 644 17 | - template: jinja 18 | - defaults: 19 | white_list: {{ white_list }} 20 | #- watch_in: 21 | # - service: nginx 22 | {%- endif %} 23 | 24 | {%- endfor %} 25 | -------------------------------------------------------------------------------- /gnsalt_modules/blacklist/instance/templates/temp.example.com.lua: -------------------------------------------------------------------------------- 1 | {%- set white_list_ls = white_list.split('/') -%} 2 | list={ 3 | {%- for ip in white_list_ls %} 4 | "{{ ip }}", 5 | {%- endfor %} 6 | } 7 | if ngx.var.http_x_forwarded_for == nil then 8 | if string.find(ngx.var.remote_addr , "^10.232") then 9 | return 10 | end 11 | else 12 | if string.find(ngx.var.http_x_forwarded_for , "^10.232") then 13 | return 14 | end 15 | sn=string.find(ngx.var.http_x_forwarded_for,",") 16 | if sn == nil then 17 | xip=ngx.var.http_x_forwarded_for 18 | else 19 | xip=string.sub(ngx.var.http_x_forwarded_for,1,sn-1) 20 | end 21 | for i = 1,#list do 22 | if xip == list[i] then 23 | return 24 | end 25 | end 26 | end 27 | ngx.exit(403) 28 | -------------------------------------------------------------------------------- /gnsalt_modules/blacklist/mkdirs.sls: -------------------------------------------------------------------------------- 1 | whitelist_config_dir: 2 | file.directory: 3 | - name: /etc/nginx/luafiles 4 | - user: root 5 | - group: root 6 | - mode: 755 7 | - makedirs: True 8 | -------------------------------------------------------------------------------- /gnsalt_modules/blacklist/pillar.json: -------------------------------------------------------------------------------- 1 | { 2 | "pillar_required":["proxy_domain_name", "iplist"], 3 | "pillar_optional":["is_disabled"] 4 | } 5 | -------------------------------------------------------------------------------- /gnsalt_modules/http_proxy/init.sls: -------------------------------------------------------------------------------- 1 | include: 2 | - proxy_base.installer 3 | - proxy_base.mkdirs 4 | - proxy_base.dyups_api 5 | - http_proxy.mkdirs 6 | - http_proxy.instance 7 | -------------------------------------------------------------------------------- /gnsalt_modules/http_proxy/instance/app_proxy.sls: -------------------------------------------------------------------------------- 1 | {%- for instance in pillar["http_proxy"].itervalues() %} 2 | {%- set proxy_domain_name=instance["proxy_domain_name"] %} 3 | {%- set app_name=instance["app_name"] %} 4 | {%- set app_servers=instance["app_servers"].split('/') %} 5 | {%- set app_port=instance["app_port"] %} 6 | 7 | {%- if instance.get("other_domain_names") %} 8 | {%- set other_domain_names=instance["other_domain_names"].split('/') %} 9 | {%- else %} 10 | {%- set other_domain_names=[] %} 11 | {%- endif %} 12 | 13 | {%- if instance.get("proxy_listen_ports") %} 14 | {%- set proxy_listen_ports=instance["proxy_listen_ports"].split('/') %} 15 | {%- else %} 16 | {%- set proxy_listen_ports=[] %} 17 | {%- endif %} 18 | 19 | {%- if instance.get("app_servers_backup") %} 20 | {%- set app_servers_backup=instance["app_servers_backup"].split('/') %} 21 | {%- else %} 22 | {%- set app_servers_backup=[] %} 23 | {%- endif %} 24 | 25 | {%- set tmp={'has_white_list':'N'} %} 26 | {%- if instance.get("__include__") %} 27 | {%- for ii in instance["__include__"].keys() %} 28 | {%- if ii.split('.')[0] == 'whitelist' %} 29 | {%- if tmp.update({'has_white_list':'Y'}) %}{%- endif%} 30 | {%- endif %} 31 | {%- endfor %} 32 | {%- endif %} 33 | 34 | {%- if instance.get('is_disabled') == '1' %} 35 | /etc/nginx/conf.d/{{ proxy_domain_name }}.conf: 36 | file.absent 37 | #- watch_in: 38 | # - service: nginx 39 | {%- else %} 40 | /etc/nginx/conf.d/{{ proxy_domain_name }}.conf: 41 | file.managed: 42 | - source: salt://http_proxy/instance/templates/temp.http_proxy.conf 43 | - user: root 44 | - group: root 45 | - mode: 644 46 | - template: jinja 47 | - defaults: 48 | instance_name: http_proxy_for_{{ app_name }} 49 | domain_name: {{ proxy_domain_name }} 50 | app_port: {{ app_port }} 51 | app_servers: {{ app_servers }} 52 | proxy_listen_ports: {{ proxy_listen_ports }} 53 | other_domain_names: {{ other_domain_names }} 54 | backup_servers: {{ app_servers_backup }} 55 | has_white_list: {{ tmp.has_white_list }} 56 | 57 | #- watch_in: 58 | # - service: nginx 59 | {%- endif %} 60 | {% endfor %} 61 | -------------------------------------------------------------------------------- /gnsalt_modules/http_proxy/instance/init.sls: -------------------------------------------------------------------------------- 1 | include: 2 | - http_proxy.instance.app_proxy 3 | -------------------------------------------------------------------------------- /gnsalt_modules/http_proxy/instance/templates/temp.http_proxy.conf: -------------------------------------------------------------------------------- 1 | # Warning: This file is managed by geniusalt, please do not to edit it manually. 2 | 3 | upstream {{ instance_name }} { 4 | {%- for target_server in app_servers %} 5 | server {{ target_server }}:{{ app_port }} max_fails=3 fail_timeout=30s {% if backup_servers and target_server in backup_servers %}backup{% endif %}; 6 | {%- endfor %} 7 | sticky; 8 | } 9 | 10 | server { 11 | listen 80; 12 | {%- for other_port in proxy_listen_ports %} 13 | listen {{ other_port }}; 14 | {%- endfor %} 15 | server_name {{ domain_name }}; 16 | {%- for dname in other_domain_names %} 17 | server_name {{ dname }}; 18 | {%- endfor %} 19 | 20 | #error_log /data1/logs/{{ domain_name }}.error.log debug_http; 21 | access_log /data1/logs/{{ domain_name }}.access.log main; 22 | 23 | location ~* /ygdconsole { 24 | return 403; 25 | } 26 | 27 | location / { 28 | {%- if has_white_list == 'Y' %} 29 | access_by_lua_file /etc/nginx/luafiles/{{ domain_name }}.lua; 30 | {%- endif %} 31 | proxy_set_header Host $host; 32 | set ${{ instance_name }} {{ instance_name }}; 33 | proxy_pass http://${{ instance_name }}; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /gnsalt_modules/http_proxy/mkdirs.sls: -------------------------------------------------------------------------------- 1 | httpproxy_config_dir: 2 | file.directory: 3 | - name: /etc/nginx/conf.d 4 | - user: root 5 | - group: root 6 | - mode: 755 7 | - makedirs: True 8 | -------------------------------------------------------------------------------- /gnsalt_modules/http_proxy/pillar.json: -------------------------------------------------------------------------------- 1 | { 2 | "pillar_required":["proxy_domain_name", "app_name", "app_servers", "app_port"], 3 | "pillar_optional":["other_domain_names", "proxy_listen_ports", "app_servers_backup", "is_disabled"] 4 | } 5 | -------------------------------------------------------------------------------- /gnsalt_modules/proxy_base/dyups_api.sls: -------------------------------------------------------------------------------- 1 | dyups_api_conf: 2 | file.managed: 3 | - name: /etc/nginx/conf.d/dyups.conf 4 | - source: salt://proxy_base/files/dyups.conf 5 | - user: root 6 | - group: root 7 | - mode: 644 8 | #- watch_in: 9 | # - service: nginx 10 | -------------------------------------------------------------------------------- /gnsalt_modules/proxy_base/files/dyups.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 50119; 3 | location / { 4 | allow 10.232.0.72; 5 | deny all; 6 | dyups_interface; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /gnsalt_modules/proxy_base/files/nginx.conf: -------------------------------------------------------------------------------- 1 | user nginx; 2 | worker_processes 2; 3 | 4 | pid /var/run/nginx.pid; 5 | worker_rlimit_nofile 204800; 6 | 7 | events { 8 | use epoll; 9 | worker_connections 204800; 10 | } 11 | 12 | 13 | 14 | stream { 15 | include /etc/nginx/stream.d/*.conf; 16 | } 17 | 18 | 19 | http { 20 | access_log off; 21 | include mime.types; 22 | default_type application/octet-stream; 23 | 24 | log_format main '$remote_addr - $remote_user [$time_local] $host $upstream_addr ' 25 | '"$request" $status $body_bytes_sent $request_time $upstream_response_time ' 26 | '"$http_referer" "$upstream_cache_status" "$http_user_agent" "$http_x_forwarded_for"'; 27 | 28 | #charset utf-8; 29 | sendfile on; 30 | tcp_nopush on; 31 | keepalive_timeout 60; 32 | keepalive_requests 100; 33 | send_timeout 3m; 34 | tcp_nodelay on; 35 | log_not_found off; 36 | 37 | server_tokens off; 38 | server_name_in_redirect on; 39 | server_names_hash_bucket_size 256; 40 | variables_hash_bucket_size 256; 41 | 42 | large_client_header_buffers 4 256k; 43 | client_max_body_size 50m; 44 | client_body_buffer_size 256k; 45 | client_header_timeout 3m; 46 | client_body_timeout 3m; 47 | 48 | gzip on; 49 | gzip_disable "MSIE [1-6]\."; 50 | gzip_min_length 0; 51 | gzip_types text/plain text/css application/javascript application/x-javascript text/xml application/xml application/xml+rss text/javascript application/json; 52 | ssl_session_cache shared:SSL:10m; 53 | ssl_session_timeout 10m; 54 | 55 | 56 | server { 57 | listen 80; 58 | server_name localhost; 59 | access_log off; 60 | location / { 61 | root html; 62 | index index.html index.htm; 63 | } 64 | } 65 | 66 | 67 | 68 | include /etc/nginx/upstream.conf; 69 | include /etc/nginx/conf.d/*.conf; 70 | 71 | 72 | } 73 | -------------------------------------------------------------------------------- /gnsalt_modules/proxy_base/files/nginx_for_logrotate: -------------------------------------------------------------------------------- 1 | /data1/logs/*.log { 2 | daily 3 | rotate 15 4 | missingok 5 | notifempty 6 | compress 7 | sharedscripts 8 | postrotate 9 | /bin/kill -USR1 $(cat /var/run/nginx.pid 2>/dev/null) 2>/dev/null || : 10 | endscript 11 | } 12 | -------------------------------------------------------------------------------- /gnsalt_modules/proxy_base/init.sls: -------------------------------------------------------------------------------- 1 | include: 2 | - proxy_base.installer 3 | - proxy_base.mkdirs 4 | - proxy_base.dyups_api 5 | -------------------------------------------------------------------------------- /gnsalt_modules/proxy_base/installer.sls: -------------------------------------------------------------------------------- 1 | ### install openresty package ### 2 | openresty_for_proxy: 3 | pkg.installed: 4 | - name: openresty 5 | service.enabled: 6 | - name: nginx 7 | #service.running: 8 | # - name: nginx 9 | # - enable: True #Start this service at system boot time. 10 | # - reload: True 11 | # - watch: 12 | # - pkg: openresty 13 | 14 | logrotate_for_proxy: 15 | file.managed: 16 | - name: /etc/logrotate.d/nginx 17 | - source: salt://proxy_base/files/nginx_for_logrotate 18 | - user: root 19 | - group: root 20 | - mode: 644 21 | 22 | /etc/nginx/nginx.conf: 23 | file.managed: 24 | - source: salt://proxy_base/files/nginx.conf 25 | - user: root 26 | - group: root 27 | - mode: 644 28 | #- watch_in: 29 | # - service: nginx 30 | -------------------------------------------------------------------------------- /gnsalt_modules/proxy_base/mkdirs.sls: -------------------------------------------------------------------------------- 1 | proxy_log_dir: 2 | file.directory: 3 | - name: /data1/logs/ 4 | - user: root 5 | - group: root 6 | - mode: 755 7 | - makedirs: True 8 | - require: 9 | - pkg: openresty 10 | -------------------------------------------------------------------------------- /gnsalt_modules/tcp_proxy/init.sls: -------------------------------------------------------------------------------- 1 | include: 2 | - proxy_base.installer 3 | - proxy_base.mkdirs 4 | - proxy_base.dyups_api 5 | - tcp_proxy.mkdirs 6 | - tcp_proxy.instance 7 | -------------------------------------------------------------------------------- /gnsalt_modules/tcp_proxy/instance/app_proxy.sls: -------------------------------------------------------------------------------- 1 | {%- for instance in pillar["tcp_proxy"].itervalues() %} 2 | {%- set app_name=instance["app_name"] %} 3 | {%- set app_servers=instance["app_servers"].split('/') %} 4 | {%- set app_port=instance["app_port"] %} 5 | 6 | {%- if instance.get("other_domain_names") %} 7 | {%- set other_domain_names=instance["other_domain_names"].split('/') %} 8 | {%- else %} 9 | {%- set other_domain_names=[] %} 10 | {%- endif %} 11 | 12 | {%- if instance.get("proxy_port") %} 13 | {%- set proxy_port=instance["proxy_port"] %} 14 | {%- else %} 15 | {%- set proxy_port=app_port %} 16 | {%- endif %} 17 | 18 | {%- if instance.get("app_servers_backup") %} 19 | {%- set app_servers_backup=instance["app_servers_backup"].split('/') %} 20 | {%- else %} 21 | {%- set app_servers_backup=[] %} 22 | {%- endif %} 23 | 24 | {%- if instance.get("is_disabled") %} 25 | {%- set is_disabled=instance["is_disabled"] %} 26 | {%- else %} 27 | {%- set is_disabled='0' %} 28 | {%- endif %} 29 | 30 | {%- if is_disabled == '1' %} 31 | /etc/nginx/stream.d/{{ app_name }}.tcp_proxy.conf: 32 | file.absent 33 | #- watch_in: 34 | # - service: nginx 35 | {%- else %} 36 | /etc/nginx/stream.d/{{ app_name }}.tcp_proxy.conf: 37 | file.managed: 38 | - source: salt://tcp_proxy/instance/templates/temp.tcp_proxy.conf 39 | - user: root 40 | - group: root 41 | - mode: 644 42 | - template: jinja 43 | - defaults: 44 | instance_name: tcp_proxy_for_{{ app_name }} 45 | app_port: {{ app_port }} 46 | app_servers: {{ app_servers }} 47 | backup_servers: {{ app_servers_backup }} 48 | proxy_port: {{ proxy_port }} 49 | #- watch_in: 50 | # - service: nginx 51 | {%- endif %} 52 | {% endfor %} 53 | -------------------------------------------------------------------------------- /gnsalt_modules/tcp_proxy/instance/init.sls: -------------------------------------------------------------------------------- 1 | include: 2 | - tcp_proxy.instance.app_proxy 3 | -------------------------------------------------------------------------------- /gnsalt_modules/tcp_proxy/instance/templates/temp.tcp_proxy.conf: -------------------------------------------------------------------------------- 1 | # Warning: This file is managed by geniusalt, please do not to edit it manually. 2 | 3 | upstream {{ instance_name }} { 4 | {%- for target_server in app_servers %} 5 | server {{ target_server }}:{{ app_port }} max_fails=3 fail_timeout=30s {% if backup_servers and target_server in backup_servers %}backup{% endif %}; 6 | {%- endfor %} 7 | } 8 | 9 | server { 10 | listen {{ proxy_port }}; 11 | proxy_connect_timeout 30s; 12 | proxy_timeout 1m; 13 | proxy_pass {{ instance_name }}; 14 | } 15 | -------------------------------------------------------------------------------- /gnsalt_modules/tcp_proxy/mkdirs.sls: -------------------------------------------------------------------------------- 1 | tcpproxy_config_dir: 2 | file.directory: 3 | - name: /etc/nginx/stream.d 4 | - user: root 5 | - group: root 6 | - mode: 755 7 | - makedirs: True 8 | -------------------------------------------------------------------------------- /gnsalt_modules/tcp_proxy/pillar.json: -------------------------------------------------------------------------------- 1 | { 2 | "pillar_required":["app_name", "app_servers", "app_port"], 3 | "pillar_optional":["proxy_port", "app_servers_backup","is_disabled"] 4 | } 5 | -------------------------------------------------------------------------------- /gnsalt_modules/whitelist/init.sls: -------------------------------------------------------------------------------- 1 | include: 2 | - proxy_base.installer 3 | - proxy_base.mkdirs 4 | - proxy_base.dyups_api 5 | - whitelist.mkdirs 6 | - whitelist.instance 7 | -------------------------------------------------------------------------------- /gnsalt_modules/whitelist/instance/init.sls: -------------------------------------------------------------------------------- 1 | include: 2 | - whitelist.instance.set_wlist 3 | -------------------------------------------------------------------------------- /gnsalt_modules/whitelist/instance/set_wlist.sls: -------------------------------------------------------------------------------- 1 | {%- for instance in pillar["whitelist"].itervalues() %} 2 | {%- set white_list=instance["iplist"] %} 3 | {%- set domain_name=instance["proxy_domain_name"] %} 4 | 5 | {%- if instance.get('is_disabled') == '1' %} 6 | /etc/nginx/luafiles/{{ domain_name }}.lua: 7 | file.absent 8 | #- watch_in: 9 | # - service: nginx 10 | {%- else %} 11 | /etc/nginx/luafiles/{{ domain_name }}.lua: 12 | file.managed: 13 | - source: salt://whitelist/instance/templates/temp.example.com.lua 14 | - user: root 15 | - group: root 16 | - mode: 644 17 | - template: jinja 18 | - defaults: 19 | white_list: {{ white_list }} 20 | #- watch_in: 21 | # - service: nginx 22 | {%- endif %} 23 | 24 | {%- endfor %} 25 | -------------------------------------------------------------------------------- /gnsalt_modules/whitelist/instance/templates/temp.example.com.lua: -------------------------------------------------------------------------------- 1 | {%- set white_list_ls = white_list.split('/') -%} 2 | list={ 3 | {%- for ip in white_list_ls %} 4 | "{{ ip }}", 5 | {%- endfor %} 6 | } 7 | if ngx.var.http_x_forwarded_for == nil then 8 | if string.find(ngx.var.remote_addr , "^10.232") then 9 | return 10 | end 11 | else 12 | if string.find(ngx.var.http_x_forwarded_for , "^10.232") then 13 | return 14 | end 15 | sn=string.find(ngx.var.http_x_forwarded_for,",") 16 | if sn == nil then 17 | xip=ngx.var.http_x_forwarded_for 18 | else 19 | xip=string.sub(ngx.var.http_x_forwarded_for,1,sn-1) 20 | end 21 | for i = 1,#list do 22 | if xip == list[i] then 23 | return 24 | end 25 | end 26 | end 27 | ngx.exit(403) 28 | -------------------------------------------------------------------------------- /gnsalt_modules/whitelist/mkdirs.sls: -------------------------------------------------------------------------------- 1 | whitelist_config_dir: 2 | file.directory: 3 | - name: /etc/nginx/luafiles 4 | - user: root 5 | - group: root 6 | - mode: 755 7 | - makedirs: True 8 | -------------------------------------------------------------------------------- /gnsalt_modules/whitelist/pillar.json: -------------------------------------------------------------------------------- 1 | { 2 | "pillar_required":["proxy_domain_name", "iplist"], 3 | "pillar_optional":["is_disabled"] 4 | } 5 | -------------------------------------------------------------------------------- /hfnlb_project/geniusalt/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alan011/NginxLB-cluster/74b2149b0980534e97145ce38023c7c5719a3d34/hfnlb_project/geniusalt/__init__.py -------------------------------------------------------------------------------- /hfnlb_project/geniusalt/api/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alan011/NginxLB-cluster/74b2149b0980534e97145ce38023c7c5719a3d34/hfnlb_project/geniusalt/api/__init__.py -------------------------------------------------------------------------------- /hfnlb_project/geniusalt/api/api_ingress.py: -------------------------------------------------------------------------------- 1 | from django.views.generic import View 2 | from django.http import HttpResponse 3 | from django.views.decorators.csrf import csrf_exempt 4 | from django.utils.decorators import method_decorator 5 | 6 | 7 | import json 8 | 9 | from geniusalt.operators import ModuleOperator, InstanceOperator, NodeOperator, RelationOperator, PushOperator 10 | from .auth import authenticate 11 | 12 | @method_decorator(csrf_exempt, name='dispatch') 13 | class GeniusaltIngress(View): 14 | type_words = { 15 | ModuleOperator: ['module', '-m', 'mod'], 16 | InstanceOperator: ['instance', '-i', 'inst'], 17 | NodeOperator: ['node', '-n'], 18 | RelationOperator: ['relation'], 19 | PushOperator: ['push'], 20 | } 21 | actions = { 22 | 'scan': {'operators':[ModuleOperator, NodeOperator,],'alias':[]}, 23 | 'add': {'operators':[ModuleOperator, InstanceOperator, NodeOperator,],'alias':[]}, 24 | 'delete': {'operators':[ModuleOperator, InstanceOperator, NodeOperator,],'alias':['del']}, 25 | 'show': {'operators':[ModuleOperator, InstanceOperator, NodeOperator,],'alias':[]}, 26 | 'pillarSet': {'operators':[InstanceOperator,],'alias':['pset']}, 27 | 'pillarDel': {'operators':[InstanceOperator,],'alias':['pdel']}, 28 | 'environmentSet':{'operators':[NodeOperator,],'alias':['eset', 'envSet']}, 29 | 'lock': {'operators':[ModuleOperator, InstanceOperator, NodeOperator,],'alias':[]}, 30 | 'unlock': {'operators':[ModuleOperator, InstanceOperator, NodeOperator,],'alias':[]}, 31 | 'showBind': {'operators':[ModuleOperator,InstanceOperator],'alias':['showb']}, 32 | 'include': {'operators':[RelationOperator,],'alias':[]}, 33 | 'exclude': {'operators':[RelationOperator,],'alias':[]}, 34 | 'hasInclude': {'operators':[RelationOperator,],'alias':[]}, 35 | 'bind': {'operators':[RelationOperator,],'alias':[]}, 36 | 'unbind': {'operators':[RelationOperator,],'alias':[]}, 37 | 'clone': {'operators':[RelationOperator,],'alias':[]}, 38 | 'push': {'operators':[PushOperator,],'alias':[]}, 39 | } 40 | 41 | 42 | def json_load(self, decode_type='utf-8'): 43 | try: 44 | post_data = json.loads(self.request.body.decode(decode_type)) 45 | except: 46 | return HttpResponse("ERROR: To load json data failed.", status=400) 47 | # print('POST DATA: %s' % str(post_data)) 48 | if isinstance(post_data, dict): 49 | return post_data 50 | else: 51 | return HttpResponse("ERROR: Post data illegal.", status=400) 52 | 53 | def get(self, request, *args, **kwargs): 54 | return HttpResponse('ERROR: Get method is not allowed.', status=400) 55 | 56 | def post(self, request, *args, **kwargs): 57 | ### To check post data in JSON. 58 | post_data = self.json_load() 59 | if isinstance(post_data, HttpResponse): 60 | return post_data 61 | 62 | ### To authenticate request. 63 | if not authenticate(post_data): 64 | return HttpResponse("ERROR: Authentication failed.", status = 403) 65 | 66 | ### To dispatch operating methods by 'action' and 'object'. 67 | action = post_data.get('action') 68 | if not action: 69 | return HttpResponse("ERROR: 'action' field is required.", status=400) 70 | object_type = post_data.get('object') 71 | if not object_type: 72 | return HttpResponse("ERROR: 'object' field is required.", status=400) 73 | 74 | operator = None 75 | for method in self.actions: 76 | if action == method or action in self.actions[method]['alias']: ### To filter out invalid actions 77 | operator_method = method 78 | for operator_class in self.type_words: 79 | if object_type in self.type_words[operator_class]: ### To filter out invalid object_types 80 | operator = operator_class(parameters = post_data) 81 | if operator is None: 82 | return HttpResponse("ERROR: 'action: %s' or 'object: %s' your provided is not supported." % (action, object_type), status=400) 83 | 84 | ### To do the real work. 85 | operator_return = getattr(operator, operator_method)() 86 | if isinstance(operator_return, dict): 87 | return HttpResponse(json.dumps(operator_return), content_type='application/json') 88 | else: 89 | return HttpResponse(str(operator_return), status=400) 90 | -------------------------------------------------------------------------------- /hfnlb_project/geniusalt/api/auth.py: -------------------------------------------------------------------------------- 1 | from geniusalt.models import AuthToken 2 | from datetime import datetime, timedelta 3 | import re 4 | 5 | def authenticate(post_data): 6 | if post_data.get('auth_token'): 7 | token_str = post_data['auth_token'] 8 | if re.search('^\w+\.\w+$', token_str): 9 | username = token_str.split('.')[0] 10 | token = token_str.split('.')[1] 11 | queryset = AuthToken.objects.filter(username=username, token=token) 12 | if queryset.exists(): 13 | obj = queryset.get(username=username, token=token) 14 | if obj.expired_time == 0: 15 | return True 16 | if obj.sign_date + timedelta(seconds=obj.expired_time) > datetime.now(): 17 | return True 18 | return False 19 | -------------------------------------------------------------------------------- /hfnlb_project/geniusalt/api/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import url 2 | from geniusalt.api.api_ingress import GeniusaltIngress 3 | from geniusalt.views import checkView 4 | 5 | app_name = 'geniusalt' 6 | urlpatterns = [ url(r'^api/v1/ingress',GeniusaltIngress.as_view(), name='GeniusaltIngress'), 7 | url(r'^check', checkView), 8 | ] 9 | -------------------------------------------------------------------------------- /hfnlb_project/geniusalt/bin/token_manager: -------------------------------------------------------------------------------- 1 | #!/usr/local/bin/python3 2 | import os, sys, django, random, re 3 | 4 | BASE_DIR = os.path.abspath(os.path.join(os.path.dirname(os.path.abspath(__file__)),'../../')) 5 | sys.path.append(BASE_DIR) 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "%s.settings" % os.path.basename(BASE_DIR)) 7 | django.setup() 8 | 9 | from geniusalt.models import AuthToken 10 | from datetime import datetime, timedelta 11 | 12 | def generateToken(length=64): 13 | seeds = 'abcdefghijklmnopqrstuvwxyZABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789' 14 | return ''.join([ seeds[random.randint(0, len(seeds) - 1)] for i in range(length)]) 15 | 16 | def generateRandomUser(length=8): 17 | seeds = 'abcdefghijklmnopqrstuvwxyZABCDEFGHIJKLMNOPQRSTUVWXYZ' 18 | return ''.join([ seeds[random.randint(0, len(seeds) - 1)] for i in range(length)]) 19 | 20 | def make_token(username=None, expired_time=None): 21 | if not username: 22 | username = generateRandomUser() 23 | while AuthToken.objects.filter(username=username).exists(): 24 | username = generateRandomUser() 25 | 26 | token = generateToken() 27 | while AuthToken.objects.filter(token=token).exists(): 28 | token = generateToken() 29 | 30 | if expired_time is None: 31 | expired_time = 86400 32 | obj = AuthToken(username=username, token=token, expired_time=expired_time) 33 | obj.save() 34 | 35 | expired_display = 'Never expired.' 36 | if obj.expired_time != 0: 37 | expired_display = '%d seconds later.' % obj.expired_time 38 | return ('%s.%s' % (username, token), expired_display) 39 | 40 | 41 | def list_token(): 42 | data = [] 43 | for obj in AuthToken.objects.all(): 44 | if obj.expired_time == 0: 45 | data.append('%s.%s\tNever expired.' % (obj.username, obj.token)) 46 | elif(datetime.now() - obj.sign_date < timedelta(seconds=obj.expired_time)): 47 | expired_date = obj.sign_date + timedelta(seconds=obj.expired_time) 48 | data.append('%s.%s\twill expired at %s.' % (obj.username, obj.token, expired_date.strftime('%F %X'))) 49 | else: 50 | data.append('%s.%s\texpired!' % (obj.username, obj.token)) 51 | return data 52 | 53 | def clean_token(): 54 | data= [] 55 | for obj in AuthToken.objects.all(): 56 | if obj.expired_time == 0 or (datetime.now() - obj.sign_date < timedelta(seconds=obj.expired_time)): 57 | pass 58 | else: 59 | data.append('%s.%s\tdeleted!' % (obj.username, obj.token)) 60 | obj.delete() 61 | return data 62 | 63 | def delete_token(username, token): 64 | data = [] 65 | for obj in AuthToken.objects.filter(username=username, token=token): 66 | obj.delete() 67 | data.append('%s.%s\tdeleted!' % (username, token)) 68 | 69 | return data 70 | 71 | def usage(): 72 | print("""========================== 73 | Usage: 74 | python3 token_manager.py add [-u ] [-e ] ### To add a token 75 | python3 token_manager.py list ### To list tokens. 76 | python3 token_manager.py delete . ### To delete a specified token. 77 | python3 token_manager.py clean ### To clean expired tokens. 78 | 79 | -u username: 80 | You can provide a specified username in length 8. If not provided, A random username will be auto-generated. 81 | -e num: 82 | You can provide a number to specified the expired_time in seconds of this token. Default is '86400', means one day. 83 | If '0' is given, the token will never expired. 84 | 85 | ==========================""") 86 | 87 | 88 | if __name__ == '__main__': 89 | if len(sys.argv) < 2 or '--help' in sys.argv or '-h' in sys.argv: 90 | usage() 91 | sys.exit(0) 92 | 93 | if sys.argv[1] == 'add': 94 | username = None 95 | expired_time = None 96 | if '-u' in sys.argv: 97 | i = sys.argv.index('-u') 98 | if len(sys.argv) == i + 1: 99 | print("ERROR: username must be given with '-u' option.\n") 100 | sys.exit(1) 101 | username = sys.argv[i + 1] 102 | if len(username) != 8: 103 | print("ERROR: length of a username must be 8.\n") 104 | sys.exit(1) 105 | if not re.search('^\w+$', username): 106 | print("ERROR: username must be consist of alphabet and numbers.\n") 107 | sys.exit(1) 108 | 109 | if '-e' in sys.argv: 110 | i = sys.argv.index('-e') 111 | if len(sys.argv) == i + 1: 112 | print("ERROR: expired_time must be given with '-e' option.\n") 113 | sys.exit(1) 114 | expired_time = sys.argv[i + 1] 115 | if not re.search('^[0-9]+$', expired_time): 116 | print("ERROR: expired_time must be a number.\n") 117 | sys.exit(1) 118 | expired_time = int(expired_time) 119 | 120 | print("Token generated successfully: %s\nToken expired time:%s" % make_token(username, expired_time)) 121 | elif sys.argv[1] == 'list': 122 | for line in list_token(): 123 | print(line) 124 | 125 | elif sys.argv[1] == 'clean': 126 | for line in clean_token(): 127 | print(line) 128 | 129 | elif sys.argv[1] == 'delete': 130 | if len(sys.argv) != 3 or not re.search('^\w+\.\w+$', sys.argv[2]): 131 | usage() 132 | sys.exit(1) 133 | 134 | result = delete_token(sys.argv[2].split('.')[0],sys.argv[2].split('.')[1]) 135 | if not result: 136 | print("ERROR: Token '%s' not found" % sys.argv[2]) 137 | else: 138 | for line in result: 139 | print(line) 140 | else: 141 | usage() 142 | sys.exit(1) 143 | -------------------------------------------------------------------------------- /hfnlb_project/geniusalt/check.py: -------------------------------------------------------------------------------- 1 | supported_configs = ['ENVIRONMENTS', 2 | 'ENVIRONMENTS_ALIAS', 3 | ] 4 | 5 | def configChecking(): 6 | pass 7 | 8 | def requirementsChecking(): 9 | pass 10 | -------------------------------------------------------------------------------- /hfnlb_project/geniusalt/config.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | import os 3 | 4 | ENVIRONMENTS = [ 5 | 'dev', 6 | 'sit', 7 | 'uat', 8 | 'pt', 9 | 'product', 10 | ] 11 | ENVIRONMENTS_ALIAS = { 12 | 'dev':'研发测试环境', 13 | 'sit':'集成测试环境', 14 | 'uat':'准生产环境', 15 | 'pt':'压测环境', 16 | 'product':'生产环境', 17 | } 18 | 19 | LOG_PATH = '/var/log/geniusalt' 20 | # SALT_FILE_ROOT = '/mnt/hgfs/hfnlb/gnsalt_modules' 21 | SALT_FILE_ROOT = os.path.join(settings.BASE_DIR, '../gnsalt_modules') 22 | -------------------------------------------------------------------------------- /hfnlb_project/geniusalt/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.3 on 2018-03-12 18:47 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ] 12 | 13 | operations = [ 14 | ] 15 | -------------------------------------------------------------------------------- /hfnlb_project/geniusalt/migrations/0002_auto_20180312_1847.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.3 on 2018-03-12 18:47 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | import django.db.models.deletion 7 | import jsonfield.fields 8 | 9 | 10 | class Migration(migrations.Migration): 11 | 12 | initial = True 13 | 14 | dependencies = [ 15 | ('geniusalt', '0001_initial'), 16 | ] 17 | 18 | operations = [ 19 | migrations.CreateModel( 20 | name='AuthToken', 21 | fields=[ 22 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 23 | ('username', models.CharField(default='', max_length=8, unique=True, verbose_name='用户名称')), 24 | ('token', models.CharField(default='', max_length=64, unique=True, verbose_name='用户TOKEN')), 25 | ('sign_date', models.DateTimeField(auto_now_add=True, verbose_name='注册日期')), 26 | ('expired_time', models.IntegerField(default=86400, verbose_name='有效期限')), 27 | ], 28 | ), 29 | migrations.CreateModel( 30 | name='IncludeRelationship', 31 | fields=[ 32 | ('id', models.AutoField(primary_key=True, serialize=False, verbose_name='ID')), 33 | ], 34 | ), 35 | migrations.CreateModel( 36 | name='Instance', 37 | fields=[ 38 | ('id', models.AutoField(primary_key=True, serialize=False, verbose_name='ID')), 39 | ('name', models.CharField(default='', max_length=128, unique=True, verbose_name='子模块名称')), 40 | ('is_lock', models.IntegerField(choices=[(0, '否'), (1, '是')], default='0', verbose_name='是否锁定')), 41 | ], 42 | ), 43 | migrations.CreateModel( 44 | name='Module', 45 | fields=[ 46 | ('id', models.AutoField(primary_key=True, serialize=False, verbose_name='ID')), 47 | ('name', models.CharField(default='', max_length=128, unique=True, verbose_name='模块名称')), 48 | ('pillar_required', jsonfield.fields.JSONField(default=[])), 49 | ('pillar_optional', jsonfield.fields.JSONField(default=[])), 50 | ('lock_count', models.IntegerField(default='0', verbose_name='锁定计数')), 51 | ], 52 | ), 53 | migrations.CreateModel( 54 | name='Node', 55 | fields=[ 56 | ('id', models.AutoField(primary_key=True, serialize=False, verbose_name='ID')), 57 | ('name', models.CharField(default='', max_length=128, unique=True, verbose_name='节点名称')), 58 | ('environment', models.CharField(default='', max_length=128, verbose_name='所属环境')), 59 | ('is_lock', models.IntegerField(choices=[(0, '否'), (1, '是')], default='0', verbose_name='是否锁定')), 60 | ('bind_instances', models.ManyToManyField(to='geniusalt.Instance')), 61 | ('bind_modules', models.ManyToManyField(to='geniusalt.Module')), 62 | ], 63 | ), 64 | migrations.CreateModel( 65 | name='Pillar', 66 | fields=[ 67 | ('id', models.AutoField(primary_key=True, serialize=False, verbose_name='ID')), 68 | ('pillar_name', models.CharField(default='', max_length=128, verbose_name='pillar名称')), 69 | ('pillar_value', models.TextField(default='', verbose_name='pillar值')), 70 | ('environment', models.CharField(default='', max_length=128, verbose_name='环境标签')), 71 | ('instance_belong', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='pillars', to='geniusalt.Instance')), 72 | ], 73 | ), 74 | migrations.AddField( 75 | model_name='instance', 76 | name='module_belong', 77 | field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='instances', to='geniusalt.Module'), 78 | ), 79 | migrations.AddField( 80 | model_name='includerelationship', 81 | name='r_included', 82 | field=models.ManyToManyField(to='geniusalt.Instance'), 83 | ), 84 | migrations.AddField( 85 | model_name='includerelationship', 86 | name='r_instance', 87 | field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='r_instance', to='geniusalt.Instance'), 88 | ), 89 | ] 90 | -------------------------------------------------------------------------------- /hfnlb_project/geniusalt/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alan011/NginxLB-cluster/74b2149b0980534e97145ce38023c7c5719a3d34/hfnlb_project/geniusalt/migrations/__init__.py -------------------------------------------------------------------------------- /hfnlb_project/geniusalt/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from jsonfield import JSONField 3 | 4 | YES_or_NO=((0,'否'),(1,'是')) 5 | 6 | class AuthToken(models.Model): 7 | username = models.CharField('用户名称',max_length=8, default='', unique=True) 8 | token = models.CharField('用户TOKEN',max_length=64, default='', unique=True) 9 | sign_date = models.DateTimeField('注册日期',auto_now_add=True) 10 | expired_time = models.IntegerField('有效期限',default=86400) ### Defautl is One day. '0' means never expired. 11 | 12 | class Module(models.Model): 13 | id = models.AutoField('ID',primary_key=True) 14 | name = models.CharField('模块名称',max_length=128, default='', unique=True) 15 | pillar_required = JSONField(default=[]) 16 | pillar_optional = JSONField(default=[]) 17 | lock_count = models.IntegerField('锁定计数', default='0') 18 | 19 | def __str__(self): 20 | return self.name 21 | 22 | class Instance(models.Model): 23 | id = models.AutoField('ID',primary_key=True) 24 | name = models.CharField('子模块名称',max_length=128, default='', unique=True) 25 | module_belong = models.ForeignKey('Module', on_delete=models.CASCADE, related_name="instances") 26 | is_lock = models.IntegerField('是否锁定', choices=YES_or_NO, default='0') 27 | 28 | def __str__(self): 29 | return self.name 30 | class IncludeRelationship(models.Model): 31 | id = models.AutoField('ID',primary_key=True) 32 | r_instance = models.OneToOneField(Instance, on_delete=models.CASCADE, related_name='r_instance') 33 | r_included = models.ManyToManyField(Instance) 34 | 35 | class Pillar(models.Model): 36 | id = models.AutoField('ID',primary_key=True) 37 | pillar_name = models.CharField('pillar名称', max_length=128, default='') 38 | pillar_value = models.TextField('pillar值', default='') 39 | environment = models.CharField('环境标签', max_length=128, default='') 40 | instance_belong = models.ForeignKey('Instance', on_delete=models.CASCADE, related_name="pillars") 41 | 42 | class Node(models.Model): 43 | id = models.AutoField('ID',primary_key=True) 44 | name = models.CharField('节点名称',max_length=128, default='', unique=True) 45 | environment = models.CharField('所属环境', max_length=128, default='') 46 | is_lock = models.IntegerField('是否锁定', choices=YES_or_NO, default='0') 47 | bind_modules = models.ManyToManyField(Module) 48 | bind_instances = models.ManyToManyField(Instance) 49 | 50 | def __str__(self): 51 | return self.name 52 | 53 | def pillar(self): 54 | pl = {} 55 | for m_obj in self.bind_modules.all(): 56 | pl[m_obj.name] = {} 57 | for i_obj in self.bind_instances.all(): 58 | if i_obj.module_belong == m_obj: 59 | pl[m_obj.name][i_obj.name] = {} 60 | ### To get default pillar without environment set. 61 | for p_obj in i_obj.pillars.filter(environment=''): 62 | pl[m_obj.name][i_obj.name][p_obj.pillar_name] = p_obj.pillar_value 63 | 64 | ### To update node's pillar with envrironment pillar objects. 65 | for p_obj in i_obj.pillars.filter(environment=self.environment): 66 | pl[m_obj.name][i_obj.name][p_obj.pillar_name] = p_obj.pillar_value 67 | 68 | ### To fill pillars of included instances. 69 | r_queryset = IncludeRelationship.objects.filter(r_instance=i_obj) 70 | if r_queryset.exists(): 71 | r_obj = r_queryset.get(r_instance=i_obj) 72 | r_included = r_obj.r_included.all() 73 | if r_included.exists(): 74 | pl[m_obj.name][i_obj.name]['__include__'] = {} 75 | for ii_obj in r_included: 76 | pl[m_obj.name][i_obj.name]['__include__'][ii_obj.name] = {} 77 | ### To get default pillar without environment set. 78 | for p in ii_obj.pillars.filter(environment=''): 79 | pl[m_obj.name][i_obj.name]['__include__'][ii_obj.name][p.pillar_name] = p.pillar_value 80 | 81 | ### To update node's pillar with envrironment pillar objects. 82 | for p in ii_obj.pillars.filter(environment=self.environment): 83 | pl[m_obj.name][i_obj.name]['__include__'][ii_obj.name][p.pillar_name] = p.pillar_value 84 | return pl 85 | -------------------------------------------------------------------------------- /hfnlb_project/geniusalt/operators/__init__.py: -------------------------------------------------------------------------------- 1 | from .instance_db_operator import InstanceOperator 2 | from .module_db_operator import ModuleOperator 3 | from .node_db_operator import NodeOperator 4 | from .push_operator import PushOperator 5 | from .relation_operator import RelationOperator 6 | 7 | __all__ = ['InstanceOperator', 'ModuleOperator', 'NodeOperator', 'PushOperator', 'RelationOperator'] 8 | -------------------------------------------------------------------------------- /hfnlb_project/geniusalt/operators/common.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | class Operator(object): 4 | fields_defination = {} 5 | def __init__(self, parameters): 6 | self.parameters = parameters 7 | self.operator_return = {'result':'SUCCESS', 'message':''} 8 | 9 | def getObject(self, model, object_name, **kwargs): 10 | data_filter = {'name':object_name} 11 | data_filter.update(kwargs) 12 | queryset = model.objects.filter(**data_filter) 13 | if queryset.exists(): 14 | return queryset.get(**data_filter) 15 | return None 16 | 17 | def dataValidating(self, fields_required, fields_optional): 18 | """ 19 | Subclass need to define attribute 'fields_defination'. 20 | """ 21 | for field in fields_required: 22 | if not self.parameters.get(field): ### Means must be provided and its value should not be empty. 23 | return "ERROR: Field '%s' is required." % field 24 | 25 | legal_data = {} 26 | for field in fields_required + fields_optional: 27 | has_collected = 0 28 | if field in self.parameters: 29 | if self.fields_defination[field]['value_type'] == str: 30 | if not isinstance(self.parameters[field], str): 31 | return "ERROR: Value of field '%s' must be a string." % field 32 | if not re.search(self.fields_defination[field]['value_regex'], self.parameters[field]): 33 | return "ERROR: Value of field '%s' contains illegal characters." % field 34 | 35 | if self.fields_defination[field]['value_type'] == 'choices' and self.parameters[field] not in self.fields_defination[field]['value_choices']: 36 | return "ERROR: Illegal value '%s' found for choices field '%s'." % (self.parameters[field],field) 37 | 38 | if self.fields_defination[field]['value_type'] == list: 39 | if not isinstance(self.parameters[field], list): 40 | return "ERROR: Value of field '%s' must be a JSON list." % field 41 | if self.fields_defination[field]['item_type'] == str: 42 | for item in self.parameters[field]: 43 | if not isinstance(item, str): 44 | return "ERROR: Item '%s' of list field '%s' must be a string." % (item, field) 45 | if not re.search(self.fields_defination[field]['item_regex'], item): 46 | return "ERROR: Item '%s' of list field '%s' contains illegal characters." % (item,field) 47 | if self.fields_defination[field]['item_type'] == 'choices': 48 | for item in self.parameters[field]: 49 | if item not in self.fields_defination[field]['item_choices']: 50 | return "ERROR: Illegal item '%s' found for item-choice-type list field '%s' ." % (item,field) 51 | 52 | if self.fields_defination[field]['item_type'] == 'object_name_list': 53 | model = self.fields_defination[field]['item_model'] 54 | object_list = [] 55 | for n in self.parameters[field]: 56 | obj = self.getObject(model, n) 57 | if obj is None: 58 | return "ERROR: Object not found for name '%s' in field '%s'." % (n, field) 59 | object_list.append(obj) 60 | legal_data[field] = object_list 61 | has_collected = 1 62 | 63 | if self.fields_defination[field]['value_type'] == 'object_name': 64 | model = self.fields_defination[field]['object_model'] 65 | obj = self.getObject(model, self.parameters[field]) 66 | if obj is None: 67 | return "ERROR: Object not found for field '%s'." % field 68 | legal_data[field] = obj 69 | has_collected = 1 70 | 71 | if self.fields_defination[field]['value_type'] == dict: 72 | if not isinstance(self.parameters[field], dict): 73 | return "ERROR: Value of field '%s' must be a JSON dict." % field 74 | if self.fields_defination[field]['key_type'] == str: 75 | for key in self.parameters[field]: 76 | if not isinstance(key, str): 77 | return "ERROR: Key '%s' of dict field '%s' must be a string." % (key, field) 78 | if not re.search(self.fields_defination[field]['key_regex'], key): 79 | return "ERROR: Key '%s' of dict field '%s' contains illegal characters." % (item,field) 80 | 81 | if self.fields_defination[field]['value_type'] == 'text': 82 | legal_data[field] = str(self.parameters[field]) 83 | has_collected = 1 84 | 85 | if has_collected == 0: 86 | legal_data[field] = self.parameters[field] 87 | return legal_data 88 | -------------------------------------------------------------------------------- /hfnlb_project/geniusalt/operators/push_operator.py: -------------------------------------------------------------------------------- 1 | from geniusalt.models import Node, Instance, Module 2 | from geniusalt.config import LOG_PATH 3 | from .common import Operator 4 | 5 | from threading import Thread 6 | import os, sys, time, json, re 7 | 8 | 9 | class SimulPush(Thread): 10 | def __init__(self, node_name, pillar): 11 | self.node_name=node_name 12 | self.pillar = pillar 13 | self.push_result=None 14 | self.log_path = None 15 | super(SimulPush, self).__init__() 16 | 17 | def run(self): 18 | self.push_result, self.log_path = self.pushOneNode() 19 | 20 | def pushOneNode(self): 21 | ### prepare log references. 22 | log_dir = os.path.join(LOG_PATH, self.node_name) 23 | if not os.path.isdir(log_dir): 24 | os.makedirs(log_dir) 25 | 26 | push_time = time.strftime('%Y-%m-%d_%H%M%S',time.localtime(time.time())) 27 | log_path = os.path.join(log_dir, 'push_at_%s.log' % (push_time,)) 28 | log_path_f = open(log_path,'a') 29 | 30 | ### run salt push by assigning module name and pillar dict. 31 | cmd_line = 'salt %s state.sls %s %s' % (self.node_name, ','.join(list(self.pillar.keys())), 'pillar=\''+json.dumps(self.pillar)+'\'') 32 | # print("\n===> Saltstack: " + cmd_line) 33 | 34 | ### write push log 35 | log_ret = [] 36 | log_f = os.popen(cmd_line) 37 | while True: 38 | log_line = log_f.readline() 39 | if log_line == '': 40 | break 41 | log_path_f.write(log_line) 42 | # print(log_line,end='') 43 | log_ret.append(log_line.rstrip()) 44 | 45 | log_path_f.close() 46 | 47 | return log_ret, log_path 48 | 49 | 50 | 51 | class PushOperator(Operator): 52 | fields_defination = {'nodes': {'value_type' : list, 53 | 'item_type': 'object_name_list', 54 | 'item_model': Node}, 55 | 56 | 'bind_modules': {'value_type': list, 57 | 'item_type': 'object_name_list', 58 | 'item_model': Module}, 59 | 60 | 'bind_instances': {'value_type': list, 61 | 'item_type': 'object_name_list', 62 | 'item_model': Instance}, 63 | 'longTerms': {'value_type': list, 64 | 'item_type': 'choices', 65 | 'item_choices':['--only-module', '--checkself']}, 66 | 67 | } 68 | def checkPushLog(self): 69 | pushlog = self.operator_return.get('pushlog') 70 | if pushlog: 71 | succeeded_nodes = [] 72 | failed_nodes = [] 73 | for n_name in pushlog: 74 | logs = '\n'.join(pushlog[n_name]) 75 | # print(logs) 76 | if re.search('-+\nSucceeded:\s+\d+(\s+\(changed=\d+\))?\nFailed:\s+0\n-+', logs): 77 | succeeded_nodes.append(n_name) 78 | else: 79 | failed_nodes.append(n_name) 80 | self.operator_return['message'] = "Push finished, succeeded Nodes: %s, failed nodes: %s" % (', '.join(succeeded_nodes), ', '.join(failed_nodes)) 81 | if failed_nodes: 82 | self.operator_return['result'] = 'FAILED' 83 | 84 | def push(self): 85 | """ 86 | To push Module or Instance to Nodes. This makes the real installation for a real host. 87 | """ 88 | fields_required, fields_optional = ['nodes'], ['bind_modules', 'bind_instances', 'longTerms'] 89 | 90 | ### To validate data in self.parameters. 91 | validate_result = self.dataValidating(fields_required, fields_optional) 92 | if not isinstance(validate_result, dict): 93 | return validate_result 94 | 95 | ### To bind Modules or Instances to Nodes first. 96 | bind_modules, bind_instances = [],[] 97 | if validate_result.get('bind_modules'): 98 | bind_modules = validate_result['bind_modules'] 99 | if validate_result.get('bind_instances'): 100 | bind_instances = validate_result['bind_instances'] 101 | for i_obj in bind_instances: 102 | bind_modules.append(i_obj.module_belong) 103 | for n_obj in validate_result['nodes']: 104 | if bind_modules: 105 | n_obj.bind_modules.add(*bind_modules) 106 | if bind_instances: 107 | n_obj.bind_instances.add(*bind_instances) 108 | n_obj.save() 109 | 110 | ### To check locked objects: 111 | for n in validate_result['nodes']: 112 | if n.is_lock != 0: 113 | return "ERROR: Push action aborted because Node '%s' has been locked." % n.name 114 | if validate_result.get('bind_modules'): 115 | for m in validate_result['bind_modules']: 116 | if m.lock_count != 0: 117 | return "ERROR: Push action aborted because Module '%s' has been locked." % m.name 118 | if validate_result.get('bind_instances'): 119 | for i in validate_result['bind_instances']: 120 | if i.is_lock != 0: 121 | return "ERROR: Push action aborted because Instance '%s' has been locked." % m.name 122 | 123 | ### To make pillar dict for each node. 124 | pillar_total = {} 125 | for n in validate_result['nodes']: 126 | bind_pillar = n.pillar() 127 | pillar_total[n.name] = {} 128 | 129 | ### If Module or Instance is specified, use the specified pillar, not all the pillars installed for this Node. 130 | if validate_result.get('bind_modules'): 131 | if self.parameters.get('longTerms') and '--only-module' in self.parameters['longTerms']: 132 | if validate_result.get('bind_instances'): 133 | return "ERROR: Instance object should not be specified with longTerms '--only-module'." 134 | ### To fill pillar with module name, without only instances. 135 | for m in validate_result['bind_modules']: 136 | pillar_total[n.name][m.name] = {} 137 | else: 138 | ### To fill pillar with module name and its all instances installed on this node. 139 | for m in validate_result['bind_modules']: 140 | pillar_total[n.name][m.name] = bind_pillar[m.name] 141 | if validate_result.get('bind_instances'): 142 | if self.parameters.get('longTerms') and '--only-module' in self.parameters['longTerms']: 143 | return "ERROR: Instance object should not be specified with longTerms '--only-module'." 144 | for i in validate_result['bind_instances']: 145 | if i.module_belong.name not in pillar_total[n.name]: 146 | pillar_total[n.name][i.module_belong.name] = {} 147 | pillar_total[n.name][i.module_belong.name][i.name] = bind_pillar[i.module_belong.name][i.name] 148 | ### If no Module or Instance specified, use all the pillars installed for this node. 149 | if pillar_total[n.name] == {}: 150 | pillar_total[n.name] = bind_pillar 151 | 152 | ### To push objects to real hosts in multi-threading. 153 | thread_pool = {} 154 | push_log = {} 155 | push_log_path = {} 156 | for n in validate_result['nodes']: 157 | if not pillar_total[n.name]: 158 | push_log[n.name] = ["Pushing ignored for this Node '%s' because no pillar installed." % n.name] 159 | else: 160 | thread_pool[n.name] = SimulPush(n.name, pillar_total[n.name]) 161 | thread_pool[n.name].start() 162 | for n_name in thread_pool: 163 | thread_pool[n_name].join() 164 | push_log[n_name] = thread_pool[n_name].push_result 165 | push_log_path[n_name] = thread_pool[n_name].log_path 166 | 167 | self.operator_return['pushlog'] = push_log 168 | self.operator_return['pushlog_path'] = push_log_path 169 | self.checkPushLog() 170 | return self.operator_return 171 | -------------------------------------------------------------------------------- /hfnlb_project/geniusalt/views.py: -------------------------------------------------------------------------------- 1 | from django.http import HttpResponse 2 | 3 | def checkView(request, *args, **kwargs): 4 | return HttpResponse('OK') 5 | -------------------------------------------------------------------------------- /hfnlb_project/hfnlb_project/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alan011/NginxLB-cluster/74b2149b0980534e97145ce38023c7c5719a3d34/hfnlb_project/hfnlb_project/__init__.py -------------------------------------------------------------------------------- /hfnlb_project/hfnlb_project/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for hfnlb_project project. 3 | 4 | Generated by 'django-admin startproject' using Django 1.11.7. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/1.11/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/1.11/ref/settings/ 11 | """ 12 | 13 | import os 14 | 15 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 16 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 17 | 18 | 19 | # Quick-start development settings - unsuitable for production 20 | # See https://docs.djangoproject.com/en/1.11/howto/deployment/checklist/ 21 | 22 | # SECURITY WARNING: keep the secret key used in production secret! 23 | SECRET_KEY = '@qjw#ipoms3-31hpv$x(m@(m367fk&@@lve6+0n93)&70!=q7m' 24 | 25 | # SECURITY WARNING: don't run with debug turned on in production! 26 | DEBUG = True 27 | 28 | ALLOWED_HOSTS = ['172.16.40.169','127.0.0.1'] 29 | 30 | 31 | # Application definition 32 | 33 | INSTALLED_APPS = [ 34 | 'django.contrib.admin', 35 | 'django.contrib.auth', 36 | 'django.contrib.contenttypes', 37 | 'django.contrib.sessions', 38 | 'django.contrib.messages', 39 | 'django.contrib.staticfiles', 40 | 'nlb_proxy', 41 | 'geniusalt', 42 | ] 43 | 44 | MIDDLEWARE = [ 45 | 'django.middleware.security.SecurityMiddleware', 46 | 'django.contrib.sessions.middleware.SessionMiddleware', 47 | 'django.middleware.common.CommonMiddleware', 48 | 'django.middleware.csrf.CsrfViewMiddleware', 49 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 50 | # 'django.contrib.auth.middleware.SessionAuthenticationMiddleware', 51 | 'django.contrib.messages.middleware.MessageMiddleware', 52 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 53 | ] 54 | 55 | ROOT_URLCONF = 'hfnlb_project.urls' 56 | 57 | TEMPLATES = [ 58 | { 59 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 60 | 'DIRS': [], 61 | 'APP_DIRS': True, 62 | 'OPTIONS': { 63 | 'context_processors': [ 64 | 'django.template.context_processors.debug', 65 | 'django.template.context_processors.request', 66 | 'django.contrib.auth.context_processors.auth', 67 | 'django.contrib.messages.context_processors.messages', 68 | ], 69 | }, 70 | }, 71 | ] 72 | 73 | WSGI_APPLICATION = 'hfnlb_project.wsgi.application' 74 | 75 | 76 | # Database 77 | # https://docs.djangoproject.com/en/1.11/ref/settings/#databases 78 | 79 | DATABASES = { 80 | 'default': { 81 | # 'ENGINE': 'django.db.backends.sqlite3', 82 | # 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), 83 | 'ENGINE': 'django.db.backends.mysql', 84 | 'NAME': 'nlb', 85 | 'HOST': '127.0.0.1', 86 | 'USER': 'root', 87 | 'PASSWORD': 'pui8exTWi6GZ2Rv1', 88 | 'OPTIONS':{'init_command': "SET sql_mode='STRICT_TRANS_TABLES'"}, 89 | } 90 | } 91 | 92 | 93 | # Password validation 94 | # https://docs.djangoproject.com/en/1.11/ref/settings/#auth-password-validators 95 | 96 | AUTH_PASSWORD_VALIDATORS = [ 97 | { 98 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', 99 | }, 100 | { 101 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 102 | }, 103 | { 104 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', 105 | }, 106 | { 107 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', 108 | }, 109 | ] 110 | 111 | 112 | # Internationalization 113 | # https://docs.djangoproject.com/en/1.11/topics/i18n/ 114 | 115 | LANGUAGE_CODE = 'en-us' 116 | 117 | TIME_ZONE = 'Asia/Shanghai' 118 | 119 | #USE_I18N = True 120 | 121 | #USE_L10N = True 122 | 123 | #USE_TZ = True 124 | 125 | 126 | # Static files (CSS, JavaScript, Images) 127 | # https://docs.djangoproject.com/en/1.11/howto/static-files/ 128 | 129 | STATIC_URL = '/static/' 130 | LOGIN_URL = '/login/' 131 | 132 | SESSION_COOKIE_AGE = 3600 133 | -------------------------------------------------------------------------------- /hfnlb_project/hfnlb_project/urls.py: -------------------------------------------------------------------------------- 1 | """hfnlb_project 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 | from nlb_proxy.views import MyIndex, MyLogin, MyLogout, ScriptServer 20 | 21 | urlpatterns = [ 22 | url(r'^admin/', admin.site.urls), 23 | url(r'^proxy/', include('nlb_proxy.api.urls')), 24 | url(r'^geniusalt/', include('geniusalt.api.urls')), 25 | url(r'^login/', MyLogin.as_view(), name="login"), 26 | url(r'^logout', MyLogout.as_view()), 27 | url(r'^scripts/(?P.+)$', ScriptServer.as_view()), 28 | url(r'^.*$', MyIndex.as_view()), 29 | ] 30 | -------------------------------------------------------------------------------- /hfnlb_project/hfnlb_project/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for hfnlb_project 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", "hfnlb_project.settings") 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /hfnlb_project/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", "hfnlb_project.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 | -------------------------------------------------------------------------------- /hfnlb_project/nlb_proxy/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alan011/NginxLB-cluster/74b2149b0980534e97145ce38023c7c5719a3d34/hfnlb_project/nlb_proxy/__init__.py -------------------------------------------------------------------------------- /hfnlb_project/nlb_proxy/api/__init__.py: -------------------------------------------------------------------------------- 1 | from .http_proxy import HTTPProxyView 2 | from .tcp_proxy import TCPProxyView 3 | from .login import APILogin, APIUserInfo 4 | from .users import APIUsers 5 | from .clustering import NLBCluster 6 | from .iplist import IPListView 7 | from .whitelist import WhitelistView 8 | from .blacklist import BlacklistView 9 | 10 | __all__ = ['HTTPProxyView', 'TCPProxyView','APILogin','APIUserInfo', 'APIUsers', 'NLBCluster', 'IPListView','WhitelistView', 'BlacklistView'] 11 | -------------------------------------------------------------------------------- /hfnlb_project/nlb_proxy/api/blacklist.py: -------------------------------------------------------------------------------- 1 | from django.views.generic import View 2 | from django.utils.decorators import method_decorator 3 | from django.contrib.auth.decorators import login_required 4 | from django.views.decorators.csrf import csrf_exempt 5 | 6 | from nlb_proxy.models import BlackListStrategy 7 | from .common import APIStrategySaltPushMixin 8 | 9 | @method_decorator(csrf_exempt, name='dispatch') 10 | @method_decorator(login_required, name='dispatch') 11 | class BlacklistView(View, APIStrategySaltPushMixin): 12 | model = BlackListStrategy 13 | -------------------------------------------------------------------------------- /hfnlb_project/nlb_proxy/api/clustering.py: -------------------------------------------------------------------------------- 1 | from django.views.generic import View 2 | from django.http import HttpResponse 3 | from django.utils.decorators import method_decorator 4 | from django.contrib.auth.decorators import login_required 5 | from django.views.decorators.csrf import csrf_exempt 6 | from nlb_proxy.models import ClusterMember, NLBConfig 7 | from nlb_proxy.tools import validateIP 8 | from nlb_proxy import config 9 | 10 | import re,json 11 | 12 | @method_decorator(csrf_exempt, name='dispatch') 13 | @method_decorator(login_required, name='dispatch') 14 | class NLBCluster(View): 15 | def __init__(self, *args, **kwargs): 16 | self.response_data = {'result':'SUCCESS','message':''} 17 | self.status = 200 18 | super().__init__(*args, **kwargs) 19 | 20 | def checkIP(self, ip): 21 | queryset = ClusterMember.objects.filter(ip = ip) 22 | if not queryset.exists(): 23 | return None 24 | else: 25 | return queryset.get(ip = ip) 26 | 27 | def saveObject(self, obj): 28 | try: 29 | obj.save() 30 | except: 31 | self.response_data.update({'result':'FAILED', 'message':"错误:数据库错误,请联系管理员!"}) 32 | self.status = 500 33 | return False 34 | return True 35 | 36 | def getInit(self): 37 | init_setting = {'sysuser':'', 38 | 'syspasswd':'', 39 | 'sethostname':'', 40 | 'hostnameprefix':'NLB-CLUSTER', 41 | } 42 | queryset = NLBConfig.objects.filter(name='initConfig') 43 | if queryset.exists(): 44 | obj = queryset.get(name='initConfig') 45 | init_setting = obj.content 46 | 47 | self.response_data['initSetting'] = init_setting 48 | 49 | def setInit(self): 50 | new_setting = {} 51 | error_count = 0 52 | if self.post_data.get('sysuser'): 53 | new_setting['sysuser'] = self.post_data['sysuser'] 54 | else: 55 | error_count += 1 56 | if self.post_data.get('syspasswd'): 57 | new_setting['syspasswd'] = self.post_data['syspasswd'] 58 | else: 59 | error_count += 1 60 | if self.post_data.get('sethostname') == 'Y': 61 | new_setting['sethostname'] = self.post_data['sethostname'] 62 | if self.post_data.get('hostnameprefix'): 63 | new_setting['hostnameprefix'] = self.post_data['hostnameprefix'] 64 | else: 65 | new_setting['hostnameprefix'] = 'NLB-CLUSTER' 66 | else: 67 | new_setting['sethostname'] = 'N' 68 | 69 | if error_count: 70 | self.response_data.update({'result':'FAILED','message':'错误:请提供操作系统用户名/密码'}) 71 | self.status = 400 72 | else: 73 | queryset = NLBConfig.objects.filter(name='initConfig') 74 | if queryset.exists(): 75 | obj = queryset.get(name='initConfig') 76 | else: 77 | obj = NLBConfig(name='initConfig') 78 | obj.content = new_setting 79 | self.saveObject(obj) 80 | 81 | def expandCluster(self): 82 | ips = self.post_data.get('ip_list') 83 | if ips: 84 | ip_list = ips.split('\n') 85 | new_ip = [] 86 | readd_ip = [] 87 | for ip in ip_list: 88 | if not validateIP(ip): 89 | is_ip = False 90 | self.response_data.update({'result':'FAILED','message':"错误:发现无效IP '%s'!" % ip}) 91 | self.status=400 92 | break 93 | else: 94 | obj = self.checkIP(ip) 95 | if obj: ### ip exists in DB. 96 | if obj.is_deleted != 0: 97 | readd_ip.append(obj) 98 | else: 99 | new_ip.append(ip) 100 | for ip in new_ip: 101 | obj = ClusterMember(ip=ip) 102 | if not self.saveObject(obj): 103 | break 104 | for obj in readd_ip: 105 | obj.is_deleted = 0 106 | obj.is_init = 0 107 | obj.is_alive = 0 108 | obj.need_reload = 0 109 | obj.role = 'MINION' 110 | if obj.ip == config.WEB_MASTER: 111 | obj.role = 'MASTER' 112 | if not self.saveObject(obj): 113 | break 114 | 115 | def kickoffNode(self): 116 | ip = self.post_data.get('ip') 117 | if ip: 118 | obj = self.checkIP(ip) 119 | if obj: 120 | if obj.is_deleted == 0: 121 | obj.is_deleted = 1 122 | self.saveObject(obj) 123 | 124 | # def setMaster(self): 125 | # ip = self.post_data.get('ip') 126 | # if ip: 127 | # obj = self.checkIP(ip) 128 | # if obj: 129 | # obj.role = 'MASTER' 130 | # obj.is_init = 2 131 | # self.saveObject(obj) 132 | 133 | # def setMinion(self): 134 | # ip = self.post_data.get('ip') 135 | # if ip: 136 | # obj = self.checkIP(ip) 137 | # if obj: 138 | # obj.role = 'MINION' 139 | # obj.is_init = 3 140 | # self.saveObject(obj) 141 | 142 | def get(self, request, *args, **kwargs): 143 | queryset = ClusterMember.objects.filter(is_deleted=0) 144 | data = [] 145 | for node in queryset: 146 | attr = {'hostname':node.hostname, 147 | 'ip':node.ip, 148 | 'role':node.role, 149 | 'status':('未知','red'), 150 | } 151 | if not node.hostname: 152 | attr['hostname'] = '获取主机信息中...' 153 | elif node.is_init == 0: 154 | attr['status'] = ('未初始化','gray') 155 | elif node.is_alive == 0: 156 | attr['status'] = ('等待被master接受','orange') 157 | elif node.is_alive == 1: 158 | attr['status'] = ('运行正常','green') 159 | if node.need_reload == 1: 160 | attr['status'] = ('配置更新自动reload失败,请联系管理员排查', 'red') 161 | elif node.is_alive == 2: 162 | attr['status'] = ('salt-minion异常','red') 163 | elif node.is_alive == 3: 164 | attr['status'] = ('nginx异常','red') 165 | elif node.is_alive == 4: 166 | attr['status'] = ('不能连接到master','red') 167 | 168 | if attr['role'] == 'MASTER': 169 | attr['role'] = 'MASTER/MINION' 170 | 171 | 172 | data.append(attr) 173 | self.response_data['data'] = data 174 | 175 | return HttpResponse(json.dumps(self.response_data),content_type='application/json',status=self.status) 176 | 177 | def post(self, request, *args, **kwargs): 178 | # legal_action = ['getInit', 'setInit', 'setMaster', 'setMinion','expandCluster', 'kickoffNode'] 179 | legal_action = ['getInit', 'setInit','expandCluster', 'kickoffNode'] 180 | self.post_data = {k:v.strip() for k,v in request.POST.items()} 181 | # print(self.post_data) 182 | 183 | action = self.post_data.get('action') 184 | if action in legal_action: 185 | getattr(self, action)() 186 | else: 187 | self.response_data.update({'result':'FAILED','message':"ERROR: action '%s' not support." % action}) 188 | self.status = 400 189 | 190 | # print("response_data: %s; status: %s" % (self.response_data,self.status)) 191 | return HttpResponse(json.dumps(self.response_data),content_type='application/json',status=self.status) 192 | -------------------------------------------------------------------------------- /hfnlb_project/nlb_proxy/api/iplist.py: -------------------------------------------------------------------------------- 1 | from django.views.generic import View 2 | from django.utils.decorators import method_decorator 3 | from django.contrib.auth.decorators import login_required 4 | from django.views.decorators.csrf import csrf_exempt 5 | from django.db.models import Q 6 | 7 | from nlb_proxy.tools import validateIP 8 | from nlb_proxy.models import AccessControlIPList, WhiteListStrategy 9 | from .common import APIMixin 10 | 11 | import re,json,os 12 | 13 | @method_decorator(csrf_exempt, name='dispatch') 14 | @method_decorator(login_required, name='dispatch') 15 | class IPListView(View, APIMixin): 16 | model = AccessControlIPList 17 | fields_required = ['ip'] 18 | fields_optional = ['description'] 19 | 20 | def dataVerify(self): 21 | form_data = {k:v.strip() for k,v in self.post_data.items() if k in self.fields_required + self.fields_optional} 22 | 23 | ### Check fields required. 24 | for field in self.fields_required: 25 | if field not in form_data: 26 | return "ERROR: Field '%s' is required!" % field 27 | 28 | ### Check 'ip' 29 | if not validateIP(form_data['ip']): 30 | return "ERROR: Illegal value detected for field 'ip': '%s'" % form_data['ip'] 31 | 32 | return form_data 33 | 34 | def checkExists(self, verified_data): 35 | filter_list =[{'ip':verified_data['ip']}, 36 | ] 37 | return self.checkExistsWithFilterList(filter_list) 38 | 39 | def normalSearch(self, search_value): 40 | return self.model.objects.filter(is_deleted = 0).filter(Q(ip__icontains= search_value )|Q(description__icontains= search_value )|Q(add_user__username__icontains= search_value )) 41 | 42 | def searchData(self): 43 | return self.normalSearch(self.request.GET.get('search_value')).order_by('-id') 44 | 45 | def makeDataList(self, queryset): 46 | data = [] 47 | for obj in queryset: 48 | attrs = {'id': obj.id, 49 | 'ip': obj.ip, 50 | 'whitelists':[ wl.strategy_name for wl in obj.whitelist_refers.filter(is_deleted=0)], 51 | 'description': obj.description, 52 | 'add_time': obj.add_time.strftime('%F %T'), 53 | 'add_user': obj.add_user.username, 54 | } 55 | # print(attrs['whitelists']) 56 | 57 | data.append(attrs) 58 | return data 59 | -------------------------------------------------------------------------------- /hfnlb_project/nlb_proxy/api/login.py: -------------------------------------------------------------------------------- 1 | from django.views.generic import View 2 | from django.http import HttpResponse, HttpResponseRedirect 3 | from django.contrib.auth import authenticate, login, logout 4 | from django.utils.decorators import method_decorator 5 | from django.views.decorators.csrf import csrf_exempt 6 | from django.contrib.auth.decorators import login_required 7 | 8 | import json, time 9 | 10 | @method_decorator(csrf_exempt, name='dispatch') 11 | class APILogin(View): 12 | def post(self, request, *args, **kwargs): 13 | action = request.POST.get('action') 14 | if action == 'login': 15 | username = self.request.POST['username'] 16 | password = self.request.POST['password'] 17 | user = authenticate(username=username, password=password) 18 | if user is not None: 19 | if user.is_active: 20 | if not self.request.POST.get('remember_me', None): 21 | self.request.session.set_expiry(0) 22 | login(self.request, user) 23 | print('User %s login successfully!' % username) 24 | return HttpResponse(json.dumps({'result':'LOGIN_SUCCESS'}), content_type='application/json') 25 | else: 26 | return HttpResponse(json.dumps({'result':'NOT_ACTIVE','message':'用户未激活'}), content_type='application/json') 27 | else: 28 | return HttpResponse(json.dumps({'result':'LOGIN_FAILED', 'message':'用户名或密码不正确'}), content_type='application/json') 29 | else: 30 | return HttpResponse(json.dumps({'message':'ERROR: Bad Request.'}), status=400) 31 | def get(self, request): 32 | return HttpResponse(json.dumps({'message':'ERROR: Bad Request.'}), status=400) 33 | 34 | 35 | @method_decorator(login_required, name='dispatch') 36 | class APIUserInfo(View): 37 | def get(self, request, *args, **kwargs): 38 | user_info = { "username": request.user.username, 39 | "realname": request.user.first_name, 40 | "result":'SUCCESS', 41 | } 42 | if not user_info["realname"]: 43 | user_info["realname"] = user_info["username"] 44 | 45 | return HttpResponse(json.dumps(user_info), content_type="application/json") 46 | 47 | def post(self, request): 48 | return HttpResponse("ERROR: Bad Request", status=400) 49 | -------------------------------------------------------------------------------- /hfnlb_project/nlb_proxy/api/tcp_proxy.py: -------------------------------------------------------------------------------- 1 | from django.views.generic import View 2 | from django.utils.decorators import method_decorator 3 | from django.contrib.auth.decorators import login_required 4 | from django.views.decorators.csrf import csrf_exempt 5 | from django.db.models import Q 6 | 7 | from nlb_proxy.tools import validateIP, unique_list_elements, validatePort 8 | from nlb_proxy.models import TcpProxy 9 | from .common import APISaltPushMixin 10 | 11 | import re,json,os 12 | 13 | @method_decorator(csrf_exempt, name='dispatch') 14 | @method_decorator(login_required, name='dispatch') 15 | class TCPProxyView(View, APISaltPushMixin): 16 | model = TcpProxy 17 | fields_required = ['proxy_domain_name','app_name','app_port','proxy_port','app_servers'] 18 | fields_optional = ['app_servers_backup','description'] 19 | pillar_attrs = ['app_name','app_port','proxy_port','app_servers', 'app_servers_backup'] 20 | 21 | def dataVerify(self): 22 | form_data = {k:v.strip() for k,v in self.post_data.items() if k in self.fields_required + self.fields_optional} 23 | 24 | ### Check fields required. 25 | for field in self.fields_required: 26 | if field not in form_data: 27 | return "ERROR: Field '%s' is required!" % field 28 | 29 | ### Check 'proxy_domain_name' 30 | if re.search('[^a-z0-9\-\.]', form_data['proxy_domain_name']): 31 | return "ERROR: Illegal value detected for field 'proxy_domain_name': '%s'" % form_data['proxy_domain_name'] 32 | 33 | ### Check 'app_name' 34 | if re.search('[^a-zA-Z0-9\-\_]', form_data['app_name']): 35 | return "ERROR: Illegal value detected for field 'app_name': '%s'" % form_data['app_name'] 36 | 37 | ### Check 'app_port' and 'proxy_port' 38 | for field in ['app_port', 'proxy_port']: 39 | if validatePort(form_data[field]): 40 | form_data[field] = int(form_data[field]) 41 | else: 42 | return "ERROR: Illegal port number: '%s'" % form_data[field] 43 | 44 | ### Check 'app_servers' 45 | app_server_list = unique_list_elements(form_data['app_servers'].split('/')) 46 | for ip in app_server_list: 47 | if not validateIP(ip): 48 | return "ERROR Invalid IP found: '%s'" % ip 49 | form_data['app_servers'] = '/'.join(app_server_list) 50 | 51 | ### Check 'app_servers_backup' 52 | if form_data.get('app_servers_backup'): 53 | app_backup_server_list = unique_list_elements(form_data['app_servers_backup'].split('/')) 54 | for ip in app_backup_server_list: 55 | if not validateIP(ip): 56 | return "ERROR Invalid IP found: '%s'" % ip 57 | form_data['app_servers_backup'] = '/'.join(app_backup_server_list) 58 | 59 | return form_data 60 | 61 | def checkExists(self, verified_data): 62 | filter_list =[{'proxy_domain_name':verified_data['proxy_domain_name']}, 63 | {'app_name':verified_data['app_name']}, 64 | {'proxy_port':verified_data['proxy_port']}, 65 | ] 66 | return self.checkExistsWithFilterList(filter_list) 67 | 68 | def normalSearch(self, search_value): 69 | return self.model.objects.filter(is_deleted = 0).filter(Q(proxy_domain_name__icontains= search_value )|Q(app_name__icontains= search_value )|Q(app_port__icontains= search_value )|Q(app_servers__icontains= search_value )|Q(app_servers_backup__icontains= search_value )|Q(proxy_port__icontains= search_value )|Q(description__icontains= search_value )|Q(add_user__username__icontains= search_value )) 70 | 71 | def searchData(self): 72 | legal_field_s = {'APP':'app_name', 73 | 'APP_PORT':'app_port', 74 | 'LISTEN_PORT':'proxy_port', 75 | 'USER':'add_user', 76 | 'DESC':'description', 77 | 'DOMAIN':'proxy_domain_name', 78 | } 79 | legal_field_m = {'SERVER':['app_servers','app_servers_backup'],} 80 | search_value = self.request.GET.get('search_value') 81 | search_value_ns = search_value.replace(' ','') 82 | 83 | queryset = None 84 | if re.search('^([A-Z]+|[A-Z]+_[A-Z]+):[^&]+(&([A-Z]+|[A-Z]+_[A-Z]+):[^&]+)*$', search_value_ns): 85 | search_dict = { item.split(':')[0]:item.split(':')[1] for item in search_value_ns.split('&')} 86 | filter_dict = {'is_deleted':0} 87 | for k in search_dict: 88 | if k in legal_field_s: 89 | filter_dict[ legal_field_s[k] + '__icontains'] = search_dict[k] 90 | 91 | queryset = self.model.objects.filter(**filter_dict) 92 | 93 | if 'SERVER' in search_dict: 94 | queryset = queryset.filter(Q(app_servers__icontains=search_dict['SERVER'])|Q(app_servers_backup__icontains=search_dict['SERVER'])) 95 | 96 | elif re.search('^([A-Z]+|[A-Z]+_[A-Z]+)=[^&]+(&([A-Z]+|[A-Z]+_[A-Z]+)=[^&]+)*$', search_value_ns): 97 | search_dict = { item.split('=')[0]:item.split('=')[1] for item in search_value_ns.split('&')} 98 | filter_dict = {'is_deleted':0} 99 | for k in search_dict: 100 | if k in legal_field_s: 101 | filter_dict[ legal_field_s[k]] = search_dict[k] 102 | queryset = self.model.objects.filter(**filter_dict) 103 | 104 | if 'SERVER' in search_dict: 105 | queryset = queryset.filter(Q(app_servers=search_dict['SERVER'])|Q(app_servers_backup=search_dict['SERVER'])) 106 | 107 | if queryset is None: 108 | queryset = self.normalSearch(search_value) 109 | 110 | return queryset.order_by('-id') 111 | 112 | def makeDataList(self, queryset): 113 | data = [] 114 | for obj in queryset: 115 | attrs = {'id': obj.id, 116 | 'proxy_domain_name': obj.proxy_domain_name, 117 | 'app_name': obj.app_name, 118 | 'app_port': obj.app_port, 119 | 'app_servers': obj.app_servers, 120 | 'app_servers_backup': obj.app_servers_backup, 121 | 'proxy_port': obj.proxy_port, 122 | 'description': obj.description, 123 | 'add_time': obj.add_time.strftime('%F %T'), 124 | 'add_user': obj.add_user.username, 125 | 'status': ['服务正常', 'green'], 126 | } 127 | 128 | if obj.apply_stat == 0: 129 | attrs['status'] = ["下发中", "orange"] 130 | elif obj.apply_stat == 2: 131 | attrs['status'] = ["下发失败","red"] 132 | elif obj.apply_stat == 3: 133 | attrs['status'] = ["下发成功,等待reload","blue"] 134 | elif obj.is_disabled: 135 | attrs['status'] = ['已停用', "gray"] 136 | 137 | data.append(attrs) 138 | return data 139 | -------------------------------------------------------------------------------- /hfnlb_project/nlb_proxy/api/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import url 2 | from nlb_proxy.api import HTTPProxyView, TCPProxyView, APILogin, APIUserInfo, APIUsers, NLBCluster, IPListView, WhitelistView, BlacklistView 3 | 4 | app_name = 'proxy' 5 | urlpatterns = [ url(r'^api/v1/httpproxy$',HTTPProxyView.as_view(), name='HTTPProxyView'), 6 | url(r'^api/v1/tcpproxy$',TCPProxyView.as_view(), name='TCPProxyView'), 7 | url(r'^api/v1/login$',APILogin.as_view(), name='APILogin'), 8 | url(r'^api/v1/userinfo$', APIUserInfo.as_view(), name='APIUserInfo'), 9 | url(r'^api/v1/users$', APIUsers.as_view(), name='APIUsers'), 10 | url(r'^api/v1/cluster$', NLBCluster.as_view(), name='NLBCluster'), 11 | url(r'^api/v1/iplist$', IPListView.as_view(), name='IPListView'), 12 | url(r'^api/v1/whitelist$', WhitelistView.as_view(), name='WhitelistView'), 13 | url(r'^api/v1/blacklist$', BlacklistView.as_view(), name='BlacklistView'), 14 | ] 15 | -------------------------------------------------------------------------------- /hfnlb_project/nlb_proxy/api/users.py: -------------------------------------------------------------------------------- 1 | from django.views.generic import View 2 | from django.http import HttpResponse 3 | from django.utils.decorators import method_decorator 4 | from django.contrib.auth.decorators import login_required 5 | from django.views.decorators.csrf import csrf_exempt 6 | from django.contrib.auth.models import User 7 | 8 | import re,json 9 | 10 | @method_decorator(csrf_exempt, name='dispatch') 11 | @method_decorator(login_required, name='dispatch') 12 | class APIUsers(View): 13 | def __init__(self, *args, **kwargs): 14 | self.response_data = {'result':'SUCCESS','message':''} 15 | self.status = 200 16 | super().__init__(*args, **kwargs) 17 | 18 | def dataVerify(self): 19 | fields_required = ['username','passwd'] 20 | fields_optional = ['email','realname'] 21 | 22 | form_data = {k:v.strip() for k,v in self.post_data.items() if k in fields_required + fields_optional} 23 | 24 | ### Check fields required. 25 | for field in fields_required: 26 | if field not in form_data: 27 | return "错误:请提供字段'%s'!" % field 28 | return form_data 29 | 30 | def checkName(self, checkExist=False): 31 | name = self.post_data.get('username') 32 | queryset = User.objects.filter(username = name) 33 | if not queryset.exists(): 34 | if not checkExist: 35 | self.response_data.update({'result':'FAILED', 'message':"错误:用户'%s'不存在!" % name}) 36 | self.status = 404 37 | return None 38 | else: 39 | return queryset.get(username = name) 40 | 41 | def saveObject(self, obj): 42 | try: 43 | obj.save() 44 | except: 45 | self.response_data.update({'result':'FAILED', 'message':"错误:数据库错误,请联系管理员!"}) 46 | self.status = 500 47 | 48 | def add(self): 49 | data = self.dataVerify() 50 | if isinstance(data, str): 51 | self.response_data.update({'result':'FAILED', 'message':data}) 52 | self.status = 400 53 | else: 54 | if self.checkName(checkExist=True): 55 | self.response_data.update({'result':'FAILED', 'message':"错误:用户'%s'已存在,请使用其他用户ID!" % data['username']}) 56 | self.status = 400 57 | else: 58 | user = User.objects.create_user(data['username'],email=data['email'],password=data['passwd'],first_name=data['realname']) 59 | self.saveObject(user) 60 | 61 | def delete(self): 62 | user = self.checkName() 63 | if user: 64 | user.delete() 65 | 66 | def edit(self): 67 | user = self.checkName() 68 | data = self.dataVerify() 69 | change = False 70 | if user: 71 | if data.get('realname') and user.first_name != data['realname']: 72 | user.first_name = data['realname'] 73 | change = True 74 | if data.get('email') and user.email != data['email']: 75 | user.email = data['email'] 76 | change = True 77 | if change: 78 | self.saveObject(user) 79 | 80 | def resetPasswd(self): 81 | user = self.checkName() 82 | if user: 83 | passwd = self.post_data.get('newPasswd') 84 | if passwd is not None: 85 | user.set_password(str(passwd).strip()) 86 | self.saveObject(user) 87 | else: 88 | self.response_data.update({'result':'FAILED', 'message':"密码重置错误:请提供一个新密码!"}) 89 | self.status = 400 90 | 91 | def get(self, request, *args, **kwargs): 92 | queryset = User.objects.all() 93 | data = [] 94 | for user in queryset: 95 | attr = {'username':user.username, 96 | 'email':user.email, 97 | 'realname':user.first_name, 98 | 'group':'admin', 99 | } 100 | data.append(attr) 101 | self.response_data['data'] = data 102 | self.response_data['groups'] = [{'lable': '管理员', 'value': 'admin'}, 103 | ] 104 | return HttpResponse(json.dumps(self.response_data),content_type='application/json',status=self.status) 105 | 106 | 107 | def post(self, request, *args, **kwargs): 108 | legal_action = ['add', 'delete', 'edit','resetPasswd'] 109 | self.post_data = {k:v.strip() for k,v in request.POST.items()} 110 | # print(self.post_data) 111 | 112 | action = self.post_data.get('action') 113 | if action in legal_action: 114 | getattr(self, action)() 115 | else: 116 | self.response_data.update({'result':'FAILED','message':"ERROR: action '%s' not support." % action}) 117 | self.status == 400 118 | 119 | return HttpResponse(json.dumps(self.response_data),content_type='application/json',status=self.status) 120 | -------------------------------------------------------------------------------- /hfnlb_project/nlb_proxy/api/whitelist.py: -------------------------------------------------------------------------------- 1 | from django.views.generic import View 2 | from django.utils.decorators import method_decorator 3 | from django.contrib.auth.decorators import login_required 4 | from django.views.decorators.csrf import csrf_exempt 5 | 6 | from nlb_proxy.models import WhiteListStrategy 7 | from .common import APIStrategySaltPushMixin 8 | 9 | @method_decorator(csrf_exempt, name='dispatch') 10 | @method_decorator(login_required, name='dispatch') 11 | class WhitelistView(View, APIStrategySaltPushMixin): 12 | model = WhiteListStrategy 13 | -------------------------------------------------------------------------------- /hfnlb_project/nlb_proxy/bin/nlb_timer: -------------------------------------------------------------------------------- 1 | ../daemon/nlb_timer.py -------------------------------------------------------------------------------- /hfnlb_project/nlb_proxy/config.py: -------------------------------------------------------------------------------- 1 | 2 | WEB_MASTER = '172.16.40.169' 3 | WEB_PORT = '10080' 4 | TIMER_LOG_PATH = '/var/log/nlb_proxy' 5 | TIMER_RUNTIME_INTERVAL = 15 ### Timer程序每隔1秒执行一次 6 | SALT_BIN = '/usr/bin/salt' ### 你的salt bin程序安装在哪儿。默认是‘/usr/bin/salt’ 7 | -------------------------------------------------------------------------------- /hfnlb_project/nlb_proxy/daemon/ReadMe_for_daemon.md: -------------------------------------------------------------------------------- 1 | daemon程序的构思逻辑 2 | ======= 3 | 4 | 5 | 本文旨在大体说明daemon程序的主要构思逻辑。 6 | 7 | 8 | 文档摘要 9 | ------ 10 | 编写时间:2018-06-13 11 | 12 | 作者:雷海龙 13 | 14 | 作者email:leihailong@hfbank.com.cn/alan001lhl@sina.com 15 | 16 | 17 | 功能概要 18 | ------ 19 | 20 | daemon程序,主要包含以下四个任务(每个任务即是daemon程序的一个子进程): 21 | 22 | * server_init: 集群初始化 23 | * manage_keys: 集群成员key的管理 24 | * apply_config: 应用配置推送 25 | * health_check: 成员健康检查 26 | 27 | daemon作为一个timer型的程序,是独立于web服务进程的。每隔一定时间便会并行启动以上四个子进程,开始工作。 28 | 29 | 间隔时间由这个配置项来指定:`nlb_proxy.config.TIMER_RUNTIME_INTERVAL` 30 | 31 | 对于以上四个任务,daemon程序作了进程唯一处理。即,当进入下一次循环,而上一次循环中启动的子进程还未完成时,daemon程序不会启动新的子进程来做相同的任务,以保证处理同一任务的进程唯一。 32 | 33 | daemon程序的所有输出信息,都由日志记录,日志路径定义在配置项:`nlb_proxy.config.TIMER_LOG_PATH` 34 | 35 | 下面分别对以上四个任务的实现做简要说明。 36 | 37 | 38 | server_init 39 | ---------- 40 | 41 | <待续> 42 | 43 | 44 | manage_keys 45 | ---------- 46 | 47 | <待续> 48 | 49 | 50 | 51 | apply_config 52 | ---------- 53 | 54 | <待续> 55 | 56 | 57 | 58 | health_check 59 | ---------- 60 | 61 | 集群成员的健康状态分以下五种情况 62 | (记录在model字段:`nlb_proxy.models.ClusterMember.is_alive`): 63 | 64 | * (0, "没有被master接受") 65 | 66 | 表示这是做完初始化的机器,但是它作为minion的key,还没有被salt-master接受。 67 | 68 | 出现这种状态的话,一般等待daemon程序调动master自动接受即可;若是有问题,需要去查daemon程序的日志,排查问题。 69 | 70 | * (1, "状态正常") 71 | 72 | 表示这台机器的所有状态均正常,是一台健康的机器。 73 | 74 | * (2, "salt-minion状态异常") 75 | 76 | 表示这台机器作为一台minion已无法跟master通信,master无法控制它了。 77 | 78 | 出现这种情况,一般是因为salt-minion挂了,或者key发生了变动。需要系统管理员手动处理。 79 | 80 | * (3, "nginx状态异常") 81 | 82 | 表示salt-minion状态正常,但nginx服务挂了。需要管理员排查nginx问题。 83 | 84 | * (4, "salt-master异常") 85 | 86 | 表示salt-master状态有问题,比如salt-master进程挂了,或者master节点未设置,或者master被移除了集群。 87 | 88 | 检测到salt-master出问题后,health_check进程会将所有成员标注为此状态,直到管理员手工恢复集群。 89 | 》》》如何自动恢复???,好像没有处理逻辑《《《 90 | 91 | 92 | health_check按照如下逻辑顺序执行: 93 | 94 | * 首先,检查salt-minion状态 95 | 96 | 在salt-master上对所有minion做salt的test.ping操作,返回True就认为salt-minion状态正常,否则认为异常。 97 | 98 | 异常的机器健康状态将被设置为2,表示不受master控制的机器。处于这种状态的机器,配置文件是不可信的,有可能跟数据库不同步。 99 | 100 | 当机器的状态由2变为1时,即恢复健康时,health_check进程会从集群中任意找一台健康的机器(以上一次的检查结果为准)为基准,将机器的配置数据clone给刚刚恢复健康的机器,并触发自动推送,以保证恢复的机器配置数据可信。 101 | 102 | 若没有健康的机器作为基准,则无法clone,这表示整个集群都down了。 103 | 104 | 注意,出现这种极端情况时,nginx服务有可能是仍然处于在线状态的。只是本系统的集群管理不再可用,本系统不会再对nginx的应用配置做任何修改,以保证服务稳定。 105 | 106 | 》》》具体如何处理,能不能程序自动处理???《《《 107 | 》》》可以在“集群管理”界面,开发一个“同步数据库”的按钮,让所有机器重新以数据库为基准同步nginx配置文件《《《 108 | 109 | * 然后,检查各机器nginx的状态 110 | 111 | 利用salt,对每台状态为1或3的机器,远程执行`nlb_proxy/scripts/service_check.sh`脚本,检测return_code,若为0,则认为机器正常;非0,即nginx状态异常。 112 | 113 | 注意,其他状态的机器是不需要做这项检查的。 114 | 115 | * 最后,reload各台机器nginx服务。 116 | 117 | 用户在界面修改了应用配置参数,apply_config进程会下发更新的配置文件(注意,下发文件不自动reload nginx),并在数据库中标记各台nginx节点为需要reload的状态。 118 | 119 | 此状态由model字段`nlb_proxy.models.ClusterMember.need_reload`记录。 120 | 121 | health_check进程根据这个字段来对需要reload的机器做reload操作。 122 | 123 | 》》》reload操作对各种非健康机器是如何处理的?《《《 124 | -------------------------------------------------------------------------------- /hfnlb_project/nlb_proxy/daemon/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alan011/NginxLB-cluster/74b2149b0980534e97145ce38023c7c5719a3d34/hfnlb_project/nlb_proxy/daemon/__init__.py -------------------------------------------------------------------------------- /hfnlb_project/nlb_proxy/daemon/apply_handler.py: -------------------------------------------------------------------------------- 1 | from geniusalt.operators import ModuleOperator, InstanceOperator, NodeOperator, RelationOperator, PushOperator 2 | from nlb_proxy.models import HttpProxy, TcpProxy, ClusterMember, PushLog, WhiteListStrategy, BlackListStrategy 3 | from nlb_proxy.tools import Logging 4 | from django.utils import timezone 5 | from .daemon_mixin import DaemonMixin 6 | 7 | class ApplyHandler(DaemonMixin): 8 | httpStrategyModels = (WhiteListStrategy, BlackListStrategy, ) 9 | 10 | def __init__(self, log_file=None): 11 | # self.apply_failed_objects = [] 12 | self.instance_op = InstanceOperator(parameters={}) 13 | self.push_op = PushOperator(parameters={}) 14 | self.module_op = ModuleOperator(parameters={}) 15 | self.relation_op = RelationOperator(parameters={}) 16 | self.log_file = log_file 17 | self.logger = None 18 | if self.log_file: 19 | self.logger = Logging(self.log_file, 'ApplyHandler') 20 | 21 | def getInstance(self, instance): 22 | self.instance_op.parameters = {'name':instance} 23 | result = self.instance_op.show() 24 | if isinstance(result, dict): 25 | for instance_attr in result['data']: 26 | if instance_attr['name'] == instance: 27 | return instance_attr 28 | 29 | def getNodes(self, filter=None): 30 | if filter is not None: 31 | queryset = ClusterMember.objects.filter(**filter) 32 | else: 33 | queryset = ClusterMember.objects.all() 34 | nodes = [] 35 | for obj in queryset: 36 | nodes.append(obj.hostname) 37 | return nodes 38 | 39 | def getModulePillars(self, module): 40 | self.module_op.parameters = {'name':module} 41 | # self.module_op.parameters = {} 42 | result = self.module_op.show() 43 | # print(result) 44 | if isinstance(result, dict): 45 | for module_attr in result['data']: 46 | if module_attr['name'] == module: 47 | return result['data'][0]['pillar_required'] + result['data'][0]['pillar_optional'] 48 | 49 | def logOperatorResult(self, result, obj=None, failed_list=None): 50 | if isinstance(result, dict): 51 | message_lines = result['message'].strip().split('\n') 52 | # print(message_lines) 53 | for line in message_lines: 54 | self.logger.log(line) 55 | if result['result'] != 'SUCCESS': 56 | if failed_list is not None and obj is not None: 57 | failed_list.append(obj) 58 | return result 59 | else: 60 | self.logger.log(result) 61 | if failed_list is not None and obj is not None: 62 | failed_list.append(obj) 63 | 64 | def getUnapplied(self, model): 65 | return model.objects.filter(apply_stat=0,is_deleted=0) 66 | 67 | 68 | def makePillar(self, module, obj): 69 | instance, pillar = '',{} 70 | if module in ('http_proxy', 'tcp_proxy'): 71 | pillar_vars = self.getModulePillars(module) 72 | # print(pillar_vars) 73 | instance = "%s.%s" % (module,obj.app_name) 74 | pillar = {k:getattr(obj, k) for k in pillar_vars if getattr(obj, k)} 75 | elif module in ('whitelist', 'blacklist'): 76 | instance = '%s.%s' % (module,obj.strategy_name) 77 | pillar = {'proxy_domain_name':obj.proxy_belong.proxy_domain_name, 78 | 'iplist':'/'.join([ip_obj.ip for ip_obj in obj.iplist.filter(is_deleted = 0)]), 79 | 'is_disabled':obj.is_disabled,} 80 | return instance, pillar 81 | 82 | def apply(self, model): 83 | nodes = self.getNodes({'is_init':1, 'is_alive':1, 'is_deleted':0}) 84 | if not nodes: 85 | self.logger.log('ERROR: No Nodes available to apply Instances!') 86 | return None 87 | 88 | queryset = self.getUnapplied(model) 89 | apply_failed_objects = [] 90 | 91 | for obj in queryset: 92 | instance, pillar = self.makePillar(self.model_to_module[model], obj) 93 | # print(pillar) 94 | old_attrs = self.getInstance(instance) 95 | if old_attrs is None: 96 | self.logger.log("Trying to add new instance '%s'" % instance) 97 | instance_attrs = {'name': instance, 98 | "module_belong":self.model_to_module[model], 99 | "pillar":pillar.copy(), 100 | } 101 | self.instance_op.parameters = instance_attrs 102 | self.logOperatorResult(self.instance_op.add(), obj, apply_failed_objects) 103 | else: 104 | self.logger.log("Trying to set pillar of instance '%s'" % instance) 105 | # print(pillar) 106 | # print(old_attrs['pillar']) 107 | for p in old_attrs['pillar']: 108 | if p not in pillar: 109 | self.instance_op.parameters = {'name':instance, 'pillar_name':p} 110 | self.logger.log("Trying to delete pillar '%s' of instance '%s'" % (p, instance)) 111 | self.logOperatorResult(self.instance_op.pillarDel(), obj, apply_failed_objects) 112 | self.instance_op.parameters = {'name':instance,'pillar':pillar.copy()} 113 | self.logOperatorResult(self.instance_op.pillarSet(), obj, apply_failed_objects) 114 | 115 | ### Handle strategy instance's relationship with http instance. 116 | new_included = False 117 | if model in self.httpStrategyModels: 118 | http_proxy_instance = "%s.%s" % (self.model_to_module[HttpProxy],obj.proxy_belong.app_name) 119 | self.relation_op.parameters = {'instances': [http_proxy_instance],'included_instances': [instance]} 120 | check_result = self.relation_op.hasInclude() 121 | if isinstance(check_result, dict): 122 | if check_result['check_result'] is False: 123 | self.logOperatorResult(self.relation_op.include(), obj, apply_failed_objects) 124 | if obj not in apply_failed_objects: 125 | new_included = True 126 | else: 127 | self.logger.log(check_result) 128 | 129 | ### push this instance to Nodes 130 | if obj not in apply_failed_objects: 131 | self.push_op.parameters = {'bind_instances':[instance], 'nodes':nodes} 132 | pushlog = {'push_time': timezone.now(), 133 | 'type':model.TYPE, 134 | 'target_id':obj.id, 135 | } 136 | result = self.logOperatorResult(self.push_op.push(), obj, apply_failed_objects) 137 | if result: 138 | pushlog['log_path'] = result['pushlog_path'] 139 | else: 140 | pushlog['message'] = 'ERROR: Push failed at this time and no logs returned.' 141 | 142 | log_obj = PushLog(**pushlog) 143 | try: 144 | log_obj.save() 145 | except: 146 | self.logger.log("ERROR: Trying to save PushLog object for app '%s' failed. Maybe some db related error occurred!" % instance) 147 | 148 | for obj in queryset: 149 | if obj in apply_failed_objects: 150 | obj.apply_stat = 2 151 | else: 152 | obj.apply_stat = 3 153 | obj.push_stat = 1 154 | if model in self.httpStrategyModels and new_included is True: 155 | obj.proxy_belong.apply_stat = 0 156 | obj.proxy_belong.save() 157 | obj.save() 158 | 159 | def applyHandler(self): 160 | # print('Start applying works.') 161 | if self.logger is None: 162 | return "ERROR: This method requires that argument 'log_file' must be provided when you trying to init an 'ApplyHandler'." 163 | for model in self.model_to_module: 164 | self.apply(model) 165 | -------------------------------------------------------------------------------- /hfnlb_project/nlb_proxy/daemon/daemon_mixin.py: -------------------------------------------------------------------------------- 1 | import paramiko 2 | from nlb_proxy.models import NLBConfig, ClusterMember, HttpProxy, TcpProxy, WhiteListStrategy, BlackListStrategy 3 | from nlb_proxy.tools import Logging 4 | 5 | class DaemonMixin(object): 6 | model_to_module = { 7 | HttpProxy: "http_proxy", 8 | TcpProxy: "tcp_proxy", 9 | WhiteListStrategy: 'whitelist', 10 | BlackListStrategy: 'blacklist' 11 | } 12 | 13 | class sshExecutorMixin(object): 14 | def __init__(self, log_file): 15 | self.log_file = log_file 16 | self.logger = None 17 | if self.log_file: 18 | self.logger = Logging(self.log_file) 19 | 20 | def getSetting(self): 21 | queryset = NLBConfig.objects.filter(name = 'initConfig') 22 | if queryset.exists(): 23 | obj = queryset.get(name='initConfig') 24 | return obj.content 25 | else: 26 | self.logger.log('ERROR: server init setting data is not set properly!') 27 | 28 | def remoteConnect(self, hostIP, sysuser, syspasswd): 29 | ### ssh pre-settings 30 | client = paramiko.client.SSHClient() 31 | client.set_missing_host_key_policy(paramiko.client.AutoAddPolicy) 32 | client.load_system_host_keys() 33 | 34 | ### works begin 35 | self.logger.log("Trying to login to host: '%s'..." % hostIP) 36 | try: 37 | client.connect(hostIP, username=sysuser, password=syspasswd) 38 | except paramiko.ssh_exception.NoValidConnectionsError as e: 39 | self.logger.log("ERROR: %s" % str(e) ) 40 | return None 41 | else: 42 | self.logger.log("Login to host '%s'... successfully, ready to run commands." % hostIP) 43 | return client 44 | 45 | def remoteRun(self, client, cmd): 46 | # print(cmd) 47 | stdin, stdout, stderr = client.exec_command(cmd, get_pty=True) 48 | for line in stdout: 49 | self.logger.log(line.strip('\n')) 50 | for line in stderr: 51 | self.logger.log(line.strip('\n')) 52 | return_code = stdout.channel.recv_exit_status() 53 | if return_code != 0: 54 | self.logger.log("ERROR: Command '%s' received error code: %d. Init-works aborted! " % (cmd, return_code)) 55 | return return_code 56 | 57 | def remoteClose(self, ip, client): 58 | client.close() 59 | self.logger.log("Logout from host: '%s'!" % ip) 60 | 61 | def connectToMaster(self, sysuser, syspasswd): 62 | queryset = ClusterMember.objects.filter(role='MASTER', is_deleted=0) 63 | if queryset.exists(): 64 | obj = queryset.get(role='MASTER') 65 | client = self.remoteConnect(obj.ip, sysuser, syspasswd) 66 | if client: 67 | return obj, client 68 | else: 69 | self.logger.log("ERROR: connectToMaster(): No available MASTER found!") 70 | return None, None 71 | -------------------------------------------------------------------------------- /hfnlb_project/nlb_proxy/daemon/health_check.py: -------------------------------------------------------------------------------- 1 | #!/usr/local/bin/python3 2 | from nlb_proxy.models import NLBConfig, ClusterMember 3 | from nlb_proxy.config import WEB_MASTER, WEB_PORT, SALT_BIN 4 | from nlb_proxy.tools import Logging 5 | from geniusalt.operators import RelationOperator 6 | from .daemon_mixin import sshExecutorMixin, DaemonMixin 7 | from os import popen 8 | import yaml, json 9 | 10 | class MinionHealthCheck(sshExecutorMixin, DaemonMixin): 11 | def __init__(self, log_path): 12 | super().__init__(log_path) 13 | self.logger.log_prefix = 'HealthCheck' 14 | self.queryset = ClusterMember.objects.filter(is_init=1, is_deleted=0) 15 | self.master_obj = self.findMaster() 16 | self.remote_script = 'http://%s:%s/scripts/service_check.sh' % (WEB_MASTER, WEB_PORT) 17 | 18 | def yamlHandler(self, result): 19 | try: 20 | result_dict = yaml.load(result) 21 | except yaml.scanner.ScannerError as e: 22 | self.logger.log('ERROR: yaml.load(): '+str(e)) 23 | else: 24 | if isinstance(result_dict, dict): 25 | return result_dict 26 | else: 27 | self.logger.log("ERROR: salt result not a dict: %s" % str(result_dict)) 28 | 29 | def jsonHandler(self, result): 30 | try: 31 | result_dict = json.loads(result) 32 | except json.decoder.JSONDecodeError as e: 33 | self.logger.log('ERROR: json.loads(): ' +str(e)) 34 | else: 35 | if isinstance(result_dict, dict): 36 | return result_dict 37 | else: 38 | self.logger.log("ERROR: salt result not a dict: %s" % str(result_dict)) 39 | 40 | def findMaster(self): 41 | master_obj = None 42 | if self.queryset.exists(): 43 | for obj in self.queryset: 44 | if obj.role == 'MASTER': 45 | master_obj = obj 46 | break 47 | else: 48 | self.logger.log('ERROR: No members found in your cluster.') 49 | if master_obj is None or master_obj.ip != WEB_MASTER: 50 | self.logger.log('ERROR: Master of your cluster is not set properly.') 51 | for obj in self.queryset: 52 | obj.is_alive = 4 53 | obj.save() 54 | 55 | return master_obj 56 | 57 | 58 | def execScriptOnMaster(self, queryset, script_args): 59 | exec_result = {} 60 | for obj in queryset: 61 | exec_result[obj] = False 62 | cmd = "%s --out json %s cmd.script %s %s" % (SALT_BIN,obj.hostname, self.remote_script, script_args ) 63 | result_dict = self.jsonHandler(popen(cmd).read().strip()) 64 | # print(result_dict) 65 | if result_dict: 66 | if isinstance(result_dict[obj.hostname], dict): 67 | if result_dict[obj.hostname]['retcode'] == 0: 68 | exec_result[obj] = True 69 | else: 70 | script_output = result_dict[obj.hostname]['stdout'] + result_dict[obj.hostname]['stderr'] 71 | self.logger.log(script_output.replace('\n', ';')) 72 | else: 73 | self.logger.log("ERROR: Node '%s' return unexpect salt-returns. " % str(result_dict[obj.hostname])) 74 | return exec_result 75 | 76 | def saltHealthCheck(self): 77 | if not self.master_obj: 78 | return None 79 | 80 | healthy_obj = None 81 | for obj in self.queryset: 82 | if obj.is_alive == 1: 83 | healthy_obj = obj 84 | break 85 | 86 | ### Checking... 87 | check_result ={} 88 | for obj in self.queryset: 89 | check_result[obj] = False 90 | cmd = '%s --out json %s test.ping' % (SALT_BIN,obj.hostname) 91 | result_dict = self.jsonHandler(popen(cmd).read().strip()) 92 | if result_dict and result_dict.get(obj.hostname) is True: 93 | check_result[obj] = True 94 | 95 | relation_op = RelationOperator(parameters={}) ### For member clone. 96 | members_need_clone = [] 97 | for obj in check_result: 98 | if check_result[obj]: 99 | ### change member's status from unhealthy to healthy. 100 | if obj.is_alive != 1: 101 | if healthy_obj is not None: 102 | ### clone pillars of this obj from a healthy member and apply these pillars to member. 103 | relation_op.parameters = {'nodes':[obj.hostname], 'clone_target':[healthy_obj.hostname],'push':True} 104 | result = relation_op.clone() 105 | # print(result) 106 | if isinstance(result, dict): 107 | if result['result'] == 'SUCCESS': 108 | obj.is_alive = 1 109 | obj.save() 110 | self.logger.log("Cluster member '%s' up." % obj.hostname) 111 | self.logger.log(result['message']) 112 | else: 113 | self.logger.log(result) 114 | else: 115 | obj.is_alive = 1 116 | obj.save() 117 | ### change member's status from healthy to unhealthy. 118 | elif obj.is_alive == 1: 119 | obj.is_alive = 2 120 | obj.save() 121 | self.logger.log("ERROR: Cluster member '%s' down!" % obj.hostname) 122 | 123 | def nginxHealthCheck(self): 124 | if not self.master_obj: 125 | return None 126 | queryset = self.queryset.filter(is_alive__in = [1,3], is_deleted=0) 127 | check_result = self.execScriptOnMaster(queryset, 'nginx_status') 128 | 129 | for obj in check_result: 130 | if check_result[obj]: 131 | if obj.is_alive == 3: ### Change status from 'nginx unhealthy' to 'healthy'. 132 | obj.is_alive = 1 133 | obj.save() 134 | else: 135 | if obj.is_alive == 1: ### Change status from 'healthy' to 'nginx unhealthy'. 136 | obj.is_alive = 3 137 | obj.save() 138 | 139 | def reloadHealthyNginx(self): 140 | if not self.master_obj: 141 | return None 142 | queryset = self.queryset.filter(is_alive=1, is_deleted=0) 143 | app_objs = [] 144 | for model in self.model_to_module: 145 | app_queryset = model.objects.filter(is_deleted=0, apply_stat=3) 146 | for app in app_queryset: 147 | app_objs.append(app) 148 | 149 | ### To reload healthy nginx. 150 | if app_objs: 151 | reload_result = self.execScriptOnMaster(queryset, 'reload_nginx') 152 | reload_failed = [] 153 | for obj in reload_result: 154 | if reload_result[obj] is False: 155 | reload_faield.append(obj.hostname) 156 | if reload_failed: 157 | obj.need_reload = 1 158 | obj.save() 159 | self.logger.log('ERROR: Some members to reload nginx failed: %s' % str(reload_failed)) 160 | else: 161 | self.logger.log('To reload nginx succeeded on all alive members: %s' % str([obj.hostname for obj in queryset])) 162 | for app_obj in app_objs: 163 | app_obj.apply_stat = 1 164 | app_obj.save() 165 | 166 | def healthCheck(self): 167 | self.saltHealthCheck() 168 | self.nginxHealthCheck() 169 | self.reloadHealthyNginx() 170 | -------------------------------------------------------------------------------- /hfnlb_project/nlb_proxy/daemon/init_handler.py: -------------------------------------------------------------------------------- 1 | from nlb_proxy.models import ClusterMember 2 | from nlb_proxy.config import WEB_MASTER, WEB_PORT 3 | from geniusalt.operators import ModuleOperator, NodeOperator 4 | from .daemon_mixin import sshExecutorMixin 5 | 6 | class ServerInitHandler(sshExecutorMixin): 7 | """ 8 | To define what works should be done when to init a master or a minion. 9 | """ 10 | 11 | def __init__(self, log_file): 12 | super().__init__(log_file) 13 | self.logger.log_prefix = 'ServerInit' 14 | 15 | def getServers(self): 16 | return ClusterMember.objects.exclude(is_init=1).filter(is_deleted=0) 17 | 18 | def getHostname(self, ip, client): 19 | stdin, stdout, stderr = client.exec_command('hostname', get_pty=True) 20 | if stdout.channel.recv_exit_status() != 0: 21 | self.logger.log("ERROR: ServerInitHandler.getHostname(): To get hostname of ip '%s' failed!" % ip) 22 | return None 23 | return ''.join(line.strip() for line in stdout) 24 | 25 | def setMaster(self): 26 | """ 27 | Master is considered to be the node which run webserver and daemon program. 28 | Master should be the only one record in DB. 29 | """ 30 | queryset = ClusterMember.objects.filter(role='MASTER') 31 | if queryset.exists(): 32 | # print("in old.") 33 | master = queryset.get(role='MASTER') 34 | old_ip = master.ip 35 | changed = False 36 | if master.is_deleted != 0: 37 | # print('in deleted.') 38 | master.is_deleted = 0 39 | changed = True 40 | if old_ip != WEB_MASTER: ### Means master has changed in config.py. 41 | # print('in changed ip.') 42 | master.ip = WEB_MASTER 43 | changed = True 44 | self.logger.log("Warning: master has changed from '{}' to '{}'.".format(old_ip, WEB_MASTER)) 45 | if changed: 46 | # print('in reset.') 47 | master.is_init = 0 48 | master.is_alive = 0 49 | master.need_reload = 0 50 | master.save() 51 | 52 | else: 53 | # print('in new master.') 54 | master = ClusterMember(ip=WEB_MASTER,role='MASTER') 55 | master.save() 56 | self.logger.log("To add master with ip '{}' succeeded.".format(WEB_MASTER)) 57 | 58 | ### To init geniusalt with internal-modules. 59 | module_op = ModuleOperator(parameters={}) 60 | modules_check = module_op.show() 61 | if not isinstance(modules_check, dict) or modules_check.get('result') != 'SUCCESS': 62 | self.logger.log("ERROR: geniusalt is not ready!") 63 | elif not modules_check['data']: 64 | result = module_op.scan() 65 | print("===> to scan modules: %s " % result) 66 | if not isinstance(result, dict) or result['result'] != 'SUCCESS': 67 | self.logger.log(" [Error from geniusalt] %s" % result) 68 | 69 | def serverInit(self): 70 | ### To set master with WEB_MASTER in config.py. 71 | self.setMaster() 72 | 73 | ### To init all members, including master. 74 | members = self.getServers() 75 | 76 | initSetting = self.getSetting() 77 | if not initSetting: 78 | return None 79 | 80 | # print("Start server init works. Ip list: %s" % str([obj.ip for obj in members])) 81 | success_members = [] 82 | for obj in members: 83 | works = [] 84 | works.append('wget -q http://%s:%s/scripts/nlb_init.sh -O /tmp/nlb_init.sh' % (WEB_MASTER,WEB_PORT)) 85 | works.append('sudo /bin/bash /tmp/nlb_init.sh %s install_packages' % obj.ip) 86 | if initSetting['sethostname'] == 'Y': 87 | hostname = "%s-%s" % (initSetting['hostnameprefix'],obj.id) 88 | works.append('sudo /bin/bash /tmp/nlb_init.sh %s set_hostname %s' % (obj.ip,hostname)) 89 | works.append('sudo /bin/bash /tmp/nlb_init.sh %s sys_config %s:%s' % (obj.ip,WEB_MASTER, WEB_PORT)) 90 | works.append('sudo /bin/bash /tmp/nlb_init.sh %s master_config %s:%s' % (obj.ip,WEB_MASTER, WEB_PORT)) 91 | works.append('sudo /bin/bash /tmp/nlb_init.sh %s minion_config %s:%s' % (obj.ip,WEB_MASTER, WEB_PORT)) 92 | if obj.role == 'MASTER': 93 | works.append('sudo /bin/bash /tmp/nlb_init.sh %s start_master' % obj.ip) 94 | works.append('sudo /bin/bash /tmp/nlb_init.sh %s start_minion' % obj.ip) 95 | 96 | ### To do the works 97 | client = self.remoteConnect(obj.ip, initSetting['sysuser'], initSetting['syspasswd']) 98 | if client is not None: 99 | success = True 100 | self.logger.log(">>> Server init work start <<<") 101 | for step in works: 102 | return_code = self.remoteRun(client, step) 103 | if return_code != 0: 104 | success = False 105 | break 106 | if success: 107 | host_name = self.getHostname(obj.ip, client) 108 | if host_name: 109 | obj.hostname = host_name 110 | obj.is_init = 1 111 | obj.save() 112 | success_members.append(obj) 113 | 114 | self.remoteClose(obj.ip, client) 115 | self.logger.log(">>> Server init work end! <<<") 116 | 117 | def acceptKeys(self): 118 | """ Tell master to accept new minion-keys """ 119 | # print('Start keys accepting works.') 120 | success_members = ClusterMember.objects.filter(is_init=1, is_alive=0, is_deleted=0) 121 | if success_members.exists(): 122 | node_op = NodeOperator(parameters={}) 123 | result = node_op.scan() 124 | print("===> To scan nodes: %s" % result) 125 | if isinstance(result, dict) and result.get('result') == 'SUCCESS': 126 | for obj in success_members: 127 | obj.is_alive = 1 128 | obj.save() 129 | else: 130 | self.logger.log(" [Error from geniusalt] %s" % result) 131 | 132 | # initSetting = self.getSetting() 133 | # if not initSetting: 134 | # return None 135 | # # print("=========> %s" % initSetting) 136 | # master_obj, master_ssh_client = self.connectToMaster(initSetting['sysuser'],initSetting['syspasswd']) 137 | # if master_obj and master_obj.is_init == 1: 138 | # return_code = self.remoteRun(master_ssh_client, 'sudo /bin/bash /tmp/nlb_init.sh %s accept_keys %s' % (master_obj.ip, ','.join(obj.hostname for obj in success_members))) 139 | # if return_code == 0: 140 | # for obj in success_members: 141 | # obj.is_alive = 1 142 | # obj.save() 143 | # ### To add new nodes to geniusalt 144 | # node_op = NodeOperator(parameters={}) 145 | # node_op.scan() 146 | -------------------------------------------------------------------------------- /hfnlb_project/nlb_proxy/daemon/nlb_timer.py: -------------------------------------------------------------------------------- 1 | #!/usr/local/bin/python3 2 | 3 | import os, sys, django, time 4 | 5 | BASE_DIR = os.path.abspath(os.path.join(os.path.dirname(os.path.abspath(__file__)),'../../')) 6 | sys.path.append(BASE_DIR) 7 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "%s.settings" % os.path.basename(BASE_DIR)) 8 | django.setup() 9 | 10 | from nlb_proxy.daemon.init_handler import ServerInitHandler 11 | from nlb_proxy.daemon.apply_handler import ApplyHandler 12 | from nlb_proxy.daemon.health_check import MinionHealthCheck 13 | from nlb_proxy.tools import Logging 14 | from nlb_proxy import config 15 | # from multiprocessing import Process 16 | from threading import Thread 17 | 18 | class NLBDaemon(object): 19 | interval = config.TIMER_RUNTIME_INTERVAL 20 | 21 | def __init__(self): 22 | self.pool = {'server_init':None, 'manage_keys':None, 'health_check':None, 'apply_config': None} 23 | self.log_file = os.path.join(config.TIMER_LOG_PATH, 'nlb_timer.log') 24 | self.logger = Logging(self.log_file, 'NLBDaemon') 25 | 26 | def worker(self, name, func): 27 | if name in self.pool: 28 | if self.pool[name] and self.pool[name].is_alive(): 29 | pass 30 | # self.logger.log("Subprocess of '%s' is not ending. Ignored it at this runtime." % name) 31 | else: 32 | # self.pool[name] = Process(target=func) 33 | self.pool[name] = Thread(target=func) 34 | self.pool[name].start() 35 | 36 | def run(self): 37 | while True: 38 | server_init = ServerInitHandler(self.log_file) 39 | apply_handler = ApplyHandler(self.log_file) 40 | health_check = MinionHealthCheck(self.log_file) 41 | 42 | self.worker('server_init',server_init.serverInit) 43 | self.worker('manage_keys',server_init.acceptKeys) 44 | self.worker('apply_config',apply_handler.applyHandler) 45 | self.worker('health_check',health_check.healthCheck) 46 | 47 | time.sleep(self.interval) 48 | 49 | 50 | if __name__ == '__main__': 51 | daemon = NLBDaemon() 52 | daemon.run() 53 | -------------------------------------------------------------------------------- /hfnlb_project/nlb_proxy/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.3 on 2018-03-12 18:47 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ] 12 | 13 | operations = [ 14 | ] 15 | -------------------------------------------------------------------------------- /hfnlb_project/nlb_proxy/migrations/0002_httpproxy_tcpproxy.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.3 on 2018-03-12 18:47 3 | from __future__ import unicode_literals 4 | 5 | from django.conf import settings 6 | from django.db import migrations, models 7 | import django.db.models.deletion 8 | import django.utils.timezone 9 | 10 | 11 | class Migration(migrations.Migration): 12 | 13 | initial = True 14 | 15 | dependencies = [ 16 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 17 | ('nlb_proxy', '0001_initial'), 18 | ] 19 | 20 | operations = [ 21 | migrations.CreateModel( 22 | name='HttpProxy', 23 | fields=[ 24 | ('id', models.AutoField(primary_key=True, serialize=False, verbose_name='ID')), 25 | ('proxy_domain_name', models.CharField(default='', max_length=128, unique=True, verbose_name='代理域名')), 26 | ('app_name', models.CharField(default='', max_length=128, unique=True, verbose_name='代理应用')), 27 | ('app_port', models.IntegerField(default=0, verbose_name='应用端口')), 28 | ('app_servers', models.TextField(default='', verbose_name='应用服务器')), 29 | ('app_servers_backup', models.TextField(default='', verbose_name='热备服务器')), 30 | ('other_domain_names', models.TextField(default='', verbose_name='其他代理域名')), 31 | ('proxy_listen_ports', models.TextField(default='', verbose_name='监听端口')), 32 | ('description', models.TextField(default='', verbose_name='描述')), 33 | ('add_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='添加时间')), 34 | ('is_deleted', models.IntegerField(choices=[(0, '否'), (1, '是')], default=0, verbose_name='是否删除')), 35 | ('is_disabled', models.IntegerField(choices=[(0, '否'), (1, '是')], default=0, verbose_name='是否停用')), 36 | ('add_user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='my_httpproxies', to=settings.AUTH_USER_MODEL)), 37 | ], 38 | ), 39 | migrations.CreateModel( 40 | name='TcpProxy', 41 | fields=[ 42 | ('id', models.AutoField(primary_key=True, serialize=False, verbose_name='ID')), 43 | ('app_name', models.CharField(default='', max_length=128, unique=True, verbose_name='应用名称')), 44 | ('app_port', models.IntegerField(default=0, verbose_name='应用端口')), 45 | ('proxy_port', models.IntegerField(default=0, unique=True, verbose_name='代理端口')), 46 | ('app_servers', models.TextField(default='', verbose_name='后端服务器')), 47 | ('app_servers_backup', models.TextField(default='', verbose_name='热备服务器')), 48 | ('description', models.TextField(default='', verbose_name='描述')), 49 | ('add_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='添加时间')), 50 | ('is_deleted', models.IntegerField(choices=[(0, '否'), (1, '是')], default=0, verbose_name='是否删除')), 51 | ('is_disabled', models.IntegerField(choices=[(0, '否'), (1, '是')], default=0, verbose_name='是否停用')), 52 | ('add_user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='my_tcpproxies', to=settings.AUTH_USER_MODEL)), 53 | ], 54 | ), 55 | ] 56 | -------------------------------------------------------------------------------- /hfnlb_project/nlb_proxy/migrations/0003_httpproxy_push_result.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.3 on 2018-03-16 15:34 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 | ('nlb_proxy', '0002_httpproxy_tcpproxy'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name='httpproxy', 17 | name='push_result', 18 | field=models.IntegerField(choices=[(0, '配置下发成功'), (1, '配置下发中'), (2, '配置下发失败')], default=1, verbose_name='推送状态'), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /hfnlb_project/nlb_proxy/migrations/0004_clustermember.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.3 on 2018-03-22 10:24 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 | ('nlb_proxy', '0003_httpproxy_push_result'), 12 | ] 13 | 14 | operations = [ 15 | migrations.CreateModel( 16 | name='ClusterMember', 17 | fields=[ 18 | ('id', models.AutoField(primary_key=True, serialize=False, verbose_name='ID')), 19 | ('hostname', models.CharField(default='', max_length=128, unique=True, verbose_name='主机名')), 20 | ('ip', models.CharField(default='', max_length=128, unique=True, verbose_name='IP地址')), 21 | ('role', models.CharField(choices=[('MASTER', '主节点'), ('MINION', '从节点')], default='MINION', max_length=16, verbose_name='集群角色')), 22 | ('is_init', models.IntegerField(choices=[(0, '否'), (1, '是')], default=0, verbose_name='是否初始化')), 23 | ('is_up', models.IntegerField(choices=[(0, '否'), (1, '是')], default=0, verbose_name='是否存活')), 24 | ], 25 | ), 26 | ] 27 | -------------------------------------------------------------------------------- /hfnlb_project/nlb_proxy/migrations/0005_auto_20180402_1608.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.3 on 2018-04-02 16:08 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 | ('nlb_proxy', '0004_clustermember'), 12 | ] 13 | 14 | operations = [ 15 | migrations.RenameField( 16 | model_name='clustermember', 17 | old_name='is_up', 18 | new_name='is_alive', 19 | ), 20 | migrations.RemoveField( 21 | model_name='httpproxy', 22 | name='push_result', 23 | ), 24 | migrations.AddField( 25 | model_name='httpproxy', 26 | name='push_stat', 27 | field=models.IntegerField(choices=[(0, '等待推送'), (1, '推送成功'), (2, '推送失败'), (3, '无法推送,请联系管理员')], default=0, verbose_name='推送状态'), 28 | ), 29 | migrations.AddField( 30 | model_name='tcpproxy', 31 | name='push_stat', 32 | field=models.IntegerField(choices=[(0, '等待推送'), (1, '推送成功'), (2, '推送失败'), (3, '无法推送,请联系管理员')], default=0, verbose_name='推送状态'), 33 | ), 34 | ] 35 | -------------------------------------------------------------------------------- /hfnlb_project/nlb_proxy/migrations/0006_nlbconfig.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.3 on 2018-04-02 16:57 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | import jsonfield.fields 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | dependencies = [ 12 | ('nlb_proxy', '0005_auto_20180402_1608'), 13 | ] 14 | 15 | operations = [ 16 | migrations.CreateModel( 17 | name='NLBConfig', 18 | fields=[ 19 | ('id', models.AutoField(primary_key=True, serialize=False, verbose_name='ID')), 20 | ('name', models.CharField(choices=[('initConfig', '初始化配置')], default='', max_length=16, unique=True, verbose_name='配置项目')), 21 | ('content', jsonfield.fields.JSONField(default={})), 22 | ], 23 | ), 24 | ] 25 | -------------------------------------------------------------------------------- /hfnlb_project/nlb_proxy/migrations/0007_auto_20180404_1302.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.3 on 2018-04-04 13:02 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 | ('nlb_proxy', '0006_nlbconfig'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterField( 16 | model_name='clustermember', 17 | name='hostname', 18 | field=models.CharField(default='', max_length=128, verbose_name='主机名'), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /hfnlb_project/nlb_proxy/migrations/0008_auto_20180404_1559.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.3 on 2018-04-04 15:59 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 | ('nlb_proxy', '0007_auto_20180404_1302'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterField( 16 | model_name='clustermember', 17 | name='is_init', 18 | field=models.IntegerField(choices=[(0, '未初始化'), (1, '已初始化'), (2, 'master有变动,需重新初始化')], default=0, verbose_name='是否初始化'), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /hfnlb_project/nlb_proxy/migrations/0009_auto_20180423_1430.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.3 on 2018-04-23 14:30 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 | ('nlb_proxy', '0008_auto_20180404_1559'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterField( 16 | model_name='clustermember', 17 | name='is_alive', 18 | field=models.IntegerField(choices=[(0, '等待被master接受'), (1, '状态正常'), (2, '状态异常')], default=0, verbose_name='是否存活'), 19 | ), 20 | migrations.AlterField( 21 | model_name='clustermember', 22 | name='is_init', 23 | field=models.IntegerField(choices=[(0, '未初始化'), (1, '已初始化')], default=0, verbose_name='是否初始化'), 24 | ), 25 | ] 26 | -------------------------------------------------------------------------------- /hfnlb_project/nlb_proxy/migrations/0010_auto_20180507_1550.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.3 on 2018-05-07 15:50 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | import jsonfield.fields 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | dependencies = [ 12 | ('nlb_proxy', '0009_auto_20180423_1430'), 13 | ] 14 | 15 | operations = [ 16 | migrations.CreateModel( 17 | name='PushLog', 18 | fields=[ 19 | ('id', models.AutoField(primary_key=True, serialize=False, verbose_name='ID')), 20 | ('type', models.CharField(choices=[('http', '七层代理'), ('tcp', '四层代理'), ('whitelist', '白名单')], default='', max_length=16, verbose_name='应用类型')), 21 | ('target_id', models.IntegerField(default=0, verbose_name='应用ID')), 22 | ('log_path', jsonfield.fields.JSONField(default={}, verbose_name='日志路径')), 23 | ('message', models.TextField(default='', verbose_name='错误消息')), 24 | ], 25 | ), 26 | migrations.AlterField( 27 | model_name='httpproxy', 28 | name='push_stat', 29 | field=models.IntegerField(choices=[(0, '等待下发'), (1, '下发成功'), (2, '下发失败')], default=0, verbose_name='推送状态'), 30 | ), 31 | migrations.AlterField( 32 | model_name='tcpproxy', 33 | name='push_stat', 34 | field=models.IntegerField(choices=[(0, '等待下发'), (1, '下发成功'), (2, '下发失败')], default=0, verbose_name='推送状态'), 35 | ), 36 | ] 37 | -------------------------------------------------------------------------------- /hfnlb_project/nlb_proxy/migrations/0011_pushlog_push_time.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.3 on 2018-05-07 16:24 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | import django.utils.timezone 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | dependencies = [ 12 | ('nlb_proxy', '0010_auto_20180507_1550'), 13 | ] 14 | 15 | operations = [ 16 | migrations.AddField( 17 | model_name='pushlog', 18 | name='push_time', 19 | field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='推送时间'), 20 | ), 21 | ] 22 | -------------------------------------------------------------------------------- /hfnlb_project/nlb_proxy/migrations/0012_tcpproxy_proxy_domain_name.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.3 on 2018-05-07 17:47 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 | ('nlb_proxy', '0011_pushlog_push_time'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name='tcpproxy', 17 | name='proxy_domain_name', 18 | field=models.CharField(default='', max_length=128, unique=True, verbose_name='代理域名'), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /hfnlb_project/nlb_proxy/migrations/0013_auto_20180509_1646.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.3 on 2018-05-09 16:46 3 | from __future__ import unicode_literals 4 | 5 | from django.conf import settings 6 | from django.db import migrations, models 7 | import django.db.models.deletion 8 | import django.utils.timezone 9 | 10 | 11 | class Migration(migrations.Migration): 12 | 13 | dependencies = [ 14 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 15 | ('nlb_proxy', '0012_tcpproxy_proxy_domain_name'), 16 | ] 17 | 18 | operations = [ 19 | migrations.CreateModel( 20 | name='HttpWhiteListIP', 21 | fields=[ 22 | ('id', models.AutoField(primary_key=True, serialize=False, verbose_name='ID')), 23 | ('ip', models.CharField(default='', max_length=32, unique=True, verbose_name='IP地址')), 24 | ('description', models.TextField(default='', verbose_name='描述')), 25 | ('add_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='添加时间')), 26 | ('is_deleted', models.IntegerField(choices=[(0, '否'), (1, '是')], default=0, verbose_name='是否删除')), 27 | ('add_user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='my_httpwhitelistips', to=settings.AUTH_USER_MODEL)), 28 | ], 29 | ), 30 | migrations.CreateModel( 31 | name='WhiteListStrategy', 32 | fields=[ 33 | ('id', models.AutoField(primary_key=True, serialize=False, verbose_name='ID')), 34 | ('strategy_name', models.CharField(default='', max_length=128, unique=True, verbose_name='策略名称')), 35 | ('http_proxy_id', models.IntegerField(default=-1, unique=True, verbose_name='所属七层代理')), 36 | ('description', models.TextField(default='', verbose_name='描述')), 37 | ('add_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='添加时间')), 38 | ('is_deleted', models.IntegerField(choices=[(0, '否'), (1, '是')], default=0, verbose_name='是否删除')), 39 | ('is_disabled', models.IntegerField(choices=[(0, '否'), (1, '是')], default=0, verbose_name='是否停用')), 40 | ('push_stat', models.IntegerField(choices=[(0, '等待下发'), (1, '下发成功'), (2, '下发失败')], default=0, verbose_name='推送状态')), 41 | ('add_user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='my_httpwhitelists', to=settings.AUTH_USER_MODEL)), 42 | ], 43 | ), 44 | migrations.AlterField( 45 | model_name='clustermember', 46 | name='ip', 47 | field=models.CharField(default='', max_length=32, unique=True, verbose_name='IP地址'), 48 | ), 49 | migrations.AlterField( 50 | model_name='pushlog', 51 | name='target_id', 52 | field=models.IntegerField(default=-1, verbose_name='应用ID'), 53 | ), 54 | migrations.AddField( 55 | model_name='httpwhitelistip', 56 | name='instance_belong', 57 | field=models.ManyToManyField(to='nlb_proxy.WhiteListStrategy'), 58 | ), 59 | ] 60 | -------------------------------------------------------------------------------- /hfnlb_project/nlb_proxy/migrations/0014_auto_20180510_1439.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.3 on 2018-05-10 14:39 3 | from __future__ import unicode_literals 4 | 5 | from django.conf import settings 6 | from django.db import migrations, models 7 | import django.db.models.deletion 8 | import django.utils.timezone 9 | 10 | 11 | class Migration(migrations.Migration): 12 | 13 | dependencies = [ 14 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 15 | ('nlb_proxy', '0013_auto_20180509_1646'), 16 | ] 17 | 18 | operations = [ 19 | migrations.CreateModel( 20 | name='AccessControlIPList', 21 | fields=[ 22 | ('id', models.AutoField(primary_key=True, serialize=False, verbose_name='ID')), 23 | ('ip', models.CharField(default='', max_length=32, unique=True, verbose_name='IP地址')), 24 | ('description', models.TextField(default='', verbose_name='描述')), 25 | ('add_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='添加时间')), 26 | ('is_deleted', models.IntegerField(choices=[(0, '否'), (1, '是')], default=0, verbose_name='是否删除')), 27 | ('add_user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='my_httpwhitelistips', to=settings.AUTH_USER_MODEL)), 28 | ], 29 | ), 30 | migrations.RemoveField( 31 | model_name='httpwhitelistip', 32 | name='add_user', 33 | ), 34 | migrations.RemoveField( 35 | model_name='httpwhitelistip', 36 | name='instance_belong', 37 | ), 38 | migrations.RemoveField( 39 | model_name='whiteliststrategy', 40 | name='http_proxy_id', 41 | ), 42 | migrations.AddField( 43 | model_name='whiteliststrategy', 44 | name='proxy_id', 45 | field=models.IntegerField(default=-1, verbose_name='所属应用代理'), 46 | ), 47 | migrations.AddField( 48 | model_name='whiteliststrategy', 49 | name='proxy_type', 50 | field=models.CharField(choices=[('http', '七层代理'), ('tcp', '四层代理')], default='http', max_length=8, verbose_name='所属代理类型'), 51 | ), 52 | migrations.DeleteModel( 53 | name='HttpWhiteListIP', 54 | ), 55 | migrations.AddField( 56 | model_name='whiteliststrategy', 57 | name='access_ip', 58 | field=models.ManyToManyField(related_name='whitelist_refers', to='nlb_proxy.AccessControlIPList'), 59 | ), 60 | ] 61 | -------------------------------------------------------------------------------- /hfnlb_project/nlb_proxy/migrations/0015_auto_20180511_1415.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.3 on 2018-05-11 14:15 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 | ('nlb_proxy', '0014_auto_20180510_1439'), 12 | ] 13 | 14 | operations = [ 15 | migrations.RemoveField( 16 | model_name='whiteliststrategy', 17 | name='proxy_id', 18 | ), 19 | migrations.RemoveField( 20 | model_name='whiteliststrategy', 21 | name='proxy_type', 22 | ), 23 | migrations.AddField( 24 | model_name='whiteliststrategy', 25 | name='proxy_domain_name', 26 | field=models.CharField(default='', max_length=128, unique=True, verbose_name='所属应用代理'), 27 | ), 28 | ] 29 | -------------------------------------------------------------------------------- /hfnlb_project/nlb_proxy/migrations/0016_auto_20180514_1120.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.3 on 2018-05-14 11:20 3 | from __future__ import unicode_literals 4 | 5 | from django.conf import settings 6 | from django.db import migrations, models 7 | import django.db.models.deletion 8 | 9 | 10 | class Migration(migrations.Migration): 11 | 12 | dependencies = [ 13 | ('nlb_proxy', '0015_auto_20180511_1415'), 14 | ] 15 | 16 | operations = [ 17 | migrations.AddField( 18 | model_name='httpproxy', 19 | name='apply_stat', 20 | field=models.IntegerField(choices=[(0, '等待下发'), (1, '下发成功'), (2, '下发失败')], default=0, verbose_name='下发状态'), 21 | ), 22 | migrations.AlterField( 23 | model_name='httpproxy', 24 | name='push_stat', 25 | field=models.IntegerField(choices=[(0, '新应用,未曾推送过'), (1, '已成功推送多的应用')], default=0, verbose_name='推送状态'), 26 | ), 27 | migrations.AlterField( 28 | model_name='tcpproxy', 29 | name='push_stat', 30 | field=models.IntegerField(choices=[(0, '新应用,未曾推送过'), (1, '已成功推送多的应用')], default=0, verbose_name='推送状态'), 31 | ), 32 | migrations.AlterField( 33 | model_name='whiteliststrategy', 34 | name='add_user', 35 | field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='whitelist_refers', to=settings.AUTH_USER_MODEL), 36 | ), 37 | migrations.AlterField( 38 | model_name='whiteliststrategy', 39 | name='push_stat', 40 | field=models.IntegerField(choices=[(0, '新应用,未曾推送过'), (1, '已成功推送多的应用')], default=0, verbose_name='推送状态'), 41 | ), 42 | ] 43 | -------------------------------------------------------------------------------- /hfnlb_project/nlb_proxy/migrations/0017_auto_20180514_1431.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.3 on 2018-05-14 14:31 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 | ('nlb_proxy', '0016_auto_20180514_1120'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name='tcpproxy', 17 | name='apply_stat', 18 | field=models.IntegerField(choices=[(0, '等待下发'), (1, '下发成功'), (2, '下发失败')], default=0, verbose_name='下发状态'), 19 | ), 20 | migrations.AddField( 21 | model_name='whiteliststrategy', 22 | name='apply_stat', 23 | field=models.IntegerField(choices=[(0, '等待下发'), (1, '下发成功'), (2, '下发失败')], default=0, verbose_name='下发状态'), 24 | ), 25 | ] 26 | -------------------------------------------------------------------------------- /hfnlb_project/nlb_proxy/migrations/0018_blackliststrategy.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.3 on 2018-05-14 15:50 3 | from __future__ import unicode_literals 4 | 5 | from django.conf import settings 6 | from django.db import migrations, models 7 | import django.db.models.deletion 8 | import django.utils.timezone 9 | 10 | 11 | class Migration(migrations.Migration): 12 | 13 | dependencies = [ 14 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 15 | ('nlb_proxy', '0017_auto_20180514_1431'), 16 | ] 17 | 18 | operations = [ 19 | migrations.CreateModel( 20 | name='BlackListStrategy', 21 | fields=[ 22 | ('id', models.AutoField(primary_key=True, serialize=False, verbose_name='ID')), 23 | ('strategy_name', models.CharField(default='', max_length=128, unique=True, verbose_name='策略名称')), 24 | ('proxy_domain_name', models.CharField(default='', max_length=128, unique=True, verbose_name='所属应用代理')), 25 | ('description', models.TextField(default='', verbose_name='描述')), 26 | ('add_time', models.DateTimeField(default=django.utils.timezone.now, verbose_name='添加时间')), 27 | ('is_deleted', models.IntegerField(choices=[(0, '否'), (1, '是')], default=0, verbose_name='是否删除')), 28 | ('is_disabled', models.IntegerField(choices=[(0, '否'), (1, '是')], default=0, verbose_name='是否停用')), 29 | ('push_stat', models.IntegerField(choices=[(0, '新应用,未曾推送过'), (1, '已成功推送多的应用')], default=0, verbose_name='推送状态')), 30 | ('apply_stat', models.IntegerField(choices=[(0, '等待下发'), (1, '下发成功'), (2, '下发失败')], default=0, verbose_name='下发状态')), 31 | ('access_ip', models.ManyToManyField(related_name='blacklist_refers', to='nlb_proxy.AccessControlIPList')), 32 | ('add_user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='blacklist_refers', to=settings.AUTH_USER_MODEL)), 33 | ], 34 | ), 35 | ] 36 | -------------------------------------------------------------------------------- /hfnlb_project/nlb_proxy/migrations/0019_auto_20180514_1628.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.3 on 2018-05-14 16:28 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('nlb_proxy', '0018_blackliststrategy'), 12 | ] 13 | 14 | operations = [ 15 | migrations.RenameField( 16 | model_name='blackliststrategy', 17 | old_name='access_ip', 18 | new_name='iplist', 19 | ), 20 | migrations.RenameField( 21 | model_name='whiteliststrategy', 22 | old_name='access_ip', 23 | new_name='iplist', 24 | ), 25 | ] 26 | -------------------------------------------------------------------------------- /hfnlb_project/nlb_proxy/migrations/0020_auto_20180516_1630.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.3 on 2018-05-16 16:30 3 | from __future__ import unicode_literals 4 | 5 | from django.conf import settings 6 | from django.db import migrations, models 7 | import django.db.models.deletion 8 | 9 | 10 | class Migration(migrations.Migration): 11 | 12 | dependencies = [ 13 | ('nlb_proxy', '0019_auto_20180514_1628'), 14 | ] 15 | 16 | operations = [ 17 | migrations.AlterField( 18 | model_name='accesscontroliplist', 19 | name='add_user', 20 | field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='my_accessips', to=settings.AUTH_USER_MODEL), 21 | ), 22 | migrations.AlterField( 23 | model_name='blackliststrategy', 24 | name='proxy_domain_name', 25 | field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='blacklist_refers', to='nlb_proxy.HttpProxy'), 26 | ), 27 | migrations.AlterField( 28 | model_name='whiteliststrategy', 29 | name='proxy_domain_name', 30 | field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='whitelist_refers', to='nlb_proxy.HttpProxy'), 31 | ), 32 | ] 33 | -------------------------------------------------------------------------------- /hfnlb_project/nlb_proxy/migrations/0021_auto_20180516_1720.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.3 on 2018-05-16 17:20 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('nlb_proxy', '0020_auto_20180516_1630'), 12 | ] 13 | 14 | operations = [ 15 | migrations.RenameField( 16 | model_name='blackliststrategy', 17 | old_name='proxy_domain_name', 18 | new_name='proxy_belong', 19 | ), 20 | migrations.RenameField( 21 | model_name='whiteliststrategy', 22 | old_name='proxy_domain_name', 23 | new_name='proxy_belong', 24 | ), 25 | ] 26 | -------------------------------------------------------------------------------- /hfnlb_project/nlb_proxy/migrations/0022_auto_20180525_1645.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.3 on 2018-05-25 16:45 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 | ('nlb_proxy', '0021_auto_20180516_1720'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name='clustermember', 17 | name='need_reload', 18 | field=models.IntegerField(choices=[(0, '否'), (1, '是')], default=0, verbose_name='是否需要reload'), 19 | ), 20 | migrations.AlterField( 21 | model_name='blackliststrategy', 22 | name='apply_stat', 23 | field=models.IntegerField(choices=[(0, '等待下发'), (1, '已生效'), (2, '下发失败'), (3, '下发成功,等待reload')], default=0, verbose_name='下发状态'), 24 | ), 25 | migrations.AlterField( 26 | model_name='blackliststrategy', 27 | name='push_stat', 28 | field=models.IntegerField(choices=[(0, '新应用,未曾推送过'), (1, '已成功推送过的应用')], default=0, verbose_name='推送状态'), 29 | ), 30 | migrations.AlterField( 31 | model_name='clustermember', 32 | name='is_alive', 33 | field=models.IntegerField(choices=[(0, '等待被master接受'), (1, '状态正常'), (2, 'salt-minion状态异常'), (3, 'nginx状态异常'), (4, 'Master is down!')], default=0, verbose_name='是否存活'), 34 | ), 35 | migrations.AlterField( 36 | model_name='httpproxy', 37 | name='apply_stat', 38 | field=models.IntegerField(choices=[(0, '等待下发'), (1, '已生效'), (2, '下发失败'), (3, '下发成功,等待reload')], default=0, verbose_name='下发状态'), 39 | ), 40 | migrations.AlterField( 41 | model_name='httpproxy', 42 | name='push_stat', 43 | field=models.IntegerField(choices=[(0, '新应用,未曾推送过'), (1, '已成功推送过的应用')], default=0, verbose_name='推送状态'), 44 | ), 45 | migrations.AlterField( 46 | model_name='tcpproxy', 47 | name='apply_stat', 48 | field=models.IntegerField(choices=[(0, '等待下发'), (1, '已生效'), (2, '下发失败'), (3, '下发成功,等待reload')], default=0, verbose_name='下发状态'), 49 | ), 50 | migrations.AlterField( 51 | model_name='tcpproxy', 52 | name='push_stat', 53 | field=models.IntegerField(choices=[(0, '新应用,未曾推送过'), (1, '已成功推送过的应用')], default=0, verbose_name='推送状态'), 54 | ), 55 | migrations.AlterField( 56 | model_name='whiteliststrategy', 57 | name='apply_stat', 58 | field=models.IntegerField(choices=[(0, '等待下发'), (1, '已生效'), (2, '下发失败'), (3, '下发成功,等待reload')], default=0, verbose_name='下发状态'), 59 | ), 60 | migrations.AlterField( 61 | model_name='whiteliststrategy', 62 | name='push_stat', 63 | field=models.IntegerField(choices=[(0, '新应用,未曾推送过'), (1, '已成功推送过的应用')], default=0, verbose_name='推送状态'), 64 | ), 65 | ] 66 | -------------------------------------------------------------------------------- /hfnlb_project/nlb_proxy/migrations/0023_clustermember_is_deleted.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.15 on 2018-09-26 10:13 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 | ('nlb_proxy', '0022_auto_20180525_1645'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name='clustermember', 17 | name='is_deleted', 18 | field=models.IntegerField(choices=[(0, '否'), (1, '是')], default=0, verbose_name='是否删除'), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /hfnlb_project/nlb_proxy/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alan011/NginxLB-cluster/74b2149b0980534e97145ce38023c7c5719a3d34/hfnlb_project/nlb_proxy/migrations/__init__.py -------------------------------------------------------------------------------- /hfnlb_project/nlb_proxy/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from django.utils import timezone 3 | from django.contrib.auth.models import User 4 | from jsonfield import JSONField 5 | 6 | YES_or_NO=((0,'否'),(1,'是')) 7 | # PUSH_STATS = ((0,"等待下发"), (1,"下发成功"),(2,"下发失败"),(3,"多次尝试后,配置仍无法应用到各节点,请联系管理员!")) 8 | PUSH_STATS = ((0,"新应用,未曾推送过"),(1,"已成功推送过的应用")) 9 | APPLY_STATS = ((0,"等待下发"),(1,"已生效"),(2,"下发失败"),(3,"下发成功,等待reload")) 10 | CLUSTER_ROLES = (('MASTER','主节点'),('MINION','从节点')) 11 | 12 | class HttpProxy(models.Model): 13 | TYPE = 'http' 14 | 15 | id = models.AutoField('ID',primary_key=True) 16 | proxy_domain_name = models.CharField('代理域名', max_length=128, default='', unique=True) 17 | app_name = models.CharField('代理应用',max_length=128, default='', unique=True) 18 | app_port = models.IntegerField('应用端口', default=0) 19 | app_servers = models.TextField('应用服务器', default='') 20 | app_servers_backup = models.TextField('热备服务器', default='') 21 | other_domain_names = models.TextField('其他代理域名', default='') 22 | proxy_listen_ports = models.TextField('监听端口', default='') 23 | description = models.TextField('描述', default='') 24 | add_time = models.DateTimeField('添加时间', default=timezone.now) 25 | add_user = models.ForeignKey(User, on_delete=models.SET_NULL, related_name="my_httpproxies", null=True, blank=True) 26 | is_deleted = models.IntegerField('是否删除',choices=YES_or_NO, default=0) 27 | is_disabled = models.IntegerField('是否停用',choices=YES_or_NO, default=0) 28 | push_stat = models.IntegerField('推送状态',choices=PUSH_STATS, default=0) ### 标识是否是新添加的应用代理。 29 | apply_stat = models.IntegerField('下发状态',choices=APPLY_STATS, default=0) ### 标识配置是否成功推送到各个nginx节点,并在nginx中是否生效。 30 | 31 | 32 | class TcpProxy(models.Model): 33 | TYPE = 'tcp' 34 | 35 | id = models.AutoField('ID',primary_key=True) 36 | proxy_domain_name = models.CharField('代理域名', max_length=128, default='', unique=True) 37 | app_name = models.CharField('应用名称',max_length=128, default='', unique=True) 38 | app_port = models.IntegerField('应用端口', default=0) 39 | proxy_port = models.IntegerField('代理端口', default=0, unique=True) 40 | app_servers = models.TextField('后端服务器', default='') 41 | app_servers_backup = models.TextField('热备服务器', default='') 42 | description = models.TextField('描述', default='') 43 | add_time = models.DateTimeField('添加时间', default=timezone.now) 44 | add_user = models.ForeignKey(User, on_delete=models.SET_NULL, related_name="my_tcpproxies", null=True, blank=True) 45 | is_deleted = models.IntegerField('是否删除',choices=YES_or_NO, default=0) 46 | is_disabled = models.IntegerField('是否停用',choices=YES_or_NO, default=0) 47 | push_stat = models.IntegerField('推送状态',choices=PUSH_STATS, default=0) ### 标识是否是新添加的应用代理。 48 | apply_stat = models.IntegerField('下发状态',choices=APPLY_STATS, default=0) ### 配置是否成功推送到各个nginx节点。 49 | 50 | class AccessControlIPList(models.Model): 51 | id = models.AutoField('ID',primary_key=True) 52 | ip = models.CharField('IP地址',max_length=32, default='', unique=True) 53 | description = models.TextField('描述', default='') 54 | add_time = models.DateTimeField('添加时间', default=timezone.now) 55 | add_user = models.ForeignKey(User, on_delete=models.SET_NULL, related_name="my_accessips", null=True, blank=True) 56 | is_deleted = models.IntegerField('是否删除',choices=YES_or_NO, default=0) 57 | 58 | class WhiteListStrategy(models.Model): 59 | TYPE = 'WhiteListStrategy' 60 | # PROXY_TYPE_CHOICES = (('http','七层代理'),('tcp','四层代理')) 61 | 62 | id = models.AutoField('ID',primary_key=True) 63 | strategy_name = models.CharField('策略名称',max_length=128, default='', unique=True) 64 | # proxy_domain_name = models.CharField('所属应用代理',max_length=128, default='', unique=True) 65 | proxy_belong = models.OneToOneField(HttpProxy, on_delete=models.SET_NULL, related_name="whitelist_refers", null=True, blank=True) 66 | # proxy_type = models.CharField('所属代理类型',max_length=8, choices=PROXY_TYPE_CHOICES, default='http') 67 | iplist = models.ManyToManyField(AccessControlIPList, related_name='whitelist_refers') 68 | description = models.TextField('描述', default='') 69 | add_time = models.DateTimeField('添加时间', default=timezone.now) 70 | add_user = models.ForeignKey(User, on_delete=models.SET_NULL, related_name="whitelist_refers", null=True, blank=True) 71 | is_deleted = models.IntegerField('是否删除',choices=YES_or_NO, default=0) 72 | is_disabled = models.IntegerField('是否停用',choices=YES_or_NO, default=0) 73 | push_stat = models.IntegerField('推送状态',choices=PUSH_STATS, default=0) ### 标识是否是新添加的应用代理。 74 | apply_stat = models.IntegerField('下发状态',choices=APPLY_STATS, default=0) ### 配置是否成功推送到各个nginx节点。 75 | 76 | class BlackListStrategy(models.Model): 77 | TYPE = 'BlackListStrategy' 78 | # PROXY_TYPE_CHOICES = (('http','七层代理'),('tcp','四层代理')) 79 | 80 | id = models.AutoField('ID',primary_key=True) 81 | strategy_name = models.CharField('策略名称',max_length=128, default='', unique=True) 82 | # proxy_domain_name = models.CharField('所属应用代理',max_length=128, default='', unique=True) 83 | proxy_belong = models.OneToOneField(HttpProxy, on_delete=models.SET_NULL, related_name="blacklist_refers", null=True, blank=True) 84 | # proxy_type = models.CharField('所属代理类型',max_length=8, choices=PROXY_TYPE_CHOICES, default='http') 85 | iplist = models.ManyToManyField(AccessControlIPList, related_name='blacklist_refers') 86 | description = models.TextField('描述', default='') 87 | add_time = models.DateTimeField('添加时间', default=timezone.now) 88 | add_user = models.ForeignKey(User, on_delete=models.SET_NULL, related_name="blacklist_refers", null=True, blank=True) 89 | is_deleted = models.IntegerField('是否删除',choices=YES_or_NO, default=0) 90 | is_disabled = models.IntegerField('是否停用',choices=YES_or_NO, default=0) 91 | push_stat = models.IntegerField('推送状态',choices=PUSH_STATS, default=0) ### 标识是否是新添加的应用代理。 92 | apply_stat = models.IntegerField('下发状态',choices=APPLY_STATS, default=0) ### 配置是否成功推送到各个nginx节点。 93 | 94 | 95 | class ClusterMember(models.Model): 96 | #INIT_STAT = ((0,'未初始化'),(1,'已初始化'),(2,'等待生效的master'), (3,'等待移除master角色')) 97 | INIT_STAT = ((0,'未初始化'),(1,'已初始化')) 98 | ALIVE_STAT = ((0, '等待被master接受'), (1, '状态正常'), (2, 'salt-minion状态异常'),(3,'nginx状态异常'),(4,'Master is down!')) 99 | id = models.AutoField('ID',primary_key=True) 100 | ip = models.CharField('IP地址',max_length=32, default='', unique=True) 101 | hostname = models.CharField('主机名',max_length=128, default='') 102 | role = models.CharField('集群角色', max_length=16, choices=CLUSTER_ROLES, default='MINION') 103 | is_init = models.IntegerField('是否初始化',choices=INIT_STAT, default=0) #安装salt-minion, 优化系统参数。 104 | is_alive = models.IntegerField('是否存活',choices=ALIVE_STAT, default=0) #主要检查是否还受salt-master控制。 105 | need_reload = models.IntegerField('是否需要reload',choices=YES_or_NO, default=0) 106 | is_deleted = models.IntegerField('是否删除', choices=YES_or_NO, default=0) 107 | 108 | class NLBConfig(models.Model): 109 | NAMES = (('initConfig','初始化配置'),) 110 | id = models.AutoField('ID',primary_key=True) 111 | name = models.CharField('配置项目',max_length=16, choices=NAMES, default='', unique=True) 112 | content = JSONField(default={}) ### 存储字符串键值对儿。 113 | 114 | class PushLog(models.Model): 115 | TYPE_CHOICES = (('http', '七层代理'),('tcp', '四层代理'),('whitelist','白名单')) 116 | id = models.AutoField('ID',primary_key=True) 117 | type = models.CharField('应用类型',max_length=16, choices=TYPE_CHOICES, default='') 118 | target_id = models.IntegerField('应用ID', default=-1) 119 | log_path = JSONField('日志路径', default={}) 120 | message = models.TextField('错误消息', default='') 121 | push_time = models.DateTimeField('推送时间', default=timezone.now) 122 | -------------------------------------------------------------------------------- /hfnlb_project/nlb_proxy/scripts/configfiles/master: -------------------------------------------------------------------------------- 1 | worker_threads: 4 2 | #external_auth: 3 | # pam: 4 | # appmgr: 5 | # - .* 6 | state_verbose: False 7 | state_output: changes 8 | file_roots: 9 | base: 10 | - /data1/salt 11 | -------------------------------------------------------------------------------- /hfnlb_project/nlb_proxy/scripts/configfiles/minion: -------------------------------------------------------------------------------- 1 | master: 2 | - 172.16.40.169 3 | multiprocessing: True 4 | -------------------------------------------------------------------------------- /hfnlb_project/nlb_proxy/scripts/configfiles/sysctl.conf: -------------------------------------------------------------------------------- 1 | net.ipv4.ip_forward = 0 2 | net.ipv4.conf.default.rp_filter = 1 3 | net.ipv4.conf.default.accept_source_route = 0 4 | kernel.sysrq = 0 5 | kernel.core_uses_pid = 1 6 | net.ipv4.tcp_syncookies = 1 7 | kernel.msgmnb = 65536 8 | kernel.msgmax = 65536 9 | kernel.shmmax = 68719476736 10 | kernel.shmall = 4294967296 11 | net.core.netdev_max_backlog = 30000 12 | net.core.rmem_max = 16777216 13 | net.core.somaxconn = 262144 14 | net.core.wmem_max = 16777216 15 | net.ipv4.route.gc_timeout = 20 16 | net.ipv4.tcp_fin_timeout = 10 17 | net.ipv4.tcp_keepalive_time = 20 18 | net.ipv4.tcp_max_orphans = 262144 19 | net.ipv4.tcp_max_syn_backlog = 655360 20 | net.ipv4.tcp_no_metrics_save = 1 21 | net.ipv4.tcp_rmem = 4096 87380 16777216 22 | net.ipv4.tcp_syn_retries = 1 23 | net.ipv4.tcp_synack_retries = 1 24 | net.ipv4.tcp_syncookies = 1 25 | net.ipv4.tcp_timestamps = 1 26 | net.ipv4.tcp_tw_recycle = 1 27 | net.ipv4.tcp_tw_reuse = 1 28 | net.ipv4.tcp_max_tw_buckets = 20480 29 | net.ipv4.tcp_wmem = 4096 65536 16777216 30 | -------------------------------------------------------------------------------- /hfnlb_project/nlb_proxy/scripts/nlb_init.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | SELF_IP=$1 4 | ACTION=$2 5 | OTHER_ARGS=$3 6 | 7 | function safe_rm(){ 8 | if [ -f $1 ]; then 9 | mv $1 /tmp/`basename $1`.`date +'%s.%N'` 10 | fi 11 | } 12 | 13 | function check_params(){ 14 | [ -z "$SELF_IP" ] && echo "ERROR: Parameter 'SELF_IP' master be given as \$1." && exit 1 15 | [ -z "$ACTION" ] && echo "ERROR: Parameter 'ACTION' master be given as \$2." && exit 1 16 | echo "===> ${ACTION}..." 17 | # echo "SELF_IP: ${SELF_IP}, ACTION: ${ACTION}, OTHER_ARGS: ${OTHER_ARGS}, WEB_MASTER: ${WEB_MASTER}, SET_HNAME: ${SET_HNAME}" && exit 1 18 | 19 | 20 | if [[ "$ACTION" =~ ^.*_config$ ]]; then 21 | if [ -z "$OTHER_ARGS" ]; then 22 | echo "ERROR: Parameter 'WEB_MASTER' master be given as \$3." && exit 1 23 | else 24 | WEB_MASTER=$OTHER_ARGS 25 | fi 26 | elif [ "$ACTION" == "set_hostname" ]; then 27 | if [ -z "$OTHER_ARGS" ]; then 28 | echo "ERROR: Parameter 'SET_HNAME' master be given as \$3." && exit 1 29 | else 30 | SET_HNAME=$OTHER_ARGS 31 | fi 32 | elif [ "$ACTION" == 'accept_keys' ]; then 33 | if [ -z "$OTHER_ARGS" ]; then 34 | echo "ERROR: Parameter 'MINION_KEYS' master be given as \$3." && exit 1 35 | else 36 | MINION_KEYS=$OTHER_ARGS 37 | fi 38 | fi 39 | } 40 | 41 | function install_packages(){ 42 | yum install -y salt-master salt-minion 43 | [ $? -ne 0 ] && echo "ERROR: To install packages failed." && exit 1 44 | echo "Complete!" 45 | } 46 | 47 | function master_config(){ 48 | wget -q http://$WEB_MASTER/scripts/configfiles/master -O /etc/salt/master 49 | [ $? -ne 0 ] && exit 1 50 | echo "Complete!" 51 | } 52 | 53 | function minion_config(){ 54 | wget -q http://$WEB_MASTER/scripts/configfiles/minion -O /etc/salt/minion 55 | [ $? -ne 0 ] && exit 1 56 | echo `hostname` > /etc/salt/minion_id 57 | echo "Complete!" 58 | } 59 | 60 | function start_master(){ 61 | /etc/init.d/salt-master start 62 | [ $? -ne 0 ] && exit 1 63 | echo "Complete!" 64 | } 65 | 66 | function start_minion() { 67 | /etc/init.d/salt-minion start 68 | [ $? -ne 0 ] && exit 1 69 | echo "Complete!" 70 | } 71 | 72 | function stop_master() { 73 | /etc/init.d/salt-master stop 74 | [ $? -ne 0 ] && exit 1 75 | echo "Complete!" 76 | } 77 | 78 | function stop_minion() { 79 | /etc/init.d/salt-minion stop 80 | [ $? -ne 0 ] && exit 1 81 | echo "Complete!" 82 | } 83 | 84 | function set_hostname() { 85 | hostname $SET_HNAME 86 | echo "NETWORKING=yes 87 | HOSTNAME=$SET_HNAME" > /etc/sysconfig/network 88 | sed -i "/^${SELF_IP} ${SET_HNAME}$/ d" /etc/hosts 89 | echo "$SELF_IP $SET_HNAME" >> /etc/hosts 90 | echo "Complete!" 91 | } 92 | 93 | function sys_config() { 94 | if [ ! -f /etc/sysctl.conf_backup_by_nlb ]; then 95 | mv /etc/sysctl.conf /etc/sysctl.conf_backup_by_nlb 96 | fi 97 | wget -q http://${WEB_MASTER}/scripts/configfiles/sysctl.conf -O /etc/sysctl.conf 98 | if [ $? -ne 0 ]; then 99 | if [ -f /etc/sysctl.conf_backup_by_nlb ]; then 100 | safe_rm /etc/sysctl.conf 101 | mv /etc/sysctl.conf_backup_by_nlb /etc/sysctl.conf 102 | fi 103 | exit 1 104 | else 105 | sysctl -p /etc/sysctl.conf 106 | echo "Complete!" 107 | fi 108 | } 109 | 110 | function accept_keys() { 111 | salt-key -a ${MINION_KEYS} -y 112 | [ $? -ne 0 ] && exit 1 113 | echo "Complete!" 114 | } 115 | 116 | #--------------- main ------------------ 117 | 118 | check_params 119 | 120 | echo "$ACTION" | egrep '^(install_packages|master_config|minion_config|start_master|start_minion|stop_master|stop_minion|set_hostname|sys_config|accept_keys)$' > /dev/null 121 | [ $? -ne 0 ] && echo "ERROR: ACTION '$ACTION' not support!" && exit 1 122 | 123 | $ACTION 124 | -------------------------------------------------------------------------------- /hfnlb_project/nlb_proxy/scripts/service_check.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ACTION=$1 4 | host_name=`hostname` 5 | 6 | function check_params(){ 7 | [ -z "$ACTION" ] && echo "ERROR: Parameter 'ACTION' master be given as \$1." && exit 1 8 | echo "===> [${host_name}] ${ACTION}..." 9 | } 10 | 11 | function start_nginx(){ 12 | /etc/init.d/nginx start 13 | [ $? -ne 0 ] && exit 1 14 | echo "Complete!" 15 | } 16 | 17 | function stop_nginx(){ 18 | /etc/init.d/nginx stop 19 | [ $? -ne 0 ] && exit 1 20 | echo "Complete!" 21 | } 22 | 23 | function reload_nginx(){ 24 | /etc/init.d/nginx reload 25 | [ $? -ne 0 ] && exit 1 26 | echo "Complete!" 27 | } 28 | 29 | function check_nginx_config(){ 30 | /usr/sbin/nginx -t 31 | [ $? -ne 0 ] && exit 1 32 | echo "Complete!" 33 | } 34 | 35 | function nginx_status(){ 36 | /etc/init.d/nginx status 37 | [ $? -ne 0 ] && exit 1 38 | exit 0 39 | } 40 | 41 | function start_master(){ 42 | /etc/init.d/salt-master start 43 | [ $? -ne 0 ] && exit 1 44 | echo "Complete!" 45 | } 46 | 47 | function start_minion() { 48 | /etc/init.d/salt-minion start 49 | [ $? -ne 0 ] && exit 1 50 | echo "Complete!" 51 | } 52 | 53 | function stop_master() { 54 | /etc/init.d/salt-master stop 55 | [ $? -ne 0 ] && exit 1 56 | echo "Complete!" 57 | } 58 | 59 | function stop_minion() { 60 | /etc/init.d/salt-minion stop 61 | [ $? -ne 0 ] && exit 1 62 | echo "Complete!" 63 | } 64 | 65 | #--------------- main ------------------ 66 | 67 | check_params 68 | 69 | echo "$ACTION" | egrep '^(start_master|start_minion|stop_master|stop_minion|start_nginx|stop_nginx|reload_nginx|check_nginx_config|nginx_status)$' > /dev/null 70 | [ $? -ne 0 ] && echo "ERROR: ACTION '$ACTION' not support!" && exit 1 71 | 72 | $ACTION 73 | -------------------------------------------------------------------------------- /hfnlb_project/nlb_proxy/static/6f0a76321d30f3c8120915e57f7bd77e.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alan011/NginxLB-cluster/74b2149b0980534e97145ce38023c7c5719a3d34/hfnlb_project/nlb_proxy/static/6f0a76321d30f3c8120915e57f7bd77e.ttf -------------------------------------------------------------------------------- /hfnlb_project/nlb_proxy/static/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alan011/NginxLB-cluster/74b2149b0980534e97145ce38023c7c5719a3d34/hfnlb_project/nlb_proxy/static/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /hfnlb_project/nlb_proxy/static/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alan011/NginxLB-cluster/74b2149b0980534e97145ce38023c7c5719a3d34/hfnlb_project/nlb_proxy/static/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /hfnlb_project/nlb_proxy/static/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alan011/NginxLB-cluster/74b2149b0980534e97145ce38023c7c5719a3d34/hfnlb_project/nlb_proxy/static/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /hfnlb_project/nlb_proxy/static/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alan011/NginxLB-cluster/74b2149b0980534e97145ce38023c7c5719a3d34/hfnlb_project/nlb_proxy/static/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /hfnlb_project/nlb_proxy/static/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alan011/NginxLB-cluster/74b2149b0980534e97145ce38023c7c5719a3d34/hfnlb_project/nlb_proxy/static/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /hfnlb_project/nlb_proxy/static/img/1111.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alan011/NginxLB-cluster/74b2149b0980534e97145ce38023c7c5719a3d34/hfnlb_project/nlb_proxy/static/img/1111.png -------------------------------------------------------------------------------- /hfnlb_project/nlb_proxy/static/img/nlb_background.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alan011/NginxLB-cluster/74b2149b0980534e97145ce38023c7c5719a3d34/hfnlb_project/nlb_proxy/static/img/nlb_background.jpeg -------------------------------------------------------------------------------- /hfnlb_project/nlb_proxy/static/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | HFBank Load Balance 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /hfnlb_project/nlb_proxy/static/index_used.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | HFBank Load Balance 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /hfnlb_project/nlb_proxy/static/login/6f0a76321d30f3c8120915e57f7bd77e.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alan011/NginxLB-cluster/74b2149b0980534e97145ce38023c7c5719a3d34/hfnlb_project/nlb_proxy/static/login/6f0a76321d30f3c8120915e57f7bd77e.ttf -------------------------------------------------------------------------------- /hfnlb_project/nlb_proxy/static/login/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | HFBank Load Balance 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /hfnlb_project/nlb_proxy/static/login/index_login.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | HFBank Load Balance 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /hfnlb_project/nlb_proxy/static/login/manifest.js: -------------------------------------------------------------------------------- 1 | !function(e){function r(n){if(t[n])return t[n].exports;var o=t[n]={i:n,l:!1,exports:{}};return e[n].call(o.exports,o,o.exports,r),o.l=!0,o.exports}var n=window.webpackJsonp;window.webpackJsonp=function(t,c,a){for(var u,i,f,l=0,s=[];l 32: 37 | return False 38 | return True 39 | 40 | def static_render(template_name): 41 | root_dir= os.path.join(settings.BASE_DIR, 'nlb_proxy/static') 42 | file_name = os.path.join(root_dir, template_name) 43 | if os.path.isfile(file_name): 44 | return open(file_name).read() 45 | else: 46 | return "ERROR: Static template not exist: %s" % file_name 47 | 48 | def unique_list_elements(l): 49 | new_list = [] 50 | for i in l: 51 | if i not in new_list: 52 | new_list.append(i) 53 | return new_list 54 | 55 | class InitException(Exception): 56 | pass 57 | 58 | class Logging(object): 59 | def __init__(self, log_file, log_prefix=None): 60 | self.log_file = log_file 61 | self.log_path = os.path.dirname(log_file) 62 | if not self.log_path: 63 | raise InitException('ERROR: Log path cannot be empty!') 64 | if not os.path.isdir(self.log_path): 65 | os.makedirs(self.log_path) 66 | self.f_obj = open(self.log_file, 'a') 67 | self.log_prefix = log_prefix 68 | 69 | def log(self,record_line): 70 | if self.log_prefix: 71 | log_line = "%s [%s] %s\n" % (datetime.datetime.now().strftime('%F %T'), self.log_prefix, record_line) 72 | else: 73 | log_line = "%s %s\n" % (datetime.datetime.now().strftime('%F %T'), record_line) 74 | self.f_obj.write(log_line) 75 | self.f_obj.flush() 76 | 77 | class MyThread(Thread): 78 | def __init__(self, works): 79 | self.works = works 80 | self.works_return = None 81 | super().__init__() 82 | 83 | def run(self): 84 | self.works_return = self.works() 85 | -------------------------------------------------------------------------------- /hfnlb_project/nlb_proxy/views.py: -------------------------------------------------------------------------------- 1 | from django.http import HttpResponse, HttpResponseRedirect 2 | from django.views.generic import View 3 | from django.contrib.auth.decorators import login_required 4 | from django.utils.decorators import method_decorator 5 | from django.contrib.auth import logout 6 | from django.http import FileResponse 7 | from django.conf import settings 8 | 9 | from nlb_proxy.tools import static_render 10 | 11 | import os 12 | 13 | @method_decorator(login_required, name='dispatch') 14 | class MyIndex(View): 15 | template_name = 'index_used.html' 16 | 17 | def get(self, request, *args, **kwargs): 18 | return HttpResponse(static_render(self.template_name)) 19 | 20 | class MyLogin(View): 21 | template_name = 'login/index_login.html' 22 | def get(self, request, *args, **kwargs): 23 | return HttpResponse(static_render(self.template_name)) 24 | 25 | class MyLogout(View): 26 | def get(self, request, *args, **kwargs): 27 | logout(request) 28 | return HttpResponseRedirect('/login/') 29 | 30 | class ScriptServer(View): 31 | def get(self, request, *args, **kwargs): 32 | response = FileResponse(open(os.path.join(settings.BASE_DIR,'nlb_proxy/scripts/%s' % kwargs['filename']), 'rb'), content_type='application/force-download') 33 | response['Content-Disposition'] = 'attachment; filename=%s' % os.path.basename(kwargs['filename']) 34 | return response 35 | -------------------------------------------------------------------------------- /hfnlb_project/start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | python3 manage.py runserver 0.0.0.0:10080 4 | --------------------------------------------------------------------------------