github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/provider/oracle/network/environ.go (about) 1 // Copyright 2017 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package network 5 6 import ( 7 "fmt" 8 "strconv" 9 "strings" 10 11 "github.com/juju/errors" 12 "github.com/juju/go-oracle-cloud/api" 13 "github.com/juju/go-oracle-cloud/common" 14 "github.com/juju/go-oracle-cloud/response" 15 "github.com/juju/loggo" 16 names "gopkg.in/juju/names.v2" 17 18 "github.com/juju/juju/core/instance" 19 "github.com/juju/juju/environs" 20 "github.com/juju/juju/environs/context" 21 "github.com/juju/juju/network" 22 commonProvider "github.com/juju/juju/provider/oracle/common" 23 ) 24 25 var logger = loggo.GetLogger("juju.provider.oracle.network") 26 27 // NetworkingAPI defines methods needed to interact with the networking features 28 // of the Oracle API 29 type NetworkingAPI interface { 30 commonProvider.Instancer 31 commonProvider.Composer 32 33 // AllIpNetworks fetches all IP networks matching a filter. A nil valued filter 34 // will return all IP networks 35 AllIpNetworks([]api.Filter) (response.AllIpNetworks, error) 36 37 // AllAcls fetches all ACLs that match a given filter. 38 AllAcls([]api.Filter) (response.AllAcls, error) 39 } 40 41 // Environ implements the environs.Networking interface 42 type Environ struct { 43 client NetworkingAPI 44 45 env commonProvider.OracleInstancer 46 } 47 48 var _ environs.Networking = (*Environ)(nil) 49 50 // NewEnviron returns a new instance of Environ 51 func NewEnviron(api NetworkingAPI, env commonProvider.OracleInstancer) *Environ { 52 return &Environ{ 53 client: api, 54 env: env, 55 } 56 } 57 58 // Subnets is defined on the environs.Networking interface. 59 func (e Environ) Subnets(ctx context.ProviderCallContext, id instance.Id, subnets []network.Id) ([]network.SubnetInfo, error) { 60 ret := []network.SubnetInfo{} 61 found := make(map[string]bool) 62 if id != instance.UnknownId { 63 instanceNets, err := e.NetworkInterfaces(ctx, id) 64 if err != nil { 65 return ret, errors.Trace(err) 66 } 67 if len(subnets) == 0 { 68 for _, val := range instanceNets { 69 found[string(val.ProviderSubnetId)] = false 70 } 71 } 72 for _, val := range instanceNets { 73 if _, ok := found[string(val.ProviderSubnetId)]; !ok { 74 continue 75 } else { 76 found[string(val.ProviderSubnetId)] = true 77 subnetInfo := network.SubnetInfo{ 78 CIDR: val.CIDR, 79 ProviderId: val.ProviderSubnetId, 80 SpaceProviderId: val.ProviderSpaceId, 81 } 82 ret = append(ret, subnetInfo) 83 } 84 } 85 } else { 86 subnets, err := e.getSubnetInfoAsMap() 87 if err != nil { 88 return ret, errors.Trace(err) 89 } 90 if len(subnets) == 0 { 91 for key := range subnets { 92 found[key] = false 93 } 94 } 95 for key, val := range subnets { 96 if _, ok := found[key]; !ok { 97 continue 98 } 99 found[key] = true 100 ret = append(ret, val) 101 } 102 } 103 missing := []string{} 104 for key, ok := range found { 105 if !ok { 106 missing = append(missing, key) 107 } 108 } 109 if len(missing) > 0 { 110 return nil, errors.Errorf( 111 "missing subnets: %s", strings.Join(missing, ",")) 112 } 113 return ret, nil 114 } 115 116 // getSubnetInfoAsMap will return the subnet information 117 // for the getSubnetInfo as a map rather than a slice 118 func (e Environ) getSubnetInfoAsMap() (map[string]network.SubnetInfo, error) { 119 subnets, err := e.getSubnetInfo() 120 if err != nil { 121 return nil, err 122 } 123 ret := make(map[string]network.SubnetInfo, len(subnets)) 124 for _, val := range subnets { 125 ret[string(val.ProviderId)] = val 126 } 127 return ret, nil 128 } 129 130 // getSubnetInfo returns subnet information for all subnets known to 131 // the oracle provider 132 func (e Environ) getSubnetInfo() ([]network.SubnetInfo, error) { 133 networks, err := e.client.AllIpNetworks(nil) 134 if err != nil { 135 return nil, errors.Trace(err) 136 } 137 subnets := make([]network.SubnetInfo, len(networks.Result)) 138 idx := 0 139 for _, val := range networks.Result { 140 var spaceId network.Id 141 if val.IpNetworkExchange != nil { 142 spaceId = network.Id(*val.IpNetworkExchange) 143 } 144 subnets[idx] = network.SubnetInfo{ 145 ProviderId: network.Id(val.Name), 146 CIDR: val.IpAddressPrefix, 147 SpaceProviderId: spaceId, 148 AvailabilityZones: []string{ 149 "default", 150 }, 151 } 152 idx++ 153 } 154 return subnets, nil 155 } 156 157 // NetworkInterfaces is defined on the environs.Networking interface. 158 func (e Environ) NetworkInterfaces(ctx context.ProviderCallContext, instId instance.Id) ([]network.InterfaceInfo, error) { 159 instance, err := e.env.Details(instId) 160 if err != nil { 161 return nil, err 162 } 163 164 if len(instance.Networking) == 0 { 165 return []network.InterfaceInfo{}, nil 166 } 167 subnetInfo, err := e.getSubnetInfoAsMap() 168 if err != nil { 169 return nil, errors.Trace(err) 170 } 171 nicAttributes := e.getNicAttributes(instance) 172 173 interfaces := make([]network.InterfaceInfo, 0, len(instance.Networking)) 174 idx := 0 175 for nicName, nicObj := range instance.Networking { 176 // gsamfira: While the API may hold any alphanumeric NIC name 177 // of up to 4 characters, inside an ubuntu instance, 178 // the NIC will always be named eth0 (where 0 is the index of the NIC). 179 // NICs inside the instance will be ordered in the same way the API 180 // returns them; i.e. first element returned by the API will be eth0, 181 // second element will be eth1 and so on. Sorting is done 182 // alphanumerically it makes sense to use the name that will be 183 // seen by the juju agent instead of the name that shows up 184 // in the provider. 185 // TODO (gsamfira): Check NIC naming in CentOS and Windows. 186 name := fmt.Sprintf("eth%s", strconv.Itoa(idx)) 187 deviceIndex := idx 188 189 idx += 1 190 191 deviceAttributes, ok := nicAttributes[nicName] 192 if !ok { 193 return nil, errors.Errorf( 194 "failed to get NIC attributes for %q", nicName) 195 } 196 mac, ip, err := GetMacAndIP(deviceAttributes.Address) 197 if err != nil { 198 return nil, err 199 } 200 addr := network.NewScopedAddress(ip, network.ScopeCloudLocal) 201 nic := network.InterfaceInfo{ 202 InterfaceName: name, 203 DeviceIndex: deviceIndex, 204 ProviderId: network.Id(deviceAttributes.Id), 205 MACAddress: mac, 206 Address: addr, 207 InterfaceType: network.EthernetInterface, 208 } 209 210 // gsamfira: VEthernet NICs are connected to shared networks 211 // I have not found a way to interrogate details about the shared 212 // networks available inside the oracle cloud. There is some 213 // documentation on the matter here: 214 // 215 // https://docs.oracle.com/cloud-machine/latest/stcomputecs/ELUAP/GUID-8CBE0F4E-E376-4C93-BB56-884836273168.htm 216 // 217 // but I have not been able to get any information about the 218 // shared networks using the resources described there. 219 // We only populate Space information for NICs attached to 220 // IPNetworks (user defined) 221 if nicObj.GetType() == common.VNic { 222 nicSubnetDetails := subnetInfo[deviceAttributes.Ipnetwork] 223 nic.ProviderSpaceId = nicSubnetDetails.SpaceProviderId 224 nic.ProviderSubnetId = nicSubnetDetails.ProviderId 225 nic.CIDR = nicSubnetDetails.CIDR 226 } 227 interfaces = append(interfaces, nic) 228 } 229 230 return interfaces, nil 231 } 232 233 // getNicAttributes returns all network cards attributes from a oracle instance 234 func (e Environ) getNicAttributes(instance response.Instance) map[string]response.Network { 235 if instance.Attributes.Network == nil { 236 return map[string]response.Network{} 237 } 238 n := len(instance.Attributes.Network) 239 ret := make(map[string]response.Network, n) 240 for name, obj := range instance.Attributes.Network { 241 tmp := strings.TrimPrefix(name, `nimbula_vcable-`) 242 ret[tmp] = obj 243 } 244 return ret 245 } 246 247 // canAccessNetworkAPI checks whether or not we have access to the necessary 248 // API endpoints needed for spaces support 249 func (e *Environ) canAccessNetworkAPI() (bool, error) { 250 _, err := e.client.AllAcls(nil) 251 if err != nil { 252 if api.IsMethodNotAllowed(err) { 253 return false, nil 254 } 255 return false, errors.Trace(err) 256 } 257 return true, nil 258 } 259 260 // SupportsSpaces is defined on the environs.Networking interface. 261 func (e Environ) SupportsSpaces(ctx context.ProviderCallContext) (bool, error) { 262 logger.Infof("checking for spaces support") 263 access, err := e.canAccessNetworkAPI() 264 if err != nil { 265 return false, errors.Trace(err) 266 } 267 if access { 268 return true, nil 269 } 270 return false, nil 271 } 272 273 // SupportsSpaceDiscovery is defined on the environs.Networking interface. 274 func (e Environ) SupportsSpaceDiscovery(ctx context.ProviderCallContext) (bool, error) { 275 access, err := e.canAccessNetworkAPI() 276 if err != nil { 277 return false, errors.Trace(err) 278 } 279 if access { 280 return true, nil 281 } 282 return false, nil 283 } 284 285 // SupportsContainerAddresses is defined on the environs.Networking interface. 286 func (e Environ) SupportsContainerAddresses(ctx context.ProviderCallContext) (bool, error) { 287 return false, errors.NotSupportedf("container address allocation") 288 } 289 290 // AllocateContainerAddresses is defined on the environs.Networking interface. 291 func (e Environ) AllocateContainerAddresses( 292 ctx context.ProviderCallContext, 293 hostInstanceID instance.Id, 294 containerTag names.MachineTag, 295 preparedInfo []network.InterfaceInfo, 296 ) ([]network.InterfaceInfo, error) { 297 return nil, errors.NotSupportedf("containers") 298 } 299 300 // ReleaseContainerAddresses is defined on the environs.Networking interface. 301 func (e Environ) ReleaseContainerAddresses(ctx context.ProviderCallContext, interfaces []network.ProviderInterfaceInfo) error { 302 return errors.NotSupportedf("container") 303 } 304 305 // Spaces is defined on the environs.Networking interface. 306 func (e Environ) Spaces(ctx context.ProviderCallContext) ([]network.SpaceInfo, error) { 307 networks, err := e.getSubnetInfo() 308 if err != nil { 309 return nil, errors.Trace(err) 310 } 311 exchanges := map[string]network.SpaceInfo{} 312 for _, val := range networks { 313 if val.SpaceProviderId == "" { 314 continue 315 } 316 logger.Infof("found network %s with space %s", 317 string(val.ProviderId), string(val.SpaceProviderId)) 318 providerID := string(val.SpaceProviderId) 319 tmp := strings.Split(providerID, `/`) 320 name := tmp[len(tmp)-1] 321 // Oracle allows us to attach an IP network to a space belonging to 322 // another user using the web portal. We recompose the provider ID 323 // (which is unique) and compare to the provider ID of the space. 324 // If they match, the space belongs to us 325 tmpProviderId := e.client.ComposeName(name) 326 if tmpProviderId != providerID { 327 continue 328 } 329 if space, ok := exchanges[name]; !ok { 330 logger.Infof("creating new space obj for %s and adding %s", 331 name, string(val.ProviderId)) 332 exchanges[name] = network.SpaceInfo{ 333 Name: name, 334 ProviderId: val.SpaceProviderId, 335 Subnets: []network.SubnetInfo{ 336 val, 337 }, 338 } 339 } else { 340 logger.Infof("appending subnet %s to %s", string(val.ProviderId), space.Name) 341 space.Subnets = append(space.Subnets, val) 342 exchanges[name] = space 343 } 344 345 } 346 var ret []network.SpaceInfo 347 for _, val := range exchanges { 348 ret = append(ret, val) 349 } 350 351 logger.Infof("returning spaces: %v", ret) 352 return ret, nil 353 } 354 355 // ProviderSpaceInfo is defined on the environs.NetworkingEnviron interface. 356 func (Environ) ProviderSpaceInfo(ctx context.ProviderCallContext, space *network.SpaceInfo) (*environs.ProviderSpaceInfo, error) { 357 return nil, errors.NotSupportedf("provider space info") 358 } 359 360 // AreSpacesRoutable is defined on the environs.NetworkingEnviron interface. 361 func (Environ) AreSpacesRoutable(ctx context.ProviderCallContext, space1, space2 *environs.ProviderSpaceInfo) (bool, error) { 362 return false, nil 363 } 364 365 // SSHAddresses is defined on the environs.SSHAddresses interface. 366 func (*Environ) SSHAddresses(ctx context.ProviderCallContext, addresses []network.Address) ([]network.Address, error) { 367 return addresses, nil 368 } 369 370 // SuperSubnets implements environs.SuperSubnets 371 func (*Environ) SuperSubnets(ctx context.ProviderCallContext) ([]string, error) { 372 return nil, errors.NotSupportedf("super subnets") 373 }