Skip to content

Commit

Permalink
feat(api): dcim dev (#642)
Browse files Browse the repository at this point in the history
  • Loading branch information
pycook authored Nov 26, 2024
1 parent 900cf1f commit e22b0c5
Show file tree
Hide file tree
Showing 24 changed files with 754 additions and 8 deletions.
5 changes: 4 additions & 1 deletion cmdb-api/api/commands/click_cmdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
from api.lib.cmdb.const import ResourceTypeEnum
from api.lib.cmdb.const import RoleEnum
from api.lib.cmdb.const import ValueTypeEnum
from api.lib.cmdb.dcim.rack import RackManager
from api.lib.exception import AbortException
from api.lib.perm.acl.acl import ACLManager
from api.lib.perm.acl.acl import UserCache
Expand Down Expand Up @@ -195,7 +196,7 @@ def cmdb_counter():
today = datetime.date.today()
while True:
try:
db.session.remove()
db.session.commit()

CMDBCounterCache.reset()

Expand All @@ -209,6 +210,8 @@ def cmdb_counter():

CMDBCounterCache.flush_sub_counter()

RackManager().check_u_slot()

i += 1
except:
import traceback
Expand Down
8 changes: 4 additions & 4 deletions cmdb-api/api/lib/cmdb/ci.py
Original file line number Diff line number Diff line change
Expand Up @@ -1289,10 +1289,10 @@ def add(cls, first_ci_id, second_ci_id,
return existed.id

@staticmethod
def delete(cr_id, apply_async=True):
def delete(cr_id, apply_async=True, valid=True):
cr = CIRelation.get_by_id(cr_id) or abort(404, ErrFormat.relation_not_found.format("id={}".format(cr_id)))

if current_app.config.get('USE_ACL') and current_user.username != 'worker':
if current_app.config.get('USE_ACL') and current_user.username != 'worker' and valid:
resource_name = CITypeRelationManager.acl_resource_name(cr.first_ci.ci_type.name, cr.second_ci.ci_type.name)
if not ACLManager().has_permission(
resource_name,
Expand Down Expand Up @@ -1331,7 +1331,7 @@ def delete_2(cls, first_ci_id, second_ci_id, ancestor_ids=None):
return cr

@classmethod
def delete_3(cls, first_ci_id, second_ci_id, apply_async=True):
def delete_3(cls, first_ci_id, second_ci_id, apply_async=True, valid=True):
cr = CIRelation.get_by(first_ci_id=first_ci_id,
second_ci_id=second_ci_id,
to_dict=False,
Expand All @@ -1341,7 +1341,7 @@ def delete_3(cls, first_ci_id, second_ci_id, apply_async=True):
# ci_relation_delete.apply_async(args=(first_ci_id, second_ci_id, cr.ancestor_ids), queue=CMDB_QUEUE)
# delete_id_filter.apply_async(args=(second_ci_id,), queue=CMDB_QUEUE)

cls.delete(cr.id, apply_async=apply_async)
cls.delete(cr.id, apply_async=apply_async, valid=valid)

return cr

Expand Down
5 changes: 5 additions & 0 deletions cmdb-api/api/lib/cmdb/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,11 @@ class BuiltinModelEnum(BaseEnum):
IPAM_ADDRESS = "ipam_address"
IPAM_SCOPE = "ipam_scope"

DCIM_REGION = "dcim_region"
DCIM_IDC = "dcim_idc"
DCIM_SERVER_ROOM = "dcim_server_room"
DCIM_RACK = "dcim_rack"


BUILTIN_ATTRIBUTES = {
"_updated_at": _l("Update Time"),
Expand Down
1 change: 1 addition & 0 deletions cmdb-api/api/lib/cmdb/dcim/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# -*- coding:utf-8 -*-
33 changes: 33 additions & 0 deletions cmdb-api/api/lib/cmdb/dcim/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# -*- coding:utf-8 -*-

from api.lib.cmdb.ci import CIManager
from api.lib.cmdb.ci import CIRelationManager
from api.lib.cmdb.const import ExistPolicy


class DCIMBase(object):
def __init__(self):
self.type_id = None

@staticmethod
def add_relation(parent_id, child_id):
if not parent_id or not child_id:
return

CIRelationManager().add(parent_id, child_id, valid=False, apply_async=False)

def add(self, parent_id, **kwargs):
ci_id = CIManager().add(self.type_id, exist_policy=ExistPolicy.REJECT, **kwargs)

if parent_id:
self.add_relation(parent_id, ci_id)

return ci_id

@classmethod
def update(cls, _id, **kwargs):
CIManager().update(_id, **kwargs)

@classmethod
def delete(cls, _id):
CIManager().delete(_id)
17 changes: 17 additions & 0 deletions cmdb-api/api/lib/cmdb/dcim/const.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# -*- coding:utf-8 -*-


from api.lib.utils import BaseEnum


class RackBuiltinAttributes(BaseEnum):
U_COUNT = 'u_count'
U_START = 'u_start'
FREE_U_COUNT = 'free_u_count'
U_SLOT_ABNORMAL = 'u_slot_abnormal'


class OperateTypeEnum(BaseEnum):
ADD_DEVICE = "0"
REMOVE_DEVICE = "1"
MOVE_DEVICE = "2"
40 changes: 40 additions & 0 deletions cmdb-api/api/lib/cmdb/dcim/history.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
from flask_login import current_user

from api.lib.cmdb.cache import AttributeCache
from api.lib.cmdb.cache import CITypeCache
from api.lib.cmdb.ci import CIManager
from api.lib.mixin import DBMixin
from api.models.cmdb import DCIMOperationHistory


class OperateHistoryManager(DBMixin):
cls = DCIMOperationHistory

@classmethod
def search(cls, page, page_size, fl=None, only_query=False, reverse=False, count_query=False,
last_size=None, **kwargs):
numfound, result = super(OperateHistoryManager, cls).search(page, page_size, fl, only_query, reverse,
count_query, last_size, **kwargs)

ci_ids = [i['ci_id'] for i in result]
id2ci = {i['_id']: i for i in (CIManager.get_cis_by_ids(ci_ids) or []) if i}
type2show_key = dict()
for i in id2ci.values():
if i.get('_type') not in type2show_key:
ci_type = CITypeCache.get(i.get('_type'))
if ci_type:
show_key = AttributeCache.get(ci_type.show_id or ci_type.unique_id)
type2show_key[i['_type']] = show_key and show_key.name

return numfound, result, id2ci, type2show_key

def _can_add(self, **kwargs):
kwargs['uid'] = current_user.uid

return kwargs

def _can_update(self, **kwargs):
pass

def _can_delete(self, **kwargs):
pass
19 changes: 19 additions & 0 deletions cmdb-api/api/lib/cmdb/dcim/idc.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# -*- coding:utf-8 -*-


from flask import abort

from api.lib.cmdb.cache import CITypeCache
from api.lib.cmdb.const import BuiltinModelEnum
from api.lib.cmdb.dcim.base import DCIMBase
from api.lib.cmdb.resp_format import ErrFormat


class IDCManager(DCIMBase):
def __init__(self):
super(IDCManager, self).__init__()

self.ci_type = CITypeCache.get(BuiltinModelEnum.DCIM_IDC) or abort(
404, ErrFormat.dcim_builtin_model_not_found.format(BuiltinModelEnum.DCIM_IDC))

self.type_id = self.ci_type.id
179 changes: 179 additions & 0 deletions cmdb-api/api/lib/cmdb/dcim/rack.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
# -*- coding:utf-8 -*-

import itertools
import redis_lock
from flask import abort

from api.extensions import rd
from api.lib.cmdb.cache import CITypeCache
from api.lib.cmdb.ci import CIManager
from api.lib.cmdb.ci import CIRelationManager
from api.lib.cmdb.const import BuiltinModelEnum
from api.lib.cmdb.dcim.base import DCIMBase
from api.lib.cmdb.dcim.const import OperateTypeEnum
from api.lib.cmdb.dcim.const import RackBuiltinAttributes
from api.lib.cmdb.dcim.history import OperateHistoryManager
from api.lib.cmdb.resp_format import ErrFormat
from api.lib.cmdb.search.ci.db.search import Search as SearchFromDB
from api.lib.cmdb.search.ci_relation.search import Search as RelationSearch


class RackManager(DCIMBase):
def __init__(self):
super(RackManager, self).__init__()

self.ci_type = CITypeCache.get(BuiltinModelEnum.DCIM_RACK) or abort(
404, ErrFormat.dcim_builtin_model_not_found.format(BuiltinModelEnum.DCIM_RACK))

self.type_id = self.ci_type.id

@classmethod
def update(cls, _id, **kwargs):
if RackBuiltinAttributes.U_COUNT in kwargs:
devices, _, _, _, _, _ = RelationSearch(
[_id],
level=[1],
fl=[RackBuiltinAttributes.U_COUNT, RackBuiltinAttributes.U_START],
count=1000000).search()
for device in devices:
u_start = device.get(RackBuiltinAttributes.U_START)
u_count = device.get(RackBuiltinAttributes.U_COUNT) or 2
if u_start and u_start + u_count - 1 > kwargs[RackBuiltinAttributes.U_COUNT]:
return abort(400, ErrFormat.dcim_rack_u_count_invalid)

CIManager().update(_id, _sync=True, **kwargs)

if RackBuiltinAttributes.U_COUNT in kwargs:
payload = {RackBuiltinAttributes.FREE_U_COUNT: cls._calc_u_free_count(_id)}

CIManager().update(_id, _sync=True, **payload)

def delete(self, _id):
super(RackManager, self).delete(_id)

payload = {RackBuiltinAttributes.U_START: None}
_, _, second_cis = CIRelationManager.get_second_cis(_id, per_page='all')
for ci in second_cis:
CIManager().update(ci['_id'], **payload)

@staticmethod
def _calc_u_free_count(rack_id, device_id=None, u_start=None, u_count=None):
rack = CIManager.get_ci_by_id(rack_id, need_children=False)
if not rack.get(RackBuiltinAttributes.U_COUNT):
return 0

if device_id is not None and u_count is None:
ci = CIManager().get_ci_by_id(device_id, need_children=False)
u_count = ci.get(RackBuiltinAttributes.U_COUNT) or 2

if u_start and u_start + u_count - 1 > rack.get(RackBuiltinAttributes.U_COUNT):
return abort(400, ErrFormat.dcim_rack_u_slot_invalid)

devices, _, _, _, _, _ = RelationSearch(
[rack_id],
level=[1],
fl=[RackBuiltinAttributes.U_COUNT, RackBuiltinAttributes.U_START],
count=1000000).search()

u_count_sum = 0
for device in devices:
u_count_sum += (device.get(RackBuiltinAttributes.U_COUNT) or 2)
if device_id is not None:
_u_start = device.get(RackBuiltinAttributes.U_START)
_u_count = device.get(RackBuiltinAttributes.U_COUNT) or 2
if not _u_start:
continue

if device.get('_id') != device_id and set(range(u_start, u_start + u_count)) & set(
range(_u_start, _u_start + _u_count)):
return abort(400, ErrFormat.dcim_rack_u_slot_invalid)

return rack[RackBuiltinAttributes.U_COUNT] - u_count_sum

def check_u_slot(self):
racks, _, _, _, _, _ = SearchFromDB(
"_type:{}".format(self.type_id),
count=10000000,
fl=[RackBuiltinAttributes.U_START, RackBuiltinAttributes.U_COUNT, RackBuiltinAttributes.U_SLOT_ABNORMAL],
parent_node_perm_passed=True).search()

for rack in racks:
devices, _, _, _, _, _ = RelationSearch(
[rack['_id']],
level=[1],
fl=[RackBuiltinAttributes.U_COUNT, RackBuiltinAttributes.U_START],
count=1000000).search()

u_slot_sets = []
for device in devices:
u_start = device.get(RackBuiltinAttributes.U_START)
u_count = device.get(RackBuiltinAttributes.U_COUNT) or 2
if u_start is not None and str(u_start).isdigit():
u_slot_sets.append(set(range(u_start, u_start + u_count)))

if len(u_slot_sets) > 1:
for a, b in itertools.combinations(u_slot_sets, 2):
u_slot_abnormal = bool(a.intersection(b))
if u_slot_abnormal != rack.get(RackBuiltinAttributes.U_SLOT_ABNORMAL):
payload = {RackBuiltinAttributes.U_SLOT_ABNORMAL: u_slot_abnormal}
CIManager().update(rack['_id'], **payload)

def add_device(self, rack_id, device_id, u_start, u_count=None):
with (redis_lock.Lock(rd.r, "DCIM_RACK_OPERATE_{}".format(rack_id))):
self._calc_u_free_count(rack_id, device_id, u_start, u_count)

self.add_relation(rack_id, device_id)

payload = {RackBuiltinAttributes.U_START: u_start}
if u_count:
payload[RackBuiltinAttributes.U_COUNT] = u_count
CIManager().update(device_id, _sync=True, **payload)

payload = {
RackBuiltinAttributes.FREE_U_COUNT: self._calc_u_free_count(rack_id, device_id, u_start, u_count)}
CIManager().update(rack_id, _sync=True, **payload)

OperateHistoryManager().add(operate_type=OperateTypeEnum.ADD_DEVICE, rack_id=rack_id, ci_id=device_id)

def remove_device(self, rack_id, device_id):
with (redis_lock.Lock(rd.r, "DCIM_RACK_OPERATE_{}".format(rack_id))):
CIRelationManager.delete_3(rack_id, device_id, apply_async=False, valid=False)

payload = {RackBuiltinAttributes.FREE_U_COUNT: self._calc_u_free_count(rack_id)}
CIManager().update(rack_id, _sync=True, **payload)

payload = {RackBuiltinAttributes.U_START: None}
CIManager().update(device_id, _sync=True, **payload)

OperateHistoryManager().add(operate_type=OperateTypeEnum.REMOVE_DEVICE, rack_id=rack_id, ci_id=device_id)

def move_device(self, rack_id, device_id, to_u_start):
with (redis_lock.Lock(rd.r, "DCIM_RACK_OPERATE_{}".format(rack_id))):
payload = {RackBuiltinAttributes.FREE_U_COUNT: self._calc_u_free_count(rack_id, device_id, to_u_start)}
CIManager().update(rack_id, _sync=True, **payload)

CIManager().update(device_id, _sync=True, **{RackBuiltinAttributes.U_START: to_u_start})

OperateHistoryManager().add(operate_type=OperateTypeEnum.MOVE_DEVICE, rack_id=rack_id, ci_id=device_id)

def migrate_device(self, rack_id, device_id, to_rack_id, to_u_start):
with (redis_lock.Lock(rd.r, "DCIM_RACK_OPERATE_{}".format(rack_id))):
self._calc_u_free_count(to_rack_id, device_id, to_u_start)

if rack_id != to_rack_id:
CIRelationManager.delete_3(rack_id, device_id, apply_async=False, valid=False)

self.add_relation(to_rack_id, device_id)

payload = {
RackBuiltinAttributes.FREE_U_COUNT: self._calc_u_free_count(to_rack_id, device_id, to_u_start)}
CIManager().update(to_rack_id, _sync=True, **payload)

CIManager().update(device_id, _sync=True, **{RackBuiltinAttributes.U_START: to_u_start})

if rack_id != to_rack_id:
payload = {RackBuiltinAttributes.FREE_U_COUNT: self._calc_u_free_count(rack_id)}
CIManager().update(rack_id, _sync=True, **payload)

OperateHistoryManager().add(operate_type=OperateTypeEnum.REMOVE_DEVICE, rack_id=rack_id, ci_id=device_id)
OperateHistoryManager().add(operate_type=OperateTypeEnum.ADD_DEVICE, rack_id=to_rack_id, ci_id=device_id)
29 changes: 29 additions & 0 deletions cmdb-api/api/lib/cmdb/dcim/region.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# -*- coding:utf-8 -*-


from flask import abort

from api.lib.cmdb.cache import CITypeCache
from api.lib.cmdb.ci import CIManager
from api.lib.cmdb.const import BuiltinModelEnum
from api.lib.cmdb.const import ExistPolicy
from api.lib.cmdb.resp_format import ErrFormat


class RegionManager(object):
def __init__(self):
self.ci_type = CITypeCache.get(BuiltinModelEnum.DCIM_REGION) or abort(
404, ErrFormat.dcim_builtin_model_not_found.format(BuiltinModelEnum.DCIM_REGION))

self.type_id = self.ci_type.id

def add(self, **kwargs):
return CIManager().add(self.type_id, exist_policy=ExistPolicy.REJECT, **kwargs)

@classmethod
def update(cls, _id, **kwargs):
CIManager().update(_id, **kwargs)

@classmethod
def delete(cls, _id):
CIManager().delete(_id)
Loading

0 comments on commit e22b0c5

Please sign in to comment.