Skip to content

Mixer Out Of Process Adapter Dev Guide

Martin Taillefer edited this page Oct 11, 2019 · 4 revisions

WARNING: This feature is under active development.

This document is for developers looking to build a gRPC back-end (a.k.a gRPC adapter) for Istio's Mixer. Adapters integrate Mixer with different infrastructure backends that deliver core functionality, such as logging, monitoring, quotas, ACL checking, and more. This guide explains the adapter model and adapter lifecycle, and also walks through the step-by-step instructions for creating a simple gRPC adapter.

Background

Mixer provides Istio's generic intermediation layer between application code and infrastructure backends such as quota systems, access control systems, logging, and so on. It is an attribute processing engine that uses operator-supplied configuration to map request attributes from the proxy into calls to these backend systems via a pluggable set of adapters. Adapters enable Mixer to expose a single consistent API, independent of the infrastructure backends in use. The exact set of adapters used at runtime is determined through operator configuration and can easily be extended to target new or custom infrastructure backends.

mixer architecture

Mixer structures its incoming attributes into a more useful form for adapters using templates. Templates describe the form of data dispatched to adapters when processing a request and the gRPC service interface that the adapter must implement to receive this data. The operator that configures Istio controls how this template-specific data is constructed and dispatched to adapters.

Out of the box, Mixer provides a suite of default templates. We strongly recommend that, when implementing adapters, you use Mixer's default templates. Though if they are not suitable for your particular needs you can also create your own templates along with adapters to consume the relevant data. Mixer also includes some (TODO point this to production gRPC adapters) built-in sample gRPC adapters by default, but users may need to implement their own to let Mixer send data to their chosen backend.

The roles of the template author, adapter author, and the operator can be summarized as:

  • The template author defines a template, which describes the data Mixer dispatches to adapters, and the interface that the adapter must implement to process that data. The supported set of templates within Mixer determines the various types of data an operator can configure Mixer to create and dispatch to the adapters.

  • The adapter author selects the templates he/she wants to support based on the functionality the adapter must provide. The adapter author's role is to implement the required set of template-specific interfaces to process the data dispatched by Mixer at runtime.

  • The operator defines what data should be collected (instances), where it can be sent (handlers), and when to send it (rules).

Template overview

To understand how an adapter receives and processes a template-specific instance, this section first provides details about various artifacts of a template that are relevant for adapter development.

As we saw in the previous section, a build of Mixer supports a set of templates, and every template defines a kind of data Mixer dispatches to adapters when processing a request, and also defines the interface for adapters to consume that data.

The following diagram shows the various components of a template.

template generated artifacts

We'll look at each of these in more detail below.

Template proto file

Templates are defined using a proto file with a message named 'Template'. Template is a simple proto message with no associated code. All of the Go artifacts used by adapters are automatically generated from the template protos.

Every template also has two additional properties associated with it:

  • Name: Every template has a unique name. Adapter code uses the name of the template to register with Mixer that it wants to consume Instance objects associated with a particular template. The template name is also used within operator config to provide template-specific fields to attribute mapping, which is used to create Instance objects.

  • Template Variety: Every template has a specific template_variety which can be either Check, Report, Quota or AttributeGenerator. The template and its variety determine the signatures of the methods that adapters must implement for consuming the associated instances. The template_variety also determines under which of the core Mixer behaviors, the instances for the templates should be created and dispatched to adapters. For example:

    • Check template variety instances are created and dispatched only during Mixer's Check API call.
    • Report template variety instances are created and dispatched only during Mixer's Report API call.
    • Quota template variety instances are created and dispatched only during Mixer's Check API call when queried for quota allocation.
    • AttributeGenerator template variety instances are created and dispatched to adapters for both Check, Report Mixer API calls. The processing of these templates happen during the supplementary attribute generation phase which happens before processing any other variety of templates. Adapters that handle AttributeGenerator templates are called attribute generating adapters. These adapters are responsible for generating output data, dictated by the template, which operators can use to create new attributes. These new attributes are combined with the attributes from the request to form the total set of attributes for the operation. These new attributes can therefore now be used by operators to configure instances of other check, report and quota variety templates.

Generated Go code

