github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/provider/maas/instance.go (about) 1 // Copyright 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package maas 5 6 import ( 7 "fmt" 8 "strings" 9 10 "github.com/juju/errors" 11 "github.com/juju/gomaasapi" 12 "gopkg.in/juju/names.v2" 13 14 "github.com/juju/juju/instance" 15 "github.com/juju/juju/network" 16 "github.com/juju/juju/status" 17 "github.com/juju/juju/storage" 18 ) 19 20 type maasInstance interface { 21 instance.Instance 22 zone() (string, error) 23 hostname() (string, error) 24 hardwareCharacteristics() (*instance.HardwareCharacteristics, error) 25 volumes(names.MachineTag, []names.VolumeTag) ([]storage.Volume, []storage.VolumeAttachment, error) 26 } 27 28 type maas1Instance struct { 29 maasObject *gomaasapi.MAASObject 30 environ *maasEnviron 31 statusGetter func(instance.Id) (string, string) 32 } 33 34 var _ maasInstance = (*maas1Instance)(nil) 35 36 func (mi *maas1Instance) String() string { 37 hostname, err := mi.hostname() 38 if err != nil { 39 // This is meant to be impossible, but be paranoid. 40 hostname = fmt.Sprintf("<DNSName failed: %q>", err) 41 } 42 return fmt.Sprintf("%s:%s", hostname, mi.Id()) 43 } 44 45 func (mi *maas1Instance) Id() instance.Id { 46 return maasObjectId(mi.maasObject) 47 } 48 49 func maasObjectId(maasObject *gomaasapi.MAASObject) instance.Id { 50 // Use the node's 'resource_uri' value. 51 return instance.Id(maasObject.URI().String()) 52 } 53 54 func normalizeStatus(statusMsg string) string { 55 return strings.ToLower(strings.TrimSpace(statusMsg)) 56 } 57 58 func convertInstanceStatus(statusMsg, substatus string, id instance.Id) instance.InstanceStatus { 59 maasInstanceStatus := status.Empty 60 switch normalizeStatus(statusMsg) { 61 case "": 62 logger.Debugf("unable to obtain status of instance %s", id) 63 statusMsg = "error in getting status" 64 case "deployed": 65 maasInstanceStatus = status.Running 66 case "deploying": 67 maasInstanceStatus = status.Allocating 68 if substatus != "" { 69 statusMsg = fmt.Sprintf("%s: %s", statusMsg, substatus) 70 } 71 case "failed deployment": 72 maasInstanceStatus = status.ProvisioningError 73 if substatus != "" { 74 statusMsg = fmt.Sprintf("%s: %s", statusMsg, substatus) 75 } 76 default: 77 maasInstanceStatus = status.Empty 78 statusMsg = fmt.Sprintf("%s: %s", statusMsg, substatus) 79 } 80 return instance.InstanceStatus{ 81 Status: maasInstanceStatus, 82 Message: statusMsg, 83 } 84 } 85 86 // Status returns a juju status based on the maas instance returned 87 // status message. 88 func (mi *maas1Instance) Status() instance.InstanceStatus { 89 statusMsg, substatus := mi.statusGetter(mi.Id()) 90 return convertInstanceStatus(statusMsg, substatus, mi.Id()) 91 } 92 93 func (mi *maas1Instance) Addresses() ([]network.Address, error) { 94 interfaceAddresses, err := mi.interfaceAddresses() 95 if err != nil { 96 return nil, errors.Annotate(err, "getting node interfaces") 97 } 98 99 logger.Debugf("instance %q has interface addresses: %+v", mi.Id(), interfaceAddresses) 100 return interfaceAddresses, nil 101 } 102 103 var refreshMAASObject = func(maasObject *gomaasapi.MAASObject) (gomaasapi.MAASObject, error) { 104 // Defined like this to allow patching in tests to overcome limitations of 105 // gomaasapi's test server. 106 return maasObject.Get() 107 } 108 109 // interfaceAddresses fetches a fresh copy of the node details from MAAS and 110 // extracts all addresses from the node's interfaces. Returns an error 111 // satisfying errors.IsNotSupported() if MAAS API does not report interfaces 112 // information. 113 func (mi *maas1Instance) interfaceAddresses() ([]network.Address, error) { 114 // Fetch a fresh copy of the instance JSON first. 115 obj, err := refreshMAASObject(mi.maasObject) 116 if err != nil { 117 return nil, errors.Annotate(err, "getting instance details") 118 } 119 120 subnetsMap, err := mi.environ.subnetToSpaceIds() 121 if err != nil { 122 return nil, errors.Trace(err) 123 } 124 // Get all the interface details and extract the addresses. 125 interfaces, err := maasObjectNetworkInterfaces(&obj, subnetsMap) 126 if err != nil { 127 return nil, errors.Trace(err) 128 } 129 130 var addresses []network.Address 131 for _, iface := range interfaces { 132 if iface.Address.Value != "" { 133 logger.Debugf("found address %q on interface %q", iface.Address, iface.InterfaceName) 134 addresses = append(addresses, iface.Address) 135 } else { 136 logger.Infof("no address found on interface %q", iface.InterfaceName) 137 } 138 } 139 return addresses, nil 140 } 141 142 func (mi *maas1Instance) architecture() (arch, subarch string, err error) { 143 // MAAS may return an architecture of the form, for example, 144 // "amd64/generic"; we only care about the major part. 145 arch, err = mi.maasObject.GetField("architecture") 146 if err != nil { 147 return "", "", err 148 } 149 parts := strings.SplitN(arch, "/", 2) 150 arch = parts[0] 151 if len(parts) == 2 { 152 subarch = parts[1] 153 } 154 return arch, subarch, nil 155 } 156 157 func (mi *maas1Instance) zone() (string, error) { 158 // TODO (anastasiamac 2016-03-31) 159 // This code is needed until gomaasapi testing code is 160 // updated to align with MAAS. 161 // Currently, "zone" property is still treated as field 162 // by gomaasi infrastructure and is searched for 163 // using matchField(node, "zone", zoneName) instead of 164 // getMap. 165 // @see gomaasapi/testservice.go#findFreeNode 166 // bug https://bugs.launchpad.net/gomaasapi/+bug/1563631 167 zone, fieldErr := mi.maasObject.GetField("zone") 168 if fieldErr == nil && zone != "" { 169 return zone, nil 170 } 171 172 obj := mi.maasObject.GetMap()["zone"] 173 if obj.IsNil() { 174 return "", errors.New("zone property not set on maas") 175 } 176 zoneMap, err := obj.GetMap() 177 if err != nil { 178 return "", errors.New("zone property is not an expected type") 179 } 180 nameObj, ok := zoneMap["name"] 181 if !ok { 182 return "", errors.New("zone property is not set correctly: name is missing") 183 } 184 str, err := nameObj.GetString() 185 if err != nil { 186 return "", err 187 } 188 return str, nil 189 } 190 191 func (mi *maas1Instance) cpuCount() (uint64, error) { 192 count, err := mi.maasObject.GetMap()["cpu_count"].GetFloat64() 193 if err != nil { 194 return 0, err 195 } 196 return uint64(count), nil 197 } 198 199 func (mi *maas1Instance) memory() (uint64, error) { 200 mem, err := mi.maasObject.GetMap()["memory"].GetFloat64() 201 if err != nil { 202 return 0, err 203 } 204 return uint64(mem), nil 205 } 206 207 func (mi *maas1Instance) tagNames() ([]string, error) { 208 obj := mi.maasObject.GetMap()["tag_names"] 209 if obj.IsNil() { 210 return nil, errors.NotFoundf("tag_names") 211 } 212 array, err := obj.GetArray() 213 if err != nil { 214 return nil, err 215 } 216 tags := make([]string, len(array)) 217 for i, obj := range array { 218 tag, err := obj.GetString() 219 if err != nil { 220 return nil, err 221 } 222 tags[i] = tag 223 } 224 return tags, nil 225 } 226 227 func (mi *maas1Instance) hardwareCharacteristics() (*instance.HardwareCharacteristics, error) { 228 nodeArch, _, err := mi.architecture() 229 if err != nil { 230 return nil, errors.Annotate(err, "error determining architecture") 231 } 232 nodeCpuCount, err := mi.cpuCount() 233 if err != nil { 234 return nil, errors.Annotate(err, "error determining cpu count") 235 } 236 nodeMemoryMB, err := mi.memory() 237 if err != nil { 238 return nil, errors.Annotate(err, "error determining available memory") 239 } 240 zone, err := mi.zone() 241 if err != nil { 242 return nil, errors.Annotate(err, "error determining availability zone") 243 } 244 hc := &instance.HardwareCharacteristics{ 245 Arch: &nodeArch, 246 CpuCores: &nodeCpuCount, 247 Mem: &nodeMemoryMB, 248 AvailabilityZone: &zone, 249 } 250 nodeTags, err := mi.tagNames() 251 if err != nil && !errors.IsNotFound(err) { 252 return nil, errors.Annotate(err, "error determining tag names") 253 } 254 if len(nodeTags) > 0 { 255 hc.Tags = &nodeTags 256 } 257 return hc, nil 258 } 259 260 func (mi *maas1Instance) hostname() (string, error) { 261 // A MAAS instance has its DNS name immediately. 262 return mi.maasObject.GetField("hostname") 263 } 264 265 // MAAS does not do firewalling so these port methods do nothing. 266 func (mi *maas1Instance) OpenPorts(machineId string, ports []network.PortRange) error { 267 logger.Debugf("unimplemented OpenPorts() called") 268 return nil 269 } 270 271 func (mi *maas1Instance) ClosePorts(machineId string, ports []network.PortRange) error { 272 logger.Debugf("unimplemented ClosePorts() called") 273 return nil 274 } 275 276 func (mi *maas1Instance) Ports(machineId string) ([]network.PortRange, error) { 277 logger.Debugf("unimplemented Ports() called") 278 return nil, nil 279 }