feat(edge-compute): add specific edge endpoint checkin interval (#3855)

* feat(endpoint): send custom checkin interval

* feat(endpoint): update edge checkin interval

* feat(endpoint): save checkin interval

* feat(endpoints): create endpoint with checkin interval

* feat(endpoints): change tooltip

* fix(edge-compute): fix typos

Co-authored-by: Anthony Lapenna <anthony.lapenna@portainer.io>

* fix(endpoints): show default interval

* fix(endpoint): rename checkin property

Co-authored-by: Anthony Lapenna <anthony.lapenna@portainer.io>
pull/3442/merge
Chaim Lev-Ari 2020-06-04 08:35:09 +03:00 committed by GitHub
parent 766ced7cb1
commit 9f4631bb6d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 183 additions and 39 deletions

View File

@ -30,6 +30,7 @@ type endpointCreatePayload struct {
TLSCertFile []byte TLSCertFile []byte
TLSKeyFile []byte TLSKeyFile []byte
TagIDs []portainer.TagID TagIDs []portainer.TagID
EdgeCheckinInterval int
} }
func (payload *endpointCreatePayload) Validate(r *http.Request) error { func (payload *endpointCreatePayload) Validate(r *http.Request) error {
@ -102,6 +103,9 @@ func (payload *endpointCreatePayload) Validate(r *http.Request) error {
publicURL, _ := request.RetrieveMultiPartFormValue(r, "PublicURL", true) publicURL, _ := request.RetrieveMultiPartFormValue(r, "PublicURL", true)
payload.PublicURL = publicURL payload.PublicURL = publicURL
checkinInterval, _ := request.RetrieveNumericMultiPartFormValue(r, "CheckinInterval", true)
payload.EdgeCheckinInterval = checkinInterval
return nil return nil
} }
@ -193,13 +197,14 @@ func (handler *Handler) createEdgeAgentEndpoint(payload *endpointCreatePayload)
TLSConfig: portainer.TLSConfiguration{ TLSConfig: portainer.TLSConfiguration{
TLS: false, TLS: false,
}, },
AuthorizedUsers: []portainer.UserID{}, AuthorizedUsers: []portainer.UserID{},
AuthorizedTeams: []portainer.TeamID{}, AuthorizedTeams: []portainer.TeamID{},
Extensions: []portainer.EndpointExtension{}, Extensions: []portainer.EndpointExtension{},
TagIDs: payload.TagIDs, TagIDs: payload.TagIDs,
Status: portainer.EndpointStatusUp, Status: portainer.EndpointStatusUp,
Snapshots: []portainer.Snapshot{}, Snapshots: []portainer.Snapshot{},
EdgeKey: edgeKey, EdgeKey: edgeKey,
EdgeCheckinInterval: payload.EdgeCheckinInterval,
} }
err = handler.saveEndpointAndUpdateAuthorizations(endpoint) err = handler.saveEndpointAndUpdateAuthorizations(endpoint)

View File

