Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions pkg/app/api/grpcapi/piped_api.go
Original file line number Diff line number Diff line change
Expand Up @@ -1105,6 +1105,56 @@ func (a *PipedAPI) CreateDeploymentChain(ctx context.Context, req *pipedservice.
return &pipedservice.CreateDeploymentChainResponse{}, nil
}

// InChainDeploymentPlannable hecks the completion and status of the previous block in the deployment chain.
// An in chain deployment is treated as plannable in case:
// - It's the first deployment of its deployment chain.
// - All deployments of its previous block in chain are at DEPLOYMENT_SUCCESS state.
func (a *PipedAPI) InChainDeploymentPlannable(ctx context.Context, req *pipedservice.InChainDeploymentPlannableRequest) (*pipedservice.InChainDeploymentPlannableResponse, error) {
_, pipedID, _, err := rpcauth.ExtractPipedToken(ctx)
if err != nil {
return nil, err
}
if err := a.validateDeploymentBelongsToPiped(ctx, req.Deployment.Id, pipedID); err != nil {
return nil, err
}

dc, err := a.deploymentChainStore.GetDeploymentChain(ctx, req.Deployment.DeploymentChainId)
if err != nil {
return nil, status.Error(codes.InvalidArgument, "unable to find the deployment chain which this deployment belongs to")
}

// Deployment of blocks[0] in the chain means it's the first deployment of the chain;
// hence it should be processed without any lock.
if req.Deployment.DeploymentChainBlockIndex == 0 {
return &pipedservice.InChainDeploymentPlannableResponse{
Plannable: true,
}, nil
}

if req.Deployment.DeploymentChainBlockIndex >= int32(len(dc.Blocks)) {
return nil, status.Error(codes.InvalidArgument, "invalid deployment with chain block index provided")
}

prevBlock := dc.Blocks[req.Deployment.DeploymentChainBlockIndex-1]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since DeploymentChainBlockIndex is int32 which is signed integer whose range is -2147483648 to 2147483647, this is a bit likely to be panic.

How about making DeploymentChainBlockIndex an unsigned integer, or adding a simple validation?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice catch, golang does not support negative indexes for slide, right? In that case, lets me address it in a separated PR since other place that not related to this change need to be updated too 👍

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fine to me 👍

plannable := true
for _, node := range prevBlock.Nodes {
// TODO: Consider add deployment status to the deployment ref in the deployment chain model
// instead of fetching deployment model here.
dp, err := a.deploymentStore.GetDeployment(ctx, node.DeploymentRef.DeploymentId)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we need the deployment status in the chain model for rendering on the web so how about having that status field in the node. Then we can use it directly instead of retrieving from DB like this.

Copy link
Member Author

@khanhtc1202 khanhtc1202 Nov 24, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In that case, when piped submits deployment status (planed, running, etc) we also need to update its status in deployment chain model too, that's quite annoying. How do you think about adding just IsCompleted to the deploymentRef in the chain model?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the second thought, status is required for showing in the UI, so I think I will go with your suggestion 👍

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nghialv Can I add a TODO note here and address it later since that change requires updates in other RPCs unrelated to this PR.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed by e883d32

if err != nil {
return nil, status.Error(codes.Internal, "unable to process previous block nodes in deployment chain")
}
if model.IsCompletedSuccessfullyDeployment(dp.Status) {
plannable = false
break
}
}

return &pipedservice.InChainDeploymentPlannableResponse{
Plannable: plannable,
}, nil
}

// validateAppBelongsToPiped checks if the given application belongs to the given piped.
// It gives back an error unless the application belongs to the piped.
func (a *PipedAPI) validateAppBelongsToPiped(ctx context.Context, appID, pipedID string) error {
Expand Down
12 changes: 12 additions & 0 deletions pkg/app/api/service/pipedservice/service.proto
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,10 @@ service PipedService {
// CreateDeploymentChain creates a new deployment chain object and all required commands to
// trigger deployment for applications in the chain.
rpc CreateDeploymentChain(CreateDeploymentChainRequest) returns (CreateDeploymentChainResponse) {}
// DeploymentPlannable checks the completion and status of the previous block in the deployment chain,
// only when all the nodes of the previous block are completed with a success status,
// the nodes of the next block will be treat as processable.
rpc InChainDeploymentPlannable(InChainDeploymentPlannableRequest) returns (InChainDeploymentPlannableResponse) {}
}

enum ListOrder {
Expand Down Expand Up @@ -495,3 +499,11 @@ message CreateDeploymentChainRequest {

message CreateDeploymentChainResponse {
}

message InChainDeploymentPlannableRequest {
pipe.model.Deployment deployment = 1 [(validate.rules).message.required = true];
}

message InChainDeploymentPlannableResponse {
bool plannable = 1;
}
9 changes: 9 additions & 0 deletions pkg/datastore/deploymentchainstore.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ var (
type DeploymentChainStore interface {
AddDeploymentChain(ctx context.Context, d *model.DeploymentChain) error
UpdateDeploymentChain(ctx context.Context, id string, updater func(*model.DeploymentChain) error) error
GetDeploymentChain(ctx context.Context, id string) (*model.DeploymentChain, error)
}

type deploymentChainStore struct {
Expand Down Expand Up @@ -97,3 +98,11 @@ func (s *deploymentChainStore) UpdateDeploymentChain(ctx context.Context, id str
return dc.Validate()
})
}

func (s *deploymentChainStore) GetDeploymentChain(ctx context.Context, id string) (*model.DeploymentChain, error) {
var entity model.DeploymentChain
if err := s.ds.Get(ctx, DeploymentChainModelKind, id, &entity); err != nil {
return nil, err
}
return &entity, nil
}
5 changes: 5 additions & 0 deletions pkg/model/deployment.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,11 @@ func IsCompletedDeployment(status DeploymentStatus) bool {
return false
}

// IsCompletedSuccessfullyDeployment checks whether the deployment is at a successfully addressed.
func IsCompletedSuccessfullyDeployment(status DeploymentStatus) bool {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I feel like this is more natural but it's on you

Suggested change
func IsCompletedSuccessfullyDeployment(status DeploymentStatus) bool {
func IsSuccessfullyCompletedDeployment(status DeploymentStatus) bool {

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

okay, lets me adopt your suggestion 🙆

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

addressed by 640e172

return status == DeploymentStatus_DEPLOYMENT_SUCCESS
}

// IsCompletedStage checks whether the stage is at a completion state.
func IsCompletedStage(status StageStatus) bool {
if status.String() == "" {
Expand Down