Skip to content

Commit 394c9b0

Browse files
authored
Merge pull request #44 from stackhpc/ceph_pools_users
Add support for pools, users, CRUSH rules and EC profiles
2 parents 1e746e1 + 03955c0 commit 394c9b0

File tree

19 files changed

+1842
-0
lines changed

19 files changed

+1842
-0
lines changed

.github/workflows/ansible.yml renamed to .github/workflows/test.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,4 @@ jobs:
2525
- name: Linting code
2626
run: |
2727
ansible-lint -v --force-color
28+
flake8 -v
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
#!/usr/bin/python3
2+
3+
# Copyright 2020, Red Hat, Inc.
4+
# Copyright 2021, StackHPC, Ltd.
5+
# NOTE: Files adapted from github.com/ceph/ceph-ansible
6+
#
7+
# Licensed under the Apache License, Version 2.0 (the "License");
8+
# you may not use this file except in compliance with the License.
9+
# You may obtain a copy of the License at
10+
#
11+
# http://www.apache.org/licenses/LICENSE-2.0
12+
#
13+
# Unless required by applicable law or agreed to in writing, software
14+
# distributed under the License is distributed on an "AS IS" BASIS,
15+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
# See the License for the specific language governing permissions and
17+
# limitations under the License.
18+
19+
import datetime
20+
21+
22+
def generate_ceph_cmd(sub_cmd, args):
23+
'''
24+
Generate 'ceph' command line to execute
25+
'''
26+
27+
cmd = [
28+
'cephadm',
29+
'--timeout',
30+
'60',
31+
'shell',
32+
'--',
33+
'ceph',
34+
]
35+
cmd.extend(sub_cmd + args)
36+
37+
return cmd
38+
39+
40+
def exec_command(module, cmd, stdin=None):
41+
'''
42+
Execute command(s)
43+
'''
44+
45+
binary_data = False
46+
if stdin:
47+
binary_data = True
48+
rc, out, err = module.run_command(cmd, data=stdin, binary_data=binary_data)
49+
50+
return rc, cmd, out, err
51+
52+
53+
def exit_module(module, out, rc, cmd, err, startd, changed=False):
54+
endd = datetime.datetime.now()
55+
delta = endd - startd
56+
57+
result = dict(
58+
cmd=cmd,
59+
start=str(startd),
60+
end=str(endd),
61+
delta=str(delta),
62+
rc=rc,
63+
stdout=out.rstrip("\r\n"),
64+
stderr=err.rstrip("\r\n"),
65+
changed=changed,
66+
)
67+
module.exit_json(**result)
68+
69+
70+
def fatal(message, module):
71+
'''
72+
Report a fatal error and exit
73+
'''
74+
75+
if module:
76+
module.fail_json(msg=message, rc=1)
77+
else:
78+
raise(Exception(message))
Lines changed: 232 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,232 @@
1+
#!/usr/bin/python3
2+
3+
# Copyright 2020, Red Hat, Inc.
4+
# Copyright 2021, StackHPC, Ltd.
5+
# NOTE: Files adapted from github.com/ceph/ceph-ansible
6+
#
7+
# Licensed under the Apache License, Version 2.0 (the "License");
8+
# you may not use this file except in compliance with the License.
9+
# You may obtain a copy of the License at
10+
#
11+
# http://www.apache.org/licenses/LICENSE-2.0
12+
#
13+
# Unless required by applicable law or agreed to in writing, software
14+
# distributed under the License is distributed on an "AS IS" BASIS,
15+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
# See the License for the specific language governing permissions and
17+
# limitations under the License.
18+
19+
from __future__ import absolute_import, division, print_function
20+
__metaclass__ = type
21+
22+
from ansible.module_utils.basic import AnsibleModule
23+
from ansible_collections.stackhpc.cephadm.plugins.module_utils.cephadm_common \
24+
import generate_ceph_cmd, exec_command, exit_module
25+
26+
import datetime
27+
import json
28+
29+
30+
DOCUMENTATION = r'''
31+
---
32+
module: cephadm_crush_rule
33+
short_description: Manage Ceph Crush Replicated/Erasure Rule
34+
version_added: "1.4.0"
35+
description:
36+
- Manage Ceph Crush rule(s) creation, deletion and updates.
37+
options:
38+
name:
39+
description:
40+
- name of the Ceph Crush rule.
41+
required: true
42+
state:
43+
description:
44+
If 'present' is used, the module creates a rule if it doesn't
45+
exist or update it if it already exists.
46+
If 'absent' is used, the module will simply delete the rule.
47+
If 'info' is used, the module will return all details about the
48+
existing rule (json formatted).
49+
required: false
50+
choices: ['present', 'absent', 'info']
51+
default: present
52+
rule_type:
53+
description:
54+
- The ceph CRUSH rule type.
55+
required: false
56+
choices: ['replicated', 'erasure']
57+
required: false
58+
bucket_root:
59+
description:
60+
- The ceph bucket root for replicated rule.
61+
required: false
62+
bucket_type:
63+
description:
64+
- The ceph bucket type for replicated rule.
65+
required: false
66+
choices: ['osd', 'host', 'chassis', 'rack', 'row', 'pdu', 'pod',
67+
'room', 'datacenter', 'zone', 'region', 'root']
68+
device_class:
69+
description:
70+
- The ceph device class for replicated rule.
71+
required: false
72+
profile:
73+
description:
74+
- The ceph erasure profile for erasure rule.
75+
required: false
76+
author:
77+
- Dimitri Savineau <[email protected]>
78+
Michal Nasiadka <[email protected]>
79+
'''
80+
81+
EXAMPLES = '''
82+
- name: create a Ceph Crush replicated rule
83+
cephadm_crush_rule:
84+
name: foo
85+
bucket_root: default
86+
bucket_type: host
87+
device_class: ssd
88+
rule_type: replicated
89+
90+
- name: create a Ceph Crush erasure rule
91+
cephadm_crush_rule:
92+
name: foo
93+
profile: bar
94+
rule_type: erasure
95+
96+
- name: get a Ceph Crush rule information
97+
cephadm_crush_rule:
98+
name: foo
99+
state: info
100+
101+
- name: delete a Ceph Crush rule
102+
cephadm_crush_rule:
103+
name: foo
104+
state: absent
105+
'''
106+
107+
RETURN = '''# '''
108+
109+
110+
def create_rule(module, container_image=None):
111+
'''
112+
Create a new crush replicated/erasure rule
113+
'''
114+
115+
name = module.params.get('name')
116+
rule_type = module.params.get('rule_type')
117+
bucket_root = module.params.get('bucket_root')
118+
bucket_type = module.params.get('bucket_type')
119+
device_class = module.params.get('device_class')
120+
profile = module.params.get('profile')
121+
122+
if rule_type == 'replicated':
123+
args = ['create-replicated', name, bucket_root, bucket_type]
124+
if device_class:
125+
args.append(device_class)
126+
else:
127+
args = ['create-erasure', name]
128+
if profile:
129+
args.append(profile)
130+
131+
cmd = generate_ceph_cmd(['osd', 'crush', 'rule'],
132+
args)
133+
134+
return cmd
135+
136+
137+
def get_rule(module, container_image=None):
138+
'''
139+
Get existing crush rule
140+
'''
141+
142+
name = module.params.get('name')
143+
144+
args = ['dump', name, '--format=json']
145+
146+
cmd = generate_ceph_cmd(['osd', 'crush', 'rule'],
147+
args)
148+
149+
return cmd
150+
151+
152+
def remove_rule(module, container_image=None):
153+
'''
154+
Remove a crush rule
155+
'''
156+
157+
name = module.params.get('name')
158+
159+
args = ['rm', name]
160+
161+
cmd = generate_ceph_cmd(['osd', 'crush', 'rule'],
162+
args)
163+
164+
return cmd
165+
166+
167+
def main():
168+
module = AnsibleModule(
169+
argument_spec=dict(
170+
name=dict(type='str', required=True),
171+
state=dict(type='str', required=False, choices=['present', 'absent', 'info'], default='present'), # noqa: E501
172+
rule_type=dict(type='str', required=False, choices=['replicated', 'erasure']), # noqa: E501
173+
bucket_root=dict(type='str', required=False),
174+
bucket_type=dict(type='str', required=False, choices=['osd', 'host', 'chassis', 'rack', 'row', 'pdu', 'pod', # noqa: E501
175+
'room', 'datacenter', 'zone', 'region', 'root']), # noqa: E501
176+
device_class=dict(type='str', required=False),
177+
profile=dict(type='str', required=False)
178+
),
179+
supports_check_mode=True,
180+
required_if=[
181+
('state', 'present', ['rule_type']),
182+
('rule_type', 'replicated', ['bucket_root', 'bucket_type']),
183+
('rule_type', 'erasure', ['profile'])
184+
]
185+
)
186+
187+
# Gather module parameters in variables
188+
name = module.params.get('name')
189+
state = module.params.get('state')
190+
rule_type = module.params.get('rule_type')
191+
192+
if module.check_mode:
193+
module.exit_json(
194+
changed=False,
195+
stdout='',
196+
stderr='',
197+
rc=0,
198+
start='',
199+
end='',
200+
delta='',
201+
)
202+
203+
startd = datetime.datetime.now()
204+
changed = False
205+
206+
if state == "present":
207+
rc, cmd, out, err = exec_command(module, get_rule(module)) # noqa: E501
208+
if rc != 0:
209+
rc, cmd, out, err = exec_command(module, create_rule(module)) # noqa: E501
210+
changed = True
211+
else:
212+
rule = json.loads(out)
213+
if (rule['type'] == 1 and rule_type == 'erasure') or (rule['type'] == 3 and rule_type == 'replicated'): # noqa: E501
214+
module.fail_json(msg="Can not convert crush rule {} to {}".format(name, rule_type), changed=False, rc=1) # noqa: E501
215+
216+
elif state == "absent":
217+
rc, cmd, out, err = exec_command(module, get_rule(module)) # noqa: E501
218+
if rc == 0:
219+
rc, cmd, out, err = exec_command(module, remove_rule(module)) # noqa: E501
220+
changed = True
221+
else:
222+
rc = 0
223+
out = "Crush Rule {} doesn't exist".format(name)
224+
225+
elif state == "info":
226+
rc, cmd, out, err = exec_command(module, get_rule(module)) # noqa: E501
227+
228+
exit_module(module=module, out=out, rc=rc, cmd=cmd, err=err, startd=startd, changed=changed) # noqa: E501
229+
230+
231+
if __name__ == '__main__':
232+
main()

0 commit comments

Comments
 (0)