Nianet Speedtest

Speedtest

This is the rewrite of the speedtest system, designed for Nianet RFS tests

Requirements

Requires ruby version 1.9.3 or newer, rubygems and bundler. Run bundle install after every update.

Runs on Puma and can be proxied by Nginx or run standalone.

Remote server must be owampd and thrulay-ng

Written by Allan Eising

Top Level Namespace

Defined Under Namespace

Classes: Array, Hash, Hastighedstest

Class: Hastighedstest

Inherits:
Sinatra::Base
  • Object
show all
Defined in:
helpers/app.rb,
helpers/format_response.rb,
helpers/formhelper.rb,
helpers/owamp.rb,
helpers/ping.rb,
helpers/results.rb,
helpers/templates.rb,
helpers/tests.rb,
helpers/thrulay.rb,
controllers/admin.rb,
controllers/forms.rb,
controllers/reports.rb,
controllers/rest.rb,
controllers/schedule.rb,
controllers/templates.rb,
controllers/test.rb

Admin controller (collapse)

Forms controller (collapse)

Reports controller (collapse)

REST API (collapse)

Schedule controller (collapse)

Templates controller (collapse)

Test controller (collapse)

Instance Method Summary (collapse)

Instance Method Details

- (Object) get("/api/test/crid/:crid")

filters tests by CRID



27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
# File 'controllers/rest.rb', line 27

get '/api/test/crid/:crid' do
    # Method to get test by CRID
    args = { :crid => params[:crid] }
    if params.has_key? "rfs"
        if params[:rfs] =~ /^(1|true)$/
            args[:rfs] = true
        elsif params[:rfs] =~ /^(0|false)$/
            args[:rfs] = nil
        end
    end
    if params.has_key? "deleted"
        if params[:deleted] =~ /^(1|true)$/
            args[:deleted] = true
        elsif params[:deleted] =~ /^(0|false)$/
            args[:deleted] = nil
        end

    end

    test = Tests.where(args)
    if test.count == 0
        halt(404)
    else
        response = []
        data = test.all
        data.each do |resp|
            tresp = resp.to_hash
            tresp[:template_fields] = JSON.parse(tresp[:template_fields])
            response << tresp
        end
        format_response(response, request.accept)
    end
end

- (Object) get("/api/test/:id")

returns a given test in JSON



14
15
16
17
18
19
20
21
22
23
# File 'controllers/rest.rb', line 14

get '/api/test/:id' do
    test = Tests.where(:id => params[:id])
    if test.count == 0
        halt(404)
    else
        data = test.first.to_hash
        data[:template_fields] = JSON.parse(data[:template_fields]) 
        format_response(data, request.accept)
    end
end

- (Object) get("/api/tests")

Returns all tests, default formatted in JSON



7
8
9
10
# File 'controllers/rest.rb', line 7

get '/api/tests' do
    data = Tests.select(:id, :crid, :customer, :location, :timestamp, :deadline, :rfs, :deleted).all
    format_response(data, request.accept)
end

- (Object) build_results(sums, done = nil)

Formats results for graphing

Parameters:

  • sums (Hash)

    x/y values to graph

  • done (Bool) (defaults to: nil)

    whether the test is done

Returns:

  • A Hash that can be graphed



127
128
129
130
131
132
133
134
135
136
137
# File 'helpers/thrulay.rb', line 127

def build_results(sums, done=nil)
    result = { :label => "Mbit/s", :data => [] }
    sums.each do |k,v|
        result[:data] << [k, v]
    end
    if done
        {:output => result, :done => done}
    else
        {:output => result}
    end
end

- (Object) format_response(data, accept)



2
3
4
5
6
7
8
# File 'helpers/format_response.rb', line 2

def format_response(data, accept)
  accept.each do |type|
   # return data.to_xml  if type.downcase.eql? 'text/xml'
    return data.to_json if type.downcase.eql? 'application/json'
    return data.to_json
  end
end

- (Object) get("/admin/forms/undelete/:id")

Undeletes a form

Parameters:

  • id (Integer)

    the id to undelete



79
80
81
82
83
84
85
86
87
88
89
# File 'controllers/admin.rb', line 79

get '/admin/forms/undelete/:id' do
    id = params[:id]
    form = Forms.where(:id => id)
    if form.count == 1
        form.update(:deleted => false)
        flash[:notice] = "Undeleted form ##{id}"
        redirect to("/admin/forms")
    else
        raise Sinatra::NotFound
    end
end

- (Object) get("/admin")

Shows admin page



8
9
10
11
12
# File 'controllers/admin.rb', line 8

get '/admin' do
    @pagename = "admin"
    @pagetitle = "Administration"
    haml :admin/index'
end

- (Object) get("/admin/external")

Manage external reports



117
118
119
120
121
122
123
# File 'controllers/admin.rb', line 117

get '/admin/external' do
    @pagetitle = "Manage external reports"
    @pagename = "admin_external"
    @results = Results.where(:test_type => "external").all

    haml :admin/external'
end

- (Object) get("/admin/external/delete/:id")

Delete external report

Parameters:

  • id (Integer)

    the id to delete



128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
# File 'controllers/admin.rb', line 128

get '/admin/external/delete/:id' do
    id = params[:id]
    result = Results.where(:id => id)
    if not result.count == 1
        halt 404
    end
    res = JSON.parse(result.first.results)
    # Delete the file
    deleted = true
    begin
        File.delete(res["path"])
    rescue => e
        $stderr.puts e.inspect
        deleted = false
    end
    result.delete if deleted
    flash[:notice] = "Deleted external report"
    redirect to("/admin/external")
end

- (Object) get("/admin/forms")

Get admin/forms



69
70
71
72
73
74
# File 'controllers/admin.rb', line 69

get '/admin/forms' do
    @pagetitle = "Manage deleted forms"
    @pagename = "admin_forms"
    @forms = Forms.where(:deleted => true).all
    haml :admin/forms'
end

- (Object) get("/admin/migrate")

Migrate from old test system



151
152
153
154
155
156
157
158
159
160
# File 'controllers/admin.rb', line 151

get '/admin/migrate' do
    # Migrate tests
    @pagename = "schedule"
    @requests = DB[:requests].where(:rfs => 0).all

    @pagetitle = "Migrate from old system"
    @pagename = "admin_migrate"

    haml :admin/migrate'
end

- (Object) get("/admin/migrate/:id")

Migrate a specific test

Parameters:

  • id (Integer)

    id in the old test database



165
166
167
168
169
170
171
172
173
174
175
176
# File 'controllers/admin.rb', line 165

get '/admin/migrate/:id' do
    id = params[:id]
    req = Requests.where(:id => id)
    if req.count == 0
        halt 404
    end
    @req = req.first
    @forms = Forms.exclude(:deleted => true)
    
    haml :admin/migrate_config'
    
end

- (Object) get("/admin/results")

Manage test results



93
94
95
96
97
98
# File 'controllers/admin.rb', line 93

get '/admin/results' do
    @pagetitle = "Manage test results"
    @pagename = "admin_results"
    @tests = Tests.all
    haml :admin/results'
end

- (Object) get("/admin/results/purge/:id")

Purge all test results for a given test



103
104
105
106
107
108
109
110
111
112
113
# File 'controllers/admin.rb', line 103

get '/admin/results/purge/:id' do
    id = params[:id]
    test = Tests.where(:id => id)
    if test.count == 1
        count = test.first.test_results_dataset.delete
        flash[:notice] = "Deleted #{count} results from ID #{id}"
        redirect to("/admin/results")
    else
        raise Sinatra::NotFound
    end
end

- (Object) get("/admin/scheduling")

Manage deleted schedules



16
17
18
19
20
21
# File 'controllers/admin.rb', line 16

