Implement e2e testing for rudder and helm

pull/2846/head
Dmitry Shulyak 9 years ago committed by Maciej Kwiek
parent ef64d1e5c7
commit 1dd3e75b16

@ -0,0 +1,194 @@
// Copyright 2017 Mirantis
//
// 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 e2e
import (
"fmt"
"os/exec"
"regexp"
"time"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/pkg/api/v1"
"strings"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
const (
experimentalTillerImage string = "nebril/tiller-experimental"
rudderAppcontroller string = "rudderAppcontroller"
)
// HelmManager provides functionality to install client/server helm and use it
type HelmManager interface {
// InstallTiller will bootstrap tiller pod in k8s
InstallTiller() error
// DeleteTiller removes tiller pod from k8s
DeleteTiller(removeHelmHome bool) error
// Install chart, returns releaseName and error
Install(chartName string) (string, error)
// Status verifies state of installed release
Status(releaseName string) error
}
// BinaryHelmManager uses helm binary to work with helm server
type BinaryHelmManager struct {
Clientset kubernetes.Interface
Namespace string
HelmBin string
}
func (m *BinaryHelmManager) InstallTiller() error {
arg := make([]string, 0, 5)
arg = append(arg, "init", "--tiller-namespace", m.Namespace)
if enableRudder {
arg = append(arg, "--tiller-image", experimentalTillerImage)
}
_, err := m.executeUsingHelm(arg...)
if err != nil {
return err
}
By("Waiting for tiller pod")
waitTillerPod(m.Clientset, m.Namespace)
if enableRudder {
return prepareRudder(m.Clientset, m.Namespace)
}
return nil
}
func (m *BinaryHelmManager) DeleteTiller(removeHelmHome bool) error {
arg := make([]string, 0, 4)
arg = append(arg, "reset", "--tiller-namespace", m.Namespace)
if removeHelmHome {
arg = append(arg, "--remove-helm-home")
}
_, err := m.executeUsingHelm(arg...)
if err != nil {
return err
}
if enableRudder {
return deleteRudder(m.Clientset, m.Namespace)
}
return nil
}
func (m *BinaryHelmManager) Install(chartName string) (string, error) {
stdout, err := m.executeUsingHelmInNamespace("install", chartName)
if err != nil {
return "", err
}
return getNameFromHelmOutput(stdout), nil
}
// Status reports nil if release is considered to be succesfull
func (m *BinaryHelmManager) Status(releaseName string) error {
stdout, err := m.executeUsingHelmInNamespace("status", releaseName)
if err != nil {
return err
}
status := getStatusFromHelmOutput(stdout)
if status == "DEPLOYED" {
return nil
}
return fmt.Errorf("Expected status is DEPLOYED. But got %v for release %v.", status, releaseName)
}
func (m *BinaryHelmManager) executeUsingHelmInNamespace(arg ...string) (string, error) {
arg = append(arg, "--namespace", m.Namespace, "--tiller-namespace", m.Namespace)
return m.executeUsingHelm(arg...)
}
func (m *BinaryHelmManager) executeUsingHelm(arg ...string) (string, error) {
Logf("Running command %+v\n", arg)
cmd := exec.Command(m.HelmBin, arg...)
stdout, err := cmd.Output()
if err != nil {
stderr := err.(*exec.ExitError)
Logf("Ccommand %+v, Err %s\n", arg, stderr.Stderr)
return "", err
}
return string(stdout), nil
}
func regexpKeyFromStructuredOutput(key, output string) string {
r := regexp.MustCompile(fmt.Sprintf("%v:[[:space:]]*(.*)", key))
// key will be captured in group with index 1
result := r.FindStringSubmatch(output)
if len(result) < 2 {
return ""
}
return result[1]
}
func prepareRudder(clientset kubernetes.Interface, namespace string) error {
rudder := &v1.Pod{
ObjectMeta: v1.ObjectMeta{
Name: rudderAppcontroller,
},
Spec: v1.PodSpec{
RestartPolicy: "Always",
Containers: []v1.Container{
{
Name: "rudder-appcontroller",
Image: "mirantis/k8s-appcontroller",
ImagePullPolicy: v1.PullNever,
},
},
},
}
_, err := clientset.Core().Pods(namespace).Create(rudder)
return err
}
func deleteRudder(clientset kubernetes.Interface, namespace string) error {
return clientset.Core().Pods(namespace).Delete(rudderAppcontroller, nil)
}
func getNameFromHelmOutput(output string) string {
return regexpKeyFromStructuredOutput("NAME", output)
}
func getStatusFromHelmOutput(output string) string {
return regexpKeyFromStructuredOutput("STATUS", output)
}
func waitTillerPod(clientset kubernetes.Interface, namespace string) {
Eventually(func() bool {
pods, err := clientset.Core().Pods(namespace).List(v1.ListOptions{})
if err != nil {
return false
}
for _, pod := range pods.Items {
if !strings.Contains(pod.Name, "tiller") {
continue
}
Logf("Found tiller pod. Phase %v\n", pod.Status.Phase)
if pod.Status.Phase != v1.PodRunning {
return false
}
for _, cond := range pod.Status.Conditions {
if cond.Type != v1.PodReady {
continue
}
return cond.Status == v1.ConditionTrue
}
}
return false
}, 2*time.Minute, 5*time.Second).Should(BeTrue(), "tiller pod is not running in namespace "+namespace)
}

@ -0,0 +1,80 @@
// Copyright 2017 Mirantis
//
// 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 e2e
import (
"flag"
"fmt"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/pkg/api/v1"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
)
// TODO move this variables under single object
var url string
var enableRudder bool
func init() {
flag.StringVar(&url, "cluster-url", "http://127.0.0.1:8080", "apiserver address to use with restclient")
flag.BoolVar(&enableRudder, "use-rudder", false, "Use to enable rudder")
}
func LoadConfig() *rest.Config {
config, err := clientcmd.BuildConfigFromFlags(url, "")
Expect(err).NotTo(HaveOccurred())
return config
}
func KubeClient() (*kubernetes.Clientset, error) {
config := LoadConfig()
clientset, err := kubernetes.NewForConfig(config)
Expect(err).NotTo(HaveOccurred())
return clientset, nil
}
func DeleteNS(clientset kubernetes.Interface, namespace *v1.Namespace) {
defer GinkgoRecover()
pods, err := clientset.Core().Pods(namespace.Name).List(v1.ListOptions{})
Expect(err).NotTo(HaveOccurred())
for _, pod := range pods.Items {
clientset.Core().Pods(namespace.Name).Delete(pod.Name, nil)
}
clientset.Core().Namespaces().Delete(namespace.Name, nil)
}
func Logf(format string, a ...interface{}) {
fmt.Fprintf(GinkgoWriter, format, a...)
}
func WaitForPod(clientset kubernetes.Interface, namespace string, name string, phase v1.PodPhase) *v1.Pod {
defer GinkgoRecover()
var podUpdated *v1.Pod
Eventually(func() error {
podUpdated, err := clientset.Core().Pods(namespace).Get(name)
if err != nil {
return err
}
if phase != "" && podUpdated.Status.Phase != phase {
return fmt.Errorf("pod %v is not %v phase: %v", podUpdated.Name, phase, podUpdated.Status.Phase)
}
return nil
}).Should(BeNil())
return podUpdated
}
Loading…
Cancel
Save