DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports Events Over 2 million developers have joined DZone. Join Today! Thanks for visiting DZone today,
Edit Profile Manage Email Subscriptions Moderation Admin Console How to Post to DZone Article Submission Guidelines
View Profile
Sign Out
Refcards
Trend Reports
Events
Zones
Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Partner Zones AWS Cloud
by AWS Developer Relations
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Partner Zones
AWS Cloud
by AWS Developer Relations
Building Scalable Real-Time Apps with AstraDB and Vaadin
Register Now

Trending

  • Design Patterns for Microservices: Ambassador, Anti-Corruption Layer, and Backends for Frontends
  • Managing Data Residency, the Demo
  • Micro Frontends on Monorepo With Remote State Management
  • 4 Expert Tips for High Availability and Disaster Recovery of Your Cloud Deployment

Trending

  • Design Patterns for Microservices: Ambassador, Anti-Corruption Layer, and Backends for Frontends
  • Managing Data Residency, the Demo
  • Micro Frontends on Monorepo With Remote State Management
  • 4 Expert Tips for High Availability and Disaster Recovery of Your Cloud Deployment
  1. DZone
  2. Software Design and Architecture
  3. Containers
  4. Deploying Artemis Broker With SSL Enabled and Use AMQP

Deploying Artemis Broker With SSL Enabled and Use AMQP

Learn how to deploy the Red Hat AMQ Broker on openshift 4.x. The external client can connect to produce/consume messages using AMQP.

Ramu kakarla user avatar by
Ramu kakarla
·
Jul. 27, 20 · Tutorial
Like (1)
Save
Tweet
Share
10.16K Views

Join the DZone community and get the full member experience.

Join For Free

Red Hat AMQ Broker

AMQ Broker is based on Apache ActiveMQ Artemis. It provides a message broker that is JMS-compliant. Apache ActiveMQ Artemis is an open-source project for an asynchronous messaging system. It is a high performance, embeddable, clustered, and supports multiple protocols.

The core ActiveMQ Artemis is JMS-agnostic and provides a non-JMS API, which is referred to as the core API. ActiveMQ Artemis also provides a JMS client API that uses a facade layer to implement the JMS semantics on top of the core API. Essentially, JMS interactions are translated into core API operations on the client-side using the JMS client API. From there, all operations are sent using the core client API and Apache ActiveMQ Artemis wire format. The server itself only uses the core API. For more details on the core API and its concepts, refer to the ActiveMQ Artemis documentation.

Red Hat Open Shift

Red Hat Open Shift offers a consistent hybrid-cloud foundation for building and scaling containerized applications. Open Shift provides an enterprise-grade, container-based platform with no vendor lock-in. Red Hat was one of the first companies to work with Google on Kubernetes, even before launch, and has become the second leading contributor to the Kubernetes upstream project. Open Shift also provides a common development platform no matter what infrastructure we use to host the application.

AMQP

The Advanced Message Queuing Protocol (AMQP) is an open standard application layer protocol for message-oriented middleware. The defining features of AMQP are message orientation, queuing, routing (including point-to-point and publish-and-subscribe), reliability and security.

In this article we will deploy the Red Hat AMQ Broker 7.7 on openshift 4.3 with SSL security enabled and produce consume messages from the external clients using AMQP protocol.

Prerequisites

For this demonstration, you will need the following technologies set up in your development environment:

  1. An Open Shift 4.3+ environment with Cluster Admin access
  2. Open shift CLI (oc)
  3. Apache Maven 3.6.3+
  4. JDK 8+ Installed

There are two ways to deploy AMQ Broker on Open Shift Container Platform:

  • Using the AMQ Broker Operator 
  • Using application templates

The AMQ Broker Operator is the recommended way to create broker deployments on Open Shift Container Platform. we will be using AMQ Broker Operator  to deploy the AMQ Broker

