44
55import base64
66import dataclasses
7+ import datetime
78import functools
89import hashlib
910import logging
1415import certifi
1516import dateutil .parser
1617import rackit
18+ import requests
1719import yaml
20+ from django .utils .timezone import make_aware
21+
22+ from azimuth .settings import cloud_settings
1823
1924from .. import base , dto , errors # noqa: TID252
2025from . import api
@@ -337,20 +342,23 @@ def quotas(self):
337342 None ,
338343 compute_limits .total_cores ,
339344 compute_limits .total_cores_used ,
345+ dto .QuotaType .COMPUTE ,
340346 ),
341347 dto .Quota (
342348 "ram" ,
343349 "RAM" ,
344350 "MB" ,
345351 compute_limits .total_ram ,
346352 compute_limits .total_ram_used ,
353+ dto .QuotaType .COMPUTE ,
347354 ),
348355 dto .Quota (
349356 "machines" ,
350357 "Machines" ,
351358 None ,
352359 compute_limits .instances ,
353360 compute_limits .instances_used ,
361+ dto .QuotaType .COMPUTE ,
354362 ),
355363 ]
356364 # Get the floating ip quota
@@ -363,8 +371,15 @@ def quotas(self):
363371 network_quotas .floatingip ,
364372 # Just get the length of the list of IPs
365373 len (list (self ._connection .network .floatingips .all ())),
374+ dto .QuotaType .NETWORK ,
366375 )
367376 )
377+ # Get coral credits if available
378+ if not (
379+ cloud_settings .CORAL_CREDITS .CORAL_URI is None
380+ or cloud_settings .CORAL_CREDITS .TOKEN is None
381+ ):
382+ quotas .extend (self ._get_coral_quotas ())
368383 # The volume service is optional
369384 # In the case where the service is not enabled, just don't add the quotas
370385 try :
@@ -377,20 +392,136 @@ def quotas(self):
377392 "GB" ,
378393 volume_limits .total_volume_gigabytes ,
379394 volume_limits .total_gigabytes_used ,
395+ dto .QuotaType .BLOCK_STORAGE ,
380396 ),
381397 dto .Quota (
382398 "volumes" ,
383399 "Volumes" ,
384400 None ,
385401 volume_limits .volumes ,
386402 volume_limits .volumes_used ,
403+ dto .QuotaType .BLOCK_STORAGE ,
387404 ),
388405 ]
389406 )
390407 except api .ServiceNotSupported :
391408 pass
392409 return quotas
393410
411+ def _coral_quotas_from_allocation (self , allocation , headers ):
412+ quotas = []
413+
414+ human_readable_names = {
415+ "MEMORY_MB" : "RAM (MB)" ,
416+ "DISK_GB" : "Root disk (GB)" ,
417+ }
418+
419+ # Add quota for time until allocation expiry
420+ current_time = make_aware (datetime .datetime .now ())
421+ target_tz = current_time .tzinfo
422+ start_time = parse_time_and_correct_tz (allocation ["start" ], target_tz )
423+ end_time = parse_time_and_correct_tz (allocation ["end" ], target_tz )
424+
425+ allocated_duration = (end_time - start_time ).total_seconds () / 3600
426+ used_duration = (current_time - start_time ).total_seconds () / 3600
427+
428+ quotas .append (
429+ dto .Quota (
430+ "expiry" ,
431+ "Allocated time used (hours)" ,
432+ "hours" ,
433+ int (allocated_duration ),
434+ int (used_duration ),
435+ dto .QuotaType .CORAL_CREDITS ,
436+ )
437+ )
438+
439+ # Add quotas for Coral resource quotas
440+ active_allocation_id = allocation ["id" ]
441+
442+ allocation_resources = requests .get (
443+ cloud_settings .CORAL_CREDITS .CORAL_URI
444+ + "/allocation/"
445+ + str (active_allocation_id )
446+ + "/resources" ,
447+ headers = headers ,
448+ ).json ()
449+
450+ if len (allocation_resources ) == 0 :
451+ self ._log ("Allocated resources found in allocation" , level = logging .WARN )
452+ return []
453+
454+ for resource in allocation_resources :
455+ resource_name = resource ["resource_class" ]["name" ]
456+ quotas .append (
457+ dto .Quota (
458+ resource_name ,
459+ human_readable_names .get (resource_name , resource_name ) + " hours" ,
460+ "resource hours" ,
461+ resource ["allocated_resource_hours" ],
462+ resource ["allocated_resource_hours" ] - resource ["resource_hours" ],
463+ dto .QuotaType .CORAL_CREDITS ,
464+ )
465+ )
466+ return quotas
467+
468+ def _get_coral_quotas (self ):
469+ headers = {"Authorization" : "Bearer " + cloud_settings .CORAL_CREDITS .TOKEN }
470+ accounts = requests .get (
471+ cloud_settings .CORAL_CREDITS .CORAL_URI + "/resource_provider_account" ,
472+ headers = headers ,
473+ ).json ()
474+
475+ tenancy_account_list = list (
476+ filter (
477+ lambda a : a ["project_id" ].replace ("-" , "" ) == self ._tenancy .id , accounts
478+ )
479+ )
480+ if len (tenancy_account_list ) != 1 :
481+ self ._log (
482+ (
483+ "There should be exactly one resource provider account associated "
484+ "with the tenancy, there are currently %s"
485+ ),
486+ len (tenancy_account_list ),
487+ level = logging .WARN ,
488+ )
489+ return []
490+ tenancy_account = tenancy_account_list [0 ]["account" ]
491+ all_allocations = requests .get (
492+ cloud_settings .CORAL_CREDITS .CORAL_URI + "/allocation" , headers = headers
493+ ).json ()
494+ account_allocations = filter (
495+ lambda a : a ["account" ] == tenancy_account , all_allocations
496+ )
497+
498+ current_time = make_aware (datetime .datetime .now ())
499+ target_tz = current_time .tzinfo
500+
501+ active_allocation_list = list (
502+ filter (
503+ lambda a : parse_time_and_correct_tz (a ["start" ], target_tz )
504+ < current_time
505+ and current_time < parse_time_and_correct_tz (a ["end" ], target_tz ),
506+ account_allocations ,
507+ )
508+ )
509+
510+ if len (active_allocation_list ) == 1 :
511+ return self ._coral_quotas_from_allocation (
512+ active_allocation_list [0 ], headers
513+ )
514+ else :
515+ self ._log (
516+ (
517+ "There should be exactly one active allocation associated "
518+ "with the tenancy, there are currently %s"
519+ ),
520+ len (active_allocation_list ),
521+ level = logging .WARN ,
522+ )
523+ return []
524+
394525 def _from_api_image (self , api_image ):
395526 """
396527 Converts an OpenStack API image object into a :py:class:`.dto.Image`.
@@ -1504,3 +1635,7 @@ def close(self):
15041635 """
15051636 # Make sure the underlying api connection is closed
15061637 self ._connection .close ()
1638+
1639+
1640+ def parse_time_and_correct_tz (time_str , tz ):
1641+ return datetime .datetime .strptime (time_str , "%Y-%m-%dT%H:%M:%SZ" ).replace (tzinfo = tz )
0 commit comments