@ -60,11 +60,16 @@ func (handler *Handler) endpointStatusInspect(w http.ResponseWriter, r *http.Req
tunnel := handler.ReverseTunnelService.GetTunnelDetails(endpoint.ID) tunnel := handler.ReverseTunnelService.GetTunnelDetails(endpoint.ID)
checkinInterval := settings.EdgeAgentCheckinInterval
if endpoint.EdgeCheckinInterval != 0 {
checkinInterval = endpoint.EdgeCheckinInterval
}
statusResponse := endpointStatusInspectResponse{ statusResponse := endpointStatusInspectResponse{
Status: tunnel.Status, Status: tunnel.Status,
Port: tunnel.Port, Port: tunnel.Port,
Schedules: tunnel.Schedules, Schedules: tunnel.Schedules,
CheckinInterval: settings.EdgeAgentCheckinInterval, CheckinInterval: checkinInterval,
Credentials: tunnel.Credentials, Credentials: tunnel.Credentials,
} }

View File

@ -23,6 +23,7 @@ type endpointUpdatePayload struct {
TagIDs []portainer.TagID TagIDs []portainer.TagID
UserAccessPolicies portainer.UserAccessPolicies UserAccessPolicies portainer.UserAccessPolicies
TeamAccessPolicies portainer.TeamAccessPolicies TeamAccessPolicies portainer.TeamAccessPolicies
EdgeCheckinInterval *int
} }
func (payload *endpointUpdatePayload) Validate(r *http.Request) error { func (payload *endpointUpdatePayload) Validate(r *http.Request) error {
@ -61,6 +62,10 @@ func (handler *Handler) endpointUpdate(w http.ResponseWriter, r *http.Request) *
endpoint.PublicURL = *payload.PublicURL endpoint.PublicURL = *payload.PublicURL
} }
if payload.EdgeCheckinInterval != nil {
endpoint.EdgeCheckinInterval = *payload.EdgeCheckinInterval
}
groupIDChanged := false groupIDChanged := false
if payload.GroupID != nil { if payload.GroupID != nil {
groupID := portainer.EndpointGroupID(*payload.GroupID) groupID := portainer.EndpointGroupID(*payload.GroupID)

View File

@ -155,21 +155,23 @@ type (
// Endpoint represents a Docker endpoint with all the info required // Endpoint represents a Docker endpoint with all the info required
// to connect to it // to connect to it
Endpoint struct { Endpoint struct {
ID EndpointID `json:"Id"` ID EndpointID `json:"Id"`
Name string `json:"Name"` Name string `json:"Name"`
Type EndpointType `json:"Type"` Type EndpointType `json:"Type"`
URL string `json:"URL"` URL string `json:"URL"`
GroupID EndpointGroupID `json:"GroupId"` GroupID EndpointGroupID `json:"GroupId"`
PublicURL string `json:"PublicURL"` PublicURL string `json:"PublicURL"`
TLSConfig TLSConfiguration `json:"TLSConfig"` TLSConfig TLSConfiguration `json:"TLSConfig"`
Extensions []EndpointExtension `json:"Extensions"` Extensions []EndpointExtension `json:"Extensions"`
TagIDs []TagID `json:"TagIds"` TagIDs []TagID `json:"TagIds"`
Status EndpointStatus `json:"Status"` Status EndpointStatus `json:"Status"`
Snapshots []Snapshot `json:"Snapshots"` Snapshots []Snapshot `json:"Snapshots"`
UserAccessPolicies UserAccessPolicies `json:"UserAccessPolicies"` UserAccessPolicies UserAccessPolicies `json:"UserAccessPolicies"`
TeamAccessPolicies TeamAccessPolicies `json:"TeamAccessPolicies"` TeamAccessPolicies TeamAccessPolicies `json:"TeamAccessPolicies"`
EdgeID string `json:"EdgeID,omitempty"` EdgeID string `json:"EdgeID,omitempty"`
EdgeKey string `json:"EdgeKey"` EdgeKey string `json:"EdgeKey"`
EdgeCheckinInterval int `json:"EdgeCheckinInterval"`
// Deprecated fields // Deprecated fields
// Deprecated in DBVersion == 4 // Deprecated in DBVersion == 4
TLS bool `json:"TLS,omitempty"` TLS bool `json:"TLS,omitempty"`

View File

@ -65,7 +65,21 @@ angular.module('portainer.app').factory('EndpointService', [
return deferred.promise; return deferred.promise;
}; };
service.createRemoteEndpoint = function (name, type, URL, PublicURL, groupID, tagIds, TLS, TLSSkipVerify, TLSSkipClientVerify, TLSCAFile, TLSCertFile, TLSKeyFile) { service.createRemoteEndpoint = function (
name,
type,
URL,
PublicURL,
groupID,
tagIds,
TLS,
TLSSkipVerify,
TLSSkipClientVerify,
TLSCAFile,
TLSCertFile,
TLSKeyFile,
checkinInterval
) {
var deferred = $q.defer(); var deferred = $q.defer();
var endpointURL = URL; var endpointURL = URL;
@ -73,7 +87,21 @@ angular.module('portainer.app').factory('EndpointService', [
endpointURL = 'tcp://' + URL; endpointURL = 'tcp://' + URL;
} }
FileUploadService.createEndpoint(name, type, endpointURL, PublicURL, groupID, tagIds, TLS, TLSSkipVerify, TLSSkipClientVerify, TLSCAFile, TLSCertFile, TLSKeyFile) FileUploadService.createEndpoint(
name,
type,
endpointURL,
PublicURL,
groupID,
tagIds,
TLS,
TLSSkipVerify,
TLSSkipClientVerify,
TLSCAFile,
TLSCertFile,
TLSKeyFile,
checkinInterval
)
.then(function success(response) { .then(function success(response) {
deferred.resolve(response.data); deferred.resolve(response.data);
}) })

View File

@ -91,12 +91,11 @@ angular.module('portainer.app').factory('FileUploadService', [
data: { data: {
file: file, file: file,
Name: stackName, Name: stackName,
EdgeGroups: Upload.json(edgeGroups) EdgeGroups: Upload.json(edgeGroups),
}, },
ignoreLoadingBar: true ignoreLoadingBar: true,
}); });
}; };
service.configureRegistry = function (registryId, registryManagementConfigurationModel) { service.configureRegistry = function (registryId, registryManagementConfigurationModel) {
return Upload.upload({ return Upload.upload({
@ -116,7 +115,7 @@ angular.module('portainer.app').factory('FileUploadService', [
}); });
}; };
service.createEndpoint = function (name, type, URL, PublicURL, groupID, tagIds, TLS, TLSSkipVerify, TLSSkipClientVerify, TLSCAFile, TLSCertFile, TLSKeyFile) { service.createEndpoint = function (name, type, URL, PublicURL, groupID, tagIds, TLS, TLSSkipVerify, TLSSkipClientVerify, TLSCAFile, TLSCertFile, TLSKeyFile, checkinInterval) {
return Upload.upload({ return Upload.upload({
url: 'api/endpoints', url: 'api/endpoints',
data: { data: {
@ -132,6 +131,7 @@ angular.module('portainer.app').factory('FileUploadService', [
TLSCACertFile: TLSCAFile, TLSCACertFile: TLSCAFile,
TLSCertFile: TLSCertFile, TLSCertFile: TLSCertFile,
TLSKeyFile: TLSKeyFile, TLSKeyFile: TLSKeyFile,
CheckinInterval: checkinInterval,
}, },
ignoreLoadingBar: true, ignoreLoadingBar: true,
}); });

View File

@ -12,6 +12,7 @@ angular
EndpointService, EndpointService,
GroupService, GroupService,
TagService, TagService,
SettingsService,
Notifications, Notifications,
Authentication Authentication
) { ) {
@ -19,6 +20,24 @@ angular
EnvironmentType: 'agent', EnvironmentType: 'agent',
actionInProgress: false, actionInProgress: false,
allowCreateTag: Authentication.isAdmin(), allowCreateTag: Authentication.isAdmin(),
availableEdgeAgentCheckinOptions: [
{ key: 'Use default interval', value: 0 },
{
key: '5 seconds',
value: 5,
},
{
key: '10 seconds',
value: 10,
},
{
key: '30 seconds',
value: 30,
},
{ key: '5 minutes', value: 300 },
{ key: '1 hour', value: 3600 },
{ key: '1 day', value: 86400 },
],
}; };
$scope.formValues = { $scope.formValues = {
@ -28,6 +47,7 @@ angular
GroupId: 1, GroupId: 1,
SecurityFormData: new EndpointSecurityFormData(), SecurityFormData: new EndpointSecurityFormData(),
TagIds: [], TagIds: [],
CheckinInterval: $scope.state.availableEdgeAgentCheckinOptions[0].value,
}; };
$scope.copyAgentCommand = function () { $scope.copyAgentCommand = function () {
@ -79,7 +99,7 @@ angular
var tagIds = $scope.formValues.TagIds; var tagIds = $scope.formValues.TagIds;
var URL = $scope.formValues.URL; var URL = $scope.formValues.URL;
addEndpoint(name, 4, URL, '', groupId, tagIds, false, false, false, null, null, null); addEndpoint(name, 4, URL, '', groupId, tagIds, false, false, false, null, null, null, $scope.formValues.CheckinInterval);
}; };
$scope.onCreateTag = function onCreateTag(tagName) { $scope.onCreateTag = function onCreateTag(tagName) {
@ -96,9 +116,23 @@ angular
} }
} }
function addEndpoint(name, type, URL, PublicURL, groupId, tagIds, TLS, TLSSkipVerify, TLSSkipClientVerify, TLSCAFile, TLSCertFile, TLSKeyFile) { function addEndpoint(name, type, URL, PublicURL, groupId, tagIds, TLS, TLSSkipVerify, TLSSkipClientVerify, TLSCAFile, TLSCertFile, TLSKeyFile, CheckinInterval) {
$scope.state.actionInProgress = true; $scope.state.actionInProgress = true;
EndpointService.createRemoteEndpoint(name, type, URL, PublicURL, groupId, tagIds, TLS, TLSSkipVerify, TLSSkipClientVerify, TLSCAFile, TLSCertFile, TLSKeyFile) EndpointService.createRemoteEndpoint(
name,
type,
URL,
PublicURL,
groupId,
tagIds,
TLS,
TLSSkipVerify,
TLSSkipClientVerify,
TLSCAFile,
TLSCertFile,
TLSKeyFile,
CheckinInterval
)
.then(function success(data) { .then(function success(data) {
Notifications.success('Endpoint created', name); Notifications.success('Endpoint created', name);
if (type === 4) { if (type === 4) {
@ -119,10 +153,14 @@ angular
$q.all({ $q.all({
groups: GroupService.groups(), groups: GroupService.groups(),
tags: TagService.tags(), tags: TagService.tags(),
settings: SettingsService.settings(),
}) })
.then(function success(data) { .then(function success(data) {
$scope.groups = data.groups; $scope.groups = data.groups;
$scope.availableTags = data.tags; $scope.availableTags = data.tags;
const settings = data.settings;
$scope.state.availableEdgeAgentCheckinOptions[0].key += ` (${settings.EdgeAgentCheckinInterval} seconds)`;
}) })
.catch(function error(err) { .catch(function error(err) {
Notifications.error('Failure', err, 'Unable to load groups'); Notifications.error('Failure', err, 'Unable to load groups');

View File

@ -145,8 +145,8 @@
</div> </div>
</div> </div>
<!-- !endpoint-url-input --> <!-- !endpoint-url-input -->
<!-- portainer-instance-input -->
<div ng-if="state.EnvironmentType === 'edge_agent'"> <div ng-if="state.EnvironmentType === 'edge_agent'">
<!-- portainer-instance-input -->
<div class="form-group"> <div class="form-group">
<label for="endpoint_url" class="col-sm-3 col-lg-2 control-label text-left"> <label for="endpoint_url" class="col-sm-3 col-lg-2 control-label text-left">
Portainer server URL Portainer server URL
@ -163,8 +163,25 @@
</div> </div>
</div> </div>
</div> </div>
<!-- !portainer-instance-input -->
<div class="form-group">
<label for="edge_checkin" class="col-sm-2 control-label text-left">
Poll frequency
<portainer-tooltip
position="bottom"
message="Interval used by this Edge agent to check in with the Portainer instance. Affects Edge endpoint management and Edge compute features."
></portainer-tooltip>
</label>
<div class="col-sm-10">
<select
id="edge_checkin"
class="form-control"
ng-model="formValues.CheckinInterval"
ng-options="+(opt.value) as opt.key for opt in state.availableEdgeAgentCheckinOptions"
></select>
</div>
</div>
</div> </div>
<!-- !portainer-instance-input -->
<!-- endpoint-public-url-input --> <!-- endpoint-public-url-input -->
<div ng-if="state.EnvironmentType === 'docker' || state.EnvironmentType === 'agent'"> <div ng-if="state.EnvironmentType === 'docker' || state.EnvironmentType === 'agent'">
<div class="form-group"> <div class="form-group">

View File

@ -36,10 +36,10 @@
<div style="margin-top: 10px;"> <div style="margin-top: 10px;">
<uib-tabset active="state.deploymentTab"> <uib-tabset active="state.deploymentTab">
<uib-tab index="0" heading="Standalone"> <uib-tab index="0" heading="Standalone">
<code style="display: block; white-space: pre-wrap; padding: 16px 90px;">{{dockerCommands.standalone}}</code> <code style="display: block; white-space: pre-wrap; padding: 16px 90px;">{{ dockerCommands.standalone }}</code>
</uib-tab> </uib-tab>
<uib-tab index="1" heading="Swarm"> <uib-tab index="1" heading="Swarm">
<code style="display: block; white-space: pre-wrap; padding: 16px 90px;">{{dockerCommands.swarm}}</code> <code style="display: block; white-space: pre-wrap; padding: 16px 90px;">{{ dockerCommands.swarm }}</code>
</uib-tab> </uib-tab>
</uib-tabset> </uib-tabset>
<div style="margin-top: 10px;"> <div style="margin-top: 10px;">
@ -118,6 +118,23 @@
<input type="text" class="form-control" id="endpoint_public_url" ng-model="endpoint.PublicURL" placeholder="e.g. 10.0.0.10 or mydocker.mydomain.com" /> <input type="text" class="form-control" id="endpoint_public_url" ng-model="endpoint.PublicURL" placeholder="e.g. 10.0.0.10 or mydocker.mydomain.com" />
</div> </div>
</div> </div>
<div class="form-group">
<label for="edge_checkin" class="col-sm-2 control-label text-left">
Poll frequency
<portainer-tooltip
position="bottom"
message="Interval used by this Edge agent to check in with the Portainer instance. Affects Edge endpoint management and Edge compute features."
></portainer-tooltip>
</label>
<div class="col-sm-10">
<select
id="edge_checkin"
class="form-control"
ng-model="endpoint.EdgeCheckinInterval"
ng-options="+(opt.value) as opt.key for opt in state.availableEdgeAgentCheckinOptions"
></select>
</div>
</div>
<!-- !endpoint-public-url-input --> <!-- !endpoint-public-url-input -->
<div class="col-sm-12 form-section-title"> <div class="col-sm-12 form-section-title">
Metadata Metadata

View File

@ -17,13 +17,32 @@ angular
TagService, TagService,
EndpointProvider, EndpointProvider,
Notifications, Notifications,
Authentication Authentication,
SettingsService
) { ) {
$scope.state = { $scope.state = {
uploadInProgress: false, uploadInProgress: false,
actionInProgress: false, actionInProgress: false,
deploymentTab: 0, deploymentTab: 0,
allowCreate: Authentication.isAdmin(), allowCreate: Authentication.isAdmin(),
availableEdgeAgentCheckinOptions: [
{ key: 'Use default interval', value: 0 },
{
key: '5 seconds',
value: 5,
},
{
key: '10 seconds',
value: 10,
},
{
key: '30 seconds',
value: 30,
},
{ key: '5 minutes', value: 300 },
{ key: '1 hour', value: 3600 },
{ key: '1 day', value: 86400 },
],
}; };
$scope.formValues = { $scope.formValues = {
@ -83,6 +102,7 @@ angular
PublicURL: endpoint.PublicURL, PublicURL: endpoint.PublicURL,
GroupID: endpoint.GroupId, GroupID: endpoint.GroupId,
TagIds: endpoint.TagIds, TagIds: endpoint.TagIds,
EdgeCheckinInterval: endpoint.EdgeCheckinInterval,
TLS: TLS, TLS: TLS,
TLSSkipVerify: TLSSkipVerify, TLSSkipVerify: TLSSkipVerify,
TLSSkipClientVerify: TLSSkipClientVerify, TLSSkipClientVerify: TLSSkipClientVerify,
@ -133,6 +153,7 @@ angular
endpoint: EndpointService.endpoint($transition$.params().id), endpoint: EndpointService.endpoint($transition$.params().id),
groups: GroupService.groups(), groups: GroupService.groups(),
tags: TagService.tags(), tags: TagService.tags(),
settings: SettingsService.settings(),
}) })
.then(function success(data) { .then(function success(data) {
var endpoint = data.endpoint; var endpoint = data.endpoint;
@ -149,6 +170,9 @@ angular
standalone: buildStandaloneCommand($scope.randomEdgeID, endpoint.EdgeKey), standalone: buildStandaloneCommand($scope.randomEdgeID, endpoint.EdgeKey),
swarm: buildSwarmCommand($scope.randomEdgeID, endpoint.EdgeKey), swarm: buildSwarmCommand($scope.randomEdgeID, endpoint.EdgeKey),
}; };
const settings = data.settings;
$scope.state.availableEdgeAgentCheckinOptions[0].key += ` (${settings.EdgeAgentCheckinInterval} seconds)`;
} }
$scope.endpoint = endpoint; $scope.endpoint = endpoint;
$scope.groups = data.groups; $scope.groups = data.groups;

View File

@ -118,8 +118,11 @@
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="edge_checkin" class="col-sm-2 control-label text-left"> <label for="edge_checkin" class="col-sm-2 control-label text-left">
Edge agent poll frequency Edge agent default poll frequency
<portainer-tooltip position="bottom" message="Specify the interval used by each Edge agent to checkin with the Portainer instance"></portainer-tooltip> <portainer-tooltip
position="bottom"
message="Interval used by default by each Edge agent to check in with the Portainer instance. Affects Edge endpoint management and Edge compute features."
></portainer-tooltip>
</label> </label>
<div class="col-sm-10"> <div class="col-sm-10">
<select <select