Deploy AMQ Broker on Open Shift

  1. In your web browser, navigate to the AMQ Broker Software Downloads page.
  2. In the Version drop-down box, ensure that the value is set to the latest AMQ Broker version, 7.7.0.
  3. Next to AMQ Broker 7.7 Operator Installation Files, click Download.

    Download of the amq-broker-operator-7.7.0-ocp-install-examples.zip compressed archive automatically begins.

  4. When the download has completed, move the archive to your chosen installation directory

  5. Log in to the Open Shift Container Platform as a cluster administrator.

Install AMQBrokerOperator

1. Create a new project

Java
 




x


 
1
oc new-project new-message-project



2. Specify a service account to use with the Operator

3. Create a service account in your project.

Java
 




x


 
1
$ oc create -f deploy/service_account.yaml
2

          



4. Create a role in your project. This file specifies the resources that the Operator can use and modify.

Java
 




xxxxxxxxxx
1


 
1
oc create -f deploy/role.yaml



5. Create the role binding in your project. The role binding binds the previously-created service account to the Operator role, based on the names you specified

Java
 




xxxxxxxxxx
1


 
1
oc create -f deploy/role_binding.yaml


  

6. Install the latest CRDs in your Open Shift cluster before deploying and starting the Operator

Deploy the main broker CRD, address CRD, and scale down controller CRD.

Java
 




xxxxxxxxxx
1


 
1
oc create -f deploy/crds/broker_activemqartemis_crd.yaml
2
oc create -f deploy/crds/broker_activemqartemisaddress_crd.yaml
3
oc create -f deploy/crds/broker_activemqartemisscaledown_crd.yaml



7. Create Imagepullsecret and associate with the account used for authentication in the Red Hat Container Registry with the default, deployer, and builder service accounts for your Open Shift project.

Java
 




xxxxxxxxxx
1


 
1
oc create secret docker-registry imagestreamsecret   --docker-server=registry.redhat.io   --docker-username=   --docker-password=   --docker-email=
2
  
3
oc secrets link --for=pull default imagestreamsecret
4
oc secrets link --for=pull deployer imagestreamsecret
5
oc secrets link --for=pull builder imagestreamsecret
6

          
7
[kkakarla@kkakarla /]$ oc get secrets | grep imagestreamsecret
8
imagestreamsecret                     kubernetes.io/dockerconfigjson        1      22h
9

          



8. Deploy the Operator

Java
 




xxxxxxxxxx
1


 
1
oc create -f deploy/operator.yaml
2
[kkakarla@kkakarla /]$ oc get pods | grep operator
3
amq-broker-operator-85598678bf-l9c2g   1/1     Running   0          22h
4

          


    

9. Now Deploy the AMQ Artemis SSL enabled broker

Add the protocols and port parameters. Set values to specify the messaging protocols to be used by the acceptor and the port on each broker Pod to expose for those protocols

The configured acceptor exposes port 5672 to AMQP clients, Core protocol to the core, Open Wire to openwire, MQTT to MQTT and STOMP to stomp

For each broker Pod in your deployment, the Operator also creates a default acceptor that uses port 61616. This default acceptor is required for broker clustering and has the Core protocol enabled.

By default, the AMQ Broker management console uses port 8161 on the broker Pod. Each broker Pod in your deployment has a dedicated service that provides access to the console

To specify the number of concurrent client connections that the acceptor allows, add the connections Allowed parameter and set a value

By default, an acceptor is exposed only to clients in the same Open Shift cluster as the broker deployment. To also expose the acceptor to clients outside Open Shift, add the expose parameter and set the value to true.

Also, to enable secure connections to the acceptor from clients outside Open Shift, add the sslEnabled parameter and set the value to true

Configuring One-Way TLS

The procedure in this section shows how to configure one-way Transport Layer Security (TLS) to secure a broker-client connection.

In one-way TLS, only the broker presents a certificate. This certificate is used by the client to authenticate the broker

1. Generate a self-signed certificate for the broker key store.

Java
 




xxxxxxxxxx
1


 
1
keytool -genkey -alias broker -keyalg RSA -keystore /home/kkakarla/development/amq7/amq7-on-openshift/broker.ks



2. Export the certificate from the broker key store, so that it can be shared with clients. Export the certificate in the Base64-encoded .pem format

