Skip to content

Commit 2e48100

Browse files
authored
Service flavor implementation (#114)
1 parent 7312361 commit 2e48100

File tree

70 files changed

+5233
-436
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

70 files changed

+5233
-436
lines changed

apis/nodecore/v1alpha1/allocation_status.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,9 @@ func (allocation *Allocation) SetStatus(status Status, msg string) {
2222
allocation.Status.LastUpdateTime = tools.GetTimeNow()
2323
allocation.Status.Message = msg
2424
}
25+
26+
// SetResourceRef sets the resource reference of the allocation.
27+
func (allocation *Allocation) SetResourceRef(resourceRef GenericRef) {
28+
allocation.Status.ResourceRef = resourceRef
29+
allocation.Status.LastUpdateTime = tools.GetTimeNow()
30+
}

apis/nodecore/v1alpha1/allocation_types.go

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,13 @@ type Status string
2323

2424
// Status is the status of the allocation.
2525
const (
26-
Active Status = "Active"
27-
Reserved Status = "Reserved"
28-
Released Status = "Released"
29-
Inactive Status = "Inactive"
30-
Error Status = "Error"
26+
Active Status = "Active"
27+
Provisioning Status = "Provisioning"
28+
ResourceCreation Status = "ResourceCreation"
29+
Peering Status = "Peering"
30+
Released Status = "Released"
31+
Inactive Status = "Inactive"
32+
Error Status = "Error"
3133
)
3234

3335
// AllocationSpec defines the desired state of Allocation.
@@ -51,10 +53,17 @@ type AllocationStatus struct {
5153

5254
// Message contains the last message of the allocation
5355
Message string `json:"message,omitempty"`
56+
57+
// Related resource of the allocation
58+
ResourceRef GenericRef `json:"resourceRef,omitempty"`
5459
}
5560

61+
//nolint:lll // kubebuilder directives are too long, but they must be on the same line
5662
//+kubebuilder:object:root=true
5763
//+kubebuilder:subresource:status
64+
//+kubebuilder:printcolumn:name="Status",type="string",JSONPath=".status.status",description="The status of the allocation"
65+
//+kubebuilder:printcolumn:name="Status Message",type="string",JSONPath=".status.message",description="The message of the status"
66+
//+kubebuilder:printcolumn:name="Resource Reference",type="string",JSONPath=".status.resourceRef.name",description="The reference to the resource",priority=1
5867
//+kubebuilder:resource:shortName=alloc;allocs
5968

6069
// Allocation is the Schema for the allocations API.

apis/nodecore/v1alpha1/common.go

Lines changed: 50 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -43,13 +43,28 @@ type GenericRef struct {
4343
// The namespace containing the resource to be referenced. It should be left
4444
// empty in case of cluster-wide resources.
4545
Namespace string `json:"namespace,omitempty"`
46+
47+
// The API version of the resource to be referenced.
48+
APIVersion string `json:"apiVersion,omitempty"`
49+
50+
// The kind of the resource to be referenced.
51+
Kind string `json:"kind,omitempty"`
52+
}
53+
54+
func (g *GenericRef) String() string {
55+
return fmt.Sprintf("%s:%s - %s/%s", g.APIVersion, g.Kind, g.Namespace, g.Name)
4656
}
4757

4858
// NodeIdentity is the identity of a FLUIDOS Node.
4959
type NodeIdentity struct {
50-
Domain string `json:"domain"`
51-
NodeID string `json:"nodeID"`
52-
IP string `json:"ip"`
60+
Domain string `json:"domain"`
61+
NodeID string `json:"nodeID"`
62+
IP string `json:"ip"`
63+
AdditionalInformation *NodeIdentityAdditionalInfo `json:"additionalInformation,omitempty"`
64+
}
65+
66+
// NodeIdentityAdditionalInfo contains additional information about the node.
67+
type NodeIdentityAdditionalInfo struct {
5368
LiqoID string `json:"liqoID,omitempty"`
5469
}
5570

@@ -71,16 +86,41 @@ type LiqoCredentials struct {
7186

7287
// ParseConfiguration parses the configuration data into the correct type.
7388
// Returns the FlavorTypeIdentifier, aka the ConfigurationTypeIdentifier and the configuration data.
74-
func ParseConfiguration(p *Configuration) (FlavorTypeIdentifier, interface{}, error) {
89+
func ParseConfiguration(configuration *Configuration, flavor *Flavor) (FlavorTypeIdentifier, interface{}, error) {
7590
var validationError error
7691

77-
switch p.ConfigurationTypeIdentifier {
92+
switch configuration.ConfigurationTypeIdentifier {
7893
case TypeK8Slice:
79-
var partition K8SliceConfiguration
80-
validationError = json.Unmarshal(p.ConfigurationData.Raw, &partition)
81-
return TypeK8Slice, partition, validationError
82-
// TODO: implement other type of partition (if any)
94+
var k8SliceConfiguration K8SliceConfiguration
95+
validationError = json.Unmarshal(configuration.ConfigurationData.Raw, &k8SliceConfiguration)
96+
return TypeK8Slice, k8SliceConfiguration, validationError
97+
case TypeService:
98+
var serviceConfiguration ServiceConfiguration
99+
validationError = json.Unmarshal(configuration.ConfigurationData.Raw, &serviceConfiguration)
100+
if validationError != nil {
101+
return "", nil, validationError
102+
}
103+
// Parse Flavor
104+
flavorType, flavorData, err := ParseFlavorType(flavor)
105+
if err != nil {
106+
return "", nil, err
107+
}
108+
// Forcing the type to be ServiceFlavor
109+
if flavorType != TypeService {
110+
return "", nil, fmt.Errorf("flavor type %s does not match the configuration type %s", flavorType, TypeService)
111+
}
112+
// Get ServiceFlavor
113+
serviceFlavor := flavorData.(ServiceFlavor)
114+
validationError = serviceConfiguration.Validate(&serviceFlavor)
115+
116+
return TypeService, serviceConfiguration, validationError
117+
case TypeVM:
118+
// TODO (VM): implement the VM configuration parsing
119+
return TypeVM, nil, nil
120+
case TypeSensor:
121+
// TODO (Sensor): implement the sensor configuration parsing
122+
return TypeSensor, nil, nil
83123
default:
84-
return "", nil, fmt.Errorf("partition type %s not supported", p.ConfigurationTypeIdentifier)
124+
return "", nil, fmt.Errorf("partition type %s not supported", configuration.ConfigurationTypeIdentifier)
85125
}
86126
}

apis/nodecore/v1alpha1/flavor_types.go

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import (
1919

2020
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2121
"k8s.io/apimachinery/pkg/runtime"
22+
"k8s.io/klog/v2"
2223
)
2324

2425
const (
@@ -145,10 +146,12 @@ func init() {
145146
SchemeBuilder.Register(&Flavor{}, &FlavorList{})
146147
}
147148

148-
// ParseFlavorType parses a Flavor into a the type and the unmarsheled raw value.
149+
// ParseFlavorType parses a Flavor into a the type and the unmarshalled raw value.
149150
func ParseFlavorType(flavor *Flavor) (FlavorTypeIdentifier, interface{}, error) {
150151
var validationErr error
151152

153+
klog.Infof("Parsing Flavor type %s", flavor.Spec.FlavorType.TypeIdentifier)
154+
152155
switch flavor.Spec.FlavorType.TypeIdentifier {
153156
case TypeK8Slice:
154157

@@ -162,11 +165,21 @@ func ParseFlavorType(flavor *Flavor) (FlavorTypeIdentifier, interface{}, error)
162165
return TypeK8Slice, *k8slice, validationErr
163166

164167
case TypeVM:
165-
// TODO: Implement VM flavor parsing
168+
// TODO (VM): implement the VM flavor parsing
166169
return "", nil, fmt.Errorf("flavor type %s not supported", flavor.Spec.FlavorType.TypeIdentifier)
167170

168171
case TypeService:
169-
// TODO: Implement Service flavor parsing
172+
var service *ServiceFlavor
173+
// Parse Service flavor
174+
service, _, err := ParseServiceFlavor(flavor.Spec.FlavorType)
175+
if err != nil {
176+
return "", nil, err
177+
}
178+
179+
return TypeService, *service, validationErr
180+
181+
case TypeSensor:
182+
// TODO (Sensor): implement the sensor flavor parsing
170183
return "", nil, fmt.Errorf("flavor type %s not supported", flavor.Spec.FlavorType.TypeIdentifier)
171184

172185
default:
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// Copyright 2022-2024 FLUIDOS Project
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package v1alpha1
16+
17+
// SensorFlavor represents a Sensor Flavor description.
18+
type SensorFlavor struct {
19+
// TODO(Sensor): Implement the SensorFlavor struct.
20+
}
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
// Copyright 2022-2024 FLUIDOS Project
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package v1alpha1
16+
17+
import (
18+
"bytes"
19+
"fmt"
20+
21+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
22+
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
23+
"k8s.io/apimachinery/pkg/runtime"
24+
"k8s.io/apimachinery/pkg/runtime/serializer/yaml"
25+
)
26+
27+
// ServiceBlueprintSpec defines the desired state of ServiceBlueprint.
28+
type ServiceBlueprintSpec struct {
29+
// Name of the Service Blueprint.
30+
Name string `json:"name"`
31+
32+
// Description of the Service Blueprint.
33+
Description string `json:"description"`
34+
35+
// Category of the Service Blueprint.
36+
Category string `json:"category"`
37+
38+
// Tags of the Service Blueprint.
39+
Tags []string `json:"tags"`
40+
41+
// HostingPolicies of the Service Blueprint.
42+
// If empty, the default behavior is to host on the provider cluster.
43+
// If multiple policies are specified, the first one is the default.
44+
HostingPolicies []HostingPolicy `json:"hostingPolicies"`
45+
46+
// Templates of the Service Blueprint.
47+
Templates []ServiceTemplate `json:"templates"`
48+
}
49+
50+
// ServiceTemplate defines the template of a Service.
51+
type ServiceTemplate struct {
52+
// Name of the Service Template.
53+
Name string `json:"name"`
54+
// Description of the Service Template.
55+
Description string `json:"description,omitempty"`
56+
// YAML template of the Service.
57+
ServiceTemplateData runtime.RawExtension `json:"serviceData"`
58+
}
59+
60+
// ServiceBlueprintStatus defines the observed state of ServiceBlueprint.
61+
type ServiceBlueprintStatus struct {
62+
// ServiceFlavor linked to the Service Blueprint.
63+
ServiceFlavors []ServiceFlavor `json:"serviceFlavors,omitempty"`
64+
}
65+
66+
//+kubebuilder:object:root=true
67+
//+kubebuilder:subresource:status
68+
69+
// ServiceBlueprint is the Schema for the serviceblueprints API
70+
// +kubebuilder:printcolumn:name="Name",type=string,JSONPath=`.spec.name`
71+
// +kubebuilder:printcolumn:name="Description",type=string,JSONPath=`.spec.description`
72+
// +kubebuilder:printcolumn:name="Category",type=string,JSONPath=`.spec.category`
73+
// +kubebuilder:printcolumn:name="Tags",type=string,JSONPath=`.spec.tags`
74+
// +kubebuilder:printcolumn:name="ServiceFlavors",type=string,JSONPath=`.status.serviceFlavors`
75+
// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp"
76+
// +kubebuilder:resource:shortName=sbp
77+
type ServiceBlueprint struct {
78+
metav1.TypeMeta `json:",inline"`
79+
metav1.ObjectMeta `json:"metadata,omitempty"`
80+
81+
Spec ServiceBlueprintSpec `json:"spec,omitempty"`
82+
Status ServiceBlueprintStatus `json:"status,omitempty"`
83+
}
84+
85+
//+kubebuilder:object:root=true
86+
87+
// ServiceBlueprintList contains a list of ServiceBlueprint.
88+
type ServiceBlueprintList struct {
89+
metav1.TypeMeta `json:",inline"`
90+
metav1.ListMeta `json:"metadata,omitempty"`
91+
Items []ServiceBlueprint `json:"items"`
92+
}
93+
94+
func init() {
95+
SchemeBuilder.Register(&ServiceBlueprint{}, &ServiceBlueprintList{})
96+
}
97+
98+
// ValidateAndExtractManifests extracts manifests from ServiceTemplateData and validates them.
99+
func ValidateAndExtractManifests(templates []ServiceTemplate) ([]*unstructured.Unstructured, error) {
100+
var manifests []*unstructured.Unstructured
101+
102+
for _, template := range templates {
103+
// Step 1: Parse the raw data from ServiceTemplateData (YAML or JSON)
104+
parsedManifests, err := parseRawServiceTemplateData(template.ServiceTemplateData)
105+
if err != nil {
106+
return nil, fmt.Errorf("failed to parse service template data for template %s: %w", template.Name, err)
107+
}
108+
109+
// Step 2: Validate the manifests
110+
for _, manifest := range parsedManifests {
111+
if err := validateManifest(manifest); err != nil {
112+
return nil, fmt.Errorf("validation failed for template %s: %w", template.Name, err)
113+
}
114+
manifests = append(manifests, manifest)
115+
}
116+
}
117+
118+
return manifests, nil
119+
}
120+
121+
// parseRawServiceTemplateData parses the raw YAML/JSON data from ServiceTemplateData.
122+
func parseRawServiceTemplateData(data runtime.RawExtension) ([]*unstructured.Unstructured, error) {
123+
var manifests []*unstructured.Unstructured
124+
125+
// Decoding YAML to JSON
126+
decoder := yaml.NewDecodingSerializer(unstructured.UnstructuredJSONScheme)
127+
128+
// Split multiple documents if necessary (YAML can have multiple docs separated by `---`)
129+
docs := bytes.Split(data.Raw, []byte("\n---\n"))
130+
131+
for _, doc := range docs {
132+
if len(bytes.TrimSpace(doc)) == 0 {
133+
continue // Skip empty docs
134+
}
135+
136+
// Parse into Unstructured to handle dynamic manifest types
137+
obj := &unstructured.Unstructured{}
138+
_, _, err := decoder.Decode(doc, nil, obj)
139+
if err != nil {
140+
return nil, fmt.Errorf("failed to decode manifest: %w", err)
141+
}
142+
143+
manifests = append(manifests, obj)
144+
}
145+
146+
return manifests, nil
147+
}
148+
149+
// validateManifest performs basic validation on the Kubernetes manifest.
150+
func validateManifest(manifest *unstructured.Unstructured) error {
151+
// Basic validation for required fields in Kubernetes objects
152+
if manifest.GetKind() == "" || manifest.GetAPIVersion() == "" {
153+
return fmt.Errorf("manifest missing kind or apiVersion")
154+
}
155+
156+
// Add more specific validation logic if necessary
157+
158+
return nil
159+
}

0 commit comments

Comments
 (0)