Network Security Internet Technology Development Database Servers Mobile Phone Android Software Apple Software Computer Software News IT Information

In addition to Weibo, there is also WeChat

Please pay attention

WeChat public account

Shulou

How to integrate Apollo configuration Center with SpringBoot

2025-01-28 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >

Share

Shulou(Shulou.com)06/02 Report--

This article will explain in detail how SpringBoot integrates the Apollo configuration center. The editor thinks it is very practical, so I share it for you as a reference. I hope you can get something after reading this article.

System environment

SpringBoot version: 2.1.8.RELEASE

Apollo version: 1.4.0

I. basic concepts

As there are many concepts of Apollo, it is more complex to use at the beginning, so it is best to go through the concept before you try to use it.

1. Background

With the increasingly complex functions of the program, the configuration of the program is increasing day by day, the switches of various functions, the configuration of parameters, the address of the server. The expectation of the program configuration is getting higher and higher, the configuration takes effect in real time after the configuration modification, grayscale release, sub-environment, sub-cluster management configuration, perfect authority, audit mechanism. In such a large environment, the traditional configuration files, databases and other ways have been increasingly unable to meet the needs of developers for configuration management. So Apollo configuration center arises at the historic moment!

2. Introduction

Apollo (Apollo) is an open source configuration management center developed by Ctrip Framework Department, which can centrally manage the configuration of different environments and clusters, and can be pushed to the application side in real time after configuration modification, and has standardized permissions, process governance and other characteristics.

Characteristics

Simple deployment

Grayscale release

Version release management

Provide an open platform API

Client configuration information monitoring

Provide Java and .net native clients

Configuration changes take effect in real time (hot release)

Rights management, release audit, operation audit

Uniformly manage the configuration of different environments and clusters

Basic model

The following is the basic model of Apollo:

(1) the user modifies and publishes the configuration in the configuration center

(2) the configuration center notifies the Apollo client that there are configuration updates

(3) the Apollo client pulls the latest configuration from the configuration center, updates the local configuration and notifies the application

5. The four dimensions of Apollo

Apollo supports 4 dimensions to manage the configuration in Key-Value format:

Application (application)

Environment (environment)

Cluster (Cluster)

Namespace (Namespace)

(1), application

The Apollo client needs to know who the current application is at run time, so that the configuration of the corresponding application can be obtained according to different applications.

Each application needs to have a unique identity. You can configure the app.id parameter in the code to identify the current application, and Apollo will identify the current application according to this reference.

(2), environment

In actual development, our applications are often deployed in different environments, which are generally divided into different environments, such as development, testing, production, and so on, and the configurations in different environments are also different. Four environments are provided by default in Apollo:

FAT (Feature Acceptance Test): functional test environment

UAT (User Acceptance Test): integrated test environment

DEV (Develop): development environment

PRO (Produce): production environment

If you want to specify which environment to use in the program, you can configure the value of the variable env to the corresponding environment name.

(3), cluster

The grouping of different instances under an application, for example, can be divided into one cluster according to the data center in Shanghai and another cluster in Beijing.

For different clusters, the same configuration can have different values. For example, two clusters are set in the two computer rooms in Beijing and Shanghai mentioned above. There are mysql configuration parameters in both clusters, and the address configured in the parameters is different.

(4), namespace

The grouping of different configurations in an application can simply compare namespace to different configuration files, and different types of configurations are stored in different files, such as database configuration files, RPC configuration files, application's own configuration files and so on.

Those who are familiar with SpringBoot know that SpringBoot projects have a default configuration file application.yml. If you still want to use multiple configurations, you can create multiple configuration files to store different configuration information, and specify different configuration files by specifying spring.profiles.active parameters. The concept of namespace here is similar, putting different configurations into different configuration namespace.

Namespace is divided into two permissions, which are:

Public (public): the Namespace of public permission, which can be obtained by any application.

Private (proprietary): can only be obtained by the application to which it belongs. The Namespace,Apollo of an application that tries to get another application private will report a "404" exception.

There are three types of Namespace, which are:

Private type: Namespace of private type has private permission. For example, application Namespace is a private type.

Public type: Namespace of public type has public permission. A public type of Namespace is equivalent to a configuration that dissociates from the application, and the public Namespace is identified by the name of the Namespace, so the name of the public Namespace must be globally unique.

