# -*- coding: utf-8 -*-
from __future__ import absolute_import
+import time
import cherrypy
from . import ApiController, RESTController, Endpoint, ReadPermission, Task
from ..services.ceph_service import CephService
from ..services.rbd import RbdConfiguration
from ..services.exception import handle_send_command_error
-from ..tools import str_to_bool
+from ..tools import str_to_bool, TaskManager
def pool_task(name, metadata, wait_for=2.0):
def set(self, pool_name, flags=None, application_metadata=None, configuration=None, **kwargs):
self._set_pool_values(pool_name, application_metadata, flags, True, kwargs)
RbdConfiguration(pool_name).set_configuration(configuration)
+ self._wait_for_pgs(pool_name)
@pool_task('create', {'pool_name': '{pool}'})
@handle_send_command_error('pool')
rule=rule_name)
self._set_pool_values(pool, application_metadata, flags, False, kwargs)
RbdConfiguration(pool).set_configuration(configuration)
+ self._wait_for_pgs(pool)
def _set_pool_values(self, pool, application_metadata, flags, update_existing, kwargs):
update_name = False
reset_arg(arg, '0')
reset_arg('compression_algorithm', 'unset')
+ def _wait_for_pgs(self, pool_name):
+ """
+ Keep the task waiting for until all pg changes are complete
+ :param pool_name: The name of the pool.
+ :type pool_name: string
+ """
+ current_pool = self._get(pool_name)
+ initial_pgs = int(current_pool['pg_placement_num']) + int(current_pool['pg_num'])
+ self._pg_wait_loop(current_pool, initial_pgs)
+
+ def _pg_wait_loop(self, pool, initial_pgs):
+ """
+ Compares if all pg changes are completed, if not it will call itself
+ until all changes are completed.
+ :param pool: The dict that represents a pool.
+ :type pool: dict
+ :param initial_pgs: The pg and pg_num count before any change happened.
+ :type initial_pgs: int
+ """
+ if 'pg_num_target' in pool:
+ target = int(pool['pg_num_target']) + int(pool['pg_placement_num_target'])
+ current = int(pool['pg_placement_num']) + int(pool['pg_num'])
+ if current != target:
+ max_diff = abs(target - initial_pgs)
+ diff = max_diff - abs(target - current)
+ percentage = int(round(diff / float(max_diff) * 100))
+ TaskManager.current_task().set_progress(percentage)
+ time.sleep(4)
+ self._pg_wait_loop(self._get(pool['pool_name']), initial_pgs)
+
@RESTController.Resource()
@ReadPermission
def configuration(self, pool_name):
let fixture: ComponentFixture<PoolListComponent>;
let poolService: PoolService;
- const addPool = (pools, name, id) => {
- const pool = new Pool(name);
- pool.pool = id;
- pool.pg_num = 256;
- pools.push(pool);
+ const createPool = (name, id): Pool => {
+ return _.merge(new Pool(name), {
+ pool: id,
+ pg_num: 256,
+ pg_placement_num: 256,
+ pg_num_target: 256,
+ pg_placement_num_target: 256
+ });
};
- const setUpPools = (pools) => {
- addPool(pools, 'a', 0);
- addPool(pools, 'b', 1);
- addPool(pools, 'c', 2);
- component.pools = pools;
+ const getPoolList = (): Pool[] => {
+ return [createPool('a', 0), createPool('b', 1), createPool('c', 2)];
};
configureTestBed({
component = fixture.componentInstance;
component.permissions.pool.read = true;
poolService = TestBed.get(PoolService);
+ spyOn(poolService, 'getList').and.callFake(() => of(getPoolList()));
fixture.detectChanges();
});
expect(component.columns.every((column) => Boolean(column.prop))).toBeTruthy();
});
+ it('returns pool details correctly', () => {
+ const pool = { prop1: 1, cdIsBinary: true, prop2: 2, cdExecuting: true, prop3: 3 };
+ const expected = { prop1: 1, prop2: 2, prop3: 3 };
+ expect(component.getPoolDetails(pool)).toEqual(expected);
+ });
+
describe('monAllowPoolDelete', () => {
let configurationService: ConfigurationService;
});
describe('handling of executing tasks', () => {
- let pools: Pool[];
let summaryService: SummaryService;
const addTask = (name: string, pool: string) => {
beforeEach(() => {
summaryService = TestBed.get(SummaryService);
summaryService['summaryDataSource'].next({ executing_tasks: [], finished_tasks: [] });
- pools = [];
- setUpPools(pools);
- spyOn(poolService, 'getList').and.callFake(() => of(pools));
- fixture.detectChanges();
});
it('gets all pools without executing pools', () => {
});
describe('transformPoolsData', () => {
- it('transforms pools data correctly', () => {
- const pools = [
- {
- stats: {
- bytes_used: { latest: 5, rate: 0, rates: [] },
- max_avail: { latest: 15, rate: 0, rates: [] },
- rd_bytes: { latest: 6, rate: 4, rates: [[0, 2], [1, 6]] }
- },
- pg_status: { 'active+clean': 8, down: 2 }
- }
- ];
- const expected = [
- {
- cdIsBinary: true,
- pg_status: '8 active+clean, 2 down',
- stats: {
- bytes_used: { latest: 5, rate: 0, rates: [] },
- max_avail: { latest: 15, rate: 0, rates: [] },
- rd: { latest: 0, rate: 0, rates: [] },
- rd_bytes: { latest: 6, rate: 4, rates: [2, 6] },
- wr: { latest: 0, rate: 0, rates: [] },
- wr_bytes: { latest: 0, rate: 0, rates: [] }
- },
- usage: 0.25
- }
- ];
- expect(component.transformPoolsData(pools)).toEqual(expected);
- });
+ let pool: Pool;
- it('transforms pools data correctly if stats are missing', () => {
- const pools = [{}];
- const expected = [
- {
+ const getPoolData = (o) => [
+ _.merge(
+ _.merge(createPool('a', 0), {
cdIsBinary: true,
pg_status: '',
stats: {
wr_bytes: { latest: 0, rate: 0, rates: [] }
},
usage: 0
- }
- ];
- expect(component.transformPoolsData(pools)).toEqual(expected);
+ }),
+ o
+ )
+ ];
+
+ beforeEach(() => {
+ pool = createPool('a', 0);
+ });
+
+ it('transforms pools data correctly', () => {
+ pool = _.merge(pool, {
+ stats: {
+ bytes_used: { latest: 5, rate: 0, rates: [] },
+ max_avail: { latest: 15, rate: 0, rates: [] },
+ rd_bytes: { latest: 6, rate: 4, rates: [[0, 2], [1, 6]] }
+ },
+ pg_status: { 'active+clean': 8, down: 2 }
+ });
+ expect(component.transformPoolsData([pool])).toEqual(
+ getPoolData({
+ pg_status: '8 active+clean, 2 down',
+ stats: {
+ bytes_used: { latest: 5, rate: 0, rates: [] },
+ max_avail: { latest: 15, rate: 0, rates: [] },
+ rd_bytes: { latest: 6, rate: 4, rates: [2, 6] }
+ },
+ usage: 0.25
+ })
+ );
+ });
+
+ it('transforms pools data correctly if stats are missing', () => {
+ expect(component.transformPoolsData([pool])).toEqual(getPoolData({}));
});
it('transforms empty pools data correctly', () => {
- const pools = undefined;
- const expected = undefined;
- expect(component.transformPoolsData(pools)).toEqual(expected);
+ expect(component.transformPoolsData(undefined)).toEqual(undefined);
+ expect(component.transformPoolsData([])).toEqual([]);
+ });
+
+ it('shows not marked pools in progress if pg_num does not match pg_num_target', () => {
+ const pools = [
+ _.merge(pool, {
+ pg_num: 32,
+ pg_num_target: 16,
+ pg_placement_num: 32,
+ pg_placement_num_target: 16
+ })
+ ];
+ expect(component.transformPoolsData(pools)).toEqual(
+ getPoolData({
+ cdExecuting: 'Updating',
+ pg_num: 32,
+ pg_num_target: 16,
+ pg_placement_num: 32,
+ pg_placement_num_target: 16
+ })
+ );
+ });
+
+ it('shows marked pools in progress as defined by task', () => {
+ const pools = [
+ _.merge(pool, {
+ pg_num: 32,
+ pg_num_target: 16,
+ pg_placement_num: 32,
+ pg_placement_num_target: 16,
+ cdExecuting: 'Updating 50%'
+ })
+ ];
+ expect(component.transformPoolsData(pools)).toEqual(
+ getPoolData({
+ cdExecuting: 'Updating 50%',
+ pg_num: 32,
+ pg_num_target: 16,
+ pg_placement_num: 32,
+ pg_placement_num_target: 16
+ })
+ );
});
});
});
});
- describe('getPoolDetails', () => {
- it('returns pool details corretly', () => {
- const pool = { prop1: 1, cdIsBinary: true, prop2: 2, cdExecuting: true, prop3: 3 };
- const expected = { prop1: 1, prop2: 2, prop3: 3 };
-
- expect(component.getPoolDetails(pool)).toEqual(expected);
- });
- });
-
describe('getSelectionTiers', () => {
- let pools: Pool[];
const setSelectionTiers = (tiers: number[]) => {
component.selection.selected = [
{
};
beforeEach(() => {
- pools = [];
- setUpPools(pools);
+ component.pools = getPoolList();
});
it('should select multiple existing cache tiers', () => {
setSelectionTiers([0, 1, 2]);
- expect(component.selectionCacheTiers).toEqual(pools);
+ expect(component.selectionCacheTiers).toEqual(getPoolList());
});
it('should select correct existing cache tier', () => {
setSelectionTiers([0]);
- expect(component.selectionCacheTiers).toEqual([{ pg_num: 256, pool: 0, pool_name: 'a' }]);
+ expect(component.selectionCacheTiers).toEqual([createPool('a', 0)]);
});
it('should not select cache tier if id is invalid', () => {
it('should be able to selected one pool with multiple tiers, than with a single tier, than with no tiers', () => {
setSelectionTiers([0, 1, 2]);
- expect(component.selectionCacheTiers).toEqual(pools);
+ expect(component.selectionCacheTiers).toEqual(getPoolList());
setSelectionTiers([0]);
- expect(component.selectionCacheTiers).toEqual([{ pg_num: 256, pool: 0, pool_name: 'a' }]);
+ expect(component.selectionCacheTiers).toEqual([createPool('a', 0)]);
setSelectionTiers([]);
expect(component.selectionCacheTiers).toEqual([]);
});
--- /dev/null
+# -*- coding: utf-8 -*-
+# pylint: disable=protected-access
+import mock
+
+from . import ControllerTestCase
+from ..controllers.pool import Pool
+
+
+class MockTask(object):
+ percentages = []
+
+ def set_progress(self, percentage):
+ self.percentages.append(percentage)
+
+
+class PoolControllerTest(ControllerTestCase):
+ @classmethod
+ def setup_server(cls):
+ Pool._cp_config['tools.authenticate.on'] = False
+ cls.setup_controllers([Pool])
+
+ def test_creation(self):
+ self._post('/api/pool', {
+ 'pool': 'test-pool',
+ 'pool_type': 1,
+ 'pg_num': 64
+ })
+ self.assertStatus(202)
+ self.assertJsonBody({'name': 'pool/create', 'metadata': {'pool_name': 'test-pool'}})
+
+ @mock.patch('dashboard.controllers.pool.Pool._get')
+ def test_wait_for_pgs_without_waiting(self, _get):
+ _get.side_effect = [{
+ 'pool_name': 'test-pool',
+ 'pg_num': 32,
+ 'pg_num_target': 32,
+ 'pg_placement_num': 32,
+ 'pg_placement_num_target': 32
+ }]
+ pool = Pool()
+ pool._wait_for_pgs('test-pool')
+ self.assertEqual(_get.call_count, 1)
+
+ @mock.patch('dashboard.controllers.pool.Pool._get')
+ @mock.patch('dashboard.tools.TaskManager.current_task')
+ def test_wait_for_pgs_with_waiting(self, taskMock, _get):
+ task = MockTask()
+ taskMock.return_value = task
+ _get.side_effect = [{
+ 'pool_name': 'test-pool',
+ 'pg_num': 64,
+ 'pg_num_target': 32,
+ 'pg_placement_num': 64,
+ 'pg_placement_num_target': 64
+ }, {
+ 'pool_name': 'test-pool',
+ 'pg_num': 63,
+ 'pg_num_target': 32,
+ 'pg_placement_num': 62,
+ 'pg_placement_num_target': 32
+ }, {
+ 'pool_name': 'test-pool',
+ 'pg_num': 48,
+ 'pg_num_target': 32,
+ 'pg_placement_num': 48,
+ 'pg_placement_num_target': 32
+ }, {
+ 'pool_name': 'test-pool',
+ 'pg_num': 48,
+ 'pg_num_target': 32,
+ 'pg_placement_num': 33,
+ 'pg_placement_num_target': 32
+ }, {
+ 'pool_name': 'test-pool',
+ 'pg_num': 33,
+ 'pg_num_target': 32,
+ 'pg_placement_num': 32,
+ 'pg_placement_num_target': 32
+ }, {
+ 'pool_name': 'test-pool',
+ 'pg_num': 32,
+ 'pg_num_target': 32,
+ 'pg_placement_num': 32,
+ 'pg_placement_num_target': 32
+ }]
+ pool = Pool()
+ pool._wait_for_pgs('test-pool')
+ self.assertEqual(_get.call_count, 6)
+ self.assertEqual(task.percentages, [0, 5, 50, 73, 98])