Individual templates are processed in order to produce five Go artifacts:

  • InstanceMsg struct: This defines the data that is passed to the adapters at request time. Mixer constructs objects of the Instance type, based on the request attributes and operator configuration.

  • OutputMsg struct (Only for ATTRIBUTE_GENERATOR templates): This defines the data that is returned by the adapters during the attribute generation phase (before other check, report, quota handling adapters are invoked). Based on operator configuration, Mixer constructs new attributes using the OutputMsg object.

  • Handler service: This defines the gRPC service RPC interface that Mixer uses to dispatch created InstanceMsg objects to the adapters at request time. Adapters must implement Handler services for all templates they support.

  • Type struct: If the datatype of a field in the InstanceMsg is dynamic (istio.policy.v1beta1.Value), the datatype of the value it will hold during request time is determined based on operator-supplied configuration. The type struct expresses the datatype of dynamic fields using the ValueType enum, which has 1:1 mapping between Go data types and its enum values.

Examples

These examples show three templates, one for each of the possible template_variety types. Each example shows a Template message, and the resulting generated Go code.

REPORT variety template

Template.proto

syntax = "proto3";

package metric;

import "mixer/adapter/model/v1beta1/type.proto";
import "mixer/adapter/model/v1beta1/extensions.proto";

option (istio.mixer.v1.config.template.template_variety) = TEMPLATE_VARIETY_REPORT;

// Metric represents a single piece of data to report.
message Template {
   // The value being reported.
   istio.mixer.adapter.model.v1beta1.Value value = 1;

   // The unique identity of the particular metric to report.
   map<string, istio.mixer.adapter.model.v1beta1.Value> dimensions = 2;
}

Auto-generated Go code used by adapter implementation

// HandleMetricService is implemented by backends that wants to handle request-time 'metric' instances.
service HandleMetricService {
    // HandleMetric is called by Mixer at request-time to deliver 'metric' instances to the backend.
    rpc HandleMetric(HandleMetricRequest) returns (istio.mixer.adapter.model.v1beta1.ReportResult);

}

// Request message for HandleMetric method.
message HandleMetricRequest {

    // 'metric' instances.
    repeated InstanceMsg instances = 1;

    // Adapter specific handler configuration.
    //
    // Note: Backends can also implement [InfrastructureBackend][https://github.com/istio/api/blob/master/mixer/adapter/model/v1beta1/infrastructure_backend.proto]
    // service and therefore opt to receive handler configuration during session creation through `InfrastructureBackend.CreateSession`
    // call. In that case, adapter_config will have type_url as 'google.protobuf.Any.type_url' and would contain string
    // value of session_id (returned from InfrastructureBackend.CreateSession).
    google.protobuf.Any adapter_config = 2;

    // Id to dedupe identical requests from Mixer.
    string dedup_id = 3;
}

// Contains instance payload for 'metric' template. This is passed to infrastructure backends during request-time
// through HandleMetricService.HandleMetric.
message InstanceMsg {

    // Name of the instance as specified in configuration.
    string name = 72295727;

    // The value being reported.
    istio.policy.v1beta1.Value value = 1;

    // The unique identity of the particular metric to report.
    map<string, istio.policy.v1beta1.Value> dimensions = 2;
}

// Contains inferred type information about specific instance of 'metric' template. This is passed to
// infrastructure backends during configuration-time through [InfrastructureBackend.CreateSession][TODO: Link to this fragment].
message Type {

    // The value being reported.
    istio.policy.v1beta1.ValueType value = 1;

    // The unique identity of the particular metric to report.
    map<string, istio.policy.v1beta1.ValueType> dimensions = 2;
}

CHECK variety template

Template.proto

syntax = "proto3";

package listentry;

import "mixer/adapter/model/v1beta1/extensions.proto";

option (istio.mixer.v1.config.template.template_variety) = TEMPLATE_VARIETY_CHECK;

// ListEntry is used to verify the presence/absence of a string
// within a list.
message Template {
    // Specifies the entry to verify in the list.
    string value = 1;
}

Auto-generated Go code used by adapter implementation

// HandleListEntryService is implemented by backends that wants to handle request-time 'listentry' instances.
service HandleListEntryService {
    // HandleListEntry is called by Mixer at request-time to deliver 'listentry' instances to the backend.
    rpc HandleListEntry(HandleListEntryRequest) returns (istio.mixer.adapter.model.v1beta1.CheckResult);

}

