Cadence workflow not executing activities after introducing versioning - cadence-workflow

I introduced cadence versioning in to cadence workflow and after that the workflow has stopped exeucting at the point of versioning introduction.
I am receiving the following error :
2020-10-29T07:23:49.587Z DEBUG internal/internal_event_handlers.go:465 ExecuteActivity {"Domain":
"domain_1", "TaskList": "tasklist_1", "WorkerID":
"6#cdnc-5ddb9ccbb5-5dt5j#tasklist", "WorkflowType":
"do_work_workflow", "WorkflowID": "CREATE", "RunID":
"cab97b65-9892-48c5-b842-3f8b462d8602", "ActivityID": "4",
"ActivityType": "do_Task_D"}
2020-10-29T07:23:49.620Z DEBUG internal/internal_task_handlers.go:1077 Cached state staled, new task has unexpected events {"Domain": "domain_1",
"TaskList": "tasklist_1", "WorkerID":
"6#cdnc-5ddb9ccbb5-5dt5j#tasklist1", "WorkflowID": "CREATE", "RunID":
"cab97b65-9892-48c5-b842-3f8b462d8602",
"CachedPreviousStartedEventID": 30, "TaskFirstEventID": 22,
"TaskStartedEventID": 30, "TaskPreviousStartedEventID": 21}
My workflow code will look like this:
func doWorkflow(ctx workflow.Context, input string) error {
err := doTaskA(input)
if err != nil {
return err
}
err = doTaskB(input)
if err != nil {
return err
}
versionTaskC := workflow.GetVersion(ctx, "ChangeID", workflow.DefaultVersion, 1)
if versionTaskC == workflow.DefaultVersion {
err = doTaskC(input)
if err != nil {
return err
}
} else {
err = doTaskD(input)
if err != nil {
return err
}
}
err = doTaskD2(input)
if err != nil {
return err
}
err = doTaskD3(input)
if err != nil {
return err
}
return nil
}
At the ChangeID , version returned is 1, and the workflow tries to execute TaskD but, it is not executing it, It is stuck in an infinite loop, trying to execute the TaskD.
The error message I get is
Cached state staled, new task has unexpected events
and
BadRequestError{Message: CadenceChangeVersion is not valid search
attribute}
Can you please help me with this issue ?

there are two possibles:
You are running a Cadence server with version lower than 0.11.
[very likely]Your Cadence server upgraded from a lower version but didn't make the elasticSearch schema change:
cadence admin cluster add-search-attr --search_attr_key CadenceChangeVersion --search_attr_type 1
And you probably also need to add this:
cadence admin cluster add-search-attr --search_attr_key BinaryChecksums --search_attr_type 1
Background:
CadenceChangeVersion is introduced in https://github.com/uber/cadence/releases/tag/v0.11.0
It's to help searching for workflow change versions.
Let me know if this is not correct.

Related

Mocking of API handler generated by swagger Golang

