September 20, 2020 By IBM Instana Team 7 min read

Everybody loves REST APIs. With such interfaces, developers can easily hook into external systems and exchange data or trigger events. In this article, we’ll look into a mechanism that will allow anyone to both specify and rely on a strict API specification.

Prerequisites

Of course, you can read this article without ever leaving the browser, but it’s a lot more fun when following along. Here’s the list of things you might want to have:

  • wget or a similar tool to download things from the internet
  • A current Java runtime environment—the java command should be on your path
  • A Golang version of 1.12 on your path
  • A text editor of your choice

So, what’s the problem with REST APIs?

Whenever we’re working with foreign REST APIs, we need to rely on the public documentation that the vendor supplies. These documents need to be as accurate as possible. As time goes by, code will inevitably diverge from your documentation as you’ll most probably enhance the feature set and fix conceptual issues.

With the IBM Instana™ platform, we have chosen to evolve our REST API over time, and without a big bang. To be able to expose accurate documentation, we automatically publish machine-generated documentation on every release to our GitHub pages documentation.

In the document, there’s nothing fancy—an HTML documentation, with some code snippets and endpoint specifications—and time to create code resembling our model into your consumer code, right? Not quite. There’s a quick and easy path to consume our API from within your system without wrestling too much with our data formats.

Enter OpenAPI

OpenAPI—the artist formerly known as “Swagger Specification”—is a project that aims to provide a path to happiness for both API consumers and vendors. By designing the specification rather than the endpoints directly, it aims at having a stable and binding contract between vendors and consumers.

What does such a specification look like? Here we have a shortened version of a specification for the infamous “PetShop” application:

openapi: "3.0.0"
info:
  version: 1.0.0
  title: Swagger Petstore
  license:
    name: MIT
servers:
  - url: http://petstore.swagger.io/v1
paths:
  /pets:
    get:
      summary: List all pets
      operationId: listPets
      tags:
        - pets
      parameters:
        - name: limit
          in: query
          description: How many items to return at one time (max 100)
          required: false
          schema:
            type: integer
            format: int32
      responses:
        '200':
          description: A paged array of pets
          headers:
            x-next:
              description: A link to the next page of responses
              schema:
                type: string
          content:
            application/json:    
              schema:
                $ref: "#/components/schemas/Pets"
components:
  schemas:
    Pet:
      type: object
      required:
        - id
        - name
      properties:
        id:
          type: integer
          format: int64
        name:
          type: string
        tag:
          type: string
    Pets:
      type: array
      items:
        $ref: "#/components/schemas/Pet"

You might already have glimpsed at the possible consequences for the API in question. It says we have an endpoint /pets with a possible GET HTTP operation on it that returns a paginated list of Pets. When we now look up the Pets object in the components section, we can quickly infer that it’s an array of Pet objects and the Pet object is specified, as well, and we can see that a Pet has mandatory name and id properties with associate types.

If we’re able to read such specifications with our own eyes, why can’t machines?

Enter OpenAPI Generator

In the OpenAPI ecosystem, there are multiple projects focused on processing specification documents. These projects range from validating documents to creating mock servers and testing utilities to generating client and even server code. The one we’ll focus on in this article is the OpenAPI Generator project.

The OpenAPI Generator project focuses on providing a convenient way to generate code you can work with from a given specification document from a single command.

To spice things up a bit, we’ll be working with our API specification. You can retrieve it through the generated documentation, YAML version or JSON version. This document is updated regularly within our release cycle, and you should be able to work with it right out of the box.

Enter Golang

The Go language is great for creating small binaries for almost every occasion in a straightforward fashion. We’ll use that language and we require at least version `1.12` for proper modules support.

First, we want to create our project directory. Download the OpenAPI Generator and see what it can do for us:

# create and enter the project directory
$ mkdir instana-golang-example && cd $_
# download the jar
$ wget https://repo1.maven.org/maven2/org/openapitools/openapi-generator-cli/5.0.0-beta/openapi-generator-cli-5.0.0-beta.jar \
  -O openapi-generator-cli.jar

# take peek at the different generators it offers us
$ java -jar openapi-generator-cli.jar

An impressive list, right? Next, we’ll use that jar file to generate a Go client for the IBM Instana REST API:

