Add active device count for status API (#169)
* Register SimulationRunner as a singleton type * Allow DeviceStatusActor report active status * Add active device count to status API
This commit is contained in:
Родитель
a6b63536fe
Коммит
d7b1916d60
|
@ -0,0 +1,84 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Diagnostics;
|
||||
using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Models;
|
||||
using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Simulation;
|
||||
using Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent.DeviceState;
|
||||
using Moq;
|
||||
using SimulationAgent.Test.helpers;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace SimulationAgent.Test.DeviceState
|
||||
{
|
||||
public class DeviceStateActorTest
|
||||
{
|
||||
private readonly Mock<ILogger> logger;
|
||||
private readonly Mock<IScriptInterpreter> scriptInterpreter;
|
||||
private readonly Mock<UpdateDeviceState> updateDeviceStateLogic;
|
||||
private readonly DeviceStateActor target;
|
||||
|
||||
public DeviceStateActorTest(ITestOutputHelper log)
|
||||
{
|
||||
this.logger = new Mock<ILogger>();
|
||||
this.scriptInterpreter = new Mock<IScriptInterpreter>();
|
||||
this.updateDeviceStateLogic = new Mock<UpdateDeviceState>(
|
||||
this.scriptInterpreter.Object,
|
||||
this.logger.Object);
|
||||
|
||||
this.target = new DeviceStateActor(
|
||||
this.logger.Object,
|
||||
this.updateDeviceStateLogic.Object);
|
||||
}
|
||||
|
||||
[Fact, Trait(Constants.TYPE, Constants.UNIT_TEST)]
|
||||
public void ReportInactiveStatusBeforeRun()
|
||||
{
|
||||
// Arrange
|
||||
SetupDeviceStateActor();
|
||||
|
||||
// Act
|
||||
var result = this.target.IsDeviceActive;
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
[Fact, Trait(Constants.TYPE, Constants.UNIT_TEST)]
|
||||
public void ReportActiveStatusAfterRun()
|
||||
{
|
||||
// Arrange
|
||||
SetupDeviceStateActor();
|
||||
|
||||
// Act
|
||||
this.target.Run();
|
||||
var result = this.target.IsDeviceActive;
|
||||
|
||||
// Assert
|
||||
Assert.True(result);
|
||||
}
|
||||
|
||||
private void SetupDeviceStateActor()
|
||||
{
|
||||
string DEVICE_ID = "01";
|
||||
int postion = 1;
|
||||
int total = 10;
|
||||
var deviceModel = new DeviceModel { Id = DEVICE_ID };
|
||||
var deviceState = new Dictionary<string, object>
|
||||
{
|
||||
{ DEVICE_ID, new Object { } }
|
||||
};
|
||||
|
||||
this.scriptInterpreter
|
||||
.Setup(x => x.Invoke(
|
||||
It.IsAny<Script>(),
|
||||
It.IsAny<Dictionary<string, object>>(),
|
||||
It.IsAny<Dictionary<string, object>>()))
|
||||
.Returns(deviceState);
|
||||
|
||||
this.target.Setup(DEVICE_ID, deviceModel, postion, total);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -12,4 +12,8 @@
|
|||
<PackageReference Include="xunit.runner.console" Version="2.3.1" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.3.1" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Services\Services.csproj" />
|
||||
<ProjectReference Include="..\SimulationAgent\SimulationAgent.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
|
@ -0,0 +1,179 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services;
|
||||
using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Concurrency;
|
||||
using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Diagnostics;
|
||||
using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Models;
|
||||
using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Runtime;
|
||||
using Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent.DeviceConnection;
|
||||
using Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent.DeviceState;
|
||||
using Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent.DeviceTelemetry;
|
||||
using Moq;
|
||||
using SimulationAgent.Test.helpers;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
using static Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Models.Simulation;
|
||||
using SimulationModel = Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Models.Simulation;
|
||||
using SimulationRunner = Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent.SimulationRunner;
|
||||
|
||||
namespace SimulationAgent.Test
|
||||
{
|
||||
public class SimulationRunnerTest
|
||||
{
|
||||
private readonly Mock<IRateLimitingConfig> ratingConfig;
|
||||
private readonly Mock<ILogger> logger;
|
||||
private readonly Mock<IDeviceModels> deviceModels;
|
||||
private readonly Mock<IDeviceModelsGeneration> deviceModelsOverriding;
|
||||
private readonly Mock<IDevices> devices;
|
||||
private readonly Mock<ISimulations> simulations;
|
||||
private readonly Mock<IFactory> factory;
|
||||
private readonly Mock<IDictionary<string, IDeviceStateActor>> deviceStateActors;
|
||||
private readonly Mock<IDeviceStateActor> deviceStateActor;
|
||||
private readonly Mock<IDeviceConnectionActor> deviceConnectionActor;
|
||||
private readonly Mock<IDeviceTelemetryActor> deviceTelemetryActor;
|
||||
private readonly Mock<UpdateDeviceState> updateDeviceStateLogic;
|
||||
private readonly SimulationRunner target;
|
||||
|
||||
public SimulationRunnerTest(ITestOutputHelper log)
|
||||
{
|
||||
this.ratingConfig = new Mock<IRateLimitingConfig>();
|
||||
this.logger = new Mock<ILogger>();
|
||||
this.deviceModels = new Mock<IDeviceModels>();
|
||||
this.deviceModelsOverriding = new Mock<IDeviceModelsGeneration>();
|
||||
this.devices = new Mock<IDevices>();
|
||||
this.simulations = new Mock<ISimulations>();
|
||||
this.factory = new Mock<IFactory>();
|
||||
this.deviceStateActors = new Mock<IDictionary<string, IDeviceStateActor>>();
|
||||
this.deviceStateActor = new Mock<IDeviceStateActor>();
|
||||
this.deviceConnectionActor = new Mock<IDeviceConnectionActor>();
|
||||
this.deviceTelemetryActor = new Mock<IDeviceTelemetryActor>();
|
||||
this.updateDeviceStateLogic = new Mock<UpdateDeviceState>();
|
||||
|
||||
this.target = new SimulationRunner(
|
||||
this.ratingConfig.Object,
|
||||
this.logger.Object,
|
||||
this.deviceModels.Object,
|
||||
this.deviceModelsOverriding.Object,
|
||||
this.devices.Object,
|
||||
this.simulations.Object,
|
||||
this.factory.Object);
|
||||
}
|
||||
|
||||
[Fact, Trait(Constants.TYPE, Constants.UNIT_TEST)]
|
||||
public void TheActiveDevicesCountIsZeroAtStart()
|
||||
{
|
||||
// Act
|
||||
var result = this.target.GetActiveDevicesCount();
|
||||
|
||||
// Assert
|
||||
Assert.Equal(0, result);
|
||||
}
|
||||
|
||||
[Fact, Trait(Constants.TYPE, Constants.UNIT_TEST)]
|
||||
public void ItReturnsTheNumberOfActiveDevices()
|
||||
{
|
||||
// Arrange
|
||||
const int ACTIVE_DEVICES_COUNT = 7;
|
||||
var models = new List<DeviceModelRef>
|
||||
{
|
||||
new DeviceModelRef { Id = "01", Count = ACTIVE_DEVICES_COUNT }
|
||||
};
|
||||
|
||||
var simulation = new SimulationModel
|
||||
{
|
||||
Id = "1",
|
||||
Created = DateTimeOffset.UtcNow.Subtract(TimeSpan.FromDays(10)),
|
||||
Modified = DateTimeOffset.UtcNow.Subtract(TimeSpan.FromDays(10)),
|
||||
ETag = "ETag0",
|
||||
Enabled = false,
|
||||
Version = 1,
|
||||
DeviceModels = models
|
||||
};
|
||||
|
||||
SetupSimulationReadyToStart();
|
||||
|
||||
// Act
|
||||
this.target.Start(simulation);
|
||||
var result = this.target.GetActiveDevicesCount();
|
||||
|
||||
// Assert
|
||||
Assert.Equal(ACTIVE_DEVICES_COUNT, result);
|
||||
}
|
||||
|
||||
private void SetupSimulationReadyToStart()
|
||||
{
|
||||
this.SetupSimulations();
|
||||
|
||||
this.SetUpDeviceModelsOverriding();
|
||||
|
||||
this.SetupDevices();
|
||||
|
||||
this.SetupDeviceStateActor();
|
||||
|
||||
this.SetupDeviceConnectionActor();
|
||||
|
||||
this.SetupDeviceTelemetryActor();
|
||||
|
||||
this.SetupActiveDeviceStatus();
|
||||
}
|
||||
|
||||
private void SetupActiveDeviceStatus()
|
||||
{
|
||||
this.deviceStateActor
|
||||
.Setup(x => x.IsDeviceActive)
|
||||
.Returns(true);
|
||||
}
|
||||
|
||||
private void SetupDeviceTelemetryActor()
|
||||
{
|
||||
this.factory
|
||||
.Setup(x => x.Resolve<IDeviceTelemetryActor>())
|
||||
.Returns(this.deviceTelemetryActor.Object);
|
||||
}
|
||||
|
||||
private void SetupDeviceConnectionActor()
|
||||
{
|
||||
this.factory
|
||||
.Setup(x => x.Resolve<IDeviceConnectionActor>())
|
||||
.Returns(this.deviceConnectionActor.Object);
|
||||
}
|
||||
|
||||
private void SetupDeviceStateActor()
|
||||
{
|
||||
this.factory
|
||||
.Setup(x => x.Resolve<IDeviceStateActor>())
|
||||
.Returns(this.deviceStateActor.Object);
|
||||
}
|
||||
|
||||
private void SetupDevices()
|
||||
{
|
||||
this.devices
|
||||
.Setup(x => x.GenerateId(
|
||||
It.IsAny<string>(),
|
||||
It.IsAny<int>()))
|
||||
.Returns("Simulate-01");
|
||||
}
|
||||
|
||||
private void SetUpDeviceModelsOverriding()
|
||||
{
|
||||
var deviceModel = new DeviceModel { Id = "01" };
|
||||
|
||||
this.deviceModelsOverriding
|
||||
.Setup(x => x.Generate(
|
||||
It.IsAny<DeviceModel>(),
|
||||
It.IsAny<DeviceModelOverride>()))
|
||||
.Returns(deviceModel);
|
||||
}
|
||||
|
||||
private void SetupSimulations()
|
||||
{
|
||||
var deviceIds = new List<string> { "01", "02" };
|
||||
|
||||
this.simulations
|
||||
.Setup(x => x.GetDeviceIds(It.IsAny<Simulation>()))
|
||||
.Returns(deviceIds);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -12,6 +12,7 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent.DeviceSt
|
|||
public interface IDeviceStateActor
|
||||
{
|
||||
Dictionary<string, object> DeviceState { get; }
|
||||
bool IsDeviceActive { get; }
|
||||
void Setup(string deviceId, DeviceModel deviceModel, int position, int totalDevices);
|
||||
void Run();
|
||||
}
|
||||
|
@ -32,6 +33,22 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent.DeviceSt
|
|||
/// </summary>
|
||||
public Dictionary<string, object> DeviceState { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The device is considered active when the state is being updated.
|
||||
///
|
||||
/// By design, rather than talking about "connected devices", we use
|
||||
/// the term "active devices" which is more generic. So when we show
|
||||
/// the number of active devices, we can include devices which are not
|
||||
/// connected yet but being simulated.
|
||||
/// </summary>
|
||||
public bool IsDeviceActive
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.status == ActorStatus.Updating;
|
||||
}
|
||||
}
|
||||
|
||||
private readonly ILogger log;
|
||||
private readonly UpdateDeviceState updateDeviceStateLogic;
|
||||
private string deviceId;
|
||||
|
|
|
@ -21,6 +21,7 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent
|
|||
{
|
||||
void Start(Services.Models.Simulation simulation);
|
||||
void Stop();
|
||||
int GetActiveDevicesCount();
|
||||
}
|
||||
|
||||
public class SimulationRunner : ISimulationRunner
|
||||
|
@ -230,6 +231,9 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent
|
|||
}
|
||||
}
|
||||
|
||||
// Method to return the count of active devices
|
||||
public int GetActiveDevicesCount() => this.deviceStateActors.Count(a => a.Value.IsDeviceActive);
|
||||
|
||||
private DeviceModel GetDeviceModel(string id, Services.Models.Simulation.DeviceModelOverride overrides)
|
||||
{
|
||||
var modelDef = new DeviceModel();
|
||||
|
|
|
@ -0,0 +1,97 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services;
|
||||
using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Diagnostics;
|
||||
using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.IotHub;
|
||||
using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Runtime;
|
||||
using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.StorageAdapter;
|
||||
using Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent;
|
||||
using Moq;
|
||||
using WebService.Test.helpers;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
using SimulationModel = Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Models.Simulation;
|
||||
using StatusController = Microsoft.Azure.IoTSolutions.DeviceSimulation.WebService.v1.Controllers.StatusController;
|
||||
|
||||
namespace WebService.Test.v1.Controllers
|
||||
{
|
||||
public class StatusControllerTest
|
||||
{
|
||||
private const string SIMULATION_ID = "1";
|
||||
|
||||
private readonly Mock<IPreprovisionedIotHub> preprovisionedIotHub;
|
||||
private readonly Mock<IStorageAdapterClient> storage;
|
||||
private readonly Mock<ISimulations> simulations;
|
||||
private readonly Mock<ILogger> logger;
|
||||
private readonly Mock<IServicesConfig> servicesConfig;
|
||||
private readonly Mock<IDeploymentConfig> deploymentConfig;
|
||||
private readonly Mock<IIotHubConnectionStringManager> connectionStringManager;
|
||||
private readonly Mock<ISimulationRunner> simulationRunner;
|
||||
private readonly StatusController target;
|
||||
|
||||
public StatusControllerTest(ITestOutputHelper log)
|
||||
{
|
||||
this.preprovisionedIotHub = new Mock<IPreprovisionedIotHub>();
|
||||
this.storage = new Mock<IStorageAdapterClient>();
|
||||
this.simulations = new Mock<ISimulations>();
|
||||
this.logger = new Mock<ILogger>();
|
||||
this.servicesConfig = new Mock<IServicesConfig>();
|
||||
this.deploymentConfig = new Mock<IDeploymentConfig>();
|
||||
this.connectionStringManager = new Mock<IIotHubConnectionStringManager>();
|
||||
this.simulationRunner = new Mock<ISimulationRunner>();
|
||||
|
||||
this.target = new StatusController(
|
||||
this.preprovisionedIotHub.Object,
|
||||
this.storage.Object,
|
||||
this.simulations.Object,
|
||||
this.logger.Object,
|
||||
this.servicesConfig.Object,
|
||||
this.deploymentConfig.Object,
|
||||
this.connectionStringManager.Object,
|
||||
this.simulationRunner.Object);
|
||||
}
|
||||
|
||||
[Fact, Trait(Constants.TYPE, Constants.UNIT_TEST)]
|
||||
public async Task ItReturnsTheNumberOfActiveDevices()
|
||||
{
|
||||
// Arrange
|
||||
const int ACTIVE_DEVICES_COUNT = 5;
|
||||
this.SetupSimulationForRunner();
|
||||
|
||||
this.simulationRunner
|
||||
.Setup(x => x.GetActiveDevicesCount())
|
||||
.Returns(ACTIVE_DEVICES_COUNT);
|
||||
|
||||
// Act
|
||||
var result = await this.target.Get();
|
||||
|
||||
// Assert
|
||||
Assert.Equal(ACTIVE_DEVICES_COUNT.ToString(), result.Properties["ActiveDevicesCount"]);
|
||||
}
|
||||
|
||||
private void SetupSimulationForRunner()
|
||||
{
|
||||
var simulation = new SimulationModel
|
||||
{
|
||||
Id = SIMULATION_ID,
|
||||
Created = DateTimeOffset.UtcNow.Subtract(TimeSpan.FromDays(10)),
|
||||
Modified = DateTimeOffset.UtcNow.Subtract(TimeSpan.FromDays(10)),
|
||||
ETag = "ETag0",
|
||||
Enabled = true,
|
||||
Version = 1
|
||||
};
|
||||
|
||||
var simulations = new List<SimulationModel>
|
||||
{
|
||||
simulation
|
||||
};
|
||||
|
||||
this.simulations
|
||||
.Setup(x => x.GetListAsync())
|
||||
.ReturnsAsync(simulations);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -91,6 +91,11 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.WebService
|
|||
builder.RegisterType<Services.Devices>().As<IDevices>().SingleInstance();
|
||||
builder.RegisterType<RateLimiting>().As<IRateLimiting>().SingleInstance();
|
||||
|
||||
// The simulation runner contains the service counters, which are read and
|
||||
// written by multiple parts of the application, so we need to make sure
|
||||
// there is only one instance storing that information.
|
||||
builder.RegisterType<SimulationRunner>().As<ISimulationRunner>().SingleInstance();
|
||||
|
||||
// Registrations required by Autofac, these classes implement the same interface
|
||||
builder.RegisterType<Connect>().As<Connect>();
|
||||
builder.RegisterType<DeviceTwinTag>().As<DeviceTwinTag>();
|
||||
|
|
|
@ -10,6 +10,7 @@ using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Diagnostics;
|
|||
using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.IotHub;
|
||||
using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Runtime;
|
||||
using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.StorageAdapter;
|
||||
using Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent;
|
||||
using Microsoft.Azure.IoTSolutions.DeviceSimulation.WebService.v1.Filters;
|
||||
using Microsoft.Azure.IoTSolutions.DeviceSimulation.WebService.v1.Models;
|
||||
|
||||
|
@ -27,6 +28,7 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.WebService.v1.Controller
|
|||
private const string PREPROVISIONED_IOTHUB_KEY = "PreprovisionedIoTHub";
|
||||
private const string PREPROVISIONED_IOTHUB_INUSE_KEY = "PreprovisionedIoTHubInUse";
|
||||
private const string PREPROVISIONED_IOTHUB_METRICS_KEY = "PreprovisionedIoTHubMetricsUrl";
|
||||
private const string ACTIVE_DEVICES_COUNT_KEY = "ActiveDevicesCount";
|
||||
|
||||
private readonly IPreprovisionedIotHub preprovisionedIotHub;
|
||||
private readonly IStorageAdapterClient storage;
|
||||
|
@ -35,6 +37,7 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.WebService.v1.Controller
|
|||
private readonly IServicesConfig servicesConfig;
|
||||
private readonly IDeploymentConfig deploymentConfig;
|
||||
private readonly IIotHubConnectionStringManager connectionStringManager;
|
||||
private readonly ISimulationRunner simulationRunner;
|
||||
|
||||
public StatusController(
|
||||
IPreprovisionedIotHub preprovisionedIotHub,
|
||||
|
@ -43,7 +46,8 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.WebService.v1.Controller
|
|||
ILogger logger,
|
||||
IServicesConfig servicesConfig,
|
||||
IDeploymentConfig deploymentConfig,
|
||||
IIotHubConnectionStringManager connectionStringManager)
|
||||
IIotHubConnectionStringManager connectionStringManager,
|
||||
ISimulationRunner simulationRunner)
|
||||
{
|
||||
this.preprovisionedIotHub = preprovisionedIotHub;
|
||||
this.storage = storage;
|
||||
|
@ -52,6 +56,7 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.WebService.v1.Controller
|
|||
this.servicesConfig = servicesConfig;
|
||||
this.deploymentConfig = deploymentConfig;
|
||||
this.connectionStringManager = connectionStringManager;
|
||||
this.simulationRunner = simulationRunner;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
|
@ -97,6 +102,10 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.WebService.v1.Controller
|
|||
}
|
||||
}
|
||||
|
||||
// Active devices status
|
||||
string activeDevicesCount = this.GetActiveDevicesCount(isRunning).ToString();
|
||||
result.Properties.Add(ACTIVE_DEVICES_COUNT_KEY, activeDevicesCount);
|
||||
|
||||
// Prepare status message and response
|
||||
if (!statusIsOk)
|
||||
{
|
||||
|
@ -219,5 +228,12 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.WebService.v1.Controller
|
|||
$"/resourceGroups/{this.deploymentConfig.AzureResourceGroup}" +
|
||||
$"/providers/Microsoft.Devices/IotHubs/{this.deploymentConfig.AzureIothubName}/Metrics";
|
||||
}
|
||||
|
||||
private int GetActiveDevicesCount(bool isRunning)
|
||||
{
|
||||
if (!isRunning) return 0;
|
||||
|
||||
return this.simulationRunner.GetActiveDevicesCount();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче