...

Source file src/edge-infra.dev/pkg/edge/api/services/vm_service.go

Documentation: edge-infra.dev/pkg/edge/api/services

     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  	// validate VirtualMachine
    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  	// check existing hostnames
    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  	// we create provided disks in DB
   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  	// get the current values for the VirtualMachine in the db
   139  	currentVM, err := v.GetVirtualMachine(ctx, updateVM.VirtualMachineID)
   140  	if err != nil {
   141  		return nil, err
   142  	}
   143  
   144  	// set current VirtualMachine values
   145  	virtualMachine = utils.UpdateVirtualMachine(currentVM, updateVM)
   146  	// validate the current VirtualMachine
   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  	// update VirtualMachine in db
   160  	if _, err = transaction.ExecContext(ctx, sqlquery.VirtualMachineUpdateQuery, args...); err != nil {
   161  		return nil, err
   162  	}
   163  
   164  	// if any disks provided to update, we should update them in DB and set Disks attribute to new disk list
   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  	// get all of the hostnames in the cluster namespace
   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 { //nolint
   336  	return &virtualMachineService{
   337  		SQLDB: sqlDB,
   338  	}
   339  }
   340  

View as plain text