Java
 




xxxxxxxxxx
1


 
1
keytool -export -alias broker -keystore /home/kkakarla/development/amq7/amq7-on-openshift/broker.ks -file /home/kkakarla/development/amq7/amq7-on-openshift/broker_cert.pem



3. On the client, create a client trust store that imports the broker certificate

Java
 




xxxxxxxxxx
1


 
1
keytool -import -alias broker -keystore /home/kkakarla/development/amq7/amq7-on-openshift/client.ts -file /home/kkakarla/development/amq7/amq7-on-openshift/broker_cert.pem



4. Create a secret to store the TLS credentials

Java
 




xxxxxxxxxx
1


 
1
oc create secret generic amq7brokersecret \
2
--from-file=broker.ks=/home/kkakarla/development/amq7/amq7-on-openshift/broker.ks \
3
--from-file=client.ts=/home/kkakarla/development/amq7/amq7-on-openshift/broker.ks \
4
--from-literal=keyStorePassword=artemis7 \
5
--from-literal=trustStorePassword=artemis7
6

          
7
[kkakarla@kkakarla /]$ oc get secrets | grep amq7brokersecret
8
amq7brokersecret                      Opaque                                4      23h



5. Add the secret to the service account that you created when installing the Operator. 

Java
 




xxxxxxxxxx
1


 
1
oc secrets add sa/amq-broker-operator secret/amq7brokersecret



6. Specify the secret name in the sslSecret parameter of your secured acceptor or connector

7. Now deploy the broker

Java
 




xxxxxxxxxx
1
16


 
1
apiVersion: broker.amq.io/v2alpha2
2
kind: ActiveMQArtemis
3
metadata:
4
  name: ex-aao
5
spec:
6
  deploymentPlan:
7
    size: 1
8
    image: registry.redhat.io/amq7/amq-broker:7.7
9
  acceptors:
10
  - name: amqp
11
    protocols: amqp,openwire
12
    port: 5672
13
    sslEnabled: true
14
    sslSecret: amq7brokersecret
15
    expose: true
16
    connectionsAllowed: 25



8. Now check all the project resources

Shell
 




xxxxxxxxxx
1
23


 
1
[kkakarla@kkakarla /]$ oc get all
2
NAME                                       READY   STATUS    RESTARTS   AGE
3
pod/amq-broker-operator-85598678bf-l9c2g   1/1     Running   0          23h
4
pod/ex-aao-ss-0                            1/1     Running   0          23h
5

          
6
NAME                          TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)              AGE
7
service/amq-broker-operator   ClusterIP   0.0.0.0          <none>        8383/TCP             23h
8
service/ex-aao-amqp-0-svc     ClusterIP   0.0.0.0          <none>        5672/TCP             23h
9
service/ex-aao-hdls-svc       ClusterIP   None             <none>        8161/TCP,61616/TCP   23h
10
service/ex-aao-ping-svc       ClusterIP   None             <none>        8888/TCP             23h
11

          
12
NAME                                  READY   UP-TO-DATE   AVAILABLE   AGE
13
deployment.apps/amq-broker-operator   1/1     1            1           23h
14

          
15
NAME                                             DESIRED   CURRENT   READY   AGE
16
replicaset.apps/amq-broker-operator-85598678bf   1         1         1       23h
17

          
18
NAME                         READY   AGE
19
statefulset.apps/ex-aao-ss   1/1     23h
20

          
21
NAME                                             HOST/PORT                                                                                PATH   SERVICES            PORT     TERMINATION        WILDCARD
22
route.route.openshift.io/ex-aao-amqp-0-svc-rte   ex-aao-amqp-0-svc-rte-new-message-project.apps.xxx.redhat.com          ex-aao-amqp-0-svc   amqp-0   passthrough/None   None
23

          



Connecting to the Broker From External Clients

When you expose an acceptor to external clients (that is, by setting the value of the expose parameter to true), a dedicated Service and Route are automatically created for each broker Pod in the deployment

An external client can connect to the broker by specifying the full hostname of the Route created for the broker Pod

