drydock/python/drydock_provisioner/drivers/node/maasdriver/models/blockdev.py

271 lines
9.3 KiB
Python

# Copyright 2017 AT&T Intellectual Property. All other rights reserved.
#
# 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.
"""API model for MaaS node block device resource."""
import uuid
from . import base as model_base
from . import partition as maas_partition
import drydock_provisioner.error as errors
class BlockDevice(model_base.ResourceBase):
resource_url = 'nodes/{system_id}/blockdevices/{resource_id}/'
fields = [
'resource_id',
'system_id',
'name',
'path',
'size',
'type',
'path',
'partitions',
'uuid',
'filesystem',
'tags',
'serial',
'model',
'id_path',
'bootable',
'available_size',
]
json_fields = [
'name',
]
"""Filesystem dictionary fields:
mount_point: the mount point on the system directory hierarchy
fstype: The filesystem format, defaults to ext4
mount_options: The mount options specified in /etc/fstab, defaults to 'defaults'
label: The filesystem lab
uuid: The filesystem uuid
"""
def __init__(self, api_client, **kwargs):
super().__init__(api_client, **kwargs)
if hasattr(self, 'resource_id') and hasattr(self, 'system_id'):
try:
self.partitions = maas_partition.Partitions(
api_client,
system_id=self.system_id,
device_id=self.resource_id)
self.partitions.refresh()
except Exception:
self.logger.warning(
"Could not load partitions on node %s block device %s" %
(self.system_id, self.resource_id))
else:
self.partitions = None
def format(self, fstype='ext4', uuid_str=None, label=None):
"""Format this block device with a filesystem.
:param fstype: String of the filesystem format to use, defaults to ext4
:param uuid: String of the UUID to assign to the filesystem. One will be
generated if this is left as None
"""
try:
data = {'fstype': fstype}
if uuid_str:
data['uuid'] = str(uuid_str)
else:
data['uuid'] = str(uuid.uuid4())
url = self.interpolate_url()
self.logger.debug(
"Formatting device %s on node %s as filesystem: fstype=%s, uuid=%s"
% (self.name, self.system_id, fstype, uuid))
resp = self.api_client.post(url, op='format', files=data)
if not resp.ok:
raise Exception(
"MAAS error: %s - %s" % (resp.status_code, resp.text))
self.refresh()
except Exception as ex:
msg = "Error: format of device %s on node %s failed: %s" \
% (self.name, self.system_id, str(ex))
self.logger.error(msg)
raise errors.DriverError(msg)
def unformat(self):
"""Unformat this block device.
Will attempt to unmount the device first.
"""
try:
self.refresh()
if self.filesystem is None:
self.logger.debug(
"Device %s not currently formatted, skipping unformat." %
(self.name))
return
if self.filesystem.get('mount_point', None) is not None:
self.unmount()
url = self.interpolate_url()
self.logger.debug("Unformatting device %s on node %s" %
(self.name, self.system_id))
resp = self.api_client.post(url, op='unformat')
if not resp.ok:
raise Exception(
"MAAS error: %s - %s" % (resp.status_code, resp.text))
self.refresh()
except Exception as ex:
msg = "Error: unformat of device %s on node %s failed: %s" \
% (self.name, self.system_id, str(ex))
self.logger.error(msg)
raise errors.DriverError(msg)
def mount(self, mount_point=None, mount_options='defaults'):
"""Mount this block device with a filesystem.
:param mount_point: The mountpoint on the system
:param mount_options: fstab style mount options, defaults to 'defaults'
"""
try:
if mount_point is None:
raise errors.DriverError(
"Cannot mount a block device on an empty mount point.")
data = {'mount_point': mount_point, 'mount_options': mount_options}
url = self.interpolate_url()
self.logger.debug(
"Mounting device %s on node %s at mount point %s" %
(self.resource_id, self.system_id, mount_point))
resp = self.api_client.post(url, op='mount', files=data)
if not resp.ok:
raise Exception(
"MAAS error: %s - %s" % (resp.status_code, resp.text))
self.refresh()
except Exception as ex:
msg = "Error: mount of device %s on node %s failed: %s" \
% (self.name, self.system_id, str(ex))
self.logger.error(msg)
raise errors.DriverError(msg)
def unmount(self):
"""Unmount this block device."""
try:
self.refresh()
if self.filesystem is None or self.filesystem.get(
'mount_point', None) is None:
self.logger.debug(
"Device %s not currently mounted, skipping unmount." %
(self.name))
url = self.interpolate_url()
self.logger.debug("Unmounting device %s on node %s" %
(self.name, self.system_id))
resp = self.api_client.post(url, op='unmount')
if not resp.ok:
raise Exception(
"MAAS error: %s - %s" % (resp.status_code, resp.text))
self.refresh()
except Exception as ex:
msg = "Error: unmount of device %s on node %s failed: %s" \
% (self.name, self.system_id, str(ex))
self.logger.error(msg)
raise errors.DriverError(msg)
def set_bootable(self):
"""Set this disk as the system bootdisk."""
try:
url = self.interpolate_url()
self.logger.debug("Setting device %s on node %s as bootable." %
(self.resource_id, self.system_id))
resp = self.api_client.post(url, op='set_boot_disk')
if not resp.ok:
raise Exception(
"MAAS error: %s - %s" % (resp.status_code, resp.text))
self.refresh()
except Exception as ex:
msg = "Error: setting device %s on node %s to boot failed: %s" \
% (self.name, self.system_id, str(ex))
self.logger.error(msg)
raise errors.DriverError(msg)
def create_partition(self, partition):
"""Create a partition on this block device.
:param partition: Instance of models.partition.Partition to be carved out of this block device
"""
if self.type == 'physical':
if self.partitions is not None:
partition = self.partitions.add(partition)
self.partitions.refresh()
return self.partitions.select(partition.resource_id)
else:
msg = "Error: could not access device %s partition list" % self.name
self.logger.error(msg)
raise errors.DriverError(msg)
else:
msg = "Error: cannot partition non-physical device %s." % (
self.name)
self.logger.error(msg)
raise errors.DriverError(msg)
def delete_partition(self, partition_id):
if self.partitions is not None:
part = self.partitions.select(partition_id)
if part is not None:
part.delete()
self.refresh()
def clear_partitions(self):
for p in getattr(self, 'partitions', []):
p.delete()
self.refresh()
@classmethod
def from_dict(cls, api_client, obj_dict):
"""Instantiate this model from a dictionary.
Because MaaS decides to replace the resource ids with the
representation of the resource, we must reverse it for a true
representation of the block device
"""
refined_dict = {k: obj_dict.get(k, None) for k in cls.fields}
if 'id' in obj_dict.keys():
refined_dict['resource_id'] = obj_dict.get('id')
i = cls(api_client, **refined_dict)
return i
class BlockDevices(model_base.ResourceCollectionBase):
collection_url = 'nodes/{system_id}/blockdevices/'
collection_resource = BlockDevice
def __init__(self, api_client, **kwargs):
super().__init__(api_client)
self.system_id = kwargs.get('system_id', None)