get '/admin/scheduling' do
    @pagetitle = "Manage deleted schedules"
    @pagename = "admin_scheduling"
    @tests = Tests.where(:deleted => true).or(:rfs => true).all
    haml :admin/scheduling'
end

- (Object) get("/admin/scheduling/undelete/:id")

Undeletes a deleted schedule

Parameters:

  • id (Integer)

    id to be undeleted



26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# File 'controllers/admin.rb', line 26

get '/admin/scheduling/undelete/:id' do
    id = params[:id]
    test = Tests.where(:id => id)
    if test.count == 1
        if test.first.deleted
            test.update(:deleted => false)
        elsif test.first.rfs
            test.update(:rfs => false)
        end
        flash[:notice] = "Undeleted scheduled test ##{id}"
        redirect to("/admin/scheduling")
    else
        raise Sinatra::NotFound
    end

end

- (Object) get("/admin/templates")

Shows the admin/templates view



45
46
47
48
49
50
# File 'controllers/admin.rb', line 45

get '/admin/templates' do
    @pagetitle = "Manage deleted templates"
    @pagename = "admin_templates"
    @templates = Templates.where(:deleted => true).all
    haml :admin/templates'
end

- (Object) get_configurable_tags(template_id)

Returns the tags that the user can configure

Parameters:

  • template_id (Integer)

    The database template ID

Returns:

  • an Array of tags



7
8
9
10
11
12
13
14
15
16
# File 'helpers/templates.rb', line 7

def get_configurable_tags(template_id)
    #speedtest = Speedtest.new($config)
    #tags = speedtest.get_template_tags(template_id)
    tags = get_template_tags(template_id)
    # We do not want to configure default tags
    default_tags = %w(testcpe testbb pairlocal pairremote netlocal netremote)
    default_tags.each { |t| tags.delete(t) }

    tags
end

- (Object) get("/forms")

Get all forms



6
7
8
9
10
11
12
13
# File 'controllers/forms.rb', line 6

get '/forms' do
    @pagename = "forms"
    @pagetitle = "Manage forms"

    @forms = Forms.exclude(:deleted => true).all

    haml :forms/forms'
end

- (Object) get("/forms/compose")

Compose form



36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
# File 'controllers/forms.rb', line 36

get '/forms/compose' do
    templates = Templates.exclude(:deleted => true).all
    @cpe_templates = []
    @bb_templates = []

    templates.each do |template|
        if template[:type] == "cpe"
            @cpe_templates << template
        else
            @bb_templates << template
        end
    end
    @pagename = "forms_compose"
    @pagetitle = "Manage forms"

    haml :forms/compose'
end

- (Object) get("/forms/delete/:id")

Deletes a form

Parameters:

  • id (Integer)

    The form id to delete



135
136
137
138
139
140
141
142
143
144
145
146
# File 'controllers/forms.rb', line 135

get '/forms/delete/:id' do

    # logic to delete id
    form = Forms.where(:id => params[:id])
    if form.count == 1
        form.update(:deleted => true)
        flash[:notice] = "Deleted form ##{params[:id]}"
        redirect to('/forms')
    else
        raise Sinatra::NotFound
    end
end

- (Object) get("/forms/update/:id")

Update an existing form



75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
# File 'controllers/forms.rb', line 75

get '/forms/update/:id' do
    id = params[:id]
    if not Forms.where(:id => id).count == 1
        halt 404
    end
    form = Forms.where(:id => id).first
    @cpe_template_id = form[:cpe_template_id]
    @backbone_template_id = form[:backbone_template_id]
    cpe_tags = get_configurable_tags(@cpe_template_id)
    backbone_tags = get_configurable_tags(@backbone_template_id)
    @tags = cpe_tags | backbone_tags
    @name = form[:name]
    @update = true
    @form_id = id
    @defaults = JSON.parse(form.defaults)

    haml :/forms/config'
end

- (Object) get("/forms/view/:id")

View forms



17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# File 'controllers/forms.rb', line 17

get '/forms/view/:id' do
    @pagename = "forms_view"
    @pagetitle = "View forms"

    @form = Forms.where(:id => params[:id])
    if not @form.count == 1
        halt 404
    else
        @form = @form.first
    end

    haml :forms/view'


end

- (Object) get("/")

Note:

This is the front page

Shows all tests scheduled



8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# File 'controllers/schedule.rb', line 8

get '/' do
    @pagename = "schedule_index"
    @pagetitle = "All Tests"
    page = params.fetch("page", 1).to_i
    if params[:crid] =~ /(N[A-Z]{2}-\d{6})/i
        @tests = Tests.exclude(:deleted => true).exclude(:rfs => true).where(:crid => $1.upcase).paginate(page, 25)
        if @tests.count == 0
            flash[:notice] = "No results found."
            redirect to("/")
        end
    elsif not params[:crid].nil?
        flash[:notice] = "No results found."
        redirect to("/")

    else
        @tests = Tests.exclude(:deleted => true).exclude(:rfs => true).order(Sequel.desc(:id)).paginate(page, 20)
    end

    haml :scheduling/index'

end

- (Object) get("/reports")

Generate reports



6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# File 'controllers/reports.rb', line 6

get '/reports' do
    @pagename = "reports"
    @pagetitle = "Generate Reports"
    @page = params.fetch "page", 1
    @page = @page.to_i
   
    if params[:crid] =~ /(N[A-Z]{2}-\d{6})/i
        atests = Tests.where(:crid => $1.upcase).all
        if atests.count == 0
            flash[:notice] = "No results found."
            redirect to("/reports")
        end
    elsif not params[:crid].nil?
        flash[:notice] = "No results found."
        redirect to("/reports")
    else
        atests = Tests.all
    end
    tests = []
    atests.each do |test| 
        if test.test_results_dataset.count > 0
            tests << test
        end
    end

    p tests
    p tests.class

    @tests = tests.paginate(:page => @page, :per_page => 20)




    haml :reports/reports'

end

- (Object) get("/reports/auto/:id")

Note:

This is meant to be run through pdfkit by adding .pdf after the id

Autoconfigures and displays a report

Parameters:

  • id (Integer)

    Id to view, suffix .pdf to view as pdf



130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
# File 'controllers/reports.rb', line 130

get '/reports/auto/:id' do
    # This meant to be run with pdfkit as view.pdf
    test = Tests.where(:id => params[:id])
    if test.count == 0
        # Raise an exception if the Test ID isn't in the DB
        raise Sinatra::NotFound
    else
        @test = test.first
    end
    external_ds =  @test.test_results_dataset.where(:test_type => "external")
    if external_ds.count > 0
        if env['REQUEST_PATH'] =~ /\.pdf$/
            new_path = env['REQUEST_PATH'].gsub(/\.pdf$/, '')
            redirect to(new_path)
        end
        result = external_ds.last
        res = JSON.parse(result.results)
        test = result.test
        file = res["path"]
        test = result.test
        Tests.where(:id => test.id).update(:rfs => true)
        tempfile = Tempfile.new(test.crid)
        kit = PDFKit.new("#{request.base_url}/internal/reports/manual/#{test.id}", :page_size => 'A4', :print_media_type => true)
        report = kit.to_pdf
        pdf = CombinePDF.new
        pdf << CombinePDF.parse(report)
        pdf << CombinePDF.parse(File.read("./#{file}"))
        pdf.save(tempfile.path)
        content_type 'application/pdf'
        File.read(tempfile.path)
    else

        rtt_ds = @test.test_results_dataset.where(:test_type => "rtt")        
        if rtt_ds.count > 0
            @rtt = JSON.parse(rtt_ds.last.results)
            @rtt_test = true
            @rtt_passed = verify_owamp(@rtt)
            
        end
        be_ds = @test.test_results_dataset.where(:test_type => "be")
        if be_ds.count > 0
            @be_result = be_ds.last
            @be_test_id = @be_result.id
            @be = get_thrulay_results(be_ds.last.results)
            @be_test = true
            @be_passed = validate_thrulay_results(be_ds.last.id, 80)
        end
        ef_ds = @test.test_results_dataset.where(:test_type => "ef")
        if ef_ds.count > 0
            @ef_result = ef_ds.last
            @ef_test_id = @ef_result.id
            @ef_test = true
            @ef = get_thrulay_results(ef_ds.last.results)
            @ef_passed = validate_thrulay_results(ef_ds.last.id, 28)
        end
        passed = true
        passed = false if @rtt_test and not @rtt_passed
        passed = false if @be_test and not @be_passed
        passed = false if @ef_test and not @ef_passed
        if passed
            test.update(:rfs => true)
            haml :reports/view', :layout => false
        else
            haml :reports/fail', :layout => false
        end
    end


