Skip to content

Commit

Permalink
Feat: add multicluster & codebase
Browse files Browse the repository at this point in the history
Signed-off-by: Yin Da <yd219913@alibaba-inc.com>
  • Loading branch information
Somefive committed Sep 1, 2022
1 parent ddb8d67 commit b70c64b
Show file tree
Hide file tree
Showing 14 changed files with 2,509 additions and 0 deletions.
4 changes: 4 additions & 0 deletions .github/CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# This file is a github code protect rule follow the codeowners https://docs.github.com/en/github/creating-cloning-and-archiving-repositories/creating-a-repository-on-github/about-code-owners#example-of-a-codeowners-file

multicluster/ @Somefive
util/ @Somefive @FogDong
72 changes: 72 additions & 0 deletions .github/workflows/unit-test.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
name: UnitTest

on:
push:
branches:
- master
- release-*
workflow_dispatch: {}
pull_request:
branches:
- master
- release-*

env:
GO_VERSION: '1.17'

jobs:

detect-noop:
runs-on: ubuntu-22.04
outputs:
noop: ${{ steps.noop.outputs.should_skip }}
steps:
- name: Detect No-op Changes
id: noop
uses: fkirc/skip-duplicate-actions@v3.3.0
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
paths_ignore: '["**.md", "**.mdx", "**.png", "**.jpg"]'
do_not_skip: '["workflow_dispatch", "schedule", "push"]'
concurrent_skipping: false

unit-test:
runs-on: ubuntu-22.04
needs: detect-noop
if: needs.detect-noop.outputs.noop != 'true'

steps:
- name: Checkout
uses: actions/checkout@v2
with:
submodules: true

- name: Setup Go
uses: actions/setup-go@v2
with:
go-version: ${{ env.GO_VERSION }}

- name: Cache Go Dependencies
uses: actions/cache@v2
with:
path: .work/pkg
key: ${{ runner.os }}-pkg-${{ hashFiles('**/go.sum') }}
restore-keys: ${{ runner.os }}-pkg-

- name: install Kubebuilder
uses: RyanSiu1995/kubebuilder-action@v1.2
with:
version: 3.1.0
kubebuilderOnly: false
kubernetesVersion: v1.22.0

- name: Run unit-test
run: make unit-test

- name: Upload coverage report
uses: codecov/codecov-action@v1
with:
token: ${{ secrets.CODECOV_TOKEN }}
file: /tmp/vela-pkg-coverage.txt
flags: unit-test
name: codecov-umbrella
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,5 @@

# Dependency directories (remove the comment below to include it)
# vendor/

.idea/
13 changes: 13 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
fmt:
go fmt ./...

vet:
go vet ./...

tidy:
go mod tidy

unit-test:
go test -v -coverpkg=./... -coverprofile=/tmp/vela-pkg-coverage.txt ./...

