Esempio n. 1
0
func ExtractPodBandwidthResources(podAnnotations map[string]string) (ingress, egress *resource.Quantity, err error) {
	str, found := podAnnotations["kubernetes.io/ingress-bandwidth"]
	if found {
		ingressValue, err := resource.ParseQuantity(str)
		if err != nil {
			return nil, nil, err
		}
		ingress = &ingressValue
		if err := validateBandwidthIsReasonable(ingress); err != nil {
			return nil, nil, err
		}
	}
	str, found = podAnnotations["kubernetes.io/egress-bandwidth"]
	if found {
		egressValue, err := resource.ParseQuantity(str)
		if err != nil {
			return nil, nil, err
		}
		egress = &egressValue
		if err := validateBandwidthIsReasonable(egress); err != nil {
			return nil, nil, err
		}
	}
	return ingress, egress, nil
}
Esempio n. 2
0
func createPVC(clientset *client.Clientset, namespace, svcName string) (*api.PersistentVolumeClaim, error) {
	capacity, err := resource.ParseQuantity("10Gi")
	if err != nil {
		return nil, err
	}

	pvc := &api.PersistentVolumeClaim{
		ObjectMeta: api.ObjectMeta{
			Name:      fmt.Sprintf("%s-etcd-claim", svcName),
			Namespace: namespace,
			Labels:    componentLabel,
			Annotations: map[string]string{
				"volume.alpha.kubernetes.io/storage-class": "yes",
			},
		},
		Spec: api.PersistentVolumeClaimSpec{
			AccessModes: []api.PersistentVolumeAccessMode{
				api.ReadWriteOnce,
			},
			Resources: api.ResourceRequirements{
				Requests: api.ResourceList{
					api.ResourceStorage: capacity,
				},
			},
		},
	}

	return clientset.Core().PersistentVolumeClaims(namespace).Create(pvc)
}
Esempio n. 3
0
// parseMinimumReclaims parses the minimum reclaim statements
func parseMinimumReclaims(expr string) (map[Signal]resource.Quantity, error) {
	if len(expr) == 0 {
		return nil, nil
	}
	results := map[Signal]resource.Quantity{}
	statements := strings.Split(expr, ",")
	for _, statement := range statements {
		parts := strings.Split(statement, "=")
		if len(parts) != 2 {
			return nil, fmt.Errorf("invalid eviction minimum reclaim syntax: %v, expected <signal>=<quantity>", statement)
		}
		signal := Signal(parts[0])
		if !validSignal(signal) {
			return nil, fmt.Errorf(unsupportedEvictionSignal, signal)
		}
		// check against duplicate statements
		if _, found := results[signal]; found {
			return nil, fmt.Errorf("duplicate eviction minimum reclaim specified for %v", signal)
		}
		quantity, err := resource.ParseQuantity(parts[1])
		if quantity.Sign() < 0 {
			return nil, fmt.Errorf("negative eviction minimum reclaim specified for %v", signal)
		}
		if err != nil {
			return nil, err
		}
		results[signal] = quantity
	}
	return results, nil
}
Esempio n. 4
0
// parseThresholdStatement parses a threshold statement.
func parseThresholdStatement(statement string) (Threshold, error) {
	tokens2Operator := map[string]ThresholdOperator{
		"<": OpLessThan,
	}
	var (
		operator ThresholdOperator
		parts    []string
	)
	for token := range tokens2Operator {
		parts = strings.Split(statement, token)
		// if we got a token, we know this was the operator...
		if len(parts) > 1 {
			operator = tokens2Operator[token]
			break
		}
	}
	if len(operator) == 0 || len(parts) != 2 {
		return Threshold{}, fmt.Errorf("invalid eviction threshold syntax %v, expected <signal><operator><value>", statement)
	}
	signal := Signal(parts[0])
	if !validSignal(signal) {
		return Threshold{}, fmt.Errorf(unsupportedEvictionSignal, signal)
	}

	quantity, err := resource.ParseQuantity(parts[1])
	if err != nil {
		return Threshold{}, err
	}
	return Threshold{
		Signal:   signal,
		Operator: operator,
		Value:    &quantity,
	}, nil
}
Esempio n. 5
0
// Computes the desired number of replicas based on the CustomMetrics passed in cmAnnotation as json-serialized
// extensions.CustomMetricsTargetList.
// Returns number of replicas, status string (also json-serialized extensions.CustomMetricsCurrentStatusList),
// last timestamp of the metrics involved in computations or error, if occurred.
func (a *HorizontalController) computeReplicasForCustomMetrics(hpa extensions.HorizontalPodAutoscaler, scale *extensions.Scale,
	cmAnnotation string) (int, string, time.Time, error) {

	currentReplicas := scale.Status.Replicas
	replicas := 0
	timestamp := time.Time{}

	if cmAnnotation == "" {
		return 0, "", time.Time{}, nil
	}

	var targetList extensions.CustomMetricTargetList
	if err := json.Unmarshal([]byte(cmAnnotation), &targetList); err != nil {
		return 0, "", time.Time{}, fmt.Errorf("failed to parse custom metrics annotation: %v", err)
	}
	if len(targetList.Items) == 0 {
		return 0, "", time.Time{}, fmt.Errorf("no custom metrics in annotation")
	}

	statusList := extensions.CustomMetricCurrentStatusList{
		Items: make([]extensions.CustomMetricCurrentStatus, 0),
	}

	for _, customMetricTarget := range targetList.Items {
		value, currentTimestamp, err := a.metricsClient.GetCustomMetric(customMetricTarget.Name, hpa.Namespace, scale.Status.Selector)
		// TODO: what to do on partial errors (like metrics obtained for 75% of pods).
		if err != nil {
			a.eventRecorder.Event(&hpa, api.EventTypeWarning, "FailedGetCustomMetrics", err.Error())
			return 0, "", time.Time{}, fmt.Errorf("failed to get custom metric value: %v", err)
		}
		floatTarget := float64(customMetricTarget.TargetValue.MilliValue()) / 1000.0
		usageRatio := *value / floatTarget

		replicaCountProposal := 0
		if math.Abs(1.0-usageRatio) > tolerance {
			replicaCountProposal = int(math.Ceil(usageRatio * float64(currentReplicas)))
		} else {
			replicaCountProposal = currentReplicas
		}
		if replicaCountProposal > replicas {
			timestamp = currentTimestamp
			replicas = replicaCountProposal
		}
		quantity, err := resource.ParseQuantity(fmt.Sprintf("%.3f", *value))
		if err != nil {
			return 0, "", time.Time{}, fmt.Errorf("failed to set custom metric value: %v", err)
		}
		statusList.Items = append(statusList.Items, extensions.CustomMetricCurrentStatus{
			Name:         customMetricTarget.Name,
			CurrentValue: *quantity,
		})
	}
	byteStatusList, err := json.Marshal(statusList)
	if err != nil {
		return 0, "", time.Time{}, fmt.Errorf("failed to serialize custom metric status: %v", err)
	}

	return replicas, string(byteStatusList), timestamp, nil

}
Esempio n. 6
0
func (o *CreateClusterQuotaOptions) Complete(cmd *cobra.Command, f *clientcmd.Factory, args []string) error {
	if len(args) != 1 {
		return fmt.Errorf("NAME is required: %v", args)
	}

	var labelSelector *unversioned.LabelSelector
	labelSelectorString := cmdutil.GetFlagString(cmd, "project-label-selector")
	if len(labelSelectorString) > 0 {
		var err error
		labelSelector, err = unversioned.ParseToLabelSelector(labelSelectorString)
		if err != nil {
			return err
		}
	}

	annotationSelector, err := parseAnnotationSelector(cmdutil.GetFlagString(cmd, "project-annotation-selector"))
	if err != nil {
		return err
	}

	o.ClusterQuota = &quotaapi.ClusterResourceQuota{
		ObjectMeta: kapi.ObjectMeta{Name: args[0]},
		Spec: quotaapi.ClusterResourceQuotaSpec{
			Selector: quotaapi.ClusterResourceQuotaSelector{
				LabelSelector:      labelSelector,
				AnnotationSelector: annotationSelector,
			},
			Quota: kapi.ResourceQuotaSpec{
				Hard: kapi.ResourceList{},
			},
		},
	}

	for _, resourceCount := range cmdutil.GetFlagStringSlice(cmd, "hard") {
		tokens := strings.Split(resourceCount, "=")
		if len(tokens) != 2 {
			return fmt.Errorf("%v must in the form of resource=quantity", resourceCount)
		}
		quantity, err := resource.ParseQuantity(tokens[1])
		if err != nil {
			return err
		}
		o.ClusterQuota.Spec.Quota.Hard[kapi.ResourceName(tokens[0])] = quantity
	}

	o.Client, _, err = f.Clients()
	if err != nil {
		return err
	}

	o.Mapper, _ = f.Object(false)
	o.OutputFormat = cmdutil.GetFlagString(cmd, "output")

	o.Printer = func(obj runtime.Object, out io.Writer) error {
		return f.PrintObject(cmd, o.Mapper, obj, out)
	}

	return nil
}
Esempio n. 7
0
func (v *VolumeOptions) Complete(f *clientcmd.Factory, cmd *cobra.Command, out, errOut io.Writer) error {
	clientConfig, err := f.ClientConfig()
	if err != nil {
		return err
	}
	v.OutputVersion, err = kcmdutil.OutputVersion(cmd, clientConfig.GroupVersion)
	if err != nil {
		return err
	}
	_, kc, err := f.Clients()
	if err != nil {
		return err
	}
	v.Client = kc

	cmdNamespace, explicit, err := f.DefaultNamespace()
	if err != nil {
		return err
	}
	mapper, typer := f.Object(false)

	v.DefaultNamespace = cmdNamespace
	v.ExplicitNamespace = explicit
	v.Out = out
	v.Err = errOut
	v.Mapper = mapper
	v.Typer = typer
	v.RESTClientFactory = f.Factory.ClientForMapping
	v.UpdatePodSpecForObject = f.UpdatePodSpecForObject
	v.Encoder = f.JSONEncoder()

	// In case of volume source ignore the default volume type
	if len(v.AddOpts.Source) > 0 {
		v.AddOpts.Type = ""
	}
	if len(v.AddOpts.ClaimSize) > 0 {
		v.AddOpts.CreateClaim = true
		if len(v.AddOpts.ClaimName) == 0 {
			v.AddOpts.ClaimName = kapi.SimpleNameGenerator.GenerateName("pvc-")
		}
		q, err := kresource.ParseQuantity(v.AddOpts.ClaimSize)
		if err != nil {
			return fmt.Errorf("--claim-size is not valid: %v", err)
		}
		v.AddOpts.ClaimSize = q.String()
	}
	switch strings.ToLower(v.AddOpts.ClaimMode) {
	case strings.ToLower(string(kapi.ReadOnlyMany)), "rom":
		v.AddOpts.ClaimMode = string(kapi.ReadOnlyMany)
	case strings.ToLower(string(kapi.ReadWriteOnce)), "rwo":
		v.AddOpts.ClaimMode = string(kapi.ReadWriteOnce)
	case strings.ToLower(string(kapi.ReadWriteMany)), "rwm":
		v.AddOpts.ClaimMode = string(kapi.ReadWriteMany)
	case "":
	default:
		return errors.New("--claim-mode must be one of ReadWriteOnce (rwo), ReadWriteMany (rwm), or ReadOnlyMany (rom)")
	}
	return nil
}
Esempio n. 8
0
// parseThresholdStatement parses a threshold statement.
func parseThresholdStatement(statement string) (Threshold, error) {
	tokens2Operator := map[string]ThresholdOperator{
		"<": OpLessThan,
	}
	var (
		operator ThresholdOperator
		parts    []string
	)
	for token := range tokens2Operator {
		parts = strings.Split(statement, token)
		// if we got a token, we know this was the operator...
		if len(parts) > 1 {
			operator = tokens2Operator[token]
			break
		}
	}
	if len(operator) == 0 || len(parts) != 2 {
		return Threshold{}, fmt.Errorf("invalid eviction threshold syntax %v, expected <signal><operator><value>", statement)
	}
	signal := Signal(parts[0])
	if !validSignal(signal) {
		return Threshold{}, fmt.Errorf(unsupportedEvictionSignal, signal)
	}

	quantityValue := parts[1]
	if strings.HasSuffix(quantityValue, "%") {
		percentage, err := parsePercentage(quantityValue)
		if err != nil {
			return Threshold{}, err
		}
		if percentage <= 0 {
			return Threshold{}, fmt.Errorf("eviction percentage threshold %v must be positive: %s", signal, quantityValue)
		}
		return Threshold{
			Signal:   signal,
			Operator: operator,
			Value: ThresholdValue{
				Percentage: percentage,
			},
		}, nil
	} else {
		quantity, err := resource.ParseQuantity(quantityValue)
		if err != nil {
			return Threshold{}, err
		}
		if quantity.Sign() < 0 || quantity.IsZero() {
			return Threshold{}, fmt.Errorf("eviction threshold %v must be positive: %s", signal, &quantity)
		}
		return Threshold{
			Signal:   signal,
			Operator: operator,
			Value: ThresholdValue{
				Quantity: &quantity,
			},
		}, nil
	}
}
func TestGetLimitResourceDetail(t *testing.T) {
	testMemory := "6G"
	testMemoryQuantity, _ := resource.ParseQuantity(testMemory)
	cases := []struct {
		limitRanges *api.LimitRange
		expected    []LimitRangeItem
	}{
		{
			&api.LimitRange{
				ObjectMeta: api.ObjectMeta{Name: "foo"},
				Spec: api.LimitRangeSpec{
					Limits: []api.LimitRangeItem{
						{
							Type: api.LimitTypePod,
							Max: map[api.ResourceName]resource.Quantity{
								api.ResourceMemory: testMemoryQuantity,
							},
							Min: map[api.ResourceName]resource.Quantity{
								api.ResourceMemory: testMemoryQuantity,
							},
							Default: map[api.ResourceName]resource.Quantity{
								api.ResourceMemory: testMemoryQuantity,
							},
							DefaultRequest: map[api.ResourceName]resource.Quantity{
								api.ResourceMemory: testMemoryQuantity,
							},
							MaxLimitRequestRatio: map[api.ResourceName]resource.Quantity{
								api.ResourceMemory: testMemoryQuantity,
							},
						},
					},
				},
			},
			[]LimitRangeItem{
				{
					ResourceType:         string(api.LimitTypePod),
					ResourceName:         string(api.ResourceMemory),
					Max:                  testMemory,
					Min:                  testMemory,
					Default:              testMemory,
					DefaultRequest:       testMemory,
					MaxLimitRequestRatio: testMemory,
				},
			},
		},
	}

	for _, c := range cases {
		actual := ToLimitRanges(c.limitRanges)
		if !reflect.DeepEqual(actual, c.expected) {
			t.Errorf("getLimitRangeDetail(%#v) == \n%#v\nexpected \n%#v\n",
				c.limitRanges, actual, c.expected)
		}
	}
}
Esempio n. 10
0
func Du(path string) (*resource.Quantity, error) {
	out, err := exec.Command("nice", "-n", "19", "du", "-s", path).CombinedOutput()
	if err != nil {
		return nil, fmt.Errorf("failed command 'du' ($ nice -n 19 du -s) on path %s with error %v", path, err)
	}
	used, err := resource.ParseQuantity(strings.Fields(string(out))[0])
	if err != nil {
		return nil, fmt.Errorf("failed to parse 'du' output %s due to error %v", out, err)
	}
	used.Format = resource.BinarySI
	return used, nil
}
func TestGetResourceQuotaDetail(t *testing.T) {

	testMemoryQuantity, _ := resource.ParseQuantity("6G")

	cases := []struct {
		resourceQuotas *api.ResourceQuota
		expected       *ResourceQuotaDetail
	}{
		{
			&api.ResourceQuota{
				ObjectMeta: api.ObjectMeta{Name: "foo"},
				Spec: api.ResourceQuotaSpec{
					Hard: map[api.ResourceName]resource.Quantity{
						api.ResourceMemory: testMemoryQuantity,
					},
					Scopes: []api.ResourceQuotaScope{
						api.ResourceQuotaScopeBestEffort,
					},
				},
				Status: api.ResourceQuotaStatus{
					Hard: map[api.ResourceName]resource.Quantity{
						api.ResourceMemory: testMemoryQuantity,
					},
					Used: map[api.ResourceName]resource.Quantity{
						api.ResourceMemory: testMemoryQuantity,
					},
				},
			},
			&ResourceQuotaDetail{
				TypeMeta:   common.TypeMeta{Kind: "resourcequota"},
				ObjectMeta: common.ObjectMeta{Name: "foo"},
				Scopes: []api.ResourceQuotaScope{
					api.ResourceQuotaScopeBestEffort,
				},
				StatusList: map[api.ResourceName]ResourceStatus{
					api.ResourceMemory: {
						Hard: testMemoryQuantity.String(),
						Used: testMemoryQuantity.String(),
					},
				},
			},
		},
	}
	for _, c := range cases {
		actual := ToResourceQuotaDetail(c.resourceQuotas)
		if !reflect.DeepEqual(actual, c.expected) {
			t.Errorf("getResourceQuotaDetail(%#v) == \n%#v\nexpected \n%#v\n",
				c.resourceQuotas, actual, c.expected)
		}
	}
}
Esempio n. 12
0
// parseMinimumReclaims parses the minimum reclaim statements
func parseMinimumReclaims(expr string) (map[Signal]ThresholdValue, error) {
	if len(expr) == 0 {
		return nil, nil
	}
	results := map[Signal]ThresholdValue{}
	statements := strings.Split(expr, ",")
	for _, statement := range statements {
		parts := strings.Split(statement, "=")
		if len(parts) != 2 {
			return nil, fmt.Errorf("invalid eviction minimum reclaim syntax: %v, expected <signal>=<value>", statement)
		}
		signal := Signal(parts[0])
		if !validSignal(signal) {
			return nil, fmt.Errorf(unsupportedEvictionSignal, signal)
		}

		quantityValue := parts[1]
		if strings.HasSuffix(quantityValue, "%") {
			percentage, err := parsePercentage(quantityValue)
			if err != nil {
				return nil, err
			}
			if percentage <= 0 {
				return nil, fmt.Errorf("eviction percentage minimum reclaim %v must be positive: %s", signal, quantityValue)
			}
			// check against duplicate statements
			if _, found := results[signal]; found {
				return nil, fmt.Errorf("duplicate eviction minimum reclaim specified for %v", signal)
			}
			results[signal] = ThresholdValue{
				Percentage: percentage,
			}
			continue
		}
		// check against duplicate statements
		if _, found := results[signal]; found {
			return nil, fmt.Errorf("duplicate eviction minimum reclaim specified for %v", signal)
		}
		quantity, err := resource.ParseQuantity(parts[1])
		if quantity.Sign() < 0 {
			return nil, fmt.Errorf("negative eviction minimum reclaim specified for %v", signal)
		}
		if err != nil {
			return nil, err
		}
		results[signal] = ThresholdValue{
			Quantity: &quantity,
		}
	}
	return results, nil
}
Esempio n. 13
0
func parseAndValidateBandwidth(value string) (int64, error) {
	rsrc, err := resource.ParseQuantity(value)
	if err != nil {
		return -1, err
	}

	if rsrc.Value() < minRsrc.Value() {
		return -1, fmt.Errorf("resource value %d is unreasonably small (< %d)", rsrc.Value(), minRsrc.Value())
	}
	if rsrc.Value() > maxRsrc.Value() {
		return -1, fmt.Errorf("resource value %d is unreasonably large (> %d)", rsrc.Value(), maxRsrc.Value())
	}
	return rsrc.Value(), nil
}
Esempio n. 14
0
func Du(path string) (*resource.Quantity, error) {
	// Uses the same niceness level as cadvisor.fs does when running du
	// Uses -B 1 to always scale to a blocksize of 1 byte
	out, err := exec.Command("nice", "-n", "19", "du", "-s", "-B", "1", path).CombinedOutput()
	if err != nil {
		return nil, fmt.Errorf("failed command 'du' ($ nice -n 19 du -s -B 1) on path %s with error %v", path, err)
	}
	used, err := resource.ParseQuantity(strings.Fields(string(out))[0])
	if err != nil {
		return nil, fmt.Errorf("failed to parse 'du' output %s due to error %v", out, err)
	}
	used.Format = resource.BinarySI
	return &used, nil
}
Esempio n. 15
0
func NewResourceMemoryIsolator(request, limit string) (*ResourceMemory, error) {
	req, err := resource.ParseQuantity(request)
	if err != nil {
		return nil, fmt.Errorf("error parsing request: %v", err)
	}
	lim, err := resource.ParseQuantity(limit)
	if err != nil {
		return nil, fmt.Errorf("error parsing limit: %v", err)
	}
	res := &ResourceMemory{
		ResourceBase{
			resourceValue{
				Request: req,
				Limit:   lim,
			},
		},
	}
	if err := res.AssertValid(); err != nil {
		// should never happen
		return nil, err
	}
	return res, nil
}
Esempio n. 16
0
// runDu executes the "du" command and writes the results to metrics.Used
func (md *metricsDu) runDu(metrics *Metrics) error {
	// Uses the same niceness level as cadvisor.fs does when running du
	// Uses -B 1 to always scale to a blocksize of 1 byte
	out, err := exec.Command("nice", "-n", "19", "du", "-s", "-B", "1", md.path).CombinedOutput()
	if err != nil {
		return fmt.Errorf("failed command 'du' on %s with error %v", md.path, err)
	}
	used, err := resource.ParseQuantity(strings.Fields(string(out))[0])
	if err != nil {
		return fmt.Errorf("failed to parse 'du' output %s due to error %v", out, err)
	}
	used.Format = resource.BinarySI
	metrics.Used = used
	return nil
}
Esempio n. 17
0
// FindEmptyDirectoryUsageOnTmpfs finds the expected usage of an empty directory existing on
// a tmpfs filesystem on this system.
func FindEmptyDirectoryUsageOnTmpfs() (*resource.Quantity, error) {
	tmpDir, err := utiltesting.MkTmpdir("metrics_du_test")
	if err != nil {
		return nil, err
	}
	out, err := exec.Command("nice", "-n", "19", "du", "-s", "-B", "1", tmpDir).CombinedOutput()
	if err != nil {
		return nil, fmt.Errorf("failed command 'du' on %s with error %v", tmpDir, err)
	}
	used, err := resource.ParseQuantity(strings.Fields(string(out))[0])
	if err != nil {
		return nil, fmt.Errorf("failed to parse 'du' output %s due to error %v", out, err)
	}
	used.Format = resource.BinarySI
	return &used, nil
}
Esempio n. 18
0
func parseResourceList(m util.ConfigurationMap) (api.ResourceList, error) {
	rl := make(api.ResourceList)
	for k, v := range m {
		switch api.ResourceName(k) {
		// Only CPU and memory resources are supported.
		case api.ResourceCPU, api.ResourceMemory:
			q, err := resource.ParseQuantity(v)
			if err != nil {
				return nil, err
			}
			rl[api.ResourceName(k)] = *q
		default:
			return nil, fmt.Errorf("cannot reserve %q resource", k)
		}
	}
	return rl, nil
}
Esempio n. 19
0
func printSinglePodMetrics(out io.Writer, m *metricsapi.PodMetrics, printContainersOnly bool, withNamespace bool) error {
	containers := make(map[string]api.ResourceList)
	podMetrics := make(api.ResourceList)
	for _, res := range MeasuredResources {
		podMetrics[res], _ = resource.ParseQuantity("0")
	}

	for _, c := range m.Containers {
		var usage api.ResourceList
		err := api.Scheme.Convert(&c.Usage, &usage, nil)
		if err != nil {
			return err
		}
		containers[c.Name] = usage
		if !printContainersOnly {
			for _, res := range MeasuredResources {
				quantity := podMetrics[res]
				quantity.Add(usage[res])
				podMetrics[res] = quantity
			}
		}
	}
	if printContainersOnly {
		for contName := range containers {
			if withNamespace {
				printValue(out, m.Namespace)
			}
			printValue(out, m.Name)
			printMetricsLine(out, &ResourceMetricsInfo{
				Name:      contName,
				Metrics:   containers[contName],
				Available: api.ResourceList{},
			})
		}
	} else {
		if withNamespace {
			printValue(out, m.Namespace)
		}
		printMetricsLine(out, &ResourceMetricsInfo{
			Name:      m.Name,
			Metrics:   podMetrics,
			Available: api.ResourceList{},
		})
	}
	return nil
}
Esempio n. 20
0
// populateResourceList takes strings of form <resourceName1>=<value1>,<resourceName1>=<value2>
func populateV1ResourceList(spec string) (v1.ResourceList, error) {
	// empty input gets a nil response to preserve generator test expected behaviors
	if spec == "" {
		return nil, nil
	}

	result := v1.ResourceList{}
	resourceStatements := strings.Split(spec, ",")
	for _, resourceStatement := range resourceStatements {
		parts := strings.Split(resourceStatement, "=")
		if len(parts) != 2 {
			return nil, fmt.Errorf("Invalid argument syntax %v, expected <resource>=<value>", resourceStatement)
		}
		resourceName := v1.ResourceName(parts[0])
		resourceQuantity, err := resource.ParseQuantity(parts[1])
		if err != nil {
			return nil, err
		}
		result[resourceName] = *resourceQuantity
	}
	return result, nil
}
Esempio n. 21
0
func printSinglePodMetrics(out io.Writer, m *metrics_api.PodMetrics, printContainersOnly bool, withNamespace bool) {
	containers := make(map[string]v1.ResourceList)
	podMetrics := make(v1.ResourceList)
	for _, res := range MeasuredResources {
		podMetrics[res], _ = resource.ParseQuantity("0")
	}
	for _, c := range m.Containers {
		containers[c.Name] = c.Usage
		if !printContainersOnly {
			for _, res := range MeasuredResources {
				quantity := podMetrics[res]
				quantity.Add(c.Usage[res])
				podMetrics[res] = quantity
			}
		}
	}
	if printContainersOnly {
		for contName := range containers {
			if withNamespace {
				printValue(out, m.Namespace)
			}
			printValue(out, m.Name)
			printMetricsLine(out, &ResourceMetricsInfo{
				Name:      contName,
				Metrics:   containers[contName],
				Timestamp: m.Timestamp.Time.Format(time.RFC1123Z),
			})
		}
	} else {
		if withNamespace {
			printValue(out, m.Namespace)
		}
		printMetricsLine(out, &ResourceMetricsInfo{
			Name:      m.Name,
			Metrics:   podMetrics,
			Timestamp: m.Timestamp.Time.Format(time.RFC1123Z),
		})
	}
}
Esempio n. 22
0
// computeReplicasForCustomMetrics computes the desired number of replicas based on the CustomMetrics passed in cmAnnotation
// as json-serialized extensions.CustomMetricsTargetList.
// Returns number of replicas, metric which required highest number of replicas,
// status string (also json-serialized extensions.CustomMetricsCurrentStatusList),
// last timestamp of the metrics involved in computations or error, if occurred.
func (a *HorizontalController) computeReplicasForCustomMetrics(hpa *autoscaling.HorizontalPodAutoscaler, scale *extensions.Scale,
	cmAnnotation string) (replicas int32, metric string, status string, timestamp time.Time, err error) {

	if cmAnnotation == "" {
		return
	}

	currentReplicas := scale.Status.Replicas

	var targetList extensions.CustomMetricTargetList
	if err := json.Unmarshal([]byte(cmAnnotation), &targetList); err != nil {
		return 0, "", "", time.Time{}, fmt.Errorf("failed to parse custom metrics annotation: %v", err)
	}
	if len(targetList.Items) == 0 {
		return 0, "", "", time.Time{}, fmt.Errorf("no custom metrics in annotation")
	}

	statusList := extensions.CustomMetricCurrentStatusList{
		Items: make([]extensions.CustomMetricCurrentStatus, 0),
	}

	for _, customMetricTarget := range targetList.Items {
		if scale.Status.Selector == nil {
			errMsg := "selector is required"
			a.eventRecorder.Event(hpa, api.EventTypeWarning, "SelectorRequired", errMsg)
			return 0, "", "", time.Time{}, fmt.Errorf("selector is required")
		}

		selector, err := unversioned.LabelSelectorAsSelector(scale.Status.Selector)
		if err != nil {
			errMsg := fmt.Sprintf("couldn't convert selector string to a corresponding selector object: %v", err)
			a.eventRecorder.Event(hpa, api.EventTypeWarning, "InvalidSelector", errMsg)
			return 0, "", "", time.Time{}, fmt.Errorf("couldn't convert selector string to a corresponding selector object: %v", err)
		}
		floatTarget := float64(customMetricTarget.TargetValue.MilliValue()) / 1000.0
		replicaCountProposal, utilizationProposal, timestampProposal, err := a.replicaCalc.GetMetricReplicas(currentReplicas, floatTarget, fmt.Sprintf("custom/%s", customMetricTarget.Name), hpa.Namespace, selector)
		if err != nil {
			lastScaleTime := getLastScaleTime(hpa)
			if time.Now().After(lastScaleTime.Add(upscaleForbiddenWindow)) {
				a.eventRecorder.Event(hpa, api.EventTypeWarning, "FailedGetCustomMetrics", err.Error())
			} else {
				a.eventRecorder.Event(hpa, api.EventTypeNormal, "CustomMetricsNotAvailableYet", err.Error())
			}

			return 0, "", "", time.Time{}, fmt.Errorf("failed to get custom metric value: %v", err)
		}

		if replicaCountProposal > replicas {
			timestamp = timestampProposal
			replicas = replicaCountProposal
			metric = fmt.Sprintf("Custom metric %s", customMetricTarget.Name)
		}
		quantity, err := resource.ParseQuantity(fmt.Sprintf("%.3f", utilizationProposal))
		if err != nil {
			return 0, "", "", time.Time{}, fmt.Errorf("failed to set custom metric value: %v", err)
		}
		statusList.Items = append(statusList.Items, extensions.CustomMetricCurrentStatus{
			Name:         customMetricTarget.Name,
			CurrentValue: quantity,
		})
	}
	byteStatusList, err := json.Marshal(statusList)
	if err != nil {
		return 0, "", "", time.Time{}, fmt.Errorf("failed to serialize custom metric status: %v", err)
	}

	return replicas, metric, string(byteStatusList), timestamp, nil
}
Esempio n. 23
0
func fakeInitHostFactory(federationName, namespaceName, ip, dnsZoneName, image, dnsProvider string) (cmdutil.Factory, error) {
	svcName := federationName + "-apiserver"
	svcUrlPrefix := "/api/v1/namespaces/federation-system/services"
	credSecretName := svcName + "-credentials"
	cmKubeconfigSecretName := federationName + "-controller-manager-kubeconfig"
	capacity, err := resource.ParseQuantity("10Gi")
	if err != nil {
		return nil, err
	}
	pvcName := svcName + "-etcd-claim"
	replicas := int32(1)

	namespace := v1.Namespace{
		TypeMeta: unversioned.TypeMeta{
			Kind:       "Namespace",
			APIVersion: testapi.Default.GroupVersion().String(),
		},
		ObjectMeta: v1.ObjectMeta{
			Name: namespaceName,
		},
	}

	svc := v1.Service{
		TypeMeta: unversioned.TypeMeta{
			Kind:       "Service",
			APIVersion: testapi.Default.GroupVersion().String(),
		},
		ObjectMeta: v1.ObjectMeta{
			Namespace: namespaceName,
			Name:      svcName,
			Labels:    componentLabel,
		},
		Spec: v1.ServiceSpec{
			Type:     v1.ServiceTypeLoadBalancer,
			Selector: apiserverSvcSelector,
			Ports: []v1.ServicePort{
				{
					Name:       "https",
					Protocol:   "TCP",
					Port:       443,
					TargetPort: intstr.FromInt(443),
				},
			},
		},
	}

	svcWithLB := svc
	svcWithLB.Status = v1.ServiceStatus{
		LoadBalancer: v1.LoadBalancerStatus{
			Ingress: []v1.LoadBalancerIngress{
				{
					IP: ip,
				},
			},
		},
	}

	credSecret := v1.Secret{
		TypeMeta: unversioned.TypeMeta{
			Kind:       "Secret",
			APIVersion: testapi.Default.GroupVersion().String(),
		},
		ObjectMeta: v1.ObjectMeta{
			Name:      credSecretName,
			Namespace: namespaceName,
		},
		Data: nil,
	}

	cmKubeconfigSecret := v1.Secret{
		TypeMeta: unversioned.TypeMeta{
			Kind:       "Secret",
			APIVersion: testapi.Default.GroupVersion().String(),
		},
		ObjectMeta: v1.ObjectMeta{
			Name:      cmKubeconfigSecretName,
			Namespace: namespaceName,
		},
		Data: nil,
	}

	pvc := v1.PersistentVolumeClaim{
		TypeMeta: unversioned.TypeMeta{
			Kind:       "PersistentVolumeClaim",
			APIVersion: testapi.Default.GroupVersion().String(),
		},
		ObjectMeta: v1.ObjectMeta{
			Name:      pvcName,
			Namespace: namespaceName,
			Labels:    componentLabel,
			Annotations: map[string]string{
				"volume.alpha.kubernetes.io/storage-class": "yes",
			},
		},
		Spec: v1.PersistentVolumeClaimSpec{
			AccessModes: []v1.PersistentVolumeAccessMode{
				v1.ReadWriteOnce,
			},
			Resources: v1.ResourceRequirements{
				Requests: v1.ResourceList{
					v1.ResourceStorage: capacity,
				},
			},
		},
	}

	apiserver := v1beta1.Deployment{
		TypeMeta: unversioned.TypeMeta{
			Kind:       "Deployment",
			APIVersion: testapi.Extensions.GroupVersion().String(),
		},
		ObjectMeta: v1.ObjectMeta{
			Name:      svcName,
			Namespace: namespaceName,
			Labels:    componentLabel,
		},
		Spec: v1beta1.DeploymentSpec{
			Replicas: &replicas,
			Selector: nil,
			Template: v1.PodTemplateSpec{
				ObjectMeta: v1.ObjectMeta{
					Name:   svcName,
					Labels: apiserverPodLabels,
				},
				Spec: v1.PodSpec{
					Containers: []v1.Container{
						{
							Name:  "apiserver",
							Image: image,
							Command: []string{
								"/hyperkube",
								"federation-apiserver",
								"--bind-address=0.0.0.0",
								"--etcd-servers=http://localhost:2379",
								"--service-cluster-ip-range=10.0.0.0/16",
								"--secure-port=443",
								"--client-ca-file=/etc/federation/apiserver/ca.crt",
								"--tls-cert-file=/etc/federation/apiserver/server.crt",
								"--tls-private-key-file=/etc/federation/apiserver/server.key",
								"--advertise-address=" + ip,
							},
							Ports: []v1.ContainerPort{
								{
									Name:          "https",
									ContainerPort: 443,
								},
								{
									Name:          "local",
									ContainerPort: 8080,
								},
							},
							VolumeMounts: []v1.VolumeMount{
								{
									Name:      credSecretName,
									MountPath: "/etc/federation/apiserver",
									ReadOnly:  true,
								},
							},
						},
						{
							Name:  "etcd",
							Image: "quay.io/coreos/etcd:v2.3.3",
							Command: []string{
								"/etcd",
								"--data-dir",
								"/var/etcd/data",
							},
							VolumeMounts: []v1.VolumeMount{
								{
									Name:      "etcddata",
									MountPath: "/var/etcd",
								},
							},
						},
					},
					Volumes: []v1.Volume{
						{
							Name: credSecretName,
							VolumeSource: v1.VolumeSource{
								Secret: &v1.SecretVolumeSource{
									SecretName: credSecretName,
								},
							},
						},
						{
							Name: "etcddata",
							VolumeSource: v1.VolumeSource{
								PersistentVolumeClaim: &v1.PersistentVolumeClaimVolumeSource{
									ClaimName: pvcName,
								},
							},
						},
					},
				},
			},
		},
	}

	cmName := federationName + "-controller-manager"
	cm := v1beta1.Deployment{
		TypeMeta: unversioned.TypeMeta{
			Kind:       "Deployment",
			APIVersion: testapi.Extensions.GroupVersion().String(),
		},
		ObjectMeta: v1.ObjectMeta{
			Name:      cmName,
			Namespace: namespaceName,
			Labels:    componentLabel,
		},
		Spec: v1beta1.DeploymentSpec{
			Replicas: &replicas,
			Selector: nil,
			Template: v1.PodTemplateSpec{
				ObjectMeta: v1.ObjectMeta{
					Name:   cmName,
					Labels: controllerManagerPodLabels,
				},
				Spec: v1.PodSpec{
					Containers: []v1.Container{
						{
							Name:  "controller-manager",
							Image: image,
							Command: []string{
								"/hyperkube",
								"federation-controller-manager",
								"--master=https://federation-apiserver",
								"--kubeconfig=/etc/federation/controller-manager/kubeconfig",
								fmt.Sprintf("--dns-provider=%s", dnsProvider),
								"--dns-provider-config=",
								fmt.Sprintf("--federation-name=%s", federationName),
								fmt.Sprintf("--zone-name=%s", dnsZoneName),
							},
							VolumeMounts: []v1.VolumeMount{
								{
									Name:      cmKubeconfigSecretName,
									MountPath: "/etc/federation/controller-manager",
									ReadOnly:  true,
								},
							},
							Env: []v1.EnvVar{
								{
									Name: "POD_NAMESPACE",
									ValueFrom: &v1.EnvVarSource{
										FieldRef: &v1.ObjectFieldSelector{
											FieldPath: "metadata.namespace",
										},
									},
								},
							},
						},
					},
					Volumes: []v1.Volume{
						{
							Name: cmKubeconfigSecretName,
							VolumeSource: v1.VolumeSource{
								Secret: &v1.SecretVolumeSource{
									SecretName: cmKubeconfigSecretName,
								},
							},
						},
					},
				},
			},
		},
	}

	f, tf, codec, _ := cmdtesting.NewAPIFactory()
	extCodec := testapi.Extensions.Codec()
	ns := dynamic.ContentConfig().NegotiatedSerializer
	tf.ClientConfig = kubefedtesting.DefaultClientConfig()
	tf.Client = &fake.RESTClient{
		NegotiatedSerializer: ns,
		Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
			switch p, m := req.URL.Path, req.Method; {
			case p == "/api/v1/namespaces" && m == http.MethodPost:
				body, err := ioutil.ReadAll(req.Body)
				if err != nil {
					return nil, err
				}
				var got v1.Namespace
				_, _, err = codec.Decode(body, nil, &got)
				if err != nil {
					return nil, err
				}
				if !api.Semantic.DeepEqual(got, namespace) {
					return nil, fmt.Errorf("Unexpected namespace object\n\tDiff: %s", diff.ObjectGoPrintDiff(got, namespace))
				}
				return &http.Response{StatusCode: http.StatusCreated, Header: kubefedtesting.DefaultHeader(), Body: kubefedtesting.ObjBody(codec, &namespace)}, nil
			case p == svcUrlPrefix && m == http.MethodPost:
				body, err := ioutil.ReadAll(req.Body)
				if err != nil {
					return nil, err
				}
				var got v1.Service
				_, _, err = codec.Decode(body, nil, &got)
				if err != nil {
					return nil, err
				}
				if !api.Semantic.DeepEqual(got, svc) {
					return nil, fmt.Errorf("Unexpected service object\n\tDiff: %s", diff.ObjectGoPrintDiff(got, svc))
				}
				return &http.Response{StatusCode: http.StatusCreated, Header: kubefedtesting.DefaultHeader(), Body: kubefedtesting.ObjBody(codec, &svc)}, nil
			case strings.HasPrefix(p, svcUrlPrefix) && m == http.MethodGet:
				got := strings.TrimPrefix(p, svcUrlPrefix+"/")
				if got != svcName {
					return nil, errors.NewNotFound(api.Resource("services"), got)
				}
				return &http.Response{StatusCode: http.StatusOK, Header: kubefedtesting.DefaultHeader(), Body: kubefedtesting.ObjBody(codec, &svcWithLB)}, nil
			case p == "/api/v1/namespaces/federation-system/secrets" && m == http.MethodPost:
				body, err := ioutil.ReadAll(req.Body)
				if err != nil {
					return nil, err
				}
				var got, want v1.Secret
				_, _, err = codec.Decode(body, nil, &got)
				if err != nil {
					return nil, err
				}
				// Obtained secret contains generated data which cannot
				// be compared, so we just nullify the generated part
				// and compare the rest of the secret. The generated
				// parts are tested in other tests.
				got.Data = nil
				switch got.Name {
				case credSecretName:
					want = credSecret
				case cmKubeconfigSecretName:
					want = cmKubeconfigSecret
				}
				if !api.Semantic.DeepEqual(got, want) {
					return nil, fmt.Errorf("Unexpected secret object\n\tDiff: %s", diff.ObjectGoPrintDiff(got, want))
				}
				return &http.Response{StatusCode: http.StatusCreated, Header: kubefedtesting.DefaultHeader(), Body: kubefedtesting.ObjBody(codec, &want)}, nil
			case p == "/api/v1/namespaces/federation-system/persistentvolumeclaims" && m == http.MethodPost:
				body, err := ioutil.ReadAll(req.Body)
				if err != nil {
					return nil, err
				}
				var got v1.PersistentVolumeClaim
				_, _, err = codec.Decode(body, nil, &got)
				if err != nil {
					return nil, err
				}
				if !api.Semantic.DeepEqual(got, pvc) {
					return nil, fmt.Errorf("Unexpected PVC object\n\tDiff: %s", diff.ObjectGoPrintDiff(got, pvc))
				}
				return &http.Response{StatusCode: http.StatusCreated, Header: kubefedtesting.DefaultHeader(), Body: kubefedtesting.ObjBody(codec, &pvc)}, nil
			case p == "/apis/extensions/v1beta1/namespaces/federation-system/deployments" && m == http.MethodPost:
				body, err := ioutil.ReadAll(req.Body)
				if err != nil {
					return nil, err
				}
				var got, want v1beta1.Deployment
				_, _, err = codec.Decode(body, nil, &got)
				if err != nil {
					return nil, err
				}
				switch got.Name {
				case svcName:
					want = apiserver
				case cmName:
					want = cm
				}
				if !api.Semantic.DeepEqual(got, want) {
					return nil, fmt.Errorf("Unexpected deployment object\n\tDiff: %s", diff.ObjectGoPrintDiff(got, want))
				}
				return &http.Response{StatusCode: http.StatusCreated, Header: kubefedtesting.DefaultHeader(), Body: kubefedtesting.ObjBody(extCodec, &want)}, nil
			default:
				return nil, fmt.Errorf("unexpected request: %#v\n%#v", req.URL, req)
			}
		}),
	}
	return f, nil
}
Esempio n. 24
0
// Computes the desired number of replicas based on the CustomMetrics passed in cmAnnotation as json-serialized
// extensions.CustomMetricsTargetList.
// Returns number of replicas, metric which required highest number of replicas,
// status string (also json-serialized extensions.CustomMetricsCurrentStatusList),
// last timestamp of the metrics involved in computations or error, if occurred.
func (a *HorizontalController) computeReplicasForCustomMetrics(hpa *autoscaling.HorizontalPodAutoscaler, scale *extensions.Scale,
	cmAnnotation string) (replicas int32, metric string, status string, timestamp time.Time, err error) {

	currentReplicas := scale.Status.Replicas
	replicas = 0
	metric = ""
	status = ""
	timestamp = time.Time{}
	err = nil

	if cmAnnotation == "" {
		return
	}

	var targetList extensions.CustomMetricTargetList
	if err := json.Unmarshal([]byte(cmAnnotation), &targetList); err != nil {
		return 0, "", "", time.Time{}, fmt.Errorf("failed to parse custom metrics annotation: %v", err)
	}
	if len(targetList.Items) == 0 {
		return 0, "", "", time.Time{}, fmt.Errorf("no custom metrics in annotation")
	}

	statusList := extensions.CustomMetricCurrentStatusList{
		Items: make([]extensions.CustomMetricCurrentStatus, 0),
	}

	for _, customMetricTarget := range targetList.Items {
		if scale.Status.Selector == nil {
			errMsg := "selector is required"
			a.eventRecorder.Event(hpa, api.EventTypeWarning, "SelectorRequired", errMsg)
			return 0, "", "", time.Time{}, fmt.Errorf("selector is required")
		}

		selector, err := unversioned.LabelSelectorAsSelector(scale.Status.Selector)
		if err != nil {
			errMsg := fmt.Sprintf("couldn't convert selector string to a corresponding selector object: %v", err)
			a.eventRecorder.Event(hpa, api.EventTypeWarning, "InvalidSelector", errMsg)
			return 0, "", "", time.Time{}, fmt.Errorf("couldn't convert selector string to a corresponding selector object: %v", err)
		}
		value, currentTimestamp, err := a.metricsClient.GetCustomMetric(customMetricTarget.Name, hpa.Namespace, selector)
		// TODO: what to do on partial errors (like metrics obtained for 75% of pods).
		if err != nil {
			a.eventRecorder.Event(hpa, api.EventTypeWarning, "FailedGetCustomMetrics", err.Error())
			return 0, "", "", time.Time{}, fmt.Errorf("failed to get custom metric value: %v", err)
		}
		floatTarget := float64(customMetricTarget.TargetValue.MilliValue()) / 1000.0
		usageRatio := *value / floatTarget

		replicaCountProposal := int32(0)
		if math.Abs(1.0-usageRatio) > tolerance {
			replicaCountProposal = int32(math.Ceil(usageRatio * float64(currentReplicas)))
		} else {
			replicaCountProposal = currentReplicas
		}
		if replicaCountProposal > replicas {
			timestamp = currentTimestamp
			replicas = replicaCountProposal
			metric = fmt.Sprintf("Custom metric %s", customMetricTarget.Name)
		}
		quantity, err := resource.ParseQuantity(fmt.Sprintf("%.3f", *value))
		if err != nil {
			return 0, "", "", time.Time{}, fmt.Errorf("failed to set custom metric value: %v", err)
		}
		statusList.Items = append(statusList.Items, extensions.CustomMetricCurrentStatus{
			Name:         customMetricTarget.Name,
			CurrentValue: *quantity,
		})
	}
	byteStatusList, err := json.Marshal(statusList)
	if err != nil {
		return 0, "", "", time.Time{}, fmt.Errorf("failed to serialize custom metric status: %v", err)
	}

	return replicas, metric, string(byteStatusList), timestamp, nil
}
func TestGetLimitResourceDetail(t *testing.T) {
	testMemory := "6G"
	testCpu := "500m"
	testMemoryQuantity, _ := resource.ParseQuantity(testMemory)
	testCpuQuantity, _ := resource.ParseQuantity(testCpu)
	cases := []struct {
		limitRanges *api.LimitRange
		expected    *LimitRangeDetail
	}{
		{
			&api.LimitRange{
				ObjectMeta: api.ObjectMeta{Name: "foo"},
				Spec: api.LimitRangeSpec{
					Limits: []api.LimitRangeItem{
						{
							Type: api.LimitTypePod,
							Max: map[api.ResourceName]resource.Quantity{
								api.ResourceMemory: testMemoryQuantity,
								api.ResourceCPU:    testCpuQuantity,
							},
							Min: map[api.ResourceName]resource.Quantity{
								api.ResourceMemory: testMemoryQuantity,
								api.ResourceCPU:    testCpuQuantity,
							},
							Default: map[api.ResourceName]resource.Quantity{
								api.ResourceMemory: testMemoryQuantity,
								api.ResourceCPU:    testCpuQuantity,
							},
							DefaultRequest: map[api.ResourceName]resource.Quantity{
								api.ResourceMemory: testMemoryQuantity,
								api.ResourceCPU:    testCpuQuantity,
							},
							MaxLimitRequestRatio: map[api.ResourceName]resource.Quantity{
								api.ResourceMemory: testMemoryQuantity,
								api.ResourceCPU:    testCpuQuantity,
							},
						},
					},
				},
			},
			&LimitRangeDetail{
				ObjectMeta: common.ObjectMeta{Name: "foo"},
				TypeMeta:   common.TypeMeta{Kind: "limitrange"},
				LimitRanges: limitRanges{
					api.LimitTypePod: rangeMap{
						api.ResourceMemory: &limitRange{
							Min:                  testMemory,
							Max:                  testMemory,
							Default:              testMemory,
							DefaultRequest:       testMemory,
							MaxLimitRequestRatio: testMemory,
						},
						api.ResourceCPU: &limitRange{
							Min:                  testCpu,
							Max:                  testCpu,
							Default:              testCpu,
							DefaultRequest:       testCpu,
							MaxLimitRequestRatio: testCpu,
						},
					},
				},
			},
		},
	}

	for _, c := range cases {
		actual := getLimitRangeDetail(c.limitRanges)
		if !reflect.DeepEqual(actual, c.expected) {
			t.Errorf("getLimitRangeDetail(%#v) == \n%#v\nexpected \n%#v\n",
				c.limitRanges, actual, c.expected)
		}
	}
}
		},
		"resources": {
			Type:     schema.TypeList,
			Optional: true,
			Elem: &schema.Resource{
				Schema: map[string]*schema.Schema{
					"limits": {
						Type:     schema.TypeList,
						Optional: true,
						Elem: &schema.Resource{
							Schema: map[string]*schema.Schema{
								"cpu": {
									Type:     schema.TypeString,
									Optional: true,
									ValidateFunc: func(v interface{}, _ string) ([]string, []error) {
										_, err := resource.ParseQuantity(v.(string))
										if err != nil {
											return nil, []error{err}
										}
										return nil, nil
									},
								},
								"memory": {
									Type:     schema.TypeString,
									Optional: true,
									ValidateFunc: func(v interface{}, _ string) ([]string, []error) {
										_, err := resource.ParseQuantity(v.(string))
										if err != nil {
											return nil, []error{err}
										}
										return nil, nil
func writePodContainer(m map[string]interface{}, item *api.Container) error {

	if x, ok := m["name"].(string); ok {
		item.Name = x
	}

	if x, ok := m["image"].(string); ok {
		item.Image = x
	}

	if x, ok := m["image_pull_policy"].(string); ok {
		item.ImagePullPolicy = api.PullPolicy(x)
	}

	if x, ok := m["termination_message_path"].(string); ok {
		item.TerminationMessagePath = x
	}

	if x, ok := m["working_dir"].(string); ok {
		item.WorkingDir = x
	}

	if x, ok := m["command"].([]interface{}); ok {
		for _, y := range x {
			item.Command = append(item.Command, y.(string))
		}
	}

	if x, ok := m["args"].([]interface{}); ok {
		for _, y := range x {
			item.Args = append(item.Args, y.(string))
		}
	}

	if x, ok := m["port"].([]interface{}); ok {
		for _, y := range x {
			ref := api.ContainerPort{}
			writeContainerPort(y.(map[string]interface{}), &ref)
			item.Ports = append(item.Ports, ref)
		}
	}

	if x, ok := m["env"].([]interface{}); ok {
		for _, y := range x {
			ref := api.EnvVar{}
			writeEnvVar(y.(map[string]interface{}), &ref)
			item.Env = append(item.Env, ref)
		}
	}

	if x, ok := m["volume_mount"].([]interface{}); ok {
		for _, y := range x {
			ref := api.VolumeMount{}
			writeVolumeMount(y.(map[string]interface{}), &ref)
			item.VolumeMounts = append(item.VolumeMounts, ref)
		}
	}

	if n, ok := extractSingleMap(m["liveness_probe"]); ok {
		item.LivenessProbe = &api.Probe{}
		writeProbe(n, item.LivenessProbe)
	}

	if n, ok := extractSingleMap(m["readiness_probe"]); ok {
		item.ReadinessProbe = &api.Probe{}
		writeProbe(n, item.ReadinessProbe)
	}

	if n, ok := extractSingleMap(m["resources"]); ok {
		if o, ok := extractSingleMap(n["limits"]); ok {
			item.Resources.Limits = make(api.ResourceList)
			if x, ok := o["cpu"].(string); ok && x != "" {
				q, err := resource.ParseQuantity(x)
				if err != nil {
					return fmt.Errorf("%s for %q", err, x)
				}
				item.Resources.Limits[api.ResourceCPU] = *q
			}
			if x, ok := o["memory"].(string); ok && x != "" {
				q, err := resource.ParseQuantity(x)
				if err != nil {
					return fmt.Errorf("%s for %q", err, x)
				}
				item.Resources.Limits[api.ResourceMemory] = *q
			}
		}
		if o, ok := extractSingleMap(n["requests"]); ok {
			item.Resources.Requests = make(api.ResourceList)
			if x, ok := o["cpu"].(string); ok && x != "" {
				q, err := resource.ParseQuantity(x)
				if err != nil {
					return fmt.Errorf("%s for %q", err, x)
				}
				item.Resources.Requests[api.ResourceCPU] = *q
			}
			if x, ok := o["memory"].(string); ok && x != "" {
				q, err := resource.ParseQuantity(x)
				if err != nil {
					return fmt.Errorf("%s for %q", err, x)
				}
				item.Resources.Requests[api.ResourceMemory] = *q
			}
		}
	}

	return nil
}
Esempio n. 28
0
func TestExtractPodBandwidthResources(t *testing.T) {
	four, _ := resource.ParseQuantity("4M")
	ten, _ := resource.ParseQuantity("10M")
	twenty, _ := resource.ParseQuantity("20M")

	testPod := func(ingress, egress string) *api.Pod {
		pod := &api.Pod{ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{}}}
		if len(ingress) != 0 {
			pod.Annotations["kubernetes.io/ingress-bandwidth"] = ingress
		}
		if len(egress) != 0 {
			pod.Annotations["kubernetes.io/egress-bandwidth"] = egress
		}
		return pod
	}

	tests := []struct {
		pod             *api.Pod
		expectedIngress *resource.Quantity
		expectedEgress  *resource.Quantity
		expectError     bool
	}{
		{
			pod: &api.Pod{},
		},
		{
			pod:             testPod("10M", ""),
			expectedIngress: &ten,
		},
		{
			pod:            testPod("", "10M"),
			expectedEgress: &ten,
		},
		{
			pod:             testPod("4M", "20M"),
			expectedIngress: &four,
			expectedEgress:  &twenty,
		},
		{
			pod:         testPod("foo", ""),
			expectError: true,
		},
	}
	for _, test := range tests {
		ingress, egress, err := ExtractPodBandwidthResources(test.pod.Annotations)
		if test.expectError {
			if err == nil {
				t.Errorf("unexpected non-error")
			}
			continue
		}
		if err != nil {
			t.Errorf("unexpected error: %v", err)
			continue
		}
		if !reflect.DeepEqual(ingress, test.expectedIngress) {
			t.Errorf("expected: %v, saw: %v", ingress, test.expectedIngress)
		}
		if !reflect.DeepEqual(egress, test.expectedEgress) {
			t.Errorf("expected: %v, saw: %v", egress, test.expectedEgress)
		}
	}
}