1/*
2Copyright 2018 The Kubernetes Authors.
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8    http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16
17package flag
18
19import (
20	"fmt"
21	"net"
22	"sort"
23	"strconv"
24	"strings"
25
26	"github.com/spf13/pflag"
27
28	v1 "k8s.io/api/core/v1"
29	"k8s.io/apimachinery/pkg/api/resource"
30	utilnet "k8s.io/apimachinery/pkg/util/net"
31	corev1helper "k8s.io/kubernetes/pkg/apis/core/v1/helper"
32	kubeletconfig "k8s.io/kubernetes/pkg/kubelet/apis/config"
33	utilsnet "k8s.io/utils/net"
34)
35
36// TODO(mikedanese): remove these flag wrapper types when we remove command line flags
37
38var (
39	_ pflag.Value = &IPVar{}
40	_ pflag.Value = &IPPortVar{}
41	_ pflag.Value = &PortRangeVar{}
42	_ pflag.Value = &ReservedMemoryVar{}
43)
44
45// IPVar is used for validating a command line option that represents an IP. It implements the pflag.Value interface
46type IPVar struct {
47	Val *string
48}
49
50// Set sets the flag value
51func (v IPVar) Set(s string) error {
52	if len(s) == 0 {
53		v.Val = nil
54		return nil
55	}
56	if net.ParseIP(s) == nil {
57		return fmt.Errorf("%q is not a valid IP address", s)
58	}
59	if v.Val == nil {
60		// it's okay to panic here since this is programmer error
61		panic("the string pointer passed into IPVar should not be nil")
62	}
63	*v.Val = s
64	return nil
65}
66
67// String returns the flag value
68func (v IPVar) String() string {
69	if v.Val == nil {
70		return ""
71	}
72	return *v.Val
73}
74
75// Type gets the flag type
76func (v IPVar) Type() string {
77	return "ip"
78}
79
80// IPPortVar is used for validating a command line option that represents an IP and a port. It implements the pflag.Value interface
81type IPPortVar struct {
82	Val *string
83}
84
85// Set sets the flag value
86func (v IPPortVar) Set(s string) error {
87	if len(s) == 0 {
88		v.Val = nil
89		return nil
90	}
91
92	if v.Val == nil {
93		// it's okay to panic here since this is programmer error
94		panic("the string pointer passed into IPPortVar should not be nil")
95	}
96
97	// Both IP and IP:port are valid.
98	// Attempt to parse into IP first.
99	if net.ParseIP(s) != nil {
100		*v.Val = s
101		return nil
102	}
103
104	// Can not parse into IP, now assume IP:port.
105	host, port, err := net.SplitHostPort(s)
106	if err != nil {
107		return fmt.Errorf("%q is not in a valid format (ip or ip:port): %v", s, err)
108	}
109	if net.ParseIP(host) == nil {
110		return fmt.Errorf("%q is not a valid IP address", host)
111	}
112	if _, err := utilsnet.ParsePort(port, true); err != nil {
113		return fmt.Errorf("%q is not a valid number", port)
114	}
115	*v.Val = s
116	return nil
117}
118
119// String returns the flag value
120func (v IPPortVar) String() string {
121	if v.Val == nil {
122		return ""
123	}
124	return *v.Val
125}
126
127// Type gets the flag type
128func (v IPPortVar) Type() string {
129	return "ipport"
130}
131
132// PortRangeVar is used for validating a command line option that represents a port range. It implements the pflag.Value interface
133type PortRangeVar struct {
134	Val *string
135}
136
137// Set sets the flag value
138func (v PortRangeVar) Set(s string) error {
139	if _, err := utilnet.ParsePortRange(s); err != nil {
140		return fmt.Errorf("%q is not a valid port range: %v", s, err)
141	}
142	if v.Val == nil {
143		// it's okay to panic here since this is programmer error
144		panic("the string pointer passed into PortRangeVar should not be nil")
145	}
146	*v.Val = s
147	return nil
148}
149
150// String returns the flag value
151func (v PortRangeVar) String() string {
152	if v.Val == nil {
153		return ""
154	}
155	return *v.Val
156}
157
158// Type gets the flag type
159func (v PortRangeVar) Type() string {
160	return "port-range"
161}
162
163// ReservedMemoryVar is used for validating a command line option that represents a reserved memory. It implements the pflag.Value interface
164type ReservedMemoryVar struct {
165	Value       *[]kubeletconfig.MemoryReservation
166	initialized bool // set to true after the first Set call
167}
168
169// Set sets the flag value
170func (v *ReservedMemoryVar) Set(s string) error {
171	if v.Value == nil {
172		return fmt.Errorf("no target (nil pointer to *[]MemoryReservation")
173	}
174
175	if s == "" {
176		v.Value = nil
177		return nil
178	}
179
180	if !v.initialized || *v.Value == nil {
181		*v.Value = make([]kubeletconfig.MemoryReservation, 0)
182		v.initialized = true
183	}
184
185	if s == "" {
186		return nil
187	}
188
189	numaNodeReservation := strings.Split(s, ":")
190	if len(numaNodeReservation) != 2 {
191		return fmt.Errorf("the reserved memory has incorrect format, expected numaNodeID:type=quantity[,type=quantity...], got %s", s)
192	}
193
194	memoryTypeReservations := strings.Split(numaNodeReservation[1], ",")
195	if len(memoryTypeReservations) < 1 {
196		return fmt.Errorf("the reserved memory has incorrect format, expected numaNodeID:type=quantity[,type=quantity...], got %s", s)
197	}
198
199	numaNodeID, err := strconv.Atoi(numaNodeReservation[0])
200	if err != nil {
201		return fmt.Errorf("failed to convert the NUMA node ID, exptected integer, got %s", numaNodeReservation[0])
202	}
203
204	memoryReservation := kubeletconfig.MemoryReservation{
205		NumaNode: int32(numaNodeID),
206		Limits:   map[v1.ResourceName]resource.Quantity{},
207	}
208
209	for _, reservation := range memoryTypeReservations {
210		limit := strings.Split(reservation, "=")
211		if len(limit) != 2 {
212			return fmt.Errorf("the reserved limit has incorrect value, expected type=quantatity, got %s", reservation)
213		}
214
215		resourceName := v1.ResourceName(limit[0])
216		if resourceName != v1.ResourceMemory && !corev1helper.IsHugePageResourceName(resourceName) {
217			return fmt.Errorf("memory type conversion error, unknown type: %q", resourceName)
218		}
219
220		q, err := resource.ParseQuantity(limit[1])
221		if err != nil {
222			return fmt.Errorf("failed to parse the quantatity, expected quantatity, got %s", limit[1])
223		}
224
225		memoryReservation.Limits[v1.ResourceName(limit[0])] = q
226	}
227
228	*v.Value = append(*v.Value, memoryReservation)
229
230	return nil
231}
232
233// String returns the flag value
234func (v *ReservedMemoryVar) String() string {
235	if v == nil || v.Value == nil {
236		return ""
237	}
238
239	var slices []string
240	for _, reservedMemory := range *v.Value {
241		var limits []string
242		for resourceName, q := range reservedMemory.Limits {
243			limits = append(limits, fmt.Sprintf("%s=%s", resourceName, q.String()))
244		}
245
246		sort.Strings(limits)
247		slices = append(slices, fmt.Sprintf("%d:%s", reservedMemory.NumaNode, strings.Join(limits, ",")))
248	}
249
250	sort.Strings(slices)
251	return strings.Join(slices, ",")
252}
253
254// Type gets the flag type
255func (v *ReservedMemoryVar) Type() string {
256	return "reserved-memory"
257}
258