245 maintenance window (#535)

* Maintenance Window: Introduced dynamic update to polling time. (#245)

Resolves #245

Added new configuration parameters and controller handling that can be
used to enable reduction in polling time near to start of a maintenance
window.

- The device may only get to know of start of maintenance window when
the device polls the server. If a window is available, server responds
with maintenanceWindow as 'available' and the update as 'forced'. Hence,
as the time for start of a maintenance window approaches, the device
should poll the server more often in order to decrease the possibility
of missing the maintenance window start time due to non-alignment of
polling with the start time, or due to packet losses and other network
issues.

- Additional configuration parameter is introduced that will be used to
specify the minimum number of times the controller should poll between
current time and the start of maintenance window. Using this parameter,
an "inverse exponential back-off" scheme for reducing the polling time
is implemented.

- Implemented inverse exponential back-off for polling.
For example, considering default polling-time = 00:05:00,
min-polling-time = 00:00:30, maintenance-window-poll-count = 3, we
expect that controller should poll the server 3 times between current
time and start of a maintenance window. Thus in following scenario
controller will get to know of start of maintenance 3 secs after
scheduled start. Whereas without this scheme, delay may be as much as 4
minutes.
time until start: 00:21:00; polling time: 00:05:00
time until start: 00:16:00; polling time: 00:05:00
time until start: 00:11:00; polling time: 00:03:40
time until start: 00:07:20; polling time: 00:02:27
time until start: 00:04:53; polling time: 00:01:38
time until start: 00:02:10; polling time: 00:00:43
time until start: 00:01:27; polling time: 00:00:30
time until start: 00:00:57; polling time: 00:00:30
time until start: 00:00:27; polling time: 00:00:30
time until start: -00:00:03; polling time: 00:05:00

- Once a maintenance window starts, the polling time is reverted to
default. Polling time is not changed near the end of a maintenance
window.

Signed-off-by: Christian Storm <christian.storm@siemens.com>
Signed-off-by: Himanshu Kumar Singh <himanshu.singh@siemens.com>
Signed-off-by: Raju HS <raju.hs@siemens.com>

* Maintenance Window: Updated the DMF API.

Added maintenance window handling for DMF API. Currently no timer or
scheduler is used to send updates to devices when a maintenance window
becomes available. The device can simulate a poll behaviour by sending a
message on UPDATE_ACTION_STATUS topic. Depending on whether there is a
maintenance window available or not, server should send
DOWNLOAD_AND_INSTALL or DOWNLOAD_AND_SKIP.

- Additional action status (DOWNLOADED) and event topic
(DOWNLOAD_AND_SKIP)

- Server sends DOWNLOAD_AND_SKIP or DOWNLOAD_AND_INSTALL based on
maintenance window using ControllerManagement

Signed-off-by: Christian Storm <christian.storm@siemens.com>
Signed-off-by: Himanshu Kumar Singh <himanshu.singh@siemens.com>
Signed-off-by: Raju HS <raju.hs@siemens.com>

* Maintenance Window: Added tests for DDI and Management APIs.

Added test cases for DDI and Management API to test for maintenance
window related logic.

- Tests for Management API for DistributionSet assignment with and
without valid maintenance schedule specified.

- Tests for lapsed maintenance schedule.

- Additional test helper methods.

Signed-off-by: Christian Storm <christian.storm@siemens.com>
Signed-off-by: Himanshu Kumar Singh <himanshu.singh@siemens.com>
Signed-off-by: Raju HS <raju.hs@siemens.com>

* Maintenance Window: Added new fields, enums, APIs and helper classes.

Extended the data model and Action API to support maintenance schedule.
Also provided helper classes to parse cron expression and calculate
maintenance window based on the schedule.

- DOWNLOADED status added to Action interface for controller to record
action status.

- Extra fields and APIs are added to Action to set schedule, duration
and time zone of maintenance window.

- Maintenance schedule can be defined using: a cron expression (e.g. "0
30 10-13 ? * WED,FRI" for 30 minutes past the hour, between 10:00 AM
and 01:59 PM, only on Wednesday and Friday), a duration (e.g. "00:30:00"
for 30 minutes) and the time zone (e.g. "+02:00" for CET summer time or
"+00:00" for UTC) relative to which maintenance window start time is
calculated.

- Extra APIs added to check if a maintenance schedule is defined or
lapsed and if a maintenance window is available based on the schedule.

- Added database fields to store the schedule, duration and timezone of
maintenance window.

- Added helper classes to parse and calculate the maintenance window
schedule.

- Added exception handling classes to handle invalid maintenance window
schedule.

Signed-off-by: Christian Storm <christian.storm@siemens.com>
Signed-off-by: Himanshu Kumar Singh <himanshu.singh@siemens.com>
Signed-off-by: Raju HS <raju.hs@siemens.com>

* Maintenance Window: Extended JSON model for REST APIs.

Extended the JSON model to support defining and communicating the
maintenance window information using the REST APIs.

- Additional JSON objects are defined to extend request and response
bodies.

- When requesting a distribution set assignment to a target, the
maintenance schedule information can be specified. Example below
specifies a maintenance schedule at 11:00 PM, only on Saturday, only in
2017, for 30 minutes. Maintenance window start time is then calculated
relative to the time zone specified, in this case CET summer time.
$ curl 'http://127.0.0.1:8080/rest/v1/targets/2/assignedDS' -i -X POST
-H 'Content-Type: application/json;charset=UTF-8' -d '{
    "forcetime" : "0",
    "id" : "1",
    "type" : "forced",
    "maintenanceWindow": {
        "schedule": "0 0 23 ? * SAT 2017",
        "duration": "00:30:00",
        "timezone": "+02:00"
    }
}'

- When device requests the action information it gets to know if there
is a 'maintenance schedule' for this action or not based on presence of
maintenanceWindow field in the response. The value of maintenanceWindow
is either 'available' (i.e. the maintenance window is now available as
per defined schedule and the update can progress) or 'unavailable'
(implying that maintenance window is not available now and update should
not be attempted). If there is no 'maintenance schedule' defined, the
field is null or not present.
$ curl
'http://127.0.0.1:8080/default/controller/v1/2/deploymentBase/1?c=411629670'
-i -H 'Accept: application/hal+json'
HTTP/1.1 200 OK
Content-Type: application/hal+json;charset=UTF-8
Content-Length: 1665
{
    "id": "1",
    "deployment": {
        "download": "forced",
        "update": "forced",
        "chunks": [ {
            "part": "os",
            "version": "1",
            "name": "1",
            "artifacts" : [ {
                "filename" : "binary.tgz",
                "hashes" : {
                    "sha1" : "8b71973e835cd0718827238b1cf89f0079e44dae",
                    "md5" : "a90218782025c48e09ba0436cf30d029"
                },
                "size" : 5,
               "_links" : {
                    "download" : {
                        "href" :
"http://127.0.0.1:8080/api/v1/default/download/controller/2/softwaremodules/8/filename/binary.tgz"
                    },
                    "md5sum" : {
                        "href" :
"http://127.0.0.1:8080/api/v1/default/download/controller/2/softwaremodules/8/filename/binary.tgz.MD5SUM"
                    },
                    "download-http" : {
                        "href" :
"http://127.0.0.1:8080/api/v1/default/download/controller/2/softwaremodules/8/filename/binary.tgz"
                    },
                    "md5sum-http" : {
                        "href" :
"http://127.0.0.1:8080/api/v1/default/download/controller/2/softwaremodules/8/filename/binary.tgz.MD5SUM"
                    }
                }
            } ]
        } ],
        "maintenanceWindow": "available"
    }
}

Signed-off-by: Christian Storm <christian.storm@siemens.com>
Signed-off-by: Himanshu Kumar Singh <himanshu.singh@siemens.com>
Signed-off-by: Raju HS <raju.hs@siemens.com>

* Maintenance Window: Added additional UI elements.

Added additional UI elements to specify maintenance window schedule in
hawkBit UI while assigning distribution set(s) to target(s).

- Added UI control to hawkBit UI when distribution set assignment is
confirmed.

- Also added required theme updates and resource strings.

Signed-off-by: Christian Storm <christian.storm@siemens.com>
Signed-off-by: Himanshu Kumar Singh <himanshu.singh@siemens.com>
Signed-off-by: Raju HS <raju.hs@siemens.com>

* Maintenance Window: Modified DDI and Mgmt APIs for maintenance window.

Modified controller management and deployment management to handle
maintenance window schedule in Management API and DDI API.

- Modified REST endpoints to accept and respond with maintenance window
information.

$ curl 'http://127.0.0.1:8080/rest/v1/targets/2/assignedDS' -i -X POST
-H 'Content-Type: application/json;charset=UTF-8' -d '{
    "forcetime" : "0",
    "id" : "1",
    "type" : "forced",
    "maintenanceWindow": {
        "schedule": "0 0 23 ? * SAT 2017",
        "duration": "00:30:00",
        "timezone": "+02:00"
    }
}'