# download the Instana openapi spec
$ mkdir resources && wget https://instana.github.io/openapi/openapi.yaml -O resources/openapi.yaml
# generated code is ugly, we'll use "gofmt" to format it in the same step
$ GO_POST_PROCESS_FILE="gofmt -s -w" java -jar openapi-generator-cli.jar generate -i resources/openapi.yaml -g go \
    -o pkg/instana \
    --additional-properties packageName=instana \
    --additional-properties isGoSubmodule=true \
    --additional-properties packageVersion=1.185.824 \
    --type-mappings=object=interface{} \
    --enable-post-process-file \
    --skip-validate-spec

Done. Don’t believe me? Let’s have a look into the generated documentation: cat pkg/instana/README.md

We can now use it in our tiny new tool. Let’s create a go.mod file in the current directory to include the library in our project:

module github.com/instana/instana-openapi-golang-example

require github.com/instana/instana-openapi-golang-example/pkg/instana v1.185.824

replace github.com/instana/instana-openapi-golang-example/pkg/instana v1.185.824 => ./pkg/instana

Cool. We just linked our local API client into the Go modules dependency management. Go modules are awesome.

To show you how to use the client, we can create a small program that will query all the available search fields you can use in the IBM Instana platform.

Let’s create a main.go file in our current directory:

package main

import (
	"context"
	"fmt"
	"os"
	"strings"
	"time"

	"github.com/antihax/optional"
	"github.com/instana/instana-openapi-golang-example/pkg/instana"
)

func main() {
	// grab settings from environment
	apiKey := os.Getenv("INSTANA_KEY")
	tenant := os.Getenv("INSTANA_TENANT")
	unit := os.Getenv("INSTANA_UNIT")

	// create golang client specific configuration
	configuration := instana.NewConfiguration()
	host := fmt.Sprintf("%s-%s.instana.io", unit, tenant)
	configuration.Host = host
	configuration.BasePath = fmt.Sprintf("https://%s", host)

	client := instana.NewAPIClient(configuration)

	// Instana uses the `apiToken` prefix in the `Authorization` header
	auth := context.WithValue(context.Background(), instana.ContextAPIKey, instana.APIKey{
		Key:    apiKey,
		Prefix: "apiToken",
	})

	searchFieldResults, _, err := client.InfrastructureCatalogApi.GetInfrastructureCatalogSearchFields(auth)
	if err != nil {
		fmt.Println(err)

		os.Exit(1)
	}

	fmt.Println("Available search fields supported by Instana:")
	for _, field := range searchFieldResults {
		fmt.Println(fmt.Sprintf("%s (%s) - %s", field.Keyword, field.Context, field.Description))
	}
}

Next, we need an API token. To get yourself a shiny new token, head to your IBM Instana instance, and go to Settings > API Tokens > Add API Token.

Side note: We need the tenant and unit from your environment. When using our SaaS offering, you can get them from your environment’s URL. If you’re accessing your environment through https://qa-acme.instana.io, then acme is the tenant and qa is the unit.

That’s it. Let’s run our code and see what search fields we can use with the IBM Instana platform:

$ INSTANA_TENANT=acme INSTANA_UNIT=qa INSTANA_KEY=$your_token$ go run main.go

Available search fields supported by Instana:
entity.kubernetes.pod.name (entities) - Kubernetes Pod Name
event.specification.id (events) - Event specification ID
entity.ping.target (entities) - Ping target
entity.azure.service.sqlserver.name (entities) - The name of the SQL database server
entity.lxc.ip (entities) - LXC container IP
entity.jboss.serverName (entities) - JBoss Server Name
...

Making use of it

Listing the available tags is one thing, but nothing fancy, right? We want to live up to the hype and get metrics from a specific Kubernetes pod. Specifically, the CPU requests over time.

To facilitate this process, we need to find what we’re searching for. The IBM Instana platform organizes metrics by plug-ins. To see what plug-ins are available, we can query them:

// search for the kubernetes pod plugin
// https://instana.github.io/openapi/#operation/getInfrastructureCatalogPlugins
plugins, _, err := client.InfrastructureCatalogApi.GetInfrastructureCatalogPlugins(auth)
if err != nil {
	println(fmt.Errorf("Error reading plugins: %s", err))

	os.Exit(1)
}

println("Let's find ourselves some kubernetes pods!")

// search for kubernetes plugins:
for _, plugin := range plugins {
	if strings.Contains(plugin.Plugin, "kubernetes") {
		fmt.Printf("Found Kubernetes plugin %s with ID %s\n", plugin.Label, plugin.Plugin)
	}
}

The output will be very similar to this:

Metric for plugin `kubernetesPod`: Containers (ID: container_count)
Metric for plugin `kubernetesPod`: Memory Requests (ID: memoryRequests)
Metric for plugin `kubernetesPod`: CPU Limits (ID: cpuLimits)
Metric for plugin `kubernetesPod`: CPU Requests (ID: cpuRequests)
Metric for plugin `kubernetesPod`: Restarts (ID: restartCount)
Metric for plugin `kubernetesPod`: Memory Limits (ID: memoryLimits)

There you go. Our metric ID for getting CPU requests from the “kubernetesPod” plugin is “cpuRequests”.

Finding your snapshot

Next thing, knowing what metric from which plug-in we want, we need to find Kubernetes pods in our infrastructure inventory. The IBM Instana platform organizes infrastructure changes over time in a concept called “snapshot.” We need to find a snapshot that contains “kubernetesPod” entities and then we can go query those metrics.

When you deploy our IBM Instana agent through one of the supported methods to your Kubernetes cluster, the agent pods themselves will have names in the form of “instana-agent-ID”. Knowing this information, we can search for all snapshots that contain entities with values of instana-agent-* in the entity.kubernetes.pod.name field.

Do the same as you would query through the Dynamic Focus query:

// let's find all the Snapshots that involve Kubernetes pods!
// https://instana.github.io/openapi/#operation/getSnapshots
snapshotsWithKubernetesPods, _, err := client.InfrastructureMetricsApi.GetSnapshots(auth, &instana.GetSnapshotsOpts{
	Plugin: optional.NewString(kubernetesPodPluginID),

	// We know that clusters, when monitored with Instana usually have pods with a name of `instana-agent-*`
	Query: optional.NewString("entity.kubernetes.pod.name:instana-agent*"),

	// We can travel through time and query data from entities that are no more!
	Offline: optional.NewBool(true),

	// Instana uses Milliseconds accross the board
	WindowSize: optional.NewInt64(7 * 86400 * 1000),
	To:         optional.NewInt64(time.Now().Unix() * int64(time.Millisecond)),
})
if err != nil {
	println(fmt.Errorf("Error reading snapshots: %s", err))

	os.Exit(1)
}

for _, snapshot := range snapshotsWithKubernetesPods.Items {
	fmt.Printf("Kubernetes Pod %s on Host %s with snapshot ID %s \n", snapshot.Label, snapshot.Host, snapshot.SnapshotId)
}

The output will look like this:

Let's find a Kubernetes Pod that contains an Instana Agent
Kubernetes Pod instana-agent/instana-agent-1-fn69h (pod) on Host with snapshot ID fkHp5kkCvpBonSAkyZD03GagAL4
Kubernetes Pod instana-agent/instana-agent-1-7jlwd (pod) on Host with snapshot ID 2sCSGsxJGUJB3mpaQ7SVOHJlMmY
Kubernetes Pod instana-agent/instana-agent-1-7rqvm (pod) on Host with snapshot ID FU7AaxMkghV9vsoB05AEXdrZqpE

Retrieving the metrics

Now that we know the snapshot IDs, we can go and fetch the metrics from those snapshots:

println("Let's put everything together: Querying the cpuRequests pod metrics from a specific snapshot")

metricID := "cpuRequests"
metricsQuery := instana.GetCombinedMetrics{
	Plugin: kubernetesPodPluginID,
	Metrics: []string{
		metricID,
	},

	SnapshotIds: []string{
		snapshotsWithKubernetesPods.Items[0].SnapshotId,
	},

	TimeFrame: instana.TimeFrame{
		To:         time.Now().Unix() * 1000,
		WindowSize: 300000,
	},

	// 5 Minutes
	Rollup: 60,
}

