chef-provisioning-oneview 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,3 @@
1
+ module OneViewAPIv1_20
2
+ # TODO
3
+ end
@@ -0,0 +1,3 @@
1
+ module OneViewAPIv2_0
2
+ # TODO
3
+ end
@@ -0,0 +1,5 @@
1
+ class Chef
2
+ module Provisioning
3
+ ONEVIEW_DRIVER_VERSION = '1.0.1'
4
+ end
5
+ end
@@ -0,0 +1,173 @@
1
+ require 'chef/json_compat'
2
+ require 'chef/knife'
3
+ require 'chef/provisioning/convergence_strategy/install_sh'
4
+ require 'chef/provisioning/driver'
5
+ require 'chef/provisioning/transport/ssh'
6
+ require 'chef/provisioning/machine/unix_machine'
7
+ require 'json'
8
+ require 'ridley'
9
+ require_relative 'driver_init/oneview'
10
+ require_relative 'oneview/version'
11
+ require_relative 'oneview/oneview_api'
12
+
13
+ module Chef::Provisioning
14
+ class OneViewDriver < Chef::Provisioning::Driver
15
+ include OneViewAPI
16
+
17
+ def self.canonicalize_url(url, config)
18
+ _scheme, oneview_url = url.split(':', 2)
19
+ if oneview_url.nil? || oneview_url == ''
20
+ oneview_url = config[:knife][:oneview_url]
21
+ end
22
+ fail 'Must set the knife[:oneview_url] attribute!' if oneview_url.nil? || oneview_url.empty?
23
+ 'oneview:' + oneview_url
24
+ end
25
+
26
+ def self.from_url(oneview_url, config)
27
+ OneViewDriver.new(oneview_url, config)
28
+ end
29
+
30
+ def initialize(canonical_url, config)
31
+ super(canonical_url, config)
32
+
33
+ @oneview_base_url = config[:knife][:oneview_url]
34
+ fail 'Must set the knife[:oneview_url] attribute!' if @oneview_base_url.nil? || @oneview_base_url.empty?
35
+ @oneview_username = config[:knife][:oneview_username]
36
+ fail 'Must set the knife[:oneview_username] attribute!' if @oneview_username.nil? || @oneview_username.empty?
37
+ @oneview_password = config[:knife][:oneview_password]
38
+ fail 'Must set the knife[:oneview_password] attribute!' if @oneview_password.nil? || @oneview_password.empty?
39
+ @oneview_disable_ssl = config[:knife][:oneview_ignore_ssl]
40
+ @oneview_api_version = 120 # get_oneview_api_version
41
+ @oneview_key = login_to_oneview
42
+
43
+ @icsp_base_url = config[:knife][:icsp_url]
44
+ fail 'Must set the knife[:icsp_url] attribute!' if @icsp_base_url.nil? || @icsp_base_url.empty?
45
+ @icsp_username = config[:knife][:icsp_username]
46
+ fail 'Must set the knife[:icsp_username] attribute!' if @icsp_username.nil? || @icsp_username.empty?
47
+ @icsp_password = config[:knife][:icsp_password]
48
+ fail 'Must set the knife[:icsp_password] attribute!' if @icsp_password.nil? || @icsp_password.empty?
49
+ @icsp_disable_ssl = config[:knife][:icsp_ignore_ssl]
50
+ @icsp_api_version = 102 # get_icsp_api_version
51
+ @icsp_key = login_to_icsp
52
+ end
53
+
54
+
55
+ def allocate_machine(action_handler, machine_spec, machine_options)
56
+ host_name = machine_options[:driver_options][:host_name]
57
+ if machine_spec.reference
58
+ if get_oneview_profile_by_sn(machine_spec.reference['serial_number']).nil? # It doesn't really exist
59
+ action_handler.report_progress "Machine #{host_name} does not really exist. Recreating ..."
60
+ machine_spec.reference = nil
61
+ end
62
+ end
63
+ if !machine_spec.reference
64
+ action_handler.perform_action "Creating server #{machine_spec.name}" do
65
+ profile = create_machine(action_handler, machine_spec, machine_options)
66
+ machine_spec.reference = {
67
+ 'driver_url' => driver_url,
68
+ 'driver_version' => ONEVIEW_DRIVER_VERSION,
69
+ 'serial_number' => profile['serialNumber']
70
+ }
71
+ end
72
+ end
73
+ end
74
+
75
+
76
+ def allocate_machines(action_handler, specs_and_options, _parallelizer)
77
+ specs_and_options.each do |machine_spec, machine_options|
78
+ allocate_machine(action_handler, machine_spec, machine_options)
79
+ end
80
+ end
81
+
82
+
83
+ def ready_machine(action_handler, machine_spec, machine_options)
84
+ profile = get_oneview_profile_by_sn(machine_spec.reference['serial_number'])
85
+ customize_machine(action_handler, machine_spec, machine_options, profile)
86
+ machine_for(machine_spec, machine_options) # Return the Machine object
87
+ end
88
+
89
+
90
+ def machine_for(machine_spec, machine_options)
91
+ bootstrap_ip_address = machine_options[:driver_options][:ip_address]
92
+ username = machine_options[:transport_options][:user] || 'root' rescue 'root'
93
+ default_ssh_options = {
94
+ # auth_methods: ['publickey'],
95
+ # keys: ['/home/username/.vagrant.d/insecure_private_key'],
96
+ password: Chef::Config.knife[:node_root_password]
97
+ }
98
+ ssh_options = machine_options[:transport_options][:ssh_options] || default_ssh_options rescue default_ssh_options
99
+ default_options = {
100
+ prefix: 'sudo ',
101
+ ssh_pty_enable: true
102
+ }
103
+ options = machine_options[:transport_options][:options] || default_options rescue default_options
104
+
105
+ transport = Chef::Provisioning::Transport::SSH.new(bootstrap_ip_address, username, ssh_options, options, config)
106
+ convergence_strategy = Chef::Provisioning::ConvergenceStrategy::InstallSh.new(
107
+ machine_options[:convergence_options], {})
108
+ Chef::Provisioning::Machine::UnixMachine.new(machine_spec, transport, convergence_strategy)
109
+ end
110
+
111
+
112
+ def stop_machine(action_handler, machine_spec, _machine_options)
113
+ power_off(action_handler, machine_spec) if machine_spec.reference
114
+ end
115
+
116
+
117
+ def destroy_machine(action_handler, machine_spec, machine_options)
118
+ if machine_spec.reference
119
+ power_off(action_handler, machine_spec) # Power off server
120
+ destroy_icsp_server(action_handler, machine_spec) # Delete os deployment server from ICSP
121
+ destroy_oneview_profile(action_handler, machine_spec) # Delete server profile from OneView
122
+
123
+ name = machine_spec.name # Save for next steps
124
+
125
+ # Delete the node from the Chef server
126
+ action_handler.perform_action "Release machine #{machine_spec.reference['serial_number']}" do
127
+ machine_spec.reference = nil
128
+ machine_spec.delete(action_handler)
129
+ end
130
+
131
+ # Delete client from the Chef server
132
+ action_handler.perform_action "Delete client '#{name}' from Chef server" do
133
+ begin
134
+ ridley = Ridley.new(
135
+ server_url: machine_options[:convergence_options][:chef_server][:chef_server_url],
136
+ client_name: machine_options[:convergence_options][:chef_server][:options][:client_name],
137
+ client_key: machine_options[:convergence_options][:chef_server][:options][:signing_key_filename]
138
+ )
139
+ ridley.client.delete(name)
140
+ rescue Exception => e
141
+ action_handler.report_progress "WARN: Failed to delete client #{name} from server!"
142
+ puts "Error: #{e.message}"
143
+ end
144
+ end
145
+
146
+ # Remove entry from known_hosts file(s)
147
+ if machine_options[:driver_options][:ip_address]
148
+ action_handler.perform_action "Delete entry for '#{machine_options[:driver_options][:ip_address]}' from known_hosts file(s)" do
149
+ files = [File.expand_path('~/.ssh/known_hosts'), File.expand_path('/etc/ssh/known_hosts')]
150
+ files.each do |f|
151
+ next if !File.exist?(f)
152
+ begin
153
+ text = File.read(f)
154
+ text.gsub!(/#{machine_options[:driver_options][:ip_address]} ssh-rsa.*(\n|\r\n)/, '')
155
+ File.open(f, 'w') {|file| file.puts text } if text
156
+ rescue Exception => e
157
+ action_handler.report_progress "WARN: Failed to delete entry for '#{machine_options[:driver_options][:ip_address]}' from known_hosts file: '#{f}'! "
158
+ puts "Error: #{e.message}"
159
+ end
160
+ end
161
+ end
162
+ end
163
+
164
+ end # End if machine_spec.reference
165
+ end # destroy_machine method end
166
+
167
+
168
+ def connect_to_machine(machine_spec, machine_options)
169
+ machine_for(machine_spec, machine_options)
170
+ end
171
+
172
+ end # class end
173
+ end # module end
@@ -0,0 +1,21 @@
1
+ require 'pry'
2
+ require_relative './../lib/chef/provisioning/driver_init/oneview'
3
+ require_relative 'support/fake_oneview'
4
+ require_relative 'support/fake_icsp'
5
+ require 'webmock/rspec'
6
+ # WebMock.disable_net_connect!(allow_localhost: true)
7
+
8
+ RSpec.configure do |config|
9
+ config.before(:each) do
10
+ allow_any_instance_of(Chef::Provisioning::OneViewDriver).to receive(:get_oneview_api_version).and_return(120)
11
+ allow_any_instance_of(Chef::Provisioning::OneViewDriver).to receive(:get_icsp_api_version).and_return(102)
12
+ allow_any_instance_of(Chef::Provisioning::OneViewDriver).to receive(:login_to_oneview).and_return('long_oneview_key')
13
+ allow_any_instance_of(Chef::Provisioning::OneViewDriver).to receive(:login_to_icsp).and_return('long_icsp_key')
14
+
15
+ stub_request(:any, /my-oneview.my-domain.com/).to_rack(FakeOneView)
16
+ stub_request(:any, /my-icsp.my-domain.com/).to_rack(FakeIcsp)
17
+ end
18
+ end
19
+
20
+ # Chef::Log.level = :debug
21
+ Chef::Config[:log_level] = :warn
@@ -0,0 +1,21 @@
1
+ require 'sinatra/base'
2
+
3
+ class FakeIcsp < Sinatra::Base
4
+ get '/rest/version' do
5
+ version = request['X-API-Version'] # TODO
6
+ json_response(200, 'version.json', version)
7
+ end
8
+
9
+ post '/rest/login-sessions' do
10
+ version = request['X-API-Version'] # TODO
11
+ json_response(200, 'login.json', version)
12
+ end
13
+
14
+ private
15
+
16
+ def json_response(response_code, file_name, version = 102)
17
+ content_type :json
18
+ status response_code
19
+ File.open(File.dirname(__FILE__) + "/fixtures/icsp/v#{version.gsub('.', '')}/" + file_name, 'rb').read
20
+ end
21
+ end
@@ -0,0 +1,21 @@
1
+ require 'sinatra/base'
2
+
3
+ class FakeOneView < Sinatra::Base
4
+ get '/rest/version' do
5
+ version = request['X-API-Version'] # TODO
6
+ json_response(200, 'version.json', version)
7
+ end
8
+
9
+ post '/rest/login-sessions' do
10
+ version = request['X-API-Version'] # TODO
11
+ json_response(200, 'login.json', version)
12
+ end
13
+
14
+ private
15
+
16
+ def json_response(response_code, file_name, version = 120)
17
+ content_type :json
18
+ status response_code
19
+ File.open(File.dirname(__FILE__) + "/fixtures/oneview/v#{version.gsub('.', '')}/" + file_name, 'rb').read
20
+ end
21
+ end
@@ -0,0 +1,14 @@
1
+ {
2
+ "errorSource": null,
3
+ "nestedErrors": [
4
+
5
+ ],
6
+ "errorCode": "GENERIC_HTTP_404",
7
+ "data": {
8
+ },
9
+ "recommendedActions": [
10
+ "Check the request URI, then resend the request."
11
+ ],
12
+ "details": "The requested resource could not be found.",
13
+ "message": "Not Found"
14
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "partnerData": {
3
+ },
4
+ "sessionID": "AA_aaAaa3AA3Aa0_aAaAA4AAAA3AAAAA"
5
+ }
@@ -0,0 +1,99 @@
1
+ {
2
+ "type": "OsdPaginatedCollection",
3
+ "members": [
4
+ {
5
+ "modifiedBy": "applianceserviceaccount",
6
+ "createdBy": "applianceserviceaccount",
7
+ "buildPlanHistory": [],
8
+ "buildPlanStepType": null,
9
+ "isCustomerContent": true,
10
+ "buildPlanCustAttrs": [],
11
+ "buildPlanItems": [],
12
+ "os": "OS - Red Hat Enterprise Linux Server 6",
13
+ "arch": "x64",
14
+ "lifeCycle": "AVAILABLE",
15
+ "description": "Performs a scripted install of Red Hat Enterprise Linux 6.5 using a generic kickstart file.\n(c) Copyright 2014 Hewlett-Packard Development Company, L.P.\n\nRequirements:\n* HP ProLiant server with iLO\n* IC server provisioning Media Server must contain the OS distribution\n\nRequired Custom Attribute: None\n\nOptional Custom Attributes:\n* encrypted_root_password (must be encrypted)\n* boot_disk\n* kernel_arguments",
16
+ "status": "",
17
+ "name": "CHEF-RHEL-6.5-x64",
18
+ "state": "",
19
+ "modified": "2015-07-14T03:51:30.000Z",
20
+ "eTag": "2015-07-14T03:51:30.000Z",
21
+ "created": "2015-05-19T19:05:37.000Z",
22
+ "category": "os-deployment-build-plans",
23
+ "uri": "/rest/os-deployment-build-plans/1280001"
24
+ },
25
+ {
26
+ "modifiedBy": "applianceserviceaccount",
27
+ "createdBy": "applianceserviceaccount",
28
+ "buildPlanHistory": [],
29
+ "buildPlanStepType": null,
30
+ "isCustomerContent": true,
31
+ "buildPlanCustAttrs": [],
32
+ "buildPlanItems": [],
33
+ "os": "Other",
34
+ "arch": "x64",
35
+ "lifeCycle": "AVAILABLE",
36
+ "description": "Reboots a target server in maintenance mode to the local disk. \n(c) Copyright 2013 Hewlett-Packard Development Company, L.P.\n\nRequirements:\n* HP ProLiant server with iLO\n\nCustom Attributes: None",
37
+ "status": "",
38
+ "name": "CHEF - RHEL 6.6 x64",
39
+ "state": "",
40
+ "modified": "2015-05-21T17:23:58.000Z",
41
+ "eTag": "2015-05-21T17:23:58.000Z",
42
+ "created": "2015-05-21T17:23:58.000Z",
43
+ "category": "os-deployment-build-plans",
44
+ "uri": "/rest/os-deployment-build-plans/1300001"
45
+ },
46
+ {
47
+ "modifiedBy": "applianceserviceaccount",
48
+ "createdBy": "applianceserviceaccount",
49
+ "buildPlanHistory": [],
50
+ "buildPlanStepType": null,
51
+ "isCustomerContent": true,
52
+ "buildPlanCustAttrs": [],
53
+ "buildPlanItems": [],
54
+ "os": "OS - Red Hat Enterprise Linux Server 7",
55
+ "arch": "x64",
56
+ "lifeCycle": "AVAILABLE",
57
+ "description": "Performs a scripted install of Red Hat Enterprise Linux 7.0 using a generic kickstart file.\n(c) Copyright 2014 Hewlett-Packard Development Company, L.P.\n\nRequirements:\n* HP ProLiant server with iLO\n* IC server provisioning Media Server must contain the OS distribution\n\nRequired Custom Attribute: None\n\nOptional Custom Attributes:\n* encrypted_root_password (must be encrypted)\n* boot_disk\n* kernel_arguments",
58
+ "status": "",
59
+ "name": "CHEF - RHEL 7.0 x64",
60
+ "state": "",
61
+ "modified": "2015-07-24T09:32:52.000Z",
62
+ "eTag": "2015-07-24T09:32:52.000Z",
63
+ "created": "2015-07-15T17:46:32.000Z",
64
+ "category": "os-deployment-build-plans",
65
+ "uri": "/rest/os-deployment-build-plans/1340001"
66
+ },
67
+ {
68
+ "modifiedBy": "detuser",
69
+ "createdBy": "opsware",
70
+ "buildPlanHistory": [],
71
+ "buildPlanStepType": null,
72
+ "isCustomerContent": false,
73
+ "buildPlanCustAttrs": [],
74
+ "buildPlanItems": [],
75
+ "os": "OS - Windows Server 2008 R2",
76
+ "arch": "x64",
77
+ "lifeCycle": "AVAILABLE",
78
+ "description": "Performs a scripted install of Windows 2008 R2 SP1 Standard using a generic unattend file.\n(c) Copyright 2013, 2014 Hewlett-Packard Development Company, L.P.\n\nRequirements:\n* HP ProLiant server with iLO\n* IC server provisioning Media Server must contain the OS distribution\n\nRequired Custom Attribute:\n* ProductKey_Win2008R2-Std-x64 - Set using the Settings -> Product Key page.\n\nOptional Custom Attributes:\n* ComputerName\n* EncryptedAdminPassword (must be encrypted)\n* SystemDisk\n* SystemDiskNumber",
79
+ "status": "",
80
+ "name": "ProLiant OS - Windows 2008 R2 SP1 Standard x64 Scripted Install",
81
+ "state": "",
82
+ "modified": "2014-09-15T02:30:17.000Z",
83
+ "eTag": "2014-09-15T02:30:17.000Z",
84
+ "created": "2014-09-15T02:30:17.000Z",
85
+ "category": "os-deployment-build-plans",
86
+ "uri": "/rest/os-deployment-build-plans/1040001"
87
+ }
88
+ ],
89
+ "start": 0,
90
+ "prevPageUri": null,
91
+ "nextPageUri": null,
92
+ "total": 0,
93
+ "count": 71,
94
+ "modified": null,
95
+ "eTag": null,
96
+ "created": null,
97
+ "category": null,
98
+ "uri": null
99
+ }
@@ -0,0 +1,179 @@
1
+ {
2
+ "type": "OsdPaginatedCollection",
3
+ "members": [
4
+ {
5
+ "modified": "2015-08-10T16:45:57.019Z",
6
+ "deviceGroups": null,
7
+ "customAttributes": [],
8
+ "created": "2015-08-10T04:35:52.000Z",
9
+ "uuid": "2b4e5e6c-6154-7849-9807-c96a7e0d445b",
10
+ "cpus": [
11
+ {
12
+ "cacheSize": "15360",
13
+ "speed": "1200",
14
+ "stepping": "7",
15
+ "model": "Intel(R) Xeon(R) CPU E5-2620 0 @ 2.00GHz",
16
+ "family": "X64",
17
+ "status": "ON-LINE",
18
+ "slot": "0"
19
+ }
20
+ ],
21
+ "ilo": null,
22
+ "serverLocation": {
23
+ "rack": "DemoRack",
24
+ "enclosure": "dazzle",
25
+ "bay": "3"
26
+ },
27
+ "operatingSystemVersion": null,
28
+ "opswLifecycle": "MANAGED",
29
+ "osSPVersion": null,
30
+ "peerIP": "192.168.133.41",
31
+ "stage": "UNKNOWN",
32
+ "running": "false",
33
+ "reporting": true,
34
+ "storageDevices": [],
35
+ "ram": "16291780",
36
+ "swap": "8216568",
37
+ "architecture": null,
38
+ "defaultGateway": "16.125.32.1",
39
+ "discoveredDate": "2015-08-10T04:35:52.000Z",
40
+ "facility": "Appliance",
41
+ "hardwareModel": "PROLIANT BL460C GEN8",
42
+ "jobsHistory": null,
43
+ "lastScannedDate": null,
44
+ "loopbackIP": null,
45
+ "managementIP": "192.168.133.41",
46
+ "manufacturer": "HP",
47
+ "mid": "1680001",
48
+ "netBios": null,
49
+ "operatingSystem": null,
50
+ "osFlavor": null,
51
+ "serialNumber": "VCGE9KB042",
52
+ "locale": null,
53
+ "description": null,
54
+ "status": "OK",
55
+ "uri": "/rest/os-deployment-servers/1680001",
56
+ "name": "chef-web02.domain.com",
57
+ "interfaces": [
58
+ {
59
+ "speed": "10000",
60
+ "netmask": "255.255.254.0",
61
+ "duplex": "FULL",
62
+ "ipv6Addr": "",
63
+ "macAddr": "AE:B2:F8:20:01:17",
64
+ "dhcpEnabled": false,
65
+ "ipv4Addr": "16.125.32.39",
66
+ "type": "ETHERNET",
67
+ "slot": "eth1"
68
+ },
69
+ {
70
+ "speed": "10000",
71
+ "netmask": "255.255.255.0",
72
+ "duplex": "FULL",
73
+ "ipv6Addr": "",
74
+ "macAddr": "AE:B2:F8:20:01:16",
75
+ "dhcpEnabled": true,
76
+ "ipv4Addr": "192.168.133.41",
77
+ "type": "ETHERNET",
78
+ "slot": "eth0"
79
+ }
80
+ ],
81
+ "state": "OK",
82
+ "hostName": "chef-web02.domain.com",
83
+ "eTag": "2015-08-10T16:45:57.019Z",
84
+ "category": "os-deployment-servers"
85
+ },
86
+ {
87
+ "modified": "2015-08-10T04:55:27.017Z",
88
+ "deviceGroups": null,
89
+ "customAttributes": [],
90
+ "created": "2015-08-10T04:35:40.000Z",
91
+ "uuid": "7d805e1c-beac-e84e-868b-2fb0b6504474",
92
+ "cpus": [
93
+ {
94
+ "cacheSize": "15360",
95
+ "speed": "1200",
96
+ "stepping": "7",
97
+ "model": "Intel(R) Xeon(R) CPU E5-2620 0 @ 2.00GHz",
98
+ "family": "X64",
99
+ "status": "ON-LINE",
100
+ "slot": "0"
101
+ }
102
+ ],
103
+ "ilo": null,
104
+ "serverLocation": {
105
+ "rack": "DemoRack",
106
+ "enclosure": "dazzle",
107
+ "bay": "2"
108
+ },
109
+ "operatingSystemVersion": null,
110
+ "opswLifecycle": "MANAGED",
111
+ "osSPVersion": null,
112
+ "peerIP": "192.168.133.36",
113
+ "stage": "UNKNOWN",
114
+ "running": "false",
115
+ "reporting": true,
116
+ "storageDevices": [],
117
+ "ram": "16291780",
118
+ "swap": "8216568",
119
+ "architecture": null,
120
+ "defaultGateway": "16.125.32.1",
121
+ "discoveredDate": "2015-08-10T04:35:39.000Z",
122
+ "facility": "Appliance",
123
+ "hardwareModel": "PROLIANT BL460C GEN8",
124
+ "jobsHistory": null,
125
+ "lastScannedDate": null,
126
+ "loopbackIP": null,
127
+ "managementIP": "192.168.133.36",
128
+ "manufacturer": "HP",
129
+ "mid": "1670001",
130
+ "netBios": null,
131
+ "operatingSystem": null,
132
+ "osFlavor": null,
133
+ "serialNumber": "VCGE9KB041",
134
+ "locale": null,
135
+ "description": null,
136
+ "status": "OK",
137
+ "uri": "/rest/os-deployment-servers/1670001",
138
+ "name": "chef-web01.domain.com",
139
+ "interfaces": [
140
+ {
141
+ "speed": "10000",
142
+ "netmask": "255.255.254.0",
143
+ "duplex": "FULL",
144
+ "ipv6Addr": "",
145
+ "macAddr": "AE:B2:F8:20:01:15",
146
+ "dhcpEnabled": false,
147
+ "ipv4Addr": "16.125.32.38",
148
+ "type": "ETHERNET",
149
+ "slot": "eth1"
150
+ },
151
+ {
152
+ "speed": "10000",
153
+ "netmask": "255.255.255.0",
154
+ "duplex": "FULL",
155
+ "ipv6Addr": "",
156
+ "macAddr": "AE:B2:F8:20:01:14",
157
+ "dhcpEnabled": true,
158
+ "ipv4Addr": "192.168.133.36",
159
+ "type": "ETHERNET",
160
+ "slot": "eth0"
161
+ }
162
+ ],
163
+ "state": "OK",
164
+ "hostName": "chef-web01.domain.com",
165
+ "eTag": "2015-08-10T04:55:27.017Z",
166
+ "category": "os-deployment-servers"
167
+ }
168
+ ],
169
+ "start": 0,
170
+ "prevPageUri": null,
171
+ "nextPageUri": null,
172
+ "total": 2,
173
+ "count": 2,
174
+ "modified": null,
175
+ "eTag": null,
176
+ "created": null,
177
+ "category": null,
178
+ "uri": null
179
+ }