first commit
This commit is contained in:
707
test/container/containerengine/containerengine.go
Normal file
707
test/container/containerengine/containerengine.go
Normal file
@@ -0,0 +1,707 @@
|
||||
package containerengine
|
||||
|
||||
/*
|
||||
© Copyright IBM Corporation 2017, 2024
|
||||
|
||||
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.
|
||||
*/
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/ibm-messaging/mq-container/test/container/pathutils"
|
||||
)
|
||||
|
||||
type ContainerInterface interface {
|
||||
ContainerCreate(config *ContainerConfig, hostConfig *ContainerHostConfig, networkingConfig *ContainerNetworkSettings, containerName string) (string, error)
|
||||
ContainerStop(container string, timeout *time.Duration) error
|
||||
ContainerKill(container string, signal string) error
|
||||
ContainerRemove(container string, options ContainerRemoveOptions) error
|
||||
ContainerStart(container string, options ContainerStartOptions) error
|
||||
ContainerWait(ctx context.Context, container string, condition string) (<-chan int64, <-chan error)
|
||||
GetContainerLogs(ctx context.Context, container string, options ContainerLogsOptions) (string, error)
|
||||
CopyFromContainer(container, srcPath string) ([]byte, error)
|
||||
|
||||
GetContainerPort(ID string, hostPort int) (string, error)
|
||||
GetContainerIPAddress(ID string) (string, error)
|
||||
ContainerInspectWithFormat(format string, ID string) (string, error)
|
||||
ExecContainer(ID string, user string, cmd []string) (int, string)
|
||||
GetMQVersion(image string) (string, error)
|
||||
ContainerInspect(containerID string) (ContainerDetails, error)
|
||||
|
||||
NetworkCreate(name string, options NetworkCreateOptions) (string, error)
|
||||
NetworkRemove(network string) error
|
||||
|
||||
VolumeCreate(options VolumeCreateOptions) (string, error)
|
||||
VolumeRemove(volumeID string, force bool) error
|
||||
|
||||
ImageBuild(context io.Reader, tag string, dockerfilename string) (string, error)
|
||||
ImageRemove(image string, options ImageRemoveOptions) (bool, error)
|
||||
ImageInspectWithFormat(format string, ID string) (string, error)
|
||||
}
|
||||
|
||||
type ContainerClient struct {
|
||||
ContainerTool string
|
||||
Version string
|
||||
logger commandLogger
|
||||
logOptions logOptions
|
||||
}
|
||||
|
||||
type commandLogger interface {
|
||||
Logf(format string, args ...any)
|
||||
}
|
||||
|
||||
type logOptions struct {
|
||||
logCommands bool
|
||||
}
|
||||
|
||||
// objects
|
||||
var objVolume = "volume"
|
||||
var objImage = "image"
|
||||
var objPort = "port"
|
||||
var objNetwork = "network"
|
||||
|
||||
// verbs
|
||||
var listContainers = "ps"
|
||||
var listImages = "images"
|
||||
var create = "create"
|
||||
var startContainer = "start"
|
||||
var waitContainer = "wait"
|
||||
var execContainer = "exec"
|
||||
var getLogs = "logs"
|
||||
var stopContainer = "stop"
|
||||
var remove = "rm"
|
||||
var inspect = "inspect"
|
||||
var copyFile = "cp"
|
||||
var build = "build"
|
||||
var killContainer = "kill"
|
||||
|
||||
// args
|
||||
var argEntrypoint = "--entrypoint"
|
||||
var argUser = "--user"
|
||||
var argExpose = "--expose"
|
||||
var argVolume = "--volume"
|
||||
var argPublish = "--publish"
|
||||
var argPrivileged = "--privileged"
|
||||
var argAddCapability = "--cap-add"
|
||||
var argDropCapability = "--cap-drop"
|
||||
var argName = "--name"
|
||||
var argCondition = "--condition"
|
||||
var argEnvironmentVariable = "--env"
|
||||
var argTail = "--tail"
|
||||
var argForce = "--force"
|
||||
var argVolumes = "--volumes"
|
||||
var argHostname = "--hostname"
|
||||
var argDriver = "--driver"
|
||||
var argFile = "--file"
|
||||
var argQuiet = "--quiet"
|
||||
var argTag = "--tag"
|
||||
var argFormat = "--format"
|
||||
var argNetwork = "--network"
|
||||
var argSecurityOptions = "--security-opt"
|
||||
var argSignal = "--signal"
|
||||
var argReadOnlyRootfs = "--read-only"
|
||||
|
||||
// generic
|
||||
var toolVersion = "version"
|
||||
var ContainerStateNotRunning = "not-running"
|
||||
var ContainerStateStopped = "stopped"
|
||||
|
||||
type ContainerConfig struct {
|
||||
Image string
|
||||
Hostname string
|
||||
User string
|
||||
Entrypoint []string
|
||||
Env []string
|
||||
ExposedPorts []string
|
||||
}
|
||||
|
||||
type ContainerDetails struct {
|
||||
ID string
|
||||
Name string
|
||||
Image string
|
||||
Path string
|
||||
Args []string
|
||||
Config ContainerConfig
|
||||
HostConfig ContainerHostConfig
|
||||
}
|
||||
|
||||
type ContainerDetailsLogging struct {
|
||||
ID string
|
||||
Name string
|
||||
Image string
|
||||
Path string
|
||||
Args []string
|
||||
CapAdd []string
|
||||
CapDrop []string
|
||||
User string
|
||||
Env []string
|
||||
}
|
||||
|
||||
type ContainerHostConfig struct {
|
||||
Binds []string // Bindings onto a volume
|
||||
PortBindings []PortBinding //Bindings from a container port to a port on the host
|
||||
Privileged bool // Give extended privileges to container
|
||||
CapAdd []string // Linux capabilities to add to the container
|
||||
CapDrop []string // Linux capabilities to drop from the container
|
||||
SecurityOpt []string
|
||||
ReadOnlyRootfs bool // Readonly root file system
|
||||
}
|
||||
|
||||
type ContainerNetworkSettings struct {
|
||||
Networks []string // A list of networks to connect the container to
|
||||
}
|
||||
|
||||
type ContainerRemoveOptions struct {
|
||||
Force bool
|
||||
RemoveVolumes bool
|
||||
}
|
||||
|
||||
type ContainerStartOptions struct {
|
||||
}
|
||||
|
||||
type NetworkCreateOptions struct {
|
||||
}
|
||||
|
||||
type ContainerLogsOptions struct {
|
||||
}
|
||||
|
||||
type ImageRemoveOptions struct {
|
||||
Force bool
|
||||
}
|
||||
|
||||
type VolumeCreateOptions struct {
|
||||
Name string
|
||||
Driver string
|
||||
}
|
||||
|
||||
// Binding from a container port to a port on the host
|
||||
type PortBinding struct {
|
||||
HostIP string
|
||||
HostPort string //Port to map to on the host
|
||||
ContainerPort string //Exposed port on the container
|
||||
}
|
||||
|
||||
// NewContainerClient returns a new container client
|
||||
// Defaults to using podman
|
||||
func NewContainerClient(options ...ContainterClientOption) ContainerClient {
|
||||
tool, set := os.LookupEnv("COMMAND")
|
||||
if !set {
|
||||
tool = "podman"
|
||||
}
|
||||
client := ContainerClient{
|
||||
ContainerTool: tool,
|
||||
Version: GetContainerToolVersion(tool),
|
||||
}
|
||||
for _, option := range options {
|
||||
option(&client)
|
||||
}
|
||||
return client
|
||||
}
|
||||
|
||||
// GetContainerToolVersion returns the version of the container tool being used
|
||||
func GetContainerToolVersion(containerTool string) string {
|
||||
if containerTool == "docker" {
|
||||
args := []string{"version", "--format", "'{{.Client.Version}}'"}
|
||||
v, err := exec.Command("docker", args...).Output()
|
||||
if err != nil {
|
||||
return "0.0.0"
|
||||
}
|
||||
return string(v)
|
||||
} else if containerTool == "podman" {
|
||||
//Default to checking the version of podman
|
||||
args := []string{"version", "--format", "'{{.Version}}'"}
|
||||
v, err := exec.Command("podman", args...).Output()
|
||||
if err != nil {
|
||||
return "0.0.0"
|
||||
}
|
||||
return string(v)
|
||||
}
|
||||
return "0.0.0"
|
||||
}
|
||||
|
||||
// GetMQVersion returns the MQ version of a given container image
|
||||
func (cli ContainerClient) GetMQVersion(image string) (string, error) {
|
||||
v, err := cli.ImageInspectWithFormat("{{.Config.Labels.version}}", image)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
|
||||
// ImageInspectWithFormat inspects an image with a given formatting string
|
||||
func (cli ContainerClient) ImageInspectWithFormat(format string, ID string) (string, error) {
|
||||
args := []string{
|
||||
objImage,
|
||||
inspect,
|
||||
ID,
|
||||
}
|
||||
if format != "" {
|
||||
args = append(args, []string{argFormat, format}...)
|
||||
}
|
||||
output, err := cli.logCommand(cli.ContainerTool, args...).Output()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(output), nil
|
||||
}
|
||||
|
||||
// ContainerInspectWithFormat inspects a container with a given formatting string
|
||||
func (cli ContainerClient) ContainerInspectWithFormat(format string, ID string) (string, error) {
|
||||
args := []string{
|
||||
inspect,
|
||||
ID,
|
||||
}
|
||||
if format != "" {
|
||||
args = append(args, []string{argFormat, format}...)
|
||||
}
|
||||
output, err := cli.logCommand(cli.ContainerTool, args...).Output()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(output), nil
|
||||
}
|
||||
|
||||
// GetContainerPort gets the ports on a container
|
||||
func (cli ContainerClient) GetContainerPort(ID string, hostPort int) (string, error) {
|
||||
args := []string{
|
||||
objPort,
|
||||
ID,
|
||||
strconv.Itoa(hostPort),
|
||||
}
|
||||
output, err := cli.logCommand(cli.ContainerTool, args...).Output()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
o := SanitizeString(string(output))
|
||||
return strings.Split((o), ":")[1], nil
|
||||
}
|
||||
|
||||
// GetContainerIPAddress gets the IP address of a container
|
||||
func (cli ContainerClient) GetContainerIPAddress(ID string) (string, error) {
|
||||
v, err := cli.ContainerInspectWithFormat("{{.NetworkSettings.IPAddress}}", ID)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
|
||||
// CopyFromContainer copies a file from a container and returns its contents
|
||||
func (cli ContainerClient) CopyFromContainer(container, srcPath string) ([]byte, error) {
|
||||
tmpDir, err := os.MkdirTemp("", "tmp")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer os.RemoveAll(tmpDir)
|
||||
args := []string{
|
||||
copyFile,
|
||||
container + ":" + srcPath,
|
||||
tmpDir + "/.",
|
||||
}
|
||||
_, err = cli.logCommand(cli.ContainerTool, args...).CombinedOutput()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
//Get file name
|
||||
fname := filepath.Base(srcPath)
|
||||
data, err := os.ReadFile(pathutils.CleanPath(tmpDir, fname))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
//Remove the file
|
||||
err = os.Remove(pathutils.CleanPath(tmpDir, fname))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func (cli ContainerClient) ContainerInspect(containerID string) (ContainerDetails, error) {
|
||||
args := []string{
|
||||
inspect,
|
||||
containerID,
|
||||
}
|
||||
output, err := cli.logCommand(cli.ContainerTool, args...).Output()
|
||||
if err != nil {
|
||||
return ContainerDetails{}, err
|
||||
}
|
||||
|
||||
var container ContainerDetails
|
||||
err = json.Unmarshal(output, &container)
|
||||
if err != nil {
|
||||
return ContainerDetails{}, err
|
||||
}
|
||||
return container, err
|
||||
}
|
||||
|
||||
func (cli ContainerClient) ContainerStop(container string, timeout *time.Duration) error {
|
||||
args := []string{
|
||||
stopContainer,
|
||||
container,
|
||||
}
|
||||
_, err := cli.logCommand(cli.ContainerTool, args...).Output()
|
||||
return err
|
||||
}
|
||||
|
||||
func (cli ContainerClient) ContainerKill(container string, signal string) error {
|
||||
args := []string{
|
||||
killContainer,
|
||||
container,
|
||||
}
|
||||
if signal != "" {
|
||||
args = append(args, []string{argSignal, signal}...)
|
||||
}
|
||||
_, err := cli.logCommand(cli.ContainerTool, args...).Output()
|
||||
return err
|
||||
}
|
||||
|
||||
func (cli ContainerClient) ContainerRemove(container string, options ContainerRemoveOptions) error {
|
||||
args := []string{
|
||||
remove,
|
||||
container,
|
||||
}
|
||||
if options.Force {
|
||||
args = append(args, argForce)
|
||||
}
|
||||
if options.RemoveVolumes {
|
||||
args = append(args, argVolumes)
|
||||
}
|
||||
_, err := cli.logCommand(cli.ContainerTool, args...).Output()
|
||||
if err != nil {
|
||||
//Silently error as the exit code 125 is present on sucessful deletion
|
||||
if strings.Contains(err.Error(), "125") {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cli ContainerClient) ExecContainer(ID string, user string, cmd []string) (int, string) {
|
||||
args := []string{
|
||||
execContainer,
|
||||
}
|
||||
if user != "" {
|
||||
args = append(args, []string{argUser, user}...)
|
||||
}
|
||||
args = append(args, ID)
|
||||
args = append(args, cmd...)
|
||||
ctx := context.Background()
|
||||
output, err := cli.logCommandContext(ctx, cli.ContainerTool, args...).CombinedOutput()
|
||||
if err != nil {
|
||||
if err.(*exec.ExitError) != nil {
|
||||
return err.(*exec.ExitError).ExitCode(), string(output)
|
||||
} else {
|
||||
return 9897, string(output)
|
||||
}
|
||||
}
|
||||
return 0, string(output)
|
||||
}
|
||||
|
||||
func (cli ContainerClient) ContainerStart(container string, options ContainerStartOptions) error {
|
||||
args := []string{
|
||||
startContainer,
|
||||
container,
|
||||
}
|
||||
_, err := cli.logCommand(cli.ContainerTool, args...).Output()
|
||||
return err
|
||||
}
|
||||
|
||||
// ContainerWait starts waiting for a container. It returns an int64 channel for receiving an exit code and an error channel for receiving errors.
|
||||
// The channels returned from this function should be used to receive the results from the wait command.
|
||||
func (cli ContainerClient) ContainerWait(ctx context.Context, container string, condition string) (<-chan int64, <-chan error) {
|
||||
args := []string{
|
||||
waitContainer,
|
||||
container,
|
||||
}
|
||||
if cli.ContainerTool == "podman" {
|
||||
if condition == ContainerStateNotRunning {
|
||||
condition = ContainerStateStopped
|
||||
}
|
||||
args = append(args, []string{argCondition, string(condition)}...)
|
||||
}
|
||||
|
||||
resultC := make(chan int64)
|
||||
errC := make(chan error, 1)
|
||||
|
||||
output, err := cli.logCommandContext(ctx, cli.ContainerTool, args...).Output()
|
||||
if err != nil {
|
||||
errC <- err
|
||||
return resultC, errC
|
||||
}
|
||||
|
||||
go func() {
|
||||
out := strings.TrimSuffix(string(output), "\n")
|
||||
exitCode, err := strconv.Atoi(out)
|
||||
if err != nil {
|
||||
errC <- err
|
||||
return
|
||||
}
|
||||
resultC <- int64(exitCode)
|
||||
}()
|
||||
|
||||
return resultC, errC
|
||||
}
|
||||
|
||||
func (cli ContainerClient) GetContainerLogs(ctx context.Context, container string, options ContainerLogsOptions) (string, error) {
|
||||
args := []string{
|
||||
getLogs,
|
||||
container,
|
||||
}
|
||||
output, err := cli.logCommand(cli.ContainerTool, args...).CombinedOutput()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(output), nil
|
||||
}
|
||||
|
||||
func (cli ContainerClient) NetworkCreate(name string, options NetworkCreateOptions) (string, error) {
|
||||
args := []string{
|
||||
objNetwork,
|
||||
create,
|
||||
}
|
||||
netID, err := cli.logCommand(cli.ContainerTool, args...).CombinedOutput()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
networkID := SanitizeString(string(netID))
|
||||
|
||||
return networkID, nil
|
||||
}
|
||||
|
||||
func (cli ContainerClient) NetworkRemove(network string) error {
|
||||
args := []string{
|
||||
objNetwork,
|
||||
remove,
|
||||
}
|
||||
_, err := cli.logCommand(cli.ContainerTool, args...).CombinedOutput()
|
||||
return err
|
||||
}
|
||||
|
||||
func (cli ContainerClient) VolumeCreate(options VolumeCreateOptions) (string, error) {
|
||||
args := []string{
|
||||
objVolume,
|
||||
create,
|
||||
options.Name,
|
||||
}
|
||||
if options.Driver != "" {
|
||||
args = append(args, []string{argDriver, options.Driver}...)
|
||||
}
|
||||
output, err := cli.logCommand(cli.ContainerTool, args...).Output()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
name := SanitizeString(string(output))
|
||||
return name, nil
|
||||
}
|
||||
|
||||
func (cli ContainerClient) VolumeRemove(volumeID string, force bool) error {
|
||||
args := []string{
|
||||
objVolume,
|
||||
remove,
|
||||
volumeID,
|
||||
}
|
||||
if force {
|
||||
args = append(args, argForce)
|
||||
}
|
||||
_, err := cli.logCommand(cli.ContainerTool, args...).Output()
|
||||
return err
|
||||
}
|
||||
|
||||
func (cli ContainerClient) ImageBuild(context io.Reader, tag string, dockerfilename string) (string, error) {
|
||||
args := []string{
|
||||
objImage,
|
||||
build,
|
||||
}
|
||||
//dockerfilename includes the path to the dockerfile
|
||||
//When using podman use the full path including the name of the Dockerfile
|
||||
if cli.ContainerTool == "podman" {
|
||||
args = append(args, []string{argFile, dockerfilename}...)
|
||||
}
|
||||
if tag != "" {
|
||||
args = append(args, []string{argTag, tag}...)
|
||||
}
|
||||
args = append(args, argQuiet)
|
||||
//When using docker remove the name 'DockerFile' from the string
|
||||
if cli.ContainerTool == "docker" {
|
||||
dfn := strings.ReplaceAll(dockerfilename, "Dockerfile", "")
|
||||
args = append(args, dfn)
|
||||
}
|
||||
output, err := cli.logCommand(cli.ContainerTool, args...).Output()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
sha := SanitizeString(string(output))
|
||||
return sha, nil
|
||||
}
|
||||
|
||||
func (cli ContainerClient) ImageRemove(image string, options ImageRemoveOptions) (bool, error) {
|
||||
args := []string{
|
||||
objImage,
|
||||
remove,
|
||||
image,
|
||||
}
|
||||
if options.Force {
|
||||
args = append(args, argForce)
|
||||
}
|
||||
_, err := cli.logCommand(cli.ContainerTool, args...).Output()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (cli ContainerClient) ContainerCreate(config *ContainerConfig, hostConfig *ContainerHostConfig, networkingConfig *ContainerNetworkSettings, containerName string) (string, error) {
|
||||
args := []string{
|
||||
create,
|
||||
argName,
|
||||
containerName,
|
||||
}
|
||||
args = getHostConfigArgs(args, hostConfig)
|
||||
args = getNetworkConfigArgs(args, networkingConfig)
|
||||
args = getContainerConfigArgs(args, config, cli.ContainerTool)
|
||||
output, err := cli.logCommand(cli.ContainerTool, args...).Output()
|
||||
lines := strings.Split(strings.ReplaceAll(string(output), "\r\n", "\n"), "\n")
|
||||
if err != nil {
|
||||
return strings.Join(lines, "\n"), err
|
||||
}
|
||||
return lines[0], nil
|
||||
}
|
||||
|
||||
func (cli ContainerClient) logCommandContext(ctx context.Context, name string, arg ...string) *exec.Cmd {
|
||||
if cli.logger != nil && cli.logOptions.logCommands {
|
||||
cli.logger.Logf("Running command: %s %s", name, strings.Join(arg, " "))
|
||||
}
|
||||
return exec.CommandContext(ctx, name, arg...)
|
||||
}
|
||||
|
||||
func (cli ContainerClient) logCommand(name string, arg ...string) *exec.Cmd {
|
||||
if cli.logger != nil && cli.logOptions.logCommands {
|
||||
cli.logger.Logf("Running command: %s %s", name, strings.Join(arg, " "))
|
||||
}
|
||||
return exec.Command(name, arg...)
|
||||
}
|
||||
|
||||
// getContainerConfigArgs converts a ContainerConfig into a set of cli arguments
|
||||
func getContainerConfigArgs(args []string, config *ContainerConfig, toolName string) []string {
|
||||
argList := []string{}
|
||||
if config.Entrypoint != nil && toolName == "podman" {
|
||||
entrypoint := "[\""
|
||||
for i, commandPart := range config.Entrypoint {
|
||||
if i != len(config.Entrypoint)-1 {
|
||||
entrypoint += commandPart + "\",\""
|
||||
} else {
|
||||
//terminate list
|
||||
entrypoint += commandPart + "\"]"
|
||||
}
|
||||
}
|
||||
args = append(args, []string{argEntrypoint, entrypoint}...)
|
||||
}
|
||||
if config.Entrypoint != nil && toolName == "docker" {
|
||||
ep1 := ""
|
||||
for i, commandPart := range config.Entrypoint {
|
||||
if i == 0 {
|
||||
ep1 = commandPart
|
||||
} else {
|
||||
argList = append(argList, commandPart)
|
||||
}
|
||||
}
|
||||
args = append(args, []string{argEntrypoint, ep1}...)
|
||||
}
|
||||
if config.User != "" {
|
||||
args = append(args, []string{argUser, config.User}...)
|
||||
}
|
||||
if config.ExposedPorts != nil {
|
||||
for _, port := range config.ExposedPorts {
|
||||
args = append(args, []string{argExpose, port}...)
|
||||
}
|
||||
}
|
||||
if config.Hostname != "" {
|
||||
args = append(args, []string{argHostname, config.Hostname}...)
|
||||
}
|
||||
for _, env := range config.Env {
|
||||
args = append(args, []string{argEnvironmentVariable, env}...)
|
||||
}
|
||||
if config.Image != "" {
|
||||
args = append(args, config.Image)
|
||||
}
|
||||
if config.Entrypoint != nil && toolName == "docker" {
|
||||
args = append(args, argList...)
|
||||
}
|
||||
return args
|
||||
}
|
||||
|
||||
// getHostConfigArgs converts a ContainerHostConfig into a set of cli arguments
|
||||
func getHostConfigArgs(args []string, hostConfig *ContainerHostConfig) []string {
|
||||
if hostConfig.Binds != nil {
|
||||
for _, volume := range hostConfig.Binds {
|
||||
args = append(args, []string{argVolume, volume}...)
|
||||
}
|
||||
}
|
||||
if hostConfig.PortBindings != nil {
|
||||
for _, binding := range hostConfig.PortBindings {
|
||||
pub := binding.HostIP + ":" + binding.HostPort + ":" + binding.ContainerPort
|
||||
args = append(args, []string{argPublish, pub}...)
|
||||
}
|
||||
}
|
||||
if hostConfig.Privileged {
|
||||
args = append(args, []string{argPrivileged}...)
|
||||
}
|
||||
if hostConfig.CapAdd != nil {
|
||||
for _, capability := range hostConfig.CapAdd {
|
||||
args = append(args, []string{argAddCapability, string(capability)}...)
|
||||
}
|
||||
}
|
||||
if hostConfig.CapDrop != nil {
|
||||
for _, capability := range hostConfig.CapDrop {
|
||||
args = append(args, []string{argDropCapability, string(capability)}...)
|
||||
}
|
||||
}
|
||||
if hostConfig.SecurityOpt != nil {
|
||||
for _, securityOption := range hostConfig.SecurityOpt {
|
||||
args = append(args, []string{argSecurityOptions, string(securityOption)}...)
|
||||
}
|
||||
}
|
||||
// Add --read-only flag to enable Read Only Root File system on the container
|
||||
if hostConfig.ReadOnlyRootfs {
|
||||
args = append(args, []string{argReadOnlyRootfs}...)
|
||||
}
|
||||
|
||||
return args
|
||||
}
|
||||
|
||||
// getNetworkConfigArgs converts a set of ContainerNetworkSettings into a set of cli arguments
|
||||
func getNetworkConfigArgs(args []string, networkingConfig *ContainerNetworkSettings) []string {
|
||||
if networkingConfig.Networks != nil {
|
||||
for _, netID := range networkingConfig.Networks {
|
||||
args = append(args, []string{argNetwork, netID}...)
|
||||
}
|
||||
}
|
||||
return args
|
||||
}
|
||||
|
||||
func SanitizeString(s string) string {
|
||||
s = strings.Replace(s, " ", "", -1)
|
||||
s = strings.Replace(s, "\t", "", -1)
|
||||
s = strings.Replace(s, "\n", "", -1)
|
||||
return s
|
||||
}
|
||||
18
test/container/containerengine/containerengine_options.go
Normal file
18
test/container/containerengine/containerengine_options.go
Normal file
@@ -0,0 +1,18 @@
|
||||
package containerengine
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type ContainterClientOption func(*ContainerClient)
|
||||
|
||||
func WithTestCommandLogger(t *testing.T) ContainterClientOption {
|
||||
return func(cc *ContainerClient) {
|
||||
cc.logger = t
|
||||
cc.logOptions = logOptions{
|
||||
logCommands: strings.ToLower(os.Getenv("TEST_LOG_CONTAINER_COMMANDS")) == "true",
|
||||
}
|
||||
}
|
||||
}
|
||||
911
test/container/devconfig_test.go
Normal file
911
test/container/devconfig_test.go
Normal file
@@ -0,0 +1,911 @@
|
||||
//go:build mqdev
|
||||
// +build mqdev
|
||||
|
||||
/*
|
||||
© Copyright IBM Corporation 2018, 2023
|
||||
|
||||
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.
|
||||
*/
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
ce "github.com/ibm-messaging/mq-container/test/container/containerengine"
|
||||
)
|
||||
|
||||
// TestDevGoldenPath tests using the default values for the default developer config.
|
||||
// Note: This test requires a separate container image to be available for the JMS tests.
|
||||
func TestDevGoldenPath(t *testing.T) {
|
||||
t.Parallel()
|
||||
cli := ce.NewContainerClient()
|
||||
qm := "qm1"
|
||||
containerConfig := ce.ContainerConfig{
|
||||
Env: []string{
|
||||
"LICENSE=accept",
|
||||
"MQ_QMGR_NAME=" + qm,
|
||||
"DEBUG=true",
|
||||
"MQ_CONNAUTH_USE_HTP=true",
|
||||
"MQ_APP_PASSWORD=" + defaultAppPasswordWeb,
|
||||
"MQ_ADMIN_PASSWORD=" + defaultAdminPassword,
|
||||
},
|
||||
}
|
||||
id := runContainerWithPorts(t, cli, &containerConfig, []int{9443, 1414})
|
||||
defer cleanContainer(t, cli, id)
|
||||
waitForReady(t, cli, id)
|
||||
waitForWebReady(t, cli, id, insecureTLSConfig)
|
||||
t.Run("JMS", func(t *testing.T) {
|
||||
// Run the JMS tests, with no password specified.
|
||||
// Use OpenJDK JRE for running testing, pass false for 7th parameter.
|
||||
// Last parameter is blank as the test doesn't use TLS.
|
||||
runJMSTests(t, cli, id, false, "app", defaultAppPasswordOS, "false", "")
|
||||
})
|
||||
t.Run("REST admin", func(t *testing.T) {
|
||||
testRESTAdmin(t, cli, id, insecureTLSConfig, "")
|
||||
})
|
||||
t.Run("REST messaging", func(t *testing.T) {
|
||||
testRESTMessaging(t, cli, id, insecureTLSConfig, qm, "app", defaultAppPasswordWeb, "")
|
||||
})
|
||||
// Stop the container cleanly
|
||||
stopContainer(t, cli, id)
|
||||
}
|
||||
|
||||
// TestDevSecure tests the default developer config using the a custom TLS key store and password.
|
||||
// Note: This test requires a separate container image to be available for the JMS tests
|
||||
func TestDevSecure(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cli := ce.NewContainerClient()
|
||||
|
||||
const tlsPassPhrase string = "passw0rd"
|
||||
qm := "qm1"
|
||||
appPassword := "differentPassw0rd"
|
||||
containerConfig := ce.ContainerConfig{
|
||||
Env: []string{
|
||||
"LICENSE=accept",
|
||||
"MQ_QMGR_NAME=" + qm,
|
||||
"MQ_CONNAUTH_USE_HTP=true",
|
||||
"MQ_APP_PASSWORD=" + appPassword,
|
||||
"MQ_ADMIN_PASSWORD=" + defaultAdminPassword,
|
||||
"DEBUG=1",
|
||||
"WLP_LOGGING_MESSAGE_FORMAT=JSON",
|
||||
"MQ_ENABLE_EMBEDDED_WEB_SERVER_LOG=true",
|
||||
},
|
||||
Image: imageName(),
|
||||
}
|
||||
hostConfig := ce.ContainerHostConfig{
|
||||
Binds: []string{
|
||||
tlsDir(t, false) + ":/etc/mqm/pki/keys/default",
|
||||
},
|
||||
}
|
||||
addCoverageBindIfAvailable(t, &hostConfig)
|
||||
// Assign a random port for the web server on the host
|
||||
// TODO: Don't do this for all tests
|
||||
var binding ce.PortBinding
|
||||
ports := []int{9443, 1414}
|
||||
for _, p := range ports {
|
||||
port := fmt.Sprintf("%v/tcp", p)
|
||||
binding = ce.PortBinding{
|
||||
ContainerPort: port,
|
||||
HostIP: "0.0.0.0",
|
||||
}
|
||||
hostConfig.PortBindings = append(hostConfig.PortBindings, binding)
|
||||
}
|
||||
networkingConfig := ce.ContainerNetworkSettings{}
|
||||
ID, err := cli.ContainerCreate(&containerConfig, &hostConfig, &networkingConfig, t.Name())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer cleanContainer(t, cli, ID)
|
||||
startContainer(t, cli, ID)
|
||||
waitForReady(t, cli, ID)
|
||||
cert := filepath.Join(tlsDir(t, true), "server.crt")
|
||||
waitForWebReady(t, cli, ID, createTLSConfig(t, cert, tlsPassPhrase))
|
||||
|
||||
t.Run("JMS", func(t *testing.T) {
|
||||
// OpenJDK is used for running tests, hence pass "false" for 7th parameter.
|
||||
// Cipher name specified is compliant with non-IBM JRE naming.
|
||||
runJMSTests(t, cli, ID, true, "app", appPassword, "false", "*TLS12ORHIGHER")
|
||||
})
|
||||
t.Run("REST admin", func(t *testing.T) {
|
||||
testRESTAdmin(t, cli, ID, insecureTLSConfig, "")
|
||||
})
|
||||
t.Run("REST messaging", func(t *testing.T) {
|
||||
testRESTMessaging(t, cli, ID, insecureTLSConfig, qm, "app", appPassword, "")
|
||||
})
|
||||
|
||||
// Stop the container cleanly
|
||||
stopContainer(t, cli, ID)
|
||||
}
|
||||
|
||||
func TestDevWebDisabled(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cli := ce.NewContainerClient()
|
||||
containerConfig := ce.ContainerConfig{
|
||||
Env: []string{
|
||||
"LICENSE=accept",
|
||||
"MQ_QMGR_NAME=qm1",
|
||||
"MQ_ENABLE_EMBEDDED_WEB_SERVER=false",
|
||||
"MQ_APP_PASSWORD=" + defaultAppPasswordOS,
|
||||
},
|
||||
}
|
||||
id := runContainerWithPorts(t, cli, &containerConfig, []int{1414})
|
||||
defer cleanContainer(t, cli, id)
|
||||
waitForReady(t, cli, id)
|
||||
t.Run("Web", func(t *testing.T) {
|
||||
_, dspmqweb := cli.ExecContainer(id, "", []string{"dspmqweb"})
|
||||
if !strings.Contains(dspmqweb, "Server mqweb is not running.") && !strings.Contains(dspmqweb, "MQWB1125I") {
|
||||
t.Errorf("Expected dspmqweb to say 'Server is not running' or 'MQWB1125I'; got \"%v\"", dspmqweb)
|
||||
}
|
||||
})
|
||||
t.Run("JMS", func(t *testing.T) {
|
||||
// Run the JMS tests, with no password specified
|
||||
// OpenJDK is used for running tests, hence pass "false" for 7th parameter.
|
||||
// Last parameter is blank as the test doesn't use TLS.
|
||||
runJMSTests(t, cli, id, false, "app", defaultAppPasswordOS, "false", "")
|
||||
})
|
||||
// Stop the container cleanly
|
||||
stopContainer(t, cli, id)
|
||||
}
|
||||
|
||||
func TestDevConfigDisabled(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cli := ce.NewContainerClient()
|
||||
containerConfig := ce.ContainerConfig{
|
||||
Env: []string{
|
||||
"LICENSE=accept",
|
||||
"MQ_QMGR_NAME=qm1",
|
||||
"MQ_DEV=false",
|
||||
},
|
||||
}
|
||||
id := runContainerWithPorts(t, cli, &containerConfig, []int{9443})
|
||||
defer cleanContainer(t, cli, id)
|
||||
waitForReady(t, cli, id)
|
||||
rc, _ := execContainer(t, cli, id, "", []string{"bash", "-c", "echo 'display qlocal(DEV*)' | runmqsc"})
|
||||
if rc == 0 {
|
||||
t.Errorf("Expected DEV queues to be missing")
|
||||
}
|
||||
// Stop the container cleanly
|
||||
stopContainer(t, cli, id)
|
||||
}
|
||||
|
||||
// Test if SSLKEYR and CERTLABL attributes are not set when key and certificate
|
||||
// are not supplied.
|
||||
func TestSSLKEYRBlank(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cli := ce.NewContainerClient()
|
||||
containerConfig := ce.ContainerConfig{
|
||||
Env: []string{
|
||||
"LICENSE=accept",
|
||||
"MQ_QMGR_NAME=QM1",
|
||||
"MQ_ENABLE_EMBEDDED_WEB_SERVER=false",
|
||||
},
|
||||
}
|
||||
id := runContainerWithPorts(t, cli, &containerConfig, []int{9443})
|
||||
defer cleanContainer(t, cli, id)
|
||||
waitForReady(t, cli, id)
|
||||
|
||||
// execute runmqsc to display qmgr SSLKEYR and CERTLABL attibutes.
|
||||
// Search the console output for exepcted values
|
||||
_, sslkeyROutput := execContainer(t, cli, id, "", []string{"bash", "-c", "echo 'DISPLAY QMGR SSLKEYR CERTLABL' | runmqsc"})
|
||||
if !strings.Contains(sslkeyROutput, "SSLKEYR( )") || !strings.Contains(sslkeyROutput, "CERTLABL( )") {
|
||||
// Although queue manager is ready, it may be that MQSC scripts have not been applied yet.
|
||||
// Hence wait for a second and retry few times before giving up.
|
||||
waitCount := 30
|
||||
var i int
|
||||
for i = 0; i < waitCount; i++ {
|
||||
time.Sleep(1 * time.Second)
|
||||
_, sslkeyROutput = execContainer(t, cli, id, "", []string{"bash", "-c", "echo 'DISPLAY QMGR SSLKEYR CERTLABL' | runmqsc"})
|
||||
if strings.Contains(sslkeyROutput, "SSLKEYR( )") && strings.Contains(sslkeyROutput, "CERTLABL( )") {
|
||||
break
|
||||
}
|
||||
}
|
||||
// Failed to get expected output? dump the contents of mqsc files.
|
||||
if i == waitCount {
|
||||
_, tls15mqsc := execContainer(t, cli, id, "", []string{"cat", "/etc/mqm/15-tls.mqsc"})
|
||||
_, autoMQSC := execContainer(t, cli, id, "", []string{"cat", "/mnt/mqm/data/qmgrs/QM1/autocfg/cached.mqsc"})
|
||||
t.Errorf("Expected SSLKEYR to be blank but it is not; got \"%v\"\n AutoConfig MQSC file contents %v\n 15-tls: %v", sslkeyROutput, autoMQSC, tls15mqsc)
|
||||
}
|
||||
}
|
||||
|
||||
// Stop the container cleanly
|
||||
stopContainer(t, cli, id)
|
||||
}
|
||||
|
||||
// Test if SSLKEYR and CERTLABL attributes are set when key and certificate
|
||||
// are supplied.
|
||||
func TestSSLKEYRWithSuppliedKeyAndCert(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cli := ce.NewContainerClient()
|
||||
|
||||
containerConfig := ce.ContainerConfig{
|
||||
Env: []string{
|
||||
"LICENSE=accept",
|
||||
"MQ_QMGR_NAME=QM1",
|
||||
"MQ_ENABLE_EMBEDDED_WEB_SERVER=false",
|
||||
},
|
||||
Image: imageName(),
|
||||
}
|
||||
hostConfig := ce.ContainerHostConfig{
|
||||
Binds: []string{
|
||||
tlsDir(t, false) + ":/etc/mqm/pki/keys/default",
|
||||
},
|
||||
}
|
||||
addCoverageBindIfAvailable(t, &hostConfig)
|
||||
networkingConfig := ce.ContainerNetworkSettings{}
|
||||
ID, err := cli.ContainerCreate(&containerConfig, &hostConfig, &networkingConfig, t.Name())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer cleanContainer(t, cli, ID)
|
||||
startContainer(t, cli, ID)
|
||||
waitForReady(t, cli, ID)
|
||||
|
||||
// execute runmqsc to display qmgr SSLKEYR and CERTLABL attibutes.
|
||||
// Search the console output for exepcted values
|
||||
_, sslkeyROutput := execContainer(t, cli, ID, "", []string{"bash", "-c", "echo 'DISPLAY QMGR SSLKEYR CERTLABL' | runmqsc"})
|
||||
if !strings.Contains(sslkeyROutput, "SSLKEYR(/run/runmqserver/tls/key)") || !strings.Contains(sslkeyROutput, "CERTLABL(default)") {
|
||||
// Although queue manager is ready, it may be that MQSC scripts have not been applied yet.
|
||||
// Hence wait for a second and retry few times before giving up.
|
||||
waitCount := 30
|
||||
var i int
|
||||
for i = 0; i < waitCount; i++ {
|
||||
time.Sleep(1 * time.Second)
|
||||
_, sslkeyROutput = execContainer(t, cli, ID, "", []string{"bash", "-c", "echo 'DISPLAY QMGR SSLKEYR CERTLABL' | runmqsc"})
|
||||
if strings.Contains(sslkeyROutput, "SSLKEYR(/run/runmqserver/tls/key)") && strings.Contains(sslkeyROutput, "CERTLABL(default)") {
|
||||
break
|
||||
}
|
||||
}
|
||||
// Failed to get expected output? dump the contents of mqsc files.
|
||||
if i == waitCount {
|
||||
_, tls15mqsc := execContainer(t, cli, ID, "", []string{"cat", "/etc/mqm/15-tls.mqsc"})
|
||||
_, autoMQSC := execContainer(t, cli, ID, "", []string{"cat", "/mnt/mqm/data/qmgrs/QM1/autocfg/cached.mqsc"})
|
||||
t.Errorf("Expected SSLKEYR to be '/run/runmqserver/tls/key' but it is not; got \"%v\" \n AutoConfig MQSC file contents %v\n 15-tls: %v", sslkeyROutput, autoMQSC, tls15mqsc)
|
||||
}
|
||||
}
|
||||
|
||||
// Stop the container cleanly
|
||||
stopContainer(t, cli, ID)
|
||||
}
|
||||
|
||||
// Test with CA cert
|
||||
func TestSSLKEYRWithCACert(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cli := ce.NewContainerClient()
|
||||
|
||||
containerConfig := ce.ContainerConfig{
|
||||
Env: []string{
|
||||
"LICENSE=accept",
|
||||
"MQ_QMGR_NAME=QM1",
|
||||
"MQ_ENABLE_EMBEDDED_WEB_SERVER=false",
|
||||
},
|
||||
Image: imageName(),
|
||||
}
|
||||
hostConfig := ce.ContainerHostConfig{
|
||||
Binds: []string{
|
||||
tlsDirWithCA(t, false) + ":/etc/mqm/pki/keys/QM1CA",
|
||||
},
|
||||
}
|
||||
addCoverageBindIfAvailable(t, &hostConfig)
|
||||
// Assign a random port for the web server on the host
|
||||
var binding ce.PortBinding
|
||||
ports := []int{9443}
|
||||
for _, p := range ports {
|
||||
port := fmt.Sprintf("%v/tcp", p)
|
||||
binding = ce.PortBinding{
|
||||
ContainerPort: port,
|
||||
HostIP: "0.0.0.0",
|
||||
}
|
||||
hostConfig.PortBindings = append(hostConfig.PortBindings, binding)
|
||||
}
|
||||
networkingConfig := ce.ContainerNetworkSettings{}
|
||||
ID, err := cli.ContainerCreate(&containerConfig, &hostConfig, &networkingConfig, t.Name())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer cleanContainer(t, cli, ID)
|
||||
startContainer(t, cli, ID)
|
||||
waitForReady(t, cli, ID)
|
||||
|
||||
// execute runmqsc to display qmgr SSLKEYR and CERTLABL attibutes.
|
||||
// Search the console output for exepcted values
|
||||
_, sslkeyROutput := execContainer(t, cli, ID, "", []string{"bash", "-c", "echo 'DISPLAY QMGR SSLKEYR CERTLABL' | runmqsc"})
|
||||
|
||||
if !strings.Contains(sslkeyROutput, "SSLKEYR(/run/runmqserver/tls/key)") {
|
||||
// Although queue manager is ready, it may be that MQSC scripts have not been applied yet.
|
||||
// Hence wait for a second and retry few times before giving up.
|
||||
waitCount := 30
|
||||
var i int
|
||||
for i = 0; i < waitCount; i++ {
|
||||
time.Sleep(1 * time.Second)
|
||||
_, sslkeyROutput = execContainer(t, cli, ID, "", []string{"bash", "-c", "echo 'DISPLAY QMGR SSLKEYR CERTLABL' | runmqsc"})
|
||||
if strings.Contains(sslkeyROutput, "SSLKEYR(/run/runmqserver/tls/key)") {
|
||||
break
|
||||
}
|
||||
}
|
||||
// Failed to get expected output? dump the contents of mqsc files.
|
||||
if i == waitCount {
|
||||
_, tls15mqsc := execContainer(t, cli, ID, "", []string{"cat", "/etc/mqm/15-tls.mqsc"})
|
||||
_, autoMQSC := execContainer(t, cli, ID, "", []string{"cat", "/mnt/mqm/data/qmgrs/QM1/autocfg/cached.mqsc"})
|
||||
t.Errorf("Expected SSLKEYR to be '/run/runmqserver/tls/key' but it is not; got \"%v\"\n AutoConfig MQSC file contents %v\n 15-tls: %v", sslkeyROutput, autoMQSC, tls15mqsc)
|
||||
}
|
||||
}
|
||||
|
||||
if !strings.Contains(sslkeyROutput, "CERTLABL(QM1CA)") {
|
||||
_, autoMQSC := execContainer(t, cli, ID, "", []string{"cat", "/etc/mqm/15-tls.mqsc"})
|
||||
t.Errorf("Expected CERTLABL to be 'QM1CA' but it is not; got \"%v\" \n MQSC File contents %v", sslkeyROutput, autoMQSC)
|
||||
}
|
||||
|
||||
// Stop the container cleanly
|
||||
stopContainer(t, cli, ID)
|
||||
}
|
||||
|
||||
// Verifies SSLFIPS is set to NO if MQ_ENABLE_FIPS=false
|
||||
func TestSSLFIPSNO(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cli := ce.NewContainerClient()
|
||||
|
||||
containerConfig := ce.ContainerConfig{
|
||||
Env: []string{
|
||||
"LICENSE=accept",
|
||||
"MQ_QMGR_NAME=QM1",
|
||||
"MQ_ENABLE_EMBEDDED_WEB_SERVER=false",
|
||||
"MQ_ENABLE_FIPS=false",
|
||||
},
|
||||
Image: imageName(),
|
||||
}
|
||||
hostConfig := ce.ContainerHostConfig{
|
||||
Binds: []string{
|
||||
tlsDir(t, false) + ":/etc/mqm/pki/keys/default",
|
||||
},
|
||||
}
|
||||
addCoverageBindIfAvailable(t, &hostConfig)
|
||||
networkingConfig := ce.ContainerNetworkSettings{}
|
||||
ID, err := cli.ContainerCreate(&containerConfig, &hostConfig, &networkingConfig, t.Name())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer cleanContainer(t, cli, ID)
|
||||
startContainer(t, cli, ID)
|
||||
waitForReady(t, cli, ID)
|
||||
|
||||
// execute runmqsc to display qmgr SSLKEYR, SSLFIPS and CERTLABL attibutes.
|
||||
// Search the console output for exepcted values
|
||||
_, sslFIPSOutput := execContainer(t, cli, ID, "", []string{"bash", "-c", "echo 'DISPLAY QMGR SSLKEYR CERTLABL SSLFIPS' | runmqsc"})
|
||||
if !strings.Contains(sslFIPSOutput, "SSLKEYR(/run/runmqserver/tls/key)") {
|
||||
t.Errorf("Expected SSLKEYR to be '/run/runmqserver/tls/key' but it is not; got \"%v\"", sslFIPSOutput)
|
||||
}
|
||||
if !strings.Contains(sslFIPSOutput, "CERTLABL(default)") {
|
||||
t.Errorf("Expected CERTLABL to be 'default' but it is not; got \"%v\"", sslFIPSOutput)
|
||||
}
|
||||
|
||||
if !strings.Contains(sslFIPSOutput, "SSLFIPS(NO)") {
|
||||
t.Errorf("Expected SSLFIPS to be 'NO' but it is not; got \"%v\"", sslFIPSOutput)
|
||||
}
|
||||
|
||||
// Stop the container cleanly
|
||||
stopContainer(t, cli, ID)
|
||||
}
|
||||
|
||||
// Verifies SSLFIPS is set to YES if certificates for queue manager
|
||||
// are supplied and MQ_ENABLE_FIPS=true
|
||||
func TestSSLFIPSYES(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cli := ce.NewContainerClient()
|
||||
|
||||
appPassword := "differentPassw0rd"
|
||||
containerConfig := ce.ContainerConfig{
|
||||
Env: []string{
|
||||
"LICENSE=accept",
|
||||
"MQ_APP_PASSWORD=" + appPassword,
|
||||
"MQ_QMGR_NAME=QM1",
|
||||
"MQ_ENABLE_EMBEDDED_WEB_SERVER=false",
|
||||
"MQ_ENABLE_FIPS=true",
|
||||
},
|
||||
Image: imageName(),
|
||||
}
|
||||
hostConfig := ce.ContainerHostConfig{
|
||||
Binds: []string{
|
||||
tlsDir(t, false) + ":/etc/mqm/pki/keys/default",
|
||||
},
|
||||
}
|
||||
addCoverageBindIfAvailable(t, &hostConfig)
|
||||
var binding ce.PortBinding
|
||||
ports := []int{1414}
|
||||
for _, p := range ports {
|
||||
port := fmt.Sprintf("%v/tcp", p)
|
||||
binding = ce.PortBinding{
|
||||
ContainerPort: port,
|
||||
HostIP: "0.0.0.0",
|
||||
}
|
||||
hostConfig.PortBindings = append(hostConfig.PortBindings, binding)
|
||||
}
|
||||
networkingConfig := ce.ContainerNetworkSettings{}
|
||||
ID, err := cli.ContainerCreate(&containerConfig, &hostConfig, &networkingConfig, t.Name())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer cleanContainer(t, cli, ID)
|
||||
startContainer(t, cli, ID)
|
||||
waitForReady(t, cli, ID)
|
||||
|
||||
// Check for expected message on container log
|
||||
logs := inspectLogs(t, cli, ID)
|
||||
if !strings.Contains(logs, "FIPS cryptography is enabled.") {
|
||||
t.Errorf("Expected 'FIPS cryptography is enabled.' but got %v\n", logs)
|
||||
}
|
||||
|
||||
// execute runmqsc to display qmgr SSLKEYR, SSLFIPS and CERTLABL attibutes.
|
||||
// Search the console output for exepcted values
|
||||
_, sslFIPSOutput := execContainer(t, cli, ID, "", []string{"bash", "-c", "echo 'DISPLAY QMGR SSLKEYR CERTLABL SSLFIPS' | runmqsc"})
|
||||
if !strings.Contains(sslFIPSOutput, "SSLKEYR(/run/runmqserver/tls/key)") {
|
||||
t.Errorf("Expected SSLKEYR to be '/run/runmqserver/tls/key' but it is not; got \"%v\"", sslFIPSOutput)
|
||||
}
|
||||
if !strings.Contains(sslFIPSOutput, "CERTLABL(default)") {
|
||||
t.Errorf("Expected CERTLABL to be 'default' but it is not; got \"%v\"", sslFIPSOutput)
|
||||
}
|
||||
|
||||
if !strings.Contains(sslFIPSOutput, "SSLFIPS(YES)") {
|
||||
t.Errorf("Expected SSLFIPS to be 'YES' but it is not; got \"%v\"", sslFIPSOutput)
|
||||
}
|
||||
|
||||
t.Run("JMS", func(t *testing.T) {
|
||||
// Run the JMS tests, with no password specified
|
||||
runJMSTests(t, cli, ID, true, "app", appPassword, "false", "*TLS12ORHIGHER")
|
||||
})
|
||||
|
||||
// Stop the container cleanly
|
||||
stopContainer(t, cli, ID)
|
||||
}
|
||||
|
||||
// TestDevSecureFIPSYESWeb verifies if the MQ Web Server is running in FIPS mode
|
||||
func TestDevSecureFIPSTrueWeb(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cli := ce.NewContainerClient()
|
||||
|
||||
const tlsPassPhrase string = "passw0rd"
|
||||
qm := "qm1"
|
||||
appPassword := "differentPassw0rd"
|
||||
containerConfig := ce.ContainerConfig{
|
||||
Env: []string{
|
||||
"LICENSE=accept",
|
||||
"MQ_QMGR_NAME=" + qm,
|
||||
"MQ_CONNAUTH_USE_HTP=true",
|
||||
"MQ_APP_PASSWORD=" + appPassword,
|
||||
"MQ_ADMIN_PASSWORD=" + defaultAdminPassword,
|
||||
"DEBUG=1",
|
||||
"WLP_LOGGING_MESSAGE_FORMAT=JSON",
|
||||
"MQ_ENABLE_EMBEDDED_WEB_SERVER_LOG=true",
|
||||
"MQ_ENABLE_FIPS=true",
|
||||
},
|
||||
Image: imageName(),
|
||||
}
|
||||
hostConfig := ce.ContainerHostConfig{
|
||||
Binds: []string{
|
||||
tlsDir(t, false) + ":/etc/mqm/pki/keys/default",
|
||||
tlsDir(t, false) + ":/etc/mqm/pki/trust/default",
|
||||
},
|
||||
}
|
||||
addCoverageBindIfAvailable(t, &hostConfig)
|
||||
// Assign a random port for the web server on the host
|
||||
// TODO: Don't do this for all tests
|
||||
var binding ce.PortBinding
|
||||
ports := []int{9443}
|
||||
for _, p := range ports {
|
||||
port := fmt.Sprintf("%v/tcp", p)
|
||||
binding = ce.PortBinding{
|
||||
ContainerPort: port,
|
||||
HostIP: "0.0.0.0",
|
||||
}
|
||||
hostConfig.PortBindings = append(hostConfig.PortBindings, binding)
|
||||
}
|
||||
networkingConfig := ce.ContainerNetworkSettings{}
|
||||
ID, err := cli.ContainerCreate(&containerConfig, &hostConfig, &networkingConfig, t.Name())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer cleanContainer(t, cli, ID)
|
||||
|
||||
startContainer(t, cli, ID)
|
||||
waitForReady(t, cli, ID)
|
||||
cert := filepath.Join(tlsDir(t, true), "server.crt")
|
||||
waitForWebReady(t, cli, ID, createTLSConfig(t, cert, tlsPassPhrase))
|
||||
|
||||
// Create a TLS Config with a cipher to use when connecting over HTTPS
|
||||
var secureTLSConfig *tls.Config = createTLSConfig(t, cert, tlsPassPhrase, withMinTLSVersion(tls.VersionTLS12))
|
||||
// Put a message to queue
|
||||
t.Run("REST messaging", func(t *testing.T) {
|
||||
testRESTMessaging(t, cli, ID, secureTLSConfig, qm, "app", appPassword, "")
|
||||
})
|
||||
|
||||
// Create a TLS Config with a non-FIPS cipher to use when connecting over HTTPS
|
||||
var secureNonFIPSCipherConfig *tls.Config = createTLSConfig(t, cert, tlsPassPhrase, withMinTLSVersion(tls.VersionTLS12))
|
||||
// Put a message to queue - the attempt to put message will fail with a EOF return message.
|
||||
t.Run("REST messaging", func(t *testing.T) {
|
||||
testRESTMessaging(t, cli, ID, secureNonFIPSCipherConfig, qm, "app", appPassword, "EOF")
|
||||
})
|
||||
|
||||
// Stop the container cleanly
|
||||
stopContainer(t, cli, ID)
|
||||
}
|
||||
|
||||
// TestDevSecureNOFIPSWeb verifies if the MQ Web Server is not running in FIPS mode
|
||||
func TestDevSecureFalseFIPSWeb(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cli := ce.NewContainerClient()
|
||||
|
||||
const tlsPassPhrase string = "passw0rd"
|
||||
qm := "qm1"
|
||||
appPassword := "differentPassw0rd"
|
||||
containerConfig := ce.ContainerConfig{
|
||||
Env: []string{
|
||||
"LICENSE=accept",
|
||||
"MQ_QMGR_NAME=" + qm,
|
||||
"MQ_CONNAUTH_USE_HTP=true",
|
||||
"MQ_APP_PASSWORD=" + appPassword,
|
||||
"MQ_ADMIN_PASSWORD=" + defaultAdminPassword,
|
||||
"DEBUG=1",
|
||||
"WLP_LOGGING_MESSAGE_FORMAT=JSON",
|
||||
"MQ_ENABLE_EMBEDDED_WEB_SERVER_LOG=true",
|
||||
"MQ_ENABLE_FIPS=false",
|
||||
},
|
||||
Image: imageName(),
|
||||
}
|
||||
hostConfig := ce.ContainerHostConfig{
|
||||
Binds: []string{
|
||||
tlsDir(t, false) + ":/etc/mqm/pki/keys/default",
|
||||
tlsDir(t, false) + ":/etc/mqm/pki/trust/default",
|
||||
},
|
||||
}
|
||||
addCoverageBindIfAvailable(t, &hostConfig)
|
||||
// Assign a random port for the web server on the host
|
||||
var binding ce.PortBinding
|
||||
ports := []int{9443}
|
||||
for _, p := range ports {
|
||||
port := fmt.Sprintf("%v/tcp", p)
|
||||
binding = ce.PortBinding{
|
||||
ContainerPort: port,
|
||||
HostIP: "0.0.0.0",
|
||||
}
|
||||
hostConfig.PortBindings = append(hostConfig.PortBindings, binding)
|
||||
}
|
||||
networkingConfig := ce.ContainerNetworkSettings{}
|
||||
ID, err := cli.ContainerCreate(&containerConfig, &hostConfig, &networkingConfig, t.Name())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer cleanContainer(t, cli, ID)
|
||||
startContainer(t, cli, ID)
|
||||
waitForReady(t, cli, ID)
|
||||
|
||||
cert := filepath.Join(tlsDir(t, true), "server.crt")
|
||||
waitForWebReady(t, cli, ID, createTLSConfig(t, cert, tlsPassPhrase))
|
||||
|
||||
// As FIPS is not enabled, the MQ WebServer (actually Java) will choose a JSSE provider from the list
|
||||
// specified in java.security file. We will need to enable java.net.debug and then parse the web server
|
||||
// logs to check what JJSE provider is being used. Hence just check the jvm.options file does not contain
|
||||
// -Dcom.ibm.jsse2.usefipsprovider line.
|
||||
_, jvmOptionsOutput := execContainer(t, cli, ID, "", []string{"bash", "-c", "cat /var/mqm/web/installations/Installation1/servers/mqweb/configDropins/defaults/jvm.options"})
|
||||
if strings.Contains(jvmOptionsOutput, "-Dcom.ibm.jsse2.usefipsprovider") {
|
||||
t.Errorf("Did not expect -Dcom.ibm.jsse2.usefipsprovider but it is not; got \"%v\"", jvmOptionsOutput)
|
||||
}
|
||||
|
||||
// Just do a HTTPS GET as well to query installation details.
|
||||
var secureTLSConfig *tls.Config = createTLSConfig(t, cert, tlsPassPhrase, withMinTLSVersion(tls.VersionTLS12))
|
||||
t.Run("REST admin", func(t *testing.T) {
|
||||
testRESTAdmin(t, cli, ID, secureTLSConfig, "")
|
||||
})
|
||||
|
||||
// Stop the container cleanly
|
||||
stopContainer(t, cli, ID)
|
||||
}
|
||||
|
||||
// Verify SSLFIPS is set to NO if no certificates were supplied
|
||||
func TestSSLFIPSTrueNoCerts(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cli := ce.NewContainerClient()
|
||||
|
||||
appPassword := "differentPassw0rd"
|
||||
containerConfig := ce.ContainerConfig{
|
||||
Env: []string{
|
||||
"LICENSE=accept",
|
||||
"MQ_CONNAUTH_USE_HTP=true",
|
||||
"MQ_APP_PASSWORD=" + appPassword,
|
||||
"MQ_QMGR_NAME=QM1",
|
||||
"MQ_ENABLE_EMBEDDED_WEB_SERVER=false",
|
||||
"MQ_ENABLE_FIPS=true",
|
||||
},
|
||||
Image: imageName(),
|
||||
}
|
||||
hostConfig := ce.ContainerHostConfig{}
|
||||
addCoverageBindIfAvailable(t, &hostConfig)
|
||||
networkingConfig := ce.ContainerNetworkSettings{}
|
||||
ID, err := cli.ContainerCreate(&containerConfig, &hostConfig, &networkingConfig, t.Name())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer cleanContainer(t, cli, ID)
|
||||
startContainer(t, cli, ID)
|
||||
waitForReady(t, cli, ID)
|
||||
|
||||
// execute runmqsc to display qmgr SSLKEYR, SSLFIPS and CERTLABL attibutes.
|
||||
// Search the console output for exepcted values
|
||||
_, sslFIPSOutput := execContainer(t, cli, ID, "", []string{"bash", "-c", "echo 'DISPLAY QMGR SSLKEYR CERTLABL SSLFIPS' | runmqsc"})
|
||||
if !strings.Contains(sslFIPSOutput, "SSLKEYR( )") {
|
||||
t.Errorf("Expected SSLKEYR to be ' ' but it is not; got \"%v\"", sslFIPSOutput)
|
||||
}
|
||||
if !strings.Contains(sslFIPSOutput, "CERTLABL( )") {
|
||||
t.Errorf("Expected CERTLABL to be blank but it is not; got \"%v\"", sslFIPSOutput)
|
||||
}
|
||||
|
||||
if !strings.Contains(sslFIPSOutput, "SSLFIPS(NO)") {
|
||||
t.Errorf("Expected SSLFIPS to be 'NO' but it is not; got \"%v\"", sslFIPSOutput)
|
||||
}
|
||||
|
||||
// Stop the container cleanly
|
||||
stopContainer(t, cli, ID)
|
||||
}
|
||||
|
||||
// Verifies SSLFIPS is set to NO if MQ_ENABLE_FIPS=tru (invalid value)
|
||||
func TestSSLFIPSInvalidValue(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cli := ce.NewContainerClient()
|
||||
|
||||
containerConfig := ce.ContainerConfig{
|
||||
Env: []string{
|
||||
"LICENSE=accept",
|
||||
"MQ_QMGR_NAME=QM1",
|
||||
"MQ_ENABLE_EMBEDDED_WEB_SERVER=false",
|
||||
"MQ_ENABLE_FIPS=tru",
|
||||
},
|
||||
Image: imageName(),
|
||||
}
|
||||
hostConfig := ce.ContainerHostConfig{
|
||||
Binds: []string{
|
||||
tlsDir(t, false) + ":/etc/mqm/pki/keys/default",
|
||||
},
|
||||
}
|
||||
addCoverageBindIfAvailable(t, &hostConfig)
|
||||
networkingConfig := ce.ContainerNetworkSettings{}
|
||||
ID, err := cli.ContainerCreate(&containerConfig, &hostConfig, &networkingConfig, t.Name())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer cleanContainer(t, cli, ID)
|
||||
startContainer(t, cli, ID)
|
||||
waitForReady(t, cli, ID)
|
||||
|
||||
// execute runmqsc to display qmgr SSLKEYR, SSLFIPS and CERTLABL attibutes.
|
||||
// Search the console output for exepcted values
|
||||
_, sslFIPSOutput := execContainer(t, cli, ID, "", []string{"bash", "-c", "echo 'DISPLAY QMGR SSLKEYR CERTLABL SSLFIPS' | runmqsc"})
|
||||
if !strings.Contains(sslFIPSOutput, "SSLKEYR(/run/runmqserver/tls/key)") {
|
||||
t.Errorf("Expected SSLKEYR to be '/run/runmqserver/tls/key' but it is not; got \"%v\"", sslFIPSOutput)
|
||||
}
|
||||
|
||||
if !strings.Contains(sslFIPSOutput, "CERTLABL(default)") {
|
||||
t.Errorf("Expected CERTLABL to be 'default' but it is not; got \"%v\"", sslFIPSOutput)
|
||||
}
|
||||
|
||||
if !strings.Contains(sslFIPSOutput, "SSLFIPS(NO)") {
|
||||
t.Errorf("Expected SSLFIPS to be 'NO' but it is not; got \"%v\"", sslFIPSOutput)
|
||||
}
|
||||
|
||||
// Stop the container cleanly
|
||||
stopContainer(t, cli, ID)
|
||||
}
|
||||
|
||||
// Container creation fails when invalid certs are passed and MQ_ENABLE_FIPS set true
|
||||
func TestSSLFIPSBadCerts(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cli := ce.NewContainerClient()
|
||||
|
||||
containerConfig := ce.ContainerConfig{
|
||||
Env: []string{
|
||||
"LICENSE=accept",
|
||||
"MQ_QMGR_NAME=QM1",
|
||||
"MQ_ENABLE_EMBEDDED_WEB_SERVER=false",
|
||||
"MQ_ENABLE_FIPS=true",
|
||||
},
|
||||
Image: imageName(),
|
||||
}
|
||||
hostConfig := ce.ContainerHostConfig{
|
||||
Binds: []string{
|
||||
tlsDirInvalid(t, false) + ":/etc/mqm/pki/keys/default",
|
||||
},
|
||||
}
|
||||
addCoverageBindIfAvailable(t, &hostConfig)
|
||||
networkingConfig := ce.ContainerNetworkSettings{}
|
||||
ID, err := cli.ContainerCreate(&containerConfig, &hostConfig, &networkingConfig, t.Name())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer cleanContainer(t, cli, ID)
|
||||
startContainer(t, cli, ID)
|
||||
|
||||
rc := waitForContainer(t, cli, ID, 20*time.Second)
|
||||
// Expect return code 1 if container failed to create.
|
||||
if rc == 1 {
|
||||
// Get container logs and search for specific message.
|
||||
logs := inspectLogs(t, cli, ID)
|
||||
if strings.Contains(logs, "Failed to parse private key") {
|
||||
t.Logf("Container creating failed because of invalid certifates")
|
||||
}
|
||||
} else {
|
||||
// Some other error occurred.
|
||||
t.Errorf("Expected rc=0, got rc=%v", rc)
|
||||
}
|
||||
|
||||
// Stop the container cleanly
|
||||
stopContainer(t, cli, ID)
|
||||
}
|
||||
|
||||
// Test REST messaging with default developer configuration
|
||||
// MQ_CONNAUTH_USE_HTP is set to true in the dev image. The test
|
||||
// specifies password for admin userId via MQ_ADMIN_PASSWORD
|
||||
// environment variable but then attempts to do REST messaging
|
||||
// usig 'app' userId. HTTP 401 is expected.
|
||||
func TestDevNoDefCreds(t *testing.T) {
|
||||
t.Parallel()
|
||||
cli := ce.NewContainerClient()
|
||||
qm := "qm1"
|
||||
containerConfig := ce.ContainerConfig{
|
||||
Env: []string{
|
||||
"LICENSE=accept",
|
||||
"MQ_QMGR_NAME=" + qm,
|
||||
"DEBUG=true",
|
||||
"MQ_ADMIN_PASSWORD=" + defaultAdminPassword,
|
||||
},
|
||||
}
|
||||
id := runContainerWithPorts(t, cli, &containerConfig, []int{9443, 1414})
|
||||
defer cleanContainer(t, cli, id)
|
||||
waitForReady(t, cli, id)
|
||||
waitForWebReady(t, cli, id, insecureTLSConfig)
|
||||
// Expect a 401 Unauthorized HTTP Response
|
||||
testRESTMessaging(t, cli, id, insecureTLSConfig, qm, "app", defaultAppPasswordWeb, "401 Unauthorized")
|
||||
// Stop the container cleanly
|
||||
stopContainer(t, cli, id)
|
||||
}
|
||||
|
||||
// MQ_CONNAUTH_USE_HTP is set to false. There should be no 'mqsimpleauth:' entries in pod log
|
||||
// eventhough MQ_ADMIN_PASSWORD is also specified.
|
||||
func TestDevNoDefCredsLogMessageConnAuthFalse(t *testing.T) {
|
||||
t.Parallel()
|
||||
testDevNoDefaultCredsUtil(t, []string{"MQ_CONNAUTH_USE_HTP=false", "MQ_ADMIN_PASSWORD=passw0rd"}, false)
|
||||
}
|
||||
|
||||
// MQ_CONNAUTH_USE_HTP is true with neither Admin nor App password specified,
|
||||
// so there should be no 'mqsimpleauth:' entries in the pod log
|
||||
func TestDevNoDefCredsLogMessageConnAuthTrue(t *testing.T) {
|
||||
t.Parallel()
|
||||
testDevNoDefaultCredsUtil(t, []string{"MQ_CONNAUTH_USE_HTP=true"}, false)
|
||||
}
|
||||
|
||||
// MQ_CONNAUTH_USE_HTP is true with App password specified,
|
||||
// there should be at least one 'mqsimpleauth:' entry in the pod log
|
||||
func TestDevNoDefCredsLogMessageConnAuthTrueWithPwd(t *testing.T) {
|
||||
t.Parallel()
|
||||
testDevNoDefaultCredsUtil(t, []string{"MQ_CONNAUTH_USE_HTP=true", "MQ_APP_PASSWORD=passw0rd"}, true)
|
||||
}
|
||||
|
||||
// Utility function for testing mqsimpleauth
|
||||
func testDevNoDefaultCredsUtil(t *testing.T, mqsimpleauthEnvs []string, htpwdInLog bool) {
|
||||
cli := ce.NewContainerClient()
|
||||
qm := "QM1"
|
||||
containerConfig := ce.ContainerConfig{
|
||||
Env: []string{
|
||||
"LICENSE=accept",
|
||||
"MQ_QMGR_NAME=" + qm,
|
||||
"DEBUG=true",
|
||||
},
|
||||
}
|
||||
|
||||
containerConfig.Env = append(containerConfig.Env, mqsimpleauthEnvs...)
|
||||
|
||||
id := runContainerWithPorts(t, cli, &containerConfig, []int{1414})
|
||||
defer cleanContainer(t, cli, id)
|
||||
waitForReady(t, cli, id)
|
||||
defer stopContainer(t, cli, id)
|
||||
|
||||
logs := inspectLogs(t, cli, id)
|
||||
if htpwdInLog {
|
||||
if !strings.Contains(logs, "mqsimpleauth:") {
|
||||
t.Errorf("Exepcted mqsimpleauth keyword in pod logs but did not find any.")
|
||||
}
|
||||
} else {
|
||||
if strings.Contains(logs, "mqsimpleauth:") {
|
||||
t.Errorf("Didn't exepct mqsimpleauth keyword in pod logs but found at least one.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Test REST messaging with default developer configuration
|
||||
// MQ_CONNAUTH_USE_HTP is set to true in the dev image with
|
||||
// read only root filesystem enabled. The test
|
||||
// specifies password for admin userId via MQ_ADMIN_PASSWORD
|
||||
// environment variable but then attempts to do REST messaging
|
||||
// usig 'app' userId. HTTP 401 is expected.
|
||||
func TestRORFSDevNoAppPassword(t *testing.T) {
|
||||
t.Parallel()
|
||||
cli := ce.NewContainerClient()
|
||||
qm := "QM1"
|
||||
containerConfig := ce.ContainerConfig{
|
||||
Env: []string{
|
||||
"LICENSE=accept",
|
||||
"MQ_QMGR_NAME=" + qm,
|
||||
"DEBUG=true",
|
||||
"MQ_CONNAUTH_USE_HTP=true",
|
||||
"MQ_ADMIN_PASSWORD=" + defaultAdminPassword,
|
||||
},
|
||||
Image: imageName(),
|
||||
}
|
||||
|
||||
// Create volumes for mounting into container
|
||||
ephData := createVolume(t, cli, "ephData"+t.Name())
|
||||
defer removeVolume(t, cli, ephData)
|
||||
ephRun := createVolume(t, cli, "ephRun"+t.Name())
|
||||
defer removeVolume(t, cli, ephRun)
|
||||
ephTmp := createVolume(t, cli, "ephTmp"+t.Name())
|
||||
defer removeVolume(t, cli, ephTmp)
|
||||
|
||||
hostConfig := ce.ContainerHostConfig{
|
||||
Binds: []string{
|
||||
ephRun + ":/run",
|
||||
ephTmp + ":/tmp",
|
||||
ephData + ":/mnt/mqm",
|
||||
},
|
||||
ReadOnlyRootfs: true, //Enable read only root filesystem
|
||||
}
|
||||
addCoverageBindIfAvailable(t, &hostConfig)
|
||||
// Assign a random port for the web server on the host
|
||||
var binding ce.PortBinding
|
||||
ports := []int{9443}
|
||||
for _, p := range ports {
|
||||
port := fmt.Sprintf("%v/tcp", p)
|
||||
binding = ce.PortBinding{
|
||||
ContainerPort: port,
|
||||
HostIP: "0.0.0.0",
|
||||
}
|
||||
hostConfig.PortBindings = append(hostConfig.PortBindings, binding)
|
||||
}
|
||||
networkingConfig := ce.ContainerNetworkSettings{}
|
||||
id, err := cli.ContainerCreate(&containerConfig, &hostConfig, &networkingConfig, t.Name())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer cleanContainer(t, cli, id)
|
||||
startContainer(t, cli, id)
|
||||
|
||||
waitForReady(t, cli, id)
|
||||
waitForWebReady(t, cli, id, insecureTLSConfig)
|
||||
// Expect a 401 Unauthorized HTTP Response
|
||||
testRESTMessaging(t, cli, id, insecureTLSConfig, qm, "app", defaultAppPasswordWeb, "401 Unauthorized")
|
||||
// Stop the container cleanly
|
||||
stopContainer(t, cli, id)
|
||||
}
|
||||
345
test/container/devconfig_test_util.go
Normal file
345
test/container/devconfig_test_util.go
Normal file
@@ -0,0 +1,345 @@
|
||||
//go:build mqdev
|
||||
// +build mqdev
|
||||
|
||||
/*
|
||||
© Copyright IBM Corporation 2018, 2024
|
||||
|
||||
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.
|
||||
*/
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
ce "github.com/ibm-messaging/mq-container/test/container/containerengine"
|
||||
"github.com/ibm-messaging/mq-container/test/container/pathutils"
|
||||
)
|
||||
|
||||
const defaultAdminPassword string = "passw0rd"
|
||||
const defaultAppPasswordOS string = "passw0rd"
|
||||
const defaultAppPasswordWeb string = "passw0rd"
|
||||
|
||||
// Disable TLS verification (server uses a self-signed certificate by default,
|
||||
// so verification isn't useful anyway)
|
||||
var insecureTLSConfig *tls.Config = &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
}
|
||||
|
||||
func waitForWebReady(t *testing.T, cli ce.ContainerInterface, ID string, tlsConfig *tls.Config) {
|
||||
t.Logf("%s Waiting for web server to be ready", time.Now().Format(time.RFC3339))
|
||||
httpClient := http.Client{
|
||||
Timeout: time.Duration(10 * time.Second),
|
||||
Transport: &http.Transport{
|
||||
TLSClientConfig: tlsConfig,
|
||||
},
|
||||
}
|
||||
port, err := cli.GetContainerPort(ID, 9443)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
url := fmt.Sprintf("https://localhost:%s/ibmmq/rest/v1/admin/installation", port)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
|
||||
defer cancel()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-time.After(1 * time.Second):
|
||||
req, err := http.NewRequest("GET", url, nil)
|
||||
req.SetBasicAuth("admin", defaultAdminPassword)
|
||||
resp, err := httpClient.Do(req)
|
||||
if err == nil && resp.StatusCode == http.StatusOK {
|
||||
t.Logf("%s MQ web server is ready", time.Now().Format(time.RFC3339))
|
||||
return
|
||||
}
|
||||
case <-ctx.Done():
|
||||
t.Fatalf("%s Timed out waiting for web server to become ready", time.Now().Format(time.RFC3339))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// tlsDir returns the host directory where the test certificate(s) are located
|
||||
func tlsDir(t *testing.T, unixPath bool) string {
|
||||
return pathutils.CleanPath(filepath.Dir(getCwd(t, unixPath)), "../tls")
|
||||
}
|
||||
|
||||
func tlsDirWithCA(t *testing.T, unixPath bool) string {
|
||||
return pathutils.CleanPath(filepath.Dir(getCwd(t, unixPath)), "../tlscacert")
|
||||
}
|
||||
|
||||
func tlsDirInvalid(t *testing.T, unixPath bool) string {
|
||||
return pathutils.CleanPath(filepath.Dir(getCwd(t, unixPath)), "../tlsinvalidcert")
|
||||
}
|
||||
|
||||
// runJMSTests runs a container with a JMS client, which connects to the queue manager container with the specified ID
|
||||
func runJMSTests(t *testing.T, cli ce.ContainerInterface, ID string, tls bool, user, password string, ibmjre string, cipherName string) {
|
||||
port, err := cli.GetContainerPort(ID, 1414)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
containerConfig := ce.ContainerConfig{
|
||||
// -e MQ_PORT_1414_TCP_ADDR=9.145.14.173 -e MQ_USERNAME=app -e MQ_PASSWORD=passw0rd -e MQ_CHANNEL=DEV.APP.SVRCONN -e MQ_TLS_TRUSTSTORE=/tls/test.p12 -e MQ_TLS_PASSPHRASE=passw0rd -v /Users/arthurbarr/go/src/github.com/ibm-messaging/mq-container/test/tls:/tls msgtest
|
||||
Env: []string{
|
||||
"MQ_PORT_1414_TCP_ADDR=127.0.0.1",
|
||||
"MQ_PORT_1414_OVERRIDE=" + port,
|
||||
"MQ_USERNAME=" + user,
|
||||
"MQ_CHANNEL=DEV.APP.SVRCONN",
|
||||
"IBMJRE=" + ibmjre,
|
||||
},
|
||||
Image: imageNameDevJMS(),
|
||||
}
|
||||
// Set a password for the client to use, if one is specified
|
||||
if password != "" {
|
||||
containerConfig.Env = append(containerConfig.Env, "MQ_PASSWORD="+password)
|
||||
}
|
||||
if tls {
|
||||
t.Log("Using TLS from JMS client")
|
||||
containerConfig.Env = append(containerConfig.Env, []string{
|
||||
"MQ_TLS_TRUSTSTORE=/var/tls/client-trust.jks",
|
||||
"MQ_TLS_PASSPHRASE=passw0rd",
|
||||
"MQ_TLS_CIPHER=" + cipherName,
|
||||
}...)
|
||||
}
|
||||
hostConfig := ce.ContainerHostConfig{
|
||||
Binds: []string{
|
||||
tlsDir(t, false) + ":/var/tls",
|
||||
},
|
||||
}
|
||||
addCoverageBindIfAvailable(t, &hostConfig)
|
||||
networkingConfig := ce.ContainerNetworkSettings{
|
||||
Networks: []string{"host"},
|
||||
}
|
||||
jmsID, err := cli.ContainerCreate(&containerConfig, &hostConfig, &networkingConfig, strings.Replace(t.Name()+"JMS", "/", "", -1))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
startContainer(t, cli, jmsID)
|
||||
rc := waitForContainer(t, cli, jmsID, 2*time.Minute)
|
||||
if rc != 0 {
|
||||
t.Errorf("JUnit container failed with rc=%v", rc)
|
||||
}
|
||||
|
||||
// Get console output of the container and process the lines
|
||||
// to see if we have any failures
|
||||
scanner := bufio.NewScanner(strings.NewReader(inspectLogs(t, cli, jmsID)))
|
||||
for scanner.Scan() {
|
||||
s := scanner.Text()
|
||||
if processJunitLogLine(s) {
|
||||
t.Errorf("JUnit container tests failed. Reason: %s", s)
|
||||
}
|
||||
}
|
||||
|
||||
defer cleanContainer(t, cli, jmsID)
|
||||
}
|
||||
|
||||
// Parse JUnit log line and return true if line contains failed or aborted tests
|
||||
func processJunitLogLine(outputLine string) bool {
|
||||
var failedLine bool
|
||||
// Sample JUnit test run output
|
||||
//[ 2 containers found ]
|
||||
//[ 0 containers skipped ]
|
||||
//[ 2 containers started ]
|
||||
//[ 0 containers aborted ]
|
||||
//[ 2 containers successful ]
|
||||
//[ 0 containers failed ]
|
||||
//[ 0 tests found ]
|
||||
//[ 0 tests skipped ]
|
||||
//[ 0 tests started ]
|
||||
//[ 0 tests aborted ]
|
||||
//[ 0 tests successful ]
|
||||
//[ 0 tests failed ]
|
||||
|
||||
// Consider only those lines that begin with '[' and with ']'
|
||||
if strings.HasPrefix(outputLine, "[") && strings.HasSuffix(outputLine, "]") {
|
||||
// Strip off [] and whitespaces
|
||||
trimmed := strings.Trim(outputLine, "[] ")
|
||||
if strings.Contains(trimmed, "aborted") || strings.Contains(trimmed, "failed") {
|
||||
// Tokenize on whitespace
|
||||
tokens := strings.Split(trimmed, " ")
|
||||
// Determine the count of aborted or failed tests
|
||||
count, err := strconv.Atoi(tokens[0])
|
||||
if err == nil {
|
||||
if count > 0 {
|
||||
failedLine = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return failedLine
|
||||
}
|
||||
|
||||
// createTLSConfig creates a tls.Config which trusts the specified certificate
|
||||
func createTLSConfig(t *testing.T, certFile, password string, tlsConfigOptions ...tlsConfigOption) *tls.Config {
|
||||
// Get the SystemCertPool, continue with an empty pool on error
|
||||
certs, err := x509.SystemCertPool()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Read in the cert file
|
||||
cert, err := os.ReadFile(certFile)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Append our cert to the system pool
|
||||
ok := certs.AppendCertsFromPEM(cert)
|
||||
if !ok {
|
||||
t.Fatal("No certs appended")
|
||||
}
|
||||
// Trust the augmented cert pool in our client
|
||||
config := &tls.Config{
|
||||
InsecureSkipVerify: false,
|
||||
RootCAs: certs,
|
||||
}
|
||||
// Apply any additional config options
|
||||
for _, applyOpt := range tlsConfigOptions {
|
||||
applyOpt(config)
|
||||
}
|
||||
return config
|
||||
}
|
||||
|
||||
func testRESTAdmin(t *testing.T, cli ce.ContainerInterface, ID string, tlsConfig *tls.Config, errorExpected string) {
|
||||
httpClient := http.Client{
|
||||
Timeout: time.Duration(30 * time.Second),
|
||||
Transport: &http.Transport{
|
||||
TLSClientConfig: tlsConfig,
|
||||
},
|
||||
}
|
||||
port, err := cli.GetContainerPort(ID, 9443)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
url := fmt.Sprintf("https://localhost:%s/ibmmq/rest/v1/admin/installation", port)
|
||||
req, err := http.NewRequest("GET", url, nil)
|
||||
req.SetBasicAuth("admin", defaultAdminPassword)
|
||||
resp, err := httpClient.Do(req)
|
||||
if err != nil {
|
||||
if len(errorExpected) > 0 {
|
||||
if !strings.Contains(err.Error(), errorExpected) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
} else {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
if resp != nil && resp.StatusCode != http.StatusOK {
|
||||
t.Errorf("Expected HTTP status code %v from 'GET installation'; got %v", http.StatusOK, resp.StatusCode)
|
||||
}
|
||||
}
|
||||
|
||||
// curl -i -k https://localhost:1234/ibmmq/rest/v1/messaging/qmgr/qm1/queue/DEV.QUEUE.1/message -X POST -u app -H “ibm-mq-rest-csrf-token: N/A” -H “Content-Type: text/plain;charset=utf-8" -d “Hello World”
|
||||
|
||||
func logHTTPRequest(t *testing.T, req *http.Request) {
|
||||
d, err := httputil.DumpRequestOut(req, true)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
t.Logf("HTTP request: %v", string(d))
|
||||
}
|
||||
|
||||
func logHTTPResponse(t *testing.T, resp *http.Response) {
|
||||
d, err := httputil.DumpResponse(resp, true)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
t.Logf("HTTP response: %v", string(d))
|
||||
}
|
||||
|
||||
func testRESTMessaging(t *testing.T, cli ce.ContainerInterface, ID string, tlsConfig *tls.Config, qmName string, user string, password string, errorExpected string) {
|
||||
httpClient := http.Client{
|
||||
Timeout: time.Duration(30 * time.Second),
|
||||
Transport: &http.Transport{
|
||||
TLSClientConfig: tlsConfig,
|
||||
},
|
||||
}
|
||||
q := "DEV.QUEUE.1"
|
||||
port, err := cli.GetContainerPort(ID, 9443)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
url := fmt.Sprintf("https://localhost:%s/ibmmq/rest/v1/messaging/qmgr/%s/queue/%s/message", port, qmName, q)
|
||||
putMessage := []byte("Hello")
|
||||
req, err := http.NewRequest("POST", url, bytes.NewBuffer(putMessage))
|
||||
req.SetBasicAuth(user, password)
|
||||
req.Header.Add("ibm-mq-rest-csrf-token", "n/a")
|
||||
req.Header.Add("Content-Type", "text/plain;charset=utf-8")
|
||||
logHTTPRequest(t, req)
|
||||
resp, err := httpClient.Do(req)
|
||||
if err != nil {
|
||||
if len(errorExpected) > 0 {
|
||||
if strings.Contains(err.Error(), errorExpected) {
|
||||
t.Logf("Error contains expected '%s' value", errorExpected)
|
||||
return
|
||||
} else {
|
||||
t.Fatal(err)
|
||||
}
|
||||
} else {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
logHTTPResponse(t, resp)
|
||||
if resp != nil && resp.StatusCode != http.StatusCreated {
|
||||
if strings.Contains(resp.Status, errorExpected) {
|
||||
t.Logf("HTTP Response code is as expected. %s", resp.Status)
|
||||
return
|
||||
} else {
|
||||
t.Errorf("Expected HTTP status code %v from 'POST to queue'; got %v", http.StatusOK, resp.StatusCode)
|
||||
t.Logf("HTTP response: %+v", resp)
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
req, err = http.NewRequest("DELETE", url, nil)
|
||||
req.Header.Add("ibm-mq-rest-csrf-token", "n/a")
|
||||
req.SetBasicAuth(user, password)
|
||||
logHTTPRequest(t, req)
|
||||
resp, err = httpClient.Do(req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
logHTTPResponse(t, resp)
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
t.Errorf("Expected HTTP status code %v from 'DELETE from queue'; got %v", http.StatusOK, resp.StatusCode)
|
||||
t.Logf("HTTP response: %+v", resp)
|
||||
t.Fail()
|
||||
}
|
||||
gotMessage, err := io.ReadAll(resp.Body)
|
||||
//gotMessage := string(b)
|
||||
if string(gotMessage) != string(putMessage) {
|
||||
t.Errorf("Expected payload to be \"%s\"; got \"%s\"", putMessage, gotMessage)
|
||||
}
|
||||
}
|
||||
|
||||
type tlsConfigOption func(*tls.Config)
|
||||
|
||||
// withMinTLSVersion is a functional option to set the minimum version for TLS
|
||||
func withMinTLSVersion(version uint16) tlsConfigOption {
|
||||
return func(cfg *tls.Config) {
|
||||
cfg.MinVersion = version
|
||||
}
|
||||
}
|
||||
2209
test/container/docker_api_test.go
Normal file
2209
test/container/docker_api_test.go
Normal file
File diff suppressed because it is too large
Load Diff
889
test/container/docker_api_test_util.go
Normal file
889
test/container/docker_api_test_util.go
Normal file
@@ -0,0 +1,889 @@
|
||||
/*
|
||||
© Copyright IBM Corporation 2017, 2024
|
||||
|
||||
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.
|
||||
*/
|
||||
package main
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/rand"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
ce "github.com/ibm-messaging/mq-container/test/container/containerengine"
|
||||
"github.com/ibm-messaging/mq-container/test/container/pathutils"
|
||||
)
|
||||
|
||||
func imageName() string {
|
||||
image, ok := os.LookupEnv("TEST_IMAGE")
|
||||
if !ok {
|
||||
image = "mq-devserver:latest-x86-64"
|
||||
}
|
||||
return image
|
||||
}
|
||||
|
||||
func imageNameDevJMS() string {
|
||||
image, ok := os.LookupEnv("DEV_JMS_IMAGE")
|
||||
if !ok {
|
||||
image = "mq-dev-jms-test"
|
||||
}
|
||||
return image
|
||||
}
|
||||
|
||||
// baseImage returns the ID of the underlying base image (e.g. "ubuntu" or "rhel")
|
||||
func baseImage(t *testing.T, cli ce.ContainerInterface) string {
|
||||
rc, out := runContainerOneShot(t, cli, "grep", "^ID=", "/etc/os-release")
|
||||
if rc != 0 {
|
||||
t.Fatal("Couldn't determine base image")
|
||||
}
|
||||
s := strings.Split(out, "=")
|
||||
if len(s) < 2 {
|
||||
t.Fatal("Couldn't determine base image string")
|
||||
}
|
||||
return s[1]
|
||||
}
|
||||
|
||||
// devImage returns true if the image under test is a developer image,
|
||||
// determined by use of the MQ_ADMIN_PASSWORD environment variable
|
||||
func devImage(t *testing.T, cli ce.ContainerInterface) bool {
|
||||
rc, _ := runContainerOneShot(t, cli, "printenv", "MQ_ADMIN_PASSWORD")
|
||||
if rc == 0 {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// isWSL return whether we are running in the Windows Subsystem for Linux
|
||||
func isWSL(t *testing.T) bool {
|
||||
if runtime.GOOS == "linux" {
|
||||
uname, err := exec.Command("uname", "-r").Output()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return strings.Contains(string(uname), "Microsoft")
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// isARM returns whether we are running an arm64 MacOS machine
|
||||
func isARM(t *testing.T) bool {
|
||||
return runtime.GOARCH == "arm64"
|
||||
}
|
||||
|
||||
// getCwd returns the working directory, in an os-specific or UNIX form
|
||||
func getCwd(t *testing.T, unixPath bool) string {
|
||||
dir, err := os.Getwd()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if isWSL(t) {
|
||||
// Check if the cwd is a symlink
|
||||
dir, err = filepath.EvalSymlinks(dir)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !unixPath {
|
||||
dir = strings.Replace(dir, getWindowsRoot(true), getWindowsRoot(false), 1)
|
||||
}
|
||||
}
|
||||
return dir
|
||||
}
|
||||
|
||||
// getWindowsRoot get the path of the root directory on Windows, in UNIX or OS-specific style
|
||||
func getWindowsRoot(unixStylePath bool) string {
|
||||
if unixStylePath {
|
||||
return "/mnt/c/"
|
||||
}
|
||||
return "C:/"
|
||||
}
|
||||
|
||||
func coverage() bool {
|
||||
cover := os.Getenv("TEST_COVER")
|
||||
if cover == "true" || cover == "1" {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// coverageDir returns the host directory to use for code coverage data
|
||||
func coverageDir(t *testing.T, unixStylePath bool) string {
|
||||
return pathutils.CleanPath(getCwd(t, unixStylePath), "coverage")
|
||||
}
|
||||
|
||||
// coverageBind returns a string to use to add a bind-mounted directory for code coverage data
|
||||
func coverageBind(t *testing.T) string {
|
||||
return coverageDir(t, false) + ":/var/coverage"
|
||||
}
|
||||
|
||||
func addCoverageBindIfAvailable(t *testing.T, cfg *ce.ContainerHostConfig) {
|
||||
info, err := os.Stat(coverageDir(t, false))
|
||||
if err != nil || !info.IsDir() {
|
||||
return
|
||||
}
|
||||
cfg.Binds = append(cfg.Binds, coverageBind(t))
|
||||
}
|
||||
|
||||
// getTempDir get the path of the tmp directory, in UNIX or OS-specific style
|
||||
func getTempDir(t *testing.T, unixStylePath bool) string {
|
||||
if isWSL(t) {
|
||||
return getWindowsRoot(unixStylePath) + "Temp/"
|
||||
}
|
||||
return "/tmp/"
|
||||
}
|
||||
|
||||
// terminationMessage return the termination message, or an empty string if not set
|
||||
func terminationMessage(t *testing.T, cli ce.ContainerInterface, ID string) string {
|
||||
r, err := cli.CopyFromContainer(ID, "/run/termination-log")
|
||||
if err != nil {
|
||||
t.Log(err)
|
||||
t.Log(string(r))
|
||||
return ""
|
||||
}
|
||||
return string(r)
|
||||
}
|
||||
|
||||
func expectTerminationMessage(t *testing.T, cli ce.ContainerInterface, ID string) {
|
||||
m := terminationMessage(t, cli, ID)
|
||||
if m == "" {
|
||||
t.Error("Expected termination message to be set")
|
||||
}
|
||||
}
|
||||
|
||||
// logContainerDetails logs selected details about the container
|
||||
func logContainerDetails(t *testing.T, cli ce.ContainerInterface, ID string) {
|
||||
i, err := cli.ContainerInspect(ID)
|
||||
if err == nil {
|
||||
d := ce.ContainerDetailsLogging{
|
||||
ID: ID,
|
||||
Name: i.Name,
|
||||
Image: i.Image,
|
||||
Path: i.Path,
|
||||
Args: i.Args,
|
||||
CapAdd: i.HostConfig.CapAdd,
|
||||
CapDrop: i.HostConfig.CapDrop,
|
||||
User: i.Config.User,
|
||||
Env: i.Config.Env,
|
||||
}
|
||||
// If you need more details, you can always just run `json.MarshalIndent(i, "", " ")` to see everything.
|
||||
t.Logf("Container details: %+v", d)
|
||||
}
|
||||
}
|
||||
|
||||
func cleanContainerQuiet(t *testing.T, cli ce.ContainerInterface, ID string) {
|
||||
timeout := 10 * time.Second
|
||||
err := cli.ContainerStop(ID, &timeout)
|
||||
if err != nil {
|
||||
// Just log the error and continue
|
||||
t.Log(err)
|
||||
}
|
||||
opts := ce.ContainerRemoveOptions{
|
||||
RemoveVolumes: true,
|
||||
Force: true,
|
||||
}
|
||||
err = cli.ContainerRemove(ID, opts)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func cleanContainer(t *testing.T, cli ce.ContainerInterface, ID string) {
|
||||
logContainerDetails(t, cli, ID)
|
||||
t.Logf("Stopping container: %v", ID)
|
||||
timeout := 10 * time.Second
|
||||
// Stop the container. This allows the coverage output to be generated.
|
||||
err := cli.ContainerStop(ID, &timeout)
|
||||
if err != nil {
|
||||
// Just log the error and continue
|
||||
t.Log(err)
|
||||
}
|
||||
t.Log("Container stopped")
|
||||
|
||||
// If a code coverage file has been generated, then rename it to match the test name
|
||||
os.Rename(pathutils.CleanPath(coverageDir(t, true), "container.cov"), pathutils.CleanPath(coverageDir(t, true), t.Name()+".cov"))
|
||||
// Log the container output for any container we're about to delete
|
||||
t.Logf("Console log from container %v:\n%v", ID, inspectTextLogs(t, cli, ID))
|
||||
|
||||
m := terminationMessage(t, cli, ID)
|
||||
if m != "" {
|
||||
t.Logf("Termination message: %v", m)
|
||||
}
|
||||
|
||||
t.Logf("Removing container: %s", ID)
|
||||
opts := ce.ContainerRemoveOptions{
|
||||
RemoveVolumes: true,
|
||||
Force: true,
|
||||
}
|
||||
err = cli.ContainerRemove(ID, opts)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func generateRandomUID() string {
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
min := 1000
|
||||
max := 9999
|
||||
return fmt.Sprint(rand.Intn(max-min) + min)
|
||||
}
|
||||
|
||||
// getDefaultHostConfig creates a HostConfig and populates it with the defaults used in testing
|
||||
func getDefaultHostConfig(t *testing.T, cli ce.ContainerInterface) *ce.ContainerHostConfig {
|
||||
hostConfig := ce.ContainerHostConfig{
|
||||
PortBindings: []ce.PortBinding{},
|
||||
CapDrop: []string{
|
||||
"ALL",
|
||||
},
|
||||
Privileged: false,
|
||||
}
|
||||
if coverage() {
|
||||
hostConfig.Binds = append(hostConfig.Binds, coverageBind(t))
|
||||
}
|
||||
if devImage(t, cli) {
|
||||
// Only needed for a RHEL-based image
|
||||
if baseImage(t, cli) != "ubuntu" {
|
||||
hostConfig.CapAdd = append(hostConfig.CapAdd, "DAC_OVERRIDE")
|
||||
}
|
||||
} else {
|
||||
t.Logf("Detected MQ Advanced image - dropping all capabilities")
|
||||
}
|
||||
return &hostConfig
|
||||
}
|
||||
|
||||
// runContainerWithHostConfig creates and starts a container, using the supplied HostConfig.
|
||||
// Note that a default HostConfig can be created using getDefaultHostConfig.
|
||||
func runContainerWithHostConfig(t *testing.T, cli ce.ContainerInterface, containerConfig *ce.ContainerConfig, hostConfig *ce.ContainerHostConfig) string {
|
||||
if containerConfig.Image == "" {
|
||||
containerConfig.Image = imageName()
|
||||
}
|
||||
// Always run as a random user, unless the test has specified otherwise
|
||||
if containerConfig.User == "" {
|
||||
containerConfig.User = generateRandomUID()
|
||||
}
|
||||
if coverage() {
|
||||
containerConfig.Env = append(containerConfig.Env, "COVERAGE_FILE="+t.Name()+".cov")
|
||||
containerConfig.Env = append(containerConfig.Env, "EXIT_CODE_FILE="+getExitCodeFilename(t))
|
||||
}
|
||||
networkingConfig := ce.ContainerNetworkSettings{}
|
||||
t.Logf("Running container (%s)", containerConfig.Image)
|
||||
ID, err := cli.ContainerCreate(containerConfig, hostConfig, &networkingConfig, t.Name())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
startContainer(t, cli, ID)
|
||||
return ID
|
||||
}
|
||||
|
||||
// runContainerWithAllConfig creates and starts a container, using the supplied ContainerConfig, HostConfig,
|
||||
// NetworkingConfig, and container name (or the value of t.Name if containerName="").
|
||||
func runContainerWithAllConfig(t *testing.T, cli ce.ContainerInterface, containerConfig *ce.ContainerConfig, hostConfig *ce.ContainerHostConfig, networkingConfig *ce.ContainerNetworkSettings, containerName string) string {
|
||||
if containerName == "" {
|
||||
containerName = t.Name()
|
||||
}
|
||||
if containerConfig.Image == "" {
|
||||
containerConfig.Image = imageName()
|
||||
}
|
||||
// Always run as a random user, unless the test has specified otherwise
|
||||
if containerConfig.User == "" {
|
||||
containerConfig.User = generateRandomUID()
|
||||
}
|
||||
if coverage() {
|
||||
containerConfig.Env = append(containerConfig.Env, "COVERAGE_FILE="+t.Name()+".cov")
|
||||
containerConfig.Env = append(containerConfig.Env, "EXIT_CODE_FILE="+getExitCodeFilename(t))
|
||||
}
|
||||
t.Logf("Running container (%s)", containerConfig.Image)
|
||||
ID, err := cli.ContainerCreate(containerConfig, hostConfig, networkingConfig, containerName)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
startContainer(t, cli, ID)
|
||||
return ID
|
||||
}
|
||||
|
||||
// runContainerWithPorts creates and starts a container, exposing the specified ports on the host.
|
||||
// If no image is specified in the container config, then the image name is retrieved from the TEST_IMAGE
|
||||
// environment variable.
|
||||
func runContainerWithPorts(t *testing.T, cli ce.ContainerInterface, containerConfig *ce.ContainerConfig, ports []int) string {
|
||||
hostConfig := getDefaultHostConfig(t, cli)
|
||||
var binding ce.PortBinding
|
||||
for _, p := range ports {
|
||||
port := fmt.Sprintf("%v/tcp", p)
|
||||
binding = ce.PortBinding{
|
||||
ContainerPort: port,
|
||||
HostIP: "0.0.0.0",
|
||||
}
|
||||
hostConfig.PortBindings = append(hostConfig.PortBindings, binding)
|
||||
}
|
||||
return runContainerWithHostConfig(t, cli, containerConfig, hostConfig)
|
||||
}
|
||||
|
||||
// runContainer creates and starts a container. If no image is specified in
|
||||
// the container config, then the image name is retrieved from the TEST_IMAGE
|
||||
// environment variable.
|
||||
func runContainer(t *testing.T, cli ce.ContainerInterface, containerConfig *ce.ContainerConfig) string {
|
||||
return runContainerWithPorts(t, cli, containerConfig, nil)
|
||||
}
|
||||
|
||||
// runContainerOneShot runs a container with a custom entrypoint, as the root
|
||||
// user and with default capabilities
|
||||
func runContainerOneShot(t *testing.T, cli ce.ContainerInterface, command ...string) (int64, string) {
|
||||
containerConfig := ce.ContainerConfig{
|
||||
Entrypoint: command,
|
||||
User: "root",
|
||||
Image: imageName(),
|
||||
}
|
||||
hostConfig := ce.ContainerHostConfig{}
|
||||
networkingConfig := ce.ContainerNetworkSettings{}
|
||||
t.Logf("Running one shot container (%s): %v", containerConfig.Image, command)
|
||||
ID, err := cli.ContainerCreate(&containerConfig, &hostConfig, &networkingConfig, t.Name()+"OneShot")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
startOptions := ce.ContainerStartOptions{}
|
||||
err = cli.ContainerStart(ID, startOptions)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer cleanContainerQuiet(t, cli, ID)
|
||||
rc := waitForContainer(t, cli, ID, 20*time.Second)
|
||||
out := inspectLogs(t, cli, ID)
|
||||
t.Logf("One shot container finished with rc=%v, output=%v", rc, out)
|
||||
return rc, out
|
||||
}
|
||||
|
||||
// runContainerOneShot runs a container with a custom entrypoint, as the root
|
||||
// user, with default capabilities, and a volume mounted
|
||||
func runContainerOneShotWithVolume(t *testing.T, cli ce.ContainerInterface, bind string, command ...string) (int64, string) {
|
||||
containerConfig := ce.ContainerConfig{
|
||||
Entrypoint: command,
|
||||
User: "root",
|
||||
Image: imageName(),
|
||||
}
|
||||
hostConfig := ce.ContainerHostConfig{
|
||||
Binds: []string{
|
||||
bind,
|
||||
},
|
||||
}
|
||||
networkingConfig := ce.ContainerNetworkSettings{}
|
||||
t.Logf("Running one shot container with volume (%s): %v", containerConfig.Image, command)
|
||||
ID, err := cli.ContainerCreate(&containerConfig, &hostConfig, &networkingConfig, t.Name()+"OneShotVolume")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
startOptions := ce.ContainerStartOptions{}
|
||||
err = cli.ContainerStart(ID, startOptions)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer cleanContainerQuiet(t, cli, ID)
|
||||
rc := waitForContainer(t, cli, ID, 20*time.Second)
|
||||
out := inspectLogs(t, cli, ID)
|
||||
t.Logf("One shot container finished with rc=%v, output=%v", rc, out)
|
||||
return rc, out
|
||||
}
|
||||
|
||||
func startMultiVolumeQueueManager(t *testing.T, cli ce.ContainerInterface, dataVol bool, qmsharedlogs string, qmshareddata string, env []string, qmRun string, qmTmp string, readOnlyRootFs bool) (error, string, string) {
|
||||
id := strconv.FormatInt(time.Now().UnixNano(), 10)
|
||||
volume := createVolume(t, cli, id)
|
||||
containerConfig := ce.ContainerConfig{
|
||||
Image: imageName(),
|
||||
Env: env,
|
||||
}
|
||||
var hostConfig ce.ContainerHostConfig
|
||||
|
||||
if !dataVol {
|
||||
hostConfig = ce.ContainerHostConfig{}
|
||||
if readOnlyRootFs {
|
||||
hostConfig.Binds = append(hostConfig.Binds, qmRun+":/run")
|
||||
hostConfig.Binds = append(hostConfig.Binds, qmTmp+":/tmp")
|
||||
hostConfig.ReadOnlyRootfs = true
|
||||
}
|
||||
} else if qmsharedlogs == "" && qmshareddata == "" {
|
||||
hostConfig = getHostConfig(t, 1, "", "", volume, qmRun, qmTmp, readOnlyRootFs)
|
||||
} else if qmsharedlogs == "" {
|
||||
hostConfig = getHostConfig(t, 2, "", qmshareddata, volume, qmRun, qmTmp, readOnlyRootFs)
|
||||
} else if qmshareddata == "" {
|
||||
hostConfig = getHostConfig(t, 3, qmsharedlogs, "", volume, qmRun, qmTmp, readOnlyRootFs)
|
||||
} else {
|
||||
hostConfig = getHostConfig(t, 4, qmsharedlogs, qmshareddata, volume, qmRun, qmTmp, readOnlyRootFs)
|
||||
}
|
||||
networkingConfig := ce.ContainerNetworkSettings{}
|
||||
qmID, err := cli.ContainerCreate(&containerConfig, &hostConfig, &networkingConfig, t.Name()+id)
|
||||
if err != nil {
|
||||
return err, "", ""
|
||||
}
|
||||
startContainer(t, cli, qmID)
|
||||
|
||||
return nil, qmID, volume
|
||||
}
|
||||
|
||||
func getHostConfig(t *testing.T, mounts int, qmsharedlogs string, qmshareddata string, qmdata string, qmRun string, qmTmp string, readOnlyRootFS bool) ce.ContainerHostConfig {
|
||||
|
||||
var hostConfig ce.ContainerHostConfig
|
||||
|
||||
switch mounts {
|
||||
case 1:
|
||||
hostConfig = ce.ContainerHostConfig{
|
||||
Binds: []string{
|
||||
qmdata + ":/mnt/mqm",
|
||||
},
|
||||
}
|
||||
case 2:
|
||||
hostConfig = ce.ContainerHostConfig{
|
||||
Binds: []string{
|
||||
qmdata + ":/mnt/mqm",
|
||||
qmshareddata + ":/mnt/mqm-data",
|
||||
},
|
||||
}
|
||||
case 3:
|
||||
hostConfig = ce.ContainerHostConfig{
|
||||
Binds: []string{
|
||||
qmdata + ":/mnt/mqm",
|
||||
qmsharedlogs + ":/mnt/mqm-log",
|
||||
},
|
||||
}
|
||||
case 4:
|
||||
hostConfig = ce.ContainerHostConfig{
|
||||
Binds: []string{
|
||||
qmdata + ":/mnt/mqm",
|
||||
qmsharedlogs + ":/mnt/mqm-log",
|
||||
qmshareddata + ":/mnt/mqm-data",
|
||||
},
|
||||
}
|
||||
}
|
||||
if coverage() {
|
||||
hostConfig.Binds = append(hostConfig.Binds, coverageBind(t))
|
||||
}
|
||||
if readOnlyRootFS {
|
||||
hostConfig.Binds = append(hostConfig.Binds, qmRun+":/run")
|
||||
hostConfig.Binds = append(hostConfig.Binds, qmTmp+":/tmp")
|
||||
hostConfig.ReadOnlyRootfs = true
|
||||
}
|
||||
return hostConfig
|
||||
}
|
||||
|
||||
func startContainer(t *testing.T, cli ce.ContainerInterface, ID string) {
|
||||
t.Logf("Starting container: %v", ID)
|
||||
startOptions := ce.ContainerStartOptions{}
|
||||
err := cli.ContainerStart(ID, startOptions)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func stopContainer(t *testing.T, cli ce.ContainerInterface, ID string) {
|
||||
t.Logf("Stopping container: %v", ID)
|
||||
timeout := 10 * time.Second
|
||||
err := cli.ContainerStop(ID, &timeout) //Duration(20)*time.Second)
|
||||
if err != nil {
|
||||
// Just log the error and continue
|
||||
t.Log(err)
|
||||
}
|
||||
}
|
||||
|
||||
func killContainer(t *testing.T, cli ce.ContainerInterface, ID string, signal string) {
|
||||
t.Logf("Killing container: %v", ID)
|
||||
err := cli.ContainerKill(ID, signal)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func getExitCodeFilename(t *testing.T) string {
|
||||
return t.Name() + "ExitCode"
|
||||
}
|
||||
|
||||
func getCoverageExitCode(t *testing.T, orig int64) int64 {
|
||||
f := pathutils.CleanPath(coverageDir(t, true), getExitCodeFilename(t))
|
||||
_, err := os.Stat(f)
|
||||
if err != nil {
|
||||
t.Log(err)
|
||||
return orig
|
||||
}
|
||||
// Remove the file, ready for the next test
|
||||
defer os.Remove(f)
|
||||
buf, err := os.ReadFile(f)
|
||||
if err != nil {
|
||||
t.Log(err)
|
||||
return orig
|
||||
}
|
||||
rc, err := strconv.Atoi(string(buf))
|
||||
if err != nil {
|
||||
t.Log(err)
|
||||
return orig
|
||||
}
|
||||
t.Logf("Retrieved exit code %v from file", rc)
|
||||
return int64(rc)
|
||||
}
|
||||
|
||||
// waitForContainer waits until a container has exited
|
||||
func waitForContainer(t *testing.T, cli ce.ContainerInterface, ID string, timeout time.Duration) int64 {
|
||||
c, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
defer cancel()
|
||||
t.Logf("Waiting for container for %s", timeout)
|
||||
okC, errC := cli.ContainerWait(c, ID, ce.ContainerStateNotRunning)
|
||||
var rc int64
|
||||
select {
|
||||
case err := <-errC:
|
||||
t.Fatal(err)
|
||||
case ok := <-okC:
|
||||
rc = ok
|
||||
}
|
||||
if coverage() {
|
||||
// COVERAGE: When running coverage, the exit code is written to a file,
|
||||
// to allow the coverage to be generated (which doesn't happen for non-zero
|
||||
// exit codes)
|
||||
rc = getCoverageExitCode(t, rc)
|
||||
}
|
||||
return rc
|
||||
}
|
||||
|
||||
// execContainer runs a command in a running container, and returns the exit code and output
|
||||
func execContainer(t *testing.T, cli ce.ContainerInterface, ID string, user string, cmd []string) (int, string) {
|
||||
t.Logf("Running command: %v", cmd)
|
||||
exitcode, outputStr := cli.ExecContainer(ID, user, cmd)
|
||||
return exitcode, outputStr
|
||||
}
|
||||
|
||||
func waitForReady(t *testing.T, cli ce.ContainerInterface, ID string) {
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 4*time.Minute)
|
||||
defer cancel()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-time.After(1 * time.Second):
|
||||
rc, _ := execContainer(t, cli, ID, "", []string{"chkmqready"})
|
||||
|
||||
if rc == 0 {
|
||||
t.Log("MQ is ready")
|
||||
return
|
||||
} else if rc == 10 {
|
||||
t.Log("MQ Readiness: Queue Manager Running as Standby")
|
||||
return
|
||||
} else if rc == 20 {
|
||||
t.Log("MQ Readiness: Queue Manager Running as Replica")
|
||||
return
|
||||
}
|
||||
case <-ctx.Done():
|
||||
t.Fatal("Timed out waiting for container to become ready")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func createNetwork(t *testing.T, cli ce.ContainerInterface) string {
|
||||
name := "test"
|
||||
t.Logf("Creating network: %v", name)
|
||||
opts := ce.NetworkCreateOptions{}
|
||||
netID, err := cli.NetworkCreate(name, opts)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Logf("Created network %v with ID %v", name, netID)
|
||||
return netID
|
||||
}
|
||||
|
||||
func removeNetwork(t *testing.T, cli ce.ContainerInterface, ID string) {
|
||||
t.Logf("Removing network ID: %v", ID)
|
||||
err := cli.NetworkRemove(ID)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func createVolume(t *testing.T, cli ce.ContainerInterface, name string) string {
|
||||
v, err := cli.VolumeCreate(ce.VolumeCreateOptions{
|
||||
Driver: "local",
|
||||
Name: name,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Logf("Created volume %v", v)
|
||||
return v
|
||||
}
|
||||
|
||||
func removeVolume(t *testing.T, cli ce.ContainerInterface, name string) {
|
||||
t.Logf("Removing volume %v", name)
|
||||
err := cli.VolumeRemove(name, true)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func inspectTextLogs(t *testing.T, cli ce.ContainerInterface, ID string) string {
|
||||
jsonLogs := inspectLogs(t, cli, ID)
|
||||
scanner := bufio.NewScanner(strings.NewReader(jsonLogs))
|
||||
b := make([]byte, 64*1024)
|
||||
buf := bytes.NewBuffer(b)
|
||||
for scanner.Scan() {
|
||||
text := scanner.Text()
|
||||
if strings.HasPrefix(text, "{") {
|
||||
// If it's a JSON log message, it makes it hard to debug the test, as the JSON
|
||||
// is embedded in the long test output. So just summarize the JSON instead.
|
||||
var e map[string]interface{}
|
||||
json.Unmarshal([]byte(text), &e)
|
||||
fmt.Fprintf(buf, "{\"ibm_datetime\": \"%v\", \"message\": \"%v\", ...}\n", e["ibm_datetime"], e["message"])
|
||||
} else {
|
||||
fmt.Fprintln(buf, text)
|
||||
}
|
||||
}
|
||||
err := scanner.Err()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func inspectLogs(t *testing.T, cli ce.ContainerInterface, ID string) string {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
logs, err := cli.GetContainerLogs(ctx, ID, ce.ContainerLogsOptions{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return logs
|
||||
}
|
||||
|
||||
// generateTAR creates a TAR-formatted []byte, with the specified files included.
|
||||
func generateTAR(t *testing.T, files []struct{ Name, Body string }) []byte {
|
||||
buf := new(bytes.Buffer)
|
||||
tw := tar.NewWriter(buf)
|
||||
for _, file := range files {
|
||||
hdr := &tar.Header{
|
||||
Name: file.Name,
|
||||
Mode: 0600,
|
||||
Size: int64(len(file.Body)),
|
||||
}
|
||||
err := tw.WriteHeader(hdr)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, err = tw.Write([]byte(file.Body))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
err := tw.Close()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
// createImage creates a new Docker image with the specified files included.
|
||||
func createImage(t *testing.T, cli ce.ContainerInterface, files []struct{ Name, Body string }) string {
|
||||
r := bytes.NewReader(generateTAR(t, files))
|
||||
tag := strings.ToLower(t.Name())
|
||||
|
||||
tmpDir, err := os.MkdirTemp("", "tmp")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
//Write files to temp directory
|
||||
for _, file := range files {
|
||||
//Add tag to file name to allow parallel testing
|
||||
f, err := os.Create(pathutils.CleanPath(tmpDir, file.Name))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
body := []byte(file.Body)
|
||||
_, err = f.Write(body)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
_, err = cli.ImageBuild(r, tag, pathutils.CleanPath(tmpDir, files[0].Name))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return tag
|
||||
}
|
||||
|
||||
// deleteImage deletes a Docker image
|
||||
func deleteImage(t *testing.T, cli ce.ContainerInterface, id string) {
|
||||
cli.ImageRemove(id, ce.ImageRemoveOptions{
|
||||
Force: true,
|
||||
})
|
||||
}
|
||||
|
||||
func copyFromContainer(t *testing.T, cli ce.ContainerInterface, id string, file string) []byte {
|
||||
b, err := cli.CopyFromContainer(id, file)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func countLines(t *testing.T, r io.Reader) int {
|
||||
scanner := bufio.NewScanner(r)
|
||||
count := 0
|
||||
for scanner.Scan() {
|
||||
count++
|
||||
}
|
||||
err := scanner.Err()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
func countTarLines(t *testing.T, b []byte) int {
|
||||
r := bytes.NewReader(b)
|
||||
tr := tar.NewReader(r)
|
||||
total := 0
|
||||
for {
|
||||
_, err := tr.Next()
|
||||
if err == io.EOF {
|
||||
// End of TAR
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
total += countLines(t, tr)
|
||||
}
|
||||
return total
|
||||
}
|
||||
|
||||
// scanForExcludedEntries scans for default excluded messages
|
||||
func scanForExcludedEntries(msg string) bool {
|
||||
if strings.Contains(msg, "AMQ5041I") || strings.Contains(msg, "AMQ5052I") ||
|
||||
strings.Contains(msg, "AMQ5051I") || strings.Contains(msg, "AMQ5037I") ||
|
||||
strings.Contains(msg, "AMQ5975I") {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// checkLogForValidJSON checks if the message is in Json format
|
||||
func checkLogForValidJSON(jsonLogs string) bool {
|
||||
scanner := bufio.NewScanner(strings.NewReader(jsonLogs))
|
||||
for scanner.Scan() {
|
||||
var obj map[string]interface{}
|
||||
s := scanner.Text()
|
||||
err := json.Unmarshal([]byte(s), &obj)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// runContainerWithAllConfig creates and starts a container, using the supplied ContainerConfig, HostConfig,
|
||||
// NetworkingConfig, and container name (or the value of t.Name if containerName="").
|
||||
func runContainerWithAllConfigError(t *testing.T, cli ce.ContainerInterface, containerConfig *ce.ContainerConfig, hostConfig *ce.ContainerHostConfig, networkingConfig *ce.ContainerNetworkSettings, containerName string) (string, error) {
|
||||
if containerName == "" {
|
||||
containerName = t.Name()
|
||||
}
|
||||
if containerConfig.Image == "" {
|
||||
containerConfig.Image = imageName()
|
||||
}
|
||||
// Always run as a random user, unless the test has specified otherwise
|
||||
if containerConfig.User == "" {
|
||||
containerConfig.User = generateRandomUID()
|
||||
}
|
||||
if coverage() {
|
||||
containerConfig.Env = append(containerConfig.Env, "COVERAGE_FILE="+t.Name()+".cov")
|
||||
containerConfig.Env = append(containerConfig.Env, "EXIT_CODE_FILE="+getExitCodeFilename(t))
|
||||
}
|
||||
t.Logf("Running container (%s)", containerConfig.Image)
|
||||
ID, err := cli.ContainerCreate(containerConfig, hostConfig, networkingConfig, containerName)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
err = startContainerError(t, cli, ID)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return ID, nil
|
||||
}
|
||||
|
||||
func startContainerError(t *testing.T, cli ce.ContainerInterface, ID string) error {
|
||||
t.Logf("Starting container: %v", ID)
|
||||
startOptions := ce.ContainerStartOptions{}
|
||||
err := cli.ContainerStart(ID, startOptions)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// testLogFilePages validates that the specified number of logFilePages is present in the qm.ini file.
|
||||
func testLogFilePages(t *testing.T, cli ce.ContainerInterface, id string, qmName string, expectedLogFilePages string) {
|
||||
catIniFileCommand := fmt.Sprintf("cat /var/mqm/qmgrs/" + qmName + "/qm.ini")
|
||||
_, iniContent := execContainer(t, cli, id, "", []string{"bash", "-c", catIniFileCommand})
|
||||
|
||||
if !strings.Contains(iniContent, "LogFilePages="+expectedLogFilePages) {
|
||||
t.Errorf("Expected qm.ini to contain LogFilePages="+expectedLogFilePages+"; got qm.ini \"%v\"", iniContent)
|
||||
}
|
||||
}
|
||||
|
||||
// waitForMessageInLog will check for a particular message with wait
|
||||
func waitForMessageInLog(t *testing.T, cli ce.ContainerInterface, id string, expectedMessageId string) (string, error) {
|
||||
var jsonLogs string
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
|
||||
defer cancel()
|
||||
for {
|
||||
select {
|
||||
case <-time.After(1 * time.Second):
|
||||
jsonLogs = inspectLogs(t, cli, id)
|
||||
if strings.Contains(jsonLogs, expectedMessageId) {
|
||||
return jsonLogs, nil
|
||||
}
|
||||
case <-ctx.Done():
|
||||
return "", fmt.Errorf("expected message Id %s was not logged", expectedMessageId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// waitForMessageCountInLog will check for a particular message with wait and must occur exact number of times in log as specified by count
|
||||
func waitForMessageCountInLog(t *testing.T, cli ce.ContainerInterface, id string, expectedMessageId string, count int) (string, error) {
|
||||
var jsonLogs string
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
|
||||
defer cancel()
|
||||
for {
|
||||
select {
|
||||
case <-time.After(1 * time.Second):
|
||||
jsonLogs = inspectLogs(t, cli, id)
|
||||
if strings.Contains(jsonLogs, expectedMessageId) && strings.Count(jsonLogs, expectedMessageId) == count {
|
||||
return jsonLogs, nil
|
||||
}
|
||||
case <-ctx.Done():
|
||||
return "", fmt.Errorf("expected message Id %s was not logged or it was not logged %v times", expectedMessageId, count)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Returns fully qualified path
|
||||
func tlsDirDN(t *testing.T, unixPath bool, certPath string) string {
|
||||
return pathutils.CleanPath(filepath.Dir(getCwd(t, unixPath)), certPath)
|
||||
}
|
||||
3
test/container/go.mod
Normal file
3
test/container/go.mod
Normal file
@@ -0,0 +1,3 @@
|
||||
module github.com/ibm-messaging/mq-container/test/container
|
||||
|
||||
go 1.19
|
||||
0
test/container/go.sum
Normal file
0
test/container/go.sum
Normal file
19
test/container/main.go
Normal file
19
test/container/main.go
Normal file
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
© Copyright IBM Corporation 2017
|
||||
|
||||
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.
|
||||
*/
|
||||
package main
|
||||
|
||||
func main() {
|
||||
}
|
||||
269
test/container/mq_multi_instance_test.go
Normal file
269
test/container/mq_multi_instance_test.go
Normal file
@@ -0,0 +1,269 @@
|
||||
/*
|
||||
© Copyright IBM Corporation 2019, 2023
|
||||
|
||||
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.
|
||||
*/
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
ce "github.com/ibm-messaging/mq-container/test/container/containerengine"
|
||||
)
|
||||
|
||||
var miEnv = []string{
|
||||
"LICENSE=accept",
|
||||
"MQ_QMGR_NAME=QM1",
|
||||
"MQ_MULTI_INSTANCE=true",
|
||||
}
|
||||
|
||||
// TestMultiInstanceStartStop creates 2 containers in a multi instance queue manager configuration
|
||||
// and starts/stop them checking we always have an active and standby
|
||||
func TestMultiInstanceStartStop(t *testing.T) {
|
||||
t.Skipf("Skipping %v until test defect fixed", t.Name())
|
||||
cli := ce.NewContainerClient()
|
||||
err, qm1aId, qm1bId, volumes := configureMultiInstance(t, cli, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, volume := range volumes {
|
||||
defer removeVolume(t, cli, volume)
|
||||
}
|
||||
defer cleanContainer(t, cli, qm1aId)
|
||||
defer cleanContainer(t, cli, qm1bId)
|
||||
|
||||
waitForReady(t, cli, qm1aId)
|
||||
waitForReady(t, cli, qm1bId)
|
||||
|
||||
err, active, standby := getActiveStandbyQueueManager(t, cli, qm1aId, qm1bId)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
killContainer(t, cli, active, "SIGTERM")
|
||||
time.Sleep(2 * time.Second)
|
||||
|
||||
if status := getQueueManagerStatus(t, cli, standby, "QM1"); strings.Compare(status, "Running") != 0 {
|
||||
t.Fatalf("Expected QM1 to be running as active queue manager, dspmq returned status of %v", status)
|
||||
}
|
||||
|
||||
startContainer(t, cli, qm1aId)
|
||||
waitForReady(t, cli, qm1aId)
|
||||
|
||||
err, _, _ = getActiveStandbyQueueManager(t, cli, qm1aId, qm1bId)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// TestMultiInstanceContainerStop starts 2 containers in a multi instance queue manager configuration,
|
||||
// stops the active queue manager, then checks to ensure the backup queue manager becomes active
|
||||
func TestMultiInstanceContainerStop(t *testing.T) {
|
||||
cli := ce.NewContainerClient()
|
||||
err, qm1aId, qm1bId, volumes := configureMultiInstance(t, cli, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, volume := range volumes {
|
||||
defer removeVolume(t, cli, volume)
|
||||
}
|
||||
defer cleanContainer(t, cli, qm1aId)
|
||||
defer cleanContainer(t, cli, qm1bId)
|
||||
|
||||
waitForReady(t, cli, qm1aId)
|
||||
waitForReady(t, cli, qm1bId)
|
||||
|
||||
err, originalActive, originalStandby := getActiveStandbyQueueManager(t, cli, qm1aId, qm1bId)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
stopContainer(t, cli, originalActive)
|
||||
|
||||
for {
|
||||
status := getQueueManagerStatus(t, cli, originalStandby, "QM1")
|
||||
select {
|
||||
case <-time.After(1 * time.Second):
|
||||
if status == "Running" {
|
||||
t.Logf("Original standby is now the active")
|
||||
return
|
||||
} else if status == "Starting" {
|
||||
t.Logf("Original standby is starting")
|
||||
}
|
||||
case <-ctx.Done():
|
||||
t.Fatalf("%s Timed out waiting for standby to become the active. Status=%v", time.Now().Format(time.RFC3339), status)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestMultiInstanceRace starts 2 containers in separate goroutines in a multi instance queue manager
|
||||
// configuration, then checks to ensure that both an active and standby queue manager have been started
|
||||
func TestMultiInstanceRace(t *testing.T) {
|
||||
t.Skipf("Skipping %v until file lock is implemented", t.Name())
|
||||
cli := ce.NewContainerClient()
|
||||
qmsharedlogs := createVolume(t, cli, "qmsharedlogs")
|
||||
defer removeVolume(t, cli, qmsharedlogs)
|
||||
qmshareddata := createVolume(t, cli, "qmshareddata")
|
||||
defer removeVolume(t, cli, qmshareddata)
|
||||
|
||||
qmsChannel := make(chan QMChan)
|
||||
|
||||
go singleMultiInstanceQueueManager(t, cli, qmsharedlogs, qmshareddata, qmsChannel)
|
||||
go singleMultiInstanceQueueManager(t, cli, qmsharedlogs, qmshareddata, qmsChannel)
|
||||
|
||||
qm1a := <-qmsChannel
|
||||
if qm1a.Error != nil {
|
||||
t.Fatal(qm1a.Error)
|
||||
}
|
||||
|
||||
qm1b := <-qmsChannel
|
||||
if qm1b.Error != nil {
|
||||
t.Fatal(qm1b.Error)
|
||||
}
|
||||
|
||||
qm1aId, qm1aData := qm1a.QMId, qm1a.QMData
|
||||
qm1bId, qm1bData := qm1b.QMId, qm1b.QMData
|
||||
|
||||
defer removeVolume(t, cli, qm1aData)
|
||||
defer removeVolume(t, cli, qm1bData)
|
||||
defer cleanContainer(t, cli, qm1aId)
|
||||
defer cleanContainer(t, cli, qm1bId)
|
||||
|
||||
waitForReady(t, cli, qm1aId)
|
||||
waitForReady(t, cli, qm1bId)
|
||||
|
||||
err, _, _ := getActiveStandbyQueueManager(t, cli, qm1aId, qm1bId)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestMultiInstanceNoSharedMounts starts 2 multi instance queue managers without providing shared log/data
|
||||
// mounts, then checks to ensure that the container terminates with the expected message
|
||||
func TestMultiInstanceNoSharedMounts(t *testing.T) {
|
||||
t.Parallel()
|
||||
cli := ce.NewContainerClient()
|
||||
|
||||
err, qm1aId, qm1aData := startMultiVolumeQueueManager(t, cli, true, "", "", miEnv, "", "", false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
defer removeVolume(t, cli, qm1aData)
|
||||
defer cleanContainer(t, cli, qm1aId)
|
||||
|
||||
waitForTerminationMessage(t, cli, qm1aId, "Missing required mount '/mnt/mqm-log'", 30*time.Second)
|
||||
}
|
||||
|
||||
// TestMultiInstanceNoSharedLogs starts 2 multi instance queue managers without providing a shared log
|
||||
// mount, then checks to ensure that the container terminates with the expected message
|
||||
func TestMultiInstanceNoSharedLogs(t *testing.T) {
|
||||
cli := ce.NewContainerClient()
|
||||
|
||||
qmshareddata := createVolume(t, cli, "qmshareddata")
|
||||
defer removeVolume(t, cli, qmshareddata)
|
||||
|
||||
err, qm1aId, qm1aData := startMultiVolumeQueueManager(t, cli, true, "", qmshareddata, miEnv, "", "", false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
defer removeVolume(t, cli, qm1aData)
|
||||
defer cleanContainer(t, cli, qm1aId)
|
||||
|
||||
waitForTerminationMessage(t, cli, qm1aId, "Missing required mount '/mnt/mqm-log'", 30*time.Second)
|
||||
}
|
||||
|
||||
// TestMultiInstanceNoSharedData starts 2 multi instance queue managers without providing a shared data
|
||||
// mount, then checks to ensure that the container terminates with the expected message
|
||||
func TestMultiInstanceNoSharedData(t *testing.T) {
|
||||
cli := ce.NewContainerClient()
|
||||
|
||||
qmsharedlogs := createVolume(t, cli, "qmsharedlogs")
|
||||
defer removeVolume(t, cli, qmsharedlogs)
|
||||
|
||||
err, qm1aId, qm1aData := startMultiVolumeQueueManager(t, cli, true, qmsharedlogs, "", miEnv, "", "", false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
defer removeVolume(t, cli, qm1aData)
|
||||
defer cleanContainer(t, cli, qm1aId)
|
||||
|
||||
waitForTerminationMessage(t, cli, qm1aId, "Missing required mount '/mnt/mqm-data'", 30*time.Second)
|
||||
}
|
||||
|
||||
// TestMultiInstanceNoMounts starts 2 multi instance queue managers without providing a shared data
|
||||
// mount, then checks to ensure that the container terminates with the expected message
|
||||
func TestMultiInstanceNoMounts(t *testing.T) {
|
||||
cli := ce.NewContainerClient()
|
||||
|
||||
err, qm1aId, qm1aData := startMultiVolumeQueueManager(t, cli, false, "", "", miEnv, "", "", false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
defer removeVolume(t, cli, qm1aData)
|
||||
defer cleanContainer(t, cli, qm1aId)
|
||||
|
||||
waitForTerminationMessage(t, cli, qm1aId, "Missing required mount '/mnt/mqm'", 30*time.Second)
|
||||
}
|
||||
|
||||
// TestRoRFsMultiInstanceContainerStop starts 2 containers in a multi instance queue manager configuration,
|
||||
// with read-only root filesystem stops the active queue manager, then checks to ensure the backup queue
|
||||
// manager becomes active
|
||||
func TestRoRFsMultiInstanceContainerStop(t *testing.T) {
|
||||
cli := ce.NewContainerClient()
|
||||
err, qm1aId, qm1bId, volumes := configureMultiInstance(t, cli, true)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, volume := range volumes {
|
||||
defer removeVolume(t, cli, volume)
|
||||
}
|
||||
defer cleanContainer(t, cli, qm1aId)
|
||||
defer cleanContainer(t, cli, qm1bId)
|
||||
|
||||
waitForReady(t, cli, qm1aId)
|
||||
waitForReady(t, cli, qm1bId)
|
||||
|
||||
err, originalActive, originalStandby := getActiveStandbyQueueManager(t, cli, qm1aId, qm1bId)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
stopContainer(t, cli, originalActive)
|
||||
|
||||
for {
|
||||
status := getQueueManagerStatus(t, cli, originalStandby, "QM1")
|
||||
select {
|
||||
case <-time.After(1 * time.Second):
|
||||
if status == "Running" {
|
||||
t.Logf("Original standby is now the active")
|
||||
return
|
||||
} else if status == "Starting" {
|
||||
t.Logf("Original standby is starting")
|
||||
}
|
||||
case <-ctx.Done():
|
||||
t.Fatalf("%s Timed out waiting for standby to become the active. Status=%v", time.Now().Format(time.RFC3339), status)
|
||||
}
|
||||
}
|
||||
}
|
||||
114
test/container/mq_multi_instance_test_util.go
Normal file
114
test/container/mq_multi_instance_test_util.go
Normal file
@@ -0,0 +1,114 @@
|
||||
/*
|
||||
© Copyright IBM Corporation 2019, 2023
|
||||
|
||||
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.
|
||||
*/
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
ce "github.com/ibm-messaging/mq-container/test/container/containerengine"
|
||||
)
|
||||
|
||||
type QMChan struct {
|
||||
QMId string
|
||||
QMData string
|
||||
Error error
|
||||
}
|
||||
|
||||
// configureMultiInstance creates the volumes and containers required for basic testing
|
||||
// of multi instance queue managers. Returns error, qm1a ID, qm1b ID, slice of volume names
|
||||
func configureMultiInstance(t *testing.T, cli ce.ContainerInterface, readOnlyRootFs bool) (error, string, string, []string) {
|
||||
|
||||
qmsharedlogs := createVolume(t, cli, "qmsharedlogs")
|
||||
qmshareddata := createVolume(t, cli, "qmshareddata")
|
||||
|
||||
// Create tmp and run volumes
|
||||
var qmRunVol, qmTmpVol string
|
||||
if readOnlyRootFs {
|
||||
qmRunVol = createVolume(t, cli, "qmRunVolume")
|
||||
qmTmpVol = createVolume(t, cli, "qmTmpVolume")
|
||||
}
|
||||
|
||||
err, qm1aId, qm1aData := startMultiVolumeQueueManager(t, cli, true, qmsharedlogs, qmshareddata, miEnv, qmRunVol, qmTmpVol, readOnlyRootFs)
|
||||
if err != nil {
|
||||
return err, "", "", []string{}
|
||||
}
|
||||
time.Sleep(10 * time.Second)
|
||||
err, qm1bId, qm1bData := startMultiVolumeQueueManager(t, cli, true, qmsharedlogs, qmshareddata, miEnv, qmRunVol, qmTmpVol, readOnlyRootFs)
|
||||
if err != nil {
|
||||
return err, "", "", []string{}
|
||||
}
|
||||
|
||||
volumes := []string{qmsharedlogs, qmshareddata, qm1aData, qm1bData}
|
||||
if readOnlyRootFs {
|
||||
volumes = append(volumes, qmRunVol)
|
||||
volumes = append(volumes, qmTmpVol)
|
||||
}
|
||||
return nil, qm1aId, qm1bId, volumes
|
||||
}
|
||||
|
||||
func singleMultiInstanceQueueManager(t *testing.T, cli ce.ContainerInterface, qmsharedlogs string, qmshareddata string, qmsChannel chan QMChan) {
|
||||
err, qmId, qmData := startMultiVolumeQueueManager(t, cli, true, qmsharedlogs, qmshareddata, miEnv, "", "", false)
|
||||
if err != nil {
|
||||
qmsChannel <- QMChan{Error: err}
|
||||
}
|
||||
qmsChannel <- QMChan{QMId: qmId, QMData: qmData}
|
||||
}
|
||||
|
||||
func getActiveStandbyQueueManager(t *testing.T, cli ce.ContainerInterface, qm1aId string, qm1bId string) (error, string, string) {
|
||||
qm1aStatus := getQueueManagerStatus(t, cli, qm1aId, "QM1")
|
||||
qm1bStatus := getQueueManagerStatus(t, cli, qm1bId, "QM1")
|
||||
|
||||
if qm1aStatus == "Running" && qm1bStatus == "Running as standby" {
|
||||
return nil, qm1aId, qm1bId
|
||||
} else if qm1bStatus == "Running" && qm1aStatus == "Running as standby" {
|
||||
return nil, qm1bId, qm1aId
|
||||
}
|
||||
err := fmt.Errorf("Expected to be running in multi instance configuration, got status 1) %v status 2) %v", qm1aStatus, qm1bStatus)
|
||||
return err, "", ""
|
||||
}
|
||||
|
||||
func getQueueManagerStatus(t *testing.T, cli ce.ContainerInterface, containerID string, queueManagerName string) string {
|
||||
_, dspmqOut := execContainer(t, cli, containerID, "", []string{"bash", "-c", "dspmq", "-m", queueManagerName})
|
||||
t.Logf("dspmq for %v (%v) returned: %v", containerID, queueManagerName, dspmqOut)
|
||||
regex := regexp.MustCompile(`STATUS\(.*\)`)
|
||||
status := regex.FindString(dspmqOut)
|
||||
status = strings.TrimSuffix(strings.TrimPrefix(status, "STATUS("), ")")
|
||||
return status
|
||||
}
|
||||
|
||||
func waitForTerminationMessage(t *testing.T, cli ce.ContainerInterface, qmId string, terminationString string, timeout time.Duration) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
defer cancel()
|
||||
for {
|
||||
select {
|
||||
case <-time.After(1 * time.Second):
|
||||
m := terminationMessage(t, cli, qmId)
|
||||
if m != "" {
|
||||
if !strings.Contains(m, terminationString) {
|
||||
t.Fatalf("Expected container to fail with termination message %v. Got termination message: %v", terminationString, m)
|
||||
}
|
||||
return
|
||||
}
|
||||
case <-ctx.Done():
|
||||
t.Fatal("Timed out waiting for container to terminate")
|
||||
}
|
||||
}
|
||||
}
|
||||
346
test/container/mq_native_ha_test.go
Normal file
346
test/container/mq_native_ha_test.go
Normal file
@@ -0,0 +1,346 @@
|
||||
/*
|
||||
© Copyright IBM Corporation 2021, 2023
|
||||
|
||||
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.
|
||||
*/
|
||||
package main
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
ce "github.com/ibm-messaging/mq-container/test/container/containerengine"
|
||||
)
|
||||
|
||||
// TestNativeHABasic creates 3 containers in a Native HA queue manager configuration
|
||||
// and ensures the queue manger and replicas start as expected
|
||||
func TestNativeHABasic(t *testing.T) {
|
||||
cli := ce.NewContainerClient()
|
||||
|
||||
version, err := cli.GetMQVersion(imageName())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if version < "9.2.2.0" {
|
||||
t.Skipf("Skipping %s as test requires at least MQ 9.2.2.0, but image is version %s", t.Name(), version)
|
||||
}
|
||||
|
||||
containerNames := [3]string{"QM1_1", "QM1_2", "QM1_3"}
|
||||
qmReplicaIDs := [3]string{}
|
||||
qmVolumes := []string{}
|
||||
//Each native HA qmgr instance is exposed on subsequent ports on the host starting with basePort
|
||||
//If the qmgr exposes more than one port (tests do not do this currently) then they are offset by +50
|
||||
basePort := 14551
|
||||
for i := 0; i <= 2; i++ {
|
||||
nhaPort := basePort + i
|
||||
vol := createVolume(t, cli, containerNames[i])
|
||||
defer removeVolume(t, cli, vol)
|
||||
qmVolumes = append(qmVolumes, vol)
|
||||
containerConfig := getNativeHAContainerConfig(containerNames[i], containerNames, basePort)
|
||||
hostConfig := getHostConfig(t, 1, "", "", vol, "", "", false)
|
||||
hostConfig = populateNativeHAPortBindings([]int{9414}, nhaPort, hostConfig)
|
||||
networkingConfig := getNativeHANetworkConfig("host")
|
||||
ctr := runContainerWithAllConfig(t, cli, &containerConfig, &hostConfig, &networkingConfig, containerNames[i])
|
||||
defer cleanContainer(t, cli, ctr)
|
||||
qmReplicaIDs[i] = ctr
|
||||
}
|
||||
|
||||
waitForReadyHA(t, cli, qmReplicaIDs)
|
||||
|
||||
_, err = getActiveReplicaInstances(t, cli, qmReplicaIDs)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// TestNativeHAFailover creates 3 containers in a Native HA queue manager configuration,
|
||||
// stops the active queue manager, checks a replica becomes active, and ensures the stopped
|
||||
// queue manager comes back as a replica
|
||||
func TestNativeHAFailover(t *testing.T) {
|
||||
|
||||
cli := ce.NewContainerClient()
|
||||
|
||||
version, err := cli.GetMQVersion(imageName())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if version < "9.2.2.0" {
|
||||
t.Skipf("Skipping %s as test requires at least MQ 9.2.2.0, but image is version %s", t.Name(), version)
|
||||
}
|
||||
|
||||
containerNames := [3]string{"QM1_1", "QM1_2", "QM1_3"}
|
||||
qmReplicaIDs := [3]string{}
|
||||
qmVolumes := []string{}
|
||||
//Each native HA qmgr instance is exposed on subsequent ports on the host starting with basePort
|
||||
//If the qmgr exposes more than one port (tests do not do this currently) then they are offset by +50
|
||||
basePort := 14551
|
||||
for i := 0; i <= 2; i++ {
|
||||
nhaPort := basePort + i
|
||||
vol := createVolume(t, cli, containerNames[i])
|
||||
defer removeVolume(t, cli, vol)
|
||||
qmVolumes = append(qmVolumes, vol)
|
||||
containerConfig := getNativeHAContainerConfig(containerNames[i], containerNames, basePort)
|
||||
hostConfig := getHostConfig(t, 1, "", "", vol, "", "", false)
|
||||
hostConfig = populateNativeHAPortBindings([]int{9414}, nhaPort, hostConfig)
|
||||
networkingConfig := getNativeHANetworkConfig("host")
|
||||
ctr := runContainerWithAllConfig(t, cli, &containerConfig, &hostConfig, &networkingConfig, containerNames[i])
|
||||
defer cleanContainer(t, cli, ctr)
|
||||
qmReplicaIDs[i] = ctr
|
||||
}
|
||||
|
||||
waitForReadyHA(t, cli, qmReplicaIDs)
|
||||
|
||||
haStatus, err := getActiveReplicaInstances(t, cli, qmReplicaIDs)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
stopContainer(t, cli, haStatus.Active)
|
||||
waitForFailoverHA(t, cli, haStatus.Replica)
|
||||
startContainer(t, cli, haStatus.Active)
|
||||
waitForReady(t, cli, haStatus.Active)
|
||||
|
||||
_, err = getActiveReplicaInstances(t, cli, qmReplicaIDs)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// TestNativeHASecure creates 3 containers in a Native HA queue manager configuration
|
||||
// with HA TLS enabled, and ensures the queue manger and replicas start as expected
|
||||
func TestNativeHASecure(t *testing.T) {
|
||||
cli := ce.NewContainerClient()
|
||||
|
||||
version, err := cli.GetMQVersion(imageName())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if version < "9.2.2.0" {
|
||||
t.Skipf("Skipping %s as test requires at least MQ 9.2.2.0, but image is version %s", t.Name(), version)
|
||||
}
|
||||
if isARM(t) {
|
||||
t.Skip("Skipping as an issue has been identified for the arm64 MQ image")
|
||||
}
|
||||
|
||||
containerNames := [3]string{"QM1_1", "QM1_2", "QM1_3"}
|
||||
qmReplicaIDs := [3]string{}
|
||||
//Each native HA qmgr instance is exposed on subsequent ports on the host starting with basePort
|
||||
//If the qmgr exposes more than one port (tests do not do this currently) then they are offset by +50
|
||||
basePort := 14551
|
||||
for i := 0; i <= 2; i++ {
|
||||
nhaPort := basePort + i
|
||||
containerConfig := getNativeHAContainerConfig(containerNames[i], containerNames, defaultHAPort)
|
||||
containerConfig.Env = append(containerConfig.Env, "MQ_NATIVE_HA_TLS=true")
|
||||
hostConfig := getNativeHASecureHostConfig(t)
|
||||
hostConfig = populateNativeHAPortBindings([]int{9414}, nhaPort, hostConfig)
|
||||
networkingConfig := getNativeHANetworkConfig("host")
|
||||
ctr := runContainerWithAllConfig(t, cli, &containerConfig, &hostConfig, &networkingConfig, containerNames[i])
|
||||
defer cleanContainer(t, cli, ctr)
|
||||
qmReplicaIDs[i] = ctr
|
||||
}
|
||||
|
||||
waitForReadyHA(t, cli, qmReplicaIDs)
|
||||
|
||||
_, err = getActiveReplicaInstances(t, cli, qmReplicaIDs)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// TestNativeHASecure creates 3 containers in a Native HA queue manager configuration
|
||||
// with HA TLS enabled, overrides the default CipherSpec, and ensures the queue manger
|
||||
// and replicas start as expected
|
||||
func TestNativeHASecureCipherSpec(t *testing.T) {
|
||||
cli := ce.NewContainerClient()
|
||||
|
||||
version, err := cli.GetMQVersion(imageName())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if version < "9.2.2.0" {
|
||||
t.Skipf("Skipping %s as test requires at least MQ 9.2.2.0, but image is version %s", t.Name(), version)
|
||||
}
|
||||
|
||||
containerNames := [3]string{"QM1_1", "QM1_2", "QM1_3"}
|
||||
qmReplicaIDs := [3]string{}
|
||||
//Each native HA qmgr instance is exposed on subsequent ports on the host starting with basePort
|
||||
//If the qmgr exposes more than one port (tests do not do this currently) then they are offset by +50
|
||||
basePort := 14551
|
||||
for i := 0; i <= 2; i++ {
|
||||
nhaPort := basePort + i
|
||||
containerConfig := getNativeHAContainerConfig(containerNames[i], containerNames, defaultHAPort)
|
||||
containerConfig.Env = append(containerConfig.Env, "MQ_NATIVE_HA_TLS=true", "MQ_NATIVE_HA_CIPHERSPEC=TLS_AES_256_GCM_SHA384")
|
||||
hostConfig := getNativeHASecureHostConfig(t)
|
||||
hostConfig = populateNativeHAPortBindings([]int{9414}, nhaPort, hostConfig)
|
||||
networkingConfig := getNativeHANetworkConfig("host")
|
||||
ctr := runContainerWithAllConfig(t, cli, &containerConfig, &hostConfig, &networkingConfig, containerNames[i])
|
||||
defer cleanContainer(t, cli, ctr)
|
||||
qmReplicaIDs[i] = ctr
|
||||
}
|
||||
|
||||
waitForReadyHA(t, cli, qmReplicaIDs)
|
||||
|
||||
_, err = getActiveReplicaInstances(t, cli, qmReplicaIDs)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// TestNativeHASecure creates 3 containers in a Native HA queue manager configuration
|
||||
// with HA TLS FIPS enabled, overrides the default CipherSpec, and ensures the queue manger
|
||||
// and replicas start as expected. This test uses FIPS compliant cipher.
|
||||
func TestNativeHASecureCipherSpecFIPS(t *testing.T) {
|
||||
cli := ce.NewContainerClient()
|
||||
|
||||
version, err := cli.GetMQVersion(imageName())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if version < "9.2.2.0" {
|
||||
t.Skipf("Skipping %s as test requires at least MQ 9.2.2.0, but image is version %s", t.Name(), version)
|
||||
}
|
||||
|
||||
containerNames := [3]string{"QM1_1", "QM1_2", "QM1_3"}
|
||||
qmReplicaIDs := [3]string{}
|
||||
//Each native HA qmgr instance is exposed on subsequent ports on the host starting with basePort
|
||||
//If the qmgr exposes more than one port (tests do not do this currently) then they are offset by +50
|
||||
basePort := 14551
|
||||
for i := 0; i <= 2; i++ {
|
||||
nhaPort := basePort + i
|
||||
containerConfig := getNativeHAContainerConfig(containerNames[i], containerNames, defaultHAPort)
|
||||
// MQ_NATIVE_HA_CIPHERSPEC is set a FIPS compliant cipherspec.
|
||||
containerConfig.Env = append(containerConfig.Env, "MQ_NATIVE_HA_TLS=true", "MQ_NATIVE_HA_CIPHERSPEC=ANY_TLS12_OR_HIGHER", "MQ_ENABLE_FIPS=true")
|
||||
hostConfig := getNativeHASecureHostConfig(t)
|
||||
hostConfig = populateNativeHAPortBindings([]int{9414}, nhaPort, hostConfig)
|
||||
networkingConfig := getNativeHANetworkConfig("host")
|
||||
ctr := runContainerWithAllConfig(t, cli, &containerConfig, &hostConfig, &networkingConfig, containerNames[i])
|
||||
defer cleanContainer(t, cli, ctr)
|
||||
qmReplicaIDs[i] = ctr
|
||||
}
|
||||
|
||||
waitForReadyHA(t, cli, qmReplicaIDs)
|
||||
// Display the contents of qm.ini
|
||||
_, qmini := execContainer(t, cli, qmReplicaIDs[0], "", []string{"cat", "/var/mqm/qmgrs/QM1/qm.ini"})
|
||||
if !strings.Contains(qmini, "SSLFipsRequired=Yes") {
|
||||
t.Errorf("Expected SSLFipsRequired=Yes but it is not; got \"%v\"", qmini)
|
||||
}
|
||||
|
||||
_, err = getActiveReplicaInstances(t, cli, qmReplicaIDs)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestNativeHASecure creates 3 containers in a Native HA queue manager configuration
|
||||
// with HA TLS FIPS enabled with non-FIPS cipher, overrides the default CipherSpec, and
|
||||
// ensures the queue manger and replicas don't start as expected
|
||||
func TestNativeHASecureCipherSpecNonFIPSCipher(t *testing.T) {
|
||||
cli := ce.NewContainerClient()
|
||||
|
||||
version, err := cli.GetMQVersion(imageName())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if version < "9.2.2.0" {
|
||||
t.Skipf("Skipping %s as test requires at least MQ 9.2.2.0, but image is version %s", t.Name(), version)
|
||||
}
|
||||
|
||||
containerNames := [3]string{"QM1_1", "QM1_2", "QM1_3"}
|
||||
qmReplicaIDs := [3]string{}
|
||||
//Each native HA qmgr instance is exposed on subsequent ports on the host starting with basePort
|
||||
//If the qmgr exposes more than one port (tests do not do this currently) then they are offset by +50
|
||||
basePort := 14551
|
||||
for i := 0; i <= 2; i++ {
|
||||
nhaPort := basePort + i
|
||||
containerConfig := getNativeHAContainerConfig(containerNames[i], containerNames, defaultHAPort)
|
||||
// MQ_NATIVE_HA_CIPHERSPEC is set a FIPS non-compliant cipherspec - SSL_ECDHE_ECDSA_WITH_RC4_128_SHA
|
||||
containerConfig.Env = append(containerConfig.Env, "MQ_NATIVE_HA_TLS=true", "MQ_NATIVE_HA_CIPHERSPEC=SSL_ECDHE_ECDSA_WITH_RC4_128_SHA", "MQ_ENABLE_FIPS=true")
|
||||
hostConfig := getNativeHASecureHostConfig(t)
|
||||
hostConfig = populateNativeHAPortBindings([]int{9414}, nhaPort, hostConfig)
|
||||
networkingConfig := getNativeHANetworkConfig("host")
|
||||
ctr := runContainerWithAllConfig(t, cli, &containerConfig, &hostConfig, &networkingConfig, containerNames[i])
|
||||
defer cleanContainer(t, cli, ctr)
|
||||
// We expect container to fail in this case because the cipher is non-FIPS and we have asked for FIPS compliance
|
||||
// by setting MQ_ENABLE_FIPS=true
|
||||
qmReplicaIDs[i] = ctr
|
||||
}
|
||||
for i := 0; i <= 2; i++ {
|
||||
waitForTerminationMessage(t, cli, qmReplicaIDs[i], "/opt/mqm/bin/strmqm: exit status 23", 60*time.Second)
|
||||
}
|
||||
}
|
||||
|
||||
// TestNativeHAFailover creates 3 containers in a Native HA queue manager configuration,
|
||||
// stops the active queue manager, checks a replica becomes active, and ensures the stopped
|
||||
// queue manager comes back as a replica
|
||||
func TestNativeHAFailoverWithRoRFs(t *testing.T) {
|
||||
|
||||
cli := ce.NewContainerClient()
|
||||
|
||||
version, err := cli.GetMQVersion(imageName())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if version < "9.2.2.0" {
|
||||
t.Skipf("Skipping %s as test requires at least MQ 9.2.2.0, but image is version %s", t.Name(), version)
|
||||
}
|
||||
|
||||
containerNames := [3]string{"QM1_1", "QM1_2", "QM1_3"}
|
||||
qmReplicaIDs := [3]string{}
|
||||
qmVolumes := []string{}
|
||||
//Each native HA qmgr instance is exposed on subsequent ports on the host starting with basePort
|
||||
//If the qmgr exposes more than one port (tests do not do this currently) then they are offset by +50
|
||||
basePort := 14551
|
||||
for i := 0; i <= 2; i++ {
|
||||
nhaPort := basePort + i
|
||||
vol := createVolume(t, cli, containerNames[i])
|
||||
defer removeVolume(t, cli, vol)
|
||||
volRun := createVolume(t, cli, "ephRun"+containerNames[i])
|
||||
defer removeVolume(t, cli, volRun)
|
||||
volTmp := createVolume(t, cli, "ephTmp"+containerNames[i])
|
||||
defer removeVolume(t, cli, volTmp)
|
||||
|
||||
qmVolumes = append(qmVolumes, vol)
|
||||
qmVolumes = append(qmVolumes, volRun)
|
||||
qmVolumes = append(qmVolumes, volTmp)
|
||||
|
||||
containerConfig := getNativeHAContainerConfig(containerNames[i], containerNames, basePort)
|
||||
hostConfig := getHostConfig(t, 1, "", "", vol, volRun, volTmp, true)
|
||||
hostConfig = populateNativeHAPortBindings([]int{9414}, nhaPort, hostConfig)
|
||||
networkingConfig := getNativeHANetworkConfig("host")
|
||||
ctr := runContainerWithAllConfig(t, cli, &containerConfig, &hostConfig, &networkingConfig, containerNames[i])
|
||||
defer cleanContainer(t, cli, ctr)
|
||||
qmReplicaIDs[i] = ctr
|
||||
}
|
||||
|
||||
waitForReadyHA(t, cli, qmReplicaIDs)
|
||||
|
||||
haStatus, err := getActiveReplicaInstances(t, cli, qmReplicaIDs)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
stopContainer(t, cli, haStatus.Active)
|
||||
waitForFailoverHA(t, cli, haStatus.Replica)
|
||||
startContainer(t, cli, haStatus.Active)
|
||||
waitForReady(t, cli, haStatus.Active)
|
||||
|
||||
_, err = getActiveReplicaInstances(t, cli, qmReplicaIDs)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
159
test/container/mq_native_ha_test_util.go
Normal file
159
test/container/mq_native_ha_test_util.go
Normal file
@@ -0,0 +1,159 @@
|
||||
/*
|
||||
© Copyright IBM Corporation 2021, 2023
|
||||
|
||||
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.
|
||||
*/
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
ce "github.com/ibm-messaging/mq-container/test/container/containerengine"
|
||||
"github.com/ibm-messaging/mq-container/test/container/pathutils"
|
||||
)
|
||||
|
||||
const defaultHAPort = 9414
|
||||
|
||||
// HAReplicaStatus represents the Active/Replica/Replica container status of the queue manager
|
||||
type HAReplicaStatus struct {
|
||||
Active string
|
||||
Replica [2]string
|
||||
}
|
||||
|
||||
func getNativeHAContainerConfig(containerName string, replicaNames [3]string, haPort int) ce.ContainerConfig {
|
||||
return ce.ContainerConfig{
|
||||
Env: []string{
|
||||
"LICENSE=accept",
|
||||
"MQ_QMGR_NAME=QM1",
|
||||
"AMQ_CLOUD_PAK=true",
|
||||
"MQ_NATIVE_HA=true",
|
||||
fmt.Sprintf("HOSTNAME=%s", containerName),
|
||||
fmt.Sprintf("MQ_NATIVE_HA_INSTANCE_0_NAME=%s", replicaNames[0]),
|
||||
fmt.Sprintf("MQ_NATIVE_HA_INSTANCE_1_NAME=%s", replicaNames[1]),
|
||||
fmt.Sprintf("MQ_NATIVE_HA_INSTANCE_2_NAME=%s", replicaNames[2]),
|
||||
fmt.Sprintf("MQ_NATIVE_HA_INSTANCE_0_REPLICATION_ADDRESS=%s(%d)", "127.0.0.1", haPort+0),
|
||||
fmt.Sprintf("MQ_NATIVE_HA_INSTANCE_1_REPLICATION_ADDRESS=%s(%d)", "127.0.0.1", haPort+1),
|
||||
fmt.Sprintf("MQ_NATIVE_HA_INSTANCE_2_REPLICATION_ADDRESS=%s(%d)", "127.0.0.1", haPort+2),
|
||||
},
|
||||
//When using the host for networking a consistent user was required. If a random user is used then the following example error was recorded.
|
||||
//AMQ3209E: Native HA connection rejected due to configuration mismatch of 'QmgrUserId=5024'
|
||||
User: "1111",
|
||||
}
|
||||
}
|
||||
|
||||
func getNativeHASecureHostConfig(t *testing.T) ce.ContainerHostConfig {
|
||||
hostConfig := ce.ContainerHostConfig{
|
||||
Binds: []string{
|
||||
pathutils.CleanPath(filepath.Dir(getCwd(t, true)), "../tls") + ":/etc/mqm/ha/pki/keys/ha",
|
||||
},
|
||||
}
|
||||
addCoverageBindIfAvailable(t, &hostConfig)
|
||||
return hostConfig
|
||||
}
|
||||
|
||||
func getNativeHANetworkConfig(networkID string) ce.ContainerNetworkSettings {
|
||||
return ce.ContainerNetworkSettings{
|
||||
Networks: []string{networkID},
|
||||
}
|
||||
}
|
||||
|
||||
// populatePortBindings writes port bindings to the host config
|
||||
func populateNativeHAPortBindings(ports []int, nativeHaPort int, hostConfig ce.ContainerHostConfig) ce.ContainerHostConfig {
|
||||
hostConfig.PortBindings = []ce.PortBinding{}
|
||||
var binding ce.PortBinding
|
||||
for i, p := range ports {
|
||||
port := fmt.Sprintf("%v/tcp", p)
|
||||
binding = ce.PortBinding{
|
||||
ContainerPort: port,
|
||||
HostIP: "0.0.0.0",
|
||||
//Offset the ports by 50 if there are multiple
|
||||
HostPort: strconv.Itoa(nativeHaPort + 50*i),
|
||||
}
|
||||
hostConfig.PortBindings = append(hostConfig.PortBindings, binding)
|
||||
}
|
||||
return hostConfig
|
||||
}
|
||||
|
||||
func getActiveReplicaInstances(t *testing.T, cli ce.ContainerInterface, qmReplicaIDs [3]string) (HAReplicaStatus, error) {
|
||||
|
||||
var actives []string
|
||||
var replicas []string
|
||||
|
||||
for _, id := range qmReplicaIDs {
|
||||
qmReplicaStatus := getQueueManagerStatus(t, cli, id, "QM1")
|
||||
if qmReplicaStatus == "Running" {
|
||||
actives = append(actives, id)
|
||||
} else if qmReplicaStatus == "Replica" {
|
||||
replicas = append(replicas, id)
|
||||
} else {
|
||||
err := fmt.Errorf("Expected status to be Running or Replica, got status: %s", qmReplicaStatus)
|
||||
return HAReplicaStatus{}, err
|
||||
}
|
||||
}
|
||||
|
||||
if len(actives) != 1 || len(replicas) != 2 {
|
||||
err := fmt.Errorf("Expected 1 Active and 2 Replicas, got: %d Active and %d Replica", len(actives), len(replicas))
|
||||
return HAReplicaStatus{}, err
|
||||
}
|
||||
|
||||
return HAReplicaStatus{actives[0], [2]string{replicas[0], replicas[1]}}, nil
|
||||
}
|
||||
|
||||
func waitForReadyHA(t *testing.T, cli ce.ContainerInterface, qmReplicaIDs [3]string) {
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 4*time.Minute)
|
||||
defer cancel()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-time.After(1 * time.Second):
|
||||
for _, id := range qmReplicaIDs {
|
||||
rc, _ := execContainer(t, cli, id, "", []string{"chkmqready"})
|
||||
if rc == 0 {
|
||||
t.Log("MQ is ready")
|
||||
rc, _ := execContainer(t, cli, id, "", []string{"chkmqstarted"})
|
||||
if rc == 0 {
|
||||
t.Log("MQ has started")
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
case <-ctx.Done():
|
||||
t.Fatal("Timed out waiting for HA Queue Manager to become ready")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func waitForFailoverHA(t *testing.T, cli ce.ContainerInterface, replicas [2]string) {
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
|
||||
defer cancel()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-time.After(1 * time.Second):
|
||||
for _, id := range replicas {
|
||||
if status := getQueueManagerStatus(t, cli, id, "QM1"); status == "Running" {
|
||||
return
|
||||
}
|
||||
}
|
||||
case <-ctx.Done():
|
||||
t.Fatal("Timed out waiting for Native HA Queue Manager to failover to an available replica")
|
||||
}
|
||||
}
|
||||
}
|
||||
420
test/container/mqmetric_test.go
Normal file
420
test/container/mqmetric_test.go
Normal file
@@ -0,0 +1,420 @@
|
||||
/*
|
||||
© Copyright IBM Corporation 2018, 2023
|
||||
|
||||
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.
|
||||
*/
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
ce "github.com/ibm-messaging/mq-container/test/container/containerengine"
|
||||
)
|
||||
|
||||
func TestGoldenPathMetric(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cli := ce.NewContainerClient()
|
||||
id := runContainerWithPorts(t, cli, metricsContainerConfig(), []int{defaultMetricPort})
|
||||
defer cleanContainer(t, cli, id)
|
||||
|
||||
port, err := cli.GetContainerPort(id, defaultMetricPort)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Now the container is ready we prod the prometheus endpoint until it's up.
|
||||
waitForMetricReady(t, port)
|
||||
|
||||
// Call once as mq_prometheus 'ignores' the first call and will not return any metrics
|
||||
getMetrics(t, port)
|
||||
time.Sleep(15 * time.Second)
|
||||
|
||||
// Now actually get the metrics (after waiting for some to become available)
|
||||
metrics := getMetrics(t, port)
|
||||
if len(metrics) <= 0 {
|
||||
t.Error("Expected some metrics to be returned but had none...")
|
||||
}
|
||||
// Stop the container cleanly
|
||||
stopContainer(t, cli, id)
|
||||
}
|
||||
|
||||
func TestMetricNames(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cli := ce.NewContainerClient()
|
||||
id := runContainerWithPorts(t, cli, metricsContainerConfig(), []int{defaultMetricPort})
|
||||
defer cleanContainer(t, cli, id)
|
||||
|
||||
port, err := cli.GetContainerPort(id, defaultMetricPort)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Now the container is ready we prod the prometheus endpoint until it's up.
|
||||
waitForMetricReady(t, port)
|
||||
|
||||
// Call once as mq_prometheus 'ignores' the first call
|
||||
getMetrics(t, port)
|
||||
time.Sleep(15 * time.Second)
|
||||
|
||||
// Now actually get the metrics (after waiting for some to become available)
|
||||
metrics := getMetrics(t, port)
|
||||
names := metricNames()
|
||||
if len(metrics) != len(names) {
|
||||
t.Errorf("Expected %d metrics to be returned, received %d", len(names), len(metrics))
|
||||
}
|
||||
|
||||
// Check all the metrics have the correct names
|
||||
for _, metric := range metrics {
|
||||
ok := false
|
||||
for _, name := range names {
|
||||
if metric.Key == "ibmmq_qmgr_"+name {
|
||||
ok = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !ok {
|
||||
t.Errorf("Metric '%s' does not have the expected name", metric.Key)
|
||||
}
|
||||
}
|
||||
|
||||
// Stop the container cleanly
|
||||
stopContainer(t, cli, id)
|
||||
}
|
||||
|
||||
func TestMetricLabels(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cli := ce.NewContainerClient()
|
||||
requiredLabels := []string{"qmgr"}
|
||||
id := runContainerWithPorts(t, cli, metricsContainerConfig(), []int{defaultMetricPort})
|
||||
defer cleanContainer(t, cli, id)
|
||||
port, err := cli.GetContainerPort(id, defaultMetricPort)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Now the container is ready we prod the prometheus endpoint until it's up.
|
||||
waitForMetricReady(t, port)
|
||||
|
||||
// Call once as mq_prometheus 'ignores' the first call
|
||||
getMetrics(t, port)
|
||||
time.Sleep(15 * time.Second)
|
||||
|
||||
// Now actually get the metrics (after waiting for some to become available)
|
||||
metrics := getMetrics(t, port)
|
||||
if len(metrics) <= 0 {
|
||||
t.Error("Expected some metrics to be returned but had none")
|
||||
}
|
||||
|
||||
// Check all the metrics have the required labels
|
||||
for _, metric := range metrics {
|
||||
found := false
|
||||
for key := range metric.Labels {
|
||||
for _, e := range requiredLabels {
|
||||
if key == e {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if found {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
t.Errorf("Metric '%s' with labels %s does not have one or more required labels - %s", metric.Key, metric.Labels, requiredLabels)
|
||||
}
|
||||
}
|
||||
|
||||
// Stop the container cleanly
|
||||
stopContainer(t, cli, id)
|
||||
}
|
||||
|
||||
func TestRapidFirePrometheus(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cli := ce.NewContainerClient()
|
||||
id := runContainerWithPorts(t, cli, metricsContainerConfig(), []int{defaultMetricPort})
|
||||
defer cleanContainer(t, cli, id)
|
||||
port, err := cli.GetContainerPort(id, defaultMetricPort)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Now the container is ready we prod the prometheus endpoint until it's up.
|
||||
waitForMetricReady(t, port)
|
||||
|
||||
// Call once as mq_prometheus 'ignores' the first call and will not return any metrics
|
||||
getMetrics(t, port)
|
||||
|
||||
// Rapid fire it then check we're still happy
|
||||
for i := 0; i < 30; i++ {
|
||||
getMetrics(t, port)
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
time.Sleep(11 * time.Second)
|
||||
|
||||
// Now actually get the metrics (after waiting for some to become available)
|
||||
metrics := getMetrics(t, port)
|
||||
if len(metrics) <= 0 {
|
||||
t.Error("Expected some metrics to be returned but had none")
|
||||
}
|
||||
|
||||
// Stop the container cleanly
|
||||
stopContainer(t, cli, id)
|
||||
}
|
||||
|
||||
func TestSlowPrometheus(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cli := ce.NewContainerClient()
|
||||
id := runContainerWithPorts(t, cli, metricsContainerConfig(), []int{defaultMetricPort})
|
||||
defer cleanContainer(t, cli, id)
|
||||
port, err := cli.GetContainerPort(id, defaultMetricPort)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Now the container is ready we prod the prometheus endpoint until it's up.
|
||||
waitForMetricReady(t, port)
|
||||
|
||||
// Call once as mq_prometheus 'ignores' the first call and will not return any metrics
|
||||
getMetrics(t, port)
|
||||
|
||||
// Send a request twice over a long period and check we're still happy
|
||||
for i := 0; i < 2; i++ {
|
||||
time.Sleep(30 * time.Second)
|
||||
metrics := getMetrics(t, port)
|
||||
if len(metrics) <= 0 {
|
||||
t.Error("Expected some metrics to be returned but had none")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Stop the container cleanly
|
||||
stopContainer(t, cli, id)
|
||||
}
|
||||
|
||||
func TestContainerRestart(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cli := ce.NewContainerClient()
|
||||
id := runContainerWithPorts(t, cli, metricsContainerConfig(), []int{defaultMetricPort})
|
||||
defer cleanContainer(t, cli, id)
|
||||
port, err := cli.GetContainerPort(id, defaultMetricPort)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Now the container is ready we prod the prometheus endpoint until it's up.
|
||||
waitForMetricReady(t, port)
|
||||
|
||||
// Call once as mq_prometheus 'ignores' the first call and will not return any metrics
|
||||
getMetrics(t, port)
|
||||
time.Sleep(15 * time.Second)
|
||||
|
||||
// Now actually get the metrics (after waiting for some to become available)
|
||||
metrics := getMetrics(t, port)
|
||||
if len(metrics) <= 0 {
|
||||
t.Fatal("Expected some metrics to be returned before the restart but had none...")
|
||||
}
|
||||
|
||||
// Stop the container cleanly
|
||||
stopContainer(t, cli, id)
|
||||
// Start the container cleanly
|
||||
startContainer(t, cli, id)
|
||||
port, err = cli.GetContainerPort(id, defaultMetricPort)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Now the container is ready we prod the prometheus endpoint until it's up.
|
||||
waitForMetricReady(t, port)
|
||||
|
||||
// Call once as mq_prometheus 'ignores' the first call and will not return any metrics
|
||||
getMetrics(t, port)
|
||||
time.Sleep(15 * time.Second)
|
||||
|
||||
// Now actually get the metrics (after waiting for some to become available)
|
||||
metrics = getMetrics(t, port)
|
||||
if len(metrics) <= 0 {
|
||||
t.Error("Expected some metrics to be returned after the restart but had none...")
|
||||
}
|
||||
|
||||
// Stop the container cleanly
|
||||
stopContainer(t, cli, id)
|
||||
}
|
||||
|
||||
func TestQMRestart(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cli := ce.NewContainerClient()
|
||||
id := runContainerWithPorts(t, cli, metricsContainerConfig(), []int{defaultMetricPort})
|
||||
defer cleanContainer(t, cli, id)
|
||||
|
||||
port, err := cli.GetContainerPort(id, defaultMetricPort)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Now the container is ready we prod the prometheus endpoint until it's up.
|
||||
waitForMetricReady(t, port)
|
||||
|
||||
// Call once as mq_prometheus 'ignores' the first call and will not return any metrics
|
||||
getMetrics(t, port)
|
||||
time.Sleep(15 * time.Second)
|
||||
|
||||
// Now actually get the metrics (after waiting for some to become available)
|
||||
metrics := getMetrics(t, port)
|
||||
if len(metrics) <= 0 {
|
||||
t.Fatal("Expected some metrics to be returned before the restart but had none...")
|
||||
}
|
||||
|
||||
// Restart just the QM (to simulate a lost connection)
|
||||
t.Log("Stopping queue manager\n")
|
||||
rc, out := execContainer(t, cli, id, "", []string{"endmqm", "-w", "-r", defaultMetricQMName})
|
||||
if rc != 0 {
|
||||
t.Fatalf("Failed to stop the queue manager. rc=%d, err=%s", rc, out)
|
||||
}
|
||||
t.Log("starting queue manager\n")
|
||||
rc, out = execContainer(t, cli, id, "", []string{"strmqm", defaultMetricQMName})
|
||||
if rc != 0 {
|
||||
t.Fatalf("Failed to start the queue manager. rc=%d, err=%s", rc, out)
|
||||
}
|
||||
|
||||
// Wait for the queue manager to come back up
|
||||
time.Sleep(10 * time.Second)
|
||||
|
||||
// Now the container is ready we prod the prometheus endpoint until it's up.
|
||||
waitForMetricReady(t, port)
|
||||
|
||||
// Call once as mq_prometheus 'ignores' the first call and will not return any metrics
|
||||
getMetrics(t, port)
|
||||
time.Sleep(15 * time.Second)
|
||||
|
||||
// Now actually get the metrics (after waiting for some to become available)
|
||||
metrics = getMetrics(t, port)
|
||||
if len(metrics) <= 0 {
|
||||
t.Errorf("Expected some metrics to be returned after the restart but had none...")
|
||||
}
|
||||
|
||||
// Stop the container cleanly
|
||||
stopContainer(t, cli, id)
|
||||
}
|
||||
|
||||
func TestValidValues(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cli := ce.NewContainerClient()
|
||||
id := runContainerWithPorts(t, cli, metricsContainerConfig(), []int{defaultMetricPort})
|
||||
defer cleanContainer(t, cli, id)
|
||||
// hostname := getIPAddress(t, cli, id)
|
||||
port, err := cli.GetContainerPort(id, defaultMetricPort)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Now the container is ready we prod the prometheus endpoint until it's up.
|
||||
waitForMetricReady(t, port)
|
||||
|
||||
// Call once as mq_prometheus 'ignores' the first call and will not return any metrics
|
||||
getMetrics(t, port)
|
||||
time.Sleep(15 * time.Second)
|
||||
|
||||
// Now actually get the metrics (after waiting for some to become available)
|
||||
metrics := getMetrics(t, port)
|
||||
if len(metrics) <= 0 {
|
||||
t.Fatal("Expected some metrics to be returned but had none...")
|
||||
}
|
||||
|
||||
// Check that the values for each metric are valid numbers
|
||||
// can be either int, float or exponential - all these can be parsed by ParseFloat function
|
||||
for _, e := range metrics {
|
||||
if _, err := strconv.ParseFloat(e.Value, 64); err != nil {
|
||||
t.Errorf("Value (%s) for key (%s) is not a valid number", e.Value, e.Key)
|
||||
}
|
||||
}
|
||||
|
||||
// Stop the container cleanly
|
||||
stopContainer(t, cli, id)
|
||||
}
|
||||
|
||||
func TestChangingValues(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cli := ce.NewContainerClient()
|
||||
id := runContainerWithPorts(t, cli, metricsContainerConfig(), []int{1414, defaultMetricPort})
|
||||
defer cleanContainer(t, cli, id)
|
||||
// hostname := getIPAddress(t, cli, id)
|
||||
port, err := cli.GetContainerPort(id, defaultMetricPort)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Now the container is ready we prod the prometheus endpoint until it's up.
|
||||
waitForMetricReady(t, port)
|
||||
|
||||
// Call once as mq_prometheus 'ignores' the first call and will not return any metrics
|
||||
getMetrics(t, port)
|
||||
time.Sleep(15 * time.Second)
|
||||
|
||||
// Now actually get the metrics (after waiting for some to become available)
|
||||
metrics := getMetrics(t, port)
|
||||
if len(metrics) <= 0 {
|
||||
t.Fatal("Expected some metrics to be returned but had none...")
|
||||
}
|
||||
|
||||
// Check we have no FDC files to start
|
||||
for _, e := range metrics {
|
||||
if e.Key == "ibmmq_qmgr_mq_fdc_file_count" {
|
||||
if e.Value != "0" {
|
||||
t.Fatalf("Expected %s to have a value of 0 but was %s", e.Key, e.Value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Send invalid data to the MQ listener to generate a FDC
|
||||
noport, err := cli.GetContainerPort(id, 1414)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
listener := fmt.Sprintf("localhost:%s", noport)
|
||||
conn, err := net.Dial("tcp", listener)
|
||||
if err != nil {
|
||||
t.Fatalf("Could not connect to the listener - %v", err)
|
||||
}
|
||||
fmt.Fprintf(conn, "THIS WILL GENERATE A FDC!")
|
||||
conn.Close()
|
||||
|
||||
// Now actually get the metrics (after waiting for some to become available)
|
||||
time.Sleep(25 * time.Second)
|
||||
metrics = getMetrics(t, port)
|
||||
if len(metrics) <= 0 {
|
||||
t.Fatal("Expected some metrics to be returned but had none...")
|
||||
}
|
||||
|
||||
// Check that there is now 1 FDC file
|
||||
for _, e := range metrics {
|
||||
if e.Key == "ibmmq_qmgr_mq_fdc_file_count" {
|
||||
if e.Value != "1" {
|
||||
t.Fatalf("Expected %s to have a value of 1 but was %s", e.Key, e.Value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Stop the container cleanly
|
||||
stopContainer(t, cli, id)
|
||||
}
|
||||
259
test/container/mqmetric_test_util.go
Normal file
259
test/container/mqmetric_test_util.go
Normal file
@@ -0,0 +1,259 @@
|
||||
/*
|
||||
© Copyright IBM Corporation 2018, 2023
|
||||
|
||||
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.
|
||||
*/
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
ce "github.com/ibm-messaging/mq-container/test/container/containerengine"
|
||||
)
|
||||
|
||||
type mqmetric struct {
|
||||
Key string
|
||||
Value string
|
||||
Labels map[string]string
|
||||
}
|
||||
|
||||
const defaultMetricURL = "/metrics"
|
||||
const defaultMetricPort = 9157
|
||||
const defaultMQNamespace = "ibmmq"
|
||||
const defaultMetricQMName = "qm1"
|
||||
|
||||
func getMetrics(t *testing.T, port string) []mqmetric {
|
||||
returned := []mqmetric{}
|
||||
urlToUse := fmt.Sprintf("http://localhost:%s%s", port, defaultMetricURL)
|
||||
resp, err := http.Get(urlToUse)
|
||||
if err != nil {
|
||||
t.Fatalf("Error from HTTP GET for metrics: %v", err)
|
||||
return returned
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
metricsRaw, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
t.Fatalf("Error reading metrics data: %v", err)
|
||||
return returned
|
||||
}
|
||||
return convertRawMetricToMap(t, string(metricsRaw))
|
||||
}
|
||||
|
||||
// Also filters out all non "ibmmq" metrics
|
||||
func convertRawMetricToMap(t *testing.T, input string) []mqmetric {
|
||||
returnList := []mqmetric{}
|
||||
scanner := bufio.NewScanner(strings.NewReader(input))
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
if strings.HasPrefix(line, "#") {
|
||||
// Comment line of HELP or TYPE. Ignore
|
||||
continue
|
||||
}
|
||||
if !strings.HasPrefix(line, defaultMQNamespace) {
|
||||
// Not an ibmmq_ metric. Ignore
|
||||
continue
|
||||
}
|
||||
//It's an IBM MQ metric!
|
||||
key, value, labelMap, err := convertMetricLineToMetric(line)
|
||||
if err != nil {
|
||||
t.Fatalf("ibmmq_ metric could not be deciphered - %v", err)
|
||||
}
|
||||
|
||||
toAdd := mqmetric{
|
||||
Key: key,
|
||||
Value: value,
|
||||
Labels: labelMap,
|
||||
}
|
||||
|
||||
returnList = append(returnList, toAdd)
|
||||
}
|
||||
|
||||
return returnList
|
||||
}
|
||||
|
||||
func convertMetricLineToMetric(input string) (string, string, map[string]string, error) {
|
||||
// Lines are in the form "<key>{<labels>}<value>" or "<key> <value>"
|
||||
// Get the key and value while skipping the label
|
||||
var key, value string
|
||||
labelMap := make(map[string]string)
|
||||
if strings.Contains(input, "{") {
|
||||
// Get key
|
||||
splitted := strings.Split(input, "{")
|
||||
if len(splitted) != 2 {
|
||||
return "", "", labelMap, fmt.Errorf("Could not split by { Expected 2 but got %d - %s", len(splitted), input)
|
||||
}
|
||||
key = strings.TrimSpace(splitted[0])
|
||||
|
||||
// Get value
|
||||
splitted = strings.Split(splitted[1], "}")
|
||||
if len(splitted) != 2 {
|
||||
return "", "", labelMap, fmt.Errorf("Could not split by } Expected 2 but got %d - %s", len(splitted), input)
|
||||
}
|
||||
value = strings.TrimSpace(splitted[1])
|
||||
|
||||
// Get labels
|
||||
allLabels := strings.Split(splitted[0], ",")
|
||||
for _, e := range allLabels {
|
||||
labelPair := strings.Split(e, "=")
|
||||
if len(labelPair) != 2 {
|
||||
return "", "", labelMap, fmt.Errorf("Could not split label by '=' Expected 2 but got %d - %s", len(labelPair), e)
|
||||
}
|
||||
lkey := strings.TrimSpace(labelPair[0])
|
||||
lvalue := strings.TrimSpace(labelPair[1])
|
||||
lvalue = strings.Trim(lvalue, "\"")
|
||||
labelMap[lkey] = lvalue
|
||||
}
|
||||
|
||||
} else {
|
||||
splitted := strings.Split(input, " ")
|
||||
if len(splitted) != 2 {
|
||||
return "", "", labelMap, fmt.Errorf("Could not split by ' ' Expected 2 but got %d - %s", len(splitted), input)
|
||||
}
|
||||
key = strings.TrimSpace(splitted[0])
|
||||
value = strings.TrimSpace(splitted[1])
|
||||
}
|
||||
return key, value, labelMap, nil
|
||||
}
|
||||
|
||||
func waitForMetricReady(t *testing.T, port string) {
|
||||
timeout := 12 // 12 * 5 = 1 minute
|
||||
for i := 0; i < timeout; i++ {
|
||||
urlToUse := fmt.Sprintf("http://localhost:%s", port)
|
||||
resp, err := http.Get(urlToUse)
|
||||
if err == nil {
|
||||
resp.Body.Close()
|
||||
return
|
||||
}
|
||||
|
||||
time.Sleep(time.Second * 10)
|
||||
}
|
||||
t.Fatalf("Metric endpoint failed to startup in timely manner")
|
||||
}
|
||||
|
||||
func metricsContainerConfig() *ce.ContainerConfig {
|
||||
return &ce.ContainerConfig{
|
||||
Env: []string{
|
||||
"LICENSE=accept",
|
||||
"MQ_QMGR_NAME=" + defaultMetricQMName,
|
||||
"MQ_ENABLE_METRICS=true",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func metricNames() []string {
|
||||
|
||||
// NB: There are currently a total of 93 metrics, but the following 3 do not generate values (based on the queue manager configuration)
|
||||
// - log_occupied_by_reusable_extents_bytes
|
||||
// - log_occupied_by_extents_waiting_to_be_archived_bytes
|
||||
// - log_required_for_media_recovery_bytes
|
||||
|
||||
names := []string{
|
||||
"cpu_load_one_minute_average_percentage",
|
||||
"cpu_load_five_minute_average_percentage",
|
||||
"cpu_load_fifteen_minute_average_percentage",
|
||||
"system_cpu_time_percentage",
|
||||
"user_cpu_time_percentage",
|
||||
"ram_free_percentage",
|
||||
// disabled : "system_ram_size_bytes",
|
||||
"system_cpu_time_estimate_for_queue_manager_percentage",
|
||||
"user_cpu_time_estimate_for_queue_manager_percentage",
|
||||
"ram_usage_estimate_for_queue_manager_bytes",
|
||||
"trace_file_system_free_space_percentage",
|
||||
"trace_file_system_in_use_bytes",
|
||||
"errors_file_system_free_space_percentage",
|
||||
"errors_file_system_in_use_bytes",
|
||||
"fdc_files",
|
||||
"queue_manager_file_system_free_space_percentage",
|
||||
"queue_manager_file_system_in_use_bytes",
|
||||
"log_logical_written_bytes_total",
|
||||
"log_physical_written_bytes_total",
|
||||
"log_primary_space_in_use_percentage",
|
||||
"log_workload_primary_space_utilization_percentage",
|
||||
"log_write_latency_seconds",
|
||||
"log_max_bytes",
|
||||
"log_write_size_bytes",
|
||||
"log_in_use_bytes",
|
||||
"log_file_system_max_bytes",
|
||||
"log_file_system_in_use_bytes",
|
||||
"durable_subscription_create_total",
|
||||
"durable_subscription_alter_total",
|
||||
"durable_subscription_resume_total",
|
||||
"durable_subscription_delete_total",
|
||||
"non_durable_subscription_create_total",
|
||||
"non_durable_subscription_delete_total",
|
||||
"failed_subscription_create_alter_resume_total",
|
||||
"failed_subscription_delete_total",
|
||||
"mqsubrq_total",
|
||||
"failed_mqsubrq_total",
|
||||
// disabled : "durable_subscriber_high_water_mark",
|
||||
// disabled : "durable_subscriber_low_water_mark",
|
||||
// disabled : "non_durable_subscriber_high_water_mark",
|
||||
// disabled : "non_durable_subscriber_low_water_mark",
|
||||
"topic_mqput_mqput1_total",
|
||||
"topic_put_bytes_total",
|
||||
"failed_topic_mqput_mqput1_total",
|
||||
"persistent_topic_mqput_mqput1_total",
|
||||
"non_persistent_topic_mqput_mqput1_total",
|
||||
"published_to_subscribers_message_total",
|
||||
"published_to_subscribers_bytes_total",
|
||||
"mqconn_mqconnx_total",
|
||||
"failed_mqconn_mqconnx_total",
|
||||
"mqdisc_total",
|
||||
// disabled : "concurrent_connections_high_water_mark",
|
||||
"mqopen_total",
|
||||
"failed_mqopen_total",
|
||||
"mqclose_total",
|
||||
"failed_mqclose_total",
|
||||
"mqinq_total",
|
||||
"failed_mqinq_total",
|
||||
"mqset_total",
|
||||
"failed_mqset_total",
|
||||
"persistent_message_mqput_total",
|
||||
"persistent_message_mqput1_total",
|
||||
"persistent_message_put_bytes_total",
|
||||
"non_persistent_message_mqput_total",
|
||||
"non_persistent_message_mqput1_total",
|
||||
"non_persistent_message_put_bytes_total",
|
||||
"mqput_mqput1_total",
|
||||
"mqput_mqput1_bytes_total",
|
||||
"failed_mqput_total",
|
||||
"failed_mqput1_total",
|
||||
"mqstat_total",
|
||||
"persistent_message_destructive_get_total",
|
||||
"persistent_message_browse_total",
|
||||
"persistent_message_get_bytes_total",
|
||||
"persistent_message_browse_bytes_total",
|
||||
"non_persistent_message_destructive_get_total",
|
||||
"non_persistent_message_browse_total",
|
||||
"non_persistent_message_get_bytes_total",
|
||||
"non_persistent_message_browse_bytes_total",
|
||||
"destructive_get_total",
|
||||
"destructive_get_bytes_total",
|
||||
"failed_mqget_total",
|
||||
"failed_browse_total",
|
||||
"mqctl_total",
|
||||
"expired_message_total",
|
||||
"purged_queue_total",
|
||||
"mqcb_total",
|
||||
"failed_mqcb_total",
|
||||
"commit_total",
|
||||
"rollback_total",
|
||||
}
|
||||
return names
|
||||
}
|
||||
39
test/container/pathutils/path_clean.go
Normal file
39
test/container/pathutils/path_clean.go
Normal file
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
© Copyright IBM Corporation 2024
|
||||
|
||||
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.
|
||||
*/
|
||||
|
||||
// Package pathutils contains code to provide sanitised file paths
|
||||
package pathutils
|
||||
|
||||
import (
|
||||
"path"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// CleanPath returns the result of joining a series of sanitised file paths (preventing directory traversal for each path)
|
||||
// If the first path is relative, a relative path is returned
|
||||
func CleanPath(paths ...string) string {
|
||||
if len(paths) == 0 {
|
||||
return ""
|
||||
}
|
||||
var combined string
|
||||
if !path.IsAbs(paths[0]) {
|
||||
combined = "./"
|
||||
}
|
||||
for _, part := range paths {
|
||||
combined = filepath.Join(combined, filepath.FromSlash(path.Clean("/"+part)))
|
||||
}
|
||||
return combined
|
||||
}
|
||||
7
test/messaging/.gitignore
vendored
Normal file
7
test/messaging/.gitignore
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
.classpath
|
||||
.gradle
|
||||
*.class
|
||||
.project
|
||||
.settings
|
||||
bin
|
||||
|
||||
38
test/messaging/Dockerfile
Normal file
38
test/messaging/Dockerfile
Normal file
@@ -0,0 +1,38 @@
|
||||
# © Copyright IBM Corporation 2018, 2022
|
||||
#
|
||||
# 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.
|
||||
|
||||
###############################################################################
|
||||
# Application build environment (Maven)
|
||||
###############################################################################
|
||||
FROM registry.access.redhat.com/ubi8/openjdk-8 as builder
|
||||
COPY pom.xml ./
|
||||
#WORKDIR /usr/src/mymaven
|
||||
# Download dependencies separately, so Docker caches them
|
||||
RUN mvn dependency:go-offline install
|
||||
# Copy source
|
||||
COPY src ./src
|
||||
# Run the main build
|
||||
RUN mvn --offline install
|
||||
# Print a list of all the files (useful for debugging)
|
||||
RUN find ./
|
||||
|
||||
###############################################################################
|
||||
# Application runtime (JRE only, no build environment)
|
||||
###############################################################################
|
||||
# OpenJDK is not technically supported with the MQ client, but is good enough for these tests
|
||||
FROM registry.access.redhat.com/ubi8/openjdk-8-runtime
|
||||
COPY --from=builder /home/jboss/target/*.jar /opt/app/
|
||||
COPY --from=builder /home/jboss/target/lib/*.jar /opt/app/
|
||||
USER 1001
|
||||
ENTRYPOINT ["java", "-classpath", "/opt/app/*", "org.junit.platform.console.ConsoleLauncher", "--fail-if-no-tests", "-p", "com.ibm.mqcontainer.test", "--details", "verbose"]
|
||||
84
test/messaging/buildah.sh
Executable file
84
test/messaging/buildah.sh
Executable file
@@ -0,0 +1,84 @@
|
||||
#!/bin/bash
|
||||
# © Copyright IBM Corporation 2018, 2019
|
||||
#
|
||||
# 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.
|
||||
|
||||
set -x
|
||||
set -e
|
||||
|
||||
###############################################################################
|
||||
# Setup MQ JMS Test container
|
||||
###############################################################################
|
||||
|
||||
# Use a "scratch" container, so the resulting image has minimal files
|
||||
# Resulting image won't have yum, for example
|
||||
readonly ctr_mq=$(buildah from rhel7-minimal)
|
||||
readonly mnt_mq=$(buildah mount $ctr_mq)
|
||||
readonly imagename=$1
|
||||
|
||||
microdnf_opts="--nodocs"
|
||||
# Check whether the host is registered with Red Hat
|
||||
if subscription-manager status ; then
|
||||
# Host is subscribed, but the minimal image has no enabled repos
|
||||
# Note that the "bc" package is the only one in "extras"
|
||||
microdnf_opts="${microdnf_opts} --enablerepo=rhel-7-server-rpms --enablerepo=rhel-7-server-extras-rpms"
|
||||
else
|
||||
# Use the Yum repositories configured on the host
|
||||
cp -R /etc/yum.repos.d/* ${mnt_mq}/etc/yum.repos.d/
|
||||
fi
|
||||
buildah run ${ctr_mq} -- microdnf ${microdnf_opts} install \
|
||||
java-1.8.0-openjdk-devel \
|
||||
java \
|
||||
which \
|
||||
wget
|
||||
|
||||
buildah run $ctr_mq -- sh -c "cd /tmp && wget https://www-eu.apache.org/dist/maven/maven-3/3.6.0/binaries/apache-maven-3.6.0-bin.tar.gz"
|
||||
tar xvf $mnt_mq/tmp/apache-maven-3.6.0-bin.tar.gz -C $mnt_mq/tmp/
|
||||
|
||||
mkdir -p $mnt_mq/usr/src/mymaven
|
||||
cp pom.xml $mnt_mq/usr/src/mymaven/
|
||||
cp -R src $mnt_mq/usr/src/mymaven/src
|
||||
|
||||
buildah run $ctr_mq -- sh -c "cd /usr/src/mymaven && export M2_HOME=/tmp/apache-maven-3.6.0 && export M2=\$M2_HOME/bin && export PATH=\$M2:\$PATH && mvn --version && mvn dependency:go-offline install && mvn --offline install"
|
||||
|
||||
mkdir -p $mnt_mq/opt/app
|
||||
|
||||
cp $mnt_mq/usr/src/mymaven/target/*.jar $mnt_mq/opt/app/
|
||||
cp $mnt_mq/usr/src/mymaven/target/lib/*.jar $mnt_mq/opt/app/
|
||||
|
||||
###############################################################################
|
||||
# Post install tidy up
|
||||
###############################################################################
|
||||
|
||||
rm -rf $mnt_mq/tmp/*
|
||||
rm -rf $mnt_mq/usr/src/mymaven
|
||||
|
||||
# Clean up cached files
|
||||
buildah run ${ctr_mq} -- microdnf ${microdnf_opts} clean all
|
||||
rm -rf ${mnt_mq}/etc/yum.repos.d/*
|
||||
|
||||
###############################################################################
|
||||
# Contain image finalization
|
||||
###############################################################################
|
||||
|
||||
buildah config \
|
||||
--os linux \
|
||||
--label architecture=amd64 \
|
||||
--label name="${imagename%:*}" \
|
||||
--cmd "" \
|
||||
--entrypoint '["java", "-classpath", "/opt/app/*", "org.junit.platform.console.ConsoleLauncher", "-p", "com.ibm.mqcontainer.test", "--details", "verbose"]' \
|
||||
$ctr_mq
|
||||
buildah unmount $ctr_mq
|
||||
buildah commit $ctr_mq $imagename
|
||||
|
||||
buildah rm $ctr_mq
|
||||
73
test/messaging/pom.xml
Normal file
73
test/messaging/pom.xml
Normal file
@@ -0,0 +1,73 @@
|
||||
<!--
|
||||
© Copyright IBM Corporation 2018, 2023
|
||||
|
||||
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.
|
||||
-->
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>com.ibm.mq.container</groupId>
|
||||
<artifactId>test-messaging</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<version>dev</version>
|
||||
<name>test-messaging</name>
|
||||
<url>http://maven.apache.org</url>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.ibm.mq</groupId>
|
||||
<artifactId>com.ibm.mq.allclient</artifactId>
|
||||
<version>9.3.0.0</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-api</artifactId>
|
||||
<version>5.10.0</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-engine</artifactId>
|
||||
<version>5.10.0</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.platform</groupId>
|
||||
<artifactId>junit-platform-console-standalone</artifactId>
|
||||
<version>1.10.0</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<artifactId>maven-dependency-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>install</phase>
|
||||
<goals>
|
||||
<goal>copy-dependencies</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<outputDirectory>${project.build.directory}/lib</outputDirectory>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
||||
</properties>
|
||||
</project>
|
||||
@@ -0,0 +1,170 @@
|
||||
/*
|
||||
© Copyright IBM Corporation 2018, 2023
|
||||
|
||||
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.
|
||||
*/
|
||||
package com.ibm.mqcontainer.test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.net.Socket;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.KeyStore;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.jms.JMSContext;
|
||||
import javax.jms.JMSException;
|
||||
import javax.jms.Message;
|
||||
import javax.jms.Queue;
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.SSLSocketFactory;
|
||||
import javax.net.ssl.TrustManagerFactory;
|
||||
|
||||
import com.ibm.mq.MQException;
|
||||
import com.ibm.mq.constants.MQConstants;
|
||||
import com.ibm.mq.jms.MQConnectionFactory;
|
||||
import com.ibm.mq.jms.MQQueue;
|
||||
import com.ibm.msg.client.wmq.WMQConstants;
|
||||
import com.ibm.msg.client.jms.DetailedJMSSecurityRuntimeException;
|
||||
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.TestInfo;
|
||||
|
||||
class JMSTests {
|
||||
private static final Logger LOGGER = Logger.getLogger(JMSTests.class.getName());
|
||||
protected static final String ADDR = System.getenv("MQ_PORT_1414_TCP_ADDR");
|
||||
protected static final String USER = System.getenv("MQ_USERNAME");
|
||||
protected static final String PORT = System.getenv().getOrDefault("MQ_PORT_1414_OVERRIDE", "1414");
|
||||
protected static final String PASSWORD = System.getenv("MQ_PASSWORD");
|
||||
protected static final String CHANNEL = System.getenv("MQ_CHANNEL");
|
||||
protected static final String TRUSTSTORE = System.getenv("MQ_TLS_TRUSTSTORE");
|
||||
protected static final String PASSPHRASE = System.getenv("MQ_TLS_PASSPHRASE");
|
||||
private JMSContext context;
|
||||
|
||||
static SSLSocketFactory createSSLSocketFactory() throws IOException, GeneralSecurityException {
|
||||
KeyStore ts=KeyStore.getInstance("jks");
|
||||
ts.load(new FileInputStream(TRUSTSTORE), PASSPHRASE.toCharArray());
|
||||
TrustManagerFactory tmf=TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
|
||||
tmf.init(ts);
|
||||
SSLContext ctx = SSLContext.getInstance("TLSv1.2");
|
||||
ctx.init(null, tmf.getTrustManagers(), null);
|
||||
return ctx.getSocketFactory();
|
||||
}
|
||||
|
||||
static MQConnectionFactory createMQConnectionFactory(String channel, String addr, String port) throws JMSException, IOException, GeneralSecurityException {
|
||||
MQConnectionFactory factory = new MQConnectionFactory();
|
||||
factory.setTransportType(WMQConstants.WMQ_CM_CLIENT);
|
||||
factory.setChannel(channel);
|
||||
factory.setConnectionNameList(String.format("%s(%s)", addr, port));
|
||||
if (TRUSTSTORE == null) {
|
||||
LOGGER.info("Not using TLS");
|
||||
}
|
||||
else {
|
||||
LOGGER.info(String.format("Using TLS. Trust store=%s", TRUSTSTORE));
|
||||
SSLSocketFactory ssl = createSSLSocketFactory();
|
||||
factory.setSSLSocketFactory(ssl);
|
||||
boolean ibmjre = System.getenv("IBMJRE").equals("true");
|
||||
if (ibmjre){
|
||||
System.setProperty("com.ibm.mq.cfg.useIBMCipherMappings", "true");
|
||||
} else {
|
||||
System.setProperty("com.ibm.mq.cfg.useIBMCipherMappings", "false");
|
||||
}
|
||||
factory.setSSLCipherSuite(System.getenv("MQ_TLS_CIPHER"));
|
||||
}
|
||||
return factory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a JMSContext with the supplied user and password.
|
||||
*/
|
||||
static JMSContext create(String channel, String addr, String port, String user, String password) throws JMSException, IOException, GeneralSecurityException {
|
||||
LOGGER.info(String.format("Connecting to %s/TCP/%s(%s) as %s", channel, addr, port, user));
|
||||
MQConnectionFactory factory = createMQConnectionFactory(channel, addr, port);
|
||||
// If a password is set, make sure it gets sent to the queue manager for authentication
|
||||
if (password != null) {
|
||||
factory.setBooleanProperty(WMQConstants.USER_AUTHENTICATION_MQCSP, true);
|
||||
}
|
||||
LOGGER.info(String.format("CSP authentication: %s", factory.getBooleanProperty(WMQConstants.USER_AUTHENTICATION_MQCSP)));
|
||||
return factory.createContext(user, password);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a JMSContext with the default user identity (from the OS)
|
||||
*/
|
||||
static JMSContext create(String channel, String addr, String port) throws JMSException, IOException, GeneralSecurityException {
|
||||
LOGGER.info(String.format("Connecting to %s/TCP/%s(%s) as OS user '%s'", channel, addr, port, System.getProperty("user.name")));
|
||||
MQConnectionFactory factory = createMQConnectionFactory(channel, addr, port);
|
||||
LOGGER.info(String.format("CSP authentication: %s", factory.getBooleanProperty(WMQConstants.USER_AUTHENTICATION_MQCSP)));
|
||||
return factory.createContext();
|
||||
}
|
||||
|
||||
@BeforeAll
|
||||
private static void waitForQueueManager() {
|
||||
for (int i = 0; i < 20; i++) {
|
||||
try {
|
||||
Socket s = new Socket(ADDR, Integer.parseInt(PORT));
|
||||
s.close();
|
||||
return;
|
||||
} catch (IOException e) {
|
||||
try {
|
||||
Thread.sleep(500);
|
||||
} catch (InterruptedException ex) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void putGetTest(TestInfo t) throws Exception {
|
||||
context = create(CHANNEL, ADDR, PORT, USER, PASSWORD);
|
||||
Queue queue = new MQQueue("DEV.QUEUE.1");
|
||||
context.createProducer().send(queue, t.getDisplayName());
|
||||
Message m = context.createConsumer(queue).receive();
|
||||
assertNotNull(m.getBody(String.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void defaultIdentityTest(TestInfo t) throws Exception {
|
||||
LOGGER.info(String.format("Password='%s'", PASSWORD));
|
||||
try {
|
||||
// Don't pass a user/password, which should cause the default identity to be used
|
||||
context = create(CHANNEL, ADDR, PORT);
|
||||
} catch (DetailedJMSSecurityRuntimeException ex) {
|
||||
Throwable cause = ex.getCause();
|
||||
assertNotNull(cause);
|
||||
assertTrue(cause instanceof MQException);
|
||||
assertEquals(MQConstants.MQRC_NOT_AUTHORIZED, ((MQException)cause).getReason());
|
||||
return;
|
||||
}
|
||||
// The default developer config allows any user to appear as "app", and use a blank password. This is done with the MCAUSER on the channel.
|
||||
// If this test is run on a queue manager without a password set, then it should be possible to connect without exception.
|
||||
// If this test is run on a queue manager with a password set, then an exception should be thrown, because this test doesn't send a password.
|
||||
if ((PASSWORD != null) && (PASSWORD != "")) {
|
||||
fail("Exception not thrown");
|
||||
}
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDown() {
|
||||
if (context != null) {
|
||||
context.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
BIN
test/tls/client-trust.jks
Normal file
BIN
test/tls/client-trust.jks
Normal file
Binary file not shown.
42
test/tls/generate-test-cert.sh
Executable file
42
test/tls/generate-test-cert.sh
Executable file
@@ -0,0 +1,42 @@
|
||||
#!/bin/bash -ex
|
||||
# -*- mode: sh -*-
|
||||
# © Copyright IBM Corporation 2018, 2021
|
||||
#
|
||||
# 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.
|
||||
|
||||
KEY=server.key
|
||||
CERT=server.crt
|
||||
PKCS=server.p12
|
||||
PASSWORD=passw0rd
|
||||
|
||||
# Create a private key and certificate in PEM format, for the server to use
|
||||
openssl req \
|
||||
-newkey rsa:2048 -nodes -keyout ${KEY} \
|
||||
-subj "/CN=localhost" \
|
||||
-addext "subjectAltName = DNS:localhost" \
|
||||
-x509 -days 3650 -out ${CERT}
|
||||
|
||||
# Add the key and certificate to a PKCS #12 key store, for the server to use
|
||||
openssl pkcs12 \
|
||||
-inkey ${KEY} \
|
||||
-in ${CERT} \
|
||||
-export -out ${PKCS} \
|
||||
-password pass:${PASSWORD}
|
||||
|
||||
# Add the certificate to a trust store in JKS format, for Java clients to use when connecting
|
||||
keytool -import \
|
||||
-alias server-cert \
|
||||
-file ${CERT} \
|
||||
-keystore client-trust.jks \
|
||||
-storepass ${PASSWORD} \
|
||||
-noprompt
|
||||
17
test/tls/server.crt
Normal file
17
test/tls/server.crt
Normal file
@@ -0,0 +1,17 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIICwzCCAaugAwIBAgIJAKnwG0VGiDelMA0GCSqGSIb3DQEBCwUAMBQxEjAQBgNV
|
||||
BAMMCWxvY2FsaG9zdDAeFw0yMTA5MDYxNTIyMDlaFw0zMTA5MDQxNTIyMDlaMBQx
|
||||
EjAQBgNVBAMMCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
|
||||
ggEBALLmDX3OB/4DlzJzKz/Edc5qVdjdIN/u8pApSQPevT0mAsSK1uw2MObeOo4C
|
||||
DCBmabYeuvGzZ4t3SiejdsHK+qAYMFW51lxTbulv5kUPvTBOY2JCENkVDFjqcK8S
|
||||
9ItI/UzTmnBolvZmUKzROHzA/pFb/jkhlzqJO+TqIBXKLF5gdFFTiHHcqfoUyVOV
|
||||
n+49V8z6W2rokz4QIWa5Dlh6VS1B6VXdihJv5P9HV8P8FOtefhA85yaSVKlFS/AC
|
||||
XRb5FmtmYHBnghLktHS71s/KcPeX27Q1NcKhmZMvHRH95hqEcP25S6SGu69eiCLk
|
||||
xpbJKqG3fntfooLUDfR2PHQUJ7UCAwEAAaMYMBYwFAYDVR0RBA0wC4IJbG9jYWxo
|
||||
b3N0MA0GCSqGSIb3DQEBCwUAA4IBAQBamIH23oDh1XxOeMPUEyPLHm5M8LM8FNhT
|
||||
GEpf1ICy4TSLipSFhIs3hGzVt22zBBzU59apQ8rXUME5SK+9PLag/t/48rG1SfUA
|
||||
VyYvaeu/cA5NQMWwuyCLqZL1MWn+BLsdAiNtbNHANesnl0i+vUb0GPzSP8soe3PP
|
||||
N7Fh8SO3Qq6e9zT3iE2tP2OFxzcpg538Xn3qoVPJwmLIfBtvsiK07zqAWdqBWtt6
|
||||
cBXyagnmgKvOyv6sKAlTpwP9HqVem3XxZVrhm1KiPHs4Dnks6e79txmB8lqzvWu5
|
||||
tu4h2ePGJjqUy5JkkoDY0j6hALwEe3ZXBvJ6XUQDi9Hou2k+MaQd
|
||||
-----END CERTIFICATE-----
|
||||
28
test/tls/server.key
Normal file
28
test/tls/server.key
Normal file
@@ -0,0 +1,28 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCy5g19zgf+A5cy
|
||||
cys/xHXOalXY3SDf7vKQKUkD3r09JgLEitbsNjDm3jqOAgwgZmm2Hrrxs2eLd0on
|
||||
o3bByvqgGDBVudZcU27pb+ZFD70wTmNiQhDZFQxY6nCvEvSLSP1M05pwaJb2ZlCs
|
||||
0Th8wP6RW/45IZc6iTvk6iAVyixeYHRRU4hx3Kn6FMlTlZ/uPVfM+ltq6JM+ECFm
|
||||
uQ5YelUtQelV3YoSb+T/R1fD/BTrXn4QPOcmklSpRUvwAl0W+RZrZmBwZ4IS5LR0
|
||||
u9bPynD3l9u0NTXCoZmTLx0R/eYahHD9uUukhruvXogi5MaWySqht357X6KC1A30
|
||||
djx0FCe1AgMBAAECggEAagD5A49+mtwjzigB+4H80Def8KVuomIi5psgAaQM+9u3
|
||||
DiC6ozKlHVeW2KiL6PLmNpzU5v0IINKpZP1uE/yjLxPGKDW6t/BUKww8JLXjw2jf
|
||||
aMx+0TKwo0sfRA32S0YPmWNVAsBmm1AbA5vhXcK51QXuiInH406H5+d25ZJrYevF
|
||||
liKWSjx9CM/0XO7t20j18mCa8RjBEdsZoHxHsoWNvFJ6DCR25cFShAhR7s4OtkUk
|
||||
yELm1tYYrFOffUM0Q/Fp9uSlCHWMSqPtf/6NEfnszfFEtzDh/N+YqC1Bexv0XPsD
|
||||
dBPOkUZjWA2Sc8Se1t2GLfrRURzj3GvWH1+GssjsAQKBgQDeIdyzQSqce4Kn0opa
|
||||
vdS5moCiv3pyfNd0nYe0awgVos4kHY7/nBq0eyMZAatRHeD3DunVsw3LmvWyEw53
|
||||
app7MTTjVrYaadoBlB6jy2elyF5RcW2jGchZExoNh+0ZQWUiMbYEozPLQTy9ZxMz
|
||||
t0OcZ1hHPngGgmj5TELZKkwEtQKBgQDOLLh7pdKrdudtim+o4H20jl5yYKl25Iq/
|
||||
DKVodwUd94cM7xAIOQJrx2XK/YPCfRkKRN1wxzAhYdIVkaaKDVhI8Jeu+H18QOa6
|
||||
5OlzzZcqJCtACpbVqLaDcmq8pRrAYekiwMIKwC95llvktjilvLfoUnQoXAaX8E8B
|
||||
yCSUvDh3AQKBgQCxa0h04DLho4Da/D3HdmHHERF3bAqoEPCh0wTF5MsjRNLzY6yI
|
||||
mq11w/hni77C3mOF0SKRrh7xpcZiQfhHBx12EfpVLjfq5uraYe0LFHanol87G6bf
|
||||
I8Oy6Z/geNW2W1YktqHUGGpRCL0z5nUe1FyrOpv2431Ibbbcj73A6JipFQKBgHdl
|
||||
vJyWpk73+AQe1JUnFIU4oYd5ZQpeRd9n8m5x5ru4+jPKSi2I3lcOTWvlrqU2Dwc8
|
||||
ZEUIhV3/qUsmYxy1p7ft5NnGO912NGhtYqjWmcEk2wsmVr17C99JpniC4OAik4G1
|
||||
wWm6bIPsSGFGCb4pcROQlIY+7O6WkxqEDnM4ITcBAoGAHXBKmadFpupUeGSkCwEo
|
||||
/VjeI4QoKKcWj9K8z8ifCVPz1FiQ1AJ91WMTM7PAmpEDX058Hor9xxJ2bEtQFwUS
|
||||
QKvjeU+/Ig0TWjsJBgBPvc0xYLaJptAbjvG4a5nBn7hwbRzLTcKx2OVTmdAkz00H
|
||||
1lq8cwizfwNgt8ldFFDDRvw=
|
||||
-----END PRIVATE KEY-----
|
||||
BIN
test/tls/server.p12
Normal file
BIN
test/tls/server.p12
Normal file
Binary file not shown.
23
test/tlscacert/cacert.crt
Normal file
23
test/tlscacert/cacert.crt
Normal file
@@ -0,0 +1,23 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDxTCCAq2gAwIBAgIUc5EKoPi8cg2M2n+SqCPn44LFjoAwDQYJKoZIhvcNAQEL
|
||||
BQAwcjELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAk5ZMREwDwYDVQQHDAhOZXcgWW9y
|
||||
azEMMAoGA1UECgwDSUJNMQwwCgYDVQQLDANJQk0xDDAKBgNVBAMMA0lCTTEZMBcG
|
||||
CSqGSIb3DQEJARYKbXFAaWJtLmNvbTAeFw0yMjEwMDYxMzA2NTVaFw0zMjEwMDMx
|
||||
MzA2NTVaMHIxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJOWTERMA8GA1UEBwwITmV3
|
||||
IFlvcmsxDDAKBgNVBAoMA0lCTTEMMAoGA1UECwwDSUJNMQwwCgYDVQQDDANJQk0x
|
||||
GTAXBgkqhkiG9w0BCQEWCm1xQGlibS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IB
|
||||
DwAwggEKAoIBAQCls3oNIDxzKct0NXVsoz1Hng3BcaDPcBRYCNgAEwDOVe3rEEbZ
|
||||
d2KFliDgCG3hCHMM1Yaabx3iTVsKklubBxr1JFmyDtgb4z9mJpMVYXS+gsKsZOs/
|
||||
vNSmzpt5VlbEadHKJ/aFf/EWxvoOP80UiEeUJt36aWFUTyjjyArd2xS8fD1DATFB
|
||||
U2bteaWfkpuLeFiTtwftZhsLv1s5T35+Ex087eX1tkm/TArxZsNl/9RrSWsbJh/t
|
||||
bjiRKn+fCZdirFsurP3Si5Jd9laCW0RBKAKYEh40XYDgjLhvcazDPTBueTHXQPG5
|
||||
S0hCOhCJiCWpPCsh8rIOCz0D9YIByZADR1WvAgMBAAGjUzBRMB0GA1UdDgQWBBS5
|
||||
OsiPqZXlMwpMqGKczUg3qVvy0zAfBgNVHSMEGDAWgBS5OsiPqZXlMwpMqGKczUg3
|
||||
qVvy0zAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQBfwYRcckke
|
||||
/NzDHlFb8TBlUDqERmlT/qTWamVZO2Zuo4Y0BFOYFEA23F5sQU2s2MFSEZcAKe5v
|
||||
mJroFE2rr4aY4bJ4Z0UXlOAYyqNxVOTI4MIxwbg3GVr8c8oWBnAmgqI9W9OpgZ52
|
||||
/bN24XL9s6I3TeOTtYI9z5O70Kl/E3nG8GcfMw0EtNIy0UPUWvJH8FgEsotsRO9v
|
||||
tPtlZklEK/D+Keozbs2shdNhKgVnDatpdTBqvwLztb1+te5AckuOnJsnG+iIrG2D
|
||||
Ehoq2O3gktIVdAk4sv2BoONzegLWB+GSxGVZsemfYF4PkN9/w+znz0LK/ATAtabK
|
||||
rikk0yC+Xg8z
|
||||
-----END CERTIFICATE-----
|
||||
34
test/tlscacert/generate-test-cert.sh
Normal file
34
test/tlscacert/generate-test-cert.sh
Normal file
@@ -0,0 +1,34 @@
|
||||
#!/bin/bash -ex
|
||||
# -*- mode: sh -*-
|
||||
# © Copyright IBM Corporation 2018, 2022
|
||||
#
|
||||
# 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.
|
||||
|
||||
KEY=server.key
|
||||
CERT=server.crt
|
||||
CACERT=cacert.crt
|
||||
CAPEM=rootcakey.pem
|
||||
|
||||
# Create a private key and certificate in PEM format, for the server to use
|
||||
openssl req \
|
||||
-newkey rsa:2048 -nodes -keyout ${KEY} \
|
||||
-subj "/CN=localhost" \
|
||||
-addext "subjectAltName = DNS:localhost" \
|
||||
-x509 -days 3650 -out ${CERT}
|
||||
|
||||
# Generate the private key of the root CA
|
||||
openssl genrsa -out ${CAPEM} 2048
|
||||
|
||||
#Generate the self-signed root CA certificate. Manual input is required when prompted
|
||||
openssl req -x509 -sha256 -new -nodes -key ${CAPEM} -days 3650 -out ${CACERT}
|
||||
|
||||
27
test/tlscacert/rootcakey.pem
Normal file
27
test/tlscacert/rootcakey.pem
Normal file
@@ -0,0 +1,27 @@
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEowIBAAKCAQEApbN6DSA8cynLdDV1bKM9R54NwXGgz3AUWAjYABMAzlXt6xBG
|
||||
2XdihZYg4Aht4QhzDNWGmm8d4k1bCpJbmwca9SRZsg7YG+M/ZiaTFWF0voLCrGTr
|
||||
P7zUps6beVZWxGnRyif2hX/xFsb6Dj/NFIhHlCbd+mlhVE8o48gK3dsUvHw9QwEx
|
||||
QVNm7Xmln5Kbi3hYk7cH7WYbC79bOU9+fhMdPO3l9bZJv0wK8WbDZf/Ua0lrGyYf
|
||||
7W44kSp/nwmXYqxbLqz90ouSXfZWgltEQSgCmBIeNF2A4Iy4b3Gswz0wbnkx10Dx
|
||||
uUtIQjoQiYglqTwrIfKyDgs9A/WCAcmQA0dVrwIDAQABAoIBAQCcL9ZltPMF4mlh
|
||||
+lnasuu6K+LvafmYTh7+9CcVutPRqfF+1nLR3NRC8sW+JnPb36kCeepMe1yByUR9
|
||||
bINoV4QzebYKPi+56bQCx21wg9IVGRACi4WrKISRTsIB1z4mGVCj6pNWNsi7HYbq
|
||||
E31tUx+VKCWoOdiCLbNvMUn84Npk5npK9P9F86qypSJqJv3HORgOa58x7qZiD2fk
|
||||
TroLuGHKFWGtSiK1vvgax8gBwMi9JvWoPhwHagINh0WwT820+3/4KbqcsvRNSIu8
|
||||
qA+ltk/Vt0ftwPMpxPYnvRFrSvzYIRE04fbWqA3mxhPr/oP3xXrwyd1hnX6GzPIR
|
||||
KXeX1i7BAoGBANGV6XtL8cq8tu/4emOYDn4tncMRICQ8uMWZqnIQAvX8PBx1w9E2
|
||||
Wbkl0oBHJ/gDtU+feDvbHI0JBvXerce2cxj4+793TGLUl980dgq776x2fcxHjvYZ
|
||||
uZjJd4M95Lh+IhtWGZQ1FviiylDg62w+mrNydX8WiFjLGYPydQqCIAAxAoGBAMpl
|
||||
m/MDqpgPxiDU1O9DAq8C/0MQUOc/p+67aGsYxmPDdCouBLA/zckQh6Cp9Wo3n7MF
|
||||
X5UHOqn72q/4ahNEx+3YQoaLqRKTjUHl3r3zj+MsM0hIDp1uOxVzbANxazuLuqqA
|
||||
C+yJTmRU7uvNPH1AMFJBKRSmhd3MJwoHF/KZAhvfAoGAFaGPU3ZnIjGP//x5RUYw
|
||||
WL2EhtmBo7vQpjRR7yvP4muCGL3e0/z0DbPloe+2JFbdo7Ylxqe6rqO74Cx3ayFd
|
||||
h7pK4VwCukCO3C6h8EGtXvNr0GWiT6wgB7DjcNw2ewQpqQCd6zn/gPHsR6SvJ6De
|
||||
fp7VmaRNtjxgCcpAYjFD9EECgYAhEPaofjnZvAH/jSX4rPb8Rr4TY9AD58d03lNR
|
||||
4+tNkzogRgJoFRR2u+ecnQfGQa4qnj8eZt7ztHzm8OvLmBodxo4f0yNdMJQMZxS7
|
||||
7dXdJHSAY51XpRGsEH5eFaKSSOLHRkIsc8ZF6AZcqdwvDlSWq6SdhhMqyFa8cao8
|
||||
7TiF+wKBgADNZ4HoZDfnuH5jUvf7y+YlxDX3jxWR+BUTLCJmt082uT+8Xg5SALec
|
||||
B8GP5s6VKglD5Wzj8IhxvpQ5yzH9DRHwEeu3vFLBinIUlWdBiXwtnbmY0E9r3PSb
|
||||
pZQH5RZ5PyrJicIVBJSqdFu2HDl4heeLJE0LGh7SQnFaexxXn397
|
||||
-----END RSA PRIVATE KEY-----
|
||||
19
test/tlscacert/server.crt
Normal file
19
test/tlscacert/server.crt
Normal file
@@ -0,0 +1,19 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDHzCCAgegAwIBAgIUUFCo8fUglrbfDY8ZUDnzAfWeq54wDQYJKoZIhvcNAQEL
|
||||
BQAwFDESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTIyMTAwNjEzMDYwMloXDTMyMTAw
|
||||
MzEzMDYwMlowFDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEF
|
||||
AAOCAQ8AMIIBCgKCAQEAxcja4TbshPj4tWgbRP73eDs2382j6Km5TNej6To13PJq
|
||||
Wyezg081ctmgFEMlgbRiowZmecpYOKjDKuVDtfLE6nZMmN+PjXXuOMGIPu67fx/4
|
||||
tnaMDYw96WIBEFNVZ7dC/pceaTIRbnjma89o1/mTudTAYPLAvKpeBqpJJFWPMDhz
|
||||
nK3NKeydTdUYc9jmEJWiFCI4bUdyvyUjp+7QrDbdODXo27/nVAV0Ih+OuU4ZnxT5
|
||||
cf1fzVV1ZqHd8jbLm25ZoAmkk+9DSXFNA2hbSepf70mRVD/Qyn8U6b5A2v+mWIfs
|
||||
B1+iAlPl7IX88W1Q9q1yu0uT8YWGWpeTbeOnJ4WJ8wIDAQABo2kwZzAdBgNVHQ4E
|
||||
FgQUEjp6AtPmpuLQyBPeiW4pW+VGb2wwHwYDVR0jBBgwFoAUEjp6AtPmpuLQyBPe
|
||||
iW4pW+VGb2wwDwYDVR0TAQH/BAUwAwEB/zAUBgNVHREEDTALgglsb2NhbGhvc3Qw
|
||||
DQYJKoZIhvcNAQELBQADggEBAL2bTWfTqxfN0YbBPjG05sR4nO8mhbNSGHDuGeiO
|
||||
OP0wPxkgAueScTpyhHWEAJmMQOMUM9KhByZj7LnqW8XY9BBS3zPAyzAdia8/o6Vl
|
||||
7El+M2JCfqz7hSupRK8M+r+XUq3hyEFjPLt+KO6D5VNzXiTM+36UueeQD3aaxxyo
|
||||
LpHSPeXFBkOrT/wt6FHi4NHvWls95PllncWZVYjxPMUUF/o30tOxSmgXwjUknrI8
|
||||
29ADKM1IbFuXd4vKYG9V+ukI6n5F86PYrN2ajPBKIidvTqU8tPzMHuJZ3YiIiv8p
|
||||
TARE2b5YLWuu+aF2z/V71MmIWr0uyOk6pZVGOCw7fwHx/wg=
|
||||
-----END CERTIFICATE-----
|
||||
28
test/tlscacert/server.key
Normal file
28
test/tlscacert/server.key
Normal file
@@ -0,0 +1,28 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDFyNrhNuyE+Pi1
|
||||
aBtE/vd4OzbfzaPoqblM16PpOjXc8mpbJ7ODTzVy2aAUQyWBtGKjBmZ5ylg4qMMq
|
||||
5UO18sTqdkyY34+Nde44wYg+7rt/H/i2dowNjD3pYgEQU1Vnt0L+lx5pMhFueOZr
|
||||
z2jX+ZO51MBg8sC8ql4GqkkkVY8wOHOcrc0p7J1N1Rhz2OYQlaIUIjhtR3K/JSOn
|
||||
7tCsNt04Nejbv+dUBXQiH465ThmfFPlx/V/NVXVmod3yNsubblmgCaST70NJcU0D
|
||||
aFtJ6l/vSZFUP9DKfxTpvkDa/6ZYh+wHX6ICU+XshfzxbVD2rXK7S5PxhYZal5Nt
|
||||
46cnhYnzAgMBAAECggEBAKLRsZZbf6QLzbqRBHntJ04b+RWOlVOQfRHMJ4x1Nig4
|
||||
i+OUsEv1pftxOj3T9QlstRKdzziNociq7VffurkLLJ4TWwUybVu37K9easncABAs
|
||||
ArQ6rRruC32YB2YoJBOoowcw4oEZDY6TCqVP7nB1be46PVDSJmZqHdOA1YuKv8Ci
|
||||
FbzLZEKYy6QGmHp9xMzc3usQ+KRNIFcR3NJb0eCbfAXb0tP3F12i4ygnxifkOVQS
|
||||
hukTJlZVbAO3W9uUEzLh5bkLoPfob6Vrwv1tGQ48uFgzgPXc4bWOUDFXHW5+vQLD
|
||||
1MKFboozrNhRR+Q5xvbRnaWEv4hMHlUNggc5ErRj6CkCgYEA5m5f1VfhfqSvEF2c
|
||||
XcIfUDiCzREpllY2ZdBSfUlz/GA6f0QUyFJBCdd4ypipQcggn60de9DoKDcNcq32
|
||||
rfVfANpsciJq9s4+xLL8MGtUuoi4HK8LHP3tc8aJaAcCVjBFbz0orKXDUOcue6A5
|
||||
Z5riDjiXOE56XSLSSNSRjWh4psUCgYEA27sfaM4J0YkdFuth/Qu+X9PeroUZyC0T
|
||||
3glMN/7PU4jZg+2v4Psfe61gj8qOt0catuWvsD0wQTy3jt+svY/KfkbspK6/7CEG
|
||||
fKx1AB1xeMr4JuQp9POFVhKRn4sBUMbHOkbjzlNpGmUI2arlLRTwT8YpuMDjCK4l
|
||||
ZuUYB/IHOVcCgYAqexqryCHIKTAlAjz7g/gl3+UtTQavsoEg0AEFG++IDW17XN+/
|
||||
9noLCHA6WV6KxAxPo6iV1POXxl5yT+P0OhIjpCDuAa5ahbdIp/6aJo9ePCpFD3gr
|
||||
Bh0qhOV8Ch7CKPAEC/Bds8mINrZ5EBbFJOab3I70UHN6jBrcVmPm/+WOSQKBgQCW
|
||||
AbBWt1qCnu2qCPWzcAH+n8DFOf645vVKPuS20ZEuwR1l8K2ClU4P+/QRFkLKIpO9
|
||||
Sx7e3VcFInNZ6Z+fJfwiqz7AysAhbwZjtMSHWJJv2XkB7AAsxtc/RJv/5ED4qUu3
|
||||
oE/DOrRlHZamKwIb/dB1VZ6ED8Ku2VyVW09FlViTLwKBgEU21xqvP1+TXzsrZNGm
|
||||
/Hj/RAaA8B6tyo5Dj9glV80oakMSaxBsLP9xHkoZjkHaJnoFosKBQSnCcPnEY4gP
|
||||
22WEyGshu8sujLibLKWhARqjeubatXv+XBxiDdMbgcd/XTwbI4HTjXy5LF0o47UI
|
||||
W6itMOg9uCfBJM/i2jrAkmQR
|
||||
-----END PRIVATE KEY-----
|
||||
21
test/tlsdifferentsubdn/difsubdn.crt
Normal file
21
test/tlsdifferentsubdn/difsubdn.crt
Normal file
@@ -0,0 +1,21 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDiTCCAnECFClOtyvBoQXVAGMcX0ObUawZJuYAMA0GCSqGSIb3DQEBCwUAMH0x
|
||||
CzAJBgNVBAYTAklOMQswCQYDVQQIDAJLQTEMMAoGA1UEBwwDQkxSMQwwCgYDVQQK
|
||||
DANJQk0xDDAKBgNVBAsMA0lTTDEQMA4GA1UEAwwHTVFNRlRRTTElMCMGCSqGSIb3
|
||||
DQEJARYWc2hhc2hpa2FudGhAaW4uaWJtLmNvbTAeFw0yMzAxMjUwNjQzMTBaFw0y
|
||||
NTA0MjkwNjQzMTBaMIGEMQswCQYDVQQGEwJVUzELMAkGA1UECAwCSUwxEDAOBgNV
|
||||
BAcMB0NISUNBR08xEDAOBgNVBAoMB0J1cnJhZ28xCzAJBgNVBAsMAkJHMRAwDgYD
|
||||
VQQDDAdNUVFNQ05UMSUwIwYJKoZIhvcNAQkBFhZzaGFzaGlrYW50aEBpbi5pYm0u
|
||||
Y29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAys3CaU4vXdZuKyw5
|
||||
AMkM4onEiJk3/TulWZIrXvFMgYLzAPX5eVUzs+eKfSBVIRiDGtsbmhcztT1GiAU6
|
||||
l8G9jHSegItJpKjiBEfFEuMmV6Fhx7ZjQdpyGdV+0bcE2IJHmeiaNxouvsV5gBJT
|
||||
vEamVsw9zU7GGOhhMyBQUUQDNy7yoHn8CBhDdoBskwJtpqPcxzohUDDt5wqSqOUl
|
||||
jo8yS375k4Q18hWzWIxJIAHoAFk+YyJqLq3mqq438Z9WSgjv9V+eLpNtKlge2IzW
|
||||
d1uH/siXE8Pp6p3WleG7cw2dzQZap+ekbx+3rRVLs8WYBHeTx08980sv6bBUKwHw
|
||||
aXXXSQIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQAMrE0nDBw8wfPR8w7hrUPyG1Ib
|
||||
/k72yAv9wqaso1pL4IpXE3DFbuoIOQLjNCr+C7h2IzegRZ1z4kZbWh7LeES7M0io
|
||||
yDgM16Vikr9ek+NCLmF5QAtn/smhfhSEOjJoGkTPUTdWR4VdLeMFGQ9D8LHc0DFP
|
||||
EyPZy0JZQpRiXAs0ZEDhlFOCxI1aZzJhwGBJd9wOlG/SZKI8izC74mNPU1eE7Js6
|
||||
1sdU+4zs2wm/QtZ1MLlkKspSQqdNis/wpSSyjTEr9TkfzxVr4f3bALjQydkrcAyv
|
||||
BkATBYqvJYSHA3PS8VxNTDVef5EgKEWXlCmP/jfMcYNsUxBjaUcqiJJcmI9e
|
||||
-----END CERTIFICATE-----
|
||||
27
test/tlsdifferentsubdn/difsubdn.key
Normal file
27
test/tlsdifferentsubdn/difsubdn.key
Normal file
@@ -0,0 +1,27 @@
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEpAIBAAKCAQEAys3CaU4vXdZuKyw5AMkM4onEiJk3/TulWZIrXvFMgYLzAPX5
|
||||
eVUzs+eKfSBVIRiDGtsbmhcztT1GiAU6l8G9jHSegItJpKjiBEfFEuMmV6Fhx7Zj
|
||||
QdpyGdV+0bcE2IJHmeiaNxouvsV5gBJTvEamVsw9zU7GGOhhMyBQUUQDNy7yoHn8
|
||||
CBhDdoBskwJtpqPcxzohUDDt5wqSqOUljo8yS375k4Q18hWzWIxJIAHoAFk+YyJq
|
||||
Lq3mqq438Z9WSgjv9V+eLpNtKlge2IzWd1uH/siXE8Pp6p3WleG7cw2dzQZap+ek
|
||||
bx+3rRVLs8WYBHeTx08980sv6bBUKwHwaXXXSQIDAQABAoIBAQCdbqs7yij1BG/T
|
||||
ben2VRx+g4ogrCiNmY7bgJ/QfSrx4vC3TztR2DVhtB2K0t2i6n9kCrFbpiVKzX2C
|
||||
O+TnR8vYS/N7QCV0AHIr9nbjGZh7MFlSiqB0z5oBuf1P2W6WkFP7A1kr61RcXbnb
|
||||
FN8R6hpYiQZ06XDYhxRldvFClLSWUaZOhM6DoEcfNV5djDWPv4tSlw0ILvgIDfeu
|
||||
DfW5d6ih51CT+NqtXUZNgWB4WCFrqxLz2H197QtFNbeEnT2kqU81LiSR6ZLU2LAc
|
||||
SEhWD9xpldPvm6CsYDy9LQazG+F4KA45OGgiHUfB43EvGnDHJEeX4HFMFWYKmefa
|
||||
VQRD5GKBAoGBAPEQXruYnfYk3B+arl4GWCZd37TQd4ik2HybkhxPK/LXLJhuImun
|
||||
6gYu0Y5dSpBv0B6LjhTencAjgfZRSiCplxbnnPMg2Tuu1FAjG7W5UlKsT8IlmHR3
|
||||
3LqublsCQDaww/pBlXMsT7TUe89uH05v5Fn4pwp5Hy6IArhJLecDoYk5AoGBANde
|
||||
hrLVn0XhFwQWe11vbhclF9KtJHqspXhKX6mRcQ4VSzoGuaji+ISyfoCaU6kVQrq8
|
||||
WCZIOcTJTDScrk3BYQbY0+I6hBIug3SXhR8pGshQHvGOC/pAUInzWO3xc3lbJAwR
|
||||
aK70zN8wXFJjc1rOivMY/mRvdJSYlPmr2e5fJg6RAoGAEj97/FVsN7LImvfZlTKD
|
||||
v7vBcG2LbuOTo7MfF1eC6yoQrSVBI8cdNwSaRl2XhGGCbp1/zuKfLGlDsEKtCtXr
|
||||
owc7YUguSY9NcReHRHVX3vw+OWMhLEfahKMppWgBNmKhIzONvZ8wFW80RBqA8i4U
|
||||
Kh9hfbB3hM0074BSojcrJjkCgYEApavzVjJ6WRjzyZM5xwBm4asJDmlefHe+ujAM
|
||||
MrbNDxZWTgbKXx5qKjnckjUlUhYmxNsJvDknJzfqRTaZ5vpxFwFIzOhSnGHngZLl
|
||||
Nrk5/wmTJCIvGIzM57GooTFxsNLpgdcKfjuWNcJP4pjaLepgfOynFL+gIIbXYtBN
|
||||
zs6myeECgYBp2shsVXAo0G2Iw5qSrPmsC0WurVYv5jZSAcnbV468vgGKP+L65nou
|
||||
c7iuLWFJdsvgZUXWyijsQNmkOpNDkfYxcimFtNWMzt2IcoAEQzk5VO0Ok6EfrVCr
|
||||
S1Kj0bo7oOyy0eZfQKVs39gE+ZKc8Fn2s4w4l6ZayhTKcfW6lwrJJA==
|
||||
-----END RSA PRIVATE KEY-----
|
||||
23
test/tlsdifferentsubdn/myca.crt
Normal file
23
test/tlsdifferentsubdn/myca.crt
Normal file
@@ -0,0 +1,23 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIID2zCCAsOgAwIBAgIUdbPSj6WWhFu2amL9voKbyCMKiB4wDQYJKoZIhvcNAQEL
|
||||
BQAwfTELMAkGA1UEBhMCSU4xCzAJBgNVBAgMAktBMQwwCgYDVQQHDANCTFIxDDAK
|
||||
BgNVBAoMA0lCTTEMMAoGA1UECwwDSVNMMRAwDgYDVQQDDAdNUU1GVFFNMSUwIwYJ
|
||||
KoZIhvcNAQkBFhZzaGFzaGlrYW50aEBpbi5pYm0uY29tMB4XDTIzMDEyNTA1MjEz
|
||||
NloXDTI4MDEyNDA1MjEzNlowfTELMAkGA1UEBhMCSU4xCzAJBgNVBAgMAktBMQww
|
||||
CgYDVQQHDANCTFIxDDAKBgNVBAoMA0lCTTEMMAoGA1UECwwDSVNMMRAwDgYDVQQD
|
||||
DAdNUU1GVFFNMSUwIwYJKoZIhvcNAQkBFhZzaGFzaGlrYW50aEBpbi5pYm0uY29t
|
||||
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwl9MlmCNG7kzk7qKGuBX
|
||||
jTONcn2OcifKfwgJlvzTqHcf8X/BIiyOOiwoeznke///LLtt9ygF0iyBMQYM/CG/
|
||||
rzOR6tbzI4y4Bmx6VqY02CkXi/p66ywQ7B5N2Fdp9Bop3SnthTcT4NoXBSEUhI1O
|
||||
ob8lDFv1KMRkCULD2sA0FUYCrHtw0M/vEOqsA9VVjyzOXsIlbbR1BSXtlWNneGeL
|
||||
OAdmQWO3QYCku/YrCyJlscvIisjp4s7guGnQh0Ws8h50R5sqag8RvdHUwExVLUfZ
|
||||
L1Od0+hCiO5mNfKekT0cs9owplcwgNHw88b8q4/aHDBtQRgsukkMxTpo00ftPJxI
|
||||
nwIDAQABo1MwUTAdBgNVHQ4EFgQUOyw89AeB0jXb5WCZv/5oDY8oWxkwHwYDVR0j
|
||||
BBgwFoAUOyw89AeB0jXb5WCZv/5oDY8oWxkwDwYDVR0TAQH/BAUwAwEB/zANBgkq
|
||||
hkiG9w0BAQsFAAOCAQEAtbybQQ9GpY5gH7xz4EWOUZ7XMmBYtuGXVrqUd+76hvCA
|
||||
H/SB0nl2bGp7tAKBttmXhfKVac6wFCbXvYe49B+Q9+iL7H9st9VZUPKLQ6K3Uet6
|
||||
L1ggMm2BhecpuYbwkG7ZidVFo/SuUCbCTnXBgHjvq4IkVCaJe7aKZmejSCh7gsIR
|
||||
BQkZvz/22Vx/WPTEYp0x/riIvSViBjLCuD25Y+nCtS8c2xGVBjs9Q4GWCOAvEfAr
|
||||
Tqs42brH1Vs92xS143p2h/wv52tmhfJI6X9QVQBBUoIjPR/VDFqZU5EYhAvuQPBi
|
||||
UADz9hNYGQ9wBzZGvzbrorpoT+7aW9nGtmUsvvupBA==
|
||||
-----END CERTIFICATE-----
|
||||
3
test/tlsdifferentsubdn/readme.txt
Normal file
3
test/tlsdifferentsubdn/readme.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
This directory contains private key and a certificate that signed by CA.
|
||||
The directory also contains the CA certificate. The certificate and it's
|
||||
CA certificate have different Subject DNs.
|
||||
68
test/tlsintermediateca/chainca.crt
Normal file
68
test/tlsintermediateca/chainca.crt
Normal file
@@ -0,0 +1,68 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIF5DCCA8ygAwIBAgICEAAwDQYJKoZIhvcNAQENBQAwgYsxCzAJBgNVBAYTAkdC
|
||||
MQ4wDAYDVQQIDAVIQU5UUzETMBEGA1UEBwwKV2luY2hlc3RlcjEMMAoGA1UECgwD
|
||||
SUJNMQ8wDQYDVQQLDAZIVVJJQk0xETAPBgNVBAMMCEhVUklCTUdCMSUwIwYJKoZI
|
||||
hvcNAQkBFhZzaGFzaGlrYW50aEBpbi5pYm0uY29tMB4XDTIzMDEyODAyMjEyNFoX
|
||||
DTMzMDEyNTAyMjEyNFowdjELMAkGA1UEBhMCR0IxDjAMBgNVBAgMBUhBTlRTMQww
|
||||
CgYDVQQKDANJQk0xDzANBgNVBAsMBkhVUklCTTERMA8GA1UEAwwISFVSSUJNR0Ix
|
||||
JTAjBgkqhkiG9w0BCQEWFnNoYXNoaWthbnRoQGluLmlibS5jb20wggIiMA0GCSqG
|
||||
SIb3DQEBAQUAA4ICDwAwggIKAoICAQDyTaomebXQXMGAs3ux3SSJnJseozpUIWS2
|
||||
eUMG8U3YK81raVpFsGPcjDi4RdCo72SqwD3LKKJmyfz4NOlKpZJq5rhqaHkECRj/
|
||||
GUKihl6Fr4OWlcJui/x4xeJmgFHvgnQEH/r8mvVVE8GqKHX9mRVOMaJtG14hm1qI
|
||||
DoK+x9IwOut/H5FMeici/C11xIDK65/54vztb4wEfyRNK5e8dFwuD9yJo2gYM0GB
|
||||
csQTq5WbKr0/uMF9rfFEx0lybHEASqgLCUA5lAGFtexefCFNxOxLnP4U2c5J+bcR
|
||||
rGQ/hpfw02m0UU+fuNba8GPJbXJ6zT+FP0kme0180OKmH1zcNDRJuGY6OQ1nICHc
|
||||
hf9QF95XQcwkf4MIltxZiNHbhSLuHrNFImv/AxiFchQJ+KPMLHV23x1uVKvRK3Mg
|
||||
ZOEZmtCZVTRbOF6AWSTMGblu1tuxjSohO9ycqk8yQk/YkHW3zuEiIV+AAZW6qMKI
|
||||
UZK64AoFb+E79gRpamz3ZOFfzNn/nKT4eMovDUUa8W4sVOVjBP2lT87xt7WBsE2f
|
||||
mK4vE19hQGMzI3jZcMjIwNp4HjKIAeZb8o21ywogGF0qHctoINnEu7nG2trH02ug
|
||||
0d+hwCfPRQHB7dicwTZm8ute4cHrwXed5tt1Bwv4Rq45GbwaOCAeZWa9S9GUaY/t
|
||||
9xeS66HLPQIDAQABo2YwZDAdBgNVHQ4EFgQUMIuRPon0lwjMxi5tLwJYRcagTN0w
|
||||
HwYDVR0jBBgwFoAUMjCCvlMnTcsiRvpiGPgBUGAA0EwwEgYDVR0TAQH/BAgwBgEB
|
||||
/wIBADAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZIhvcNAQENBQADggIBACPpl2Im9El6
|
||||
m7T8cmtgXEW+rcQH/AA3px3+J/RnSWyRUi5fb0lc1RkfxQvuG7/sH3ywOc+uuB2b
|
||||
3lg6zkNaP4tZYTEjrrkj5qFycoQcvem/5nBKuQ18+ZGulHqE+YzcPTmbNO2VzD5e
|
||||
RJJpboiJa5TtM4wBeIuWbicWN+2wPeOkiQHxHEWtXOEVmbKzuREI6JHg2CDYMpWb
|
||||
GWqS3hn9hfQoNhtSdtDFGChlsTpgKB/gRpYEfrlLKYKekJafD+xhEl/ZC2JDOtUr
|
||||
/dZW4JicC++rUrMt89aFzEAOtcaR4dbL4NQhMRAlF9MdjZvHdcJqYEd3wqOymEnV
|
||||
4ce1VpraZIh3qywqoOqf54vjd5DugpFoxjye+5ynzfMGX6RjwWnVRiY36kjFiMFR
|
||||
iGhFT74lbRmw86RUQFjiXWAG9R0cR5s0EyOM3Egp/qRkWxbG6z7JeCdUd7Z/aaR3
|
||||
iT081PUcQsl+jzCw2J6SDkemzZn/spkViodfzGJ5xEXB0fldIDarbDqOeWjSoKaz
|
||||
LA7qIfPJptJkwybK/hBqvfR5fXx1VuubJdMfbo5jmP4OVCkNV5I6D5EMkYCxPrOl
|
||||
9eVjYbndAVAXRF8vopJRqe067c+IwKPTnG4iIoXcR28efCBQ0Tv8DXYmHjSlVLdt
|
||||
UCqYhFBxgGOUJLmqe7O9TWVk/aIM3Q3M
|
||||
-----END CERTIFICATE-----
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIF9jCCA96gAwIBAgIBADANBgkqhkiG9w0BAQ0FADCBizELMAkGA1UEBhMCR0Ix
|
||||
DjAMBgNVBAgMBUhBTlRTMRMwEQYDVQQHDApXaW5jaGVzdGVyMQwwCgYDVQQKDANJ
|
||||
Qk0xDzANBgNVBAsMBkhVUklCTTERMA8GA1UEAwwISFVSSUJNR0IxJTAjBgkqhkiG
|
||||
9w0BCQEWFnNoYXNoaWthbnRoQGluLmlibS5jb20wHhcNMjMwMTI4MDIxNjIzWhcN
|
||||
MzMwMTI1MDIxNjIzWjCBizELMAkGA1UEBhMCR0IxDjAMBgNVBAgMBUhBTlRTMRMw
|
||||
EQYDVQQHDApXaW5jaGVzdGVyMQwwCgYDVQQKDANJQk0xDzANBgNVBAsMBkhVUklC
|
||||
TTERMA8GA1UEAwwISFVSSUJNR0IxJTAjBgkqhkiG9w0BCQEWFnNoYXNoaWthbnRo
|
||||
QGluLmlibS5jb20wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDjTlg8
|
||||
0bZ3vzeYLJ1Z4zp/HplJSDzAn22EROJtHNb1pyfWfaO7UyUy5jNt1UJ6hTXy1xOS
|
||||
r9eGaSu3kAm/R/METYgsMM56ALQ458VlLpZUEJBMgu63W6/FTckl5iUno7n9y3qL
|
||||
2CKkHuEXG9jd/jPhBX+GLc+h1iwe1URaJSe1CLdSc8SMsGZUC3wMO3EAAqEOHh9j
|
||||
BQy2qeWpR6oAF8HST5JX6lW+k/4NeNthdse8/Oqv1otPzseXE9jz09tz+4Qd5fdt
|
||||
J8UcCWPcpAFb/TM6S2Hpr8xSqZr5+Em4JimdNDtEbJZKjzSwvmZ4S3qsUP0xb9ad
|
||||
NgDjxYjBqNStgjrSzKGebazD6U4EDa8dXS6UxqgnxmtcSQLXIPcwfH9FniKnR7Tu
|
||||
/cmSDOdWCedyxeDjrLlGuFUtJiSXCqRHU8c5KxTVkyDV1YZPjISktVJQS1/n39Lf
|
||||
2TtHNTH3qTDfPzcgBqqczF+vlIKspE4YcTFBynOlL4biWZEsddOjpoqzamycMBqj
|
||||
sEMlvydggS+Z8oZqDFVTU9/BCC2mth9HZr7hjO950IQcgVKexXOqxgPP4rmzEl2k
|
||||
jEp9PKdDqBv7lOYjYO4taegfCZs5FPwaKtCStmzQLERMwSifqXe0TGV5obA8asRY
|
||||
pOiZf6d9nVBvIYKotB1mNJTMqBke5hom8uarPQIDAQABo2MwYTAdBgNVHQ4EFgQU
|
||||
MjCCvlMnTcsiRvpiGPgBUGAA0EwwHwYDVR0jBBgwFoAUMjCCvlMnTcsiRvpiGPgB
|
||||
UGAA0EwwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZIhvcN
|
||||
AQENBQADggIBADYAHeWqQEg+oIC5qy8obEul4Zuk5J9HignrP8ef0+wdJf+Zx9/j
|
||||
3nZKBrUrgZiBP+RJgNYLRByO9QT/q+P5dLfuLikyP377oLlVUcXQIXbJU60Yxdl5
|
||||
wrpS9O8nfbwMUdi+1foGmA17zYNiTfBZ0/vem6yrhKdP6pS4h1+291tF3+mac0Q1
|
||||
sx7RhssZT22YclH9u3U4q7Ef3q5ilqV5fb/Rm2z/iGtOLF2swm/jS1CEpJbyb0EE
|
||||
yF8hoX8oliJdunmUqGV8bK5D7GFx47tyS5lU4B/Q14xBYUUc4hs83xAs667h0fym
|
||||
0fbyQGh92bASPEDw6ep8eI6Iv+DSniwEFPG0ayDtnSEM/HkRVML497Rhqks/cP/9
|
||||
rMJe9hUoetKO+p390R22EQLqGT6nvWV1jsGMHIMjJB7kfBLbgvFz8pQQdo7IxK4N
|
||||
IirFdEn/atri2Z6baT6eqKt2tnjodQs1F+14rK1cj0QbH/tkHF8S411MclmufthB
|
||||
U+VlEcjr4pnpVzvE4B78kXtlRH1N7Pj+vIgwpiC+hnWcQdqVF58E+7ERz9U1JqaQ
|
||||
tX9c6aZW4m1pMcxR3c76NnYJi6rmhoL8xUNDq7HXrkK3DM0/rUvxl5HwfgGC0FGS
|
||||
QmFgXgNddo2pj/TjnssE9XxFjsU+gEbGudGe0p5HR5qlKzTLc44WxcmW
|
||||
-----END CERTIFICATE-----
|
||||
34
test/tlsintermediateca/ibmmq.crt
Normal file
34
test/tlsintermediateca/ibmmq.crt
Normal file
@@ -0,0 +1,34 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIF7jCCA9agAwIBAgICEjQwDQYJKoZIhvcNAQENBQAwdjELMAkGA1UEBhMCR0Ix
|
||||
DjAMBgNVBAgMBUhBTlRTMQwwCgYDVQQKDANJQk0xDzANBgNVBAsMBkhVUklCTTER
|
||||
MA8GA1UEAwwISFVSSUJNR0IxJTAjBgkqhkiG9w0BCQEWFnNoYXNoaWthbnRoQGlu
|
||||
LmlibS5jb20wHhcNMjMwMTI4MDM1NTAxWhcNMzMwNTA1MDM1NTAxWjBHMQswCQYD
|
||||
VQQGEwJJTjELMAkGA1UECAwCS0ExDDAKBgNVBAcMA0JMUjEMMAoGA1UECgwDSUJN
|
||||
MQ8wDQYDVQQDDAZJQk1JU0wwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
|
||||
AQCvn7rGrAzGr1BI688hirHC+P1xcI2dUAc1EZx9bQ1mzSAP+9F5hBzT0Ty6ay0Q
|
||||
zs8qKOs9YpHy1qCrETlrFJxWmd8IYVhT2QoKPxF6jhfYMf6anabtYSRZe1c3v/zi
|
||||
DyuLuS2XuDeHnfuJl732YTtteYcG4nAx2Y2GcKLnEfNyrB49+ZGbjnNTqIBsR1tD
|
||||
U96C1h1z4PGtZDxDGbdjGlaB1AJXH9r2tocaS2WyBKo8w9ogTI5d63NYdVvsPKm8
|
||||
AKwnVLu1kE/xk8/5fraW3tyX4JmCFk4Tt/rf+Oy0dbFfoC9m6JmHPeEopqE9f/qy
|
||||
m0WpqM3PHuz2Kke7RQ3GNYFDAgMBAAGjggGzMIIBrzAJBgNVHRMEAjAAMBEGCWCG
|
||||
SAGG+EIBAQQEAwIGQDAzBglghkgBhvhCAQ0EJhYkT3BlblNTTCBHZW5lcmF0ZWQg
|
||||
U2VydmVyIENlcnRpZmljYXRlMB0GA1UdDgQWBBTaihsw+XFiyTZXn27Y7qpTFQSD
|
||||
2DCBuQYDVR0jBIGxMIGugBQwi5E+ifSXCMzGLm0vAlhFxqBM3aGBkaSBjjCBizEL
|
||||
MAkGA1UEBhMCR0IxDjAMBgNVBAgMBUhBTlRTMRMwEQYDVQQHDApXaW5jaGVzdGVy
|
||||
MQwwCgYDVQQKDANJQk0xDzANBgNVBAsMBkhVUklCTTERMA8GA1UEAwwISFVSSUJN
|
||||
R0IxJTAjBgkqhkiG9w0BCQEWFnNoYXNoaWthbnRoQGluLmlibS5jb22CAhAAMA4G
|
||||
A1UdDwEB/wQEAwIFoDATBgNVHSUEDDAKBggrBgEFBQcDATBaBgNVHREEUzBRgh1b
|
||||
RnVsbHkgUXVhbGlmaWVkIERvbWFpbiBOYW1lXYIXW0FueSB2YXJpYXRpb24gb2Yg
|
||||
RlFETl2CF1tBbnkgdmFyaWF0aW9uIG9mIEZRRE5dMA0GCSqGSIb3DQEBDQUAA4IC
|
||||
AQCD3iROwkBiSfJ1jCzUdblYawclZE3kX7remHR77sAuGYHEsHhU4PmXUs+A70JJ
|
||||
jF8gzc1cqMqy4Kwd1BGbNLp9cdtre4TigQ9UqbqxCENyoq0aTIhHmJ5GP3RKMwC1
|
||||
jaNmH/MUlFhOKZsTLKymkBGCA9GLhD+quU4AIQHMLGoxMIbRZwZzyuGpa7/Gl2Om
|
||||
f6taMfBsnmFFC+O+saGvu8TG+Q28bGA7wJQM5WMxyVbVY2Lkbb4u2/gDEY1/6T7g
|
||||
ZkGuCxVlyQ2+dy5teKe7I2AGkgTbwl39i4YMGdj9ZC7ydRIANnNgCuygUTZ3c+w3
|
||||
PvA33cX7ICWrmrk0y8Ulox7uj1jNi/npdwPkfjyuh9fJpdV4J/BcsQyZ/j4F5Z4B
|
||||
5MrQeJ7wEELYDv1OOTntyQoqH1HH3TrZ3PFS0whA6gTT76ci2ra85vLVW4SJrbKj
|
||||
VvDr5VcHE+IsJBedscbP2fO6imkAB74xdkBx9uy7x4aXJi399DHvw6b7mMsbR0om
|
||||
6CpI0akjprfhdyv4Ri3vvWpWrzHMUMjzLuj6HYopBlFLErMe6WTY1BAh4ljUFKZ0
|
||||
141/BCkGzNpH/5g1O1QwdQXzEIgUjG16Dm7gM2WKAeJFBttogX2ygTRnVRCZs/fY
|
||||
JE3CwtxKczC0XteonH/ylGTQQR/0J8Y/ozLAWQxDyzi2Og==
|
||||
-----END CERTIFICATE-----
|
||||
28
test/tlsintermediateca/ibmmq.key
Normal file
28
test/tlsintermediateca/ibmmq.key
Normal file
@@ -0,0 +1,28 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCvn7rGrAzGr1BI
|
||||
688hirHC+P1xcI2dUAc1EZx9bQ1mzSAP+9F5hBzT0Ty6ay0Qzs8qKOs9YpHy1qCr
|
||||
ETlrFJxWmd8IYVhT2QoKPxF6jhfYMf6anabtYSRZe1c3v/ziDyuLuS2XuDeHnfuJ
|
||||
l732YTtteYcG4nAx2Y2GcKLnEfNyrB49+ZGbjnNTqIBsR1tDU96C1h1z4PGtZDxD
|
||||
GbdjGlaB1AJXH9r2tocaS2WyBKo8w9ogTI5d63NYdVvsPKm8AKwnVLu1kE/xk8/5
|
||||
fraW3tyX4JmCFk4Tt/rf+Oy0dbFfoC9m6JmHPeEopqE9f/qym0WpqM3PHuz2Kke7
|
||||
RQ3GNYFDAgMBAAECggEAPeL1eEjsf58LlYazCMjM9z2yYaUd4g9vWr4H/RLOpCko
|
||||
YTmFiWKKngGfermFueSGj/63VnxDneUP3PhG2Xr71HCIbXWQIIvcw9uRlzQ3JtIH
|
||||
PAjN59xRaM7T3ytiO27JE4V/kXUy7DE5kDTOleGRhXRLpptomchl3LgYT4C93uw+
|
||||
Pul/KS4PZrGvTD+QnKNd4aOmlEaVLDRcwMqvg96bofE4Qk8LniQB1xEuYUpJFtPm
|
||||
dBaGmtkTg8M6ghqOYMMqx8qNjqcv/HxIUFbV6F6fmhDOAAvt0gRzhHsmJieYiWFv
|
||||
Igj/pmJbo/sfcgiiV4g3lEfqP4i9WpMmpVtEbOrvUQKBgQDio+YfKOwO4Pv0soi6
|
||||
4qPpAZyYvDeQNFYfm0GP/FBuFHUR7dMA88snPZUY8mSgtfG/zoQdcyI3G7HEsugD
|
||||
redwGoVvp0s9435xW7KCB3tMP3PhmeMjXihQw5cXyJx+LrHjorme5zQ4OkcB23lF
|
||||
cjN/Yv0ZxRp2wWpOp3F08CvntQKBgQDGX/h3DTx4IHRjfL6jnL8vKJIsxNFhy/Rz
|
||||
SMMnwAXLmYv29O+rupqsx/MNbM/VA99HqGQt8p2fVEGafYlolaFjICZM6DH5j30B
|
||||
t1M4LsPZf+fI5vWI9KHwHT1JxCqfqM5GwwHMrEw8pdicr/+FS6O16Kymln/aAILb
|
||||
sjvpFyLwFwKBgQCO1m06RjhASFuDJOI3po9XUsS3HiiGofWFhfwUGxk1x37hBdpu
|
||||
RzhKSu2lA1+YShNKp4VsahuuT64CIh9H8lpitNRUQkORhccy+m/Os5hpvbPzA2G7
|
||||
8KPIAv0+6Bh5DkTfCreiBmVK6q/F4+TSd98s8d5CV48OOWgemjlPUe7Z4QKBgQCu
|
||||
MiYP/NqFrhImLquFJqantZuunmIy25NcDJ/6bt9n6vyCLpGrniAm6yneNxfFuTG/
|
||||
TfoycuLAv48gJ26bHRHr5pZbYGZJ/BtMf3wfUMmAW5Xg0Bb6Xb86B6MC/LRlISmJ
|
||||
78HLxdzoQMYWyWG63jHzEk9RtcStXVeLrlZ3l26BnwKBgHxMtBHvcaXWzRdm1PZ0
|
||||
2DVFOP5lm/5t5WC8re8L9oeVLt0+yMBEKXUwX66Z1s5iO24CWsHFN8zDjQhVKfZM
|
||||
/YOxbfjQWYO1LsgJweAEeEauYI+ncMnQEZf1Ei1P8g2X1GTS+n0r4YANawWczp71
|
||||
tZURwKJSJwKpGMChxucQDJc9
|
||||
-----END PRIVATE KEY-----
|
||||
3
test/tlsintermediateca/readme.txt
Normal file
3
test/tlsintermediateca/readme.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
This directory contains private key and a certificate that signed by intermediate CA.
|
||||
The directory also contains the intermediate CA certificate and it's root certificate.
|
||||
The certificate and it's CA certificate have different Subject DNs.
|
||||
30
test/tlsinvalidcert/expired.key
Normal file
30
test/tlsinvalidcert/expired.key
Normal file
@@ -0,0 +1,30 @@
|
||||
-----BEGIN ENCRYPTED PRIVATE KEY-----
|
||||
MIIFDjBABgkqhkiG9w0BBQ0wMzAbBgkqhkiG9w0BBQwwDgQIxuHuzG+WcZACAggA
|
||||
MBQGCCqGSIb3DQMHBAgMKyhajZd/ewSCBMiCL57/BUvVwtxmhaIqQBSjHLnx+AvG
|
||||
f+frFkwM52KuAg0Q+vF/1iQXMbZRrHpxNz/1t3cIdwzlc9S3FB+L42q0CEH0c1FW
|
||||
6gB6csmb1AyhGYffwkeT8bPkELJhpO2taIFTynwDhHaeKeDce3EwQ5xQd4ydq1Ku
|
||||
t4oJZun51J5qvNs91BiHHVB3/q1Lnh4VK4TTDgXTGfu8qcuOexHQcOgko4oemorv
|
||||
E1Woy6Lf99bePxedSuMIxPAXysSCBqcrZDbY9K3yaxV6Gr7S9FBVvOx9r8PTaFEY
|
||||
sXxdtB22qM1PqZIIjK6nzwXb5iu8wcaZFatD6y7RQaS0kz5EcS5kExrEocFV7D1T
|
||||
dtTf6tt31PbtKfEauRjz3fBXMw+r3Pu/hWRXd3og9hfzFQz49v9RdvgkHy57uCY+
|
||||
F/fN3BKyTcTqJWKpBDMx/xtD1kXWHf81SFmckuShbgKjDvZraR4RmzO/9upIDCFF
|
||||
rJ85takmPvZyAdfvZp6RqVja+cphI4nmxVvmOmUiFMmUJ0lv9MhqpxdVFfleWz0w
|
||||
9XxeL4l+cd/Kbl2ihHwF4oOzWlamyRZjoWiq5s/ika9I1iQ40rGTThqnphonlhUz
|
||||
HX4NvA+fbugwqyOjqEkg5AqyP5ucUN3Hl0qd7wqAJprDkB938unKo/1sr82PfdO/
|
||||
5UvzsKz2ieVavEzi/NxYyR+Xjm3YHG1akcftvElcpYepvemt/PtXJH8fX29b1X6C
|
||||
TYxV03MeA1sJXeM9iqIf/wGQJ657U5ciXUyUtU8s0dDY+jqpZVjxekgCove9FNXz
|
||||
Mja15yiAy/F8xKX20ZRisg+Ly60UQYbX6Wu6Uy6NMjWkxZOYebCVYLMag6UjS0CS
|
||||
8n3/DNvuVooGWbgBFs0tyabtlz/Fz5jGktkMBQeEK4PZKZdMt9aXnDfPFGJWrsme
|
||||
bUzEZLZSWD2Zwr/ujzfBgVeg56AyHJzHuOOLaofZ90ecv69Udl7sYZBelFXxgN8R
|
||||
JVXyV0kNZYj9LaqRTi54H8YToAIV7GXIcasH3jP54dOba3px2NXbaiMf7Oe8apyW
|
||||
E7/vKQNoIbWaglRHXVPGoATTUzYNdBO4jca1rTOXjmzsiZl1JGPwQsS03PuTfa8C
|
||||
F7Uqxj8P9m71G1KuLYA1es55aNXzXSx6uqrRYeu0iug/jE+voGfjbRW3BUY/qxtl
|
||||
OjR8pCDPLN6/rt//ejvBdA4gPDgjRNH4fO9DULQDqIVHmYlysZqhbPbhzmiBXZVP
|
||||
zw1/z9amWR00OeN7xUvm9n+65dosoIn2v1dOz8JBh6Uooj63D2cOghBwEXoPg53B
|
||||
3vLgqy3NKyNZQ/gGfBjXTTeoWzAy8U8hKecNKcsTBquBeBUfS55WwkbD7Mz0Deho
|
||||
EcMZPZUwAPyJ2kHL74etpoBXuH7Jzo8SVedhE+C5J6F+oUP/JcoXdehdGLI89CHU
|
||||
cUARt91OiF3WQo51xT21GlGL8RMHVpjTnYZinnQ/fE1y7JfWtYshBya+YLlAk5vb
|
||||
9jt0tZ9I/KhJ579ZwWBH8V179kBr5Vn+uWlEBBaJu0abdbRSDjt6fAaxi3Q8XK+c
|
||||
isuVuzN/8JAeRgRpm7hRfIoG6XOsrY72ktz0JiHflZ+90xaPRvpCA9/mFC5qp1zh
|
||||
yBs=
|
||||
-----END ENCRYPTED PRIVATE KEY-----
|
||||
BIN
test/tlsinvalidcert/expired.p12
Normal file
BIN
test/tlsinvalidcert/expired.p12
Normal file
Binary file not shown.
26
test/tlsinvalidcert/expired.pem
Normal file
26
test/tlsinvalidcert/expired.pem
Normal file
@@ -0,0 +1,26 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIEaTCCAlECAQEwDQYJKoZIhvcNAQEFBQAwgZ8xCzAJBgNVBAYTAlVTMREwDwYD
|
||||
VQQIDAhOZXcgWW9yazEPMA0GA1UEBwwGQXJtb25rMTQwMgYDVQQKDCtJbnRlcm5h
|
||||
dGlvbmFsIEJ1c2luZXNzIE1hY2hpbmVzIENvcnBvcmF0aW9uMTYwNAYDVQQDDC1N
|
||||
USBMaWdodCBFeHBpcmVkIENBIChmb3IgZXhwaXJlZCBzaWduZWQgY2VydCkwHhcN
|
||||
MTUwMzI4MTMwNDI2WhcNMTUwMzI5MTMwNDI2WjBVMQswCQYDVQQGEwJHQjESMBAG
|
||||
A1UECAwJSGFtcHNoaXJlMRAwDgYDVQQHDAdIdXJzbGV5MQwwCgYDVQQKDANJQk0x
|
||||
EjAQBgNVBAMMCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
|
||||
ggEBAMeNfSaC0XZoi9CHXLUHz7BA8frIlz+gnl1sjaGimsofiMjd8a0c4pOhlusR
|
||||
kvB4qAQDxmDYhh9QTuqiJZvQxzTI3sNt4W3kcm/bEVawg8HvsWBY40fKkWJYlypV
|
||||
K4Oizcri7CLaeUwgpJlrSlYiJSzjaaytTDeC1F+RSTrdDg0CxI+pfhxIkc9+HPMC
|
||||
oSv1ynxktcJPnCLGRIIHYCE4jm1ZsYxAkSV1crja5OQdd3czEMz6wfHdYzTJUEa1
|
||||
pmnTOa9lhuxlYb8ykt7G2CK66tXaoN0SsAsy6sT2SblI16RDkyOL5XJk56ThhHrn
|
||||
XjjWr3Zo4/tE/pgn5fd+91oi2+ECAwEAATANBgkqhkiG9w0BAQUFAAOCAgEAZNZn
|
||||
OJVQ54NLCzCyTiTauG2jJU+3aYEG4hRhpLM6N11vrukIFTeVfbrr1uKp7sLHYB0E
|
||||
6xLgTxhKF2lDHzCPjA3bOO0tDsxwkOZNP//jmMi+9VKd6Voh/UENOVnBHaXLb5G2
|
||||
9OXi70W/K8eQU7Qi+tu+snWNLYHb5nsYKnPed3+h/zLV3iNB7cz2DCNCas2cKekx
|
||||
6hJo+tWoC+jXcpM97pnqY6saYNmKmugIdZ1W77jueoEIIkSMAZvd8sgXzE6Ad8mD
|
||||
bTMimX9ri0APgxZewaF51YzR2yKqgkpvLVBef0nCbjCDK5zgJVSQHtVJswE4mIcL
|
||||
J3PbSxtmt3BcIr9jmYZZjmoXSjK0+YQkDYT9/uiE5h+6KXrk4AOTkcO9kuxQcBAD
|
||||
QYJB8Hr7h9OcWmpOOJDjOz4BspBwQ7Vs2swHBQgWu9mS9I187JFSFm46dp9CnqSV
|
||||
K5i5amsrJUcRtqlBs4JzqsLivUaet+BtQwlWDfl52TgWpWeIs2EiPFERZxiEggVr
|
||||
pIhjZ9F2d0pL1Oj5jCWMRGWrz1px5W4r92uzfYvTrT/eyGNGGNdDeAgidJ4SftXu
|
||||
XMcqgVjAy0kKvQEK9bCqfoOxTllFqLcneaPI6O0hLREph6sti6eDvJeMsUGoYm/7
|
||||
MxfCaqBU1HStl0HHPaaqHnPP+FW+/xuXhHij1hY=
|
||||
-----END CERTIFICATE-----
|
||||
BIN
test/tlsinvalidcert/expiredCA.p12
Normal file
BIN
test/tlsinvalidcert/expiredCA.p12
Normal file
Binary file not shown.
35
test/tlsinvalidcert/expiredCA.pem
Normal file
35
test/tlsinvalidcert/expiredCA.pem
Normal file
@@ -0,0 +1,35 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIGEzCCA/ugAwIBAgIJAI+sAC+/ups4MA0GCSqGSIb3DQEBBQUAMIGfMQswCQYD
|
||||
VQQGEwJVUzERMA8GA1UECAwITmV3IFlvcmsxDzANBgNVBAcMBkFybW9uazE0MDIG
|
||||
A1UECgwrSW50ZXJuYXRpb25hbCBCdXNpbmVzcyBNYWNoaW5lcyBDb3Jwb3JhdGlv
|
||||
bjE2MDQGA1UEAwwtTVEgTGlnaHQgRXhwaXJlZCBDQSAoZm9yIGV4cGlyZWQgc2ln
|
||||
bmVkIGNlcnQpMB4XDTE1MDQwMTExNTkxNloXDTQyMDgxNjExNTkxNlowgZ8xCzAJ
|
||||
BgNVBAYTAlVTMREwDwYDVQQIDAhOZXcgWW9yazEPMA0GA1UEBwwGQXJtb25rMTQw
|
||||
MgYDVQQKDCtJbnRlcm5hdGlvbmFsIEJ1c2luZXNzIE1hY2hpbmVzIENvcnBvcmF0
|
||||
aW9uMTYwNAYDVQQDDC1NUSBMaWdodCBFeHBpcmVkIENBIChmb3IgZXhwaXJlZCBz
|
||||
aWduZWQgY2VydCkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDLGn4P
|
||||
97BZteNO/5zIlGH4F067TPlOjFiLI1TFI/bKp3bx1fHpsQdMX5C9y1s6BqoOzMJV
|
||||
VHDJH4NlxvnMUGNzYvluynjujejhyDryDrb4uY3JMcewvnHmrWArzbfZw4eBzANr
|
||||
feBkro4xr6m5HWMxM4GJZeFaEGqbXNE5vAYhZ270qUKqrkSHv4g/LqapITaBPJDC
|
||||
3tAtV0UD4/HmwB2ogZlXy4DkbBsgcc2rS4xPxx3Qki78DeA9oT6aeLP7KZnoEgo9
|
||||
Xqb/UfRuZ5MYR17u8yTuJz/JdAs6Saf80t5PLDO/SRKj7Uu1lt+HbkpEghdEdB/V
|
||||
pPdmlIfo7uNe7rFG/OWif2qwVm2t9o02oIUqlAHggS4WYeOmcB8L9EtmlfZHaoDA
|
||||
H0a0xlbZ+XlE/EMpphgrzSE5g9h9Vw7vvH5Ygzks8lSXKd5VS8C9y8tWqaMCynAy
|
||||
5MTIZdXMvouG8vy5Ip+iWck4QSo6eJBryLNt/vzljxo6XntveXRZVvywyjh3NuVu
|
||||
nVLPe0CFkmRHXK5/zA6H8rZeGb3eK8Y4VcLKj7x4Lnluo47BeGnKH7UcKBA2AREC
|
||||
VGFVsHcr0HwKgCCSsyv4qRefY/mJWO59BL38shHuzydLxlKHf/uPGdsZ3DU0rb6i
|
||||
QXMCHC6lzAjJHVCDJ2witiPSjmW2LRZzRqdTqwIDAQABo1AwTjAdBgNVHQ4EFgQU
|
||||
dv3XdSdsOkZJDWgusfuZqBx/jpMwHwYDVR0jBBgwFoAUdv3XdSdsOkZJDWgusfuZ
|
||||
qBx/jpMwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAAOCAgEAR9o32FR4AjTZ
|
||||
LV5nH5rsQ9OVon1TCv9yemAI4coh08RHWVf7cDyIp8XKy3gTM/lq70wzsXUHwaAS
|
||||
4xSOjc/fBLP0UTEwCrdyy1guRs9ZjKIhqth1AkB92sF9HV4P811vUNppVfA/BDmb
|
||||
Ttl6i1Joyl6nhnDxn1HzjcSBB4ZVt/1MRwIaEfRXrqAklYbMOtw8D6Onth6IQ7dS
|
||||
27ulUPD+AA8H5Ilm2XhPI6ttnR+822+mgB1K9WAjmuIbDJwQJl32UmTUiXPwdlWm
|
||||
KqyKZGST8wV6Jylha84ETL99IlJsxoAMGmNshMe4t773n0LousAvnbVt9uZcBNUH
|
||||
d/53EWP7kw1PPhY6jo0gLVK/ZQt8MHBJuT3f5IM9dmkLJmyM9t8tEmvjNEoC2xSW
|
||||
JHxoWfbMOxxFx+S5CwwcvySpKltio7s5bf/dYexE+Dv/+zMDeMb8GXv811TCqsD2
|
||||
lm4kTsHjwd+zSGCuvH+R4vWbkSrd65CATmFsVpVPLsIVXDa3DrgFQBmUI/q9UFaG
|
||||
K25HztaHk8GJkBZdBA7xXxzSJxW37yqPGKqQ/ZoqSz6XFnsgy5AsW0fDM+cPUl7l
|
||||
tftOlLeSqkaBq9O++76yY2asb0AyqjNs0tajluzxGFZ38DOqA1xkltV/c+KYnEv3
|
||||
q+K4agMHIKMr7lGJfSBevz3mG+NJ7AU=
|
||||
-----END CERTIFICATE-----
|
||||
28
test/tlsnocert/server.key
Normal file
28
test/tlsnocert/server.key
Normal file
@@ -0,0 +1,28 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCy5g19zgf+A5cy
|
||||
cys/xHXOalXY3SDf7vKQKUkD3r09JgLEitbsNjDm3jqOAgwgZmm2Hrrxs2eLd0on
|
||||
o3bByvqgGDBVudZcU27pb+ZFD70wTmNiQhDZFQxY6nCvEvSLSP1M05pwaJb2ZlCs
|
||||
0Th8wP6RW/45IZc6iTvk6iAVyixeYHRRU4hx3Kn6FMlTlZ/uPVfM+ltq6JM+ECFm
|
||||
uQ5YelUtQelV3YoSb+T/R1fD/BTrXn4QPOcmklSpRUvwAl0W+RZrZmBwZ4IS5LR0
|
||||
u9bPynD3l9u0NTXCoZmTLx0R/eYahHD9uUukhruvXogi5MaWySqht357X6KC1A30
|
||||
djx0FCe1AgMBAAECggEAagD5A49+mtwjzigB+4H80Def8KVuomIi5psgAaQM+9u3
|
||||
DiC6ozKlHVeW2KiL6PLmNpzU5v0IINKpZP1uE/yjLxPGKDW6t/BUKww8JLXjw2jf
|
||||
aMx+0TKwo0sfRA32S0YPmWNVAsBmm1AbA5vhXcK51QXuiInH406H5+d25ZJrYevF
|
||||
liKWSjx9CM/0XO7t20j18mCa8RjBEdsZoHxHsoWNvFJ6DCR25cFShAhR7s4OtkUk
|
||||
yELm1tYYrFOffUM0Q/Fp9uSlCHWMSqPtf/6NEfnszfFEtzDh/N+YqC1Bexv0XPsD
|
||||
dBPOkUZjWA2Sc8Se1t2GLfrRURzj3GvWH1+GssjsAQKBgQDeIdyzQSqce4Kn0opa
|
||||
vdS5moCiv3pyfNd0nYe0awgVos4kHY7/nBq0eyMZAatRHeD3DunVsw3LmvWyEw53
|
||||
app7MTTjVrYaadoBlB6jy2elyF5RcW2jGchZExoNh+0ZQWUiMbYEozPLQTy9ZxMz
|
||||
t0OcZ1hHPngGgmj5TELZKkwEtQKBgQDOLLh7pdKrdudtim+o4H20jl5yYKl25Iq/
|
||||
DKVodwUd94cM7xAIOQJrx2XK/YPCfRkKRN1wxzAhYdIVkaaKDVhI8Jeu+H18QOa6
|
||||
5OlzzZcqJCtACpbVqLaDcmq8pRrAYekiwMIKwC95llvktjilvLfoUnQoXAaX8E8B
|
||||
yCSUvDh3AQKBgQCxa0h04DLho4Da/D3HdmHHERF3bAqoEPCh0wTF5MsjRNLzY6yI
|
||||
mq11w/hni77C3mOF0SKRrh7xpcZiQfhHBx12EfpVLjfq5uraYe0LFHanol87G6bf
|
||||
I8Oy6Z/geNW2W1YktqHUGGpRCL0z5nUe1FyrOpv2431Ibbbcj73A6JipFQKBgHdl
|
||||
vJyWpk73+AQe1JUnFIU4oYd5ZQpeRd9n8m5x5ru4+jPKSi2I3lcOTWvlrqU2Dwc8
|
||||
ZEUIhV3/qUsmYxy1p7ft5NnGO912NGhtYqjWmcEk2wsmVr17C99JpniC4OAik4G1
|
||||
wWm6bIPsSGFGCb4pcROQlIY+7O6WkxqEDnM4ITcBAoGAHXBKmadFpupUeGSkCwEo
|
||||
/VjeI4QoKKcWj9K8z8ifCVPz1FiQ1AJ91WMTM7PAmpEDX058Hor9xxJ2bEtQFwUS
|
||||
QKvjeU+/Ig0TWjsJBgBPvc0xYLaJptAbjvG4a5nBn7hwbRzLTcKx2OVTmdAkz00H
|
||||
1lq8cwizfwNgt8ldFFDDRvw=
|
||||
-----END PRIVATE KEY-----
|
||||
23
test/tlssamesubdn/myca.crt
Normal file
23
test/tlssamesubdn/myca.crt
Normal file
@@ -0,0 +1,23 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIID2zCCAsOgAwIBAgIUdbPSj6WWhFu2amL9voKbyCMKiB4wDQYJKoZIhvcNAQEL
|
||||
BQAwfTELMAkGA1UEBhMCSU4xCzAJBgNVBAgMAktBMQwwCgYDVQQHDANCTFIxDDAK
|
||||
BgNVBAoMA0lCTTEMMAoGA1UECwwDSVNMMRAwDgYDVQQDDAdNUU1GVFFNMSUwIwYJ
|
||||
KoZIhvcNAQkBFhZzaGFzaGlrYW50aEBpbi5pYm0uY29tMB4XDTIzMDEyNTA1MjEz
|
||||
NloXDTI4MDEyNDA1MjEzNlowfTELMAkGA1UEBhMCSU4xCzAJBgNVBAgMAktBMQww
|
||||
CgYDVQQHDANCTFIxDDAKBgNVBAoMA0lCTTEMMAoGA1UECwwDSVNMMRAwDgYDVQQD
|
||||
DAdNUU1GVFFNMSUwIwYJKoZIhvcNAQkBFhZzaGFzaGlrYW50aEBpbi5pYm0uY29t
|
||||
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwl9MlmCNG7kzk7qKGuBX
|
||||
jTONcn2OcifKfwgJlvzTqHcf8X/BIiyOOiwoeznke///LLtt9ygF0iyBMQYM/CG/
|
||||
rzOR6tbzI4y4Bmx6VqY02CkXi/p66ywQ7B5N2Fdp9Bop3SnthTcT4NoXBSEUhI1O
|
||||
ob8lDFv1KMRkCULD2sA0FUYCrHtw0M/vEOqsA9VVjyzOXsIlbbR1BSXtlWNneGeL
|
||||
OAdmQWO3QYCku/YrCyJlscvIisjp4s7guGnQh0Ws8h50R5sqag8RvdHUwExVLUfZ
|
||||
L1Od0+hCiO5mNfKekT0cs9owplcwgNHw88b8q4/aHDBtQRgsukkMxTpo00ftPJxI
|
||||
nwIDAQABo1MwUTAdBgNVHQ4EFgQUOyw89AeB0jXb5WCZv/5oDY8oWxkwHwYDVR0j
|
||||
BBgwFoAUOyw89AeB0jXb5WCZv/5oDY8oWxkwDwYDVR0TAQH/BAUwAwEB/zANBgkq
|
||||
hkiG9w0BAQsFAAOCAQEAtbybQQ9GpY5gH7xz4EWOUZ7XMmBYtuGXVrqUd+76hvCA
|
||||
H/SB0nl2bGp7tAKBttmXhfKVac6wFCbXvYe49B+Q9+iL7H9st9VZUPKLQ6K3Uet6
|
||||
L1ggMm2BhecpuYbwkG7ZidVFo/SuUCbCTnXBgHjvq4IkVCaJe7aKZmejSCh7gsIR
|
||||
BQkZvz/22Vx/WPTEYp0x/riIvSViBjLCuD25Y+nCtS8c2xGVBjs9Q4GWCOAvEfAr
|
||||
Tqs42brH1Vs92xS143p2h/wv52tmhfJI6X9QVQBBUoIjPR/VDFqZU5EYhAvuQPBi
|
||||
UADz9hNYGQ9wBzZGvzbrorpoT+7aW9nGtmUsvvupBA==
|
||||
-----END CERTIFICATE-----
|
||||
3
test/tlssamesubdn/readme.txt
Normal file
3
test/tlssamesubdn/readme.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
This directory contains private key and a certificate that signed by CA.
|
||||
The directory also contains the CA certificate. The certificate and it's
|
||||
CA certificate have same Subject DNs.
|
||||
21
test/tlssamesubdn/samesubdn.crt
Normal file
21
test/tlssamesubdn/samesubdn.crt
Normal file
@@ -0,0 +1,21 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDgTCCAmkCFClOtyvBoQXVAGMcX0ObUawZJuX/MA0GCSqGSIb3DQEBCwUAMH0x
|
||||
CzAJBgNVBAYTAklOMQswCQYDVQQIDAJLQTEMMAoGA1UEBwwDQkxSMQwwCgYDVQQK
|
||||
DANJQk0xDDAKBgNVBAsMA0lTTDEQMA4GA1UEAwwHTVFNRlRRTTElMCMGCSqGSIb3
|
||||
DQEJARYWc2hhc2hpa2FudGhAaW4uaWJtLmNvbTAeFw0yMzAxMjUwNTM4MzBaFw0y
|
||||
NTA0MjkwNTM4MzBaMH0xCzAJBgNVBAYTAklOMQswCQYDVQQIDAJLQTEMMAoGA1UE
|
||||
BwwDQkxSMQwwCgYDVQQKDANJQk0xDDAKBgNVBAsMA0lTTDEQMA4GA1UEAwwHTVFN
|
||||
RlRRTTElMCMGCSqGSIb3DQEJARYWc2hhc2hpa2FudGhAaW4uaWJtLmNvbTCCASIw
|
||||
DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALJNAGirCyLzsHpV2cyso8pc2BOP
|
||||
4j6lu2ZTsdSE+WVZsqX3YWmV8TCYpJQRD4jmlrYIEdYAGmKPZvbvWjhzZWOJIxfI
|
||||
MGRwQ4dl8bSDZpcc7OAcHgLaW1OnMy2/1NgVyyXOKTaG4PmzZCzSKnhDF8/+FhUm
|
||||
rIYi5EU6bn1MuY34HOswAau0G7YVeroBUPs+LUjUpQx4vp5Baz9sWBzWcvsZsoSo
|
||||
LTMWEqCocqYOhj1ALrwN61NzdRwVtoS41dcjHAiFKEVTKIbqE0ib57Of1sohO9dr
|
||||
JgjegBNi18lsVUqDnYvydbe1hTEZfoXyN3QHm6QZLYtKB0RqR4HlcufnFWkCAwEA
|
||||
ATANBgkqhkiG9w0BAQsFAAOCAQEAtgG/2/7q2xPJG+Z0xJIyS9O7h6Igus2LhrAw
|
||||
tkudX5FZtJclBmDx3KvtJMPzpoGFRF48nXaqNcDSI3+8MiMk+JfpfGtmFbYuaa+l
|
||||
seqk/3byv/y1ofd8JMk0olY219tI1/BK4AU+fPP1obYV0tFcwIDOqQaS6f/N4RP1
|
||||
MO0PE+j9dDO6GQjIEUHZVVis70WvGaTc6DfGPVehZzDbNW9nTdkuacZf+XtlLVXq
|
||||
LzJpGeMxvX7SRRkdyegNigiWHJzOchVcoNnO+rUm9JDjY8pukDC08uZEQVqNpOXD
|
||||
tO1t4rpBT9HWxPRCLt8IwOhoYAGb5DWwUeIaiY637CSPTJVjCQ==
|
||||
-----END CERTIFICATE-----
|
||||
27
test/tlssamesubdn/samesubdn.key
Normal file
27
test/tlssamesubdn/samesubdn.key
Normal file
@@ -0,0 +1,27 @@
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEogIBAAKCAQEAsk0AaKsLIvOwelXZzKyjylzYE4/iPqW7ZlOx1IT5ZVmypfdh
|
||||
aZXxMJiklBEPiOaWtggR1gAaYo9m9u9aOHNlY4kjF8gwZHBDh2XxtINmlxzs4Bwe
|
||||
AtpbU6czLb/U2BXLJc4pNobg+bNkLNIqeEMXz/4WFSashiLkRTpufUy5jfgc6zAB
|
||||
q7QbthV6ugFQ+z4tSNSlDHi+nkFrP2xYHNZy+xmyhKgtMxYSoKhypg6GPUAuvA3r
|
||||
U3N1HBW2hLjV1yMcCIUoRVMohuoTSJvns5/WyiE712smCN6AE2LXyWxVSoOdi/J1
|
||||
t7WFMRl+hfI3dAebpBkti0oHRGpHgeVy5+cVaQIDAQABAoIBAGlAmVAwQBe24OOm
|
||||
kDaJZvrLSeZqVnUC4pgqKdy5Tnusso/Uc5WfpMw6H1UkqRX4gNkd9GFumCS8YFy+
|
||||
uHSAckaKqsOcMizoNITWAhO8SbBEq/QzxOeMPMMp/UUxa5TPcKd1htCgWGgupKDs
|
||||
w5NQj9sBM8nylX9tU3EmaBjhVNvfsZSeA76s0z+JDDwJLUlH33dwvgidw+baRxYM
|
||||
U6Tr3Z7YCFZ7+iZbOTHZjkKVtS2EqHtU5OW/pQltbBSovhYQMXjBsCHpTK78+jbV
|
||||
jKjtDUYWplcAYoN/O8ljYHU6lKQlTfo8KQInLtek/Ycen7uOCT6kFAreWFg08g4x
|
||||
5IH/pXECgYEA4ivLQjKR1BHiO+6rDZIfQxvbTa2RgT3Yr9W923OpGY1LiuapGFfJ
|
||||
97PjVKQf6HromQuzveYZR9YFKR4u8YTUn5PCB16v9MEc7nxnwIfE1kH5aEMvMSq9
|
||||
MvxvYObs3dJu4dx+AF8q8REuviBkQ2br34ukbl8ssaB/7zHPXyNJDt0CgYEAydD3
|
||||
BNtPRwKorQKOC1VXQxAFtlu/iiia4LEocAODDUX8N0iGxRBpQcfY/OuqV2j8Alfx
|
||||
n7+Zv/NGEah+WLaApc6wZVvOOuJevam/3ZJReawUtpCcG5em388WSQDuIj3AKhVV
|
||||
IzRRRFlGrTuHENdsqG5rBzbYWvans4lpVkB6Kf0CgYA0YUQfvqp7XPDyRGIlMHRB
|
||||
DJCLuuj576LhhjUEQBMnscuPDcCXvK0vyt+ZWLFzHBQGbelgz4uHUY+8aBsjIEpF
|
||||
8uh64HkLzDWxHerBcjAqFvl2Jiklz+olhsUcwh6VeQjpEjG0UFYXoo0ax1GxMoLq
|
||||
MOMSFMS7FprKgNSwCfH/mQKBgBLA8lNnNcS5gIcjN6Ph+EvpDz7U48Wo5EuA6usN
|
||||
yH3RRRC2Ep/5WG6ebZGDLL8WqGRnW7KmkVj++EHn0GXZ/7ZosIeasl1Sb89cGNI0
|
||||
KJIP5ZTZd0gxHXaqvb1m8roH1vWSKektkWeyjBjI8VIlUpmMOTHgbNZ3GTpbyIgV
|
||||
UcTZAoGAJp5AX+5u6OhLcCI56dpKYGiIS+8fXd2+oz7uZYf2yNTZONulehh2earM
|
||||
4soiUF5ArPgc3KbZhorvIXjOUSb5wc7I2nQKxXQt9j2fyZMD8qsyI1kBqLQ53Z0r
|
||||
agAkX1xMvhvHMQFKmPMXt3pUfXEx/oa32f3ciyPAhyyQNpv5gx4=
|
||||
-----END RSA PRIVATE KEY-----
|
||||
Reference in New Issue
Block a user