2 * Copyright 2018 Intel Corporation, Inc
3 * Copyright © 2021 Samsung Electronics
4 * Copyright © 2021 Orange
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
10 * http://www.apache.org/licenses/LICENSE-2.0
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
26 "github.com/onap/multicloud-k8s/src/k8splugin/internal/db"
27 "github.com/onap/multicloud-k8s/src/k8splugin/internal/helm"
28 "github.com/onap/multicloud-k8s/src/k8splugin/internal/namegenerator"
29 "github.com/onap/multicloud-k8s/src/k8splugin/internal/rb"
31 pkgerrors "github.com/pkg/errors"
34 // InstanceRequest contains the parameters needed for instantiation
36 type InstanceRequest struct {
37 RBName string `json:"rb-name"`
38 RBVersion string `json:"rb-version"`
39 ProfileName string `json:"profile-name"`
40 ReleaseName string `json:"release-name"`
41 CloudRegion string `json:"cloud-region"`
42 Labels map[string]string `json:"labels"`
43 OverrideValues map[string]string `json:"override-values"`
46 // InstanceResponse contains the response from instantiation
47 type InstanceResponse struct {
49 Request InstanceRequest `json:"request"`
50 Namespace string `json:"namespace"`
51 ReleaseName string `json:"release-name"`
52 Resources []helm.KubernetesResource `json:"resources"`
53 Hooks []*helm.Hook `json:"-"`
56 // InstanceMiniResponse contains the response from instantiation
57 // It does NOT include the created resources.
58 // Use the regular GET to get the created resources for a particular instance
59 type InstanceMiniResponse struct {
61 Request InstanceRequest `json:"request"`
62 ReleaseName string `json:"release-name"`
63 Namespace string `json:"namespace"`
66 // InstanceStatus is what is returned when status is queried for an instance
67 type InstanceStatus struct {
68 Request InstanceRequest `json:"request"`
69 Ready bool `json:"ready"`
70 ResourceCount int32 `json:"resourceCount"`
71 ResourcesStatus []ResourceStatus `json:"resourcesStatus"`
74 // InstanceManager is an interface exposes the instantiation functionality
75 type InstanceManager interface {
76 Create(i InstanceRequest) (InstanceResponse, error)
77 Get(id string) (InstanceResponse, error)
78 Status(id string) (InstanceStatus, error)
79 Query(id, apiVersion, kind, name, labels string) (InstanceStatus, error)
80 List(rbname, rbversion, profilename string) ([]InstanceMiniResponse, error)
81 Find(rbName string, ver string, profile string, labelKeys map[string]string) ([]InstanceMiniResponse, error)
82 Delete(id string) error
85 // InstanceKey is used as the primary key in the db
86 type InstanceKey struct {
90 // We will use json marshalling to convert to string to
91 // preserve the underlying structure.
92 func (dk InstanceKey) String() string {
93 out, err := json.Marshal(dk)
101 // InstanceClient implements the InstanceManager interface
102 // It will also be used to maintain some localized state
103 type InstanceClient struct {
108 // NewInstanceClient returns an instance of the InstanceClient
109 // which implements the InstanceManager
110 func NewInstanceClient() *InstanceClient {
111 return &InstanceClient{
117 // Simplified function to retrieve model data from instance ID
118 func resolveModelFromInstance(instanceID string) (rbName, rbVersion, profileName, releaseName string, err error) {
119 v := NewInstanceClient()
120 resp, err := v.Get(instanceID)
122 return "", "", "", "", pkgerrors.Wrap(err, "Getting instance")
124 return resp.Request.RBName, resp.Request.RBVersion, resp.Request.ProfileName, resp.ReleaseName, nil
127 // Create an instance of rb on the cluster in the database
128 func (v *InstanceClient) Create(i InstanceRequest) (InstanceResponse, error) {
131 if i.RBName == "" || i.RBVersion == "" || i.ProfileName == "" || i.CloudRegion == "" {
132 return InstanceResponse{},
133 pkgerrors.New("RBName, RBversion, ProfileName, CloudRegion are required to create a new instance")
136 //Check if profile exists
137 profile, err := rb.NewProfileClient().Get(i.RBName, i.RBVersion, i.ProfileName)
139 return InstanceResponse{}, pkgerrors.New("Unable to find Profile to create instance")
142 //Convert override values from map to array of strings of the following format
144 overrideValues := []string{}
145 if i.OverrideValues != nil {
146 for k, v := range i.OverrideValues {
147 overrideValues = append(overrideValues, k+"="+v)
151 //Execute the kubernetes create command
152 sortedTemplates, hookList, releaseName, err := rb.NewProfileClient().Resolve(i.RBName, i.RBVersion, i.ProfileName, overrideValues, i.ReleaseName)
154 return InstanceResponse{}, pkgerrors.Wrap(err, "Error resolving helm charts")
157 // TODO: Only generate if id is not provided
158 id := namegenerator.Generate()
160 k8sClient := KubernetesClient{}
161 err = k8sClient.Init(i.CloudRegion, id)
163 return InstanceResponse{}, pkgerrors.Wrap(err, "Getting CloudRegion Information")
166 createdResources, err := k8sClient.createResources(sortedTemplates, profile.Namespace)
168 return InstanceResponse{}, pkgerrors.Wrap(err, "Create Kubernetes Resources")
171 //Compose the return response
172 resp := InstanceResponse{
175 Namespace: profile.Namespace,
176 ReleaseName: releaseName,
177 Resources: createdResources,
184 err = db.DBconn.Create(v.storeName, key, v.tagInst, resp)
186 return InstanceResponse{}, pkgerrors.Wrap(err, "Creating Instance DB Entry")
192 // Get returns the instance for corresponding ID
193 func (v *InstanceClient) Get(id string) (InstanceResponse, error) {
197 value, err := db.DBconn.Read(v.storeName, key, v.tagInst)
199 return InstanceResponse{}, pkgerrors.Wrap(err, "Get Instance")
202 //value is a byte array
204 resp := InstanceResponse{}
205 err = db.DBconn.Unmarshal(value, &resp)
207 return InstanceResponse{}, pkgerrors.Wrap(err, "Unmarshaling Instance Value")
212 return InstanceResponse{}, pkgerrors.New("Error getting Instance")
215 // Query returns state of instance's filtered resources
216 func (v *InstanceClient) Query(id, apiVersion, kind, name, labels string) (InstanceStatus, error) {
218 queryClient := NewQueryClient()
219 //Read the status from the DB
223 value, err := db.DBconn.Read(v.storeName, key, v.tagInst)
225 return InstanceStatus{}, pkgerrors.Wrap(err, "Get Instance")
227 if value == nil { //value is a byte array
228 return InstanceStatus{}, pkgerrors.New("Status is not available")
230 resResp := InstanceResponse{}
231 err = db.DBconn.Unmarshal(value, &resResp)
233 return InstanceStatus{}, pkgerrors.Wrap(err, "Unmarshaling Instance Value")
236 resources, err := queryClient.Query(resResp.Namespace, resResp.Request.CloudRegion, apiVersion, kind, name, labels, id)
238 return InstanceStatus{}, pkgerrors.Wrap(err, "Querying Resources")
241 resp := InstanceStatus{
242 Request: resResp.Request,
243 ResourceCount: resources.ResourceCount,
244 ResourcesStatus: resources.ResourcesStatus,
249 // Status returns the status for the instance
250 func (v *InstanceClient) Status(id string) (InstanceStatus, error) {
252 //Read the status from the DB
257 value, err := db.DBconn.Read(v.storeName, key, v.tagInst)
259 return InstanceStatus{}, pkgerrors.Wrap(err, "Get Instance")
262 //value is a byte array
264 return InstanceStatus{}, pkgerrors.New("Status is not available")
267 resResp := InstanceResponse{}
268 err = db.DBconn.Unmarshal(value, &resResp)
270 return InstanceStatus{}, pkgerrors.Wrap(err, "Unmarshaling Instance Value")
273 k8sClient := KubernetesClient{}
274 err = k8sClient.Init(resResp.Request.CloudRegion, id)
276 return InstanceStatus{}, pkgerrors.Wrap(err, "Getting CloudRegion Information")
279 cumulatedErrorMsg := make([]string, 0)
280 podsStatus, err := k8sClient.getPodsByLabel(resResp.Namespace)
282 cumulatedErrorMsg = append(cumulatedErrorMsg, err.Error())
285 generalStatus := make([]ResourceStatus, 0, len(resResp.Resources))
287 for _, resource := range resResp.Resources {
288 for _, pod := range podsStatus {
289 if resource.GVK == pod.GVK && resource.Name == pod.Name {
290 continue Main //Don't double check pods if someone decided to define pod explicitly in helm chart
293 status, err := k8sClient.GetResourceStatus(resource, resResp.Namespace)
295 cumulatedErrorMsg = append(cumulatedErrorMsg, err.Error())
297 generalStatus = append(generalStatus, status)
300 resp := InstanceStatus{
301 Request: resResp.Request,
302 ResourceCount: int32(len(generalStatus) + len(podsStatus)),
303 Ready: false, //FIXME To determine readiness, some parsing of status fields is necessary
304 ResourcesStatus: append(generalStatus, podsStatus...),
307 if len(cumulatedErrorMsg) != 0 {
308 err = pkgerrors.New("Getting Resources Status:\n" +
309 strings.Join(cumulatedErrorMsg, "\n"))
312 //TODO Filter response content by requested verbosity (brief, ...)?
317 // List returns the instance for corresponding ID
318 // Empty string returns all
319 func (v *InstanceClient) List(rbname, rbversion, profilename string) ([]InstanceMiniResponse, error) {
321 dbres, err := db.DBconn.ReadAll(v.storeName, v.tagInst)
322 if err != nil || len(dbres) == 0 {
323 return []InstanceMiniResponse{}, pkgerrors.Wrap(err, "Listing Instances")
326 var results []InstanceMiniResponse
328 for key, value := range dbres {
329 //value is a byte array
331 resp := InstanceResponse{}
332 err = db.DBconn.Unmarshal(value, &resp)
334 log.Printf("[Instance] Error: %s Unmarshaling Instance: %s", err.Error(), key)
337 miniresp := InstanceMiniResponse{
339 Request: resp.Request,
340 Namespace: resp.Namespace,
341 ReleaseName: resp.ReleaseName,
344 //Filter based on the accepted keys
345 if len(rbname) != 0 &&
346 miniresp.Request.RBName != rbname {
349 if len(rbversion) != 0 &&
350 miniresp.Request.RBVersion != rbversion {
353 if len(profilename) != 0 &&
354 miniresp.Request.ProfileName != profilename {
358 results = append(results, miniresp)
365 // Find returns the instances that match the given criteria
366 // If version is empty, it will return all instances for a given rbName
367 // If profile is empty, it will return all instances for a given rbName+version
368 // If labelKeys are provided, the results are filtered based on that.
369 // It is an AND operation for labelkeys.
370 func (v *InstanceClient) Find(rbName string, version string, profile string, labelKeys map[string]string) ([]InstanceMiniResponse, error) {
371 if rbName == "" && len(labelKeys) == 0 {
372 return []InstanceMiniResponse{}, pkgerrors.New("rbName or labelkeys is required and cannot be empty")
375 responses, err := v.List(rbName, version, profile)
377 return []InstanceMiniResponse{}, pkgerrors.Wrap(err, "Listing Instances")
380 ret := []InstanceMiniResponse{}
382 //filter the list by labelKeys now
383 for _, resp := range responses {
386 for k, v := range labelKeys {
387 if resp.Request.Labels[k] != v {
392 // If label was not found in the response, don't add it
394 ret = append(ret, resp)
402 // Delete the Instance from database
403 func (v *InstanceClient) Delete(id string) error {
404 inst, err := v.Get(id)
406 return pkgerrors.Wrap(err, "Error getting Instance")
409 k8sClient := KubernetesClient{}
410 err = k8sClient.Init(inst.Request.CloudRegion, inst.ID)
412 return pkgerrors.Wrap(err, "Getting CloudRegion Information")
415 err = k8sClient.deleteResources(inst.Resources, inst.Namespace)
417 return pkgerrors.Wrap(err, "Deleting Instance Resources")
423 err = db.DBconn.Delete(v.storeName, key, v.tagInst)
425 return pkgerrors.Wrap(err, "Delete Instance")