Skip to content

Commit

Permalink
CRD type definitions for the Service object (#412) (#577)
Browse files Browse the repository at this point in the history
* add types for Service and generate related code. Issue #412

* add types for Service and generate related code. Issue #412

* Convert tabs to spaces, reformat service_types.go

* add tests for service types

* convert tabs to spaces

* fix spelling mistakes in service definition

* rerun codegen after fixing spelling in json types
  • Loading branch information
mikehelmick authored and Ville Aikas committed Apr 3, 2018
1 parent d8b70af commit f265fff
Show file tree
Hide file tree
Showing 19 changed files with 1,057 additions and 0 deletions.
2 changes: 2 additions & 0 deletions pkg/apis/ela/v1alpha1/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ go_library(
"register.go",
"revision_types.go",
"route_types.go",
"service_types.go",
"zz_generated.deepcopy.go",
],
importpath = "github.com/elafros/elafros/pkg/apis/ela/v1alpha1",
Expand All @@ -28,6 +29,7 @@ go_test(
"configuration_types_test.go",
"revision_types_test.go",
"route_types_test.go",
"service_types_test.go",
],
embed = [":go_default_library"],
deps = ["//vendor/k8s.io/api/core/v1:go_default_library"],
Expand Down
2 changes: 2 additions & 0 deletions pkg/apis/ela/v1alpha1/register.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ func addKnownTypes(scheme *runtime.Scheme) error {
&ConfigurationList{},
&Route{},
&RouteList{},
&Service{},
&ServiceList{},
)
metav1.AddToGroupVersion(scheme, SchemeGroupVersion)
return nil
Expand Down
167 changes: 167 additions & 0 deletions pkg/apis/ela/v1alpha1/service_types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
/*
Copyright 2018 Google LLC.
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 v1alpha1

import (
"encoding/json"

corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

// +genclient
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object

// Service
type Service struct {
metav1.TypeMeta `jsonL",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`

Spec ServiceSpec `json:"spec,omitempty"`
Status ServiceStatus `json:"status,omitempty"`
}

// ServiceSpec represents the configuration for the Service object.
// Exactly one of its members (other than Generation) must be specified.
type ServiceSpec struct {
// TODO: Generation does not work correctly with CRD. They are scrubbed
// by the APIserver (https://github.com/kubernetes/kubernetes/issues/58778)
// So, we add Generation here. Once that gets fixed, remove this and use
// ObjectMeta.Generation instead.
Generation int64 `json:"generation,omitempty"`

// RunLatest defines a simple Service. It will automatically
// configure a route that keeps the latest ready revision
// from the supplied configuration running.
// +optional
RunLatest *RunLatestType `json:"runLatest,omitempty"`

// Pins this service to a specific revision name. The revision must
// be owned by the configuration provided.
// +optional
Pinned *PinnedType `json:"pinned,omitempty"`
}

type RunLatestType struct {
// The configuration for this service.
Configuration *ConfigurationSpec `json:"configuration,omitempty"`
}

type PinnedType struct {
// The revision name to pin this service to until changed
// to a different service type.
RevisionName string `json:"revisionName,omitempty"`

// The configuration for this service.
Configuration *ConfigurationSpec `json:"configuration,omitempty"`
}

type ServiceCondition struct {
Type ServiceConditionType `json:"state"`

Status corev1.ConditionStatus `json:"status" description:"status of the condition, one of True, False, Unknown"`

// +optional
Reason string `json:"reason,omitempty" description:"one-word CamelCase reason for the condition's last transition"`
// +optional
Message string `json:"message,omitempty" description:"human-readable message indicating details about last transition"`
}

// ServiceConditionType represents an Service condition value
type ServiceConditionType string

const (
// ServiceConditionReady is set when the service is configured
// and has available backends ready to receive traffic.
ServiceConditionReady ServiceConditionType = "Ready"
// ServiceConditionFailed is set when the service is not configured
// properly or has no available backends ready to receive traffic.
ServiceConditionFailed ServiceConditionType = "Failed"
)


type ServiceStatus struct {
Conditions []ServiceCondition `json:"conditions,omitempty"`

// ObservedGeneration is the 'Generation' of the Service that
// was last processed by the controller.
ObservedGeneration int64 `json:"observedGeneration,omitempty"`
}

// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object

// ServiceList is a list of Service resources
type ServiceList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata"`

Items []Service `json:"items"`
}

func (s *Service) GetGeneration() int64 {
return s.Spec.Generation
}

func (s *Service) SetGeneration(generation int64) {
s.Spec.Generation = generation
}

func (s *Service) GetSpecJSON() ([]byte, error) {
return json.Marshal(s.Spec)
}

func (ss *ServiceStatus) IsReady() bool {
if c := ss.GetCondition(ServiceConditionReady); c != nil {
return c.Status == corev1.ConditionTrue
}
return false
}

func (ss *ServiceStatus) GetCondition(t ServiceConditionType) *ServiceCondition {
for _, cond := range ss.Conditions {
if cond.Type == t {
return &cond
}
}
return nil
}

func (ss *ServiceStatus) SetCondition(new *ServiceCondition) {
if new == nil {
return
}

t := new.Type
var conditions []ServiceCondition
for _, cond := range ss.Conditions {
if cond.Type != t {
conditions = append(conditions, cond)
}
}
conditions = append(conditions, *new)
ss.Conditions = conditions
}

func (ss *ServiceStatus) RemoveCondition(t ServiceConditionType) {
var conditions []ServiceCondition
for _, cond := range ss.Conditions {
if cond.Type != t {
conditions = append(conditions, cond)
}
}
ss.Conditions = conditions
}
185 changes: 185 additions & 0 deletions pkg/apis/ela/v1alpha1/service_types_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
/*
Copyright 2018 Google LLC. All rights reserved.
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 v1alpha1

import (
"testing"

corev1 "k8s.io/api/core/v1"
)

func TestServiceGeneration(t *testing.T) {
service := Service{}
if got, want := service.GetGeneration(), int64(0); got != want {
t.Errorf("Empty Service generation should be %d, was %d", want, got)
}

answer := int64(42)
service.SetGeneration(answer)
if got := service.GetGeneration(); got != answer {
t.Errorf("GetGeneration mismatch; got %d, want %d", got, answer)
}
}

func TestServiceIsReady(t *testing.T) {
cases := []struct {
name string
status ServiceStatus
isReady bool
}{
{
name: "empty status should not be ready",
status: ServiceStatus{},
isReady: false,
},
{
name: "Different condition type should not be ready",
status: ServiceStatus{
Conditions: []ServiceCondition{
{
Type: "Foo",
Status: corev1.ConditionTrue,
},
},
},
isReady: false,
},
{
name: "False condition status should not be ready",
status: ServiceStatus{
Conditions: []ServiceCondition{
{
Type: ServiceConditionReady,
Status: corev1.ConditionFalse,
},
},
},
isReady: false,
},
{
name: "Unknown condition status should not be ready",
status: ServiceStatus{
Conditions: []ServiceCondition{
{
Type: ServiceConditionReady,
Status: corev1.ConditionUnknown,
},
},
},
isReady: false,
},
{
name: "Missing condition status should not be ready",
status: ServiceStatus{
Conditions: []ServiceCondition{
{
Type: ServiceConditionReady,
},
},
},
isReady: false,
},
{
name: "True condition status should be ready",
status: ServiceStatus{
Conditions: []ServiceCondition{
{
Type: ServiceConditionReady,
Status: corev1.ConditionTrue,
},
},
},
isReady: true,
},
{
name: "Multiple conditions with ready status should be ready",
status: ServiceStatus{
Conditions: []ServiceCondition{
{
Type: "Foo",
Status: corev1.ConditionTrue,
},
{
Type: ServiceConditionReady,
Status: corev1.ConditionTrue,
},
},
},
isReady: true,
},
{
name: "Multiple conditions with ready status false should not be ready",
status: ServiceStatus{
Conditions: []ServiceCondition{
{
Type: "Foo",
Status: corev1.ConditionTrue,
},
{
Type: ServiceConditionReady,
Status: corev1.ConditionFalse,
},
},
},
isReady: false,
},
}

for _, tc := range cases {
if e, a := tc.isReady, tc.status.IsReady(); e != a {
t.Errorf("%q expected: %v got: %v", tc.name, e, a)
}
}
}

func TestServiceConditions(t *testing.T) {
svc := &Service{}
foo := &ServiceCondition {
Type: "Foo",
Status: "True",
}
bar := &ServiceCondition {
Type: "Bar",
Status: "True",
}

// Add a single condition.
svc.Status.SetCondition(foo)
if got, want := len(svc.Status.Conditions), 1; got != want {
t.Fatalf("Unexpected Condition length; got %d, want %d", got, want)
}

// Remove non-existent condition.
svc.Status.RemoveCondition(bar.Type)
if got, want := len(svc.Status.Conditions), 1; got != want {
t.Fatalf("Unexpected Condition length; got %d, want %d", got, want)
}

// Add a second Condition.
svc.Status.SetCondition(bar)
if got, want := len(svc.Status.Conditions), 2; got != want {
t.Fatalf("Unexpected Condition length; got %d, want %d", got, want)
}

// Remove the first Condition.
svc.Status.RemoveCondition(foo.Type)
if got, want := len(svc.Status.Conditions), 1; got != want {
t.Fatalf("Unexpected condition length; got %d, want %d", got, want)
}

// Test Add nil condition.
svc.Status.SetCondition(nil)
if got, want := len(svc.Status.Conditions), 1; got != want {
t.Fatal("Error, nil condition was allowed to be added.")
}
}
Loading

0 comments on commit f265fff

Please sign in to comment.