class HostControllerNoOrchestratorTest(DashboardTestCase):
def test_host_create(self):
- self._post('/api/host?hostname=foo', {'status': ''})
+ self._post('/api/host?hostname=foo', {'status': ''}, version='0.1')
self.assertStatus(503)
self.assertError(code='orchestrator_status_unavailable',
component='orchestrator')
@allow_empty_body
-def add_host(hostname: str, status: Optional[str] = None):
+def add_host(hostname: str, addr: Optional[str] = None,
+ labels: Optional[List[str]] = None,
+ status: Optional[str] = None):
orch_client = OrchClient.instance()
host = Host()
host.check_orchestrator_host_op(orch_client, hostname)
- orch_client.hosts.add(hostname)
+ orch_client.hosts.add(hostname, addr, labels)
if status == 'maintenance':
orch_client.hosts.enter_maintenance(hostname)
@EndpointDoc('',
parameters={
'hostname': (str, 'Hostname'),
- 'status': (str, 'Host Status')
+ 'addr': (str, 'Network Address'),
+ 'labels': ([str], 'Host Labels'),
+ 'status': (str, 'Host Status'),
},
responses={200: None, 204: None})
+ @RESTController.MethodMap(version='0.1')
def create(self, hostname: str,
+ addr: Optional[str] = None,
+ labels: Optional[List[str]] = None,
status: Optional[str] = None): # pragma: no cover - requires realtime env
- add_host(hostname, status)
+ add_host(hostname, addr, labels, status)
@raise_if_no_orchestrator([OrchFeature.HOST_LIST, OrchFeature.HOST_DELETE])
@handle_orchestrator_error('host')
'force': (bool, 'Force Enter Maintenance')
},
responses={200: None, 204: None})
+ @RESTController.MethodMap(version='0.1')
def set(self, hostname: str, update_labels: bool = False,
labels: List[str] = None, maintenance: bool = False,
force: bool = False):
</div>
</div>
+ <!-- Address -->
+ <div class="form-group row">
+ <label class="cd-col-form-label"
+ for="addr"
+ i18n>Nework address</label>
+ <div class="cd-col-form-input">
+ <input class="form-control"
+ type="text"
+ placeholder="192.168.0.1"
+ id="addr"
+ name="addr"
+ formControlName="addr">
+ <span class="invalid-feedback"
+ *ngIf="hostForm.showError('addr', formDir, 'pattern')"
+ i18n>The value is not a valid IP address.</span>
+ </div>
+ </div>
+
+ <!-- Labels -->
+ <div class="form-group row">
+ <label i18n
+ for="labels"
+ class="cd-col-form-label">Labels</label>
+ <div class="cd-col-form-input">
+ <cd-select-badges id="labels"
+ [data]="hostForm.controls.labels.value"
+ [customBadges]="true"
+ [messages]="messages">
+ </cd-select-badges>
+ </div>
+ </div>
+
<!-- Maintenance Mode -->
<div class="form-group row">
<div class="cd-col-form-offset">
import { HttpClientTestingModule } from '@angular/common/http/testing';
-import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing';
import { ReactiveFormsModule } from '@angular/forms';
import { RouterTestingModule } from '@angular/router/testing';
import { LoadingPanelComponent } from '~/app/shared/components/loading-panel/loading-panel.component';
import { SharedModule } from '~/app/shared/shared.module';
-import { configureTestBed } from '~/testing/unit-test-helper';
+import { configureTestBed, FormHelper } from '~/testing/unit-test-helper';
import { HostFormComponent } from './host-form.component';
describe('HostFormComponent', () => {
let component: HostFormComponent;
let fixture: ComponentFixture<HostFormComponent>;
+ let formHelper: FormHelper;
configureTestBed(
{
beforeEach(() => {
fixture = TestBed.createComponent(HostFormComponent);
component = fixture.componentInstance;
+ formHelper = new FormHelper(component.hostForm);
fixture.detectChanges();
});
expect(component).toBeTruthy();
});
+ it('should validate the network address is valid', fakeAsync(() => {
+ formHelper.setValue('addr', '115.42.150.37', true);
+ tick();
+ formHelper.expectValid('addr');
+ }));
+
+ it('should show error if network address is invalid', fakeAsync(() => {
+ formHelper.setValue('addr', '666.10.10.20', true);
+ tick();
+ formHelper.expectError('addr', 'pattern');
+ }));
+
+ it('should submit the network address', () => {
+ component.hostForm.get('addr').setValue('127.0.0.1');
+ fixture.detectChanges();
+ component.submit();
+ expect(component.addr).toBe('127.0.0.1');
+ });
+
+ it('should validate the labels are added', () => {
+ const labels = ['label1', 'label2'];
+ component.hostForm.get('labels').patchValue(labels);
+ fixture.detectChanges();
+ component.submit();
+ expect(component.allLabels).toBe(labels);
+ });
+
it('should select maintenance mode', () => {
component.hostForm.get('maintenance').setValue(true);
fixture.detectChanges();
import { Router } from '@angular/router';
import { HostService } from '~/app/shared/api/host.service';
+import { SelectMessages } from '~/app/shared/components/select/select-messages.model';
import { ActionLabelsI18n, URLVerbs } from '~/app/shared/constants/app.constants';
import { CdForm } from '~/app/shared/forms/cd-form';
import { CdFormGroup } from '~/app/shared/forms/cd-form-group';
action: string;
resource: string;
hostnames: string[];
+ addr: string;
status: string;
+ allLabels: any;
+
+ messages = new SelectMessages({
+ empty: $localize`There are no labels.`,
+ filter: $localize`Filter or add labels`,
+ add: $localize`Add label`
+ });
constructor(
private router: Router,
})
]
}),
+ addr: new FormControl('', {
+ validators: [CdValidators.ip()]
+ }),
+ labels: new FormControl([]),
maintenance: new FormControl(false)
});
}
submit() {
const hostname = this.hostForm.get('hostname').value;
+ this.addr = this.hostForm.get('addr').value;
this.status = this.hostForm.get('maintenance').value ? 'maintenance' : '';
+ this.allLabels = this.hostForm.get('labels').value;
this.taskWrapper
.wrapTaskAroundCall({
task: new FinishedTask('host/' + URLVerbs.CREATE, {
hostname: hostname
}),
- call: this.hostService.create(hostname, this.status)
+ call: this.hostService.create(hostname, this.addr, this.allLabels, this.status)
})
.subscribe({
error: () => {
return this.http.get<object[]>(this.baseURL);
}
- create(hostname: string, status: string) {
+ create(hostname: string, addr: string, labels: string[], status: string) {
return this.http.post(
this.baseURL,
- { hostname: hostname, status: status },
- { observe: 'response' }
+ { hostname: hostname, addr: addr, labels: labels, status: status },
+ { observe: 'response', headers: { Accept: 'application/vnd.ceph.api.v0.1+json' } }
);
}
maintenance = false,
force = false
) {
- return this.http.put(`${this.baseURL}/${hostname}`, {
- update_labels: updateLabels,
- labels: labels,
- maintenance: maintenance,
- force: force
- });
+ return this.http.put(
+ `${this.baseURL}/${hostname}`,
+ {
+ update_labels: updateLabels,
+ labels: labels,
+ maintenance: maintenance,
+ force: force
+ },
+ { headers: { Accept: 'application/vnd.ceph.api.v0.1+json' } }
+ );
}
identifyDevice(hostname: string, device: string, duration: number) {
application/json:
schema:
properties:
+ addr:
+ description: Network Address
+ type: string
hostname:
description: Hostname
type: string
+ labels:
+ description: Host Labels
+ items:
+ type: string
+ type: array
status:
description: Host Status
type: string
responses:
'201':
content:
- application/vnd.ceph.api.v1.0+json:
+ application/vnd.ceph.api.v0.1+json:
type: object
description: Resource created.
'202':
content:
- application/vnd.ceph.api.v1.0+json:
+ application/vnd.ceph.api.v0.1+json:
type: object
description: Operation is still executing. Please check the task queue.
'400':
responses:
'200':
content:
- application/vnd.ceph.api.v1.0+json:
+ application/vnd.ceph.api.v0.1+json:
schema:
properties: {}
type: object
description: Resource updated.
'202':
content:
- application/vnd.ceph.api.v1.0+json:
+ application/vnd.ceph.api.v0.1+json:
type: object
description: Operation is still executing. Please check the task queue.
'400':
return hosts[0] if hosts else None
@wait_api_result
- def add(self, hostname: str):
- return self.api.add_host(HostSpec(hostname))
+ def add(self, hostname: str, addr: str, labels: List[str]):
+ return self.api.add_host(HostSpec(hostname, addr=addr, labels=labels))
@wait_api_result
def remove(self, hostname: str):
self._get('{}/node1'.format(self.URL_HOST))
self.assertStatus(200)
self.assertIn('labels', self.json_body())
+ self.assertIn('status', self.json_body())
+ self.assertIn('addr', self.json_body())
def test_get_3(self):
mgr.list_servers.return_value = []
self._get('{}/node1'.format(self.URL_HOST))
self.assertStatus(200)
self.assertIn('labels', self.json_body())
+ self.assertIn('status', self.json_body())
+ self.assertIn('addr', self.json_body())
@mock.patch('dashboard.controllers.host.add_host')
def test_add_host(self, mock_add_host):
with patch_orch(True):
payload = {
'hostname': 'node0',
+ 'addr': '192.0.2.0',
+ 'labels': 'mon',
'status': 'maintenance'
}
- self._post(self.URL_HOST, payload)
+ self._post(self.URL_HOST, payload, version='0.1')
self.assertStatus(201)
mock_add_host.assert_called()
fake_client.hosts.add_label = mock.Mock()
payload = {'update_labels': True, 'labels': ['bbb', 'ccc']}
- self._put('{}/node0'.format(self.URL_HOST), payload)
+ self._put('{}/node0'.format(self.URL_HOST), payload, version='0.1')
self.assertStatus(200)
+ self.assertHeader('Content-Type',
+ 'application/vnd.ceph.api.v0.1+json')
fake_client.hosts.remove_label.assert_called_once_with('node0', 'aaa')
fake_client.hosts.add_label.assert_called_once_with('node0', 'ccc')
# return 400 if type other than List[str]
self._put('{}/node0'.format(self.URL_HOST), {'update_labels': True,
- 'labels': 'ddd'})
+ 'labels': 'ddd'}, version='0.1')
self.assertStatus(400)
def test_host_maintenance(self):
]
with patch_orch(True, hosts=orch_hosts):
# enter maintenance mode
- self._put('{}/node0'.format(self.URL_HOST), {'maintenance': True})
+ self._put('{}/node0'.format(self.URL_HOST), {'maintenance': True}, version='0.1')
self.assertStatus(200)
+ self.assertHeader('Content-Type',
+ 'application/vnd.ceph.api.v0.1+json')
# force enter maintenance mode
- self._put('{}/node1'.format(self.URL_HOST), {'maintenance': True, 'force': True})
+ self._put('{}/node1'.format(self.URL_HOST), {'maintenance': True, 'force': True},
+ version='0.1')
self.assertStatus(200)
# exit maintenance mode
- self._put('{}/node0'.format(self.URL_HOST), {'maintenance': True})
+ self._put('{}/node0'.format(self.URL_HOST), {'maintenance': True}, version='0.1')
self.assertStatus(200)
- self._put('{}/node1'.format(self.URL_HOST), {'maintenance': True})
+ self._put('{}/node1'.format(self.URL_HOST), {'maintenance': True}, version='0.1')
self.assertStatus(200)
# maintenance without orchestrator service
with patch_orch(False):
- self._put('{}/node0'.format(self.URL_HOST), {'maintenance': True})
+ self._put('{}/node0'.format(self.URL_HOST), {'maintenance': True}, version='0.1')
self.assertStatus(503)
@mock.patch('dashboard.controllers.host.time')