- Check for availability of a maintenance window based on the schedule.

- Device gets go-ahead to install by change in ETAG and 'update' status
to 'forced' when it polls during an available maintenance window.

$ curl
'http://127.0.0.1:8080/default/controller/v1/2/deploymentBase/1?c=411659461'
-i -H 'Accept: application/hal+json'
HTTP/1.1 200 OK
Content-Type: application/hal+json;charset=UTF-8
Content-Length: 1665
{
    "id": "1",
    "deployment": {
        "download": "forced",
        "update": "forced",
        "chunks": [ {
            "part": "os",
            "version": "1",
            "name": "1",
            "artifacts" : [ {
                "filename" : "binary.tgz",
                "hashes" : {
                    "sha1" : "8b71973e835cd0718827238b1cf89f0079e44dae",
                    "md5" : "a90218782025c48e09ba0436cf30d029"
                },
                "size" : 5,
                "_links" : {
                    "download" : {
                        "href" :
"http://127.0.0.1:8080/api/v1/default/download/controller/2/softwaremodules/8/filename/binary.tgz"
                    },
                    "md5sum" : {
                        "href" :
"http://127.0.0.1:8080/api/v1/default/download/controller/2/softwaremodules/8/filename/binary.tgz.MD5SUM"
                    },
                    "download-http" : {
                        "href" :
"http://127.0.0.1:8080/api/v1/default/download/controller/2/softwaremodules/8/filename/binary.tgz"
                    },
                    "md5sum-http" : {
                        "href" :
"http://127.0.0.1:8080/api/v1/default/download/controller/2/softwaremodules/8/filename/binary.tgz.MD5SUM"
                    }
                }
            } ]
        } ],
        "maintenanceWindow": "available"
    }
}

