Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add annotations #176

Merged
merged 4 commits into from
Oct 26, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
166 changes: 51 additions & 115 deletions internal/api/v1beta1/app_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import (
"fmt"
"math"
"regexp"
"strings"
"strconv"
"time"

"github.com/go-logr/logr"
Expand Down Expand Up @@ -524,33 +524,33 @@ func getUpdatedUnits(weight uint8, targetUnits uint16) (int, int) {
func (app *App) DoCanary(now metav1.Time, logger logr.Logger, recorder record.EventRecorder) error {
if !app.Spec.Canary.Active {
failEvent := newCanaryEvent(app, CanaryNotActiveEvent, CanaryNotActiveEventDesc)
recorder.Event(app, v1.EventTypeNormal, failEvent.Name, failEvent.Message())
recorder.AnnotatedEventf(app, failEvent.Annotations, v1.EventTypeNormal, failEvent.Name, failEvent.Message())
return nil
}

if len(app.Spec.Deployments) <= 1 {
failEvent := newCanaryEvent(app, CanaryNoDeployments, CanaryNoDeploymentsDesc)
recorder.Event(app, v1.EventTypeWarning, failEvent.Name, failEvent.Message())
recorder.AnnotatedEventf(app, failEvent.Annotations, v1.EventTypeWarning, failEvent.Name, failEvent.Message())
return errors.New("no canary deployment found")
}

if app.Spec.Canary.NextScheduledTime == nil {
failEvent := newCanaryEvent(app, CanaryNoScheduledSteps, CanaryNoScheduledStepsDesc)
recorder.Event(app, v1.EventTypeWarning, failEvent.Name, failEvent.Message())
recorder.AnnotatedEventf(app, failEvent.Annotations, v1.EventTypeWarning, failEvent.Name, failEvent.Message())
return errors.New("canary is active but the next step is not scheduled")
}

if app.Spec.Canary.NextScheduledTime.Equal(&now) || app.Spec.Canary.NextScheduledTime.Before(&now) {
if app.Spec.Canary.CurrentStep == 1 {
event := newCanaryEvent(app, CanaryStarted, CanaryStartedDesc)
recorder.Eventf(app, v1.EventTypeNormal, event.Name, event.Message())
recorder.AnnotatedEventf(app, event.Annotations, v1.EventTypeNormal, event.Name, event.Message())
}
// update traffic weight distributions across deployments
app.Spec.Deployments[0].RoutingSettings.Weight = app.Spec.Deployments[0].RoutingSettings.Weight - app.Spec.Canary.StepWeight
app.Spec.Deployments[1].RoutingSettings.Weight = app.Spec.Deployments[1].RoutingSettings.Weight + app.Spec.Canary.StepWeight

eventStep := newCanaryNextStepEvent(app)
recorder.Eventf(app, v1.EventTypeNormal, eventStep.Event.Name, eventStep.Message())
recorder.AnnotatedEventf(app, eventStep.Annotations, v1.EventTypeNormal, eventStep.Name, eventStep.Message())

if app.Spec.Canary.Target != nil {
// scale units based on weight and process target
Expand All @@ -565,7 +565,7 @@ func (app *App) DoCanary(now metav1.Time, logger logr.Logger, recorder record.Ev
}

eventTarget := newCanaryTargetChangeEvent(app, processName, p1Units, p2Units)
recorder.Event(app, v1.EventTypeNormal, eventTarget.Event.Name, eventTarget.Message())
recorder.AnnotatedEventf(app, eventTarget.Annotations, v1.EventTypeNormal, eventTarget.Name, eventTarget.Message())
}

// if a process in the updated deployment isn't found in target create 1 unit
Expand Down Expand Up @@ -677,12 +677,25 @@ const (
CanaryNextStepDesc = "weight change"
CanaryStepTarget = "CanaryStepTarget"
CanaryStepTargetDesc = "units change"

CanaryAnnotationAppName = "canary.shipa.io/app-name"
CanaryAnnotationDevelopmentVersion = "canary.shipa.io/deployment-version"
CanaryAnnotationEventName = "canary.shipa.io/event-name"
CanaryAnnotationDescription = "canary.shipa.io/description"
CanaryAnnotationStep = "canary.shipa.io/step"
CanaryAnnotationVersionSource = "canary.shipa.io/version-source"
CanaryAnnotationVersionDest = "canary.shipa.io/version-dest"
CanaryAnnotationWeightSource = "canary.shipa.io/weight-source"
CanaryAnnotationWeightDest = "canary.shipa.io/weight-dest"
CanaryAnnotationProcessName = "canary.shipa.io/process-name"
CanaryAnnotationProcessUnitsSource = "canary.shipa.io/source-process-units"
CanaryAnnotationProcessUnitsDest = "canary.shipa.io/dest-process-units"
)

type CanaryEvent struct {
// AppName represents event for certain app
AppName string
// DeploymentVersion represents for which deployment event is accosted with
// DeploymentVersion represents for which deployment event is associated with
DeploymentVersion int

// Name represents canary event name. It is translated into Reason column of kubernetes event
Expand All @@ -691,6 +704,8 @@ type CanaryEvent struct {
Name string
// Description states what is the outcome of this event
Description string
// Annotations contain details on the Canary deployment
Annotations map[string]string
}

func newCanaryEvent(app *App, event string, desc string) CanaryEvent {
Expand All @@ -704,6 +719,12 @@ func newCanaryEvent(app *App, event string, desc string) CanaryEvent {
DeploymentVersion: int(version),
Name: event,
Description: desc,
Annotations: map[string]string{
CanaryAnnotationAppName: app.Name,
CanaryAnnotationDevelopmentVersion: version.String(),
CanaryAnnotationEventName: event,
CanaryAnnotationDescription: desc,
},
}
}

Expand All @@ -712,119 +733,34 @@ func (c CanaryEvent) Message() string {
return fmt.Sprintf("%s - Canary for app %s | version %d - %s", c.Name, c.AppName, c.DeploymentVersion, c.Description)
}

// CanaryEventFromMessage creates CanaryEvent from given message
// NOTE as description can contain spaces, it won't be perfectly populated for some events
func CanaryEventFromMessage(msg string) (*CanaryEvent, error) {
event := CanaryEvent{}

_, err := fmt.Sscanf(msg, "%s - Canary for app %s | version %d - %s", &event.Name, &event.AppName, &event.DeploymentVersion, &event.Description)
if err != nil {
return nil, fmt.Errorf(`unable to parse CanaryEvent: %s, err: %v`, msg, err)
func newCanaryNextStepEvent(app *App) CanaryEvent {
additionalAnnotations := map[string]string{
CanaryAnnotationStep: strconv.Itoa(app.Spec.Canary.CurrentStep),
CanaryAnnotationVersionSource: app.Spec.Deployments[0].Version.String(),
CanaryAnnotationVersionDest: app.Spec.Deployments[1].Version.String(),
CanaryAnnotationWeightSource: strconv.Itoa(int(app.Spec.Deployments[0].RoutingSettings.Weight)),
CanaryAnnotationWeightDest: strconv.Itoa(int(app.Spec.Deployments[1].RoutingSettings.Weight)),
}

return &event, nil
}

type CanaryNextStepEvent struct {
Event CanaryEvent

Step int
VersionSource int
VersionDest int
WeightSource uint8
WeightDest uint8
}

func newCanaryNextStepEvent(app *App) CanaryNextStepEvent {
return CanaryNextStepEvent{
Event: newCanaryEvent(app, CanaryNextStep, CanaryNextStepDesc),

Step: app.Spec.Canary.CurrentStep,
VersionSource: int(app.Spec.Deployments[0].Version),
VersionDest: int(app.Spec.Deployments[1].Version),
WeightSource: app.Spec.Deployments[0].RoutingSettings.Weight,
WeightDest: app.Spec.Deployments[1].RoutingSettings.Weight,
event := newCanaryEvent(app, CanaryNextStep, CanaryNextStepDesc)
for key, value := range additionalAnnotations {
event.Annotations[key] = value
}
return event
}

func (c CanaryNextStepEvent) Message() string {
return fmt.Sprintf("%s: Step: %d | Source version: %d | Dest version: %d | Source weight: %d | Dest weight: %d", c.Event.Message(), c.Step, c.VersionSource, c.VersionDest, c.WeightSource, c.WeightDest)
}

// CanaryNextStepEventFromString creates CanaryNextStepEvent from given message
// NOTE as description can contain spaces, it won't be perfectly populated for some events
func CanaryNextStepEventFromString(msg string) (*CanaryNextStepEvent, error) {
split := strings.SplitAfterN(msg, ":", 2)
if len(split) < 2 {
return nil, errors.New("invalid message format")
}

canaryEvent, err := CanaryEventFromMessage(split[0])
if err != nil {
return nil, err
}

event := CanaryNextStepEvent{
Event: *canaryEvent,
func newCanaryTargetChangeEvent(app *App, processName string, sourceUnits, destUnits int) CanaryEvent {
additionalAnnotations := map[string]string{
CanaryAnnotationVersionSource: app.Spec.Deployments[0].Version.String(),
CanaryAnnotationVersionDest: app.Spec.Deployments[1].Version.String(),
CanaryAnnotationProcessName: processName,
CanaryAnnotationProcessUnitsSource: strconv.Itoa(sourceUnits),
CanaryAnnotationProcessUnitsDest: strconv.Itoa(destUnits),
}

_, err = fmt.Sscanf(split[1], " Step: %d | Source version: %d | Dest version: %d | Source weight: %d | Dest weight: %d", &event.Step, &event.VersionSource, &event.VersionDest, &event.WeightSource, &event.WeightDest)
if err != nil {
return nil, fmt.Errorf(`unable to parse CanaryEvent: %s, err: %v`, msg, err)
event := newCanaryEvent(app, CanaryStepTarget, CanaryStepTargetDesc)
for key, value := range additionalAnnotations {
event.Annotations[key] = value
}

return &event, nil
}

type CanaryTargetChangeEvent struct {
Event CanaryEvent

VersionSource int
VersionDest int
ProcessName string
SourceProcessUnits int
DestinationProcessUnits int
}

func newCanaryTargetChangeEvent(app *App, processName string, sourceUnits, destUnits int) CanaryTargetChangeEvent {
return CanaryTargetChangeEvent{
Event: newCanaryEvent(app, CanaryStepTarget, CanaryStepTargetDesc),

VersionSource: int(app.Spec.Deployments[0].Version),
VersionDest: int(app.Spec.Deployments[1].Version),
ProcessName: processName,
SourceProcessUnits: sourceUnits,
DestinationProcessUnits: destUnits,
}
}

func (c CanaryTargetChangeEvent) Message() string {
return fmt.Sprintf("%s: Source version: %d | Dest version: %d | Process: %s | Source units: %d | Dest units: %d", c.Event.Message(), c.VersionSource, c.VersionDest, c.ProcessName, c.SourceProcessUnits, c.DestinationProcessUnits)
}

// CanaryTargetChangeEventFromString creates CanaryTargetChangeEvent from given message
// NOTE as description can contain spaces, it won't be perfectly populated for some events
func CanaryTargetChangeEventFromString(msg string) (*CanaryTargetChangeEvent, error) {
split := strings.SplitN(msg, ":", 2)
if len(split) < 2 {
return nil, errors.New("invalid message format")
}

canaryEvent, err := CanaryEventFromMessage(split[0])
if err != nil {
return nil, err
}

event := CanaryTargetChangeEvent{
Event: *canaryEvent,
}

_, err = fmt.Sscanf(split[1], " Source version: %d | Dest version: %d | Process: %s | Source units: %d | Dest units: %d", &event.VersionSource, &event.VersionDest, &event.ProcessName, &event.SourceProcessUnits, &event.DestinationProcessUnits)
if err != nil {
return nil, fmt.Errorf(`unable to parse CanaryEvent: %s, err: %v`, msg, err)
}

return &event, nil
return event
}

const (
Expand Down
93 changes: 32 additions & 61 deletions internal/api/v1beta1/app_types_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1318,6 +1318,12 @@ func TestGetUpdatedUnits(t *testing.T) {
}

func TestCanaryEvent_Message(t *testing.T) {
expectedAnnotations := map[string]string{
CanaryAnnotationAppName: "app1",
CanaryAnnotationDevelopmentVersion: "2",
CanaryAnnotationDescription: "started",
CanaryAnnotationEventName: "CanaryStarted",
}
event := newCanaryEvent(&App{
ObjectMeta: metav1.ObjectMeta{Name: "app1"},
Spec: AppSpec{
Expand All @@ -1329,23 +1335,21 @@ func TestCanaryEvent_Message(t *testing.T) {
}},
CanaryStarted, CanaryStartedDesc,
)

require.Equal(t, "CanaryStarted - Canary for app app1 | version 2 - started", event.Message())
}

func TestCanaryEventFromMessage(t *testing.T) {
message := "CanaryStarted - Canary for app app1 | version 2 - started"
event, err := CanaryEventFromMessage(message)
require.Nil(t, err)
require.Equal(t, CanaryEvent{
AppName: "app1",
DeploymentVersion: 2,
Name: "CanaryStarted",
Description: "started",
}, *event)
require.Equal(t, expectedAnnotations, event.Annotations)
}

func TestCanaryNextStepEvent_Message(t *testing.T) {
expectedAnnotations := map[string]string{
CanaryAnnotationAppName: "app1",
CanaryAnnotationDevelopmentVersion: "2",
CanaryAnnotationDescription: "weight change",
CanaryAnnotationEventName: "CanaryNextStep",
CanaryAnnotationStep: "10",
CanaryAnnotationVersionDest: "2",
CanaryAnnotationVersionSource: "1",
CanaryAnnotationWeightDest: "70",
CanaryAnnotationWeightSource: "30",
}
event := newCanaryNextStepEvent(&App{
ObjectMeta: metav1.ObjectMeta{Name: "app1"},
Spec: AppSpec{
Expand All @@ -1356,32 +1360,21 @@ func TestCanaryNextStepEvent_Message(t *testing.T) {
},
}},
)

require.Equal(t, "CanaryNextStep - Canary for app app1 | version 2 - weight change: Step: 10 | Source version: 1 | Dest version: 2 | Source weight: 30 | Dest weight: 70", event.Message())
require.Equal(t, expectedAnnotations, event.Annotations)
}

func TestCanaryNextStepEventFromString(t *testing.T) {
message := "CanaryNextStep - Canary for app app1 | version 2 - weight change: Step: 10 | Source version: 1 | Dest version: 2 | Source weight: 30 | Dest weight: 70"
event, err := CanaryNextStepEventFromString(message)
require.Nil(t, err)
require.Equal(t, *event,
CanaryNextStepEvent{
Event: CanaryEvent{
AppName: "app1",
DeploymentVersion: 2,
Name: "CanaryNextStep",
Description: "weight",
},
Step: 10,
VersionSource: 1,
VersionDest: 2,
WeightSource: 30,
WeightDest: 70,
},
)
}

func TestCanaryTargetChangeEvent_Message(t *testing.T) {
func TestCanaryTargetChangeEvent_Annotations(t *testing.T) {
expectedAnnotations := map[string]string{
CanaryAnnotationAppName: "app1",
CanaryAnnotationDevelopmentVersion: "2",
CanaryAnnotationDescription: "units change",
CanaryAnnotationProcessUnitsDest: "5",
CanaryAnnotationEventName: "CanaryStepTarget",
CanaryAnnotationProcessName: "p1",
CanaryAnnotationProcessUnitsSource: "2",
CanaryAnnotationVersionDest: "2",
CanaryAnnotationVersionSource: "1",
}
event := newCanaryTargetChangeEvent(&App{
ObjectMeta: metav1.ObjectMeta{Name: "app1"},
Spec: AppSpec{
Expand All @@ -1393,29 +1386,7 @@ func TestCanaryTargetChangeEvent_Message(t *testing.T) {
}},
"p1", 2, 5,
)

require.Equal(t, "CanaryStepTarget - Canary for app app1 | version 2 - units change: Source version: 1 | Dest version: 2 | Process: p1 | Source units: 2 | Dest units: 5", event.Message())
}

func TestCanaryTargetChangeEventFromString(t *testing.T) {
message := "CanaryStepTarget - Canary for app app1 | version 2 - units change: Source version: 1 | Dest version: 2 | Process: p1 | Source units: 2 | Dest units: 5"
event, err := CanaryTargetChangeEventFromString(message)
require.Nil(t, err)
require.Equal(t, *event,
CanaryTargetChangeEvent{
Event: CanaryEvent{
AppName: "app1",
DeploymentVersion: 2,
Name: "CanaryStepTarget",
Description: "units",
},
VersionSource: 1,
VersionDest: 2,
ProcessName: "p1",
SourceProcessUnits: 2,
DestinationProcessUnits: 5,
},
)
require.Equal(t, expectedAnnotations, event.Annotations)
}

func TestAppReconcileOutcome_String(t *testing.T) {
Expand Down