Add list-crush-rules action
This action provides a list of crush rules defined in CEPH clusters. Closes-bug: #1957458 Change-Id: I2a5fdae776e00d869a624e1107ab42cf69bb2f50
This commit is contained in:
parent
c07fb2dc6a
commit
37105f11cd
12
actions.yaml
12
actions.yaml
@ -405,3 +405,15 @@ get-quorum-status:
|
|||||||
- text
|
- text
|
||||||
- json
|
- json
|
||||||
description: Specify output format (text|json).
|
description: Specify output format (text|json).
|
||||||
|
list-crush-rules:
|
||||||
|
description: "List CEPH crush rules"
|
||||||
|
params:
|
||||||
|
format:
|
||||||
|
type: string
|
||||||
|
enum:
|
||||||
|
- json
|
||||||
|
- yaml
|
||||||
|
- text
|
||||||
|
default: text
|
||||||
|
description: "The output format, either json, yaml or text (default)"
|
||||||
|
additionalProperties: false
|
||||||
|
1
actions/list-crush-rules
Symbolic link
1
actions/list-crush-rules
Symbolic link
@ -0,0 +1 @@
|
|||||||
|
list_crush_rules.py
|
76
actions/list_crush_rules.py
Executable file
76
actions/list_crush_rules.py
Executable file
@ -0,0 +1,76 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
#
|
||||||
|
# Copyright 2022 Canonical Ltd
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# 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 json
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import yaml
|
||||||
|
from subprocess import check_output, CalledProcessError
|
||||||
|
|
||||||
|
_path = os.path.dirname(os.path.realpath(__file__))
|
||||||
|
_hooks = os.path.abspath(os.path.join(_path, "../hooks"))
|
||||||
|
|
||||||
|
|
||||||
|
def _add_path(path):
|
||||||
|
if path not in sys.path:
|
||||||
|
sys.path.insert(1, path)
|
||||||
|
|
||||||
|
|
||||||
|
_add_path(_hooks)
|
||||||
|
|
||||||
|
|
||||||
|
from charmhelpers.core.hookenv import (
|
||||||
|
ERROR,
|
||||||
|
log,
|
||||||
|
function_fail,
|
||||||
|
function_get,
|
||||||
|
function_set
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_list_crush_rules(output_format="text"):
|
||||||
|
"""Get list of Ceph crush rules.
|
||||||
|
|
||||||
|
:param output_format: specify output format
|
||||||
|
:type output_format: str
|
||||||
|
:returns: text: list of tuple (<rule_id> <rule_name>) or
|
||||||
|
yaml: list of crush rules in yaml format
|
||||||
|
json: list of crush rules in json format
|
||||||
|
:rtype: str
|
||||||
|
"""
|
||||||
|
crush_rules = check_output(["ceph", "--id", "admin", "osd", "crush",
|
||||||
|
"rule", "dump", "-f", "json"]).decode("UTF-8")
|
||||||
|
crush_rules = json.loads(crush_rules)
|
||||||
|
|
||||||
|
if output_format == "text":
|
||||||
|
return ",".join(["({}, {})".format(rule["rule_id"], rule["rule_name"])
|
||||||
|
for rule in crush_rules])
|
||||||
|
elif output_format == "yaml":
|
||||||
|
return yaml.dump(crush_rules)
|
||||||
|
else:
|
||||||
|
return json.dumps(crush_rules)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
try:
|
||||||
|
list_crush_rules = get_list_crush_rules(function_get("format"))
|
||||||
|
function_set({"message": list_crush_rules})
|
||||||
|
except CalledProcessError as error:
|
||||||
|
log(error, ERROR)
|
||||||
|
function_fail("List crush rules failed with error: {}".format(error))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
@ -34,5 +34,4 @@ class ReweightTestCase(CharmTestCase):
|
|||||||
osd_num = 4
|
osd_num = 4
|
||||||
new_weight = 1.2
|
new_weight = 1.2
|
||||||
action.crush_reweight(osd_num, new_weight)
|
action.crush_reweight(osd_num, new_weight)
|
||||||
print(_reweight_osd.calls)
|
|
||||||
_reweight_osd.assert_has_calls([mock.call("4", "1.2")])
|
_reweight_osd.assert_has_calls([mock.call("4", "1.2")])
|
||||||
|
155
unit_tests/test_action_list_crush_rules.py
Normal file
155
unit_tests/test_action_list_crush_rules.py
Normal file
@ -0,0 +1,155 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
#
|
||||||
|
# Copyright 2022 Canonical Ltd
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# 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 json
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
from actions import list_crush_rules
|
||||||
|
from test_utils import CharmTestCase
|
||||||
|
|
||||||
|
|
||||||
|
class ListCrushRulesTestCase(CharmTestCase):
|
||||||
|
ceph_osd_crush_rule_dump = b"""
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"rule_id": 0,
|
||||||
|
"rule_name": "replicated_rule",
|
||||||
|
"ruleset": 0,
|
||||||
|
"type": 1,
|
||||||
|
"min_size": 1,
|
||||||
|
"max_size": 10,
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"op": "take",
|
||||||
|
"item": -1,
|
||||||
|
"item_name": "default"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "chooseleaf_firstn",
|
||||||
|
"num": 0,
|
||||||
|
"type": "host"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "emit"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rule_id": 1,
|
||||||
|
"rule_name": "test-host",
|
||||||
|
"ruleset": 1,
|
||||||
|
"type": 1,
|
||||||
|
"min_size": 1,
|
||||||
|
"max_size": 10,
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"op": "take",
|
||||||
|
"item": -1,
|
||||||
|
"item_name": "default"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "chooseleaf_firstn",
|
||||||
|
"num": 0,
|
||||||
|
"type": "host"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "emit"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rule_id": 2,
|
||||||
|
"rule_name": "test-chassis",
|
||||||
|
"ruleset": 2,
|
||||||
|
"type": 1,
|
||||||
|
"min_size": 1,
|
||||||
|
"max_size": 10,
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"op": "take",
|
||||||
|
"item": -1,
|
||||||
|
"item_name": "default"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "chooseleaf_firstn",
|
||||||
|
"num": 0,
|
||||||
|
"type": "chassis"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "emit"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rule_id": 3,
|
||||||
|
"rule_name": "test-rack-hdd",
|
||||||
|
"ruleset": 3,
|
||||||
|
"type": 1,
|
||||||
|
"min_size": 1,
|
||||||
|
"max_size": 10,
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"op": "take",
|
||||||
|
"item": -2,
|
||||||
|
"item_name": "default~hdd"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "chooseleaf_firstn",
|
||||||
|
"num": 0,
|
||||||
|
"type": "rack"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "emit"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
"""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(ListCrushRulesTestCase, self).setUp(
|
||||||
|
list_crush_rules, ["check_output", "function_fail", "function_get",
|
||||||
|
"function_set"])
|
||||||
|
self.function_get.return_value = "json" # format=json
|
||||||
|
self.check_output.return_value = self.ceph_osd_crush_rule_dump
|
||||||
|
|
||||||
|
def test_getting_list_crush_rules_text_format(self):
|
||||||
|
"""Test getting list of crush rules in text format."""
|
||||||
|
self.function_get.return_value = "text"
|
||||||
|
list_crush_rules.main()
|
||||||
|
self.function_get.assert_called_once_with("format")
|
||||||
|
self.function_set.assert_called_once_with(
|
||||||
|
{"message": "(0, replicated_rule),(1, test-host),"
|
||||||
|
"(2, test-chassis),(3, test-rack-hdd)"})
|
||||||
|
|
||||||
|
def test_getting_list_crush_rules_json_format(self):
|
||||||
|
"""Test getting list of crush rules in json format."""
|
||||||
|
crush_rules = self.ceph_osd_crush_rule_dump.decode("UTF-8")
|
||||||
|
crush_rules = json.loads(crush_rules)
|
||||||
|
self.function_get.return_value = "json"
|
||||||
|
list_crush_rules.main()
|
||||||
|
self.function_get.assert_called_once_with("format")
|
||||||
|
self.function_set.assert_called_once_with(
|
||||||
|
{"message": json.dumps(crush_rules)})
|
||||||
|
|
||||||
|
def test_getting_list_crush_rules_yaml_format(self):
|
||||||
|
"""Test getting list of crush rules in yaml format."""
|
||||||
|
crush_rules = self.ceph_osd_crush_rule_dump.decode("UTF-8")
|
||||||
|
crush_rules = json.loads(crush_rules)
|
||||||
|
self.function_get.return_value = "yaml"
|
||||||
|
list_crush_rules.main()
|
||||||
|
self.function_get.assert_called_once_with("format")
|
||||||
|
self.function_set.assert_called_once_with(
|
||||||
|
{"message": yaml.dump(crush_rules)})
|
Loading…
Reference in New Issue
Block a user