Skip to content

Commit

Permalink
Merge pull request #142 from grafana/poc-js-api-redesign
Browse files Browse the repository at this point in the history
 Js API Refactoring
  • Loading branch information
pablochacin authored Apr 24, 2023
2 parents 689dd45 + 388549b commit 7d19c2d
Show file tree
Hide file tree
Showing 6 changed files with 120 additions and 60 deletions.
110 changes: 75 additions & 35 deletions pkg/api/api.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
// Package api implements a layer between javascript code (via goja)) and the disruptors
// allowing for validations and type conversions when needed
//
// The implementation of the JS API follows the design described in
package api

import (
"context"
"fmt"
"reflect"
"time"

"github.com/dop251/goja"
Expand All @@ -18,24 +21,48 @@ func convertValue(rt *goja.Runtime, value goja.Value, target interface{}) error
return Convert(value.Export(), target)
}

// JsPodDisruptor implements the JS interface for PodDisruptor
type JsPodDisruptor struct {
rt *goja.Runtime
disruptor disruptors.PodDisruptor
// buildObject returns the value as a
func buildObject(rt *goja.Runtime, value interface{}) (*goja.Object, error) {
obj := rt.NewObject()

t := reflect.TypeOf(value)
v := reflect.ValueOf(value)
for i := 0; i < t.NumMethod(); i++ {
name := t.Method(i).Name
f := v.MethodByName(name)
err := obj.Set(toCamelCase(name), f.Interface())
if err != nil {
return nil, err
}
}

return obj, nil
}

// jsDisruptor implements the JS interface for PodDisruptor
type jsDisruptor struct {
rt *goja.Runtime
disruptors.Disruptor
}

// Targets is a proxy method. Validates parameters and delegates to the PodDisruptor method
func (p *JsPodDisruptor) Targets() goja.Value {
targets, err := p.disruptor.Targets()
func (p *jsDisruptor) Targets() goja.Value {
targets, err := p.Disruptor.Targets()
if err != nil {
common.Throw(p.rt, fmt.Errorf("error getting kubernetes config path: %w", err))
}

return p.rt.ToValue(targets)
}

// InjectHTTPFaults is a proxy method. Validates parameters and delegates to the PodDisruptor method
func (p *JsPodDisruptor) InjectHTTPFaults(args ...goja.Value) {
// jsProtocolFaultInjector implements the JS interface for PodDisruptor
type jsProtocolFaultInjector struct {
rt *goja.Runtime
disruptors.ProtocolFaultInjector
}

// injectHTTPFaults is a proxy method. Validates parameters and delegates to the Protocol Disruptor method
func (p *jsProtocolFaultInjector) InjectHTTPFaults(args ...goja.Value) {
if len(args) < 2 {
common.Throw(p.rt, fmt.Errorf("HTTPFault and duration are required"))
}
Expand All @@ -60,14 +87,14 @@ func (p *JsPodDisruptor) InjectHTTPFaults(args ...goja.Value) {
}
}

err = p.disruptor.InjectHTTPFaults(fault, duration, opts)
err = p.ProtocolFaultInjector.InjectHTTPFaults(fault, duration, opts)
if err != nil {
common.Throw(p.rt, fmt.Errorf("error injecting fault: %w", err))
}
}

// InjectGrpcFaults is a proxy method. Validates parameters and delegates to the PodDisruptor method
func (p *JsPodDisruptor) InjectGrpcFaults(args ...goja.Value) {
func (p *jsProtocolFaultInjector) InjectGrpcFaults(args ...goja.Value) {
if len(args) < 2 {
common.Throw(p.rt, fmt.Errorf("GrpcFault and duration are required"))
}
Expand All @@ -92,35 +119,52 @@ func (p *JsPodDisruptor) InjectGrpcFaults(args ...goja.Value) {
}
}

err = p.disruptor.InjectGrpcFaults(fault, duration, opts)
err = p.ProtocolFaultInjector.InjectGrpcFaults(fault, duration, opts)
if err != nil {
common.Throw(p.rt, fmt.Errorf("error injecting fault: %w", err))
}
}

func buildJsPodDisruptor(rt *goja.Runtime, disruptor disruptors.PodDisruptor) (*goja.Object, error) {
jsDisruptor := JsPodDisruptor{
rt: rt,
disruptor: disruptor,
}
type jsPodDisruptor struct {
jsDisruptor
jsProtocolFaultInjector
}

obj := rt.NewObject()
err := obj.Set("targets", jsDisruptor.Targets)
if err != nil {
return nil, err
}
// buildJsPodDisruptor builds a goja object that implements the PodDisruptor API
func buildJsPodDisruptor(rt *goja.Runtime, disruptor disruptors.PodDisruptor) (*goja.Object, error) {
d := &jsPodDisruptor{
jsDisruptor: jsDisruptor{
rt: rt,
Disruptor: disruptor,
},
jsProtocolFaultInjector: jsProtocolFaultInjector{
rt: rt,
ProtocolFaultInjector: disruptor,
},
}

return buildObject(rt, d)
}

err = obj.Set("injectHTTPFaults", jsDisruptor.InjectHTTPFaults)
if err != nil {
return nil, err
}
type jsServiceDisruptor struct {
jsDisruptor
jsProtocolFaultInjector
}

err = obj.Set("injectGrpcFaults", jsDisruptor.InjectGrpcFaults)
if err != nil {
return nil, err
// buildJsServiceDisruptor builds a goja object that implements the ServiceDisruptor API
func buildJsServiceDisruptor(rt *goja.Runtime, disruptor disruptors.ServiceDisruptor) (*goja.Object, error) {
d := &jsServiceDisruptor{
jsDisruptor: jsDisruptor{
rt: rt,
Disruptor: disruptor,
},
jsProtocolFaultInjector: jsProtocolFaultInjector{
rt: rt,
ProtocolFaultInjector: disruptor,
},
}

return obj, nil
return buildObject(rt, d)
}

// NewPodDisruptor creates an instance of a PodDisruptor
Expand Down Expand Up @@ -162,7 +206,7 @@ func NewPodDisruptor(
return obj, nil
}

// NewServiceDisruptor creates an instance of a ServiceDisruptor
// NewServiceDisruptor creates an instance of a ServiceDisruptor and returns it as a goja object
func NewServiceDisruptor(
ctx context.Context,
rt *goja.Runtime,
Expand Down Expand Up @@ -199,11 +243,7 @@ func NewServiceDisruptor(
return nil, fmt.Errorf("error creating ServiceDisruptor: %w", err)
}

// ServiceDisruptor is a wrapper to PodDisruptor, so we can use it for building a JsPodDisruptor.
// Notice that when [1] is implemented, this will make even more sense because there will be only
// a Disruptor interface.
// [1] https://github.com/grafana/xk6-disruptor/issues/60
obj, err := buildJsPodDisruptor(rt, disruptor)
obj, err := buildJsServiceDisruptor(rt, disruptor)
if err != nil {
return nil, fmt.Errorf("error creating ServiceDisruptor: %w", err)
}
Expand Down
8 changes: 8 additions & 0 deletions pkg/api/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,11 @@ func toGoCase(name string) string {

return goCase
}

// toCamelCase transforms an identifier from its Go case to camel case.
// Maps 'FieldName' to 'fieldName'
func toCamelCase(name string) string {
runes := []rune(name)
first := strings.ToLower(string(runes[0]))
return first + string(runes[1:])
}
7 changes: 7 additions & 0 deletions pkg/disruptors/disruptor.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package disruptors

// Disruptor defines the generic interface implemented by all disruptors
type Disruptor interface {
// Targets returns the list of targets for the disruptor
Targets() ([]string, error)
}
26 changes: 2 additions & 24 deletions pkg/disruptors/pod.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,32 +15,10 @@ type PodAttributes struct {
Labels map[string]string
}

// HTTPDisruptionOptions defines options for the injection of HTTP faults in a target pod
type HTTPDisruptionOptions struct {
// Port used by the agent for listening
ProxyPort uint `js:"proxyPort"`
// Network interface the agent will be listening traffic from
Iface string
}

// GrpcDisruptionOptions defines options for the injection of grpc faults in a target pod
type GrpcDisruptionOptions struct {
// Port used by the agent for listening
ProxyPort uint `js:"proxyPort"`
// Network interface the agent will be listening traffic from
Iface string
}

// PodDisruptor defines the types of faults that can be injected in a Pod
type PodDisruptor interface {
// Targets returns the list of targets for the disruptor
Targets() ([]string, error)
// InjectHTTPFault injects faults in the HTTP requests sent to the disruptor's targets
// for the specified duration
InjectHTTPFaults(fault HTTPFault, duration time.Duration, options HTTPDisruptionOptions) error
// InjectGrpcFault injects faults in the grpc requests sent to the disruptor's targets
// for the specified duration
InjectGrpcFaults(fault GrpcFault, duration time.Duration, options GrpcDisruptionOptions) error
Disruptor
ProtocolFaultInjector
}

// PodDisruptorOptions defines options that controls the PodDisruptor's behavior
Expand Down
26 changes: 26 additions & 0 deletions pkg/disruptors/faults.go → pkg/disruptors/protocol.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,32 @@ package disruptors

import "time"

// ProtocolFaultInjector defines the methods for injecting protocol faults
type ProtocolFaultInjector interface {
// InjectHTTPFault injects faults in the HTTP requests sent to the disruptor's targets
// for the specified duration
InjectHTTPFaults(fault HTTPFault, duration time.Duration, options HTTPDisruptionOptions) error
// InjectGrpcFault injects faults in the grpc requests sent to the disruptor's targets
// for the specified duration
InjectGrpcFaults(fault GrpcFault, duration time.Duration, options GrpcDisruptionOptions) error
}

// HTTPDisruptionOptions defines options for the injection of HTTP faults in a target pod
type HTTPDisruptionOptions struct {
// Port used by the agent for listening
ProxyPort uint `js:"proxyPort"`
// Network interface the agent will be listening traffic from
Iface string
}

// GrpcDisruptionOptions defines options for the injection of grpc faults in a target pod
type GrpcDisruptionOptions struct {
// Port used by the agent for listening
ProxyPort uint `js:"proxyPort"`
// Network interface the agent will be listening traffic from
Iface string
}

// HTTPFault specifies a fault to be injected in http requests
type HTTPFault struct {
// port the disruptions will be applied to
Expand Down
3 changes: 2 additions & 1 deletion pkg/disruptors/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ import (

// ServiceDisruptor defines operations for injecting faults in services
type ServiceDisruptor interface {
PodDisruptor
Disruptor
ProtocolFaultInjector
}

// ServiceDisruptorOptions defines options that controls the behavior of the ServiceDisruptor
Expand Down

0 comments on commit 7d19c2d

Please sign in to comment.