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 }
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) }
// 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 }
// 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 }
// 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 }
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 = "aapi.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 }
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 }
// 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) } } }
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) } } }
// 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 }
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 }
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 }
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 }
// 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 }
// 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 }
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 }
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 }
// 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 }
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), }) } }
// 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 }
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 }
// 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 }
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) } } }