- In case the current available maintenance window lapses, the device is
again sent 'update' status as 'skip'. The device may choose then to
abort the update and restart during next available window.

$ curl
'http://127.0.0.1:8080/default/controller/v1/2/deploymentBase/1?c=411659455'
-i -H 'Accept: application/hal+json'
HTTP/1.1 200 OK
Content-Type: application/hal+json;charset=UTF-8
Content-Length: 1665
{
    "id": "1",
    "deployment": {
        "download": "forced",
        "update": "skip",
        "chunks": [ {
            "part": "os",
            "version": "1",
            "name": "1",
            "artifacts" : [ {
                "filename" : "binary.tgz",
                "hashes" : {
                    "sha1" : "8b71973e835cd0718827238b1cf89f0079e44dae",
                    "md5" : "a90218782025c48e09ba0436cf30d029"
                },
                "size" : 5,
                "_links" : {
                    "download" : {
                        "href" :
"http://127.0.0.1:8080/api/v1/default/download/controller/2/softwaremodules/8/filename/binary.tgz"
                    },
                    "md5sum" : {
                        "href" :
"http://127.0.0.1:8080/api/v1/default/download/controller/2/softwaremodules/8/filename/binary.tgz.MD5SUM"
                    },
                    "download-http" : {
                        "href" :
"http://127.0.0.1:8080/api/v1/default/download/controller/2/softwaremodules/8/filename/binary.tgz"
                    },
                    "md5sum-http" : {
                        "href" :
"http://127.0.0.1:8080/api/v1/default/download/controller/2/softwaremodules/8/filename/binary.tgz.MD5SUM"
                    }
                }
            } ]
        } ],
        "maintenanceWindow": "unavailable"
    }
}

- In case the entire maintenance schedule lapses, before device is able
to send the completed status, the action is canceled.