i really need some help to test the behaviour of my API, let me explain the context :
This API is written in go generated by swagger using the repository patern. In case of creating a new object i need to check if the name is not already exists in the database so i call repository's function to get Applications by name in the database (see below)
func (a *mongoApplicationRepository) GetApplicationByName(name string, ctx context.Context) (*models.Application, error) {
_, span := otel.Tracer("GetApplicationByName").Start(ctx, "GetApplicationByName")
defer span.End()
var application models.Application
err := a.mgConn.Collection("applications").
FindOne(context.TODO(), bson.M{"name": name}).Decode(&application)
if err != nil {
if err == mongo.ErrNoDocuments {
return &application, nil
}
return nil, err
}
return &application, nil
}
This function is called by the api handler that you can see bellow
type ApplicationCreateHandler struct {
API *operations.KubeesAPI
repo repo.ApplicationRepository
statsRepo repo.StatsRepository
}
func NewApplicationCreateHandler(API *operations.KubeesAPI, repo repo.ApplicationRepository, statsRepo repo.StatsRepository) ApplicationCreateHandler {
return ApplicationCreateHandler{
API: API,
repo: repo,
statsRepo: statsRepo,
}
}
// Handle is the HTTP handler for application creation
func (h *ApplicationCreateHandler) Handle(params application.AppCreateParams, principal *models.Principal) middleware.Responder {
traceName := "application-create"
ctx, span := otel.GetTracerProvider().Tracer(traceName).Start(context.TODO(), traceName)
defer span.End()
if params.Data == nil {
err := "unable to validate input"
logger().Errorln(err)
return application.NewAppCreateBadRequest().WithPayload(&models.APIResponse{
Msg: &err,
})
}
if err := params.Data.Validate(h.API.Formats()); err != nil {
err := fmt.Sprintf("unable to validate input: %v", err)
logger().Errorln(err)
return application.NewAppCreateBadRequest().WithPayload(&models.APIResponse{
Msg: &err,
})
}
app := models.Application{
ID: uuid.NewString(),
Name: params.Data.Name,
Selector: params.Data.Selector,
}
c, err := h.repo.GetApplicationByName(*params.Data.Name, ctx)
if err != nil {
err := fmt.Sprintf("Unable to get Application: %v", err)
logger().Errorln(err)
return application.NewAppCreateInternalServerError().WithPayload(&models.APIResponse{
Msg: &err,
})
}
if c.ID != "" {
r := "name already taken"
logger().Errorln(r)
return application.NewAppCreateInternalServerError().WithPayload(&models.APIResponse{
Msg: &r,
})
}
appDomain, err := app.ToDomainModel()
if err != nil {
err := fmt.Sprintf("Unable to convert application to domain model: %v", err)
logger().Errorln(err)
return application.NewAppCreateInternalServerError().WithPayload(&models.APIResponse{
Msg: &err,
})
}
err = h.repo.CreateApplication(appDomain, ctx)
if err != nil {
err := fmt.Sprintf("Unable to create Application: %v", err)
logger().Errorln(err)
return application.NewAppCreateInternalServerError().WithPayload(&models.APIResponse{
Msg: &err,
})
}
if err := h.statsRepo.UpdateApplicationsHistory(ctx); err != nil {
err := fmt.Sprintf("unable to update applications history: %v", err)
logger().Errorln(err)
return application.NewAppCreateInternalServerError().WithPayload(&models.APIResponse{
Msg: &err,
})
}
return application.NewAppCreateCreated()
}
I just want to check this behaviour and test in case we have already an application called the same as the new created one, which would raise an error and not in opposite case. But i don't know what to mock because the repository have some external dependencies
I would strongly suggest to make sure your ApplicationCreateHandler does only rely on interfaces. When that's the case you can mock the interfaces by writing a mock class, or generating on. You can test the ApplicationCreateHandlerby creating a new instance, and passing the mocked dependencies to it. Then execute theHandle()` method and ensure all dependencies are called.
I would strongly recommend generting your mock-classes. When you write your mocks yourself it's possible to introduce bugs in your test code. You wouldn't want that, right?
One great example of a mocking library is mockgen. You can easily instal it by following their github-readme.
Steps
Create interfaces for your dependencies.
It is best define the interface on the side that relies on the interface. So in you case that would be in the ApplicationCreateHandler file. The interfaces should reflect the needs of the handler.
type (
ApplicationRepo interface {
GetApplicationByName(name string, ctx context.Context)
CreateApplication(domain Domain, ctx context.Context)
}
// other dependencies here...
)
Writing a unit test
Create a new unit test
// youfile_test.go
func TestApplicationHandler(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
mockRepo := NewMockRepository(ctrl)
mockRepo.EXPECT().
Handle(gomock.any(), gomock.any()). // name, ctx
Return(nil, nil). // c, err
Once()
// other mocks...
// mockApi
// mockStatsRepo
h := NewApplicationHandler(mockApi, mockStats, mockRepo)
h.Handle(/*your test input*/)
// do checks
}
The test will throw an error if the mockRepo wasn't called exactly once.

Not able to use mongodb transactions using mongo go driver