end

- (Object) get("/reports/configure/:id")

Configure the report for a given id

Parameters:

  • id (Integer)

    the report id to configure



46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
# File 'controllers/reports.rb', line 46

get '/reports/configure/:id' do
    @pagename = "reports_configure"
    @pagetitle = "Configure Report"
    id = params[:id]
    test = Tests.where(:id => id)
        
    if test.count == 0
        raise Sinatra::NotFound
    else
        @test = test.first
    end
    if params[:remote]
        backbone_template = @test.form.backbone_template.contents_deconfigure
        cpe_template = @test.form.cpe_template.contents_deconfigure
        # Load template values
        template_fields = JSON.parse(@test.template_fields)
        # Find the pair entry
        pairs = settings.pairs
        pair = pairs.select { |x| x["remote"] == params[:remote] }.first
        # add defaults to the two hashes
        template_fields[:testcpe] = pair["cpe"]
        template_fields[:testbb] = pair["bb"]
        template_fields[:pairlocal] = pair["local"]
        template_fields[:pairremote] = pair["remote"]
        if pair.has_key? "netlocal"
            template_fields[:netlocal] = pair["netlocal"]
            template_fields[:netremote] = pair["netremote"]
        end
        @backbone_config = Mustache.render(backbone_template, template_fields)
        @cpe_config = Mustache.render(cpe_template, template_fields)

    end
    haml :reports/configure'
end

- (Object) get("/runtest.json")

Starts the thrulay test

Parameters:

  • test_id (Integer)

    the ID of the test

  • type (String)

    the test type, either be or ef

  • protocol (String)

    the protocol, tcp or udp

  • speed (Integer)

    The bandwidth to test for (UDP only)

  • sessions (Integer)

    Number of parallel TCP sessions

  • tcpwindowsize (Integer)

    TCP Window Size

  • blocksize (Integer)

    Thrulay block size



143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
# File 'controllers/test.rb', line 143

get '/runtest.json' do
    content_type :json
    test_id = params[:test_id]
    remote = params[:remote]
    type = params[:testtype]
    protocol = params[:protocol]
    speed = params[:speed].to_i
    sessions = params[:sessions]
    tcpwindowsize = params[:tcpwindowsize]
    blocksize = params[:blocksize]
    options = { :tcpwindowsize => tcpwindowsize, :sessions => sessions, :blocksize => blocksize }
    tag = params[:tag]
    content_type :json
    if type == "be"
        time = settings.testoptions["duration"]["be"]
        dscp = 0
    else
        time = settings.testoptions["duration"]["ef"]
        dscp = 46
    end
    if protocol == "tcp"
        $status = :running
        Processes.create(:status => "running", :tag => params[:tag])
        id = run_tcp_test(tag, test_id, remote, time, dscp, options)
    elsif protocol == "udp"
        $status = :running
        Processes.create(:status => "running", :tag => params[:tag])
        id = run_udp_test(tag, test_id, remote, time, speed, dscp)
    end

    { "status" => "success", "id" => id }.to_json


end

- (Object) get("/schedule")

Schedule a new test



32
33
34
35
36
37
38
# File 'controllers/schedule.rb', line 32

get '/schedule' do
    @pagename = "schedule"
    @pagetitle = "Schedule new test"
    @forms = Forms.exclude(:deleted => true).exclude(:inconsistent => true).all

    haml :scheduling/schedule'
end

- (Object) get("/schedule/delete/:id")

Deletes a scheduled test

Parameters:

  • id (Integer)

    Id of scheduled test



179
180
181
182
183
184
185
186
187
188
189
# File 'controllers/schedule.rb', line 179

get '/schedule/delete/:id' do
    id = params[:id]
    test = Tests.where(:id => id)
    if test.count == 1
        test.update(:deleted => true)
        flash[:notice] = "Deleted test ##{id}"
        redirect to("/")
    else
        raise Sinatra::NotFound
    end
end

- (Object) get("/schedule/embed")

Note:

This is intended to be embedded in BackOffice as an iframe.

Schedule a new test



43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
# File 'controllers/schedule.rb', line 43

get '/schedule/embed' do
    @embed = true

    @pagename = "schedule"
    @pagetitle = "Schedule new test"
    @forms = Forms.exclude(:deleted => true).exclude(:inconsistent => true)
    if params[:crid] =~ /^N[A-Z]{2}-\d{6}$/
        @crid = params[:crid]
    end
    if params[:customer]
        @customer = params[:customer]
    end
    if params[:location]
        @location = params[:location]
    end
    if params[:speed] =~ /^\d+$/
        @speed = params[:speed]
    end
    if @embed
        haml :scheduling/embed/schedule'
    else
        haml :scheduling/schedule'
    end

end

- (Object) get("/schedule/embedview/:id")

Note:

Uses the embedded BackOffice stylesheet

View test details



156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
# File 'controllers/schedule.rb', line 156

get '/schedule/embedview/:id' do
    @pagename = "test_embedview"
    @pagetitle = "Test details"

    id = params[:id]

    @test = Tests.where(:id => id)
    if not @test.count == 1
        halt 404
    end
    @test = @test.first
    @template_fields = JSON.parse(@test.template_fields)
    @test_id = id
    @embed = true

    haml :scheduling/embed/view'

end

- (Object) get("/stoptest/:tag")

Stops a test with a given tag

Parameters:

  • tag (String)

    Tag of the test to stop



115
116
117
118
119
120
# File 'controllers/test.rb', line 115

get '/stoptest/:tag' do
    if params[:tag]
        @process = Processes.where(:tag => params[:tag]).update(:status => "stopped")
    end
    haml :test/stop'
end

- (Object) get("/stoptest.json/:tag")

Note:

unsure if this is still in use

Stops a test with a given tag

Parameters:

  • tag (String)

    Tag of the test to stop



126
127
128
129
130
131
132
# File 'controllers/test.rb', line 126

get '/stoptest.json/:tag' do
    if params[:tag]
        pr = Processes.where(:tag => params[:tag]).update(:status => "stopped")
        p pr
    end
    haml :test/stop'
end

- (Object) get("/template/delete/:id")

Deletes a template

Parameters:

  • id (Integer)

    the template ID to delete



120
121
122
123
124
125
# File 'controllers/templates.rb', line 120

get '/template/delete/:id' do
    id = params[:id]
    Templates.where(:id => id).limit(1).update(:deleted => true)
    flash[:notice] = "Template ID #{id} deleted"
    redirect to("/templates")
end

- (Object) get("/template/:id")

TODO:

Handle lack of tags Prints to stderr. Necessary?

View template



17
18
19
20
21
22
23
# File 'controllers/templates.rb', line 17

get '/template/:id' do
    # TODO: Hande lack of tags
    id = params[:id]
    @template = Templates.where(:id => id).first
    p @template # TODO: Can I be removed?
    haml :templates/view'
end

- (Object) get_template_tags(id)

Extracts all tags from a Mustache template

