class InstanceGroup(ndb.Model): """Datastore representation of a GCE instance group. Key: InstanceGroup is a root entity. id: Hash of the instance group name. """ # rpc_messages.Dimensions describing members of this instance group. dimensions = msgprop.MessageProperty(rpc_messages.Dimensions, required=True) # Names of members of this instance group. members = ndb.LocalStructuredProperty(Instance, repeated=True) # Name of this instance group. name = ndb.StringProperty(required=True) # rpc_messages.Policies governing members of this instance group. policies = msgprop.MessageProperty(rpc_messages.Policies, required=True) # Name of the project this instance group exists in. project = ndb.StringProperty(required=True) # Zone the members of this instance group exist in. e.g. us-central1-f. zone = ndb.StringProperty(required=True) @classmethod def generate_key(cls, name): """Generates the key for an InstanceGroup with the given name. Args: name: Name of this instance group. Returns: An ndb.Key instance. """ return ndb.Key(cls, hashlib.sha1(name).hexdigest())
class InstanceGroup(ndb.Model): """Datastore representation of a GCE instance group. Key: Instance group is a root entity. id: Hash of the instance group name. """ # rpc_messages.Dimensions describing members of this instance group. dimensions = msgprop.MessageProperty(rpc_messages.Dimensions, required=True) # Names of members of this instance group. members = ndb.StringProperty(repeated=True) # Name of this instance group. name = ndb.StringProperty(required=True) # rpc_messages.Policies governing members of this instance group. policies = msgprop.MessageProperty(rpc_messages.Policies, required=True) @classmethod def create_and_put(cls, name, dimensions, policies, members): """Creates a new InstanceGroup entity and puts it in the datastore. Args: name: Name of this instance group. dimensions: rpc_messages.Dimensions describing members of this instance group. policies: rpc_messages.Policies governing members of this instance group. members: A list of names of members of this instance group. """ assert dimensions.backend == rpc_messages.Backend.GCE cls( key=cls.generate_key(name), dimensions=dimensions, members=members, name=name, policies=policies, ).put() @classmethod def generate_key(cls, name): """Generates the key for an InstanceGroup with the given name. Args: name: Name of this instance group. Returns: An ndb.Key instance. """ return ndb.Key(cls, hashlib.sha1(name).hexdigest())
class MachineType(ndb.Model): """A type of machine which should be leased from the Machine Provider. Key: id: A human-readable name for this machine type. kind: MachineType. Is a root entity. """ # Current number of active leases. current_size = ndb.ComputedProperty(lambda self: len(self.leases)) # Description of this machine type for humans. description = ndb.StringProperty(indexed=False) # Whether or not to attempt to lease machines of this type. enabled = ndb.BooleanProperty(default=True) # Duration to lease each machine for. lease_duration_secs = ndb.IntegerProperty(indexed=False) # List of active lease requests. leases = ndb.LocalStructuredProperty(MachineLease, repeated=True) # machine_provider.Dimensions describing the machine. mp_dimensions = msgprop.MessageProperty(machine_provider.Dimensions, indexed=False) # Number of bots pending deletion. num_pending_deletion = ndb.ComputedProperty( lambda self: len(self.pending_deletion)) # List of hostnames whose leases have expired and should be deleted. pending_deletion = ndb.StringProperty(indexed=False, repeated=True) # Last request number used. request_count = ndb.IntegerProperty(default=0, required=True) # Request ID base string. request_id_base = ndb.StringProperty(indexed=False, required=True) # Target number of machines of this type to have leased at once. target_size = ndb.IntegerProperty(indexed=False, required=True)
class InstanceTemplateRevision(ndb.Model): """A specific revision of an instance template in the config. Key: id: Checksum of the instance template config. kind: InstanceTemplate. """ # List of ndb.Keys for the InstanceGroupManagers. active = ndb.KeyProperty(kind=InstanceGroupManager, repeated=True) # rpc_messages.Dimensions describing instances created from this template. dimensions = msgprop.MessageProperty(rpc_messages.Dimensions) # Disk size in GiB for instances created from this template. disk_size_gb = ndb.IntegerProperty(indexed=False) # List of ndb.Keys for drained InstanceGroupManagers. drained = ndb.KeyProperty(kind=InstanceGroupManager, repeated=True) # Name of the image for instances created from this template. image_name = ndb.StringProperty(indexed=False) # GCE machine type for instances created from this template. machine_type = ndb.StringProperty(indexed=False) # Initial metadata to apply when creating instances from this template. metadata = ndb.JsonProperty() # Project to create the instance template in. project = ndb.StringProperty(indexed=False) # List of service accounts available to instances created from this template. service_accounts = ndb.LocalStructuredProperty(ServiceAccount, repeated=True) # Initial list of tags to apply when creating instances from this template. tags = ndb.StringProperty(indexed=False, repeated=True) # URL of the instance template created from this entity. url = ndb.StringProperty(indexed=False)
class MachineLease(ndb.Model): """A lease request for a machine from the Machine Provider. Key: id: A string in the form <machine type id>-<number>. kind: MachineLease. Is a root entity. """ # Request ID used to generate this request. client_request_id = ndb.StringProperty(indexed=True) # Whether or not this MachineLease should issue lease requests. drained = ndb.BooleanProperty(indexed=True) # Number of seconds ahead of lease_expiration_ts to release leases. early_release_secs = ndb.IntegerProperty(indexed=False) # Hostname of the machine currently allocated for this request. hostname = ndb.StringProperty() # Duration to lease for. lease_duration_secs = ndb.IntegerProperty(indexed=False) # DateTime indicating lease expiration time. lease_expiration_ts = ndb.DateTimeProperty() # Lease ID assigned by Machine Provider. lease_id = ndb.StringProperty(indexed=False) # ndb.Key for the MachineType this MachineLease is created for. machine_type = ndb.KeyProperty() # machine_provider.Dimensions describing the machine. mp_dimensions = msgprop.MessageProperty(machine_provider.Dimensions, indexed=False) # Last request number used. request_count = ndb.IntegerProperty(default=0, required=True) # Task ID for the termination task scheduled for this machine. termination_task = ndb.StringProperty(indexed=False)
class CatalogEntry(ndb.Model): """Datastore representation of an entry in the catalog.""" # rpc_messages.Dimensions describing this machine. dimensions = msgprop.MessageProperty( rpc_messages.Dimensions, indexed_fields=[ field.name for field in rpc_messages.Dimensions.all_fields() ], )
class Instruction(ndb.Model): """Datastore representation of an instruction for a machine. Standalone instances should not be present in the datastore. """ # Instruction to execute. instruction = msgprop.MessageProperty(rpc_messages.Instruction) # State of the instruction. state = ndb.StringProperty(choices=InstructionStates)
class CatalogEntry(ndb.Model): """Datastore representation of an entry in the catalog.""" # rpc_messages.Dimensions describing this machine. dimensions = msgprop.MessageProperty( rpc_messages.Dimensions, indexed_fields=[ field.name for field in rpc_messages.Dimensions.all_fields() ], ) # DateTime indicating the last modified time. last_modified_ts = ndb.DateTimeProperty(auto_now=True)
class MachineType(ndb.Model): """A type of machine which should be leased from the Machine Provider. Key: id: A human-readable name for this machine type. kind: MachineType. Is a root entity. """ # Description of this machine type for humans. description = ndb.StringProperty(indexed=False) # Number of seconds ahead of lease_expiration_ts to release leases. early_release_secs = ndb.IntegerProperty(indexed=False) # Whether or not to attempt to lease machines of this type. enabled = ndb.BooleanProperty(default=True) # Duration to lease each machine for. lease_duration_secs = ndb.IntegerProperty(indexed=False) # machine_provider.Dimensions describing the machine. mp_dimensions = msgprop.MessageProperty(machine_provider.Dimensions, indexed=False) # Target number of machines of this type to have leased at once. target_size = ndb.IntegerProperty(indexed=False, required=True)
class InstanceTemplateRevision(ndb.Model): """A specific revision of an instance template in the config. Key: id: Checksum of the instance template config. parent: InstanceTemplate. """ # List of ndb.Keys for the InstanceGroupManagers. active = ndb.KeyProperty(kind=InstanceGroupManager, repeated=True) # Enable external network with automatic IP assignment. auto_assign_external_ip = ndb.BooleanProperty(indexed=False) # rpc_messages.Dimensions describing instances created from this template. dimensions = msgprop.MessageProperty(rpc_messages.Dimensions) # Disk size in GiB for instances created from this template. disk_size_gb = ndb.IntegerProperty(indexed=False) # Disk type for instances created from this template. disk_type = ndb.StringProperty(indexed=False) # List of ndb.Keys for drained InstanceGroupManagers. drained = ndb.KeyProperty(kind=InstanceGroupManager, repeated=True) # Name of the image for instances created from this template. image_name = ndb.StringProperty(indexed=False) # Project containing the image specified by image_name. image_project = ndb.StringProperty(indexed=False) # GCE machine type for instances created from this template. machine_type = ndb.StringProperty(indexed=False) # Initial metadata to apply when creating instances from this template. metadata = ndb.JsonProperty() # Minimum CPU platform for instances created from this template. min_cpu_platform = ndb.StringProperty(indexed=False) # Network URL for this template. network_url = ndb.StringProperty(indexed=False) # Project to create the instance template in. project = ndb.StringProperty(indexed=False) # List of service accounts available to instances created from this template. service_accounts = ndb.LocalStructuredProperty(ServiceAccount, repeated=True) # Initial list of tags to apply when creating instances from this template. tags = ndb.StringProperty(indexed=False, repeated=True) # URL of the instance template created from this entity. url = ndb.StringProperty(indexed=False)
class PlacesStore(ndb.Model): places = msgprop.MessageProperty( Places, indexed_fields=['places.name', 'places.latitude', 'places.longitude'])
class PersistentUser(ndb.Model): email = ndb.StringProperty() domain = ndb.StringProperty() created = ndb.DateTimeProperty(auto_now_add=True) modified = ndb.DateTimeProperty(auto_now=True) modified_by = ndb.StringProperty() created_by = ndb.StringProperty() num_folders = ndb.IntegerProperty() is_wildcard = ndb.BooleanProperty() folders = msgprop.MessageProperty(FolderMessage, repeated=True) folder_status = msgprop.EnumProperty(FolderStatus) questions = msgprop.MessageProperty(QuestionMessage, repeated=True) reason = ndb.TextProperty() def _pre_put_hook(self): if self.email: self.email = self.email.strip().lower() self.domain = self.email.split('@')[-1] self.is_wildcard = self.email[0] == '*' if self.created_by: self.created_by = self.created_by.strip().lower() if self.modified_by: self.modified_by = self.modified_by.strip().lower() if self.folders: folder_status = FolderStatus.ALL_APPROVED for folder in self.folders: if folder.has_requested: folder_status = FolderStatus.SOME_REQUESTED self.folder_status = folder_status self.num_folders = len( [folder for folder in self.folders if folder.has_access]) @classmethod def search(cls, query_string=None, cursor=None, limit=None): limit = limit or 200 start_cursor = datastore_query.Cursor(urlsafe=cursor) \ if cursor else None query = cls.query() if query_string: # TODO: Support query language. query = query.filter(cls.email == query_string) query = query.order(-cls.created) results, next_cursor, has_more = \ query.fetch_page(limit, start_cursor=start_cursor) return (results, next_cursor, has_more) @classmethod def normalize_email(cls, email): return email.strip().lower().replace(' ', '') def can_read(self, path_from_url): # When FOLDERS have changed after the user was added. # This should be moved to when PersistentUser is instantiated. self.folders = self.normalize_folders() for folder in self.folders: path_regex = folder.regex if re.match(path_regex, path_from_url): if not folder.has_access: return False return True def normalize_folders(self): ids_to_folders = {} all_folders = list_folder_messages() for folder in all_folders: ids_to_folders[folder.folder_id] = folder for folder in self.folders: # Old folder no longer used. if folder.folder_id not in ids_to_folders: continue ids_to_folders[folder.folder_id].has_access = folder.has_access ids_to_folders[ folder.folder_id].has_requested = folder.has_requested all_folders = ids_to_folders.values() return sorted(all_folders, key=lambda folder: folder.title) @classmethod def import_from_sheets(cls, sheet_id, sheet_gid, folders=None, created_by=None): rows = google_sheets.get_sheet(sheet_id, gid=sheet_gid) emails = [row['email'] for row in rows] if not folders: folders = list_folder_messages(default_has_access=True) return cls.create_or_update_multi(emails, folders=folders, created_by=created_by) @classmethod def to_csv(cls): # TODO: Move to separate file. from protorpc import protojson import io import csv import json _csv_header = [ 'created', 'email', 'folders', ] header = _csv_header ents, _, _ = cls.search(limit=5000) rows = [] for ent in ents: row = json.loads(protojson.encode_message(ent.to_message())) for key in row.keys(): if key not in header: del row[key] for key in row: if key == 'folders': row[key] = json.dumps(row[key]) if isinstance(row[key], unicode): row[key] = row[key].encode('utf-8') rows.append(row) if not rows: return '' fp = io.BytesIO() writer = csv.DictWriter(fp, header) writer.writeheader() writer.writerows(rows) fp.seek(0) return fp.read() @classmethod def create(cls, email, folders=None, created_by=None): user = cls._create(email, folders, created_by) user.put() return user @classmethod def _create(cls, email, folders=None, created_by=None): email = cls.normalize_email(email) key = ndb.Key('PersistentUser', email) user = cls(key=key) user.email = email if folders: user.folders = folders else: user.folders = list_folder_messages() user.created_by = created_by user.modified_by = created_by return user def add_folders(self, folders): ids_to_folders = {} for folder in self.normalize_folders(): ids_to_folders[folder.folder_id] = folder for folder in folders: if folder.has_access and folder.folder_id in ids_to_folders: ids_to_folders[folder.folder_id].has_access = True all_folders = ids_to_folders.values() all_folders = sorted(all_folders, key=lambda folder: folder.title) self.folders = all_folders @classmethod def create_or_update_multi(cls, emails, folders=None, created_by=None): keys = [ ndb.Key('PersistentUser', cls.normalize_email(email)) for email in emails if email ] ents = ndb.get_multi(keys) for i, ent in enumerate(ents): if not ent: ents[i] = cls._create(emails[i], folders=folders, created_by=created_by) continue ent.add_folders(folders) ndb.put_multi(ents) return ents @classmethod def create_multi(cls, emails, folders=None, created_by=None): ents = [ cls._create(email, folders=folders, created_by=created_by) for email in emails ] ndb.put_multi(ents) return ents @classmethod def get(cls, email): email = cls.normalize_email(email) key = ndb.Key('PersistentUser', email) return key.get() @classmethod def get_by_email(cls, email): email = cls.normalize_email(email) key = ndb.Key('PersistentUser', email) return key.get() @classmethod def get_or_create(cls, email): ent = cls.get(email) return ent or cls.create(email) def delete(self): self.key.delete() def to_message(self): message = UserMessage() message.created_by = self.created_by message.modified_by = self.modified_by message.created = self.created message.domain = self.domain message.email = self.email if self.folders: message.folders = self.normalize_folders() message.folder_status = self.folder_status message.modified = self.modified message.num_folders = self.num_folders if self.questions: message.questions = self.questions if self.reason: message.reason = self.reason return message def request_access(self, folders, questions=None, reason=None, send_notification=False): if questions: self.questions = questions if reason: self.reason = reason all_folders = self.normalize_folders() requested_folder_ids = [folder.folder_id for folder in folders] for i, folder in enumerate(all_folders): is_requested = folder.folder_id in requested_folder_ids # Set newly-requested folders while leaving previously requested # folders as-is. if is_requested: all_folders[i].has_requested = is_requested self.folders = all_folders self.put() if send_notification: build_server_config = config.instance() email_config = build_server_config['access_requests']['emails'] req = { 'email': self.email, 'form': questions, } from . import access_requests access_requests.send_email_to_admins(req, email_config=email_config) def update_folders(self, folders, updated_by=None): self.folders = folders if updated_by: self.updated_by = updated_by self.put()
class Game(ndb.Model): """ Google AppEngine Datastore Entity representing a Battleship match. Properties: player_one: ndb Key to the player that hosted the game. player_two: ndb Key to the player that joined the game. game_state: GameState representing the state of the game. game_settings: The BoardRules set for the game at creation. game_board: JsonProperty that holds the players' ship positions. A dictionary in the form: { 'player_one': [{ship list}] 'player_two': [{ship list}] } ship_list: A list of all the ships in the players fleet. A ship is a list of string representations of each coordinate that the ship occupies. game_history: JsonProperty that holds the history of players' guesses. A python list of each guess in the form: [{user_name},{coords},{result}] user_name: string of the name of user who made guess coords: string in form 'x,y' of user's guess result: string result of the guess. Typically hit or miss. player_winner: ndb Key to the winner of the match. last_update: A datetime of the last time the game was updated. """ class GameState(messages.Enum): """ Enum for representing the different states of the game. """ # Game has just been created and is waiting for second player. WAITING_FOR_OPPONENT = 0 # Second player has just joined the game. Waiting for ship placements. PREPARING_BOARD = 1 # Waiting for player one to guess. PLAYER_ONE_TURN = 2 # Waiting for player two to guess. PLAYER_TWO_TURN = 3 # A player has sunk all their opponent's ships. GAME_COMPLETE = 4 # A player has cancelled the game. GAME_CANCELLED = 5 class BoardRules(messages.Message): """ Message that describes the rules of a game. Properties: width: The width of the game board. Must be between 8-20. height: The height of the game board. Must be between 8-20. ship_2: The number of ships of length 2. Must be between 0-5. ship_3: The number of ships of length 3. Must be between 0-5. ship_4: The number of ships of length 4. Must be between 0-5. ship_5: The number of ships of length 5. Must be between 0-5. """ width = messages.IntegerField(1, default=10) height = messages.IntegerField(2, default=10) ship_2 = messages.IntegerField(3, default=1) ship_3 = messages.IntegerField(4, default=2) ship_4 = messages.IntegerField(5, default=1) ship_5 = messages.IntegerField(6, default=1) player_one = ndb.KeyProperty(required=True, kind='User') player_two = ndb.KeyProperty(kind='User') game_state = msgprop.EnumProperty(GameState, required=True) game_settings = msgprop.MessageProperty(BoardRules, required=True) game_board = ndb.JsonProperty() game_history = ndb.JsonProperty() player_winner = ndb.KeyProperty(kind='User') last_update = ndb.DateTimeProperty(auto_now=True) @classmethod def create_game(cls, user, form): """ Creates a new Game. Args: user: User that is creating the game form: NewGameForm containing the game's rules Returns: Returns the newly created Game. """ settings = form.get_assigned_value('rules') or cls.BoardRules() # Fix an issue with default values not saving until assigned. settings.width = settings.width settings.height = settings.height settings.ship_2 = settings.ship_2 settings.ship_3 = settings.ship_3 settings.ship_4 = settings.ship_4 settings.ship_5 = settings.ship_5 # Check that rules are valid if (settings.width < 8 or settings.width > 20 or settings.height < 8 or settings.height > 20): raise endpoints.BadRequestException( 'Board dimensions must be between 8-20') if (settings.ship_2 < 0 or settings.ship_2 > 5 or settings.ship_3 < 0 or settings.ship_3 > 5 or settings.ship_4 < 0 or settings.ship_4 > 5 or settings.ship_5 < 0 or settings.ship_5 > 5): raise endpoints.BadRequestException( 'Ship count must be between 0-5') game = Game(player_one=user.key, game_state=cls.GameState.WAITING_FOR_OPPONENT, game_settings=settings, game_board={}, game_history=[]) game.put() return game @classmethod def by_urlsafe(cls, urlsafe): """ Search for a game by its urlsafe key """ try: return ndb.Key(urlsafe=urlsafe).get() except TypeError: raise endpoints.BadRequestException('Invalid Key') except Exception, e: if e.__class__.__name__ == 'ProtocolBufferDecodeError': raise endpoints.BadRequestException('Invalid Key') else: raise
class LeaseRequest(ndb.Model): """Datastore representation of a LeaseRequest. Key: id: Hash of the client + client-generated request ID which issued the original rpc_messages.LeaseRequest instance. Used for easy deduplication. kind: LeaseRequest. This root entity does not reference any parents. """ # DateTime indicating original datastore write time. created_ts = ndb.DateTimeProperty(auto_now_add=True) # Checksum of the rpc_messages.LeaseRequest instance. Used to compare incoming # LeaseRequets for deduplication. deduplication_checksum = ndb.StringProperty(required=True, indexed=False) # ID of the CatalogMachineEntry provided for this lease. machine_id = ndb.StringProperty() # auth.model.Identity of the issuer of the original request. owner = auth.IdentityProperty(required=True) # Element of LeaseRequestStates giving the state of this request. state = ndb.StringProperty(choices=LeaseRequestStates, required=True) # rpc_messages.LeaseRequest instance representing the original request. request = msgprop.MessageProperty(rpc_messages.LeaseRequest, required=True) # rpc_messages.LeaseResponse instance representing the current response. # This field will be updated as the request is being processed. response = msgprop.MessageProperty(rpc_messages.LeaseResponse) @classmethod def compute_deduplication_checksum(cls, request): """Computes the deduplication checksum for the given request. Args: request: The rpc_messages.LeaseRequest instance to deduplicate. Returns: The deduplication checksum. """ return hashlib.sha1(protobuf.encode_message(request)).hexdigest() @classmethod def generate_key(cls, user, request): """Generates the key for the given request initiated by the given user. Args: user: An auth.model.Identity instance representing the requester. request: The rpc_messages.LeaseRequest sent by the user. Returns: An ndb.Key instance. """ # Enforces per-user request ID uniqueness return ndb.Key( cls, hashlib.sha1('%s\0%s' % (user, request.request_id)).hexdigest(), ) @classmethod def query_untriaged(cls): """Queries for untriaged LeaseRequests. Yields: Untriaged LeaseRequests in no guaranteed order. """ for request in cls.query(cls.state == LeaseRequestStates.UNTRIAGED): yield request
class CatalogMachineEntry(CatalogEntry): """Datastore representation of a machine in the catalog. Key: id: Hash of the backend + hostname dimensions. Used to enforce per-backend hostname uniqueness. kind: CatalogMachineEntry. This root entity does not reference any parents. """ # ID of the LeaseRequest this machine is provided for. lease_id = ndb.StringProperty() # DateTime indicating lease expiration time. lease_expiration_ts = ndb.DateTimeProperty() # rpc_messages.Policies governing this machine. policies = msgprop.MessageProperty(rpc_messages.Policies) # Element of CatalogMachineEntryStates giving the state of this entry. state = ndb.StringProperty( choices=CatalogMachineEntryStates, default=CatalogMachineEntryStates.AVAILABLE, indexed=True, required=True, ) @classmethod def create_and_put(cls, dimensions, policies, state): """Creates a new CatalogEntry entity and puts it in the datastore. Args: dimensions: rpc_messages.Dimensions describing this machine. policies: rpc_messages.Policies governing this machine. state: Element of CatalogMachineEntryState describing this machine. """ cls( dimensions=dimensions, policies=policies, state=state, key=cls.generate_key(dimensions), ).put() @classmethod def generate_key(cls, dimensions): """Generates the key for a CatalogEntry with the given dimensions. Args: dimensions: rpc_messages.Dimensions describing this machine. Returns: An ndb.Key instance. """ # Enforces per-backend hostname uniqueness. assert dimensions.backend is not None assert dimensions.hostname is not None return ndb.Key( cls, hashlib.sha1( '%s\0%s' % (dimensions.backend, dimensions.hostname)).hexdigest(), ) @classmethod def query_available(cls, *filters): """Queries for available machines. Args: *filters: Any additional filters to include in the query. Yields: CatalogMachineEntry keys in no guaranteed order. """ available = cls.state == CatalogMachineEntryStates.AVAILABLE for machine in cls.query(available, *filters).fetch(keys_only=True): yield machine
class Leaderboard(ndb.Model): leaderboard = msgprop.MessageProperty(Leaderboard_m)
class NoteStore(ndb.Model): note = msgprop.MessageProperty(Note, indexed_fields=['when']) name = ndb.StringProperty()
class CatalogMachineEntry(CatalogEntry): """Datastore representation of a machine in the catalog. Key: id: Hash of the backend + hostname dimensions. Used to enforce per-backend hostname uniqueness. kind: CatalogMachineEntry. This root entity does not reference any parents. """ # Instruction for this machine. instruction = ndb.LocalStructuredProperty(Instruction) # ID of the LeaseRequest this machine is provided for. lease_id = ndb.StringProperty() # DateTime indicating lease expiration time. lease_expiration_ts = ndb.DateTimeProperty() # rpc_messages.Policies governing this machine. policies = msgprop.MessageProperty(rpc_messages.Policies) # Determines sorted order relative to other CatalogMachineEntries. sort_ordering = ndb.ComputedProperty(lambda self: '%s:%s' % ( self.dimensions.backend, self.dimensions.hostname)) # Element of CatalogMachineEntryStates giving the state of this entry. state = ndb.StringProperty( choices=CatalogMachineEntryStates, default=CatalogMachineEntryStates.AVAILABLE, indexed=True, required=True, ) @classmethod def generate_key(cls, dimensions): """Generates the key for a CatalogEntry with the given dimensions. Args: dimensions: rpc_messages.Dimensions describing this machine. Returns: An ndb.Key instance. """ # Enforces per-backend hostname uniqueness. assert dimensions.backend is not None assert dimensions.hostname is not None return cls._generate_key(dimensions.backend, dimensions.hostname) @classmethod def _generate_key(cls, backend, hostname): """Generates the key for a CatalogEntry with the given backend and hostname. Args: backend: rpc_messages.Backend. hostname: Hostname of the machine. Returns: An ndb.Key instance. """ return ndb.Key( cls, hashlib.sha1('%s\0%s' % (backend, hostname)).hexdigest(), ) @classmethod def get(cls, backend, hostname): """Gets the CatalogEntry with by backend and hostname. Args: backend: rpc_messages.Backend. hostname: Hostname of the machine. Returns: An ndb.Key instance. """ return cls._generate_key(backend, hostname).get() @classmethod def query_available(cls, *filters): """Queries for available machines. Args: *filters: Any additional filters to include in the query. Yields: CatalogMachineEntry keys in no guaranteed order. """ available = cls.state == CatalogMachineEntryStates.AVAILABLE for machine in cls.query(available, *filters).fetch(keys_only=True): yield machine
class SignedStorableNotebook(ndb.Model): author = ndb.StringProperty() nb = msgprop.MessageProperty(Notebook, indexed_fields=['notes.text', 'notes.when'])