Skip to content

Commit

Permalink
Implement ProvReq service
Browse files Browse the repository at this point in the history
  • Loading branch information
yaroslava-serdiuk committed Nov 20, 2023
1 parent edad525 commit 75e9926
Show file tree
Hide file tree
Showing 6 changed files with 691 additions and 0 deletions.
137 changes: 137 additions & 0 deletions cluster-autoscaler/provisioningrequest/provreqwrapper/wrapper.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
/*
Copyright 2023 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 provreqwrapper

import (
"fmt"
"strings"

apiv1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
"k8s.io/autoscaler/cluster-autoscaler/provisioningrequest/apis/autoscaling.x-k8s.io/v1beta1"
)

// ProvisioningRequest wrapper representation of the ProvisisoningRequest
type ProvisioningRequest struct {
v1Beta1PR *v1beta1.ProvisioningRequest
v1Beta1PodTemplates []*apiv1.PodTemplate
}

// PodSet wrapper representation of the PodSet.
type PodSet struct {
// Count number of pods with given template.
Count int32
// PodTemplate template of given pod set.
PodTemplate apiv1.PodTemplateSpec
}

// NewV1Beta1ProvisioningRequest creates new ProvisioningRequest based on v1beta1 CR.
func NewV1Beta1ProvisioningRequest(v1Beta1PR *v1beta1.ProvisioningRequest, v1Beta1PodTemplates []*apiv1.PodTemplate) *ProvisioningRequest {
return &ProvisioningRequest{
v1Beta1PR: v1Beta1PR,
v1Beta1PodTemplates: v1Beta1PodTemplates,
}
}

// Name of the Provisioning Request.
func (pr *ProvisioningRequest) Name() string {
return pr.v1Beta1PR.Name
}

// Namespace of the Provisioning Request.
func (pr *ProvisioningRequest) Namespace() string {
return pr.v1Beta1PR.Namespace
}

// CreationTimestamp of the Provisioning Request.
func (pr *ProvisioningRequest) CreationTimestamp() metav1.Time {
return pr.v1Beta1PR.CreationTimestamp
}

// RuntimeObject returns runtime.Object of the Provisioning Request.
func (pr *ProvisioningRequest) RuntimeObject() runtime.Object {
return pr.v1Beta1PR
}

// APIVersion returns APIVersion of the Provisioning Request.
func (pr *ProvisioningRequest) APIVersion() string {
return pr.v1Beta1PR.APIVersion
}

// Kind returns Kind of the Provisioning Request.
func (pr *ProvisioningRequest) Kind() string {
return pr.v1Beta1PR.Kind

}

// UID returns UID of the Provisioning Request.
func (pr *ProvisioningRequest) UID() types.UID {
return pr.v1Beta1PR.UID
}

// Conditions of the Provisioning Request.
func (pr *ProvisioningRequest) Conditions() []metav1.Condition {
return pr.v1Beta1PR.Status.Conditions
}

// SetConditions of the Provisioning Request.
func (pr *ProvisioningRequest) SetConditions(conditions []metav1.Condition) {
pr.v1Beta1PR.Status.Conditions = conditions
return
}

// PodSets of the Provisioning Request.
func (pr *ProvisioningRequest) PodSets() ([]PodSet, error) {
if len(pr.v1Beta1PR.Spec.PodSets) != len(pr.v1Beta1PodTemplates) {
return nil, errMissingPodTemplates(pr.v1Beta1PR.Spec.PodSets, pr.v1Beta1PodTemplates)
}
podSets := make([]PodSet, 0, len(pr.v1Beta1PR.Spec.PodSets))
for i, podSet := range pr.v1Beta1PR.Spec.PodSets {
podSets = append(podSets, PodSet{
Count: podSet.Count,
PodTemplate: pr.v1Beta1PodTemplates[i].Template,
})
}
return podSets, nil
}

// V1Beta1 returns v1beta1 object CR, to be used only to pass information to clients.
func (pr *ProvisioningRequest) V1Beta1() *v1beta1.ProvisioningRequest {
return pr.v1Beta1PR
}

// PodTemplates returns pod templates associated with the Provisioning Request, to be used only to pass information to clients.
func (pr *ProvisioningRequest) PodTemplates() []*apiv1.PodTemplate {
return pr.v1Beta1PodTemplates
}

// errMissingPodTemplates creates error that is passed when there are missing pod templates.
func errMissingPodTemplates(podSets []v1beta1.PodSet, podTemplates []*apiv1.PodTemplate) error {
foundPodTemplates := map[string]struct{}{}
for _, pt := range podTemplates {
foundPodTemplates[pt.Name] = struct{}{}
}
missingTemplates := make([]string, 0)
for _, ps := range podSets {
if _, found := foundPodTemplates[ps.PodTemplateRef.Name]; !found {
missingTemplates = append(missingTemplates, ps.PodTemplateRef.Name)
}
}
return fmt.Errorf("missing pod templates, %d pod templates were referenced, %d templates were missing: %s", len(podSets), len(missingTemplates), strings.Join(missingTemplates, ","))
}
142 changes: 142 additions & 0 deletions cluster-autoscaler/provisioningrequest/provreqwrapper/wrapper_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
/*
Copyright 2023 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 provreqwrapper

import (
"testing"
"time"

"github.com/stretchr/testify/assert"
apiv1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/autoscaler/cluster-autoscaler/provisioningrequest/apis/autoscaling.x-k8s.io/v1beta1"
)

func TestProvisioningRequestWrapper(t *testing.T) {
creationTimestamp := metav1.NewTime(time.Date(2023, 11, 12, 13, 14, 15, 0, time.UTC))
conditions := []metav1.Condition{
{
LastTransitionTime: metav1.NewTime(time.Date(2022, 11, 12, 13, 14, 15, 0, time.UTC)),
Message: "Message",
ObservedGeneration: 1,
Reason: "Reason",
Status: "Status",
Type: "ConditionType",
},
}
podSets := []PodSet{
{
Count: 1,
PodTemplate: apiv1.PodTemplateSpec{
Spec: apiv1.PodSpec{
Containers: []apiv1.Container{
{
Name: "test-container",
Image: "test-image",
},
},
},
},
},
}

podTemplates := []*apiv1.PodTemplate{
{
ObjectMeta: metav1.ObjectMeta{
Name: "name-pod-template-beta",
Namespace: "namespace-beta",
CreationTimestamp: creationTimestamp,
},
Template: apiv1.PodTemplateSpec{
Spec: apiv1.PodSpec{
Containers: []apiv1.Container{
{
Name: "test-container",
Image: "test-image",
},
},
},
},
},
}
v1Beta1PR := &v1beta1.ProvisioningRequest{
TypeMeta: metav1.TypeMeta{
APIVersion: "beta-api",
Kind: "beta-kind",
},
ObjectMeta: metav1.ObjectMeta{
Name: "name-beta",
Namespace: "namespace-beta",
CreationTimestamp: creationTimestamp,
UID: types.UID("beta-uid"),
},
Spec: v1beta1.ProvisioningRequestSpec{
ProvisioningClassName: "queued-provisioning.gke.io",
PodSets: []v1beta1.PodSet{
{
Count: 1,
PodTemplateRef: v1beta1.Reference{
Name: podTemplates[0].Name,
},
},
},
},
Status: v1beta1.ProvisioningRequestStatus{
Conditions: conditions,
ProvisioningClassDetails: map[string]v1beta1.Detail{},
},
}

wrappedBetaPR := NewV1Beta1ProvisioningRequest(v1Beta1PR, podTemplates)

// Check Name, Namespace and Creation accessors
assert.Equal(t, "name-beta", wrappedBetaPR.Name())
assert.Equal(t, "namespace-beta", wrappedBetaPR.Namespace())
assert.Equal(t, creationTimestamp, wrappedBetaPR.CreationTimestamp())

// Check APIVersion, Kind and UID accessors
assert.Equal(t, "beta-api", wrappedBetaPR.APIVersion())
assert.Equal(t, "beta-kind", wrappedBetaPR.Kind())
assert.Equal(t, types.UID("beta-uid"), wrappedBetaPR.UID())

// Check the initial conditions
assert.Equal(t, conditions, wrappedBetaPR.Conditions())

// Clear conditions and check the values
wrappedBetaPR.SetConditions(nil)
assert.Nil(t, wrappedBetaPR.Conditions())

// Set conditions and check the values
wrappedBetaPR.SetConditions(conditions)
assert.Equal(t, conditions, wrappedBetaPR.Conditions())

// Check the PodSets
betaPodSets, betaErr := wrappedBetaPR.PodSets()
assert.Nil(t, betaErr)
assert.Equal(t, podSets, betaPodSets)

// Check the type accessors.
assert.Equal(t, v1Beta1PR, wrappedBetaPR.V1Beta1())
assert.Equal(t, podTemplates, wrappedBetaPR.PodTemplates())

// Check case where the Provisioning Request is missing Pod Templates.
wrappedBetaPRMissingPodTemplates := NewV1Beta1ProvisioningRequest(v1Beta1PR, nil)
podSets, err := wrappedBetaPRMissingPodTemplates.PodSets()
assert.Nil(t, podSets)
assert.EqualError(t, err, "missing pod templates, 1 pod templates were referenced, 1 templates were missing: name-pod-template-beta")
}
72 changes: 72 additions & 0 deletions cluster-autoscaler/provisioningrequest/service/service.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/*
Copyright 2023 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 provreqservice

import (
"fmt"

"k8s.io/autoscaler/cluster-autoscaler/provisioningrequest/provreqwrapper"
"k8s.io/autoscaler/cluster-autoscaler/provisioningrequest/service/v1beta1client"
"k8s.io/client-go/rest"
)

// ProvisioningRequestService represents the service that is able to list,
// access and delete different Provisioning Requests.
type ProvisioningRequestService struct {
provReqV1Beta1Client *v1beta1client.ProvisioningRequestClient
}

// NewProvisioningRequestService returns new service for interacting with ProvisioningRequests.
func NewProvisioningRequestService(kubeConfig *rest.Config) (*ProvisioningRequestService, error) {
v1Beta1Client, err := v1beta1client.NewProvisioningRequestClient(kubeConfig)
if err != nil {
return nil, err
}
return &ProvisioningRequestService{
provReqV1Beta1Client: v1Beta1Client,
}, nil
}

// ProvisioningRequest gets a specific ProvisioningRequest CR.
func (s *ProvisioningRequestService) ProvisioningRequest(namespace, name string) (*provreqwrapper.ProvisioningRequest, error) {
v1Beta1PR, err := s.provReqV1Beta1Client.ProvisioningRequest(namespace, name)
if err == nil {
podTemplates, errPodTemplates := s.provReqV1Beta1Client.FetchPodTemplates(v1Beta1PR)
if errPodTemplates != nil {
return nil, fmt.Errorf("while fetching pod templates for Get Provisioning Request %s/%s got error: %v", namespace, name, errPodTemplates)
}
return provreqwrapper.NewV1Beta1ProvisioningRequest(v1Beta1PR, podTemplates), nil
}
return nil, err
}

// ProvisioningRequests gets all Queued ProvisioningRequest CRs.
func (s *ProvisioningRequestService) ProvisioningRequests() ([]*provreqwrapper.ProvisioningRequest, error) {
v1Beta1PRs, err := s.provReqV1Beta1Client.ProvisioningRequests()
if err != nil {
return nil, err
}
prs := make([]*provreqwrapper.ProvisioningRequest, 0, len(v1Beta1PRs))
for _, v1Beta1PR := range v1Beta1PRs {
podTemplates, errPodTemplates := s.provReqV1Beta1Client.FetchPodTemplates(v1Beta1PR)
if errPodTemplates != nil {
return nil, fmt.Errorf("while fetching pod templates for List Provisioning Request %s/%s got error: %v", v1Beta1PR.Namespace, v1Beta1PR.Name, errPodTemplates)
}
prs = append(prs, provreqwrapper.NewV1Beta1ProvisioningRequest(v1Beta1PR, podTemplates))
}
return prs, nil
}
Loading

0 comments on commit 75e9926

Please sign in to comment.