async def adapt_cluster(request): user = request["user"] cluster_name = request.match_info["cluster_name"] backend = request.app["backend"] cluster = await backend.get_cluster(cluster_name) if cluster is None: raise web.HTTPNotFound(reason=f"Cluster {cluster_name} not found") if not user.has_permissions(cluster): raise web.HTTPForbidden( reason= f"User {user.name} lacks permissions to view cluster {cluster_name}" ) msg = await request.json() minimum = msg.get("minimum", None) maximum = msg.get("maximum", None) active = msg.get("active", True) max_workers = cluster.config.get("cluster_max_workers") resp_msg = None if max_workers is not None: if maximum is None: maximum = max_workers if minimum is None: minimum = 0 if maximum > max_workers or minimum > max_workers: orig_max = maximum orig_min = minimum maximum = min(max_workers, maximum) minimum = min(max_workers, minimum) resp_msg = ( f"Adapt with `maximum={orig_max}, minimum={orig_min}` workers " f"would exceed resource limit of {max_workers} workers. Using " f"`maximum={maximum}, minimum={minimum}` instead.") try: await backend.forward_message_to_scheduler( cluster, { "op": "adapt", "minimum": minimum, "maximum": maximum, "active": active }, ) except PublicException as exc: raise web.HTTPConflict(reason=str(exc)) return web.json_response({"ok": not resp_msg, "msg": resp_msg})
async def create(cls, identity, age=None): if await cls._exists(identity): # Anti brute force must be employed to disable this exploitation raise web.HTTPConflict( text='Identity conflicts, please try again!') if False: raise web.HTTPInsufficientStorage( text='Disk is full, please contact the admin! Thanks.') folder = Folder(identity, age) folder_key, msg_key = cls._keys(identity) # we might save a Folder object as a hashmap (i.e., a dict) # but all field values are strings # save as a json object is more convenient await redis.set(folder_key, folder.serialize()) log.info('Create a new folder {}'.format(identity)) return True
async def upload_resource(request: web.Request) -> web.Response: """Upload resource chunk. Read resource metadata and save another chunk to the resource. If this is a final chunk, move resource to original file name and remove resource metadata. """ # Ensure resource metadata is readable and resource file exists as well resource = get_resource_or_410(request) # Ensure resource offset equals to expected upload offset upload_offset = int( request.headers.get(constants.HEADER_UPLOAD_OFFSET) or 0) if upload_offset != resource.offset: raise web.HTTPConflict(headers=constants.BASE_HEADERS) # Save current chunk to the resource config = get_config(request) match_info = request.match_info resource.save(config=config, match_info=match_info, chunk=await request.read()) # If this is a final chunk - complete upload chunk_size = int(request.headers.get(constants.HEADER_CONTENT_LENGTH) or 0) next_offset = resource.offset + chunk_size if next_offset == resource.file_size: file_path = resource.complete(config=config, match_info=match_info) await on_upload_done(request=request, config=config, resource=resource, file_path=file_path) # But if it is not - store new metadata else: next_resource = attr.evolve(resource, offset=next_offset) next_resource.save_metadata(config=config, match_info=match_info) # Return upload headers return web.Response( status=204, headers={ **constants.BASE_HEADERS, constants.HEADER_TUS_TEMP_FILENAME: resource.uid, constants.HEADER_UPLOAD_OFFSET: str(next_offset), }, )
def IDputClasslist(self, data, request): """Accept classlist upload. Only "manager" can perform this action. The classlist should be provided as a ordered list of (str, str) pairs where each pair is (student ID, student name). Side effects on the server test spec file: * If numberToName and/or numberToProduce are -1, values are set based on this classlist (spec is permanently altered)/ * If numberToName < 0 but numberToProduce is too small for the result, respond with HTTPNotAcceptable. Returns: aiohttp.web_response.Response: Success or failure. Can be: 200: success 400: authentication problem. HTTPBadRequest: not manager, or malformed request. HTTPConflict: we already have a classlist. TODO: would be nice to be able to "try again". HTTPNotAcceptable: classlist too short (see above). """ if not data["user"] == "manager": raise web.HTTPBadRequest(reason="Not manager") if os.path.isfile(Path(specdir) / "classlist.csv"): raise web.HTTPConflict(reason="we already have a classlist") classlist = data["classlist"] # TODO should we make copy until sure it passes verification? spec = self.server.testSpec if spec.numberToName < 0 or spec.numberToProduce < 0: if spec.number_to_name < 0: spec.set_number_papers_to_name(len(classlist)) if spec.number_to_produce < 0: spec.set_number_papers_add_spares(len(classlist)) try: spec.verifySpec(verbose="log") except ValueError as e: raise web.HTTPNotAcceptable(reason=str(e)) spec.saveVerifiedSpec() with open(Path(specdir) / "classlist.csv", "w") as csvfile: writer = csv.writer(csvfile) writer.writerow(["id", "studentName"]) writer.writerows(classlist) return web.Response()
async def delete_certificate(request): """ Delete a certificate :Example: curl -X DELETE http://localhost:8081/foglamp/certificate/foglamp """ cert_name = request.match_info.get('name', None) certs_dir = _get_certs_dir() cert_file = certs_dir + '/{}.cert'.format(cert_name) key_file = certs_dir + '/{}.key'.format(cert_name) if not os.path.isfile(cert_file) and not os.path.isfile(key_file): raise web.HTTPNotFound( reason='Certificate with name {} does not exist'.format(cert_name)) # read config # if cert_name is currently set for 'certificateName' in config for 'rest_api' cf_mgr = ConfigurationManager(connect.get_storage_async()) result = await cf_mgr.get_category_item(category_name='rest_api', item_name='certificateName') if cert_name == result['value']: raise web.HTTPConflict( reason= 'Certificate with name {} is already in use, you can not delete'. format(cert_name)) msg = '' cert_file_found_and_removed = False if os.path.isfile(cert_file): os.remove(cert_file) msg = "{}.cert has been deleted successfully".format(cert_name) cert_file_found_and_removed = True key_file_found_and_removed = False if os.path.isfile(key_file): os.remove(key_file) msg = "{}.key has been deleted successfully".format(cert_name) key_file_found_and_removed = True if key_file_found_and_removed and cert_file_found_and_removed: msg = "{}.key, {}.cert have been deleted successfully".format( cert_name, cert_name) return web.json_response({'result': msg})
async def post(self): user = self.request.get("user") if user: data = await self.request.post() c = models.Chat() chat, err = c.Create(creator=user, name=data.get('name', ''), raw_members=data.getall('members', [])) if err == None: response = await chat.to_json() return web.json_response(response, status=201) else: if err == exceptions.ChatWithMembersAlreadyExists: return web.HTTPConflict() else: return web.Response(text="422: %s" % str(err), status=422) return web.HTTPUnauthorized()
async def post(self) -> web.Response: LOG.info('Creating new user') try: new_user = user.User.parse_obj(await self.request.json()) await user.save( db_pool=self.request.config_dict['db_pool'], name=new_user.name, password=new_user.password, role=new_user.role, ) except pd.ValidationError: LOG.exception('Invalid new user request body') raise web.HTTPBadRequest(text='Invalid new user') except asyncpg.exceptions.UniqueViolationError: LOG.exception(f'Attempted to duplicate user {new_user.name}') raise web.HTTPConflict(text=f'User {new_user.name} already exists') return web.json_response(status=201, data=new_user.dict())
async def get(self: web.View) -> Response: await check_permission(self.request, Permissions.READ) source_id = int(self.request.match_info["id"]) async with db.transaction(): source = await Source.get(source_id) source_model = self.schema.from_orm(source) source_config = json.loads(source_model.config_json) table_fullname = source_config["table_name"] if not table_fullname: raise web.HTTPConflict( text="table_name not defined in source config") source_executor: AbstractSource = LIVE_SOURCES[source_model.typename] availability_intervals = await source_executor.list_availability_intervals( interval=3600, table_fullname=table_fullname) source_model.available_intervals = [] for res in availability_intervals: source_model.available_intervals.append([res[0], res[1]]) return web.json_response(body=source_model.json())
async def get(self: web.View) -> Response: await check_permission(self.request, Permissions.READ) source_id = int(self.request.match_info["id"]) async with db.transaction(): source = await Source.get(source_id) source_model = self.schema.from_orm(source) source_config = json.loads(source_model.config_json) table_fullname = source_config["table_name"] if not table_fullname: raise web.HTTPConflict( text="table_name not defined in source config") source_executor: AbstractSource = LIVE_SOURCES[source_model.typename] res = await source_executor.list_data_in_interval( table_fullname=table_fullname, start=self.request.query.get("start"), end=self.request.query.get("end"), ) source_model.data = res return web.json_response(body=source_model.json())
def begin_job(jobdesc): global updater_process if updater_process is not None: if updater_process.poll() is not None: # Update complete updater_process = None else: error_body = { "error": "update_already_running", "pid": updater_process.pid } raise web.HTTPConflict(body=error_body) job_id = str(uuid.uuid1()) (fwfd, fwfilepath) = mkstemp() if not os.path.exists(UPDATE_JOB_DIR): os.makedirs(UPDATE_JOB_DIR) statusfilepath = os.path.join(UPDATE_JOB_DIR, str(job_id) + ".json") status = {"pid": 0, "state": "fetching"} with open(statusfilepath, "wb") as sfh: sfh.write(json.dumps(status)) try: fwdata = urllib.request.urlopen(jobdesc["fw_url"]) with os.fdopen(fwfd, "wb") as fwfile: fwfile.write(fwdata.read()) fwfile.flush() except: os.remove(statusfilepath) raise updater = UPDATERS[jobdesc.get("updater", "delta")] updater_process = Popen([ updater, "--addr", str(jobdesc["address"]), "--statusfile", statusfilepath, "--rmfwfile", fwfilepath, ]) status = {"pid": updater_process.pid, "state": "starting"} with open(statusfilepath, "wb") as sfh: sfh.write(json.dumps(status)) return {"job_id": job_id, "pid": updater_process.pid}
async def create_user(request: web.Request, body) -> web.Response: user_table = get_model_by_name('user') login = body['login'] exist = await request.app['pg'].fetchval( select([exists().where(user_table.c.login == login)])) if exist: return web.HTTPConflict(body=json.dumps( {'error': f'User with login "{login}" already exist'}), content_type='application/json') data = await request.app['pg'].fetchrow( user_table.insert().values(**body).returning(literal_column('*'))) body['user_id'] = data['user_id'] del body['password'] return web.Response(status=201, content_type='application/json', body=json.dumps(body))
async def _update(session, **kwargs): if "downloadRateLimit" in kwargs: download_rate_limit = int(kwargs["downloadRateLimit"]) session.set_download_rate_limit(download_rate_limit) if "uploadRateLimit" in kwargs: upload_rate_limit = int(kwargs["uploadRateLimit"]) session.set_upload_rate_limit(upload_rate_limit) if "connectionsLimit" in kwargs: connections_limit = int(kwargs["connectionsLimit"]) session.set_connections_limit(connections_limit) if "paused" in kwargs: paused = bool(kwargs["paused"]) if paused and not session.paused: await session.pause() elif not paused and session.paused: await session.resume() else: raise web.HTTPConflict() return _to_dict(session)
async def post(self): # get the payload payload = await self.payload with suppress(AttributeError): # allow custom schema method to modify the payload before validation payload = await self.schema.procure_payload(self.request, payload) cleaned_payload = await self._validate_singular_payload(payload=payload ) cleaned_payload = self._clean_write_payload(cleaned_payload) if hasattr(self.schema, 'before_post'): cleaned_payload = await self.schema.before_post( weakref.proxy(self), self.request, cleaned_payload) or cleaned_payload insert_query = self._render_insert_query(cleaned_payload) async with self.request.app.db_pool.acquire() as conn: async with conn.cursor() as cur: await self.request.app.commons.execute(cur, insert_query, cleaned_payload) res = await cur.fetchone() if res is None: if self.request.session: # Most likely a cross workspace insert or non-existent FK raise web.HTTPConflict( reason= 'Illegal cross workspace insert or non-existent FK supplied' ) self.request.app.access_logger = self.request.app.access_logger.bind( msg_context={'query': cur.query.decode()}) raise post_exceptions.CreateFailed() if hasattr(self.schema, 'after_post'): await spawn( self.request, self.schema.after_post(self.request, cleaned_payload, res[0])) return json_response({self.pk_column_name: res[0]})
async def get_mediation_record_by_connection_id( backchannel: "AcaPyAgentBackchannel", connection_id: str ): (_, resp_text) = await backchannel.admin_GET( "/mediation/requests", params={"conn_id": connection_id} ) resp_json = json.loads(resp_text) if len(resp_json["results"]) == 0: raise web.HTTPNotFound( reason=f"No mediation record found for connection with id {connection_id}" ) # Should not be possible. ACA-Py only allows one mediation record set-up per connection if len(resp_json["results"]) > 1: raise web.HTTPConflict( reason=f"Multiple mediation records found for connection with id {connection_id}" ) return resp_json["results"][0]
async def register_service(request, db_pool): """Register a new service at host.""" LOG.debug('Register new service.') # Get POST request body JSON as python dict service = await request.json() # Parse query params from path, `_` denotes service_id from path param /services/{service_id}, which is not used here _, params = await query_params(request) # Response object response = {'message': '', 'beaconServiceKey': ''} if 'remote' in params: # This option is used at Aggregators when they do a remote registration at a Registry LOG.debug(f'Remote registration request to {params["remote"]}.') # Register self (aggregator) at remote service (registry) via self, not manually service_key = await remote_registration(db_pool, request, params['remote']) response = { 'message': f'Service has been registered remotely at {params["remote"]}. Service key for updating and deleting registration included in this response, keep it safe.', 'beaconServiceKey': service_key } else: LOG.debug('Local registration at host.') # Take connection from database pool, re-use connection for all tasks async with db_pool.acquire() as connection: # Check that the chosen service ID is not taken id_taken = await db_check_service_id(connection, service['id']) if id_taken: raise web.HTTPConflict(text='Service ID is taken.') # Register service to host service_key = await db_register_service(connection, service) response = { 'message': 'Service has been registered. Service key for updating and deleting registration included in this response, keep it safe.', 'beaconServiceKey': service_key } return response
async def python_eval( req: web.Request) -> Union[Dict[str, Any], web.StreamResponse]: if not req.config_dict["args"].with_eval: raise web.HTTPForbidden(reason="Python eval page not ebabled") if req.app.get("eval-session", None) is not None: raise web.HTTPConflict(reason="Eval session already launched") ws_current = web.WebSocketResponse() ws_ready = ws_current.can_prepare(req) if not ws_ready.ok: return {} await ws_current.prepare(req) req.app["eval-session"] = ws_current async for msg in ws_current: if msg.type == aiohttp.WSMsgType.text: server_log.warn( f"Evaluating code from python eval page: {msg.data}") try: stdout, tb, returned = await eval_code(msg.data, req) except CompilationError as e: await ws_current.send_json({ "action": "eval_compilation_error", "text": str(e) }) continue await ws_current.send_json({ "action": "eval_result", "stdout": stdout, "traceback": tb, "returned": str(returned), }) req.app["eval-session"] = None return ws_current
def IDputClasslist(self, data, request): """Accept classlist upload. Only "manager" can perform this action. Responds with status success, HTTPBadRequest or HTTPConflict. The classlist should be provided as a ordered list of (str, str) pairs where each pair is (student ID, student name). Returns: aiohttp.web_response.Response: A response indicating whether the operation was a failure or a success. """ if not data["user"] == "manager": raise web.HTTPBadRequest(reason="Not manager") classlist = data["classlist"] if os.path.isfile(Path(specdir) / "classlist.csv"): raise web.HTTPConflict(reason="we already have a classlist") with open(Path(specdir) / "classlist.csv", "w") as csvfile: writer = csv.writer(csvfile) writer.writerow(["id", "studentName"]) writer.writerows(classlist) return web.Response()
async def post(body: typing.Dict[str, typing.Any], db_pool: asyncpg.pool.Pool) -> web.Response: LOG.info('Received request to create new user') user_id = str(uuid.uuid4()) async with db_pool.acquire() as conn: try: database_user = await conn.fetchrow( 'SELECT id FROM users WHERE user_name = $1 ', body['user_name']) if database_user: raise web.HTTPConflict( body=ujson.dumps({'message': 'User already exists'})) await conn.execute( 'INSERT INTO users(id, user_name, first_name, last_name, email, address, postal_code)' 'VALUES ($1, $2, $3, $4, $5, $6, $7)', user_id, body['user_name'], body['first_name'], body['last_name'], body['email'], body.get('address', ''), body.get('postal_code', ''), ) except asyncpg.PostgresError: LOG.exception('Failed to add user') raise web.HTTPInternalServerError( body=ujson.dumps({'message': 'Failed to create new user'})) return web.json_response( data=user.User( id=user_id, userName=body['user_name'], firstName=body['first_name'], lastName=body['last_name'], email=body['email'], address=body.get('address'), postalCode=body.get('postal_code'), ), dumps=user.User.dumps, )
async def worker_register(request: web.Request) -> web.Response: """For worker to register itself request headers: * X-DISTRIBUTED-WORKER-NAME: worker's name, worker should been registered already. required responses: * 200 OK: successful * 400 Bad Request: missing worker name header * 409 Conflict: worker has already registered """ worker_name = _get_worker_name(request) master = request.app["master"] if not master.worker_register(worker_name): # already registered raise web.HTTPConflict(text="worker has already registered") return web.Response()
async def post_users(request: web.Request) -> web.Response: """A client is asking to create a new user """ data = await request.json() if not all(key in data.keys() for key in {"username", "email", "password"}): raise web.HTTPBadRequest(reason="Missing required input fields") logger.debug("Trying to create user %s", data["username"]) if not is_email(data["email"]): raise web.HTTPBadRequest(reason="Email is not valid") try: await request.app["identity_backend"].register_user( data["username"], data["password"], data["email"]) except UserAlreadyExist: raise web.HTTPConflict(reason="User already exist") location = f"/users/{data['username']}/" return web.Response(status=201, headers={"Location": location})
def IdentifyPaperTask(self, data, request): """Identify a paper based on a task. Returns: 403: some other user owns this task. 404: papernum not found, or other data errors. 409: student number `data["sid"]` is already in use. """ papernum = request.match_info["task"] r, what, msg = self.server.ID_id_paper(papernum, data["user"], data["sid"], data["sname"]) if r: return web.Response(status=200) elif what == 409: raise web.HTTPConflict(reason=msg) elif what == 404: raise web.HTTPNotFound(reason=msg) elif what == 403: raise web.HTTPForbidden(reason=msg) else: # catch all that should not happen. raise web.HTTPInternalServerError(reason=msg)
def begin_job(jobdesc): global updater_process if updater_process is not None: if updater_process.poll() is not None: # Update complete updater_process = None else: error_body = { 'error': 'update_already_running', 'pid': updater_process.pid } raise web.HTTPConflict(body=error_body) job_id = str(uuid.uuid1()) (fwfd, fwfilepath) = mkstemp() if not os.path.exists(UPDATE_JOB_DIR): os.makedirs(UPDATE_JOB_DIR) statusfilepath = os.path.join(UPDATE_JOB_DIR, str(job_id) + '.json') status = {'pid': 0, 'state': 'fetching'} with open(statusfilepath, 'wb') as sfh: sfh.write(json.dumps(status)) try: fwdata = urllib.request.urlopen(jobdesc['fw_url']) with os.fdopen(fwfd, 'wb') as fwfile: fwfile.write(fwdata.read()) fwfile.flush() except: os.remove(statusfilepath) raise updater = UPDATERS[jobdesc.get('updater', 'delta')] updater_process = Popen([ updater, '--addr', str(jobdesc['address']), '--statusfile', statusfilepath, '--rmfwfile', fwfilepath ]) status = {'pid': updater_process.pid, 'state': 'starting'} with open(statusfilepath, 'wb') as sfh: sfh.write(json.dumps(status)) return {'job_id': job_id, 'pid': updater_process.pid}
async def db_update_service(connection, id, service): """Update service.""" LOG.debug("Update service.") # Check if user wants to change service id if not id == service["id"]: # Check if desired service ID is taken service_id = await db_check_service_id(connection, service["id"]) if service_id: # Chosen service ID is taken raise web.HTTPConflict(text="Service ID is taken.") # Apply updates try: await connection.execute( """UPDATE services SET id=$1, name=$2, type=$3, description=$4, url=$5, contact_url=$6, api_version=$7, service_version=$8, environment=$9, organization=$10, organization_url=$11, organization_logo=$12, updated_at=NOW() WHERE id=$13""", service["id"], service["name"], service["type"], service["description"], service["url"], service["contact_url"], service["api_version"], service["service_version"], service["environment"], service["organization"], service["organization_url"], service["organization_logo"], id, ) except Exception as e: LOG.debug(f"DB error: {e}") raise web.HTTPInternalServerError( text= "Database error occurred while attempting to update service details." )
async def register(request): username = await authorized_userid(request) if username: return web.HTTPOk() data = await request.json() username = data["username"] password = data["password"] if User.by_name(username): return web.HTTPConflict(reason="Username already taken") elif not username: return web.HTTPBadRequest(reason="Please provide a username") elif not password: return web.HTTPBadRequest(reason="Please provide a password") else: with db.atomic(): u = User(name=username) u.set_password(password) u.save() response = web.HTTPOk() await remember(request, response, username) return response
async def delete_schedule(request): """ Delete a schedule from schedules table :Example: curl -X DELETE http://localhost:8081/foglamp/schedule/dc9bfc01-066a-4cc0-b068-9c35486db87f """ try: schedule_id = request.match_info.get('schedule_id', None) try: assert uuid.UUID(schedule_id) except ValueError as ex: raise web.HTTPNotFound(reason="Invalid Schedule ID {}".format(schedule_id)) retval, message = await server.Server.scheduler.delete_schedule(uuid.UUID(schedule_id)) return web.json_response({'message': message, 'id': schedule_id}) except RuntimeWarning: raise web.HTTPConflict(reason="Enabled Schedule {} cannot be deleted.".format(schedule_id)) except (ValueError, ScheduleNotFoundError, NotReadyError) as ex: raise web.HTTPNotFound(reason=str(ex))
async def websocketHandler(request): cid = request.match_info['cid'] if cid in websockets: return web.HTTPConflict(text="Already have a connection here") ws = web.WebSocketResponse(max_msg_size=MSG_MAX, heartbeat=60.0) await ws.prepare(request) websockets[cid] = {"ws": ws, "recv": None} print(f'({len(websockets)}) {cid}: connection opened') try: msg = await ws.receive_str() if websockets[cid]["recv"] is not None: websockets[cid]["recv"].put_nowait(msg) except: # Clear the queue if websockets[cid]["recv"] is not None: try: websockets[cid]["recv"].put_nowait("") except: pass del websockets[cid] print(f'({len(websockets)}) {cid}: connection closed')
async def post_handler(self, request: web.Request) -> web.Response: """POST handler to create a resource""" try: request_data = await request.json() except Exception: raise web.HTTPBadRequest( text=json.dumps({"errors": ["The supplied JSON is invalid."]})) try: request_entity = self.adapter.to_entity(request_data) except aiokea.errors.ValidationError as e: raise web.HTTPUnprocessableEntity(text=json.dumps( {"errors": e.errors}), content_type="application/json") try: service_entity = await self.service.create(request_entity) except DuplicateResourceError as e: raise web.HTTPConflict( text=json.dumps({"errors": [e.msg]}), content_type="application/json", ) response_data = self.adapter.from_entity(service_entity) return web.json_response({"data": response_data})
def populateExamDatabase(self, data, request): """Instruct the server to generate paper data in the database. TODO: maybe the api call should just be for one row of the database. TODO: or maybe we can pass the page-to-version mapping to this? """ if not data["user"] == "manager": return web.Response(status=400) # malformed request. from plom.db import buildExamDatabaseFromSpec # TODO this is not the design we have elsewhere, should call helper function try: r, summary = buildExamDatabaseFromSpec(self.server.testSpec, self.server.DB) except ValueError: raise web.HTTPConflict( reason="Database already present: not overwriting") from None if r: return web.Response(text=summary, status=200) else: raise web.HTTPInternalServerError(text=summary)
async def remove_job(self, request): data = await request.post() self.logger.debug("received remove request {0}".format(data)) if 'command' not in data or \ 'minute' not in data or \ 'hour' not in data or \ 'dom' not in data or \ 'month' not in data or \ 'dow' not in data: return web.Response(status=500, text='not all mandatory fields submitted') cron_item = self.generate_cron_item(data, removable=True) if cron_item not in self.storage.cluster_jobs: raise web.HTTPConflict(text='job not found') self.logger.debug("broadcasting remove result") broadcast(self.udp_port, UdpSerializer.dump(cron_item, self.hash_key)) raise web.HTTPAccepted()
async def scale_cluster(request): user = request["user"] cluster_name = request.match_info["cluster_name"] backend = request.app["backend"] cluster = await backend.get_cluster(cluster_name) if cluster is None: raise web.HTTPNotFound(reason=f"Cluster {cluster_name} not found") if not user.has_permissions(cluster): raise web.HTTPForbidden( reason= f"User {user.name} lacks permissions to view cluster {cluster_name}" ) msg = await request.json() count = msg.get("count", None) if not isinstance(count, int) or count < 0: raise web.HTTPUnprocessableEntity( reason=f"Scale expects a non-negative integer, got {count}") max_workers = cluster.config.get("cluster_max_workers") resp_msg = None if max_workers is not None and count > max_workers: resp_msg = ( f"Scale request of {count} workers would exceed resource limit of " f"{max_workers} workers. Scaling to {max_workers} instead.") count = max_workers try: await backend.forward_message_to_scheduler(cluster, { "op": "scale", "count": count }) except PublicException as exc: raise web.HTTPConflict(reason=str(exc)) return web.json_response({"ok": not resp_msg, "msg": resp_msg})