def update_disk(self, params): """Update disk information.""" # Only admin users can perform delete. if not reload_object(self.user).is_superuser: raise HandlerPermissionError() node = self.get_object(params) device = BlockDevice.objects.get(id=params['block_id'], node=node).actual_instance if device.type == 'physical': form = UpdatePhysicalBlockDeviceForm(instance=device, data=params) elif device.type == 'virtual': form = UpdateVirtualBlockDeviceForm(instance=device, data=params) else: raise HandlerError('Cannot update block device of type %s' % device.type) if not form.is_valid(): raise HandlerError(form.errors) else: disk_obj = form.save() self._update_obj_tags(disk_obj, params) if 'fstype' in params: self.update_blockdevice_filesystem( disk_obj, params['fstype'], params.get('mount_point', ''), params.get('mount_options', ''))
def update_blockdevice_filesystem(self, blockdevice, fstype, mount_point, mount_options): fs = blockdevice.get_effective_filesystem() if not fstype: if fs: fs.delete() return if fs is None or fstype != fs.fstype: form = FormatBlockDeviceForm(blockdevice, {'fstype': fstype}) if not form.is_valid(): raise HandlerError(form.errors) form.save() fs = blockdevice.get_effective_filesystem() if mount_point != fs.mount_point: # XXX: Elsewhere, a mount_point of "" would somtimes mean that the # filesystem is mounted, sometimes that it is *not* mounted. Which # is correct was not clear from the code history, so the existing # behaviour is maintained here. if mount_point is None or mount_point == "": fs.mount_point = None fs.mount_options = None fs.save() else: form = MountFilesystemForm( blockdevice.get_effective_filesystem(), { 'mount_point': mount_point, 'mount_options': mount_options }) if not form.is_valid(): raise HandlerError(form.errors) else: form.save()
def create_bcache(self, params): """Create a bcache.""" node = self._get_node_or_permission_error(params) block_id = params.get('block_id') partition_id = params.get('partition_id') data = { "name": params["name"], "cache_set": params["cache_set"], "cache_mode": params["cache_mode"], } if partition_id is not None: data["backing_partition"] = partition_id elif block_id is not None: data["backing_device"] = block_id else: raise HandlerError("Either block_id or partition_id is required.") form = CreateBcacheForm(node=node, data=data) if not form.is_valid(): raise HandlerError(form.errors) else: bcache = form.save() self._update_obj_tags(bcache.virtual_device, params) if 'fstype' in params: self.update_blockdevice_filesystem(bcache.virtual_device, params.get("fstype"), params.get("mount_point"), params.get("mount_options"))
def update_disk(self, params): """Update disk information.""" node = self._get_node_or_permission_error( params, permission=self._meta.edit_permission) device = BlockDevice.objects.get( id=params['block_id'], node=node).actual_instance if device.type == 'physical': form = UpdatePhysicalBlockDeviceForm( instance=device, data=params) elif device.type == 'virtual': form = UpdateVirtualBlockDeviceForm( instance=device, data=params) else: raise HandlerError( 'Cannot update block device of type %s' % device.type) if not form.is_valid(): raise HandlerError(form.errors) else: disk_obj = form.save() self._update_obj_tags(disk_obj, params) if 'fstype' in params: self.update_blockdevice_filesystem( disk_obj, params['fstype'], params.get('mount_point', ''), params.get('mount_options', ''))
def fetch(self, params): """Fetch the releases and the arches from the provided source.""" # Must be administrator. assert self.user.is_superuser, "Permission denied." # Build a source, but its not saved into the database. boot_source = self.get_bootsource(params, from_db=False) try: # Validate the boot source fields without committing it. boot_source.clean_fields() except ValidationError as error: raise HandlerValidationError(error) source = boot_source.to_dict_without_selections() # FIXME: This modifies the environment of the entire process, which is # Not Cool. We should integrate with simplestreams in a more # Pythonic manner. set_simplestreams_env() with tempdir("keyrings") as keyrings_path: [source] = write_all_keyrings(keyrings_path, [source]) try: descriptions = download_all_image_descriptions( [source], user_agent=get_maas_user_agent()) except Exception as error: raise HandlerError(str(error)) items = list(descriptions.items()) err_msg = "Mirror provides no Ubuntu images." if len(items) == 0: raise HandlerError(err_msg) releases = {} arches = {} for image_spec, product_info in items: # Only care about Ubuntu images. if image_spec.os != "ubuntu": continue releases[image_spec.release] = { "name": image_spec.release, "title": product_info.get( "release_title", format_ubuntu_distro_series(image_spec.release), ), "checked": False, "deleted": False, } arches[image_spec.arch] = { "name": image_spec.arch, "title": image_spec.arch, "checked": False, "deleted": False, } if len(releases) == 0 or len(arches) == 0: raise HandlerError(err_msg) return json.dumps({ "releases": list(releases.values()), "arches": list(arches.values()), })
def update_filesystem(self, params): node = self._get_node_or_permission_error( params, permission=NodePermission.edit) if node.locked: raise HandlerPermissionError() block_id = params.get("block_id") partition_id = params.get("partition_id") fstype = params.get("fstype") mount_point = params.get("mount_point") mount_options = params.get("mount_options") if node.status not in [NODE_STATUS.ALLOCATED, NODE_STATUS.READY]: raise HandlerError( "Node must be allocated or ready to edit storage") # If this is on a block device, check if the tags need to be updated. # (The client sends them in from the same form.) blockdevice = None if block_id is not None: blockdevice = BlockDevice.objects.get(id=block_id, node=node) tags = params.get("tags", None) # If the tags parameter was left out, that means "don't touch the # tags". (An empty list means "clear the tags".) if tags is not None: tags = [tag["text"] for tag in tags] if set(blockdevice.tags) != set(tags): blockdevice.tags = tags blockdevice.save() if partition_id: self.update_partition_filesystem(node, partition_id, fstype, mount_point, mount_options) elif blockdevice is not None: self.update_blockdevice_filesystem(blockdevice, fstype, mount_point, mount_options)
def update_filesystem(self, params): node = self.get_object(params) block_id = params.get('block_id') partition_id = params.get('partition_id') fstype = params.get('fstype') mount_point = params.get('mount_point') mount_options = params.get('mount_options') if node.status not in [NODE_STATUS.ALLOCATED, NODE_STATUS.READY]: raise HandlerError( "Node must be allocated or ready to edit storage") if (not reload_object(self.user).is_superuser and node.owner_id != self.user.id): raise HandlerPermissionError() # If this is on a block device, check if the tags need to be updated. # (The client sends them in from the same form.) blockdevice = None if block_id is not None: blockdevice = BlockDevice.objects.get(id=block_id, node=node) tags = params.get('tags', None) # If the tags parameter was left out, that means "don't touch the # tags". (An empty list means "clear the tags".) if tags is not None: tags = [tag['text'] for tag in tags] if set(blockdevice.tags) != set(tags): blockdevice.tags = tags blockdevice.save() if partition_id: self.update_partition_filesystem(node, partition_id, fstype, mount_point, mount_options) elif blockdevice is not None: self.update_blockdevice_filesystem(blockdevice, fstype, mount_point, mount_options)
def create_logical_volume(self, params): """Create a logical volume.""" # Only admin users can perform delete. if not reload_object(self.user).is_superuser: raise HandlerPermissionError() node = self.get_object(params) volume_group = VolumeGroup.objects.get(id=params['volume_group_id']) if volume_group.get_node() != node: raise VolumeGroup.DoesNotExist() form = CreateLogicalVolumeForm(volume_group, { 'name': params['name'], 'size': params['size'], }) if not form.is_valid(): raise HandlerError(form.errors) else: logical_volume = form.save() self._update_obj_tags(logical_volume, params) if 'fstype' in params: self.update_blockdevice_filesystem(logical_volume, params.get("fstype"), params.get("mount_point"), params.get("mount_options"))
def update_vmfs_datastore(self, params): """Add or remove block devices or partitions from a datastore.""" vmfs = self._get_vmfs_datastore(params) form = UpdateVMFSForm(vmfs, data=params) if not form.is_valid(): raise HandlerError(form.errors) else: form.save()
def get_form_class(self, action): """Return the form class used for `action`.""" if action == "create" and self._deployed: return AdminMachineForm if action in ("create", "update"): return AdminMachineWithMACAddressesForm else: raise HandlerError("Unknown action: %s" % action)
def get_form_class(self, action): """Return the form class used for `action`.""" if action == "create": return DeviceWithMACsForm elif action == "update": return DeviceForm else: raise HandlerError("Unknown action: %s" % action)
def apply_storage_layout(self, params): """Apply the specified storage layout.""" node = self._get_node_or_permission_error( params, permission=self._meta.edit_permission) form = StorageLayoutForm(required=True, data=params) if not form.is_valid(): raise HandlerError(form.errors) storage_layout = params.get("storage_layout") try: node.set_storage_layout(storage_layout) except StorageLayoutMissingBootDiskError: raise HandlerError( "Machine is missing a boot disk; no storage layout can be " "applied.") except StorageLayoutError as e: raise HandlerError("Failed to configure storage layout '%s': %s" % (storage_layout, str(e)))
def create_volume_group(self, params): """Create a volume group.""" node = self._get_node_or_permission_error(params) form = CreateVolumeGroupForm(node=node, data=params) if not form.is_valid(): raise HandlerError(form.errors) else: form.save()
def create_vmfs_datastore(self, params): """Create a VMFS datastore.""" node = self._get_node_or_permission_error( params, permission=self._meta.edit_permission) form = CreateVMFSForm(node, data=params) if not form.is_valid(): raise HandlerError(form.errors) else: form.save()
def set_boot_disk(self, params): """Set the disk as the boot disk.""" node = self._get_node_or_permission_error(params) device = BlockDevice.objects.get(id=params['block_id'], node=node).actual_instance if device.type != 'physical': raise HandlerError( "Only a physical disk can be set as the boot disk.") node.boot_disk = device node.save()
def create_cache_set(self, params): """Create a cache set.""" node = self._get_node_or_permission_error(params) block_id = params.get('block_id') partition_id = params.get('partition_id') data = {} if partition_id is not None: data["cache_partition"] = partition_id elif block_id is not None: data["cache_device"] = block_id else: raise HandlerError("Either block_id or partition_id is required.") form = CreateCacheSetForm(node=node, data=data) if not form.is_valid(): raise HandlerError(form.errors) else: form.save()
def import_keys(self, params): """Import the requesting user's SSH keys. Import SSH keys for a given protocol and authorization ID in protocol:auth_id format. """ try: KeySource.objects.save_keys_for_user(user=self.user, protocol=params['protocol'], auth_id=params['auth_id']) except ImportSSHKeysError as e: raise HandlerError(str(e))
def create_volume_group(self, params): """Create a volume group.""" # Only admin users can perform delete. if not reload_object(self.user).is_superuser: raise HandlerPermissionError() node = self.get_object(params) form = CreateVolumeGroupForm(node=node, data=params) if not form.is_valid(): raise HandlerError(form.errors) else: form.save()
def update_partition_filesystem( self, node, partition_id, fstype, mount_point, mount_options ): partition = Partition.objects.get( id=partition_id, partition_table__block_device__node=node ) fs = partition.get_effective_filesystem() if not fstype: if fs: fs.delete() return if fs is None or fstype != fs.fstype: form = FormatPartitionForm(partition, {"fstype": fstype}) if not form.is_valid(): raise HandlerError(form.errors) form.save() fs = partition.get_effective_filesystem() if mount_point != fs.mount_point: # XXX: Elsewhere, a mount_point of "" would somtimes mean that the # filesystem is mounted, sometimes that it is *not* mounted. Which # is correct was not clear from the code history, so the existing # behaviour is maintained here. if mount_point is None or mount_point == "": fs.mount_point = None fs.mount_options = None fs.save() else: form = MountFilesystemForm( partition.get_effective_filesystem(), { "mount_point": mount_point, "mount_options": mount_options, }, ) if not form.is_valid(): raise HandlerError(form.errors) else: form.save()
def create_raid(self, params): """Create a RAID.""" node = self._get_node_or_permission_error(params) form = CreateRaidForm(node=node, data=params) if not form.is_valid(): raise HandlerError(form.errors) else: raid = form.save() self._update_obj_tags(raid.virtual_device, params) if 'fstype' in params: self.update_blockdevice_filesystem( raid.virtual_device, params.get("fstype"), params.get("mount_point"), params.get("mount_options"))
def set_boot_disk(self, params): """Set the disk as the boot disk.""" # Only admin users can perform delete. if not reload_object(self.user).is_superuser: raise HandlerPermissionError() node = self.get_object(params) device = BlockDevice.objects.get(id=params['block_id'], node=node).actual_instance if device.type != 'physical': raise HandlerError( "Only a physical disk can be set as the boot disk.") node.boot_disk = device node.save()
def create_cache_set(self, params): """Create a cache set.""" # Only admin users can perform delete. if not reload_object(self.user).is_superuser: raise HandlerPermissionError() node = self.get_object(params) block_id = params.get('block_id') partition_id = params.get('partition_id') data = {} if partition_id is not None: data["cache_partition"] = partition_id elif block_id is not None: data["cache_device"] = block_id else: raise HandlerError("Either block_id or partition_id is required.") form = CreateCacheSetForm(node=node, data=data) if not form.is_valid(): raise HandlerError(form.errors) else: form.save()
def create_partition(self, params): """Create a partition.""" node = self._get_node_or_permission_error(params) disk_obj = BlockDevice.objects.get(id=params['block_id'], node=node) form = AddPartitionForm(disk_obj, { 'size': params['partition_size'], }) if not form.is_valid(): raise HandlerError(form.errors) else: partition = form.save() if 'fstype' in params: self.update_partition_filesystem(node, partition.id, params.get("fstype"), params.get("mount_point"), params.get("mount_options"))
def import_keys(self, params): """Import the requesting user's SSH keys. Import SSH keys for a given protocol and authorization ID in protocol:auth_id format. """ try: KeySource.objects.save_keys_for_user( user=self.user, protocol=params['protocol'], auth_id=params['auth_id']) request = HttpRequest() request.user = self.user create_audit_event( EVENT_TYPES.AUTHORISATION, ENDPOINT.UI, request, None, description="Imported SSH keys.") except ImportSSHKeysError as e: raise HandlerError(str(e))
def create_raid(self, params): """Create a RAID.""" # Only admin users can perform delete. if not reload_object(self.user).is_superuser: raise HandlerPermissionError() node = self.get_object(params) form = CreateRaidForm(node=node, data=params) if not form.is_valid(): raise HandlerError(form.errors) else: raid = form.save() self._update_obj_tags(raid.virtual_device, params) if 'fstype' in params: self.update_blockdevice_filesystem(raid.virtual_device, params.get("fstype"), params.get("mount_point"), params.get("mount_options"))
def create_partition(self, params): """Create a partition.""" node = self._get_node_or_permission_error( params, permission=self._meta.edit_permission) disk_obj = BlockDevice.objects.get(id=params["block_id"], node=node) form = AddPartitionForm(disk_obj, {"size": params["partition_size"]}) if not form.is_valid(): raise HandlerError(form.errors) else: partition = form.save() self._update_obj_tags(partition, params) if "fstype" in params: self.update_partition_filesystem( node, partition.id, params.get("fstype"), params.get("mount_point"), params.get("mount_options"), )
def create_partition(self, params): """Create a partition.""" # Only admin users can perform delete. if not reload_object(self.user).is_superuser: raise HandlerPermissionError() node = self.get_object(params) disk_obj = BlockDevice.objects.get(id=params['block_id'], node=node) form = AddPartitionForm(disk_obj, { 'size': params['partition_size'], }) if not form.is_valid(): raise HandlerError(form.errors) else: partition = form.save() if 'fstype' in params: self.update_partition_filesystem(node, partition.id, params.get("fstype"), params.get("mount_point"), params.get("mount_options"))
def update_tags(self, node_obj, tags): # Loop through the nodes current tags. If the tag exists in `tags` then # nothing needs to be done so its removed from `tags`. If it does not # exists then the tag was removed from the node and should be removed # from the nodes set of tags. for tag in node_obj.tags.all(): if tag.name not in tags: node_obj.tags.remove(tag) else: tags.remove(tag.name) # All the tags remaining in `tags` are tags that are not linked to # node. Get or create that tag and add the node to the tags set. for tag_name in tags: tag_obj, _ = Tag.objects.get_or_create(name=tag_name) if tag_obj.is_defined: raise HandlerError( "Cannot add tag %s to node because it has a " "definition." % tag_name) tag_obj.node_set.add(node_obj) tag_obj.save()
def create_logical_volume(self, params): """Create a logical volume.""" node = self._get_node_or_permission_error( params, permission=self._meta.edit_permission) volume_group = VolumeGroup.objects.get(id=params['volume_group_id']) if volume_group.get_node() != node: raise VolumeGroup.DoesNotExist() form = CreateLogicalVolumeForm( volume_group, { 'name': params['name'], 'size': params['size'], }) if not form.is_valid(): raise HandlerError(form.errors) else: logical_volume = form.save() self._update_obj_tags(logical_volume, params) if 'fstype' in params: self.update_blockdevice_filesystem( logical_volume, params.get("fstype"), params.get("mount_point"), params.get("mount_options"))
def get_bootsource(self, params, from_db=False): source_type = params.get("source_type", "custom") if source_type == "maas.io": url = DEFAULT_IMAGES_URL keyring_filename = DEFAULT_KEYRINGS_PATH keyring_data = b"" elif source_type == "custom": url = params["url"] keyring_filename = params.get("keyring_filename", "") keyring_data = params.get("keyring_data", "").encode("utf-8") if keyring_filename == "" and keyring_data == b"": keyring_filename = DEFAULT_KEYRINGS_PATH else: raise HandlerError("Unknown source_type: %s" % source_type) if from_db: source, created = BootSource.objects.get_or_create( url=url, defaults={ "keyring_filename": keyring_filename, "keyring_data": keyring_data, }, ) if not created: source.keyring_filename = keyring_filename source.keyring_data = keyring_data source.save() else: # This was a new source, make sure its the only source in the # database. This is because the UI only supports handling one # source at a time. BootSource.objects.exclude(id=source.id).delete() return source else: return BootSource( url=url, keyring_filename=keyring_filename, keyring_data=keyring_data, )