I have created a mongodb replica set. I am able to run transactions from the mongo shell. But when I try to do it using mongo-go-driver I always get this error (IllegalOperation) Transaction numbers are only allowed on a replica set member or mongos. I am not sure where I am going wrong. I am using this as a reference https://github.com/simagix/mongo-go-examples/blob/master/examples/transaction_test.go.
I create the client like this
client, err := mongo.NewClient(options.Client().ApplyURI("mongodb://localhost:27017,localhost:27018,localhost:27019?replicaSet=rs"))
I can connect to the individual mongodb instances, just not the replica set.
This is the transaction that I am trying to run
var session mongo.Session
coll := db.Collection("collectionname")
if session, err = client.StartSession(); err != nil {
return "", fmt.Errorf("Could not start session: %q", err)
}
if err = session.StartTransaction(); err != nil {
return "", fmt.Errorf("Could not start Transaction: %q", err)
}
if err = mongo.WithSession(ctx, session, func(sc md.SessionContext) error {
newVal, err = coll.InsertOne(sc, val) // some val that I have
if err != nil {
sc.AbortTransaction(sc)
return fmt.Errorf("Error during New address creation, aborting: %q", err)
}
if err = sc.CommitTransaction(sc); err != nil {
return fmt.Errorf("Error While commiting New address Transaction: %q", err)
}
return nil
}); err != nil {
return "", err
}
session.EndSession(ctx)
Is there something I am missing. Is there some other example maybe that I can reference. Thanks for the help/suggestions.
This is an issue with your connection code - not your transaction implementation most likely. Try using the more modern connection string for connecting to a replica set:
connectionString := "mongodb+srv://USERNAME:PASSWORD#mongoatlas.1mxpg.mongodb.net/?retryWrites=true&w=majority"
var err error
Client, err = mongo.NewClient(options.Client().ApplyURI(ConnectionString))
if err != nil {
log.Fatal(err)
}

How does one programmatically subscribe an SQS queue to an SNS topic in Go?

