Skip to content

Commit

Permalink
fix build-pod strategy on kubernetes
Browse files Browse the repository at this point in the history
  • Loading branch information
lburgazzoli committed Jul 19, 2019
1 parent b03fe86 commit d86ae77
Show file tree
Hide file tree
Showing 8 changed files with 204 additions and 76 deletions.
1 change: 1 addition & 0 deletions deploy/builder-role-kubernetes.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ rules:
- ""
resources:
- events
- configmaps
verbs:
- get
- list
Expand Down
1 change: 1 addition & 0 deletions deploy/resources.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion pkg/controller/build/build_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,8 @@ func (r *ReconcileBuild) Reconcile(request reconcile.Request) (reconcile.Result,
}

actions := []Action{
NewInitializeAction(),
NewInitializeRoutineAction(),
NewInitializePodAction(),
NewScheduleRoutineAction(r.reader, r.builder, &r.routines),
NewSchedulePodAction(r.reader),
NewMonitorRoutineAction(&r.routines),
Expand Down
88 changes: 88 additions & 0 deletions pkg/controller/build/initialize_pod.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/*
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 build

import (
"context"

"github.com/apache/camel-k/pkg/install"
"github.com/pkg/errors"
corev1 "k8s.io/api/core/v1"
k8serrors "k8s.io/apimachinery/pkg/api/errors"
k8sclient "sigs.k8s.io/controller-runtime/pkg/client"

"github.com/apache/camel-k/pkg/apis/camel/v1alpha1"
)

// NewInitializePodAction creates a new initialize action
func NewInitializePodAction() Action {
return &initializePodAction{}
}

type initializePodAction struct {
baseAction
}

// Name returns a common name of the action
func (action *initializePodAction) Name() string {
return "initialize-pod"
}

// CanHandle tells whether this action can handle the build
func (action *initializePodAction) CanHandle(build *v1alpha1.Build) bool {
return build.Status.Phase == v1alpha1.BuildPhaseInitialization &&
build.Spec.Platform.Build.BuildStrategy == v1alpha1.IntegrationPlatformBuildStrategyPod
}

// Handle handles the builds
func (action *initializePodAction) Handle(ctx context.Context, build *v1alpha1.Build) (*v1alpha1.Build, error) {
// Ensure service account is present
// TODO: maybe this should be done by the platform trait ??
if err := action.ensureServiceAccount(ctx, build); err != nil {
return nil, errors.Wrap(err, "cannot ensure service account is present")
}

if err := deleteBuilderPod(ctx, action.client, build); err != nil {
return nil, errors.Wrap(err, "cannot delete build pod")
}

pod, err := getBuilderPod(ctx, action.client, build)
if err != nil || pod != nil {
return nil, err
}

build.Status.Phase = v1alpha1.BuildPhaseScheduling

return build, nil
}

func (action *initializePodAction) ensureServiceAccount(ctx context.Context, build *v1alpha1.Build) error {
sa := corev1.ServiceAccount{}
saKey := k8sclient.ObjectKey{
Name: "camel-k-builder",
Namespace: build.Namespace,
}

err := action.client.Get(ctx, saKey, &sa)
if err != nil && k8serrors.IsNotFound(err) {
// Create a proper service account
return install.BuilderServiceAccountRoles(ctx, action.client, build.Namespace)
}

return err
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,27 +23,28 @@ import (
"github.com/apache/camel-k/pkg/apis/camel/v1alpha1"
)

// NewInitializeAction creates a new initialize action
func NewInitializeAction() Action {
return &initializeAction{}
// NewInitializeRoutineAction creates a new initialize action
func NewInitializeRoutineAction() Action {
return &initializeRoutineAction{}
}

type initializeAction struct {
type initializeRoutineAction struct {
baseAction
}

// Name returns a common name of the action
func (action *initializeAction) Name() string {
func (action *initializeRoutineAction) Name() string {
return "initialize"
}

// CanHandle tells whether this action can handle the build
func (action *initializeAction) CanHandle(build *v1alpha1.Build) bool {
return build.Status.Phase == v1alpha1.BuildPhaseInitialization
func (action *initializeRoutineAction) CanHandle(build *v1alpha1.Build) bool {
return build.Status.Phase == v1alpha1.BuildPhaseInitialization &&
build.Spec.Platform.Build.BuildStrategy == v1alpha1.IntegrationPlatformBuildStrategyRoutine
}

// Handle handles the builds
func (action *initializeAction) Handle(ctx context.Context, build *v1alpha1.Build) (*v1alpha1.Build, error) {
func (action *initializeRoutineAction) Handle(ctx context.Context, build *v1alpha1.Build) (*v1alpha1.Build, error) {
build.Status.Phase = v1alpha1.BuildPhaseScheduling

return build, nil
Expand Down
26 changes: 10 additions & 16 deletions pkg/controller/build/monitor_pod.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,8 @@ package build
import (
"context"

corev1 "k8s.io/api/core/v1"
k8serrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/types"

"github.com/apache/camel-k/pkg/apis/camel/v1alpha1"
corev1 "k8s.io/api/core/v1"
)

// NewMonitorPodAction creates a new monitor action for scheduled pod
Expand All @@ -51,24 +48,21 @@ func (action *monitorPodAction) CanHandle(build *v1alpha1.Build) bool {
// Handle handles the builds
func (action *monitorPodAction) Handle(ctx context.Context, build *v1alpha1.Build) (*v1alpha1.Build, error) {
// Get the build pod
pod := &corev1.Pod{}
err := action.client.Get(ctx, types.NamespacedName{Namespace: build.Namespace, Name: buildPodName(build.Spec.Meta)}, pod)
pod, err := getBuilderPod(ctx, action.client, build)
if err != nil {
if k8serrors.IsNotFound(err) {
// Let's reschedule the build
build.Status.Phase = v1alpha1.BuildPhaseScheduling
} else {
return nil, err
}
return nil, err
}

var buildPhase v1alpha1.BuildPhase

switch pod.Status.Phase {
case corev1.PodSucceeded:
switch {
case pod == nil:
build.Status.Phase = v1alpha1.BuildPhaseScheduling
case pod.Status.Phase == corev1.PodSucceeded:
buildPhase = v1alpha1.BuildPhaseSucceeded
case corev1.PodFailed:
case pod.Status.Phase == corev1.PodFailed:
buildPhase = v1alpha1.BuildPhaseFailed
default:
buildPhase = build.Status.Phase
}

if build.Status.Phase == buildPhase {
Expand Down
71 changes: 20 additions & 51 deletions pkg/controller/build/schedule_pod.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,10 @@ import (
"sync"

"github.com/apache/camel-k/pkg/apis/camel/v1alpha1"
"github.com/apache/camel-k/pkg/install"
"github.com/apache/camel-k/pkg/platform"
"github.com/apache/camel-k/pkg/util/defaults"

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

k8sclient "sigs.k8s.io/controller-runtime/pkg/client"
Expand Down Expand Up @@ -77,70 +75,45 @@ func (action *schedulePodAction) Handle(ctx context.Context, build *v1alpha1.Bui

// Emulate a serialized working queue to only allow one build to run at a given time.
// This is currently necessary for the incremental build to work as expected.
hasScheduledBuild := false
for _, b := range builds.Items {
if b.Status.Phase == v1alpha1.BuildPhasePending || b.Status.Phase == v1alpha1.BuildPhaseRunning {
hasScheduledBuild = true
break
// Let's requeue the build in case one is already running
return nil, nil
}
}

if hasScheduledBuild {
// Let's requeue the build in case one is already running
return nil, nil
}

// Try to get operator image name before starting the build
operatorImage, err := platform.GetCurrentOperatorImage(ctx, action.client)
pod, err := getBuilderPod(ctx, action.client, build)
if err != nil {
return nil, err
}

// Otherwise, let's create the build pod
// We may want to explicitly manage build priority as opposed to relying on
// the reconcile loop to handle the queuing
pod := newBuildPod(build, operatorImage)

// Set the Build instance as the owner and controller
if err := controllerutil.SetControllerReference(build, pod, action.client.GetScheme()); err != nil {
return nil, err
}
if pod == nil {
// Try to get operator image name before starting the build
operatorImage, err := platform.GetCurrentOperatorImage(ctx, action.client)
if err != nil {
return nil, err
}

// Ensure service account is present
if err := action.ensureServiceAccount(ctx, pod); err != nil {
return nil, errors.Wrap(err, "cannot ensure service account is present")
}
// Otherwise, let's create the build pod
// We may want to explicitly manage build priority as opposed to relying on
// the reconcile loop to handle the queuing
pod = newBuildPod(build, operatorImage)

err = action.client.Delete(ctx, pod)
if err != nil && !k8serrors.IsNotFound(err) {
return nil, errors.Wrap(err, "cannot delete build pod")
}
// Set the Build instance as the owner and controller
if err := controllerutil.SetControllerReference(build, pod, action.client.GetScheme()); err != nil {
return nil, err
}

err = action.client.Create(ctx, pod)
if err != nil {
return nil, errors.Wrap(err, "cannot create build pod")
if err := action.client.Create(ctx, pod); err != nil {
return nil, errors.Wrap(err, "cannot create build pod")
}
}

build.Status.Phase = v1alpha1.BuildPhasePending

return build, nil
}

func (action *schedulePodAction) ensureServiceAccount(ctx context.Context, buildPod *corev1.Pod) error {
sa := corev1.ServiceAccount{}
saKey := k8sclient.ObjectKey{
Name: "camel-k-builder",
Namespace: buildPod.Namespace,
}

err := action.client.Get(ctx, saKey, &sa)
if err != nil && k8serrors.IsNotFound(err) {
// Create a proper service account
return install.BuilderServiceAccountRoles(ctx, action.client, buildPod.Namespace)
}
return err
}

func newBuildPod(build *v1alpha1.Build, operatorImage string) *corev1.Pod {
builderImage := operatorImage
if builderImage == "" {
Expand Down Expand Up @@ -219,7 +192,3 @@ func newBuildPod(build *v1alpha1.Build, operatorImage string) *corev1.Pod {

return pod
}

func buildPodName(object metav1.ObjectMeta) string {
return "camel-k-" + object.Name + "-builder"
}
73 changes: 73 additions & 0 deletions pkg/controller/build/util_pod.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/*
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 build

import (
"context"

corev1 "k8s.io/api/core/v1"
k8serrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
k8sclient "sigs.k8s.io/controller-runtime/pkg/client"

"github.com/apache/camel-k/pkg/apis/camel/v1alpha1"
)

// deleteBuilderPod --
func deleteBuilderPod(ctx context.Context, client k8sclient.Writer, build *v1alpha1.Build) error {
pod := corev1.Pod{
TypeMeta: metav1.TypeMeta{
APIVersion: corev1.SchemeGroupVersion.String(),
Kind: "Pod",
},
ObjectMeta: metav1.ObjectMeta{
Namespace: build.Namespace,
Name: buildPodName(build.Spec.Meta),
Labels: map[string]string{
"camel.apache.org/build": build.Name,
},
},
}

err := client.Delete(ctx, &pod)
if err != nil && k8serrors.IsNotFound(err) {
return nil
}

return err
}

// getBuilderPod --
func getBuilderPod(ctx context.Context, client k8sclient.Reader, build *v1alpha1.Build) (*corev1.Pod, error) {
key := k8sclient.ObjectKey{Namespace: build.Namespace, Name: buildPodName(build.ObjectMeta)}
pod := corev1.Pod{}

err := client.Get(ctx, key, &pod)
if err != nil && k8serrors.IsNotFound(err) {
return nil, nil
}
if err != nil {
return nil, err
}

return &pod, nil
}

func buildPodName(object metav1.ObjectMeta) string {
return "camel-k-" + object.Name + "-builder"
}

0 comments on commit d86ae77

Please sign in to comment.