Пример #1
0
        /// <summary>
        /// Used to start the fixture within a <see cref="ComposedFixture"/>.
        /// </summary>
        /// <param name="settings">Optional Cadence settings.</param>
        /// <param name="name">Optionally specifies the Cadence container name (defaults to <c>cadence-dev</c>).</param>
        /// <param name="composeFile">
        /// <para>
        /// Optionally specifies the Temporal Docker compose file text.  This defaults to
        /// <see cref="DefaultComposeFile"/> which configures Temporal server to start with
        /// a new Cassandra database instance listening on port <b>9042</b> as well as the
        /// Temporal web UI running on port <b>8088</b>.  Temporal server is listening on
        /// its standard gRPC port <b>7233</b>.
        /// </para>
        /// <para>
        /// You may specify your own Docker compose text file to customize this by configuring
        /// a different backend database, etc.
        /// </para>
        /// </param>
        /// <param name="defaultDomain">Optionally specifies the default domain for the fixture's client.  This defaults to <b>test-domain</b>.</param>
        /// <param name="logLevel">Specifies the Cadence log level.  This defaults to <see cref="Neon.Diagnostics.LogLevel.None"/>.</param>
        /// <param name="reconnect">
        /// Optionally specifies that a new Cadence connection <b>should</b> be established for each
        /// unit test case.  By default, the same connection will be reused which will save about a
        /// second per test case.
        /// </param>
        /// <param name="keepRunning">
        /// Optionally indicates that the container should remain running after the fixture is disposed.
        /// This is handy for using the Temporal web UI for port mortems after tests have completed.
        /// </param>
        /// <param name="noClient">
        /// Optionally disables establishing a client connection when <c>true</c>
        /// is passed.  The <see cref="Client"/> and <see cref="HttpClient"/> properties
        /// will be set to <c>null</c> in this case.
        /// </param>
        /// <param name="noReset">
        /// Optionally prevents the fixture from calling <see cref="CadenceClient.Reset()"/> to
        /// put the Cadence client library into its initial state before the fixture starts as well
        /// as when the fixture itself is reset.
        /// </param>
        /// <remarks>
        /// <note>
        /// A fresh Cadence client <see cref="Client"/> will be established every time this
        /// fixture is started, regardless of whether the fixture has already been started.  This
        /// ensures that each unit test will start with a client in the default state.
        /// </note>
        /// </remarks>
        public void StartAsComposed(
            CadenceSettings settings = null,
            string name          = "cadence-dev",
            string composeFile   = DefaultComposeFile,
            string defaultDomain = DefaultDomain,
            LogLevel logLevel    = LogLevel.None,
            bool reconnect       = false,
            bool keepRunning     = false,
            bool noClient        = false,
            bool noReset         = false)
        {
            Covenant.Requires <ArgumentNullException>(!string.IsNullOrEmpty(composeFile), nameof(composeFile));

            base.CheckWithinAction();

            if (!IsRunning)
            {
                // $hack(jefflill):
                //
                // The [temporal-dev] Docker test stack may be running from a previous test run.
                // We need to stop this to avoid network port conflicts.  We're just going to
                // force the removal of the stack's Docker containers.
                //
                // This is somewhat fragile because it hardcodes the container names and won't
                // remove any other stack assets like networks.

                NeonHelper.ExecuteCapture(NeonHelper.DockerCli,
                                          new object[]
                {
                    "rm", "--force",
                    new string[]
                    {
                        "temporal-dev_cassandra_1",
                        "temporal-dev_temporal-web_1",
                        "temporal-dev_temporal_1"
                    }
                });

                // Reset CadenceClient to its initial state.

                this.noReset = noReset;

                if (!noReset)
                {
                    CadenceClient.Reset();
                }

                // Start the Cadence container.

                base.StartAsComposed(name, composeFile, keepRunning);

                // It can take Cadence server some time to start.  Rather than relying on [cadence-proxy]
                // to handle retries (which may take longer than the connect timeout), we're going to wait
                // up to 4 minutes for Temporal to start listening on its RPC socket.

                var retry = new LinearRetryPolicy(e => true, maxAttempts: int.MaxValue, retryInterval: TimeSpan.FromSeconds(0.5), timeout: TimeSpan.FromMinutes(4));

                retry.Invoke(
                    () =>
                {
                    // The [socket.Connect()] calls below will throw [SocketException] until
                    // Temporal starts listening on its RPC socket.

                    var socket = new Socket(AddressFamily.InterNetworkV6, SocketType.Stream, ProtocolType.Tcp);

                    socket.Connect(IPAddress.IPv6Loopback, NetworkPorts.Cadence);
                    socket.Close();
                });

                Thread.Sleep(TimeSpan.FromSeconds(5));  // Wait a bit longer for luck!

                // Initialize the settings.

                settings = settings ?? new CadenceSettings()
                {
                    CreateDomain  = true,
                    DefaultDomain = defaultDomain,
                    LogLevel      = logLevel
                };

                if (settings.Servers.Count == 0)
                {
                    settings.Servers.Add($"http://localhost:{NetworkPorts.Cadence}");
                }

                this.settings  = settings;
                this.reconnect = reconnect;

                if (!noClient)
                {
                    // Establish the Cadence connection via the cadence proxy.

                    Client = CadenceClient.ConnectAsync(settings).Result;

                    HttpClient = new HttpClient()
                    {
                        BaseAddress = Client.ListenUri
                    };
                }
            }
        }
