Skip to content

Commit

Permalink
Merge pull request #741 from eriknelson/bz1293985-2
Browse files Browse the repository at this point in the history
1293985 - Disconnected content mirror validation
  • Loading branch information
jmrodri committed Mar 31, 2016
2 parents f50c102 + 0705cec commit e955c4b
Show file tree
Hide file tree
Showing 9 changed files with 349 additions and 8 deletions.
132 changes: 132 additions & 0 deletions fusor-ember-cli/app/components/content-mirror-f.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import Ember from 'ember';
import TextFComponent from './text-f';
import request from 'ic-ajax';

const CDN_VERIFY_TIMEOUT = 3000;

export default TextFComponent.extend({
responseCounter: 0,
validationTrigger: null,
isVerifyingContentMirror: false,
dirty: false,

didInsertElement: function (){
if(this.get('cdnUrl')) {
this.queueValidation();
}
},

contentMirrorObserver: Ember.observer('cdnUrl', function() {
this.queueValidation();
}),

queueValidation: function() {
if(this.get('isVerifyingContentMirror') === false) {
this.setIsVerifyingContentMirror(true);
}

this.set('dirty', true);
const validationTrigger = this.get('validationTrigger');

if(validationTrigger) {
Ember.run.cancel(validationTrigger);
}

this.set(
'validationTrigger',
Ember.run.later(this, () => this.onValidate(), CDN_VERIFY_TIMEOUT)
);
},

onValidate: function() {
const cdnUrl = this.get('cdnUrl');
const protocolCheckRx = /^https?:\/\//;

if(!protocolCheckRx.test(cdnUrl)) {
this.setIsVerifyingContentMirror(false);
this.setContentMirrorValidation(false, 'Missing http protocol');
return;
}

// Guard against race condition of newer responses returning faster
// than old responses that could result in valid content mirrors
// being marked invalid, or vice versa
const responseCounter = this.get('responseCounter') + 1;
this.set('responseCounter', responseCounter);

const token = Ember.$('meta[name="csrf-token"]').attr('content');
const deploymentId = this.get('deploymentId');

const shouldUpdate = () => {
return responseCounter === this.get('responseCounter') &&
!this.get('dirty');
};

this.set('dirty', false);
this.set('validationTrigger', null);

request({
url: `/fusor/api/v21/deployments/${deploymentId}/validate_cdn`,
headers: {
"Accept": "application/json",
"X-CSRF-Token": token
},
data: {
cdn_url: encodeURIComponent(cdnUrl)
}
}).then((res) => {
// If the response is not the newest response local responseCounter
// will be less than the responseCounter member field),
// we want throw away the result since we know a more accurate
// result is incoming or already has updated our state
if(shouldUpdate()) {
this.setContentMirrorValidation(res.cdn_url_code === '200');
}
}).catch((err) => {
if(shouldUpdate()) {
this.setContentMirrorValidation(false);
}
}).finally(() => {
if(shouldUpdate()) {
this.setIsVerifyingContentMirror(false);
}
});
},

setContentMirrorValidation: function(isValid, validationMsg) {
this.set('isContentMirrorValid', isValid);

if(isValid) {
if(!validationMsg) {
this.set(
'contentMirrorValidationMsg',
'Content mirror verified'
);
}
this.sendAction('mirrorStatusUpdate', this.get('MirrorStatus').VALID);
} else {
if(!validationMsg) {
this.set(
'contentMirrorValidationMsg',
'Invalid content mirror'
);
}
this.sendAction('mirrorStatusUpdate', this.get('MirrorStatus').INVALID);
}

if(validationMsg) {
this.set(
'contentMirrorValidationMsg',
validationMsg
);
}
},
setIsVerifyingContentMirror: function(isVerifying) {
this.set('isVerifyingContentMirror', isVerifying);

if(isVerifying) {
this.sendAction(
'mirrorStatusUpdate', this.get('MirrorStatus').VALIDATING);
}
}
});
25 changes: 24 additions & 1 deletion fusor-ember-cli/app/controllers/subscriptions/credentials.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@ import Ember from 'ember';
import request from 'ic-ajax';
import NeedsDeploymentMixin from "../../mixins/needs-deployment-mixin";

const MirrorStatus = {
VALID: 1,
INVALID: 2,
VALIDATING: 3
};

