github.com/hernad/nomad@v1.6.112/e2e/e2eutil/consul.go (about) 1 // Copyright (c) HashiCorp, Inc. 2 // SPDX-License-Identifier: MPL-2.0 3 4 package e2eutil 5 6 import ( 7 "fmt" 8 "testing" 9 "time" 10 11 capi "github.com/hashicorp/consul/api" 12 "github.com/hernad/nomad/testutil" 13 "github.com/kr/pretty" 14 "github.com/stretchr/testify/assert" 15 "github.com/stretchr/testify/require" 16 ) 17 18 // RequireConsulStatus asserts the aggregate health of the service converges to the expected status. 19 func RequireConsulStatus(require *require.Assertions, client *capi.Client, namespace, service, expectedStatus string) { 20 testutil.WaitForResultRetries(30, func() (bool, error) { 21 defer time.Sleep(time.Second) // needs a long time for killing tasks/clients 22 23 _, status := serviceStatus(require, client, namespace, service) 24 return status == expectedStatus, fmt.Errorf("service %s/%s: expected %s but found %s", namespace, service, expectedStatus, status) 25 }, func(err error) { 26 require.NoError(err, "timedout waiting for consul status") 27 }) 28 } 29 30 // serviceStatus gets the aggregate health of the service and returns the []ServiceEntry for further checking. 31 func serviceStatus(require *require.Assertions, client *capi.Client, namespace, service string) ([]*capi.ServiceEntry, string) { 32 services, _, err := client.Health().Service(service, "", false, &capi.QueryOptions{Namespace: namespace}) 33 require.NoError(err, "expected no error for %s/%s, got %s", namespace, service, err) 34 if len(services) > 0 { 35 return services, services[0].Checks.AggregatedStatus() 36 } 37 return nil, "(unknown status)" 38 } 39 40 // RequireConsulDeregistered asserts that the service eventually is de-registered from Consul. 41 func RequireConsulDeregistered(require *require.Assertions, client *capi.Client, namespace, service string) { 42 testutil.WaitForResultRetries(5, func() (bool, error) { 43 defer time.Sleep(time.Second) 44 45 services, _, err := client.Health().Service(service, "", false, &capi.QueryOptions{Namespace: namespace}) 46 require.NoError(err) 47 if len(services) != 0 { 48 return false, fmt.Errorf("service %v: expected empty services but found %v %v", service, len(services), pretty.Sprint(services)) 49 } 50 return true, nil 51 }, func(err error) { 52 require.NoError(err) 53 }) 54 } 55 56 // RequireConsulRegistered assert that the service is registered in Consul. 57 func RequireConsulRegistered(require *require.Assertions, client *capi.Client, namespace, service string, count int) { 58 testutil.WaitForResultRetries(10, func() (bool, error) { 59 defer time.Sleep(2 * time.Second) 60 61 services, _, err := client.Catalog().Service(service, "", &capi.QueryOptions{Namespace: namespace}) 62 require.NoError(err) 63 if len(services) != count { 64 return false, fmt.Errorf("service %v: expected %v services but found %v %v", service, count, len(services), pretty.Sprint(services)) 65 } 66 return true, nil 67 }, func(err error) { 68 require.NoError(err) 69 }) 70 } 71 72 // CreateConsulNamespaces will create each namespace in Consul, with a description 73 // containing the namespace name. 74 // 75 // Requires Consul Enterprise. 76 func CreateConsulNamespaces(t *testing.T, client *capi.Client, namespaces []string) { 77 nsClient := client.Namespaces() 78 79 for _, namespace := range namespaces { 80 _, _, err := nsClient.Create(&capi.Namespace{ 81 Name: namespace, 82 Description: fmt.Sprintf("An e2e namespace called %q", namespace), 83 }, nil) 84 require.NoError(t, err) 85 } 86 } 87 88 // DeleteConsulNamespaces will delete each namespace from Consul. 89 // 90 // Requires Consul Enterprise. 91 func DeleteConsulNamespaces(t *testing.T, client *capi.Client, namespaces []string) { 92 nsClient := client.Namespaces() 93 94 for _, namespace := range namespaces { 95 _, err := nsClient.Delete(namespace, nil) 96 assert.NoError(t, err) // be lenient; used in cleanup 97 } 98 } 99 100 // ListConsulNamespaces will list the namespaces in Consul. 101 // 102 // Requires Consul Enterprise. 103 func ListConsulNamespaces(t *testing.T, client *capi.Client) []string { 104 nsClient := client.Namespaces() 105 106 namespaces, _, err := nsClient.List(nil) 107 require.NoError(t, err) 108 109 result := make([]string, 0, len(namespaces)) 110 for _, namespace := range namespaces { 111 result = append(result, namespace.Name) 112 } 113 return result 114 } 115 116 // PutConsulKey sets key:value in the Consul KV store under given namespace. 117 // 118 // Requires Consul Enterprise. 119 func PutConsulKey(t *testing.T, client *capi.Client, namespace, key, value string) { 120 kvClient := client.KV() 121 opts := &capi.WriteOptions{Namespace: namespace} 122 123 _, err := kvClient.Put(&capi.KVPair{Key: key, Value: []byte(value)}, opts) 124 require.NoError(t, err) 125 } 126 127 // DeleteConsulKey deletes the key from the Consul KV store from given namespace. 128 // 129 // Requires Consul Enterprise. 130 func DeleteConsulKey(t *testing.T, client *capi.Client, namespace, key string) { 131 kvClient := client.KV() 132 opts := &capi.WriteOptions{Namespace: namespace} 133 134 _, err := kvClient.Delete(key, opts) 135 require.NoError(t, err) 136 } 137 138 // ReadConsulConfigEntry retrieves the ConfigEntry of the given namespace, kind, 139 // and name. 140 // 141 // Requires Consul Enterprise. 142 func ReadConsulConfigEntry(t *testing.T, client *capi.Client, namespace, kind, name string) capi.ConfigEntry { 143 ceClient := client.ConfigEntries() 144 opts := &capi.QueryOptions{Namespace: namespace} 145 146 ce, _, err := ceClient.Get(kind, name, opts) 147 require.NoError(t, err) 148 return ce 149 } 150 151 // DeleteConsulConfigEntry deletes the ConfigEntry of the given namespace, kind, 152 // and name. 153 // 154 // Requires Consul Enterprise. 155 func DeleteConsulConfigEntry(t *testing.T, client *capi.Client, namespace, kind, name string) { 156 ceClient := client.ConfigEntries() 157 opts := &capi.WriteOptions{Namespace: namespace} 158 159 _, err := ceClient.Delete(kind, name, opts) 160 require.NoError(t, err) 161 } 162 163 // ConsulPolicy is used for create Consul ACL policies that Consul ACL tokens 164 // can make use of. 165 type ConsulPolicy struct { 166 Name string // e.g. nomad-operator 167 Rules string // e.g. service "" { policy="write" } 168 } 169 170 // CreateConsulPolicy is used to create a Consul ACL policy backed by the given 171 // ConsulPolicy in the specified namespace. 172 // 173 // Requires Consul Enterprise. 174 func CreateConsulPolicy(t *testing.T, client *capi.Client, namespace string, policy ConsulPolicy) string { 175 aclClient := client.ACL() 176 opts := &capi.WriteOptions{Namespace: namespace} 177 178 result, _, err := aclClient.PolicyCreate(&capi.ACLPolicy{ 179 Name: policy.Name, 180 Rules: policy.Rules, 181 Description: fmt.Sprintf("An e2e test policy %q", policy.Name), 182 }, opts) 183 require.NoError(t, err, "failed to create consul acl policy") 184 return result.ID 185 } 186 187 // DeleteConsulPolicies is used to delete a set Consul ACL policies from Consul. 188 // 189 // Requires Consul Enterprise. 190 func DeleteConsulPolicies(t *testing.T, client *capi.Client, policies map[string][]string) { 191 aclClient := client.ACL() 192 193 for namespace, policyIDs := range policies { 194 opts := &capi.WriteOptions{Namespace: namespace} 195 for _, policyID := range policyIDs { 196 _, err := aclClient.PolicyDelete(policyID, opts) 197 assert.NoError(t, err) 198 } 199 } 200 } 201 202 // CreateConsulToken is used to create a Consul ACL token backed by the policy of 203 // the given policyID in the specified namespace. 204 // 205 // Requires Consul Enterprise. 206 func CreateConsulToken(t *testing.T, client *capi.Client, namespace, policyID string) string { 207 aclClient := client.ACL() 208 opts := &capi.WriteOptions{Namespace: namespace} 209 210 token, _, err := aclClient.TokenCreate(&capi.ACLToken{ 211 Policies: []*capi.ACLTokenPolicyLink{{ID: policyID}}, 212 Description: "An e2e test token", 213 }, opts) 214 require.NoError(t, err, "failed to create consul acl token") 215 return token.SecretID 216 } 217 218 // DeleteConsulTokens is used to delete a set of tokens from Consul. 219 // 220 // Requires Consul Enterprise. 221 func DeleteConsulTokens(t *testing.T, client *capi.Client, tokens map[string][]string) { 222 aclClient := client.ACL() 223 224 for namespace, tokenIDs := range tokens { 225 opts := &capi.WriteOptions{Namespace: namespace} 226 for _, tokenID := range tokenIDs { 227 _, err := aclClient.TokenDelete(tokenID, opts) 228 assert.NoError(t, err) 229 } 230 } 231 }