CMDB后端开发(下)
API平台开发
新建主机功能
腾讯云
- 在线API调试平台:https://console.cloud.tencent.com/api/explorer
- API文档:https://cloud.tencent.com/document/product/296/19850
- Python SDK:https://github.com/TencentCloud/tencentcloud-sdk-python
- 获取AceessKey地址:https://console.cloud.tencent.com/cam/capi
- 安装腾讯云sdk:
pip install tencentcloud-sdk-python -i https://mirrors.tencent.com/pypi/simple/
- 腾讯云信息获取脚本: devops_api/libs/tencent_cloud.py
from tencentcloud.common import credential
from tencentcloud.common.exception.tencent_cloud_sdk_exception import TencentCloudSDKException
from tencentcloud.cvm.v20170312 import cvm_client, models
class TCloud():
def __init__(self, secret_id, secret_key):
self.secret_id = secret_id
self.secret_key = secret_key
self.cred = credential.Credential(self.secret_id, self.secret_key)
def region_list(self):
client = cvm_client.CvmClient(self.cred, None)
req = models.DescribeRegionsRequest() # 获取地区
try:
resp = client.DescribeRegions(req) # resp=[{"Region": "ap-guangzhou", "RegionName": "华南地区(广州)", "RegionState": "AVAILABLE"}, ]
resp.code = 200
return resp
except TencentCloudSDKException as e:
return {'code': '500', 'msg': e.message}
def zone_list(self, region_id):
client = cvm_client.CvmClient(self.cred, region_id)
req = models.DescribeZonesRequest()
try:
resp = client.DescribeZones(req)
resp.code = 200
return resp
except TencentCloudSDKException as e:
return {'code': '500', 'msg': e.message}
def instance_list(self, region_id):
client = cvm_client.CvmClient(self.cred, region_id) # 获取北京区域
req = models.DescribeInstancesRequest()
# req.InstanceIds = "ins-1511w4tn" #根据实例id获取
try:
resp = client.DescribeInstances(req)
resp.code = 200
return resp
except TencentCloudSDKException as e:
return {'code': '500', 'msg': e.message}
if __name__ == '__main__':
cloud = TCloud("AKIDKUP3QWNxGI4ZnkQpwnMgiTxA7mAH8i02", "nJYWDtJSKwGJ5aokPownMegRu61f27wU")
#result = cloud.region_list()
#result = cloud.zone_list("ap-beijing")
result = cloud.instance_list("ap-beijing")
print(result)
- 获取腾讯云主机region, 示例信息接口: devops_api/cmdb/views.py
...
from libs.tencent_cloud import TCloud
class TencentCloudView(APIView):
"""
腾讯云获取云主机信息
"""
def get(self, request):
"""
返回所有区域
"""
secret_id = request.query_params.get('secret_id')
secret_key = request.query_params.get('secret_key')
# cloud = TencentCloud("AKIDKUP3QWNxGI4ZnkQpwnMgiTxA7mAH8i02", "nJYWDtJSKwGJ5aokPownMegRu61f27wU")
cloud = TCloud(secret_id, secret_key)
region_result = cloud.region_list()
code = region_result.code
if code == 200:
# 二次处理,固定字段名,与阿里云的一致
region = []
for r in region_result.RegionSet:
region.append({"region_id": r.Region, 'region_name': r.RegionName})
res = {'code': code, 'msg': '获取区域列表成功!', 'data': region}
else:
res = {'code': code, 'msg': region_result.msg}
return Response(res)
def post(self, request):
"""
根据区域名称创建机房,再导入云主机(关联机房)到数据库
"""
secret_id = request.data.get('secret_id')
secret_key = request.data.get('secret_key')
server_group_id = int(request.data.get('server_group'))
region_id = request.data.get('region')
ssh_ip = request.data.get('ssh_ip')
ssh_port = int(request.data.get('ssh_port'))
cloud = TCloud(secret_id, secret_key)
instance_result = cloud.instance_list(region_id)
instance_list = []
if instance_result.code == 200:
instance_list = instance_result.InstanceSet
if instance_result.TotalCount == 0:
res = {'code': 500, 'msg': '该区域未发现云主机,请重新选择!'}
return Response(res)
elif instance_result.code == 500:
res = {'code': 500, 'msg': '%s' %instance_result['msg']}
return Response(res)
# InstanceSet中可用区字段值是英文
# 先获取可用区英文与中文对应,下面遍历主机再获取中文名
zone_result = cloud.zone_list(region_id)
zone_dict = {}
for z in zone_result.ZoneSet:
zone_dict[z.Zone] = z.ZoneName
# 获取主机所在可用区
# 可用区,机房表:机房名称
host_zone_set = set()
for host in instance_list:
zone = host.Placement.Zone # 可用区,例如 ap-beijing-1
host_zone_set.add(zone_dict[zone]) # 获取中文名
# 根据可用区创建机房
for zone in host_zone_set:
# 如果存在不创建
idc = Idc.objects.filter(name=zone)
if not idc:
city = ""
region_result = cloud.region_list()
for r in region_result.RegionSet: # 获取区域对应中文名
if r.Region == region_id:
city = r.RegionName
Idc.objects.create(
name=zone,
city=city,
provider="腾讯云"
)
# 导入云主机信息到数据库
for host in instance_list:
zone = host.Placement.Zone
instance_id = host.InstanceId # 实例ID
instance_name = host.InstanceName # 机器名称
os_version = host.OsName
private_ip = host.PrivateIpAddresses
public_ip = host.PublicIpAddresses
cpu = "%s核" %host.CPU
memory = "%sG" %host.Memory
disk = [{'device': 'None', 'size': host.SystemDisk.DiskSize, 'type': 'None'}] # 默认保存是系统盘
data_list = host.DataDisks
if data_list:
for d in data_list:
disk.append({'device': 'None', 'size': d.DiskSize, 'type': 'None'})
create_date = time.strftime("%Y-%m-%d",time.strptime(host.CreatedTime, "%Y-%m-%dT%H:%M:%SZ"))
expired_time = time.strftime("%Y-%m-%d %H:%M:%S",time.strptime(host.ExpiredTime, "%Y-%m-%dT%H:%M:%SZ"))
# state = host.InstanceState
# 创建服务器
idc_name = zone_dict[zone]
idc = Idc.objects.get(name=idc_name)
if ssh_ip == "public":
ssh_ip = public_ip[0] # 使用第一个IP连接
elif ssh_ip == "private":
ssh_ip = private_ip[0]
# 如果instance_id不存在才创建
data = {'idc': idc,
'name': instance_name,
'hostname': instance_id,
'ssh_ip': ssh_ip,
'ssh_port': ssh_port,
'machine_type': 'cloud_vm',
'os_version': os_version,
'public_ip': public_ip,
'private_ip': private_ip,
'cpu_num': cpu,
'memory': memory,
'disk': disk,
'put_shelves_date': create_date,
'expire_datetime': expired_time,
'is_verified': 'verified'}
server = Server.objects.filter(hostname=instance_id)
if not server:
server = Server.objects.create(**data)
# 分组多对多
group = ServerGroup.objects.get(id=server_group_id) # 根据id查询分组
server.server_group.add(group) # 将服务器添加到分组
else:
server.update(**data)
res = {'code': 200, 'msg': '导入云主机成功'}
return Response(res)
- 路由配置: devops_api/devops_api/urls.py
...
from cmdb.views import CreateHostView,ExcelCreateHostView,AliyunCloudView,TencentCloudView
...
urlpatterns = [
path('admin/', admin.site.urls),
re_path('^api/login/$', CustomAuthToken.as_view()),
re_path('^api/change_password/$', token_auth.ChangeUserPasswordView.as_view()),
re_path('^api/cmdb/create_host/$', CreateHostView.as_view()),
re_path('^api/cmdb/excel_create_host/$', ExcelCreateHostView.as_view()),
re_path('^api/cmdb/aliyun_cloud/$', AliyunCloudView.as_view()),
re_path('^api/cmdb/tencent_cloud/$', TencentCloudView.as_view()),
]
...
- Apipost get 接口测试