// Request message for HandleListEntry method.
message HandleListEntryRequest {

    // 'listentry' instance.
    InstanceMsg instance = 1;

    // Adapter specific handler configuration.
    //
    // Note: Backends can also implement [InfrastructureBackend](https://github.com/istio/api/blob/master/mixer/adapter/model/v1beta1/infrastructure_backend.proto)
    // service and therefore opt to receive handler configuration during session creation through `InfrastructureBackend.CreateSession`
    // call. In that case, adapter_config will have type_url as 'google.protobuf.Any.type_url' and would contain string
    // value of session_id (returned from InfrastructureBackend.CreateSession).
    google.protobuf.Any adapter_config = 2;

    // Id to dedupe identical requests from Mixer.
    string dedup_id = 3;
}

// Contains instance payload for 'listentry' template. This is passed to infrastructure backends during request-time
// through HandleListEntryService.HandleListEntry.
message InstanceMsg {

    // Name of the instance as specified in configuration.
    string name = 72295727;

    // Specifies the entry to verify in the list.
    string value = 1;

}

// Contains inferred type information about specific instance of 'listentry' template. This is passed to
// infrastructure backends during configuration-time through [InfrastructureBackend.CreateSession][TODO: Link to this fragment].
message Type {

}

QUOTA variety template

Template.proto

package quota;

import "policy/v1beta1/type.proto";
import "mixer/adapter/model/v1beta1/extensions.proto";

option (istio.mixer.adapter.model.v1beta1.template_variety) = TEMPLATE_VARIETY_QUOTA;

// The `quota` template represents a piece of data to check Quota for.
//
// When writing the configuration, the value for the fields associated with this template can either be a
// literal or an [expression](https://istio.io/docs/reference//config/policy-and-telemetry/expression-language/). Please note that if the datatype of a field is not istio.policy.v1beta1.Value,
// then the expression's [inferred type](https://istio.io/docs/reference//config/policy-and-telemetry/expression-language/#type-checking) must match the datatype of the field.
message Template {
    // The unique identity of the particular quota to manipulate.
    map<string, istio.policy.v1beta1.Value> dimensions = 1;
}

Auto-generated Go code used by adapter implementation

// HandleQuotaService is implemented by backends that wants to handle request-time 'quota' instances.
service HandleQuotaService {
    // HandleQuota is called by Mixer at request-time to deliver 'quota' instances to the backend.
    rpc HandleQuota(HandleQuotaRequest) returns (istio.mixer.adapter.model.v1beta1.QuotaResult);

}

// Request message for HandleQuota method.
message HandleQuotaRequest {

    // 'quota' instance.
    InstanceMsg instance = 1;

    // Adapter specific handler configuration.
    //
    // Note: Backends can also implement [InfrastructureBackend][https://istio.io/docs/reference/config/mixer/istio.mixer.adapter.model.v1beta1.html#InfrastructureBackend]
    // service and therefore opt to receive handler configuration during session creation through [InfrastructureBackend.CreateSession][TODO: Link to this fragment]
    // call. In that case, adapter_config will have type_url as 'google.protobuf.Any.type_url' and would contain string
    // value of session_id (returned from InfrastructureBackend.CreateSession).
    google.protobuf.Any adapter_config = 2;

    // Id to dedupe identical requests from Mixer.
    string dedup_id = 3;

    // Expresses the quota allocation request.
    istio.mixer.adapter.model.v1beta1.QuotaRequest quota_request = 4;
}

// Contains instance payload for 'quota' template. This is passed to infrastructure backends during request-time
// through HandleQuotaService.HandleQuota.
message InstanceMsg {

    // Name of the instance as specified in configuration.
    string name = 72295727;

    // The unique identity of the particular quota to manipulate.
    map<string, istio.policy.v1beta1.Value> dimensions = 1;

}

// Contains inferred type information about specific instance of 'quota' template. This is passed to
// infrastructure backends during configuration-time through [InfrastructureBackend.CreateSession][TODO: Link to this fragment].
message Type {

    // The unique identity of the particular quota to manipulate.
    map<string, istio.policy.v1beta1.ValueType> dimensions = 1;

}

Adapter lifecycle

This section explains various Mixer states during which it interacts with adapters. This is important in order to understand how to implement adapter code and manage the states of various objects within the adapter code itself, such as remote connections and local caches. This section explains how an adapter implementation is usually structured to achieve clear separation of concerns between configuration-time and request-time responsibilities.

Adapter implementation choices:

