import cherrypy
from ceph.deployment.service_spec import ServiceSpec
-from ..exceptions import DashboardException
from ..security import Scope
-from ..services.exception import handle_orchestrator_error
+from ..services.exception import handle_custom_error, handle_orchestrator_error
from ..services.orchestrator import OrchClient, OrchFeature
from . import APIDoc, APIRouter, CreatePermission, DeletePermission, Endpoint, \
- ReadPermission, RESTController, Task
+ ReadPermission, RESTController, Task, UpdatePermission
from .orchestrator import raise_if_no_orchestrator
return [d.to_dict() for d in daemons]
@CreatePermission
+ @handle_custom_error('service', exceptions=(ValueError, TypeError))
@raise_if_no_orchestrator([OrchFeature.SERVICE_CREATE])
@handle_orchestrator_error('service')
@service_task('create', {'service_name': '{service_name}'})
:param service_name: The service name, e.g. 'alertmanager'.
:return: None
"""
- try:
- orch = OrchClient.instance()
- orch.services.apply(service_spec)
- except (ValueError, TypeError) as e:
- raise DashboardException(e, component='service')
+
+ OrchClient.instance().services.apply(service_spec, no_overwrite=True)
+
+ @UpdatePermission
+ @handle_custom_error('service', exceptions=(ValueError, TypeError))
+ @raise_if_no_orchestrator([OrchFeature.SERVICE_CREATE])
+ @handle_orchestrator_error('service')
+ @service_task('edit', {'service_name': '{service_name}'})
+ def set(self, service_spec: Dict, service_name: str): # pylint: disable=W0613
+ """
+ :param service_spec: The service specification as JSON.
+ :param service_name: The service name, e.g. 'alertmanager'.
+ :return: None
+ """
+
+ OrchClient.instance().services.apply(service_spec, no_overwrite=False)
@DeletePermission
@raise_if_no_orchestrator([OrchFeature.SERVICE_DELETE])
<select id="service_type"
name="service_type"
class="form-control"
- formControlName="service_type">
+ formControlName="service_type"
+ (change)="getServiceIds($event.target.value)">
<option i18n
[ngValue]="null">-- Select a service type --</option>
<option *ngFor="let serviceType of serviceTypes"
<span class="invalid-feedback"
*ngIf="serviceForm.showError('service_id', frm, 'required')"
i18n>This field is required.</span>
+ <span class="invalid-feedback"
+ *ngIf="serviceForm.showError('service_id', frm, 'uniqueName')"
+ i18n>This service id is already in use.</span>
<span class="invalid-feedback"
*ngIf="serviceForm.showError('service_id', frm, 'rgwPattern')"
i18n>The value does not match the pattern <strong><service_id>[.<realm_name>.<zone_name>]</strong>.</span>
action: string;
resource: string;
serviceTypes: string[] = [];
+ serviceIds: string[] = [];
hosts: any;
labels: string[];
labelClick = new Subject<string>();
pools: Array<object>;
services: Array<CephServiceSpec> = [];
pageURL: string;
+ serviceList: CephServiceSpec[];
constructor(
public actionLabels: ActionLabelsI18n,
return !this.RGW_SVC_ID_PATTERN.test(value);
})
]
- )
+ ),
+ CdValidators.custom('uniqueName', (service_id: string) => {
+ return this.serviceIds && this.serviceIds.includes(service_id);
+ })
]
],
placement: ['hosts'],
this.serviceType = params.type;
});
}
+
+ this.cephServiceService.list().subscribe((services: CephServiceSpec[]) => {
+ this.serviceList = services;
+ this.services = services.filter((service: any) =>
+ this.INGRESS_SUPPORTED_SERVICE_TYPES.includes(service.service_type)
+ );
+ });
+
this.cephServiceService.getKnownTypes().subscribe((resp: Array<string>) => {
// Remove service types:
// osd - This is deployed a different way.
this.poolService.getList().subscribe((resp: Array<object>) => {
this.pools = resp;
});
- this.cephServiceService.list().subscribe((services: CephServiceSpec[]) => {
- this.services = services.filter((service: any) =>
- this.INGRESS_SUPPORTED_SERVICE_TYPES.includes(service.service_type)
- );
- });
if (this.editing) {
this.action = this.actionLabels.EDIT;
}
}
+ getServiceIds(selectedServiceType: string) {
+ this.serviceIds = this.serviceList
+ .filter((service) => service['service_type'] === selectedServiceType)
+ .map((service) => service['service_id']);
+ }
+
disableForEditing(serviceType: string) {
const disableForEditKeys = ['service_type', 'service_id'];
disableForEditKeys.forEach((key) => {
task: new FinishedTask(taskUrl, {
service_name: serviceName
}),
- call: this.cephServiceService.create(serviceSpec)
+ call: this.editing
+ ? this.cephServiceService.update(serviceSpec)
+ : this.cephServiceService.create(serviceSpec)
})
.subscribe({
error() {
);
}
+ update(serviceSpec: { [key: string]: any }) {
+ const serviceName = serviceSpec['service_id']
+ ? `${serviceSpec['service_type']}.${serviceSpec['service_id']}`
+ : serviceSpec['service_type'];
+ return this.http.put(
+ `${this.url}/${serviceName}`,
+ {
+ service_name: serviceName,
+ service_spec: serviceSpec
+ },
+ { observe: 'response' }
+ );
+ }
+
delete(serviceName: string) {
return this.http.delete(`${this.url}/${serviceName}`, { observe: 'response' });
}
- jwt: []
tags:
- Service
+ put:
+ description: "\n :param service_spec: The service specification as JSON.\n\
+ \ :param service_name: The service name, e.g. 'alertmanager'.\n \
+ \ :return: None\n "
+ parameters:
+ - in: path
+ name: service_name
+ required: true
+ schema:
+ type: string
+ requestBody:
+ content:
+ application/json:
+ schema:
+ properties:
+ service_spec:
+ type: string
+ required:
+ - service_spec
+ type: object
+ responses:
+ '200':
+ content:
+ application/vnd.ceph.api.v1.0+json:
+ type: object
+ description: Resource updated.
+ '202':
+ content:
+ application/vnd.ceph.api.v1.0+json:
+ type: object
+ description: Operation is still executing. Please check the task queue.
+ '400':
+ description: Operation exception. Please check the response body for details.
+ '401':
+ description: Unauthenticated access. Please login first.
+ '403':
+ description: Unauthorized access. Please check your permissions.
+ '500':
+ description: Unexpected error. Please check the response body for the stack
+ trace.
+ security:
+ - jwt: []
+ tags:
+ - Service
/api/service/{service_name}/daemons:
get:
parameters:
yield
except Exception as e: # pylint: disable=broad-except
raise DashboardException(e, component=component, http_status_code=http_status_code)
+
+
+@contextmanager
+def handle_custom_error(component, http_status_code=None, exceptions=()):
+ try:
+ yield
+ except exceptions as e:
+ raise DashboardException(e, component=component, http_status_code=http_status_code)
raise_if_exception(c)
@wait_api_result
- def apply(self, service_spec: Dict) -> OrchResult[List[str]]:
+ def apply(self,
+ service_spec: Dict,
+ no_overwrite: Optional[bool] = False) -> OrchResult[List[str]]:
spec = ServiceSpec.from_json(service_spec)
- return self.api.apply([spec])
+ return self.api.apply([spec], no_overwrite)
@wait_api_result
def remove(self, service_name: str) -> List[str]: