add route

This commit is contained in:
jeff
2018-06-03 19:36:01 +08:00
parent 40a0be0836
commit a64153b1c7
18 changed files with 1091 additions and 4 deletions

View File

@@ -1,5 +1,10 @@
FROM alpine:3.6
RUN apk add --update ca-certificates && update-ca-certificates
COPY ./* /usr/local/bin/
RUN apk add --update ca-certificates \
&& update-ca-certificates \
&& mkdir -p /etc/kubesphere/ingress-controller
COPY ./bin/* /usr/local/bin/
COPY ./ingress-controller /etc/kubesphere/ingress-controller
CMD ["sh"]

View File

@@ -86,10 +86,10 @@ fmt-check: fmt-all
.PHONY: build
build: fmt
mkdir -p ./tmp/bin
mkdir -p ./tmp/bin && cp -r ./install/ ./tmp/
$(call get_build_flags)
$(RUN_IN_DOCKER) time go install -ldflags '$(BUILD_FLAG)' $(TRAG.Gopkg)/cmd/...
@docker build -t $(TARG.Name) -f ./Dockerfile.dev ./tmp/bin
@docker build -t $(TARG.Name) -f ./Dockerfile.dev ./tmp
@docker image prune -f 1>/dev/null 2>&1
@echo "build done"

View File

@@ -0,0 +1,51 @@
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRole
metadata:
name: kubesphere-router-clusterrole
rules:
- apiGroups:
- ""
resources:
- configmaps
- endpoints
- nodes
- pods
- secrets
verbs:
- list
- watch
- apiGroups:
- ""
resources:
- nodes
verbs:
- get
- apiGroups:
- ""
resources:
- services
verbs:
- get
- list
- watch
- apiGroups:
- "extensions"
resources:
- ingresses
verbs:
- get
- list
- watch
- apiGroups:
- ""
resources:
- events
verbs:
- create
- patch
- apiGroups:
- "extensions"
resources:
- ingresses/status
verbs:
- update

View File

@@ -0,0 +1,39 @@
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: Role
metadata:
name: kubesphere-router-role
rules:
- apiGroups:
- ""
resources:
- configmaps
- pods
- secrets
- namespaces
verbs:
- get
- apiGroups:
- ""
resources:
- configmaps
resourceNames:
# Defaults to "<election-id>-<ingress-class>"
# Here: "<ingress-controller-leader>-<nginx>"
# This has to be adapted if you change either parameter
# when launching the nginx-ingress-controller.
- "ingress-controller-leader-nginx"
verbs:
- get
- update
- apiGroups:
- ""
resources:
- configmaps
verbs:
- create
- apiGroups:
- ""
resources:
- endpoints
verbs:
- get

View File

@@ -0,0 +1,4 @@
apiVersion: v1
kind: ServiceAccount
metadata:
name: kubesphere-router-serviceaccount

View File

@@ -0,0 +1,11 @@
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
name: nginx-ingress-clusterrole-nisa-binding
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: kubesphere-roter-clusterrole
subjects:
- kind: ServiceAccount
name: kubesphere-router-serviceaccount

View File

@@ -0,0 +1,11 @@
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: RoleBinding
metadata:
name: nginx-ingress-role-nisa-binding
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: kubesphere-router-role
subjects:
- kind: ServiceAccount
name: kubesphere-router-serviceaccount

View File

@@ -0,0 +1,42 @@
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: default-http-backend
labels:
app: kubesphere
component: kubesphere-router
spec:
replicas: 1
selector:
matchLabels:
app: kubesphere
component: kubesphere-router
template:
metadata:
labels:
app: kubesphere
component: kubesphere-router
spec:
terminationGracePeriodSeconds: 60
containers:
- name: default-http-backend
# Any image is permissible as long as:
# 1. It serves a 404 page at /
# 2. It serves 200 on a /healthz endpoint
image: googlecontainer/defaultbackend-amd64:1.4
livenessProbe:
httpGet:
path: /healthz
port: 8080
scheme: HTTP
initialDelaySeconds: 30
timeoutSeconds: 5
ports:
- containerPort: 8080
resources:
limits:
cpu: 10m
memory: 20Mi
requests:
cpu: 10m
memory: 20Mi

View File

@@ -0,0 +1,14 @@
apiVersion: v1
kind: Service
metadata:
name: default-http-backend
labels:
app: kubesphere
component: kubesphere-router
spec:
ports:
- port: 80
targetPort: 8080
selector:
app: kubespshere
component: kubesphere-router

View File

@@ -0,0 +1,6 @@
kind: ConfigMap
apiVersion: v1
metadata:
name: nginx-configuration
labels:
app: ingress-nginx

View File

@@ -0,0 +1,63 @@
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: kubesphere-router
spec:
replicas: 1
selector:
matchLabels:
app: kubesphere-router
template:
metadata:
labels:
app: kubesphere-router
annotations:
prometheus.io/port: '10254'
prometheus.io/scrape: 'true'
spec:
serviceAccountName: nginx-ingress-serviceaccount
containers:
- name: nginx-ingress-controller
image: quay.io/kubernetes-ingress-controller/nginx-ingress-controller:0.14.0
args:
- /nginx-ingress-controller
- --default-backend-service=$(POD_NAMESPACE)/default-http-backend
- --configmap=$(POD_NAMESPACE)/nginx-configuration
- --annotations-prefix=nginx.ingress.kubernetes.io
- --watch-namespace=$(POD_NAMESPACE)
env:
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
ports:
- name: http
containerPort: 80
- name: https
containerPort: 443
livenessProbe:
failureThreshold: 3
httpGet:
path: /healthz
port: 10254
scheme: HTTP
initialDelaySeconds: 10
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 1
readinessProbe:
failureThreshold: 3
httpGet:
path: /healthz
port: 10254
scheme: HTTP
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 1
securityContext:
runAsNonRoot: false

View File

@@ -0,0 +1,21 @@
apiVersion: v1
kind: Service
metadata:
name: kubesphere-router-gateway
labels:
app: kubesphere
component: kubesphere-router
spec:
selector:
app:
type: LoadBalancer
ports:
- name: http
protocol: TCP
port: 80
targetPort: 80
- name: https
protocol: TCP
port: 443
targetPort: 443

View File

@@ -28,6 +28,7 @@ import (
"kubesphere.io/kubesphere/pkg/apis/v1alpha/volumes"
"kubesphere.io/kubesphere/pkg/apis/v1alpha/iam"
"kubesphere.io/kubesphere/pkg/apis/v1alpha/components"
"kubesphere.io/kubesphere/pkg/apis/v1alpha/routes"
)
func init() {
@@ -45,6 +46,9 @@ func init() {
containers.Register(ws)
iam.Register(ws)
components.Register(ws,"/components")
routes.Register(ws)
// add webservice to default container
restful.Add(ws)

View File

@@ -0,0 +1,195 @@
/*
Copyright 2018 The KubeSphere 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 routes
import (
"github.com/emicklei/go-restful"
"errors"
"net/http"
"strings"
"github.com/golang/glog"
"k8s.io/api/core/v1"
"kubesphere.io/kubesphere/pkg/constants"
"kubesphere.io/kubesphere/pkg/filter/route"
"kubesphere.io/kubesphere/pkg/models"
)
func Register(ws *restful.WebService) {
ws.Route(ws.GET("/routers").To(GetAllRouters).
Doc("Get all routers").
Filter(route.RouteLogging).
Produces(restful.MIME_JSON))
ws.Route(ws.GET("/{namespace}/router").To(GetRouter).
Doc("Get router of a specified project").
Param(ws.PathParameter("namespace", "name of the project").DataType("string")).
Filter(route.RouteLogging).
Produces(restful.MIME_JSON))
ws.Route(ws.DELETE("/{namespace}/router").To(DeleteRouter).
Doc("Get router of a specified project").
Param(ws.PathParameter("namespace", "name of the project").DataType("string")).
Filter(route.RouteLogging).
Produces(restful.MIME_JSON))
ws.Route(ws.POST("/{namespace}/router").To(CreateRouter).
Doc("Create a router for a specified project").
Param(ws.PathParameter("namespace", "name of the project").DataType("string")).
Filter(route.RouteLogging).
Consumes(restful.MIME_JSON).
Produces(restful.MIME_JSON))
ws.Route(ws.PUT("/{namespace}/router").To(UpdateRouter).
Doc("Update a router for a specified project").
Param(ws.PathParameter("namespace", "name of the project").DataType("string")).
Filter(route.RouteLogging).
Consumes(restful.MIME_JSON).
Produces(restful.MIME_JSON))
}
// Get all namespace ingress controller services
func GetAllRouters(request *restful.Request, response *restful.Response) {
routers, err := models.GetAllRouters()
if err != nil {
glog.Error(err)
response.WriteHeaderAndEntity(http.StatusInternalServerError, constants.MessageResponse{Message: err.Error()})
} else {
response.WriteAsJson(routers)
}
}
// Get ingress controller service for specified namespace
func GetRouter(request *restful.Request, response *restful.Response) {
namespace := request.PathParameter("namespace")
router, err := models.GetRouter(namespace)
if err != nil {
glog.Error(err)
response.WriteHeaderAndEntity(http.StatusInternalServerError, constants.MessageResponse{Message: err.Error()})
} else if router == nil {
response.WriteHeaderAndEntity(http.StatusNotFound, constants.MessageResponse{Message: "Reseource Not Found"})
} else {
response.WriteAsJson(router)
}
}
// Create ingress controller and related services
func CreateRouter(request *restful.Request, response *restful.Response) {
namespace := request.PathParameter("namespace")
newRouter := models.Router{}
err := request.ReadEntity(&newRouter)
if err != nil {
response.WriteAsJson(err)
return
}
var router *v1.Service
serviceType, annotationMap, err := ParseParameter(newRouter)
if err != nil {
glog.Error("Wrong annotations, missing key or value")
response.WriteHeaderAndEntity(http.StatusBadRequest,
constants.MessageResponse{Message: "Wrong annotations, missing key or value"})
return
}
router, err = models.CreateRouter(namespace, serviceType, annotationMap)
if err != nil {
response.WriteHeaderAndEntity(http.StatusInternalServerError, constants.MessageResponse{Message: err.Error()})
} else {
response.WriteAsJson(*router)
}
}
// Delete ingress controller and services
func DeleteRouter(request *restful.Request, response *restful.Response) {
namespace := request.PathParameter("namespace")
router, err := models.DeleteRouter(namespace)
if err != nil {
response.WriteHeaderAndEntity(http.StatusInternalServerError, constants.MessageResponse{Message: err.Error()})
return
} else {
response.WriteAsJson(router)
}
}
func UpdateRouter(request *restful.Request, response *restful.Response) {
namespace := request.PathParameter("namespace")
newRouter := models.Router{}
err := request.ReadEntity(&newRouter)
if err != nil {
glog.Error(err)
response.WriteHeaderAndEntity(http.StatusInternalServerError, constants.MessageResponse{Message: err.Error()})
return
}
serviceType, annotationMap, err := ParseParameter(newRouter)
router, err := models.UpdateRouter(namespace, serviceType, annotationMap)
if err != nil {
response.WriteHeaderAndEntity(http.StatusInternalServerError, constants.MessageResponse{Message: err.Error()})
return
} else {
response.WriteAsJson(router)
}
}
func ParseParameter(router models.Router) (routerType v1.ServiceType, annotationMap map[string]string, err error) {
routerType = v1.ServiceTypeNodePort
annotationMap = make(map[string]string)
if strings.Compare(strings.ToLower(router.RouterType), "loadbalancer") == 0 {
annotations := router.Annotations
annotation := strings.FieldsFunc(annotations, func(r rune) bool {
return r == ',' || r == '='
})
if len(annotation)%2 != 0 {
glog.Error("Wrong annotations, missing key or value")
return routerType, annotationMap, errors.New("wrong annotations, missing key or value")
}
for i := 0; i < len(annotation); i += 2 {
annotationMap[annotation[i]] = annotation[i+1]
}
return v1.ServiceTypeLoadBalancer, annotationMap, nil
} else {
return v1.ServiceTypeNodePort, nil, nil
}
}

325
pkg/models/routes.go Normal file
View File

@@ -0,0 +1,325 @@
/*
Copyright 2018 The KubeSphere 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 models
import (
"strings"
"io/ioutil"
coreV1 "k8s.io/api/core/v1"
metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes/scheme"
extensionsV1beta1 "k8s.io/api/extensions/v1beta1"
"k8s.io/api/rbac/v1beta1"
"github.com/golang/glog"
"kubesphere.io/kubesphere/pkg/client"
)
const RouterYamlDirectory = "/etc/kubesphere/ingress-controller/"
type Router struct {
RouterType string `json:"type"`
Annotations string `json:"annotations"`
}
func GetAllRouters() ([] *coreV1.Service, error) {
k8sClient := client.NewK8sClient()
routers := []*coreV1.Service{}
opts := metaV1.ListOptions{}
namespaces, err := k8sClient.CoreV1().Namespaces().List(opts)
if err != nil {
glog.Error(err)
return routers, err
}
opts = metaV1.ListOptions{
LabelSelector: "app=kubesphere,component=kubesphere-router",
FieldSelector: "metadata.name=kubesphere-router-gateway",
}
for _, namespace := range namespaces.Items {
services, err := k8sClient.CoreV1().Services(namespace.Name).List(opts)
if err != nil {
glog.Error(err)
return nil, err
}
if len(services.Items) > 0 {
routers = append(routers, &services.Items[0])
}
}
return routers, nil
}
// Get router from a namespace
func GetRouter(namespace string) (*coreV1.Service, error) {
k8sClient := client.NewK8sClient()
var router *coreV1.Service
opts := metaV1.ListOptions{
LabelSelector: "app=kubesphere,component=kubesphere-router",
FieldSelector: "metadata.name=kubesphere-router-gateway",
}
services, err := k8sClient.CoreV1().Services(namespace).List(opts)
if err != nil {
glog.Error(err)
return nil, err
}
if len(services.Items) > 0 {
router = &services.Items[0]
}
return router, nil
}
// Load all resource yamls
func LoadYamls() ([]string, error) {
var yamls []string
files, err := ioutil.ReadDir(RouterYamlDirectory)
if err != nil {
glog.Error(err)
return nil, err
}
for _, file := range files {
content, err := ioutil.ReadFile(RouterYamlDirectory + "/" + file.Name())
if err != nil {
glog.Error(err)
return nil, err
} else {
yamls = append(yamls, string(content))
}
}
return yamls, nil
}
func IsRouterService(serviceName string) bool {
if strings.Compare(strings.ToLower(serviceName), "default-http-backend") == 0 {
return false
}
return true
}
// Create a ingress controller in a namespace
func CreateRouter(namespace string, routerType coreV1.ServiceType, annotations map[string]string) (*coreV1.Service, error) {
k8sClient := client.NewK8sClient()
var router *coreV1.Service
yamls, err := LoadYamls()
if err != nil {
glog.Error(err)
}
for _, f := range yamls {
decode := scheme.Codecs.UniversalDeserializer().Decode
obj, _, err := decode([]byte(f), nil, nil)
if err != nil {
glog.Error(err)
return router, err
}
switch obj.(type) {
case *v1beta1.Role:
role := obj.(*v1beta1.Role)
role, err := k8sClient.RbacV1beta1().Roles(namespace).Create(role)
if err != nil {
glog.Error(err)
}
case *v1beta1.ClusterRole:
clusterRole := obj.(*v1beta1.ClusterRole)
clusterRole, err := k8sClient.RbacV1beta1().ClusterRoles().Create(clusterRole)
if err != nil {
glog.Error(err)
}
case *v1beta1.ClusterRoleBinding:
clusterRoleBinding := obj.(*v1beta1.ClusterRoleBinding)
clusterRoleBinding.Subjects[0].Namespace = namespace
clusterRoleBinding, err := k8sClient.RbacV1beta1().ClusterRoleBindings().Create(clusterRoleBinding)
if err != nil {
glog.Error(err)
}
case *v1beta1.RoleBinding:
roleBinding := obj.(*v1beta1.RoleBinding)
roleBinding.Subjects[0].Namespace = namespace
roleBinding, err := k8sClient.RbacV1beta1().RoleBindings(namespace).Create(roleBinding)
if err != nil {
glog.Error(err)
}
case *coreV1.ServiceAccount:
sa := obj.(*coreV1.ServiceAccount)
sa, err := k8sClient.CoreV1().ServiceAccounts(namespace).Create(sa)
if err != nil {
glog.Error(err)
}
case *coreV1.Service:
service := obj.(*coreV1.Service)
if IsRouterService(service.Name) {
service.SetAnnotations(annotations)
service.Spec.Type = routerType
}
service, err := k8sClient.CoreV1().Services(namespace).Create(service)
if err != nil {
glog.Error(err)
return nil, err
}
if IsRouterService(service.Name) {
router = service
}
case *extensionsV1beta1.Deployment:
deployment := obj.(*extensionsV1beta1.Deployment)
deployment, err := k8sClient.ExtensionsV1beta1().Deployments(namespace).Create(deployment)
if err != nil {
glog.Error(err)
}
default:
//glog.Info("Default resource")
}
}
return router, nil
}
// DeleteRouter is used to delete ingress controller related resources in namespace
// It will not delete ClusterRole resource cause it maybe used other controllers
func DeleteRouter(namespace string) (*coreV1.Service, error) {
k8sClient := client.NewK8sClient()
var router *coreV1.Service
yamls, err := LoadYamls()
if err != nil {
glog.Error(err)
}
for _, f := range yamls {
decode := scheme.Codecs.UniversalDeserializer().Decode
obj, _, err := decode([]byte(f), nil, nil)
if err != nil {
glog.Error(err)
return router, err
}
options := metaV1.DeleteOptions{}
switch obj.(type) {
case *v1beta1.Role:
role := obj.(*v1beta1.Role)
err = k8sClient.RbacV1beta1().Roles(namespace).Delete(role.Name, &options)
if err != nil {
glog.Error(err)
}
case *v1beta1.ClusterRoleBinding:
clusterRoleBinding := obj.(*v1beta1.ClusterRoleBinding)
err = k8sClient.RbacV1beta1().ClusterRoleBindings().Delete(clusterRoleBinding.Name, &options)
if err != nil {
glog.Error(err)
}
case *v1beta1.RoleBinding:
roleBinding := obj.(*v1beta1.RoleBinding)
err = k8sClient.RbacV1beta1().RoleBindings(namespace).Delete(roleBinding.Name, &options)
if err != nil {
glog.Error(err)
}
case *coreV1.ServiceAccount:
sa := obj.(*coreV1.ServiceAccount)
err = k8sClient.CoreV1().ServiceAccounts(namespace).Delete(sa.Name, &options)
if err != nil {
glog.Error(err)
}
case *coreV1.Service:
service := obj.(*coreV1.Service)
err = k8sClient.CoreV1().Services(namespace).Delete(service.Name, &options)
if err != nil {
glog.Error(err)
}
if IsRouterService(service.Name) {
router = service
}
case *extensionsV1beta1.Deployment:
deployment := obj.(*extensionsV1beta1.Deployment)
err = k8sClient.ExtensionsV1beta1().Deployments(namespace).Delete(deployment.Name, &options)
if err != nil {
glog.Error(err)
}
default:
//glog.Info("Default resource")
}
}
return router, nil
}
// Update Ingress Controller Service, change type from NodePort to Loadbalancer or vice versa.
func UpdateRouter(namespace string, routerType coreV1.ServiceType, annotations map[string]string) (*coreV1.Service, error) {
k8sClient := client.NewK8sClient()
var router *coreV1.Service
router, err := GetRouter(namespace)
if err != nil {
glog.Error(err)
return router, nil
}
if router.Spec.Type != routerType {
router.Spec.Type = routerType
router.SetAnnotations(annotations)
router, err = k8sClient.CoreV1().Services(namespace).Update(router)
if err != nil {
glog.Error(err)
return router, err
}
}
return router, nil
}

33
vendor/k8s.io/kubernetes/pkg/util/slice/BUILD generated vendored Normal file
View File

@@ -0,0 +1,33 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_library(
name = "go_default_library",
srcs = ["slice.go"],
importpath = "k8s.io/kubernetes/pkg/util/slice",
deps = ["//vendor/k8s.io/apimachinery/pkg/util/rand:go_default_library"],
)
go_test(
name = "go_default_test",
srcs = ["slice_test.go"],
embed = [":go_default_library"],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)

91
vendor/k8s.io/kubernetes/pkg/util/slice/slice.go generated vendored Normal file
View File

@@ -0,0 +1,91 @@
/*
Copyright 2015 The Kubernetes 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 slice provides utility methods for common operations on slices.
package slice
import (
"sort"
utilrand "k8s.io/apimachinery/pkg/util/rand"
)
// CopyStrings copies the contents of the specified string slice
// into a new slice.
func CopyStrings(s []string) []string {
if s == nil {
return nil
}
c := make([]string, len(s))
copy(c, s)
return c
}
// SortStrings sorts the specified string slice in place. It returns the same
// slice that was provided in order to facilitate method chaining.
func SortStrings(s []string) []string {
sort.Strings(s)
return s
}
// ShuffleStrings copies strings from the specified slice into a copy in random
// order. It returns a new slice.
func ShuffleStrings(s []string) []string {
if s == nil {
return nil
}
shuffled := make([]string, len(s))
perm := utilrand.Perm(len(s))
for i, j := range perm {
shuffled[j] = s[i]
}
return shuffled
}
// ContainsString checks if a given slice of strings contains the provided string.
// If a modifier func is provided, it is called with the slice item before the comparation.
func ContainsString(slice []string, s string, modifier func(s string) string) bool {
for _, item := range slice {
if item == s {
return true
}
if modifier != nil && modifier(item) == s {
return true
}
}
return false
}
// RemoveString returns a newly created []string that contains all items from slice that
// are not equal to s and modifier(s) in case modifier func is provided.
func RemoveString(slice []string, s string, modifier func(s string) string) []string {
newSlice := make([]string, 0)
for _, item := range slice {
if item == s {
continue
}
if modifier != nil && modifier(item) == s {
continue
}
newSlice = append(newSlice, item)
}
if len(newSlice) == 0 {
// Sanitize for unit tests so we don't need to distinguish empty array
// and nil.
newSlice = nil
}
return newSlice
}

172
vendor/k8s.io/kubernetes/pkg/util/slice/slice_test.go generated vendored Normal file
View File

@@ -0,0 +1,172 @@
/*
Copyright 2015 The Kubernetes 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 slice
import (
"reflect"
"testing"
)
func TestCopyStrings(t *testing.T) {
var src1 []string
dest1 := CopyStrings(src1)
if !reflect.DeepEqual(src1, dest1) {
t.Errorf("%v and %v are not equal", src1, dest1)
}
src2 := []string{}
dest2 := CopyStrings(src2)
if !reflect.DeepEqual(src2, dest2) {
t.Errorf("%v and %v are not equal", src2, dest2)
}
src3 := []string{"a", "c", "b"}
dest3 := CopyStrings(src3)
if !reflect.DeepEqual(src3, dest3) {
t.Errorf("%v and %v are not equal", src3, dest3)
}
src3[0] = "A"
if reflect.DeepEqual(src3, dest3) {
t.Errorf("CopyStrings didn't make a copy")
}
}
func TestSortStrings(t *testing.T) {
src := []string{"a", "c", "b"}
dest := SortStrings(src)
expected := []string{"a", "b", "c"}
if !reflect.DeepEqual(dest, expected) {
t.Errorf("SortString didn't sort the strings")
}
if !reflect.DeepEqual(src, expected) {
t.Errorf("SortString didn't sort in place")
}
}
func TestShuffleStrings(t *testing.T) {
var src []string
dest := ShuffleStrings(src)
if dest != nil {
t.Errorf("ShuffleStrings for a nil slice got a non-nil slice")
}
src = []string{"a", "b", "c", "d", "e", "f"}
dest = ShuffleStrings(src)
if len(src) != len(dest) {
t.Errorf("Shuffled slice is wrong length, expected %v got %v", len(src), len(dest))
}
m := make(map[string]bool, len(dest))
for _, s := range dest {
m[s] = true
}
for _, k := range src {
if _, exists := m[k]; !exists {
t.Errorf("Element %v missing from shuffled slice", k)
}
}
}
func TestContainsString(t *testing.T) {
src := []string{"aa", "bb", "cc"}
if !ContainsString(src, "bb", nil) {
t.Errorf("ContainsString didn't find the string as expected")
}
modifier := func(s string) string {
if s == "cc" {
return "ee"
}
return s
}
if !ContainsString(src, "ee", modifier) {
t.Errorf("ContainsString didn't find the string by modifier")
}
}
func TestRemoveString(t *testing.T) {
modifier := func(s string) string {
if s == "ab" {
return "ee"
}
return s
}
tests := []struct {
testName string
input []string
remove string
modifier func(s string) string
want []string
}{
{
testName: "Nil input slice",
input: nil,
remove: "",
modifier: nil,
want: nil,
},
{
testName: "Slice doesn't contain the string",
input: []string{"a", "ab", "cdef"},
remove: "NotPresentInSlice",
modifier: nil,
want: []string{"a", "ab", "cdef"},
},
{
testName: "All strings removed, result is nil",
input: []string{"a"},
remove: "a",
modifier: nil,
want: nil,
},
{
testName: "No modifier func, one string removed",
input: []string{"a", "ab", "cdef"},
remove: "ab",
modifier: nil,
want: []string{"a", "cdef"},
},
{
testName: "No modifier func, all(three) strings removed",
input: []string{"ab", "a", "ab", "cdef", "ab"},
remove: "ab",
modifier: nil,
want: []string{"a", "cdef"},
},
{
testName: "Removed both the string and the modifier func result",
input: []string{"a", "cd", "ab", "ee"},
remove: "ee",
modifier: modifier,
want: []string{"a", "cd"},
},
}
for _, tt := range tests {
if got := RemoveString(tt.input, tt.remove, tt.modifier); !reflect.DeepEqual(got, tt.want) {
t.Errorf("%v: RemoveString(%v, %q, %T) = %v WANT %v", tt.testName, tt.input, tt.remove, tt.modifier, got, tt.want)
}
}
}