gRPC adapters can be either session based or no-session based.

  • session based model: NOTE: Not implemented yet. Mixer passes the adapter specific configuration to the gRPC adapter only once and establishes a session using a session identifier. All future communications between Mixer and the adapter uses the session identifier which refers to the previously passed in configuration data. Session based adapters must also implement the InfrastructureBackend service, along with template specific Handle service. Mixer creates and closes a session with the adapter using the InfrastructureBackend rpcs. Mixer calls the Validate function, followed by CreateSession. The session_id returned from CreateSession is used to make subsequent request-time Handle calls and the eventual CloseSession function. Mixer assumes that, given the session_id, the backend can retrieve the necessary context to serve the Handle calls that contains the request-time payload (template-specific InstanceMsg protobuf).

  • no-session based model: Adapter only implements template specific Handle service (example HandleMetricService, HandlerQuotaService etc.). Mixer only communicates with adapter during request time. On each Handle rpc call the Request message contains the entire adapter configuration along with the InstanceMsg payload. For example, the HandleMetricRequest is passed on each request-time HandleMetric rpc and contains the metric.InstanceMsg as well as the adapter configuration.

Mixer and no-session adapter interactions

High level flow between Mixer and adapters.

flow: mixer and no-session gRPC adapter interaction

Mixer dispatches InstanceMsg objects to the adapter based on the rules configured by the operator. Mixer does this by invoking the Handle RPC on the adapter gRPC service.

Plug adapter into Mixer

Every adapter needs to provide a resource config that the operator need to ingest into Mixer's configuration store. Creating resource config is easy; Use the mixer/tool/mixgen tool to create an adapter resource, or use the mixer_codegen.sh with -a flag passing the adapter configuration. mixer_codegen.sh will create the <adapter name>.yaml file that defines the adapter's resource definition. Adapter developer makes this file available for operators to use and ingest into their Mixer configuration store.

//go:generate $REPO_ROOT/bin/mixer_codegen.sh -a mixer/adapter/prometheus/config/config.proto -x "-n prometheus -s=false -t metric "

In both cases the command is executed as part of the go generate step and both generate prometheus adapter's configuration. The generated resource defines the adapter as no-session that supports metric template and also embeds the adapter's configuration proto. Here is how the generated resource yaml looks

# this config is created through command
# mixgen adapter -c $GOPATH/src/istio.io/istio/mixer/adapter/prometheus/config/config.proto_descriptor -o $GOPATH/src/istio.io/istio/mixer/adapter/prometheus/config -n prometheus -t metric -s=false
apiVersion: "config.istio.io/v1alpha2"
kind: adapter
metadata:
  name: prometheus
  namespace: istio-system
spec:
  description:
  session_based: false
  templates:
  - metric
  config: CsD3AgogZ29vZ2xlL3Byb3RvYnVmL2Rlc2NyaXB0b3.....

Once this resource is ingested into Mixer, operators can write handler configurations that references the adapter based on the name specified by the -n flag.

Notice::

  • The template name metric is referencing to a template resource which must also be ingested into Mixer's configuration store. metric template resource is always available for a default Mixer deployment. However, in case the template is custom or the custom Mixer build does not have the necessary template resource, operator will have to explicitly add it. It is a good practice for adapters to provide the template resources they depend on along side their adapter resource. Here is how a template resource yaml is created
//go:generate $REPO_ROOT/bin/mixer_codegen.sh -t mixer/template/apikey/template.proto

mixer_codegen.sh takes the template proto and generates the template-specific artifacts (template specific service along with request messages, as well as the template resource yaml file). Almost always every template proto has a corresponding mixer_codegen.sh invoked on it which generates the template specific artifacts within the same directory.

  • Connection information for the gRPC adapter: Operator needs the endpoint address of the gRPC adapter to write the handler's connection configuration. Using this information Mixer connects to the gRPC adapter. Adapter must therefore also document, in their config.proto or some web portal, the gRPC address that operator can use in their configuration.

Testing

We provide a simple adapter test framework. The framework instantiates an in-process Mixer gRPC server with a config store backed by the local filesystem, and also a Mixer gRPC client in test process, which allows stepping through adapter code in test cases. The test framework is implemented in pkg/adapter/test/integration.go. A sample test is provided to show how to use this test framework to test a prometheus reporting gRPC adapter.

Built-in templates

Mixer ships with a set of built-in templates that are ready to use by adapters:

Implementation walkthrough

Please refer to Out-Of-Process Adapter Walkthrough

Dev Environment

Writing Code

Pull Requests

Testing

Performance

Releases

Misc

Central Istiod

Security

Mixer

Pilot

Telemetry

Clone this wiki locally