-
Notifications
You must be signed in to change notification settings - Fork 365
Openshift volume Attach/Detach #9521
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
chitra607
wants to merge
6
commits into
ManageIQ:master
Choose a base branch
from
chitra607:volume_attach_detach
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
class ApplicationHelper::Button::VmAttachVolume < ApplicationHelper::Button::Basic | ||
needs :@record | ||
def visible? | ||
@record.kind_of?(ManageIQ::Providers::Kubevirt::InfraManager::Vm) | ||
end | ||
end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
class ApplicationHelper::Button::VmDetachVolume < ApplicationHelper::Button::Basic | ||
needs :@record | ||
def visible? | ||
@record.kind_of?(ManageIQ::Providers::Kubevirt::InfraManager::Vm) | ||
end | ||
end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,137 @@ | ||
import React, { useState, useEffect, useMemo } from "react"; | ||
import PropTypes from "prop-types"; | ||
import { Grid } from "carbon-components-react"; | ||
import MiqFormRenderer from "../../forms/data-driven-form"; | ||
import { API } from "../../http_api"; | ||
import createSchema from "./add-volume.schema"; | ||
import miqRedirectBack from "../../helpers/miq-redirect-back"; | ||
|
||
const AddVolumeForm = ({ recordId, redirect }) => { | ||
const [state, setState] = useState({ | ||
isLoading: true, | ||
volumes: [], | ||
storageClasses: [], | ||
}); | ||
|
||
const [isSubmitDisabled, setSubmitDisabled] = useState(true); | ||
|
||
useEffect(() => { | ||
const fetchPersistentVolumeClaims = async () => { | ||
try { | ||
setState(prev => ({ ...prev, isLoading: true, error: null })); | ||
|
||
// Fetch PVCs | ||
const pvcResponse = await fetch(`/vm_infra/persistentvolumeclaims/${recordId}`); | ||
const pvcData = await pvcResponse.json(); | ||
if (!pvcResponse.ok) throw new Error((pvcData.error && pvcData.error.message) || "Failed to fetch PVCs"); | ||
|
||
// Fetch Storage Classes | ||
const scResponse = await fetch(`/vm_infra/storage_class_list/${recordId}`); | ||
const scData = await scResponse.json(); | ||
if (!scResponse.ok) throw new Error((scData.error && scData.error.message) || "Failed to fetch Storage Classes"); | ||
|
||
setState(prev => ({ | ||
...prev, | ||
isLoading: false, | ||
volumes: pvcData.resources || [], | ||
storageClasses: scData.resources || [], | ||
vmInfo: { | ||
name: pvcData.vm_name, | ||
namespace: pvcData.vm_namespace | ||
} | ||
})); | ||
} catch (error) { | ||
console.error('Error fetching PVCs:', error); | ||
setState(prev => ({ | ||
...prev, | ||
isLoading: false, | ||
error: error.message, | ||
volumes: [], | ||
storageClasses: [], | ||
})); | ||
} | ||
}; | ||
|
||
fetchPersistentVolumeClaims(); | ||
}, [recordId]); | ||
|
||
const schema = useMemo(() => createSchema(state.volumes, state.storageClasses), [state.volumes, state.storageClasses]); | ||
|
||
const onFormChange = (values) => { | ||
if (values.volumeSourceType === "existing") { | ||
setSubmitDisabled(!values.pvcName); | ||
} else if (values.volumeSourceType === "new") { | ||
setSubmitDisabled(!values.newVolumeName || !values.newVolumeSize); | ||
} else { | ||
setSubmitDisabled(true); | ||
} | ||
}; | ||
|
||
const onSubmit = (values) => { | ||
const { volumeSourceType } = values; | ||
|
||
let payload; | ||
|
||
if (volumeSourceType === "existing") { | ||
const volumeNameFinal = | ||
values.volumeName && values.volumeName.trim() | ||
? values.volumeName.trim() | ||
: values.pvcName; | ||
|
||
payload = { | ||
action: "attach", | ||
resource: { | ||
pvc_name: values.pvcName, | ||
volume_name: volumeNameFinal, | ||
vm_id: recordId | ||
} | ||
}; | ||
} else { | ||
payload = { | ||
action: "create_and_attach_volume", | ||
resource: { | ||
volume_name: values.newVolumeName.trim(), | ||
volume_size: values.newVolumeSize.trim(), | ||
storage_class: values.storage_class, | ||
access_mode: values.access_mode, | ||
vm_id: recordId, | ||
device: values.device_mountpoint ? values.device_mountpoint : '' | ||
}, | ||
}; | ||
} | ||
|
||
const request = API.post(`/api/container_volumes/${recordId}`, payload); | ||
|
||
request.then(() => { | ||
const message = sprintf( | ||
__('Attechment of Volume has been successfully queued.') | ||
); | ||
miqRedirectBack(message, 'success', redirect); | ||
}).catch((error) => { | ||
miqRedirectBack(error.message || __("Failed to attach volume"), "error", redirect); | ||
}).finally(miqSparkleOff); | ||
|
||
}; | ||
|
||
const onCancel = () => | ||
miqRedirectBack(__("Add Volume was cancelled by the user"), "warning", redirect); | ||
|
||
return state.isLoading ? null : ( | ||
<Grid> | ||
<MiqFormRenderer | ||
schema={schema} | ||
onSubmit={onSubmit} | ||
onCancel={onCancel} | ||
canSubmit={!isSubmitDisabled} | ||
onStateUpdate={onFormChange} | ||
/> | ||
</Grid> | ||
); | ||
}; | ||
|
||
AddVolumeForm.propTypes = { | ||
recordId: PropTypes.string.isRequired, | ||
redirect: PropTypes.string.isRequired, | ||
}; | ||
|
||
export default AddVolumeForm; |
110 changes: 110 additions & 0 deletions
110
app/javascript/components/vm-infra/add-volume.schema.js
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
import { componentTypes } from "@@ddf"; | ||
|
||
const createSchema = (volumes = [], storageClasses = []) => ({ | ||
fields: [ | ||
{ | ||
component: componentTypes.RADIO, | ||
name: "volumeSourceType", | ||
label: __("Volume Source Type"), | ||
isRequired: true, | ||
options: [ | ||
{ label: __("Select Existing PVC"), value: "existing" }, | ||
{ label: __("Create New PVC"), value: "new" }, | ||
], | ||
initialValue: "existing", | ||
}, | ||
// Existing PVC selection | ||
{ | ||
component: componentTypes.SELECT, | ||
name: "pvcName", | ||
id: "pvcName", | ||
label: __("Select Persistent Volume Claim"), | ||
placeholder: volumes.length > 0 ? __("Select PVC") : __("No PVCs available"), | ||
options: volumes.length > 0 | ||
? [ | ||
{ label: __("Select PVC"), value: null, isDisabled: true }, | ||
...volumes.map(({ metadata }) => ({ | ||
label: metadata.name, | ||
value: metadata.name, | ||
})), | ||
] | ||
: [{ label: __("No PVCs available"), value: "", isDisabled: true }], | ||
condition: { | ||
when: "volumeSourceType", | ||
is: "existing", | ||
}, | ||
isRequired: true, | ||
validate: [{ type: "required", message: __("PVC selection is required") }], | ||
}, | ||
|
||
|
||
// New volume name | ||
{ | ||
component: componentTypes.TEXT_FIELD, | ||
name: "newVolumeName", | ||
id: "newVolumeName", | ||
label: __("New Volume Name"), | ||
isRequired: true, | ||
condition: { | ||
when: "volumeSourceType", | ||
is: "new", | ||
}, | ||
validate: [{ type: "required", message: __("Volume name is required") }], | ||
}, | ||
// New volume size | ||
{ | ||
component: componentTypes.TEXT_FIELD, | ||
name: "newVolumeSize", | ||
id: "newVolumeSize", | ||
label: __("New Volume Size (e.g., 3Gi)"), | ||
isRequired: true, | ||
condition: { | ||
when: "volumeSourceType", | ||
is: "new", | ||
}, | ||
validate: [ | ||
{ type: "required", message: __("Volume size is required") }, | ||
{ | ||
type: "pattern", | ||
pattern: "^[0-9]+Gi$", | ||
message: __("Size must be in Gi format (e.g., 3Gi)"), | ||
}, | ||
], | ||
}, | ||
{ | ||
component: componentTypes.SELECT, | ||
id: "storage_class", | ||
name: "storage_class", | ||
label: __("Storage Class"), | ||
isRequired: true, | ||
condition: { when: "volumeSourceType", is: "new" }, | ||
includeEmpty: true, | ||
options: | ||
storageClasses.length > 0 | ||
? [ | ||
{ label: __("Select Storage Class"), value: "", isDisabled: true }, | ||
...storageClasses.map((sc) => ({ | ||
label: sc.name || sc, | ||
value: sc.name || sc, | ||
})), | ||
] | ||
: [{ label: __("No Storage Classes Available"), value: "", isDisabled: true }], | ||
}, | ||
{ | ||
component: componentTypes.SELECT, | ||
id: "access_mode", | ||
name: "access_mode", | ||
label: __("Access Mode"), | ||
isRequired: true, | ||
condition: { when: "volumeSourceType", is: "new" }, | ||
includeEmpty: true, | ||
options: [ | ||
{ label: "Single Use (RWO)", value: "ReadWriteOnce" }, | ||
{ label: "Shared Access (RWX)", value: "ReadWriteMany" }, | ||
{ label: "Read Write Once Pod (RWOP)", value: "ReadWriteOncePod" }, | ||
], | ||
}, | ||
], | ||
}); | ||
|
||
export default createSchema; |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
NOTE storage_classes isn't something that we currently have. We would need to collect them in inventory refresh as part of the provider and persist them in the database for this to work. We do currently have a
StorageProfile
model which we could use to store these without having to create a new table/model.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @agrare. Just to clarify, storage_classes here is a function I added that fetches the list directly from the OpenShift/KubeVirt API at runtime, it is not a table/model. Would it make sense to use the existing StorageProfile model to persist storage classes list in OpenShift/KubeVirt as you suggested?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah that's not going to work for a couple of reasons, first this is generic VM code so we can't have provider specific methods in here, also since this is running from the UI role we can't run anything that would hit the live provider since that can only be guaranteed to be accessed via ems_operations role in the proper zone.
StorageProfiles sound like they could be a good match, what data do you need for them other than uid/name?