blob: cc11b1689b98c546709a98ce8cfe0799d065577a [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 proxy
import (
"testing"
autoscalingv2beta2 "k8s.io/api/autoscaling/v2beta2"
"k8s.io/apimachinery/pkg/api/resource"
"k8s.io/utils/pointer"
"github.com/apache/shardingsphere-on-cloud/shardingsphere-operator/api/v1alpha1"
"github.com/stretchr/testify/assert"
appsv1 "k8s.io/api/apps/v1"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func Test_IsRunning(t *testing.T) {
ts := metav1.Time{}
cases := []struct {
podlist *v1.PodList
exp bool
message string
}{
{
podlist: &v1.PodList{
Items: []v1.Pod{},
},
exp: false,
message: "Empty PodList should be false",
},
{
podlist: &v1.PodList{
Items: []v1.Pod{
{
Status: v1.PodStatus{
Phase: v1.PodRunning,
},
},
{
Status: v1.PodStatus{
Phase: v1.PodFailed,
},
},
},
},
// At least one Pod is Running considered the Cluster be availabe
exp: true,
message: "First Pod is Running and second Pod is not Running should be false",
},
{
podlist: &v1.PodList{
Items: []v1.Pod{
{
Status: v1.PodStatus{
Phase: v1.PodFailed,
},
},
{
Status: v1.PodStatus{
Phase: v1.PodRunning,
},
},
},
},
// At least one Pod is Running considered the Cluster be availabe
exp: true,
message: "First Pod is not Running and second Pod is Running should be false",
},
{
podlist: &v1.PodList{
Items: []v1.Pod{
{
Status: v1.PodStatus{
Phase: v1.PodRunning,
},
},
{
Status: v1.PodStatus{
Phase: v1.PodRunning,
},
},
},
},
exp: true,
message: "All Pods are running should be true",
},
{
podlist: &v1.PodList{
Items: []v1.Pod{
{
Status: v1.PodStatus{
Phase: v1.PodFailed,
},
},
{
Status: v1.PodStatus{
Phase: v1.PodFailed,
},
},
},
},
exp: false,
message: "All Pods are not running should be false",
},
{
podlist: &v1.PodList{
Items: []v1.Pod{
{
ObjectMeta: metav1.ObjectMeta{
DeletionTimestamp: &ts,
},
Status: v1.PodStatus{
Phase: v1.PodRunning,
},
},
},
},
exp: false,
message: "Pod is running with deletion timestamp should be false",
},
}
for _, c := range cases {
act := IsRunning(c.podlist)
if len(c.podlist.Items) != 0 {
assert.Equal(t, c.exp, act, c.message)
}
}
}
func Test_CountingReadyPods(t *testing.T) {
ts := metav1.Time{}
cases := []struct {
podlist *v1.PodList
exp int32
message string
}{
{
podlist: &v1.PodList{
Items: []v1.Pod{},
},
exp: 0,
message: "Empty PodList should be 0",
},
{
podlist: &v1.PodList{
Items: []v1.Pod{
{
Status: v1.PodStatus{
ContainerStatuses: []v1.ContainerStatus{},
},
},
},
},
exp: 0,
message: "Only one Pod without any container statuses should be 0",
},
{
podlist: &v1.PodList{
Items: []v1.Pod{
{
Status: v1.PodStatus{
ContainerStatuses: []v1.ContainerStatus{
{
Ready: true,
},
},
},
},
},
},
exp: 1,
message: "Only one Pod is running should be 1",
},
{
podlist: &v1.PodList{
Items: []v1.Pod{
{
ObjectMeta: metav1.ObjectMeta{
DeletionTimestamp: &ts,
},
Status: v1.PodStatus{
ContainerStatuses: []v1.ContainerStatus{
{
Ready: true,
},
},
},
},
},
},
exp: 0,
message: "Pod has ready container but with deletion timestamp should be 0",
},
{
podlist: &v1.PodList{
Items: []v1.Pod{
{
ObjectMeta: metav1.ObjectMeta{
DeletionTimestamp: &ts,
},
Status: v1.PodStatus{
ContainerStatuses: []v1.ContainerStatus{
{
Ready: true,
},
},
},
},
{
Status: v1.PodStatus{
ContainerStatuses: []v1.ContainerStatus{
{
Ready: true,
},
},
},
},
},
},
exp: 1,
message: "First Pod has ready container, second Pod has ready container but with deletion timestamp should be 0",
},
{
podlist: &v1.PodList{
Items: []v1.Pod{
{
Status: v1.PodStatus{
ContainerStatuses: []v1.ContainerStatus{
{
Ready: true,
},
},
},
},
{
ObjectMeta: metav1.ObjectMeta{
DeletionTimestamp: &ts,
},
Status: v1.PodStatus{
ContainerStatuses: []v1.ContainerStatus{
{
Ready: true,
},
},
},
},
},
},
exp: 1,
message: "First Pod has ready container but with deletion timestamp, second Pod has ready container should be 0",
},
}
for _, c := range cases {
act := CountingReadyPods(c.podlist)
assert.Equal(t, c.exp, act, c.message)
}
}
func Test_ConstructCascadingService(t *testing.T) {
cases := []struct {
proxy *v1alpha1.ShardingSphereProxy
exp *v1.Service
message string
}{
{
exp: &v1.Service{},
message: "Nil ShardingSphereProxy definition should lead to empty Service",
},
{
proxy: &v1alpha1.ShardingSphereProxy{},
exp: &v1.Service{},
message: "Empty ShardingSphereProxy definition should lead to empty Service",
},
{
proxy: &v1alpha1.ShardingSphereProxy{
ObjectMeta: metav1.ObjectMeta{
Name: "testname",
Namespace: "testnamespace",
},
Spec: v1alpha1.ProxySpec{
ServiceType: v1alpha1.ServiceType{
Type: v1.ServiceTypeNodePort,
NodePort: 33007,
},
Port: 3307,
},
},
exp: &v1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: "testname",
Namespace: "testnamespace",
},
Spec: v1.ServiceSpec{
Selector: map[string]string{
"apps": "testname",
},
Type: v1.ServiceTypeNodePort,
Ports: []v1.ServicePort{
{
Name: "proxy-port",
TargetPort: fromInt32(3307),
Port: 3307,
NodePort: 33007,
},
},
},
},
message: "Normal ShardingSphereProxy definition should lead to normal Service",
},
}
for _, c := range cases {
act := ConstructCascadingService(c.proxy)
assert.Equal(t, c.exp.ObjectMeta.Name, act.ObjectMeta.Name, c.message)
assert.Equal(t, c.exp.ObjectMeta.Namespace, act.ObjectMeta.Namespace, c.message)
if c.proxy != nil {
assert.EqualValues(t, c.exp.Spec.Selector, act.Spec.Selector, c.message)
assert.Equal(t, c.exp.Spec.Type, act.Spec.Type, c.message)
assert.Equal(t, c.exp.Spec.Ports, act.Spec.Ports, c.message)
}
}
}
func Test_ConstructCascadingConfigmap(t *testing.T) {
}
func Test_ConstructHPA(t *testing.T) {
}
func Test_UpdateService(t *testing.T) {
cases := []struct {
proxy *v1alpha1.ShardingSphereProxy
service *v1.Service
message string
}{
{
proxy: &v1alpha1.ShardingSphereProxy{
Spec: v1alpha1.ProxySpec{
ServiceType: v1alpha1.ServiceType{
Type: "ServiceTypeNodePort",
NodePort: 3001,
},
Port: 3000,
},
},
service: &v1.Service{
Spec: v1.ServiceSpec{
Ports: []v1.ServicePort{{
Port: 3000,
NodePort: 3001,
}},
},
},
message: "Service should be updated",
},
{
proxy: &v1alpha1.ShardingSphereProxy{
Spec: v1alpha1.ProxySpec{
ServiceType: v1alpha1.ServiceType{
Type: "ServiceTypeNodePort",
NodePort: 0,
},
Port: 3000,
},
},
service: &v1.Service{
Spec: v1.ServiceSpec{
Ports: []v1.ServicePort{{
Port: 3000,
NodePort: 3001,
}},
},
},
message: "Service NodePort should not be updated if proxy nodeport is 0",
},
}
for _, c := range cases {
UpdateService(c.proxy, c.service)
assert.Equal(t, c.proxy.Spec.ServiceType.Type, c.service.Spec.Type, c.message)
assert.Equal(t, c.proxy.Spec.Port, c.service.Spec.Ports[0].Port, c.message)
assert.Equal(t, c.proxy.Spec.Port, c.service.Spec.Ports[0].TargetPort.IntVal, c.message)
if c.proxy.Spec.ServiceType.NodePort != 0 {
assert.Equal(t, c.proxy.Spec.ServiceType.NodePort, c.service.Spec.Ports[0].NodePort, c.message)
}
}
}
func Test_UpdateHPA(t *testing.T) {
cases := []struct {
proxy *v1alpha1.ShardingSphereProxy
hpa *autoscalingv2beta2.HorizontalPodAutoscaler
message string
}{
{
proxy: &v1alpha1.ShardingSphereProxy{
Spec: v1alpha1.ProxySpec{
AutomaticScaling: &v1alpha1.AutomaticScaling{
Enable: true,
ScaleUpWindows: 15,
ScaleDownWindows: 30,
MaxInstance: 5,
MinInstance: 3,
Target: 70,
},
},
},
hpa: &autoscalingv2beta2.HorizontalPodAutoscaler{
Spec: autoscalingv2beta2.HorizontalPodAutoscalerSpec{
ScaleTargetRef: autoscalingv2beta2.CrossVersionObjectReference{
Kind: "Deployment",
Name: "test",
APIVersion: appsv1.SchemeGroupVersion.String(),
},
MinReplicas: pointer.Int32(3),
MaxReplicas: 10,
Metrics: []autoscalingv2beta2.MetricSpec{
{
Type: autoscalingv2beta2.ResourceMetricSourceType,
Resource: &autoscalingv2beta2.ResourceMetricSource{
Name: "cpu",
Target: autoscalingv2beta2.MetricTarget{
Type: autoscalingv2beta2.UtilizationMetricType,
AverageUtilization: pointer.Int32(60),
},
},
},
},
Behavior: &autoscalingv2beta2.HorizontalPodAutoscalerBehavior{
ScaleUp: &autoscalingv2beta2.HPAScalingRules{
StabilizationWindowSeconds: pointer.Int32(15),
},
ScaleDown: &autoscalingv2beta2.HPAScalingRules{
StabilizationWindowSeconds: pointer.Int32(60),
Policies: []autoscalingv2beta2.HPAScalingPolicy{
{
Type: autoscalingv2beta2.PodsScalingPolicy,
Value: 1,
PeriodSeconds: 30,
},
},
},
},
},
},
message: "hpa should be updated",
},
}
for _, c := range cases {
UpdateHPA(c.proxy, c.hpa)
assert.Equal(t, c.proxy.Spec.AutomaticScaling.Target, *c.hpa.Spec.Metrics[0].Resource.Target.AverageUtilization)
assert.Equal(t, c.proxy.Spec.AutomaticScaling.ScaleDownWindows, *c.hpa.Spec.Behavior.ScaleDown.StabilizationWindowSeconds)
}
}
func Test_UpdateHPAMetric(t *testing.T) {
cases := []struct {
proxy *v1alpha1.ShardingSphereProxy
hpa *autoscalingv2beta2.HorizontalPodAutoscaler
message string
}{
{
proxy: &v1alpha1.ShardingSphereProxy{
Spec: v1alpha1.ProxySpec{
AutomaticScaling: &v1alpha1.AutomaticScaling{
Enable: true,
ScaleUpWindows: 15,
ScaleDownWindows: 30,
MaxInstance: 5,
MinInstance: 3,
Target: 70,
CustomMetrics: []autoscalingv2beta2.MetricSpec{
{
Type: "pods",
Pods: &autoscalingv2beta2.PodsMetricSource{
Metric: autoscalingv2beta2.MetricIdentifier{
Name: "latency_seconds_test_avg",
},
Target: autoscalingv2beta2.MetricTarget{
Type: autoscalingv2beta2.AverageValueMetricType,
AverageValue: resource.NewQuantity(12, resource.DecimalSI),
},
},
},
},
},
},
},
hpa: &autoscalingv2beta2.HorizontalPodAutoscaler{
Spec: autoscalingv2beta2.HorizontalPodAutoscalerSpec{
ScaleTargetRef: autoscalingv2beta2.CrossVersionObjectReference{
Kind: "Deployment",
Name: "test",
APIVersion: appsv1.SchemeGroupVersion.String(),
},
MinReplicas: pointer.Int32(3),
MaxReplicas: 10,
Metrics: []autoscalingv2beta2.MetricSpec{
{
Type: autoscalingv2beta2.ResourceMetricSourceType,
Resource: &autoscalingv2beta2.ResourceMetricSource{
Name: "cpu",
Target: autoscalingv2beta2.MetricTarget{
Type: autoscalingv2beta2.UtilizationMetricType,
AverageUtilization: pointer.Int32(60),
},
},
},
},
Behavior: &autoscalingv2beta2.HorizontalPodAutoscalerBehavior{
ScaleUp: &autoscalingv2beta2.HPAScalingRules{
StabilizationWindowSeconds: pointer.Int32(15),
},
ScaleDown: &autoscalingv2beta2.HPAScalingRules{
StabilizationWindowSeconds: pointer.Int32(60),
Policies: []autoscalingv2beta2.HPAScalingPolicy{
{
Type: autoscalingv2beta2.PodsScalingPolicy,
Value: 1,
PeriodSeconds: 30,
},
},
},
},
},
},
message: "hpa should be updated",
},
}
for _, c := range cases {
UpdateHPA(c.proxy, c.hpa)
assert.Equal(t, c.proxy.Spec.AutomaticScaling.CustomMetrics, c.hpa.Spec.Metrics)
}
}