Fix some bugs when testing opensds ansible
[stor4nfv.git] / src / ceph / src / ceph-volume / ceph_volume / devices / lvm / listing.py
1 from __future__ import print_function
2 import argparse
3 import json
4 import logging
5 from textwrap import dedent
6 from ceph_volume import decorators
7 from ceph_volume.util import disk
8 from ceph_volume.api import lvm as api
9
10 logger = logging.getLogger(__name__)
11
12
13 osd_list_header_template = """\n
14 {osd_id:=^20}"""
15
16
17 osd_device_header_template = """
18
19   [{type: >4}]    {path}
20 """
21
22 device_metadata_item_template = """
23       {tag_name: <25} {value}"""
24
25
26 def readable_tag(tag):
27     actual_name = tag.split('.')[-1]
28     return actual_name.replace('_', ' ')
29
30
31 def pretty_report(report):
32     output = []
33     for _id, devices in report.items():
34         output.append(
35             osd_list_header_template.format(osd_id=" osd.%s " % _id)
36         )
37         for device in devices:
38             output.append(
39                 osd_device_header_template.format(
40                     type=device['type'],
41                     path=device['path']
42                 )
43             )
44             for tag_name, value in device.get('tags', {}).items():
45                 output.append(
46                     device_metadata_item_template.format(
47                         tag_name=readable_tag(tag_name),
48                         value=value
49                     )
50                 )
51     print(''.join(output))
52
53
54 class List(object):
55
56     help = 'list logical volumes and devices associated with Ceph'
57
58     def __init__(self, argv):
59         self.argv = argv
60
61     @decorators.needs_root
62     def list(self, args):
63         # ensure everything is up to date before calling out
64         # to list lv's
65         self.update()
66         report = self.generate(args)
67         if args.format == 'json':
68             # If the report is empty, we don't return a non-zero exit status
69             # because it is assumed this is going to be consumed by automated
70             # systems like ceph-ansible which would be forced to ignore the
71             # non-zero exit status if all they need is the information in the
72             # JSON object
73             print(json.dumps(report, indent=4, sort_keys=True))
74         else:
75             if not report:
76                 raise SystemExit('No valid Ceph devices found')
77             pretty_report(report)
78
79     def update(self):
80         """
81         Ensure all journal devices are up to date if they aren't a logical
82         volume
83         """
84         lvs = api.Volumes()
85         for lv in lvs:
86             try:
87                 lv.tags['ceph.osd_id']
88             except KeyError:
89                 # only consider ceph-based logical volumes, everything else
90                 # will get ignored
91                 continue
92
93             for device_type in ['journal', 'block', 'wal', 'db']:
94                 device_name = 'ceph.%s_device' % device_type
95                 device_uuid = lv.tags.get('ceph.%s_uuid' % device_type)
96                 if not device_uuid:
97                     # bluestore will not have a journal, filestore will not have
98                     # a block/wal/db, so we must skip if not present
99                     continue
100                 disk_device = disk.get_device_from_partuuid(device_uuid)
101                 if disk_device:
102                     if lv.tags[device_name] != disk_device:
103                         # this means that the device has changed, so it must be updated
104                         # on the API to reflect this
105                         lv.set_tags({device_name: disk_device})
106
107     def generate(self, args):
108         """
109         Generate reports for an individual device or for all Ceph-related
110         devices, logical or physical, as long as they have been prepared by
111         this tool before and contain enough metadata.
112         """
113         if args.device:
114             return self.single_report(args.device)
115         else:
116             return self.full_report()
117
118     def single_report(self, device):
119         """
120         Generate a report for a single device. This can be either a logical
121         volume in the form of vg/lv or a device with an absolute path like
122         /dev/sda1
123         """
124         lvs = api.Volumes()
125         report = {}
126         lv = api.get_lv_from_argument(device)
127         if lv:
128             try:
129                 _id = lv.tags['ceph.osd_id']
130             except KeyError:
131                 logger.warning('device is not part of ceph: %s', device)
132                 return report
133
134             report.setdefault(_id, [])
135             report[_id].append(
136                 lv.as_dict()
137             )
138
139         else:
140             # this has to be a journal/wal/db device (not a logical volume) so try
141             # to find the PARTUUID that should be stored in the OSD logical
142             # volume
143             for device_type in ['journal', 'block', 'wal', 'db']:
144                 device_tag_name = 'ceph.%s_device' % device_type
145                 device_tag_uuid = 'ceph.%s_uuid' % device_type
146                 associated_lv = lvs.get(lv_tags={device_tag_name: device})
147                 if associated_lv:
148                     _id = associated_lv.tags['ceph.osd_id']
149                     uuid = associated_lv.tags[device_tag_uuid]
150
151                     report.setdefault(_id, [])
152                     report[_id].append(
153                         {
154                             'tags': {'PARTUUID': uuid},
155                             'type': device_type,
156                             'path': device,
157                         }
158                     )
159         return report
160
161     def full_report(self):
162         """
163         Generate a report for all the logical volumes and associated devices
164         that have been previously prepared by Ceph
165         """
166         lvs = api.Volumes()
167         report = {}
168         for lv in lvs:
169             try:
170                 _id = lv.tags['ceph.osd_id']
171             except KeyError:
172                 # only consider ceph-based logical volumes, everything else
173                 # will get ignored
174                 continue
175
176             report.setdefault(_id, [])
177             report[_id].append(
178                 lv.as_dict()
179             )
180
181             for device_type in ['journal', 'block', 'wal', 'db']:
182                 device_uuid = lv.tags.get('ceph.%s_uuid' % device_type)
183                 if not device_uuid:
184                     # bluestore will not have a journal, filestore will not have
185                     # a block/wal/db, so we must skip if not present
186                     continue
187                 if not api.get_lv(lv_uuid=device_uuid):
188                     # means we have a regular device, so query blkid
189                     disk_device = disk.get_device_from_partuuid(device_uuid)
190                     if disk_device:
191                         report[_id].append(
192                             {
193                                 'tags': {'PARTUUID': device_uuid},
194                                 'type': device_type,
195                                 'path': disk_device,
196                             }
197                         )
198
199         return report
200
201     def main(self):
202         sub_command_help = dedent("""
203         List devices or logical volumes associated with Ceph. An association is
204         determined if a device has information relating to an OSD. This is
205         verified by querying LVM's metadata and correlating it with devices.
206
207         The lvs associated with the OSD need to have been prepared previously,
208         so that all needed tags and metadata exist.
209
210         Full listing of all system devices associated with a cluster::
211
212             ceph-volume lvm list
213
214         List a particular device, reporting all metadata about it::
215
216             ceph-volume lvm list /dev/sda1
217
218         List a logical volume, along with all its metadata (vg is a volume
219         group, and lv the logical volume name)::
220
221             ceph-volume lvm list {vg/lv}
222         """)
223         parser = argparse.ArgumentParser(
224             prog='ceph-volume lvm list',
225             formatter_class=argparse.RawDescriptionHelpFormatter,
226             description=sub_command_help,
227         )
228
229         parser.add_argument(
230             'device',
231             metavar='DEVICE',
232             nargs='?',
233             help='Path to an lv (as vg/lv) or to a device like /dev/sda1'
234         )
235
236         parser.add_argument(
237             '--format',
238             help='output format, defaults to "pretty"',
239             default='pretty',
240             choices=['json', 'pretty'],
241         )
242
243         args = parser.parse_args(self.argv)
244         self.list(args)