Пример #2
0
        public async Task Worker()
        {
            await SyncContext.Clear;

            await client.RegisterDomainAsync("test-domain", ignoreDuplicates : true);

            // Verify that the stub builder methods don't barf.

            CadenceClient.BuildActivityStub <ITestActivity>();
            CadenceClient.BuildWorkflowStub <ITestWorkflow>();
            CadenceClient.BuildAssemblyStubs(Assembly.GetExecutingAssembly());

            // Verify that creating workers with the same attributes actually
            // return the pre-existing instance with an incremented reference
            // count.

            var activityWorker1 = await client.StartWorkerAsync("tasks1", new WorkerOptions()
            {
                DisableWorkflowWorker = true
            });

            Assert.Equal(0, activityWorker1.RefCount);

            var activityWorker2 = await client.StartWorkerAsync("tasks1", new WorkerOptions()
            {
                DisableWorkflowWorker = true
            });

            Assert.Same(activityWorker1, activityWorker2);
            Assert.Equal(1, activityWorker2.RefCount);

            var workflowWorker1 = await client.StartWorkerAsync("tasks1", new WorkerOptions()
            {
                DisableActivityWorker = true
            });

            Assert.Equal(0, workflowWorker1.RefCount);

            var workflowWorker2 = await client.StartWorkerAsync("tasks1", new WorkerOptions()
            {
                DisableActivityWorker = true
            });

            Assert.Same(workflowWorker1, workflowWorker2);
            Assert.Equal(1, workflowWorker2.RefCount);

            Assert.Same(workflowWorker1, workflowWorker2);
            Assert.Equal(1, workflowWorker2.RefCount);

            var worker1 = await client.StartWorkerAsync("tasks2");

            Assert.Equal(0, worker1.RefCount);

            var worker2 = await client.StartWorkerAsync("tasks2");

            Assert.Same(worker1, worker2);
            Assert.Equal(1, worker2.RefCount);

            // Verify the dispose/refcount behavior.

            activityWorker2.Dispose();
            Assert.False(activityWorker2.IsDisposed);
            Assert.Equal(0, activityWorker2.RefCount);

            activityWorker2.Dispose();
            Assert.True(activityWorker2.IsDisposed);
            Assert.Equal(-1, activityWorker2.RefCount);

            workflowWorker2.Dispose();
            Assert.False(workflowWorker2.IsDisposed);
            Assert.Equal(0, workflowWorker2.RefCount);

            workflowWorker2.Dispose();
            Assert.True(workflowWorker2.IsDisposed);
            Assert.Equal(-1, workflowWorker2.RefCount);

            // Verify that we're not allowed to restart workers.

            await Assert.ThrowsAsync <InvalidOperationException>(async() => await client.StartWorkerAsync("tasks1", new WorkerOptions()
            {
                DisableWorkflowWorker = true
            }));

            await Assert.ThrowsAsync <InvalidOperationException>(async() => await client.StartWorkerAsync("tasks1", new WorkerOptions()
            {
                DisableActivityWorker = true
            }));
        }
