class SubNet(core_models.DescribableMixin, structure_models.ServiceProperty): network = models.ForeignKey( on_delete=models.CASCADE, to=Network, related_name='subnets' ) cidr = models.CharField(max_length=32, blank=True) gateway_ip = models.GenericIPAddressField(protocol='IPv4', null=True) allocation_pools = JSONField(default=dict) ip_version = models.SmallIntegerField(default=4) enable_dhcp = models.BooleanField(default=True) dns_nameservers = JSONField( default=list, help_text=_('List of DNS name servers associated with the subnet.'), ) class Meta: verbose_name = _('Subnet') verbose_name_plural = _('Subnets') unique_together = ('settings', 'backend_id') def __str__(self): return '%s (%s)' % (self.name, self.cidr) @classmethod def get_url_name(cls): return 'openstacktenant-subnet'
class VirtualMachine(structure_models.VirtualMachine): service_project_link = models.ForeignKey(AzureServiceProjectLink, related_name='virtualmachines', on_delete=models.PROTECT) public_ips = JSONField(default=list, help_text=_('List of public IP addresses'), blank=True) private_ips = JSONField(default=list, help_text=_('List of private IP addresses'), blank=True) user_username = models.CharField(max_length=50) user_password = models.CharField(max_length=50) @classmethod def get_url_name(cls): return 'azure-virtualmachine' def get_access_url_name(self): return 'azure-virtualmachine-rdp' @property def external_ips(self): return self.public_ips @property def internal_ips(self): return self.private_ips @classmethod def get_backend_fields(cls): return super(VirtualMachine, cls).get_backend_fields() + ( 'public_ips', 'private_ips', 'endpoints')
class Volume(TenantQuotaMixin, structure_models.Volume): # backend_id is nullable on purpose, otherwise # it wouldn't be possible to put a unique constraint on it backend_id = models.CharField(max_length=255, blank=True, null=True) service_project_link = models.ForeignKey(OpenStackTenantServiceProjectLink, related_name='volumes', on_delete=models.PROTECT) instance = models.ForeignKey('Instance', related_name='volumes', blank=True, null=True) device = models.CharField( max_length=50, blank=True, validators=[ RegexValidator( '^/dev/[a-zA-Z0-9]+$', message=_('Device should match pattern "/dev/alphanumeric+"')) ], help_text=_('Name of volume as instance device e.g. /dev/vdb.')) bootable = models.BooleanField(default=False) metadata = JSONField(blank=True) image = models.ForeignKey(Image, blank=True, null=True, on_delete=models.SET_NULL) image_name = models.CharField(max_length=150, blank=True) image_metadata = JSONField(blank=True) type = models.CharField(max_length=100, blank=True) source_snapshot = models.ForeignKey('Snapshot', related_name='volumes', blank=True, null=True, on_delete=models.SET_NULL) # TODO: Move this fields to resource model. action = models.CharField(max_length=50, blank=True) action_details = JSONField(default=dict) tracker = FieldTracker() class Meta(object): unique_together = ('service_project_link', 'backend_id') def get_quota_deltas(self): return { TenantQuotas.volumes: 1, TenantQuotas.volumes_size: self.size, TenantQuotas.storage: self.size, } @classmethod def get_url_name(cls): return 'openstacktenant-volume' @classmethod def get_backend_fields(cls): return super(Volume, cls).get_backend_fields() + ( 'name', 'description', 'size', 'metadata', 'type', 'bootable', 'runtime_state', 'device')
class Snapshot(TenantQuotaMixin, structure_models.Snapshot): # backend_id is nullable on purpose, otherwise # it wouldn't be possible to put a unique constraint on it backend_id = models.CharField(max_length=255, blank=True, null=True) service_project_link = models.ForeignKey( OpenStackTenantServiceProjectLink, related_name='snapshots', on_delete=models.PROTECT, ) source_volume: Volume = models.ForeignKey( Volume, related_name='snapshots', null=True, on_delete=models.PROTECT ) metadata = JSONField(blank=True) # TODO: Move this fields to resource model. action = models.CharField(max_length=50, blank=True) action_details = JSONField(default=dict) snapshot_schedule = models.ForeignKey( 'SnapshotSchedule', blank=True, null=True, on_delete=models.SET_NULL, related_name='snapshots', ) tracker = FieldTracker() kept_until = models.DateTimeField( null=True, blank=True, help_text=_('Guaranteed time of snapshot retention. If null - keep forever.'), ) class Meta: unique_together = ('service_project_link', 'backend_id') @classmethod def get_url_name(cls): return 'openstacktenant-snapshot' def get_quota_deltas(self): deltas = { TenantQuotas.snapshots: 1, TenantQuotas.snapshots_size: self.size, TenantQuotas.storage: self.size, } if self.source_volume and self.source_volume.type: deltas['gigabytes_' + self.source_volume.type.backend_id] = self.size / 1024 return deltas @classmethod def get_backend_fields(cls): return super(Snapshot, cls).get_backend_fields() + ( 'name', 'description', 'size', 'metadata', 'source_volume', 'runtime_state', )
class Instance(structure_models.VirtualMachine): service_project_link = models.ForeignKey(AWSServiceProjectLink, related_name='instances', on_delete=models.PROTECT) region = models.ForeignKey(on_delete=models.CASCADE, to=Region) public_ips = JSONField(default=list, help_text=_('List of public IP addresses'), blank=True) private_ips = JSONField(default=list, help_text=_('List of private IP addresses'), blank=True) size_backend_id = models.CharField(max_length=150, blank=True) def increase_backend_quotas_usage(self, validate=True): spl = self.service_project_link spl.add_quota_usage(spl.Quotas.storage, self.disk, validate=validate) spl.add_quota_usage(spl.Quotas.ram, self.ram, validate=validate) spl.add_quota_usage(spl.Quotas.vcpu, self.cores, validate=validate) def decrease_backend_quotas_usage(self): self.service_project_link.add_quota_usage( self.service_project_link.Quotas.storage, -self.disk) self.service_project_link.add_quota_usage( self.service_project_link.Quotas.ram, -self.ram) self.service_project_link.add_quota_usage( self.service_project_link.Quotas.vcpu, -self.cores) @property def external_ips(self): return self.public_ips @property def internal_ips(self): return self.private_ips def detect_coordinates(self): if self.external_ips: return get_coordinates_by_ip(self.external_ips[0]) region = self.region.backend_id endpoint = REGION_DETAILS[region]['endpoint'] return get_coordinates_by_ip(endpoint) @classmethod def get_url_name(cls): return 'aws-instance' @classmethod def get_backend_fields(cls): return super(Instance, cls).get_backend_fields() + ('runtime_state', ) @classmethod def get_online_state(cls): return 'running' @classmethod def get_offline_state(cls): return 'stopped'
class Snapshot(structure_models.Snapshot): # backend_id is nullable on purpose, otherwise # it wouldn't be possible to put a unique constraint on it backend_id = models.CharField(max_length=255, blank=True, null=True) service_project_link = models.ForeignKey(OpenStackTenantServiceProjectLink, related_name='snapshots', on_delete=models.PROTECT) source_volume = models.ForeignKey(Volume, related_name='snapshots', null=True, on_delete=models.PROTECT) metadata = JSONField(blank=True) # TODO: Move this fields to resource model. action = models.CharField(max_length=50, blank=True) action_details = JSONField(default=dict) snapshot_schedule = models.ForeignKey('SnapshotSchedule', blank=True, null=True, on_delete=models.SET_NULL, related_name='snapshots') tracker = FieldTracker() kept_until = models.DateTimeField( null=True, blank=True, help_text=_( 'Guaranteed time of snapshot retention. If null - keep forever.')) class Meta(object): unique_together = ('service_project_link', 'backend_id') @classmethod def get_url_name(cls): return 'openstacktenant-snapshot' def increase_backend_quotas_usage(self, validate=True): settings = self.service_project_link.service.settings settings.add_quota_usage(settings.Quotas.snapshots, 1, validate=validate) settings.add_quota_usage(settings.Quotas.storage, self.size, validate=validate) def decrease_backend_quotas_usage(self): settings = self.service_project_link.service.settings settings.add_quota_usage(settings.Quotas.snapshots, -1) settings.add_quota_usage(settings.Quotas.storage, -self.size) @classmethod def get_backend_fields(cls): return super(Snapshot, cls).get_backend_fields() + ( 'name', 'description', 'size', 'metadata', 'source_volume', 'runtime_state')
class Router(structure_models.SubResource): tenant: Tenant = models.ForeignKey( on_delete=models.CASCADE, to=Tenant, related_name='routers' ) routes = JSONField(default=list) fixed_ips = JSONField(default=list) def get_backend(self): return self.tenant.get_backend() @classmethod def get_url_name(cls): return 'openstack-router'
class EventTypesMixin(models.Model): """ Mixin to add a event_types and event_groups fields. """ class Meta(object): abstract = True event_types = JSONField('List of event types') event_groups = JSONField('List of event groups', default=list) @classmethod @lru_cache(maxsize=1) def get_all_models(cls): return [model for model in apps.get_models() if issubclass(model, cls)]
class SubNet(structure_models.SubResource): service_project_link = models.ForeignKey( OpenStackServiceProjectLink, related_name='subnets', on_delete=models.PROTECT ) network = models.ForeignKey( on_delete=models.CASCADE, to=Network, related_name='subnets' ) cidr = models.CharField(max_length=32, blank=True) gateway_ip = models.GenericIPAddressField(protocol='IPv4', null=True) allocation_pools = JSONField(default=dict) ip_version = models.SmallIntegerField(default=4) disable_gateway = models.BooleanField(default=False) enable_dhcp = models.BooleanField(default=True) dns_nameservers = JSONField( default=list, help_text=_('List of DNS name servers associated with the subnet.'), ) class Meta: verbose_name = _('Subnet') verbose_name_plural = _('Subnets') def get_backend(self): return self.network.get_backend() @classmethod def get_url_name(cls): return 'openstack-subnet' def increase_backend_quotas_usage(self, validate=True): self.network.tenant.add_quota_usage( self.network.tenant.Quotas.subnet_count, 1, validate=validate ) def decrease_backend_quotas_usage(self): self.network.tenant.add_quota_usage(self.network.tenant.Quotas.subnet_count, -1) @classmethod def get_backend_fields(cls): return super(SubNet, cls).get_backend_fields() + ( 'name', 'description', 'allocation_pools', 'cidr', 'ip_version', 'enable_dhcp', 'gateway_ip', 'dns_nameservers', )
class Project(structure_models.NewResource, core_models.RuntimeStateMixin): class Permissions(structure_models.NewResource.Permissions): pass service_project_link = models.ForeignKey(JiraServiceProjectLink, related_name='projects', on_delete=models.PROTECT) template = models.ForeignKey(on_delete=models.CASCADE, to=ProjectTemplate, blank=True, null=True) action = models.CharField(max_length=50, blank=True) action_details = JSONField(default=dict) def get_backend(self): return super(Project, self).get_backend(project=self.backend_id) def get_access_url(self): base_url = self.service_project_link.service.settings.backend_url return urljoin(base_url, 'projects/' + self.backend_id) @classmethod def get_url_name(cls): return 'jira-projects' @property def priorities(self): return Priority.objects.filter( settings=self.service_project_link.service.settings)
class BaseSubNet(models.Model): class Meta: abstract = True cidr = models.CharField(max_length=32, blank=True) gateway_ip = models.GenericIPAddressField(protocol='IPv4', null=True) allocation_pools = JSONField(default=dict) ip_version = models.SmallIntegerField(default=4) enable_dhcp = models.BooleanField(default=True) dns_nameservers = JSONField( default=list, help_text=_('List of DNS name servers associated with the subnet.'), ) is_connected = models.BooleanField( default=True, help_text=_('Is subnet connected to the default tenant router.'))
class GenericInvoiceItem(InvoiceItem): invoice = models.ForeignKey(Invoice, related_name='generic_items') content_type = models.ForeignKey(ContentType, null=True, related_name='+') object_id = models.PositiveIntegerField(null=True) quantity = models.PositiveIntegerField(default=0) scope = GenericForeignKey('content_type', 'object_id') details = JSONField(default={}, blank=True, help_text=_('Stores data about scope')) objects = managers.GenericInvoiceItemManager() tracker = FieldTracker() @property def name(self): if self.details: return self.details['name'] return registrators.RegistrationManager.get_name(self.scope) def freeze(self): if self.scope: self.details = registrators.RegistrationManager.get_details( self.scope) self.details['name'] = registrators.RegistrationManager.get_name( self.scope) self.details['scope_uuid'] = self.scope.uuid.hex self.save(update_fields=['details'])
class Port(core_models.BackendModelMixin, models.Model): # TODO: Use dedicated field: https://github.com/django-macaddress/django-macaddress mac_address = models.CharField(max_length=32, blank=True) ip4_address = models.GenericIPAddressField(null=True, blank=True, protocol='IPv4') ip6_address = models.GenericIPAddressField(null=True, blank=True, protocol='IPv6') backend_id = models.CharField(max_length=255, blank=True) allowed_address_pairs = JSONField( default=list, help_text= _('A server can send a packet with source address which matches one of the specified allowed address pairs.' ), ) class Meta: abstract = True def __str__(self): return self.ip4_address or self.ip6_address or 'Not initialized' @classmethod def get_backend_fields(cls): return super(Port, cls).get_backend_fields() + ( 'ip4_address', 'ip6_address', 'mac_address', 'allowed_address_pairs', )
class GenericInvoiceItem(InvoiceItem): """ It is expected that get_scope_type method is defined as class method in scope class as it is used in generic invoice item serializer. """ invoice = models.ForeignKey(Invoice, related_name='generic_items') content_type = models.ForeignKey(ContentType, null=True, related_name='+') object_id = models.PositiveIntegerField(null=True) quantity = models.PositiveIntegerField(default=0) scope = GenericForeignKey('content_type', 'object_id') details = JSONField(default=dict, blank=True, help_text=_('Stores data about scope')) objects = managers.GenericInvoiceItemManager() tracker = FieldTracker() @property def name(self): if self.details.get('name'): return self.details.get('name') if self.scope: return registrators.RegistrationManager.get_name(self.scope) # Ilja: temporary workaround to unlock creation of new invoices due to issues caused by 0027 migration if self.details: return ', '.join(['%s: %s' % (k, v) for k, v in self.details.items()]) if self.content_type: return '%s.%s' % (self.content_type.app_label, self.content_type.model) return '' def freeze(self): if self.scope: self.details = registrators.RegistrationManager.get_details(self.scope) self.details['name'] = registrators.RegistrationManager.get_name(self.scope) self.details['scope_uuid'] = self.scope.uuid.hex self.save(update_fields=['details'])
class Backup(structure_models.SubResource): service_project_link = models.ForeignKey( OpenStackTenantServiceProjectLink, related_name='backups', on_delete=models.PROTECT, ) instance = models.ForeignKey( Instance, related_name='backups', on_delete=models.PROTECT ) backup_schedule = models.ForeignKey( 'BackupSchedule', blank=True, null=True, on_delete=models.SET_NULL, related_name='backups', ) kept_until = models.DateTimeField( null=True, blank=True, help_text=_('Guaranteed time of backup retention. If null - keep forever.'), ) metadata = JSONField( blank=True, help_text=_( 'Additional information about backup, can be used for backup restoration or deletion' ), ) snapshots = models.ManyToManyField('Snapshot', related_name='backups') @classmethod def get_url_name(cls): return 'openstacktenant-backup'
class Router(structure_models.SubResource): service_project_link = models.ForeignKey(OpenStackServiceProjectLink, related_name='routers', on_delete=models.PROTECT) tenant: Tenant = models.ForeignKey(on_delete=models.CASCADE, to=Tenant, related_name='routers') routes = JSONField(default=list) fixed_ips = JSONField(default=list) def get_backend(self): return self.tenant.get_backend() @classmethod def get_url_name(cls): return 'openstack-router'
class SystemNotification(EventTypesMixin, models.Model): # Model doesn't inherit NameMixin, because this is circular dependence. name = models.CharField(_('name'), max_length=150) hook_content_type = models.ForeignKey(on_delete=models.CASCADE, to=ct_models.ContentType, related_name='+') roles = JSONField('List of roles', default=list) @staticmethod def get_valid_roles(): return 'admin', 'manager', 'owner' @classmethod def get_hooks(cls, event_type, project=None, customer=None): from waldur_core.structure import models as structure_models from waldur_core.logging import loggers groups = [ g[0] for g in loggers.event_logger.get_all_groups().items() if event_type in g[1] ] for hook in cls.objects.filter( models.Q(event_types__contains=event_type) | models.Q(event_groups__has_any_keys=groups)): hook_class = hook.hook_content_type.model_class() users_qs = [] if project: if 'admin' in hook.roles: users_qs.append( project.get_users( structure_models.ProjectRole.ADMINISTRATOR)) if 'manager' in hook.roles: users_qs.append( project.get_users( structure_models.ProjectRole.MANAGER)) if 'owner' in hook.roles: users_qs.append(project.customer.get_owners()) if customer: if 'owner' in hook.roles: users_qs.append(customer.get_owners()) if len(users_qs) > 1: users = users_qs[0].union(*users_qs[1:]).distinct() elif len(users_qs) == 1: users = users_qs[0] else: users = [] for user in users: if user.email: yield hook_class(user=user, event_types=hook.event_types, email=user.email) def __str__(self): return '%s | %s' % (self.hook_content_type, self.name)
class PrivateCloud(quotas_models.QuotaModelMixin, core_models.RuntimeStateMixin, NewResource): extra_configuration = JSONField( default=dict, help_text=_( 'Configuration details that are not represented on backend.')) class Meta(object): abstract = True
class SubNet(structure_models.ServiceProperty): network = models.ForeignKey(Network, related_name='subnets') cidr = models.CharField(max_length=32) gateway_ip = models.GenericIPAddressField(protocol='IPv4') allocation_pools = JSONField() dns_nameservers = JSONField( help_text=_('List of DNS name servers associated with the subnet.')) class Meta(object): verbose_name = _('Subnet') verbose_name_plural = _('Subnets') unique_together = ('settings', 'backend_id') def __str__(self): return '%s (%s)' % (self.name, self.cidr) @classmethod def get_url_name(cls): return 'rijkscloud-subnet'
class Instance(structure_models.VirtualMachine): region = models.ForeignKey(on_delete=models.CASCADE, to=Region) public_ips = JSONField(default=list, help_text=_('List of public IP addresses'), blank=True) private_ips = JSONField(default=list, help_text=_('List of private IP addresses'), blank=True) size_backend_id = models.CharField(max_length=150, blank=True) @property def external_ips(self): return self.public_ips @property def internal_ips(self): return self.private_ips def detect_coordinates(self): if self.external_ips: return get_coordinates_by_ip(self.external_ips[0]) region = self.region.backend_id endpoint = REGION_DETAILS[region]['endpoint'] return get_coordinates_by_ip(endpoint) @classmethod def get_url_name(cls): return 'aws-instance' @classmethod def get_backend_fields(cls): return super(Instance, cls).get_backend_fields() + ('runtime_state', ) @classmethod def get_online_state(cls): return 'running' @classmethod def get_offline_state(cls): return 'stopped'
class Port(core_models.BackendModelMixin, models.Model): # TODO: Use dedicated field: https://github.com/django-macaddress/django-macaddress mac_address = models.CharField(max_length=32, blank=True) fixed_ips = JSONField( default=list, help_text=_( 'A list of tuples (ip_address, subnet_id), where ip_address can be both IPv4 and IPv6 ' 'and subnet_id is a backend id of the subnet'), ) backend_id = models.CharField(max_length=255, blank=True) allowed_address_pairs = JSONField( default=list, help_text= _('A server can send a packet with source address which matches one of the specified allowed address pairs.' ), ) device_id = models.CharField( max_length=255, null=True, blank=True, ) device_owner = models.CharField( max_length=100, null=True, blank=True, ) class Meta: abstract = True @classmethod def get_backend_fields(cls): return super(Port, cls).get_backend_fields() + ( 'fixed_ips', 'mac_address', 'allowed_address_pairs', 'device_id', 'device_owner', )
class Offering(core_models.UuidMixin, core_models.NameMixin, core_models.DescribableMixin, quotas_models.QuotaModelMixin, structure_models.StructureModel, structure_models.TimeStampedModel): thumbnail = models.ImageField( upload_to='marketplace_service_offering_thumbnails', blank=True, null=True) full_description = models.TextField(blank=True) rating = models.IntegerField( null=True, validators=[MaxValueValidator(5), MinValueValidator(1)], help_text=_('Rating is value from 1 to 5.')) category = models.ForeignKey(Category, related_name='offerings') customer = models.ForeignKey(structure_models.Customer, related_name='+', null=True) attributes = BetterJSONField(blank=True, default=dict) geolocations = JSONField( default=list, blank=True, help_text=_( 'List of latitudes and longitudes. For example: ' '[{"latitude": 123, "longitude": 345}, {"latitude": 456, "longitude": 678}]' )) is_active = models.BooleanField(default=True) native_name = models.CharField(max_length=160, default='', blank=True) native_description = models.CharField(max_length=500, default='', blank=True) class Permissions(object): customer_path = 'customer' class Meta(object): verbose_name = _('Offering') class Quotas(quotas_models.QuotaModelMixin.Quotas): order_item_count = quotas_fields.CounterQuotaField( target_models=lambda: [OrderItem], path_to_scope='offering', ) def __str__(self): return six.text_type(self.name) @classmethod def get_url_name(cls): return 'marketplace-offering'
class Volume(structure_models.Volume): service_project_link = models.ForeignKey(RijkscloudServiceProjectLink, related_name='volumes', on_delete=models.PROTECT) metadata = JSONField(blank=True) @classmethod def get_url_name(cls): return 'rijkscloud-volume' @classmethod def get_backend_fields(cls): return super(Volume, cls).get_backend_fields() + ( 'name', 'size', 'metadata', 'runtime_state')
class SubNet(openstack_base_models.BaseSubNet, structure_models.SubResource): service_project_link = models.ForeignKey(OpenStackServiceProjectLink, related_name='subnets', on_delete=models.PROTECT) network = models.ForeignKey(on_delete=models.CASCADE, to=Network, related_name='subnets') disable_gateway = models.BooleanField(default=False) host_routes = JSONField( default=list, help_text=_('List of additional routes for the subnet.'), ) class Meta: verbose_name = _('Subnet') verbose_name_plural = _('Subnets') def get_backend(self): return self.network.get_backend() @classmethod def get_url_name(cls): return 'openstack-subnet' def increase_backend_quotas_usage(self, validate=True): self.network.tenant.add_quota_usage( self.network.tenant.Quotas.subnet_count, 1, validate=validate) def decrease_backend_quotas_usage(self): self.network.tenant.add_quota_usage( self.network.tenant.Quotas.subnet_count, -1) @classmethod def get_backend_fields(cls): return super(SubNet, cls).get_backend_fields() + ( 'name', 'description', 'allocation_pools', 'cidr', 'ip_version', 'enable_dhcp', 'gateway_ip', 'dns_nameservers', 'host_routes', 'is_connected', )
class Alert(UuidMixin, TimeStampedModel): class Meta: unique_together = ("content_type", "object_id", "alert_type", "is_closed") class SeverityChoices(object): DEBUG = 10 INFO = 20 WARNING = 30 ERROR = 40 CHOICES = ((DEBUG, 'Debug'), (INFO, 'Info'), (WARNING, 'Warning'), (ERROR, 'Error')) alert_type = models.CharField(max_length=50, db_index=True) message = models.CharField(max_length=255) severity = models.SmallIntegerField(choices=SeverityChoices.CHOICES) closed = models.DateTimeField(null=True, blank=True) # Hack: This field stays blank until alert closing. # After closing it gets unique value to avoid unique together constraint break. is_closed = models.CharField(blank=True, max_length=32) acknowledged = models.BooleanField(default=False) context = JSONField(blank=True) content_type = models.ForeignKey(ct_models.ContentType, null=True, on_delete=models.SET_NULL) object_id = models.PositiveIntegerField(null=True) scope = ct_fields.GenericForeignKey('content_type', 'object_id') objects = managers.AlertManager() def close(self): self.closed = timezone.now() self.is_closed = uuid.uuid4().hex self.save() def acknowledge(self): self.acknowledged = True self.save() def cancel_acknowledgment(self): self.acknowledged = False self.save()
class OfferingItem(InvoiceItem): """ OfferingItem stores details for invoices about purchased custom offering item. """ invoice = models.ForeignKey(Invoice, related_name='offering_items') offering = models.ForeignKey(support_models.Offering, on_delete=models.SET_NULL, null=True, related_name='+') offering_details = JSONField(default=dict, blank=True, help_text=_('Stores data about offering')) tracker = FieldTracker() @property def name(self): if self.offering_details: name = self.offering_details.get('offering_name', self.project_name) return '%s (%s)' % (name, self.offering_details['offering_type']) return '%s (%s)' % (self.offering.name, self.offering.type) def freeze(self): """ Saves offering type and offering name in "offering_details" if offering exists """ if self.offering: self.offering_details['offering_type'] = self.offering.type self.offering_details['offering_name'] = self.offering.name self.offering_details['offering_uuid'] = self.offering.uuid.hex self.offering = None self.save(update_fields=['offering_details', 'offering']) def get_offering_uuid(self): if self.offering: return self.offering.uuid.hex else: return self.offering_details.get('offering_uuid') def get_offering_type(self): if self.offering: return self.offering.type else: return self.offering_details.get('offering_type')
class Instance(TenantQuotaMixin, structure_models.VirtualMachine): class RuntimeStates: # All possible OpenStack Instance states on backend. # See https://docs.openstack.org/developer/nova/vmstates.html ACTIVE = 'ACTIVE' BUILDING = 'BUILDING' DELETED = 'DELETED' SOFT_DELETED = 'SOFT_DELETED' ERROR = 'ERROR' UNKNOWN = 'UNKNOWN' HARD_REBOOT = 'HARD_REBOOT' REBOOT = 'REBOOT' REBUILD = 'REBUILD' PASSWORD = '******' PAUSED = 'PAUSED' RESCUED = 'RESCUED' RESIZED = 'RESIZED' REVERT_RESIZE = 'REVERT_RESIZE' SHUTOFF = 'SHUTOFF' STOPPED = 'STOPPED' SUSPENDED = 'SUSPENDED' VERIFY_RESIZE = 'VERIFY_RESIZE' # backend_id is nullable on purpose, otherwise # it wouldn't be possible to put a unique constraint on it backend_id = models.CharField(max_length=255, blank=True, null=True) service_project_link = models.ForeignKey( OpenStackTenantServiceProjectLink, related_name='instances', on_delete=models.PROTECT, ) availability_zone = models.ForeignKey( InstanceAvailabilityZone, blank=True, null=True, on_delete=models.SET_NULL ) flavor_name = models.CharField(max_length=255, blank=True) flavor_disk = models.PositiveIntegerField( default=0, help_text=_('Flavor disk size in MiB') ) security_groups = models.ManyToManyField(SecurityGroup, related_name='instances') # TODO: Move this fields to resource model. action = models.CharField(max_length=50, blank=True) action_details = JSONField(default=dict) subnets = models.ManyToManyField('SubNet', through='InternalIP') tracker = FieldTracker() class Meta: unique_together = ('service_project_link', 'backend_id') @property def external_ips(self): return list(self.floating_ips.values_list('address', flat=True)) @property def internal_ips(self): return list(self.internal_ips_set.values_list('ip4_address', flat=True)) @property def size(self): return self.volumes.aggregate(models.Sum('size'))['size__sum'] @classmethod def get_url_name(cls): return 'openstacktenant-instance' def get_log_fields(self): return ( 'uuid', 'name', 'type', 'service_project_link', 'ram', 'cores', ) def detect_coordinates(self): settings = self.service_project_link.service.settings options = settings.options or {} if 'latitude' in options and 'longitude' in options: return structure_utils.Coordinates( latitude=settings['latitude'], longitude=settings['longitude'] ) else: hostname = urlparse(settings.backend_url).hostname if hostname: return get_coordinates_by_ip(hostname) def get_quota_deltas(self): return { TenantQuotas.instances: 1, TenantQuotas.ram: self.ram, TenantQuotas.vcpu: self.cores, } @property def floating_ips(self): return FloatingIP.objects.filter(internal_ip__instance=self) @classmethod def get_backend_fields(cls): return super(Instance, cls).get_backend_fields() + ( 'flavor_name', 'flavor_disk', 'ram', 'cores', 'disk', 'runtime_state', 'availability_zone', ) @classmethod def get_online_state(cls): return Instance.RuntimeStates.ACTIVE @classmethod def get_offline_state(cls): return Instance.RuntimeStates.SHUTOFF
class ServiceSettings(quotas_models.ExtendableQuotaModelMixin, core_models.UuidMixin, core_models.NameMixin, core_models.StateMixin, TagMixin, LoggableMixin): class Meta: verbose_name = "Service settings" verbose_name_plural = "Service settings" class Permissions(object): customer_path = 'customer' extra_query = dict(shared=True) customer = models.ForeignKey(Customer, verbose_name=_('organization'), related_name='service_settings', blank=True, null=True) backend_url = core_fields.BackendURLField(max_length=200, blank=True, null=True) username = models.CharField(max_length=100, blank=True, null=True) password = models.CharField(max_length=100, blank=True, null=True) domain = models.CharField(max_length=200, blank=True, null=True) token = models.CharField(max_length=255, blank=True, null=True) certificate = models.FileField(upload_to='certs', blank=True, null=True, validators=[FileTypeValidator( allowed_types=[ 'application/x-pem-file', 'application/x-x509-ca-cert', 'text/plain'], allowed_extensions=['pem'])]) type = models.CharField(max_length=255, db_index=True, validators=[validate_service_type]) options = JSONField(default={}, help_text=_('Extra options'), blank=True) geolocations = JSONField(default=[], blank=True, help_text=_('List of latitudes and longitudes. For example: ' '[{"latitude": 123, "longitude": 345}, {"latitude": 456, "longitude": 678}]')) shared = models.BooleanField(default=False, help_text=_('Anybody can use it')) homepage = models.URLField(max_length=255, blank=True) terms_of_services = models.URLField(max_length=255, blank=True) certifications = models.ManyToManyField(to='ServiceCertification', related_name='service_settings', blank=True) tracker = FieldTracker() # service settings scope - VM that contains service content_type = models.ForeignKey(ContentType, null=True) object_id = models.PositiveIntegerField(null=True) scope = GenericForeignKey('content_type', 'object_id') objects = ServiceSettingsManager('scope') def get_backend(self, **kwargs): return SupportedServices.get_service_backend(self.type)(self, **kwargs) def get_option(self, name): options = self.options or {} if name in options: return options.get(name) else: defaults = self.get_backend().DEFAULTS return defaults.get(name) def __str__(self): return '%s (%s)' % (self.name, self.get_type_display()) def get_log_fields(self): return ('uuid', 'name', 'customer') def _get_log_context(self, entity_name): context = super(ServiceSettings, self)._get_log_context(entity_name) context['service_settings_type'] = self.get_type_display() return context def get_type_display(self): return SupportedServices.get_name_for_type(self.type) def get_services(self): service_model = SupportedServices.get_service_models()[self.type]['service'] return service_model.objects.filter(settings=self) def unlink_descendants(self): for service in self.get_services(): service.unlink_descendants() service.delete()
class Offering(core_models.UuidMixin, core_models.NameMixin, core_models.DescribableMixin, quotas_models.QuotaModelMixin, structure_models.StructureModel, structure_models.TimeStampedModel, ScopeMixin): class States(object): DRAFT = 1 ACTIVE = 2 PAUSED = 3 ARCHIVED = 4 CHOICES = ( (DRAFT, 'Draft'), (ACTIVE, 'Active'), (PAUSED, 'Paused'), (ARCHIVED, 'Archived'), ) thumbnail = models.FileField( upload_to='marketplace_service_offering_thumbnails', blank=True, null=True, validators=[VectorizedImageValidator]) full_description = models.TextField(blank=True) vendor_details = models.TextField(blank=True) rating = models.IntegerField( null=True, validators=[MaxValueValidator(5), MinValueValidator(1)], help_text=_('Rating is value from 1 to 5.')) category = models.ForeignKey(Category, related_name='offerings') customer = models.ForeignKey(structure_models.Customer, related_name='+', null=True) attributes = BetterJSONField(blank=True, default=dict, help_text=_('Fields describing Category.')) options = BetterJSONField( blank=True, default=dict, help_text=_('Fields describing Offering request form.')) geolocations = JSONField( default=list, blank=True, help_text=_( 'List of latitudes and longitudes. For example: ' '[{"latitude": 123, "longitude": 345}, {"latitude": 456, "longitude": 678}]' )) native_name = models.CharField(max_length=160, default='', blank=True) native_description = models.CharField(max_length=500, default='', blank=True) type = models.CharField(max_length=100) state = FSMIntegerField(default=States.DRAFT, choices=States.CHOICES) # If offering is not shared, it is available only to following user categories: # 1) staff user; # 2) global support user; # 3) users with active permission in original customer; # 4) users with active permission in allowed customers and nested projects. shared = models.BooleanField(default=False, help_text=_('Anybody can use it')) allowed_customers = models.ManyToManyField(structure_models.Customer, blank=True) objects = managers.MixinManager('scope') tracker = FieldTracker() class Permissions(object): customer_path = 'customer' class Meta(object): verbose_name = _('Offering') class Quotas(quotas_models.QuotaModelMixin.Quotas): order_item_count = quotas_fields.CounterQuotaField( target_models=lambda: [OrderItem], path_to_scope='offering', ) @transition(field=state, source=States.DRAFT, target=States.ACTIVE) def activate(self): pass @transition(field=state, source=States.ACTIVE, target=States.PAUSED) def pause(self): pass @transition(field=state, source='*', target=States.ARCHIVED) def archive(self): pass def __str__(self): return six.text_type(self.name) @classmethod def get_url_name(cls): return 'marketplace-offering'
class ServiceSettings( quotas_models.ExtendableQuotaModelMixin, core_models.UuidMixin, core_models.NameMixin, core_models.StateMixin, TagMixin, StructureLoggableMixin, ): class Meta: verbose_name = "Service settings" verbose_name_plural = "Service settings" ordering = ('name', ) class Permissions: customer_path = 'customer' build_query = build_service_settings_query customer = models.ForeignKey( on_delete=models.CASCADE, to=Customer, verbose_name=_('organization'), related_name='service_settings', blank=True, null=True, ) backend_url = core_fields.BackendURLField(max_length=200, blank=True, null=True) username = models.CharField(max_length=100, blank=True, null=True) password = models.CharField(max_length=100, blank=True, null=True) domain = models.CharField(max_length=200, blank=True, null=True) token = models.CharField(max_length=255, blank=True, null=True) certificate = models.FileField(upload_to='certs', blank=True, null=True, validators=[CertificateValidator]) type = models.CharField(max_length=255, db_index=True, validators=[validate_service_type]) options = JSONField(default=dict, help_text=_('Extra options'), blank=True) shared = models.BooleanField(default=False, help_text=_('Anybody can use it')) terms_of_services = models.URLField(max_length=255, blank=True) tracker = FieldTracker() # service settings scope - VM that contains service content_type = models.ForeignKey(on_delete=models.CASCADE, to=ContentType, null=True) object_id = models.PositiveIntegerField(null=True) scope = GenericForeignKey('content_type', 'object_id') objects = ServiceSettingsManager('scope') is_active = models.BooleanField( default=True, help_text= 'Information about inactive service settings will not be updated in the background', ) def get_backend(self, **kwargs): return SupportedServices.get_service_backend(self.type)(self, **kwargs) def get_option(self, name): options = self.options or {} if name in options: return options.get(name) else: defaults = self.get_backend().DEFAULTS return defaults.get(name) def __str__(self): return '%s (%s)' % (self.name, self.type) def get_log_fields(self): return ('uuid', 'name', 'customer') def _get_log_context(self, entity_name): context = super(ServiceSettings, self)._get_log_context(entity_name) context['service_settings_type'] = self.type return context def get_type_display(self): return self.type