def enterprise_two_clients_bootstrapped_with_gateway(request): env = container_factory.get_enterprise_setup_with_gateway(num_clients=0) request.addfinalizer(env.teardown) env.setup() reset_mender_api(env) tenant = create_tenant(env) tenant = create_tenant(env) new_tenant_client(env, "mender-client-1", tenant["tenant_token"], network="mender_local") new_tenant_client(env, "mender-client-2", tenant["tenant_token"], network="mender_local") env.device_group.ssh_is_opened() env.start_tenant_mender_gateway(tenant["tenant_token"]) env.device_gateway = MenderDevice( env.get_mender_gateways(network="mender").pop()) env.device_gateway.ssh_is_opened() devauth_tenant = DeviceAuthV2(env.auth) # Three devices: two client devices and the gateway device (which also runs mender client) devauth_tenant.accept_devices(3) devices = devauth_tenant.get_devices_status("accepted") assert 3 == len(devices) return env
def enterprise_one_client(request): env = container_factory.get_enterprise_setup(num_clients=0) request.addfinalizer(env.teardown) env.setup() reset_mender_api(env) tenant = create_tenant(env) new_tenant_client(env, "mender-client", tenant["tenant_token"]) env.device_group.ssh_is_opened() return env
def prepare_env(self, env, user_name): u = User("", user_name, "whatsupdoc") cli = CliTenantadm(containers_namespace=env.name) uuidv4 = str(uuid.uuid4()) name = "test.mender.io-" + uuidv4 tid = cli.create_org(name, u.name, u.pwd, plan="enterprise") tenant = cli.get_tenant(tid) tenant = json.loads(tenant) auth = authentication.Authentication(name=name, username=u.name, password=u.pwd) auth.create_org = False auth.reset_auth_token() devauth_tenant = DeviceAuthV2(auth) mender_device = new_tenant_client(env, "test-container", tenant["tenant_token"]) mender_device.ssh_is_opened() devauth_tenant.accept_devices(1) logger.info("%s: env ready.", inspect.stack()[1].function) return mender_device
def enterprise_with_legacy_client(request): env = container_factory.get_enterprise_legacy_client_setup(num_clients=0) request.addfinalizer(env.teardown) env.setup() reset_mender_api(env) tenant = create_tenant(env) new_tenant_client(env, "mender-client", tenant["tenant_token"]) env.device_group.ssh_is_opened() devauth_tenant = DeviceAuthV2(env.auth) devauth_tenant.accept_devices(1) devices = devauth_tenant.get_devices_status("accepted") assert 1 == len(devices) return env
def enterprise_two_clients_bootstrapped(request): env = container_factory.get_enterprise_setup(num_clients=0) request.addfinalizer(env.teardown) env.setup() reset_mender_api(env) tenant = create_tenant(env) new_tenant_client(env, "mender-client-1", tenant["tenant_token"]) new_tenant_client(env, "mender-client-2", tenant["tenant_token"]) env.device_group.ssh_is_opened() devauth_tenant = DeviceAuthV2(env.auth) devauth_tenant.accept_devices(2) devices = devauth_tenant.get_devices_status("accepted", expected_devices=2) assert 2 == len(devices) return env
def enterprise_one_docker_client_bootstrapped(request): env = container_factory.getEnterpriseDockerClientSetup(num_clients=0) request.addfinalizer(env.teardown) env.setup() reset_mender_api(env) tenant = create_tenant(env) new_tenant_client(env, "mender-client", tenant["tenant_token"], docker=True) env.device_group.ssh_is_opened() devauth_tenant = DeviceAuthV2(env.auth) devauth_tenant.accept_devices(1) devices = devauth_tenant.get_devices_status("accepted") assert 1 == len(devices) return env
def _make_tenant(self, plan, env): uuidv4 = str(uuid.uuid4()) tname = "test.mender.io-{}-{}".format(uuidv4, plan) email = "some.user+{}@example.com".format(uuidv4) cli = CliTenantadm(containers_namespace=env.name) tid = cli.create_org(tname, email, "correcthorse", plan) tenant = cli.get_tenant(tid) tenant = json.loads(tenant) ttoken = tenant["tenant_token"] # the cli now sets all addons to 'enabled' - # disable them for initial 'all disabled' state update_tenant( tenant["id"], addons=[], container_manager=get_container_manager(), ) auth = Authentication(name=tname, username=email, password="******") auth.create_org = False auth.reset_auth_token() devauth = DeviceAuthV2(auth) new_tenant_client(env, "mender-client-{}".format(plan), ttoken) devauth.accept_devices(1) devices = list( set([device["id"] for device in devauth.get_devices_status("accepted")]) ) assert 1 == len(devices) tenant = Tenant(tname, tid, ttoken) u = User("", email, "correcthorse") tenant.users.append(u) tenant.device_id = devices[0] tenant.auth = auth tenant.devauth = devauth return tenant
def test_multi_tenancy_deployment(self, enterprise_no_client, valid_image_with_mender_conf): """Simply make sure we are able to run the multi tenancy setup and bootstrap 2 different devices to different tenants""" auth.reset_auth_token() uuidv4 = str(uuid.uuid4()) users = [ { "email": "*****@*****.**" % uuidv4, "password": "******", "username": "******" + uuidv4 + "-foo2", "container": "mender-client-deployment-1", }, { "email": "*****@*****.**" % uuidv4, "password": "******", "username": "******" + uuidv4 + "-bar2", "container": "mender-client-deployment-2", }, ] for user in users: auth.new_tenant(user["username"], user["email"], user["password"]) t = auth.current_tenant["tenant_token"] mender_device = new_tenant_client(enterprise_no_client, user["container"], t) devauth.accept_devices(1) assert len(inv.get_devices()) == 1 # By default the valid_image has a tenant_token=dummy in the mender.conf # Therefore, replace the 'mender.conf' in the image with the one from the # currently running image, which has a valid 'mender.conf'. mender_conf = mender_device.run("cat /etc/mender/mender.conf") logger.info( "Injecting the mender.conf into the update image:\n{conf}\n". format(conf=mender_conf)) update_image_name = valid_image_with_mender_conf(mender_conf) host_ip = enterprise_no_client.get_virtual_network_host_ip() update_image( mender_device, host_ip, install_image=update_image_name, )
def prepare_env_for_connect(env): uuidv4 = str(uuid.uuid4()) tname = "test.mender.io-{}".format(uuidv4) email = "some.user+{}@example.com".format(uuidv4) u = User("", email, "whatsupdoc") cli = CliTenantadm(containers_namespace=env.name) tid = cli.create_org(tname, u.name, u.pwd, plan="os") # FT requires "troubleshoot" update_tenant( tid, addons=["troubleshoot"], container_manager=get_container_manager(), ) tenant = cli.get_tenant(tid) tenant = json.loads(tenant) env.tenant = tenant auth = authentication.Authentication(name="os-tenant", username=u.name, password=u.pwd) auth.create_org = False auth.reset_auth_token() devauth_tenant = DeviceAuthV2(auth) mender_device = new_tenant_client(env, "mender-client", tenant["tenant_token"]) mender_device.ssh_is_opened() devauth_tenant.accept_devices(1) devices = devauth_tenant.get_devices_status("accepted") assert 1 == len(devices) devid = devices[0]["id"] authtoken = auth.get_auth_token() wait_for_connect(auth, devid) return devid, authtoken, auth, mender_device
def test_token_validity(self, enterprise_no_client): """verify that only devices with valid tokens can bootstrap successfully to a multitenancy setup""" wrong_token = "wrong-token" auth.reset_auth_token() uuidv4 = str(uuid.uuid4()) auth.new_tenant( "test.mender.io-" + uuidv4, "some.user+" + uuidv4 + "@example.com", "hunter2hunter2", ) token = auth.current_tenant["tenant_token"] # create a new client with an incorrect token set mender_device = new_tenant_client(enterprise_no_client, "mender-client", wrong_token) mender_device.ssh_is_opened() client_service_name = mender_device.get_client_service_name() mender_device.run( 'journalctl -u %s | grep "authentication request rejected server error message: Unauthorized"' % client_service_name, wait=70, ) for _ in range(5): time.sleep(5) devauth.get_devices( expected_devices=0) # make sure device not seen # setting the correct token makes the client visible to the backend mender_device.run("sed -i 's/%s/%s/g' /etc/mender/mender.conf" % (wrong_token, token)) mender_device.run("systemctl restart %s" % client_service_name) devauth.get_devices(expected_devices=1)
def _make_trial_tenant(self, env): uuidv4 = str(uuid.uuid4()) tname = "test.mender.io-{}-{}".format(uuidv4, "trial") email = "some.user+{}@example.com".format(uuidv4) tadmm = ApiClient( host=get_container_manager().get_mender_gateway(), base_url=tenantadm_v2.URL_MGMT, ) args = { "organization": tname, "email": email, "password": "******", "name": "foo", "g-recaptcha-response": "dummy", "plan": "enterprise", } res = tadmm.call("POST", tenantadm_v2.URL_CREATE_ORG_TRIAL, body=args,) assert res.status_code == 202 # get tenant id tenantadm_host = ( tenantadm.HOST if isK8S() else get_container_manager().get_ip_of_service("mender-tenantadm")[0] + ":8080" ) tadmi = ApiClient( host=tenantadm_host, base_url=tenantadm.URL_INTERNAL, schema="http://", ) res = tadmi.call( "GET", tenantadm.URL_INTERNAL_TENANTS, qs_params={"username": email} ) assert res.status_code == 200 assert len(res.json()) == 1 apitenant = res.json()[0] cli = CliTenantadm(containers_namespace=env.name) tenant = cli.get_tenant(apitenant["id"]) tenant = json.loads(tenant) ttoken = tenant["tenant_token"] auth = Authentication(name=tname, username=email, password="******") auth.create_org = False auth.reset_auth_token() devauth = DeviceAuthV2(auth) new_tenant_client(env, "mender-client-trial", ttoken) devauth.accept_devices(1) devices = list( set([device["id"] for device in devauth.get_devices_status("accepted")]) ) assert 1 == len(devices) tenant = Tenant(tname, apitenant["id"], ttoken) u = User("", email, "correcthorse") tenant.users.append(u) tenant.device_id = devices[0] tenant.auth = auth tenant.devauth = devauth return tenant
def test_update_control_limit( self, enterprise_no_client, valid_image_with_mender_conf, ): """MEN-5421: Test that the client gracefully handles being rate-limited by the Mender server. Set the rate-limit to 1 request/ 60 seconds for the deployments/next endpoint, then schedule an update with a pause in ArtifactReboot_Enter only, then continue, after the client has reported the paused substate back to the server, all the way until the deployment is successfully finished. """ uuidv4 = str(uuid.uuid4()) tname = "test.mender.io-{}".format(uuidv4) email = "some.user+{}@example.com".format(uuidv4) u = User("", email, "whatsupdoc") cli = CliTenantadm(containers_namespace=enterprise_no_client.name) tid = cli.create_org(tname, u.name, u.pwd, plan="enterprise") # Rate-limit the /deployments/next endpoint tc = ApiClient( tenantadm.URL_INTERNAL, host=get_container_manager().get_ip_of_service("mender-tenantadm") [0] + ":8080", schema="http://", ) r = tc.call( "PUT", tenantadm.URL_INTERNAL_TENANTS + "/" + tid, body={ "api_limits": { "devices": { "bursts": [{ "action": "POST", "uri": "/api/devices/v2/deployments/device/deployments/next", "min_interval_sec": 60, }], } } }, ) assert r.ok, "Failed to set the rate-limit on the 'deployements/next' endpoint" time.sleep(10) r = tc.call( "GET", tenantadm.URL_INTERNAL_TENANTS + "/" + tid, ) resp_json = r.json() if (resp_json.get("api_limits", {}).get("devices", {}).get( "bursts", [{}])[0].get("min_interval_sec") != 60): pytest.fail("rate limits not enabled. The test is invalid") tenant = cli.get_tenant(tid) tenant = json.loads(tenant) ttoken = tenant["tenant_token"] auth = Authentication(name="enterprise-tenant", username=u.name, password=u.pwd) auth.create_org = False auth.reset_auth_token() devauth = DeviceAuthV2(auth) device = new_tenant_client(enterprise_no_client, "mender-client", ttoken) devauth.accept_devices(1) deploy = Deployments(auth, devauth) devices = list( set([ device["id"] for device in devauth.get_devices_status("accepted") ])) assert 1 == len(devices) mender_conf = device.run("cat /etc/mender/mender.conf") with tempfile.NamedTemporaryFile() as artifact_file: image.make_rootfs_artifact( valid_image_with_mender_conf(mender_conf), conftest.machine_name, "test-update-control", artifact_file.name, ) deploy.upload_image(artifact_file.name, description="control map update test") deployment_id = deploy.trigger_deployment( name="New valid update", artifact_name="test-update-control", devices=devices, update_control_map={ "Priority": 1, "States": { "ArtifactReboot_Enter": { "action": "pause" } }, }, ) # Query the deployment, and verify that the map returned contains the # deployment ID res_json = deploy.get_deployment(deployment_id) assert deployment_id == res_json.get("update_control_map").get( "id"), res_json # Wait for the device to pause in ArtifactInstall deploy.check_expected_statistics(deployment_id, "pause_before_rebooting", 1) deploy.patch_deployment( deployment_id, update_control_map={ "Priority": 2, "States": { "ArtifactReboot_Enter": { "action": "force_continue" }, }, }, ) deploy.check_expected_status("finished", deployment_id) deploy.check_expected_statistics(deployment_id, "success", 1)
def test_deployment_retry_failed_update(self, enterprise_no_client): """Tests that a client installing a deployment created with a retry limit This is done through setting up a new tenant on the enterprise plan, with a device bootstrapped to the tenant. Then an Artifact is created which contains a script, for the script update module. The script will store a retry-count in a temp-file on the device, and fail, as long as the retry-count < 3. On the third go, the script will, pass, and along with it, so should the update. """ env = enterprise_no_client # Create an enterprise plan tenant uuidv4 = str(uuid.uuid4()) tname = "test.mender.io-{}".format(uuidv4) email = "some.user+{}@example.com".format(uuidv4) u = User("", email, "whatsupdoc") cli = CliTenantadm(containers_namespace=env.name) tid = cli.create_org(tname, u.name, u.pwd, plan="enterprise") tenant = cli.get_tenant(tid) tenant = json.loads(tenant) ttoken = tenant["tenant_token"] logger.info(f"tenant json: {tenant}") tenant = Tenant("tenant", tid, ttoken) tenant.users.append(u) # And authorize the user to the tenant account auth = Authentication(name="enterprise-tenant", username=u.name, password=u.pwd) auth.create_org = False auth.reset_auth_token() devauth = DeviceAuthV2(auth) deploy = Deployments(auth, devauth) # Add a client to the tenant device = new_tenant_client( enterprise_no_client, "mender-client", tenant.tenant_token ) devauth.accept_devices(1) with tempfile.NamedTemporaryFile() as tf: artifact = make_script_artifact( "retry-artifact", conftest.machine_name, tf.name ) deploy.upload_image(artifact) devices = list( set([device["id"] for device in devauth.get_devices_status("accepted")]) ) assert len(devices) == 1 deployment_id = deploy.trigger_deployment( "retry-test", artifact_name="retry-artifact", devices=devices, retries=3 ) logger.info(deploy.get_deployment(deployment_id)) # Now just wait for the update to succeed deploy.check_expected_statistics(deployment_id, "success", 1) deploy.check_expected_status("finished", deployment_id) # Verify the update was actually installed on the device out = device.run("mender show-artifact").strip() assert out == "retry-artifact" # Verify the number of attempts taken to install the update out = device.run("cat /tmp/retry-attempts").strip() assert out == "3"
def test_configuration(self, enterprise_no_client): """Tests the deployment and reporting of the configuration The tests set the configuration of a device and verifies the new configuration is reported back to the back-end. """ env = enterprise_no_client # Create an enterprise plan tenant uuidv4 = str(uuid.uuid4()) tname = "test.mender.io-{}".format(uuidv4) email = "some.user+{}@example.com".format(uuidv4) u = User("", email, "whatsupdoc") cli = CliTenantadm(containers_namespace=env.name) tid = cli.create_org(tname, u.name, u.pwd, plan="enterprise") # what we really need is "configure" # but for trigger tests we're also checking device avail. in "deviceconnect" # so add "troubleshoot" as well update_tenant( tid, addons=["configure", "troubleshoot"], container_manager=get_container_manager(), ) tenant = cli.get_tenant(tid) tenant = json.loads(tenant) ttoken = tenant["tenant_token"] logger.info(f"tenant json: {tenant}") tenant = Tenant("tenant", tid, ttoken) tenant.users.append(u) # And authorize the user to the tenant account auth = Authentication(name="enterprise-tenant", username=u.name, password=u.pwd) auth.create_org = False auth.reset_auth_token() devauth_tenant = DeviceAuthV2(auth) # Add a client to the tenant mender_device = new_tenant_client(enterprise_no_client, "mender-client", tenant.tenant_token) mender_device.ssh_is_opened() devauth_tenant.accept_devices(1) # list of devices devices = list( set([ device["id"] for device in devauth_tenant.get_devices_status("accepted") ])) assert 1 == len(devices) wait_for_connect(auth, devices[0]) # set and verify the device's configuration # retry to skip possible race conditions between update poll and update trigger for _ in redo.retrier(attempts=3, sleeptime=1): set_and_verify_config({"key": "value"}, devices[0], auth.get_auth_token()) forced = was_update_forced(mender_device) if forced: return assert False, "the update check was never triggered"
def test_update_control_with_broken_map( self, enterprise_no_client, valid_image, ): """ Schedule an update with an invalid map, which should fail. """ uuidv4 = str(uuid.uuid4()) tname = "test.mender.io-{}".format(uuidv4) email = "some.user+{}@example.com".format(uuidv4) u = User("", email, "whatsupdoc") cli = CliTenantadm(containers_namespace=enterprise_no_client.name) tid = cli.create_org(tname, u.name, u.pwd, plan="enterprise") tenant = cli.get_tenant(tid) tenant = json.loads(tenant) ttoken = tenant["tenant_token"] auth = Authentication(name="enterprise-tenant", username=u.name, password=u.pwd) auth.create_org = False auth.reset_auth_token() devauth = DeviceAuthV2(auth) new_tenant_client(enterprise_no_client, "mender-client", ttoken) devauth.accept_devices(1) deploy = Deployments(auth, devauth) devices = list( set([ device["id"] for device in devauth.get_devices_status("accepted") ])) assert 1 == len(devices) with tempfile.NamedTemporaryFile() as artifact_file: image.make_rootfs_artifact( valid_image, conftest.machine_name, "test-update-control", artifact_file.name, ) deploy.upload_image(artifact_file.name, description="control map update test") deployment_id = deploy.trigger_deployment( name="New valid update", artifact_name="test-update-control", devices=devices, update_control_map={ "Priority": 1, "States": { "BogusState_Enter": { "action": "pause" } }, }, ) # Query the deployment, and verify that the map returned contains the # deployment ID res_json = deploy.get_deployment(deployment_id) assert deployment_id == res_json.get("update_control_map").get( "id"), res_json # Wait for the device to reject it and fail. deploy.check_expected_status("finished", deployment_id) deploy.check_expected_statistics(deployment_id, "failure", 1)
def test_update_control_with_expiring_control_map( self, enterprise_no_client, valid_image_with_mender_conf, ): """Run an update, in which the download stage takes longer than the expiry time of the control map. In other words, test MEN-5096. This is done by having an Artifact script pause in Download for a time longer than the UpdateControlMapExpiration time. This will only pass if the client renewes the control map. """ user_name = "ci.email.tests+{}@mender.io".format(str(uuid.uuid4())) u = User("", user_name, "whatsupdoc") cli = CliTenantadm(containers_namespace=enterprise_no_client.name) tid = cli.create_org("enterprise-tenant", u.name, u.pwd, "enterprise") tenant = cli.get_tenant(tid) tenant = json.loads(tenant) ttoken = tenant["tenant_token"] auth = Authentication(name="enterprise-tenant", username=u.name, password=u.pwd) auth.create_org = False auth.reset_auth_token() devauth = DeviceAuthV2(auth) device = new_tenant_client(enterprise_no_client, "mender-client", ttoken) devauth.accept_devices(1) deploy = Deployments(auth, devauth) devices = list( set([ device["id"] for device in devauth.get_devices_status("accepted") ])) assert 1 == len(devices) mender_conf = device.run("cat /etc/mender/mender.conf") with tempfile.NamedTemporaryFile(prefix="Download_Leave_01_", mode="w") as sleep_script: expiration = int( json.loads(mender_conf) ["UpdateControlMapExpirationTimeSeconds"]) sleep_script.writelines( ["#! /bin/bash\n", "sleep {}".format(expiration + 60)]) sleep_script.flush() os.fchmod(sleep_script.fileno(), 0o0755) device.put( os.path.basename(sleep_script.name), local_path=os.path.dirname(sleep_script.name), remote_path="/etc/mender/scripts/", ) with tempfile.NamedTemporaryFile() as artifact_file: created_artifact = image.make_rootfs_artifact( valid_image_with_mender_conf(mender_conf), conftest.machine_name, "test-update-control", artifact_file.name, ) deploy.upload_image(artifact_file.name, description="control map update test") deployment_id = deploy.trigger_deployment( name="New valid update", artifact_name="test-update-control", devices=devices, update_control_map={ "Priority": 1, "States": { "ArtifactInstall_Enter": { "action": "pause", }, }, }, ) # Query the deployment, and verify that the map returned contains the # deployment ID res_json = deploy.get_deployment(deployment_id) assert deployment_id == res_json.get("update_control_map").get( "id"), res_json deploy.check_expected_statistics(deployment_id, "pause_before_installing", 1) deploy.patch_deployment( deployment_id, update_control_map={ "Priority": 2, "States": { "ArtifactInstall_Enter": { "action": "force_continue", }, }, }, ) deploy.check_expected_status("finished", deployment_id) deploy.check_expected_statistics(deployment_id, "success", 1)
def test_update_control( self, enterprise_no_client, valid_image_with_mender_conf, ): """ Schedule an update with a pause in ArtifactInstall_Enter, ArtifactReboot_Enter, and ArtifactCommit_Enter, then continue, after the client has reported the paused substate back to the server, all the way until the deployment is successfully finished. """ uuidv4 = str(uuid.uuid4()) tname = "test.mender.io-{}".format(uuidv4) email = "some.user+{}@example.com".format(uuidv4) u = User("", email, "whatsupdoc") cli = CliTenantadm(containers_namespace=enterprise_no_client.name) tid = cli.create_org(tname, u.name, u.pwd, plan="enterprise") tenant = cli.get_tenant(tid) tenant = json.loads(tenant) ttoken = tenant["tenant_token"] auth = Authentication(name="enterprise-tenant", username=u.name, password=u.pwd) auth.create_org = False auth.reset_auth_token() devauth = DeviceAuthV2(auth) device = new_tenant_client(enterprise_no_client, "mender-client", ttoken) devauth.accept_devices(1) deploy = Deployments(auth, devauth) devices = list( set([ device["id"] for device in devauth.get_devices_status("accepted") ])) assert 1 == len(devices) mender_conf = device.run("cat /etc/mender/mender.conf") with tempfile.NamedTemporaryFile() as artifact_file: image.make_rootfs_artifact( valid_image_with_mender_conf(mender_conf), conftest.machine_name, "test-update-control", artifact_file.name, ) deploy.upload_image(artifact_file.name, description="control map update test") deployment_id = deploy.trigger_deployment( name="New valid update", artifact_name="test-update-control", devices=devices, update_control_map={ "Priority": 1, "States": { "ArtifactInstall_Enter": { "action": "pause" } }, }, ) # Query the deployment, and verify that the map returned contains the # deployment ID res_json = deploy.get_deployment(deployment_id) assert deployment_id == res_json.get("update_control_map").get( "id"), res_json # Wait for the device to pause in ArtifactInstall deploy.check_expected_statistics(deployment_id, "pause_before_installing", 1) deploy.patch_deployment( deployment_id, update_control_map={ "Priority": 2, "States": { "ArtifactInstall_Enter": { "action": "force_continue" }, "ArtifactReboot_Enter": { "action": "pause" }, }, }, ) # Wait for the device to pause in ArtifactReboot deploy.check_expected_statistics(deployment_id, "pause_before_rebooting", 1) deploy.patch_deployment( deployment_id, update_control_map={ "Priority": 2, "States": { "ArtifactReboot_Enter": { "action": "force_continue" }, "ArtifactCommit_Enter": { "action": "pause" }, }, }, ) # Wait for the device to pause in ArtifactCommit deploy.check_expected_statistics(deployment_id, "pause_before_committing", 1) deploy.patch_deployment( deployment_id, update_control_map={ "Priority": 2, "States": { "ArtifactCommit_Enter": { "action": "force_continue" } }, }, ) deploy.check_expected_status("finished", deployment_id) deploy.check_expected_statistics(deployment_id, "success", 1)