Parameters:

  • id (Integer)

    the template ID from the database

Returns:

  • The tags from both the configure and deconfigure templates



22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# File 'helpers/templates.rb', line 22

def get_template_tags(id)
    config_text = Templates.where(:id => id).first.contents
    if config_text
        config = Mustache::Template.new(Templates.where(:id => id).first.contents).tags
    else
        config = []
    end
    deconfig_text = Templates.where(:id => id).first.contents_deconfigure
    if deconfig_text
        deconfig = Mustache::Template.new(Templates.where(:id => id).first.contents_deconfigure).tags
    else
        deconfig = []
    end
    config | deconfig
end

- (Object) get("/templates")

Administrate templates



6
7
8
9
10
11
# File 'controllers/templates.rb', line 6

get '/templates' do
    @pagename = "templates"
    @pagetitle = "Administrate templates"
    @templates = Templates.exclude(:deleted => true).all
    haml :templates/templates'
end

- (Object) get("/templates/compose")

Compose template



33
34
35
36
37
38
# File 'controllers/templates.rb', line 33

get '/templates/compose' do
    @pagename = "templates_compose"
    @pagetitle = "Administrate templates"
    @template = {}
    haml :templates/compose'
end

- (Object) get("/templates/edit/:id")

Edit a template



42
43
44
45
46
47
48
49
50
51
52
53
54
# File 'controllers/templates.rb', line 42

get '/templates/edit/:id' do
    @pagename = "templates_compose"
    @pagetitle = "Administrate templates"
    @template = Templates.where(:id => params[:id]).first
    @edit = true
    if @template.type == "cpe"
        @forms = @template.form_cpe_template_dataset.all
    else
        @forms = @template.form_backbone_template_dataset.all
    end

    haml :templates/edit'
end

- (Object) get("/templates/tags/:id")

Configure validators and datatypes for a template

Parameters:

  • id (Integer)

    the template ID



82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
# File 'controllers/templates.rb', line 82

get '/templates/tags/:id' do
    @pagetitle = "Configure validators"
    @pagename = "templates_tags"
    id = params[:id]
    @validators = settings.validators
    @templateid = id
    error = false
    begin
        @tags = get_configurable_tags(id)
    rescue Mustache::Parser::SyntaxError => @e
        error = true
    end
    if error
        Templates.where(:id => id).delete
        haml :templates/error'
    else
        haml :templates/tags'
    end
end

- (Object) get("/admin/templates/undelete/:id")

Undeletes a deleted template

Parameters:

  • id (Integer)

    the id to undelete



55
56
57
58
59
60
61
62
63
64
65
# File 'controllers/admin.rb', line 55

get '/admin/templates/undelete/:id' do
    id = params[:id]
    template = Templates.where(:id => id)
    if template.count == 1
        template.update(:deleted => false)
        flash[:notice] = "Undeleted template ##{id}"
        redirect to("/admin/templates")
    else
        raise Sinatra::NotFound
    end
end

- (Object) get("/templates/validators.json")

Returns validators in JSON



27
28
29
# File 'controllers/templates.rb', line 27

get '/templates/validators.json' do
    config[:validators].to_json
end

- (Object) get("/test/delay")

Runs owping and shows the results



191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
# File 'controllers/test.rb', line 191

get '/test/delay' do
    remote = params[:remote]
    test_id = params[:test_id]
    if test_id =~ /^\d+$/ and Tests.where(:id => test_id).count == 1
        test_results = run_owamp(remote)

        res = Results.create(:test_id => test_id, :test_type => "rtt", :results => test_results.to_json, :timestamp => Time.now)

        # Write output
        output = "<h3>RTT A-B</h3>\n<label>Delay minimum/median/max</label><br />\n"
        output += "#{test_results[:local][:min]}/#{test_results[:local][:median]}/#{test_results[:local][:max]} ms<br />\n"
        output += "<label>One-way Jitter</label><br />\n"
        output += "#{test_results[:local][:jitter]} ms<br />\n"
        output += "<label>Packet loss</label><br />\n"
        output += "#{test_results[:local][:loss]}<br />"
        output += "<h3>RTT B-A</h3>\n<label>Delay minimum/median/max</label><br />\n"
        output += "#{test_results[:remote][:min]}/#{test_results[:remote][:median]}/#{test_results[:remote][:max]} ms<br />\n"
        output += "<label>One-way Jitter</label><br />\n"
        output += "#{test_results[:remote][:jitter]} ms<br />\n"
        output += "<label>Packet loss</label><br />\n"
        output += "#{test_results[:remote][:loss]}<br />"
        (verified, errors) = verify_owamp(test_results, true)
        if not verified
            output += "<span class=\"error\">This test failed. Click next to run the test again, or  <a href=\"/test/deprovision/#{@test_id}?remote=#{@remote}\">deprovision the test</a>.</span>\n"
            output += "<h3>Errors:</h3>\n"
            output += "<ul>\n"
            errors.each do |error|
                output += "<li>#{error}</li>\n"
            end
            output += "</ul><br />\n"
        end


        output
    else
        raise Sinatra::NotFound
    end
end

- (Object) get("/test/deprovision/:id")

Returns configuration to deconfigure test



353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
# File 'controllers/test.rb', line 353

get '/test/deprovision/:id' do
    test = Tests.where(:id => params[:id])
    if test.count == 0
        halt(404)
    end
    test = test.first
    if params[:remote]
        backbone_template = test.form.backbone_template.contents_deconfigure
        cpe_template = test.form.cpe_template.contents_deconfigure
        # Load template values
        template_fields = JSON.parse(test.template_fields)
        # Find the pair entry
        pairs = settings.pairs
        pair = pairs.select { |x| x["remote"] == params[:remote] }.first
        # add defaults to the two hashes
        template_fields[:testcpe] = pair["cpe"]
        template_fields[:testbb] = pair["bb"]
        template_fields[:pairlocal] = pair["local"]
        template_fields[:pairremote] = pair["remote"]
        if pair.has_key? "netlocal"
            template_fields[:netlocal] = pair["netlocal"]
            template_fields[:netremote] = pair["netremote"]
        end
        @backbone_config = Mustache.render(backbone_template, template_fields)
        @cpe_config = Mustache.render(cpe_template, template_fields)
        haml :test/deprovision'
    else
        halt(404)
    end
        
end

- (Object) get("/test/graph.json/:id")

Function to fetch stored graph data as JSON



287
288
289
290
291
292
293
294
295
# File 'controllers/test.rb', line 287

get '/test/graph.json/:id' do
    content_type :json
    result = Results.where(:id => params[:id])
    if result.count == 0
        raise Sinatra::NotFound
    else
        result.first.results
    end
end

- (Object) get("/test/run/:id")

Runs a specific test



7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# File 'controllers/test.rb', line 7

get '/test/run/:id' do
    @pagename = "test_run"
    @pagetitle = "Run test"
    
    id = params[:id]

    @test = Tests.where(:id => id).first
    @template_fields = JSON.parse(@test.template_fields)
    if @test.form.name =~ /L3POI/
        @pairs = []
        settings.pairs.each do |pair|
            if pair["l3"]
                @pairs << pair
            end
        end
    else
        @pairs = settings.pairs
    end
    @test_id = id

    haml :test/run'

end

- (Object) get("/test/saveimage/:id")

This function finds the latest graph of a specific type, with no image data, reloads that graph and tries to save the image



299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
# File 'controllers/test.rb', line 299

get '/test/saveimage/:id' do
    @pagename = "test_saveimage"
    @pagetitle = "Save graph image"
    testds = Tests.where(:id => params[:id])
    halt(404) unless testds.count == 1
    halt(404) unless params[:test_type] =~ /^(be|ef)$/
    halt(404) unless params[:next]
    halt(404) unless params[:remote]
    resultds = testds.first.test_results_dataset.where(:test_type => params[:test_type], :image => nil)
    halt(404) if resultds.count == 0
    @result = resultds.last
    @next = params[:next]
    @remote = params[:remote]

    haml :test/saveimage'