Association type (inheritance type): an association type is also called an inheritance type, and the association type has private permission. The Namespace of the association type inherits from the Namespace of the public type, inherits all the configurations in it, and can be used to override some configurations of the public Namespace.

Inheritance and can be used to override some configurations of the public Namespace.

6. Local cache

The Apollo client caches the configuration obtained from the server in the local file system, which is used to restore the configuration locally when the service is unavailable or the network is unavailable, without affecting the normal operation of the application.

The local cache path is located in the following path by default, so make sure that the / opt/data or C:\ opt\ data\ directory exists and that the application has read and write permissions.

Mac/Linux: / opt/data/ {appId} / config-cache

Windows: C:\ opt\ data {appId}\ config-cache

The local configuration file is placed under the local cache path in the following file name format:

1 {appId} + {cluster} + {namespace} .properties

7. Client design

The above figure briefly describes the implementation principle of the Apollo client.

The client and the server maintain a long connection so that they can get the push of the configuration update the first time.

The client also periodically pulls the latest configuration of the application from the Apollo configuration center server.

This is a fallback mechanism to prevent the configuration from being updated due to the failure of the push mechanism

Regular pull by the client will report the local version, so in general, the server will return 304-Not Modified for the scheduled pull operation.

The timing frequency defaults to pulling every 5 minutes, and the client can also override it by specifying apollo.refreshInterval at run time, in minutes.

After the client obtains the latest configuration of the application from the Apollo configuration center server, it will be saved in memory.

The client will cache the configuration obtained from the server in the local file system so that the configuration can still be restored locally in the event that the service is unavailable or the network is not available.

The application gets the latest configuration and subscribe to configuration update notifications from the Apollo client.

Configure update push implementation

As mentioned earlier, the Apollo client and server maintain a long connection so that they can get a push of configuration updates as soon as possible. Persistent connections are actually achieved through Http Long Polling, specifically:

The client initiates a Http request to the server

The server will keep the connection for 60 seconds.

If there is a configuration change concerned by the client within 60 seconds, the held client request will immediately return and inform the client of the namespace information of the configuration change, and the client will pull the latest configuration of the corresponding namespace accordingly.

If there is no configuration change concerned by the client within 60 seconds, the Http status code 304 will be returned to the client.

The client will re-initiate the connection immediately after receiving the server request, returning to the first step

Considering that tens of thousands of clients will initiate long connections to the server, we use async servlet (Spring DeferredResult) to serve Http Long Polling requests on the server.

8. Overall design

The figure above briefly describes the overall design of Apollo, which we can look at from the bottom up:

Config Service provides configured read, push and other functions, and the service object is the Apollo client

Admin Service provides functions such as configuration modification and release, and the service object is Apollo Portal (management interface).

Both Config Service and Admin Service are multi-instance, stateless deployments, so you need to register yourself with Eureka and keep your heart beating

We set up a layer of Meta Server service discovery interface on top of Eureka to encapsulate Eureka.

Client obtains the list of Config Service services (IP+Port) by accessing Meta Server through the domain name, and then accesses the service directly through IP+Port. At the same time, a load balance error retry will be made on the Client side.

Portal obtains the list of Admin Service services (IP+Port) by accessing Meta Server through the domain name, and then accesses the service directly through IP+Port. At the same time, load balance and error retry will be done on the Portal side.

To simplify deployment, we will actually deploy the three logical roles Config Service, Eureka and Meta Server in the same JVM process

9. Usability considerations

As a basic service, the configuration center requires very high availability. The following table describes the availability of Apollo in different scenarios:

The reason for the degradation caused by the impact of the scene is that there is no effect when a certain config service is offline.

Config service is stateless, and the client reconnects with other config service offline clients. All config service offline clients cannot read the latest configuration. When the Portal does not affect the client restart, it can read the local cache configuration file.

There is no effect on the offline of a certain admin service.

Admin service is stateless, Portal reconnects all admin service offline clients of other admin service, no impact, portal cannot update configuration

There is no effect on the offline of a certain portal.

Portal domain name is bound to multiple servers through slb. After retry, all available servers are directed to portal offline clients. Portal cannot update the configuration.

