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