- Apipost post 接口测试

- Idc数据查看:

- Server 数据查看

数据同步接口开发
- 创建同步视图: devops_api/cmdb/views.py
...
class HostCollectView(APIView):
def get(self, request):
hostname = request.query_params.get('hostname')
server = Server.objects.get(hostname=hostname)
ssh_ip = server.ssh_ip
ssh_port = server.ssh_port
# 未绑定凭据并且没有选择凭据
credential_id = request.query_params.get('credential_id')
if not server.credential and not credential_id:
result = {'code': 500, 'msg': '未发现凭据,请选择!'}
return Response(result)
elif server.credential:
credential_id = int(server.credential.id)
elif credential_id:
credential_id = int(request.query_params.get('credential_id'))
credential = Credential.objects.get(id=credential_id)
username = credential.username
if credential.auth_mode == 1:
password = credential.password
ssh = SSH(ssh_ip, ssh_port, username, password=password)
else:
private_key = credential.private_key
ssh = SSH(ssh_ip, ssh_port, username, key=private_key)
# 先SSH基本测试
test = ssh.test()
if test['code'] == 200:
local_file = os.path.join(settings.BASE_DIR, 'cmdb', 'files', 'host_collect.py')
remote_file = os.path.join('/tmp/host_collect.py')
ssh.scp(local_file, remote_file)
result = ssh.command('python %s' %remote_file)
if result['code'] == 200: # 采集脚本执行成功
# 再进一步判断客户端采集脚本提交结果
data = json.loads(result['data'])
Server.objects.filter(hostname=hostname).update(**data)
# 更新凭据ID
server = Server.objects.get(hostname=hostname)
server.credential = credential
server.is_verified = 'verified'
server.save()
result = {'code': 200, 'msg': '主机配置同步成功'}
else:
result = {'code': 500, 'msg': '主机配置同步失败!错误:%s' %result['msg']}
else:
result = {'code': 500, 'msg': 'SSH连接异常!错误:%s' %test['msg']}
return Response(result)
- 修改路由: devops_api/devops_api/urls.py
...
from cmdb.views import CreateHostView,ExcelCreateHostView,AliyunCloudView,TencentCloudView,HostCollectView
...
urlpatterns = [
path('admin/', admin.site.urls),
re_path('^api/login/$', CustomAuthToken.as_view()),
re_path('^api/change_password/$', token_auth.ChangeUserPasswordView.as_view()),
re_path('^api/cmdb/create_host/$', CreateHostView.as_view()),
re_path('^api/cmdb/excel_create_host/$', ExcelCreateHostView.as_view()),
re_path('^api/cmdb/aliyun_cloud/$', AliyunCloudView.as_view()),
re_path('^api/cmdb/tencent_cloud/$', TencentCloudView.as_view()),
re_path('^api/cmdb/host_collect/$', HostCollectView.as_view()),
]
...
- 找个本地虚拟机故意改错配置: 4核改1核