Here is what I tried, using version 53eb8b070e9a5067829fd029539966181632032a of aws-sdk-go.
// main.go
package main
import (
"errors"
"fmt"
"log"
"net/http"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/sns"
"github.com/aws/aws-sdk-go/service/sqs"
)
func main() {
if err := makeTopicAndQueue(); err != nil {
log.Fatalf("aws-sns-sqs: %v", err)
}
}
func makeTopicAndQueue() error {
sess, err := session.NewSession(&aws.Config{
HTTPClient: &http.Client{},
Region: aws.String("us-east-2"),
Credentials: nil,
MaxRetries: aws.Int(0),
})
log.Printf("Creating an SNS topic.")
snsClient := sns.New(sess, &aws.Config{})
topicName := "test-topic"
out, err := snsClient.CreateTopic(&sns.CreateTopicInput{Name: aws.String(topicName)})
if err != nil {
return fmt.Errorf(`creating topic "%s": %v`, topicName, err)
}
defer snsClient.DeleteTopic(&sns.DeleteTopicInput{TopicArn: out.TopicArn})
log.Printf("Creating an SQS queue.")
sqsClient := sqs.New(sess, &aws.Config{})
subName := "test-subscription"
out2, err := sqsClient.CreateQueue(&sqs.CreateQueueInput{QueueName: aws.String(subName)})
if err != nil {
return fmt.Errorf(`creating subscription queue "%s": %v`, subName, err)
}
log.Printf("Getting queue ARN.")
out3, err := sqsClient.GetQueueAttributes(&sqs.GetQueueAttributesInput{
QueueUrl: out2.QueueUrl,
AttributeNames: []*string{aws.String("QueueArn")},
})
if err != nil {
return fmt.Errorf("getting queue ARN for %s: %v", *out2.QueueUrl, err)
}
qARN := out3.Attributes["QueueArn"]
log.Printf("Subscribing the queue to the topic.")
_, err = snsClient.Subscribe(&sns.SubscribeInput{
TopicArn: out.TopicArn,
Endpoint: qARN,
Protocol: aws.String("sqs"),
})
if err != nil {
return fmt.Errorf("subscribing: %v", err)
}
log.Printf("Getting the confirmation token from the queue.")
out4, err := sqsClient.ReceiveMessage(&sqs.ReceiveMessageInput{
QueueUrl: out2.QueueUrl,
})
if err != nil {
return fmt.Errorf("receiving subscription confirmation message from queue: %v", err)
}
ms := out4.Messages
var token *string
switch len(ms) {
case 0:
return errors.New("no subscription confirmation message found in queue")
case 1:
m := ms[0]
token = m.Body
default:
return fmt.Errorf("%d messages found in queue, want exactly 1", len(ms))
}
log.Printf("Using the token to finish subscribing.")
_, err = snsClient.ConfirmSubscription(&sns.ConfirmSubscriptionInput{
TopicArn: out.TopicArn,
Token: token,
})
if err != nil {
return fmt.Errorf("confirming subscription: %v", err)
}
sqsClient.DeleteQueue(&sqs.DeleteQueueInput{QueueUrl: out2.QueueUrl})
return nil
}
I expected it to get to the end but it failed with this output:
[ ~/src/aws-sqs-issue ] go run main.go
2019/01/15 09:31:19 Creating an SNS topic.
2019/01/15 09:31:19 Creating an SQS queue.
2019/01/15 09:31:20 Getting queue ARN.
2019/01/15 09:31:20 Subscribing the queue to the topic.
2019/01/15 09:31:21 Getting the confirmation token from the queue.
2019/01/15 09:31:21 aws-sns-sqs: no subscription confirmation message found in queue
Am I doing something wrong or is this a bug in the SDK?
I'm not sure what else to say about this. Here is some additional verbiage to somehow get the warning about the post being mostly code to go away. It's best to stop reading at this point because all the rest of this is bound to make for boring reading. I don't know much much longer I can keep on making up nonsense to satisfy this silly algorithm. Why don't they allow a simple post that contains a lot of code? I have no idea. Ah well. There is an upside down gopher on my desk. I think it was intentional. Due to the poor critter's anatomy, he's doing more of an eyeball-stand than a head-stand. My desk plant didn't hold up too well over the holidays. Better give it some water. Wowee, this thing really wants an awful lot of words. Well, I aim to please. If I keep going, will I accidentally output some Shakespeare? Well, it's over, thank the Bard.
This one is quite old, but here is the trick to solve it:
For an Amazon SNS topic to be able to send messages to a queue, you must set a policy on the queue that allows the Amazon SNS topic to perform the sqs:SendMessage action. See more
To do so using the current version of the V1 SDK, when creating the Queue you must define the Policy manually as a Queue attribute, allowing the SNS Topic to send messages to your SQS Queue.
Here is an example as code:
queueARN := fmt.Sprintf("arn:aws:sqs:%s:%s:%s", "us-east-1", "xxx", "my-queue")
topicARN := fmt.Sprintf("arn:aws:sns:%s:%s:%s", "us-east-1", "xxx", "my-topic")
_, err := b.sqs.CreateQueue(&sqs.CreateQueueInput{
QueueName: aws.String("my-queue"),
Attributes: map[string]*string{
"Policy": aws.String(fmt.Sprintf(`{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "SNSTopicSendMessage",
"Effect": "Allow",
"Principal": "*",
"Action": "SQS:SendMessage",
"Resource": "%s",
"Condition": {
"ArnEquals": {
"aws:SourceArn": "%s"
}
}
}
]
}`, queueARN, b.eventsTopicARN)),
},
})
if err != nil {
return err
}

Very Sporadic Go HTTP Error: multiple response.WriteHeader calls

