Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add some features #949

Merged
merged 4 commits into from
May 26, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 52 additions & 28 deletions bcs-app/backend/components/iam/permissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
# an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
# specific language governing permissions and limitations under the License.
#
from typing import Dict, Optional

from django.conf import settings

try:
Expand All @@ -26,11 +28,6 @@


class IAMClient(Client):
def _list_policies(self, data):
path = f"/api/v1/systems/{self._app_code}/policies"
ok, message, data = self._call_iam_api(http_get, path, data)
return ok, message, data

def list_policies(self, data):
policies = []
page, page_size = 1, 100
Expand Down Expand Up @@ -76,16 +73,17 @@ def grant_resource_creator_actions(self, bk_token, bk_username, data):

return True, "success"

def _list_policies(self, data):
path = f"/api/v1/systems/{self._app_code}/policies"
ok, message, data = self._call_iam_api(http_get, path, data)
return ok, message, data


class BCSIAM(IAM):
def __init__(self, app_code, app_secret, bk_iam_host, bk_paas_host):
self._client = IAMClient(app_code, app_secret, bk_iam_host, bk_paas_host)

def make_dict_filter(self, request, key_mapping=None):
"""
仅支持{'op': 'in', 'field': 'project.id', 'value': [1, 2, 3]}
或者{'op': 'eq', 'field': 'project.id', 'value': 1}
"""
def do_policy_query(self, request) -> Optional[Dict]:
# 1. validate
if not isinstance(request, Request):
raise AuthInvalidRequest("request should be instance of iam.auth.models.Request")
Expand All @@ -99,22 +97,7 @@ def make_dict_filter(self, request, key_mapping=None):
if not policies:
return None

op = policies["op"]
if op not in [OP.IN, OP.EQ, OP.ANY]:
raise AuthInvalidRequest("make_dict_filter only support OP.IN or OP.EQ or OP.ANY")

value = policies["value"]
if op == OP.EQ:
value = [
value,
]

field = policies["field"]
if key_mapping:
k = key_mapping.get(field) or field
return {k: value, "op": op}

return {field: value, "op": op}
return policies

def _match_resource_id(self, expression, resource_type_id, resource_id):
if expression["op"] in [OP.AND, OP.OR]:
Expand Down Expand Up @@ -265,10 +248,14 @@ def can_edit(self, username, project_id, raise_exception=False):
action_id = self.actions.EDIT.value
return self._allowed_do_project_inst(username, action_id, project_id, raise_exception)

def make_view_perm_filter(self, username):
def make_view_perm_filter(self, username: str) -> Dict:
action_id = self.actions.VIEW.value
request = self._make_request_with_resources(username, action_id)
return self.iam.make_dict_filter(request, {"project.id": "project_id_list"})
policies = self.iam.do_policy_query(request)
if not policies:
return {}

return self._make_dict_filter(policies)

def op_is_any(self, filter):
if not filter:
Expand Down Expand Up @@ -302,3 +289,40 @@ def query_authorized_users(self, project_id, action_id):

def grant_related_action_perms(self, username, project_id, project_name):
return self.iam.grant_resource_creator_action(username, self.resource_type_id, project_id, project_name)

def _make_dict_filter(self, policies: Dict) -> Dict:
"""
基于策略规则, 生成 project_id 过滤器。

:params policies: 权限中心返回的策略规则,如 {'op': OP.IN, 'value': [2, 1], 'field': 'project.id'}
:returns : project_id 过滤器, 如 {'project_id_list': [1, 2], 'op': OP.IN}
"""
op = policies["op"]
if op not in [OP.IN, OP.EQ, OP.ANY, OP.OR, OP.AND]:
raise AuthInvalidRequest(f"make_dict_filter does not support op:{op}")

field, key = "project.id", "project_id_list"

if op == OP.EQ:
return {key: [policies["value"]], "op": OP.IN}

if op in [OP.IN, OP.ANY]:
return {key: policies["value"] or [], "op": op}

# 如果 op 是 OP.OR 或 OP.AND,只处理一级,不考虑嵌套的情况
value_list = []
for policy in policies["content"]:
if policy["field"] != field:
continue

op = policy["op"]
if op == OP.ANY:
return {key: policy["value"] or [], "op": op}

value = policy["value"]
if op == OP.IN:
value_list.extend(value)
elif op == OP.EQ:
value_list.append(value)

