3 from ceph
.deployment
.inventory
import Device
4 from ceph
.deployment
.service_spec
import ServiceSpecValidationError
, ServiceSpec
, PlacementSpec
7 from typing
import Optional
, List
, Dict
, Any
13 class DeviceSelection(object):
15 Used within :class:`ceph.deployment.drive_group.DriveGroupSpec` to specify the devices
16 used by the Drive Group.
18 Any attributes (even none) can be included in the device
19 specification structure.
22 _supported_filters
= [
23 "paths", "size", "vendor", "model", "rotational", "limit", "all"
27 paths
=None, # type: Optional[List[str]]
28 model
=None, # type: Optional[str]
29 size
=None, # type: Optional[str]
30 rotational
=None, # type: Optional[bool]
31 limit
=None, # type: Optional[int]
32 vendor
=None, # type: Optional[str]
33 all
=False, # type: bool
36 ephemeral drive group device specification
38 #: List of Device objects for devices paths.
39 self
.paths
= [] if paths
is None else [Device(path
) for path
in paths
] # type: List[Device]
41 #: A wildcard string. e.g: "SDD*" or "SanDisk SD8SN8U5"
44 #: Match on the VENDOR property of the drive
47 #: Size specification of format LOW:HIGH.
48 #: Can also take the the form :HIGH, LOW:
49 #: or an exact value (as ceph-volume inventory reports)
50 self
.size
: Optional
[str] = size
52 #: is the drive rotating or not
53 self
.rotational
= rotational
55 #: Limit the number of devices added to this Drive Group. Devices
56 #: are used from top to bottom in the output of ``ceph-volume inventory``
59 #: Matches all devices. Can only be used for data devices
66 props
= [self
.model
, self
.vendor
, self
.size
, self
.rotational
] # type: List[Any]
67 if self
.paths
and any(p
is not None for p
in props
):
68 raise DriveGroupValidationError(
69 'DeviceSelection: `paths` and other parameters are mutually exclusive')
70 is_empty
= not any(p
is not None and p
!= [] for p
in [self
.paths
] + props
)
71 if not self
.all
and is_empty
:
72 raise DriveGroupValidationError('DeviceSelection cannot be empty')
74 if self
.all
and not is_empty
:
75 raise DriveGroupValidationError(
76 'DeviceSelection `all` and other parameters are mutually exclusive. {}'.format(
80 def from_json(cls
, device_spec
):
81 # type: (dict) -> Optional[DeviceSelection]
84 for applied_filter
in list(device_spec
.keys()):
85 if applied_filter
not in cls
._supported
_filters
:
86 raise DriveGroupValidationError(
87 "Filtering for <{}> is not supported".format(applied_filter
))
89 return cls(**device_spec
)
92 # type: () -> Dict[str, Any]
93 ret
: Dict
[str, Any
] = {}
95 ret
['paths'] = [p
.path
for p
in self
.paths
]
97 ret
['model'] = self
.model
99 ret
['vendor'] = self
.vendor
101 ret
['size'] = self
.size
103 ret
['rotational'] = self
.rotational
105 ret
['limit'] = self
.limit
107 ret
['all'] = self
.all
113 key
for key
in self
._supported
_filters
+ ['limit'] if getattr(self
, key
) is not None
115 if 'paths' in keys
and self
.paths
== []:
117 return "DeviceSelection({})".format(
118 ', '.join('{}={}'.format(key
, repr(getattr(self
, key
))) for key
in keys
)
121 def __eq__(self
, other
):
122 return repr(self
) == repr(other
)
125 class DriveGroupValidationError(ServiceSpecValidationError
):
127 Defining an exception here is a bit problematic, cause you cannot properly catch it,
128 if it was raised in a different mgr module.
131 def __init__(self
, msg
):
132 super(DriveGroupValidationError
, self
).__init
__('Failed to validate Drive Group: ' + msg
)
135 class DriveGroupSpec(ServiceSpec
):
137 Describe a drive group in the same form that ceph-volume
141 _supported_features
= [
142 "encrypted", "block_wal_size", "osds_per_device",
143 "db_slots", "wal_slots", "block_db_size", "placement", "service_id", "service_type",
144 "data_devices", "db_devices", "wal_devices", "journal_devices",
145 "data_directories", "osds_per_device", "objectstore", "osd_id_claims",
146 "journal_size", "unmanaged", "filter_logic", "preview_only"
150 placement
=None, # type: Optional[PlacementSpec]
151 service_id
=None, # type: str
152 data_devices
=None, # type: Optional[DeviceSelection]
153 db_devices
=None, # type: Optional[DeviceSelection]
154 wal_devices
=None, # type: Optional[DeviceSelection]
155 journal_devices
=None, # type: Optional[DeviceSelection]
156 data_directories
=None, # type: Optional[List[str]]
157 osds_per_device
=None, # type: Optional[int]
158 objectstore
='bluestore', # type: str
159 encrypted
=False, # type: bool
160 db_slots
=None, # type: Optional[int]
161 wal_slots
=None, # type: Optional[int]
162 osd_id_claims
=None, # type: Optional[Dict[str, List[str]]]
163 block_db_size
=None, # type: Optional[int]
164 block_wal_size
=None, # type: Optional[int]
165 journal_size
=None, # type: Optional[int]
166 service_type
=None, # type: Optional[str]
167 unmanaged
=False, # type: bool
168 filter_logic
='AND', # type: str
169 preview_only
=False, # type: bool
171 assert service_type
is None or service_type
== 'osd'
172 super(DriveGroupSpec
, self
).__init
__('osd', service_id
=service_id
,
175 preview_only
=preview_only
)
177 #: A :class:`ceph.deployment.drive_group.DeviceSelection`
178 self
.data_devices
= data_devices
180 #: A :class:`ceph.deployment.drive_group.DeviceSelection`
181 self
.db_devices
= db_devices
183 #: A :class:`ceph.deployment.drive_group.DeviceSelection`
184 self
.wal_devices
= wal_devices
186 #: A :class:`ceph.deployment.drive_group.DeviceSelection`
187 self
.journal_devices
= journal_devices
189 #: Set (or override) the "bluestore_block_wal_size" value, in bytes
190 self
.block_wal_size
= block_wal_size
192 #: Set (or override) the "bluestore_block_db_size" value, in bytes
193 self
.block_db_size
= block_db_size
195 #: set journal_size in bytes
196 self
.journal_size
= journal_size
198 #: Number of osd daemons per "DATA" device.
199 #: To fully utilize nvme devices multiple osds are required.
200 self
.osds_per_device
= osds_per_device
202 #: A list of strings, containing paths which should back OSDs
203 self
.data_directories
= data_directories
205 #: ``filestore`` or ``bluestore``
206 self
.objectstore
= objectstore
208 #: ``true`` or ``false``
209 self
.encrypted
= encrypted
211 #: How many OSDs per DB device
212 self
.db_slots
= db_slots
214 #: How many OSDs per WAL device
215 self
.wal_slots
= wal_slots
217 #: Optional: mapping of host -> List of osd_ids that should be replaced
218 #: See :ref:`orchestrator-osd-replace`
219 self
.osd_id_claims
= osd_id_claims
or dict()
221 #: The logic gate we use to match disks with filters.
223 self
.filter_logic
= filter_logic
.upper()
225 #: If this should be treated as a 'preview' spec
226 self
.preview_only
= preview_only
229 def _from_json_impl(cls
, json_drive_group
):
230 # type: (dict) -> DriveGroupSpec
232 Initialize 'Drive group' structure
234 :param json_drive_group: A valid json string with a Drive Group
238 # legacy json (pre Octopus)
239 if 'host_pattern' in json_drive_group
and 'placement' not in json_drive_group
:
240 json_drive_group
['placement'] = {'host_pattern': json_drive_group
['host_pattern']}
241 del json_drive_group
['host_pattern']
244 args
['placement'] = PlacementSpec
.from_json(json_drive_group
.pop('placement'))
246 raise DriveGroupValidationError('OSD spec needs a `placement` key.')
248 args
['service_type'] = json_drive_group
.pop('service_type', 'osd')
250 # service_id was not required in early octopus.
251 args
['service_id'] = json_drive_group
.pop('service_id', '')
253 # spec: was not mandatory in octopus
254 if 'spec' in json_drive_group
:
255 args
.update(cls
._drive
_group
_spec
_from
_json
(json_drive_group
.pop('spec')))
257 args
.update(cls
._drive
_group
_spec
_from
_json
(json_drive_group
))
262 def _drive_group_spec_from_json(cls
, json_drive_group
: dict) -> dict:
263 for applied_filter
in list(json_drive_group
.keys()):
264 if applied_filter
not in cls
._supported
_features
:
265 raise DriveGroupValidationError(
266 "Feature <{}> is not supported".format(applied_filter
))
268 for key
in ('block_wal_size', 'block_db_size', 'journal_size'):
269 if key
in json_drive_group
:
270 if isinstance(json_drive_group
[key
], six
.string_types
):
271 from ceph
.deployment
.drive_selection
import SizeMatcher
272 json_drive_group
[key
] = SizeMatcher
.str_to_byte(json_drive_group
[key
])
275 args
= {k
: (DeviceSelection
.from_json(v
) if k
.endswith('_devices') else v
) for k
, v
in
276 json_drive_group
.items()}
278 raise DriveGroupValidationError("Didn't find Drivegroup specs")
280 except (KeyError, TypeError) as e
:
281 raise DriveGroupValidationError(str(e
))
285 super(DriveGroupSpec
, self
).validate()
287 if not self
.service_id
:
288 raise DriveGroupValidationError('service_id is required')
290 if not isinstance(self
.placement
.host_pattern
, six
.string_types
) and \
291 self
.placement
.host_pattern
is not None:
292 raise DriveGroupValidationError('host_pattern must be of type string')
294 specs
= [self
.data_devices
, self
.db_devices
, self
.wal_devices
, self
.journal_devices
]
295 for s
in filter(None, specs
):
297 for s
in filter(None, [self
.db_devices
, self
.wal_devices
, self
.journal_devices
]):
299 raise DriveGroupValidationError("`paths` is only allowed for data_devices")
301 raise DriveGroupValidationError("`all` is only allowed for data_devices")
303 if self
.objectstore
not in ('bluestore'):
304 raise DriveGroupValidationError(f
"{self.objectstore} is not supported. Must be "
305 f
"one of ('bluestore')")
307 if self
.block_wal_size
is not None and type(self
.block_wal_size
) != int:
308 raise DriveGroupValidationError('block_wal_size must be of type int')
309 if self
.block_db_size
is not None and type(self
.block_db_size
) != int:
310 raise DriveGroupValidationError('block_db_size must be of type int')
312 if self
.filter_logic
not in ['AND', 'OR']:
313 raise DriveGroupValidationError('filter_logic must be either <AND> or <OR>')
317 key
for key
in self
._supported
_features
if getattr(self
, key
) is not None
319 if 'encrypted' in keys
and not self
.encrypted
:
320 keys
.remove('encrypted')
321 if 'objectstore' in keys
and self
.objectstore
== 'bluestore':
322 keys
.remove('objectstore')
323 return "DriveGroupSpec(name={}->{})".format(
325 ', '.join('{}={}'.format(key
, repr(getattr(self
, key
))) for key
in keys
)
328 def __eq__(self
, other
):
329 return repr(self
) == repr(other
)
332 yaml
.add_representer(DriveGroupSpec
, DriveGroupSpec
.yaml_representer
)