It doesn't matter if a data center goes offline.

Multi-data center deployment, complete data synchronization, Meta Server/Portal domain name automatically switched to other surviving data centers via slb. 2. Apollo configuration center to create projects and configurations

Next, we will create an Apollo client project and use Apollo to implement configuration dynamic updates. However, before this, we need to enter the Apollo Portal interface in advance, create a project in advance and configure a parameter in it, so that subsequent clients can introduce the configuration parameter to test whether it can be changed dynamically.

1. Log in to Apollo

Here, I deploy to Kubernetes, expose a port through NodePort, open this address and log in to Apollo:

User name: apollo

Secret code: admin

2. Modify and add department data

When creating a project after logging in, the selection department can only select two options: test Department 1 and Test Department 2, which are included in Apollo by default.

At first, this is really confusing. It turns out that Apoloo does not have any management programs to modify or add department information, so we can only add or modify data by modifying the database. Here, open the table ApolloPortalDB in the Portal monthly database to modify the json data of value key to organizations, and change it to your own department information.

3. Create a project

After modifying the database department information, log in to Apollo Portal again, and then create the project. At this time, the selection department can see that it has become our own modified department information, select our custom department, and then set the application ID to apollo-test and the application name to apollo-demo.

Enter the configuration management interface after the creation is completed

4. Create a configuration parameter

Create a configuration parameter to facilitate subsequent Apollo client projects to introduce this parameter for dynamic configuration testing.

Set key to test value to 123456 and then set a comment to save.

After the creation is completed, you can see that a new configuration has been added to the configuration management program.

Next, we will publish this configuration through the publish button.

Third, create an Apollo client test project

Here you create a SpringBoot project and introduce the Apollo client to interact with the Apollo configuration center server.

1. Maven add Apollo dependency 1

two

4 4.0.0

five

six

7 org.springframework.boot

8 spring-boot-starter-parent

9 2.1.8.RELEASE

ten

eleven

12 club.mydlq

13 apollo-demo

14 0.0.1

15 apollo-demo

16 Apollo Demo

seventeen

eighteen

19 1.8

twenty

twenty-one

twenty-two

twenty-three

24 org.springframework.boot

25 spring-boot-starter-web

twenty-six

twenty-seven

28 com.ctrip.framework.apollo

29 apollo-client

30 1.4.0

thirty-one

thirty-two

thirty-three

thirty-four

thirty-five

thirty-six

37 org.springframework.boot

38 spring-boot-maven-plugin

thirty-nine

forty

forty-one

2. Add parameters to the configuration file

Add the following parameters to the application.yml configuration file. Here is a brief description of the role of the Apollo parameter:

Apollo.meta: Apollo configuration center address.

Apollo.cluster: specifies to use the configuration under a certain cluster.

Apollo.bootstrap.enabled: whether to enable Apollo.

Apollo.bootstrap.namespaces: the configuration that specifies which Namespace to use, the default application.

Apollo.cacheDir=/opt/data/some-cache-dir: in order to prevent the configuration center from being unable to connect, Apollo will automatically cache a copy of the configuration locally.

Apollo.autoUpdateInjectedSpringProperties: Spring applications usually use Placeholder to inject the configuration, such as ${someKey:someDefaultValue}, with key before the colon and default value after the colon. If you want to turn off the automatic update of placeholder at run time, you can set it to false.

Apollo.bootstrap.eagerLoad.enabled: if you set Apollo load to false before initializing the log system, the log information of Apollo will be printed. However, since printing Apollo log information requires log startup, the log configuration cannot be modified after startup, so Apollo cannot manage the log configuration of the application. If set to true, Apollo can manage the configuration of logs, but cannot print the log information of Apollo.

"apply configuration

2server:

3 port: 8080

4spring:

5 application:

6 name: apollo-demo

seven

8#Apollo configuration

9app:

10 id: apollo-test # App ID

11apollo:

12 cacheDir: / opt/data/ # configure the local configuration cache directory

13 cluster: default # specifies the configuration of which cluster to use

14 meta: http://192.168.2.11:30002 # DEV environment configuration center address

15 autoUpdateInjectedSpringProperties: whether to enable automatic update of Spring parameters in true #

16 bootstrap:

17 enabled: whether true # enables Apollo