$ curl 'http://127.0.0.1:8080/default/controller/v1/2' -i -H 'Accept:
application/hal+json'
HTTP/1.1 200 OK
Content-Type: application/hal+json;charset=UTF-8
Content-Length: 340
{
    "config": {
        "polling": {
        "sleep": "00:05:00"
        }
    },
    "_links": {
        "cancelAction": {
            "href":
"http://127.0.0.1:8080/default/controller/v1/2/cancelAction/1"
        },
        "configData": {
            "href":
"http://127.0.0.1:8080/default/controller/v1/2/configData"
        }
    }
}

Signed-off-by: Christian Storm <christian.storm@siemens.com>
Signed-off-by: Himanshu Kumar Singh <himanshu.singh@siemens.com>
Signed-off-by: Raju HS <raju.hs@siemens.com>
This commit is contained in:
stormc
2018-03-06 16:17:31 +01:00
committed by Kai Zimmermann
parent aa39675f4b
commit f4278c45ef
42 changed files with 1884 additions and 45 deletions

View File

@@ -0,0 +1,246 @@
/**
* Copyright (c) Siemens AG, 2018
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.ui.management.footer;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeParseException;
import java.util.List;
import java.util.stream.Collectors;
import org.eclipse.hawkbit.repository.MaintenanceScheduleHelper;
import org.eclipse.hawkbit.ui.utils.SPDateTimeUtil;
import org.eclipse.hawkbit.ui.utils.VaadinMessageSource;
import com.vaadin.data.Property.ValueChangeEvent;
import com.vaadin.data.Property.ValueChangeListener;
import com.vaadin.data.Validator;
import com.vaadin.ui.CheckBox;
import com.vaadin.ui.ComboBox;
import com.vaadin.ui.HorizontalLayout;
import com.vaadin.ui.Notification;
import com.vaadin.ui.TextField;
import com.vaadin.ui.VerticalLayout;
import com.vaadin.ui.themes.ValoTheme;
/**
* {@link MaintenanceWindowLayout} defines UI layout that is used to specify the
* maintenance schedule while assigning distribution set(s) to the target(s).
*/
public class MaintenanceWindowLayout extends VerticalLayout {
private static final long serialVersionUID = 722511089585562455L;
private final VaadinMessageSource i18n;
private CheckBox maintenanceWindowSelection;
private TextField schedule;
private TextField duration;
private ComboBox timeZone;
/**
* Constructor for the control to specify the maintenance schedule.
*
* @param i18n
* (@link VaadinMessageSource} to get the localized resource
* strings.
*/
public MaintenanceWindowLayout(final VaadinMessageSource i18n) {
HorizontalLayout optionContainer;
HorizontalLayout controlContainer;
this.i18n = i18n;
optionContainer = new HorizontalLayout();
controlContainer = new HorizontalLayout();
addComponent(optionContainer);
addComponent(controlContainer);
createMaintenanceWindowOption();
createMaintenanceScheduleControl();
createMaintenanceDurationControl();
createMaintenanceTimeZoneControl();
optionContainer.addComponent(maintenanceWindowSelection);
controlContainer.addComponent(schedule);
controlContainer.addComponent(duration);
controlContainer.addComponent(timeZone);
addValueChangeListener();
maintenanceWindowSelection.setValue(false);
setStyleName("dist-window-maintenance-window-layout");
}
/**
* Validates if the maintenance schedule is a valid cron expression.
*/
class CronValidation implements Validator {
private static final long serialVersionUID = 1L;
@Override
public void validate(Object value) throws InvalidValueException {
try {
String expr = (String) value;
if (!expr.isEmpty()) {
new MaintenanceScheduleHelper((String) value, "00:00:00", getClientTimeZone());
}
} catch (IllegalArgumentException e) {
Notification.show(e.getMessage());
throw e;
}
}
}
/**
* Validates if the duration is specified in expected format.
*/
class DurationValidator implements Validator {
private static final long serialVersionUID = 1L;
@Override
public void validate(Object value) throws InvalidValueException {
try {
String expr = (String) value;
if (!expr.isEmpty()) {
MaintenanceScheduleHelper.convertToISODuration((String) value);
}
} catch (DateTimeParseException e) {
Notification.show(e.getMessage());
throw e;
}
}
}
/**
* Create check box to enable or disable maintenance window.
*/
private void createMaintenanceWindowOption() {
maintenanceWindowSelection = new CheckBox(i18n.getMessage("caption.maintenancewindow.enable"));
maintenanceWindowSelection.addStyleName(ValoTheme.CHECKBOX_SMALL);
}
/**
* Text field to specify the schedule.
*/
private void createMaintenanceScheduleControl() {
schedule = new TextField();
schedule.setCaption(i18n.getMessage("caption.maintenancewindow.schedule"));
schedule.addValidator(new CronValidation());
schedule.setEnabled(false);
schedule.addStyleName(ValoTheme.TEXTFIELD_SMALL);
}
/**
* Text field to specify the duration.
*/
private void createMaintenanceDurationControl() {
duration = new TextField();
duration.setCaption(i18n.getMessage("caption.maintenancewindow.duration"));
duration.addValidator(new DurationValidator());
duration.setEnabled(false);
schedule.addStyleName(ValoTheme.TEXTFIELD_SMALL);
}
/**
* Combo box to pick the time zone offset.
*/
private void createMaintenanceTimeZoneControl() {
timeZone = new ComboBox();
timeZone.setCaption(i18n.getMessage("caption.maintenancewindow.timezone"));
timeZone.addItems(getAllTimeZones());
timeZone.setTextInputAllowed(false);
timeZone.setValue(getClientTimeZone());
timeZone.setEnabled(false);
timeZone.addStyleName(ValoTheme.COMBOBOX_SMALL);
}
/**
* Get time zone of the browser client to be used as default.
*/
private static String getClientTimeZone() {
return ZonedDateTime.now(ZoneId.of(SPDateTimeUtil.getBrowserTimeZone().getID())).getOffset().getId()
.replaceAll("Z", "+00:00");
}
/**
* Get list of all time zone offsets supported.
*/
private static List<String> getAllTimeZones() {
List<String> lst = ZoneId.getAvailableZoneIds().stream()
.map(id -> ZonedDateTime.now(ZoneId.of(id)).getOffset().getId().replace("Z", "+00:00")).distinct()
.collect(Collectors.toList());
lst.sort(null);
return lst;
}
/**
* Create a listener to enable and disable maintenance schedule controls.
*/
private void addValueChangeListener() {
maintenanceWindowSelection.addValueChangeListener(new ValueChangeListener() {
private static final long serialVersionUID = 1L;
@Override
public void valueChange(final ValueChangeEvent event) {
schedule.setEnabled(maintenanceWindowSelection.getValue());
schedule.setRequired(maintenanceWindowSelection.getValue());
schedule.setValue("");
duration.setEnabled(maintenanceWindowSelection.getValue());
duration.setRequired(maintenanceWindowSelection.getValue());
duration.setValue("");
timeZone.setEnabled(maintenanceWindowSelection.getValue());
timeZone.setRequired(maintenanceWindowSelection.getValue());
timeZone.setValue(getClientTimeZone());
}
});
}
/**
* Get whether the maintenance schedule option is enabled or not.
*
* @return boolean.
*/
public boolean getMaintenanceOption() {
return maintenanceWindowSelection.getValue();
}
/**
* Get the cron expression for maintenance schedule.
*
* @return {@link String}.
*/
public String getMaintenanceSchedule() {
return schedule.getValue();
}
/**
* Get the maintenance window duration.
*
* @return {@link String}.
*/
public String getMaintenanceDuration() {
return duration.getValue();
}
/**
* Get the cron expression for maintenance window timezone.
*
* @return {@link String}.
*/
public String getMaintenanceTimeZone() {
return timeZone.getValue().toString();
}
}

