Implements basic functionality of running starting Healthcheck.
Results can be inspected so-far without dedicated API, by using, for
example, Query API.
Issue-ID: MULTICLOUD-1233
Signed-off-by: Konrad Bańka <k.banka@samsung.com>
Change-Id: Ia4d96d936d573173d7d8f41e6c39d059bf5f8b1f
import (
"github.com/onap/multicloud-k8s/src/k8splugin/internal/app"
"github.com/onap/multicloud-k8s/src/k8splugin/internal/connection"
+ "github.com/onap/multicloud-k8s/src/k8splugin/internal/healthcheck"
"github.com/onap/multicloud-k8s/src/k8splugin/internal/rb"
"github.com/gorilla/mux"
instClient app.InstanceManager,
configClient app.ConfigManager,
connectionClient connection.ConnectionManager,
- templateClient rb.ConfigTemplateManager) *mux.Router {
+ templateClient rb.ConfigTemplateManager,
+ healthcheckClient healthcheck.InstanceHCManager) *mux.Router {
router := mux.NewRouter()
instRouter.HandleFunc("/instance/{instID}/config/rollback", configHandler.rollbackHandler).Methods("POST")
instRouter.HandleFunc("/instance/{instID}/config/tagit", configHandler.tagitHandler).Methods("POST")
+ // Instance Healthcheck API
+ if healthcheckClient == nil {
+ healthcheckClient = healthcheck.NewHCClient()
+ }
+ healthcheckHandler := instanceHCHandler{client: healthcheckClient}
+ instRouter.HandleFunc("/instance/{instID}/healthcheck", healthcheckHandler.listHandler).Methods("GET")
+ instRouter.HandleFunc("/instance/{instID}/healthcheck", healthcheckHandler.createHandler).Methods("POST")
+ instRouter.HandleFunc("/instance/{instID}/healthcheck/{hcID}", healthcheckHandler.getHandler).Methods("GET")
+ instRouter.HandleFunc("/instance/{instID}/healthcheck/{hcID}", healthcheckHandler.deleteHandler).Methods("DELETE")
+
// Add healthcheck path
instRouter.HandleFunc("/healthcheck", healthCheckHandler).Methods("GET")
t.Run(testCase.label, func(t *testing.T) {
request := httptest.NewRequest("POST", "/cloudowner/cloudregion/infra_workload", testCase.input)
- resp := executeRequest(request, NewRouter(nil, nil, testCase.instClient, nil, nil, nil))
+ resp := executeRequest(request, NewRouter(nil, nil, testCase.instClient, nil, nil, nil, nil))
defer resp.Body.Close()
if testCase.expectedCode != resp.StatusCode {
for _, testCase := range testCases {
t.Run(testCase.label, func(t *testing.T) {
request := httptest.NewRequest("GET", "/cloudowner/cloudregion/infra_workload/"+testCase.input, nil)
- resp := executeRequest(request, NewRouter(nil, nil, testCase.instClient, nil, nil, nil))
+ resp := executeRequest(request, NewRouter(nil, nil, testCase.instClient, nil, nil, nil, nil))
if testCase.expectedCode != resp.StatusCode {
t.Fatalf("Request method returned: %v and it was expected: %v",
for _, testCase := range testCases {
t.Run(testCase.label, func(t *testing.T) {
request := httptest.NewRequest("GET", "/cloudowner/cloudregion/infra_workload?name="+testCase.input, nil)
- resp := executeRequest(request, NewRouter(nil, nil, testCase.instClient, nil, nil, nil))
+ resp := executeRequest(request, NewRouter(nil, nil, testCase.instClient, nil, nil, nil, nil))
if testCase.expectedCode != resp.StatusCode {
t.Fatalf("Request method returned: %v and it was expected: %v",
for _, testCase := range testCases {
t.Run(testCase.label, func(t *testing.T) {
request := httptest.NewRequest("DELETE", "/cloudowner/cloudregion/infra_workload/"+testCase.input, nil)
- resp := executeRequest(request, NewRouter(nil, nil, testCase.instClient, nil, nil, nil))
+ resp := executeRequest(request, NewRouter(nil, nil, testCase.instClient, nil, nil, nil, nil))
if testCase.expectedCode != resp.StatusCode {
t.Fatalf("Request method returned: %v and it was expected: %v", resp.StatusCode, testCase.expectedCode)
for _, testCase := range testCases {
t.Run(testCase.label, func(t *testing.T) {
request := httptest.NewRequest("POST", "/v1/rb/definition", testCase.reader)
- resp := executeRequest(request, NewRouter(testCase.rbDefClient, nil, nil, nil, nil, nil))
+ resp := executeRequest(request, NewRouter(testCase.rbDefClient, nil, nil, nil, nil, nil, nil))
//Check returned code
if resp.StatusCode != testCase.expectedCode {
for _, testCase := range testCases {
t.Run(testCase.label, func(t *testing.T) {
request := httptest.NewRequest("GET", "/v1/rb/definition/testresourcebundle", nil)
- resp := executeRequest(request, NewRouter(testCase.rbDefClient, nil, nil, nil, nil, nil))
+ resp := executeRequest(request, NewRouter(testCase.rbDefClient, nil, nil, nil, nil, nil, nil))
//Check returned code
if resp.StatusCode != testCase.expectedCode {
for _, testCase := range testCases {
t.Run(testCase.label, func(t *testing.T) {
request := httptest.NewRequest("GET", "/v1/rb/definition", nil)
- resp := executeRequest(request, NewRouter(testCase.rbDefClient, nil, nil, nil, nil, nil))
+ resp := executeRequest(request, NewRouter(testCase.rbDefClient, nil, nil, nil, nil, nil, nil))
//Check returned code
if resp.StatusCode != testCase.expectedCode {
for _, testCase := range testCases {
t.Run(testCase.label, func(t *testing.T) {
request := httptest.NewRequest("GET", "/v1/rb/definition/"+testCase.name+"/"+testCase.version, nil)
- resp := executeRequest(request, NewRouter(testCase.rbDefClient, nil, nil, nil, nil, nil))
+ resp := executeRequest(request, NewRouter(testCase.rbDefClient, nil, nil, nil, nil, nil, nil))
//Check returned code
if resp.StatusCode != testCase.expectedCode {
for _, testCase := range testCases {
t.Run(testCase.label, func(t *testing.T) {
request := httptest.NewRequest("DELETE", "/v1/rb/definition/"+testCase.name+"/"+testCase.version, nil)
- resp := executeRequest(request, NewRouter(testCase.rbDefClient, nil, nil, nil, nil, nil))
+ resp := executeRequest(request, NewRouter(testCase.rbDefClient, nil, nil, nil, nil, nil, nil))
//Check returned code
if resp.StatusCode != testCase.expectedCode {
t.Run(testCase.label, func(t *testing.T) {
request := httptest.NewRequest("POST",
"/v1/rb/definition/"+testCase.name+"/"+testCase.version+"/content", testCase.body)
- resp := executeRequest(request, NewRouter(testCase.rbDefClient, nil, nil, nil, nil, nil))
+ resp := executeRequest(request, NewRouter(testCase.rbDefClient, nil, nil, nil, nil, nil, nil))
//Check returned code
if resp.StatusCode != testCase.expectedCode {
Err: nil,
}
request := httptest.NewRequest("GET", "/v1/healthcheck", nil)
- resp := executeRequest(request, NewRouter(nil, nil, nil, nil, nil, nil))
+ resp := executeRequest(request, NewRouter(nil, nil, nil, nil, nil, nil, nil))
//Check returned code
if resp.StatusCode != http.StatusOK {
Err: pkgerrors.New("Runtime Error in DB"),
}
request := httptest.NewRequest("GET", "/v1/healthcheck", nil)
- resp := executeRequest(request, NewRouter(nil, nil, nil, nil, nil, nil))
+ resp := executeRequest(request, NewRouter(nil, nil, nil, nil, nil, nil, nil))
//Check returned code
if resp.StatusCode != http.StatusInternalServerError {
t.Run(testCase.label, func(t *testing.T) {
request := httptest.NewRequest("POST", "/v1/instance", testCase.input)
- resp := executeRequest(request, NewRouter(nil, nil, testCase.instClient, nil, nil, nil))
+ resp := executeRequest(request, NewRouter(nil, nil, testCase.instClient, nil, nil, nil, nil))
if testCase.expectedCode != resp.StatusCode {
body, _ := ioutil.ReadAll(resp.Body)
for _, testCase := range testCases {
t.Run(testCase.label, func(t *testing.T) {
request := httptest.NewRequest("GET", "/v1/instance/"+testCase.input, nil)
- resp := executeRequest(request, NewRouter(nil, nil, testCase.instClient, nil, nil, nil))
+ resp := executeRequest(request, NewRouter(nil, nil, testCase.instClient, nil, nil, nil, nil))
if testCase.expectedCode != resp.StatusCode {
t.Fatalf("Request method returned: %v and it was expected: %v",
}
request.URL.RawQuery = q.Encode()
}
- resp := executeRequest(request, NewRouter(nil, nil, testCase.instClient, nil, nil, nil))
+ resp := executeRequest(request, NewRouter(nil, nil, testCase.instClient, nil, nil, nil, nil))
if testCase.expectedCode != resp.StatusCode {
t.Fatalf("Request method returned: %v and it was expected: %v",
for _, testCase := range testCases {
t.Run(testCase.label, func(t *testing.T) {
request := httptest.NewRequest("DELETE", "/v1/instance/"+testCase.input, nil)
- resp := executeRequest(request, NewRouter(nil, nil, testCase.instClient, nil, nil, nil))
+ resp := executeRequest(request, NewRouter(nil, nil, testCase.instClient, nil, nil, nil, nil))
if testCase.expectedCode != resp.StatusCode {
t.Fatalf("Request method returned: %v and it was expected: %v", resp.StatusCode, testCase.expectedCode)
--- /dev/null
+/*
+Copyright © 2021 Samsung Electronics
+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 api
+
+import (
+ "encoding/json"
+ "net/http"
+
+ "github.com/onap/multicloud-k8s/src/k8splugin/internal/healthcheck"
+ log "github.com/onap/multicloud-k8s/src/k8splugin/internal/logutils"
+
+ "github.com/gorilla/mux"
+)
+
+// Used to store the backend implementation objects
+// Also simplifies the mocking needed for unit testing
+type instanceHCHandler struct {
+ // Interface that implements Healthcheck operations
+ client healthcheck.InstanceHCManager
+}
+
+func (ih instanceHCHandler) createHandler(w http.ResponseWriter, r *http.Request) {
+ vars := mux.Vars(r)
+ id := vars["instID"]
+
+ resp, err := ih.client.Create(id)
+ if err != nil {
+ log.Error("Error scheduling healhtcheck", log.Fields{
+ "error": err,
+ "instance": id,
+ "response": resp,
+ })
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+
+ w.Header().Set("Content-Type", "application/json")
+ w.WriteHeader(http.StatusOK)
+ err = json.NewEncoder(w).Encode(resp)
+ if err != nil {
+ log.Error("Error Marshaling Reponse", log.Fields{
+ "error": err,
+ "response": resp,
+ })
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+}
+
+func (ih instanceHCHandler) getHandler(w http.ResponseWriter, r *http.Request) {
+}
+
+func (ih instanceHCHandler) deleteHandler(w http.ResponseWriter, r *http.Request) {
+}
+
+func (ih instanceHCHandler) listHandler(w http.ResponseWriter, r *http.Request) {
+}
t.Run(testCase.label, func(t *testing.T) {
request := httptest.NewRequest("POST", "/v1/rb/definition/test-rbdef/v1/profile",
testCase.reader)
- resp := executeRequest(request, NewRouter(nil, testCase.rbProClient, nil, nil, nil, nil))
+ resp := executeRequest(request, NewRouter(nil, testCase.rbProClient, nil, nil, nil, nil, nil))
//Check returned code
if resp.StatusCode != testCase.expectedCode {
for _, testCase := range testCases {
t.Run(testCase.label, func(t *testing.T) {
request := httptest.NewRequest("GET", "/v1/rb/definition/test-rbdef/v1/profile/"+testCase.prname, nil)
- resp := executeRequest(request, NewRouter(nil, testCase.rbProClient, nil, nil, nil, nil))
+ resp := executeRequest(request, NewRouter(nil, testCase.rbProClient, nil, nil, nil, nil, nil))
//Check returned code
if resp.StatusCode != testCase.expectedCode {
for _, testCase := range testCases {
t.Run(testCase.label, func(t *testing.T) {
request := httptest.NewRequest("GET", "/v1/rb/definition/"+testCase.def+"/"+testCase.version+"/profile", nil)
- resp := executeRequest(request, NewRouter(nil, testCase.rbProClient, nil, nil, nil, nil))
+ resp := executeRequest(request, NewRouter(nil, testCase.rbProClient, nil, nil, nil, nil, nil))
//Check returned code
if resp.StatusCode != testCase.expectedCode {
for _, testCase := range testCases {
t.Run(testCase.label, func(t *testing.T) {
request := httptest.NewRequest("DELETE", "/v1/rb/definition/test-rbdef/v1/profile/"+testCase.prname, nil)
- resp := executeRequest(request, NewRouter(nil, testCase.rbProClient, nil, nil, nil, nil))
+ resp := executeRequest(request, NewRouter(nil, testCase.rbProClient, nil, nil, nil, nil, nil))
//Check returned code
if resp.StatusCode != testCase.expectedCode {
t.Run(testCase.label, func(t *testing.T) {
request := httptest.NewRequest("POST",
"/v1/rb/definition/test-rbdef/v1/profile/"+testCase.prname+"/content", testCase.body)
- resp := executeRequest(request, NewRouter(nil, testCase.rbProClient, nil, nil, nil, nil))
+ resp := executeRequest(request, NewRouter(nil, testCase.rbProClient, nil, nil, nil, nil, nil))
//Check returned code
if resp.StatusCode != testCase.expectedCode {
/*
Copyright 2018 Intel Corporation.
+Copyright © 2021 Samsung Electronics
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
rand.Seed(time.Now().UnixNano())
- httpRouter := api.NewRouter(nil, nil, nil, nil, nil, nil)
+ httpRouter := api.NewRouter(nil, nil, nil, nil, nil, nil, nil)
loggedRouter := handlers.LoggingHandler(os.Stdout, httpRouter)
log.Println("Starting Kubernetes Multicloud API")
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
+github.com/onap/multicloud-k8s v0.0.0-20210224130448-2f09583725c8 h1:eJ6xWCP7UmTqn+5Z8emFxI0rH/EOPzWupxw0G7hBkxs=
github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.10.1 h1:q/mM8GF/n0shIN8SaAZ0V+jnLPzen6WIVZdiwrRlMlo=
package app
import (
+ "io/ioutil"
"os"
"strings"
"time"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
+ "k8s.io/client-go/discovery"
"k8s.io/client-go/discovery/cached/disk"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/kubernetes"
+ "k8s.io/client-go/rest"
"k8s.io/client-go/restmapper"
"k8s.io/client-go/tools/clientcmd"
)
// KubernetesClient encapsulates the different clients' interfaces
// we need when interacting with a Kubernetes cluster
type KubernetesClient struct {
+ rawConfig clientcmd.ClientConfig
+ restConfig *rest.Config
clientSet kubernetes.Interface
dynamicClient dynamic.Interface
discoverClient *disk.CachedDiscoveryClient
return kubeConfigPath, nil
}
-// init loads the Kubernetes configuation values stored into the local configuration file
-func (k *KubernetesClient) init(cloudregion string, iid string) error {
+// Init loads the Kubernetes configuation values stored into the local configuration file
+func (k *KubernetesClient) Init(cloudregion string, iid string) error {
if cloudregion == "" {
return pkgerrors.New("Cloudregion is empty")
}
}
k.restMapper = restmapper.NewDeferredDiscoveryRESTMapper(k.discoverClient)
+ k.restConfig = config
+
+ //Spawn ClientConfig
+ kubeFile, err := os.Open(configPath)
+ if err != nil {
+ return pkgerrors.Wrap(err, "Opening kubeConfig")
+ }
+ kubeData, err := ioutil.ReadAll(kubeFile)
+ if err != nil {
+ return pkgerrors.Wrap(err, "Reading kubeConfig")
+ }
+ k.rawConfig, err = clientcmd.NewClientConfigFromBytes(kubeData)
+ if err != nil {
+ return pkgerrors.Wrap(err, "Creating rawConfig")
+ }
return nil
}
func (k *KubernetesClient) GetInstanceID() string {
return k.instanceID
}
+
+//Following set of methods are implemented so that KubernetesClient
+//implements genericclioptions.RESTClientGetter interface
+func (k *KubernetesClient) ToDiscoveryClient() (discovery.CachedDiscoveryInterface, error) {
+ return k.discoverClient, nil
+}
+func (k *KubernetesClient) ToRESTMapper() (meta.RESTMapper, error) {
+ return k.GetMapper(), nil
+}
+func (k *KubernetesClient) ToRawKubeConfigLoader() clientcmd.ClientConfig {
+ return k.rawConfig
+}
+func (k *KubernetesClient) ToRESTConfig() (*rest.Config, error) {
+ return k.restConfig, nil
+}
kubeClient := KubernetesClient{}
// Refer to the connection via its name
- err = kubeClient.init("mock_connection", "abcdefg")
+ err = kubeClient.Init("mock_connection", "abcdefg")
if err != nil {
t.Fatalf("TestGetKubeClient returned an error (%s)", err)
}
log.Printf("[scheduleResources]: POST %v %v", data.profile, data.resourceTemplates)
for _, inst := range resp {
k8sClient := KubernetesClient{}
- err = k8sClient.init(inst.Request.CloudRegion, inst.ID)
+ err = k8sClient.Init(inst.Request.CloudRegion, inst.ID)
if err != nil {
log.Printf("Getting CloudRegion Information: %s", err.Error())
//Move onto the next cloud region
log.Printf("[scheduleResources]: DELETE %v %v", data.profile, data.resourceTemplates)
for _, inst := range resp {
k8sClient := KubernetesClient{}
- err = k8sClient.init(inst.Request.CloudRegion, inst.ID)
+ err = k8sClient.Init(inst.Request.CloudRegion, inst.ID)
if err != nil {
log.Printf("Getting CloudRegion Information: %s", err.Error())
//Move onto the next cloud region
profile.ReleaseName)
chartPath := filepath.Join(chartBasePath, t.ChartName)
- resTemplates, err = helmClient.GenerateKubernetesArtifacts(chartPath,
+ resTemplates, _, err = helmClient.GenerateKubernetesArtifacts(chartPath,
[]string{outputfile.Name()},
nil)
if err != nil {
"log"
"strings"
+ protorelease "k8s.io/helm/pkg/proto/hapi/release"
+
"github.com/onap/multicloud-k8s/src/k8splugin/internal/db"
"github.com/onap/multicloud-k8s/src/k8splugin/internal/helm"
"github.com/onap/multicloud-k8s/src/k8splugin/internal/namegenerator"
Namespace string `json:"namespace"`
ReleaseName string `json:"release-name"`
Resources []helm.KubernetesResource `json:"resources"`
+ Hooks []*protorelease.Hook `json:"hooks"`
}
// InstanceMiniResponse contains the response from instantiation
}
//Execute the kubernetes create command
- sortedTemplates, releaseName, err := rb.NewProfileClient().Resolve(i.RBName, i.RBVersion, i.ProfileName, overrideValues, i.ReleaseName)
+ sortedTemplates, hookList, releaseName, err := rb.NewProfileClient().Resolve(i.RBName, i.RBVersion, i.ProfileName, overrideValues, i.ReleaseName)
if err != nil {
return InstanceResponse{}, pkgerrors.Wrap(err, "Error resolving helm charts")
}
id := namegenerator.Generate()
k8sClient := KubernetesClient{}
- err = k8sClient.init(i.CloudRegion, id)
+ err = k8sClient.Init(i.CloudRegion, id)
if err != nil {
return InstanceResponse{}, pkgerrors.Wrap(err, "Getting CloudRegion Information")
}
Namespace: profile.Namespace,
ReleaseName: releaseName,
Resources: createdResources,
+ Hooks: hookList,
}
key := InstanceKey{
}
k8sClient := KubernetesClient{}
- err = k8sClient.init(resResp.Request.CloudRegion, id)
+ err = k8sClient.Init(resResp.Request.CloudRegion, id)
if err != nil {
return InstanceStatus{}, pkgerrors.Wrap(err, "Getting CloudRegion Information")
}
}
k8sClient := KubernetesClient{}
- err = k8sClient.init(resResp.Request.CloudRegion, id)
+ err = k8sClient.Init(resResp.Request.CloudRegion, id)
if err != nil {
return InstanceStatus{}, pkgerrors.Wrap(err, "Getting CloudRegion Information")
}
}
k8sClient := KubernetesClient{}
- err = k8sClient.init(inst.Request.CloudRegion, inst.ID)
+ err = k8sClient.Init(inst.Request.CloudRegion, inst.ID)
if err != nil {
return pkgerrors.Wrap(err, "Getting CloudRegion Information")
}
log.Println(ir)
if len(ir.Resources) == 0 {
- t.Fatalf("TestInstanceCreate returned empty data (%s)", ir)
+ t.Fatalf("TestInstanceCreate returned empty data (%+v)", ir)
}
})
--- /dev/null
+/*
+Copyright © 2021 Samsung Electronics
+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 healthcheck
+
+import (
+ "encoding/json"
+
+ protorelease "k8s.io/helm/pkg/proto/hapi/release"
+ "k8s.io/helm/pkg/releasetesting"
+
+ "github.com/onap/multicloud-k8s/src/k8splugin/internal/app"
+ "github.com/onap/multicloud-k8s/src/k8splugin/internal/db"
+ log "github.com/onap/multicloud-k8s/src/k8splugin/internal/logutils"
+ "github.com/onap/multicloud-k8s/src/k8splugin/internal/namegenerator"
+
+ pkgerrors "github.com/pkg/errors"
+)
+
+// HealthcheckState holds possible states of Healthcheck instance
+type HealthcheckState string
+
+const (
+ HcS_UNKNOWN HealthcheckState = "UNKNOWN"
+ HcS_STARTED HealthcheckState = "STARTED"
+ HcS_RUNNING HealthcheckState = "RUNNING"
+ HcS_SUCCESS HealthcheckState = "SUCCESS"
+ HcS_FAILURE HealthcheckState = "FAILURE"
+)
+
+// InstanceHCManager interface exposes instance Healthcheck fuctionalities
+type InstanceHCManager interface {
+ Create(instanceId string) (InstanceHCStatus, error)
+ Get(instanceId, healthcheckId string) (InstanceHCStatus, error)
+ List(instanceId string) ([]InstanceHCStatus, error)
+ Delete(instanceId, healthcheckId string) error
+}
+
+// HealthcheckKey is used as the primary key in the db
+type HealthcheckKey struct {
+ InstanceId string `json:"instance-id"`
+ HealthcheckId string `json:"healthcheck-id"`
+}
+
+// We will use json marshalling to convert to string to
+// preserve the underlying structure.
+func (dk HealthcheckKey) String() string {
+ out, err := json.Marshal(dk)
+ if err != nil {
+ return ""
+ }
+
+ return string(out)
+}
+
+// InstanceHCClient implements InstanceHCManager
+type InstanceHCClient struct {
+ storeName string
+ tagInst string
+}
+
+// InstanceHCStatus holds healthcheck status
+type InstanceHCStatus struct {
+ releasetesting.TestSuite
+ Id string
+ Status HealthcheckState
+}
+
+func NewHCClient() *InstanceHCClient {
+ return &InstanceHCClient{
+ storeName: "rbdef",
+ tagInst: "instanceHC",
+ }
+}
+
+func (ihc InstanceHCClient) Create(instanceId string) (InstanceHCStatus, error) {
+ //Generate ID
+ id := namegenerator.Generate()
+
+ //Determine Cloud Region and namespace
+ v := app.NewInstanceClient()
+ instance, err := v.Get(instanceId)
+ if err != nil {
+ return InstanceHCStatus{}, pkgerrors.Wrap(err, "Getting instance")
+ }
+
+ //Prepare Environment, Request and Release structs
+ //TODO In future could derive params from request
+ client, err := NewKubeClient(instanceId, instance.Request.CloudRegion)
+ if err != nil {
+ return InstanceHCStatus{}, pkgerrors.Wrap(err, "Preparing KubeClient")
+ }
+ env := &releasetesting.Environment{
+ Namespace: instance.Namespace,
+ KubeClient: client,
+ Parallel: false,
+ }
+ release := protorelease.Release{
+ Name: instance.ReleaseName,
+ Hooks: instance.Hooks,
+ }
+
+ //Run HC
+ testSuite, err := releasetesting.NewTestSuite(&release)
+ if err != nil {
+ log.Error("Error creating TestSuite", log.Fields{
+ "Release": release,
+ })
+ return InstanceHCStatus{}, pkgerrors.Wrap(err, "Creating TestSuite")
+ }
+ if err = testSuite.Run(env); err != nil {
+ log.Error("Error running TestSuite", log.Fields{
+ "TestSuite": testSuite,
+ "Environment": env,
+ })
+ return InstanceHCStatus{}, pkgerrors.Wrap(err, "Running TestSuite")
+ }
+
+ //Save state
+ ihcs := InstanceHCStatus{
+ TestSuite: *testSuite,
+ Id: id,
+ Status: HcS_STARTED,
+ }
+ key := HealthcheckKey{
+ InstanceId: instance.ID,
+ HealthcheckId: id,
+ }
+ err = db.DBconn.Create(ihc.storeName, key, ihc.tagInst, ihcs)
+ if err != nil {
+ return ihcs, pkgerrors.Wrap(err, "Creating Instance DB Entry")
+ }
+
+ return ihcs, nil
+}
+
+func (ihc InstanceHCClient) Get(instanceId, healthcheckId string) (InstanceHCStatus, error) {
+ return InstanceHCStatus{}, nil
+}
+
+func (ihc InstanceHCClient) Delete(instanceId, healthcheckId string) error {
+ return nil
+}
+
+func (ihc InstanceHCClient) List(instanceId string) ([]InstanceHCStatus, error) {
+ return make([]InstanceHCStatus, 0), nil
+}
--- /dev/null
+/*
+Copyright © 2021 Samsung Electronics
+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 healthcheck
+
+import (
+ "k8s.io/helm/pkg/kube"
+ "k8s.io/helm/pkg/tiller/environment"
+
+ "github.com/onap/multicloud-k8s/src/k8splugin/internal/app"
+ "github.com/onap/multicloud-k8s/src/k8splugin/internal/config"
+
+ pkgerrors "github.com/pkg/errors"
+)
+
+//implements environment.KubeClient but overrides it so that
+//custom labels can be injected into created resources
+// using internal k8sClient
+type KubeClientImpl struct {
+ environment.KubeClient
+ labels map[string]string
+ k app.KubernetesClient
+}
+
+func NewKubeClient(instanceId, cloudRegion string) (*KubeClientImpl, error) {
+ k8sClient := app.KubernetesClient{}
+ err := k8sClient.Init(cloudRegion, instanceId)
+ if err != nil {
+ return nil, pkgerrors.Wrap(err, "Initializing k8sClient")
+ }
+ return &KubeClientImpl{
+ labels: map[string]string{
+ config.GetConfiguration().KubernetesLabelName: instanceId,
+ },
+ KubeClient: kube.New(&k8sClient),
+ k: k8sClient,
+ }, nil
+}
+
+/* FIXME
+// Need to correct this later and provide override of Create method to use our k8sClient
+// So that healthcheck hook resources would be labeled with vf-module data just like currently
+// every k8splugin-managed resource is
+
+//Create function is overrided to label test resources with custom labels
+func (kci *KubeClientImpl) Create(namespace string, reader io.Reader, timeout int64, shouldWait bool) error {
+ return nil
+}
+*/
/*
* Copyright 2018 Intel Corporation, Inc
+ * Copyright © 2021 Samsung Electronics
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
"path/filepath"
"regexp"
"sort"
+ "strconv"
"strings"
utils "github.com/onap/multicloud-k8s/src/k8splugin/internal"
"k8s.io/apimachinery/pkg/util/validation"
k8syaml "k8s.io/apimachinery/pkg/util/yaml"
"k8s.io/helm/pkg/chartutil"
+ "k8s.io/helm/pkg/hooks"
"k8s.io/helm/pkg/manifest"
"k8s.io/helm/pkg/proto/hapi/chart"
+ protorelease "k8s.io/helm/pkg/proto/hapi/release"
"k8s.io/helm/pkg/releaseutil"
"k8s.io/helm/pkg/renderutil"
"k8s.io/helm/pkg/tiller"
// Template is the interface for all helm templating commands
// Any backend implementation will implement this interface and will
// access the functionality via this.
+// FIXME Template is not referenced anywhere
type Template interface {
GenerateKubernetesArtifacts(
chartPath string,
}
}
+// Define hooks that are honored by k8splugin
+var honoredEvents = map[string]protorelease.Hook_Event{
+ hooks.ReleaseTestSuccess: protorelease.Hook_RELEASE_TEST_SUCCESS,
+ hooks.ReleaseTestFailure: protorelease.Hook_RELEASE_TEST_FAILURE,
+}
+
// Combines valueFiles and values into a single values stream.
// values takes precedence over valueFiles
func (h *TemplateClient) processValues(valueFiles []string, values []string) ([]byte, error) {
return dest
}
+// Checks whether resource is a hook and if it is, returns hook struct
+//Logic is based on private method
+//file *manifestFile) sort(result *result) error
+//of helm/pkg/tiller package
+func isHook(path, resource string) (*protorelease.Hook, error) {
+
+ var entry releaseutil.SimpleHead
+ err := yaml.Unmarshal([]byte(resource), &entry)
+ if err != nil {
+ return nil, pkgerrors.Wrap(err, "Loading resource to YAML")
+ }
+ //If resource has no metadata it can't be a hook
+ if entry.Metadata == nil ||
+ entry.Metadata.Annotations == nil ||
+ len(entry.Metadata.Annotations) == 0 {
+ return nil, nil
+ }
+ //Determine hook weight
+ hookWeight, err := strconv.Atoi(entry.Metadata.Annotations[hooks.HookWeightAnno])
+ if err != nil {
+ hookWeight = 0
+ }
+ //Prepare hook obj
+ resultHook := &protorelease.Hook{
+ Name: entry.Metadata.Name,
+ Kind: entry.Kind,
+ Path: path,
+ Manifest: resource,
+ Events: []protorelease.Hook_Event{},
+ Weight: int32(hookWeight),
+ DeletePolicies: []protorelease.Hook_DeletePolicy{},
+ }
+ //Determine hook's events
+ hookTypes, ok := entry.Metadata.Annotations[hooks.HookAnno]
+ if !ok {
+ return resultHook, nil
+ }
+ for _, hookType := range strings.Split(hookTypes, ",") {
+ hookType = strings.ToLower(strings.TrimSpace(hookType))
+ e, ok := honoredEvents[hookType]
+ if ok {
+ resultHook.Events = append(resultHook.Events, e)
+ }
+ }
+ return resultHook, nil
+}
+
// GenerateKubernetesArtifacts a mapping of type to fully evaluated helm template
func (h *TemplateClient) GenerateKubernetesArtifacts(inputPath string, valueFiles []string,
- values []string) ([]KubernetesResourceTemplate, error) {
+ values []string) ([]KubernetesResourceTemplate, []*protorelease.Hook, error) {
var outputDir, chartPath, namespace, releaseName string
var retData []KubernetesResourceTemplate
+ var hookList []*protorelease.Hook
releaseName = h.releaseName
namespace = h.kubeNameSpace
// verify chart path exists
if _, err := os.Stat(inputPath); err == nil {
if chartPath, err = filepath.Abs(inputPath); err != nil {
- return retData, err
+ return retData, hookList, err
}
} else {
- return retData, err
+ return retData, hookList, err
}
//Create a temp directory in the system temp folder
outputDir, err := ioutil.TempDir("", "helm-tmpl-")
if err != nil {
- return retData, pkgerrors.Wrap(err, "Got error creating temp dir")
+ return retData, hookList, pkgerrors.Wrap(err, "Got error creating temp dir")
}
if namespace == "" {
// get combined values and create config
rawVals, err := h.processValues(valueFiles, values)
if err != nil {
- return retData, err
+ return retData, hookList, err
}
config := &chart.Config{Raw: string(rawVals), Values: map[string]*chart.Value{}}
if msgs := validation.IsDNS1123Label(releaseName); releaseName != "" && len(msgs) > 0 {
- return retData, fmt.Errorf("release name %s is not a valid DNS label: %s", releaseName, strings.Join(msgs, ";"))
+ return retData, hookList, fmt.Errorf("release name %s is not a valid DNS label: %s", releaseName, strings.Join(msgs, ";"))
}
// Check chart requirements to make sure all dependencies are present in /charts
c, err := chartutil.Load(chartPath)
if err != nil {
- return retData, pkgerrors.Errorf("Got error: %s", err.Error())
+ return retData, hookList, pkgerrors.Errorf("Got error: %s", err.Error())
}
renderOpts := renderutil.Options{
renderedTemplates, err := renderutil.Render(c, config, renderOpts)
if err != nil {
- return retData, err
+ return retData, hookList, err
}
newRenderedTemplates := make(map[string]string)
continue
}
+ hook, _ := isHook(b, data)
+ // if hook is not nil, then append it to hooks list and continue
+ // if it's not, disregard error
+ if hook != nil {
+ hookList = append(hookList, hook)
+ continue
+ }
+
mfilePath := filepath.Join(outputDir, m.Name)
utils.EnsureDirectory(mfilePath)
err = ioutil.WriteFile(mfilePath, []byte(data), 0666)
if err != nil {
- return retData, err
+ return retData, hookList, err
}
gvk, err := getGroupVersionKind(data)
if err != nil {
- return retData, err
+ return retData, hookList, err
}
kres := KubernetesResourceTemplate{
}
retData = append(retData, kres)
}
- return retData, nil
+ return retData, hookList, nil
}
func getGroupVersionKind(data string) (schema.GroupVersionKind, error) {
for _, testCase := range testCases {
t.Run(testCase.label, func(t *testing.T) {
tc := NewTemplateClient("1.12.3", "testnamespace", "testreleasename")
- out, err := tc.GenerateKubernetesArtifacts(testCase.chartPath, testCase.valueFiles,
+ out, _, err := tc.GenerateKubernetesArtifacts(testCase.chartPath, testCase.valueFiles,
testCase.values)
if err != nil {
if testCase.expectedError == "" {
/*
* Copyright 2018 Intel Corporation, Inc
+ * Copyright © 2021 Samsung Electronics
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
"log"
"path/filepath"
+ protorelease "k8s.io/helm/pkg/proto/hapi/release"
+
"github.com/onap/multicloud-k8s/src/k8splugin/internal/db"
"github.com/onap/multicloud-k8s/src/k8splugin/internal/helm"
//Resolve returns the path where the helm chart merged with
//configuration overrides resides and final ReleaseName picked for instantiation
func (v *ProfileClient) Resolve(rbName string, rbVersion string,
- profileName string, values []string, overrideReleaseName string) ([]helm.KubernetesResourceTemplate, string, error) {
+ profileName string, values []string, overrideReleaseName string) ([]helm.KubernetesResourceTemplate, []*protorelease.Hook, string, error) {
var sortedTemplates []helm.KubernetesResourceTemplate
+ var hookList []*protorelease.Hook
var finalReleaseName string
//Download and process the profile first
//If everything seems okay, then download the definition
prData, err := v.Download(rbName, rbVersion, profileName)
if err != nil {
- return sortedTemplates, finalReleaseName, pkgerrors.Wrap(err, "Downloading Profile")
+ return sortedTemplates, hookList, finalReleaseName, pkgerrors.Wrap(err, "Downloading Profile")
}
prPath, err := ExtractTarBall(bytes.NewBuffer(prData))
if err != nil {
- return sortedTemplates, finalReleaseName, pkgerrors.Wrap(err, "Extracting Profile Content")
+ return sortedTemplates, hookList, finalReleaseName, pkgerrors.Wrap(err, "Extracting Profile Content")
}
prYamlClient, err := ProcessProfileYaml(prPath, v.manifestName)
if err != nil {
- return sortedTemplates, finalReleaseName, pkgerrors.Wrap(err, "Processing Profile Manifest")
+ return sortedTemplates, hookList, finalReleaseName, pkgerrors.Wrap(err, "Processing Profile Manifest")
}
definitionClient := NewDefinitionClient()
definition, err := definitionClient.Get(rbName, rbVersion)
if err != nil {
- return sortedTemplates, finalReleaseName, pkgerrors.Wrap(err, "Getting Definition Metadata")
+ return sortedTemplates, hookList, finalReleaseName, pkgerrors.Wrap(err, "Getting Definition Metadata")
}
defData, err := definitionClient.Download(rbName, rbVersion)
if err != nil {
- return sortedTemplates, finalReleaseName, pkgerrors.Wrap(err, "Downloading Definition")
+ return sortedTemplates, hookList, finalReleaseName, pkgerrors.Wrap(err, "Downloading Definition")
}
chartBasePath, err := ExtractTarBall(bytes.NewBuffer(defData))
if err != nil {
- return sortedTemplates, finalReleaseName, pkgerrors.Wrap(err, "Extracting Definition Charts")
+ return sortedTemplates, hookList, finalReleaseName, pkgerrors.Wrap(err, "Extracting Definition Charts")
}
//Get the definition ID and download its contents
profile, err := v.Get(rbName, rbVersion, profileName)
if err != nil {
- return sortedTemplates, finalReleaseName, pkgerrors.Wrap(err, "Getting Profile")
+ return sortedTemplates, hookList, finalReleaseName, pkgerrors.Wrap(err, "Getting Profile")
}
//Copy the profile configresources to the chart locations
// chartpath: chart/config/resources/config.yaml
err = prYamlClient.CopyConfigurationOverrides(chartBasePath)
if err != nil {
- return sortedTemplates, finalReleaseName, pkgerrors.Wrap(err, "Copying configresources to chart")
+ return sortedTemplates, hookList, finalReleaseName, pkgerrors.Wrap(err, "Copying configresources to chart")
}
if overrideReleaseName == "" {
finalReleaseName)
chartPath := filepath.Join(chartBasePath, definition.ChartName)
- sortedTemplates, err = helmClient.GenerateKubernetesArtifacts(chartPath,
+ sortedTemplates, hookList, err = helmClient.GenerateKubernetesArtifacts(chartPath,
[]string{prYamlClient.GetValues()},
values)
if err != nil {
- return sortedTemplates, finalReleaseName, pkgerrors.Wrap(err, "Generate final k8s yaml")
+ return sortedTemplates, hookList, finalReleaseName, pkgerrors.Wrap(err, "Generate final k8s yaml")
}
- return sortedTemplates, finalReleaseName, nil
+ return sortedTemplates, hookList, finalReleaseName, nil
}
// Returns an empty profile with the following contents
t.Run(testCase.label, func(t *testing.T) {
db.DBconn = testCase.mockdb
impl := NewProfileClient()
- data, releaseName, err := impl.Resolve(testCase.rbname,
+ data, _, releaseName, err := impl.Resolve(testCase.rbname,
testCase.rbversion, testCase.prname, []string{}, testCase.releaseName)
defer cleanup(data)
if err != nil {