export default Ember.Controller.extend(NeedsDeploymentMixin, {

deploymentId: Ember.computed.alias("deploymentController.model.id"),
Expand Down Expand Up @@ -62,6 +68,17 @@ export default Ember.Controller.extend(NeedsDeploymentMixin, {
hasManifestFile: Ember.computed.notEmpty('manifestFile'),
noManifestFile: Ember.computed.empty('manifestFile'),

disableNextDisconnected: Ember.computed(
'noManifestFile',
'currentMirrorStatus',
function()
{
// If currentMirrorStatus is not VALID, disable next
let retVal = this.get('noManifestFile') ||
this.get('currentMirrorStatus') !== this.get('MirrorStatus').VALID;
return retVal;
}),

contentProviderType: Ember.computed('isDisconnected', function() {
return (this.get('isDisconnected') ? "disconnected" : "redhat_cdn");
}),
Expand All @@ -74,6 +91,9 @@ export default Ember.Controller.extend(NeedsDeploymentMixin, {
return (this.get('contentProviderType') === 'disconnected');
}),

MirrorStatus: MirrorStatus,
currentMirrorStatus: MirrorStatus.INVALID,

actions: {
providerTypeChanged() {
return this.set('isDisconnected', this.get('isDisconnectedSelected'));
Expand Down Expand Up @@ -112,8 +132,11 @@ export default Ember.Controller.extend(NeedsDeploymentMixin, {

uploadDifferentManifest() {
return this.set("manifestFile", null);
},

mirrorStatusUpdate(newStatus) {
this.set('currentMirrorStatus', newStatus);
}
}


});
21 changes: 21 additions & 0 deletions fusor-ember-cli/app/styles/custom.scss
Original file line number Diff line number Diff line change
Expand Up @@ -543,3 +543,24 @@ table.fusor-table {
.btn-icon.btn-icon-primary {
color: #189ad1;
}

.content-mirror-status {
margin-top: 5px;
margin-left: 3px;

.spinner {
margin-left: 5px;
width: 15px;
height: 15px;
}

.valid-msg {
color: green;
font-weight: bold;
}

.invalid-msg {
color: red;
font-weight: bold;
}
}
12 changes: 12 additions & 0 deletions fusor-ember-cli/app/templates/components/content-mirror-f.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{{#text-f label="Content Mirror URL" value=cdnUrl disabled=false}}
<div class="content-mirror-status">
{{#if isVerifyingContentMirror}}
<span>Verifying mirror...</span>
<span class="spinner spinner-md spinner-inline"></span>
{{else if isContentMirrorValid}}
<span class="valid-msg">{{contentMirrorValidationMsg}}</span>
{{else}}
<span class="invalid-msg">{{contentMirrorValidationMsg}}</span>
{{/if}}
</div>
{{/text-f}}
10 changes: 8 additions & 2 deletions fusor-ember-cli/app/templates/subscriptions/credentials.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,13 @@
<p>
Satellite needs a URL within the network from which it can access content.
</p>
{{text-f label="Content Mirror URL" value=cdnUrl disabled=false}}

{{content-mirror-f
cdnUrl=cdnUrl
deploymentId=deploymentId
MirrorStatus=MirrorStatus
mirrorStatusUpdate="mirrorStatusUpdate"
}}

<p>
<br />
Expand Down Expand Up @@ -138,7 +144,7 @@
{{cancel-back-next backRouteName=backRouteNameonCredentials
disableBack=false
nextRouteName='subscriptions.review-subscriptions'
disableNext=noManifestFile
disableNext=disableNextDisconnected
disableCancel=isStarted
deploymentName=deploymentName}}

Expand Down
46 changes: 41 additions & 5 deletions server/app/controllers/fusor/api/v21/deployments_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@
# have received a copy of GPLv2 along with this software; if not, see
# http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt.

require "net/http"
require "uri"

module Fusor
class Api::V21::DeploymentsController < Api::V2::DeploymentsController

Expand Down Expand Up @@ -80,14 +83,47 @@ def redeploy
def validate
@deployment.valid?
render json: {
:validation => {
:deployment_id => @deployment.id,
:errors => @deployment.errors.full_messages,
:warnings => @deployment.warnings
}
:validation => {
:deployment_id => @deployment.id,
:errors => @deployment.errors.full_messages,
:warnings => @deployment.warnings
}
}
end

def validate_cdn
begin
if params.key?('cdn_url')
ad_hoc_req = lambda do |uri_str|
uri = URI.parse(uri_str)
http = Net::HTTP.new(uri.host, uri.port)
request = Net::HTTP::Head.new(uri.request_uri)
http.request(request)
end

unescaped_uri_str = URI.unescape(params[:cdn_url])
# Best we can reasonably do here is to check to make sure we get
# back a 200 when we hit $URL/content, since we can be reasonably
# certain a repo needs to have the /content path
full_uri_str = "#{unescaped_uri_str}/content"
full_uri_str = "#{unescaped_uri_str}content" if unescaped_uri_str =~ /\/$/

response = ad_hoc_req.call(full_uri_str)
# Follow a 301 once in case redirect /content -> /content/
final_code = response.code
final_code = ad_hoc_req.call(response['location']).code if response.code == '301'

render json: { :cdn_url_code => final_code }, status: 200
else
raise 'cdn_url parameter missing'
end
rescue => error
message = 'Malformed request'
message = error.message if error.respond_to?(:message)
render json: { :error => message }, status: 400
end
end

def log
log_type_param = params[:log_type] || 'fusor_log'
reader = create_log_reader(log_type_param)
Expand Down
1 change: 1 addition & 0 deletions server/config/routes/api/v21.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
put :deploy
put :redeploy
get :validate
get :validate_cdn
get :log
end
end
Expand Down
5 changes: 5 additions & 0 deletions server/test/.rubocop.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
inherit_from: ../../.rubocop.yml

# Disable class length offenses for test files
Metrics/ClassLength:
Enabled: false

0 comments on commit e955c4b

Please sign in to comment.