1 package services
2
3 import (
4 "context"
5 "database/sql"
6 "errors"
7 "fmt"
8 "strings"
9
10 "github.com/google/uuid"
11
12 sqlerr "edge-infra.dev/pkg/edge/api/apierror/sql"
13 "edge-infra.dev/pkg/edge/api/graph/mapper"
14 "edge-infra.dev/pkg/edge/api/graph/model"
15 sqlquery "edge-infra.dev/pkg/edge/api/sql"
16 "edge-infra.dev/pkg/edge/api/utils"
17 )
18
19 type VirtualMachineService interface {
20 CreateVirtualMachineEntry(ctx context.Context, createVM *model.VirtualMachineCreateInput) (*model.VirtualMachine, string, error)
21 DeleteVirtualMachineEntry(ctx context.Context, virtualMachineID string) error
22 UpdateVirtualMachineEntry(ctx context.Context, updateVM *model.VirtualMachineIDInput) (*model.VirtualMachine, error)
23 GetVirtualMachine(ctx context.Context, virtualMachineID string) (*model.VirtualMachine, error)
24 GetVirtualMachines(ctx context.Context, clusterEdgeID *string, virtualMachineHostname *string) ([]*model.VirtualMachine, error)
25 CreateVirtualMachineDiskEntries(ctx context.Context, virtualMachineID string, createDisks []*model.VirtualMachineDiskCreateInput) ([]*model.VirtualMachineDisk, error)
26 DeleteVirtualMachineDiskEntry(ctx context.Context, diskID string) (*model.VirtualMachineDisk, error)
27 UpdateVirtualMachineDiskEntries(ctx context.Context, updateDisks []*model.VirtualMachineDiskIDInput) ([]*model.VirtualMachineDisk, error)
28 GetVirtualMachineDisk(ctx context.Context, diskID string) (*model.VirtualMachineDisk, error)
29 GetVirtualMachineDisks(ctx context.Context, virtualMachineID string) ([]*model.VirtualMachineDisk, error)
30 GetVirtualMachineFromDisk(ctx context.Context, diskID string) (*model.VirtualMachine, error)
31 CreateDSDSKubeVirtualMachineCR(virtualMachine *model.VirtualMachine) (string, error)
32 GetVirtualMachineWithDisks(ctx context.Context, virtualMachineID string) (*model.VirtualMachine, error)
33 }
34
35 type virtualMachineService struct {
36 SQLDB *sql.DB
37 }
38
39 func (v *virtualMachineService) CreateVirtualMachineEntry(ctx context.Context, createVM *model.VirtualMachineCreateInput) (virtualMachine *model.VirtualMachine, namespace string, err error) {
40 if len(createVM.Disks) == 0 {
41 return nil, "", fmt.Errorf("must provide at least one disk when creating a virtual machine")
42 }
43
44 transaction, err := v.SQLDB.BeginTx(ctx, nil)
45 if err != nil {
46 return nil, "", err
47 }
48
49 defer func() {
50 if err != nil {
51 err = errors.Join(err, transaction.Rollback())
52 }
53 }()
54
55 row := transaction.QueryRowContext(ctx, sqlquery.GetClusterNameByClusterEdgeIDQuery, createVM.ClusterEdgeID)
56 var clusterName string
57 if err = row.Scan(&clusterName); err != nil {
58 return nil, "", err
59 }
60
61 namespaceService := NewNamespaceService(v.SQLDB)
62 namespace, err = namespaceService.GetNamespaceName(ctx, createVM.NamespaceEdgeID)
63 if err != nil {
64 return nil, "", err
65 }
66
67 hostname := strings.ToLower(createVM.Hostname)
68 createVM.Hostname = hostname
69
70 modelVM := utils.CreateVirtualMachineModel(uuid.NewString(), namespace, createVM.ClusterEdgeID, clusterName, createVM.Hostname, *createVM.TargetPowerState, *createVM.Cpus, *createVM.Memory, *createVM.MachineType)
71 virtualMachine = &modelVM
72
73 if err = utils.ValidateVirtualMachine(virtualMachine); err != nil {
74 return nil, "", err
75 }
76 if err = namespaceService.CreateClusterNamespaceEntry(ctx, transaction, createVM.NamespaceEdgeID, createVM.ClusterEdgeID); err != nil {
77 return nil, "", err
78 }
79 clusterNamespaceEdgeID, err := namespaceService.GetClusterNamespaceEdgeID(ctx, transaction, createVM.NamespaceEdgeID, createVM.ClusterEdgeID)
80 if err != nil {
81 return nil, "", err
82 }
83
84
85 if err := v.checkHostnames(ctx, hostname, clusterNamespaceEdgeID); err != nil {
86 return nil, "", err
87 }
88
89 args := []interface{}{
90 virtualMachine.VirtualMachineID,
91 clusterNamespaceEdgeID,
92 virtualMachine.Hostname,
93 virtualMachine.TargetPowerState,
94 virtualMachine.Cpus,
95 virtualMachine.Memory,
96 virtualMachine.MachineType,
97 }
98
99 if _, err = transaction.ExecContext(ctx, sqlquery.VirtualMachineCreateQuery, args...); err != nil {
100 return nil, "", err
101 }
102
103
104 createdDisks, err := v.createVirtualMachineDiskEntries(ctx, transaction, createVM.Disks, virtualMachine.VirtualMachineID)
105 if err != nil {
106 return nil, "", err
107 }
108 if utils.HasDuplicateDiskBootOrders(createdDisks) {
109 return nil, "", fmt.Errorf("cannot create vm with disks that have duplicate boot orders")
110 }
111
112 virtualMachine.Disks = createdDisks
113
114 if err = transaction.Commit(); err != nil {
115 return nil, "", err
116 }
117
118 return virtualMachine, namespace, nil
119 }
120
121 func (v *virtualMachineService) DeleteVirtualMachineEntry(ctx context.Context, virtualMachineID string) error {
122 _, err := v.SQLDB.ExecContext(ctx, sqlquery.VirtualMachineDeleteQuery, virtualMachineID)
123 return err
124 }
125
126 func (v *virtualMachineService) UpdateVirtualMachineEntry(ctx context.Context, updateVM *model.VirtualMachineIDInput) (virtualMachine *model.VirtualMachine, err error) {
127 transaction, err := v.SQLDB.BeginTx(ctx, nil)
128 if err != nil {
129 return nil, err
130 }
131
132 defer func() {
133 if err != nil {
134 err = errors.Join(err, transaction.Rollback())
135 }
136 }()
137
138
139 currentVM, err := v.GetVirtualMachine(ctx, updateVM.VirtualMachineID)
140 if err != nil {
141 return nil, err
142 }
143
144
145 virtualMachine = utils.UpdateVirtualMachine(currentVM, updateVM)
146
147 if err = utils.ValidateVirtualMachine(virtualMachine); err != nil {
148 return nil, err
149 }
150
151 args := []interface{}{
152 virtualMachine.TargetPowerState,
153 virtualMachine.Cpus,
154 virtualMachine.Memory,
155 virtualMachine.MachineType,
156 virtualMachine.VirtualMachineID,
157 }
158
159
160 if _, err = transaction.ExecContext(ctx, sqlquery.VirtualMachineUpdateQuery, args...); err != nil {
161 return nil, err
162 }
163
164
165 if len(updateVM.VirtualMachineValues.Disks) != 0 {
166 _, err := v.updateVirtualMachineDiskEntries(ctx, transaction, updateVM.VirtualMachineValues.Disks)
167 if err != nil {
168 return nil, err
169 }
170 }
171
172 allDisks, err := v.getVirtualMachineDisks(ctx, transaction, updateVM.VirtualMachineID)
173 if err != nil {
174 return nil, err
175 }
176
177 if utils.HasDuplicateDiskBootOrders(allDisks) {
178 return nil, fmt.Errorf("cannot update vm with disks that cause duplicate boot orders")
179 }
180
181 if err = transaction.Commit(); err != nil {
182 return nil, err
183 }
184
185 virtualMachine.Disks = allDisks
186
187 return virtualMachine, nil
188 }
189
190 func (v *virtualMachineService) GetVirtualMachine(ctx context.Context, virtualMachineID string) (*model.VirtualMachine, error) {
191 row := v.SQLDB.QueryRowContext(ctx, sqlquery.GetVirtualMachineByIDQuery, virtualMachineID)
192 virtualMachine, err := v.scanVirtualMachineRow(row)
193 if err != nil {
194 return nil, err
195 }
196 return virtualMachine, nil
197 }
198
199 func (v *virtualMachineService) GetVirtualMachines(ctx context.Context, clusterEdgeID *string, virtualMachineHostname *string) ([]*model.VirtualMachine, error) {
200 switch {
201 case clusterEdgeID == nil && virtualMachineHostname == nil:
202 return v.getAllVirtualMachines(ctx)
203 case clusterEdgeID != nil && virtualMachineHostname == nil:
204 return v.getClusterEdgeIDVirtualMachines(ctx, *clusterEdgeID)
205 case clusterEdgeID == nil && virtualMachineHostname != nil:
206 return v.getHostnameVirtualMachines(ctx, *virtualMachineHostname)
207 default:
208 return v.getClusterEdgeIDAndHostnameVirtualMachines(ctx, *clusterEdgeID, *virtualMachineHostname)
209 }
210 }
211
212 func (v *virtualMachineService) CreateDSDSKubeVirtualMachineCR(virtualMachine *model.VirtualMachine) (string, error) {
213 kubeVM, err := mapper.VirtualMachineToKubeVirtualMachine(virtualMachine)
214 if err != nil {
215 return "", err
216 }
217 encodedKubeVM, err := utils.ConvertStructToBase64(kubeVM)
218 if err != nil {
219 return "", err
220 }
221 return encodedKubeVM, nil
222 }
223
224 func (v *virtualMachineService) GetVirtualMachineWithDisks(ctx context.Context, virtualMachineID string) (*model.VirtualMachine, error) {
225 virtualMachine, err := v.GetVirtualMachine(ctx, virtualMachineID)
226 if err != nil {
227 return nil, err
228 }
229 virtualMachine.Disks, err = v.GetVirtualMachineDisks(ctx, virtualMachineID)
230 if err != nil {
231 return nil, err
232 }
233 return virtualMachine, nil
234 }
235
236 func (v *virtualMachineService) scanVirtualMachineRow(row *sql.Row) (*model.VirtualMachine, error) {
237 virtualMachine := &model.VirtualMachine{}
238 err := row.Scan(&virtualMachine.VirtualMachineID, &virtualMachine.Namespace, &virtualMachine.ClusterEdgeID, &virtualMachine.ClusterName, &virtualMachine.Hostname, &virtualMachine.TargetPowerState, &virtualMachine.Cpus, &virtualMachine.Memory, &virtualMachine.MachineType)
239 if err != nil {
240 return nil, err
241 }
242 return virtualMachine, nil
243 }
244
245 func (v *virtualMachineService) scanVirtualMachineRows(rows *sql.Rows) ([]*model.VirtualMachine, error) {
246 virtualMachines := []*model.VirtualMachine{}
247 for rows.Next() {
248 virtualMachine := &model.VirtualMachine{}
249 err := rows.Scan(&virtualMachine.VirtualMachineID, &virtualMachine.Namespace, &virtualMachine.ClusterEdgeID, &virtualMachine.ClusterName, &virtualMachine.Hostname, &virtualMachine.TargetPowerState, &virtualMachine.Cpus, &virtualMachine.Memory, &virtualMachine.MachineType)
250 if err != nil {
251 return nil, err
252 }
253 virtualMachines = append(virtualMachines, virtualMachine)
254 }
255 if err := rows.Err(); err != nil {
256 return nil, sqlerr.Wrap(err)
257 }
258 return virtualMachines, nil
259 }
260
261 func (v *virtualMachineService) getAllVirtualMachines(ctx context.Context) ([]*model.VirtualMachine, error) {
262 rows, err := v.SQLDB.QueryContext(ctx, sqlquery.GetAllVirtualMachinesQuery)
263 if err != nil {
264 return nil, err
265 }
266 virtualMachines, err := v.scanVirtualMachineRows(rows)
267 if err != nil {
268 return nil, err
269 }
270 return virtualMachines, nil
271 }
272
273 func (v *virtualMachineService) getClusterEdgeIDVirtualMachines(ctx context.Context, clusterEdgeID string) ([]*model.VirtualMachine, error) {
274 rows, err := v.SQLDB.QueryContext(ctx, sqlquery.GetVirtualMachinesByClusterEdgeIDQuery, clusterEdgeID)
275 if err != nil {
276 return nil, err
277 }
278
279 virtualMachines, err := v.scanVirtualMachineRows(rows)
280 if err != nil {
281 return nil, err
282 }
283 return virtualMachines, nil
284 }
285
286 func (v *virtualMachineService) getHostnameVirtualMachines(ctx context.Context, virtualMachineHostname string) ([]*model.VirtualMachine, error) {
287 rows, err := v.SQLDB.QueryContext(ctx, sqlquery.GetVirtualMachinesByHostnameQuery, virtualMachineHostname)
288 if err != nil {
289 return nil, err
290 }
291
292 virtualMachines, err := v.scanVirtualMachineRows(rows)
293 if err != nil {
294 return nil, err
295 }
296 return virtualMachines, nil
297 }
298
299 func (v *virtualMachineService) getClusterEdgeIDAndHostnameVirtualMachines(ctx context.Context, clusterEdgeID, virtualMachineHostname string) ([]*model.VirtualMachine, error) {
300 rows, err := v.SQLDB.QueryContext(ctx, sqlquery.GetVirtualMachinesByClusterEdgeIDAndHostnameQuery, clusterEdgeID, virtualMachineHostname)
301 if err != nil {
302 return nil, err
303 }
304
305 virtualMachines, err := v.scanVirtualMachineRows(rows)
306 if err != nil {
307 return nil, err
308 }
309 return virtualMachines, nil
310 }
311
312 func (v *virtualMachineService) checkHostnames(ctx context.Context, nodeName string, clusterNamespaceEdgeID string) error {
313
314 rows, err := v.SQLDB.QueryContext(ctx, sqlquery.GetVirtualMachineHostnamesForAClusterNamespaceQuery, clusterNamespaceEdgeID)
315 if err != nil {
316 return err
317 }
318
319 var hostname string
320 for rows.Next() {
321 err := rows.Scan(&hostname)
322 if err != nil {
323 return err
324 }
325 if hostname == nodeName {
326 return fmt.Errorf("cannot create VirtualMachine - hostname %s already exists in this cluster namespace", hostname)
327 }
328 }
329 if err := rows.Err(); err != nil {
330 return sqlerr.Wrap(err)
331 }
332 return nil
333 }
334
335 func NewVirtualMachineService(sqlDB *sql.DB) *virtualMachineService {
336 return &virtualMachineService{
337 SQLDB: sqlDB,
338 }
339 }
340
View as plain text