Merge "Remove zaqar" into stable/wallaby

This commit is contained in:
Zuul 2021-10-12 20:25:54 +00:00 committed by Gerrit Code Review
commit 3f5b1321c6
7 changed files with 0 additions and 608 deletions

View File

@ -326,10 +326,6 @@
# specific monitoring we do from HAProxy for Redis
# Defaults to undef
#
# [*zaqar_api*]
# (optional) Enable or not Zaqar Api binding
# Defaults to hiera('zaqar_api_enabled', false)
#
# [*ceph_rgw*]
# (optional) Enable or not Ceph RadosGW binding
# Defaults to hiera('ceph_rgw_enabled', false)
@ -351,10 +347,6 @@
# if ovn_dbs is enabled.
# Defaults to false
#
# [*zaqar_ws*]
# (optional) Enable or not Zaqar Websockets binding
# Defaults to false
#
# [*aodh_network*]
# (optional) Specify the network aodh is running on.
# Defaults to hiera('aodh_api_network', undef)
@ -476,14 +468,6 @@
# (optional) Specify the network swift_proxy_server is running on.
# Defaults to hiera('swift_proxy_network', undef)
#
# [*zaqar_api_network*]
# (optional) Specify the network zaqar_api is running on.
# Defaults to hiera('zaqar_api_network', undef)
#
# [*zaqar_ws_timeout_tunnel*]
# (optional) Specify the tunnel timeout in seconds for the Zaqar API.
# Defaults to hiera('zaqar_ws_timeout_tunnel', '14400')
#
# [*service_ports*]
# (optional) Hash that contains the values to override from the service ports
# The available keys to modify the services' ports are:
@ -530,18 +514,12 @@
# 'ovn_sbdb_ssl_port' (Defaults to 13642)
# 'swift_proxy_port' (Defaults to 8080)
# 'swift_proxy_ssl_port' (Defaults to 13808)
# 'zaqar_api_port' (Defaults to 8888)
# 'zaqar_api_ssl_port' (Defaults to 13888)
# 'ceph_rgw_port' (Defaults to 8080)
# 'ceph_rgw_ssl_port' (Defaults to 13808)
# 'ceph_grafana_port' (Defaults to 3100)
# 'ceph_grafana_ssl_port' (Defaults to 3100)
# 'ceph_dashboard_port' (Defaults to 8444)
# 'ceph_dashboard_ssl_port' (Defaults to 8444)
# 'zaqar_ws_port' (Defaults to 9000)
# 'zaqar_ws_ssl_port' (Defaults to 3000)
# * Note that for zaqar's websockets we don't support having a different
# port for SSL, because it ignores the handshake.
# Defaults to {}
#
class tripleo::haproxy (
@ -614,11 +592,9 @@ class tripleo::haproxy (
$docker_registry = hiera('enable_docker_registry', false),
$redis = hiera('redis_enabled', false),
$redis_password = undef,
$zaqar_api = hiera('zaqar_api_enabled', false),
$ceph_rgw = hiera('ceph_rgw_enabled', false),
$ovn_dbs = hiera('ovn_dbs_enabled', false),
$ovn_dbs_manage_lb = false,
$zaqar_ws = hiera('zaqar_api_enabled', false),
$aodh_network = hiera('aodh_api_network', undef),
$barbican_network = hiera('barbican_api_network', false),
$ceph_rgw_network = hiera('ceph_rgw_network', undef),
@ -649,8 +625,6 @@ class tripleo::haproxy (
$ovn_dbs_network = hiera('ovn_dbs_network', undef),
$etcd_network = hiera('etcd_network', undef),
$swift_proxy_server_network = hiera('swift_proxy_network', undef),
$zaqar_api_network = hiera('zaqar_api_network', undef),
$zaqar_ws_timeout_tunnel = hiera('zaqar_ws_timeout_tunnel', '14400'),
$service_ports = {}
) {
$default_service_ports = {
@ -700,12 +674,8 @@ class tripleo::haproxy (
ovn_sbdb_ssl_port => 13642,
swift_proxy_port => 8080,
swift_proxy_ssl_port => 13808,
zaqar_api_port => 8888,
zaqar_api_ssl_port => 13888,
ceph_rgw_port => 8080,
ceph_rgw_ssl_port => 13808,
zaqar_ws_port => 9000,
zaqar_ws_ssl_port => 3000,
ceph_grafana_port => 3100,
ceph_grafana_ssl_port => 3100,
ceph_prometheus_port => 9092,
@ -1691,20 +1661,6 @@ class tripleo::haproxy (
}
}
if $zaqar_api {
::tripleo::haproxy::endpoint { 'zaqar_api':
public_virtual_ip => $public_virtual_ip,
internal_ip => hiera('zaqar_api_vip', $controller_virtual_ip),
service_port => $ports[zaqar_api_port],
ip_addresses => hiera('zaqar_api_node_ips', $controller_hosts_real),
server_names => hiera('zaqar_api_node_names', $controller_hosts_names_real),
mode => 'http',
public_ssl_port => $ports[zaqar_api_ssl_port],
service_network => $zaqar_api_network,
member_options => union($haproxy_member_options, $internal_tls_member_options),
}
}
if $ceph_rgw {
$ceph_rgw_backend_opts = {
'option' => [ 'httpchk GET /swift/healthcheck' ],
@ -1800,37 +1756,4 @@ class tripleo::haproxy (
mode => 'tcp'
}
}
if $zaqar_ws {
$zaqar_ws_frontend_opts = {
# NOTE(jaosorior): Websockets have more overhead in establishing
# connections than regular HTTP connections. Also, since it begins
# as an HTTP connection and then "upgrades" to a TCP connection, some
# timeouts get overridden by others at certain times of the connection.
# The following values were taken from the following site:
# http://blog.haproxy.com/2012/11/07/websockets-load-balancing-with-haproxy/
'timeout' => ['client 25s'],
'http-request' => [join(['set-header Host %[dst]:', $ports[zaqar_ws_port]])],
}
$zaqar_ws_backend_opts = {
'timeout' => ['connect 5s', 'server 25s', regsubst('tunnel Xs', 'X', $zaqar_ws_timeout_tunnel)],
}
$zaqar_ws_listen_opts = merge_hash_values($zaqar_ws_frontend_opts,
$zaqar_ws_backend_opts)
::tripleo::haproxy::endpoint { 'zaqar_ws':
public_virtual_ip => $public_virtual_ip,
internal_ip => hiera('zaqar_ws_vip', $controller_virtual_ip),
service_port => $ports[zaqar_ws_port],
ip_addresses => hiera('zaqar_ws_node_ips', $controller_hosts_real),
server_names => hiera('zaqar_ws_node_names', $controller_hosts_names_real),
mode => 'http',
haproxy_listen_bind_param => [], # We don't use a transparent proxy here
listen_options => $zaqar_ws_listen_opts,
frontend_options => $zaqar_ws_frontend_opts,
backend_options => $zaqar_ws_backend_opts,
public_ssl_port => $ports[zaqar_ws_ssl_port],
service_network => $zaqar_api_network,
}
}
}

View File

@ -292,10 +292,6 @@ class tripleo::profile::base::database::mysql (
if hiera('octavia_api_enabled', false) {
tripleo::profile::base::database::mysql::include_and_check_auth{'octavia::db::mysql':}
}
if hiera('zaqar_api_enabled', false) and hiera('zaqar::db::mysql::user', '') == 'zaqar' {
# NOTE: by default zaqar uses sqlalchemy
tripleo::profile::base::database::mysql::include_and_check_auth{'zaqar::db::mysql':}
}
}
}

View File

@ -1,134 +0,0 @@
# Copyright 2016 Red Hat, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
# == Class: tripleo::profile::base::zaqar
#
# Zaqar profile for tripleo
#
# === Parameters
#
# [*bootstrap_node*]
# (Optional) The hostname of the node responsible for bootstrapping tasks
# Defaults to hiera('zaqar_api_short_bootstrap_node_name')
#
# [*management_store*]
# (Optional) The management store for Zaqar.
# Defaults to 'sqlalchemy'
#
# [*messaging_store*]
# (Optional) The messaging store for Zaqar.
# Defaults to 'redis'
#
# [*certificates_specs*]
# (Optional) The specifications to give to certmonger for the certificate(s)
# it will create.
# Example with hiera:
# apache_certificates_specs:
# httpd-internal_api:
# hostname: <overcloud controller fqdn>
# service_certificate: <service certificate path>
# service_key: <service key path>
# principal: "haproxy/<overcloud controller fqdn>"
# Defaults to hiera('apache_certificate_specs', {}).
#
# [*enable_internal_tls*]
# (Optional) Whether TLS in the internal network is enabled or not.
# Defaults to hiera('enable_internal_tls', false)
#
# [*zaqar_api_network*]
# (Optional) The network name where the zaqar API endpoint is listening on.
# This is set by t-h-t.
# Defaults to hiera('zaqar_api_network', undef)
#
# [*zaqar_redis_password*]
# (Optional) Password for the gnocchi redis user for the coordination url
# Defaults to hiera('zaqar_redis_password')
#
# [*redis_vip*]
# (Optional) Redis ip address for the coordination url
# Defaults to hiera('redis_vip')
#
# [*step*]
# (Optional) The current step in deployment. See tripleo-heat-templates
# for more details.
# Defaults to hiera('step')
#
class tripleo::profile::base::zaqar (
$bootstrap_node = hiera('zaqar_api_short_bootstrap_node_name', undef),
$management_store = 'sqlalchemy',
$messaging_store = 'redis',
$certificates_specs = hiera('apache_certificates_specs', {}),
$enable_internal_tls = hiera('enable_internal_tls', false),
$zaqar_api_network = hiera('zaqar_api_network', undef),
$zaqar_redis_password = hiera('zaqar_redis_password', undef),
$redis_vip = hiera('redis_vip', undef),
$step = Integer(hiera('step')),
) {
if $bootstrap_node and $::hostname == downcase($bootstrap_node) {
$is_bootstrap = true
} else {
$is_bootstrap = false
}
include tripleo::profile::base::zaqar::authtoken
if $enable_internal_tls {
if !$zaqar_api_network {
fail('zaqar_api_network is not set in the hieradata.')
}
$tls_certfile = $certificates_specs["httpd-${zaqar_api_network}"]['service_certificate']
$tls_keyfile = $certificates_specs["httpd-${zaqar_api_network}"]['service_key']
} else {
$tls_certfile = undef
$tls_keyfile = undef
}
if $step >= 4 or ( $step >= 3 and $is_bootstrap ) {
include zaqar
if $messaging_store == 'swift' {
include zaqar::messaging::swift
} elsif $messaging_store == 'redis' {
class {'zaqar::messaging::redis':
uri => join(['redis://:', $zaqar_redis_password, '@', normalize_ip_for_uri($redis_vip), ':6379/']),
}
} else {
fail("unsupported Zaqar messaging_store set: ${messaging_store}")
}
if $management_store == 'sqlalchemy' {
include zaqar::management::sqlalchemy
} else {
fail("unsupported Zaqar management_store set: ${management_store}")
}
include zaqar::transport::websocket
include tripleo::profile::base::apache
include zaqar::transport::wsgi
include zaqar::config
include zaqar::logging
# TODO (bcrochet): At some point, the transports should be split out to
# separate services.
include zaqar::server
class { 'zaqar::wsgi::apache':
ssl_cert => $tls_certfile,
ssl_key => $tls_keyfile,
}
zaqar::server_instance{ '1':
transport => 'websocket'
}
}
}

View File

@ -1,79 +0,0 @@
# Copyright 2020 Red Hat, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
# == Class: tripleo::profile::base::zaqar::authtoken
#
# Zaqar authtoken profile for TripleO
#
# === Parameters
#
# [*step*]
# (Optional) The current step in deployment. See tripleo-heat-templates
# for more details.
# Defaults to hiera('step')
#
# [*memcached_hosts*]
# (Optional) Array of hostnames, ipv4 or ipv6 addresses for memcache.
# Defaults to hiera('memcached_node_names', [])
#
# [*memcached_port*]
# (Optional) Memcached port to use.
# Defaults to hiera('memcached_authtoken_port', 11211)
#
# [*security_strategy*]
# (Optional) Memcached (authtoken) security strategy.
# Defaults to hiera('memcached_authtoken_security_strategy', undef)
#
# [*secret_key*]
# (Optional) Memcached (authtoken) secret key, used with security_strategy.
# The key is hashed with a salt, to isolate services.
# Defaults to hiera('memcached_authtoken_secret_key', undef)
#
# DEPRECATED PARAMETERS
#
# [*memcached_ips*]
# (Optional) Array of ipv4 or ipv6 addresses for memcache.
# Defaults to undef
#
class tripleo::profile::base::zaqar::authtoken (
$step = Integer(hiera('step')),
$memcached_hosts = hiera('memcached_node_names', []),
$memcached_port = hiera('memcached_authtoken_port', 11211),
$security_strategy = hiera('memcached_authtoken_security_strategy', undef),
$secret_key = hiera('memcached_authtoken_secret_key', undef),
# DEPRECATED PARAMETERS
$memcached_ips = undef
) {
$memcached_hosts_real = pick($memcached_ips, $memcached_hosts)
if $step >= 3 {
if $memcached_hosts_real[0] =~ Stdlib::Compat::Ipv6 {
$memcache_servers = prefix(suffix(any2array(normalize_ip_for_uri($memcached_hosts_real)), ":${memcached_port}"), 'inet6:')
} else {
$memcache_servers = suffix(any2array(normalize_ip_for_uri($memcached_hosts_real)), ":${memcached_port}")
}
if $secret_key {
$hashed_secret_key = sha256("${secret_key}+zaqar")
} else {
$hashed_secret_key = undef
}
class { 'zaqar::keystone::authtoken':
memcached_servers => $memcache_servers,
memcache_security_strategy => $security_strategy,
memcache_secret_key => $hashed_secret_key,
}
}
}

View File

@ -1,70 +0,0 @@
#
# Copyright (C) 2020 Red Hat, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
require 'spec_helper'
describe 'tripleo::profile::base::zaqar::authtoken' do
shared_examples_for 'tripleo::profile::base::zaqar::authtoken' do
context 'with step less than 3' do
let(:params) { {
:step => 1,
} }
it {
is_expected.to contain_class('tripleo::profile::base::zaqar::authtoken')
is_expected.to_not contain_class('zaqar::keystone::authtoken')
}
end
context 'with step 3' do
let(:params) { {
:step => 3,
:memcached_hosts => '127.0.0.1',
} }
it {
is_expected.to contain_class('tripleo::profile::base::zaqar::authtoken')
is_expected.to contain_class('zaqar::keystone::authtoken').with(
:memcached_servers => ['127.0.0.1:11211'])
}
end
context 'with step 3 with ipv6' do
let(:params) { {
:step => 3,
:memcached_hosts => '::1',
} }
it {
is_expected.to contain_class('tripleo::profile::base::zaqar::authtoken')
is_expected.to contain_class('zaqar::keystone::authtoken').with(
:memcached_servers => ['[::1]:11211'])
}
end
end
on_supported_os.each do |os, facts|
context "on #{os}" do
let(:facts) do
facts.merge({ :hostname => 'node.example.com' })
end
it_behaves_like 'tripleo::profile::base::zaqar::authtoken'
end
end
end

View File

@ -1,174 +0,0 @@
#
# Copyright (C) 2020 Red Hat, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
require 'spec_helper'
describe 'tripleo::profile::base::zaqar' do
shared_examples_for 'tripleo::profile::base::zaqar' do
let(:pre_condition) do
<<-eos
class { 'tripleo::profile::base::zaqar::authtoken':
step => #{params[:step]},
}
eos
end
context 'with step less than 3' do
let(:params) { {
:step => 1,
} }
it {
is_expected.to contain_class('tripleo::profile::base::zaqar')
is_expected.to contain_class('tripleo::profile::base::zaqar::authtoken')
is_expected.to_not contain_class('tripleo::profile::base::apache')
is_expected.to_not contain_class('zaqar')
is_expected.to_not contain_class('zaqar::messaging::swift')
is_expected.to_not contain_class('zaqar::messaging::redis')
is_expected.to_not contain_class('zaqar::management::sqlalchemy')
is_expected.to_not contain_class('zaqar::transport::websocket')
is_expected.to_not contain_class('zaqar::transport::wsgi')
is_expected.to_not contain_class('zaqar::config')
is_expected.to_not contain_class('zaqar::logging')
is_expected.to_not contain_class('zaqar::server')
is_expected.to_not contain_class('zaqar::wsgi::apache')
is_expected.to_not contain_zaqar__server_instance('1')
}
end
context 'with step 3 on bootstrap node' do
let(:params) { {
:step => 3,
:bootstrap_node => 'node.example.com',
:redis_vip => '192.168.0.1',
:zaqar_redis_password => 'zaqar',
} }
it {
is_expected.to contain_class('tripleo::profile::base::zaqar')
is_expected.to contain_class('tripleo::profile::base::zaqar::authtoken')
is_expected.to contain_class('tripleo::profile::base::apache')
is_expected.to contain_class('zaqar')
is_expected.to_not contain_class('zaqar::messaging::swift')
is_expected.to contain_class('zaqar::messaging::redis').with(
:uri => 'redis://:zaqar@192.168.0.1:6379/',
)
is_expected.to contain_class('zaqar::management::sqlalchemy')
is_expected.to contain_class('zaqar::transport::websocket')
is_expected.to contain_class('zaqar::transport::wsgi')
is_expected.to contain_class('zaqar::config')
is_expected.to contain_class('zaqar::logging')
is_expected.to contain_class('zaqar::server')
is_expected.to contain_class('zaqar::wsgi::apache')
is_expected.to contain_zaqar__server_instance('1').with(
:transport => 'websocket'
)
}
end
context 'with step 3 not on bootstrap node' do
let(:params) { {
:step => 3,
:bootstrap_node => 'other.example.com',
} }
it {
is_expected.to contain_class('tripleo::profile::base::zaqar')
is_expected.to contain_class('tripleo::profile::base::zaqar::authtoken')
is_expected.to_not contain_class('tripleo::profile::base::apache')
is_expected.to_not contain_class('zaqar')
is_expected.to_not contain_class('zaqar::messaging::swift')
is_expected.to_not contain_class('zaqar::messaging::redis')
is_expected.to_not contain_class('zaqar::management::sqlalchemy')
is_expected.to_not contain_class('zaqar::transport::websocket')
is_expected.to_not contain_class('zaqar::transport::wsgi')
is_expected.to_not contain_class('zaqar::config')
is_expected.to_not contain_class('zaqar::logging')
is_expected.to_not contain_class('zaqar::server')
is_expected.to_not contain_class('zaqar::wsgi::apache')
is_expected.to_not contain_zaqar__server_instance('1')
}
end
context 'with step 4 not on bootstrap node' do
let(:params) { {
:step => 4,
:bootstrap_node => 'node.example.com',
:redis_vip => '192.168.0.1',
:zaqar_redis_password => 'zaqar',
} }
it {
is_expected.to contain_class('tripleo::profile::base::zaqar')
is_expected.to contain_class('tripleo::profile::base::zaqar::authtoken')
is_expected.to contain_class('tripleo::profile::base::apache')
is_expected.to contain_class('zaqar')
is_expected.to_not contain_class('zaqar::messaging::swift')
is_expected.to contain_class('zaqar::messaging::redis').with({
:uri => 'redis://:zaqar@192.168.0.1:6379/',
})
is_expected.to contain_class('zaqar::management::sqlalchemy')
is_expected.to contain_class('zaqar::transport::websocket')
is_expected.to contain_class('zaqar::transport::wsgi')
is_expected.to contain_class('zaqar::config')
is_expected.to contain_class('zaqar::logging')
is_expected.to contain_class('zaqar::server')
is_expected.to contain_class('zaqar::wsgi::apache')
is_expected.to contain_zaqar__server_instance('1').with(
:transport => 'websocket'
)
}
end
context 'with step 4 and swift messaging store' do
let(:params) { {
:step => 4,
:bootstrap_node => 'node.example.com',
:messaging_store => 'swift',
} }
it {
is_expected.to contain_class('tripleo::profile::base::zaqar')
is_expected.to contain_class('tripleo::profile::base::zaqar::authtoken')
is_expected.to contain_class('tripleo::profile::base::apache')
is_expected.to contain_class('zaqar')
is_expected.to contain_class('zaqar::messaging::swift')
is_expected.to_not contain_class('zaqar::messaging::redis')
is_expected.to contain_class('zaqar::management::sqlalchemy')
is_expected.to contain_class('zaqar::transport::websocket')
is_expected.to contain_class('zaqar::transport::wsgi')
is_expected.to contain_class('zaqar::config')
is_expected.to contain_class('zaqar::logging')
is_expected.to contain_class('zaqar::server')
is_expected.to contain_class('zaqar::wsgi::apache')
is_expected.to contain_zaqar__server_instance('1').with(
:transport => 'websocket'
)
}
end
end
on_supported_os.each do |os, facts|
context "on #{os}" do
let(:facts) do
facts.merge({ :hostname => 'node.example.com' })
end
it_behaves_like 'tripleo::profile::base::zaqar'
end
end
end

View File

@ -1,70 +0,0 @@
#
# Copyright (C) 2020 Red Hat, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
require 'spec_helper'
describe 'tripleo::profile::base::zaqar::authtoken' do
shared_examples_for 'tripleo::profile::base::zaqar::authtoken' do
context 'with step less than 3' do
let(:params) { {
:step => 1,
} }
it {
is_expected.to contain_class('tripleo::profile::base::zaqar::authtoken')
is_expected.to_not contain_class('zaqar::keystone::authtoken')
}
end
context 'with step 3' do
let(:params) { {
:step => 3,
:memcached_hosts => '127.0.0.1',
} }
it {
is_expected.to contain_class('tripleo::profile::base::zaqar::authtoken')
is_expected.to contain_class('zaqar::keystone::authtoken').with(
:memcached_servers => ['127.0.0.1:11211'])
}
end
context 'with step 3 with ipv6' do
let(:params) { {
:step => 3,
:memcached_hosts => '::1',
} }
it {
is_expected.to contain_class('tripleo::profile::base::zaqar::authtoken')
is_expected.to contain_class('zaqar::keystone::authtoken').with(
:memcached_servers => ['[::1]:11211'])
}
end
end
on_supported_os.each do |os, facts|
context "on #{os}" do
let(:facts) do
facts.merge({ :hostname => 'node.example.com' })
end
it_behaves_like 'tripleo::profile::base::zaqar::authtoken'
end
end
end