Add broker request to move Disk
This allows a remote charm, ideally an OSD unit, to request that the monitor move a disk to a given bucket in the Crushmap Change-Id: I77d8159d588f3b31d6eb1c25cd5ca16e53c6f01d
This commit is contained in:
parent
c04b57a79f
commit
2ecb4f641a
@ -455,6 +455,33 @@ class CrushLocation(object):
|
|||||||
return self.name < other.name
|
return self.name < other.name
|
||||||
|
|
||||||
|
|
||||||
|
def get_osd_weight(osd_id):
|
||||||
|
"""
|
||||||
|
Returns the weight of the specified OSD
|
||||||
|
:return: Float :raise: ValueError if the monmap fails to parse.
|
||||||
|
Also raises CalledProcessError if our ceph command fails
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
tree = subprocess.check_output(
|
||||||
|
['ceph', 'osd', 'tree', '--format=json'])
|
||||||
|
try:
|
||||||
|
json_tree = json.loads(tree)
|
||||||
|
# Make sure children are present in the json
|
||||||
|
if not json_tree['nodes']:
|
||||||
|
return None
|
||||||
|
for device in json_tree['nodes']:
|
||||||
|
if device['type'] == 'osd' and device['name'] == osd_id:
|
||||||
|
return device['crush_weight']
|
||||||
|
except ValueError as v:
|
||||||
|
log("Unable to parse ceph tree json: {}. Error: {}".format(
|
||||||
|
tree, v.message))
|
||||||
|
raise
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
log("ceph osd tree command failed with message: {}".format(
|
||||||
|
e.message))
|
||||||
|
raise
|
||||||
|
|
||||||
|
|
||||||
def get_osd_tree(service):
|
def get_osd_tree(service):
|
||||||
"""
|
"""
|
||||||
Returns the current osd map in JSON.
|
Returns the current osd map in JSON.
|
||||||
|
@ -24,7 +24,11 @@ from charmhelpers.core.hookenv import (
|
|||||||
INFO,
|
INFO,
|
||||||
ERROR,
|
ERROR,
|
||||||
)
|
)
|
||||||
from ceph import get_cephfs
|
from ceph import (
|
||||||
|
get_cephfs,
|
||||||
|
get_osd_weight
|
||||||
|
)
|
||||||
|
from ceph_helpers import Crushmap
|
||||||
from charmhelpers.contrib.storage.linux.ceph import (
|
from charmhelpers.contrib.storage.linux.ceph import (
|
||||||
create_erasure_profile,
|
create_erasure_profile,
|
||||||
delete_pool,
|
delete_pool,
|
||||||
@ -360,6 +364,36 @@ def handle_rgw_zone_set(request, service):
|
|||||||
os.unlink(infile.name)
|
os.unlink(infile.name)
|
||||||
|
|
||||||
|
|
||||||
|
def handle_put_osd_in_bucket(request, service):
|
||||||
|
osd_id = request.get('osd')
|
||||||
|
target_bucket = request.get('bucket')
|
||||||
|
if not osd_id or not target_bucket:
|
||||||
|
msg = "Missing OSD ID or Bucket"
|
||||||
|
log(msg, level=ERROR)
|
||||||
|
return {'exit-code': 1, 'stderr': msg}
|
||||||
|
crushmap = Crushmap()
|
||||||
|
try:
|
||||||
|
crushmap.ensure_bucket_is_present(target_bucket)
|
||||||
|
check_output(
|
||||||
|
[
|
||||||
|
'ceph',
|
||||||
|
'--id', service,
|
||||||
|
'osd',
|
||||||
|
'crush',
|
||||||
|
'set',
|
||||||
|
osd_id,
|
||||||
|
get_osd_weight(osd_id),
|
||||||
|
"root={}".format(target_bucket)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
except Exception as exc:
|
||||||
|
msg = "Failed to move OSD " \
|
||||||
|
"{} into Bucket {} :: {}".format(osd_id, target_bucket, exc)
|
||||||
|
log(msg, level=ERROR)
|
||||||
|
return {'exit-code': 1, 'stderr': msg}
|
||||||
|
|
||||||
|
|
||||||
def handle_rgw_create_user(request, service):
|
def handle_rgw_create_user(request, service):
|
||||||
user_id = request.get('rgw-uid')
|
user_id = request.get('rgw-uid')
|
||||||
display_name = request.get('display-name')
|
display_name = request.get('display-name')
|
||||||
@ -534,6 +568,8 @@ def process_requests_v1(reqs):
|
|||||||
ret = handle_rgw_regionmap_default(request=req, service=svc)
|
ret = handle_rgw_regionmap_default(request=req, service=svc)
|
||||||
elif op == "rgw-create-user":
|
elif op == "rgw-create-user":
|
||||||
ret = handle_rgw_create_user(request=req, service=svc)
|
ret = handle_rgw_create_user(request=req, service=svc)
|
||||||
|
elif op == "move-osd-to-bucket":
|
||||||
|
ret = handle_put_osd_in_bucket(request=req, service=svc)
|
||||||
else:
|
else:
|
||||||
msg = "Unknown operation '%s'" % op
|
msg = "Unknown operation '%s'" % op
|
||||||
log(msg, level=ERROR)
|
log(msg, level=ERROR)
|
||||||
|
@ -193,6 +193,11 @@ class Crushmap(object):
|
|||||||
log("load_crushmap error: {}".format(e))
|
log("load_crushmap error: {}".format(e))
|
||||||
raise "Failed to read Crushmap"
|
raise "Failed to read Crushmap"
|
||||||
|
|
||||||
|
def ensure_bucket_is_present(self, bucket_name):
|
||||||
|
if bucket_name not in [bucket.name() for bucket in self.buckets()]:
|
||||||
|
self.add_bucket(bucket_name)
|
||||||
|
self.save()
|
||||||
|
|
||||||
def buckets(self):
|
def buckets(self):
|
||||||
"""Return a list of buckets that are in the Crushmap."""
|
"""Return a list of buckets that are in the Crushmap."""
|
||||||
return self._buckets
|
return self._buckets
|
||||||
|
@ -37,6 +37,73 @@ class CephTestCase(unittest.TestCase):
|
|||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(CephTestCase, self).setUp()
|
super(CephTestCase, self).setUp()
|
||||||
|
|
||||||
|
@mock.patch('subprocess.check_output')
|
||||||
|
def test_get_osd_weight(self, output):
|
||||||
|
"""It gives an OSD's weight"""
|
||||||
|
output.return_value = """{
|
||||||
|
"nodes": [{
|
||||||
|
"id": -1,
|
||||||
|
"name": "default",
|
||||||
|
"type": "root",
|
||||||
|
"type_id": 10,
|
||||||
|
"children": [-4, -3, -2]
|
||||||
|
}, {
|
||||||
|
"id": -2,
|
||||||
|
"name": "ip-172-31-11-147",
|
||||||
|
"type": "host",
|
||||||
|
"type_id": 1,
|
||||||
|
"children": [0]
|
||||||
|
}, {
|
||||||
|
"id": 0,
|
||||||
|
"name": "osd.0",
|
||||||
|
"type": "osd",
|
||||||
|
"type_id": 0,
|
||||||
|
"crush_weight": 0.002899,
|
||||||
|
"depth": 2,
|
||||||
|
"exists": 1,
|
||||||
|
"status": "up",
|
||||||
|
"reweight": 1.000000,
|
||||||
|
"primary_affinity": 1.000000
|
||||||
|
}, {
|
||||||
|
"id": -3,
|
||||||
|
"name": "ip-172-31-56-198",
|
||||||
|
"type": "host",
|
||||||
|
"type_id": 1,
|
||||||
|
"children": [2]
|
||||||
|
}, {
|
||||||
|
"id": 2,
|
||||||
|
"name": "osd.2",
|
||||||
|
"type": "osd",
|
||||||
|
"type_id": 0,
|
||||||
|
"crush_weight": 0.002899,
|
||||||
|
"depth": 2,
|
||||||
|
"exists": 1,
|
||||||
|
"status": "up",
|
||||||
|
"reweight": 1.000000,
|
||||||
|
"primary_affinity": 1.000000
|
||||||
|
}, {
|
||||||
|
"id": -4,
|
||||||
|
"name": "ip-172-31-24-103",
|
||||||
|
"type": "host",
|
||||||
|
"type_id": 1,
|
||||||
|
"children": [1]
|
||||||
|
}, {
|
||||||
|
"id": 1,
|
||||||
|
"name": "osd.1",
|
||||||
|
"type": "osd",
|
||||||
|
"type_id": 0,
|
||||||
|
"crush_weight": 0.002899,
|
||||||
|
"depth": 2,
|
||||||
|
"exists": 1,
|
||||||
|
"status": "up",
|
||||||
|
"reweight": 1.000000,
|
||||||
|
"primary_affinity": 1.000000
|
||||||
|
}],
|
||||||
|
"stray": []
|
||||||
|
}"""
|
||||||
|
weight = ceph.get_osd_weight('osd.0')
|
||||||
|
self.assertEqual(weight, 0.002899)
|
||||||
|
|
||||||
def test_get_named_key_with_pool(self):
|
def test_get_named_key_with_pool(self):
|
||||||
with mock.patch.object(ceph, "ceph_user", return_value="ceph"):
|
with mock.patch.object(ceph, "ceph_user", return_value="ceph"):
|
||||||
with mock.patch.object(ceph.subprocess, "check_output") \
|
with mock.patch.object(ceph.subprocess, "check_output") \
|
||||||
|
@ -146,6 +146,36 @@ class CephBrokerTestCase(unittest.TestCase):
|
|||||||
self.assertEqual(json.loads(rc)['exit-code'], 0)
|
self.assertEqual(json.loads(rc)['exit-code'], 0)
|
||||||
self.assertEqual(json.loads(rc)['request-id'], '1ef5aede')
|
self.assertEqual(json.loads(rc)['request-id'], '1ef5aede')
|
||||||
|
|
||||||
|
@mock.patch('ceph_broker.check_output')
|
||||||
|
@mock.patch('ceph_helpers.Crushmap.load_crushmap')
|
||||||
|
@mock.patch('ceph_helpers.Crushmap.ensure_bucket_is_present')
|
||||||
|
@mock.patch('ceph_broker.get_osd_weight')
|
||||||
|
@mock.patch('ceph_broker.log')
|
||||||
|
def test_process_requests_move_osd(self,
|
||||||
|
mock_log,
|
||||||
|
get_osd_weight,
|
||||||
|
ensure_bucket_is_present,
|
||||||
|
load_crushmap,
|
||||||
|
check_output):
|
||||||
|
load_crushmap.return_value = ""
|
||||||
|
ensure_bucket_is_present.return_value = None
|
||||||
|
get_osd_weight.return_value = 1
|
||||||
|
reqs = json.dumps({'api-version': 1,
|
||||||
|
'request-id': '1ef5aede',
|
||||||
|
'ops': [{
|
||||||
|
'op': 'move-osd-to-bucket',
|
||||||
|
'osd': 'osd.0',
|
||||||
|
'bucket': 'test'
|
||||||
|
}]})
|
||||||
|
rc = ceph_broker.process_requests(reqs)
|
||||||
|
check_output.assert_called_with(["ceph",
|
||||||
|
'--id', 'admin',
|
||||||
|
"osd", "crush", "set",
|
||||||
|
u"osd.0", 1, "root=test"])
|
||||||
|
|
||||||
|
self.assertEqual(json.loads(rc)['exit-code'], 0)
|
||||||
|
self.assertEqual(json.loads(rc)['request-id'], '1ef5aede')
|
||||||
|
|
||||||
@mock.patch('ceph_broker.log')
|
@mock.patch('ceph_broker.log')
|
||||||
def test_process_requests_invalid_api_rid(self, mock_log):
|
def test_process_requests_invalid_api_rid(self, mock_log):
|
||||||
reqs = json.dumps({'api-version': 0, 'request-id': '1ef5aede',
|
reqs = json.dumps({'api-version': 0, 'request-id': '1ef5aede',
|
||||||
|
Loading…
Reference in New Issue
Block a user