By default, the Open Shift router listens to port 80 for non-secured (that is, non-SSL) traffic and port 443 for secured (that is, SSL-encrypted) traffic. For an HTTP connection, the router automatically directs traffic to port 443 if you specify a secure connection URL, or to port 80 if you specify a non-secure connection URL

Clients must explicitly specify the port number (for example, port 443) as part of the connection URL.

For one-way TLS, the client must specify the path to its trust store and the corresponding password, as part of the connection URL.

To Produce Messages using amqps

Java
 




xxxxxxxxxx
1
13


 
1
 public void amqpTest() throws Exception{
2

          
3
        JmsConnectionFactory activeMQConnectionFactory = new JmsConnectionFactory("user-name","password","amqps://ex-aao-amqp-0-svc-rte-new-message-project.apps.xxx.redhat.com:443?" +
4
                "transport.trustStoreLocation=/home/kkakarla/development/amq7/amq7-on-openshift/client.ts&transport.keyStoreLocation=/home/kkakarla/development/amq7/amq7-on-openshift/broker.ks" +
5
                "&transport.trustStorePassword=artemis7&transport.keyStorePassword=artemis7&transport.verifyHost=false");
6
        Connection connection = activeMQConnectionFactory.createConnection();
7
        connection.start();
8
        Session session = connection.createSession(false,Session.AUTO_ACKNOWLEDGE);
9
        MessageProducer messageProducer = session.createProducer(session.createQueue("test_q"));
10
        Message message = session.createTextMessage("this is amqp two way ssl testing");
11
        messageProducer.send(message);
12
        connection.close();
13
    }



For simplicity. I defined the above class as a bean and called from the camel route.

Java
 




xxxxxxxxxx
1
16


 
1

          
2
<bean id="amqpTest" class="com.mycompany.camel.AmqpsslExample"></div>
3
<camelContext id="_camelContext1"
4
        xmlns="http://camel.apache.org/schema/spring">
5
    
6
      <route>
7
      <from uri="timer:foo?period=5000&amp;repeatCount=1"></from>
8
      <setBody>
9
          <method ref="amqpTest" method="amqpTest"></method>
10
      </setBody>
11
      <log message="The message sent"></log>
12
      <to uri="mock:result"></to>
13
    </route>   
14
    
15
  
16
  



To Consume Messages using amqps.

Java
 




xxxxxxxxxx
1
16


 
1
 public void amqpTestConsumer() throws Exception{
2

          
3
        JmsConnectionFactory activeMQConnectionFactory = new JmsConnectionFactory("user-name","password","amqps://ex-aao-amqp-0-svc-rte-new-message-project.apps.xxx.redhat.com:443?" +
4
                "transport.trustStoreLocation=/home/kkakarla/development/amq7/amq7-on-openshift/client.ts&transport.keyStoreLocation=//home/kkakarla/development/amq7/amq7-on-openshift/broker.ks" +
5
                "&transport.trustStorePassword=artemis7&transport.keyStorePassword=artemis7&transport.verifyHost=false");
6
        Connection connection = activeMQConnectionFactory.createConnection();
7
        connection.start();
8
        Session session = connection.createSession(false,Session.AUTO_ACKNOWLEDGE);
9
        // Step 5. create a moving receiver, this means the message will be removed from the queue
10
        MessageConsumer consumer = session.createConsumer(session.createQueue("test_q"));
11

          
12
        // Step 7. receive the simple message
13
        TextMessage m = (TextMessage) consumer.receive(5000);
14
        System.out.println("message = " + m.getText());
15
        connection.close();
16
    }



camel consumer route

Java
 




x


 
1
 <route >
2
      <from uri="timer:foo?period=5000&amp;repeatCount=1"/>
3
      <setBody>
4
          <method ref="amqpTest" method="amqpTestConsumer()"/>
5
      </setBody>
6
      <to uri="mock:result"/>
7
    </route> 



Now Run the Test case to connect the AMQ Broker deployed on openshift and  produce/consume messages

Java
 




