- Move quickstart from `utils` to `quickstart` - Update quickstart script to add more container manipulation - Move API functional tests back under `modules/api` - Move build-related scripts to `build/` directory - Add quickstart containers that can run the local version of the code
14 KiB
Developer Guide
Table of Contents
Developer Requirements (Local)
-
Java 8 (at least u162)
-
Scala 2.12
-
sbt 1.4+
-
curl
-
docker
-
docker-compose
-
GNU Make 3.82+
-
grunt
-
npm
-
Python 3.5+
Developer Requirements (Docker)
Since almost everything can be run with Docker and GNU Make, if you don't want to setup a local development environment, then you simply need:
Docker
v19.03+ (earlier versions may work fine)Docker Compose
v2.0+ (earlier versions may work fine)GNU Make
v3.82+Bash
3.2+- Basic utilities:
awk
,sed
,curl
,grep
, etc may be needed for scripts
- Basic utilities:
Project Layout
SYSTEM_DESIGN.md provides a high-level architectural overview of VinylDNS and interoperability of its components.
The main codebase is a multi-module Scala project with multiple sub-modules. To start working with the project, from the
root directory run sbt
. Most of the code can be found in the modules
directory. The following modules are present:
root
- this is the parent project, if you run tasks here, it will run against all sub-modulescore
: core modules that are used by both the API and portal, such as cryptography implementations.api
: the API is the main engine for all of VinylDNS. This is the most active area of the codebase, as everything else typically just funnels through the API.portal
: The portal is a user interface wrapper around the API. Most of the business rules, logic, and processing can be found in the API. The only features in the portal not found in the API are creation of users and user authentication.docs
: documentation for VinylDNS.
Core
Code that is used across multiple modules in the VinylDNS ecosystem live in core
.
Code Layout
src/main
- the main source codesrc/test
- unit tests
API
The API is the RESTful API for interacting with VinylDNS. The following technologies are used:
- Akka HTTP - Used primarily for REST and HTTP calls.
- FS2 - Used for backend change processing off of message queues. FS2 has back-pressure built in, and gives us tools like throttling and concurrency.
- Cats Effect - A replacement of
Future
with theIO
monad - Cats - Used for functional programming.
- PureConfig - For loading configuration values.
The API has the following dependencies:
- MySQL - the SQL database that houses the data
- SQS - for managing concurrent updates and enabling high-availability
- Bind9 - for testing integration with a real DNS system
Code Layout
The API code can be found in modules/api
.
src/it
- integration testssrc/main
- the main source codesrc/test
- unit testssrc/universal
- items that are packaged in the Docker image for the VinylDNS API
The package structure for the source code follows:
vinyldns.api.domain
- contains the core front-end logic. This includes things like the application services, repository interfaces, domain model, validations, and business rules.vinyldns.api.engine
- the back-end processing engine. This is where we process commands including record changes, zone changes, and zone syncs.vinyldns.api.protobuf
- marshalling and unmarshalling to and from protobuf to types in our systemvinyldns.api.repository
- repository implementations live herevinyldns.api.route
- HTTP endpoints
Portal
The project is built using:
The portal is mostly a shim around the API. Most actions in the user interface are translated into API calls.
The features that the Portal provides that are not in the API include:
- Authentication against LDAP
- Creation of users - when a user logs in for the first time, VinylDNS automatically creates a user and new credentials for them in the database with their LDAP information.
Code Layout
The portal code can be found in modules/portal
.
app
- source code for portal back-endmodels
- data structures that are used by the portalviews
- HTML templates for each web pagecontrollers
- logic for updating data
conf
- configurations and endpoint routespublic
- source code for portal front-endcss
- stylesheetsimages
- images, including icons, used in the portaljs
- scriptsmocks
- mock JSON used in Grunt teststemplates
- modal templates
test
- unit tests for portal back-end
Documentation
Code used to build the microsite content for the API, operator and portal guides at https://www.vinyldns.io/. Some
settings for the microsite are also configured in build.sbt
of the project root.
Code Layout
src/main/resources
- Microsite resources and configurationssrc/main/mdoc
- Content for microsite web pages
Running VinylDNS Locally
VinylDNS can be started in the background by running the quickstart instructions located in the README. However, VinylDNS can also be run in the foreground.
Starting the API Server
Before starting the API service, you can start the dependencies for local development:
cd test/api/integration
make build && make run-bg
This will start a container running in the background with necessary prerequisites.
Once the prerequisites are running, you can start up sbt by running sbt
from the root directory.
project api
to change the sbt project to the APIreStart
to start up the API server- Wait until you see the message
VINYLDNS SERVER STARTED SUCCESSFULLY
before working with the server - To stop the VinylDNS server, run
reStop
from the api project - To stop the dependent Docker containers:
utils/clean-vinyldns-containers.sh
See the API Configuration Guide for information regarding API configuration.
Starting the Portal
To run the portal locally, you first have to start up the VinylDNS API Server. This can be done by following the instructions for [Staring the API Server](#Starting the API Server) or by using the QuickStart:
quickstart/quickstart-vinyldns.sh --api-only
Once that is done, in the same sbt
session or a different one, go to project portal
and then
execute ;preparePortal; run
.
See the Portal Configuration Guide for information regarding portal configuration.
Loading test data
Normally the portal can be used for all VinylDNS requests. Test users are locked down to only have access to test zones,
which the portal connection modal has not been updated to incorporate. To connect to a zone with testuser, you will need
to use an alternative client and set isTest=true
on the zone being connected to.
Use the vinyldns-js client (Note, you need Node installed):
git clone https://github.com/vinyldns/vinyldns-js.git
cd vinyldns-js
npm install
export VINYLDNS_API_SERVER=http://localhost:9000
export VINYLDNS_ACCESS_KEY_ID=testUserAccessKey
export VINYLDNS_SECRET_ACCESS_KEY=testUserSecretKey
npm run repl
> var groupId;
> vinyl.createGroup({"name": "test-group", "email":"test@test.com", members: [{id: "testuser"}], admins: [{id: "testuser"}]}).then(res => {groupId = res.id}).catch(err => {console.log(err)});
> vinyl.createZone ({name: "ok.", isTest: true, adminGroupId: groupId, email: "test@test.com"}).then(res => { console.log(res) }).catch(err => { console.log(err) })
You should now be able to see the zone in the portal at localhost:9001 when logged in as username=testuser password=testpassword
Testing
Unit Tests
- First, start up your Scala build tool:
sbt
. Running clean immediately after starting is recommended. - (Optionally) Go to the project you want to work on, for example
project api
for the API;project portal
for the portal. - Run all unit tests by just running
test
. - Run an individual unit test by running
testOnly *MySpec
. - If you are working on a unit test and production code at the same time, use
~
(e.g.,~testOnly *MySpec
) to automatically background compile for you!
Integration Tests
Integration tests are used to test integration with real dependent services. We use Docker to spin up those backend services for integration test development.
- Type
dockerComposeUp
to start up dependent background services - Go to the target module in sbt, example:
project api
- Run all integration tests by typing
it:test
. - Run an individual integration test by typing
it:testOnly *MyIntegrationSpec
- You can background compile as well if working on a single spec by using
~it:testOnly *MyIntegrationSpec
- You must stop (
dockerComposeStop
) and start (dockerComposeUp
) the dependent services from the root project (project root
) before you rerun the tests. - For the mysql module, you may need to wait up to 30 seconds after starting the services before running the tests for setup to complete.
Running both
You can run all unit and integration tests for the api and portal by running sbt verify
Functional Tests
When adding new features, you will often need to write new functional tests that black box / regression test the API.
- The API functional tests are written in Python and live under
test/api/functional
. - The Portal functional tests are written in JavaScript and live under
test/portal/functional
.
Running Functional Tests
To run functional tests you can simply execute the following command:
make build && make run
During iterative test development, you can use make run-local
which will mount the current functional tests in the
container, allowing for easier test development.
Additionally, you can pass --interactive
to make run
or make run-local
to drop to a shell inside the container.
From there you can run tests with the /functional_test/run.sh
command. This allows for finer-grained control over the
test execution process as well as easier inspection of logs.
API Functional Tests
You can run a specific test by name by running make run -- -k <name of test function>
. Any arguments after
make run --
will be passed to the test runner test/api/functional/run.sh
.
Setup
We use pytest for python tests. It is helpful that you browse the documentation so that you are familiar with pytest and how our functional tests operate.
We also use PyHamcrest for matchers in order to write easy to read tests. Please browse that documentation as well so that you are familiar with the different matchers for PyHamcrest. There aren't a lot, so it should be quick.
In the test/api/functional
directory are a few important files for you to be familiar with:
vinyl_client.py
- this provides the interface to the VinylDNS API. It handles signing the request for you, as well as building and executing the requests, and giving you back valid responses. For all new API endpoints, there should be a corresponding function in the vinyl_clientutils.py
- provides general use functions that can be used anywhere in your tests. Feel free to contribute new functions here when you see repetition in the code
In the test/api/functional/tests
directory, we have directories / modules for different areas of the application.
batch
- for managing batch updatesinternal
- for internal endpoints (not intended for public consumption)membership
- for managing groups and usersrecordsets
- for managing record setszones
- for managing zones
Functional Test Context
Our functional tests use pytest
contexts. There is a main test context that lives in shared_zone_test_context.py
that creates and tears down a shared test context used by many functional tests. The beauty of pytest is that it will
ensure that the test context is stood up exactly once, then all individual tests that use the context are called using
that same context.
The shared test context sets up several things that can be reused:
- An
ok
user and group - A
dummy
user and group - a separate user and group helpful for testing access controls and authorization - An
ok.
zone accessible only by theok
user andok
group - A
dummy.
zone accessible only by thedummy
user anddummy
group - An IPv6 reverse zone
- A normal IPv4 reverse zone
- A classless IPv4 reverse zone
- A parent zone that has child zones - used for testing NS record management and zone delegations
Partitioning
Each of the test zones are configured in a partition
. By default, there are four partitions. These partitions are
effectively copies of the zones so that parallel tests can run without interfering with one another.
For instance, there are four zones for the ok
zone: ok1
, ok2
, ok3
, and ok4
. The functional tests will handle
distributing which zone is being used by which of the parallel test runners.
As such, you should never hardcode the name of the zone. Always get the zone from the shared_zone_test_context
.
For instance, to get the ok
zone, you would write:
zone = shared_zone_test_context.ok_zone
zone_name = shared_zone_test_context.ok_zone["name"]
zone_id = shared_zone_test_context.ok_zone["id"]
Really Important Test Context Rules!
- Try to use the
shared_zone_test_context
whenever possible! This reduces the time it takes to run functional tests (which is in minutes). - Be mindful of changes to users, groups, and zones in the shared test context, as doing so could impact downstream tests
- If you do modify any entities in the shared zone context, roll those back when your function completes!
Managing Test Zone Files
When functional tests are run, we spin up several Docker containers. One of the Docker containers is a Bind9 DNS server.
If you need to add or modify the test DNS zone files, you can find them in
quickstart/bind9/zones