reviewable: fmt vet
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,8 @@
# pkg
[![UnitTest](https://github.com/kubevela/pkg/actions/workflows/unit-test.yml/badge.svg)](https://github.com/kubevela/pkg/actions/workflows/unit-test.yml/badge.svg)
[![Go Report Card](https://goreportcard.com/badge/github.com/kubevela/pkg)](https://goreportcard.com/report/github.com/kubevela/pkg)
[![codecov](https://codecov.io/gh/kubevela/pkg/branch/master/graph/badge.svg)](https://codecov.io/gh/kubevela/vela-pkg)
[![LICENSE](https://img.shields.io/github/license/kubevela/pkg.svg?style=flat-square)](/LICENSE)
[![Total alerts](https://img.shields.io/lgtm/alerts/g/kubevela/pkg.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/kubevela/pkg/alerts/)

A set of common libraries for writing KubeVela ecosystem controllers.
10 changes: 10 additions & 0 deletions codecov.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
coverage:
status:
project:
default:
threshold: 0.1%
patch:
default:
target: 70%
ignore:
- "test/**"
31 changes: 31 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
module github.com/kubevela/pkg

go 1.17

require (
github.com/oam-dev/cluster-gateway v1.4.0
github.com/stretchr/testify v1.7.0
k8s.io/apimachinery v0.23.1
k8s.io/client-go v0.23.1
k8s.io/klog/v2 v2.30.0
k8s.io/utils v0.0.0-20210930125809-cb0fa318a74b
)

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/go-logr/logr v1.2.0 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/kr/pretty v0.2.1 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
golang.org/x/net v0.0.0-20211209124913-491a49abca63 // indirect
golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f // indirect
golang.org/x/text v0.3.7 // indirect
golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/protobuf v1.27.1 // indirect
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
)

replace sigs.k8s.io/apiserver-network-proxy/konnectivity-client => sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.24
2,077 changes: 2,077 additions & 0 deletions go.sum

Large diffs are not rendered by default.

37 changes: 37 additions & 0 deletions multicluster/context.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
Copyright 2022 The KubeVela Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package multicluster

import "context"

type key int

const (
// clusterKey is the context key for multi-cluster request
clusterKey key = iota
)

// WithCluster returns a copy of parent in which the cluster value is set
func WithCluster(parent context.Context, cluster string) context.Context {
return context.WithValue(parent, clusterKey, cluster)
}

// ClusterFrom returns the value of the cluster key on the ctx
func ClusterFrom(ctx context.Context) (string, bool) {
cluster, ok := ctx.Value(clusterKey).(string)
return cluster, ok
}
118 changes: 118 additions & 0 deletions multicluster/transport.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
/*
Copyright 2022 The KubeVela Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package multicluster

import (
"net/http"
"path"
"strings"

clustergatewayconfig "github.com/oam-dev/cluster-gateway/pkg/config"
knet "k8s.io/apimachinery/pkg/util/net"
"k8s.io/client-go/transport"
"k8s.io/utils/pointer"

"github.com/kubevela/pkg/util/net"
)

// Transport the transport for multi-cluster request
type Transport struct {
// delegate the underlying RoundTripper
delegate http.RoundTripper

// cluster the proxy target. If empty, the target will be determined from
// request context dynamically.
cluster *string
}

// TransportOption option for creating transport
type TransportOption interface {
ApplyToTransport(*Transport)
}

// ForCluster create transport for specified cluster
type ForCluster string

// ApplyToTransport .
func (op ForCluster) ApplyToTransport(t *Transport) {
t.cluster = pointer.String(string(op))
}

// NewTransport create a transport instance for handling multi-cluster request
func NewTransport(rt http.RoundTripper) *Transport {
return &Transport{delegate: rt}
}

// NewTransportWrapper create a WrapperFunc for wrapping RoundTripper with
// multi-cluster transport
func NewTransportWrapper(options ...TransportOption) transport.WrapperFunc {
return func(rt http.RoundTripper) http.RoundTripper {
t := NewTransport(rt)
for _, op := range options {
op.ApplyToTransport(t)
}
return t
}
}

var _ http.RoundTripper = &Transport{}
var _ knet.RoundTripperWrapper = &Transport{}

// formatProxyURL will format the request API path by the cluster gateway resources rule
func formatProxyURL(cluster, originalPath string) string {
originalPath = strings.TrimPrefix(originalPath, "/")
return path.Clean(strings.Join([]string{
"/apis",
clustergatewayconfig.MetaApiGroupName,
clustergatewayconfig.MetaApiVersionName,
clustergatewayconfig.MetaApiResourceName,
cluster,
"proxy",
originalPath,
}, "/"))
}

// getClusterFor get cluster for incoming request. If cluster set in transport,
// it will return the pre-set cluster. Otherwise, it will find cluster in
// context.
func (t *Transport) getClusterFor(req *http.Request) string {
if t.cluster != nil {
return *t.cluster
}
cluster, _ := ClusterFrom(req.Context())
return cluster
}

// RoundTrip is the main function for the re-write API path logic
func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error) {
cluster := t.getClusterFor(req)
if !IsLocal(cluster) {
req = req.Clone(req.Context())
req.URL.Path = formatProxyURL(cluster, req.URL.Path)
}
return t.delegate.RoundTrip(req)
}

// CancelRequest will try cancel request with the inner round tripper
func (t *Transport) CancelRequest(req *http.Request) {
net.TryCancelRequest(t.WrappedRoundTripper(), req)
}

// WrappedRoundTripper can get the wrapped RoundTripper
func (t *Transport) WrappedRoundTripper() http.RoundTripper {
return t.delegate
}
58 changes: 58 additions & 0 deletions multicluster/transport_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
Copyright 2022 The KubeVela Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package multicluster

import (
"context"
"net/http"
"net/url"
"testing"

"github.com/stretchr/testify/require"
)

type fakeRoundTripper struct{}

func (rt *fakeRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
return &http.Response{Request: req}, nil
}

func (rt *fakeRoundTripper) CancelRequest(req *http.Request) {}

func TestTransport(t *testing.T) {
r := require.New(t)
rt := &fakeRoundTripper{}

// Test dynamic transport
tp := NewTransport(rt)
req := &http.Request{URL: &url.URL{Path: "/test-path"}}
resp, err := tp.RoundTrip(req.WithContext(WithCluster(context.Background(), "example")))
r.NoError(err)
r.Equal("/apis/cluster.core.oam.dev/v1alpha1/clustergateways/example/proxy/test-path", resp.Request.URL.Path)
resp, err = tp.RoundTrip(req.WithContext(WithCluster(context.Background(), Local)))
r.NoError(err)
r.Equal("/test-path", resp.Request.URL.Path)

// Test static transport
_rt := NewTransportWrapper(ForCluster("static"))(rt)
resp, err = _rt.RoundTrip(req.WithContext(WithCluster(context.Background(), "example")))
r.NoError(err)
r.Equal("/apis/cluster.core.oam.dev/v1alpha1/clustergateways/static/proxy/test-path", resp.Request.URL.Path)

// Test cancel request
_rt.(*Transport).CancelRequest(req)
}
22 changes: 22 additions & 0 deletions multicluster/utils.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
Copyright 2022 The KubeVela Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package multicluster

// IsLocal check if cluster is local cluster
func IsLocal(cluster string) bool {
return cluster == Local || cluster == ""
}
Loading

0 comments on commit b70c64b

Please sign in to comment.