end

- (Object) get("/test/setup")

Page that sets up the next test and validates test results



232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
# File 'controllers/test.rb', line 232

get '/test/setup' do
    id = params[:test_id]
    @test_id = id
    @remote = params[:remote]
    @test = Tests.where(:id => id).first
    @config = settings.testoptions
    if params[:last_test] =~ /^(be|ef)$/
        result = @test.test_results_dataset.where(:test_type => params[:last_test]).last
        speed = @test.speed
        if params[:last_test] == "ef"
            percentage = 24
        else
            percentage = 80
        end
        validated = validate_thrulay_results(result.id, percentage)
    elsif params[:last_test] == "rtt"
        result = @test.test_results_dataset.where(:test_type => params[:last_test]).last
        validated = verify_owamp(JSON.parse(result.results))
    else
        validated = true
    end
    if validated 
        @measurements = params[:next].split(',')
        @measure = @measurements.shift
    else
        result.delete
        @error = "The last test was not passed. Run the test again, or <a href=\"/test/deprovision/#{@test_id}?remote=#{@remote}\">deprovision the test</a>."
        @measure = params[:last_test]
        @measurements = params[:next].split(',')
    end


    case @measure
    when "verify"
        @pagetitle = "Verifying connectivity"
        @pagename = "test_verify"
        haml :test/verify'
    when "rtt"
        @pagetitle = "Running delay test"
        @pagename = "test_delay"
        haml :test/delay'
    when "report"
        redirect to("/reports/configure/#{id}?remote=#{@remote}")
    when /^(be|ef)$/
        @pagetitle = "Set up test"
        @pagename = "test_setup"
        @testtype = @measure
        haml :test/setup'
    else
        raise Sinatra::NotFound
    end
end

- (Object) get("/test/tagstatus.json/:tag")

Returns the status for a given tag



318
319
320
321
322
323
# File 'controllers/test.rb', line 318

get '/test/tagstatus.json/:tag' do
    content_type :json
    process = Processes.where(:tag => params[:tag])
    halt(404) if process.count == 0 
    { :tag => params[:tag], :statustext => process.first.status }.to_json
end

- (Object) get("/test/upload/:id")

Upload an external PDF report to merge with report



387
388
389
390
391
392
393
394
395
396
397
398
399
400
# File 'controllers/test.rb', line 387

get '/test/upload/:id' do
    # Upload PDF to merge with report
    @pagename = "test_upload"
    @pagetitle = "Upload custom Report"
    
    tests = Tests.exclude(:deleted => true).exclude(:rfs => true).where(:id => params[:id])
    if not tests.count == 1
        halt(404)
    end
    @test = tests.first

    haml :test/upload'
    
end

- (Object) get("/test/verify.json/:ip")

Verify reachability of the remote ip

Parameters:

  • ip (String)

    IP address to test

Returns:

  • JSON hash



96
97
98
99
100
101
102
103
104
105
106
107
108
# File 'controllers/test.rb', line 96

get '/test/verify.json/:ip' do
    content_type :json
    if params[:ip] =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/
        ip = params[:ip]
    else
        raise Sinatra::NotFound
    end
    if ping(ip)
        { :ip => ip, :response => "ok"}.to_json
    else
        { :ip => ip, :response => "fail"}.to_json
    end
end

- (Object) get("/test/viewimage/:id")

views urlencoded image



341
342
343
344
345
346
347
348
349
# File 'controllers/test.rb', line 341

get '/test/viewimage/:id' do
    result = Results.where(:id => params[:id])
    if result.count == 0
        raise Sinatra::NotFound
    end
    content_type :image/png"
    uri = URI::Data.new(result.first.image)
    uri.data
end

- (Object) get_thrulay_results(result)

Extract thrulay results from JSON document

Parameters:

  • result (JSON)

    The JSON formatted results column from the database

Returns:

  • A result hash including `:average`, `:max` and `:min` values



15
16
17
18
19
20
21
# File 'helpers/results.rb', line 15

def get_thrulay_results(result)
    # This function expects the JSON formatted results column from the db
    data = JSON.parse(result)
    numbers = []
    data["output"]["data"].each { |v| numbers << v[1] }
    { :average => numbers.mean.round(2), :max => numbers.max.round(2), :min => numbers.min.round(2) }
end

- (Object) get("/admin/results/purge/:id")

Purge all test results for a given test



103
104
105
106
107
108
109
110
111
112
113
# File 'controllers/admin.rb', line 103

get '/admin/results/purge/:id' do
    id = params[:id]
    test = Tests.where(:id => id)
    if test.count == 1
        count = test.first.test_results_dataset.delete
        flash[:notice] = "Deleted #{count} results from ID #{id}"
        redirect to("/admin/results")
    else
        raise Sinatra::NotFound
    end
end

- (Object) input(type, name, value = nil)

Builds an html input tag

Parameters:

type

the html input type, e.g. text, hidden, password, etc.

name

the form element name

value

(Optional) the value in the input field

Returns:

An <input /> tag



14
15
16
17
18
19
20
# File 'helpers/formhelper.rb', line 14

def input(type, name, value=nil)
    if value
        "<input type=\"#{type}\" name=\"#{name}\" value=\"#{value}\" id=\"#{name}\" />"
    else
        "<input type=\"#{type}\" name=\"#{name}\" id=\"#{name}\" />"
    end
end

- (Object) ping(remote)

Pings a remote host with one packet

Parameters:

  • remote (String)

    The host to ping

Returns:

  • true or false



6
7
8
9
# File 'helpers/ping.rb', line 6

def ping(remote)
    result = system("ping -c 1 -W 1 #{remote} > /dev/null")
    result
end

- (Object) post("/forms/add")

Submits a form



97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
# File 'controllers/forms.rb', line 97

post '/forms/add' do
    defaults = {}
    params.each do |param, value|
        if res = param.match(/^tag\.(.*)$/)
            defaults[res[1]] = {} unless defaults.has_key? res[1]
            defaults[res[1]][:value] = value 
        end
        if res = param.match(/^name\.(.*)$/)
            defaults[res[1]] = {} unless defaults.has_key? res[1]
            defaults[res[1]][:name] = value
        end
    end
    args = { 
        :cpe_template_id => params[:cpe_template_id], 
        :backbone_template_id => params[:backbone_template_id],
        :name => params[:name],
        :defaults => defaults.to_json,
        :inconsistent => nil
    }
    if params[:form_id]
        form = Forms.where(:id => params[:form_id])
        if form.count == 1
            form = form.first
            form.update(args)
            flash[:notice] = "updated form ##{form.id}"
        else
            halt 404
        end
    else
        form = Forms.create(args)
        flash[:notice] = "Added form ##{form.id}"
    end
    redirect to('/forms')
end

- (Object) post("/forms/config")

Configure composed form



58
59
60
61
62
63
64
65
66
67
68
69
70
# File 'controllers/forms.rb', line 58

post '/forms/config' do
    @defaults = {}
    @pagename = "forms_config"
    @pagetitle = "Configure form"
    @cpe_template_id = params[:cpe_template_id]
    @backbone_template_id = params[:backbone_template_id]
    cpe_tags = get_configurable_tags(@cpe_template_id)
    backbone_tags = get_configurable_tags(@backbone_template_id)
    @tags = cpe_tags | backbone_tags
    @name = params[:name]

    haml :/forms/config'
end

- (Object) post("/reports/merge")

Merges a report with an external report



202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
# File 'controllers/reports.rb', line 202