View File

@@ -22,6 +22,7 @@ import java.util.stream.Collectors;
import org.eclipse.hawkbit.repository.DeploymentManagement;
import org.eclipse.hawkbit.repository.DistributionSetManagement;
import org.eclipse.hawkbit.repository.TargetManagement;
import org.eclipse.hawkbit.repository.model.TargetWithActionType;
import org.eclipse.hawkbit.repository.model.Action.ActionType;
import org.eclipse.hawkbit.repository.model.DistributionSetAssignmentResult;
import org.eclipse.hawkbit.repository.model.RepositoryModelConstants;
@@ -78,6 +79,8 @@ public class ManangementConfirmationWindowLayout extends AbstractConfirmationWin
private final ActionTypeOptionGroupLayout actionTypeOptionGroupLayout;
private final MaintenanceWindowLayout maintenanceWindowLayout;
private ConfirmationTab assignmentTab;
public ManangementConfirmationWindowLayout(final VaadinMessageSource i18n, final UIEventBus eventBus,
@@ -90,6 +93,7 @@ public class ManangementConfirmationWindowLayout extends AbstractConfirmationWin
this.deploymentManagement = deploymentManagement;
this.distributionSetManagement = distributionSetManagement;
this.actionTypeOptionGroupLayout = new ActionTypeOptionGroupLayout(i18n);
this.maintenanceWindowLayout = new MaintenanceWindowLayout(i18n);
}
@Override
@@ -138,6 +142,7 @@ public class ManangementConfirmationWindowLayout extends AbstractConfirmationWin
actionTypeOptionGroupLayout.selectDefaultOption();
assignmentTab.addComponent(actionTypeOptionGroupLayout, 1);
assignmentTab.addComponent(maintenanceWindowLayout, 1);
return assignmentTab;
}
@@ -154,6 +159,15 @@ public class ManangementConfirmationWindowLayout extends AbstractConfirmationWin
? actionTypeOptionGroupLayout.getForcedTimeDateField().getValue().getTime()
: RepositoryModelConstants.NO_FORCE_TIME;
final String maintenanceSchedule = maintenanceWindowLayout.getMaintenanceOption()
? maintenanceWindowLayout.getMaintenanceSchedule() : null;
final String maintenanceDuration = maintenanceWindowLayout.getMaintenanceOption()
? maintenanceWindowLayout.getMaintenanceDuration() : null;
final String maintenanceTimeZone = maintenanceWindowLayout.getMaintenanceOption()
? maintenanceWindowLayout.getMaintenanceTimeZone() : null;
final Map<Long, List<TargetIdName>> saveAssignedList = Maps.newHashMapWithExpectedSize(itemIds.size());
int successAssignmentCount = 0;
@@ -174,8 +188,11 @@ public class ManangementConfirmationWindowLayout extends AbstractConfirmationWin
for (final Map.Entry<Long, List<TargetIdName>> mapEntry : saveAssignedList.entrySet()) {
tempIdList = saveAssignedList.get(mapEntry.getKey());
final DistributionSetAssignmentResult distributionSetAssignmentResult = deploymentManagement
.assignDistributionSet(mapEntry.getKey(), actionType, forcedTimeStamp,
tempIdList.stream().map(t -> t.getControllerId()).collect(Collectors.toList()));
.assignDistributionSet(mapEntry.getKey(),
tempIdList.stream()
.map(t -> new TargetWithActionType(t.getControllerId(), actionType, forcedTimeStamp,
maintenanceSchedule, maintenanceDuration, maintenanceTimeZone))
.collect(Collectors.toList()));
if (distributionSetAssignmentResult.getAssigned() > 0) {
successAssignmentCount += distributionSetAssignmentResult.getAssigned();

View File

@@ -83,6 +83,42 @@
@include sp-button-icon-only-href;
}
.dist-window-maintenance-window-layout {
vertical-align: middle;
horizontal-align: middle;
.v-slot {
vertical-align: middle;
padding-left: 8px;
padding-right: 5px;
}
.v-caption {
vertical-align: middle;
margin-top: 0;
font-size: 12px;
}
.v-checkbox {
font-size: 12px;
font-weight: 400;
}
.v-filterselect {
font-size: 12px;
font-weight: 400;
width: 160px;
height: 24px;
}
.v-textfield {
font-size: 12px;
font-weight: 400;
width: 160px;
height: 24px;
}
}
///force,soft,time forced radio button styles - start**
.dist-window-actiontype-horz-layout {
padding-bottom: 15px;

View File

@@ -139,6 +139,11 @@ caption.metadata.delete.action.confirmbox = Confirm Metadata Delete Action
caption.confirm.assign.consequences = Auto assign consequences
caption.auto.assignment.ds = Auto assignment
caption.maintenancewindow.enable = Maintenance Schedule
caption.maintenancewindow.schedule = Schedule (Cron Expression)
caption.maintenancewindow.duration = Duration (hh:mm:ss)
caption.maintenancewindow.timezone = Time Zone
# Labels prefix with - label
label.dist.details.type = Type :
label.dist.details.name = Name :