| # Build API Endpoint Tutorial Part 4 |
| |
| This README is a continuation of |
| [Hello World Tutorial Part 3](3_hello_validation.md), |
| creating a "Hello world!" endpoint from scratch. |
| This tutorial assumes you have already executed all the steps in Parts 1-3, |
| and have all the code in place. |
| |
| |
| ## Hello World Part 4: Faux Calls |
| |
| In this tutorial, we'll be adding mock calls in our endpoint using `faux` |
| decorators. |
| |
| |
| ### Why? |
| |
| The `faux` module implements the mock call functionality for the Build API. |
| Our goal is to provide Build API consumers a quick and easy way to validate |
| their implementations. |
| By adding simple, static responses that have the correct "shape" (i.e. the data |
| is all filled in but values like "foo" are fine), Build API consumers can |
| verify their implementation without running full builds. |
| |
| |
| ### Mock calls |
| |
| Before we talk about implementing the mock calls, we'll look at how to execute |
| them. |
| The config_proto argument to the controllers is what controls the behavior. |
| In general, endpoints shouldn't need to use it directly, the decorators will |
| be able to handle any case where it's needed. |
| We do have to be able to set it to execute the functionality, though. |
| The `gen_call_scripts` workflow has arguments to set up the configs. |
| |
| The important ones are `--mock-success`, `--mock-failure`, and |
| `--validate-only`. |
| We'll be working with the mock arguments in this tutorial, but if you use the |
| `--validate-only` argument per the following instructions now and rerun the |
| endpoint, you will see output identical to our final result in |
| [Part 3](3_hello_validation.md), |
| but without the "Hello, moon!" line in the output! |
| |
| To run one of the special call variants, simply pass the argument to |
| `gen_call_scripts`. |
| **You can only have one call variant generated at a time!** |
| The API can only generate one output at a time, so right now you must choose |
| which mode you want to apply to all endpoint executions. |
| |
| |
| ### Case 1: Empty responses |
| |
| Since our current endpoint has no value to return, our work is very easy. |
| |
| `chromite/api/controller/hello.py`: |
| ```python |
| from chromite.api import faux |
| from chromite.api import validate |
| from chromite.lib import hello_lib |
| |
| @faux.all_empty |
| @validate.require('target') |
| @validate.validation_complete |
| def Hello(input_proto, output_proto, config_proto): |
| hello_lib.hello(target=input_proto.target) |
| ``` |
| |
| The new decorator, `@faux.all_empty`, simply ensures an empty response for both |
| the success and error mock calls. |
| Now let's run the mock call and see what we get. |
| |
| ```shell script |
| $> cd ~/chromiumos/chromite/api/contrib/ |
| $> ./gen_call_scripts --mock-success |
| $> ./call_scripts/hello__hello |
| Running chromite.api.HelloService/Hello |
| 13:59:31: DEBUG: Services registered successfully. |
| Completed chromite.api.HelloService/Hello |
| Success! |
| Return Code: 0 |
| Result: |
| {} |
| $> ./gen_call_scripts --mock-failure |
| $> ./call_scripts/hello__hello |
| Running chromite.api.HelloService/Hello |
| 14:02:56: DEBUG: Services registered successfully. |
| Completed chromite.api.HelloService/Hello |
| Return Code: 1 |
| Result: |
| {} |
| ``` |
| |
| The mock success case looks just like the normal success output, except it is |
| missing the "Hello, moon!" message we would normally see because we never |
| actually executed the endpoint itself! |
| The failure output we haven't seen yet, but the important distinctions to note |
| are the lack of the "Success!" line and the `Return Code` line having changed |
| to non-zero. |
| |
| |
| ### Returning the message |
| |
| Before we go further, let's update our endpoint to not just print our message, |
| but also return it. |
| Below is our full, new `hello.proto`, with the added line commented. |
| |
| `chromite/infra/proto/src/chromite/api/hello.proto`: |
| ```protobuf |
| // Proto config. |
| syntax = "proto3"; |
| package chromite.api; |
| |
| option go_package = "go.chromium.org/chromiumos/infra/proto/go/chromite/api"; |
| |
| import "chromite/api/build_api.proto"; |
| |
| // HelloService/Hello request and response messages. |
| message HelloRequest { |
| string target = 1; |
| } |
| |
| message HelloResponse { |
| // NEW! |
| string hello_message = 1; |
| } |
| |
| service HelloService { |
| option (service_options) = { |
| module: "hello", |
| }; |
| |
| rpc Hello(HelloRequest) returns (HelloResponse); |
| } |
| ``` |
| |
| Next we need to regenerate the protobuf bindings again. |
| |
| ```shell script |
| $> cd ~/chromiumos/chromite/infra/proto |
| $> ./generate.sh |
| $> cd ~/chromiumos/chromite/api |
| $> ./compile_build_api_proto |
| ``` |
| |
| We'll then return the message from our `hello_lib.py` function. |
| |
| `chromite/lib/hello_lib.py`: |
| ```python |
| def hello(target): |
| msg = f'Hello, {target}!' |
| print(msg) |
| return msg |
| ``` |
| |
| Now we can set the value in the response. |
| |
| `chromite/api/controller/hello.py`: |
| ```python |
| @faux.all_empty |
| @validate.require('target') |
| @validate.validation_complete |
| def Hello(input_proto, output_proto, config_proto): |
| hello_message = hello_lib.hello(target=input_proto.target) |
| output_proto.hello_message = hello_message |
| ``` |
| |
| Let's run the endpoint to make sure it works. |
| |
| ```shell script |
| $> cd ~/chromiumos/chromite/api/contrib/ |
| $> ./gen_call_scripts |
| $> cd ./call_scripts/ |
| $> echo '{"target": "moon"}' > hello__hello_input.json |
| $> ./hello__hello |
| Running chromite.api.HelloService/Hello |
| 13:32:04: DEBUG: Services registered successfully. |
| 13:32:04: DEBUG: Validating target is set. |
| Hello, moon! |
| Completed chromite.api.HelloService/Hello |
| Success! |
| Return Code: 0 |
| Result: |
| { |
| "helloMessage": "Hello, moon!" |
| } |
| ``` |
| |
| The output json file is automatically printed in the `call_scripts` output |
| (the `Result:` section), so we can see the output now includes our message! |
| |
| |
| ### Case 2: Success message |
| |
| As we just saw, our successful execution cases now include data in the output |
| message, so let's update the mock success case to also return a message. |
| |
| `chromite/api/controller/hello.py`: |
| ```python |
| from chromite.api import faux |
| from chromite.api import validate |
| from chromite.lib import hello_lib |
| |
| def _hello_success(_input_proto, output_proto, _config_proto): |
| output_proto.hello_message = 'Hello, world!' |
| |
| @faux.success(_hello_success) |
| @faux.empty_error |
| @validate.require('target') |
| @validate.validation_complete |
| def Hello(input_proto, output_proto, _config_proto): |
| hello_message = hello_lib.hello(target=input_proto.target) |
| output_proto.hello_message = hello_message |
| ``` |
| |
| You will notice the endpoint itself is untouched, except that have prefixed |
| the config_proto argument with an underscore. |
| This convention is used throughout chromite to denote unused arguments, and is |
| added now for reasons discussed below. |
| |
| First, we replaced the `@faux.all_empty` decorator with two new ones. |
| As one might expect, the second (`@faux.empty_error`) is the same failure case |
| behavior as the `@faux.all_empty` decorator. |
| The success case, `@faux.success`, and the new function, `_hello_success`, are |
| the bulk of our change. |
| |
| The `@faux.success` decorator (and each of its error counterparts) takes a |
| function to execute when the mock call is executed. |
| For our success case, we need to populate `output_proto.hello_message`, just |
| like we did when we added the output. |
| You'll notice these success and error functions always have underscores |
| prefixing their input and config arguments. |
| This is because we don't care what the input is for our mock calls, and we |
| never need to concern ourselves with the config values at the point where |
| we're generating a mock response. |
| For our mock success, our goal is just to fill out a static, valid output. |
| So we just choose a message that looks sort of like something we might return |
| normally, and hard code it in. |
| For more information about the `faux` decorators, see the |
| [faux reference](../references/faux.md). |
| |
| Now let's try running it. |
| |
| ```shell script |
| $> cd ~/chromiumos/chromite/api/contrib/ |
| $> echo '{"target": "moon"}' > ./call_scripts/hello__hello_input.json |
| $> ./gen_call_scripts |
| $> echo "Regular execution to compare." |
| $> ./call_scripts/hello__hello |
| Running chromite.api.HelloService/Hello |
| 14:35:27: DEBUG: Services registered successfully. |
| 14:35:27: DEBUG: Validating target is set. |
| Hello, moon! |
| Completed chromite.api.HelloService/Hello |
| Success! |
| Return Code: 0 |
| Result: |
| { |
| "helloMessage": "Hello, moon!" |
| } |
| $> ./gen_call_scripts --mock-success |
| $> echo "Mock success execution." |
| $> ./call_scripts/hello__hello |
| Running chromite.api.HelloService/Hello |
| 14:37:31: DEBUG: Services registered successfully. |
| Completed chromite.api.HelloService/Hello |
| Success! |
| Return Code: 0 |
| Result: |
| { |
| "helloMessage": "Hello, world!" |
| } |
| ``` |
| |
| We use the same input file in both calls, but as you can see by the output it |
| is only used in the normal call. |
| As we saw in the first example using `@faux.all_empty`, we are also missing |
| the printed message since we are not executing the endpoint itself, instead |
| we're only executing our `_hello_success` function, that only populates the |
| output value with our static "Hello, world!" message. |
| |
| Success! |
| |
| |
| ### Case 3: Error responses. |
| |
| We will not go through the exercise of adding error responses here as it is |
| an identical process to the success case, except using different decorators. |
| To do the exercise yourself, use the `@faux.error` decorator instead of the |
| `@faux.empty_error` we used above, passing through your new error function |
| like we did with the `@faux.success` decorator. |
| In practice, it is unlikely a success and error case would fill out the |
| same output field, but for the purposes of a tutorial, it's just fine! |