18 namespaces: application # set Namespace

19 eagerLoad:

20 enabled: false # raises the Apollo load to before initializing the logging system

3. Create a test Controller class

Write a Controller class to output the value of the test variable, using the @ Value annotation of Spring to read the value of the variable in the configuration file. Here, test this value. Whether the value of the variable read after the project is started is the default value set in the application configuration file or the value in the remote Apollo. If it is the value configured in Apollo, then test whether there will be changes after changing the value of the variable in the Apollo configuration center.

1import org.springframework.beans.factory.annotation.Value

2import org.springframework.web.bind.annotation.GetMapping

3import org.springframework.web.bind.annotation.RestController

four

5@RestController

6public class TestController {

seven

8 @ Value ("${test: default}")

9 private String test

ten

11 @ GetMapping ("/ test")

12 public String test () {

The value of 13 return "test is:" + test

14}

15}

4. Create a startup class

SpringBoot project startup class.

1import org.springframework.boot.SpringApplication

2import org.springframework.boot.autoconfigure.SpringBootApplication

three

4@SpringBootApplication

5public class Application {

six

7 public static void main (String [] args) {

8 SpringApplication.run (Application.class, args)

9}

ten

11}

5. JVM startup parameters add startup parameters

Since my Apollo is deployed in a Kubernetes environment, two variables must be added to the JVM parameter:

Env: the environment in which the application uses Apollo. For example, setting it to DEV specifies the use of the development environment. If set to PRO, it specifies the use of the production environment.

Apollo.configService: specify the address of the configuration center, skip the configuration of meta, and specify the meta address during testing has no effect. If the Apollo is deployed in Kubernetes, you must set this parameter to the configuration center address. If the Apollo is not in the Kubernetes environment, you can not set this parameter and just set the meta parameter. In general, configService and meta values are the same.

If you are starting in Idea, you can configure the startup parameters, plus:

1murDapollo.configService= http://192.168.2.11:30002-Denv=DEV

If it is a java command to start the program, you need JVM plus:

1$ java-Dapollo.configService= http://192.168.2.11:30002-Denv=DEV-jar apollo-demo.jar

Note: the environment specified by env above should be consistent with the environment where Config address is specified by apollo.meta. For example, if Denv=DEV is used in the development environment, then the Config of apollo.meta= http://xxx.xxx.xxx:8080 is also the configuration center service in the development environment, rather than the configuration center in PRO or other environments.

Start the project to test 1. Test whether the value set in Apollo can be obtained.

Start the above test case and enter the address http://localhost:8080/test to view:

The value of 1test is: 123456

You can see that the value of 123456 of the test parameter configured in Apollo is used instead of the default value.

2. Test whether the client can refresh in time when the parameter values are modified in Apollo.

Modify the Apollo configuration center parameter test to 666666, and then publish it again.

Enter the address http://localhost:8080/test again after publishing to view:

The value of 1test is: 666666

You can see that the value in the sample application has been changed to the latest value.

3. Test whether the client can change in time when Apollo performs the configuration rollback operation

After the rollback is completed, the status will become unpublished, then enter the address http://localhost:8080/test to view:

The value of 1test is: 123456

You can see that it has been rolled back to the value of the previous test configuration.

4. Test the changes of the client when the Apollo cannot be accessed

Here we deliberately correct the address of the Apollo configuration center in the JVM parameter:

1murDapollo.configService= http://192.168.2.100:30002-Denv=DEV

Then enter the address http://localhost:8080/test and you can see that the value is:

The value of 1test is: 123456

You can see that the value displayed is not the default value we defined, but the value of the test parameter configured by the Apollo configuration center. Considering that Apollo will cache a copy of the configuration locally, the above reason is estimated to be that the cache takes effect. When the client cannot connect to the Apollo configuration center, the configuration in the local cache file is used by default.

Above, we configure the local cache configuration file to be stored at "/ opt/data/". Next, enter the cache directory, find the corresponding cache configuration file, delete the cache configuration file, restart the application, and enter the address again to view:

The value of 1test is: default

After deleting the cache profile, you can see that the output value is the default value defined by yourself.

5. Test the changes of the client when the parameters are deleted in Apollo

Here we go to the Apollo configuration center, delete the previously created test parameters, and then publish.