post '/reports/merge' do
    result_id = params[:result_id]
    result = Results.where(:id => result_id)
    if not result.count == 1
        $stderr.puts "result #{params[:test_id]} not found"
        halt(404)
    end
    result = result.last
    res = JSON.parse(result.results)
    file = res["path"]
    test = result.test
    if params[:delete] == "true"
        Tests.where(:id => test.id).update(:rfs => true)
    end
    tempfile = Tempfile.new(test.crid)
    kit = PDFKit.new("#{request.base_url}/internal/reports/manual/#{test.id}", :page_size => 'A4', :print_media_type => true)
    report = kit.to_pdf
    pdf = CombinePDF.new
    pdf << CombinePDF.parse(report)
    pdf << CombinePDF.parse(File.read("./#{file}"))
    pdf.save(tempfile.path)
    content_type 'application/pdf'
    File.read(tempfile.path)
end

- (Object) post("/reports/view")

Note:

This is supposed to be run through pdfkit as view.pdf

Views a report



84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
# File 'controllers/reports.rb', line 84

post '/reports/view' do
    # This meant to be run with pdfkit as view.pdf
    test = Tests.where(:id => params[:test_id])
    passed = true
    if test.count == 0
        # Raise an exception if the Test ID isn't in the DB
        raise Sinatra::NotFound
    else
        @test = test.first
    end
    if params[:rtt_test_id]
        @rtt_test = true
        @rtt = JSON.parse(Results.where(:id => params[:rtt_test_id]).first.results)
    end
    if params[:be_test_id]
        @be_test = true
        @be_test_id = params[:be_test_id]
        @be_result = Results.where(:id => @be_test_id).first
        @be = get_thrulay_results(@be_result.results)
        @be_passed = validate_thrulay_results(@be_test_id, 80)
        if @be_passed == false
            passed = false
        end
    end
    if params[:ef_test_id]
        @ef_test = true
        @ef_test_id = params[:ef_test_id]
        @ef_result = Results.where(:id => @ef_test_id).first
        @ef = get_thrulay_results(@ef_result.results)
        @ef_passed = validate_thrulay_results(@ef_test_id, 28)
        if @ef_passed == false
            passed = false
        end
    end
    # Delete the request
    if params[:delete] == "true" and passed
        test.update(:rfs => true) 
    end

    haml :reports/view', :layout => false
end

- (Object) post("/schedule")

TODO:

Sanitize input

Collect form elements for the scheduled test



133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
# File 'controllers/schedule.rb', line 133

post '/schedule' do
    # collect form elements
    # TODO: sanitize input
    template_fields = {}
    params.each do |param, value|
        vars = %w(deadline crid customer location speed form_id submit embed)
        next if vars.include? param
        name = param.gsub('.', '__')
        template_fields[name] = value
    end
    deadline = Date.strptime(params[:deadline], '%d-%m-%Y')
    test = Tests.create(:template_fields => template_fields.to_json, :crid => params[:crid], :customer => params[:customer], :location => params[:location], :speed => params[:speed], :form_id => params[:form_id], :deadline => deadline, :timestamp => Time.now)
    flash[:notice] = "Created test with ID #{test.id}"
    if params[:embed] == "true"
        redirect to("/schedule/embedview/#{test.id}")
    else
        redirect to("/")
    end
end

- (Object) post("/schedule/config")

Configure test



73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
# File 'controllers/schedule.rb', line 73

