55from appsettings .settings import app_settings
66from computes .models import Compute
77from django .contrib import messages
8- from django .http import HttpResponse , HttpResponseRedirect
8+ from django .http import HttpResponse , HttpResponseRedirect , JsonResponse
99from django .shortcuts import get_object_or_404 , redirect , render
1010from django .urls import reverse
1111from django .utils .translation import gettext_lazy as _
1212from libvirt import libvirtError
13+ import paramiko
14+
15+ from vrtManager .connection import CONN_SSH , CONN_SOCKET
1316from vrtManager .storage import wvmStorage , wvmStorages
1417
1518from storages .forms import AddStgPool , CloneImage , CreateVolumeForm
@@ -102,20 +105,52 @@ def storage(request, compute_id, pool):
102105 :param pool:
103106 :return:
104107 """
108+ def handle_uploaded_file (conn , path , file_name , file_chunk , is_last_chunk ):
109+ temp_name = f"{ file_name } .part"
110+ target_temp = os .path .normpath (os .path .join (path , temp_name ))
111+ target_final = os .path .normpath (os .path .join (path , file_name ))
105112
106- def handle_uploaded_file (path , f_name ):
107- target = os .path .normpath (os .path .join (path , str (f_name )))
108- if not target .startswith (path ):
113+ if not target_temp .startswith (path ) or not target_final .startswith (path ):
109114 raise Exception (_ ("Security Issues with file uploading" ))
110115
111- try :
112- with open (target , "wb+" ) as f :
113- for chunk in f_name .chunks ():
114- f .write (chunk )
115- except FileNotFoundError :
116- messages .error (
117- request , _ ("File not found. Check the path variable and filename" )
118- )
116+ if conn .conn == CONN_SSH :
117+ try :
118+ hostname , port = conn .host , 22
119+ if ":" in hostname :
120+ hostname , port_str = hostname .split (":" )
121+ port = int (port_str )
122+
123+ ssh = paramiko .SSHClient ()
124+ ssh .set_missing_host_key_policy (paramiko .AutoAddPolicy ())
125+ ssh .connect (hostname = hostname , port = port , username = conn .login , password = conn .passwd )
126+ sftp = ssh .open_sftp ()
127+
128+ remote_file = sftp .open (target_temp , 'ab' )
129+ remote_file .set_pipelined (True )
130+ for chunk_data in file_chunk .chunks ():
131+ remote_file .write (chunk_data )
132+ remote_file .close ()
133+
134+ if is_last_chunk :
135+ sftp .rename (target_temp , target_final )
136+
137+ sftp .close ()
138+ ssh .close ()
139+ except Exception as e :
140+ raise Exception (_ ("SSH upload failed: {}" ).format (e ))
141+ elif conn .conn == CONN_SOCKET :
142+ try :
143+ with open (target_temp , "ab" ) as f :
144+ for chunk_data in file_chunk .chunks ():
145+ f .write (chunk_data )
146+ if is_last_chunk :
147+ if os .path .exists (target_final ):
148+ os .remove (target_final )
149+ os .rename (target_temp , target_final )
150+ except FileNotFoundError :
151+ raise Exception (_ ("File not found. Check the path variable and filename" ))
152+ else :
153+ raise Exception (_ ("Unsupported connection type for file upload." ))
119154
120155 compute = get_object_or_404 (Compute , pk = compute_id )
121156 meta_prealloc = False
@@ -127,12 +162,16 @@ def handle_uploaded_file(path, f_name):
127162
128163 storages = conn .get_storages ()
129164 state = conn .is_active ()
130- size , free = conn .get_size ()
131- used = size - free
132- if state :
133- percent = (used * 100 ) // size
134- else :
135- percent = 0
165+ try :
166+ size , free = conn .get_size ()
167+ used = size - free
168+ if state :
169+ percent = (used * 100 ) // size
170+ else :
171+ percent = 0
172+ except libvirtError :
173+ size , free , used , percent = 0 , 0 , 0 , 0
174+
136175 status = conn .get_status ()
137176 path = conn .get_target_path ()
138177 type = conn .get_type ()
@@ -170,16 +209,56 @@ def handle_uploaded_file(path, f_name):
170209 return redirect (reverse ("storage" , args = [compute .id , pool ]))
171210 # return HttpResponseRedirect(request.get_full_path())
172211 if "iso_upload" in request .POST :
173- if str (request .FILES ["file" ]) in conn .update_volumes ():
174- error_msg = _ ("ISO image already exist" )
212+ file_chunk = request .FILES .get ("file" )
213+ if not file_chunk :
214+ return JsonResponse ({"error" : _ ("No file chunk was submitted." )}, status = 400 )
215+
216+ file_name = request .POST .get ("file_name" )
217+ chunk_index = int (request .POST .get ("chunk_index" , 0 ))
218+ total_chunks = int (request .POST .get ("total_chunks" , 1 ))
219+ is_last_chunk = chunk_index == total_chunks - 1
220+
221+ # On first chunk, check if file already exists
222+ if chunk_index == 0 :
223+ if file_name in conn .get_volumes ():
224+ return JsonResponse ({"error" : _ ("ISO image already exists" )}, status = 400 )
225+ # Clean up any partial files from previous failed uploads
226+ temp_part_file = os .path .normpath (os .path .join (path , f"{ file_name } .part" ))
227+ if conn .conn == CONN_SOCKET and os .path .exists (temp_part_file ):
228+ os .remove (temp_part_file )
229+ elif conn .conn == CONN_SSH :
230+ try :
231+ hostname , port = conn .host , 22
232+ if ":" in hostname :
233+ hostname , port_str = hostname .split (":" )
234+ port = int (port_str )
235+ ssh = paramiko .SSHClient ()
236+ ssh .set_missing_host_key_policy (paramiko .AutoAddPolicy ())
237+ ssh .connect (hostname = hostname , port = port , username = conn .login , password = conn .passwd )
238+ sftp = ssh .open_sftp ()
239+ try :
240+ sftp .remove (temp_part_file )
241+ except FileNotFoundError :
242+ pass # File doesn't exist, which is fine
243+ sftp .close ()
244+ ssh .close ()
245+ except Exception :
246+ # Best effort to clean up, if it fails, let it be.
247+ pass
248+
249+ try :
250+ handle_uploaded_file (conn , path , file_name , file_chunk , is_last_chunk )
251+
252+ if is_last_chunk :
253+ success_msg = _ ("ISO: %(file)s has been uploaded successfully." ) % {"file" : file_name }
254+ messages .success (request , success_msg )
255+ return JsonResponse ({"success" : True , "message" : success_msg , "reload" : True })
256+ else :
257+ return JsonResponse ({"success" : True , "message" : "Chunk received." })
258+ except Exception as e :
259+ error_msg = str (e )
175260 messages .error (request , error_msg )
176- else :
177- handle_uploaded_file (path , request .FILES ["file" ])
178- messages .success (
179- request ,
180- _ ("ISO: %(file)s is uploaded." ) % {"file" : request .FILES ["file" ]},
181- )
182- return HttpResponseRedirect (request .get_full_path ())
261+ return JsonResponse ({"error" : error_msg }, status = 500 )
183262 if "cln_volume" in request .POST :
184263 form = CloneImage (request .POST )
185264 if form .is_valid ():
0 commit comments