Then open the address http://localhost:8080/test again to view:

The value of 1test is: default

You can see that the default values set in the application are displayed.

Fifth, probe into the Cluster and Namespace of Apollo.

In Apollo, configuration can be divided into Dev (development), Prod (production) and other environments according to different environments, different Cluster (clusters) according to regions, and different Namespace (namespaces) according to the functions of configuration parameters. Here, how to use the above capabilities.

1. Configuration in different environments

(1) add parameters to the PRO environment of Apollo configuration center

Open the Apollo configuration center, click PRO environment in the environment list, and then add a new configuration, which is the same as the parameters in the previous example. These are all test parameters, and will be released after creation.

Then modify the example project above to specify the configuration parameter as the PRO environment:

(2) the sample project modifies the application.yml configuration file

Change the apollo.meta parameter to the configuration center address of RPO

1.

two

3apollo:

4 meta: http://192.168.2.11:30005 # RPO environment configuration center address

five

6.

(3) the sample project modifies the JVM parameters

Change the apollo.configService parameter to the address of the PRO configuration center, and the value of the env parameter to PRO.

1murDapollo.configService= http://192.168.2.11:30005-Denv=PRO

(4) start the sample project to observe the results

Start the sample project, and then enter the address http://localhost:8080/test to view the information:

The value of 1test is: abcdefg

You can see that it has been changed to the build environment configuration, so in the actual project, if you want to change the environment, you need to modify the JVM parameter env (and if the Apollo is deployed in the Kubernetes environment, you need to modify the apollo.configService parameter), and modify the parameter apollo.meta value of the application.yml configuration file.

2. Configurations under different clusters

(1) create two clusters

For example, in the process of development, applications are often deployed to different computer rooms, where two clusters of beijing and shanghai are created.

(2) both clusters are configured with the same parameters and different values

In both clusters beijing and shanghai, the parameter test is configured uniformly and different values are set.

(3) the sample project application.yml modifies the cluster configuration parameters, and starts the project to observe the results

Specify the cluster as beijing:

1.

two

3apollo:

4 cluster: beijing # specifies the use of beijing clusters

five

6.

Start the sample project, and then enter the address http://localhost:8080/test to view the information:

The value of 1test is: Cluster-BeiJing

You can see that the configuration of the beijing cluster is used.

Specify the cluster as shanghai:

1.

two

3apollo:

4 cluster: shanghai # specifies the use of shanghai clusters

five

6.

Start the sample project, and then enter the address http://localhost:8080/test to view the information:

The value of 1test is: Cluster-ShangHai

You can see that the configuration of the shanghai cluster is used.

3. Configurations under different namespaces

(1), create two namespaces

There are two kinds of namespaces, one is public (public), and the other is private private. All projects in public namespaces can read configuration information, while private ones can only read configurations if the app.id value belongs to the application.

Two private namespaces, dev-1 and dev-2, are created here for testing.

(2) both clusters are configured with the same parameters and different values

In both namespaces, the parameter test is configured uniformly, and different values are set, and then released.

(3) the sample project application.yml modifies the namespace configuration parameters and starts the project to observe the results

Specify the namespace as dev-1:

1.

two

3apollo:

4 bootstrap:

5 namespaces: dev-1 # sets the dev-1 namespace

six

7.

Start the sample project, and then enter the address http://localhost:8080/test to view the information:

The value of 1test is: dev-1 Namespace

As you can see, the configuration using the dev-1 namespace

Specify the namespace as dev-2:

1.

two

3apollo:

4 bootstrap:

5 namespaces: dev-2 # sets the dev-1 namespace

six

7.

Start the sample project, and then enter the address http://localhost:8080/test to view the information:

The value of 1test is: dev-2 Namespace

As you can see, the configuration using the dev-2 namespace

6. The SpringBoot application of Kubernetes uses Apollo to configure the center

My Apollo and SpringBoot applications are generally based on Kubernetes deployment, so here is a brief introduction to how to deploy SpringBoot applications in the Kubernetes environment and use Apollo as the configuration center.

The project here still uses the above example, but first compile it into a Docker image to facilitate subsequent deployment to the Kubernetes environment.

1. Build a Docker image

(1) perform Maven compilation

