1// +build !providerless
2
3/*
4Copyright 2020 The Kubernetes Authors.
5
6Licensed under the Apache License, Version 2.0 (the "License");
7you may not use this file except in compliance with the License.
8You may obtain a copy of the License at
9
10    http://www.apache.org/licenses/LICENSE-2.0
11
12Unless required by applicable law or agreed to in writing, software
13distributed under the License is distributed on an "AS IS" BASIS,
14WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15See the License for the specific language governing permissions and
16limitations under the License.
17*/
18
19package publicipclient
20
21import (
22	"context"
23	"fmt"
24	"net/http"
25	"time"
26
27	"github.com/Azure/azure-sdk-for-go/services/network/mgmt/2019-06-01/network"
28	"github.com/Azure/go-autorest/autorest"
29	"github.com/Azure/go-autorest/autorest/azure"
30	"github.com/Azure/go-autorest/autorest/to"
31
32	"k8s.io/client-go/util/flowcontrol"
33	"k8s.io/klog/v2"
34	azclients "k8s.io/legacy-cloud-providers/azure/clients"
35	"k8s.io/legacy-cloud-providers/azure/clients/armclient"
36	"k8s.io/legacy-cloud-providers/azure/metrics"
37	"k8s.io/legacy-cloud-providers/azure/retry"
38)
39
40var _ Interface = &Client{}
41
42// Client implements PublicIPAddress client Interface.
43type Client struct {
44	armClient      armclient.Interface
45	subscriptionID string
46
47	// Rate limiting configures.
48	rateLimiterReader flowcontrol.RateLimiter
49	rateLimiterWriter flowcontrol.RateLimiter
50
51	// ARM throttling configures.
52	RetryAfterReader time.Time
53	RetryAfterWriter time.Time
54}
55
56// New creates a new PublicIPAddress client with ratelimiting.
57func New(config *azclients.ClientConfig) *Client {
58	baseURI := config.ResourceManagerEndpoint
59	authorizer := config.Authorizer
60	armClient := armclient.New(authorizer, baseURI, config.UserAgent, APIVersion, config.Location, config.Backoff)
61	rateLimiterReader, rateLimiterWriter := azclients.NewRateLimiter(config.RateLimitConfig)
62
63	klog.V(2).Infof("Azure PublicIPAddressesClient (read ops) using rate limit config: QPS=%g, bucket=%d",
64		config.RateLimitConfig.CloudProviderRateLimitQPS,
65		config.RateLimitConfig.CloudProviderRateLimitBucket)
66	klog.V(2).Infof("Azure PublicIPAddressesClient (write ops) using rate limit config: QPS=%g, bucket=%d",
67		config.RateLimitConfig.CloudProviderRateLimitQPSWrite,
68		config.RateLimitConfig.CloudProviderRateLimitBucketWrite)
69
70	client := &Client{
71		armClient:         armClient,
72		rateLimiterReader: rateLimiterReader,
73		rateLimiterWriter: rateLimiterWriter,
74		subscriptionID:    config.SubscriptionID,
75	}
76
77	return client
78}
79
80// Get gets a PublicIPAddress.
81func (c *Client) Get(ctx context.Context, resourceGroupName string, publicIPAddressName string, expand string) (network.PublicIPAddress, *retry.Error) {
82	mc := metrics.NewMetricContext("public_ip_addresses", "get", resourceGroupName, c.subscriptionID, "")
83
84	// Report errors if the client is rate limited.
85	if !c.rateLimiterReader.TryAccept() {
86		mc.RateLimitedCount()
87		return network.PublicIPAddress{}, retry.GetRateLimitError(false, "PublicIPGet")
88	}
89
90	// Report errors if the client is throttled.
91	if c.RetryAfterReader.After(time.Now()) {
92		mc.ThrottledCount()
93		rerr := retry.GetThrottlingError("PublicIPGet", "client throttled", c.RetryAfterReader)
94		return network.PublicIPAddress{}, rerr
95	}
96
97	result, rerr := c.getPublicIPAddress(ctx, resourceGroupName, publicIPAddressName, expand)
98	mc.Observe(rerr.Error())
99	if rerr != nil {
100		if rerr.IsThrottled() {
101			// Update RetryAfterReader so that no more requests would be sent until RetryAfter expires.
102			c.RetryAfterReader = rerr.RetryAfter
103		}
104
105		return result, rerr
106	}
107
108	return result, nil
109}
110
111// getPublicIPAddress gets a PublicIPAddress.
112func (c *Client) getPublicIPAddress(ctx context.Context, resourceGroupName string, publicIPAddressName string, expand string) (network.PublicIPAddress, *retry.Error) {
113	resourceID := armclient.GetResourceID(
114		c.subscriptionID,
115		resourceGroupName,
116		"Microsoft.Network/publicIPAddresses",
117		publicIPAddressName,
118	)
119	result := network.PublicIPAddress{}
120
121	response, rerr := c.armClient.GetResource(ctx, resourceID, expand)
122	defer c.armClient.CloseResponse(ctx, response)
123	if rerr != nil {
124		klog.V(5).Infof("Received error in %s: resourceID: %s, error: %s", "publicip.get.request", resourceID, rerr.Error())
125		return result, rerr
126	}
127
128	err := autorest.Respond(
129		response,
130		azure.WithErrorUnlessStatusCode(http.StatusOK),
131		autorest.ByUnmarshallingJSON(&result))
132	if err != nil {
133		klog.V(5).Infof("Received error in %s: resourceID: %s, error: %s", "publicip.get.respond", resourceID, err)
134		return result, retry.GetError(response, err)
135	}
136
137	result.Response = autorest.Response{Response: response}
138	return result, nil
139}
140
141// GetVirtualMachineScaleSetPublicIPAddress gets a PublicIPAddress for VMSS VM.
142func (c *Client) GetVirtualMachineScaleSetPublicIPAddress(ctx context.Context, resourceGroupName string, virtualMachineScaleSetName string, virtualmachineIndex string, networkInterfaceName string, IPConfigurationName string, publicIPAddressName string, expand string) (network.PublicIPAddress, *retry.Error) {
143	mc := metrics.NewMetricContext("vmss_public_ip_addresses", "get", resourceGroupName, c.subscriptionID, "")
144
145	// Report errors if the client is rate limited.
146	if !c.rateLimiterReader.TryAccept() {
147		mc.RateLimitedCount()
148		return network.PublicIPAddress{}, retry.GetRateLimitError(false, "VMSSPublicIPGet")
149	}
150
151	// Report errors if the client is throttled.
152	if c.RetryAfterReader.After(time.Now()) {
153		mc.ThrottledCount()
154		rerr := retry.GetThrottlingError("VMSSPublicIPGet", "client throttled", c.RetryAfterReader)
155		return network.PublicIPAddress{}, rerr
156	}
157
158	result, rerr := c.getVMSSPublicIPAddress(ctx, resourceGroupName, virtualMachineScaleSetName, virtualmachineIndex, networkInterfaceName, IPConfigurationName, publicIPAddressName, expand)
159	mc.Observe(rerr.Error())
160	if rerr != nil {
161		if rerr.IsThrottled() {
162			// Update RetryAfterReader so that no more requests would be sent until RetryAfter expires.
163			c.RetryAfterReader = rerr.RetryAfter
164		}
165
166		return result, rerr
167	}
168
169	return result, nil
170}
171
172// getVMSSPublicIPAddress gets a PublicIPAddress for VMSS VM.
173func (c *Client) getVMSSPublicIPAddress(ctx context.Context, resourceGroupName string, virtualMachineScaleSetName string, virtualmachineIndex string, networkInterfaceName string, IPConfigurationName string, publicIPAddressName string, expand string) (network.PublicIPAddress, *retry.Error) {
174	resourceID := fmt.Sprintf("/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Compute/virtualMachineScaleSets/%s/virtualMachines/%s/networkInterfaces/%s/ipconfigurations/%s/publicipaddresses/%s",
175		autorest.Encode("path", c.subscriptionID),
176		autorest.Encode("path", resourceGroupName),
177		autorest.Encode("path", virtualMachineScaleSetName),
178		autorest.Encode("path", virtualmachineIndex),
179		autorest.Encode("path", networkInterfaceName),
180		autorest.Encode("path", IPConfigurationName),
181		autorest.Encode("path", publicIPAddressName),
182	)
183
184	result := network.PublicIPAddress{}
185	queryParameters := map[string]interface{}{
186		"api-version": ComputeAPIVersion,
187	}
188	if len(expand) > 0 {
189		queryParameters["$expand"] = autorest.Encode("query", expand)
190	}
191	decorators := []autorest.PrepareDecorator{
192		autorest.WithQueryParameters(queryParameters),
193	}
194	response, rerr := c.armClient.GetResourceWithDecorators(ctx, resourceID, decorators)
195	defer c.armClient.CloseResponse(ctx, response)
196	if rerr != nil {
197		klog.V(5).Infof("Received error in %s: resourceID: %s, error: %s", "vmsspublicip.get.request", resourceID, rerr.Error())
198		return result, rerr
199	}
200
201	err := autorest.Respond(
202		response,
203		azure.WithErrorUnlessStatusCode(http.StatusOK),
204		autorest.ByUnmarshallingJSON(&result))
205	if err != nil {
206		klog.V(5).Infof("Received error in %s: resourceID: %s, error: %s", "vmsspublicip.get.respond", resourceID, err)
207		return result, retry.GetError(response, err)
208	}
209
210	result.Response = autorest.Response{Response: response}
211	return result, nil
212}
213
214// List gets a list of PublicIPAddress in the resource group.
215func (c *Client) List(ctx context.Context, resourceGroupName string) ([]network.PublicIPAddress, *retry.Error) {
216	mc := metrics.NewMetricContext("public_ip_addresses", "list", resourceGroupName, c.subscriptionID, "")
217
218	// Report errors if the client is rate limited.
219	if !c.rateLimiterReader.TryAccept() {
220		mc.RateLimitedCount()
221		return nil, retry.GetRateLimitError(false, "PublicIPList")
222	}
223
224	// Report errors if the client is throttled.
225	if c.RetryAfterReader.After(time.Now()) {
226		mc.ThrottledCount()
227		rerr := retry.GetThrottlingError("PublicIPList", "client throttled", c.RetryAfterReader)
228		return nil, rerr
229	}
230
231	result, rerr := c.listPublicIPAddress(ctx, resourceGroupName)
232	mc.Observe(rerr.Error())
233	if rerr != nil {
234		if rerr.IsThrottled() {
235			// Update RetryAfterReader so that no more requests would be sent until RetryAfter expires.
236			c.RetryAfterReader = rerr.RetryAfter
237		}
238
239		return result, rerr
240	}
241
242	return result, nil
243}
244
245// listPublicIPAddress gets a list of PublicIPAddress in the resource group.
246func (c *Client) listPublicIPAddress(ctx context.Context, resourceGroupName string) ([]network.PublicIPAddress, *retry.Error) {
247	resourceID := fmt.Sprintf("/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Network/publicIPAddresses",
248		autorest.Encode("path", c.subscriptionID),
249		autorest.Encode("path", resourceGroupName))
250	result := make([]network.PublicIPAddress, 0)
251	page := &PublicIPAddressListResultPage{}
252	page.fn = c.listNextResults
253
254	resp, rerr := c.armClient.GetResource(ctx, resourceID, "")
255	defer c.armClient.CloseResponse(ctx, resp)
256	if rerr != nil {
257		klog.V(5).Infof("Received error in %s: resourceID: %s, error: %s", "publicip.list.request", resourceID, rerr.Error())
258		return result, rerr
259	}
260
261	var err error
262	page.pialr, err = c.listResponder(resp)
263	if err != nil {
264		klog.V(5).Infof("Received error in %s: resourceID: %s, error: %s", "publicip.list.respond", resourceID, err)
265		return result, retry.GetError(resp, err)
266	}
267
268	for {
269		result = append(result, page.Values()...)
270
271		// Abort the loop when there's no nextLink in the response.
272		if to.String(page.Response().NextLink) == "" {
273			break
274		}
275
276		if err = page.NextWithContext(ctx); err != nil {
277			klog.V(5).Infof("Received error in %s: resourceID: %s, error: %s", "publicip.list.next", resourceID, err)
278			return result, retry.GetError(page.Response().Response.Response, err)
279		}
280	}
281
282	return result, nil
283}
284
285// CreateOrUpdate creates or updates a PublicIPAddress.
286func (c *Client) CreateOrUpdate(ctx context.Context, resourceGroupName string, publicIPAddressName string, parameters network.PublicIPAddress) *retry.Error {
287	mc := metrics.NewMetricContext("public_ip_addresses", "create_or_update", resourceGroupName, c.subscriptionID, "")
288
289	// Report errors if the client is rate limited.
290	if !c.rateLimiterWriter.TryAccept() {
291		mc.RateLimitedCount()
292		return retry.GetRateLimitError(true, "PublicIPCreateOrUpdate")
293	}
294
295	// Report errors if the client is throttled.
296	if c.RetryAfterWriter.After(time.Now()) {
297		mc.ThrottledCount()
298		rerr := retry.GetThrottlingError("PublicIPCreateOrUpdate", "client throttled", c.RetryAfterWriter)
299		return rerr
300	}
301
302	rerr := c.createOrUpdatePublicIP(ctx, resourceGroupName, publicIPAddressName, parameters)
303	mc.Observe(rerr.Error())
304	if rerr != nil {
305		if rerr.IsThrottled() {
306			// Update RetryAfterReader so that no more requests would be sent until RetryAfter expires.
307			c.RetryAfterWriter = rerr.RetryAfter
308		}
309
310		return rerr
311	}
312
313	return nil
314}
315
316// createOrUpdatePublicIP creates or updates a PublicIPAddress.
317func (c *Client) createOrUpdatePublicIP(ctx context.Context, resourceGroupName string, publicIPAddressName string, parameters network.PublicIPAddress) *retry.Error {
318	resourceID := armclient.GetResourceID(
319		c.subscriptionID,
320		resourceGroupName,
321		"Microsoft.Network/publicIPAddresses",
322		publicIPAddressName,
323	)
324
325	response, rerr := c.armClient.PutResource(ctx, resourceID, parameters)
326	defer c.armClient.CloseResponse(ctx, response)
327	if rerr != nil {
328		klog.V(5).Infof("Received error in %s: resourceID: %s, error: %s", "publicip.put.request", resourceID, rerr.Error())
329		return rerr
330	}
331
332	if response != nil && response.StatusCode != http.StatusNoContent {
333		_, rerr = c.createOrUpdateResponder(response)
334		if rerr != nil {
335			klog.V(5).Infof("Received error in %s: resourceID: %s, error: %s", "publicip.put.respond", resourceID, rerr.Error())
336			return rerr
337		}
338	}
339
340	return nil
341}
342
343func (c *Client) createOrUpdateResponder(resp *http.Response) (*network.PublicIPAddress, *retry.Error) {
344	result := &network.PublicIPAddress{}
345	err := autorest.Respond(
346		resp,
347		azure.WithErrorUnlessStatusCode(http.StatusOK, http.StatusCreated),
348		autorest.ByUnmarshallingJSON(&result))
349	result.Response = autorest.Response{Response: resp}
350	return result, retry.GetError(resp, err)
351}
352
353// Delete deletes a PublicIPAddress by name.
354func (c *Client) Delete(ctx context.Context, resourceGroupName string, publicIPAddressName string) *retry.Error {
355	mc := metrics.NewMetricContext("public_ip_addresses", "delete", resourceGroupName, c.subscriptionID, "")
356
357	// Report errors if the client is rate limited.
358	if !c.rateLimiterWriter.TryAccept() {
359		mc.RateLimitedCount()
360		return retry.GetRateLimitError(true, "PublicIPDelete")
361	}
362
363	// Report errors if the client is throttled.
364	if c.RetryAfterWriter.After(time.Now()) {
365		mc.ThrottledCount()
366		rerr := retry.GetThrottlingError("PublicIPDelete", "client throttled", c.RetryAfterWriter)
367		return rerr
368	}
369
370	rerr := c.deletePublicIP(ctx, resourceGroupName, publicIPAddressName)
371	mc.Observe(rerr.Error())
372	if rerr != nil {
373		if rerr.IsThrottled() {
374			// Update RetryAfterReader so that no more requests would be sent until RetryAfter expires.
375			c.RetryAfterWriter = rerr.RetryAfter
376		}
377
378		return rerr
379	}
380
381	return nil
382}
383
384// deletePublicIP deletes a PublicIPAddress by name.
385func (c *Client) deletePublicIP(ctx context.Context, resourceGroupName string, publicIPAddressName string) *retry.Error {
386	resourceID := armclient.GetResourceID(
387		c.subscriptionID,
388		resourceGroupName,
389		"Microsoft.Network/publicIPAddresses",
390		publicIPAddressName,
391	)
392
393	return c.armClient.DeleteResource(ctx, resourceID, "")
394}
395
396func (c *Client) listResponder(resp *http.Response) (result network.PublicIPAddressListResult, err error) {
397	err = autorest.Respond(
398		resp,
399		autorest.ByIgnoring(),
400		azure.WithErrorUnlessStatusCode(http.StatusOK),
401		autorest.ByUnmarshallingJSON(&result))
402	result.Response = autorest.Response{Response: resp}
403	return
404}
405
406// publicIPAddressListResultPreparer prepares a request to retrieve the next set of results.
407// It returns nil if no more results exist.
408func (c *Client) publicIPAddressListResultPreparer(ctx context.Context, lr network.PublicIPAddressListResult) (*http.Request, error) {
409	if lr.NextLink == nil || len(to.String(lr.NextLink)) < 1 {
410		return nil, nil
411	}
412
413	decorators := []autorest.PrepareDecorator{
414		autorest.WithBaseURL(to.String(lr.NextLink)),
415	}
416	return c.armClient.PrepareGetRequest(ctx, decorators...)
417}
418
419// listNextResults retrieves the next set of results, if any.
420func (c *Client) listNextResults(ctx context.Context, lastResults network.PublicIPAddressListResult) (result network.PublicIPAddressListResult, err error) {
421	req, err := c.publicIPAddressListResultPreparer(ctx, lastResults)
422	if err != nil {
423		return result, autorest.NewErrorWithError(err, "publicipclient", "listNextResults", nil, "Failure preparing next results request")
424	}
425	if req == nil {
426		return
427	}
428
429	resp, rerr := c.armClient.Send(ctx, req)
430	defer c.armClient.CloseResponse(ctx, resp)
431	if rerr != nil {
432		result.Response = autorest.Response{Response: resp}
433		return result, autorest.NewErrorWithError(rerr.Error(), "publicipclient", "listNextResults", resp, "Failure sending next results request")
434	}
435
436	result, err = c.listResponder(resp)
437	if err != nil {
438		err = autorest.NewErrorWithError(err, "publicipclient", "listNextResults", resp, "Failure responding to next results request")
439	}
440
441	return
442}
443
444// PublicIPAddressListResultPage contains a page of PublicIPAddress values.
445type PublicIPAddressListResultPage struct {
446	fn    func(context.Context, network.PublicIPAddressListResult) (network.PublicIPAddressListResult, error)
447	pialr network.PublicIPAddressListResult
448}
449
450// NextWithContext advances to the next page of values.  If there was an error making
451// the request the page does not advance and the error is returned.
452func (page *PublicIPAddressListResultPage) NextWithContext(ctx context.Context) (err error) {
453	next, err := page.fn(ctx, page.pialr)
454	if err != nil {
455		return err
456	}
457	page.pialr = next
458	return nil
459}
460
461// Next advances to the next page of values.  If there was an error making
462// the request the page does not advance and the error is returned.
463// Deprecated: Use NextWithContext() instead.
464func (page *PublicIPAddressListResultPage) Next() error {
465	return page.NextWithContext(context.Background())
466}
467
468// NotDone returns true if the page enumeration should be started or is not yet complete.
469func (page PublicIPAddressListResultPage) NotDone() bool {
470	return !page.pialr.IsEmpty()
471}
472
473// Response returns the raw server response from the last page request.
474func (page PublicIPAddressListResultPage) Response() network.PublicIPAddressListResult {
475	return page.pialr
476}
477
478// Values returns the slice of values for the current page or nil if there are no values.
479func (page PublicIPAddressListResultPage) Values() []network.PublicIPAddress {
480	if page.pialr.IsEmpty() {
481		return nil
482	}
483	return *page.pialr.Value
484}
485