github.com/voedger/voedger@v0.0.0-20240520144910-273e84102129/pkg/cluster/impl_deployapp.go (about) 1 /* 2 * Copyright (c) 2024-present unTill Software Development Group B.V. 3 * @author Denis Gribanov 4 */ 5 6 package cluster 7 8 import ( 9 "fmt" 10 "net/http" 11 12 "github.com/voedger/voedger/pkg/appdef" 13 "github.com/voedger/voedger/pkg/goutils/logger" 14 "github.com/voedger/voedger/pkg/istructs" 15 "github.com/voedger/voedger/pkg/istructsmem" 16 "github.com/voedger/voedger/pkg/state" 17 "github.com/voedger/voedger/pkg/sys/authnz" 18 "github.com/voedger/voedger/pkg/sys/uniques" 19 "github.com/voedger/voedger/pkg/sys/workspace" 20 coreutils "github.com/voedger/voedger/pkg/utils" 21 ) 22 23 // wrong to use IAppPartitions to get total NumAppPartition because the app the cmd is called for is not deployed yet 24 func provideExecDeployApp(asp istructs.IAppStructsProvider, timeFunc coreutils.TimeFunc) istructsmem.ExecCommandClosure { 25 return func(args istructs.ExecCommandArgs) (err error) { 26 appQNameStr := args.ArgumentObject.AsString(Field_AppQName) 27 appQName, err := istructs.ParseAppQName(appQNameStr) 28 if err != nil { 29 return coreutils.NewHTTPErrorf(http.StatusBadRequest, fmt.Sprintf("failed to parse AppQName %s: %s", appQNameStr, err.Error())) 30 } 31 32 if appQName == istructs.AppQName_sys_cluster { 33 return coreutils.NewHTTPErrorf(http.StatusBadRequest, fmt.Sprintf("%s app can not be deployed by c.cluster.DeployApp", istructs.AppQName_sys_cluster)) 34 } 35 36 clusterAppStructs, err := asp.AppStructs(istructs.AppQName_sys_cluster) 37 if err != nil { 38 // notest 39 return err 40 } 41 wdocAppRecordID, err := uniques.GetRecordIDByUniqueCombination(args.WSID, qNameWDocApp, clusterAppStructs, map[string]interface{}{ 42 Field_AppQName: appQNameStr, 43 }) 44 if err != nil { 45 // notest 46 return err 47 } 48 numAppWorkspacesToDeploy := istructs.NumAppWorkspaces(args.ArgumentObject.AsInt32(Field_NumAppWorkspaces)) 49 numAppPartitionsToDeploy := istructs.NumAppPartitions(args.ArgumentObject.AsInt32(Field_NumPartitions)) 50 if wdocAppRecordID != istructs.NullRecordID { 51 kb, err := args.State.KeyBuilder(state.Record, qNameWDocApp) 52 if err != nil { 53 // notest 54 return err 55 } 56 kb.PutRecordID(state.Field_ID, wdocAppRecordID) 57 appRec, err := args.State.MustExist(kb) 58 if err != nil { 59 // notest 60 return err 61 } 62 numPartitionsDeployed := istructs.NumAppPartitions(appRec.AsInt32(Field_NumPartitions)) 63 numAppWorkspacesDeployed := istructs.NumAppWorkspaces(appRec.AsInt32(Field_NumAppWorkspaces)) 64 65 // Check application compatibility (409) 66 if numPartitionsDeployed != numAppPartitionsToDeploy { 67 return coreutils.NewHTTPErrorf(http.StatusConflict, fmt.Sprintf("%s: app %s declaring NumPartitions=%d but was previously deployed with NumPartitions=%d", ErrNumPartitionsChanged.Error(), 68 appQName, numAppPartitionsToDeploy, numPartitionsDeployed)) 69 } 70 if numAppWorkspacesDeployed != numAppWorkspacesToDeploy { 71 return coreutils.NewHTTPErrorf(http.StatusConflict, fmt.Sprintf("%s: app %s declaring NumAppWorkspaces=%d but was previously deployed with NumAppWorksaces=%d", ErrNumAppWorkspacesChanged.Error(), 72 appQName, numAppWorkspacesToDeploy, numAppWorkspacesDeployed)) 73 } 74 75 // idempotency: was deployed already and nothing changed -> do not initiaize app workspaces 76 return nil 77 } 78 79 kb, err := args.State.KeyBuilder(state.Record, qNameWDocApp) 80 if err != nil { 81 // notest 82 return err 83 } 84 vb, err := args.Intents.NewValue(kb) 85 if err != nil { 86 // notest 87 return err 88 } 89 90 vb.PutRecordID(appdef.SystemField_ID, 1) 91 vb.PutString(Field_AppQName, appQNameStr) 92 vb.PutInt32(Field_NumAppWorkspaces, int32(numAppWorkspacesToDeploy)) 93 vb.PutInt32(Field_NumPartitions, int32(numAppPartitionsToDeploy)) 94 95 // Create storage if not exists 96 // Initialize appstructs data 97 // note: for builtin apps that does nothing because IAppStructs is already initialized (including storage initialization) on VVM wiring 98 // note: it is good that it is done here, not before return if nothing changed because we're want to initialize (i.e. create) keyspace here - that must be done once 99 as, err := asp.AppStructs(appQName) 100 if err != nil { 101 // notest 102 return fmt.Errorf("failed to get IAppStructs for %s", appQName) 103 } 104 105 // Initialize app workspaces 106 if _, err = InitAppWSes(as, numAppWorkspacesToDeploy, numAppPartitionsToDeploy, istructs.UnixMilli(timeFunc().UnixMilli())); err != nil { 107 // notest 108 return fmt.Errorf("failed to deploy %s: %w", appQName, err) 109 } 110 logger.Info(fmt.Sprintf("app %s successfully deployed: NumPartitions=%d, NumAppWorkspaces=%d", appQName, numAppPartitionsToDeploy, numAppWorkspacesToDeploy)) 111 return nil 112 } 113 } 114 115 // returns an array of inited AppWSIDs. Inited already -> AppWSID is not in the array. Need for testing only 116 func InitAppWSes(as istructs.IAppStructs, numAppWorkspaces istructs.NumAppWorkspaces, numAppPartitions istructs.NumAppPartitions, currentMillis istructs.UnixMilli) ([]istructs.WSID, error) { 117 pLogOffsets := map[istructs.PartitionID]istructs.Offset{} 118 wLogOffset := istructs.FirstOffset 119 res := []istructs.WSID{} 120 for wsNum := 0; istructs.NumAppWorkspaces(wsNum) < numAppWorkspaces; wsNum++ { 121 appWSID := istructs.NewWSID(istructs.MainClusterID, istructs.WSID(wsNum+int(istructs.FirstBaseAppWSID))) 122 partitionID := coreutils.AppPartitionID(appWSID, numAppPartitions) 123 if _, ok := pLogOffsets[partitionID]; !ok { 124 pLogOffsets[partitionID] = istructs.FirstOffset 125 } 126 inited, err := InitAppWS(as, partitionID, appWSID, pLogOffsets[partitionID], wLogOffset, currentMillis) 127 if err != nil { 128 // notest 129 return nil, err 130 } 131 pLogOffsets[partitionID]++ 132 wLogOffset++ 133 if inited { 134 res = append(res, appWSID) 135 } 136 } 137 return res, nil 138 } 139 140 func InitAppWS(as istructs.IAppStructs, partitionID istructs.PartitionID, appWSID istructs.WSID, plogOffset, wlogOffset istructs.Offset, currentMillis istructs.UnixMilli) (inited bool, err error) { 141 existingCDocWSDesc, err := as.Records().GetSingleton(appWSID, authnz.QNameCDocWorkspaceDescriptor) 142 if err != nil { 143 // notest 144 return false, err 145 } 146 if existingCDocWSDesc.QName() != appdef.NullQName { 147 logger.Verbose("app workspace", as.AppQName(), appWSID-appWSID.BaseWSID(), "(", appWSID, ") inited already") 148 return false, nil 149 } 150 151 grebp := istructs.GenericRawEventBuilderParams{ 152 HandlingPartition: partitionID, 153 Workspace: appWSID, 154 QName: istructs.QNameCommandCUD, 155 RegisteredAt: currentMillis, 156 PLogOffset: plogOffset, 157 WLogOffset: wlogOffset, 158 } 159 reb := as.Events().GetSyncRawEventBuilder( 160 istructs.SyncRawEventBuilderParams{ 161 GenericRawEventBuilderParams: grebp, 162 SyncedAt: currentMillis, 163 }, 164 ) 165 cdocWSDesc := reb.CUDBuilder().Create(authnz.QNameCDocWorkspaceDescriptor) 166 cdocWSDesc.PutRecordID(appdef.SystemField_ID, 1) 167 cdocWSDesc.PutString(authnz.Field_WSName, "appWS0") 168 cdocWSDesc.PutQName(authnz.Field_WSKind, authnz.QNameCDoc_WorkspaceKind_AppWorkspace) 169 cdocWSDesc.PutInt64(authnz.Field_CreatedAtMs, int64(currentMillis)) 170 cdocWSDesc.PutInt64(workspace.Field_InitCompletedAtMs, int64(currentMillis)) 171 rawEvent, err := reb.BuildRawEvent() 172 if err != nil { 173 // notest 174 return false, err 175 } 176 // ok to local IDGenerator here. Actual next record IDs will be determined on the partition recovery stage 177 pLogEvent, err := as.Events().PutPlog(rawEvent, nil, istructsmem.NewIDGenerator()) 178 if err != nil { 179 // notest 180 return false, err 181 } 182 defer pLogEvent.Release() 183 if err := as.Records().Apply(pLogEvent); err != nil { 184 // notest 185 return false, err 186 } 187 if err = as.Events().PutWlog(pLogEvent); err != nil { 188 // notest 189 return false, err 190 } 191 logger.Verbose("app workspace", as.AppQName(), appWSID.BaseWSID()-istructs.FirstBaseAppWSID, "(", appWSID, ") initialized") 192 return true, nil 193 }