I wrote Kanali which is an open source Kubernetes Ingress/API management tool and for about 1/200k requests I receive the following fatal error:
2017/08/16 12:40:57 http: multiple response.WriteHeader calls
{"level":"error","method":"GET","msg":"unknown error","time":"2017-08-16T12:40:57Z","uri":"/ommitted/path"}
{"level":"fatal","msg":"write tcp4 192.168.2.160:8443-\u003e192.168.4.0:54554: write: broken pipe","time":"2017-08-16T12:40:57Z"}
I'm having a really hard time reproducing it but here is the relevant code. Kanali is a large project but the td;lr is that after this first code snippet is executed, the second code snipped is executed which handles errors.
func (step WriteResponseStep) Do(ctx context.Context, m *metrics.Metrics, c *controller.Controller, w http.ResponseWriter, r *http.Request, resp *http.Response, trace opentracing.Span) error {
for k, v := range resp.Header {
for _, value := range v {
w.Header().Set(k, value)
}
}
closer, str, err := utils.DupReaderAndString(resp.Body)
if err != nil {
logrus.Errorf("error copying response body, response may not be as expected: %s", err.Error())
}
trace.SetTag("http.status_code", resp.StatusCode)
trace.SetTag("http.response_body", str)
w.WriteHeader(resp.StatusCode)
if _, err := io.Copy(w, closer); err != nil {
return err
}
return nil
}
later in the code...
if err != nil {
w.Header().Set("Content-Type", "application/json")
switch e := err.(type) {
case utils.Error:
logrus.WithFields(logrus.Fields{
"method": r.Method,
"uri": r.RequestURI,
}).Error(e.Error())
errStatus, err := json.Marshal(utils.JSONErr{Code: e.Status(), Msg: e.Error()})
if err != nil {
logrus.Warnf("could not marsah request headers into JSON - tracing data maybe not be as expected")
}
w.WriteHeader(e.Status())
if err := json.NewEncoder(w).Encode(utils.JSONErr{Code: e.Status(), Msg: e.Error()}); err != nil {
logrus.Fatal(err.Error())
}
default:
logrus.WithFields(logrus.Fields{
"method": r.Method,
"uri": r.RequestURI,
}).Error("unknown error")
errStatus, err := json.Marshal(utils.JSONErr{Code: http.StatusInternalServerError, Msg: "unknown error"})
if err != nil {
logrus.Warnf("could not marsah request headers into JSON - tracing data maybe not be as expected")
}
w.WriteHeader(http.StatusInternalServerError)
if err := json.NewEncoder(w).Encode(utils.JSONErr{Code: http.StatusInternalServerError, Msg: "unknown error"}); err != nil {
logrus.Fatal(err.Error())
}
}
}
The error message is telling you that WriteHeader is called more than once (either directly or indirectly by a call to Write). The header can only be written to the network once. Both snippets have a call to WriteHeader.

Kubernetes go client api for log of a particular pod

I am using kube go client with kube api to access kube data. I am currently not finding any api call for logs of a particular pod.
kubectl logs pod-name
returns the logs for a particular pod. How do I do this using go client?
I am using v1.0.6 of kubernetes.
I can get the pod by using
client.Pods("namespace").Get("pod-name")
Client Go has offered a function GetLogs for this, which has been answered in How to get logs from kubernetes using Go?
Looking at how kubectl implements its commands can be helpful when getting a feel for how to use the client library. In this case, kubectl's implementation of the logs command looks like this:
req := client.RESTClient.Get().
Namespace(namespace).
Name(podID).
Resource("pods").
SubResource("log").
Param("follow", strconv.FormatBool(logOptions.Follow)).
Param("container", logOptions.Container).
Param("previous", strconv.FormatBool(logOptions.Previous)).
Param("timestamps", strconv.FormatBool(logOptions.Timestamps))
if logOptions.SinceSeconds != nil {
req.Param("sinceSeconds", strconv.FormatInt(*logOptions.SinceSeconds, 10))
}
if logOptions.SinceTime != nil {
req.Param("sinceTime", logOptions.SinceTime.Format(time.RFC3339))
}
if logOptions.LimitBytes != nil {
req.Param("limitBytes", strconv.FormatInt(*logOptions.LimitBytes, 10))
}
if logOptions.TailLines != nil {
req.Param("tailLines", strconv.FormatInt(*logOptions.TailLines, 10))
}
readCloser, err := req.Stream()
if err != nil {
return err
}
defer readCloser.Close()
_, err = io.Copy(out, readCloser)
return err
type Pipe struct {
InReader *io.PipeReader
InWriter *io.PipeWriter
OutReader *io.PipeReader
OutWriter *io.PipeWriter
}
req := client.RESTClient().Get().Resource("pods").
Name(option.Name).Namespace(option.Namespace).SubResource("log")
opt := &coreV1.PodLogOptions{
Follow: option.Follow,
Previous: option.Previous,
SinceSeconds: option.SinceSeconds,
Timestamps: option.Timestamps,
TailLines: option.TailLines,
LimitBytes: option.LimitBytes,
}
if option.Container != "" {
opt.Container = option.Container
}
req.VersionedParams(
opt,
scheme.ParameterCodec,
)
exec, err := remotecommand.NewSPDYExecutor(k.cli.kubeConfig, http.MethodGet, req.URL())
if err != nil {
return err
}
err = exec.Stream(remotecommand.StreamOptions{
Stdin: pipe.InReader,
Stdout: pipe.OutWriter,
Stderr: pipe.OutWriter,
Tty: true,
})
if err != nil {
return err
}
return nil