├── 0-Setup.md ├── 1-Labs.md ├── 3-First-Function.md ├── 3x3.jpg ├── 4-Java-Functions.md ├── 5-Troubleshooting.md ├── 6-Container-as-Function.md ├── 7-Functions-Clients-oci-curl.md ├── 8-Functions-Clients-SDK.md ├── 9-Functions-Invoke-OCI-Events.md ├── README.md ├── functions-sdk ├── pom.xml └── src │ └── main │ └── java │ └── com │ └── example │ └── fn │ └── InvokeById.java ├── images ├── apikeys-fingerprint.png ├── appliance-to-import.jpg ├── application-createdialog.png ├── applications-compartment.png ├── applications-none.png ├── auth-generate.png ├── auth-token-copy.png ├── auth-token-name.png ├── auth-tokens.png ├── create-application.png ├── create-bucket.png ├── create-email-subscription.jpg ├── create-rule-action.jpg ├── create-rule-event.jpg ├── create-topic.jpg ├── createdialog.jpg ├── dashboard.jpg ├── edit-rule-action.jpg ├── events.jpg ├── fnlab-ova.jpg ├── function-logs.jpg ├── functions-not-available.png ├── functions-unavailable.png ├── import-appliance.jpg ├── import-settings.jpg ├── importing-ova.jpg ├── labapp-nodefn.png ├── labapp.png ├── linux-desktop.jpg ├── logdestination.jpg ├── login-new-password.png ├── login-user.png ├── login.png ├── logout.png ├── logs.jpg ├── make-bucket-private.jpg ├── make-bucket-public.jpg ├── notifications-nav-menu.jpg ├── object-storage-menu.png ├── oci-events-nav-menu.jpg ├── oci-events-service.jpg ├── papertrail-dashboard.png ├── papertrail-logs.png ├── region-selection.png ├── region.png ├── select-phoenix.png ├── settings.jpg ├── tenancy-ocid.png ├── upload-image-to-bucket.jpg ├── user-ocid.png ├── userinput.png ├── usermenu-tenancy.png ├── usermenu-user.png ├── virtualbox-manager.jpg ├── vnc-login.jpg └── warning.svg └── oci-event-triggers └── sachin-in.jpg /0-Setup.md: -------------------------------------------------------------------------------- 1 | # Environment Setup 2 | 3 | For convenience, each lab participant will be provided with an Oracle Cloud 4 | Infrastructure virtual machine configured with all the tools you'll need for 5 | this workshop. 6 | 7 | Your workshop instructor will provide you with the following: 8 | 9 | 1. Participant Number--your unique id that will be incorporated into your user 10 | id and your virtual machine name. We'll also use it in the labs. 11 | 2. User Id/Password--to enable you to log into the OCI Console and to deploy 12 | functions 13 | 3. Tenancy Name--that you will use to log into the OCI account. 14 | 4. IP Address--of your hosted development environment machine 15 | 5. VNC Password--to allow you to log into your hosted development machine 16 | 17 | 18 | > As you make your way through this lab, look out for this icon. 19 | ![user input](images/userinput.png) Whenever you see it, it's time for you to 20 | perform an action. 21 | 22 | ## Pre-requisites 23 | 24 | Before we get started you'll need to log into the OCI account you've been 25 | provided to change your password from the temporary initial password. Although 26 | we're going to be working in the Phoenix region, you'll need to login into 27 | Ashburn to reset your password. 28 | 29 | ![user input](images/userinput.png) Log into the OCI console in Ashburn 30 | specifying the *tenancy* your lab instructor shared with you. 31 | 32 | https://console.us-ashburn-1.oraclecloud.com 33 | 34 | ![Login Tenancy](images/login.png) 35 | 36 | ![user input](images/userinput.png) Provide your username along with the initial password you were provided. 37 | 38 | ![Login User](images/login-user.png) 39 | 40 | ![user input](images/userinput.png) Provide a new password satisfying the 41 | requirements and record it for use during the workshop. 42 | 43 | ![Login New Password](images/login-new-password.png) 44 | 45 | ![user input](images/userinput.png) Once you've successfully changed 46 | your password and logged in, log out. Later on we'll be logging into the 47 | Phoenix region console. 48 | 49 | ![Select Region](images/logout.png) 50 | 51 | ## Configuring your Environment 52 | 53 | Now that your user account is accessible, let's log into the provided VM where 54 | you'll be ready to start no configuration. 55 | 56 | To access your cloud-based development environment you'll need a VNC client 57 | on your laptop. You can use whatever you have previously installed or you can 58 | use the VNC Viewer for Chrome that is extremely easy to install. 59 | 60 | https://chrome.google.com/webstore/detail/vnc%C2%AE-viewer-for-google-ch/iabmpiboiopbgfabjmgeedhcmjenhbla/related 61 | 62 | > NOTE: If you're curious about how to setup your own machine to build functions 63 | > and deploy them to Oracle Functions you can follow the instructions in the 64 | > [Quick Start 65 | > Guide](https://www.oracle.com/webfolder/technetwork/tutorials/infographics/oci_faas_gettingstarted_quickview/functions_quickview_top/functions_quickview/index.html#) 66 | 67 | ![user input](images/userinput.png) Log into your VM using the provided IP 68 | Address and password. The VNC port is **5903** so the server address you'll need 69 | to provide will look like **`n.n.n.n:5903`** 70 | 71 | Enter the provided VNC password to complete your login. 72 | 73 | ![vnc login](images/vnc-login.jpg) 74 | 75 | > TIP: How to use copy/paste inside your VNC session: 76 | > * Copy text from Firefox browser: Ctrl+C 77 | > * Paste text in to terminal: Shift+Ctrl+V 78 | > * Copy text from terminal: Shift+Ctrl+C 79 | > * Other than the terminal, Ctrl+C/V should work 80 | > * When in doubt, check the application's Edit menu for shortcut keys 81 | 82 | Open a terminal and type the following command to ensure you are using the 83 | `workshop` context that points to Oracle Functions: 84 | 85 | ![user input](images/userinput.png) 86 | >```sh 87 | >fn ls contexts 88 | >``` 89 | 90 | Your output should look something like the following: 91 | 92 | ```shell 93 | CURRENT NAME PROVIDER API URL REGISTRY 94 | default default 95 | * workshop oracle https://functions.us-phoenix-1.oraclecloud.com phx.ocir.io/cloudnative-devrel/workshop-NNN 96 | ``` 97 | 98 | Now to make sure you can be authenticated correctly and communicate with 99 | Oracle Functions let's run a command to list all of the existing applications. 100 | It doesn't matter what the results--just that it runs successfully to confirm 101 | connectivity. 102 | 103 | ![user input](images/userinput.png) 104 | >```sh 105 | >fn ls apps 106 | >``` 107 | 108 | You may see a list of applications something like this: 109 | 110 | ```shell 111 | NAME ID 112 | labapp-NNN ocid1.fnapp.oc1.us-phoenix-1.aaaaaaaaag4h7xotdzz27sp7z23ci6z4jqj4raq43ui6ouae5k2kl7irx34a 113 | ``` 114 | 115 | Or you may see a message saying "No apps found". That's fine too. 116 | 117 | ## Clone the Workshop Repo 118 | 119 | Before we actually get started let's clone the git repo for this workshop so 120 | that you have all of the necessary materials. Open a terminal and in your 121 | home directory (i.e., /home/demo) type: 122 | 123 | ![user input](images/userinput.png) 124 | >```sh 125 | >git clone https://github.com/shaunsmith/functionslab.git 126 | >``` 127 | 128 | In the `functionslab` folder you'll find the sources for this workshop along 129 | with materials for some of the labs you'll be doing. 130 | 131 | ## All Set! 132 | 133 | Now that you're logged into your development machine and are able to communicate 134 | with Oracle Functions it's time to get started! 135 | 136 | NEXT: [*Function Labs*](1-Labs.md), UP: [*INDEX*](README.md) 137 | 138 | -------------------------------------------------------------------------------- /1-Labs.md: -------------------------------------------------------------------------------- 1 | # Functions Labs 2 | 3 | In each of the following labs you'll explore a different aspect of Oracle 4 | Functions from creating, to deploying, to troubleshooting, to invoking. In all 5 | cases you'll be using the `fn` CLI extensively so you may find these references 6 | helpful: 7 | 8 | * [fn Commands 9 | Cheatsheet](https://github.com/sachin-pikle/functionslab/wiki/Functions-Commands-Cheatsheet) 10 | 11 | * [fn CLI docs](https://github.com/fnproject/docs/blob/master/cli/README.md) 12 | 13 | ## Your First Function 14 | 15 | Now that `fn` CLI is installed and your development envirionment is configured, 16 | we can dig into creating and running functions. In this lab you'll create, 17 | deploy, and run a Node.js function. If you aren't a Node.js programmer, don't 18 | panic! All the code is provided and is pretty easy to understand. The focus of 19 | this tutorial is on becoming familiar with the basics of Fn, not Node.js 20 | programming. 21 | 22 | So let's [create and deploy your first function](3-First-Function.md). 23 | 24 | ## Java Functions 25 | 26 | Fn provides an FDK (Function Development Kit) for each of the core supported 27 | programming languages. But the Java FDK is the most advanced with support for 28 | Maven builds, automatic function argument type conversions, and comprehenive 29 | support for function testing with JUnit. 30 | 31 | The [Introduction to Java Functions](4-Java-Functions.md) lab covers all these 32 | topics and more. 33 | 34 | ## Troubleshooting 35 | 36 | If you've been following the instructions in the tutorials carefully you 37 | shouldn't have run into any unexpected failures--hopefully!! But in real life 38 | when you're writing code things go wrong--builds fail, exceptions are thrown, 39 | etc. Fortunately the 40 | [Troubleshooting](5-Troubleshooting.md) tutorial 41 | introduces techniques you can use to track down the source of a failure. 42 | 43 | ## Automatically invoke Functions using OCI Events service 44 | 45 | OCI Events service lets you monitor OCI resource changes and send 46 | notifications and/or trigger a function automatically in response to that 47 | change. We'll explore sending email notifications, and invoking a function via 48 | the OCI Events when an image is uploaded to an OCI Object Storage bucket in 49 | [Automatically invoke Functions using OCI Events](9-Functions-Invoke-OCI-Events.md). 50 | 51 | ## Functions Clients 52 | 53 | Functions can be invoked over HTTP using their "invoke endpoint". You can 54 | either invoke the endpoint directly or use the OCI SDK to both manage and 55 | invoke functions. We'll explore how to invoke a function using: 56 | * [OCI SDK for Functions](8-Functions-Clients-SDK.md) 57 | * [`oci-curl` utility](7-Functions-Clients-oci-curl.md) 58 | 59 | ## Containers as Functions 60 | 61 | One of the coolest features of Fn is that while it's easy to write functions in 62 | various programming languages, you can also deploy Docker images as functions. 63 | This opens up entire worlds of opportunity as you can package existing code, 64 | utilities, or use a programming language not yet supported by Fn. Try the 65 | [Containers as Functions](6-Container-as-Function.md) 66 | tutorial to see how easy it is. 67 | -------------------------------------------------------------------------------- /3-First-Function.md: -------------------------------------------------------------------------------- 1 | # Your First Function with Node.js 2 | 3 | In this introductory lab we'll walk through developing a function using the 4 | JavaScript programming language and Node.js (without installing any Node.js 5 | tools!) and deploying that function Oracle Functions. We'll also learn about 6 | the core Fn concepts like applications and triggers. 7 | 8 | > As you make your way through this lab, look out for this icon. 9 | ![user input](images/userinput.png) Whenever you see it, it's time for you to 10 | perform an action. 11 | 12 | Let's start with a very simple "hello world" function written in [Node.js 13 | JavaScript](https://nodejs.org/). Don't worry, you don't need to know Node! In 14 | fact you don't even need to have Node installed on your development machine as 15 | Fn provides the necessary Node tools as a Docker container. Let's walk through 16 | your first function to become familiar with the process and how Fn supports 17 | development. 18 | 19 | 20 | ### Create your Function 21 | In the terminal type the following. 22 | 23 | ![user input](images/userinput.png) 24 | >``` 25 | > fn init --runtime node nodefn 26 | >``` 27 | 28 | The output will be 29 | 30 | ```yaml 31 | Creating function at: /nodefn 32 | Function boilerplate generated. 33 | func.yaml created. 34 | ``` 35 | 36 | The `fn init` command creates a simple function with a bit of boilerplate to get 37 | you started. The `--runtime` option is used to indicate that the function we're 38 | going to develop will be written in Node. A number of other runtimes are also 39 | supported. Fn creates the simple function along with several supporting files 40 | in the `nodefn` directory. 41 | 42 | ### Review your Function File 43 | 44 | With your function created change into the `/nodefn` directory. 45 | 46 | ![user input](images/userinput.png) 47 | >``` 48 | > cd nodefn 49 | >``` 50 | 51 | Now get a list of the directory contents. 52 | 53 | ![user input](images/userinput.png) 54 | >``` 55 | > ls 56 | >``` 57 | 58 | ```sh 59 | func.js func.yaml package.json 60 | ``` 61 | 62 | The `func.js` file which contains your actual Node function is generated along 63 | with several supporting files. To view your Node function type: 64 | 65 | ![user input](images/userinput.png) 66 | >```sh 67 | > cat func.js 68 | >``` 69 | 70 | ```js 71 | const fdk=require('@fnproject/fdk'); 72 | 73 | fdk.handle(function(input){ 74 | let name = 'World'; 75 | if (input.name) { 76 | name = input.name; 77 | } 78 | return {'message': 'Hello ' + name} 79 | }) 80 | ``` 81 | 82 | This function looks for JSON input in the form of `{"name": "Bob"}`. If this 83 | JSON example is passed to the function, the function returns `{"message":"Hello 84 | Bob"}`. If no JSON data is found, the function returns `{"message":"Hello 85 | World"}`. 86 | 87 | ### Understanding func.yaml 88 | 89 | The `fn init` command generated a `func.yaml` function 90 | configuration file. Let's look at the contents: 91 | 92 | ![user input](images/userinput.png) 93 | >```sh 94 | > cat func.yaml 95 | >``` 96 | 97 | ```yaml 98 | schema_version: 20180708 99 | name: nodefn 100 | version: 0.0.1 101 | runtime: node 102 | entrypoint: node func.js 103 | ``` 104 | 105 | The generated `func.yaml` file contains metadata about your function and 106 | declares a number of properties including: 107 | 108 | * schema_version--identifies the version of the schema for this function file. 109 | Essentially, it determines which fields are present in `func.yaml`. 110 | * name--the name of the function. Matches the directory name. 111 | * version--automatically starting at 0.0.1. 112 | * runtime--the name of the runtime/language which was set based on the value set 113 | in `--runtime`. 114 | * entrypoint--the name of the executable to invoke when your function is called, 115 | in this case `node func.js`. 116 | 117 | There are other user-specifiable properties but these will suffice for this 118 | example. Note that if not specified, the name of your function will be taken 119 | from the containing folder name. 120 | 121 | ### Other Function Files 122 | 123 | The `fn init` command generated one other file. 124 | 125 | * `package.json` -- specifies all the Node.js dependencies for your Node 126 | function. 127 | 128 | ## Your Deployment Target 129 | 130 | With the `nodefn` directory containing `func.js` and `func.yaml` you've got 131 | everything you need to deploy the function to Oracle Functions. 132 | 133 | Make sure your context is set to point to Oracle Functions. Use the `fn list 134 | contexts` command to check. 135 | 136 | ![user input](images/userinput.png) 137 | >```sh 138 | > fn list contexts 139 | >``` 140 | 141 | The `API URL` and `REGISTRY` values of the row with the `*` should be pointing 142 | to Oracle Functions and OCIR, respectively: 143 | 144 | ```shell 145 | CURRENT NAME PROVIDER API URL REGISTRY 146 | default default shaunsmith 147 | * workshop oracle https://functions.us-phoenix-1.oraclecloud.com phx.ocir.io/mytenancy/myuser 148 | ``` 149 | 150 | If your context *is not* configured correctly, please return to the 151 | [*Environment Setup*](0-Setup.md) instructions before proceeding. 152 | 153 | ## Deploying your First Function 154 | 155 | Deploying your function is how you publish the function and make it accessible 156 | to other users and systems. To see the details of what is happening during a 157 | function deploy, use the `-v / --verbose` switch. The first time you build a 158 | function of a particular language it takes longer as the `fn` CLI must download 159 | the necessary Docker images. The `--verbose` option allows you to see this 160 | process. 161 | 162 | ### Creating an Application 163 | 164 | Before you can deploy a function you'll need to create an application. You can 165 | do this using the `fn` CLI or in the Oracle Functions web console. We'll use the 166 | console as it's somewhat simpler to click on options than to copy/paste network 167 | IDs for use on the command line. 168 | 169 | Open your browser to the Oracle Functions console and 170 | login: 171 | 172 | ![user input](images/userinput.png) https://console.us-ashburn-1.oraclecloud.com/functions 173 | 174 | ![user input](images/userinput.png) Once you've logged in, select the Phoenix (us-phoenix-1) region: 175 | 176 | ![Region Selection](images/region-selection.png) 177 | 178 | ![user input](images/userinput.png) Navigate down the compartment hierarchy on the left hand dropdown to the compartment your instructor will provide. 179 | 180 | ![Applications List](images/applications-compartment.png) 181 | 182 | If you haven't selected the `us-phoenix-1` region you'll see the following error. To 183 | correct simply choose the Phoenix region from the drop down menu. 184 | 185 | ![Functions Unavailable](images/functions-unavailable.png) 186 | 187 | ![user input](images/userinput.png) click "Create Application" and complete the 188 | form with following values where NNN is your lab participant number. 189 | 190 | **IMPORTANT NOTE**: Lab participants are all working in the same OCI tenancy and 191 | compartment so to avoid confusion you need to name your applications with your 192 | participant number. Wherever you see `NNN` in the lab instructions please 193 | substitute in your number. 194 | 195 | >```sh 196 | > name: `labapp-NNN` 197 | > vcn: `workshop-vcn` 198 | > subnet: `Public Subnet nFuS:PHX-AD-1` 199 | >``` 200 | 201 | ![Create Application](images/create-application.png) 202 | 203 | Functions deployed as part of this application will be attached to the 204 | specifiied vcn and subnet. 205 | 206 | ![user input](images/userinput.png) click "Create" to finish. 207 | 208 | ### Building and Deploying your Function 209 | 210 | The `fn` CLI makes it easy to build and deploy functions. Building a function 211 | is the process of compiling (if necessary) and packaging your code into a Docker 212 | container. The deployment step will push the container to OCIR and define the 213 | function in Oracle Functions. You can do all of this in one command. 214 | 215 | ![user input](images/userinput.png) 216 | >```sh 217 | > fn --v deploy --app labapp-NNN 218 | >``` 219 | 220 | When we deploy a single function we have to specify the application it belongs 221 | to. 222 | 223 | You should see output similar to: 224 | 225 | ```shell 226 | Deploying nodefn to app: labapp-NNN 227 | Bumped to version 0.0.2 228 | Building image phx.ocir.io/mytenancy/myuser/nodefn:0.0.2 229 | FN_REGISTRY: phx.ocir.io/mytenancy/myuser 230 | Current Context: workshop 231 | Sending build context to Docker daemon 5.12kB 232 | Step 1/9 : FROM fnproject/node:dev as build-stage 233 | ---> 016382f39a51 234 | Step 2/9 : WORKDIR /function 235 | ---> Using cache 236 | ---> 17d33f5e5433 237 | Step 3/9 : ADD package.json /function/ 238 | ---> Using cache 239 | ---> 26821de105b3 240 | Step 4/9 : RUN npm install 241 | ---> Using cache 242 | ---> 42c39f1f14bb 243 | Step 5/9 : FROM fnproject/node 244 | ---> 016382f39a51 245 | Step 6/9 : WORKDIR /function 246 | ---> Using cache 247 | ---> 17d33f5e5433 248 | Step 7/9 : ADD . /function/ 249 | ---> 0973c9239c46 250 | Step 8/9 : COPY --from=build-stage /function/node_modules/ /function/node_modules/ 251 | ---> 1785a3d39bec 252 | Step 9/9 : ENTRYPOINT ["node", "func.js"] 253 | ---> Running in 78424021a8ee 254 | Removing intermediate container 78424021a8ee 255 | ---> 5c0b79de04e8 256 | Successfully built 5c0b79de04e8 257 | Successfully tagged phx.ocir.io/mytenancy/myuser/nodefn:0.0.2 258 | 259 | Parts: [phx.ocir.io mytenancy myuser nodefn:0.0.2] 260 | Pushing phx.ocir.io/mytenancy/myuser/nodefn:0.0.2 to docker registry...The push refers to repository [phx.ocir.io/mytenancy/myuser/nodefn] 261 | ffc0648dc97f: Pushed 262 | d85ee6e79290: Pushed 263 | 0677ac33d692: Pushed 264 | 0b3e54ee2e85: Pushed 265 | ad77849d4540: Pushed 266 | 5bef08742407: Pushed 267 | 0.0.2: digest: sha256:6a15a46ca32ec1bdfe7f49ba5e3ac705adbe15658e795747adb661e46ba73c7f size: 1571 268 | Updating function nodefn using image phx.ocir.io/mytenancy/myuser/nodefn:0.0.2... 269 | Successfully created function: nodefn with phx.ocir.io/mytenancy/myuser/nodefn:0.0.2 270 | ``` 271 | 272 | Since we turned on verbose mode, the steps to build the Docker container image 273 | are displayed. Normally you deploy an application without the `-v/--verbose` 274 | option. If you rerun the command a new image and version is created, pushed to 275 | OCIR, and deployed. 276 | 277 | ### Functions in the Oracle Functions Console 278 | 279 | Click on `labapp-NNN` in the Functions Applications List and you'll see the 280 | `nodefn` function appears in the functions list. 281 | 282 | ![labapp-NNN Functions](images/labapp-nodefn.png) 283 | 284 | ### Invoking your Function with the CLI 285 | 286 | There are a few ways you can invoke your function. The easiest is with the `fn` 287 | CLI. We'll see other ways in subsequent labs. Type the following: 288 | 289 | ![user input](images/userinput.png) 290 | >```sh 291 | > fn invoke labapp-NNN nodefn 292 | >``` 293 | 294 | which results in: 295 | 296 | ```js 297 | {"message":"Hello World"} 298 | ``` 299 | 300 | The first time you call a function in a new application you may encounter what 301 | is commonly called a "cold start," which results in the function call taking 302 | longer than usual. When you invoked "labapp-NNN nodefn," Oracle Functions looked 303 | up the "labapp-NNN" application and then looked for the Docker container image 304 | bound to the "nodefn" function and executed the code. The necessary compute 305 | infrastructure was also allocated. If you invoke the function a second time 306 | you'll notice it's much faster. 307 | 308 | You can also pass data to the invoke command, for example: 309 | 310 | ![user input](images/userinput.png) 311 | >```sh 312 | > echo -n '{"name":"Bob"}' | fn invoke labapp-NNN nodefn 313 | >``` 314 | 315 | ```js 316 | {"message":"Hello Bob"} 317 | ``` 318 | 319 | The JSON data was parsed and since `name` was set to "Bob", that value is passed 320 | in the function response. 321 | 322 | ### Understand fn deploy 323 | 324 | If you have used Docker before the output of `fn --verbose deploy` should look 325 | familiar--it looks like the output you see when running `docker build` with a 326 | Dockerfile. Of course this is exactly what's happening! When you deploy a 327 | function like this the `fn` CLI is dynamically generating a Dockerfile for your 328 | function and building a container image. 329 | 330 | > __NOTE__: two images are actually being used. The first contains the language 331 | > compiler and all the necessary build tools. The second image packages all 332 | > dependencies and any necessary language runtime components. Using this 333 | > strategy, the final function image size can be kept as small and secure as 334 | > possible. Smaller Docker images are naturally faster to push and pull from a 335 | > registry which improves overall performance. For more details on this 336 | > technique see [Multi-Stage Docker Builds for Creating Tiny Go 337 | > Images](https://medium.com/travis-on-docker/multi-stage-docker-builds-for-creating-tiny-go-images-e0e1867efe5a). 338 | 339 | As the `fn` CLI is built on Docker you can use the `docker` command to see the local 340 | container image you just generated. You may have a number of Docker images so 341 | use the following command to see only versions of nodefn: 342 | 343 | ![user input](images/userinput.png) 344 | >```sh 345 | > docker images | grep nodefn 346 | >``` 347 | 348 | You should see something like: 349 | 350 | ```sh 351 | phx.ocir.io/mytenancy/myuser/nodefn 0.0.2 5c0b79de04e8 15 minutes ago 66.3MB 352 | phx.ocir.io/mytenancy/myuser/nodefn 0.0.1 4936dbed0df6 40 minutes ago 66.3MB 353 | ``` 354 | 355 | ### Explore your Application 356 | 357 | The fn CLI provides a couple of commands to let us see what we've deployed. 358 | `fn list apps` returns a list of all of the defined applications. 359 | 360 | ![user input](images/userinput.png) 361 | >```sh 362 | > fn list apps 363 | >``` 364 | 365 | Which, in our case, returns the name of the application we created when we 366 | deployed our `nodefn` function: 367 | 368 | ```shell 369 | NAME ID 370 | labapp-NNN ocid1.fnapp.oc1.us-phoenix-1.aaaaaaaaafe33ayhrq5dkeplxk6wd5fpyhc7chufehxjhv7kgbnrbevbovma 371 | ``` 372 | 373 | We can also see the functions that are defined by an application. To list the 374 | functions included in "labapp-NNN" we can type: 375 | 376 | ![user input](images/userinput.png) 377 | >```sh 378 | > fn ls f labapp-NNN 379 | >``` 380 | 381 | ```sh 382 | NAME IMAGE ID 383 | nodefn phx.ocir.io/mytenancy/myuser/nodefn:0.0.2 ocid1.fnfunc.oc1.us-phoenix-1.aaaaaaaaacm4u6futn2q34fh4hbkirwsxdffss42kvd3kl6xxakkful5yehq 384 | ``` 385 | 386 | ## Wrap Up 387 | 388 | Congratulations! In this lab you've accomplished a lot. You've created 389 | your first function, deployed it to Oracle Functions and invoked it! 390 | 391 | NEXT: [*Java Functions*](4-Java-Functions.md), UP: [*Labs*](1-Labs.md), HOME: 392 | [*INDEX*](README.md) 393 | -------------------------------------------------------------------------------- /3x3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaunsmith/functionslab/5a9dad48646fae8554060b4dc78b1d86cfae740d/3x3.jpg -------------------------------------------------------------------------------- /4-Java-Functions.md: -------------------------------------------------------------------------------- 1 | # Java Functions and Unit Testing 2 | 3 | This lab introduces the 4 | [Fn Java FDK (Function Development Kit)](https://github.com/fnproject/fdk-java) 5 | and takes you through the developer experience for building and unit testing 6 | Java functions. 7 | 8 | > As you make your way through this lab, look out for this icon. ![user 9 | input](images/userinput.png) Whenever you see it, it's time for you to perform 10 | an action. 11 | 12 | ## Creating a basic Java Function 13 | 14 | Let's start by creating a new function. In a terminal type the following: 15 | 16 | ![user input](images/userinput.png) 17 | >`fn init --runtime java javafn` 18 | 19 | The output will be: 20 | 21 | ```sh 22 | Creating function at: /javafn 23 | Function boilerplate generated. 24 | func.yaml created. 25 | ``` 26 | 27 | ![user input](images/userinput.png) 28 | >```sh 29 | >cd javafn 30 | >``` 31 | 32 | The `fn init` command creates an simple function with a bit of boilerplate to 33 | get you started. The `--runtime` option is used to indicate that the function 34 | we're going to develop will be written in Java 11, the default version as of 35 | this writing. A number of other runtimes are also supported. 36 | 37 | Use the `find` command to see the directory structure and files that the 38 | `init` command has created. 39 | 40 | ![user input](images/userinput.png) 41 | >`find .` 42 | 43 | ```sh 44 | . 45 | ./func.yaml 46 | ./pom.xml 47 | ./src 48 | ./src/test 49 | ./src/test/java 50 | ./src/test/java/com 51 | ./src/test/java/com/example 52 | ./src/test/java/com/example/fn 53 | ./src/test/java/com/example/fn/HelloFunctionTest.java 54 | ./src/main 55 | ./src/main/java 56 | ./src/main/java/com 57 | ./src/main/java/com/example 58 | ./src/main/java/com/example/fn 59 | ./src/main/java/com/example/fn/HelloFunction.java 60 | ``` 61 | 62 | As usual, the init command has created a `func.yaml` file for your 63 | function but in the case of Java it also creates a Maven `pom.xml` file 64 | as well as a function class and function test class. 65 | 66 | Take a look at the contents of the generated func.yaml file. 67 | 68 | ![user input](images/userinput.png) 69 | >```sh 70 | >cat func.yaml 71 | >``` 72 | 73 | ```yaml 74 | schema_version: 20180708 75 | name: javafn 76 | version: 0.0.1 77 | runtime: java 78 | build_image: fnproject/fn-java-fdk-build:jdk11-1.0.86 79 | run_image: fnproject/fn-java-fdk:jre11-1.0.86 80 | cmd: com.example.fn.HelloFunction::handleRequest 81 | ``` 82 | 83 | The generated `func.yaml` file contains metadata about your function and 84 | declares a number of properties including: 85 | 86 | * schema_version--identifies the version of the schema for this function file 87 | * version--the version of the function 88 | * runtime--the language used for this function 89 | * build_image--the image used to build your function's image 90 | * run_image--the image your function runs in 91 | * cmd--the `cmd` property is set to the fully qualified name of the Java 92 | class and method that should be invoked when your `javafn` function is 93 | called 94 | 95 | The Java function init also generates a Maven `pom.xml` file to build and test 96 | your function. The pom includes the Fn Java FDK runtime and the test libraries 97 | your function needs. 98 | 99 | ## Deploy your Java Function 100 | 101 | With the `javafn` directory containing `pom.xml` and `func.yaml` you've got 102 | everything you need to deploy the function to Oracle Functions. 103 | 104 | Make sure your context is set to point to Oracle Functions. Use the `fn list 105 | context` command to check. If everything is fine then let's deploy the function 106 | to the app you created in the previous lab. It should be named `labapp-NNN` 107 | where `NNN` is your lap participant number. 108 | 109 | ![user input](images/userinput.png) 110 | >`fn --v deploy --app labapp-NNN ` 111 | 112 | ```yaml 113 | Deploying javafn to app: labapp-NNN 114 | Building image phx.ocir.io/mytenancy/myuser/javafn:0.0.2 115 | FN_REGISTRY: phx.ocir.io/mytenancy/myuser 116 | Current Context: workshop 117 | Sending build context to Docker daemon 14.34kB 118 | Step 1/11 : FROM fnproject/fn-java-fdk-build:jdk11-1.0.86 as build-stage 119 | ---> 0ab30d8e3524 120 | Step 2/11 : WORKDIR /function 121 | ---> Using cache 122 | ---> d6c3e60e0c04 123 | Step 3/11 : ENV MAVEN_OPTS -Dhttp.proxyHost= -Dhttp.proxyPort= -Dhttps.proxyHost= -Dhttps.proxyPort= -Dhttp.nonProxyHosts= -Dmaven.repo.local=/usr/share/maven/ref/repository 124 | ---> Using cache 125 | ---> 9133a74699d5 126 | Step 4/11 : ADD pom.xml /function/pom.xml 127 | ---> Using cache 128 | ---> f41ec165b9a8 129 | Step 5/11 : RUN ["mvn", "package", "dependency:copy-dependencies", "-DincludeScope=runtime", "-DskipTests=true", "-Dmdep.prependGroupId=true", "-DoutputDirectory=target", "--fail-never"] 130 | ---> Using cache 131 | ---> 384bcd123a67 132 | Step 6/11 : ADD src /function/src 133 | ---> Using cache 134 | ---> 2f3afddbba1b 135 | Step 7/11 : RUN ["mvn", "package"] 136 | ---> Using cache 137 | ---> 00a1b46e3258 138 | Step 8/11 : FROM fnproject/fn-java-fdk:jre11-1.0.86 139 | ---> d7caad608803 140 | Step 9/11 : WORKDIR /function 141 | ---> Using cache 142 | ---> d075b963bbfd 143 | Step 10/11 : COPY --from=build-stage /function/target/*.jar /function/app/ 144 | ---> Using cache 145 | ---> c6a836a20c57 146 | Step 11/11 : CMD ["com.example.fn.HelloFunction::handleRequest"] 147 | ---> Using cache 148 | ---> 10586c295622 149 | Successfully built 10586c295622 150 | Successfully tagged phx.ocir.io/mytenancy/myuser/javafn:0.0.2 151 | 152 | Parts: [phx.ocir.io mytenancy myuser javafn:0.0.2] 153 | Pushing phx.ocir.io/mytenancy/myuser/javafn:0.0.2 to docker registry...The push refers to repository [phx.ocir.io/mytenancy/myuser/javafn] 154 | ... 155 | Updating function javafn using image phx.ocir.io/mytenancy/myuser/javafn:0.0.2... 156 | ``` 157 | 158 | The output message 159 | `Updating function javafn using image phx.ocir.io/mytenancy/myuser/javafn:0.0.2...` 160 | let's us know that the function is packaged in the image 161 | "phx.ocir.io/mytenancy/myuser/javafn:0.0.2". 162 | 163 | ## Invoke your Deployed Function 164 | 165 | Use the the `fn invoke` command to call your function from the command line. 166 | 167 | ### Invoke with the CLI 168 | 169 | The first is using the Fn CLI which makes invoking your function relatively 170 | easy. Type the following: 171 | 172 | ![user input](images/userinput.png) 173 | >```sh 174 | > fn invoke labapp-NNN javafn 175 | >``` 176 | 177 | which results in: 178 | 179 | ```txt 180 | Hello, World! 181 | ``` 182 | 183 | You can also pass data to the invoke command. For example: 184 | 185 | ![user input](images/userinput.png) 186 | >```sh 187 | > echo -n 'Bob' | fn invoke labapp-NNN javafn 188 | >``` 189 | 190 | ```txt 191 | Hello, Bob! 192 | ``` 193 | 194 | "Bob" was passed to the function where it is processed and returned in the 195 | output. 196 | 197 | ## Exploring the Code 198 | 199 | We've generated, compiled, deployed, and invoked the Java function so let's take 200 | a look at the code. You may want to open the code in one of the IDEs available 201 | in the lab environment. 202 | 203 | Below is the generated `com.example.fn.HelloFunction` class. As you can 204 | see the function is just a method on a POJO that takes a string value 205 | and returns another string value, but the Java FDK also supports binding 206 | input parameters to streams, primitive types, byte arrays and Java POJOs 207 | unmarshalled from JSON. Functions can also be static or instance 208 | methods. 209 | 210 | ```java 211 | package com.example.fn; 212 | 213 | public class HelloFunction { 214 | 215 | public String handleRequest(String input) { 216 | String name = (input == null || input.isEmpty()) ? "world" : input; 217 | 218 | return "Hello, " + name + "!"; 219 | } 220 | 221 | } 222 | ``` 223 | 224 | This function returns the string "Hello, world!" unless an input string 225 | is provided, in which case it returns "Hello, <input string>!". We saw 226 | this previously when we piped "Bob" into the function. Notice that 227 | the Java FDK reads from standard input and automatically puts the 228 | content into the string passed to the function. This greatly simplifies 229 | the function code. 230 | 231 | ## Testing with JUnit 232 | 233 | The `fn init` command also generated a JUnit test for the function which uses 234 | the Java FDK's function test framework. With this framework you can setup test 235 | fixtures with various function input values and verify the results. 236 | 237 | The generated test confirms that when no input is provided the function returns 238 | "Hello, world!". 239 | 240 | ```java 241 | package com.example.fn; 242 | 243 | import com.fnproject.fn.testing.*; 244 | import org.junit.*; 245 | 246 | import static org.junit.Assert.*; 247 | 248 | public class HelloFunctionTest { 249 | 250 | @Rule 251 | public final FnTestingRule testing = FnTestingRule.createDefault(); 252 | 253 | @Test 254 | public void shouldReturnGreeting() { 255 | testing.givenEvent().enqueue(); 256 | testing.thenRun(HelloFunction.class, "handleRequest"); 257 | 258 | FnResult result = testing.getOnlyResult(); 259 | assertEquals("Hello, world!", result.getBodyAsString()); 260 | } 261 | 262 | } 263 | ``` 264 | 265 | Let's add a test that confirms that when an input string like "Bob" is 266 | provided we get the expected result. 267 | 268 | ![user input](images/userinput.png) Add the following method to the `HelloFunctionTest` class: 269 | 270 | 271 | ```java 272 | @Test 273 | public void shouldReturnWithInput() { 274 | testing.givenEvent().withBody("Bob").enqueue(); 275 | testing.thenRun(HelloFunction.class, "handleRequest"); 276 | 277 | FnResult result = testing.getOnlyResult(); 278 | assertEquals("Hello, Bob!", result.getBodyAsString()); 279 | } 280 | ``` 281 | 282 | You can see the `withBody()` method used to specify the value of the 283 | function input. 284 | 285 | You can run the tests by building your function with `fn build`. This will 286 | cause Maven to compile and run the updated test class. You can also invoke your 287 | tests directly from Maven using `mvn test` or from your IDE. 288 | 289 | ![user input](images/userinput.png) 290 | >`fn build` 291 | 292 | ```sh 293 | Building image fndemouser/javafn:0.0.2 ....... 294 | Function fndemouser/javafn:0.0.2 built successfully. 295 | ``` 296 | 297 | ## Accepting JSON Input 298 | 299 | Let's convert this function to use JSON for its input and output. 300 | 301 | ![user input](images/userinput.png) Replace the definition of `HelloFunction` 302 | with the following: 303 | 304 | ```java 305 | package com.example.fn; 306 | 307 | public class HelloFunction { 308 | 309 | public static class Input { 310 | public String name; 311 | } 312 | 313 | public static class Result { 314 | public String salutation; 315 | } 316 | 317 | public Result handleRequest(Input input) { 318 | Result result = new Result(); 319 | result.salutation = "Hello " + input.name; 320 | return result; 321 | } 322 | 323 | } 324 | ``` 325 | 326 | We've created a couple of simple Pojos to bind the JSON input and output 327 | to and changed the function signature to use these Pojos. The 328 | Java FDK will *automatically* bind input data based on the Java arguments 329 | to the function. JSON support is built-in but input and output binding 330 | is extensible and you could plug in marshallers for other 331 | data formats like protobuf, avro or xml. 332 | 333 | Let's build the updated function: 334 | 335 | ![user input](images/userinput.png) 336 | >`fn build` 337 | 338 | returns: 339 | 340 | ```sh 341 | Building image fndemouser/javafn:0.0.2 ..... 342 | Error during build. Run with `--verbose` flag to see what went wrong. eg: `fn --verbose CMD` 343 | 344 | Fn: error running docker build: exit status 1 345 | 346 | See 'fn --help' for more information. Client version: 0.5.16 347 | ``` 348 | 349 | Uh oh! To find out what happened rerun build with the verbose switch: 350 | 351 | ![user input](images/userinput.png) 352 | >`fn --verbose build` 353 | 354 | ```sh 355 | ... 356 | ------------------------------------------------------- 357 | T E S T S 358 | ------------------------------------------------------- 359 | Running com.example.fn.HelloFunctionTest 360 | An exception was thrown during Input Coercion: Failed to coerce event to user function parameter type class com.example.fn.HelloFunction$Input 361 | ... 362 | An exception was thrown during Input Coercion: Failed to coerce event to user function parameter type class com.example.fn.HelloFunction$Input 363 | ... 364 | Tests run: 2, Failures: 0, Errors: 2, Skipped: 0, Time elapsed: 0.893 sec <<< FAILURE! 365 | ... 366 | Results : 367 | 368 | Tests in error: 369 | shouldReturnGreeting(com.example.fn.HelloFunctionTest): One and only one response expected, but 0 responses were generated. 370 | shouldReturnWithInput(com.example.fn.HelloFunctionTest): One and only one response expected, but 0 responses were generated. 371 | 372 | Tests run: 2, Failures: 0, Errors: 2, Skipped: 0 373 | 374 | [INFO] ------------------------------------------------------------------------ 375 | [INFO] BUILD FAILURE 376 | [INFO] ------------------------------------------------------------------------ 377 | [INFO] Total time: 3.477 s 378 | [INFO] Finished at: 2017-09-21T14:59:21Z 379 | [INFO] Final Memory: 16M/128M 380 | [INFO] ------------------------------------------------------------------------ 381 | [ERROR] Failed to execute goal org.apache.maven.plugins:maven-surefire-plugin:2.12.4:test (default-test) on project hello: There are test failures. 382 | ``` 383 | 384 | *Oops!* as we can see this function build has failed due to test failures--we 385 | changed the code significantly but didn't update our tests! We really 386 | should be doing test driven development and updating the test first, but 387 | at least our bad behavior has been caught. Let's update the tests 388 | to reflect our new expected results. 389 | 390 | ![user input](images/userinput.png) Replace the definition of 391 | `HelloFunctionTest` with: 392 | 393 | ```java 394 | 395 | package com.example.fn; 396 | 397 | import com.fnproject.fn.testing.*; 398 | import org.junit.*; 399 | 400 | import static org.junit.Assert.*; 401 | 402 | public class HelloFunctionTest { 403 | 404 | @Rule 405 | public final FnTestingRule testing = FnTestingRule.createDefault(); 406 | 407 | @Test 408 | public void shouldReturnGreeting(){ 409 | testing.givenEvent().withBody("{\"name\":\"Bob\"}").enqueue(); 410 | testing.thenRun(HelloFunction.class,"handleRequest"); 411 | 412 | FnResult result = testing.getOnlyResult(); 413 | assertEquals("{\"salutation\":\"Hello Bob\"}", result.getBodyAsString()); 414 | } 415 | } 416 | 417 | ``` 418 | 419 | In the new `shouldReturnGreeting()` test method we're passing in the 420 | JSON document 421 | 422 | ```js 423 | { 424 | "name": "Bob" 425 | } 426 | ``` 427 | and expecting a result of 428 | ```js 429 | { 430 | "salutation": "Hello Bob" 431 | } 432 | ``` 433 | 434 | If you re-run the tests in your IDE or via `fn -verbose build` we can see that 435 | it now passes: 436 | 437 | ![user input](images/userinput.png) 438 | >`fn --verbose build` 439 | 440 | ## Wrap Up 441 | 442 | Congratulations! You've just completed an introduction to the Fn Java FDK. 443 | There's so much more in the FDK than we can cover in a brief introduction. Next 444 | we'll take a look at troubleshooting. We saw a little of this when our tests 445 | failed but there are a few more techniques available. 446 | 447 | NEXT: [*Troubleshooting*](5-Troubleshooting.md), UP: [*Labs*](1-Labs.md), HOME: 448 | [*INDEX*](README.md) 449 | -------------------------------------------------------------------------------- /5-Troubleshooting.md: -------------------------------------------------------------------------------- 1 | # Troubleshooting and Logging with Fn 2 | 3 | Even if you've got excellent unit tests (e.g., using the [Fn Java JUnit 4 | support](https://github.com/fnproject/fdk-java/blob/master/docs/TestingFunctions.md)) 5 | things can still go wrong. Perhaps your function is throwing an exception or 6 | returning unexpected results. So what can you do to troubleshoot your 7 | function? In this tutorial we'll look at a number of techniques and Fn 8 | features you can use to get to the root cause of your problem. 9 | 10 | As you make your way through this tutorial, look out for this icon. 11 | ![](images/userinput.png) Whenever you see it, it's time for you to 12 | perform an action. 13 | 14 | ## Getting Started 15 | 16 | First, let's create a simple Java function called `trouble`. In a new folder 17 | type: 18 | 19 | ![user input](images/userinput.png) 20 | >```sh 21 | > fn init --runtime java trouble 22 | >``` 23 | 24 | This will create a boilerplate Java hello world function in the `trouble` 25 | folder. Let's cd into that folder. 26 | 27 | ![user input](images/userinput.png) 28 | >```sh 29 | > cd trouble 30 | >``` 31 | 32 | And let's delete the unit tests so we can concentrate on the troubleshooting 33 | techniques rather than keeping the tests up to date. 34 | 35 | ![user input](images/userinput.png) 36 | >```sh 37 | > rm -rf src/test 38 | >``` 39 | 40 | Use the `find` command verify that your directory structure looks like this: 41 | 42 | ![user input](images/userinput.png) 43 | >`find .` 44 | 45 | ```sh 46 | . 47 | ./func.yaml 48 | ./pom.xml 49 | ./src 50 | ./src/main 51 | ./src/main/java 52 | ./src/main/java/com 53 | ./src/main/java/com/example 54 | ./src/main/java/com/example/fn 55 | ./src/main/java/com/example/fn/HelloFunction.java 56 | ``` 57 | 58 | Ok, we're ready to begin! 59 | 60 | ## Verbose Mode 61 | 62 | When you run commands like `fn build` or `fn deploy` you typically see "progress 63 | dots" (i.e., `...`) that let you know some action is taking place. Let's build 64 | our function and observe the output. 65 | 66 | ![user input](images/userinput.png) 67 | >```sh 68 | > fn build 69 | >``` 70 | 71 | You should see something like: 72 | 73 | ```sh 74 | Building image phx.ocir.io/mytenancy/myuser/trouble:0.0.1 ......................... 75 | Function phx.ocir.io/mytenancy/myuser/trouble:0.0.1 built successfully. 76 | ``` 77 | 78 | Perfect! But if your code can't be built successfully, either not compiling or 79 | failing unit tests, then you get a helpful error message suggesting you rerun 80 | your command with the `--verbose`/`-v` flag. 81 | 82 | To see this let's 83 | break the function so it won't compile. Comment out the return statement in the 84 | `HelloFunction` class' `handleRequest` function by putting `//` in front of the 85 | `return` statement so it looks like: 86 | 87 | ![user input](images/userinput.png) 88 | ```java 89 | package com.example.fn; 90 | 91 | public class HelloFunction { 92 | 93 | public String handleRequest(String input) { 94 | String name = (input == null || input.isEmpty()) ? "world" : input; 95 | 96 | //return "Hello, " + name + "!"; 97 | } 98 | 99 | } 100 | ``` 101 | 102 | Let's build again and check out the error message. 103 | 104 | ![user input](images/userinput.png) 105 | >```sh 106 | > fn build 107 | >``` 108 | 109 | Results in: 110 | 111 | ``` 112 | Building image phx.ocir.io/mytenancy/myuser/trouble:0.0.1 ......... 113 | Error during build. Run with `--verbose` flag to see what went wrong. eg: `fn --verbose CMD` 114 | 115 | Fn: error running docker build: exit status 1 116 | 117 | See 'fn --help' for more information. Client version: 0.5.63 118 | ``` 119 | 120 | Now let's try the build with the `--verbose` flag, which you need to put 121 | *immediately* after `fn` (i.e. `fn build --verbose` will *not* work): 122 | 123 | ![user input](images/userinput.png) 124 | >```sh 125 | > fn --verbose build 126 | >``` 127 | 128 | Now we see details of the build and the failure (output abbreviated): 129 | 130 | ```sh 131 | Building image phx.ocir.io/mytenancy/myuser/trouble:0.0.1 132 | FN_REGISTRY: phx.ocir.io/mytenancy/myuser 133 | Current Context: workshop 134 | Sending build context to Docker daemon 24.06kB 135 | Step 1/11 : FROM fnproject/fn-java-fdk-build:jdk11-1.0.87 as build-stage 136 | ---> 668f47f46c7d 137 | ... 138 | [ERROR] COMPILATION ERROR : 139 | [INFO] ------------------------------------------------------------- 140 | [ERROR] /function/src/main/java/com/example/fn/HelloFunction.java:[9,5] missing return statement 141 | [INFO] 1 error 142 | ... 143 | [ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.3:compile (default-compile) on project hello: Compilation failure 144 | [ERROR] /function/src/main/java/com/example/fn/HelloFunction.java:[9,5] missing return 145 | ... 146 | The command 'mvn package' returned a non-zero code: 1 147 | ... 148 | ``` 149 | 150 | With verbose output we see the entirety of the Maven build which includes an 151 | error message telling us we're missing a return statement--as we expected. 152 | 153 | When an unexpected error happens, verbose output is the first thing you need to 154 | enable to diagnose the issue. 155 | 156 | ## Logs 157 | 158 | When calling a deployed function, Fn captures all standard error output and 159 | sends it to a syslog server, if configured. So if you have a function throwing 160 | an exception and the stack trace is being written to standard error, it's 161 | straightforward to get that stack trace via syslog. 162 | 163 | Let's update our HelloFunction so that it throws an exception in the 164 | `handleRequst` method. Replace the definition of HelloFunction with the 165 | following: 166 | 167 | ![user input](images/userinput.png) 168 | ```java 169 | package com.example.fn; 170 | 171 | public class HelloFunction { 172 | 173 | public String handleRequest(String input) { 174 | throw new RuntimeException("Something went horribly wrong!"); 175 | } 176 | 177 | } 178 | ``` 179 | 180 | With this change let's deploy the function and invoke it. 181 | 182 | ![user input](images/userinput.png) 183 | >```sh 184 | > fn deploy --app labapp-NNN 185 | >``` 186 | 187 | ```sh 188 | Deploying trouble to app: labapp-NNN 189 | Bumped to version 0.0.2 190 | Building image phx.ocir.io/mytenancy/myuser/trouble:0.0.2 ...... 191 | ... 192 | Updating function trouble using image phx.ocir.io/mytenancy/myuser/trouble:0.0.2... 193 | Successfully created function: trouble with phx.ocir.io/mytenancy/myuser/trouble:0.0.2 194 | ``` 195 | 196 | You can verify the function is deployed successfully by listing 197 | the functions of the 'labapp-NNN' app: 198 | 199 | ![user input](images/userinput.png) 200 | >```sh 201 | > fn ls functions labapp-NNN 202 | >``` 203 | 204 | Or the slightly more economical: 205 | 206 | ![user input](images/userinput.png) 207 | >```sh 208 | > fn ls f labapp-NNN 209 | >``` 210 | 211 | ```sh 212 | NAME IMAGE ID 213 | trouble phx.ocir.io/mytenancy/myuser/trouble:0.0.2 ocid1.fnfunc.oc1.us-phoenix-1.aaaaaaaaabjdjzulxszlmvflzqs6d3yycw3foro7xbf7wenkhn3mvvldag7q 214 | ... 215 | ``` 216 | 217 | With the function defined let's invoke it and see what happens when it fails: 218 | 219 | ![user input](images/userinput.png) 220 | >```sh 221 | > fn invoke labapp-NNN trouble 222 | >``` 223 | 224 | ```sh 225 | Error invoking function. status: 502 message: function failed 226 | ``` 227 | 228 | "function failed" is not much information to go on to debug the problem. What 229 | we need to do is look at the logs! 230 | 231 | ### Log Capture 232 | 233 | > NOTE: In the future, Oracle Functions will be integrated with the OCI Logging 234 | > Service. Until then, logs can be captured and viewed via the "syslog" 235 | > functionality provided by open source Fn. 236 | 237 | To see what's happening when the function fails, we're going to have to capture 238 | its logs. To do so, we need to configure the `labapp-NNN` application with the 239 | URL of a syslog server. You can do this when you create an app, or you can 240 | update an existing app. 241 | 242 | When creating a new app using the `fn` CLI you can specify the URL using the 243 | `--syslog-url` option as in: 244 | 245 | ```sh 246 | fn create app labapp-NNN --syslog-url tcp://mysyslogserver.com 247 | ``` 248 | 249 | However, since we've already created the 'labapp-NNN' app, we'll have to update 250 | it using `fn update app`. But first we'll need a syslog server ready to 251 | receive log data. For the purposes of this tutorial we'll set up and use a free 252 | [Papertrail](https://papertrailapp.com/) account. Papertrail is a cloud log 253 | management service. To get set up: 254 | 255 | 1. Sign up for a [free Papertrail 256 | account](https://papertrailapp.com/signup?plan=free) 257 | 2. On the Papertrail website, go to 'Settings' (top right hand corner), click on 258 | 'Log Destinations', and click 'Create a Log Destination'. ![Settings 259 | Dialog](images/settings.jpg) 260 | 3. In the create dialog, under TCP unselect 'TLS' and under both TCP and UDP 261 | select 'Plain Text' ![Create Dialog](images/createdialog.jpg) 262 | 4. Click 'Create' or 'Update' 263 | 5. You'll see the address of your log destination displayed on the the 264 | page looking something like `logs.papertrailapp.com:`. Copy this value 265 | to your clipboard for use in a minute. ![Log 266 | Destination](images/logdestination.jpg) 267 | 268 | Ok, now that we have a log destination we can update the syslog url of our 269 | application: 270 | 271 | ![user input](images/userinput.png) 272 | >```sh 273 | > fn update app labapp-NNN --syslog-url tcp://[your Papertrail destination] 274 | >``` 275 | 276 | ```sh 277 | app labapp-NNN updated 278 | ``` 279 | 280 | You can confirm that the syslog URL is set correctly by inspecting your 281 | application: 282 | 283 | ![user input](images/userinput.png) 284 | >```sh 285 | > fn inspect app labapp-NNN 286 | >``` 287 | 288 | Which will return JSON looking something like: 289 | 290 | ```json 291 | { 292 | "annotations": { 293 | "oracle.com/oci/appCode": "k2kl7irx34a", 294 | "oracle.com/oci/compartmentId": "ocid1.compartment.oc1..aaaaaaaaon25g3kisxyv4k54vvtrxadpbj2bbpetrpcacwax72uhmzpflyua", 295 | "oracle.com/oci/subnetIds": [ 296 | "ocid1.subnet.oc1.phx.aaaaaaaajedmpudrrbstjsowuzcvzwugcoepj6zjxn6fohrf74z45zrv5hdq" 297 | ] 298 | }, 299 | "created_at": "2019-04-02T21:46:14.988Z", 300 | "id": "ocid1.fnapp.oc1.us-phoenix-1.aaaaaaaaag4h7xotdzz27sp7z23ci6z4jqj4raq43ui6ouae5k2kl7irx34a", 301 | "name": "labapp-NNN", 302 | "syslog_url": "tcp://mylog.papertrailapp.com:999", 303 | "updated_at": "2019-04-04T15:48:24.605Z" 304 | } 305 | ``` 306 | 307 | `syslog_url` looks to be pointing to Papertrail so let's rerun our failing 308 | function: 309 | 310 | ![user input](images/userinput.png) 311 | >```sh 312 | > fn invoke labapp-NNN trouble 313 | >``` 314 | 315 | Of course it still fails. Let's go over to the Papertrail "Dashboard" page and 316 | click on our "System" to open a page with the log showing our exception. 317 | 318 | ![Dashboard](images/papertrail-dashboard.png) 319 | 320 | ![Logs](images/papertrail-logs.png) 321 | 322 | In the log you can see the stack dump from our exception. You can leave the 323 | Papertrail log view open while debugging to monitor the log output in near 324 | realtime. Give it a try! 325 | 326 | ## DEBUG=1 327 | 328 | If you're interacting with functions via the `fn` CLI, you can enable debug 329 | mode to see the full details of the HTTP requests going to the Fn server and 330 | the responses. The `fn` CLI simply wraps the Fn API to make it easier to 331 | manage your applications and functions. 332 | 333 | You enable debug mode by adding `DEBUG=1` before `fn` on each command. For 334 | example try the following: 335 | 336 | ![user input](images/userinput.png) 337 | >```sh 338 | > DEBUG=1 fn ls apps 339 | >``` 340 | 341 | Which, with debugging turn on, prints the following: 342 | 343 | ```sh 344 | GET /v2/apps HTTP/1.1 345 | Host: functions.us-phoenix-1.oraclecloud.com:443 346 | User-Agent: Go-http-client/1.1 347 | Accept: application/json 348 | Accept-Encoding: gzip 349 | 350 | 351 | HTTP/1.1 200 OK 352 | Content-Length: 547 353 | Connection: keep-alive 354 | Content-Type: application/json 355 | Date: Thu, 04 Apr 2019 15:57:19 GMT 356 | Opc-Request-Id: /01D7MH36ZX1BT12G00000070ET/01D7MH36ZX1BT12G00000070EV 357 | 358 | {"items":[{"id":"ocid1.fnapp.oc1.us-phoenix-1.aaaaaaaaag4h7xotdzz27sp7z23ci6z4jqj4raq43ui6ouae5k2kl7irx34a","name":"labapp-NNN","annotations":{"oracle.com/oci/appCode":"k2kl7irx34a","oracle.com/oci/compartmentId":"ocid1.compartment.oc1..aaaaaaaaon25g3kisxyv4k54vvtrxadpbj2bbpetrpcacwax72uhmzpflyua","oracle.com/oci/subnetIds":["ocid1.subnet.oc1.phx.aaaaaaaajedmpudrrbstjsowuzcvzwugcoepj6zjxn6fohrf74z45zrv5hdq"]},"syslog_url":"tcp://mylog.papertrailapp.com:999","created_at":"2019-04-02T21:46:14.988Z","updated_at":"2019-04-04T15:48:24.605Z"}]} 359 | 360 | NAME ID 361 | labapp-NNN ocid1.fnapp.oc1.us-phoenix-1.aaaaaaaaag4h7xotdzz27sp7z23ci6z4jqj4raq43ui6ouae5k2kl7irx34a 362 | ``` 363 | 364 | All debug output is written to stderr while the normal response is written 365 | to stdout so it's easy to capture or pipe either for processing. 366 | 367 | ## Wrapping Up 368 | 369 | That's a brief intro to current techniques available for troubleshooting for Fn. 370 | We'll update this lab as new features become available. 371 | 372 | NEXT: [*Automatically invoke Functions using OCI Events*](9-Functions-Invoke-OCI-Events.md), 373 | UP: [*Labs*](1-Labs.md), HOME: [*INDEX*](README.md) 374 | -------------------------------------------------------------------------------- /6-Container-as-Function.md: -------------------------------------------------------------------------------- 1 | # Creating a Function from a Docker Image 2 | 3 | This lab walks through how to use a custom Docker image to define an 4 | Fn function. Although Fn functions are packaged as Docker images, when 5 | developing functions using the Fn CLI developers are not directly exposed 6 | to the underlying Docker platform. Docker isn't hidden (you can see 7 | Docker build output and image names and tags), but you aren't 8 | required to be very Docker-savvy to develop functions with Fn. 9 | 10 | However, sometimes you need to handle advanced use cases and must take 11 | complete control of the creation of the function container image. Fortunately 12 | the design and implementation of Fn enables you to do exactly that. Let's 13 | build a simple custom function container image to become familiar with the key 14 | elements of the process. 15 | 16 | Unlike in previous labs we aren't going to create a function using the 17 | `fn init` command. We're going to create all the necessary files from scratch. 18 | 19 | As you make your way through this tutorial, look out for this icon. 20 | ![](images/userinput.png) Whenever you see it, it's time for you to 21 | perform an action. 22 | 23 | # Magick Functions 24 | 25 | One of the most common reasons for writing a custom Dockerfile for a function 26 | is to install a Linux package that your function needs. In our example we're 27 | going to use the the ever-popular [ImageMagick](https://www.imagemagick.org) to 28 | do some image processing in our function and while there is a Node.js module for 29 | ImageMagick, it's just a wrapper on the underlying native libary. So we'll 30 | have to install the native library in addition to adding the Node module to our 31 | `package.json` dependencies. Let's start by creating the Node function. 32 | 33 | ## Function Definition 34 | 35 | ![](images/userinput.png) 36 | > In an **empty folder** create a file named `func.js` and copy/paste the 37 | following as its content: 38 | 39 | ```javascript 40 | const fdk = require('@fnproject/fdk'); 41 | const fs = require('fs'); 42 | const tmp = require('tmp'); 43 | const im = require('imagemagick'); 44 | 45 | fdk.handle((buffer, ctx) => { 46 | return new Promise((resolve, reject) => { 47 | tmp.tmpName((err, tmpFile) => { 48 | if (err) throw err; 49 | fs.writeFile(tmpFile, buffer, (err) => { 50 | if (err) throw err; 51 | im.identify(['-format', '{"width": %w, "height": %h}', tmpFile], 52 | (err, output) => { 53 | if (err) { 54 | reject(err); 55 | } else { 56 | resolve(JSON.parse(output)); 57 | } 58 | } 59 | ); 60 | }); 61 | }); 62 | }); 63 | }, { inputMode: 'buffer' }); 64 | ``` 65 | 66 | The function takes a binary image as its argument, writes it to a tmp file, and 67 | then uses ImageMagick to obtain the width and height of the image. Since the 68 | function argument type is binary we need to set the "inputMode" property to 69 | "buffer" when we call the the FDK's handle function, i.e., 70 | `{ inputMode: 'buffer' }`. Unlike when using the Java FDK where the function 71 | signature is examined to determine what input format is expected, in Node.js you 72 | have to declare what you expect. 73 | 74 | ## Declaring Node.js Dependencies 75 | 76 | There are lots of interesting elements to this function (other than the typical 77 | Node "callback hell") but the key one for us is the use of the "imagemagick" 78 | Node module for image processing. To use it we need to include it along with 79 | our other dependencies in our`package.json` file. 80 | 81 | ![](images/userinput.png) 82 | > In same folder as the `func.js` file, create a `package.json` file and 83 | copy/paste the following as its content: 84 | 85 | ```json 86 | { 87 | "name": "imagedims", 88 | "version": "1.0.0", 89 | "description": "Function using ImageMagick that returns dimensions", 90 | "main": "func.js", 91 | "author": "fnproject.io", 92 | "license": "Apache-2.0", 93 | "dependencies": { 94 | "@fnproject/fdk": ">=0.0.11", 95 | "tmp": "^0.0.33", 96 | "imagemagick": "^0.1.3" 97 | } 98 | } 99 | ``` 100 | 101 | Like with all Node.js functions, we include the Fn Node FDK as a dependency 102 | along with the "tmp" module for temporary file utilities and "imagemagick" for 103 | image processing. 104 | 105 | ## Function Metadata 106 | 107 | Now that we have a Node.js function and it's dependencies captured in the 108 | `package.json` we need a `func.yaml` to capture the function metadata. 109 | 110 | ![](images/userinput.png) 111 | > In the folder containing the previously created files, create a `func.yaml` 112 | file and copy/paste the following as its content: 113 | 114 | ```yaml 115 | schema_version: 20180708 116 | name: imagedims 117 | version: 0.0.1 118 | runtime: docker 119 | ``` 120 | 121 | This is a typical `func.yaml` for a Node.js function except that instead of 122 | declaring the **runtime** as "node" we've specified "**docker**". If you were 123 | to type `fn build` right now you'd get the error: 124 | 125 | > ``` 126 | > Fn: Dockerfile does not exist for 'docker' runtime 127 | > ``` 128 | 129 | This is because when you set the runtime type to "docker" `fn build` defers to 130 | your Dockerfile to build the function container image--and you haven't defined 131 | one yet! 132 | 133 | One more small point worthy of note is that if you don't declare the `runtime` 134 | property then it will default to `docker` and the CLI will look for a 135 | Dockerfile. 136 | 137 | ## Default Node.js Function Dockerfile 138 | 139 | The Dockerfile that `fn build` would normally automatically generate to build a 140 | Node.js function container image looks like this: 141 | 142 | ```Dockerfile 143 | FROM fnproject/node:dev as build-stage 144 | WORKDIR /function 145 | ADD package.json /function/ 146 | RUN npm install 147 | 148 | FROM fnproject/node 149 | WORKDIR /function 150 | ADD . /function/ 151 | COPY --from=build-stage /function/node_modules/ /function/node_modules/ 152 | ENTRYPOINT ["node", "func.js"] 153 | ``` 154 | 155 | It's a two stage build with the `fnproject/node:dev` image containing `npm` and 156 | other build tools, and the `fnproject/node` image containing just the Node 157 | runtime. This approach is designed to ensure that deployable function container 158 | images are as small as possible--which is beneficial for a number of reasons 159 | including the time it takes to transfer the image from a Docker respository to 160 | the compute node where the function is to be run. 161 | 162 | ## Custom Node.js Function Dockerfile 163 | 164 | The `fnproject/node` container image is built on Alpine so we'll need to install 165 | the 166 | [ImageMagick Alpine package](https://pkgs.alpinelinux.org/packages?name=imagemagick&branch=edge) 167 | using the `apk` package management utility. You can do this with a Dockerfile 168 | `RUN` command: 169 | 170 | ```Dockerfile 171 | RUN apk add --no-cache imagemagick 172 | ``` 173 | 174 | We want to install ImageMagick into the runtime image, not the build image, 175 | so we need to add the `RUN` command after the `FROM fnproject/node` command. 176 | 177 | ![](images/userinput.png) 178 | > In the folder containing the previously created files, create a file named 179 | `Dockerfile` and copy/paste the following as its content: 180 | 181 | ```Dockerfile 182 | FROM fnproject/node:dev as build-stage 183 | WORKDIR /function 184 | ADD package.json /function/ 185 | RUN npm install 186 | 187 | FROM fnproject/node 188 | RUN apk add --no-cache imagemagick 189 | WORKDIR /function 190 | ADD . /function/ 191 | COPY --from=build-stage /function/node_modules/ /function/node_modules/ 192 | ENTRYPOINT ["node", "func.js"] 193 | ``` 194 | 195 | With this Dockerfile the Node.js function, its dependencies (including the 196 | "imagemagick" Node wrapper), and the "imagemagick" Alpine package will be 197 | included in an image derived from the base `fnproject/node` image. We should be 198 | good to go! 199 | 200 | ## Building and Deploying 201 | 202 | Once you have your custom Dockerfile you can simply use `fn build` to build 203 | your function. Give it a try: 204 | 205 | ![](images/userinput.png) 206 | >``` 207 | > fn -v build 208 | >``` 209 | 210 | You should see output similar to: 211 | 212 | ```shell 213 | Building image phx.ocir.io/mytenancy/myuser/imagedims:0.0.1 214 | FN_REGISTRY: phx.ocir.io/mytenancy/myuser 215 | Current Context: workshop 216 | Sending build context to Docker daemon 39.94kB 217 | Step 1/10 : FROM fnproject/node:dev as build-stage 218 | ---> 016382f39a51 219 | ... 220 | Step 6/10 : RUN apk add --no-cache imagemagick 221 | ---> Using cache 222 | ---> f86803cfbf80 223 | ... 224 | Successfully built 1565a9a99aec 225 | Successfully tagged phx.ocir.io/mytenancy/myuser/imagedims:0.0.1 226 | 227 | Function phx.ocir.io/mytenancy/myuser/imagedims:0.0.1 built successfully. 228 | ``` 229 | 230 | Just like with a default build, the output is a container image. From this 231 | point forward everything is just as it would be for any Fn function. Let's 232 | deploy to your previously created `labapp-NNN` application, where `NNN` is 233 | your lab participant number. 234 | 235 | 236 | ![](images/userinput.png) 237 | >``` 238 | > fn deploy --app labapp-NNN 239 | >``` 240 | 241 | We can confirm the function is correctly defined by getting a list of the 242 | functions in the "tutorial" application: 243 | 244 | ![](images/userinput.png) 245 | >``` 246 | > fn list functions labapp-NNN 247 | >``` 248 | 249 | **Pro tip**: The fn cli lets you abbreviate most of the keywords so you can 250 | also type `fn ls f labapp-NNN` 251 | 252 | You should see output similar to: 253 | 254 | ```shell 255 | NAME IMAGE ID 256 | imagedims phx.ocir.io/mytenancy/myuser/imagedims:0.0.2 ocid1.fnfunc.oc1.us-phoenix-1.aaaaaaaaacw6cjiagzwc64hhacuj3ssd7c4e37y4kdsdnjbcmduczrcuywfq 257 | ``` 258 | 259 | ## Invoking the Function 260 | 261 | With the function deployed let's invoke it to make sure it's working as 262 | expected. You'll need a jpeg or png file so either find one on your machine 263 | or download one. If you've cloned this lab's Git repo you can use the 264 | `3x3.jpg` image that has a height and width of 3 pixels, or you can download 265 | it from the `images` folder in github. 266 | 267 | ![](images/userinput.png) 268 | >``` 269 | > cat 3x3.jpg | fn invoke labapp-NNN imagedims 270 | >``` 271 | 272 | The first time you invoke the function you'll incur some "cold start" cost. For 273 | this input file you should see the following output: 274 | 275 | >```json 276 | >{"width":3,"height":3} 277 | >``` 278 | 279 | # Conclusion 280 | 281 | One of the most powerful features of Fn and Oracle Functions is the ability to 282 | use custom-defined Docker container images as functions. This feature makes it 283 | possible to customize your function's runtime environment including letting you 284 | install any Linux libraries or utilities that your function might need. And 285 | thanks to the Fn CLI's support for Dockerfiles it's the same user experience as 286 | when developing any function. 287 | 288 | Having completed this lab you've successfully built a function using 289 | a custom Dockerfile. Congratulations! 290 | 291 | UP: [*Labs*](1-Labs.md), HOME: [*INDEX*](README.md) 292 | -------------------------------------------------------------------------------- /7-Functions-Clients-oci-curl.md: -------------------------------------------------------------------------------- 1 | ## Invoking oci-curl Configuration 2 | 3 | In OCI you need to sign your requests to function http endpoints. Refer to [OCI 4 | Signed 5 | Requests](https://docs.cloud.oracle.com/iaas/Content/API/Concepts/signingrequests.htm?TocPath=Developer%20Tools%20|REST%20APIs%20|_____4) 6 | for more information and for sample clients in various popular languages. 7 | 8 | One such client that OCI supports is Bash. `oci-curl` is a Bash function that 9 | which wraps `curl` and performs the required OCI request signing. It's useful 10 | and easy to configure so we'll quickly get it setup to see how it can be used to 11 | invoke a function. 12 | 13 | 1. Download the [oci-curl Bash 14 | script](https://docs.cloud.oracle.com/iaas/Content/API/Concepts/signingrequests.htm#Bash) 15 | to `oci-curl.sh` and open it in an editor (e.g., gedit). 16 | 17 | 2. Edit the file to include your account details which you can find in your 18 | `~/.oci/config` file. Copy paste from the config file to the oci-curl.sh 19 | script. 20 | 21 | ```shell 22 | function oci-curl { 23 | 24 | # TODO: update these values to your own 25 | local tenancyId=""; 26 | local authUserId=""; 27 | local keyFingerprint=""; 28 | local privateKeyPath=""; 29 | ``` 30 | 31 | With the script updated with your credentials you can source it to define the 32 | oci-curl function in your Bash shell. 33 | 34 | ![user input](images/userinput.png) 35 | >``` 36 | > source oci-curl.sh 37 | >``` 38 | 39 | ## Function Invocation Endpoint 40 | 41 | To invoke a function you'll need its "invoke endpoint" which can be obtained by 42 | inspecting the function using the `fn inspect` command. To inspect the "javafn" 43 | function you defined earlier, type the following: 44 | 45 | ![user input](images/userinput.png) 46 | >``` 47 | > fn inspect f labapp-NNN javafn 48 | >``` 49 | 50 | which will return a JSON object similar to the following: 51 | 52 | ```json 53 | { 54 | "annotations": { 55 | "fnproject.io/fn/invokeEndpoint": "https://k2kl7irx34a.us-phoenix-1.functions.oci.oraclecloud.com/20181201/functions/ocid1.fnfunc.oc1.us-phoenix-1.aaaaaaaaabzleh7l3ry7l2zpro6ddmgv3kwnwkn7xrtuxodtddsjbvwp3swa/actions/invoke", 56 | "oracle.com/oci/compartmentId": "ocid1.compartment.oc1..aaaaaaaaon25g3kisxyv4k54vvtrxadpbj2bbpetrpcacwax72uhmzpflyua" 57 | }, 58 | "app_id": "ocid1.fnapp.oc1.us-phoenix-1.aaaaaaaaag4h7xotdzz27sp7z23ci6z4jqj4raq43ui6ouae5k2kl7irx34a", 59 | "created_at": "2019-04-02T21:56:55.812Z", 60 | "id": "ocid1.fnfunc.oc1.us-phoenix-1.aaaaaaaaabzleh7l3ry7l2zpro6ddmgv3kwnwkn7xrtuxodtddsjbvwp3swa", 61 | "idle_timeout": 30, 62 | "image": "phx.ocir.io/cloudnative-devrel/shsmith/javafn:0.0.2", 63 | "memory": 128, 64 | "name": "javafn", 65 | "timeout": 30, 66 | "updated_at": "2019-04-02T22:01:02.171Z" 67 | } 68 | ``` 69 | 70 | If you look closely you can see there's an `fnproject.io/fn/invokeEndpoint` 71 | property buried in the JSON. You could copy/paste from here but there's a 72 | somewhat more convenient way to just get the invoke endpoint. You just add 73 | `--endpoint` to your ispect command: 74 | 75 | ![user input](images/userinput.png) 76 | >``` 77 | > fn inspect f --endpoint labapp-NNN javafn 78 | >``` 79 | 80 | which just returns the value of the `fnproject.io/fn/invokeEndpoint` property: 81 | 82 | ```shell 83 | https://k2kl7irx34a.us-phoenix-1.functions.oci.oraclecloud.com/20181201/functions/ocid1.fnfunc.oc1.us-phoenix-1.aaaaaaaaabzleh7l3ry7l2zpro6ddmgv3kwnwkn7xrtuxodtddsjbvwp3swa/actions/invoke 84 | ``` 85 | 86 | ## Calling the Function with oci-curl 87 | 88 | The syntax of `oci-curl` is *not* exactly like `curl`. To do a **POST**, which 89 | is the required invocation method for a function, it is: 90 | 91 | `oci-curl post ` 92 | 93 | This means you're going to have to extract the hostname and path elements from 94 | the url you obtained. In our example we have: 95 | 96 | hostname=`k2kl7irx34a.us-phoenix-1.functions.oci.oraclecloud.com` 97 | path=`/20181201/functions/ocid1.fnfunc.oc1.us-phoenix-1.aaaaaaaaabzleh7l3ry7l2zpro6ddmgv3kwnwkn7xrtuxodtddsjbvwp3swa/actions/invoke` 98 | 99 | **NOTE:** the "path" must begin with a `/`. 100 | 101 | As mentioned above, you need to invoke a function with POST as you are passing a 102 | payload to your function for processing. `oci-curl` takes a payload via a file 103 | so let's create a file to pass. In a terminal you can create a `payload.txt` 104 | file by `cat`ing the text "Functions" into the it (closing with ctrl-d) or you 105 | can create and edit it using a text editor: 106 | 107 | ![user input](images/userinput.png) 108 | >``` 109 | > cat > payload.txt 110 | > Functions 111 | >^d 112 | >``` 113 | 114 | Now let's invoke the function. You'll need to use the host and path elements 115 | of your function endpoint, but using those from our example we'd have: 116 | 117 | ![user input](images/userinput.png) 118 | >``` 119 | > oci-curl k2kl7irx34a.us-phoenix-1.functions.oci.oraclecloud.com post payload.txt /20181201/functions/ocid1.fnfunc.oc1.us-phoenix-1.aaaaaaaaabzleh7l3ry7l2zpro6ddmgv3kwnwkn7xrtuxodtddsjbvwp3swa/actions/invoke 120 | >``` 121 | 122 | You'll be prompted for the passphrase for your private key, which is **workshop**. 123 | 124 | ![](images/userinput.png) 125 | >``` 126 | > Enter pass phrase for /home/demo/.oci/workshop_pri_key.pem 127 | .pem: workshop 128 | > 129 | >``` 130 | 131 | If all goes well you should see the output concatinating "Hello, " with the 132 | contents of the payload.txt file. 133 | 134 | ```sh 135 | Hello, Functions 136 | ``` 137 | 138 | You've successfully invoked a function using a signed HTTP request using curl! 139 | Next we'll look how we can call functions from client code using the OCI SDK. 140 | 141 | NEXT: [*Container as Function*](6-Container-as-Function.md), 142 | UP: [*Labs*](1-Labs.md), HOME: [*INDEX*](README.md) 143 | -------------------------------------------------------------------------------- /8-Functions-Clients-SDK.md: -------------------------------------------------------------------------------- 1 | # Invoke Oracle Functions using the OCI SDK 2 | 3 | This lab walks you through how to invoke a function deployed to Oracle Functions 4 | using (a preview version of) the Oracle Cloud Infrastructure Java SDK. The OCI 5 | Java SDK exposes two endpoints specificially for Oracle Functions: 6 | 7 | - `FunctionsManagementClient` - which provides APIs for function and application 8 | lifecycle management (e.g., CRUD operations) 9 | - `FunctionsInvokeClient` - for invoking functions 10 | 11 | The SDK also provides a number of utility classes for building function 12 | invocation requests and handling request results. 13 | 14 | In this example, we'll invoke a existing function by it's id so we will only 15 | need the `FunctionsInvokeClient`. The key method we're going to use is the 16 | suitably named `invokeFunction`, which takes an `InvokeFunctionRequest`. 17 | 18 | The required steps our client code will need to perform are: 19 | 20 | 1. Authenticate with OCI (more on this below) 21 | 2. Create a `FunctionsInvokeClient` with the auth credentials 22 | 3. Set the `invokeEndpoint` of the client to the URL of the Oracle Functions service in your region 23 | 3. Create an `InvokeFunctionRequest` with the function id 24 | 4. Pass the `InvokeFunctionRequest` to the `FunctionsInvokeClient`to call the function 25 | 5. Extract the result from an `InvokeFunctionResponse` 26 | 27 | ## OCI Java SDK 28 | 29 | This example uses the latest OCI Java SDK. The SDK is available in Maven Central. 30 | 31 | ## A Tour of the Code 32 | 33 | The entire functions client class is [InvokeById](functions-sdk/src/main/java/com/example/fn/InvokeById.java) but the core functionality is in the following lines: 34 | 35 | ```java 36 | AuthenticationDetailsProvider authProvider = new ConfigFileAuthenticationDetailsProvider(ociProfile); 37 | try (FunctionsInvokeClient fnInvokeClient = new FunctionsInvokeClient(authProvider)) { 38 | fnInvokeClient.setEndpoint(invokeEndpointURL); 39 | 40 | InvokeFunctionRequest ifr = InvokeFunctionRequest.builder().functionId(functionId) 41 | .invokeFunctionBody(StreamUtils.createByteArrayInputStream(payload.getBytes())).build(); 42 | 43 | System.out.println("Invoking function endpoint - " + invokeEndpointURL + " with payload [" + payload + "]"); 44 | InvokeFunctionResponse resp = fnInvokeClient.invokeFunction(ifr); 45 | System.out.println(IOUtils.toString(resp.getInputStream(), StandardCharsets.UTF_8)); 46 | } 47 | ``` 48 | 49 | The first line creates an instance of `ConfigFileAuthenticationDetailsProvider` 50 | which will read the `~/.oci/config` file and attempt to authenticate with OCI. 51 | In this case, we will pass the OCI profile as the first argument while running this program. 52 | 53 | ```java 54 | AuthenticationDetailsProvider authProvider = new ConfigFileAuthenticationDetailsProvider(ociProfile); 55 | ``` 56 | 57 | The next two lines instantiate a `FunctionsInvokeClient` using the config file 58 | auth provide and sets the Oracle Functions service endpoint for the region. 59 | 60 | ```java 61 | try (FunctionsInvokeClient fnInvokeClient = new FunctionsInvokeClient(authProvider)) { 62 | fnInvokeClient.setEndpoint(invokeEndpointURL); 63 | ``` 64 | 65 | The next few lines builds an `InvokeFunctionRequest` object for a function with 66 | the specified id (OCID) and sets the function body (payload) to the string 67 | value that was passed in from the command line. 68 | 69 | >NOTE: When setting the payload be sure to use the SDK's **StreamUtils** class. 70 | 71 | ```java 72 | InvokeFunctionRequest ifr = InvokeFunctionRequest.builder().functionId(functionId) 73 | .invokeFunctionBody(StreamUtils.createByteArrayInputStream(payload.getBytes())).build(); 74 | ``` 75 | 76 | Finally we use the invoke client to call the function we constructed, getting 77 | an `InvokeFunctionResponse` in return. 78 | 79 | ## Build the Client app and configure your environment 80 | 81 | 1. Since you've previously cloned the workshop git repo into your home 82 | directory, cd into the `functionslab/functions-sdk` folder where you'll find 83 | the Java code and a `pom.xml` to build it. 84 | 85 | ![user input](images/userinput.png) 86 | >``` 87 | >cd ~/functionslab/functions-sdk 88 | >``` 89 | 90 | 2. Build the Functions client using Maven 91 | 92 | ![user input](images/userinput.png) 93 | >``` 94 | >mvn clean package 95 | >``` 96 | 97 | 3. Define OCI authentication properties 98 | 99 | Similar to what we saw when using the `oci-curl` Bash script, Functions 100 | clients need to authenticate with OCI before being able to make service 101 | calls. There are a few ways to authenticate. This example uses an 102 | `ConfigFileAuthenticationDetailsProvider`, which reads user properties from 103 | the OCI config file located in `~/.oci/config`. This class can be instructed 104 | to read an optionally specified OCI profile. For this lab let's use 105 | the "workshop" profile. 106 | 107 | You're `~/.oci/config` file was pre-populated with all of the right values 108 | for your tenancy, compartment, user id, etc. So it's ready to go! There's 109 | only one profile defined, but you can have many profiles for use with 110 | different tenancies or compartments. 111 | 112 | Review your `~/.oci/config` file to become familiar with its contents and 113 | structure. In a terminal type: 114 | 115 | ![user input](images/userinput.png) 116 | >``` 117 | >cat ~/.oci/config 118 | >``` 119 | 120 | ## Invoke your Function 121 | 122 | Before we take a look at the client code, let's run it. 123 | 124 | The Maven build produces a jar in the target folder. The syntax to run the 125 | example is: 126 | 127 | `java -jar target/.jar []` 128 | 129 | Unlike the `oci-curl` example, the OCI SDK supports invoking a function by its 130 | id, not directly using its invoke endpoint. But we need to find the Orace 131 | Functions invoke endpoint (protocol + host) and the function id. We can do this by 132 | inspecting the function you want to invoke using the Fn CLI, e.g.,: 133 | 134 | ![user input](images/userinput.png) 135 | >``` 136 | > fn inspect fn labapp-NNN nodefn 137 | >``` 138 | 139 | The result will be a JSON structure similar to the following: 140 | 141 | ```JSON 142 | { 143 | "annotations": { 144 | "fnproject.io/fn/invokeEndpoint": "https://toyh4yqssuq.us-phoenix-1.functions.oci.oraclecloud.com/20181201/functions/ocid1.fnfunc.oc1.phx.abcdefg..hijk/actions/invoke", 145 | "oracle.com/oci/compartmentId": "ocid1.compartment.oc1..abcdefg" 146 | }, 147 | "app_id": "ocid1.fnapp.oc1.phx.abcd..fg", 148 | "created_at": "2019-08-26T21:28:04.866Z", 149 | "id": "ocid1.fnfunc.oc1.phx.abcdefg..hijk", 150 | "idle_timeout": 30, 151 | "image": "phx.ocir.io/oracle/shs/nodefn:0.0.4", 152 | "memory": 128, 153 | "name": "nodefn", 154 | "timeout": 30, 155 | "updated_at": "2019-08-26T21:28:04.866Z" 156 | } 157 | ``` 158 | 159 | The invoke endpoint you need to pass to the example can be extracted from the 160 | value of the `fnproject.io/fn/invokeEndpoint` property. You just need the 161 | protcol and name of the host. For the example above that would be: 162 | `https://toyh4yqssuq.us-phoenix-1.functions.oci.oraclecloud.com`. The `id` 163 | property contains the function id. 164 | 165 | > NOTE: Payload is optional. If your function doesn't expect any input you 166 | > can omit it. 167 | 168 | e.g., without payload: 169 | 170 | `java -jar target/fn-java-sdk-invokebyid-1.0-SNAPSHOT.jar workshop https://toyh4yqssuq.us-phoenix-1.functions.oci.oraclecloud.com ocid1.fnfunc.oc1.phx.abcdefg..hijk` 171 | 172 | e.g., with payload: 173 | 174 | `java -jar target/fn-java-sdk-invokebyid-1.0-SNAPSHOT.jar workshop https://toyh4yqssuq.us-phoenix-1.functions.oci.oraclecloud.com ocid1.fnfunc.oc1.phx.abcdefg..hijk '{"name":"foobar"}'` 175 | 176 | ## What if my function needs input in binary form? 177 | 178 | See the [Invoke by Function 179 | name](https://github.com/abhirockzz/fn-java-sdk-invoke) example for details on 180 | how to attach a binary payload to an `InvokeFunctionRequest`. 181 | 182 | ## Troubleshooting 183 | 184 | 1. If you fail to provide a "workshop" profile in the OCI `config` file you'll get 185 | the following exception: 186 | 187 | Exception in thread "main" java.lang.NullPointerException: missing fingerprint in config 188 | 189 | 2. If you provide an invalid value for function id you'll get an exception 190 | similar to the following: 191 | 192 | Exception in thread "main" com.oracle.bmc.model.BmcException: (404, Unknown, false) Unexpected Content-Type: application/json;charset=utf-8 instead of application/json. Response body: {"code":"NotAuthorizedOrNotFound","message":"Resource is not authorized or not found"} (opc-request-id: F8BC6E9DC19F44BD8E8967AAEC/01D5424B1F1BT1AW8ZJ0003Z6C/01D5424B1F1BT1AW8ZJ0003Z6D) 193 | 194 | 3. If you provide an incorrect `tenancy` or `user` or 195 | `fingerprint` in your OCI `config` file you'll receive an authentication exception similar to the following: 196 | 197 | Exception in thread "main" com.oracle.bmc.model.BmcException: (401, Unknown, false) Unexpected Content-Type: application/json;charset=utf-8 instead of application/json. Response body: {"code":"NotAuthenticated","message":"Not authenticated"} (opc-request-id: 3FD3E66DF81F4BB490A6424530/01D5427GTX1BT1D68ZJ0003Z9E/01D5427GTX1BT1D68ZJ0003Z9F) 198 | 199 | ## Wrapping Up 200 | 201 | In this lab you've got a taste of what it's like to use the OCI SDK for 202 | Functions to invoke a function. The OCI SDK provides support for the entire 203 | Functions API so you can both invoke and manage functions and applications. 204 | Please refer to the SDK docs for more details. 205 | 206 | NEXT: [*Functions Clients-oci curl*](7-Functions-Clients-oci-curl.md), 207 | UP: [*Labs*](1-Labs.md), HOME: [*INDEX*](README.md) 208 | -------------------------------------------------------------------------------- /9-Functions-Invoke-OCI-Events.md: -------------------------------------------------------------------------------- 1 | # Invoke Oracle Functions Using OCI Events service 2 | 3 | This lab walks you through how to invoke a function deployed to Oracle Functions 4 | automatically using another serverless service - Oracle Cloud Infrastructure 5 | (OCI) Events service. We will trigger a function when an object is uploaded to 6 | an OCI Object Storage bucket. We will use OCI Events service to automatically 7 | invoke our function which will retrieve metadata from the image. All this 8 | without having to provision, manage and scale servers! 9 | 10 | ## OCI Events service 11 | 12 | OCI Events service is a fully managed service that lets you track changes in 13 | your OCI resources and respond to them using Oracle Functions, Notifications, 14 | and Streaming services. OCI Events service is compliant with Cloud Native 15 | Computing Foundation (CNCF) CloudEvents for seamless interoperability with the 16 | cloud native ecosystem. 17 | 18 | You can build event-driven, cloud native, serverless applications using Oracle Functions 19 | and OCI Events. 20 | 21 | ![OCI Events Service](images/oci-events-service.jpg) 22 | 23 | For more information about the OCI Events service please see the [service 24 | documentation](https://docs.cloud.oracle.com/iaas/Content/Events/Concepts/eventsoverview.htm). 25 | Before we dive in to our function, let us start with a simple test using OCI 26 | Notifications service. Let us create a topic with an email subscription and 27 | configure an Event rule to route the event to your email address. 28 | 29 | ## Create an OCI Notifications Service (ONS) Topic and Email Subscription 30 | 31 | ![user input](images/userinput.png) 32 | Click on Application Integration -> Notifications in the left navigation menu: 33 | 34 | ![OCI Nav Menu > ONS](images/notifications-nav-menu.jpg) 35 | 36 | 37 | ![user input](images/userinput.png) 38 | Click on "Create Topic": 39 | 40 | **IMPORTANT NOTE**: All lab participants are working in the same OCI tenancy and 41 | compartment. So, to avoid confusion you need to name your OCI resources with your 42 | participant number. Wherever you see `NNN` in the lab instructions, please 43 | substitute it with your participant number. 44 | 45 | >``` 46 | > Name: topic-NNN 47 | >``` 48 | 49 | ![Create an ONS Topic](images/create-topic.jpg) 50 | 51 | 52 | ![user input](images/userinput.png) 53 | Navigate into the Topic Details page, Click on "Create Subscription", select 54 | "Email", and enter your email address: 55 | 56 | >``` 57 | > Protocol: Email 58 | > Email address: 59 | >``` 60 | 61 | ![Create an ONS Email Subscription](images/create-email-subscription.jpg) 62 | 63 | 64 | Next, you need to activate your email subscription. 65 | 66 | ![user input](images/userinput.png) 67 | Check your email. You will receive an email with the subject "Oracle Cloud 68 | Infrastructure Notifications Service Subscription Confirmation". Confirm the 69 | subscription by clicking on the link provided in the email. You will see a 70 | "Subscription confirmed" message in the browser. Now this subscription will be 71 | in the "Active" state in the OCI console. 72 | 73 | Next, let us test the email subscription with a test message. 74 | 75 | ![user input](images/userinput.png) 76 | You can test the subscription by clicking the "Publish Message" button on the 77 | topic screen in the OCI console. Confirm that you receive the test message in 78 | your inbox. 79 | 80 | 81 | ## Create an OCI Event rule 82 | 83 | Now, let us create an Event rule to route Object Storage object created events 84 | to the above ONS topic/email subscription. 85 | 86 | ![user input](images/userinput.png) 87 | Click on Application Integration -> Events Service in the left navigation menu: 88 | 89 | ![OCI Nav Menu > Events](images/oci-events-nav-menu.jpg) 90 | 91 | 92 | You can listen to specific events using filter criteria. In this case, we are 93 | only interested in uploads to our specific Object Storage bucket. So, let us use 94 | the "Object Storage - Create Object" event with "bucketName" as the filter 95 | criteria. Refer to the service documentation for more information on the event 96 | types and filters. 97 | 98 | ![user input](images/userinput.png) 99 | Select your-lab-compartment. Click on 'Create Rule' and populate the form with 100 | the following values: 101 | 102 | >``` 103 | > Name: cloud-events-NNN 104 | > 105 | > Select "Event Type" 106 | > Service Name: Object Storage 107 | > Event Type: Object Storage - Create Object 108 | > 109 | > Add Condition and select "Attribute" 110 | > Attribute Name: bucketName 111 | > Attribute Value: object-upload-NNN 112 | >``` 113 | 114 | ![Create rule event](images/create-rule-event.jpg) 115 | 116 | 117 | ![user input](images/userinput.png) 118 | Under Actions, select the ONS topic created above. 119 | 120 | >``` 121 | > Action Type: Notifications 122 | > Compartment: select-your-lab-compartment 123 | > Topic: topic-NNN 124 | >``` 125 | 126 | ![Create rule action](images/create-rule-action.jpg) 127 | 128 | ![user input](images/userinput.png) 129 | Click Create Rule 130 | 131 | ## Create a public Object Storage bucket and upload an image 132 | 133 | Next, we will create a public Object Storage bucket and upload an image to 134 | trigger an email notification. 135 | 136 | ![user input](images/userinput.png) 137 | Navigate to the Object Storage console. 138 | 139 | ![Object Storage Menu](images/object-storage-menu.png) 140 | 141 | ![user input](images/userinput.png) 142 | Select your-lab-compartment. Create an Object Storage bucket: 143 | 144 | >``` 145 | > Bucket name: object-upload-NNN 146 | >``` 147 | 148 | ![Create bucket](images/create-bucket.png) 149 | 150 | Once the bucket is created, we change its visibility to make it public. 151 | 152 | ![user input](images/userinput.png) 153 | Click on "Edit Visibility" in the item menu and select "Public": 154 | 155 | ![Make bucket public](images/make-bucket-public.jpg) 156 | 157 | Now that our bucket is created and made public, we can upload an image file. 158 | 159 | ![user input](images/userinput.png) 160 | Upload the image `sachin-in.jpg` from this location 161 | [/oci-event-triggers/sachin-in.jpg](/oci-event-triggers/sachin-in.jpg) 162 | to your bucket. 163 | 164 | ![Upload image to bucket](images/upload-image-to-bucket.jpg) 165 | 166 | 167 | This will generate an Object Created event, which in turn will trigger an email 168 | notification. In about 60 seconds, you should see an email in your inbox with 169 | the cloud event JSON (similar to the JSON shown below): 170 | 171 | ```json 172 | { 173 | "eventType" : "com.oraclecloud.objectstorage.createobject", 174 | "cloudEventsVersion" : "0.1", 175 | "eventTypeVersion" : "2.0", 176 | "source" : "ObjectStorage", 177 | "eventTime" : "2019-11-17T21:08:03.401Z", 178 | "contentType" : "application/json", 179 | "data" : { 180 | "compartmentId" : "ocid1.compartment.oc1..aaaaaaaanqa2dxz6gtcygu6yeh6wigslwxvwllbhd2sxasdfasdfasdfbq", 181 | "compartmentName" : "compartment-name", 182 | "resourceName" : "sachin-in.jpg", 183 | "resourceId" : "/n/namespace/b/bucket/o/sachin-in.jpg", 184 | "availabilityDomain" : "PHX-AD-1", 185 | "additionalDetails" : { 186 | "bucketName" : "object-upload-NNN", 187 | "archivalState" : "Available", 188 | "namespace" : "your-namespace-name", 189 | "bucketId" : "ocid1.bucket.oc1.phx.aaaaaaaaaboojfoukd7gdvkfundi33pl6cxcebev5b237t6asdfasdfasdfuq", 190 | "eTag" : "b744b0de-db9f-4804-8624-90a2883821f0" 191 | } 192 | }, 193 | "eventID" : "c5c593eb-8dad-f249-97fe-9a1a9cad0e25", 194 | "extensions" : { 195 | "compartmentId" : "ocid1.compartment.oc1..aaaaaaaanqa2dxz6gtcygu6yeh6wigslwxvwllbhd2sxasdfasdfasdfbq" 196 | } 197 | } 198 | ``` 199 | 200 | Congratulations! We have confirmed the event gets generated and triggers the 201 | event rule to send an email notification. Now let us proceed to trigger a 202 | function in response to the event. 203 | 204 | ## Create a function 205 | 206 | Now, let us create a function to process the cloud event. 207 | 208 | ![user input](images/userinput.png) 209 | Create a boilerplate Java function using the fn CLI: 210 | 211 | >``` 212 | > fn init --runtime java cloud-events-demo-fn 213 | >``` 214 | 215 | The output will be: 216 | 217 | ```shell 218 | Creating function at: ./cloud-events-demo-fn 219 | Function boilerplate generated. 220 | func.yaml created. 221 | ``` 222 | 223 | ![user input](images/userinput.png) 224 | >``` 225 | > cd cloud-events-demo-fn 226 | >``` 227 | 228 | Now we will add two dependencies to the pom.xml file. One for the 229 | cloudevents-api, and another for metadata-extractor so we can extract the image 230 | metadata later on. 231 | 232 | ![user input](images/userinput.png) 233 | Edit the pom.xml file and add the following dependencies: 234 | 235 | ```xml 236 | 237 | io.cloudevents 238 | cloudevents-api 239 | 0.2.1 240 | 241 | 242 | com.drewnoakes 243 | metadata-extractor 244 | 2.12.0 245 | 246 | ``` 247 | 248 | Next, lets implement a function to handle the incoming cloud event. Since OCI 249 | Cloud Events conform to the CNCF Cloud Events specification, we can safely type 250 | our incoming parameter as a CloudEvent and the FDK will handle properly 251 | serializing the parameter when the function is triggered. Once we have our 252 | CloudEvent data we can construct a URL that points to our image (a public image 253 | in this case) and open that URL as a stream that can be passed to the metadata 254 | extractor. 255 | 256 | ![user input](images/userinput.png) 257 | Replace the definition of HelloFunction with the following: 258 | 259 | ```java 260 | package com.example.fn; 261 | 262 | import com.drew.imaging.ImageMetadataReader; 263 | import com.drew.imaging.ImageProcessingException; 264 | import com.drew.metadata.Metadata; 265 | import com.fasterxml.jackson.databind.ObjectMapper; 266 | import io.cloudevents.CloudEvent; 267 | 268 | import java.io.IOException; 269 | import java.io.InputStream; 270 | import java.net.URL; 271 | import java.util.Map; 272 | 273 | public class HelloFunction { 274 | 275 | public Metadata handleRequest(CloudEvent event) throws IOException, ImageProcessingException { 276 | ObjectMapper objectMapper = new ObjectMapper(); 277 | Map data = objectMapper.convertValue(event.getData().get(), Map.class); 278 | Map additionalDetails = objectMapper.convertValue(data.get("additionalDetails"), Map.class); 279 | 280 | String imageUrl = "https://objectstorage.us-phoenix-1.oraclecloud.com/n/" + 281 | additionalDetails.get("namespace") + 282 | "/b/" + 283 | additionalDetails.get("bucketName") + 284 | "/o/" + 285 | data.get("resourceName"); 286 | System.out.println("imageUrl: " + imageUrl); 287 | 288 | InputStream imageStream = new URL(imageUrl).openStream(); 289 | Metadata metadata = ImageMetadataReader.readMetadata(imageStream); 290 | System.out.println(objectMapper.writeValueAsString(metadata)); 291 | 292 | return metadata; 293 | } 294 | 295 | } 296 | ``` 297 | 298 | ## Test class for your function 299 | 300 | Before we run an end-to-end test, let us write a simple test for our 301 | function. 302 | 303 | **Note: This test assumes the image `sachin-in.jpg` has been uploaded to 304 | your `public` bucket `object-upload-NNN` in the above step** 305 | 306 | ![user input](images/userinput.png) 307 | Replace the definition of HelloFunctionTest with the following: 308 | 309 | ```java 310 | package com.example.fn; 311 | 312 | import com.fnproject.fn.testing.*; 313 | import org.junit.*; 314 | 315 | import static org.junit.Assert.*; 316 | 317 | public class HelloFunctionTest { 318 | 319 | @Rule 320 | public final FnTestingRule testing = FnTestingRule.createDefault(); 321 | 322 | @Test 323 | public void shouldReturnGreeting() { 324 | String event = ""; 325 | testing.givenEvent().withBody(event).enqueue(); 326 | testing.thenRun(HelloFunction.class, "handleRequest"); 327 | 328 | FnResult result = testing.getOnlyResult(); 329 | assertTrue(result.isSuccess()); 330 | } 331 | 332 | } 333 | ``` 334 | 335 | ![user input](images/userinput.png) 336 | Generate a string representation of **your** Object Created cloud event JSON 337 | using a JSON-to-string conversion tool like 338 | https://tools.knowledgewalls.com/jsontostring 339 | 340 | **Note: In the string representation, look for `\/` and remove the backslash `\` 341 | so for example `application\/json` looks like `application/json` and 342 | `"\/n\/namespace\/b\/bucket\/o\/sachin-in.jpg\"` looks like 343 | `"/n/namespace/b/bucket/o/sachin-in.jpg\"` ** 344 | 345 | 346 | ```json 347 | "{\"eventType\":\"com.oraclecloud.objectstorage.createobject\",\"cloudEventsVersion\":\"0.1\",\"eventTypeVersion\":\"2.0\",\"source\":\"ObjectStorage\",\"eventTime\":\"2019-11-17T21:08:03.401Z\",\"contentType\":\"application/json\",\"data\":{\"compartmentId\":\"ocid1.compartment.oc1..aaaaaaaanqa2dxz6gtcygu6yeh6wigslwxvwllbhd2sxasdfasdfasdfbq\",\"compartmentName\":\"compartment-name\",\"resourceName\":\"sachin-in.jpg\",\"resourceId\":\"/n/namespace/b/bucket/o/sachin-in.jpg\",\"availabilityDomain\":\"PHX-AD-1\",\"additionalDetails\":{\"bucketName\":\"object-upload-NNN\",\"archivalState\":\"Available\",\"namespace\":\"your-namespace-name\",\"bucketId\":\"ocid1.bucket.oc1.phx.aaaaaaaaaboojfoukd7gdvkfundi33pl6cxcebev5b237t6asdfasdfasdfuq\",\"eTag\":\"b744b0de-db9f-4804-8624-90a2883821f0\"}},\"eventID\":\"c5c593eb-8dad-f249-97fe-9a1a9cad0e25\",\"extensions\":{\"compartmentId\":\"ocid1.compartment.oc1..aaaaaaaanqa2dxz6gtcygu6yeh6wigslwxvwllbhd2sxasdfasdfasdfbq\"}}" 348 | ``` 349 | 350 | ![user input](images/userinput.png) 351 | In HelloFunctionTest.java, replace the test event "your test image event 352 | JSON" with the generated string representation of **your** cloud event JSON: 353 | 354 | ```java 355 | String event = "{\"eventType\":\"com.oraclecloud.objectstorage.createobject\",\"cloudEventsVersion\":\"0.1\",\"eventTypeVersion\":\"2.0\",\"source\":\"ObjectStorage\",\"eventTime\":\"2019-11-17T21:08:03.401Z\",\"contentType\":\"application/json\",\"data\":{\"compartmentId\":\"ocid1.compartment.oc1..aaaaaaaanqa2dxz6gtcygu6yeh6wigslwxvwllbhd2sxasdfasdfasdfbq\",\"compartmentName\":\"compartment-name\",\"resourceName\":\"sachin-in.jpg\",\"resourceId\":\"/n/namespace/b/bucket/o/sachin-in.jpg\",\"availabilityDomain\":\"PHX-AD-1\",\"additionalDetails\":{\"bucketName\":\"object-upload-NNN\",\"archivalState\":\"Available\",\"namespace\":\"your-namespace-name\",\"bucketId\":\"ocid1.bucket.oc1.phx.aaaaaaaaaboojfoukd7gdvkfundi33pl6cxcebev5b237t6asdfasdfasdfuq\",\"eTag\":\"b744b0de-db9f-4804-8624-90a2883821f0\"}},\"eventID\":\"c5c593eb-8dad-f249-97fe-9a1a9cad0e25\",\"extensions\":{\"compartmentId\":\"ocid1.compartment.oc1..aaaaaaaanqa2dxz6gtcygu6yeh6wigslwxvwllbhd2sxasdfasdfasdfbq\"}}"; 356 | ``` 357 | 358 | Now our test class is ready for use. Let's proceed to build/deploy your 359 | function. 360 | 361 | ## Build, test and deploy your function 362 | 363 | At this point we have everything we need to build, test and deploy our 364 | function. 365 | 366 | ![user input](images/userinput.png) 367 | Run the fn deploy command: 368 | 369 | >``` 370 | > fn -v deploy --app labapp-NNN 371 | >``` 372 | 373 | You should see the following output: 374 | 375 | ```shell 376 | Deploying cloud-events-demo-fn to app: labapp-NNN 377 | Bumped to version 0.0.8 378 | Building image iad.ocir.io/tenant-namespace/workshop-NNN/cloud-events-demo-fn:0.0.8 379 | FN_REGISTRY: iad.ocir.io/tenant-namespace/workshop-NNN 380 | Current Context: workshop 381 | Sending build context to Docker daemon 16.9kB 382 | Step 1/11 : FROM fnproject/fn-java-fdk-build:jdk11-1.0.99 as build-stage 383 | ---> 8f671937cc94 384 | Step 2/11 : WORKDIR /function 385 | ---> Using cache 386 | ---> ac43afba97f2 387 | Step 3/11 : ENV MAVEN_OPTS -Dhttp.proxyHost= -Dhttp.proxyPort= -Dhttps.proxyHost= -Dhttps.proxyPort= -Dhttp.nonProxyHosts= -Dmaven.repo.local=/usr/share/maven/ref/repository 388 | ---> Using cache 389 | ---> 11eaa8831c5e 390 | Step 4/11 : ADD pom.xml /function/pom.xml 391 | ---> Using cache 392 | ---> 655878f46baf 393 | Step 5/11 : RUN ["mvn", "package", "dependency:copy-dependencies", "-DincludeScope=runtime", "-DskipTests=true", "-Dmdep.prependGroupId=true", "-DoutputDirectory=target", "--fail-never"] 394 | ---> Using cache 395 | ---> a606117e028c 396 | Step 6/11 : ADD src /function/src 397 | ---> 56a9bcfd99fe 398 | Step 7/11 : RUN ["mvn", "package"] 399 | ---> Running in 4d481e7bfa33 400 | [INFO] Scanning for projects... 401 | [INFO] 402 | [INFO] ------------------------< com.example.fn:hello >------------------------ 403 | [INFO] Building hello 1.0.0 404 | [INFO] --------------------------------[ jar ]--------------------------------- 405 | [INFO] 406 | [INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ hello --- 407 | [INFO] Using 'UTF-8' encoding to copy filtered resources. 408 | [INFO] skip non existing resourceDirectory /function/src/main/resources 409 | [INFO] 410 | [INFO] --- maven-compiler-plugin:3.3:compile (default-compile) @ hello --- 411 | [INFO] Changes detected - recompiling the module! 412 | [INFO] Compiling 1 source file to /function/target/classes 413 | [INFO] 414 | [INFO] --- maven-resources-plugin:2.6:testResources (default-testResources) @ hello --- 415 | [INFO] Using 'UTF-8' encoding to copy filtered resources. 416 | [INFO] skip non existing resourceDirectory /function/src/test/resources 417 | [INFO] 418 | [INFO] --- maven-compiler-plugin:3.3:testCompile (default-testCompile) @ hello --- 419 | [INFO] Changes detected - recompiling the module! 420 | [INFO] Compiling 1 source file to /function/target/test-classes 421 | [INFO] 422 | [INFO] --- maven-surefire-plugin:2.22.1:test (default-test) @ hello --- 423 | [INFO] 424 | [INFO] ------------------------------------------------------- 425 | [INFO] T E S T S 426 | [INFO] ------------------------------------------------------- 427 | [INFO] Running com.example.fn.HelloFunctionTest 428 | imageUrl: https://objectstorage.us-phoenix-1.oraclecloud.com/n/tenant-namespace/b/object-upload-NNN/o/sachin-in.jpg 429 | {"directoryCount":6,"directories":[{"imageWidth":500,"imageHeight":324,"numberOfComponents":3,"name":"JPEG","tagCount":8,"errorCount":0,"tags":[{"description":"Baseline","tagName":"Compression Type","tagType":-3,"tagTypeHex":"0xfffffffd","directoryName":"JPEG"},{"description":"8 bits","tagName":"Data Precision","tagType":0,"tagTypeHex":"0x0000","directoryName":"JPEG"},{"description":"324 pixels","tagName":"Image Height","tagType":1,"tagTypeHex":"0x0001","directoryName":"JPEG"},{"description":"500 pixels","tagName":"Image Width","tagType":3,"tagTypeHex":"0x0003","directoryName":"JPEG"},{"description":"3","tagName":"Number of Components","tagType":5,"tagTypeHex":"0x0005","directoryName":"JPEG"},{"description":"Y component: Quantization table 0, Sampling factors 1 horiz/1 vert","tagName":"Component 1","tagType":6,"tagTypeHex":"0x0006","directoryName":"JPEG"},{"description":"Cb component: Quantization table 1, Sampling factors 1 horiz/1 vert","tagName":"Component 2","tagType":7,"tagTypeHex":"0x0007","directoryName":"JPEG"},{"description":"Cr component: Quantization table 1, Sampling factors 1 horiz/1 vert","tagName":"Component 3","tagType":8,"tagTypeHex":"0x0008","directoryName":"JPEG"}],"errors":[],"empty":false,"parent":null},{"version":258,"resUnits":0,"resY":100,"resX":100,"imageWidth":100,"imageHeight":100,"name":"JFIF","tagCount":6,"errorCount":0,"tags":[{"description":"1.2","tagName":"Version","tagType":5,"tagTypeHex":"0x0005","directoryName":"JFIF"},{"description":"none","tagName":"Resolution Units","tagType":7,"tagTypeHex":"0x0007","directoryName":"JFIF"},{"description":"100 dots","tagName":"X Resolution","tagType":8,"tagTypeHex":"0x0008","directoryName":"JFIF"},{"description":"100 dots","tagName":"Y Resolution","tagType":10,"tagTypeHex":"0x000a","directoryName":"JFIF"},{"description":"0","tagName":"Thumbnail Width Pixels","tagType":12,"tagTypeHex":"0x000c","directoryName":"JFIF"},{"description":"0","tagName":"Thumbnail Height Pixels","tagType":13,"tagTypeHex":"0x000d","directoryName":"JFIF"}],"errors":[],"empty":false,"parent":null},{"name":"Ducky","tagCount":2,"errorCount":0,"tags":[{"description":"64","tagName":"Quality","tagType":1,"tagTypeHex":"0x0001","directoryName":"Ducky"},{"description":"Oct 1989: Portrait of Sachin Tendulkar of India in Lahore, Pakistan. \\ Mandatory Credit: Ben Radford/Allsport","tagName":"Comment","tagType":2,"tagTypeHex":"0x0002","directoryName":"Ducky"}],"errors":[],"empty":false,"parent":null},{"name":"Adobe JPEG","tagCount":4,"errorCount":0,"tags":[{"description":"25600","tagName":"DCT Encode Version","tagType":0,"tagTypeHex":"0x0000","directoryName":"Adobe JPEG"},{"description":"192","tagName":"Flags 0","tagType":1,"tagTypeHex":"0x0001","directoryName":"Adobe JPEG"},{"description":"0","tagName":"Flags 1","tagType":2,"tagTypeHex":"0x0002","directoryName":"Adobe JPEG"},{"description":"YCbCr","tagName":"Color Transform","tagType":3,"tagTypeHex":"0x0003","directoryName":"Adobe JPEG"}],"errors":[],"empty":false,"parent":null},{"numberOfTables":4,"typical":false,"optimized":true,"name":"Huffman","tagCount":1,"errorCount":0,"tags":[{"description":"4 Huffman tables","tagName":"Number of Tables","tagType":1,"tagTypeHex":"0x0001","directoryName":"Huffman"}],"errors":[],"empty":false,"parent":null},{"name":"File Type","tagCount":4,"errorCount":0,"tags":[{"description":"JPEG","tagName":"Detected File Type Name","tagType":1,"tagTypeHex":"0x0001","directoryName":"File Type"},{"description":"Joint Photographic Experts Group","tagName":"Detected File Type Long Name","tagType":2,"tagTypeHex":"0x0002","directoryName":"File Type"},{"description":"image/jpeg","tagName":"Detected MIME Type","tagType":3,"tagTypeHex":"0x0003","directoryName":"File Type"},{"description":"jpg","tagName":"Expected File Name Extension","tagType":4,"tagTypeHex":"0x0004","directoryName":"File Type"}],"errors":[],"empty":false,"parent":null}]} 430 | [INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 3.969 s - in com.example.fn.HelloFunctionTest 431 | [INFO] 432 | [INFO] Results: 433 | [INFO] 434 | [INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0 435 | [INFO] 436 | [INFO] 437 | [INFO] --- maven-jar-plugin:2.4:jar (default-jar) @ hello --- 438 | [INFO] Building jar: /function/target/hello-1.0.0.jar 439 | [INFO] ------------------------------------------------------------------------ 440 | [INFO] BUILD SUCCESS 441 | [INFO] ------------------------------------------------------------------------ 442 | [INFO] Total time: 9.405 s 443 | [INFO] Finished at: 2019-08-25T14:16:52Z 444 | [INFO] ------------------------------------------------------------------------ 445 | Removing intermediate container 4d481e7bfa33 446 | ---> 820e9a6852dd 447 | Step 8/11 : FROM fnproject/fn-java-fdk:jre11-1.0.99 448 | ---> 552d503f8aa1 449 | Step 9/11 : WORKDIR /function 450 | ---> Using cache 451 | ---> e3c8525f04ad 452 | Step 10/11 : COPY --from=build-stage /function/target/*.jar /function/app/ 453 | ---> e05353e99461 454 | Step 11/11 : CMD ["com.example.fn.HelloFunction::handleRequest"] 455 | ---> Running in 1921bac644c5 456 | Removing intermediate container 1921bac644c5 457 | ---> 14dbc09a9918 458 | Successfully built 14dbc09a9918 459 | Successfully tagged iad.ocir.io/tenant-namespace/workshop-NNN/cloud-events-demo-fn:0.0.8 460 | 461 | Parts: [iad.ocir.io tenant-namespace workshop-NNN cloud-events-demo-fn:0.0.8] 462 | Pushing iad.ocir.io/tenant-namespace/workshop-NNN/cloud-events-demo-fn:0.0.8 to docker registry...The push refers to repository [iad.ocir.io/tenant-namespace/workshop-NNN/cloud-events-demo-fn] 463 | d910070a76f0: Pushed 464 | 41b8a6435811: Layer already exists 465 | 9f47f6e68a04: Layer already exists 466 | 815ce0cceeb3: Layer already exists 467 | 580508f3f933: Layer already exists 468 | be4626a574d8: Layer already exists 469 | 15d57950dad3: Layer already exists 470 | 2bf534399aca: Layer already exists 471 | 1c95c77433e8: Layer already exists 472 | 0.0.8: digest: sha256:ed7f354009fd2a990e3a03eabd6fd0a887c7cb24ef32aee600bfaed4518448f6 size: 2208 473 | Updating function cloud-events-demo-fn using image iad.ocir.io/tenant-namespace/workshop-NNN/cloud-events-demo-fn:0.0.8... 474 | ``` 475 | 476 | Congratulations! You have successfully built, tested and deployed your function. 477 | Before we run the end-to-end test, let us try and invoke the deployed function 478 | manually. 479 | 480 | 481 | ## Manually invoke your function 482 | 483 | We can manually invoke our function by passing the above cloud event string. 484 | 485 | **Note: This test assumes the image `sachin-in.jpg` has been uploaded to 486 | your `public` bucket `object-upload-NNN` in the above step** 487 | 488 | ![user input](images/userinput.png) 489 | Run the fn invoke command: 490 | 491 | >``` 492 | > echo "[event JSON string]" | fn invoke labapp-NNN cloud-events-demo-fn 493 | >``` 494 | 495 | Here's the same command showing a sample cloud event string: 496 | 497 | >``` 498 | > echo "{\"cloudEventsVersion\":\"0.1\",\"eventID\":\"aa00367d-8281-476a-b918-0e821f1e2f6d\",\"eventType\":\"com.oraclecloud.objectstorage.createobject\",\"source\":\"objectstorage\",\"eventTypeVersion\":\"1.0\",\"eventTime\":\"2019-08-25T14:01:46Z\",\"schemaURL\":null,\"contentType\":\"application/json\",\"extensions\":{\"compartmentId\":\"ocid1.compartment.oc1..aaa...\"},\"data\":{\"compartmentId\":\"ocid1.compartment.oc1..aaa...\",\"compartmentName\":\"your-compartment-name\",\"resourceName\":\"sachin-in.jpg\",\"resourceId\":\"\",\"availabilityDomain\":null,\"freeFormTags\":{},\"definedTags\":{},\"additionalDetails\":{\"eTag\":\"65efdaae-9464-45e8-b564-4df86f11198a\",\"namespace\":\"tenant-namespace\",\"archivalState\":\"Available\",\"bucketName\":\"object-upload-NNN\",\"bucketId\":\"ocid1.bucket.oc1.iad.aaa...\"}}}" | fn invoke labapp-NNN cloud-events-demo-fn 499 | >``` 500 | 501 | You should see the following output on the screen: 502 | 503 | ```shell 504 | {"directories":[{"name":"JPEG","imageWidth":500,"imageHeight":324,"numberOfComponents":3,"empty":false,"parent":null,"tagCount":8,"errorCount":0,"tags":[{"directoryName":"JPEG","description":"Baseline","tagName":"Compression Type","tagType":-3,"tagTypeHex":"0xfffffffd"},{"directoryName":"JPEG","description":"8 bits","tagName":"Data Precision","tagType":0,"tagTypeHex":"0x0000"},{"directoryName":"JPEG","description":"324 pixels","tagName":"Image Height","tagType":1,"tagTypeHex":"0x0001"},{"directoryName":"JPEG","description":"500 pixels","tagName":"Image Width","tagType":3,"tagTypeHex":"0x0003"},{"directoryName":"JPEG","description":"3","tagName":"Number of Components","tagType":5,"tagTypeHex":"0x0005"},{"directoryName":"JPEG","description":"Y component: Quantization table 0, Sampling factors 1 horiz/1 vert","tagName":"Component 1","tagType":6,"tagTypeHex":"0x0006"},{"directoryName":"JPEG","description":"Cb component: Quantization table 1, Sampling factors 1 horiz/1 vert","tagName":"Component 2","tagType":7,"tagTypeHex":"0x0007"},{"directoryName":"JPEG","description":"Cr component: Quantization table 1, Sampling factors 1 horiz/1 vert","tagName":"Component 3","tagType":8,"tagTypeHex":"0x0008"}],"errors":[]},{"name":"JFIF","version":258,"resUnits":0,"resY":100,"resX":100,"imageWidth":100,"imageHeight":100,"empty":false,"parent":null,"tagCount":6,"errorCount":0,"tags":[{"directoryName":"JFIF","description":"1.2","tagName":"Version","tagType":5,"tagTypeHex":"0x0005"},{"directoryName":"JFIF","description":"none","tagName":"Resolution Units","tagType":7,"tagTypeHex":"0x0007"},{"directoryName":"JFIF","description":"100 dots","tagName":"X Resolution","tagType":8,"tagTypeHex":"0x0008"},{"directoryName":"JFIF","description":"100 dots","tagName":"Y Resolution","tagType":10,"tagTypeHex":"0x000a"},{"directoryName":"JFIF","description":"0","tagName":"Thumbnail Width Pixels","tagType":12,"tagTypeHex":"0x000c"},{"directoryName":"JFIF","description":"0","tagName":"Thumbnail Height Pixels","tagType":13,"tagTypeHex":"0x000d"}],"errors":[]},{"name":"Ducky","empty":false,"parent":null,"tagCount":2,"errorCount":0,"tags":[{"directoryName":"Ducky","description":"64","tagName":"Quality","tagType":1,"tagTypeHex":"0x0001"},{"directoryName":"Ducky","description":"Oct 1989: Portrait of Sachin Tendulkar of India in Lahore, Pakistan. \\ Mandatory Credit: Ben Radford/Allsport","tagName":"Comment","tagType":2,"tagTypeHex":"0x0002"}],"errors":[]},{"name":"Adobe JPEG","empty":false,"parent":null,"tagCount":4,"errorCount":0,"tags":[{"directoryName":"Adobe JPEG","description":"25600","tagName":"DCT Encode Version","tagType":0,"tagTypeHex":"0x0000"},{"directoryName":"Adobe JPEG","description":"192","tagName":"Flags 0","tagType":1,"tagTypeHex":"0x0001"},{"directoryName":"Adobe JPEG","description":"0","tagName":"Flags 1","tagType":2,"tagTypeHex":"0x0002"},{"directoryName":"Adobe JPEG","description":"YCbCr","tagName":"Color Transform","tagType":3,"tagTypeHex":"0x0003"}],"errors":[]},{"name":"Huffman","numberOfTables":4,"typical":false,"optimized":true,"empty":false,"parent":null,"tagCount":1,"errorCount":0,"tags":[{"directoryName":"Huffman","description":"4 Huffman tables","tagName":"Number of Tables","tagType":1,"tagTypeHex":"0x0001"}],"errors":[]},{"name":"File Type","empty":false,"parent":null,"tagCount":4,"errorCount":0,"tags":[{"directoryName":"File Type","description":"JPEG","tagName":"Detected File Type Name","tagType":1,"tagTypeHex":"0x0001"},{"directoryName":"File Type","description":"Joint Photographic Experts Group","tagName":"Detected File Type Long Name","tagType":2,"tagTypeHex":"0x0002"},{"directoryName":"File Type","description":"image/jpeg","tagName":"Detected MIME Type","tagType":3,"tagTypeHex":"0x0003"},{"directoryName":"File Type","description":"jpg","tagName":"Expected File Name Extension","tagType":4,"tagTypeHex":"0x0004"}],"errors":[]}],"directoryCount":6} 505 | ``` 506 | 507 | Also, you should see the following output in the logs: 508 | 509 | ```shell 510 | imageUrl: https://objectstorage.us-phoenix-1.oraclecloud.com/n/tenant-namespace/b/object-upload-NNN/o/sachin-in.jpg 511 | {"directories":[{"name":"JPEG","imageWidth":500,"imageHeight":324,"numberOfComponents":3,"empty":false,"parent":null,"tagCount":8,"errorCount":0,"tags":[{"directoryName":"JPEG","description":"Baseline","tagName":"Compression Type","tagType":-3,"tagTypeHex":"0xfffffffd"},{"directoryName":"JPEG","description":"8 bits","tagName":"Data Precision","tagType":0,"tagTypeHex":"0x0000"},{"directoryName":"JPEG","description":"324 pixels","tagName":"Image Height","tagType":1,"tagTypeHex":"0x0001"},{"directoryName":"JPEG","description":"500 pixels","tagName":"Image Width","tagType":3,"tagTypeHex":"0x0003"},{"directoryName":"JPEG","description":"3","tagName":"Number of Components","tagType":5,"tagTypeHex":"0x0005"},{"directoryName":"JPEG","description":"Y component: Quantization table 0, Sampling factors 1 horiz/1 vert","tagName":"Component 1","tagType":6,"tagTypeHex":"0x0006"},{"directoryName":"JPEG","description":"Cb component: Quantization table 1, Sampling factors 1 horiz/1 vert","tagName":"Component 2","tagType":7,"tagTypeHex":"0x0007"},{"directoryName":"JPEG","description":"Cr component: Quantization table 1, Sampling factors 1 horiz/1 vert","tagName":"Component 3","tagType":8,"tagTypeHex":"0x0008"}],"errors":[]},{"name":"JFIF","version":258,"resUnits":0,"resY":100,"resX":100,"imageWidth":100,"imageHeight":100,"empty":false,"parent":null,"tagCount":6,"errorCount":0,"tags":[{"directoryName":"JFIF","description":"1.2","tagName":"Version","tagType":5,"tagTypeHex":"0x0005"},{"directoryName":"JFIF","description":"none","tagName":"Resolution Units","tagType":7,"tagTypeHex":"0x0007"},{"directoryName":"JFIF","description":"100 dots","tagName":"X Resolution","tagType":8,"tagTypeHex":"0x0008"},{"directoryName":"JFIF","description":"100 dots","tagName":"Y Resolution","tagType":10,"tagTypeHex":"0x000a"},{"directoryName":"JFIF","description":"0","tagName":"Thumbnail Width Pixels","tagType":12,"tagTypeHex":"0x000c"},{"directoryName":"JFIF","description":"0","tagName":"Thumbnail Height Pixels","tagType":13,"tagTypeHex":"0x000d"}],"errors":[]},{"name":"Ducky","empty":false,"parent":null,"tagCount":2,"errorCount":0,"tags":[{"directoryName":"Ducky","description":"64","tagName":"Quality","tagType":1,"tagTypeHex":"0x0001"},{"directoryName":"Ducky","description":"Oct 1989: Portrait of Sachin Tendulkar of India in Lahore, Pakistan. \\ Mandatory Credit: Ben Radford/Allsport","tagName":"Comment","tagType":2,"tagTypeHex":"0x0002"}],"errors":[]},{"name":"Adobe JPEG","empty":false,"parent":null,"tagCount":4,"errorCount":0,"tags":[{"directoryName":"Adobe JPEG","description":"25600","tagName":"DCT Encode Version","tagType":0,"tagTypeHex":"0x0000"},{"directoryName":"Adobe JPEG","description":"192","tagName":"Flags 0","tagType":1,"tagTypeHex":"0x0001"},{"directoryName":"Adobe JPEG","description":"0","tagName":"Flags 1","tagType":2,"tagTypeHex":"0x0002"},{"directoryName":"Adobe JPEG","description":"YCbCr","tagName":"Color Transform","tagType":3,"tagTypeHex":"0x0003"}],"errors":[]},{"name":"Huffman","numberOfTables":4,"typical":false,"optimized":true,"empty":false,"parent":null,"tagCount":1,"errorCount":0,"tags":[{"directoryName":"Huffman","description":"4 Huffman tables","tagName":"Number of Tables","tagType":1,"tagTypeHex":"0x0001"}],"errors":[]},{"name":"File Type","empty":false,"parent":null,"tagCount":4,"errorCount":0,"tags":[{"directoryName":"File Type","description":"JPEG","tagName":"Detected File Type Name","tagType":1,"tagTypeHex":"0x0001"},{"directoryName":"File Type","description":"Joint Photographic Experts Group","tagName":"Detected File Type Long Name","tagType":2,"tagTypeHex":"0x0002"},{"directoryName":"File Type","description":"image/jpeg","tagName":"Detected MIME Type","tagType":3,"tagTypeHex":"0x0003"},{"directoryName":"File Type","description":"jpg","tagName":"Expected File Name Extension","tagType":4,"tagTypeHex":"0x0004"}],"errors":[]}],"directoryCount":6} 512 | ``` 513 | 514 | And we see that our function works as expected. Now let's use a cloud event rule 515 | to trigger the function! 516 | 517 | 518 | ## Update the OCI Event rule action 519 | 520 | Let's go back to OCI Events service console, edit our rule and add a new action. 521 | In this case, we want to call our serverless function. 522 | 523 | ![user input](images/userinput.png) 524 | Under Actions, click "Add Action" and select the function created above. 525 | 526 | >``` 527 | > Add Action 528 | > 529 | > Action Type: Functions 530 | > Compartment: select-your-lab-compartment 531 | > Application: labapp-NNN 532 | > Function: cloud-events-demo-fn 533 | >``` 534 | 535 | ![Edit rule action](images/edit-rule-action.jpg) 536 | 537 | Now that we have set up the rule to trigger your function when your upload an 538 | image to your Object Storage bucket, let's try an end-to-end test. 539 | 540 | ## End-to-end test 541 | 542 | Let's upload an image file to your Object Storage bucket and see your function 543 | triggered automatically by OCI Events service! 544 | 545 | ![user input](images/userinput.png) 546 | Go to your bucket details page in the OCI Object Storage console and upload an 547 | image. Note: If you wish to upload the same image 548 | [sachin-in.jpg](/oci-event-triggers/sachin-in.jpg) again, make sure you delete 549 | the existing image from your bucket and then upload the same image again. 550 | 551 | 552 | ![user input](images/userinput.png) 553 | In about 1-2 minutes, check your function logs in PaperTrail! You should see the 554 | following: 555 | 556 | ![Check function logs](images/function-logs.jpg) 557 | 558 | 559 | ## Wrap Up 560 | 561 | Congratulations! In this lab we created a serverless function (to extract image 562 | metadata) that is automatically triggered when an image is uploaded to a given 563 | OCI Object Storage bucket. We learned that cloud event rules can be tied to 564 | various actions within our OCI tenancy such as Object Storage activities and can 565 | automatically trigger serverless Functions, and send Notifications. 566 | 567 | Before you go, don't forget to mark your bucket (object-upload-NNN) as "Private" again! 568 | 569 | ![user input](images/userinput.png) 570 | Click on "Edit Visibility" and select "Private": 571 | 572 | ![Make bucket public](images/make-bucket-private.jpg) 573 | 574 | 575 | NEXT: [*Functions Clients-OCI SDK*](8-Functions-Clients-SDK.md), UP: 576 | [*Labs*](1-Labs.md), HOME: [*INDEX*](README.md) 577 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Oracle Functions Lab 2 | 3 | In this lab you will explore serverless computing using Oracle Functions. 4 | 5 | ## Oracle Functions and Fn 6 | 7 | Oracle Functions is built on the open source Fn Project and so much of the 8 | development experience is exactly the same so any experience you have with Fn 9 | will be 100% applicable. The key differences are that Oracle Functions is a 10 | managed service so there's no need to start and manage an Fn server (it's truly 11 | serverless!) and being a cloud service Oracle Functions is integrated with the 12 | Oracle Cloud Infrastructure (OCI) Idenity and Acess Management (IAM), 13 | Networking, Events and other services. 14 | 15 | ## Overview 16 | 17 | This tutorial will show you how to setup your functions development environment 18 | and then guide you through a series of labs focusing on different topics 19 | including: 20 | 21 | 1. Function Basics 22 | 2. Java Functions and Unit testing 23 | 3. Troubleshooting Functions 24 | 4. Automatically invoking functions with OCI Events service 25 | 5. Functions clients including the OCI SDK 26 | 6. Calling functions using oci-curl 27 | 7. Customizing the function execution environment 28 | 29 | Step one is getting your environment setup. 30 | 31 | NEXT: [*Setup*](0-Setup.md) 32 | 33 | 34 | -------------------------------------------------------------------------------- /functions-sdk/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | com.test 6 | fn-java-sdk-invokebyid 7 | 1.0-SNAPSHOT 8 | jar 9 | 10 | UTF-8 11 | 1.8 12 | 1.8 13 | 14 | 15 | 16 | 17 | 18 | com.oracle.oci.sdk 19 | oci-java-sdk-bom 20 | 1.6.1 21 | pom 22 | import 23 | 24 | 25 | 26 | 27 | 28 | 29 | com.oracle.oci.sdk 30 | oci-java-sdk-functions 31 | 32 | 33 | 34 | 35 | 36 | 37 | org.apache.maven.plugins 38 | maven-dependency-plugin 39 | 40 | 41 | copy-dependencies 42 | prepare-package 43 | 44 | copy-dependencies 45 | 46 | 47 | 48 | ${project.build.directory}/libs 49 | 50 | 51 | 52 | 53 | 54 | 55 | org.apache.maven.plugins 56 | maven-jar-plugin 57 | 58 | 59 | 60 | true 61 | libs/ 62 | com.example.fn.InvokeById 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /functions-sdk/src/main/java/com/example/fn/InvokeById.java: -------------------------------------------------------------------------------- 1 | /*** 2 | * @author shaunsmith 3 | * @author abhirockzz 4 | */ 5 | 6 | package com.example.fn; 7 | 8 | import java.nio.charset.StandardCharsets; 9 | 10 | import org.apache.commons.io.IOUtils; 11 | 12 | import com.oracle.bmc.auth.AuthenticationDetailsProvider; 13 | import com.oracle.bmc.auth.ConfigFileAuthenticationDetailsProvider; 14 | import com.oracle.bmc.functions.FunctionsInvokeClient; 15 | import com.oracle.bmc.functions.requests.InvokeFunctionRequest; 16 | import com.oracle.bmc.functions.responses.InvokeFunctionResponse; 17 | import com.oracle.bmc.util.StreamUtils; 18 | 19 | public class InvokeById { 20 | 21 | static String USAGE = "Usage: java -jar .jar []"; 22 | 23 | public static void main(String[] args) throws Exception { 24 | 25 | if (args.length < 3) { 26 | System.out.println(USAGE); 27 | System.exit(-1); 28 | } 29 | 30 | String ociProfile = args[0]; 31 | String invokeEndpointURL = args[1]; 32 | String functionId = args[2]; 33 | String payload = args.length == 4 ? args[3] : ""; 34 | 35 | AuthenticationDetailsProvider authProvider = new ConfigFileAuthenticationDetailsProvider(ociProfile); 36 | try (FunctionsInvokeClient fnInvokeClient = new FunctionsInvokeClient(authProvider)) { 37 | fnInvokeClient.setEndpoint(invokeEndpointURL); 38 | 39 | InvokeFunctionRequest ifr = InvokeFunctionRequest.builder().functionId(functionId) 40 | .invokeFunctionBody(StreamUtils.createByteArrayInputStream(payload.getBytes())).build(); 41 | 42 | System.out.println("Invoking function endpoint - " + invokeEndpointURL + " with payload [" + payload + "]"); 43 | InvokeFunctionResponse resp = fnInvokeClient.invokeFunction(ifr); 44 | System.out.println(IOUtils.toString(resp.getInputStream(), StandardCharsets.UTF_8)); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /images/apikeys-fingerprint.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaunsmith/functionslab/5a9dad48646fae8554060b4dc78b1d86cfae740d/images/apikeys-fingerprint.png -------------------------------------------------------------------------------- /images/appliance-to-import.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaunsmith/functionslab/5a9dad48646fae8554060b4dc78b1d86cfae740d/images/appliance-to-import.jpg -------------------------------------------------------------------------------- /images/application-createdialog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaunsmith/functionslab/5a9dad48646fae8554060b4dc78b1d86cfae740d/images/application-createdialog.png -------------------------------------------------------------------------------- /images/applications-compartment.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaunsmith/functionslab/5a9dad48646fae8554060b4dc78b1d86cfae740d/images/applications-compartment.png -------------------------------------------------------------------------------- /images/applications-none.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaunsmith/functionslab/5a9dad48646fae8554060b4dc78b1d86cfae740d/images/applications-none.png -------------------------------------------------------------------------------- /images/auth-generate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaunsmith/functionslab/5a9dad48646fae8554060b4dc78b1d86cfae740d/images/auth-generate.png -------------------------------------------------------------------------------- /images/auth-token-copy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaunsmith/functionslab/5a9dad48646fae8554060b4dc78b1d86cfae740d/images/auth-token-copy.png -------------------------------------------------------------------------------- /images/auth-token-name.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaunsmith/functionslab/5a9dad48646fae8554060b4dc78b1d86cfae740d/images/auth-token-name.png -------------------------------------------------------------------------------- /images/auth-tokens.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaunsmith/functionslab/5a9dad48646fae8554060b4dc78b1d86cfae740d/images/auth-tokens.png -------------------------------------------------------------------------------- /images/create-application.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaunsmith/functionslab/5a9dad48646fae8554060b4dc78b1d86cfae740d/images/create-application.png -------------------------------------------------------------------------------- /images/create-bucket.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaunsmith/functionslab/5a9dad48646fae8554060b4dc78b1d86cfae740d/images/create-bucket.png -------------------------------------------------------------------------------- /images/create-email-subscription.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaunsmith/functionslab/5a9dad48646fae8554060b4dc78b1d86cfae740d/images/create-email-subscription.jpg -------------------------------------------------------------------------------- /images/create-rule-action.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaunsmith/functionslab/5a9dad48646fae8554060b4dc78b1d86cfae740d/images/create-rule-action.jpg -------------------------------------------------------------------------------- /images/create-rule-event.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaunsmith/functionslab/5a9dad48646fae8554060b4dc78b1d86cfae740d/images/create-rule-event.jpg -------------------------------------------------------------------------------- /images/create-topic.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaunsmith/functionslab/5a9dad48646fae8554060b4dc78b1d86cfae740d/images/create-topic.jpg -------------------------------------------------------------------------------- /images/createdialog.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaunsmith/functionslab/5a9dad48646fae8554060b4dc78b1d86cfae740d/images/createdialog.jpg -------------------------------------------------------------------------------- /images/dashboard.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaunsmith/functionslab/5a9dad48646fae8554060b4dc78b1d86cfae740d/images/dashboard.jpg -------------------------------------------------------------------------------- /images/edit-rule-action.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaunsmith/functionslab/5a9dad48646fae8554060b4dc78b1d86cfae740d/images/edit-rule-action.jpg -------------------------------------------------------------------------------- /images/events.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaunsmith/functionslab/5a9dad48646fae8554060b4dc78b1d86cfae740d/images/events.jpg -------------------------------------------------------------------------------- /images/fnlab-ova.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaunsmith/functionslab/5a9dad48646fae8554060b4dc78b1d86cfae740d/images/fnlab-ova.jpg -------------------------------------------------------------------------------- /images/function-logs.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaunsmith/functionslab/5a9dad48646fae8554060b4dc78b1d86cfae740d/images/function-logs.jpg -------------------------------------------------------------------------------- /images/functions-not-available.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaunsmith/functionslab/5a9dad48646fae8554060b4dc78b1d86cfae740d/images/functions-not-available.png -------------------------------------------------------------------------------- /images/functions-unavailable.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaunsmith/functionslab/5a9dad48646fae8554060b4dc78b1d86cfae740d/images/functions-unavailable.png -------------------------------------------------------------------------------- /images/import-appliance.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaunsmith/functionslab/5a9dad48646fae8554060b4dc78b1d86cfae740d/images/import-appliance.jpg -------------------------------------------------------------------------------- /images/import-settings.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaunsmith/functionslab/5a9dad48646fae8554060b4dc78b1d86cfae740d/images/import-settings.jpg -------------------------------------------------------------------------------- /images/importing-ova.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaunsmith/functionslab/5a9dad48646fae8554060b4dc78b1d86cfae740d/images/importing-ova.jpg -------------------------------------------------------------------------------- /images/labapp-nodefn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaunsmith/functionslab/5a9dad48646fae8554060b4dc78b1d86cfae740d/images/labapp-nodefn.png -------------------------------------------------------------------------------- /images/labapp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaunsmith/functionslab/5a9dad48646fae8554060b4dc78b1d86cfae740d/images/labapp.png -------------------------------------------------------------------------------- /images/linux-desktop.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaunsmith/functionslab/5a9dad48646fae8554060b4dc78b1d86cfae740d/images/linux-desktop.jpg -------------------------------------------------------------------------------- /images/logdestination.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaunsmith/functionslab/5a9dad48646fae8554060b4dc78b1d86cfae740d/images/logdestination.jpg -------------------------------------------------------------------------------- /images/login-new-password.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaunsmith/functionslab/5a9dad48646fae8554060b4dc78b1d86cfae740d/images/login-new-password.png -------------------------------------------------------------------------------- /images/login-user.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaunsmith/functionslab/5a9dad48646fae8554060b4dc78b1d86cfae740d/images/login-user.png -------------------------------------------------------------------------------- /images/login.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaunsmith/functionslab/5a9dad48646fae8554060b4dc78b1d86cfae740d/images/login.png -------------------------------------------------------------------------------- /images/logout.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaunsmith/functionslab/5a9dad48646fae8554060b4dc78b1d86cfae740d/images/logout.png -------------------------------------------------------------------------------- /images/logs.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaunsmith/functionslab/5a9dad48646fae8554060b4dc78b1d86cfae740d/images/logs.jpg -------------------------------------------------------------------------------- /images/make-bucket-private.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaunsmith/functionslab/5a9dad48646fae8554060b4dc78b1d86cfae740d/images/make-bucket-private.jpg -------------------------------------------------------------------------------- /images/make-bucket-public.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaunsmith/functionslab/5a9dad48646fae8554060b4dc78b1d86cfae740d/images/make-bucket-public.jpg -------------------------------------------------------------------------------- /images/notifications-nav-menu.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaunsmith/functionslab/5a9dad48646fae8554060b4dc78b1d86cfae740d/images/notifications-nav-menu.jpg -------------------------------------------------------------------------------- /images/object-storage-menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaunsmith/functionslab/5a9dad48646fae8554060b4dc78b1d86cfae740d/images/object-storage-menu.png -------------------------------------------------------------------------------- /images/oci-events-nav-menu.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaunsmith/functionslab/5a9dad48646fae8554060b4dc78b1d86cfae740d/images/oci-events-nav-menu.jpg -------------------------------------------------------------------------------- /images/oci-events-service.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaunsmith/functionslab/5a9dad48646fae8554060b4dc78b1d86cfae740d/images/oci-events-service.jpg -------------------------------------------------------------------------------- /images/papertrail-dashboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaunsmith/functionslab/5a9dad48646fae8554060b4dc78b1d86cfae740d/images/papertrail-dashboard.png -------------------------------------------------------------------------------- /images/papertrail-logs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaunsmith/functionslab/5a9dad48646fae8554060b4dc78b1d86cfae740d/images/papertrail-logs.png -------------------------------------------------------------------------------- /images/region-selection.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaunsmith/functionslab/5a9dad48646fae8554060b4dc78b1d86cfae740d/images/region-selection.png -------------------------------------------------------------------------------- /images/region.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaunsmith/functionslab/5a9dad48646fae8554060b4dc78b1d86cfae740d/images/region.png -------------------------------------------------------------------------------- /images/select-phoenix.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaunsmith/functionslab/5a9dad48646fae8554060b4dc78b1d86cfae740d/images/select-phoenix.png -------------------------------------------------------------------------------- /images/settings.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaunsmith/functionslab/5a9dad48646fae8554060b4dc78b1d86cfae740d/images/settings.jpg -------------------------------------------------------------------------------- /images/tenancy-ocid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaunsmith/functionslab/5a9dad48646fae8554060b4dc78b1d86cfae740d/images/tenancy-ocid.png -------------------------------------------------------------------------------- /images/upload-image-to-bucket.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaunsmith/functionslab/5a9dad48646fae8554060b4dc78b1d86cfae740d/images/upload-image-to-bucket.jpg -------------------------------------------------------------------------------- /images/user-ocid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaunsmith/functionslab/5a9dad48646fae8554060b4dc78b1d86cfae740d/images/user-ocid.png -------------------------------------------------------------------------------- /images/userinput.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaunsmith/functionslab/5a9dad48646fae8554060b4dc78b1d86cfae740d/images/userinput.png -------------------------------------------------------------------------------- /images/usermenu-tenancy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaunsmith/functionslab/5a9dad48646fae8554060b4dc78b1d86cfae740d/images/usermenu-tenancy.png -------------------------------------------------------------------------------- /images/usermenu-user.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaunsmith/functionslab/5a9dad48646fae8554060b4dc78b1d86cfae740d/images/usermenu-user.png -------------------------------------------------------------------------------- /images/virtualbox-manager.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaunsmith/functionslab/5a9dad48646fae8554060b4dc78b1d86cfae740d/images/virtualbox-manager.jpg -------------------------------------------------------------------------------- /images/vnc-login.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaunsmith/functionslab/5a9dad48646fae8554060b4dc78b1d86cfae740d/images/vnc-login.jpg -------------------------------------------------------------------------------- /images/warning.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /oci-event-triggers/sachin-in.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaunsmith/functionslab/5a9dad48646fae8554060b4dc78b1d86cfae740d/oci-event-triggers/sachin-in.jpg --------------------------------------------------------------------------------