11# -*- coding: utf-8 -*-
2+ import abc
23import logging
34from collections import OrderedDict
45
@@ -50,18 +51,40 @@ class Meta:
5051 )
5152
5253
53- class CloudFlavor (AdminAbsoluteUrlMixin , BaseObject ):
54- name = models .CharField (_ ("name" ), max_length = 255 , db_index = True )
55- cloudprovider = models .ForeignKey (CloudProvider , on_delete = models .CASCADE )
56- cloudprovider ._autocomplete = False
54+ class VirtualComponentDescriptor (metaclass = abc .ABCMeta ):
55+ """
56+ Base descriptor for virtual component fields (cores, memory, disk).
57+ e.g. CloudHost -[through VirtualComponent]-> ComponentModel
58+ Then it gets weird because in ComponentModel if it's CPU cores we read column 'cores'
59+ but if it's memory or disk we read column 'size'
60+ That's why we have field_path
61+ Also, disk is stored in MiB in ComponentModel, but we show it in GiB
62+ """
5763
58- flavor_id = models .CharField (unique = True , max_length = 100 )
64+ def __init__ (self ):
65+ self .name = None
5966
60- def __str__ (self ):
61- return self .name
67+ def __set_name__ (self , owner , name ):
68+ self .name = name
69+
70+ def _get_component (self , instance ):
71+ """Get component value from the instance."""
72+ try :
73+ components = instance ._prefetched_objects_cache ["virtualcomponent_set" ]
74+ except (KeyError , AttributeError ):
75+ return (
76+ instance .virtualcomponent_set .filter (model__type = self .component_type )
77+ .values_list (self .field_path , flat = True )
78+ .first ()
79+ )
80+ else :
81+ for component in components :
82+ if component .model .type == self .component_type :
83+ return get_value_by_relation_path (component , self .field_path )
84+ return None
6285
63- def _set_component (self , model_args ):
64- """create /modify component cpu, mem or disk """
86+ def _set_component (self , instance , model_args ):
87+ """Create /modify component. """
6588 try :
6689 model = ComponentModel .objects .get (name = model_args ["name" ])
6790 except ObjectDoesNotExist :
@@ -70,77 +93,97 @@ def _set_component(self, model_args):
7093 setattr (model , key , value )
7194 model .save ()
7295 try :
73- VirtualComponent .objects .get (base_object = self , model = model )
96+ VirtualComponent .objects .get (base_object = instance , model = model )
7497 except ObjectDoesNotExist :
75- for component in self .virtualcomponent_set .filter (
98+ for component in instance .virtualcomponent_set .filter (
7699 model__type = model_args ["type" ]
77100 ):
78101 component .delete ()
102+ VirtualComponent (base_object = instance , model = model ).save ()
79103
80- VirtualComponent (base_object = self , model = model ).save ()
81-
82- def _get_component (self , model_type , field_path ):
83- # use cached components if already prefetched (using prefetch_related)
84- # otherwise, perform regular SQL query
85- try :
86- components = self ._prefetched_objects_cache ["virtualcomponent_set" ]
87- except (KeyError , AttributeError ):
88- return (
89- self .virtualcomponent_set .filter (model__type = model_type )
90- .values_list (field_path , flat = True )
91- .first ()
92- )
93- else :
94- for component in components :
95- if component .model .type == model_type :
96- return get_value_by_relation_path (component , field_path )
97- return None
104+ def __get__ (self , instance , owner ):
105+ if instance is None :
106+ return self
107+ return self ._get_component (instance )
98108
99109 @property
100- def cores (self ):
101- """Number of cores"""
102- return self ._get_component (ComponentType .processor , "model__cores" )
103-
104- @cores .setter
105- def cores (self , new_cores ):
106- cpu = {
107- "name" : "{} cores vCPU" .format (new_cores ),
108- "cores" : new_cores ,
109- "family" : "vcpu" ,
110- "type" : ComponentType .processor ,
111- }
112- if self .cores != new_cores :
113- self ._set_component (cpu )
110+ @abc .abstractmethod
111+ def field_path (self ) -> str :
112+ raise NotImplementedError ("Subclasses must implement field_path" )
114113
115114 @property
116- def memory (self ):
117- """RAM memory size in MiB"""
118- return self ._get_component (ComponentType .memory , "model__size" )
119-
120- @memory .setter
121- def memory (self , new_memory ):
122- ram = {
123- "name" : "{} MiB vMEM" .format (new_memory ),
124- "size" : new_memory ,
125- "type" : ComponentType .memory ,
126- }
127- if self .memory != new_memory :
128- self ._set_component (ram )
115+ @abc .abstractmethod
116+ def component_type (self ) -> ComponentType :
117+ raise NotImplementedError ("Subclasses must implement component_type" )
129118
130- @property
131- def disk (self ):
132- """Disk size in MiB"""
133- return self ._get_component (ComponentType .disk , "model__size" )
119+ @abc .abstractmethod
120+ def __set__ (self , instance , value ):
121+ raise NotImplementedError ("Subclasses must implement __set__" )
122+
123+
124+ class CoresVirtualComponent (VirtualComponentDescriptor ):
125+ """Descriptor for CPU cores virtual component."""
126+
127+ component_type = ComponentType .processor
128+ field_path = "model__cores"
129+
130+ def __set__ (self , instance , new_cores ):
131+ if self .__get__ (instance , type (instance )) != new_cores :
132+ cpu = {
133+ "name" : "{} cores vCPU" .format (new_cores ),
134+ "cores" : new_cores ,
135+ "family" : "vcpu" ,
136+ "type" : ComponentType .processor ,
137+ }
138+ self ._set_component (instance , cpu )
139+
140+
141+ class MemoryVirtualComponent (VirtualComponentDescriptor ):
142+ """Descriptor for RAM memory virtual component (size in MiB)."""
143+
144+ component_type = ComponentType .memory
145+ field_path = "model__size"
146+
147+ def __set__ (self , instance , new_memory ):
148+ if self .__get__ (instance , type (instance )) != new_memory :
149+ ram = {
150+ "name" : "{} MiB vMEM" .format (new_memory ),
151+ "size" : new_memory ,
152+ "type" : ComponentType .memory ,
153+ }
154+ self ._set_component (instance , ram )
155+
156+
157+ class DiskVirtualComponent (VirtualComponentDescriptor ):
158+ """Descriptor for disk virtual component (size in MiB)."""
134159
135- @disk .setter
136- def disk (self , new_disk ):
137- disk = {
138- "name" : "{} GiB vHDD" .format (int (new_disk / 1024 )),
139- "size" : new_disk ,
140- "type" : ComponentType .disk ,
141- }
142- if self .disk != new_disk :
143- self ._set_component (disk )
160+ component_type = ComponentType .disk
161+ field_path = "model__size"
162+
163+ def __set__ (self , instance , new_disk ):
164+ if self .__get__ (instance , type (instance )) != new_disk :
165+ disk = {
166+ "name" : "{} GiB vHDD" .format (
167+ int (new_disk / 1024 ) if new_disk is not None else None
168+ ),
169+ "size" : new_disk ,
170+ "type" : ComponentType .disk ,
171+ }
172+ self ._set_component (instance , disk )
173+
174+
175+ class CloudFlavor (AdminAbsoluteUrlMixin , BaseObject ):
176+ name = models .CharField (_ ("name" ), max_length = 255 , db_index = True )
177+ cores = CoresVirtualComponent ()
178+ memory = MemoryVirtualComponent ()
179+ disk = DiskVirtualComponent ()
180+ cloudprovider = models .ForeignKey (CloudProvider , on_delete = models .CASCADE )
181+ cloudprovider ._autocomplete = False
182+
183+ flavor_id = models .CharField (unique = True , max_length = 100 )
184+
185+ def __str__ (self ):
186+ return self .name
144187
145188
146189class CloudProject (PreviousStateMixin , AdminAbsoluteUrlMixin , BaseObject ):
@@ -211,6 +254,9 @@ def save(self, *args, **kwargs):
211254 DataCenterAsset , blank = True , null = True , on_delete = models .CASCADE
212255 )
213256 image_name = models .CharField (max_length = 255 , null = True , blank = True )
257+ cores = CoresVirtualComponent ()
258+ memory = MemoryVirtualComponent ()
259+ disk = DiskVirtualComponent ()
214260
215261 class Meta :
216262 verbose_name = _ ("Cloud host" )
0 commit comments