diff --git a/config/v1.3/aws-k8s-cni.yaml b/config/v1.3/aws-k8s-cni.yaml index 33956b84c7..7cc05ac10b 100644 --- a/config/v1.3/aws-k8s-cni.yaml +++ b/config/v1.3/aws-k8s-cni.yaml @@ -69,7 +69,7 @@ spec: tolerations: - operator: Exists containers: - - image: 602401143452.dkr.ecr.us-west-2.amazonaws.com/amazon-k8s-cni:v1.3.3 + - image: 602401143452.dkr.ecr.us-west-2.amazonaws.com/amazon-k8s-cni:v1.3.4 imagePullPolicy: Always ports: - containerPort: 61678 @@ -126,5 +126,3 @@ spec: plural: eniconfigs singular: eniconfig kind: ENIConfig - - diff --git a/config/v1.4/calico.yaml b/config/v1.4/calico.yaml index c84bb33628..7c3cb3739b 100644 --- a/config/v1.4/calico.yaml +++ b/config/v1.4/calico.yaml @@ -62,6 +62,10 @@ spec: # Set Felix endpoint to host default action to ACCEPT. - name: FELIX_DEFAULTENDPOINTTOHOSTACTION value: "ACCEPT" + # This will make Felix honor AWS VPC CNI's mangle table + # rules. + - name: FELIX_IPTABLESMANGLEALLOWACTION + value: Return # Disable IPV6 on Kubernetes. - name: FELIX_IPV6SUPPORT value: "false" @@ -86,9 +90,6 @@ spec: value: "true" securityContext: privileged: true - resources: - requests: - cpu: 250m livenessProbe: httpGet: path: /liveness @@ -451,6 +452,10 @@ spec: value: "1" - name: TYPHA_HEALTHENABLED value: "true" + # This will make Felix honor AWS VPC CNI's mangle table + # rules. + - name: FELIX_IPTABLESMANGLEALLOWACTION + value: Return livenessProbe: exec: command: diff --git a/config/v1.4/cni_metrics_helper.yaml b/config/v1.4/cni_metrics_helper.yaml deleted file mode 100644 index 3724fcaa78..0000000000 --- a/config/v1.4/cni_metrics_helper.yaml +++ /dev/null @@ -1,85 +0,0 @@ ---- -apiVersion: rbac.authorization.k8s.io/v1 -# kubernetes versions before 1.8.0 should use rbac.authorization.k8s.io/v1beta1 -kind: ClusterRole -metadata: - name: cni-metrics-helper -rules: -- apiGroups: [""] - resources: - - nodes - - pods - - pods/proxy - - services - - resourcequotas - - replicationcontrollers - - limitranges - - persistentvolumeclaims - - persistentvolumes - - namespaces - - endpoints - verbs: ["list", "watch", "get"] -- apiGroups: ["extensions"] - resources: - - daemonsets - - deployments - - replicasets - verbs: ["list", "watch"] -- apiGroups: ["apps"] - resources: - - statefulsets - verbs: ["list", "watch"] -- apiGroups: ["batch"] - resources: - - cronjobs - - jobs - verbs: ["list", "watch"] -- apiGroups: ["autoscaling"] - resources: - - horizontalpodautoscalers - verbs: ["list", "watch"] ---- -apiVersion: v1 -kind: ServiceAccount -metadata: - name: cni-metrics-helper - namespace: kube-system ---- -apiVersion: rbac.authorization.k8s.io/v1 -# kubernetes versions before 1.8.0 should use rbac.authorization.k8s.io/v1beta1 -kind: ClusterRoleBinding -metadata: - name: cni-metrics-helper -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: ClusterRole - name: cni-metrics-helper -subjects: -- kind: ServiceAccount - name: cni-metrics-helper - namespace: kube-system ---- -kind: Deployment -apiVersion: extensions/v1beta1 -metadata: - name: cni-metrics-helper - namespace: kube-system - labels: - k8s-app: cni-metrics-helper -spec: - selector: - matchLabels: - k8s-app: cni-metrics-helper - template: - metadata: - labels: - k8s-app: cni-metrics-helper - spec: - serviceAccountName: cni-metrics-helper - containers: - - image: 694065802095.dkr.ecr.us-west-2.amazonaws.com/cni-metrics-helper:v1.4.0 - imagePullPolicy: Always - name: cni-metrics-helper - env: - - name: USE_CLOUDWATCH - value: "yes" diff --git a/ipamd/datastore/data_store.go b/ipamd/datastore/data_store.go index f6ceba04ae..8df804fa81 100644 --- a/ipamd/datastore/data_store.go +++ b/ipamd/datastore/data_store.go @@ -319,21 +319,42 @@ func (ds *DataStore) GetStats() (int, int) { return ds.total, ds.assigned } -func (ds *DataStore) getDeletableENI() *ENIIPPool { +// IsRequiredForWarmIPTarget determines if this ENI has warm IPs that are required to fulfill whatever WARM_IP_TARGET is +// set to. +func (ds *DataStore) isRequiredForWarmIPTarget(warmIPTarget int, eni *ENIIPPool) bool { + otherWarmIPs := 0 + for _, other := range ds.eniIPPools { + if other.ID != eni.ID { + otherWarmIPs += len(other.IPv4Addresses) - other.AssignedIPv4Addresses + } + } + return otherWarmIPs < warmIPTarget +} + +func (ds *DataStore) getDeletableENI(warmIPTarget int) *ENIIPPool { for _, eni := range ds.eniIPPools { if eni.IsPrimary { + log.Debugf("ENI %s cannot be deleted because it is primary", eni.ID) + continue + } + + if eni.isTooYoung() { + log.Debugf("ENI %s cannot be deleted because it is too young", eni.ID) continue } - if time.Now().Sub(eni.createTime) < minLifeTime { + if eni.hasIPInCooling() { + log.Debugf("ENI %s cannot be deleted because has IPs in cooling", eni.ID) continue } - if time.Now().Sub(eni.lastUnassignedTime) < addressENICoolingPeriod { + if eni.hasPods() { + log.Debugf("ENI %s cannot be deleted because it has pods assigned", eni.ID) continue } - if eni.AssignedIPv4Addresses != 0 { + if warmIPTarget != 0 && ds.isRequiredForWarmIPTarget(warmIPTarget, eni) { + log.Debugf("ENI %s cannot be deleted because it is required for WARM_IP_TARGET: %d", eni.ID, warmIPTarget) continue } @@ -343,6 +364,21 @@ func (ds *DataStore) getDeletableENI() *ENIIPPool { return nil } +// IsTooYoung returns true if the ENI hasn't been around long enough to be deleted. +func (e *ENIIPPool) isTooYoung() bool { + return time.Now().Sub(e.createTime) < minLifeTime +} + +// HasIPInCooling returns true if an IP address was unassigned recently. +func (e *ENIIPPool) hasIPInCooling() bool { + return time.Now().Sub(e.lastUnassignedTime) < addressENICoolingPeriod +} + +// HasPods returns true if the ENI has pods assigned to it. +func (e *ENIIPPool) hasPods() bool { + return e.AssignedIPv4Addresses != 0 +} + // GetENINeedsIP finds an ENI in the datastore that needs more IP addresses allocated func (ds *DataStore) GetENINeedsIP(maxIPperENI int, skipPrimary bool) *ENIIPPool { for _, eni := range ds.eniIPPools { @@ -362,11 +398,11 @@ func (ds *DataStore) GetENINeedsIP(maxIPperENI int, skipPrimary bool) *ENIIPPool // RemoveUnusedENIFromStore removes a deletable ENI from the data store. // It returns the name of the ENI which has been removed from the data store and needs to be deleted, // or empty string if no ENI could be removed. -func (ds *DataStore) RemoveUnusedENIFromStore() string { +func (ds *DataStore) RemoveUnusedENIFromStore(warmIPTarget int) string { ds.lock.Lock() defer ds.lock.Unlock() - deletableENI := ds.getDeletableENI() + deletableENI := ds.getDeletableENI(warmIPTarget) if deletableENI == nil { log.Debugf("No ENI can be deleted at this time") return "" diff --git a/ipamd/datastore/data_store_test.go b/ipamd/datastore/data_store_test.go index e8e7bb2c66..2efca93606 100644 --- a/ipamd/datastore/data_store_test.go +++ b/ipamd/datastore/data_store_test.go @@ -287,13 +287,15 @@ func TestPodIPv4Address(t *testing.T) { assert.Equal(t, len(ds.eniIPPools["eni-2"].IPv4Addresses), 1) assert.Equal(t, ds.eniIPPools["eni-2"].AssignedIPv4Addresses, 0) + noWarmIPTarget := 0 + // should not able to free this eni - eni := ds.RemoveUnusedENIFromStore() + eni := ds.RemoveUnusedENIFromStore(noWarmIPTarget) assert.True(t, eni == "") ds.eniIPPools["eni-2"].createTime = time.Time{} ds.eniIPPools["eni-2"].lastUnassignedTime = time.Time{} - eni = ds.RemoveUnusedENIFromStore() + eni = ds.RemoveUnusedENIFromStore(noWarmIPTarget) assert.Equal(t, eni, "eni-2") assert.Equal(t, ds.total, 2) diff --git a/ipamd/introspect.go b/ipamd/introspect.go index df723d6694..c054abec95 100644 --- a/ipamd/introspect.go +++ b/ipamd/introspect.go @@ -70,7 +70,6 @@ func (c *IPAMContext) ServeIntrospection() { } func (c *IPAMContext) setupIntrospectionServer() *http.Server { - // If enabled, add introspection endpoints serverFunctions := map[string]func(w http.ResponseWriter, r *http.Request){ "/v1/enis": eniV1RequestHandler(c), "/v1/eni-configs": eniConfigRequestHandler(c), @@ -87,7 +86,7 @@ func (c *IPAMContext) setupIntrospectionServer() *http.Server { availableCommandResponse, err := json.Marshal(&availableCommands) if err != nil { - log.Error("Failed to marshal: %v", err) + log.Errorf("Failed to marshal: %v", err) } defaultHandler := func(w http.ResponseWriter, r *http.Request) { diff --git a/ipamd/ipamd.go b/ipamd/ipamd.go index 32015ad8be..6642552066 100644 --- a/ipamd/ipamd.go +++ b/ipamd/ipamd.go @@ -19,6 +19,7 @@ import ( "os" "strconv" "strings" + "sync" "time" log "github.com/cihub/seelog" @@ -51,6 +52,10 @@ const ( maxK8SRetries = 12 retryK8SInterval = 5 * time.Second + // ipReconcileCooldown is the amount of time that an IP address must wait until it can be added to the data store + // during reconciliation after being discovered on the EC2 instance metadata. + ipReconcileCooldown = 60 * time.Second + // This environment variable is used to specify the desired number of free IPs always available in the "warm pool". // When it is not set, ipamd defaults to use all available IPs per ENI for that instance type. // For example, for a m4.4xlarge node, @@ -160,6 +165,46 @@ type IPAMContext struct { primaryIP map[string]string lastNodeIPPoolAction time.Time lastDecreaseIPPool time.Time + + // reconcileCooldownCache keeps timestamps of the last time an IP address was unassigned from an ENI, + // so that we don't reconcile and add it back too quickly if IMDS lags behind reality. + reconcileCooldownCache ReconcileCooldownCache +} + +// Keep track of recently freed IPs to avoid reading stale EC2 metadata +type ReconcileCooldownCache struct { + cache map[string]time.Time + lock sync.RWMutex +} + +// Add sets a timestamp for the list of IPs added that says how long they are not to be put back in the data store. +func (r *ReconcileCooldownCache) Add(ips []string) { + r.lock.Lock() + defer r.lock.Unlock() + expiry := time.Now().Add(ipReconcileCooldown) + for _, ip := range ips { + r.cache[ip] = expiry + } +} + +// Remove removes an IP from the cooldown cache. +func (r *ReconcileCooldownCache) Remove(ip string) { + r.lock.Lock() + defer r.lock.Unlock() + log.Debugf("Removing %s from cooldown cache.", ip) + delete(r.cache, ip) +} + +// RecentlyFreed checks if this IP was recently freed. +func (r *ReconcileCooldownCache) RecentlyFreed(ip string) (found, recentlyFreed bool) { + r.lock.Lock() + defer r.lock.Unlock() + now := time.Now() + if expiry, ok := r.cache[ip]; ok { + log.Debugf("Checking if IP %s has been recently freed. Cooldown expires at: %s. (Cooldown: %v)", ip, expiry, now.Sub(expiry) < 0) + return true, now.Sub(expiry) < 0 + } + return false, false } func prometheusRegister() { @@ -208,7 +253,11 @@ func (c *IPAMContext) nodeInit() error { log.Debugf("Start node init") - instanceMaxENIs, _ := c.awsClient.GetENILimit() + instanceMaxENIs, err := c.awsClient.GetENILimit() + if err != nil { + log.Errorf("Failed to get ENI limit: %s") + } + maxENIs := getMaxENI(instanceMaxENIs) if maxENIs >= 1 { enisMax.Set(float64(maxENIs)) @@ -219,6 +268,7 @@ func (c *IPAMContext) nodeInit() error { ipMax.Set(float64(maxIPs * maxENIs)) } c.primaryIP = make(map[string]string) + c.reconcileCooldownCache.cache = make(map[string]time.Time) enis, err := c.awsClient.GetAttachedENIs() if err != nil { @@ -241,12 +291,25 @@ func (c *IPAMContext) nodeInit() error { c.dataStore = datastore.NewDataStore() for _, eni := range enis { - log.Debugf("Discovered ENI %s", eni.ENIID) + log.Debugf("Discovered ENI %s, trying to set it up", eni.ENIID) + // Retry ENI sync + retry := 0 + for { + retry++ + err = c.setupENI(eni.ENIID, eni) + if retry > maxRetryCheckENI { + log.Errorf("Unable to discover attached IPs for ENI from metadata service") + ipamdErrInc("waitENIAttachedMaxRetryExceeded", err) + break + } - err = c.setupENI(eni.ENIID, eni) - if err != nil { - log.Errorf("Failed to setup ENI %s network: %v", eni.ENIID, err) - return errors.Wrapf(err, "Failed to setup ENI %v", eni.ENIID) + if err != nil { + log.Debugf("Unable to discover IPs for this ENI yet (attempt %d/%d)", retry, maxRetryCheckENI) + time.Sleep(eniAttachTime) + continue + } + log.Infof("ENI %s set up.", eni.ENIID) + break } } @@ -256,7 +319,7 @@ func (c *IPAMContext) nodeInit() error { ipamdErrInc("nodeInitK8SGetLocalPodIPsFailed", err) // This can happens when L-IPAMD starts before kubelet. // TODO need to add node health stats here - return nil + return errors.Wrap(err, "failed to get running pods!") } rules, err := c.networkClient.GetRuleList() @@ -316,12 +379,11 @@ func (c *IPAMContext) getLocalPodsWithRetry() ([]*k8sapi.K8SPodInfo, error) { } if err != nil { - return nil, err + return nil, errors.Wrap(err, "no pods because apiserver not running.") } if pods == nil { - log.Info("No pods found on this node") - return pods, nil + return nil, nil } var containers map[string]*docker.ContainerInfo @@ -350,9 +412,11 @@ func (c *IPAMContext) getLocalPodsWithRetry() ([]*k8sapi.K8SPodInfo, error) { // StartNodeIPPoolManager monitors the IP pool, add or del them when it is required. func (c *IPAMContext) StartNodeIPPoolManager() { + sleepDuration := ipPoolMonitorInterval / 2 for { - time.Sleep(ipPoolMonitorInterval) + time.Sleep(sleepDuration) c.updateIPPoolIfRequired() + time.Sleep(sleepDuration) c.nodeIPPoolReconcile(nodeIPPoolReconcileInterval) } } @@ -363,6 +427,10 @@ func (c *IPAMContext) updateIPPoolIfRequired() { } else if c.nodeIPPoolTooHigh() { c.decreaseIPPool(decreaseIPPoolInterval) } + + if c.shouldRemoveExtraENIs() { + c.tryFreeENI() + } } // decreaseIPPool runs every `interval` and attempts to return unused ENIs and IPs @@ -379,7 +447,6 @@ func (c *IPAMContext) decreaseIPPool(interval time.Duration) { log.Debugf("Starting to decrease IP pool") - c.tryFreeENI() c.tryUnassignIPsFromAll() c.lastDecreaseIPPool = now @@ -391,7 +458,9 @@ func (c *IPAMContext) decreaseIPPool(interval time.Duration) { // tryFreeENI always trys to free one ENI func (c *IPAMContext) tryFreeENI() { - eni := c.dataStore.RemoveUnusedENIFromStore() + warmIPTarget := getWarmIPTarget() + + eni := c.dataStore.RemoveUnusedENIFromStore(warmIPTarget) if eni == "" { log.Info("No ENI to remove, all ENIs have IPs in use") return @@ -426,7 +495,7 @@ func (c *IPAMContext) tryUnassignIPsFromAll() { for _, toDelete := range ips { err := c.dataStore.DelIPv4AddressFromStore(eniID, toDelete) if err != nil { - log.Errorf("Failed to delete IP %s on ENI %s from datastore: %s", toDelete, eniID, err) + log.Warnf("Failed to delete IP %s on ENI %s from datastore: %s", toDelete, eniID, err) ipamdErrInc("decreaseIPPool", err) continue } else { @@ -436,10 +505,14 @@ func (c *IPAMContext) tryUnassignIPsFromAll() { // Deallocate IPs from the instance if they aren't used by pods. if err := c.awsClient.DeallocIPAddresses(eniID, deletedIPs); err != nil { - log.Debugf(fmt.Sprintf("Failed to decrease IP pool by removing IPs %v from ENI %s: %s", ips, eniID, err)) + log.Warnf("Failed to decrease IP pool by removing IPs %v from ENI %s: %s", deletedIPs, eniID, err) } else { - log.Debugf(fmt.Sprintf("Successfully decreased IP pool by removing IPs %v from ENI %s", ips, eniID)) + log.Debugf("Successfully decreased IP pool by removing IPs %v from ENI %s", deletedIPs, eniID) } + + // Track the last time we unassigned IPs from an ENI. We won't reconcile any IPs in this cache + // for at least ipReconcileCooldown + c.reconcileCooldownCache.Add(deletedIPs) } } } @@ -488,7 +561,7 @@ func isAttachmentLimitExceededError(err error) bool { } func (c *IPAMContext) increaseIPPool() { - log.Debug("Start increasing IP pool size") + log.Debug("Starting to increase IP pool size") ipamdActionsInprogress.WithLabelValues("increaseIPPool").Add(float64(1)) defer ipamdActionsInprogress.WithLabelValues("increaseIPPool").Sub(float64(1)) @@ -499,23 +572,45 @@ func (c *IPAMContext) increaseIPPool() { } instanceMaxENIs, err := c.awsClient.GetENILimit() + if err != nil { + log.Errorf("Failed to get ENI limit: %s") + } + + // instanceMaxENIs will be 0 if the instance type is unknown. In this case, getMaxENI returns 0 or will use + // MAX_ENI if it is set. maxENIs := getMaxENI(instanceMaxENIs) if maxENIs >= 1 { enisMax.Set(float64(maxENIs)) } - if err == nil && maxENIs == c.dataStore.GetENIs() { - log.Debugf("Skipping increase IP pool due to max ENI already attached to the instance: %d", maxENIs) + // Unknown instance type and MAX_ENI is not set + if maxENIs == 0 { + log.Errorf("Unknown instance type and MAX_ENI is not set. Cannot increase IP pool.") return } - if (c.maxENI > 0) && (c.maxENI == c.dataStore.GetENIs()) { - log.Debugf("Skipping increase IP pool due to max ENI already attached to the instance: %d", c.maxENI) - return + + if c.dataStore.GetENIs() < maxENIs { + // c.maxENI represent the discovered maximum number of ENIs + if (c.maxENI > 0) && (c.maxENI == c.dataStore.GetENIs()) { + log.Debugf("Skipping ENI allocation due to max ENI already attached to the instance: %d", c.maxENI) + } else { + c.tryAllocateENI() + c.updateLastNodeIPPoolAction() + } + } else { + log.Debugf("Skipping ENI allocation due to max ENI already attached to the instance: %d", maxENIs) } - c.tryAllocateENI() - c.tryAssignIPs() + increasedPool, err := c.tryAssignIPs() + if err != nil { + log.Errorf(err.Error()) + } + if increasedPool { + c.updateLastNodeIPPoolAction() + } +} +func (c *IPAMContext) updateLastNodeIPPoolAction() { c.lastNodeIPPoolAction = time.Now() total, used := c.dataStore.GetStats() log.Debugf("Successfully increased IP pool") @@ -576,7 +671,7 @@ func (c *IPAMContext) tryAllocateENI() { eniMetadata, err := c.waitENIAttached(eni) if err != nil { ipamdErrInc("increaseIPPoolwaitENIAttachedFailed", err) - log.Errorf("Failed to increase pool size: not able to discover attached ENI from metadata service %v", err) + log.Errorf("Failed to increase pool size: Unable to discover attached ENI from metadata service %v", err) return } @@ -589,40 +684,39 @@ func (c *IPAMContext) tryAllocateENI() { } // For an ENI, try to fill in missing IPs -func (c *IPAMContext) tryAssignIPs() { +func (c *IPAMContext) tryAssignIPs() (increasedPool bool, err error) { // if WARM_IP_TARGET is set, only proceed if we are short of target short, _, warmIPTargetDefined := c.ipTargetState() if warmIPTargetDefined && short == 0 { - return + return false, nil } maxIPPerENI, err := c.awsClient.GetENIipLimit() if err != nil { - log.Infof("Failed to retrieve ENI IP limit: %v", err) - return + return false, errors.Wrap(err, "failed to retrieve ENI IP limit during IP allocation") } eni := c.dataStore.GetENINeedsIP(maxIPPerENI, UseCustomNetworkCfg()) - if len(eni.IPv4Addresses) < maxIPPerENI { - log.Debugf("Found ENI %s that has less than the maximum number of IP addresses allocated: cur=%d, max=%d", - eni.ID, len(eni.IPv4Addresses), maxIPPerENI) + if eni != nil && len(eni.IPv4Addresses) < maxIPPerENI { + log.Debugf("Found ENI %s that has less than the maximum number of IP addresses allocated: cur=%d, max=%d", eni.ID, len(eni.IPv4Addresses), maxIPPerENI) err = c.awsClient.AllocIPAddresses(eni.ID, maxIPPerENI-len(eni.IPv4Addresses)) if err != nil { - log.Warnf("Failed to allocate all available ip addresses on an eni %s: %s", eni.ID, err) + return false, errors.Wrap(err, fmt.Sprintf("failed to allocate all available IP addresses on ENI %s", eni.ID)) } ec2Addrs, _, err := c.getENIaddresses(eni.ID) if err != nil { ipamdErrInc("increaseIPPoolGetENIaddressesFailed", err) - log.Warn("During eni repair: failed to get ENI ip addresses", err) - return + return true, errors.Wrap(err, "failed to get ENI IP addresses during IP allocation") } c.addENIaddressesToDataStore(ec2Addrs, eni.ID) + return true, nil } + return false, nil } // setupENI does following: @@ -651,6 +745,7 @@ func (c *IPAMContext) setupENI(eni string, eniMetadata awsutils.ENIMetadata) err // an ENI, for example: ipamd has NOT allocated all IPs on the ENI yet. c.currentMaxAddrsPerENI = len(ec2Addrs) } + if c.currentMaxAddrsPerENI > c.maxAddrsPerENI { c.maxAddrsPerENI = c.currentMaxAddrsPerENI } @@ -682,7 +777,6 @@ func (c *IPAMContext) addENIaddressesToDataStore(ec2Addrs []*ec2.NetworkInterfac ipamdErrInc("addENIaddressesToDataStoreAddENIIPv4AddressFailed", err) } } - return primaryIP } @@ -763,7 +857,7 @@ func getWarmENITarget() int { if input < 0 { return defaultWarmENITarget } - log.Debugf("Using WARM-ENI-TARGET %v", input) + log.Debugf("Using WARM_ENI_TARGET %v", input) return input } return defaultWarmENITarget @@ -781,29 +875,53 @@ func (c *IPAMContext) nodeIPPoolTooLow() bool { return short > 0 } - // If WARM-IP-TARGET not defined fallback using number of ENIs + // If WARM_IP_TARGET not defined fallback using number of ENIs warmENITarget := getWarmENITarget() total, used := c.dataStore.GetStats() logPoolStats(total, used, c.currentMaxAddrsPerENI, c.maxAddrsPerENI) available := total - used - return available < c.maxAddrsPerENI*warmENITarget + poolTooLow := available < c.maxAddrsPerENI*warmENITarget + if poolTooLow { + log.Debugf("IP pool is too low: available (%d) < ENI target (%d) * addrsPerENI (%d)", available, warmENITarget, c.maxAddrsPerENI) + } else { + log.Debugf("IP pool is NOT too low: available (%d) >= ENI target (%d) * addrsPerENI (%d)", available, warmENITarget, c.maxAddrsPerENI) + } + return poolTooLow } // nodeIPPoolTooHigh returns true if IP pool is above high threshold func (c *IPAMContext) nodeIPPoolTooHigh() bool { + _, over, warmIPTargetDefined := c.ipTargetState() + if warmIPTargetDefined { + return over > 0 + } + + // We only ever report the pool being too high if WARM_IP_TARGET is set + return false +} + +// shouldRemoveExtraENIs returns true if we should attempt to find an ENI to free. When WARM_IP_TARGET is set, we +// always check and do verification in getDeletableENI() +func (c *IPAMContext) shouldRemoveExtraENIs() bool { + _, _, warmIPTargetDefined := c.ipTargetState() + if warmIPTargetDefined { + return true + } + warmENITarget := getWarmENITarget() total, used := c.dataStore.GetStats() logPoolStats(total, used, c.currentMaxAddrsPerENI, c.maxAddrsPerENI) available := total - used - - target := getWarmIPTarget() - if target != noWarmIPTarget { - return target > available + // We need the +1 to make sure we are not going below the WARM_ENI_TARGET. + shouldRemoveExtra := available >= (warmENITarget+1)*c.maxAddrsPerENI + if shouldRemoveExtra { + log.Debugf("It might be possible to remove extra ENIs because available (%d) > ENI target (%d) * addrsPerENI (%d): ", available, warmENITarget, c.maxAddrsPerENI) + } else { + log.Debugf("Its NOT possible to remove extra ENIs because available (%d) <= ENI target (%d) * addrsPerENI (%d): ", available, warmENITarget, c.maxAddrsPerENI) } - - return available >= (warmENITarget+1)*c.maxAddrsPerENI + return shouldRemoveExtra } func ipamdErrInc(fn string, err error) { @@ -826,7 +944,7 @@ func (c *IPAMContext) nodeIPPoolReconcile(interval time.Duration) { attachedENIs, err := c.awsClient.GetAttachedENIs() if err != nil { - log.Error("IP pool reconcile: Failed to get attached ENI info", err.Error()) + log.Errorf("IP pool reconcile: Failed to get attached ENI info: %v", err.Error()) ipamdErrInc("reconcileFailedGetENIs", err) return } @@ -880,6 +998,39 @@ func (c *IPAMContext) eniIPPoolReconcile(ipPool map[string]*datastore.AddressInf continue } + // Check if this IP was recently freed + found, recentlyFreed := c.reconcileCooldownCache.RecentlyFreed(localIP) + if found { + if recentlyFreed { + log.Debugf("Reconcile skipping IP %s on ENI %s because it was recently unassigned from the ENI.", localIP, eni) + continue + } else { + log.Debugf("This IP was recently freed, but is out of cooldown. We need to verify with EC2 control plane.") + // Call EC2 to verify + ec2Addresses, _, err := c.getENIaddresses(eni) + if err != nil { + log.Error("Failed to fetch ENI IP addresses!") + continue + } else { + // Verify that the IP really belongs to this ENI + isReallyAttachedToENI := false + for _, ec2Addr := range ec2Addresses { + if localIP == aws.StringValue(ec2Addr.PrivateIpAddress) { + isReallyAttachedToENI = true + log.Debugf("Verified that IP %s is attached to ENI %s", localIP, eni) + break + } + } + if isReallyAttachedToENI { + c.reconcileCooldownCache.Remove(localIP) + } else { + log.Warnf(" Skipping IP %s on ENI %s because it does not belong to this ENI!.", localIP, eni) + continue + } + } + } + } + err := c.dataStore.AddIPv4AddressFromStore(eni, localIP) if err != nil && err.Error() == datastore.DuplicateIPError { log.Debugf("Reconciled IP %s on ENI %s", localIP, eni) @@ -932,7 +1083,7 @@ func getWarmIPTarget() int { if input, err := strconv.Atoi(inputStr); err == nil { if input >= 0 { - log.Debugf("Using WARM-IP-TARGET %v", input) + log.Debugf("Using WARM_IP_TARGET %v", input) return input } } diff --git a/ipamd/metrics.go b/ipamd/metrics.go index 795db72e95..8c36df8b31 100644 --- a/ipamd/metrics.go +++ b/ipamd/metrics.go @@ -55,7 +55,6 @@ func (c *IPAMContext) ServeMetrics() { } func (c *IPAMContext) setupMetricsServer() *http.Server { - // Always add the metrics endpoint serveMux := http.NewServeMux() serveMux.Handle("/metrics", promhttp.Handler()) server := &http.Server{ diff --git a/mocks/awsutils_mocks.go b/mocks/awsutils_mocks.go new file mode 100644 index 0000000000..e69de29bb2 diff --git a/pkg/awsutils/awsutils.go b/pkg/awsutils/awsutils.go index 77de0e9da7..f560dfc855 100644 --- a/pkg/awsutils/awsutils.go +++ b/pkg/awsutils/awsutils.go @@ -860,8 +860,7 @@ func (cache *EC2InstanceMetadataCache) GetENILimit() (int, error) { eniLimit, ok := InstanceENIsAvailable[cache.instanceType] if !ok { - log.Errorf("Failed to get ENI limit due to unknown instance type %s", cache.instanceType) - return 0, errors.New(UnknownInstanceType) + return 0, errors.New(fmt.Sprintf("%s: %s", UnknownInstanceType, cache.instanceType)) } return eniLimit, nil } @@ -880,7 +879,7 @@ func (cache *EC2InstanceMetadataCache) AllocIPAddresses(eniID string, numIPs int return nil } - log.Infof("Trying to allocate %d IP address on ENI %s", needIPs, eniID) + log.Infof("Trying to allocate %d IP addresses on ENI %s", needIPs, eniID) input := &ec2.AssignPrivateIpAddressesInput{ NetworkInterfaceId: aws.String(eniID), @@ -973,7 +972,6 @@ func (cache *EC2InstanceMetadataCache) DeallocIPAddresses(eniID string, ips []st awsAPILatency.WithLabelValues("UnassignPrivateIpAddressesWithContext", fmt.Sprint(err != nil)).Observe(msSince(start)) if err != nil { awsAPIErrInc("UnassignPrivateIpAddressesWithContext", err) - log.Errorf("Failed to deallocate a private IP address %v", err) return errors.Wrap(err, fmt.Sprintf("deallocate IP addresses: failed to deallocate private IP addresses: %s", ips)) } diff --git a/pkg/k8sapi/discovery.go b/pkg/k8sapi/discovery.go index a7470124e3..d41047a699 100644 --- a/pkg/k8sapi/discovery.go +++ b/pkg/k8sapi/discovery.go @@ -291,15 +291,16 @@ func (c *controller) handleErr(err error, key interface{}) { } func (d *Controller) run(threadiness int, stopCh chan struct{}) { - // Let the workers stop when we are done defer d.controller.queue.ShutDown() log.Info("Starting Pod controller") go d.controller.informer.Run(stopCh) + log.Info("Waiting for controller cache sync") // Wait for all involved caches to be synced, before processing items from the queue is started if !cache.WaitForCacheSync(stopCh, d.controller.informer.HasSynced) { + log.Error("Timed out waiting for caches to sync!") runtime.HandleError(fmt.Errorf("timed out waiting for caches to sync")) return } diff --git a/vendor/github.com/deckarep/golang-set/.gitignore b/vendor/github.com/deckarep/golang-set/.gitignore new file mode 100644 index 0000000000..00268614f0 --- /dev/null +++ b/vendor/github.com/deckarep/golang-set/.gitignore @@ -0,0 +1,22 @@ +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe diff --git a/vendor/github.com/deckarep/golang-set/.travis.yml b/vendor/github.com/deckarep/golang-set/.travis.yml new file mode 100644 index 0000000000..c760d24d1e --- /dev/null +++ b/vendor/github.com/deckarep/golang-set/.travis.yml @@ -0,0 +1,11 @@ +language: go + +go: + - 1.8 + - 1.9 + - tip + +script: + - go test -race ./... + - go test -bench=. + diff --git a/vendor/github.com/deckarep/golang-set/LICENSE b/vendor/github.com/deckarep/golang-set/LICENSE new file mode 100644 index 0000000000..b5768f89cf --- /dev/null +++ b/vendor/github.com/deckarep/golang-set/LICENSE @@ -0,0 +1,22 @@ +Open Source Initiative OSI - The MIT License (MIT):Licensing + +The MIT License (MIT) +Copyright (c) 2013 Ralph Caraveo (deckarep@gmail.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/vendor/github.com/deckarep/golang-set/README.md b/vendor/github.com/deckarep/golang-set/README.md new file mode 100644 index 0000000000..c3b50b2c5c --- /dev/null +++ b/vendor/github.com/deckarep/golang-set/README.md @@ -0,0 +1,95 @@ +[![Build Status](https://travis-ci.org/deckarep/golang-set.svg?branch=master)](https://travis-ci.org/deckarep/golang-set) +[![Go Report Card](https://goreportcard.com/badge/github.com/deckarep/golang-set)](https://goreportcard.com/report/github.com/deckarep/golang-set) +[![GoDoc](https://godoc.org/github.com/deckarep/golang-set?status.svg)](http://godoc.org/github.com/deckarep/golang-set) + +## golang-set + + +The missing set collection for the Go language. Until Go has sets built-in...use this. + +Coming from Python one of the things I miss is the superbly wonderful set collection. This is my attempt to mimic the primary features of the set from Python. +You can of course argue that there is no need for a set in Go, otherwise the creators would have added one to the standard library. To those I say simply ignore this repository +and carry-on and to the rest that find this useful please contribute in helping me make it better by: + +* Helping to make more idiomatic improvements to the code. +* Helping to increase the performance of it. ~~(So far, no attempt has been made, but since it uses a map internally, I expect it to be mostly performant.)~~ +* Helping to make the unit-tests more robust and kick-ass. +* Helping to fill in the [documentation.](http://godoc.org/github.com/deckarep/golang-set) +* Simply offering feedback and suggestions. (Positive, constructive feedback is appreciated.) + +I have to give some credit for helping seed the idea with this post on [stackoverflow.](http://programmers.stackexchange.com/questions/177428/sets-data-structure-in-golang) + +*Update* - as of 3/9/2014, you can use a compile-time generic version of this package in the [gen](http://clipperhouse.github.io/gen/) framework. This framework allows you to use the golang-set in a completely generic and type-safe way by allowing you to generate a supporting .go file based on your custom types. + +## Features (as of 9/22/2014) + +* a CartesianProduct() method has been added with unit-tests: [Read more about the cartesian product](http://en.wikipedia.org/wiki/Cartesian_product) + +## Features (as of 9/15/2014) + +* a PowerSet() method has been added with unit-tests: [Read more about the Power set](http://en.wikipedia.org/wiki/Power_set) + +## Features (as of 4/22/2014) + +* One common interface to both implementations +* Two set implementations to choose from + * a thread-safe implementation designed for concurrent use + * a non-thread-safe implementation designed for performance +* 75 benchmarks for both implementations +* 35 unit tests for both implementations +* 14 concurrent tests for the thread-safe implementation + + + +Please see the unit test file for additional usage examples. The Python set documentation will also do a better job than I can of explaining how a set typically [works.](http://docs.python.org/2/library/sets.html) Please keep in mind +however that the Python set is a built-in type and supports additional features and syntax that make it awesome. + +## Examples but not exhaustive: + +```go +requiredClasses := mapset.NewSet() +requiredClasses.Add("Cooking") +requiredClasses.Add("English") +requiredClasses.Add("Math") +requiredClasses.Add("Biology") + +scienceSlice := []interface{}{"Biology", "Chemistry"} +scienceClasses := mapset.NewSetFromSlice(scienceSlice) + +electiveClasses := mapset.NewSet() +electiveClasses.Add("Welding") +electiveClasses.Add("Music") +electiveClasses.Add("Automotive") + +bonusClasses := mapset.NewSet() +bonusClasses.Add("Go Programming") +bonusClasses.Add("Python Programming") + +//Show me all the available classes I can take +allClasses := requiredClasses.Union(scienceClasses).Union(electiveClasses).Union(bonusClasses) +fmt.Println(allClasses) //Set{Cooking, English, Math, Chemistry, Welding, Biology, Music, Automotive, Go Programming, Python Programming} + + +//Is cooking considered a science class? +fmt.Println(scienceClasses.Contains("Cooking")) //false + +//Show me all classes that are not science classes, since I hate science. +fmt.Println(allClasses.Difference(scienceClasses)) //Set{Music, Automotive, Go Programming, Python Programming, Cooking, English, Math, Welding} + +//Which science classes are also required classes? +fmt.Println(scienceClasses.Intersect(requiredClasses)) //Set{Biology} + +//How many bonus classes do you offer? +fmt.Println(bonusClasses.Cardinality()) //2 + +//Do you have the following classes? Welding, Automotive and English? +fmt.Println(allClasses.IsSuperset(mapset.NewSetFromSlice([]interface{}{"Welding", "Automotive", "English"}))) //true +``` + +Thanks! + +-Ralph + +[![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/deckarep/golang-set/trend.png)](https://bitdeli.com/free "Bitdeli Badge") + +[![Analytics](https://ga-beacon.appspot.com/UA-42584447-2/deckarep/golang-set)](https://github.com/igrigorik/ga-beacon) diff --git a/vendor/github.com/deckarep/golang-set/bench_test.go b/vendor/github.com/deckarep/golang-set/bench_test.go new file mode 100644 index 0000000000..f893d101d0 --- /dev/null +++ b/vendor/github.com/deckarep/golang-set/bench_test.go @@ -0,0 +1,674 @@ +package mapset + +import ( + "math/rand" + "testing" +) + +func nrand(n int) []int { + i := make([]int, n) + for ind := range i { + i[ind] = rand.Int() + } + return i +} + +func toInterfaces(i []int) []interface{} { + ifs := make([]interface{}, len(i)) + for ind, v := range i { + ifs[ind] = v + } + return ifs +} + +func benchAdd(b *testing.B, s Set) { + nums := nrand(b.N) + b.ResetTimer() + for _, v := range nums { + s.Add(v) + } +} + +func BenchmarkAddSafe(b *testing.B) { + benchAdd(b, NewSet()) +} + +func BenchmarkAddUnsafe(b *testing.B) { + benchAdd(b, NewThreadUnsafeSet()) +} + +func benchRemove(b *testing.B, s Set) { + nums := nrand(b.N) + for _, v := range nums { + s.Add(v) + } + + b.ResetTimer() + for _, v := range nums { + s.Remove(v) + } +} + +func BenchmarkRemoveSafe(b *testing.B) { + benchRemove(b, NewSet()) +} + +func BenchmarkRemoveUnsafe(b *testing.B) { + benchRemove(b, NewThreadUnsafeSet()) +} + +func benchCardinality(b *testing.B, s Set) { + for i := 0; i < b.N; i++ { + s.Cardinality() + } +} + +func BenchmarkCardinalitySafe(b *testing.B) { + benchCardinality(b, NewSet()) +} + +func BenchmarkCardinalityUnsafe(b *testing.B) { + benchCardinality(b, NewThreadUnsafeSet()) +} + +func benchClear(b *testing.B, s Set) { + b.ResetTimer() + for i := 0; i < b.N; i++ { + s.Clear() + } +} + +func BenchmarkClearSafe(b *testing.B) { + benchClear(b, NewSet()) +} + +func BenchmarkClearUnsafe(b *testing.B) { + benchClear(b, NewThreadUnsafeSet()) +} + +func benchClone(b *testing.B, n int, s Set) { + nums := toInterfaces(nrand(n)) + for _, v := range nums { + s.Add(v) + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + s.Clone() + } +} + +func BenchmarkClone1Safe(b *testing.B) { + benchClone(b, 1, NewSet()) +} + +func BenchmarkClone1Unsafe(b *testing.B) { + benchClone(b, 1, NewThreadUnsafeSet()) +} + +func BenchmarkClone10Safe(b *testing.B) { + benchClone(b, 10, NewSet()) +} + +func BenchmarkClone10Unsafe(b *testing.B) { + benchClone(b, 10, NewThreadUnsafeSet()) +} + +func BenchmarkClone100Safe(b *testing.B) { + benchClone(b, 100, NewSet()) +} + +func BenchmarkClone100Unsafe(b *testing.B) { + benchClone(b, 100, NewThreadUnsafeSet()) +} + +func benchContains(b *testing.B, n int, s Set) { + nums := toInterfaces(nrand(n)) + for _, v := range nums { + s.Add(v) + } + + nums[n-1] = -1 // Definitely not in s + + b.ResetTimer() + for i := 0; i < b.N; i++ { + s.Contains(nums...) + } +} + +func BenchmarkContains1Safe(b *testing.B) { + benchContains(b, 1, NewSet()) +} + +func BenchmarkContains1Unsafe(b *testing.B) { + benchContains(b, 1, NewThreadUnsafeSet()) +} + +func BenchmarkContains10Safe(b *testing.B) { + benchContains(b, 10, NewSet()) +} + +func BenchmarkContains10Unsafe(b *testing.B) { + benchContains(b, 10, NewThreadUnsafeSet()) +} + +func BenchmarkContains100Safe(b *testing.B) { + benchContains(b, 100, NewSet()) +} + +func BenchmarkContains100Unsafe(b *testing.B) { + benchContains(b, 100, NewThreadUnsafeSet()) +} + +func benchEqual(b *testing.B, n int, s, t Set) { + nums := nrand(n) + for _, v := range nums { + s.Add(v) + t.Add(v) + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + s.Equal(t) + } +} + +func BenchmarkEqual1Safe(b *testing.B) { + benchEqual(b, 1, NewSet(), NewSet()) +} + +func BenchmarkEqual1Unsafe(b *testing.B) { + benchEqual(b, 1, NewThreadUnsafeSet(), NewThreadUnsafeSet()) +} + +func BenchmarkEqual10Safe(b *testing.B) { + benchEqual(b, 10, NewSet(), NewSet()) +} + +func BenchmarkEqual10Unsafe(b *testing.B) { + benchEqual(b, 10, NewThreadUnsafeSet(), NewThreadUnsafeSet()) +} + +func BenchmarkEqual100Safe(b *testing.B) { + benchEqual(b, 100, NewSet(), NewSet()) +} + +func BenchmarkEqual100Unsafe(b *testing.B) { + benchEqual(b, 100, NewThreadUnsafeSet(), NewThreadUnsafeSet()) +} + +func benchDifference(b *testing.B, n int, s, t Set) { + nums := nrand(n) + for _, v := range nums { + s.Add(v) + } + for _, v := range nums[:n/2] { + t.Add(v) + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + s.Difference(t) + } +} + +func benchIsSubset(b *testing.B, n int, s, t Set) { + nums := nrand(n) + for _, v := range nums { + s.Add(v) + t.Add(v) + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + s.IsSubset(t) + } +} + +func BenchmarkIsSubset1Safe(b *testing.B) { + benchIsSubset(b, 1, NewSet(), NewSet()) +} + +func BenchmarkIsSubset1Unsafe(b *testing.B) { + benchIsSubset(b, 1, NewThreadUnsafeSet(), NewThreadUnsafeSet()) +} + +func BenchmarkIsSubset10Safe(b *testing.B) { + benchIsSubset(b, 10, NewSet(), NewSet()) +} + +func BenchmarkIsSubset10Unsafe(b *testing.B) { + benchIsSubset(b, 10, NewThreadUnsafeSet(), NewThreadUnsafeSet()) +} + +func BenchmarkIsSubset100Safe(b *testing.B) { + benchIsSubset(b, 100, NewSet(), NewSet()) +} + +func BenchmarkIsSubset100Unsafe(b *testing.B) { + benchIsSubset(b, 100, NewThreadUnsafeSet(), NewThreadUnsafeSet()) +} + +func benchIsSuperset(b *testing.B, n int, s, t Set) { + nums := nrand(n) + for _, v := range nums { + s.Add(v) + t.Add(v) + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + s.IsSuperset(t) + } +} + +func BenchmarkIsSuperset1Safe(b *testing.B) { + benchIsSuperset(b, 1, NewSet(), NewSet()) +} + +func BenchmarkIsSuperset1Unsafe(b *testing.B) { + benchIsSuperset(b, 1, NewThreadUnsafeSet(), NewThreadUnsafeSet()) +} + +func BenchmarkIsSuperset10Safe(b *testing.B) { + benchIsSuperset(b, 10, NewSet(), NewSet()) +} + +func BenchmarkIsSuperset10Unsafe(b *testing.B) { + benchIsSuperset(b, 10, NewThreadUnsafeSet(), NewThreadUnsafeSet()) +} + +func BenchmarkIsSuperset100Safe(b *testing.B) { + benchIsSuperset(b, 100, NewSet(), NewSet()) +} + +func BenchmarkIsSuperset100Unsafe(b *testing.B) { + benchIsSuperset(b, 100, NewThreadUnsafeSet(), NewThreadUnsafeSet()) +} + +func benchIsProperSubset(b *testing.B, n int, s, t Set) { + nums := nrand(n) + for _, v := range nums { + s.Add(v) + t.Add(v) + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + s.IsProperSubset(t) + } +} + +func BenchmarkIsProperSubset1Safe(b *testing.B) { + benchIsProperSubset(b, 1, NewSet(), NewSet()) +} + +func BenchmarkIsProperSubset1Unsafe(b *testing.B) { + benchIsProperSubset(b, 1, NewThreadUnsafeSet(), NewThreadUnsafeSet()) +} + +func BenchmarkIsProperSubset10Safe(b *testing.B) { + benchIsProperSubset(b, 10, NewSet(), NewSet()) +} + +func BenchmarkIsProperSubset10Unsafe(b *testing.B) { + benchIsProperSubset(b, 10, NewThreadUnsafeSet(), NewThreadUnsafeSet()) +} + +func BenchmarkIsProperSubset100Safe(b *testing.B) { + benchIsProperSubset(b, 100, NewSet(), NewSet()) +} + +func BenchmarkIsProperSubset100Unsafe(b *testing.B) { + benchIsProperSubset(b, 100, NewThreadUnsafeSet(), NewThreadUnsafeSet()) +} + +func benchIsProperSuperset(b *testing.B, n int, s, t Set) { + nums := nrand(n) + for _, v := range nums { + s.Add(v) + t.Add(v) + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + s.IsProperSuperset(t) + } +} + +func BenchmarkIsProperSuperset1Safe(b *testing.B) { + benchIsProperSuperset(b, 1, NewSet(), NewSet()) +} + +func BenchmarkIsProperSuperset1Unsafe(b *testing.B) { + benchIsProperSuperset(b, 1, NewThreadUnsafeSet(), NewThreadUnsafeSet()) +} + +func BenchmarkIsProperSuperset10Safe(b *testing.B) { + benchIsProperSuperset(b, 10, NewSet(), NewSet()) +} + +func BenchmarkIsProperSuperset10Unsafe(b *testing.B) { + benchIsProperSuperset(b, 10, NewThreadUnsafeSet(), NewThreadUnsafeSet()) +} + +func BenchmarkIsProperSuperset100Safe(b *testing.B) { + benchIsProperSuperset(b, 100, NewSet(), NewSet()) +} + +func BenchmarkIsProperSuperset100Unsafe(b *testing.B) { + benchIsProperSuperset(b, 100, NewThreadUnsafeSet(), NewThreadUnsafeSet()) +} + +func BenchmarkDifference1Safe(b *testing.B) { + benchDifference(b, 1, NewSet(), NewSet()) +} + +func BenchmarkDifference1Unsafe(b *testing.B) { + benchDifference(b, 1, NewThreadUnsafeSet(), NewThreadUnsafeSet()) +} + +func BenchmarkDifference10Safe(b *testing.B) { + benchDifference(b, 10, NewSet(), NewSet()) +} + +func BenchmarkDifference10Unsafe(b *testing.B) { + benchDifference(b, 10, NewThreadUnsafeSet(), NewThreadUnsafeSet()) +} + +func BenchmarkDifference100Safe(b *testing.B) { + benchDifference(b, 100, NewSet(), NewSet()) +} + +func BenchmarkDifference100Unsafe(b *testing.B) { + benchDifference(b, 100, NewThreadUnsafeSet(), NewThreadUnsafeSet()) +} + +func benchIntersect(b *testing.B, n int, s, t Set) { + nums := nrand(int(float64(n) * float64(1.5))) + for _, v := range nums[:n] { + s.Add(v) + } + for _, v := range nums[n/2:] { + t.Add(v) + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + s.Intersect(t) + } +} + +func BenchmarkIntersect1Safe(b *testing.B) { + benchIntersect(b, 1, NewSet(), NewSet()) +} + +func BenchmarkIntersect1Unsafe(b *testing.B) { + benchIntersect(b, 1, NewThreadUnsafeSet(), NewThreadUnsafeSet()) +} + +func BenchmarkIntersect10Safe(b *testing.B) { + benchIntersect(b, 10, NewSet(), NewSet()) +} + +func BenchmarkIntersect10Unsafe(b *testing.B) { + benchIntersect(b, 10, NewThreadUnsafeSet(), NewThreadUnsafeSet()) +} + +func BenchmarkIntersect100Safe(b *testing.B) { + benchIntersect(b, 100, NewSet(), NewSet()) +} + +func BenchmarkIntersect100Unsafe(b *testing.B) { + benchIntersect(b, 100, NewThreadUnsafeSet(), NewThreadUnsafeSet()) +} + +func benchSymmetricDifference(b *testing.B, n int, s, t Set) { + nums := nrand(int(float64(n) * float64(1.5))) + for _, v := range nums[:n] { + s.Add(v) + } + for _, v := range nums[n/2:] { + t.Add(v) + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + s.SymmetricDifference(t) + } +} + +func BenchmarkSymmetricDifference1Safe(b *testing.B) { + benchSymmetricDifference(b, 1, NewSet(), NewSet()) +} + +func BenchmarkSymmetricDifference1Unsafe(b *testing.B) { + benchSymmetricDifference(b, 1, NewThreadUnsafeSet(), NewThreadUnsafeSet()) +} + +func BenchmarkSymmetricDifference10Safe(b *testing.B) { + benchSymmetricDifference(b, 10, NewSet(), NewSet()) +} + +func BenchmarkSymmetricDifference10Unsafe(b *testing.B) { + benchSymmetricDifference(b, 10, NewThreadUnsafeSet(), NewThreadUnsafeSet()) +} + +func BenchmarkSymmetricDifference100Safe(b *testing.B) { + benchSymmetricDifference(b, 100, NewSet(), NewSet()) +} + +func BenchmarkSymmetricDifference100Unsafe(b *testing.B) { + benchSymmetricDifference(b, 100, NewThreadUnsafeSet(), NewThreadUnsafeSet()) +} + +func benchUnion(b *testing.B, n int, s, t Set) { + nums := nrand(n) + for _, v := range nums[:n/2] { + s.Add(v) + } + for _, v := range nums[n/2:] { + t.Add(v) + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + s.Union(t) + } +} + +func BenchmarkUnion1Safe(b *testing.B) { + benchUnion(b, 1, NewSet(), NewSet()) +} + +func BenchmarkUnion1Unsafe(b *testing.B) { + benchUnion(b, 1, NewThreadUnsafeSet(), NewThreadUnsafeSet()) +} + +func BenchmarkUnion10Safe(b *testing.B) { + benchUnion(b, 10, NewSet(), NewSet()) +} + +func BenchmarkUnion10Unsafe(b *testing.B) { + benchUnion(b, 10, NewThreadUnsafeSet(), NewThreadUnsafeSet()) +} + +func BenchmarkUnion100Safe(b *testing.B) { + benchUnion(b, 100, NewSet(), NewSet()) +} + +func BenchmarkUnion100Unsafe(b *testing.B) { + benchUnion(b, 100, NewThreadUnsafeSet(), NewThreadUnsafeSet()) +} + +func benchEach(b *testing.B, n int, s Set) { + nums := nrand(n) + for _, v := range nums { + s.Add(v) + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + s.Each(func(elem interface{}) bool { + return false + }) + } +} + +func BenchmarkEach1Safe(b *testing.B) { + benchEach(b, 1, NewSet()) +} + +func BenchmarkEach1Unsafe(b *testing.B) { + benchEach(b, 1, NewThreadUnsafeSet()) +} + +func BenchmarkEach10Safe(b *testing.B) { + benchEach(b, 10, NewSet()) +} + +func BenchmarkEach10Unsafe(b *testing.B) { + benchEach(b, 10, NewThreadUnsafeSet()) +} + +func BenchmarkEach100Safe(b *testing.B) { + benchEach(b, 100, NewSet()) +} + +func BenchmarkEach100Unsafe(b *testing.B) { + benchEach(b, 100, NewThreadUnsafeSet()) +} + +func benchIter(b *testing.B, n int, s Set) { + nums := nrand(n) + for _, v := range nums { + s.Add(v) + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + c := s.Iter() + for range c { + + } + } +} + +func BenchmarkIter1Safe(b *testing.B) { + benchIter(b, 1, NewSet()) +} + +func BenchmarkIter1Unsafe(b *testing.B) { + benchIter(b, 1, NewThreadUnsafeSet()) +} + +func BenchmarkIter10Safe(b *testing.B) { + benchIter(b, 10, NewSet()) +} + +func BenchmarkIter10Unsafe(b *testing.B) { + benchIter(b, 10, NewThreadUnsafeSet()) +} + +func BenchmarkIter100Safe(b *testing.B) { + benchIter(b, 100, NewSet()) +} + +func BenchmarkIter100Unsafe(b *testing.B) { + benchIter(b, 100, NewThreadUnsafeSet()) +} + +func benchIterator(b *testing.B, n int, s Set) { + nums := nrand(n) + for _, v := range nums { + s.Add(v) + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + c := s.Iterator().C + for range c { + + } + } +} + +func BenchmarkIterator1Safe(b *testing.B) { + benchIterator(b, 1, NewSet()) +} + +func BenchmarkIterator1Unsafe(b *testing.B) { + benchIterator(b, 1, NewThreadUnsafeSet()) +} + +func BenchmarkIterator10Safe(b *testing.B) { + benchIterator(b, 10, NewSet()) +} + +func BenchmarkIterator10Unsafe(b *testing.B) { + benchIterator(b, 10, NewThreadUnsafeSet()) +} + +func BenchmarkIterator100Safe(b *testing.B) { + benchIterator(b, 100, NewSet()) +} + +func BenchmarkIterator100Unsafe(b *testing.B) { + benchIterator(b, 100, NewThreadUnsafeSet()) +} + +func benchString(b *testing.B, n int, s Set) { + nums := nrand(n) + for _, v := range nums { + s.Add(v) + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + _ = s.String() + } +} + +func BenchmarkString1Safe(b *testing.B) { + benchString(b, 1, NewSet()) +} + +func BenchmarkString1Unsafe(b *testing.B) { + benchString(b, 1, NewThreadUnsafeSet()) +} + +func BenchmarkString10Safe(b *testing.B) { + benchString(b, 10, NewSet()) +} + +func BenchmarkString10Unsafe(b *testing.B) { + benchString(b, 10, NewThreadUnsafeSet()) +} + +func BenchmarkString100Safe(b *testing.B) { + benchString(b, 100, NewSet()) +} + +func BenchmarkString100Unsafe(b *testing.B) { + benchString(b, 100, NewThreadUnsafeSet()) +} + +func benchToSlice(b *testing.B, s Set) { + nums := nrand(b.N) + for _, v := range nums { + s.Add(v) + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + s.ToSlice() + } +} + +func BenchmarkToSliceSafe(b *testing.B) { + benchToSlice(b, NewSet()) +} + +func BenchmarkToSliceUnsafe(b *testing.B) { + benchToSlice(b, NewThreadUnsafeSet()) +} diff --git a/vendor/github.com/deckarep/golang-set/iterator.go b/vendor/github.com/deckarep/golang-set/iterator.go new file mode 100644 index 0000000000..9dfecade42 --- /dev/null +++ b/vendor/github.com/deckarep/golang-set/iterator.go @@ -0,0 +1,58 @@ +/* +Open Source Initiative OSI - The MIT License (MIT):Licensing + +The MIT License (MIT) +Copyright (c) 2013 Ralph Caraveo (deckarep@gmail.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +package mapset + +// Iterator defines an iterator over a Set, its C channel can be used to range over the Set's +// elements. +type Iterator struct { + C <-chan interface{} + stop chan struct{} +} + +// Stop stops the Iterator, no further elements will be received on C, C will be closed. +func (i *Iterator) Stop() { + // Allows for Stop() to be called multiple times + // (close() panics when called on already closed channel) + defer func() { + recover() + }() + + close(i.stop) + + // Exhaust any remaining elements. + for range i.C { + } +} + +// newIterator returns a new Iterator instance together with its item and stop channels. +func newIterator() (*Iterator, chan<- interface{}, <-chan struct{}) { + itemChan := make(chan interface{}) + stopChan := make(chan struct{}) + return &Iterator{ + C: itemChan, + stop: stopChan, + }, itemChan, stopChan +} diff --git a/vendor/github.com/deckarep/golang-set/iterator_example_test.go b/vendor/github.com/deckarep/golang-set/iterator_example_test.go new file mode 100644 index 0000000000..fc4235d748 --- /dev/null +++ b/vendor/github.com/deckarep/golang-set/iterator_example_test.go @@ -0,0 +1,32 @@ +package mapset + +import ( + "fmt" +) + +type YourType struct { + Name string +} + +func ExampleIterator() { + set := NewSetFromSlice([]interface{}{ + &YourType{Name: "Alise"}, + &YourType{Name: "Bob"}, + &YourType{Name: "John"}, + &YourType{Name: "Nick"}, + }) + + var found *YourType + it := set.Iterator() + + for elem := range it.C { + if elem.(*YourType).Name == "John" { + found = elem.(*YourType) + it.Stop() + } + } + + fmt.Printf("Found %+v\n", found) + + // Output: Found &{Name:John} +} diff --git a/vendor/github.com/deckarep/golang-set/set.go b/vendor/github.com/deckarep/golang-set/set.go new file mode 100644 index 0000000000..29eb2e5a22 --- /dev/null +++ b/vendor/github.com/deckarep/golang-set/set.go @@ -0,0 +1,217 @@ +/* +Open Source Initiative OSI - The MIT License (MIT):Licensing + +The MIT License (MIT) +Copyright (c) 2013 Ralph Caraveo (deckarep@gmail.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +// Package mapset implements a simple and generic set collection. +// Items stored within it are unordered and unique. It supports +// typical set operations: membership testing, intersection, union, +// difference, symmetric difference and cloning. +// +// Package mapset provides two implementations of the Set +// interface. The default implementation is safe for concurrent +// access, but a non-thread-safe implementation is also provided for +// programs that can benefit from the slight speed improvement and +// that can enforce mutual exclusion through other means. +package mapset + +// Set is the primary interface provided by the mapset package. It +// represents an unordered set of data and a large number of +// operations that can be applied to that set. +type Set interface { + // Adds an element to the set. Returns whether + // the item was added. + Add(i interface{}) bool + + // Returns the number of elements in the set. + Cardinality() int + + // Removes all elements from the set, leaving + // the empty set. + Clear() + + // Returns a clone of the set using the same + // implementation, duplicating all keys. + Clone() Set + + // Returns whether the given items + // are all in the set. + Contains(i ...interface{}) bool + + // Returns the difference between this set + // and other. The returned set will contain + // all elements of this set that are not also + // elements of other. + // + // Note that the argument to Difference + // must be of the same type as the receiver + // of the method. Otherwise, Difference will + // panic. + Difference(other Set) Set + + // Determines if two sets are equal to each + // other. If they have the same cardinality + // and contain the same elements, they are + // considered equal. The order in which + // the elements were added is irrelevant. + // + // Note that the argument to Equal must be + // of the same type as the receiver of the + // method. Otherwise, Equal will panic. + Equal(other Set) bool + + // Returns a new set containing only the elements + // that exist only in both sets. + // + // Note that the argument to Intersect + // must be of the same type as the receiver + // of the method. Otherwise, Intersect will + // panic. + Intersect(other Set) Set + + // Determines if every element in this set is in + // the other set but the two sets are not equal. + // + // Note that the argument to IsProperSubset + // must be of the same type as the receiver + // of the method. Otherwise, IsProperSubset + // will panic. + IsProperSubset(other Set) bool + + // Determines if every element in the other set + // is in this set but the two sets are not + // equal. + // + // Note that the argument to IsSuperset + // must be of the same type as the receiver + // of the method. Otherwise, IsSuperset will + // panic. + IsProperSuperset(other Set) bool + + // Determines if every element in this set is in + // the other set. + // + // Note that the argument to IsSubset + // must be of the same type as the receiver + // of the method. Otherwise, IsSubset will + // panic. + IsSubset(other Set) bool + + // Determines if every element in the other set + // is in this set. + // + // Note that the argument to IsSuperset + // must be of the same type as the receiver + // of the method. Otherwise, IsSuperset will + // panic. + IsSuperset(other Set) bool + + // Iterates over elements and executes the passed func against each element. + // If passed func returns true, stop iteration at the time. + Each(func(interface{}) bool) + + // Returns a channel of elements that you can + // range over. + Iter() <-chan interface{} + + // Returns an Iterator object that you can + // use to range over the set. + Iterator() *Iterator + + // Remove a single element from the set. + Remove(i interface{}) + + // Provides a convenient string representation + // of the current state of the set. + String() string + + // Returns a new set with all elements which are + // in either this set or the other set but not in both. + // + // Note that the argument to SymmetricDifference + // must be of the same type as the receiver + // of the method. Otherwise, SymmetricDifference + // will panic. + SymmetricDifference(other Set) Set + + // Returns a new set with all elements in both sets. + // + // Note that the argument to Union must be of the + + // same type as the receiver of the method. + // Otherwise, IsSuperset will panic. + Union(other Set) Set + + // Pop removes and returns an arbitrary item from the set. + Pop() interface{} + + // Returns all subsets of a given set (Power Set). + PowerSet() Set + + // Returns the Cartesian Product of two sets. + CartesianProduct(other Set) Set + + // Returns the members of the set as a slice. + ToSlice() []interface{} +} + +// NewSet creates and returns a reference to an empty set. Operations +// on the resulting set are thread-safe. +func NewSet(s ...interface{}) Set { + set := newThreadSafeSet() + for _, item := range s { + set.Add(item) + } + return &set +} + +// NewSetWith creates and returns a new set with the given elements. +// Operations on the resulting set are thread-safe. +func NewSetWith(elts ...interface{}) Set { + return NewSetFromSlice(elts) +} + +// NewSetFromSlice creates and returns a reference to a set from an +// existing slice. Operations on the resulting set are thread-safe. +func NewSetFromSlice(s []interface{}) Set { + a := NewSet(s...) + return a +} + +// NewThreadUnsafeSet creates and returns a reference to an empty set. +// Operations on the resulting set are not thread-safe. +func NewThreadUnsafeSet() Set { + set := newThreadUnsafeSet() + return &set +} + +// NewThreadUnsafeSetFromSlice creates and returns a reference to a +// set from an existing slice. Operations on the resulting set are +// not thread-safe. +func NewThreadUnsafeSetFromSlice(s []interface{}) Set { + a := NewThreadUnsafeSet() + for _, item := range s { + a.Add(item) + } + return a +} diff --git a/vendor/github.com/deckarep/golang-set/set_test.go b/vendor/github.com/deckarep/golang-set/set_test.go new file mode 100644 index 0000000000..6cdf5833c5 --- /dev/null +++ b/vendor/github.com/deckarep/golang-set/set_test.go @@ -0,0 +1,1200 @@ +/* +Open Source Initiative OSI - The MIT License (MIT):Licensing + +The MIT License (MIT) +Copyright (c) 2013 Ralph Caraveo (deckarep@gmail.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +package mapset + +import "testing" + +func makeSet(ints []int) Set { + set := NewSet() + for _, i := range ints { + set.Add(i) + } + return set +} + +func makeUnsafeSet(ints []int) Set { + set := NewThreadUnsafeSet() + for _, i := range ints { + set.Add(i) + } + return set +} + +func assertEqual(a, b Set, t *testing.T) { + if !a.Equal(b) { + t.Errorf("%v != %v\n", a, b) + } +} + +func Test_NewSet(t *testing.T) { + a := NewSet() + if a.Cardinality() != 0 { + t.Error("NewSet should start out as an empty set") + } + + assertEqual(NewSetFromSlice([]interface{}{}), NewSet(), t) + assertEqual(NewSetFromSlice([]interface{}{1}), NewSet(1), t) + assertEqual(NewSetFromSlice([]interface{}{1, 2}), NewSet(1, 2), t) + assertEqual(NewSetFromSlice([]interface{}{"a"}), NewSet("a"), t) + assertEqual(NewSetFromSlice([]interface{}{"a", "b"}), NewSet("a", "b"), t) +} + +func Test_NewUnsafeSet(t *testing.T) { + a := NewThreadUnsafeSet() + + if a.Cardinality() != 0 { + t.Error("NewSet should start out as an empty set") + } +} + +func Test_AddSet(t *testing.T) { + a := makeSet([]int{1, 2, 3}) + + if a.Cardinality() != 3 { + t.Error("AddSet does not have a size of 3 even though 3 items were added to a new set") + } +} + +func Test_AddUnsafeSet(t *testing.T) { + a := makeUnsafeSet([]int{1, 2, 3}) + + if a.Cardinality() != 3 { + t.Error("AddSet does not have a size of 3 even though 3 items were added to a new set") + } +} + +func Test_AddSetNoDuplicate(t *testing.T) { + a := makeSet([]int{7, 5, 3, 7}) + + if a.Cardinality() != 3 { + t.Error("AddSetNoDuplicate set should have 3 elements since 7 is a duplicate") + } + + if !(a.Contains(7) && a.Contains(5) && a.Contains(3)) { + t.Error("AddSetNoDuplicate set should have a 7, 5, and 3 in it.") + } +} + +func Test_AddUnsafeSetNoDuplicate(t *testing.T) { + a := makeUnsafeSet([]int{7, 5, 3, 7}) + + if a.Cardinality() != 3 { + t.Error("AddSetNoDuplicate set should have 3 elements since 7 is a duplicate") + } + + if !(a.Contains(7) && a.Contains(5) && a.Contains(3)) { + t.Error("AddSetNoDuplicate set should have a 7, 5, and 3 in it.") + } +} + +func Test_RemoveSet(t *testing.T) { + a := makeSet([]int{6, 3, 1}) + + a.Remove(3) + + if a.Cardinality() != 2 { + t.Error("RemoveSet should only have 2 items in the set") + } + + if !(a.Contains(6) && a.Contains(1)) { + t.Error("RemoveSet should have only items 6 and 1 in the set") + } + + a.Remove(6) + a.Remove(1) + + if a.Cardinality() != 0 { + t.Error("RemoveSet should be an empty set after removing 6 and 1") + } +} + +func Test_RemoveUnsafeSet(t *testing.T) { + a := makeUnsafeSet([]int{6, 3, 1}) + + a.Remove(3) + + if a.Cardinality() != 2 { + t.Error("RemoveSet should only have 2 items in the set") + } + + if !(a.Contains(6) && a.Contains(1)) { + t.Error("RemoveSet should have only items 6 and 1 in the set") + } + + a.Remove(6) + a.Remove(1) + + if a.Cardinality() != 0 { + t.Error("RemoveSet should be an empty set after removing 6 and 1") + } +} + +func Test_ContainsSet(t *testing.T) { + a := NewSet() + + a.Add(71) + + if !a.Contains(71) { + t.Error("ContainsSet should contain 71") + } + + a.Remove(71) + + if a.Contains(71) { + t.Error("ContainsSet should not contain 71") + } + + a.Add(13) + a.Add(7) + a.Add(1) + + if !(a.Contains(13) && a.Contains(7) && a.Contains(1)) { + t.Error("ContainsSet should contain 13, 7, 1") + } +} + +func Test_ContainsUnsafeSet(t *testing.T) { + a := NewThreadUnsafeSet() + + a.Add(71) + + if !a.Contains(71) { + t.Error("ContainsSet should contain 71") + } + + a.Remove(71) + + if a.Contains(71) { + t.Error("ContainsSet should not contain 71") + } + + a.Add(13) + a.Add(7) + a.Add(1) + + if !(a.Contains(13) && a.Contains(7) && a.Contains(1)) { + t.Error("ContainsSet should contain 13, 7, 1") + } +} + +func Test_ContainsMultipleSet(t *testing.T) { + a := makeSet([]int{8, 6, 7, 5, 3, 0, 9}) + + if !a.Contains(8, 6, 7, 5, 3, 0, 9) { + t.Error("ContainsAll should contain Jenny's phone number") + } + + if a.Contains(8, 6, 11, 5, 3, 0, 9) { + t.Error("ContainsAll should not have all of these numbers") + } +} + +func Test_ContainsMultipleUnsafeSet(t *testing.T) { + a := makeUnsafeSet([]int{8, 6, 7, 5, 3, 0, 9}) + + if !a.Contains(8, 6, 7, 5, 3, 0, 9) { + t.Error("ContainsAll should contain Jenny's phone number") + } + + if a.Contains(8, 6, 11, 5, 3, 0, 9) { + t.Error("ContainsAll should not have all of these numbers") + } +} + +func Test_ClearSet(t *testing.T) { + a := makeSet([]int{2, 5, 9, 10}) + + a.Clear() + + if a.Cardinality() != 0 { + t.Error("ClearSet should be an empty set") + } +} + +func Test_ClearUnsafeSet(t *testing.T) { + a := makeUnsafeSet([]int{2, 5, 9, 10}) + + a.Clear() + + if a.Cardinality() != 0 { + t.Error("ClearSet should be an empty set") + } +} + +func Test_CardinalitySet(t *testing.T) { + a := NewSet() + + if a.Cardinality() != 0 { + t.Error("set should be an empty set") + } + + a.Add(1) + + if a.Cardinality() != 1 { + t.Error("set should have a size of 1") + } + + a.Remove(1) + + if a.Cardinality() != 0 { + t.Error("set should be an empty set") + } + + a.Add(9) + + if a.Cardinality() != 1 { + t.Error("set should have a size of 1") + } + + a.Clear() + + if a.Cardinality() != 0 { + t.Error("set should have a size of 1") + } +} + +func Test_CardinalityUnsafeSet(t *testing.T) { + a := NewThreadUnsafeSet() + + if a.Cardinality() != 0 { + t.Error("set should be an empty set") + } + + a.Add(1) + + if a.Cardinality() != 1 { + t.Error("set should have a size of 1") + } + + a.Remove(1) + + if a.Cardinality() != 0 { + t.Error("set should be an empty set") + } + + a.Add(9) + + if a.Cardinality() != 1 { + t.Error("set should have a size of 1") + } + + a.Clear() + + if a.Cardinality() != 0 { + t.Error("set should have a size of 1") + } +} + +func Test_SetIsSubset(t *testing.T) { + a := makeSet([]int{1, 2, 3, 5, 7}) + + b := NewSet() + b.Add(3) + b.Add(5) + b.Add(7) + + if !b.IsSubset(a) { + t.Error("set b should be a subset of set a") + } + + b.Add(72) + + if b.IsSubset(a) { + t.Error("set b should not be a subset of set a because it contains 72 which is not in the set of a") + } +} + +func Test_SetIsProperSubset(t *testing.T) { + a := makeSet([]int{1, 2, 3, 5, 7}) + b := makeSet([]int{7, 5, 3, 2, 1}) + + if !a.IsSubset(b) { + t.Error("set a should be a subset of set b") + } + if a.IsProperSubset(b) { + t.Error("set a should not be a proper subset of set b (they're equal)") + } + + b.Add(72) + + if !a.IsSubset(b) { + t.Error("set a should be a subset of set b") + } + if !a.IsProperSubset(b) { + t.Error("set a should be a proper subset of set b") + } +} + +func Test_UnsafeSetIsSubset(t *testing.T) { + a := makeUnsafeSet([]int{1, 2, 3, 5, 7}) + + b := NewThreadUnsafeSet() + b.Add(3) + b.Add(5) + b.Add(7) + + if !b.IsSubset(a) { + t.Error("set b should be a subset of set a") + } + + b.Add(72) + + if b.IsSubset(a) { + t.Error("set b should not be a subset of set a because it contains 72 which is not in the set of a") + } +} + +func Test_UnsafeSetIsProperSubset(t *testing.T) { + a := makeUnsafeSet([]int{1, 2, 3, 5, 7}) + b := NewThreadUnsafeSet() + b.Add(7) + b.Add(1) + b.Add(5) + b.Add(3) + b.Add(2) + + if !a.IsSubset(b) { + t.Error("set a should be a subset of set b") + } + if a.IsProperSubset(b) { + t.Error("set a should not be a proper subset of set b (they're equal)") + } + + b.Add(72) + + if !a.IsSubset(b) { + t.Error("set a should be a subset of set b") + } + if !a.IsProperSubset(b) { + t.Error("set a should be a proper subset of set b because set b has 72") + } +} + +func Test_SetIsSuperset(t *testing.T) { + a := NewSet() + a.Add(9) + a.Add(5) + a.Add(2) + a.Add(1) + a.Add(11) + + b := NewSet() + b.Add(5) + b.Add(2) + b.Add(11) + + if !a.IsSuperset(b) { + t.Error("set a should be a superset of set b") + } + + b.Add(42) + + if a.IsSuperset(b) { + t.Error("set a should not be a superset of set b because set b has a 42") + } +} + +func Test_SetIsProperSuperset(t *testing.T) { + a := NewSet() + a.Add(5) + a.Add(2) + a.Add(11) + + b := NewSet() + b.Add(2) + b.Add(5) + b.Add(11) + + if !a.IsSuperset(b) { + t.Error("set a should be a superset of set b") + } + if a.IsProperSuperset(b) { + t.Error("set a should not be a proper superset of set b (they're equal)") + } + + a.Add(9) + + if !a.IsSuperset(b) { + t.Error("set a should be a superset of set b") + } + if !a.IsProperSuperset(b) { + t.Error("set a not be a proper superset of set b because set a has a 9") + } + + b.Add(42) + + if a.IsSuperset(b) { + t.Error("set a should not be a superset of set b because set b has a 42") + } + if a.IsProperSuperset(b) { + t.Error("set a should not be a proper superset of set b because set b has a 42") + } +} + +func Test_UnsafeSetIsSuperset(t *testing.T) { + a := NewThreadUnsafeSet() + a.Add(9) + a.Add(5) + a.Add(2) + a.Add(1) + a.Add(11) + + b := NewThreadUnsafeSet() + b.Add(5) + b.Add(2) + b.Add(11) + + if !a.IsSuperset(b) { + t.Error("set a should be a superset of set b") + } + + b.Add(42) + + if a.IsSuperset(b) { + t.Error("set a should not be a superset of set b because set a has a 42") + } +} + +func Test_UnsafeSetIsProperSuperset(t *testing.T) { + a := NewThreadUnsafeSet() + a.Add(5) + a.Add(2) + a.Add(11) + + b := NewThreadUnsafeSet() + b.Add(2) + b.Add(5) + b.Add(11) + + if !a.IsSuperset(b) { + t.Error("set a should be a superset of set b") + } + if a.IsProperSuperset(b) { + t.Error("set a should not be a proper superset of set b (they're equal)") + } + + a.Add(9) + + if !a.IsSuperset(b) { + t.Error("set a should be a superset of set b") + } + if !a.IsProperSuperset(b) { + t.Error("set a not be a proper superset of set b because set a has a 9") + } + + b.Add(42) + + if a.IsSuperset(b) { + t.Error("set a should not be a superset of set b because set b has a 42") + } + if a.IsProperSuperset(b) { + t.Error("set a should not be a proper superset of set b because set b has a 42") + } +} + +func Test_SetUnion(t *testing.T) { + a := NewSet() + + b := NewSet() + b.Add(1) + b.Add(2) + b.Add(3) + b.Add(4) + b.Add(5) + + c := a.Union(b) + + if c.Cardinality() != 5 { + t.Error("set c is unioned with an empty set and therefore should have 5 elements in it") + } + + d := NewSet() + d.Add(10) + d.Add(14) + d.Add(0) + + e := c.Union(d) + if e.Cardinality() != 8 { + t.Error("set e should should have 8 elements in it after being unioned with set c to d") + } + + f := NewSet() + f.Add(14) + f.Add(3) + + g := f.Union(e) + if g.Cardinality() != 8 { + t.Error("set g should still have 8 elements in it after being unioned with set f that has duplicates") + } +} + +func Test_UnsafeSetUnion(t *testing.T) { + a := NewThreadUnsafeSet() + + b := NewThreadUnsafeSet() + b.Add(1) + b.Add(2) + b.Add(3) + b.Add(4) + b.Add(5) + + c := a.Union(b) + + if c.Cardinality() != 5 { + t.Error("set c is unioned with an empty set and therefore should have 5 elements in it") + } + + d := NewThreadUnsafeSet() + d.Add(10) + d.Add(14) + d.Add(0) + + e := c.Union(d) + if e.Cardinality() != 8 { + t.Error("set e should should have 8 elements in it after being unioned with set c to d") + } + + f := NewThreadUnsafeSet() + f.Add(14) + f.Add(3) + + g := f.Union(e) + if g.Cardinality() != 8 { + t.Error("set g should still have 8 elements in it after being unioned with set f that has duplicates") + } +} + +func Test_SetIntersect(t *testing.T) { + a := NewSet() + a.Add(1) + a.Add(3) + a.Add(5) + + b := NewSet() + a.Add(2) + a.Add(4) + a.Add(6) + + c := a.Intersect(b) + + if c.Cardinality() != 0 { + t.Error("set c should be the empty set because there is no common items to intersect") + } + + a.Add(10) + b.Add(10) + + d := a.Intersect(b) + + if !(d.Cardinality() == 1 && d.Contains(10)) { + t.Error("set d should have a size of 1 and contain the item 10") + } +} + +func Test_UnsafeSetIntersect(t *testing.T) { + a := NewThreadUnsafeSet() + a.Add(1) + a.Add(3) + a.Add(5) + + b := NewThreadUnsafeSet() + a.Add(2) + a.Add(4) + a.Add(6) + + c := a.Intersect(b) + + if c.Cardinality() != 0 { + t.Error("set c should be the empty set because there is no common items to intersect") + } + + a.Add(10) + b.Add(10) + + d := a.Intersect(b) + + if !(d.Cardinality() == 1 && d.Contains(10)) { + t.Error("set d should have a size of 1 and contain the item 10") + } +} + +func Test_SetDifference(t *testing.T) { + a := NewSet() + a.Add(1) + a.Add(2) + a.Add(3) + + b := NewSet() + b.Add(1) + b.Add(3) + b.Add(4) + b.Add(5) + b.Add(6) + b.Add(99) + + c := a.Difference(b) + + if !(c.Cardinality() == 1 && c.Contains(2)) { + t.Error("the difference of set a to b is the set of 1 item: 2") + } +} + +func Test_UnsafeSetDifference(t *testing.T) { + a := NewThreadUnsafeSet() + a.Add(1) + a.Add(2) + a.Add(3) + + b := NewThreadUnsafeSet() + b.Add(1) + b.Add(3) + b.Add(4) + b.Add(5) + b.Add(6) + b.Add(99) + + c := a.Difference(b) + + if !(c.Cardinality() == 1 && c.Contains(2)) { + t.Error("the difference of set a to b is the set of 1 item: 2") + } +} + +func Test_SetSymmetricDifference(t *testing.T) { + a := NewSet() + a.Add(1) + a.Add(2) + a.Add(3) + a.Add(45) + + b := NewSet() + b.Add(1) + b.Add(3) + b.Add(4) + b.Add(5) + b.Add(6) + b.Add(99) + + c := a.SymmetricDifference(b) + + if !(c.Cardinality() == 6 && c.Contains(2) && c.Contains(45) && c.Contains(4) && c.Contains(5) && c.Contains(6) && c.Contains(99)) { + t.Error("the symmetric difference of set a to b is the set of 6 items: 2, 45, 4, 5, 6, 99") + } +} + +func Test_UnsafeSetSymmetricDifference(t *testing.T) { + a := NewThreadUnsafeSet() + a.Add(1) + a.Add(2) + a.Add(3) + a.Add(45) + + b := NewThreadUnsafeSet() + b.Add(1) + b.Add(3) + b.Add(4) + b.Add(5) + b.Add(6) + b.Add(99) + + c := a.SymmetricDifference(b) + + if !(c.Cardinality() == 6 && c.Contains(2) && c.Contains(45) && c.Contains(4) && c.Contains(5) && c.Contains(6) && c.Contains(99)) { + t.Error("the symmetric difference of set a to b is the set of 6 items: 2, 45, 4, 5, 6, 99") + } +} + +func Test_SetEqual(t *testing.T) { + a := NewSet() + b := NewSet() + + if !a.Equal(b) { + t.Error("Both a and b are empty sets, and should be equal") + } + + a.Add(10) + + if a.Equal(b) { + t.Error("a should not be equal to b because b is empty and a has item 1 in it") + } + + b.Add(10) + + if !a.Equal(b) { + t.Error("a is now equal again to b because both have the item 10 in them") + } + + b.Add(8) + b.Add(3) + b.Add(47) + + if a.Equal(b) { + t.Error("b has 3 more elements in it so therefore should not be equal to a") + } + + a.Add(8) + a.Add(3) + a.Add(47) + + if !a.Equal(b) { + t.Error("a and b should be equal with the same number of elements") + } +} + +func Test_UnsafeSetEqual(t *testing.T) { + a := NewThreadUnsafeSet() + b := NewThreadUnsafeSet() + + if !a.Equal(b) { + t.Error("Both a and b are empty sets, and should be equal") + } + + a.Add(10) + + if a.Equal(b) { + t.Error("a should not be equal to b because b is empty and a has item 1 in it") + } + + b.Add(10) + + if !a.Equal(b) { + t.Error("a is now equal again to b because both have the item 10 in them") + } + + b.Add(8) + b.Add(3) + b.Add(47) + + if a.Equal(b) { + t.Error("b has 3 more elements in it so therefore should not be equal to a") + } + + a.Add(8) + a.Add(3) + a.Add(47) + + if !a.Equal(b) { + t.Error("a and b should be equal with the same number of elements") + } +} + +func Test_SetClone(t *testing.T) { + a := NewSet() + a.Add(1) + a.Add(2) + + b := a.Clone() + + if !a.Equal(b) { + t.Error("Clones should be equal") + } + + a.Add(3) + if a.Equal(b) { + t.Error("a contains one more element, they should not be equal") + } + + c := a.Clone() + c.Remove(1) + + if a.Equal(c) { + t.Error("C contains one element less, they should not be equal") + } +} + +func Test_UnsafeSetClone(t *testing.T) { + a := NewThreadUnsafeSet() + a.Add(1) + a.Add(2) + + b := a.Clone() + + if !a.Equal(b) { + t.Error("Clones should be equal") + } + + a.Add(3) + if a.Equal(b) { + t.Error("a contains one more element, they should not be equal") + } + + c := a.Clone() + c.Remove(1) + + if a.Equal(c) { + t.Error("C contains one element less, they should not be equal") + } +} + +func Test_Each(t *testing.T) { + a := NewSet() + + a.Add("Z") + a.Add("Y") + a.Add("X") + a.Add("W") + + b := NewSet() + a.Each(func(elem interface{}) bool { + b.Add(elem) + return false + }) + + if !a.Equal(b) { + t.Error("The sets are not equal after iterating (Each) through the first set") + } + + var count int + a.Each(func(elem interface{}) bool { + if count == 2 { + return true + } + count++ + return false + }) + if count != 2 { + t.Error("Iteration should stop on the way") + } +} + +func Test_Iter(t *testing.T) { + a := NewSet() + + a.Add("Z") + a.Add("Y") + a.Add("X") + a.Add("W") + + b := NewSet() + for val := range a.Iter() { + b.Add(val) + } + + if !a.Equal(b) { + t.Error("The sets are not equal after iterating (Iter) through the first set") + } +} + +func Test_UnsafeIter(t *testing.T) { + a := NewThreadUnsafeSet() + + a.Add("Z") + a.Add("Y") + a.Add("X") + a.Add("W") + + b := NewThreadUnsafeSet() + for val := range a.Iter() { + b.Add(val) + } + + if !a.Equal(b) { + t.Error("The sets are not equal after iterating (Iter) through the first set") + } +} + +func Test_Iterator(t *testing.T) { + a := NewSet() + + a.Add("Z") + a.Add("Y") + a.Add("X") + a.Add("W") + + b := NewSet() + for val := range a.Iterator().C { + b.Add(val) + } + + if !a.Equal(b) { + t.Error("The sets are not equal after iterating (Iterator) through the first set") + } +} + +func Test_UnsafeIterator(t *testing.T) { + a := NewThreadUnsafeSet() + + a.Add("Z") + a.Add("Y") + a.Add("X") + a.Add("W") + + b := NewThreadUnsafeSet() + for val := range a.Iterator().C { + b.Add(val) + } + + if !a.Equal(b) { + t.Error("The sets are not equal after iterating (Iterator) through the first set") + } +} + +func Test_IteratorStop(t *testing.T) { + a := NewSet() + + a.Add("Z") + a.Add("Y") + a.Add("X") + a.Add("W") + + it := a.Iterator() + it.Stop() + for range it.C { + t.Error("The iterating (Iterator) did not stop after Stop() has been called") + } +} + +func Test_PopSafe(t *testing.T) { + a := NewSet() + + a.Add("a") + a.Add("b") + a.Add("c") + a.Add("d") + + captureSet := NewSet() + captureSet.Add(a.Pop()) + captureSet.Add(a.Pop()) + captureSet.Add(a.Pop()) + captureSet.Add(a.Pop()) + finalNil := a.Pop() + + if captureSet.Cardinality() != 4 { + t.Error("unexpected captureSet cardinality; should be 4") + } + + if a.Cardinality() != 0 { + t.Error("unepxected a cardinality; should be zero") + } + + if !captureSet.Contains("c", "a", "d", "b") { + t.Error("unexpected result set; should be a,b,c,d (any order is fine") + } + + if finalNil != nil { + t.Error("when original set is empty, further pops should result in nil") + } +} + +func Test_PopUnsafe(t *testing.T) { + a := NewThreadUnsafeSet() + + a.Add("a") + a.Add("b") + a.Add("c") + a.Add("d") + + captureSet := NewThreadUnsafeSet() + captureSet.Add(a.Pop()) + captureSet.Add(a.Pop()) + captureSet.Add(a.Pop()) + captureSet.Add(a.Pop()) + finalNil := a.Pop() + + if captureSet.Cardinality() != 4 { + t.Error("unexpected captureSet cardinality; should be 4") + } + + if a.Cardinality() != 0 { + t.Error("unepxected a cardinality; should be zero") + } + + if !captureSet.Contains("c", "a", "d", "b") { + t.Error("unexpected result set; should be a,b,c,d (any order is fine") + } + + if finalNil != nil { + t.Error("when original set is empty, further pops should result in nil") + } +} + +func Test_PowerSet(t *testing.T) { + a := NewThreadUnsafeSet() + + a.Add(1) + a.Add("delta") + a.Add("chi") + a.Add(4) + + b := a.PowerSet() + if b.Cardinality() != 16 { + t.Error("unexpected PowerSet cardinality") + } +} + +func Test_PowerSetThreadSafe(t *testing.T) { + set := NewSet().PowerSet() + _, setIsThreadSafe := set.(*threadSafeSet) + if !setIsThreadSafe { + t.Error("result of PowerSet should be thread safe") + } + + subset := set.Pop() + _, subsetIsThreadSafe := subset.(*threadSafeSet) + if !subsetIsThreadSafe { + t.Error("subsets in PowerSet result should be thread safe") + } +} + +func Test_EmptySetProperties(t *testing.T) { + empty := NewSet() + + a := NewSet() + a.Add(1) + a.Add("foo") + a.Add("bar") + + b := NewSet() + b.Add("one") + b.Add("two") + b.Add(3) + b.Add(4) + + if !empty.IsSubset(a) || !empty.IsSubset(b) { + t.Error("The empty set is supposed to be a subset of all sets") + } + + if !a.IsSuperset(empty) || !b.IsSuperset(empty) { + t.Error("All sets are supposed to be a superset of the empty set") + } + + if !empty.IsSubset(empty) || !empty.IsSuperset(empty) { + t.Error("The empty set is supposed to be a subset and a superset of itself") + } + + c := a.Union(empty) + if !c.Equal(a) { + t.Error("The union of any set with the empty set is supposed to be equal to itself") + } + + c = a.Intersect(empty) + if !c.Equal(empty) { + t.Error("The intesection of any set with the empty set is supposed to be the empty set") + } + + c = a.CartesianProduct(empty) + if c.Cardinality() != 0 { + t.Error("Cartesian product of any set and the empty set must be the empty set") + } + + if empty.Cardinality() != 0 { + t.Error("Cardinality of the empty set is supposed to be zero") + } + + c = empty.PowerSet() + if c.Cardinality() != 1 { + t.Error("Cardinality of the power set of the empty set is supposed to be one { {} }") + } +} + +func Test_CartesianProduct(t *testing.T) { + a := NewThreadUnsafeSet() + b := NewThreadUnsafeSet() + empty := NewThreadUnsafeSet() + + a.Add(1) + a.Add(2) + a.Add(3) + + b.Add("one") + b.Add("two") + b.Add("three") + b.Add("alpha") + b.Add("gamma") + + c := a.CartesianProduct(b) + d := b.CartesianProduct(a) + + if c.Cardinality() != d.Cardinality() { + t.Error("Cardinality of AxB must be equal to BxA") + } + + if c.Cardinality() != (a.Cardinality() * b.Cardinality()) { + t.Error("Unexpected cardinality for cartesian product set") + } + + c = a.CartesianProduct(empty) + d = empty.CartesianProduct(b) + + if c.Cardinality() != 0 || d.Cardinality() != 0 { + t.Error("Cartesian product of any set and the empty set Ax0 || 0xA must be the empty set") + } +} + +func Test_ToSliceUnthreadsafe(t *testing.T) { + s := makeUnsafeSet([]int{1, 2, 3}) + setAsSlice := s.ToSlice() + if len(setAsSlice) != s.Cardinality() { + t.Errorf("Set length is incorrect: %v", len(setAsSlice)) + } + + for _, i := range setAsSlice { + if !s.Contains(i) { + t.Errorf("Set is missing element: %v", i) + } + } +} + +func Test_Example(t *testing.T) { + /* + requiredClasses := NewSet() + requiredClasses.Add("Cooking") + requiredClasses.Add("English") + requiredClasses.Add("Math") + requiredClasses.Add("Biology") + + scienceSlice := []interface{}{"Biology", "Chemistry"} + scienceClasses := NewSetFromSlice(scienceSlice) + + electiveClasses := NewSet() + electiveClasses.Add("Welding") + electiveClasses.Add("Music") + electiveClasses.Add("Automotive") + + bonusClasses := NewSet() + bonusClasses.Add("Go Programming") + bonusClasses.Add("Python Programming") + + //Show me all the available classes I can take + allClasses := requiredClasses.Union(scienceClasses).Union(electiveClasses).Union(bonusClasses) + fmt.Println(allClasses) //Set{English, Chemistry, Automotive, Cooking, Math, Biology, Welding, Music, Go Programming} + + //Is cooking considered a science class? + fmt.Println(scienceClasses.Contains("Cooking")) //false + + //Show me all classes that are not science classes, since I hate science. + fmt.Println(allClasses.Difference(scienceClasses)) //Set{English, Automotive, Cooking, Math, Welding, Music, Go Programming} + + //Which science classes are also required classes? + fmt.Println(scienceClasses.Intersect(requiredClasses)) //Set{Biology} + + //How many bonus classes do you offer? + fmt.Println(bonusClasses.Cardinality()) //2 + + //Do you have the following classes? Welding, Automotive and English? + fmt.Println(allClasses.ContainsAll("Welding", "Automotive", "English")) + */ +} diff --git a/vendor/github.com/deckarep/golang-set/threadsafe.go b/vendor/github.com/deckarep/golang-set/threadsafe.go new file mode 100644 index 0000000000..269b4ab0cb --- /dev/null +++ b/vendor/github.com/deckarep/golang-set/threadsafe.go @@ -0,0 +1,283 @@ +/* +Open Source Initiative OSI - The MIT License (MIT):Licensing + +The MIT License (MIT) +Copyright (c) 2013 Ralph Caraveo (deckarep@gmail.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +package mapset + +import "sync" + +type threadSafeSet struct { + s threadUnsafeSet + sync.RWMutex +} + +func newThreadSafeSet() threadSafeSet { + return threadSafeSet{s: newThreadUnsafeSet()} +} + +func (set *threadSafeSet) Add(i interface{}) bool { + set.Lock() + ret := set.s.Add(i) + set.Unlock() + return ret +} + +func (set *threadSafeSet) Contains(i ...interface{}) bool { + set.RLock() + ret := set.s.Contains(i...) + set.RUnlock() + return ret +} + +func (set *threadSafeSet) IsSubset(other Set) bool { + o := other.(*threadSafeSet) + + set.RLock() + o.RLock() + + ret := set.s.IsSubset(&o.s) + set.RUnlock() + o.RUnlock() + return ret +} + +func (set *threadSafeSet) IsProperSubset(other Set) bool { + o := other.(*threadSafeSet) + + set.RLock() + defer set.RUnlock() + o.RLock() + defer o.RUnlock() + + return set.s.IsProperSubset(&o.s) +} + +func (set *threadSafeSet) IsSuperset(other Set) bool { + return other.IsSubset(set) +} + +func (set *threadSafeSet) IsProperSuperset(other Set) bool { + return other.IsProperSubset(set) +} + +func (set *threadSafeSet) Union(other Set) Set { + o := other.(*threadSafeSet) + + set.RLock() + o.RLock() + + unsafeUnion := set.s.Union(&o.s).(*threadUnsafeSet) + ret := &threadSafeSet{s: *unsafeUnion} + set.RUnlock() + o.RUnlock() + return ret +} + +func (set *threadSafeSet) Intersect(other Set) Set { + o := other.(*threadSafeSet) + + set.RLock() + o.RLock() + + unsafeIntersection := set.s.Intersect(&o.s).(*threadUnsafeSet) + ret := &threadSafeSet{s: *unsafeIntersection} + set.RUnlock() + o.RUnlock() + return ret +} + +func (set *threadSafeSet) Difference(other Set) Set { + o := other.(*threadSafeSet) + + set.RLock() + o.RLock() + + unsafeDifference := set.s.Difference(&o.s).(*threadUnsafeSet) + ret := &threadSafeSet{s: *unsafeDifference} + set.RUnlock() + o.RUnlock() + return ret +} + +func (set *threadSafeSet) SymmetricDifference(other Set) Set { + o := other.(*threadSafeSet) + + set.RLock() + o.RLock() + + unsafeDifference := set.s.SymmetricDifference(&o.s).(*threadUnsafeSet) + ret := &threadSafeSet{s: *unsafeDifference} + set.RUnlock() + o.RUnlock() + return ret +} + +func (set *threadSafeSet) Clear() { + set.Lock() + set.s = newThreadUnsafeSet() + set.Unlock() +} + +func (set *threadSafeSet) Remove(i interface{}) { + set.Lock() + delete(set.s, i) + set.Unlock() +} + +func (set *threadSafeSet) Cardinality() int { + set.RLock() + defer set.RUnlock() + return len(set.s) +} + +func (set *threadSafeSet) Each(cb func(interface{}) bool) { + set.RLock() + for elem := range set.s { + if cb(elem) { + break + } + } + set.RUnlock() +} + +func (set *threadSafeSet) Iter() <-chan interface{} { + ch := make(chan interface{}) + go func() { + set.RLock() + + for elem := range set.s { + ch <- elem + } + close(ch) + set.RUnlock() + }() + + return ch +} + +func (set *threadSafeSet) Iterator() *Iterator { + iterator, ch, stopCh := newIterator() + + go func() { + set.RLock() + L: + for elem := range set.s { + select { + case <-stopCh: + break L + case ch <- elem: + } + } + close(ch) + set.RUnlock() + }() + + return iterator +} + +func (set *threadSafeSet) Equal(other Set) bool { + o := other.(*threadSafeSet) + + set.RLock() + o.RLock() + + ret := set.s.Equal(&o.s) + set.RUnlock() + o.RUnlock() + return ret +} + +func (set *threadSafeSet) Clone() Set { + set.RLock() + + unsafeClone := set.s.Clone().(*threadUnsafeSet) + ret := &threadSafeSet{s: *unsafeClone} + set.RUnlock() + return ret +} + +func (set *threadSafeSet) String() string { + set.RLock() + ret := set.s.String() + set.RUnlock() + return ret +} + +func (set *threadSafeSet) PowerSet() Set { + set.RLock() + unsafePowerSet := set.s.PowerSet().(*threadUnsafeSet) + set.RUnlock() + + ret := &threadSafeSet{s: newThreadUnsafeSet()} + for subset := range unsafePowerSet.Iter() { + unsafeSubset := subset.(*threadUnsafeSet) + ret.Add(&threadSafeSet{s: *unsafeSubset}) + } + return ret +} + +func (set *threadSafeSet) Pop() interface{} { + set.Lock() + defer set.Unlock() + return set.s.Pop() +} + +func (set *threadSafeSet) CartesianProduct(other Set) Set { + o := other.(*threadSafeSet) + + set.RLock() + o.RLock() + + unsafeCartProduct := set.s.CartesianProduct(&o.s).(*threadUnsafeSet) + ret := &threadSafeSet{s: *unsafeCartProduct} + set.RUnlock() + o.RUnlock() + return ret +} + +func (set *threadSafeSet) ToSlice() []interface{} { + keys := make([]interface{}, 0, set.Cardinality()) + set.RLock() + for elem := range set.s { + keys = append(keys, elem) + } + set.RUnlock() + return keys +} + +func (set *threadSafeSet) MarshalJSON() ([]byte, error) { + set.RLock() + b, err := set.s.MarshalJSON() + set.RUnlock() + + return b, err +} + +func (set *threadSafeSet) UnmarshalJSON(p []byte) error { + set.RLock() + err := set.s.UnmarshalJSON(p) + set.RUnlock() + + return err +} diff --git a/vendor/github.com/deckarep/golang-set/threadsafe_test.go b/vendor/github.com/deckarep/golang-set/threadsafe_test.go new file mode 100644 index 0000000000..5c32fcbd5d --- /dev/null +++ b/vendor/github.com/deckarep/golang-set/threadsafe_test.go @@ -0,0 +1,524 @@ +/* +Open Source Initiative OSI - The MIT License (MIT):Licensing + +The MIT License (MIT) +Copyright (c) 2013 Ralph Caraveo (deckarep@gmail.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +package mapset + +import ( + "encoding/json" + "math/rand" + "runtime" + "sync" + "sync/atomic" + "testing" +) + +const N = 1000 + +func Test_AddConcurrent(t *testing.T) { + runtime.GOMAXPROCS(2) + + s := NewSet() + ints := rand.Perm(N) + + var wg sync.WaitGroup + wg.Add(len(ints)) + for i := 0; i < len(ints); i++ { + go func(i int) { + s.Add(i) + wg.Done() + }(i) + } + + wg.Wait() + for _, i := range ints { + if !s.Contains(i) { + t.Errorf("Set is missing element: %v", i) + } + } +} + +func Test_CardinalityConcurrent(t *testing.T) { + runtime.GOMAXPROCS(2) + + s := NewSet() + + var wg sync.WaitGroup + wg.Add(1) + go func() { + elems := s.Cardinality() + for i := 0; i < N; i++ { + newElems := s.Cardinality() + if newElems < elems { + t.Errorf("Cardinality shrunk from %v to %v", elems, newElems) + } + } + wg.Done() + }() + + for i := 0; i < N; i++ { + s.Add(rand.Int()) + } + wg.Wait() +} + +func Test_ClearConcurrent(t *testing.T) { + runtime.GOMAXPROCS(2) + + s := NewSet() + ints := rand.Perm(N) + + var wg sync.WaitGroup + wg.Add(len(ints)) + for i := 0; i < len(ints); i++ { + go func() { + s.Clear() + wg.Done() + }() + go func(i int) { + s.Add(i) + }(i) + } + + wg.Wait() +} + +func Test_CloneConcurrent(t *testing.T) { + runtime.GOMAXPROCS(2) + + s := NewSet() + ints := rand.Perm(N) + + for _, v := range ints { + s.Add(v) + } + + var wg sync.WaitGroup + wg.Add(len(ints)) + for i := range ints { + go func(i int) { + s.Remove(i) + wg.Done() + }(i) + } + + s.Clone() +} + +func Test_ContainsConcurrent(t *testing.T) { + runtime.GOMAXPROCS(2) + + s := NewSet() + ints := rand.Perm(N) + interfaces := make([]interface{}, 0) + for _, v := range ints { + s.Add(v) + interfaces = append(interfaces, v) + } + + var wg sync.WaitGroup + for range ints { + wg.Add(1) + go func() { + s.Contains(interfaces...) + wg.Done() + }() + } + wg.Wait() +} + +func Test_DifferenceConcurrent(t *testing.T) { + runtime.GOMAXPROCS(2) + + s, ss := NewSet(), NewSet() + ints := rand.Perm(N) + for _, v := range ints { + s.Add(v) + ss.Add(v) + } + + var wg sync.WaitGroup + for range ints { + wg.Add(1) + go func() { + s.Difference(ss) + wg.Done() + }() + } + wg.Wait() +} + +func Test_EqualConcurrent(t *testing.T) { + runtime.GOMAXPROCS(2) + + s, ss := NewSet(), NewSet() + ints := rand.Perm(N) + for _, v := range ints { + s.Add(v) + ss.Add(v) + } + + var wg sync.WaitGroup + for range ints { + wg.Add(1) + go func() { + s.Equal(ss) + wg.Done() + }() + } + wg.Wait() +} + +func Test_IntersectConcurrent(t *testing.T) { + runtime.GOMAXPROCS(2) + + s, ss := NewSet(), NewSet() + ints := rand.Perm(N) + for _, v := range ints { + s.Add(v) + ss.Add(v) + } + + var wg sync.WaitGroup + for range ints { + wg.Add(1) + go func() { + s.Intersect(ss) + wg.Done() + }() + } + wg.Wait() +} + +func Test_IsSubsetConcurrent(t *testing.T) { + runtime.GOMAXPROCS(2) + + s, ss := NewSet(), NewSet() + ints := rand.Perm(N) + for _, v := range ints { + s.Add(v) + ss.Add(v) + } + + var wg sync.WaitGroup + for range ints { + wg.Add(1) + go func() { + s.IsSubset(ss) + wg.Done() + }() + } + wg.Wait() +} + +func Test_IsProperSubsetConcurrent(t *testing.T) { + runtime.GOMAXPROCS(2) + + s, ss := NewSet(), NewSet() + ints := rand.Perm(N) + for _, v := range ints { + s.Add(v) + ss.Add(v) + } + + var wg sync.WaitGroup + for range ints { + wg.Add(1) + go func() { + s.IsProperSubset(ss) + wg.Done() + }() + } + wg.Wait() +} + +func Test_IsSupersetConcurrent(t *testing.T) { + runtime.GOMAXPROCS(2) + + s, ss := NewSet(), NewSet() + ints := rand.Perm(N) + for _, v := range ints { + s.Add(v) + ss.Add(v) + } + + var wg sync.WaitGroup + for range ints { + wg.Add(1) + go func() { + s.IsSuperset(ss) + wg.Done() + }() + } + wg.Wait() +} + +func Test_IsProperSupersetConcurrent(t *testing.T) { + runtime.GOMAXPROCS(2) + + s, ss := NewSet(), NewSet() + ints := rand.Perm(N) + for _, v := range ints { + s.Add(v) + ss.Add(v) + } + + var wg sync.WaitGroup + for range ints { + wg.Add(1) + go func() { + s.IsProperSuperset(ss) + wg.Done() + }() + } + wg.Wait() +} + +func Test_EachConcurrent(t *testing.T) { + runtime.GOMAXPROCS(2) + concurrent := 10 + + s := NewSet() + ints := rand.Perm(N) + for _, v := range ints { + s.Add(v) + } + + var count int64 + wg := new(sync.WaitGroup) + wg.Add(concurrent) + for n := 0; n < concurrent; n++ { + go func() { + defer wg.Done() + s.Each(func(elem interface{}) bool { + atomic.AddInt64(&count, 1) + return false + }) + }() + } + wg.Wait() + + if count != int64(N*concurrent) { + t.Errorf("%v != %v", count, int64(N*concurrent)) + } +} + +func Test_IterConcurrent(t *testing.T) { + runtime.GOMAXPROCS(2) + + s := NewSet() + ints := rand.Perm(N) + for _, v := range ints { + s.Add(v) + } + + cs := make([]<-chan interface{}, 0) + for range ints { + cs = append(cs, s.Iter()) + } + + c := make(chan interface{}) + go func() { + for n := 0; n < len(ints)*N; { + for _, d := range cs { + select { + case <-d: + n++ + c <- nil + default: + } + } + } + close(c) + }() + + for range c { + } +} + +func Test_RemoveConcurrent(t *testing.T) { + runtime.GOMAXPROCS(2) + + s := NewSet() + ints := rand.Perm(N) + for _, v := range ints { + s.Add(v) + } + + var wg sync.WaitGroup + wg.Add(len(ints)) + for _, v := range ints { + go func(i int) { + s.Remove(i) + wg.Done() + }(v) + } + wg.Wait() + + if s.Cardinality() != 0 { + t.Errorf("Expected cardinality 0; got %v", s.Cardinality()) + } +} + +func Test_StringConcurrent(t *testing.T) { + runtime.GOMAXPROCS(2) + + s := NewSet() + ints := rand.Perm(N) + for _, v := range ints { + s.Add(v) + } + + var wg sync.WaitGroup + wg.Add(len(ints)) + for range ints { + go func() { + _ = s.String() + wg.Done() + }() + } + wg.Wait() +} + +func Test_SymmetricDifferenceConcurrent(t *testing.T) { + runtime.GOMAXPROCS(2) + + s, ss := NewSet(), NewSet() + ints := rand.Perm(N) + for _, v := range ints { + s.Add(v) + ss.Add(v) + } + + var wg sync.WaitGroup + for range ints { + wg.Add(1) + go func() { + s.SymmetricDifference(ss) + wg.Done() + }() + } + wg.Wait() +} + +func Test_ToSlice(t *testing.T) { + runtime.GOMAXPROCS(2) + + s := NewSet() + ints := rand.Perm(N) + + var wg sync.WaitGroup + wg.Add(len(ints)) + for i := 0; i < len(ints); i++ { + go func(i int) { + s.Add(i) + wg.Done() + }(i) + } + + wg.Wait() + setAsSlice := s.ToSlice() + if len(setAsSlice) != s.Cardinality() { + t.Errorf("Set length is incorrect: %v", len(setAsSlice)) + } + + for _, i := range setAsSlice { + if !s.Contains(i) { + t.Errorf("Set is missing element: %v", i) + } + } +} + +// Test_ToSliceDeadlock - fixes issue: https://github.com/deckarep/golang-set/issues/36 +// This code reveals the deadlock however it doesn't happen consistently. +func Test_ToSliceDeadlock(t *testing.T) { + runtime.GOMAXPROCS(2) + + var wg sync.WaitGroup + set := NewSet() + workers := 10 + wg.Add(workers) + for i := 1; i <= workers; i++ { + go func() { + for j := 0; j < 1000; j++ { + set.Add(1) + set.ToSlice() + } + wg.Done() + }() + } + wg.Wait() +} + +func Test_UnmarshalJSON(t *testing.T) { + s := []byte(`["test", 1, 2, 3, ["4,5,6"]]`) + expected := NewSetFromSlice( + []interface{}{ + json.Number("1"), + json.Number("2"), + json.Number("3"), + "test", + }, + ) + actual := NewSet() + err := json.Unmarshal(s, actual) + if err != nil { + t.Errorf("Error should be nil: %v", err) + } + + if !expected.Equal(actual) { + t.Errorf("Expected no difference, got: %v", expected.Difference(actual)) + } +} + +func Test_MarshalJSON(t *testing.T) { + expected := NewSetFromSlice( + []interface{}{ + json.Number("1"), + "test", + }, + ) + + b, err := json.Marshal( + NewSetFromSlice( + []interface{}{ + 1, + "test", + }, + ), + ) + if err != nil { + t.Errorf("Error should be nil: %v", err) + } + + actual := NewSet() + err = json.Unmarshal(b, actual) + if err != nil { + t.Errorf("Error should be nil: %v", err) + } + + if !expected.Equal(actual) { + t.Errorf("Expected no difference, got: %v", expected.Difference(actual)) + } +} diff --git a/vendor/github.com/deckarep/golang-set/threadunsafe.go b/vendor/github.com/deckarep/golang-set/threadunsafe.go new file mode 100644 index 0000000000..10bdd46f15 --- /dev/null +++ b/vendor/github.com/deckarep/golang-set/threadunsafe.go @@ -0,0 +1,337 @@ +/* +Open Source Initiative OSI - The MIT License (MIT):Licensing + +The MIT License (MIT) +Copyright (c) 2013 Ralph Caraveo (deckarep@gmail.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +package mapset + +import ( + "bytes" + "encoding/json" + "fmt" + "reflect" + "strings" +) + +type threadUnsafeSet map[interface{}]struct{} + +// An OrderedPair represents a 2-tuple of values. +type OrderedPair struct { + First interface{} + Second interface{} +} + +func newThreadUnsafeSet() threadUnsafeSet { + return make(threadUnsafeSet) +} + +// Equal says whether two 2-tuples contain the same values in the same order. +func (pair *OrderedPair) Equal(other OrderedPair) bool { + if pair.First == other.First && + pair.Second == other.Second { + return true + } + + return false +} + +func (set *threadUnsafeSet) Add(i interface{}) bool { + _, found := (*set)[i] + if found { + return false //False if it existed already + } + + (*set)[i] = struct{}{} + return true +} + +func (set *threadUnsafeSet) Contains(i ...interface{}) bool { + for _, val := range i { + if _, ok := (*set)[val]; !ok { + return false + } + } + return true +} + +func (set *threadUnsafeSet) IsSubset(other Set) bool { + _ = other.(*threadUnsafeSet) + for elem := range *set { + if !other.Contains(elem) { + return false + } + } + return true +} + +func (set *threadUnsafeSet) IsProperSubset(other Set) bool { + return set.IsSubset(other) && !set.Equal(other) +} + +func (set *threadUnsafeSet) IsSuperset(other Set) bool { + return other.IsSubset(set) +} + +func (set *threadUnsafeSet) IsProperSuperset(other Set) bool { + return set.IsSuperset(other) && !set.Equal(other) +} + +func (set *threadUnsafeSet) Union(other Set) Set { + o := other.(*threadUnsafeSet) + + unionedSet := newThreadUnsafeSet() + + for elem := range *set { + unionedSet.Add(elem) + } + for elem := range *o { + unionedSet.Add(elem) + } + return &unionedSet +} + +func (set *threadUnsafeSet) Intersect(other Set) Set { + o := other.(*threadUnsafeSet) + + intersection := newThreadUnsafeSet() + // loop over smaller set + if set.Cardinality() < other.Cardinality() { + for elem := range *set { + if other.Contains(elem) { + intersection.Add(elem) + } + } + } else { + for elem := range *o { + if set.Contains(elem) { + intersection.Add(elem) + } + } + } + return &intersection +} + +func (set *threadUnsafeSet) Difference(other Set) Set { + _ = other.(*threadUnsafeSet) + + difference := newThreadUnsafeSet() + for elem := range *set { + if !other.Contains(elem) { + difference.Add(elem) + } + } + return &difference +} + +func (set *threadUnsafeSet) SymmetricDifference(other Set) Set { + _ = other.(*threadUnsafeSet) + + aDiff := set.Difference(other) + bDiff := other.Difference(set) + return aDiff.Union(bDiff) +} + +func (set *threadUnsafeSet) Clear() { + *set = newThreadUnsafeSet() +} + +func (set *threadUnsafeSet) Remove(i interface{}) { + delete(*set, i) +} + +func (set *threadUnsafeSet) Cardinality() int { + return len(*set) +} + +func (set *threadUnsafeSet) Each(cb func(interface{}) bool) { + for elem := range *set { + if cb(elem) { + break + } + } +} + +func (set *threadUnsafeSet) Iter() <-chan interface{} { + ch := make(chan interface{}) + go func() { + for elem := range *set { + ch <- elem + } + close(ch) + }() + + return ch +} + +func (set *threadUnsafeSet) Iterator() *Iterator { + iterator, ch, stopCh := newIterator() + + go func() { + L: + for elem := range *set { + select { + case <-stopCh: + break L + case ch <- elem: + } + } + close(ch) + }() + + return iterator +} + +func (set *threadUnsafeSet) Equal(other Set) bool { + _ = other.(*threadUnsafeSet) + + if set.Cardinality() != other.Cardinality() { + return false + } + for elem := range *set { + if !other.Contains(elem) { + return false + } + } + return true +} + +func (set *threadUnsafeSet) Clone() Set { + clonedSet := newThreadUnsafeSet() + for elem := range *set { + clonedSet.Add(elem) + } + return &clonedSet +} + +func (set *threadUnsafeSet) String() string { + items := make([]string, 0, len(*set)) + + for elem := range *set { + items = append(items, fmt.Sprintf("%v", elem)) + } + return fmt.Sprintf("Set{%s}", strings.Join(items, ", ")) +} + +// String outputs a 2-tuple in the form "(A, B)". +func (pair OrderedPair) String() string { + return fmt.Sprintf("(%v, %v)", pair.First, pair.Second) +} + +func (set *threadUnsafeSet) Pop() interface{} { + for item := range *set { + delete(*set, item) + return item + } + return nil +} + +func (set *threadUnsafeSet) PowerSet() Set { + powSet := NewThreadUnsafeSet() + nullset := newThreadUnsafeSet() + powSet.Add(&nullset) + + for es := range *set { + u := newThreadUnsafeSet() + j := powSet.Iter() + for er := range j { + p := newThreadUnsafeSet() + if reflect.TypeOf(er).Name() == "" { + k := er.(*threadUnsafeSet) + for ek := range *(k) { + p.Add(ek) + } + } else { + p.Add(er) + } + p.Add(es) + u.Add(&p) + } + + powSet = powSet.Union(&u) + } + + return powSet +} + +func (set *threadUnsafeSet) CartesianProduct(other Set) Set { + o := other.(*threadUnsafeSet) + cartProduct := NewThreadUnsafeSet() + + for i := range *set { + for j := range *o { + elem := OrderedPair{First: i, Second: j} + cartProduct.Add(elem) + } + } + + return cartProduct +} + +func (set *threadUnsafeSet) ToSlice() []interface{} { + keys := make([]interface{}, 0, set.Cardinality()) + for elem := range *set { + keys = append(keys, elem) + } + + return keys +} + +// MarshalJSON creates a JSON array from the set, it marshals all elements +func (set *threadUnsafeSet) MarshalJSON() ([]byte, error) { + items := make([]string, 0, set.Cardinality()) + + for elem := range *set { + b, err := json.Marshal(elem) + if err != nil { + return nil, err + } + + items = append(items, string(b)) + } + + return []byte(fmt.Sprintf("[%s]", strings.Join(items, ","))), nil +} + +// UnmarshalJSON recreates a set from a JSON array, it only decodes +// primitive types. Numbers are decoded as json.Number. +func (set *threadUnsafeSet) UnmarshalJSON(b []byte) error { + var i []interface{} + + d := json.NewDecoder(bytes.NewReader(b)) + d.UseNumber() + err := d.Decode(&i) + if err != nil { + return err + } + + for _, v := range i { + switch t := v.(type) { + case []interface{}, map[string]interface{}: + continue + default: + set.Add(t) + } + } + + return nil +}