- Apipost 同步测试

- 检查是否同步成功

启用接口文档
- 安装swagger
pip install django-rest-swagger
- 项目中启用swagger: devops_api/devops_api/settings.py
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'rest_framework',
'rest_framework.authtoken',
'rest_framework_swagger',
'django_filters',
'cmdb',
'system_config'
]
...
REST_FRAMEWORK = {
'DEFAULT_PAGINATION_CLASS': 'libs.pagination.MyPagination',
# 认证
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework.authentication.SessionAuthentication',
'rest_framework.authentication.TokenAuthentication',
],
# 权限
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.IsAuthenticated' # 登录后就能访问所有API
],
# API接口文档
'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.coreapi.AutoSchema',
}
- 配置路由: devops_api/devops_api/urls.py
...
#接口文档
from rest_framework_swagger.views import get_swagger_view
schema_view = get_swagger_view(title='接口文档')
urlpatterns = [
path('admin/', admin.site.urls),
re_path('^api/docs/$', schema_view),
re_path('^api/login/$', CustomAuthToken.as_view()),
re_path('^api/change_password/$', token_auth.ChangeUserPasswordView.as_view()),
re_path('^api/cmdb/create_host/$', CreateHostView.as_view()),
re_path('^api/cmdb/excel_create_host/$', ExcelCreateHostView.as_view()),
re_path('^api/cmdb/aliyun_cloud/$', AliyunCloudView.as_view()),
re_path('^api/cmdb/tencent_cloud/$', TencentCloudView.as_view()),
re_path('^api/cmdb/host_collect/$', HostCollectView.as_view()),
]
...
- 测试查看
