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

fix build-pod strategy on kubernetes #844

Merged
merged 1 commit into from
Jul 20, 2019
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
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
89 changes: 89 additions & 0 deletions pkg/controller/build/initialize_pod.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/*
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 {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It'd be useful to add a comment that we return and wait until the next delete event for the build pod to be de-queued.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

// We return and wait for the pod to be deleted before de-queue the build pod.
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
70 changes: 19 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,44 @@ 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")
}
// 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 +191,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"
}