github.com/Axway/agent-sdk@v1.1.101/pkg/agent/resource/manager.go (about)

     1  package resource
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"strconv"
     7  	"strings"
     8  	"time"
     9  
    10  	"github.com/Axway/agent-sdk/pkg/apic"
    11  	apiv1 "github.com/Axway/agent-sdk/pkg/apic/apiserver/models/api/v1"
    12  	v1 "github.com/Axway/agent-sdk/pkg/apic/apiserver/models/api/v1"
    13  	management "github.com/Axway/agent-sdk/pkg/apic/apiserver/models/management/v1alpha1"
    14  	"github.com/Axway/agent-sdk/pkg/apic/definitions"
    15  	"github.com/Axway/agent-sdk/pkg/config"
    16  	"github.com/Axway/agent-sdk/pkg/util"
    17  	"github.com/Axway/agent-sdk/pkg/util/errors"
    18  	"github.com/Axway/agent-sdk/pkg/util/log"
    19  )
    20  
    21  // QA EnvVars
    22  const qaTriggerSevenDayRefreshCache = "QA_CENTRAL_TRIGGER_REFRESH_CACHE"
    23  
    24  type EventSyncCache interface {
    25  	RebuildCache()
    26  }
    27  
    28  // Manager - interface to manage agent resource
    29  type Manager interface {
    30  	OnConfigChange(cfg config.CentralConfig, apicClient apic.Client)
    31  	GetAgentResource() *apiv1.ResourceInstance
    32  	SetAgentResource(agentResource *apiv1.ResourceInstance)
    33  	FetchAgentResource() error
    34  	UpdateAgentStatus(status, prevStatus, message string) error
    35  	AddUpdateAgentDetails(key, value string)
    36  	SetRebuildCacheFunc(rebuildCache EventSyncCache)
    37  }
    38  
    39  type executeAPIClient interface {
    40  	CreateSubResource(rm v1.ResourceMeta, subs map[string]interface{}) error
    41  	GetResource(url string) (*v1.ResourceInstance, error)
    42  	CreateResourceInstance(ri apiv1.Interface) (*apiv1.ResourceInstance, error)
    43  	UpdateResourceInstance(ri apiv1.Interface) (*apiv1.ResourceInstance, error)
    44  }
    45  
    46  type agentResourceManager struct {
    47  	agentResource              *apiv1.ResourceInstance
    48  	prevAgentResHash           uint64
    49  	apicClient                 executeAPIClient
    50  	cfg                        config.CentralConfig
    51  	agentResourceChangeHandler func()
    52  	agentDetails               map[string]interface{}
    53  	logger                     log.FieldLogger
    54  	rebuildCache               EventSyncCache
    55  }
    56  
    57  // NewAgentResourceManager - Create a new agent resource manager
    58  func NewAgentResourceManager(cfg config.CentralConfig, apicClient executeAPIClient, agentResourceChangeHandler func()) (Manager, error) {
    59  
    60  	logger := log.NewFieldLogger().
    61  		WithPackage("sdk.agent").
    62  		WithComponent("agentResourceManager")
    63  	m := &agentResourceManager{
    64  		cfg:                        cfg,
    65  		apicClient:                 apicClient,
    66  		agentResourceChangeHandler: agentResourceChangeHandler,
    67  		agentDetails:               make(map[string]interface{}),
    68  		logger:                     logger,
    69  	}
    70  
    71  	if m.getAgentResourceType() != nil {
    72  		err := m.FetchAgentResource()
    73  		if err != nil {
    74  			return nil, err
    75  		}
    76  	} else if m.cfg.GetAgentName() != "" {
    77  		return nil, errors.Wrap(apic.ErrCentralConfig, "Agent name cannot be set. Config is used only for agents with API server resource definition")
    78  	}
    79  	return m, nil
    80  }
    81  
    82  // OnConfigChange - Applies central config change to the manager
    83  func (a *agentResourceManager) OnConfigChange(cfg config.CentralConfig, apicClient apic.Client) {
    84  	a.apicClient = apicClient
    85  	a.cfg = cfg
    86  }
    87  
    88  // GetAgentResource - Returns the agent resource
    89  func (a *agentResourceManager) GetAgentResource() *apiv1.ResourceInstance {
    90  	return a.agentResource
    91  }
    92  
    93  // SetAgentResource - Sets the agent resource which triggers agent resource change handler
    94  func (a *agentResourceManager) SetAgentResource(agentResource *apiv1.ResourceInstance) {
    95  	if agentResource != nil && agentResource.Name == a.cfg.GetAgentName() {
    96  		a.agentResource = agentResource
    97  		a.onResourceChange()
    98  	}
    99  }
   100  
   101  func (a *agentResourceManager) SetRebuildCacheFunc(rebuildCache EventSyncCache) {
   102  	a.rebuildCache = rebuildCache
   103  }
   104  
   105  // FetchAgentResource - Gets the agent resource using API call to apiserver
   106  func (a *agentResourceManager) FetchAgentResource() error {
   107  	if a.cfg.GetAgentName() == "" {
   108  		return nil
   109  	}
   110  
   111  	var err error
   112  	a.agentResource, err = a.getAgentResource()
   113  	if err != nil {
   114  		if strings.Contains(err.Error(), "404") {
   115  			a.agentResource, err = a.createAgentResource()
   116  			if err != nil {
   117  				return err
   118  			}
   119  		} else {
   120  			return err
   121  		}
   122  	} else {
   123  		a.checkAgentResource()
   124  	}
   125  
   126  	a.onResourceChange()
   127  	return nil
   128  }
   129  
   130  // UpdateAgentStatus - Updates the agent status in agent resource
   131  func (a *agentResourceManager) UpdateAgentStatus(status, prevStatus, message string) error {
   132  	if a.cfg == nil || a.cfg.GetAgentName() == "" {
   133  		return nil
   134  	}
   135  
   136  	if a.agentResource == nil {
   137  		return nil
   138  	}
   139  
   140  	agentInstance := a.getAgentResourceType()
   141  
   142  	statusSubResourceName := management.DiscoveryAgentStatusSubResourceName
   143  	// using discovery agent status here, but all agent status resources have the same structure
   144  	agentInstance.SubResources[statusSubResourceName] = management.DiscoveryAgentStatus{
   145  		Version:                config.AgentVersion,
   146  		LatestAvailableVersion: config.AgentLatestVersion,
   147  		State:                  status,
   148  		PreviousState:          prevStatus,
   149  		Message:                message,
   150  		LastActivityTime:       getTimestamp(),
   151  		SdkVersion:             config.SDKVersion,
   152  	}
   153  
   154  	// See if we need to rebuildCache
   155  	timeToRebuild, _ := a.shouldRebuildCache()
   156  	if timeToRebuild && a.rebuildCache != nil {
   157  		a.rebuildCache.RebuildCache()
   158  	}
   159  
   160  	subResources := make(map[string]interface{})
   161  	subResources[statusSubResourceName] = agentInstance.SubResources[statusSubResourceName]
   162  	// add any details
   163  	if len(a.agentDetails) > 0 {
   164  		util.SetAgentDetails(agentInstance, a.agentDetails)
   165  		subResources[definitions.XAgentDetails] = agentInstance.SubResources[definitions.XAgentDetails]
   166  	}
   167  
   168  	err := a.apicClient.CreateSubResource(agentInstance.ResourceMeta, subResources)
   169  	return err
   170  }
   171  
   172  // 1. On UpdateAgentStatus, if x-agent-details, key "cacheUpdateTime" doesn't exist or empty, rebuild cache to populate cacheUpdateTime
   173  // 2. On UpdateAgentStatus, if x-agent-details exists, check to see if its past 7 days since rebuildCache was ran.  If its pass 7 days, rebuildCache
   174  func (a *agentResourceManager) shouldRebuildCache() (bool, error) {
   175  	rebuildCache := false
   176  	agentInstance := a.GetAgentResource()
   177  	agentDetails := agentInstance.GetSubResource(definitions.XAgentDetails)
   178  
   179  	if agentDetails == nil {
   180  		// x-agent-details hasn't been established yet. Rebuild cache to populate cacheUpdateTime
   181  		a.logger.Trace("create x-agent-detail subresource and add key 'cacheUpdateTime'")
   182  		rebuildCache = true
   183  	} else {
   184  		value, exists := agentDetails.(map[string]interface{})["cacheUpdateTime"]
   185  		if value != nil {
   186  			// get current cacheUpdateTime from x-agent-details
   187  			convToTimestamp, err := strconv.ParseInt(value.(string), 10, 64)
   188  			if err != nil {
   189  				return false, err
   190  			}
   191  			currentCacheUpdateTime := time.Unix(0, convToTimestamp)
   192  			a.logger.Tracef("the current scheduled refresh cache date - %s", time.Unix(0, currentCacheUpdateTime.UnixNano()).Format("2006-01-02 15:04:05.000000"))
   193  
   194  			// check to see if 7 days have passed since last refresh cache. currentCacheUpdateTime is the date at the time we rebuilt cache plus 7 days(in event sync - RebuildCache)
   195  			if a.getCurrentTime() > currentCacheUpdateTime.UnixNano() {
   196  				a.logger.Trace("the current date is greater than the current scheduled refresh date - time to rebuild cache")
   197  				rebuildCache = true
   198  			}
   199  		} else {
   200  			if !exists {
   201  				// x-agent-details exists, however, cacheUpdateTime key doesn't exist. Rebuild cache to populate cacheUpdateTime
   202  				a.logger.Trace("update x-agent-detail subresource and add key 'cacheUpdateTime'")
   203  				rebuildCache = true
   204  			}
   205  		}
   206  	}
   207  
   208  	return rebuildCache, nil
   209  }
   210  
   211  func (a *agentResourceManager) getCurrentTime() int64 {
   212  	val := os.Getenv(qaTriggerSevenDayRefreshCache)
   213  	if val == "" {
   214  		// if this isn't set, then just pass back the current time
   215  		return time.Now().UnixNano()
   216  	}
   217  	// if this is set, then pass back the current time, plus 7 days to trigger a rebuild
   218  	return time.Now().Add(7 * 24 * time.Hour).UnixNano()
   219  }
   220  
   221  // GetAgentDetails - Gets current agent details
   222  func (a *agentResourceManager) GetAgentDetails() map[string]interface{} {
   223  	return a.agentDetails
   224  }
   225  
   226  // AddUpdateAgentDetails - Adds a new or Updates an existing key on the agent details sub resource
   227  func (a *agentResourceManager) AddUpdateAgentDetails(key, value string) {
   228  	a.agentDetails[key] = value
   229  }
   230  
   231  // getTimestamp - Returns current timestamp formatted for API Server
   232  func getTimestamp() apiv1.Time {
   233  	activityTime := time.Now()
   234  	newV1Time := apiv1.Time(activityTime)
   235  	return newV1Time
   236  }
   237  
   238  func applyResConfigToCentralConfig(cfg *config.CentralConfiguration, resCfgAdditionalTags, resCfgTeamID, resCfgLogLevel string) {
   239  	agentResLogLevel := log.GlobalLoggerConfig.GetLevel()
   240  	if resCfgLogLevel != "" && !strings.EqualFold(agentResLogLevel, resCfgLogLevel) {
   241  		log.GlobalLoggerConfig.Level(resCfgLogLevel).Apply()
   242  	}
   243  
   244  	if resCfgAdditionalTags != "" && !strings.EqualFold(cfg.TagsToPublish, resCfgAdditionalTags) {
   245  		cfg.TagsToPublish = resCfgAdditionalTags
   246  	}
   247  
   248  	// If config team is blank, check resource team name.  If resource team name is not blank, use resource team name
   249  	if resCfgTeamID != "" && cfg.TeamName == "" {
   250  		cfg.SetTeamID(resCfgTeamID)
   251  	}
   252  }
   253  
   254  func (a *agentResourceManager) onResourceChange() {
   255  	if a.agentResource != nil {
   256  		subRes := a.agentResource.GetSubResource(definitions.XAgentDetails)
   257  		if details, ok := subRes.(map[string]interface{}); ok {
   258  			a.agentDetails = details
   259  		}
   260  	}
   261  
   262  	isChanged := (a.prevAgentResHash != 0)
   263  	agentResHash, _ := util.ComputeHash(a.agentResource)
   264  	if a.prevAgentResHash != 0 && a.prevAgentResHash == agentResHash {
   265  		isChanged = false
   266  	}
   267  	a.prevAgentResHash = agentResHash
   268  	if isChanged {
   269  		// merge agent resource config with central config
   270  		a.mergeResourceWithConfig()
   271  		if a.agentResourceChangeHandler != nil {
   272  			a.agentResourceChangeHandler()
   273  		}
   274  	}
   275  }
   276  
   277  func (a *agentResourceManager) getAgentResourceType() *v1.ResourceInstance {
   278  	var agentRes v1.Interface
   279  	switch a.cfg.GetAgentType() {
   280  	case config.DiscoveryAgent:
   281  		res := management.NewDiscoveryAgent(a.cfg.GetAgentName(), a.cfg.GetEnvironmentName())
   282  		res.Spec.DataplaneType = config.AgentDataPlaneType
   283  		agentRes = res
   284  	case config.TraceabilityAgent:
   285  		res := management.NewTraceabilityAgent(a.cfg.GetAgentName(), a.cfg.GetEnvironmentName())
   286  		res.Spec.DataplaneType = config.AgentDataPlaneType
   287  		agentRes = res
   288  	}
   289  	var agentInstance *v1.ResourceInstance
   290  	if agentRes != nil {
   291  		agentInstance, _ = agentRes.AsInstance()
   292  	}
   293  	return agentInstance
   294  }
   295  
   296  // GetAgentResource - returns the agent resource
   297  func (a *agentResourceManager) createAgentResource() (*v1.ResourceInstance, error) {
   298  	agentRes := a.getAgentResourceType()
   299  	if agentRes == nil {
   300  		return nil, fmt.Errorf("unknown agent type")
   301  	}
   302  	a.logger.
   303  		WithField("scope", agentRes.Metadata.Scope).
   304  		WithField("kind", agentRes.Kind).
   305  		WithField("name", agentRes.Name).
   306  		Info("creating agent resource")
   307  	return a.apicClient.CreateResourceInstance(agentRes)
   308  }
   309  
   310  // GetAgentResource - returns the agent resource
   311  func (a *agentResourceManager) checkAgentResource() (*v1.ResourceInstance, error) {
   312  	var agentRes v1.Interface
   313  	logger := a.logger.WithField("scope", a.agentResource.Metadata.Scope).WithField("kind", a.agentResource.Kind).WithField("name", a.agentResource.Name)
   314  
   315  	currDataplaneType := apic.Unidentified.String()
   316  	if a.agentResource.Kind == management.DiscoveryAgentGVK().Kind {
   317  		da := management.NewDiscoveryAgent("", "")
   318  		da.FromInstance(a.agentResource)
   319  		currDataplaneType = da.Spec.DataplaneType
   320  		da.Spec.DataplaneType = config.AgentDataPlaneType
   321  		agentRes = da
   322  	} else if a.agentResource.Kind == management.TraceabilityAgentGVK().Kind {
   323  		ta := management.NewTraceabilityAgent("", "")
   324  		ta.FromInstance(a.agentResource)
   325  		currDataplaneType = ta.Spec.DataplaneType
   326  		ta.Spec.DataplaneType = config.AgentDataPlaneType
   327  		agentRes = ta
   328  	}
   329  
   330  	// nothing to update
   331  	if currDataplaneType == config.AgentDataPlaneType {
   332  		return a.agentResource, nil
   333  	}
   334  
   335  	logger.Info("updating agent resource")
   336  	return a.apicClient.UpdateResourceInstance(agentRes)
   337  }
   338  
   339  // GetAgentResource - returns the agent resource
   340  func (a *agentResourceManager) getAgentResource() (*v1.ResourceInstance, error) {
   341  	agentRes := a.getAgentResourceType()
   342  	if agentRes == nil {
   343  		return nil, fmt.Errorf("unknown agent type")
   344  	}
   345  
   346  	return a.apicClient.GetResource(agentRes.GetSelfLink())
   347  }
   348  
   349  func (a *agentResourceManager) mergeResourceWithConfig() {
   350  	// IMP - To be removed once the model is in production
   351  	if a.cfg.GetAgentName() == "" {
   352  		return
   353  	}
   354  
   355  	switch a.getAgentResourceType().Kind {
   356  	case management.DiscoveryAgentGVK().Kind:
   357  		mergeDiscoveryAgentWithConfig(a.GetAgentResource(), a.cfg.(*config.CentralConfiguration))
   358  	case management.TraceabilityAgentGVK().Kind:
   359  		mergeTraceabilityAgentWithConfig(a.GetAgentResource(), a.cfg.(*config.CentralConfiguration))
   360  	default:
   361  		panic(ErrUnsupportedAgentType)
   362  	}
   363  }