class CloudSize(me.Document): """A base Cloud Size Model.""" id = me.StringField(primary_key=True, default=lambda: uuid.uuid4().hex) cloud = me.ReferenceField('Cloud', required=True, reverse_delete_rule=me.CASCADE) external_id = me.StringField(required=True) name = me.StringField() cpus = me.IntField() ram = me.IntField() disk = me.IntField() bandwidth = me.IntField() missing_since = me.DateTimeField() extra = MistDictField() # price info is included here meta = { 'collection': 'sizes', 'indexes': [ { 'fields': ['cloud', 'external_id'], 'sparse': False, 'unique': True, 'cls': False, }, ] } def __str__(self): name = "%s, %s (%s)" % (self.name, self.cloud.id, self.external_id) return name def as_dict(self): return { 'id': self.id, 'cloud': self.cloud.id, 'external_id': self.external_id, 'name': self.name, 'cpus': self.cpus, 'ram': self.ram, 'bandwidth': self.bandwidth, 'extra': self.extra, 'disk': self.disk, 'missing_since': str( self.missing_since.replace( tzinfo=None) if self.missing_since else '') }
class CloudLocation(OwnershipMixin, me.Document): """A base Cloud Location Model.""" id = me.StringField(primary_key=True, default=lambda: uuid.uuid4().hex) cloud = me.ReferenceField('Cloud', required=True, reverse_delete_rule=me.CASCADE) owner = me.ReferenceField('Organization', required=True, reverse_delete_rule=me.CASCADE) external_id = me.StringField(required=True) name = me.StringField() country = me.StringField() missing_since = me.DateTimeField() extra = MistDictField() meta = { 'collection': 'locations', 'indexes': [ { 'fields': ['cloud', 'external_id'], 'sparse': False, 'unique': True, 'cls': False, }, ] } def __str__(self): name = "%s, %s (%s)" % (self.name, self.cloud.id, self.external_id) return name def as_dict(self): return { 'id': self.id, 'extra': self.extra, 'cloud': self.cloud.id, 'external_id': self.external_id, 'name': self.name, 'country': self.country, 'missing_since': str( self.missing_since.replace( tzinfo=None) if self.missing_since else '') } def clean(self): # Populate owner field based on self.cloud.owner if not self.owner: self.owner = self.cloud.owner
class CloudImage(me.Document): """A base Cloud Image Model.""" id = me.StringField(primary_key=True, default=lambda: uuid.uuid4().hex) cloud = me.ReferenceField('Cloud', required=True, reverse_delete_rule=me.CASCADE) external_id = me.StringField(required=True) name = me.StringField() starred = me.BooleanField(default=True) stored_after_search = me.BooleanField(default=False) missing_since = me.DateTimeField() extra = MistDictField() os_type = me.StringField(default='linux') meta = { 'collection': 'images', 'indexes': [ { 'fields': ['cloud', 'external_id'], 'sparse': False, 'unique': True, 'cls': False, }, ] } def __str__(self): name = "%s, %s (%s)" % (self.name, self.cloud.id, self.external_id) return name def as_dict(self): return { 'id': self.id, 'cloud': self.cloud.id, 'external_id': self.external_id, 'name': self.name, 'starred': self.starred, 'extra': self.extra, 'os_type': self.os_type, 'missing_since': str(self.missing_since.replace(tzinfo=None) if self.missing_since else '') }
class Machine(OwnershipMixin, me.Document): """The basic machine model""" id = me.StringField(primary_key=True, default=lambda: uuid.uuid4().hex) cloud = me.ReferenceField('Cloud', required=True, reverse_delete_rule=me.CASCADE) owner = me.ReferenceField('Organization', required=True, reverse_delete_rule=me.CASCADE) location = me.ReferenceField('CloudLocation', required=False, reverse_delete_rule=me.DENY) size = me.ReferenceField('CloudSize', required=False, reverse_delete_rule=me.DENY) image = me.ReferenceField('CloudImage', required=False, reverse_delete_rule=me.DENY) network = me.ReferenceField('Network', required=False, reverse_delete_rule=me.NULLIFY) subnet = me.ReferenceField('Subnet', required=False, reverse_delete_rule=me.NULLIFY) name = me.StringField() # Info gathered mostly by libcloud (or in some cases user input). # Be more specific about what this is. # We should perhaps come up with a better name. machine_id = me.StringField(required=True) hostname = me.StringField() public_ips = me.ListField() private_ips = me.ListField() ssh_port = me.IntField(default=22) OS_TYPES = ('windows', 'coreos', 'freebsd', 'linux', 'unix') os_type = me.StringField(default='unix', choices=OS_TYPES) rdp_port = me.IntField(default=3389) actions = me.EmbeddedDocumentField(Actions, default=lambda: Actions()) extra = MistDictField() cost = me.EmbeddedDocumentField(Cost, default=lambda: Cost()) # libcloud.compute.types.NodeState state = me.StringField(default='unknown', choices=tuple(config.STATES.values())) machine_type = me.StringField(default='machine', choices=('machine', 'vm', 'container', 'hypervisor', 'container-host', 'ilo-host')) parent = me.ReferenceField('Machine', required=False, reverse_delete_rule=me.NULLIFY) # Deprecated TODO: Remove in v5 key_associations = me.EmbeddedDocumentListField(KeyAssociation) last_seen = me.DateTimeField() missing_since = me.DateTimeField() unreachable_since = me.DateTimeField() created = me.DateTimeField() monitoring = me.EmbeddedDocumentField(Monitoring, default=lambda: Monitoring()) ssh_probe = me.EmbeddedDocumentField(SSHProbe, required=False) ping_probe = me.EmbeddedDocumentField(PingProbe, required=False) expiration = me.ReferenceField(Schedule, required=False, reverse_delete_rule=me.NULLIFY) # Number of vCPUs gathered from various sources. This field is meant to # be updated ONLY by the mist.api.metering.tasks:find_machine_cores task. cores = me.IntField() meta = { 'collection': 'machines', 'indexes': [ { 'fields': [ 'cloud', 'machine_id' ], 'sparse': False, 'unique': True, 'cls': False, }, { 'fields': [ 'monitoring.installation_status.activated_at' ], 'sparse': True, 'unique': False } ], 'strict': False, } def __init__(self, *args, **kwargs): super(Machine, self).__init__(*args, **kwargs) self.ctl = MachineController(self) def clean(self): # Remove any KeyAssociation, whose `keypair` has been deleted. Do NOT # perform an atomic update on self, but rather remove items from the # self.key_associations list by iterating over it and popping matched # embedded documents in order to ensure that the most recent list is # always processed and saved. key_associations = KeyMachineAssociation.objects(machine=self) for ka in reversed(list(range(len(key_associations)))): if key_associations[ka].key.deleted: key_associations[ka].delete() # Reset key_associations in case self goes missing/destroyed. This is # going to prevent the machine from showing up as "missing" in the # corresponding keys' associated machines list. if self.missing_since: self.key_associations = [] # Populate owner field based on self.cloud.owner if not self.owner: self.owner = self.cloud.owner self.clean_os_type() if self.monitoring.method not in config.MONITORING_METHODS: self.monitoring.method = config.DEFAULT_MONITORING_METHOD def clean_os_type(self): """Clean self.os_type""" if self.os_type not in self.OS_TYPES: for os_type in self.OS_TYPES: if self.os_type.lower() == os_type: self.os_type = os_type break else: self.os_type = 'unix' def delete(self): if self.expiration: self.expiration.delete() super(Machine, self).delete() mist.api.tag.models.Tag.objects( resource_id=self.id, resource_type='machine').delete() try: self.owner.mapper.remove(self) except (AttributeError, me.DoesNotExist) as exc: log.error(exc) try: if self.owned_by: self.owned_by.get_ownership_mapper(self.owner).remove(self) except (AttributeError, me.DoesNotExist) as exc: log.error(exc) def as_dict(self): # Return a dict as it will be returned to the API tags = {tag.key: tag.value for tag in mist.api.tag.models.Tag.objects( resource_id=self.id, resource_type='machine' ).only('key', 'value')} try: if self.expiration: expiration = { 'id': self.expiration.id, 'action': self.expiration.task_type.action, 'date': self.expiration.schedule_type.entry.isoformat(), 'notify': self.expiration.reminder and int(( self.expiration.schedule_type.entry - self.expiration.reminder.schedule_type.entry ).total_seconds()) or 0, } else: expiration = None except Exception as exc: log.error("Error getting expiration for machine %s: %r" % ( self.id, exc)) self.expiration = None self.save() expiration = None try: from bson import json_util extra = json.loads(json.dumps(self.extra, default=json_util.default)) except Exception as exc: log.error('Failed to serialize extra metadata for %s: %s\n%s' % ( self, self.extra, exc)) extra = {} return { 'id': self.id, 'hostname': self.hostname, 'public_ips': self.public_ips, 'private_ips': self.private_ips, 'name': self.name, 'ssh_port': self.ssh_port, 'os_type': self.os_type, 'rdp_port': self.rdp_port, 'machine_id': self.machine_id, 'actions': {action: self.actions[action] for action in self.actions}, 'extra': extra, 'cost': self.cost.as_dict(), 'state': self.state, 'tags': tags, 'monitoring': self.monitoring.as_dict() if self.monitoring and self.monitoring.hasmonitoring else '', 'key_associations': [ka.as_dict() for ka in KeyMachineAssociation.objects( machine=self)], 'cloud': self.cloud.id, 'location': self.location.id if self.location else '', 'size': self.size.name if self.size else '', 'image': self.image.id if self.image else '', 'cloud_title': self.cloud.title, 'last_seen': str(self.last_seen.replace(tzinfo=None) if self.last_seen else ''), 'missing_since': str(self.missing_since.replace(tzinfo=None) if self.missing_since else ''), 'unreachable_since': str( self.unreachable_since.replace(tzinfo=None) if self.unreachable_since else ''), 'created': str(self.created.replace(tzinfo=None) if self.created else ''), 'machine_type': self.machine_type, 'parent': self.parent.id if self.parent is not None else '', 'probe': { 'ping': (self.ping_probe.as_dict() if self.ping_probe is not None else PingProbe().as_dict()), 'ssh': (self.ssh_probe.as_dict() if self.ssh_probe is not None else SSHProbe().as_dict()), }, 'cores': self.cores, 'network': self.network.id if self.network else '', 'subnet': self.subnet.id if self.subnet else '', 'owned_by': self.owned_by.id if self.owned_by else '', 'created_by': self.created_by.id if self.created_by else '', 'expiration': expiration, 'provider': self.cloud.ctl.provider } def __str__(self): return 'Machine %s (%s) in %s' % (self.name, self.id, self.cloud)
class Zone(OwnershipMixin, me.Document): """This is the class definition for the Mongo Engine Document related to a DNS zone. """ id = me.StringField(primary_key=True, default=lambda: uuid.uuid4().hex) owner = me.ReferenceField('Organization', required=True, reverse_delete_rule=me.CASCADE) zone_id = me.StringField(required=True) domain = me.StringField(required=True) type = me.StringField(required=True) ttl = me.IntField(required=True, default=0) extra = MistDictField() cloud = me.ReferenceField(Cloud, required=True, reverse_delete_rule=me.CASCADE) deleted = me.DateTimeField() meta = { 'collection': 'zones', 'indexes': [ 'owner', { 'fields': ['cloud', 'zone_id', 'deleted'], 'sparse': False, 'unique': True, 'cls': False, } ], } def __init__(self, *args, **kwargs): super(Zone, self).__init__(*args, **kwargs) self.ctl = ZoneController(self) @classmethod def add(cls, owner, cloud, id='', **kwargs): """Add Zone This is a class method, meaning that it is meant to be called on the class itself and not on an instance of the class. You're not meant to be calling this directly, but on a cloud subclass instead like this: zone = Zone.add(owner=org, domain='domain.com.') Params: - owner and domain are common and required params - only provide a custom zone id if you're migrating something - kwargs will be passed to appropriate controller, in most cases these should match the extra fields of the particular zone type. """ if not kwargs['domain']: raise RequiredParameterMissingError('domain') if not cloud or not isinstance(cloud, Cloud): raise BadRequestError('cloud') if not owner or not isinstance(owner, Organization): raise BadRequestError('owner') zone = cls(owner=owner, cloud=cloud, domain=kwargs['domain']) if id: zone.id = id return zone.ctl.create_zone(**kwargs) def delete(self): super(Zone, self).delete() Tag.objects(resource_id=self.id, resource_type='zone').delete() self.owner.mapper.remove(self) if self.owned_by: self.owned_by.get_ownership_mapper(self.owner).remove(self) @property def tags(self): """Return the tags of this zone.""" return {tag.key: tag.value for tag in Tag.objects( resource_id=self.id, resource_type='zone')} def as_dict(self): """Return a dict with the model values.""" return { 'id': self.id, 'zone_id': self.zone_id, 'domain': self.domain, 'type': self.type, 'ttl': self.ttl, 'extra': self.extra, 'cloud': self.cloud.id, 'owned_by': self.owned_by.id if self.owned_by else '', 'created_by': self.created_by.id if self.created_by else '', 'records': {r.id: r.as_dict() for r in Record.objects(zone=self, deleted=None)}, 'tags': self.tags } def clean(self): """Overriding the default clean method to implement param checking""" if not self.domain.endswith('.'): self.domain += "." def __str__(self): return 'Zone %s (%s/%s) of %s' % (self.id, self.zone_id, self.domain, self.owner)
class Record(OwnershipMixin, me.Document): """This is the class definition for the Mongo Engine Document related to a DNS record. """ id = me.StringField(primary_key=True, default=lambda: uuid.uuid4().hex) record_id = me.StringField(required=True) name = me.StringField(required=True) type = me.StringField(required=True) rdata = me.ListField(required=True) extra = MistDictField() ttl = me.IntField(default=0) # This ensures that any records that are under a zone are also deleted when # we delete the zone. zone = me.ReferenceField(Zone, required=True, reverse_delete_rule=me.CASCADE) owner = me.ReferenceField('Organization', required=True, reverse_delete_rule=me.CASCADE) deleted = me.DateTimeField() meta = { 'collection': 'records', 'allow_inheritance': True, 'indexes': [ { 'fields': ['zone', 'record_id', 'deleted'], 'sparse': False, 'unique': True, 'cls': False, } ], } _record_type = None def __init__(self, *args, **kwargs): super(Record, self).__init__(*args, **kwargs) self.ctl = RecordController(self) @classmethod def add(cls, owner=None, zone=None, id='', **kwargs): """Add Record This is a class method, meaning that it is meant to be called on the class itself and not on an instance of the class. You're not meant to be calling this directly, but on a cloud subclass instead like this: record = Record.add(zone=zone, **kwargs) Params: - zone is a required param - only provide a custom record id if you're migrating something - kwargs will be passed to appropriate controller, in most cases these should match the extra fields of the particular record type. """ if not kwargs['name']: raise RequiredParameterMissingError('name') if not kwargs['data']: raise RequiredParameterMissingError('data') if not kwargs['type']: raise RequiredParameterMissingError('type') # If we were not given a zone then we need the owner to try and find # the best matching domain. if not zone and kwargs['type'] in ['A', 'AAAA', 'CNAME']: assert isinstance(owner, Organization) zone = BaseDNSController.find_best_matching_zone(owner, kwargs['name']) assert isinstance(zone, Zone) record = cls(zone=zone) if id: record.id = id return record.ctl.create_record(**kwargs) def delete(self): super(Record, self).delete() Tag.objects(resource_id=self.id, resource_type='record').delete() self.zone.owner.mapper.remove(self) if self.owned_by: self.owned_by.get_ownership_mapper(self.owner).remove(self) def clean(self): """Overriding the default clean method to implement param checking""" self.type = self._record_type if not self.owner: self.owner = self.zone.owner def __str__(self): return 'Record %s (name:%s, type:%s) of %s' % ( self.id, self.name, self.type, self.zone.domain) @property def tags(self): """Return the tags of this record.""" return {tag.key: tag.value for tag in Tag.objects(resource_id=self.id, resource_type='record')} def as_dict(self): """ Return a dict with the model values.""" return { 'id': self.id, 'record_id': self.record_id, 'name': self.name, 'type': self.type, 'rdata': self.rdata, 'ttl': self.ttl, 'extra': self.extra, 'zone': self.zone.id, 'owned_by': self.owned_by.id if self.owned_by else '', 'created_by': self.created_by.id if self.created_by else '', 'tags': self.tags }
class Network(OwnershipMixin, me.Document): """The basic Network model. This class is only meant to be used as a basic class for cloud-specific `Network` subclasses. `Network` contains all common, provider-independent fields and handlers. """ id = me.StringField(primary_key=True, default=lambda: uuid.uuid4().hex) owner = me.ReferenceField('Organization', reverse_delete_rule=me.CASCADE) cloud = me.ReferenceField(Cloud, required=True, reverse_delete_rule=me.CASCADE) network_id = me.StringField() # required=True) name = me.StringField() cidr = me.StringField() description = me.StringField() location = me.ReferenceField('CloudLocation', required=False, reverse_delete_rule=me.DENY) extra = MistDictField() # The `extra` dictionary returned by libcloud. missing_since = me.DateTimeField() meta = { 'allow_inheritance': True, 'collection': 'networks', 'indexes': [ { 'fields': ['cloud', 'network_id'], 'sparse': False, 'unique': True, 'cls': False, }, ], } def __init__(self, *args, **kwargs): super(Network, self).__init__(*args, **kwargs) # Set `ctl` attribute. self.ctl = NetworkController(self) # Calculate and store network type specific fields. self._network_specific_fields = [ field for field in type(self)._fields if field not in Network._fields ] @classmethod def add(cls, cloud, cidr=None, name='', description='', id='', **kwargs): """Add a Network. This is a class method, meaning that it is meant to be called on the class itself and not on an instance of the class. You're not meant to be calling this directly, but on a network subclass instead like this: network = AmazonNetwork.add(cloud=cloud, name='Ec2Network') :param cloud: the Cloud on which the network is going to be created. :param cidr: :param name: the name to be assigned to the new network. :param description: an optional description. :param id: a custom object id, passed in case of a migration. :param kwargs: the kwargs to be passed to the corresponding controller. """ assert isinstance(cloud, Cloud) network = cls(cloud=cloud, cidr=cidr, name=name, description=description) if id: network.id = id return network.ctl.create(**kwargs) @property def tags(self): """Return the tags of this network.""" return { tag.key: tag.value for tag in Tag.objects(resource_id=self.id, resource_type='network') } def clean(self): """Checks the CIDR to determine if it maps to a valid IPv4 network.""" if self.cidr: try: netaddr.cidr_to_glob(self.cidr) except (TypeError, netaddr.AddrFormatError) as err: raise me.ValidationError(err) self.owner = self.owner or self.cloud.owner def delete(self): super(Network, self).delete() self.owner.mapper.remove(self) Tag.objects(resource_id=self.id, resource_type='network').delete() if self.owned_by: self.owned_by.get_ownership_mapper(self.owner).remove(self) def as_dict(self): """Returns the API representation of the `Network` object.""" net_dict = { 'id': self.id, 'subnets': { s.id: s.as_dict() for s in Subnet.objects(network=self, missing_since=None) }, 'cloud': self.cloud.id, 'network_id': self.network_id, 'name': self.name, 'cidr': self.cidr, 'description': self.description, 'extra': self.extra, 'tags': self.tags, 'owned_by': self.owned_by.id if self.owned_by else '', 'created_by': self.created_by.id if self.created_by else '', 'location': self.location.id if self.location else '', } net_dict.update( {key: getattr(self, key) for key in self._network_specific_fields}) return net_dict def __str__(self): return '%s "%s" (%s)' % (self.__class__.__name__, self.name, self.id)
class Subnet(me.Document): """The basic Subnet model. This class is only meant to be used as a basic class for cloud-specific `Subnet` subclasses. `Subnet` contains all common, provider-independent fields and handlers. """ id = me.StringField(primary_key=True, default=lambda: uuid.uuid4().hex) owner = me.ReferenceField('Organization', reverse_delete_rule=me.CASCADE) network = me.ReferenceField('Network', required=True, reverse_delete_rule=me.CASCADE) subnet_id = me.StringField() name = me.StringField() cidr = me.StringField(required=True) description = me.StringField() extra = MistDictField() # The `extra` dictionary returned by libcloud. missing_since = me.DateTimeField() meta = { 'allow_inheritance': True, 'collection': 'subnets', 'indexes': [ { 'fields': ['network', 'subnet_id'], 'sparse': False, 'unique': True, 'cls': False, }, ], } def __init__(self, *args, **kwargs): super(Subnet, self).__init__(*args, **kwargs) # Set `ctl` attribute. self.ctl = SubnetController(self) # Calculate and store subnet type specific fields. self._subnet_specific_fields = [ field for field in type(self)._fields if field not in Subnet._fields ] @classmethod def add(cls, network, cidr, name='', description='', id='', **kwargs): """Add a Subnet. This is a class method, meaning that it is meant to be called on the class itself and not on an instance of the class. You're not meant to be calling this directly, but on a network subclass instead like this: subnet = AmazonSubnet.add(network=network, name='Ec2Subnet', cidr='172.31.10.0/24') :param network: the Network nn which the subnet is going to be created. :param cidr: the CIDR to be assigned to the new subnet. :param name: the name to be assigned to the new subnet. :param description: an optional description. :param id: a custom object id, passed in case of a migration. :param kwargs: the kwargs to be passed to the corresponding controller. """ assert isinstance(network, Network) if not cidr: raise RequiredParameterMissingError('cidr') subnet = cls(network=network, cidr=cidr, name=name, description=description) if id: subnet.id = id return subnet.ctl.create(**kwargs) @property def tags(self): """Return the tags of this subnet.""" return { tag.key: tag.value for tag in Tag.objects(resource_id=self.id, resource_type='subnet') } def clean(self): """Checks the CIDR to determine if it maps to a valid IPv4 network.""" self.owner = self.owner or self.network.cloud.owner try: netaddr.cidr_to_glob(self.cidr) except (TypeError, netaddr.AddrFormatError) as err: raise me.ValidationError(err) def delete(self): super(Subnet, self).delete() Tag.objects(resource_id=self.id, resource_type='subnet').delete() def as_dict(self): """Returns the API representation of the `Subnet` object.""" subnet_dict = { 'id': self.id, 'cloud': self.network.cloud.id, 'network': self.network.id, 'subnet_id': self.subnet_id, 'name': self.name, 'cidr': self.cidr, 'description': self.description, 'extra': self.extra, 'tags': self.tags, } subnet_dict.update( {key: getattr(self, key) for key in self._subnet_specific_fields}) return subnet_dict def __str__(self): return '%s "%s" (%s)' % (self.__class__.__name__, self.name, self.id)