metricsResult, _, err := client.InfrastructureMetricsApi.GetInfrastructureMetrics(auth, &instana.GetInfrastructureMetricsOpts{
	GetCombinedMetrics: optional.NewInterface(metricsQuery),
})

if err != nil {
	println(fmt.Errorf("Error reading metrics: %s", err.(instana.GenericOpenAPIError)))

	os.Exit(1)
}

for _, metric := range metricsResult.Items {
	for _, bracket := range metric.Metrics[metricID] {
		parsedTime := time.Unix(0, int64(bracket[0])*int64(time.Millisecond))
		fmt.Printf("CPU requests of Kubernetes Pod %s at %s: %f\n", snapshotsWithKubernetesPods.Items[0].Label, parsedTime, bracket[1])
	}
}

The output will look like this:

Let's put everything together: Querying the cpuRequests pod metrics from a specific snapshot
CPU requests of Kubernetes Pod instana-agent/instana-agent-1-fn69h (pod) at 2020-09-03 10:56:06.656 +0000 UTC: 0.599900
CPU requests of Kubernetes Pod instana-agent/instana-agent-1-fn69h (pod) at 2020-09-03 10:58:17.728 +0000 UTC: 0.599900
CPU requests of Kubernetes Pod instana-agent/instana-agent-1-fn69h (pod) at 2020-09-03 10:58:17.728 +0000 UTC: 0.599900
CPU requests of Kubernetes Pod instana-agent/instana-agent-1-fn69h (pod) at 2020-09-03 11:00:28.8 +0000 UTC: 0.599900
CPU requests of Kubernetes Pod instana-agent/instana-agent-1-fn69h (pod) at 2020-09-03 11:00:28.8 +0000 UTC: 0.599900
CPU requests of Kubernetes Pod instana-agent/instana-agent-1-fn69h (pod) at 2020-09-03 11:02:39.872 +0000 UTC: 0.599900

Having read up to here, you learned how you can:

  • Create or, rather, generate a API client in Golang through an OpenAPI specification.
  • Use that client to query the IBM Instana REST API in a type-safe way.
  • Resolve metric values from a specific item from the IBM Instana platform’s vast, automatically created infrastructure store.

Summary

In this article, we learned how to generate an API client for a specific programming environment based on an OpenAPI specification that’s provided by a third party. This method is adaptable for many different projects and languages. An upside of this approach is that the contract is basically also the code that’s being executed to communicate with the remote API.

In case of the IBM Instana REST API, the OpenAPI makes it easy for us, and our clients to version our API specification and separate our on-premises product and SaaS offering, so you can always generate a client library for your specific environment.

The complete code is available on GitHub.

Get started with IBM Instana
Was this article helpful?
YesNo

More from IBM Instana

Probable Root Cause: Accelerating incident remediation with causal AI 

5 min read - It has been proven time and time again that a business application’s outages are very costly. The estimated cost of an average downtime can run USD 50,000 to 500,000 per hour, and more as businesses are actively moving to digitization. The complexity of applications is growing as well, so Site Reliability Engineers (SREs) require hours—and sometimes days—to identify and resolve problems.   To alleviate this problem, we have introduced the new feature Probable Root Cause as part of Intelligent Incident…

Observe GenAI with IBM Instana Observability

6 min read - The emergence of generative artificial intelligence (GenAI), powered by large language models (LLMs) has accelerated the widespread adoption of artificial intelligence. GenAI is proving to be very effective in tackling a variety of complex use cases with AI systems operating at levels that are comparable to humans. Organisations are quickly realizing the value of AI and its transformative potential for business, adding trillions of dollars to the economy. Given this emerging landscape, IBM Instana Observability is on a mission to…

Average 219% ROI: The Total Economic Impact™ of IBM Instana Observability

2 min read - What can your organization achieve with a modern observability solution? Data from a new Forrester Consulting study showed that a composite organization that used the IBM Instana™ Observability platform achieved a 219% ROI over three years. Likewise, it saw a 90% reduction in troubleshooting time by providing high fidelity data to the right people at the right time. About the study IBM commissioned Forrester to conduct the Total Economic Impact™ (TEI) study by interviewing four clients about the value of their…

IBM Newsletters

Get our newsletters and topic updates that deliver the latest thought leadership and insights on emerging trends.
Subscribe now More newsletters