Пример #3
0
 /// <summary>
 /// Creates a workflow stub instance suitable for starting a new child workflow.
 /// </summary>
 /// <param name="client">The associated <see cref="CadenceClient"/>.</param>
 /// <param name="dataConverter">The data converter.</param>
 /// <param name="workflowTypeName">Specifies the workflow type name.</param>
 /// <param name="options">Specifies the child workflow options.</param>
 /// <returns>The workflow stub as an <see cref="object"/>.</returns>
 public object Create(CadenceClient client, IDataConverter dataConverter, string workflowTypeName, ChildWorkflowOptions options)
 {
     return(childConstructor.Invoke(new object[] { client, dataConverter, workflowTypeName, options }));
 }
Пример #4
0
 /// <summary>
 /// Creates a workflow stub instance suitable for starting a new external workflow.
 /// </summary>
 /// <param name="client">The associated <see cref="CadenceClient"/>.</param>
 /// <param name="dataConverter">The data converter.</param>
 /// <param name="workflowTypeName">Specifies the workflow type name.</param>
 /// <param name="taskList">Specifies the target task list.</param>
 /// <param name="options">Specifies the <see cref="WorkflowOptions"/>.</param>
 /// <param name="domain">Specifies the target domain.</param>
 /// <returns>The workflow stub as an <see cref="object"/>.</returns>
 public object Create(CadenceClient client, IDataConverter dataConverter, string workflowTypeName, string taskList, WorkflowOptions options, string domain)
 {
     return(startConstructor.Invoke(new object[] { client, dataConverter, workflowTypeName, taskList, options, domain }));
 }
Пример #5
0
 /// <summary>
 /// Creates a local activity stub instance suitable for executing a non-local activity.
 /// </summary>
 /// <param name="client">The associated <see cref="CadenceClient"/>.</param>
 /// <param name="workflow">The parent workflow.</param>
 /// <param name="activityType">The activity implementation type.</param>
 /// <param name="options">Specifies the <see cref="LocalActivityOptions"/>.</param>
 /// <returns>The activity stub as an <see cref="object"/>.</returns>
 public object CreateLocal(CadenceClient client, Workflow workflow, Type activityType, LocalActivityOptions options)
 {
     return(localConstructor.Invoke(new object[] { client, client.DataConverter, workflow.Parent, activityType, options }));
 }
Пример #6
0
        /// <inheritdoc/>
        protected async override Task <int> OnRunAsync()
        {
            // Verify the environment variables.

            var settings = new CadenceSettings();
            var servers  = GetEnvironmentVariable("CADENCE_SERVERS");
            var domain   = GetEnvironmentVariable("CADENCE_DOMAIN");
            var taskList = GetEnvironmentVariable("CADENCE_TASKLIST");
            var error    = false;

            Log.LogInfo(() => $"CADENCE_SERVERS:  {servers}");
            Log.LogInfo(() => $"CADENCE_DOMAIN:   {domain}");
            Log.LogInfo(() => $"CADENCE_TASKLIST: {taskList}");

            if (string.IsNullOrEmpty(servers))
            {
                error = true;
                Log.LogError("The [CADENCE_SERVERS] environment variable is required.");
            }

            try
            {
                foreach (var item in servers.Split(','))
                {
                    var uri = new Uri(item.Trim(), UriKind.Absolute);

                    settings.Servers.Add(uri.ToString());
                }
            }
            catch
            {
                error = true;
                Log.LogError(() => $"One or more URIs are invalid: CADENCE_SERVERS={servers}");
            }

            if (string.IsNullOrEmpty(domain))
            {
                error = true;
                Log.LogError("The [CADENCE_DOMAIN] environment variable is required.");
            }

            if (string.IsNullOrEmpty(taskList))
            {
                error = true;
                Log.LogError("The [CADENCE_TASKLIST] environment variable is required.");
            }

            if (error)
            {
                return(1);
            }

            // Connect to Cadence and register the workflows and activities.

            Log.LogInfo("Connecting to Cadence.");

            settings.DefaultDomain = domain;

            using (var client = await CadenceClient.ConnectAsync(settings))
            {
                // Register the workflows.

                Log.LogInfo("Registering workflows.");
                await client.RegisterAssemblyAsync(Assembly.GetExecutingAssembly());

                // Start the worker.

                Log.LogInfo("Starting worker.");

                using (var worker = client.StartWorkerAsync(taskList))
                {
                    // Indicate that the service is running.

                    Log.LogInfo("Ready for work.");
                    await StartedAsync();
                }
            }

            // Handle termination gracefully.

            await Terminator.StopEvent.WaitAsync();

            Terminator.ReadyToExit();

            return(0);
        }
Пример #7
0
        public async Task Multiple_TaskLists()
        {
            await SyncContext.ClearAsync;

            // Test the scenario where there multiple clients without
            // workers that will be used to simulate apps that make calls
            // on workflows and then create multiple clients that register
            // different workflows and activities and then verify that
            // each of the workerless clients are able to execute workflows
            // and activities and that these end up being executed on the
            // correct clients.

            var clients = new List <CadenceClient>();

            try
            {
                // Initialize the non-worker clients.

                CadenceClient client1;
                CadenceClient client2;
                CadenceClient client3;

                clients.Add(client1 = await CadenceClient.ConnectAsync(fixture.Settings));
                clients.Add(client2 = await CadenceClient.ConnectAsync(fixture.Settings));
                clients.Add(client3 = await CadenceClient.ConnectAsync(fixture.Settings));

                // Initialize the worker clients.

                clients.Add(workerClient1 = await CadenceClient.ConnectAsync(fixture.Settings));
                clients.Add(workerClient2 = await CadenceClient.ConnectAsync(fixture.Settings));
                clients.Add(workerClient3 = await CadenceClient.ConnectAsync(fixture.Settings));

                // Start the workers.

                await workerClient1.RegisterActivityAsync <ActivityWorker1>();

                await workerClient1.RegisterWorkflowAsync <WorkflowWorker1>();

                await workerClient1.StartWorkerAsync("tasklist-1");

                await workerClient2.RegisterActivityAsync <ActivityWorker2>();

                await workerClient2.RegisterWorkflowAsync <WorkflowWorker2>();

                await workerClient2.StartWorkerAsync("tasklist-2");

                await workerClient3.RegisterActivityAsync <ActivityWorker3>();

                await workerClient3.RegisterWorkflowAsync <WorkflowWorker3>();

                await workerClient3.StartWorkerAsync("tasklist-3");

                // Execute each of the worker workflows WITHOUT the associated activities
                // from each client (both the worker and non-worker clients).

                foreach (var client in clients)
                {
                    var stub = client.NewWorkflowStub <IWorkflowWorker1>();

                    Assert.True(await stub.RunAsync(testActivity: false));
                }

                foreach (var client in clients)
                {
                    var stub = client.NewWorkflowStub <IWorkflowWorker2>();

                    Assert.True(await stub.RunAsync(testActivity: false));
                }

                foreach (var client in clients)
                {
                    var stub = client.NewWorkflowStub <IWorkflowWorker3>();

                    Assert.True(await stub.RunAsync(testActivity: false));
                }

                // Re-run the workflows calling the activities this time.

                foreach (var client in clients)
                {
                    var stub = client.NewWorkflowStub <IWorkflowWorker1>();

                    Assert.True(await stub.RunAsync(testActivity: true));
                }

                foreach (var client in clients)
                {
                    var stub = client.NewWorkflowStub <IWorkflowWorker2>();

                    Assert.True(await stub.RunAsync(testActivity: true));
                }

                foreach (var client in clients)
                {
                    var stub = client.NewWorkflowStub <IWorkflowWorker3>();

                    Assert.True(await stub.RunAsync(testActivity: true));
                }
            }
            finally
            {
                foreach (var client in clients)
                {
                    client.Dispose();
                }

                workerClient1 = null;
                workerClient2 = null;
                workerClient3 = null;
            }
        }