xxxxxxxxxx
1
12


 
1
[kkakarla@kkakarla camel-artemis-openshiftexample]$ mvn camel:run
2
[INFO] Scanning for projects...
3
[INFO] 
4
[INFO] ----------------< com.mycompany:camel-artemis-example1 >----------------
5
[INFO] Building Fuse CBR Quickstart - Java 1.0.0-SNAPSHOT
6
[INFO] -------------------------------[ bundle ]-------------------------------
7
[INFO] 
8
---------------------------------------------------------------------------
9
----------------------------------------------------------------------------
10
[pache.camel.spring.Main.main()] SpringCamelContext             INFO  Route: route1 started and consuming from: timer://foo?period=5000&repeatCount=1
11
[pache.camel.spring.Main.main()] SpringCamelContext             INFO  Route: route2 started and consuming from: timer://foo?period=5000&repeatCount=1
12
[pache.camel.spring.Main.main()] SpringCamelContext             INFO  Total 2 routes, of which 2 are started
13
[pache.camel.spring.Main.main()] SpringCamelContext             INFO  Apache Camel 2.21.0.fuse-000077-redhat-1 (CamelContext: _camelContext1) started in 0.226 seconds
14
[pache.camel.spring.Main.main()] DefaultLifecycleProcessor      INFO  Starting beans in phase 2147483646
15
[b.upshift.rdu2.redhat.com:443]] SaslMechanismFinder            INFO  Best match for SASL auth was: SASL-PLAIN
16
[b.upshift.rdu2.redhat.com:443]] JmsConnection                  INFO  Connection ID:ae130afa-ffa9-4ba6-884f-2d40840d8421:1 connected to remote Broker: amqps://ex-aao-amqp-0-svc-rte-new-message-project.apps.xxx.redhat.com:443?transport.trustStoreLocation=%2Fhome%2Fkkakarla%2Fdevelopment%2Famq7%2Famq7-on-openshift%2Fclient.ts&transport.keyStoreLocation=%2Fhome%2Fkkakarla%2Fdevelopment%2Famq7%2Famq7-on-openshift%2Fbroker.ks&transport.trustStorePassword=artemis7&transport.keyStorePassword=artemis7&transport.verifyHost=false
17
[text1) thread #2 - timer://foo] route1                         INFO  The message sent
18
[b.upshift.rdu2.redhat.com:443]] SaslMechanismFinder            INFO  Best match for SASL auth was: SASL-PLAIN
19
[b.upshift.rdu2.redhat.com:443]] JmsConnection                  INFO  Connection ID:c9b34adb-4355-49fb-a1e8-ef95a11e698e:1 connected to remote Broker: amqps://ex-aao-amqp-0-svc-rte-new-message-project.apps.xxx.redhat.com:443?transport.trustStoreLocation=%2Fhome%2Fkkakarla%2Fdevelopment%2Famq7%2Famq7-on-openshift%2Fclient.ts&transport.keyStoreLocation=%2F%2Fhome%2Fkkakarla%2Fdevelopment%2Famq7%2Famq7-on-openshift%2Fbroker.ks&transport.trustStorePassword=artemis7&transport.keyStorePassword=artemis7&transport.verifyHost=false
20
message = this is amqp two way ssl testing
21

          



Similarly, we can connect using the open-wire protocol.

Java
 




xxxxxxxxxx
1
71


 
1
<bean id="activeMQConnectionFactory" class="org.apache.activemq.ActiveMQSslConnectionFactory">
2

          
3
        <property name="brokerURL" value="ssl://ex-aao-amqp-0-svc-rte-new-message-project.apps.xxx.redhat.com:443" />
4

          
5
        <property name="userName" value="7nRMRFDZ" />
6

          
7
        <property name="password" value="bFxUBMQJ" />
8

          
9
        <property name="trustStore" value="/home/kkakarla/development/amq7/amq7-on-openshift/client.ts" />
10

          
11
        <property name="trustStorePassword" value="artemis7" />
12

          
13
    </bean>    
14

          
15
      
16

          
17
<bean id="pooledConnectionFactory" class="org.apache.activemq.pool.PooledConnectionFactory"
18

          
19
        init-method="start" destroy-method="stop">
