forked from juju/juju
-
Notifications
You must be signed in to change notification settings - Fork 0
/
space.go
342 lines (282 loc) · 11 KB
/
space.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
// Copyright 2015 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.
package space
import (
"io"
"net"
"github.com/juju/cmd/v3"
"github.com/juju/collections/set"
"github.com/juju/errors"
"github.com/juju/loggo"
"github.com/juju/names/v4"
"github.com/DavinZhang/juju/api"
"github.com/DavinZhang/juju/api/spaces"
"github.com/DavinZhang/juju/api/subnets"
"github.com/DavinZhang/juju/apiserver/params"
"github.com/DavinZhang/juju/cmd/modelcmd"
"github.com/DavinZhang/juju/core/network"
)
// SpaceAPI defines the necessary API methods needed by the space
// subcommands.
type SpaceAPI interface {
// ListSpaces returns all Juju network spaces and their subnets.
ListSpaces() ([]params.Space, error)
// AddSpace adds a new Juju network space, associating the
// specified subnets with it (optional; can be empty), setting the
// space and subnets access to public or private.
AddSpace(name string, subnetIds []string, public bool) error
// TODO(dimitern): All of the following api methods should take
// names.SpaceTag instead of name, the only exceptions are
// AddSpace, and RenameSpace as the named space doesn't exist
// yet.
// RemoveSpace removes an existing Juju network space, transferring
// any associated subnets to the default space.
RemoveSpace(name string, force bool, dryRun bool) (params.RemoveSpaceResult, error)
// RenameSpace changes the name of the space.
RenameSpace(name, newName string) error
// ReloadSpaces fetches spaces and subnets from substrate
ReloadSpaces() error
// ShowSpace fetches space information.
ShowSpace(name string) (params.ShowSpaceResult, error)
// MoveSubnets ensures that the input subnets are in the input space.
MoveSubnets(names.SpaceTag, []names.SubnetTag, bool) (params.MoveSubnetsResult, error)
}
// SubnetAPI defines the necessary API methods needed by the subnet subcommands.
type SubnetAPI interface {
// SubnetsByCIDR returns the collection of subnets matching each CIDR in the input.
SubnetsByCIDR([]string) ([]params.SubnetsResult, error)
}
// API defines the contract for requesting the API facades.
type API interface {
io.Closer
SpaceAPI
SubnetAPI
}
var logger = loggo.GetLogger("juju.cmd.juju.space")
// SpaceCommandBase is the base type embedded into all space subcommands.
type SpaceCommandBase struct {
modelcmd.ModelCommandBase
modelcmd.IAASOnlyCommand
api API
}
// ParseNameAndCIDRs verifies the input args and returns any errors,
// like missing/invalid name or CIDRs (validated when given, but it's
// an error for CIDRs to be empty if cidrsOptional is false).
func ParseNameAndCIDRs(args []string, cidrsOptional bool) (
name string, CIDRs set.Strings, err error,
) {
defer errors.DeferredAnnotatef(&err, "invalid arguments specified")
if len(args) == 0 {
return "", nil, errors.New("space name is required")
}
name, err = CheckName(args[0])
if err != nil {
return name, nil, errors.Trace(err)
}
CIDRs, err = CheckCIDRs(args[1:], cidrsOptional)
return name, CIDRs, errors.Trace(err)
}
// CheckName checks whether name is a valid space name.
func CheckName(name string) (string, error) {
// Validate given name.
if !names.IsValidSpace(name) {
return "", errors.Errorf("%q is not a valid space name", name)
}
return name, nil
}
// CheckCIDRs parses the list of strings as CIDRs, checking for
// correct formatting, no duplication and no overlaps. Returns error
// if no CIDRs are provided, unless cidrsOptional is true.
func CheckCIDRs(args []string, cidrsOptional bool) (set.Strings, error) {
// Validate any given CIDRs.
CIDRs := set.NewStrings()
for _, arg := range args {
_, ipNet, err := net.ParseCIDR(arg)
if err != nil {
logger.Debugf("cannot parse %q: %v", arg, err)
return CIDRs, errors.Errorf("%q is not a valid CIDR", arg)
}
cidr := ipNet.String()
if CIDRs.Contains(cidr) {
if cidr == arg {
return CIDRs, errors.Errorf("duplicate subnet %q specified", cidr)
}
return CIDRs, errors.Errorf("subnet %q overlaps with %q", arg, cidr)
}
CIDRs.Add(cidr)
}
if CIDRs.IsEmpty() && !cidrsOptional {
return CIDRs, errors.New("CIDRs required but not provided")
}
return CIDRs, nil
}
// APIShim forwards SpaceAPI methods to the real API facade for
// implemented methods only.
type APIShim struct {
SpaceAPI
apiState api.Connection
spaceAPI *spaces.API
subnetAPI *subnets.API
}
func (m *APIShim) Close() error {
return m.apiState.Close()
}
// AddSpace adds a new Juju network space, associating the
// specified subnets with it (optional; can be empty), setting the
// space and subnets access to public or private.
func (m *APIShim) AddSpace(name string, subnetIds []string, public bool) error {
return m.spaceAPI.CreateSpace(name, subnetIds, public)
}
// ListSpaces returns all Juju network spaces and their subnets.
func (m *APIShim) ListSpaces() ([]params.Space, error) {
return m.spaceAPI.ListSpaces()
}
// ReloadSpaces fetches spaces and subnets from substrate
func (m *APIShim) ReloadSpaces() error {
return m.spaceAPI.ReloadSpaces()
}
// RemoveSpace removes an existing Juju network space, transferring
// any associated subnets to the default space.
func (m *APIShim) RemoveSpace(name string, force bool, dryRun bool) (params.RemoveSpaceResult, error) {
return m.spaceAPI.RemoveSpace(name, force, dryRun)
}
// RenameSpace changes the name of the space.
func (m *APIShim) RenameSpace(oldName, newName string) error {
return m.spaceAPI.RenameSpace(oldName, newName)
}
// ShowSpace fetches space information.
func (m *APIShim) ShowSpace(name string) (params.ShowSpaceResult, error) {
return m.spaceAPI.ShowSpace(name)
}
// MoveSubnets ensures that the input subnets are in the input space.
func (m *APIShim) MoveSubnets(space names.SpaceTag, subnets []names.SubnetTag, force bool) (params.MoveSubnetsResult, error) {
return m.spaceAPI.MoveSubnets(space, subnets, force)
}
// SubnetsByCIDR returns the collection of subnets matching each CIDR in the input.
func (m *APIShim) SubnetsByCIDR(cidrs []string) ([]params.SubnetsResult, error) {
return m.subnetAPI.SubnetsByCIDR(cidrs)
}
// NewAPI returns a API for the root api endpoint that the
// environment command returns.
func (c *SpaceCommandBase) NewAPI() (API, error) {
if c.api != nil {
// Already added.
return c.api, nil
}
root, err := c.NewAPIRoot()
if err != nil {
return nil, errors.Trace(err)
}
// This is tested with a feature test.
shim := &APIShim{
apiState: root,
spaceAPI: spaces.NewAPI(root),
subnetAPI: subnets.NewAPI(root),
}
return shim, nil
}
type RunOnAPI func(api API, ctx *cmd.Context) error
func (c *SpaceCommandBase) RunWithAPI(ctx *cmd.Context, toRun RunOnAPI) error {
api, err := c.NewAPI()
if err != nil {
return errors.Annotate(err, "cannot connect to the API server")
}
defer api.Close()
return toRun(api, ctx)
}
type RunOnSpaceAPI func(api SpaceAPI, ctx *cmd.Context) error
func (c *SpaceCommandBase) RunWithSpaceAPI(ctx *cmd.Context, toRun RunOnSpaceAPI) error {
api, err := c.NewAPI()
if err != nil {
return errors.Annotate(err, "cannot connect to the API server")
}
defer api.Close()
return toRun(api, ctx)
}
// SubnetInfo is a source-agnostic representation of a subnet.
// It may originate from state, or from a provider.
type SubnetInfo struct {
// CIDR of the network, in 123.45.67.89/24 format.
CIDR string `json:"cidr" yaml:"cidr"`
// ProviderId is a provider-specific subnet ID.
ProviderId string `json:"provider-id,omitempty" yaml:"provider-id,omitempty"`
// ProviderSpaceId holds the provider ID of the space associated
// with this subnet. Can be empty if not supported.
ProviderSpaceId string `json:"provider-space-id,omitempty" yaml:"provider-space-id,omitempty"`
// ProviderNetworkId holds the provider ID of the network
// containing this subnet, for example VPC id for EC2.
ProviderNetworkId string `json:"provider-network-id,omitempty" yaml:"provider-network-id,omitempty"`
// VLANTag needs to be between 1 and 4094 for VLANs and 0 for
// normal networks. It's defined by IEEE 802.1Q standard, and used
// to define a VLAN network. For more information, see:
// http://en.wikipedia.org/wiki/IEEE_802.1Q.
VLANTag int `json:"vlan-tag" yaml:"vlan-tag"`
// AvailabilityZones describes which availability zones this
// subnet is in. It can be empty if the provider does not support
// availability zones.
AvailabilityZones []string `json:"zones,omitempty" yaml:"zones,omitempty"`
// SpaceID is the id of the space the subnet is associated with.
// Default value should be AlphaSpaceId. It can be empty if
// the subnet is returned from an networkingEnviron. SpaceID is
// preferred over SpaceName in state and non networkingEnviron use.
SpaceID string `json:"space-id,omitempty" yaml:"space-id,omitempty"`
// SpaceName is the name of the space the subnet is associated with.
// An empty string indicates it is part of the AlphaSpaceName OR
// if the SpaceID is set. Should primarily be used in an networkingEnviron.
SpaceName string `json:"space-name,omitempty" yaml:"space-name,omitempty"`
// FanInfo describes the fan networking setup for the subnet.
// It may be empty if this is not a fan subnet,
// or if this subnet information comes from a provider.
FanInfo *network.FanCIDRs `json:"fan-info,omitempty" yaml:"fan-info,omitempty"`
// IsPublic describes whether a subnet is public or not.
IsPublic bool `json:"is-public,omitempty" yaml:"is-public,omitempty"`
}
// SpaceInfo defines a network space.
type SpaceInfo struct {
// ID is the unique identifier for the space.
ID string `json:"id" yaml:"id"`
// Name is the name of the space.
// It is used by operators for identifying a space and should be unique.
Name string `json:"name" yaml:"name"`
// ProviderId is the provider's unique identifier for the space,
// such as used by MAAS.
ProviderId string `json:"provider-id,omitempty" yaml:"provider-id,omitempty"`
// Subnets are the subnets that have been grouped into this network space.
Subnets []SubnetInfo `json:"subnets" yaml:"subnets"`
}
// FanCIDRs describes the subnets relevant to a fan network.
type FanCIDRs struct {
// FanLocalUnderlay is the CIDR of the local underlying fan network.
// It allows easy identification of the device the fan is running on.
FanLocalUnderlay string `json:"fan-local-underlay" yaml:"fan-local-underlay"`
// FanOverlay is the CIDR of the complete fan setup.
FanOverlay string `json:"fan-overlay" yaml:"fan-overlay"`
}
// convertEntitiesToStringAndSkipModel skips the modelTag as this will be used on another place.
func convertEntitiesToStringAndSkipModel(entities []params.Entity) ([]string, error) {
var outputString []string
for _, ent := range entities {
tag, err := names.ParseTag(ent.Tag)
if err != nil {
return nil, err
}
if tag.Kind() == names.ModelTagKind {
continue
} else {
outputString = append(outputString, tag.Id())
}
}
return outputString, nil
}
func hasModelConstraint(entities []params.Entity) (bool, error) {
for _, entity := range entities {
tag, err := names.ParseTag(entity.Tag)
if err != nil {
return false, err
}
if tag.Kind() == names.ModelTagKind {
return true, nil
}
}
return false, nil
}