Skip to content

Commit

Permalink
Merge pull request #70 from pablochacin/generic-k8s-interface
Browse files Browse the repository at this point in the history
Generic k8s interface
  • Loading branch information
pablochacin authored Sep 29, 2022
2 parents 0c01c2c + dea1bf7 commit c6d495d
Show file tree
Hide file tree
Showing 7 changed files with 693 additions and 59 deletions.
123 changes: 106 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,39 +36,131 @@ cd xk6-kubernetes
make
```

Using the `k6` binary with `xk6-kubernetes`, run the k6 test as usual:

```bash
./k6 run k8s-test-script.js
```
# Usage

The API assumes a `kubeconfig` configuration is available at any of the following default locations:
* at the location pointed by the `KUBECONFIG` environment variable
* at `$HOME/.kube`


# API

## Example
## Generic API

This API offers methods for creating, retrieving, listing and deleting resources of any of the supported kinds.

| Method | Parameters| Description |
| ------------ | ---| ------ |
| apply | manifest string| creates a Kubernetes resource given a YAML manifest |
| create | spec object | creates a Kubernetes resource given its specification |
| delete | kind | removes the named resource |
| | name |
| | namespace|
| get | kind| returns the named resource |
| | name |
| | namespace |
| list | kind| returns a collection of resources of a given kind
| | namespace |


The kinds of resources currently supported are:
* ConfigMap
* Deployment
* Job
* Namespace
* Node
* PersistentVolume
* PersistentVolumeClaim
* Pod
* Secret
* Service
* StatefulSet

### Examples

#### Creating a pod using a specification
```javascript
import { Kubernetes } from 'k6/x/kubernetes';
const podSpec = {
apiVersion: "v1",
kind: "Pod",
metadata: {
name: "busybox",
namespace: "testns"
},
spec: {
containers: [
{
name: "busybox",
image: "busybox",
command: ["sh", "-c", "sleep 30"]
}
]
}
}
export default function () {
const kubernetes = new Kubernetes({
// config_path: "/path/to/kube/config", ~/.kube/config by default
});
const kubernetes = new Kubernetes();
kubernetes.create(pod)
const pods = kubernetes.pods.list();
const pods = kubernetes.list("Pod", "testns");
console.log(`${pods.length} Pods found:`);
pods.map(function(pod) {
console.log(` ${pod.name}`)
console.log(` ${pod.metadata.name}`)
});
}
```
Using the `k6` binary with `xk6-kubernetes`, run the k6 test as usual:
#### Creating a job using a YAML manifest
```javascript
import { Kubernetes } from 'k6/x/kubernetes';
```bash
./k6 run k8s-test-script.js
const manifest = `
apiVersion: batch/v1
kind: Job
metadata:
name: busybox
namespace: testns
spec:
template:
spec:
containers:
- name: busybox
image: busybox
command: ["sleep", "300"]
restartPolicy: Never
`
...
INFO[0001] 16 Pods found: source=console
...
export default function () {
const kubernetes = new Kubernetes();
kubernetes.apply(manifest)
const jobs = kubernetes.list("Job", "testns");
console.log(`${jobs.length} Jobs found:`);
pods.map(function(job) {
console.log(` ${job.metadata.name}`)
});
}
```
## Resource kind helpers
## APIs
This API offers a helper for each kind of Kubernetes resources supported (Pods, Deployments, Secrets, et cetera). For each one, an interface for creating, getting, listing and deleting objects is offered.
>⚠️ This interface is deprecated and will be removed soon
> -
</br>
### Create a client: `new Kubernetes(config)`
Expand All @@ -89,9 +181,6 @@ export default function () {
}
```
### `Client.config_maps`
| Method | Description | |
Expand Down
46 changes: 46 additions & 0 deletions examples/create_get_list_delete_pod.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { Kubernetes } from 'k6/x/kubernetes'
import { sleep } from 'k6'

const k8s = new Kubernetes()

const podSpec = {
apiVersion: "v1",
kind: "Pod",
metadata: {
name: "busybox",
namespace: "default"
},
spec: {
containers: [
{
name: "busybox",
image: "busybox",
command: ["sh", "-c", "sleep 30"]
}
]
}
}

export default function(){
var created = k8s.create(podSpec)
console.log("pod '" + created.metadata.name +"' created")

var pod = k8s.get(podSpec.kind, podSpec.metadata.name, podSpec.metadata.namespace)
if (podSpec.metadata.name != pod.metadata.name) {
throw new Error("fetch by name did not return the Pod. Expected: " + podSpec.metadata.name + " but got: " + fetched.name)
}

const pods = k8s.list(podSpec.kind, podSpec.metadata.namespace)
if (pods === undefined || pods.length < 1) {
throw new Error("expected listing with 1 Pod")
}

k8s.delete(podSpec.kind, podSpec.metadata.name, podSpec.metadata.namespace)

// give time for the pod to be deleted
sleep(5)

if (k8s.list(podSpec.kind, podSpec.metadata.namespace).length != 0) {
throw new Error("deletion failed to remove pod")
}
}
17 changes: 17 additions & 0 deletions internal/testutils/fake.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package testutils

import (
"k8s.io/apimachinery/pkg/runtime"
dynamicfake "k8s.io/client-go/dynamic/fake"
"k8s.io/client-go/kubernetes/fake"
)

// NewFakeDynamic creates a new instance of a fake dynamic client with a default scheme
func NewFakeDynamic() (*dynamicfake.FakeDynamicClient, error) {
scheme := runtime.NewScheme()
err := fake.AddToScheme(scheme)
if err != nil {
return nil, err
}
return dynamicfake.NewSimpleDynamicClient(scheme), nil
}
33 changes: 33 additions & 0 deletions kubernetes.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"go.k6.io/k6/js/common"
"k8s.io/client-go/rest"

"github.com/grafana/xk6-kubernetes/pkg/api"
"github.com/grafana/xk6-kubernetes/pkg/configmaps"
"github.com/grafana/xk6-kubernetes/pkg/deployments"
"github.com/grafana/xk6-kubernetes/pkg/ingresses"
Expand All @@ -25,6 +26,7 @@ import (

"go.k6.io/k6/js/modules"
metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/kubernetes"
_ "k8s.io/client-go/plugin/pkg/client/auth" // Required for access to GKE and AKS
"k8s.io/client-go/tools/clientcmd"
Expand All @@ -44,10 +46,13 @@ type ModuleInstance struct {
vu modules.VU
// clientset enables injection of a pre-configured Kubernetes environment for unit tests
clientset kubernetes.Interface
// dynamic enables injection of a fake dynamic client for unit tests
dynamic dynamic.Interface
}

// Kubernetes is the exported object used within JavaScript.
type Kubernetes struct {
api.Kubernetes
client kubernetes.Interface
metaOptions metaV1.ListOptions
ctx context.Context
Expand Down Expand Up @@ -100,6 +105,7 @@ func (mi *ModuleInstance) newClient(c goja.ConstructorCall) *goja.Object {
obj := &Kubernetes{}
var config *rest.Config

// if clientset was not injected for unit testing
if mi.clientset == nil {
var options KubeConfig
err := rt.ExportTo(c.Argument(0), &options)
Expand All @@ -121,6 +127,33 @@ func (mi *ModuleInstance) newClient(c goja.ConstructorCall) *goja.Object {
obj.client = mi.clientset
}

// If dynamic client was not injected for unit testing
// It is assumed rest config is set
if mi.dynamic == nil {
k8s, err := api.NewFromConfig(
api.KubernetesConfig{
Config: config,
Context: ctx,
},
)
if err != nil {
common.Throw(rt, err)
}
obj.Kubernetes = k8s
} else {
// Pre-configured dynamic client is injected for unit testing
k8s, err := api.NewFromConfig(
api.KubernetesConfig{
Client: mi.dynamic,
Context: ctx,
},
)
if err != nil {
common.Throw(rt, err)
}
obj.Kubernetes = k8s
}

obj.metaOptions = metaV1.ListOptions{}
obj.ctx = ctx

Expand Down
Loading

0 comments on commit c6d495d

Please sign in to comment.