def get(self, request): """Handles an incoming CatalogMachineRetrievalRequest.""" user = auth.get_current_identity().to_bytes() logging.info( 'Received CatalogMachineRetrievalRequest:\nUser: %s\n%s', user, request, ) if acl.is_catalog_admin(): if not request.backend: raise endpoints.BadRequestException( 'Backend unspecified by administrator') elif acl.is_backend_service(): current_backend = acl.get_current_backend() if request.backend is None: request.backend = current_backend if request.backend != current_backend: raise endpoints.ForbiddenException('Mismatched backend') entry = models.CatalogMachineEntry.get(request.backend, request.hostname) if not entry: raise endpoints.NotFoundException('CatalogMachineEntry not found') response = rpc_messages.CatalogMachineRetrievalResponse( dimensions=entry.dimensions, policies=entry.policies, state=entry.state, ) if entry.lease_expiration_ts: # datetime_to_timestamp returns microseconds, convert to seconds. response.lease_expiration_ts = utils.datetime_to_timestamp( entry.lease_expiration_ts) / 1000 / 1000 return response
def check_backend(request): """Checks that the given catalog manipulation request specifies a backend. Returns: rpc_messages.CatalogManipulationRequestError.UNSPECIFIED_BACKEND if the backend is unspecified and can't be inferred, otherwise None. """ if acl.is_catalog_admin(): # Catalog administrators may update any CatalogEntry, but the backend must # be specified because hostname uniqueness is enforced per-backend. if not request.dimensions.backend: logging.warning('Backend unspecified by administrator') return rpc_messages.CatalogManipulationRequestError.UNSPECIFIED_BACKEND elif acl.is_backend_service(): # Backends may only update their own machines. current_backend = acl.get_current_backend() if request.dimensions.backend is None: request.dimensions.backend = current_backend if request.dimensions.backend != current_backend: logging.warning('Mismatched backend') return rpc_messages.CatalogManipulationRequestError.MISMATCHED_BACKEND
def poll(self, request): """Handles an incoming PollRequest.""" user = auth.get_current_identity() if not request.backend: if acl.is_backend_service(): # Backends may omit this field to mean backend == self. request.backend = acl.get_current_backend() else: # Anyone else omitting the backend field is an error. raise endpoints.BadRequestException('Backend unspecified') entry = models.CatalogMachineEntry.get(request.backend, request.hostname) if not entry: raise endpoints.NotFoundException('CatalogMachineEntry not found') # Determine authorization. User must be the known service account for the # machine, the backend service that owns the machine, or an administrator. machine_service_account = None if entry.policies: machine_service_account = entry.policies.machine_service_account if user.name != machine_service_account: if entry.dimensions.backend != acl.get_current_backend(): if not acl.is_catalog_admin(): # It's found, but raise 404 so we don't reveal the existence of # machines to unauthorized users. logging.warning( 'Unauthorized poll\nUser: %s\nMachine: %s', user.to_bytes(), entry, ) raise endpoints.NotFoundException( 'CatalogMachineEntry not found') # Authorized request, return the current instruction for the machine. if not entry.lease_id or not entry.instruction: return rpc_messages.PollResponse() # The cron job which processes expired leases may not have run yet. Check # the lease expiration time to make sure this lease is still active. if entry.lease_expiration_ts <= utils.utcnow(): return rpc_messages.PollResponse() # The cron job which processes early lease releases may not have run yet. # Check the LeaseRequest to make sure this lease is still active. lease = models.LeaseRequest.get_by_id(entry.lease_id) if not lease or lease.released: return rpc_messages.PollResponse() # Only the machine itself polling for its own instructions causes # the state of the instruction to be updated. Only update the state # if it's PENDING. if entry.instruction.state == models.InstructionStates.PENDING: if user.name == machine_service_account: self._update_instruction_state( entry.key, models.InstructionStates.RECEIVED) return rpc_messages.PollResponse( instruction=entry.instruction.instruction, state=entry.instruction.state, )