20

          
21
        <!-- maxConnections : Sets the maximum number of pooled Connection. Default = 1) -->
22

          
23
        <property name="maxConnections" value="10" />
24

          
25
        <!-- maximumActiveSessionPerConnection - The maximum number of active session per connection in the pool. -->
26

          
27
        <property name="maximumActiveSessionPerConnection" value="20" />
28

          
29
        <!-- blockIfSessionPoolIsFull : Controls behavior of session pool. Blocks call to Connection.getSession()
30

          
31
         if the session pool is full. Default = true -->
32

          
33
        <property name="blockIfSessionPoolIsFull" value="true" />
34

          
35
        <!-- createConnectionOnStartup - true to create a connection on startup. Used to warm-up the pool on startup.  -->
36

          
37
        <property name="createConnectionOnStartup" value="true" />
38

          
39
        <!-- idleTimeout : The maximum time a pooled Connection can sit unused before it is eligible for removal. Default=30sec -->
40

          
41
        <property name="idleTimeout" value="50" />
42

          
43
        <!-- connectionFactory : Sets the ConnectionFactory used to create new pooled Connections. -->
44

          
45
        <property name="connectionFactory" ref="activeMQConnectionFactory" />
46

          
47
    </bean>
48
   
49

          
50
 <bean id="jmsConfiguration" class="org.apache.camel.component.jms.JmsConfiguration">
51

          
52
        <property name="connectionFactory" ref="pooledConnectionFactory"/>
53

          
54
        <!-- concurrentConsumers : Maximum no.of concurrent invokers -->
55

          
56
        <property name="concurrentConsumers" value="5"/>
57

          
58
        <!-- maxConcurrentConsumers : Allows dynamic scaling for no.of concurrent invokers as well as dynamic shrinking back to the standard no.of consumers once the load decreases.-->
59

          
60
        <property name="maxConcurrentConsumers" value="10"/>
61

          
62
    </bean>
63
   
64

          
65
 <bean id="activemq" class="org.apache.activemq.camel.component.ActiveMQComponent">
66

          
67
        <property name="configuration" ref="jmsConfiguration"/>
68

          
69
        <property name="deliveryPersistent" value="false" />
70

          
71
    </bean>
72
    <camelContext id="_camelContext1"
73
        xmlns="http://camel.apache.org/schema/spring">
74
        
75
        <route id="test1">
76
            <from id="testfrom1" uri="file:src/main/data?noop=true"/>
77
            <log id="test-1og1" message=" Transferring"/>
78
             <to id="test-to1" uri="direct:ExampleQueue"/>
79
            
80
        </route>
81
        
82
         <route>
83
            <from  uri="direct:ExampleQueue"/>
84
            <log  message=" Transferring to queue"/>
85
            <to  uri="activemq:queue:SCIENCEQUEUE"/>
86
        </route> 
87
        
88
         <route >
89
            <from  uri="activemq:queue:SCIENCEQUEUE"/>
90
            <log  message=" consuming from queue"/>
91
            <to  uri="file:src/out"/>
92
        </route>  
93
       
94
   </camelContext>
95
   
96
   



I hope it will help those who want to deploy the Red Hat AMQ Broker on openshift 4.x and the external client can connect to produce/consume messages using AMQP and open-wire protocols

Java (programming language) TLS Kubernetes Operator (extension) clustering Protocol (object-oriented programming) API Apache ActiveMQ Connection (dance)

Opinions expressed by DZone contributors are their own.

Trending

  • Design Patterns for Microservices: Ambassador, Anti-Corruption Layer, and Backends for Frontends
  • Managing Data Residency, the Demo
  • Micro Frontends on Monorepo With Remote State Management
  • 4 Expert Tips for High Availability and Disaster Recovery of Your Cloud Deployment

Comments

Partner Resources

X

ABOUT US

  • About DZone
  • Send feedback
  • Careers
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 600 Park Offices Drive
  • Suite 300
  • Durham, NC 27709
  • support@dzone.com

Let's be friends: