/// <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 }; } } }
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 })); }
/// <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 })); }
/// <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 })); }
/// <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 })); }
/// <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); }
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; } }