return {key: list(set(value_list)), "op": OP.IN}
6 changes: 6 additions & 0 deletions bcs-app/backend/settings/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,12 @@ def get_logging_config(log_level, rds_hander_settings=None, log_path="app.log"):
"handlers": ["console", "logstash_redis", "file"],
"level": "DEBUG",
},
# 配置iam logger
'iam': {
'handlers': ['file'],
'level': os.getenv('IAM_LOG_LEVEL', 'ERROR'),
'propagate': False,
},
"sentry_logger": {"handlers": ["sentry"], "level": "ERROR"},
},
}
Expand Down
55 changes: 55 additions & 0 deletions bcs-app/backend/tests/components/test_permissions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# -*- coding: utf-8 -*-
#
# Tencent is pleased to support the open source community by making 蓝鲸智云PaaS平台社区版 (BlueKing PaaS Community Edition) available.
# Copyright (C) 2017-2019 THL A29 Limited, a Tencent company. All rights reserved.
# Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://opensource.org/licenses/MIT
#
# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
# an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
# specific language governing permissions and limitations under the License.
#
import pytest
from iam import OP

from backend.components.iam.permissions import ProjectPermission

test_dict_filter_data = [
({'op': OP.IN, 'value': [2, 1], 'field': 'project.id'}, {'project_id_list': [1, 2], 'op': OP.IN}),
({'op': OP.EQ, 'value': 1, 'field': 'project.id'}, {'project_id_list': [1], 'op': OP.IN}),
({'op': OP.ANY, 'value': [], 'field': 'project.id'}, {'project_id_list': [], 'op': OP.ANY}),
(
{
'op': OP.OR,
'content': [
{'op': OP.IN, 'field': 'project.id', 'value': [2, 1, 5]},
{'op': OP.ANY, 'field': 'project.id', 'value': []},
{'op': OP.EQ, 'field': 'project.id', 'value': 3},
{'op': OP.IN, 'field': 'project.id', 'value': [4]},
],
},
{'project_id_list': [], 'op': OP.ANY},
),
(
{
'op': OP.OR,
'content': [
{'op': OP.IN, 'field': 'project.id', 'value': [2, 1, 5]},
{'op': OP.EQ, 'field': 'project.id', 'value': 3},
{'op': OP.IN, 'field': 'fake_project.id', 'value': [4, 6]},
],
},
{'project_id_list': [1, 2, 3, 5], 'op': OP.IN},
),
]


class TestProjectPermission:
@pytest.mark.parametrize('policies, expected_dict_filter', test_dict_filter_data)
def test_make_dict_filter(self, policies, expected_dict_filter):
project_perm = ProjectPermission()
dict_filter = project_perm._make_dict_filter(policies)
assert dict_filter['project_id_list'].sort() == expected_dict_filter['project_id_list'].sort()
assert dict_filter['op'] == expected_dict_filter['op']
76 changes: 26 additions & 50 deletions bcs-app/frontend/src/views/cluster/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -209,53 +209,6 @@
:cur-project="curProject"
:cur-cluster="cluster">
</status-progress>
<!-- <div class="biz-cluster-content" :class="curProject.kind === PROJECT_MESOS ? 'more-info' : ''">
<div class="biz-progress-box">
<div class="progress-header">
<span class="title">{{$t('CPU使用率')}}</span>
<span class="percent">
{{conversionPercent(cluster.remain_cpu, cluster.total_cpu)}}%
</span>
</div>
<div class="progress">
<div class="progress-bar primary"
:style="{ width: `${conversionPercent(cluster.remain_cpu, cluster.total_cpu)}%` }"></div>
</div>
</div>
<div class="biz-progress-box">
<div class="progress-header">
<span class="title">{{$t('内存使用率')}}</span>
<span class="percent">
{{conversionPercent(cluster.remain_mem, cluster.total_mem)}}%
</span>
</div>
<div class="progress">
<div class="progress-bar success" :style="{ width: `${conversionPercent(cluster.remain_mem, cluster.total_mem)}%` }"></div>
</div>
</div>
<div class="biz-progress-box">
<div class="progress-header">
<span class="title">{{$t('磁盘使用率')}}</span>
<span class="percent">
{{conversionPercent(cluster.remain_disk, cluster.total_disk)}}%
</span>
</div>
<div class="progress">
<div class="progress-bar warning" :style="{ width: `${conversionPercent(cluster.remain_disk, cluster.total_disk)}%` }"></div>
</div>
</div>
<div class="biz-progress-box" v-if="curProject.kind === PROJECT_MESOS">
<div class="progress-header">
<span class="title">{{$t('集群IP')}}</span>
<span class="percent">
{{cluster.allip === 0 ? 0 : `${cluster.activeip} / ${cluster.allip}(${$t('剩余')}${cluster.availableip})`}}
</span>
</div>
<div class="progress">
<div class="progress-bar warning" :style="{ width: `${cluster.allip === 0 ? 0 : conversionPercent(cluster.activeip, cluster.allip, true)}%` }"></div>
</div>
</div>
</div> -->
<div class="add-node-btn-wrapper">
<button class="bk-button bk-default add-node-btn" @click="goOverviewOrNode('clusterNode', cluster)">
<span>{{$t('添加节点')}}</span>
Expand Down Expand Up @@ -733,16 +686,23 @@
this.permissions = JSON.parse(JSON.stringify(res.permissions || {}))

