...

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

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

     1  package services
     2  
     3  import (
     4  	"context"
     5  	"database/sql"
     6  	"fmt"
     7  	"slices"
     8  	"strings"
     9  
    10  	sqlerr "edge-infra.dev/pkg/edge/api/apierror/sql"
    11  	"edge-infra.dev/pkg/edge/api/graph/model"
    12  	sqlquery "edge-infra.dev/pkg/edge/api/sql"
    13  	"edge-infra.dev/pkg/edge/api/utils"
    14  	"edge-infra.dev/pkg/edge/capabilities"
    15  	"edge-infra.dev/pkg/edge/chariot/client"
    16  	"edge-infra.dev/pkg/lib/uuid"
    17  	v1ien "edge-infra.dev/pkg/sds/ien/k8s/apis/v1"
    18  )
    19  
    20  //go:generate mockgen -destination=../mocks/mock_terminal_label_service.go -package=mocks edge-infra.dev/pkg/edge/api/services TerminalLabelService
    21  type TerminalLabelService interface {
    22  	CreateTerminalLabel(context.Context, string, ...string) error
    23  	GetTerminalLabel(context.Context, string) (*model.TerminalLabel, error)
    24  	DeleteTerminalLabels(context.Context, model.SearchTerminalLabelInput) ([]*model.TerminalLabel, error)
    25  	SendUpdatedIENCRAfterDeletion(context.Context, []*model.TerminalLabel) error
    26  	GetTerminalLabels(context.Context, model.SearchTerminalLabelInput) ([]*model.TerminalLabel, error)
    27  	GetTerminalLabelsInfo(ctx context.Context, terminals []*model.Terminal) ([]*model.Terminal, error)
    28  }
    29  
    30  type terminalLabelService struct {
    31  	SQLDB               *sql.DB
    32  	ChariotService      ChariotService
    33  	TerminalService     TerminalService
    34  	StoreClusterService StoreClusterService
    35  	LabelService        LabelService
    36  }
    37  
    38  func (n *terminalLabelService) CreateTerminalLabel(ctx context.Context, terminalID string, labelEdgeIDs ...string) error {
    39  	tx, err := n.SQLDB.BeginTx(ctx, &sql.TxOptions{})
    40  	if err != nil {
    41  		return err
    42  	}
    43  	customLabels, err := insertTerminalLabels(ctx, tx, terminalID, labelEdgeIDs...)
    44  	if err != nil {
    45  		if rollbackErr := tx.Rollback(); rollbackErr != nil {
    46  			return rollbackErr
    47  		}
    48  		return err
    49  	}
    50  	getLabel := false
    51  	terminal, err := n.TerminalService.GetTerminal(ctx, terminalID, &getLabel)
    52  	if err != nil {
    53  		if rollbackErr := tx.Rollback(); rollbackErr != nil {
    54  			return rollbackErr
    55  		}
    56  		return err
    57  	}
    58  	cluster, err := n.StoreClusterService.GetCluster(ctx, terminal.ClusterEdgeID)
    59  	if err != nil {
    60  		if rollbackErr := tx.Rollback(); rollbackErr != nil {
    61  			return rollbackErr
    62  		}
    63  		return err
    64  	}
    65  	clusterNetworkServices, err := n.StoreClusterService.GetClusterNetworkServices(ctx, cluster.ClusterEdgeID)
    66  	if err != nil {
    67  		if rollbackErr := tx.Rollback(); rollbackErr != nil {
    68  			return rollbackErr
    69  		}
    70  		return err
    71  	}
    72  	ienNodeCRBase64, err := n.TerminalService.CreateDSDSIENodeCR(terminal, clusterNetworkServices, customLabels, cluster.FleetVersion)
    73  	if err != nil {
    74  		if rollbackErr := tx.Rollback(); rollbackErr != nil {
    75  			return rollbackErr
    76  		}
    77  		return err
    78  	}
    79  
    80  	msg := client.NewChariotMessage().
    81  		SetBanner(cluster.ProjectID).
    82  		SetOperation(client.Create).
    83  		SetCluster(cluster.ClusterEdgeID).
    84  		SetOwner(ComponentOwner).
    85  		AddObject(ienNodeCRBase64)
    86  	if err := n.ChariotService.InvokeChariotPubsub(ctx, msg, make(map[string]string)); err != nil {
    87  		if rollbackErr := tx.Rollback(); rollbackErr != nil {
    88  			return rollbackErr
    89  		}
    90  		return err
    91  	}
    92  	return tx.Commit()
    93  }
    94  
    95  func (n *terminalLabelService) GetTerminalLabel(ctx context.Context, terminalID string) (*model.TerminalLabel, error) {
    96  	terminalLabel := &model.TerminalLabel{}
    97  	row := n.SQLDB.QueryRowContext(ctx, sqlquery.GetTerminalLabel, terminalID)
    98  	err := row.Scan(&terminalLabel.TerminalID, &terminalLabel.TerminalLabelEdgeID, &terminalLabel.LabelEdgeID)
    99  
   100  	if err != nil {
   101  		return nil, err
   102  	}
   103  	labelDetails, err := n.LabelService.GetLabel(ctx, terminalLabel.LabelEdgeID)
   104  
   105  	if err != nil {
   106  		// If label data not found, handle gracefully
   107  		terminalLabel.Label = nil
   108  	} else {
   109  		terminalLabel.Label = labelDetails
   110  	}
   111  
   112  	return terminalLabel, err
   113  }
   114  
   115  func (n *terminalLabelService) GetTerminalLabels(ctx context.Context, terminalLabelInput model.SearchTerminalLabelInput) ([]*model.TerminalLabel, error) {
   116  	terminalLabels := make([]*model.TerminalLabel, 0)
   117  	if terminalLabelInput.ClusterEdgeID != nil { // nolint:nestif
   118  		terminalrows, err := n.SQLDB.QueryContext(ctx, sqlquery.GetTerminalByClusterEdgeIDQuery, terminalLabelInput.ClusterEdgeID)
   119  		if err != nil {
   120  			return nil, err
   121  		}
   122  		terminals := []*model.Terminal{}
   123  		for terminalrows.Next() {
   124  			terminal := &model.Terminal{}
   125  			err := terminalrows.Scan(&terminal.TerminalID, &terminal.Lane, &terminal.Role, &terminal.ClusterEdgeID, &terminal.ClusterName, &terminal.Class, &terminal.DiscoverDisks, &terminal.BootDisk, &terminal.PrimaryInterface, &terminal.ExistingEfiPart, &terminal.SwapEnabled, &terminal.Hostname)
   126  			if err != nil {
   127  				return nil, err
   128  			}
   129  			terminals = append(terminals, terminal)
   130  		}
   131  		if err := terminalrows.Err(); err != nil {
   132  			return nil, sqlerr.Wrap(err)
   133  		}
   134  		for _, terminal := range terminals {
   135  			terminalID := terminal.TerminalID
   136  			if terminalLabelInput.TerminalID != nil {
   137  				terminalID = *terminalLabelInput.TerminalID
   138  			}
   139  			terminalLabelsrow, err := n.SQLDB.QueryContext(ctx, sqlquery.GetTerminalLabels, terminalID, terminalLabelInput.LabelEdgeID)
   140  			if err != nil {
   141  				return nil, err
   142  			}
   143  			for terminalLabelsrow.Next() {
   144  				terminalLabel := &model.TerminalLabel{Label: &model.Label{}}
   145  				err := terminalLabelsrow.Scan(&terminalLabel.TerminalID, &terminalLabel.TerminalLabelEdgeID, &terminalLabel.LabelEdgeID,
   146  					&terminalLabel.Label.Key, &terminalLabel.Label.Color, &terminalLabel.Label.Visible, &terminalLabel.Label.Editable,
   147  					&terminalLabel.Label.BannerEdgeID, &terminalLabel.Label.Unique, &terminalLabel.Label.Description, &terminalLabel.Label.Type)
   148  				if err != nil {
   149  					return nil, err
   150  				}
   151  				terminalLabels = append(terminalLabels, terminalLabel)
   152  			}
   153  			if err := terminalLabelsrow.Err(); err != nil {
   154  				return nil, sqlerr.Wrap(err)
   155  			}
   156  		}
   157  		return terminalLabels, nil
   158  	}
   159  	rows, err := n.SQLDB.QueryContext(ctx, sqlquery.GetTerminalLabels, terminalLabelInput.TerminalID, terminalLabelInput.LabelEdgeID)
   160  	if err != nil {
   161  		return terminalLabels, err
   162  	}
   163  	for rows.Next() {
   164  		terminalLabel := &model.TerminalLabel{Label: &model.Label{}}
   165  		//label_key, color, visible, editable, banner_edge_id, label_unique, description, label_type
   166  		err := rows.Scan(&terminalLabel.TerminalID, &terminalLabel.TerminalLabelEdgeID, &terminalLabel.LabelEdgeID,
   167  			&terminalLabel.Label.Key, &terminalLabel.Label.Color, &terminalLabel.Label.Visible, &terminalLabel.Label.Editable,
   168  			&terminalLabel.Label.BannerEdgeID, &terminalLabel.Label.Unique, &terminalLabel.Label.Description, &terminalLabel.Label.Type)
   169  		if err != nil {
   170  			return terminalLabels, err
   171  		}
   172  		terminalLabels = append(terminalLabels, terminalLabel)
   173  	}
   174  	if err := rows.Err(); err != nil {
   175  		return nil, sqlerr.Wrap(err)
   176  	}
   177  	return terminalLabels, nil
   178  }
   179  
   180  func (n *terminalLabelService) GetTerminalLabelsInfo(ctx context.Context, terminals []*model.Terminal) ([]*model.Terminal, error) {
   181  	for _, terminal := range terminals {
   182  		terminalLabels, err := n.GetTerminalLabels(ctx, model.SearchTerminalLabelInput{TerminalID: &terminal.TerminalID})
   183  		if err != nil {
   184  			return terminals, err
   185  		}
   186  		terminal.Labels = terminalLabels
   187  	}
   188  	return terminals, nil
   189  }
   190  
   191  func insertTerminalLabels(ctx context.Context, tx *sql.Tx, terminalID string, labelEdgeIDs ...string) (map[string]string, error) {
   192  	customLabels := make(map[string]string, 0)
   193  	rows, err := tx.QueryContext(ctx, sqlquery.GetTerminalLabels, terminalID, sql.NullString{})
   194  	// in a perfect world, sql error will be exported but no rows error is not exported so using contains.
   195  	if err != nil && !strings.Contains(err.Error(), "no rows in result set") {
   196  		return customLabels, err
   197  	}
   198  	for rows.Next() {
   199  		terminalLabel := &model.TerminalLabel{Label: &model.Label{}}
   200  		//label_key, color, visible, editable, banner_edge_id, label_unique, description, label_type
   201  		err := rows.Scan(&terminalLabel.TerminalID, &terminalLabel.TerminalLabelEdgeID, &terminalLabel.LabelEdgeID,
   202  			&terminalLabel.Label.Key, &terminalLabel.Label.Color, &terminalLabel.Label.Visible, &terminalLabel.Label.Editable,
   203  			&terminalLabel.Label.BannerEdgeID, &terminalLabel.Label.Unique, &terminalLabel.Label.Description, &terminalLabel.Label.Type)
   204  		if err != nil {
   205  			return customLabels, err
   206  		}
   207  		// for the terminal label description, we hash and then limit the number of chars to 20 chars.
   208  		labelDescription := uuid.FromUUID(terminalLabel.Label.Description).Hash()
   209  		labelKey := utils.ToK8sName(terminalLabel.Label.Key)
   210  		if err = utils.ValuesValidation([]string{labelDescription, labelKey}); err != nil {
   211  			return customLabels, err
   212  		}
   213  
   214  		// avoid prefixing labels automatically added by Edge
   215  		key := formatCustomLabelKey(terminalLabel.Label)
   216  		customLabels[key] = labelDescription
   217  		if terminalLabel.Label.Type != "" && !slices.Contains(capabilities.EdgeAutomatedCapabilityLabelTypes, terminalLabel.Label.Type) {
   218  			customLabels[fmt.Sprintf(v1ien.CustomNodeLabel, terminalLabel.Label.Type)] = labelKey
   219  		}
   220  	}
   221  	if err := rows.Err(); err != nil {
   222  		return nil, sqlerr.Wrap(err)
   223  	}
   224  	for _, labelEdgeID := range labelEdgeIDs {
   225  		_, err := tx.ExecContext(ctx, sqlquery.InsertTerminalLabel, terminalID, labelEdgeID)
   226  		if err != nil {
   227  			return customLabels, err
   228  		}
   229  		label := &model.Label{}
   230  		row := tx.QueryRowContext(ctx, sqlquery.GetLabelQuery, labelEdgeID)
   231  		if err := row.Scan(&label.LabelEdgeID, &label.Key, &label.Color, &label.Visible, &label.Editable, &label.BannerEdgeID, &label.Unique, &label.Description, &label.Type); err != nil {
   232  			return customLabels, err
   233  		}
   234  		// for the terminal label description, we hash and then limit the number of chars to 20 chars.
   235  		labelDescription := uuid.FromUUID(label.Description).Hash()
   236  		labelKey := utils.ToK8sName(label.Key)
   237  		if err = utils.ValuesValidation([]string{labelDescription, labelKey}); err != nil {
   238  			return customLabels, err
   239  		}
   240  		key := formatCustomLabelKey(label)
   241  		customLabels[key] = labelDescription
   242  		if label.Type != "" && !slices.Contains(capabilities.EdgeAutomatedCapabilityLabelTypes, label.Type) {
   243  			customLabels[fmt.Sprintf(v1ien.CustomNodeLabel, label.Type)] = labelKey
   244  		}
   245  	}
   246  	return customLabels, nil
   247  }
   248  
   249  func (n *terminalLabelService) DeleteTerminalLabels(ctx context.Context, terminalLabelInput model.SearchTerminalLabelInput) ([]*model.TerminalLabel, error) {
   250  	terminaLabels, err := n.GetTerminalLabels(ctx, terminalLabelInput)
   251  	if err != nil {
   252  		return nil, err
   253  	}
   254  	for _, terminaLabel := range terminaLabels {
   255  		_, err = n.SQLDB.ExecContext(ctx, sqlquery.DeleteTerminalLabel, terminaLabel.TerminalLabelEdgeID)
   256  		if err != nil {
   257  			return nil, err
   258  		}
   259  	}
   260  	return terminaLabels, nil
   261  }
   262  
   263  func (n *terminalLabelService) SendUpdatedIENCRAfterDeletion(ctx context.Context, affectedTerminalLabels []*model.TerminalLabel) error {
   264  	//First set an empty map for all effected terminals so we will build an update ien cr for them
   265  	updatedTerminals := make(map[string]map[string]string, 1)
   266  	for _, terminalLabel := range affectedTerminalLabels {
   267  		updatedTerminals[terminalLabel.TerminalID] = make(map[string]string)
   268  	}
   269  	getLabels := true
   270  	for terminalID, customLabels := range updatedTerminals {
   271  		terminal, err := n.TerminalService.GetTerminal(ctx, terminalID, &getLabels)
   272  		if err != nil {
   273  			return err
   274  		}
   275  		//add remaining labels to the custom labels to be on update ien node cr
   276  		for _, terminalLabel := range terminal.Labels {
   277  			key := formatCustomLabelKey(terminalLabel.Label)
   278  			customLabels[key] = uuid.FromUUID(terminalLabel.Label.Description).Hash()
   279  			if terminalLabel.Label.Type != "" && !slices.Contains(capabilities.EdgeAutomatedCapabilityLabelTypes, terminalLabel.Label.Type) {
   280  				customLabels[fmt.Sprintf(v1ien.CustomNodeLabel, terminalLabel.Label.Key)] = utils.ToK8sName(terminalLabel.Label.Description)
   281  			}
   282  		}
   283  		cluster, err := n.StoreClusterService.GetCluster(ctx, terminal.ClusterEdgeID)
   284  		if err != nil {
   285  			return err
   286  		}
   287  		clusterNetworkServices, err := n.StoreClusterService.GetClusterNetworkServices(ctx, cluster.ClusterEdgeID)
   288  		if err != nil {
   289  			return err
   290  		}
   291  		ienNodeCRBase64, err := n.TerminalService.CreateDSDSIENodeCR(terminal, clusterNetworkServices, customLabels, cluster.FleetVersion)
   292  		if err != nil {
   293  			return err
   294  		}
   295  		msg := client.NewChariotMessage().
   296  			SetBanner(cluster.ProjectID).
   297  			SetOperation(client.Create).
   298  			SetCluster(cluster.ClusterEdgeID).
   299  			SetOwner(ComponentOwner).
   300  			AddObject(ienNodeCRBase64)
   301  		if err := n.ChariotService.InvokeChariotPubsub(ctx, msg, make(map[string]string)); err != nil {
   302  			return err
   303  		}
   304  	}
   305  	return nil
   306  }
   307  
   308  func NewTerminalLabelService(sqlDB *sql.DB, chariotSvc ChariotService, terminalSvc TerminalService, storeClusterSvc StoreClusterService, labelSvc LabelService) TerminalLabelService { //nolint
   309  	return &terminalLabelService{
   310  		SQLDB:               sqlDB,
   311  		ChariotService:      chariotSvc,
   312  		TerminalService:     terminalSvc,
   313  		StoreClusterService: storeClusterSvc,
   314  		LabelService:        labelSvc,
   315  	}
   316  }
   317  
   318  // formatCustomLabel returns a formatted key with custom label prefix
   319  func formatCustomLabelKey(label *model.Label) string {
   320  	if !slices.Contains(capabilities.EdgeAutomatedCapabilityLabelTypes, label.Type) {
   321  		return fmt.Sprintf(v1ien.CustomNodeLabel, label.Key)
   322  	}
   323  	return label.Key
   324  }
   325  

View as plain text