post '/schedule/config' do
    @pagename = "schedule_config"
    @pagetitle = "Configure test"
    @crid = params[:bo_CRID]
    @customer = params[:bo_FullCompanyName]
    @location = params[:bo_LocationAFullName]
    @speed = params[:bo_CPE.CPEConnectionSpeed"]
    @form_id = params[:form_id]
    @deadline = params[:BO_ContractDeliveryDate"]
    if params[:request_id]
        @req = Requests.where(:id => params[:request_id])
        if not @req.count == 1
            halt 404
        end
        @req = @req.first
    end

    if params[:embed] == "true"
        @embed = true
    end
    validators = settings.validators
    form = Forms.where(:id => params[:form_id]).first
    defaults = JSON.parse(form.defaults)
    # Other tags
    all_cpe_tags = get_configurable_tags(form[:cpe_template_id])
    all_backbone_tags = get_configurable_tags(form[:backbone_template_id])
    all_cpe_fields = JSON.parse(form.cpe_template.fields)
    all_backbone_fields = JSON.parse(form.backbone_template.fields)


    all_cpe_tags.each do |tag| 
        unless defaults.has_key? tag
            defaults[tag] = {}
        end
        if all_cpe_fields.has_key? tag
            next if all_cpe_fields[tag] == "none"
            defaults[tag][:klass] = validators[all_cpe_fields[tag]][:class]
        end
    end
    all_backbone_tags.each do |tag|
        unless defaults.has_key? tag
            defaults[tag] = {}
        end
        if all_backbone_fields.has_key? tag
            next if all_backbone_fields[tag] == "none"
            defaults[tag][:klass] = validators[all_backbone_fields[tag]][:class]
        end
    end

    @defaults = defaults
    if @embed
        haml :scheduling/embed/config'
    else
        haml :scheduling/config'
    end
end

- (Object) post("/templates/tags")

Submit template tags to the db



104
105
106
107
108
109
110
111
112
113
114
115
# File 'controllers/templates.rb', line 104

post '/templates/tags' do
    tags = {}
    params.each do |param, value|
        if param =~ /^tag\.(.*)$/
            tags[$1] = value
        end
    end
    dbid = params[:templateid]
    Templates.where(:id => dbid).update(:fields => tags.to_json)

    redirect to("/templates")
end

- (Object) post("/templates/add")

Add a template



59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
# File 'controllers/templates.rb', line 59

post '/templates/add' do
    args = { :name => params[:name], :contents => params[:contents], :type => params[:type], :description => params[:description], :contents_deconfigure => params[:contents_deconfigure] }
    if params[:template_id] =~ /\d+/
        Templates.where(:id => params[:template_id]).update(args)
        id = params[:template_id]
        if params[:forms].class == Array
            params[:forms].each do |form_id|
                form = Forms.where(:id => form_id)
                if form.count == 1
                    form.update(:inconsistent => true)
                end
            end
        end
    else
        template = Templates.create(args)
        id = template.id
    end
    redirect to("/templates/tags/#{id}")
end

- (Object) post("/test/config")

Configure test



33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
# File 'controllers/test.rb', line 33

post '/test/config' do
    @pagename = "test_config"
    @pagetitle = "Configure test"
    @measurements = params[:tests]
    @measurements << "report"
    @measurements.unshift "verify"

    id = params[:test_id]

    test = Tests.where(:id => id).first
    
    # Load templates
    backbone_template = test.form.backbone_template.contents
    cpe_template = test.form.cpe_template.contents
    # Load template values
    template_fields = JSON.parse(test.template_fields)
    
    # Find the pair entry
    pairs = settings.pairs
    pair = pairs.select { |x| x["remote"] == params[:remote] }.first
    # add defaults to the two hashes
    template_fields[:testcpe] = pair["cpe"]
    template_fields[:testbb] = pair["bb"]
    template_fields[:pairlocal] = pair["local"]
    template_fields[:pairremote] = pair["remote"]
    if pair.has_key? "netlocal"
        template_fields[:netlocal] = pair["netlocal"]
        template_fields[:netremote] = pair["netremote"]
    end
    # BO names
    if template_fields.has_key? "bo_CPE__CPEIP"
        @mgmtcpe = template_fields["bo_CPE__CPEIP"]
    end
    if template_fields.has_key? "bo_LocationB_DeviceName"
        @bbnode = template_fields["bo_LocationB_DeviceName"]
    end
    if template_fields.has_key? "bo_CPE__Router2_DeviceName"
        @bbnode = "Two routers"
    end

    if template_fields.has_key? "mgmtcpe"
        @mgmtcpe = template_fields["mgmtcpe"]
    end
    if template_fields.has_key? "bbnode"
        @bbnode = template_fields["bbnode"]
    end


    # Compile configs
    @backbone_config = Mustache.render(backbone_template, template_fields)
    @cpe_config = Mustache.render(cpe_template, template_fields)
    @remote = pair["remote"]
    @test_id = id
    @params = params

    haml :test/config'

end

- (Object) post("/test/result")

Initiates test



180
181
182
183
184
185
186
187
# File 'controllers/test.rb', line 180

post '/test/result' do
    @opts = params
    @pagename = "test_result"
    @pagetitle = "Running test"
    @tag = Time.now.to_i

    haml :test/result'
end

- (Object) post("/test/saveimage")

Saves a graph



327
328
329
330
331
332
333
334
335
336
337
# File 'controllers/test.rb', line 327

post '/test/saveimage' do
    data = params[:data]
    result_id = params[:result_id]

    result = Results.where(:id => result_id)
    if result.count == 0
        raise Sinatra::NotFound
    end
    result.update(:image => data)
    "<b>Successfully inserted image!<b>\n"
end

- (Object) post("/test/upload")

Upload function



404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
# File 'controllers/test.rb', line 404

post '/test/upload' do
    test_id = params[:test_id]
    test = Tests.where(:id => test_id)
    if not test.count == 1
        halt 404
    end
    test = test.first
    destination = "files/report_#{test.crid}_test_#{test_id}.pdf"
    file = params[:file][:tempfile]
    File.open("./#{destination}", "wb") do |f|
        f.write(file.read)
    end
    Results.create(:test_id => test_id, :test_type => "external", :results => { "path" => destination, "filename" => params[:file][:filename] }.to_json, :timestamp => Time.now)
    redirect to("/reports/configure/#{test_id}")
end

- (Hash) run_owamp(remote)

Launches owping agains a remote address

Parameters:

  • remote (String)

    the remote server ip

Returns:

  • (Hash)

    the results in a Hash format



7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
# File 'helpers/owamp.rb', line 7

def run_owamp(remote)
    #owping_bin = "/usr/local/bin/owping" # FIXME before release
    owping_bin = settings.binaries["owping"]
    owping_opts = "-c 1000 -i .01 -n m #{remote}"
    results = {}
    # Run test a-b
    Open3.popen3("#{owping_bin} #{owping_opts}") do |stdin, stdout, stderr, thread|
        { :out => stdout, :err => stderr }.each do |key, stream|
            Thread.abort_on_exception=true
            te = Thread.new do
                until (raw_line = stream.gets).nil? do
                    if res = raw_line.match(/---.*to\s\[#{remote}/)
                        direction = :local
                    end
                    if res = raw_line.match(/---.*from\s\[#{remote}/)
                        direction = :remote
                    end
                    if res = raw_line.match(/^(?<sent>\d+)\ssent,\s(?<lost>\d+)\slost\s\((?<percentage>\d+\.\d+%)\),\s(?<duplicates>\d+)\sduplicates$/)
                        loss = { :loss => res["percentage"] }
                        if results[direction]
                            results[direction].merge loss
                        else
                            results[direction] = loss
                        end
                    end
                    if res = raw_line.match(/^one-way delay min\/median\/max = (?<min>\d+(?:\.\d+)?)\/(?<median>\d+(?:\.\d+)?)\/(?<max>\d+(?:\.\d+)?) ms/)
                        results[direction][:min] = res["min"]
                        results[direction][:median] = res["median"]
                        results[direction][:max] = res["max"]
                    end
                    if res = raw_line.match(/^one-way jitter = (?<jitter>\d+(?:\.\d+)?) ms/)
                        results[direction][:jitter] = res["jitter"]
                    end
                end
            end
            te.join
        end
        thread.join
    end
    results
end

- (Object) run_tcp_test(tag, test_id, server, time, dscp = 0, opts = nil)

Run thrulay in TCP mode

Parameters:

  • tag (String)

    a tag to identify the running test process

  • test_id (Integer)

    the ID of the current test from the DB

  • server (String)

    the IP of the remote thrulayd

  • time (Integer)

    number of seconds to run the test

  • dscp (Integer) (defaults to: 0)

    DiffServ Code Point for the test

  • opts (Hash) (defaults to: nil)

    Options to pass to thrulay

Returns:

  • id of the results table row



40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
# File 'helpers/thrulay.rb', line 40

def run_tcp_test(tag, test_id, server, time, dscp=0, opts=nil)
    if dscp == 0
        test_type = "be"
    else
        test_type = "ef"
    end
    ps = Processes.where(:tag => tag)
    raise "No test_id" if test_id.nil?
    config = test_config[:testoptions].first
    if opts
        if opts.has_key? :tcpwindowsize
            windowsize = opts[:tcpwindowsize]
        else
            windowsize = config[:tcpwindowsize]
        end
        if opts.has_key? :sessions
            sessions = opts[:sessions]
        else
            sessions = config[:default_sessions]
        end
        if opts.has_key? :blocksize
            blocksize = opts[:blocksize]
        else
            blocksize = config[:blocksize]
        end
    else
        windowsize = config[:tcpwindowsize]
        sessions = config[:default_sessions]
        blocksize = config[:blocksize]
    end



    thrulay_bin = settings.binaries["thrulay"]
    thrulay_opts = "-D #{dscp} -t #{time} -m #{sessions} -l #{blocksize} -w #{windowsize} #{server}"
    sums = {}
    cur_end = 0

    result = Results.create(:test_id => test_id, :test_type => test_type, :protocol => "tcp")
    # Wrap everything around spork
    runfork(ps) do
        Open3.popen3("#{thrulay_bin} #{thrulay_opts}") do |stdin, stdout, stderr, thread|
            pid = thread.pid
            { :out => stdout, :err => stderr }.each do |key, stream|
                t = Thread.new do
                    until (raw_line = stream.gets).nil? do
                        if ps.first.status == "stopped"
                            result.delete
                            Process.kill("TERM", pid)
                            Thread.kill(t)
                        end
                        if res = raw_line.match(/\s+\(\s?(\d+)\)\s+(\d+)\.\d+\s+(\d+)\.\d+\s+(\d+\.\d+)/)
                            thread = res[1]
                            bw = res[4]
                            s_end = res[3]

                            if sums.has_key? s_end.to_i
                                sums[s_end.to_i] += bw.to_f
                            else
                                sums[s_end.to_i] = bw.to_f
                            end
                            if cur_end != s_end
                                # Write results to db
                                result.update(:results => build_results(sums).to_json, :timestamp => Time.now)
                                cur_end = s_end
                            end


                        end
                    end
                end
            end
            thread.join
        end

        result.update(:results => build_results(sums).to_json, :timestamp => Time.now)
        ps.update(:status => "done")
    end
    return result.id
end

- (Object) run_udp_test(tag, test_id, server, time, speed, dscp = 0)

Run a udp test

Parameters:

  • tag (String)

    a tag to identify the running test process

  • test_id (Integer)

    the ID of the current test from the DB

  • server (String)

    the IP of the remote thrulayd

  • time (Integer)

    number of seconds to run the test

  • speed (Integer)

    Bandwidth to run the UDP test in. Mbit/s.

  • dscp (Integer) (defaults to: 0)

    DiffServ Code Point for the test.

Returns:

  • id of the results table row



147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
# File 'helpers/thrulay.rb', line 147

def run_udp_test(tag, test_id, server, time, speed, dscp=0)
    raise "No test_id" if test_id.nil?
    thrulay_bin = settings.binaries["thrulay"]
    if dscp == 0
        test_type = "be"
    else
        test_type = "ef"
    end

    cur_time = 0

    sums = { }
    result = Results.create(:test_id => test_id, :test_type => test_type, :protocol => "udp")
    process = Processes.where(:tag => tag)

    runfork do
        while cur_time < time
            if time - cur_time < 5
                ltime = time - cur_time
            else
                ltime = 5
            end
            if process.first.status == "stopped"
                result.delete
                break
            end
            thrulay_opts = "-D #{dscp} -t #{ltime} -u#{speed}M -l 1500 #{server}"
            Open3.popen3("#{thrulay_bin} #{thrulay_opts}") do |stdin, stdout, stderr, thread|
                { :out => stdout, :err => stderr }.each do |key, stream|
                    Thread.new do
                        until (raw_line = stream.gets).nil? do
                            if res = raw_line.match(/received (\d+) unique packets/)
                                packets = res[1].to_f
                                mbps = packets * 1500.0 / ltime.to_f * 8.0 / 1000000.0
                                cur_time += ltime
                                sums[cur_time] = mbps
                                #Update the db
                            end
                        end
                    end
                end
                thread.join
                result.update(:results => build_results(sums).to_json, :timestamp => Time.now)
            end
        end
        result.update(:results => build_results(sums).to_json, :timestamp => Time.now)
        process.update(:status => "done")

    end
    return result.id
end

- (Object) runfork(ps = false)

Fork and run child process in block

Returns:

  • a block



9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# File 'helpers/thrulay.rb', line 9

def runfork(ps=false)
    logger = Logger.new(STDERR) # Log everything to STDERR
    logger.debug "Forking: Parent ID = #{Process.pid}"
    DB.disconnect
    child = fork do
        begin
            start = Time.now
            logger.debug "Child PID = #{Process.pid}"
            yield
        rescue => ex
            ps.update(:status => "done") if ps
            logger.error "Child returned an exception: - #{ex.class}: #{ex.message}"
        ensure
            logger.info "Child #{Process.pid} took #{Time.now - start} sec"
            exit!(0)
        end
    end
    Process.detach(child)
    return child

end

- (Object) store_template_tags(id, fields)

Updates the tags in the database

Parameters:

  • id (Integer)

    the template db ID

  • fields (Hash)

    the new value of the fields column



42
43
44
45
46
47
# File 'helpers/templates.rb', line 42

def store_template_tags(id, fields)
    ds = Templates.where(:id => id)
    if ds.count == 1
        ds.update(:fields => fields.to_json)
    end
end

- (Object) tag_to_elements(fields)

Converts the database tags in to HTML elements

Parameters:

  • fields (JSON)

    The fields in JSON format

Returns:

  • an HTML table



54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
# File 'helpers/templates.rb', line 54

def tag_to_elements(fields)
    tags = JSON.parse(fields)
    #output = "<ul>\n"

    output = "<table class=\"smalltable\">\n"
    tags.each do |k,v|
        if v.class == Hash
            output += "  <tr><td>#{k} (#{v["name"]}) - #{v["value"]}</td></tr>"
        else
            output += "  <tr><td><b>#{k}</b></td><td>#{v}</td></tr>"
        end
        #output += "  <li>#{k}: #{v}</li>\n"
    end
    #output += "</u>\n"
    output += "</table>\n"
    output
end

- (Object) tag_to_form(name, value = nil, klass = nil, prefix = "")

Converts the template tags in to HTML form elements

Parameters:

  • name (String)

    input tag name

  • value (String) (defaults to: nil)

    input tag value (or nil)

  • klass (String) (defaults to: nil)

    the validator css/js class (optional)

  • prefix (String) (defaults to: "")

    Prefix the input name with this (optional)

Returns:

  • Generated HTML



94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
# File 'helpers/templates.rb', line 94

def tag_to_form(name, value=nil, klass=nil, prefix="")
    if name =~ /__/
        tname = name.gsub(/__/, '.')
    else
        tname = name
    end
    output = "<input type=\"text\" name=\"#{prefix}#{tname}\" "
    if klass
        output += "class=\"required #{klass}\" "
    else
        output += "class=\"required\" "
    end
    if value
        output += "value=\"#{value}\""
    end
    output += "/>"
    output
end

- (Object) tagsplit(string)

Splits key value paired tags and outputs html

Parameters:

  • string (String)

    the key-value pair seperated with `:`

Returns:

  • HTML string



77
78
79
80
81
82
83
84
85
# File 'helpers/templates.rb', line 77

def tagsplit(string)
    output = ""
    arr = string.split(/,/)
    arr.each do |pair|
        (key, value) = pair.split(/:/)
        output += "<b>#{key}</b><br />#{value}<br />\n"
    end
    output
end

- (Object) test_config

Load the test configuration YAML file

Returns:

  • A Hash from YAML



6
7
8
9
# File 'helpers/tests.rb', line 6

def test_config
    config = YAML.load_file("etc/tests.yml")
    config
end

- (Object) validate_thrulay_results(result_id, min_percentage)

Validate thrulay results

Parameters:

  • result_id (Integer)

    The database id of the result set

  • min_percentage

    The percentage of CIR that the test must hit to validate

Returns:

  • true or false

Raises:

  • (Sinatra::NotFound)


29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
# File 'helpers/results.rb', line 29

def validate_thrulay_results(result_id, min_percentage)
    result = Results.where(:id => result_id)
    raise Sinatra::NotFound if result.count == 0
    raise Sinatra::NotFound if result.first.results.nil?
    raise "Invalid percentage" unless min_percentage.class == Fixnum
    average = get_thrulay_results(result.first.results)[:average]
    cir = result.first.test.speed.to_f
    res = average / cir * 100
    if (min_percentage..100).include? res
        return true
    elsif res > 100
        return true
    else
        return false
    end
end

- (Object) verify_owamp(data, extended = false)

TODO:

The values against which results are validated should not be hard coded.

Verifies the owping data

Parameters:

  • data (Hash)

    results generated by #run_owamp

  • extended (Bool) (defaults to: false)

    include validation errors in output

Returns:

  • either returns true or false, or with extended set to true, returns an array that includes the error



55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
# File 'helpers/owamp.rb', line 55

def verify_owamp(data, extended=false)

    errors = []
    failed = false
    if data.has_key? :local
        directions = [:local, :remote]
    else
        directions = ["local", "remote"]
    end
    directions.each do |direction|
        if data.has_key? direction
            begin
                if data[direction][:max].to_f > 15 
                    errors << "#{direction}: Max RTT over 15ms"
                    failed = true
                end
                if data[direction][:jitter].to_f > 10
                    errors << "#{direction}: Jitter over 10 ms"
                    failed = true
                end
                if data[direction][:loss].to_f > 0.5
                    errors << "#{direction}: Packet loss over 0.5%"
                    failed = true
                end
            rescue
                errors << "An unhanded exception occured"
                failed = true
            end
        else
            errors << "No data found for test direction #{direction}."
            failed = true
        end
    end

    if extended
        if failed
            [false, errors]
        else
            [true, nil]
        end
    else
        if failed
            return false
        else
            return true
        end
    end
end

Class: Hash

Inherits:
Object
  • Object
show all
Defined in:
helpers/format_response.rb

Instance Method Summary (collapse)

Instance Method Details

- (Object) to_xml



13
14
15
16
17
18
# File 'helpers/format_response.rb', line 13

def to_xml
  map do |k, v|
    text = Hash === v ? v.to_xml : v
    "<%s>%s</%s>" % [k, text, k]
  end.join
end

Class: Array

Inherits:
Object
  • Object
show all
Defined in:
helpers/results.rb

Instance Method Summary (collapse)

Instance Method Details

- (Object) mean



6
7
8
# File 'helpers/results.rb', line 6

def mean
    sum / size
end

- (Object) sum



2
3
4
# File 'helpers/results.rb', line 2

def sum
    inject(0.0) { |result, el| result + el }
end