First execute the Maven command to compile the project into an executable JAR.

1$ mvn clean install

(2) prepare Dockerfile

Create the Dockerfile file needed to build the Docker image, copy the JAR compiled by Maven into the image, and then set two variables, namely:

JAVA_OPTS: Java JVM startup parameter variable. You need to add a time zone parameter here.

APP_OPTS: the Spring container starts the parameter variable, which can be used to configure the Spring parameter for subsequent operations.

Dockerfile:

1FROM openjdk:8u222-jre-slim

2VOLUME / tmp

3ADD target/*.jar app.jar

4RUN sh-c 'touch / app.jar'

5ENV JAVA_OPTS= "- Duser.timezone=Asia/Shanghai"

6ENV APP_OPTS= ""

7ENTRYPOINT ["sh", "- c", "java $JAVA_OPTS-Djava.security.egd=file:/dev/./urandom-jar / app.jar $APP_OPTS"]

(3) build a Docker image

Execute the Docker Build command to build the Docker image.

1$ docker build-t mydlqclub/springboot-apollo:0.0.1.

2. Deploy the sample application with Kubernetes

(1) Kubernetes deployment file that creates SpringBoot and uses Apollo configuration center

Here, create the SpringBoot deployment file apollo-demo-example.yaml under Kubernetes. Previously, two environment variables, JAVA_OPTS and APP_OPTS, were set in Dockerfile. The value of the JAVA_OPTS variable will be used as the JVM startup parameter, and the value of the APP_OPTS variable will be used as the configuration parameter of the application. So, here we put the Apollo configuration parameter into the variable, which makes it easy to modify and maintain the configuration information of Apollo.

In the environment variable parameters configured below, the configuration center address is set to http://service-apollo-config-server-dev.mydlqclub:8080. This is because Apollo is deployed in K8S environment and can be accessed by domain name. Service-apollo-config-server-dev is the Service name of the application, and mydlqcloud is the Namespace name under K8S.

Springboot-apollo.yaml

1 apiVersion: v1

2 kind: Service

3 metadata:

4 name: springboot-apollo

5 spec:

6 type: NodePort

7 ports:

8-name: server

9 nodePort: 31080

10 port: 8080

11 targetPort: 8080

12-name: management

13 nodePort: 31081

14 port: 8081

15 targetPort: 8081

16 selector:

17 app: springboot-apollo

18

19 apiVersion: apps/v1

20 kind: Deployment

21 metadata:

22 name: springboot-apollo

23 labels:

24 app: springboot-apollo

25 spec:

26 replicas: 1

27 selector:

28 matchLabels:

29 app: springboot-apollo

30 template:

31 metadata:

32 name: springboot-apollo

33 labels:

34 app: springboot-apollo

35 spec:

36 restartPolicy: Always

37 containers:

38-name: springboot-apollo

39 image: mydlqclub/springboot-apollo:0.0.1

40 imagePullPolicy: Always

41 ports:

42-containerPort: 8080

43 name: server

44 env:

45-name: JAVA_OPTS

46 value: "- Denv=DEV"

47 # # make sure to change the mydlqcloud here to your own Namespace name

48-name: APP_OPTS

49 value: "

50-app.id=apollo-demo

51-apollo.bootstrap.enabled=true

52-apollo.bootstrap.eagerLoad.enabled=false

53-apollo.cacheDir=/opt/data/

54-- apollo.cluster=default

55-apollo.bootstrap.namespaces=application

56-apollo.autoUpdateInjectedSpringProperties=true

57-apollo.meta= http://service-apollo-config-server-dev.mydlqcloud:8080

58 "

59 resources:

60 limits:

61 memory: 1000Mi

62 cpu: 1000m

63 requests:

64 memory: 500Mi

65 cpu: 500m

(2) deploy SpringBoot applications to Kubernetes

-n: create and apply to the specified Namespace.

1$ kubectl apply-f springboot-apollo.yaml-n mydlqcloud on "SpringBoot how to integrate Apollo configuration center" this article is shared here, I hope the above content can be of some help to you, so that you can learn more knowledge, if you think the article is good, please share it out for more people to see.

Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.

Views: 0

*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.

Share To

Development

Wechat

© 2024 shulou.com SLNews company. All rights reserved.

12
Report