const list = res.data.results || []

list.forEach((item, index) => {
item.cpu_usage = {}
item.mem_usage = {}
item.disk_usage = {}

item.activeip = 0
item.availableip = 0
item.reservedip = 0
item.allip = 0
if (this.curProject.kind === this.PROJECT_MESOS) {
this.getClusterIp(item, index)
// if (this.curProject.kind === this.PROJECT_MESOS) {
// this.getClusterIp(item, index)
// }
if (item.type === 'mesos' && item.func_wlist && item.func_wlist.indexOf('MesosResource') > -1) {
if (!notLoading) {
this.getClusterIp(item, index)
}
}
})

Expand All @@ -753,6 +713,10 @@
resListItem.cpu_usage = c.cpu_usage
resListItem.mem_usage = c.mem_usage
resListItem.disk_usage = c.disk_usage
resListItem.activeip = c.activeip
resListItem.availableip = c.availableip
resListItem.reservedip = c.reservedip
resListItem.allip = c.allip
}
})
}
Expand All @@ -766,9 +730,15 @@
this.$set(l, index, item)
this.$store.commit('cluster/forceUpdateClusterList', l)

const args = {}
if (item.type === 'mesos' && item.func_wlist && item.func_wlist.indexOf('MesosResource') > -1) {
args.dimensions = 'mesos_memory_usage,mesos_cpu_usage'
}

await this.$store.dispatch('cluster/clusterOverview', {
projectId: this.projectId,
clusterId: item.cluster_id
clusterId: item.cluster_id,
data: args
}).then(d => {
item.cpu_usage = d.data.cpu_usage
// item.cpu_usage = {
Expand All @@ -778,6 +748,12 @@
item.mem_usage = d.data.mem_usage
item.disk_usage = d.data.disk_usage

// 如果是 mesos,返回是 mesos_memory_usage 和 mesos_cpu_usage
if (item.type === 'mesos' && item.func_wlist && item.func_wlist.indexOf('MesosResource') > -1) {
item.cpu_usage = d.data.mesos_cpu_usage
item.mem_usage = d.data.mesos_memory_usage
}

const l = []
l.splice(0, 0, ...this.clusterList)
this.$set(l, index, item)
Expand Down
40 changes: 29 additions & 11 deletions bcs-app/frontend/src/views/cluster/searcher/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,16 @@
statusList: [
{ text: this.$t('正常'), value: ['normal'] },
{ text: this.$t('不可调度'), value: ['to_removed', 'removable'] }
]
],
mesosLabelMap: {}
}
},
computed: {
curProject () {
return this.$store.state.curProject
},
isMesosProject () {
return this.curProject.kind === window.PROJECT_MESOS
}
},
watch: {
Expand Down Expand Up @@ -302,12 +311,15 @@
this.showKey = true
this.tagLoading = true
try {
const res = await this.$store.dispatch('cluster/getNodeKeyList', {
const api = this.isMesosProject ? 'cluster/getMesosNodeLabels' : 'cluster/getNodeKeyList'
const res = await this.$store.dispatch(api, {
projectId: this.projectId,
clusterId: this.clusterId
})
this.keyList.splice(0, this.keyList.length, ...(res.data || []))
this.keyListTmp.splice(0, this.keyList.length, ...(res.data || []))
const keyList = this.isMesosProject ? Object.keys(res.data || {}) : res.data || []
this.isMesosProject && (this.mesosLabelMap = res.data || {})
this.keyList.splice(0, this.keyList.length, ...keyList)
this.keyListTmp.splice(0, this.keyList.length, ...keyList)
this.$nextTick(() => {
this.$refs.searchInput.focus()
this.isListeningInputKeyup = true
Expand Down Expand Up @@ -366,13 +378,19 @@
this.tagLoading = true
this.curInputValue = ''
this.inputPlaceholder = this.$t('请输入要搜索的value')
const res = await this.$store.dispatch('cluster/getNodeValueListByKey', {
projectId: this.projectId,
clusterId: this.clusterId,
keyName: k
})
this.valueList.splice(0, this.valueList.length, ...(res.data || []))
this.valueListTmp.splice(0, this.valueList.length, ...(res.data || []))
let valueList = []
if (this.isMesosProject) {
valueList = this.mesosLabelMap[k] || []
} else {
const res = await this.$store.dispatch('cluster/getNodeValueListByKey', {
projectId: this.projectId,
clusterId: this.clusterId,
keyName: k
})
valueList = res.data || []
}
this.valueList.splice(0, this.valueList.length, ...valueList)
this.valueListTmp.splice(0, this.valueList.length, ...valueList)
this.$refs.searchInput.focus()
} catch (e) {
catchErrorHandler(e, this)
Expand Down
Loading